Subversion Repositories Filer-Free

Rev

Rev 34 | Rev 36 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

// This file is part of the free Filer module for RISC OS.
// Copyright © 2007 Graham Shaw.
// Redistribution and modification are permitted under the terms of the
// GNU General Public License (version 2 or any later version).

#include <cstring>
#include <cstdio>

#include "oslib/osbyte.h"
#include "oslib/wimp.h"

#include "template_loader.h"
#include "layout_method.h"
#include "filer_menu.h"
#include "filer_window.h"
#include "filer_application.h"

namespace {

/** The maximum length of a leafname. */
const size_t max_name_length=256;

/** The initial horizontal offset for opening a subdirectory. */
const int open_xstart=20;

/** The initial vertical offset for opening a subdirectory. */
const int open_ystart=-32;

/** The horizontal offset between subdirectories. */
const int open_xstep=20;

/** The vertical offset between subdirectories. */
const int open_ystep=0;

/** The number of distinct subdirectory offsets. */
const unsigned int open_count=8;

}; /* anonymous namespace */

filer_window::filer_window(filer_application* app,const char* pathname,
        const os_box& box,const filer_options& options):
        window(app),
        _pathname(new char[std::strlen(pathname)+1]),
        _options(options),
        _directory(options.sort()),
        _xcsize(68),
        _ycsize(68),
        _xccount(1),
        _yccount(1),
        _temp_selection(directory::npos),
        _auto_pos(open_xstart,open_ystart,open_xstep,open_ystep,open_count)
{
        // Copy pathname into buffer owned by this object.
        std::strcpy(_pathname,pathname);

        // Load directory listing.
        reload();

        // Create window from template.
        template_loader loader("directory","Resources:$.Resources.Filer.Templates");
        wimp_window& wblock=loader;
        wblock.title_flags|=wimp_ICON_INDIRECTED;
        wblock.title_data.indirected_text.text=_pathname;
        wblock.title_data.indirected_text.validation="";
        wblock.title_data.indirected_text.size=strlen(_pathname);
        wblock.visible=box;
        wblock.extent.x0=0;
        wblock.extent.y0=-(box.y1-box.y0);
        wblock.extent.x1=box.x1-box.x0;
        wblock.extent.y1=0;
        wblock.flags|=wimp_WINDOW_IGNORE_XEXTENT;
        wblock.icon_count=0;
        create_window(wblock);

        // Resize extent to match actual visible area.
        wimp_window_state wstate;
        get_window_state(wstate);
        os_box extent;
        extent.x0=0;
        extent.y0=-(wstate.visible.y1-wstate.visible.y0);
        extent.x1=wstate.visible.x1-wstate.visible.x0;
        extent.y1=0;
        window::set_extent(extent);

        // Open window, but keep it hidden from view.
        wimp_window_info winfo;
        get_window_info_header(winfo);
        wimp_open open;
        open.visible.x0=winfo.visible.x0;
        open.visible.y0=winfo.visible.y1-(winfo.extent.y1-winfo.extent.y0);
        open.visible.x1=winfo.visible.x0+(winfo.extent.x1-winfo.extent.x0);
        open.visible.y1=winfo.visible.y1;
        open.visible=winfo.visible;
        open.xscroll=0;
        open.yscroll=0;
        open.next=wimp_HIDDEN;
        open_window(open);

        // Reformat content of window.
        reformat();

        // Reopen window, now making it visible.
        get_window_state(wstate);
        open.visible=wstate.visible;
        open.xscroll=wstate.xscroll;
        open.yscroll=wstate.yscroll;
        open.next=wimp_TOP;
        open_window(open);
}

filer_window::~filer_window()
{
        // Delete pathname buffer.
        delete[] _pathname;
}

void filer_window::handle_redraw_request(wimp_draw& block)
{
        // Fetch layout.
        layout_method& layout=_options.layout();
        int xgap=layout.xgap();
        int ygap=layout.ygap();

        int more=0;
        xwimp_redraw_window(&block,&more);
        while (more)
        {
                // Get clipping coordinates with respect to this window.
                int xmin=block.clip.x0-block.box.x0+block.xscroll-xgap;
                int ymin=block.clip.y0-block.box.y1+block.yscroll+ygap;
                int xmax=block.clip.x1-block.box.x0+block.xscroll-xgap;
                int ymax=block.clip.y1-block.box.y1+block.yscroll+ygap;

                // Determine which cells are within clipping box.
                int xcmin=(xmin)/(_xcsize+xgap);
                int ycmin=(-ymax)/(_ycsize+ygap);
                int xcmax=(xmax-1)/(_xcsize+xgap)+1;
                int ycmax=(-ymin-1)/(_ycsize+ygap)+1;
                if (xcmin<0) xcmin=0;
                if (ycmin<0) ycmin=0;
                if (xcmax>_xccount) xcmax=_xccount;
                if (ycmax>_yccount) ycmax=_yccount;

                // Iterate over cells to be redrawn.
                for (int yc=ycmin;yc<ycmax;++yc)
                {
                        for (int xc=xcmin;xc<xcmax;++xc)
                        {
                                // Determine whether cell is occupied.
                                unsigned int i=xc+yc*_xccount;
                                if (i<_directory.size())
                                {
                                        // Redraw cell.
                                        os_box box;
                                        box.x0=xc*(_xcsize+xgap)+xgap;
                                        box.y0=-yc*(_ycsize+ygap)-_ycsize-ygap;
                                        box.x1=box.x0+_xcsize;
                                        box.y1=box.y0+_ycsize;
                                        layout.plot(box,*_directory[i],
                                                _directory[i].selected(),_options);
                                }
                        }
                }

                xwimp_get_rectangle(&block,&more);
        }
}

void filer_window::handle_open_request(wimp_open& block)
{
        int xccount=_xccount;
        int yccount=_yccount;
        reformat(block.visible.x1-block.visible.x0);
        xwimp_open_window(&block);
        if (_xccount!=xccount||_yccount!=yccount) force_redraw();
}

void filer_window::handle_close_request(wimp_close& block)
{
        wimp_pointer pointer;
        xwimp_get_pointer_info(&pointer);

        if (pointer.buttons&wimp_CLICK_ADJUST)
        {
                os_coord offset;
                offset.x=0;
                offset.y=0;
                open_parent(offset);
        }

        delete this;
}

void filer_window::handle_mouse_click(wimp_pointer& block)
{
        // Fetch layout.
        layout_method& layout=options().layout();

        // Read shift state.
        int shift_state=0;
        xosbyte1(osbyte_VAR_KEYBOARD_STATE,0,0xff,&shift_state);
        int shift=(shift_state&0x08)!=0;

        // Find cell under pointer.
        unsigned int index=find_cell(block.pos);

        bool close=false;
        if (block.buttons==wimp_CLICK_MENU)
        {
                // Any existing menu will be deleted by this action.
                handle_menus_deleted();

                // Count number of cells selected.
                int count=0;
                for (unsigned int i=0,i_end=_directory.size();i!=i_end;++i)
                {
                        if (_directory[i].selected()) ++count;
                }

                // If pointer is over a cell and nothing has yet been selected
                // then temporarily select the cell under the pointer.
                if (index!=directory::npos&&!_directory[index].selected()&&!count)
                {
                        _directory[index].selected(true);
                        _temp_selection=index;
                        force_redraw_cell(index);
                }

                // Update then open the menu.
                shared_menu().update(this);
                shared_menu().show(block);
        }
        else if (block.buttons&(wimp_DOUBLE_ADJUST|wimp_DOUBLE_SELECT))
        {
                // If double click within a cell then run that object.
                if (index!=directory::npos)
                {
                        select_all(0);
                        close=(block.buttons&wimp_DOUBLE_ADJUST)!=0;
                        if (close) close_window();
                        run(*_directory[index],close,shift);
                }
        }
        else if (block.buttons&(wimp_SINGLE_SELECT|wimp_SINGLE_ADJUST))
        {
                // If single select click then first de-select everything.
                if (block.buttons&wimp_SINGLE_SELECT) select_all(0);

                // If click within a cell then select that cell.
                if (index!=directory::npos)
                {
                        _directory[index].selected(!_directory[index].selected());
                        force_redraw_cell(index);
                }
        }

        // If this window has closed as a result of the mouse event
        // then delete it.
        if (close) delete this;
}

void filer_window::handle_menus_deleted()
{
        if (_temp_selection!=directory::npos)
        {
                _directory[_temp_selection].selected(0);
                force_redraw_cell(_temp_selection);
                _temp_selection=directory::npos;
        }
}

filer_menu& filer_window::shared_menu() const
{
        static filer_menu* _shared_menu=0;
        if (!_shared_menu)
        {
                _shared_menu=new filer_menu(parent_application());
        }
        return *_shared_menu;
}

void filer_window::reload()
{
        static int buffer[256];
        _directory.load(_pathname,buffer,sizeof(buffer));
}

void filer_window::reformat()
{
        wimp_window_state wstate;
        get_window_state(wstate);
        reformat(wstate.visible.x1-wstate.visible.x0);
}

void filer_window::reformat(int xsize)
{
        // Fetch layout.
        layout_method& layout=options().layout();
        int xgap=layout.xgap();
        int ygap=layout.ygap();

        // Calculate cell size.  This must be able to accommodate
        // - the minimum cell size for the chosen layout, and
        // - the actual size occupied by each file to be listed.
        os_coord csize=layout.min_size(_options);
        for (unsigned int i=0;i!=_directory.size();++i)
        {
                os_coord size=layout.size(*_directory[i],_options);
                if (size.x>csize.x) csize.x=size.x;
                if (size.y>csize.y) csize.y=size.y;
        }

        // Apply the calculated cell size.
        _xcsize=csize.x;
        _ycsize=csize.y;

        // Determine number of rows and columns.
        _xccount=xsize/(_xcsize+xgap);
        if (_xccount<1) _xccount=1;
        _yccount=(_directory.size()+_xccount-1)/_xccount;
        if (_yccount<1) _yccount=1;

        // Calculate new extent.
        int extent_xsize=_xccount*(_xcsize+xgap)+xgap;
        int extent_ysize=_yccount*(_ycsize+ygap)+ygap;

        // Do not force the window to become narrower than the width requested,
        // unless there are too few directory entries to justify that width.
        if (extent_xsize<xsize)
        {
                extent_xsize=xsize;
                if (((extent_xsize+_xcsize-1)/(_xcsize+xgap))>
                        static_cast<int>(_directory.size()))
                {
                        extent_xsize=_directory.size()*(_xcsize+xgap)+xgap;
                }
        }

        // Apply the calculated extent.
        os_box extent;
        extent.x0=0;
        extent.y0=-extent_ysize;
        extent.x1=extent_xsize;
        extent.y1=0;
        set_extent(extent);
}

unsigned int filer_window::find_cell(const os_coord& p)
{
        layout_method& layout=options().layout();
        int xgap=layout.xgap();
        int ygap=layout.ygap();

        wimp_window_state state;
        get_window_state(state);
        int x=p.x-state.visible.x0+state.xscroll-xgap;
        int y=p.y-state.visible.y1+state.yscroll+ygap;

        int xc=x/(_xcsize+xgap);
        int yc=(-y)/(_ycsize+ygap);
        unsigned int index=xc+yc*_xccount;

        x-=xc*(_xcsize+xgap);
        y+=yc*(_ycsize+ygap);

        int found=(x>=0)&&(y>=-_ycsize)&&(x<_xcsize)&&(y<0)&&
                (xc>=0)&&(yc>=0)&&(xc<_xccount)&&(yc<_yccount)&&
                (index<_directory.size());

        return (found)?index:directory::npos;
}

void filer_window::force_redraw_cell(unsigned int index)
{
        layout_method& layout=options().layout();
        int xgap=layout.xgap();
        int ygap=layout.ygap();

        int xc=index%_xccount;
        int yc=index/_xccount;

        os_box box;
        box.x0=xgap+xc*(_xcsize+xgap);
        box.y0=-(yc+1)*(_ycsize+ygap);
        box.x1=box.x0+_xcsize;
        box.y1=box.y0+_ycsize;
        force_redraw(box);
}

void filer_window::refresh()
{
        reload();
        reformat();
        force_redraw();
}

void filer_window::select_all(int selected)
{
        layout_method& layout=options().layout();
        int xgap=layout.xgap();
        int ygap=layout.ygap();

        int xc_min=_xccount;
        int yc_min=_yccount;
        int xc_max=0;
        int yc_max=0;

        for (unsigned int i=0;i!=_directory.size();++i)
        {
                if (_directory[i].selected()!=selected)
                {
                        _directory[i].selected(selected);

                        int xc=i%_xccount;
                        int yc=i/_xccount;
                        if (xc<xc_min) xc_min=xc;
                        if (yc<yc_min) yc_min=yc;
                        if (xc+1>xc_max) xc_max=xc+1;
                        if (yc+1>yc_max) yc_max=yc+1;
                }
        }

        if ((xc_max>xc_min)&&(yc_max>yc_min))
        {
                os_box box;
                box.x0=xgap+xc_min*(_xcsize+xgap);
                box.y0=-yc_max*(_ycsize+ygap);
                box.x1=xc_max*(_xcsize+xgap);
                box.y1=-ygap-yc_min*(_ycsize+ygap);
                force_redraw(box);
        }

        _temp_selection=directory::npos;
}

void filer_window::open_parent(const os_coord& offset) const
{
        // Determine whether there is a parent directory.
        char* lastdot=strrchr(_pathname,'.');
        if (lastdot&&strcmp(lastdot+1,"$"))
        {
                // Find top-left corner of this window.
                wimp_window_state state;
                get_window_state(state);

                // Add offset to give top-left corner of new window.
                os_box box;
                box.x0=state.visible.x0+offset.x;
                box.y1=state.visible.y1+offset.y;
                box.x1=box.x0;
                box.y0=box.y1;

                // Open new window.
                *lastdot=0;
                new filer_window((filer_application*)parent_application(),
                        _pathname,box,_options);
                *lastdot='.';
        }
}

void filer_window::run(osgbpb_info& info,int close,int shift)
{
        // Need to at least partially handle shift key here, because
        // Filer_Run does not allow coordinates to be specified when
        // shift-clicking an application directory.
        // (... unless there is an alternative way to invoke Filer_Run?)

        static char buffer[12+max_name_length];
        if ((info.obj_type&fileswitch_IS_DIR)&&((info.name[0]!='!')||shift))
        {
                // Get dimensions of this window.
                wimp_window_state state;
                get_window_state(state);

                // Choose coordinates for new window.
                // If closing this window then open in same position,
                // otherwise open below and to the right.
                os_coord offset=_auto_pos();
                os_box box;
                box.x0=state.visible.x0+((close)?0:offset.x);
                box.y1=state.visible.y1+((close)?0:offset.y);
                box.x1=box.x0;
                box.y0=box.y1;

                // Open new window.
                if (std::strlen(_pathname)+std::strlen(info.name)+1<max_name_length)
                {
                        std::sprintf(buffer,"%s.%s",_pathname,info.name);
                        window* w=new filer_window(
                                (filer_application*)parent_application(),
                                buffer,box,_options);
                }
        }
        else
        {
                // Run object.
                if (std::strlen(_pathname)+std::strlen(info.name)+1<max_name_length)
                {
                        std::sprintf(buffer,"Run %s.%s",_pathname,info.name);
                        wimp_start_task(buffer);
                }
        }
}