Revision 64 (by gdshaw@RISCPKG.ORG, 2007/05/21 05:44:33) Disabled exceptions and enabled optimisation to reduce code size.
// 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 <new>

#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),
	_copy_dbox(app),
	_access_dbox(app),
	_info_dbox(app),
	_rename_leafname(0),
	_find_pattern(0),
	_filetype_field(0),
	_selection_text(new(std::nothrow) char[12+max_name_length]),
	_layout_count(3),
	_layout_methods(new(std::nothrow) layout_method*[_layout_count]),
	_sort_count(4),
	_sort_methods(new(std::nothrow) 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(std::nothrow) 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[4].dbox(_access_dbox);
	_access_menu[1].separator(1);
	_access_menu[3].separator(1);

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

	_filetype_field=new(std::nothrow) 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[0].dbox(_copy_dbox);
	_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 3: /* access */
			switch (block.items[2])
			{
			case 0: /* protected */
			case 1: /* unprotected */
			case 2: /* public */
			case 3: /* private */
				{
					int attr_bits=0;
					int attr_mask=0xffff;
					switch (block.items[2])
					{
					case 0: /* protected */
						attr_bits|=
							fileswitch_ATTR_OWNER_LOCKED;
						attr_mask&=~(
							fileswitch_ATTR_OWNER_WRITE|
							fileswitch_ATTR_OWNER_LOCKED|
							fileswitch_ATTR_WORLD_WRITE);
						break;
					case 1: /* unprotected */
						attr_bits|=
							fileswitch_ATTR_OWNER_WRITE;
						attr_mask&=~(
							fileswitch_ATTR_OWNER_WRITE|
							fileswitch_ATTR_OWNER_LOCKED);
						break;
					case 2: /* public */
						attr_bits|=
							fileswitch_ATTR_WORLD_READ;
						attr_mask&=~(
							fileswitch_ATTR_WORLD_READ);
						break;
					case 3: /* private */
						attr_mask&=~(
							fileswitch_ATTR_WORLD_READ|
							fileswitch_ATTR_WORLD_WRITE);
						break;
					}
					int attr=attr_bits|(attr_mask<<16);
					wimp_t handle=_owner->begin_filer_action();
					xfileractionsendstartoperation_access(handle,
						_owner->options().flags()|fileraction_RECURSE,&attr);
				}
			}
			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 7: /* find */
			{
				wimp_t handle=_owner->begin_filer_action();
				xfileractionsendstartoperation_find(handle,
					_owner->options().flags()|fileraction_RECURSE,
					_find_pattern,strlen(_find_pattern));
			}
			break;
		case 8: /* set type */
			{
				bits filetype=
					osfscontrol_file_type_from_string(_filetype_field);
				wimp_t handle=_owner->begin_filer_action();
				xfileractionsendstartoperation_set_type(handle,
					_owner->options().flags()|fileraction_RECURSE,&filetype);
			}
			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 ''");
		_access_dbox.update(0);
	}
	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.
		_copy_dbox.update(*object);
		_access_dbox.update(object);
		_info_dbox.update(*object);
	}
	else
	{
		// More than one object selected.
		_object=0;
		std::sprintf(_selection_text,"Selection");
		_access_dbox.update(0);
	}

	// 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)
	{
		_new_dir_dbox.owner(_owner);
		_copy_dbox.owner(_owner);
		_access_dbox.owner(_owner);
		update_layout();
		update_sort();
		update_selection();
		update_pathname();
		update_options();
	}
}