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/osbyte.h"
#include "oslib/osfscontrol.h"
#include "oslib/wimp.h"

#include "template_loader.h"
#include "filer_sprite.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;

/** The pathname for the template file. */
const char* template_pathname="Resources:$.Resources.Filer.Templates";

}; /* anonymous namespace */

filer_window::filer_window(filer_application* app,const char* pathname,
	const os_box& box,const filer_options& options):
	window(app),
	_pathname(new(std::nothrow) 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",template_pathname);
	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);

	// Register this window in the pathnames table.
	if (app) _pathname_node=app->register_pathname(*this);
}

filer_window::~filer_window()
{
	// Deregister this window from the pathnames table.
	if (filer_application* app=
		dynamic_cast<filer_application*>(parent_application()))
	{
		app->deregister_pathname(_pathname_node);
	}

	// 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);
	bool alt=(shift_state&0x01)!=0;
	bool 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_DRAG_SELECT|wimp_DRAG_ADJUST))
	{
		// No action unless the pointer is over a cell.
		if (index!=directory::npos)
		{
			// No action if select drag with alt.
			if ((block.buttons&wimp_DRAG_ADJUST)||!alt)
			{
				// Choose sprite to be dragged.
				filer_sprite sprite(*_directory[index]);

				// Begin the drag with this sprite.
				begin_drag(block,sprite.name());
			}
		}
	}
	else if (block.buttons&wimp_SINGLE_ADJUST)
	{
		// Cancel rename if one is in progress.
		if (filer_application* app=
			dynamic_cast<filer_application*>(parent_application()))
		{
			app->cancel_rename();
		}

		// Invert selection state of cell under pointer.
		if (index!=directory::npos)
		{
			_directory[index].selected(!_directory[index].selected());
			force_redraw_cell(index);
		}
	}
	else if (block.buttons&wimp_SINGLE_SELECT)
	{
		// Cancel rename if one is in progress.
		if (filer_application* app=
			dynamic_cast<filer_application*>(parent_application()))
		{
			app->cancel_rename();
		}

		if (alt&&index!=directory::npos)
		{
			if (filer_application* app=dynamic_cast<filer_application*>(
				parent_application()))
			{
				app->begin_rename(*this,index,block.pos);
			}
		}
		else if (index==directory::npos||!_directory[index].selected())
		{
			// Select click over a cell which is not already selected.
			// De-select everything.
			select_all(0);

			// Select cell under pointer.
			if (index!=directory::npos)
			{
				_directory[index].selected(true);
				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_user_drag_box(wimp_dragged& block)
{
	// Read pointer state.
	wimp_pointer pointer;
	xwimp_get_pointer_info(&pointer);

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

	if (application* app=parent_application())
	{
		if (pointer.w==handle())
		{
			// Object has been dropped back onto this window: no action.
		}
		else if (filer_window* w=
			dynamic_cast<filer_window*>(app->find_window(pointer.w)))
		{
			// Object has been dropped onto another filer window.
			wimp_t filer_action_handle=begin_filer_action();

			// Copy or move according to shift state.
			const char* dst_pathname=w->pathname();
			fileraction_flags flags=options().flags()|fileraction_RECURSE;
			if (shift)
			{
				fileractionsendstartoperation_move(filer_action_handle,flags,
					dst_pathname,strlen(dst_pathname)+1);
			}
			else
			{
				fileractionsendstartoperation_copy(filer_action_handle,flags,
					dst_pathname,strlen(dst_pathname)+1);
			}
		}
		else
		{
			// Object has been dropped onto some other type of window.
			// Construct Message_DataLoad.
			wimp_message block2;
			block2.your_ref=0;
			block2.action=message_DATA_LOAD;
			block2.data.data_xfer.w=pointer.w;
			block2.data.data_xfer.i=pointer.i;
			block2.data.data_xfer.pos=pointer.pos;
			block2.data.data_xfer.est_size=-1; /* can do better than this */
			block2.data.data_xfer.file_type=0xfff; /* can better this too */

			// Send a Message_DataLoad for each object in the selection.
			int count=0;
			for (unsigned int i=0,i_end=_directory.size();i!=i_end;++i)
			{
				if (_directory[i].selected())
				{
					++count;

					sprintf(block2.data.data_xfer.file_name,"%s.%s",_pathname,
						_directory[i]->name);
					block2.size=(44+strlen(block2.data.data_xfer.file_name)+4)&~3;

					wimp_send_message_to_window(wimp_USER_MESSAGE,&block2,
						pointer.w,pointer.i);
				}
			}
		}
	}
}

void filer_window::handle_key_pressed(wimp_key& block)
{
	parent_application()->handle_key_pressed(block);
}

void filer_window::handle_data_xfer(wimp_message& block)
{
	// A buffer to temporarily hold the leafname.
	static char leafname[212];

	switch (block.action)
	{
	case message_DATA_SAVE:
		{
			// Extract leafname.
			char* p=std::strrchr(block.data.data_xfer.file_name,'.');
			if (p) ++p;
			else p=block.data.data_xfer.file_name;
			std::strcpy(leafname,p);

			// Construct and send acknowledgement.
			block.your_ref=block.my_ref;
			block.action=message_DATA_SAVE_ACK;
			std::sprintf(block.data.data_xfer.file_name,"%s.%s",
				_pathname,leafname);
			block.size=(44+std::strlen(block.data.data_xfer.file_name)+4)&~3;
			xwimp_send_message(wimp_USER_MESSAGE,&block,block.sender);
		}
		break;
	case message_DATA_LOAD:
		{
			// Construct and send acknowledgement.
			block.your_ref=block.my_ref;
			block.action=message_DATA_LOAD_ACK;
			xwimp_send_message(wimp_USER_MESSAGE,&block,block.sender);
		}
		break;
	}
}

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;
	}
}

void filer_window::options(const filer_options& options)
{
	// Keep track of whether window needs to be reformatted, restored
	// and redrawn.
	int need_reformat=0;
	int need_resort=0;
	int need_redraw=0;

	// Check whether layout method has changed.
	if (&options.layout()!=&_options.layout())
	{
		need_reformat=1;
		need_redraw=1;
	}

	// Check whether sorting method has changed.
	if (&options.sort()!=&_options.sort())
	{
		need_resort=1;
		need_redraw=1;
	}

	// Update options.
	_options=options;

	// Reformat, resort and redraw window as required.
	if (need_resort) _directory.sort(_options.sort());
	if (need_redraw) force_redraw();
	if (need_reformat)
	{
		reformat();
		if (need_redraw) force_redraw();
	}
}

filer_menu& filer_window::shared_menu() const
{
	static filer_menu* _shared_menu=0;
	if (!_shared_menu)
	{
		wimp_open_template(template_pathname);
		_shared_menu=new(std::nothrow) filer_menu(parent_application());
		wimp_close_template();
	}
	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();

	// Window will automatically resize if visible area matches extent.
	wimp_window_info winfo;
	get_window_info_header(winfo);
	int xauto=(winfo.xscroll==0)&&
		(winfo.visible.x1-winfo.visible.x0==winfo.extent.x1-winfo.extent.x0);
	int yauto=(winfo.yscroll==0)&&
		(winfo.visible.y1-winfo.visible.y0==winfo.extent.y1-winfo.extent.y0);

	// 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.
	if (xauto&&(_yccount<2))
	{
		_xccount=(_directory.size()<4)?_directory.size():4;
	}
	else
	{
		_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);
}

void filer_window::set_extent(const os_box& extent)
{
	// Window will automatically resize if visible area matches extent.
	wimp_window_info winfo;
	get_window_info_header(winfo);
	int xauto=(winfo.xscroll==0)&&
		(winfo.visible.x1-winfo.visible.x0==winfo.extent.x1-winfo.extent.x0);
	int yauto=(winfo.yscroll==0)&&
		(winfo.visible.y1-winfo.visible.y0==winfo.extent.y1-winfo.extent.y0);

	// Set the new extent.
	window::set_extent(extent);

	// Open the window.
	wimp_open open;
	open.visible=winfo.visible;
	open.xscroll=winfo.xscroll;
	open.yscroll=winfo.yscroll;
	open.next=winfo.next;

	// If automatically resizing then adjust visible area to match extent.
	int reopen=0;
	if (xauto&&(winfo.visible.x1-winfo.visible.x0!=extent.x1-extent.x0))
	{
		open.visible.x1=open.visible.x0+(extent.x1-extent.x0);
		reopen=1;
	}
	if (yauto&&(open.visible.y1-open.visible.y0!=extent.y1-extent.y0))
	{
		open.visible.y0=open.visible.y1-(extent.y1-extent.y0);
		reopen=1;
	}
	if (reopen) open_window(open);
}

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)-ygap;
	box.x1=box.x0+_xcsize;
	box.y1=box.y0+_ycsize+ygap;
	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;
}

wimp_i filer_window::open_rename(directory::size_type index,os_coord pos,
	char* buffer)
{
	// Fetch layout.
	layout_method& layout=_options.layout();
	int xgap=layout.xgap();
	int ygap=layout.ygap();

	// Calculate cell coordinates from cell index.
	int xc=index%_xccount;
	int yc=index/_xccount;
	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;

	// Construct icon block for name field.
	wimp_icon_create icon_c;
	icon_c.w=handle();
	icon_c.icon=*layout.icon_data(layout_method::icon_name,box,
		*_directory[index],false,_options);

	// Extend field upwards and downwards by 4 OS units,
	// and sideways to fill cell.
	icon_c.icon.extent.x0=box.x0;
	icon_c.icon.extent.y0-=4;
	icon_c.icon.extent.x1=box.x1;
	icon_c.icon.extent.y1+=4;

	// Modify icon flags to make it a writable field.
	icon_c.icon.flags&=~(wimp_ICON_BUTTON_TYPE|wimp_ICON_BG_COLOUR);
	icon_c.icon.flags|=wimp_ICON_BORDER|wimp_ICON_FILLED;
	icon_c.icon.flags|=wimp_BUTTON_WRITABLE<<wimp_ICON_BUTTON_TYPE_SHIFT;
	icon_c.icon.flags|=wimp_COLOUR_WHITE<<wimp_ICON_BG_COLOUR_SHIFT;

	// Switch to using dedicated buffer.
	// (The one returned by the layout class is neither large enough
	// nor sufficiently persistant.)
	std::strcpy(buffer,icon_c.icon.data.indirected_text.text);
	icon_c.icon.data.indirected_text.text=buffer;
	icon_c.icon.data.indirected_text.size=256;

	// Make pointer coordinates relative to work area.
	wimp_window_state wstate;
	wstate.w=handle();
	wimp_get_window_state(&wstate);
	os_coord rel_pos;
	rel_pos.x=pos.x-wstate.visible.x0+wstate.xscroll;
	rel_pos.y=pos.y-wstate.visible.y1+wstate.yscroll;

	// Create the icon, claiming the caret.
	wimp_i ic=wimp_create_icon(&icon_c);
	force_redraw_cell(index);
	wimp_set_caret_position(handle(),ic,rel_pos.x,rel_pos.y,-1,-1);

	// Return the icon handle.
	return ic;
}

void filer_window::close_rename(wimp_i ic,directory::size_type index)
{
	wimp_delete_icon(handle(),ic);
	force_redraw_cell(index);
}

void filer_window::rename(directory::size_type index,const char* leafname)
{
	osgbpb_info* object=_directory[index];
	if (object&&std::strlen(leafname))
	{
		const size_t max_pathname=255;
		size_t max_leafname=
			max_pathname-std::strlen(pathname())-1;
		if (max_leafname>max_pathname) max_leafname=0;
		if ((std::strlen(object->name)<=max_leafname)&&
			(std::strlen(leafname)<=max_leafname))
		{
			static char src_pathname[max_pathname+1];
			static char dst_pathname[max_pathname+1];
			std::sprintf(src_pathname,"%s.%s",pathname(),object->name);
			std::sprintf(dst_pathname,"%s.%s",pathname(),leafname);
			osfscontrol_rename(src_pathname,dst_pathname);
		}
	}
}

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(std::nothrow) filer_window(
			(filer_application*)parent_application(),_pathname,box,_options);
		*lastdot='.';
	}
}

void filer_window::set_work_directory() const
{
	osfscontrol_dir(_pathname);
}

wimp_t filer_window::begin_filer_action()
{
	// Start a FilerAction task.
	wimp_t handle=wimp_start_task("Filer_Action");

	// Send it the pathname of the directory on which it is to act.
	fileraction_send_selected_directory(handle,pathname());

	// Send it the list of leafnames on which it is to act.
	for (unsigned int i=0,i_end=_directory.size();i!=i_end;++i)
	{
		if (_directory[i].selected())
		{
			const char* leafname=_directory[i]->name;
			fileraction_send_selected_file(handle,leafname);
		}
	}

	// Return the task handle of the FilerAction task.
	return handle;
}

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(std::nothrow) 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);
		}
	}
}