Subversion Repositories Filer-Free

Rev

Rev 47 | Rev 49 | 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/fileraction.h"
#include "oslib/osfscontrol.h"

#include "filer_options.h"
#include "filer_window.h"
#include "filer_menu.h"

namespace {

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

}; /* anonymous namespace */

filer_menu::filer_menu(application* app):
        menu(app,"Filer",9),
        _owner(0),
        _object(0),
        _display_menu(app,"Display",8),
        _rename_menu(app,"Rename",1),
        _access_menu(app,"Access",5),
        _find_menu(app,"Find",1),
        _filetype_menu(app,"Set type",1),
        _selection_menu(app,"File",11),
        _options_menu(app,"Options",6),
        _new_dir_dbox(app),
        _info_dbox(app),
        _rename_leafname(0),
        _find_pattern(0),
        _filetype_field(0),
        _selection_text(new char[12+max_name_length]),
        _layout_count(3),
        _layout_methods(new layout_method*[_layout_count]),
        _sort_count(4),
        _sort_methods(new sort_method*[_sort_count])
{
        _layout_methods[0]=&filer_options::layout_large();
        _layout_methods[1]=&filer_options::layout_small();
        _layout_methods[2]=&filer_options::layout_full();

        _sort_methods[0]=&filer_options::sort_by_name();
        _sort_methods[1]=&filer_options::sort_by_type();
        _sort_methods[2]=&filer_options::sort_by_size();
        _sort_methods[3]=&filer_options::sort_by_date();

        _display_menu[0].text("Large icons");
        _display_menu[1].text("Small icons");
        _display_menu[2].text("Full info");
        _display_menu[3].text("Thumbnails");
        _display_menu[4].text("Sort by name");
        _display_menu[5].text("Sort by type");
        _display_menu[6].text("Sort by size");
        _display_menu[7].text("Sort by date");
        _display_menu[3].separator(1);

        _rename_leafname=new char[256];
        _rename_leafname[0]=0;
        _rename_menu[0].text(_rename_leafname,256);
        _rename_menu[0].writable(1);

        _access_menu[0].text("Protected");
        _access_menu[1].text("Unprotected");
        _access_menu[2].text("Public");
        _access_menu[3].text("Private");
        _access_menu[4].text("Access details");
        _access_menu[1].separator(1);
        _access_menu[3].separator(1);

        _find_pattern=new char[256];
        _find_pattern[0]=0;
        _find_menu[0].text(_find_pattern,256);
        _find_menu[0].writable(1);

        _filetype_field=new char[16];
        _filetype_field[0]=0;
        _filetype_menu[0].text(_filetype_field,16);
        _filetype_menu[0].writable(1);

        _selection_menu[0].text("Copy");
        _selection_menu[1].text("Rename");
        _selection_menu[2].text("Delete");
        _selection_menu[3].text("Access");
        _selection_menu[4].text("Count");
        _selection_menu[5].text("Help");
        _selection_menu[6].text("Info");
        _selection_menu[7].text("Find");
        _selection_menu[8].text("Set type");
        _selection_menu[9].text("Stamp");
        _selection_menu[10].text("Share");
        _selection_menu[1].submenu(_rename_menu);
        _selection_menu[3].submenu(_access_menu);
        _selection_menu[6].dbox(_info_dbox);
        _selection_menu[7].submenu(_find_menu);
        _selection_menu[8].submenu(_filetype_menu);

        _options_menu[0].text("Confirm all");
        _options_menu[1].text("Confirm deletes");
        _options_menu[2].text("Verbose");
        _options_menu[3].text("Force");
        _options_menu[4].text("Newer");
        _options_menu[5].text("Faster");
        _options_menu[1].separator(1);

        *_selection_text=0;
        (*this)[0].text("Display");
        (*this)[1].text(_selection_text);
        (*this)[2].text("Select all");
        (*this)[3].text("Clear selection");
        (*this)[4].text("Options");
        (*this)[5].text("New directory");
        (*this)[6].text("Set work directory");
        (*this)[7].text("Open parent");
        (*this)[8].text("Refresh");
        (*this)[0].submenu(_display_menu);
        (*this)[1].submenu(_selection_menu);
        (*this)[4].submenu(_options_menu);
        (*this)[5].dbox(_new_dir_dbox);
}

filer_menu::~filer_menu()
{
        delete[] _rename_leafname;
        delete[] _find_pattern;
        delete[] _filetype_field;
        delete[] _selection_text;
        delete[] _layout_methods;
        delete[] _sort_methods;
}

void filer_menu::handle_menu_selection(wimp_selection& block)
{
        // Ensure that handler can never be executed when the menu
        // has no owner.
        if (!_owner) return;

        // Get mouse button state for later use.
        // (Do this at an early stage in case it changes.)
        wimp_pointer info;
        xwimp_get_pointer_info(&info);

        // Get copy of filer options.
        // (This needs to be a copy so that it can be modified,
        // and so that the filer window can see what has changed.)
        filer_options options(_owner->options());

        switch (block.items[0])
        {
        case 0: /* display */
                switch (block.items[1])
                {
                case 0:
                case 1:
                case 2:
                        options.layout(*_layout_methods[block.items[1]]);
                        _owner->options(options);
                        update_layout();
                        break;
                case 4:
                case 5:
                case 6:
                case 7:
                        options.sort(*_sort_methods[block.items[1]-4]);
                        _owner->options(options);
                        update_sort();
                        break;
                }
                break;
        case 1: /* object */
                switch (block.items[1])
                {
                case 1: /* rename */
                        switch (block.items[2])
                        {
                        case 0:
                                if (_object&&strlen(_rename_leafname))
                                {
                                        const size_t max_pathname=255;
                                        size_t max_leafname=
                                                max_pathname-strlen(_owner->pathname())-1;
                                        if (max_leafname>max_pathname) max_leafname=0;
                                        if ((strlen(_object->name)<=max_leafname)&&
                                                (strlen(_rename_leafname)<=max_leafname))
                                        {
                                                static char src_pathname[max_pathname+1];
                                                static char dst_pathname[max_pathname+1];
                                                sprintf(src_pathname,"%s.%s",_owner->pathname(),
                                                        _object->name);
                                                sprintf(dst_pathname,"%s.%s",_owner->pathname(),
                                                        _rename_leafname);
                                                osfscontrol_rename(src_pathname,dst_pathname);
                                        }
                                }
                                break;
                        }
                        break;
                case 2: /* delete */
                        {
                                wimp_t handle=_owner->begin_filer_action();
                                xfileractionsendstartoperation_delete(handle,
                                        _owner->options().flags()|fileraction_RECURSE);
                        }
                        break;
                case 4: /* count */
                        {
                                wimp_t handle=_owner->begin_filer_action();
                                xfileractionsendstartoperation_count(handle,
                                        _owner->options().flags()|fileraction_RECURSE);
                        }
                        break;
                case 6: /* info */
                        break;
                case 9: /* stamp */
                        {
                                wimp_t handle=_owner->begin_filer_action();
                                xfileractionsendstartoperation_stamp(handle,
                                        _owner->options().flags()|fileraction_RECURSE);
                        }
                        break;
                }
                break;
        case 2: /* select all */
                _owner->select_all(1);
                break;
        case 3: /* clear selection */
                _owner->select_all(0);
                break;
        case 4: /* options */
                {
                        filer_options options(_owner->options());
                        switch (block.items[1])
                        {
                        case 0:
                                options.flags(fileraction_CONFIRM,
                                        fileraction_CONFIRM_DELETES_ONLY);
                                break;
                        case 1:
                                options.flags(fileraction_CONFIRM_DELETES_ONLY,
                                        fileraction_CONFIRM);
                                break;
                        case 2:
                                options.flags(fileraction_VERBOSE,0);
                                break;
                        case 3:
                                options.flags(fileraction_FORCE,0);
                                break;
                        case 4:
                                options.flags(fileraction_NEWER,0);
                                break;
                        case 5:
                                options.flags(fileraction_FASTER,0);
                                break;
                        }
                        _owner->options(options);
                        update_options();
                }
                break;
        case 6: /* set work directory */
                _owner->set_work_directory();
                break;
        case 7: /* open parent */
                {
                        os_coord offset;
                        offset.x=-20;
                        offset.y=32;
                        _owner->open_parent(offset);
                }
                break;
        case 8: /* refresh */
                _owner->refresh();
                break;
        }

        if (info.buttons&1)
        {
                // If selection made with adjust click then re-open menu.
                menu::show(info);
        }
        else
        {
                // Otherwise, allow menu to close but inform filer window.
                if (_owner) _owner->handle_menus_deleted();
        }
}

void filer_menu::handle_menus_deleted()
{
        // If menu is closed, other than because of a selection, then
        // inform filer window.
        if (_owner) _owner->handle_menus_deleted();
}

void filer_menu::show(wimp_pointer& block)
{
        menu::show(block);
}

void filer_menu::update_layout()
{
        // Iterate over the list of layout methods.
        // Tick the one which matches the window, untick the others.
        layout_method& method=_owner->options().layout();
        for (unsigned int i=0;i!=_layout_count;++i)
                _display_menu[i].tick(_layout_methods[i]==&method);
}

void filer_menu::update_sort()
{
        sort_method& method=_owner->options().sort();
        for (unsigned int i=0;i!=_sort_count;++i)
                _display_menu[4+i].tick(_sort_methods[i]==&method);
}

void filer_menu::update_selection()
{
        // Fetch selection and temporary selection.
        directory& objects=_owner->selection();
        unsigned int temp_selection=_owner->temp_selection();

        // Iterate over selection, counting objects and distinct filetypes
        // (in the latter case, distinguishing only between 0, 1 and some).
        // If there is only one selected object then record its identity.
        // Similarly if there is only one distinct filetype.
        osgbpb_info* object=0;
        unsigned int count=0;
        bits filetype_last=(bits)-1;
        int filetype_count=0;
        for (int i=0,i_end=objects.size();i!=i_end;++i)
        {
                if (objects[i].selected())
                {
                        // Count selected objects.
                        osgbpb_info& info=*objects[i];
                        switch (info.obj_type)
                        {
                        case fileswitch_IS_FILE:
                        case fileswitch_IS_DIR:
                        case fileswitch_IS_IMAGE:
                                object=&info;
                                ++count;
                                break;
                        }

                        // Count distinct filetypes.
                        // In fact what is recorded is the number of times the filetype
                        // changes whilst iterating through the selection, which is
                        // useful enough but easier to compute.
                        bits filetype=(bits)-1;
                        xosfscontrol_info_to_file_type(info.name,info.load_addr,
                                info.exec_addr,info.size,info.attr,info.obj_type,&filetype);
                        if (filetype!=filetype_last)
                        {
                                filetype_last=filetype;
                                ++filetype_count;
                        }
                }
        }

        // Determine how many objects were counted because they are part of
        // a temporary selection.  (This will be either 0 or 1.)
        unsigned int temp_count=0;
        if ((temp_selection!=directory::npos)&&
                (objects[temp_selection].selected())) ++temp_count;

        // Update text of menu entry leading to selection submenu.
        if (count==0)
        {
                // No objects selected.
                _object=0;
                std::sprintf(_selection_text,"File ''");
        }
        else if (count==1)
        {
                // One object selected.
                // Text depends on whether it is a file, directory, image file,
                // or application directory.
                _object=object;
                const char* fmt="";
                switch (object->obj_type)
                {
                case fileswitch_IS_FILE:
                        fmt="File '%.*s'";
                        break;
                case fileswitch_IS_IMAGE:
                        if (object->name[0]=='!') fmt="App. '%.*s'";
                        else fmt="Image '%.*s'";
                        break;
                case fileswitch_IS_DIR:
                        if (object->name[0]=='!') fmt="App. '%.*s'";
                        else fmt="Dir. '%.*s'";
                        break;
                }
                std::sprintf(_selection_text,fmt,max_name_length,object->name);

                // Update dialogue boxes.
                _info_dbox.update(*object);
        }
        else
        {
                // More than one object selected.
                _object=0;
                std::sprintf(_selection_text,"Selection");
        }

        // Disable selection submenu if nothing selected.
        // Disable select all if everything already selected.
        // Disable clear selection if nothing selected.
        // (Disregard any temporary selection.)
        (*this)[1].disabled(count==0);
        (*this)[2].disabled(count-temp_count==objects.size());
        (*this)[3].disabled(count-temp_count==0);

        // Disable copy and rename options unless exactly one object selected.
        _selection_menu[0].disabled(count!=1);
        _selection_menu[1].disabled(count!=1);

        // Disable help option unless exactly one application selected.
        _selection_menu[5].disabled(!(count==1&&
                (object->obj_type&fileswitch_IS_DIR)&&object->name[0]=='!'));

        // Disable info option unless exactly one object selected.
        _selection_menu[6].disabled(count!=1);

        // Populate filetype field.
        if ((filetype_count==1)&&(filetype_last<0x1000))
        {
                union
                {
                        bits words[2];
                        char chars[9];
                };
                xosfscontrol_read_file_type(filetype_last,&words[0],&words[1]);
                int i=8;
                while (i&&chars[i-1]==' ') --i;
                chars[i]=0;
                std::strcpy(_filetype_field,chars);
        }
        else _filetype_field[0]=0;
}

void filer_menu::update_pathname()
{
        // Disable open parent option if already at root.
        char* pathname=(char*)_owner->pathname();
        char* lastdot=strrchr(pathname,'.');
        int isroot=!(lastdot&&strcmp(lastdot+1,"$"));
        (*this)[7].disabled(isroot);
}

void filer_menu::update_options()
{
        fileraction_flags flags=_owner->options().flags();
        _options_menu[0].tick(flags&fileraction_CONFIRM);
        _options_menu[1].tick(flags&fileraction_CONFIRM_DELETES_ONLY);
        _options_menu[2].tick(flags&fileraction_VERBOSE);
        _options_menu[3].tick(flags&fileraction_FORCE);
        _options_menu[4].tick(flags&fileraction_NEWER);
        _options_menu[5].tick(flags&fileraction_FASTER);
}

void filer_menu::update(filer_window* owner)
{
        _owner=owner;
        if (_owner)
        {
                update_layout();
                update_sort();
                update_selection();
                update_pathname();
                update_options();
                _new_dir_dbox.owner(_owner);
        }
}