Line # Revision Author
1 31 gdshaw@RISCPKG.ORG // This file is part of the free Filer module for RISC OS.
2 // Copyright � 2007 Graham Shaw.
3 // Redistribution and modification are permitted under the terms of the
4 // GNU General Public License (version 2 or any later version).
5
6 #include <cstring>
7 #include <cstdio>
8 64 gdshaw@RISCPKG.ORG #include <new>
9 31 gdshaw@RISCPKG.ORG
10 #include "oslib/fileraction.h"
11 #include "oslib/osfscontrol.h"
12
13 #include "filer_options.h"
14 #include "filer_window.h"
15 #include "filer_menu.h"
16
17 namespace {
18
19 /** The maximum length of a leafname. */
20 const size_t max_name_length=256;
21
22 }; /* anonymous namespace */
23
24 filer_menu::filer_menu(application* app):
25 menu(app,"Filer",9),
26 _owner(0),
27 _object(0),
28 _display_menu(app,"Display",8),
29 _rename_menu(app,"Rename",1),
30 _access_menu(app,"Access",5),
31 _find_menu(app,"Find",1),
32 _filetype_menu(app,"Set type",1),
33 _selection_menu(app,"File",11),
34 _options_menu(app,"Options",6),
35 38 gdshaw@RISCPKG.ORG _new_dir_dbox(app),
36 49 gdshaw@RISCPKG.ORG _copy_dbox(app),
37 52 gdshaw@RISCPKG.ORG _access_dbox(app),
38 41 gdshaw@RISCPKG.ORG _info_dbox(app),
39 31 gdshaw@RISCPKG.ORG _rename_leafname(0),
40 _find_pattern(0),
41 _filetype_field(0),
42 64 gdshaw@RISCPKG.ORG _selection_text(new(std::nothrow) char[12+max_name_length]),
43 31 gdshaw@RISCPKG.ORG _layout_count(3),
44 64 gdshaw@RISCPKG.ORG _layout_methods(new(std::nothrow) layout_method*[_layout_count]),
45 31 gdshaw@RISCPKG.ORG _sort_count(4),
46 64 gdshaw@RISCPKG.ORG _sort_methods(new(std::nothrow) sort_method*[_sort_count])
47 31 gdshaw@RISCPKG.ORG {
48 _layout_methods[0]=&filer_options::layout_large();
49 _layout_methods[1]=&filer_options::layout_small();
50 _layout_methods[2]=&filer_options::layout_full();
51
52 _sort_methods[0]=&filer_options::sort_by_name();
53 _sort_methods[1]=&filer_options::sort_by_type();
54 _sort_methods[2]=&filer_options::sort_by_size();
55 _sort_methods[3]=&filer_options::sort_by_date();
56
57 _display_menu[0].text("Large icons");
58 _display_menu[1].text("Small icons");
59 _display_menu[2].text("Full info");
60 _display_menu[3].text("Thumbnails");
61 _display_menu[4].text("Sort by name");
62 _display_menu[5].text("Sort by type");
63 _display_menu[6].text("Sort by size");
64 _display_menu[7].text("Sort by date");
65 _display_menu[3].separator(1);
66
67 64 gdshaw@RISCPKG.ORG _rename_leafname=new(std::nothrow) char[256];
68 31 gdshaw@RISCPKG.ORG _rename_leafname[0]=0;
69 _rename_menu[0].text(_rename_leafname,256);
70 _rename_menu[0].writable(1);
71
72 _access_menu[0].text("Protected");
73 _access_menu[1].text("Unprotected");
74 _access_menu[2].text("Public");
75 _access_menu[3].text("Private");
76 _access_menu[4].text("Access details");
77 52 gdshaw@RISCPKG.ORG _access_menu[4].dbox(_access_dbox);
78 31 gdshaw@RISCPKG.ORG _access_menu[1].separator(1);
79 _access_menu[3].separator(1);
80
81 64 gdshaw@RISCPKG.ORG _find_pattern=new(std::nothrow) char[256];
82 31 gdshaw@RISCPKG.ORG _find_pattern[0]=0;
83 _find_menu[0].text(_find_pattern,256);
84 _find_menu[0].writable(1);
85
86 64 gdshaw@RISCPKG.ORG _filetype_field=new(std::nothrow) char[16];
87 31 gdshaw@RISCPKG.ORG _filetype_field[0]=0;
88 _filetype_menu[0].text(_filetype_field,16);
89 _filetype_menu[0].writable(1);
90
91 _selection_menu[0].text("Copy");
92 _selection_menu[1].text("Rename");
93 _selection_menu[2].text("Delete");
94 _selection_menu[3].text("Access");
95 _selection_menu[4].text("Count");
96 _selection_menu[5].text("Help");
97 _selection_menu[6].text("Info");
98 _selection_menu[7].text("Find");
99 _selection_menu[8].text("Set type");
100 _selection_menu[9].text("Stamp");
101 _selection_menu[10].text("Share");
102 49 gdshaw@RISCPKG.ORG _selection_menu[0].dbox(_copy_dbox);
103 31 gdshaw@RISCPKG.ORG _selection_menu[1].submenu(_rename_menu);
104 _selection_menu[3].submenu(_access_menu);
105 41 gdshaw@RISCPKG.ORG _selection_menu[6].dbox(_info_dbox);
106 31 gdshaw@RISCPKG.ORG _selection_menu[7].submenu(_find_menu);
107 _selection_menu[8].submenu(_filetype_menu);
108
109 _options_menu[0].text("Confirm all");
110 _options_menu[1].text("Confirm deletes");
111 _options_menu[2].text("Verbose");
112 _options_menu[3].text("Force");
113 _options_menu[4].text("Newer");
114 _options_menu[5].text("Faster");
115 _options_menu[1].separator(1);
116
117 *_selection_text=0;
118 (*this)[0].text("Display");
119 (*this)[1].text(_selection_text);
120 (*this)[2].text("Select all");
121 (*this)[3].text("Clear selection");
122 (*this)[4].text("Options");
123 (*this)[5].text("New directory");
124 (*this)[6].text("Set work directory");
125 (*this)[7].text("Open parent");
126 (*this)[8].text("Refresh");
127 (*this)[0].submenu(_display_menu);
128 (*this)[1].submenu(_selection_menu);
129 (*this)[4].submenu(_options_menu);
130 38 gdshaw@RISCPKG.ORG (*this)[5].dbox(_new_dir_dbox);
131 31 gdshaw@RISCPKG.ORG }
132
133 filer_menu::~filer_menu()
134 {
135 delete[] _rename_leafname;
136 delete[] _find_pattern;
137 delete[] _filetype_field;
138 delete[] _selection_text;
139 delete[] _layout_methods;
140 delete[] _sort_methods;
141 }
142
143 void filer_menu::handle_menu_selection(wimp_selection& block)
144 {
145 // Ensure that handler can never be executed when the menu
146 // has no owner.
147 if (!_owner) return;
148
149 // Get mouse button state for later use.
150 // (Do this at an early stage in case it changes.)
151 wimp_pointer info;
152 xwimp_get_pointer_info(&info);
153
154 36 gdshaw@RISCPKG.ORG // Get copy of filer options.
155 // (This needs to be a copy so that it can be modified,
156 // and so that the filer window can see what has changed.)
157 filer_options options(_owner->options());
158
159 32 gdshaw@RISCPKG.ORG switch (block.items[0])
160 {
161 36 gdshaw@RISCPKG.ORG case 0: /* display */
162 switch (block.items[1])
163 {
164 case 0:
165 case 1:
166 case 2:
167 options.layout(*_layout_methods[block.items[1]]);
168 _owner->options(options);
169 update_layout();
170 break;
171 case 4:
172 case 5:
173 case 6:
174 case 7:
175 options.sort(*_sort_methods[block.items[1]-4]);
176 _owner->options(options);
177 update_sort();
178 break;
179 }
180 break;
181 41 gdshaw@RISCPKG.ORG case 1: /* object */
182 switch (block.items[1])
183 {
184 48 gdshaw@RISCPKG.ORG case 1: /* rename */
185 switch (block.items[2])
186 {
187 case 0:
188 if (_object&&strlen(_rename_leafname))
189 {
190 const size_t max_pathname=255;
191 size_t max_leafname=
192 max_pathname-strlen(_owner->pathname())-1;
193 if (max_leafname>max_pathname) max_leafname=0;
194 if ((strlen(_object->name)<=max_leafname)&&
195 (strlen(_rename_leafname)<=max_leafname))
196 {
197 static char src_pathname[max_pathname+1];
198 static char dst_pathname[max_pathname+1];
199 sprintf(src_pathname,"%s.%s",_owner->pathname(),
200 _object->name);
201 sprintf(dst_pathname,"%s.%s",_owner->pathname(),
202 _rename_leafname);
203 osfscontrol_rename(src_pathname,dst_pathname);
204 }
205 }
206 break;
207 }
208 break;
209 46 gdshaw@RISCPKG.ORG case 2: /* delete */
210 {
211 wimp_t handle=_owner->begin_filer_action();
212 xfileractionsendstartoperation_delete(handle,
213 _owner->options().flags()|fileraction_RECURSE);
214 }
215 break;
216 52 gdshaw@RISCPKG.ORG case 3: /* access */
217 switch (block.items[2])
218 {
219 case 0: /* protected */
220 case 1: /* unprotected */
221 case 2: /* public */
222 case 3: /* private */
223 {
224 int attr_bits=0;
225 int attr_mask=0xffff;
226 switch (block.items[2])
227 {
228 case 0: /* protected */
229 attr_bits|=
230 fileswitch_ATTR_OWNER_LOCKED;
231 attr_mask&=~(
232 fileswitch_ATTR_OWNER_WRITE|
233 fileswitch_ATTR_OWNER_LOCKED|
234 fileswitch_ATTR_WORLD_WRITE);
235 break;
236 case 1: /* unprotected */
237 attr_bits|=
238 fileswitch_ATTR_OWNER_WRITE;
239 attr_mask&=~(
240 fileswitch_ATTR_OWNER_WRITE|
241 fileswitch_ATTR_OWNER_LOCKED);
242 break;
243 case 2: /* public */
244 attr_bits|=
245 fileswitch_ATTR_WORLD_READ;
246 attr_mask&=~(
247 fileswitch_ATTR_WORLD_READ);
248 break;
249 case 3: /* private */
250 attr_mask&=~(
251 fileswitch_ATTR_WORLD_READ|
252 fileswitch_ATTR_WORLD_WRITE);
253 break;
254 }
255 int attr=attr_bits|(attr_mask<<16);
256 wimp_t handle=_owner->begin_filer_action();
257 xfileractionsendstartoperation_access(handle,
258 _owner->options().flags()|fileraction_RECURSE,&attr);
259 }
260 }
261 break;
262 45 gdshaw@RISCPKG.ORG case 4: /* count */
263 {
264 wimp_t handle=_owner->begin_filer_action();
265 xfileractionsendstartoperation_count(handle,
266 _owner->options().flags()|fileraction_RECURSE);
267 }
268 break;
269 41 gdshaw@RISCPKG.ORG case 6: /* info */
270 break;
271 50 gdshaw@RISCPKG.ORG case 7: /* find */
272 {
273 wimp_t handle=_owner->begin_filer_action();
274 xfileractionsendstartoperation_find(handle,
275 _owner->options().flags()|fileraction_RECURSE,
276 _find_pattern,strlen(_find_pattern));
277 }
278 break;
279 51 gdshaw@RISCPKG.ORG case 8: /* set type */
280 {
281 bits filetype=
282 osfscontrol_file_type_from_string(_filetype_field);
283 wimp_t handle=_owner->begin_filer_action();
284 xfileractionsendstartoperation_set_type(handle,
285 _owner->options().flags()|fileraction_RECURSE,&filetype);
286 }
287 break;
288 47 gdshaw@RISCPKG.ORG case 9: /* stamp */
289 {
290 wimp_t handle=_owner->begin_filer_action();
291 xfileractionsendstartoperation_stamp(handle,
292 _owner->options().flags()|fileraction_RECURSE);
293 }
294 break;
295 48 gdshaw@RISCPKG.ORG }
296 45 gdshaw@RISCPKG.ORG break;
297 33 gdshaw@RISCPKG.ORG case 2: /* select all */
298 32 gdshaw@RISCPKG.ORG _owner->select_all(1);
299 break;
300 33 gdshaw@RISCPKG.ORG case 3: /* clear selection */
301 32 gdshaw@RISCPKG.ORG _owner->select_all(0);
302 break;
303 43 gdshaw@RISCPKG.ORG case 4: /* options */
304 {
305 filer_options options(_owner->options());
306 switch (block.items[1])
307 {
308 case 0:
309 options.flags(fileraction_CONFIRM,
310 fileraction_CONFIRM_DELETES_ONLY);
311 break;
312 case 1:
313 options.flags(fileraction_CONFIRM_DELETES_ONLY,
314 fileraction_CONFIRM);
315 break;
316 case 2:
317 options.flags(fileraction_VERBOSE,0);
318 break;
319 case 3:
320 options.flags(fileraction_FORCE,0);
321 break;
322 case 4:
323 options.flags(fileraction_NEWER,0);
324 break;
325 case 5:
326 options.flags(fileraction_FASTER,0);
327 break;
328 }
329 _owner->options(options);
330 update_options();
331 }
332 break;
333 42 gdshaw@RISCPKG.ORG case 6: /* set work directory */
334 _owner->set_work_directory();
335 break;
336 33 gdshaw@RISCPKG.ORG case 7: /* open parent */
337 {
338 os_coord offset;
339 offset.x=-20;
340 offset.y=32;
341 _owner->open_parent(offset);
342 }
343 break;
344 34 gdshaw@RISCPKG.ORG case 8: /* refresh */
345 _owner->refresh();
346 break;
347 32 gdshaw@RISCPKG.ORG }
348
349 31 gdshaw@RISCPKG.ORG if (info.buttons&1)
350 {
351 // If selection made with adjust click then re-open menu.
352 menu::show(info);
353 }
354 else
355 {
356 // Otherwise, allow menu to close but inform filer window.
357 if (_owner) _owner->handle_menus_deleted();
358 }
359 }
360
361 void filer_menu::handle_menus_deleted()
362 {
363 // If menu is closed, other than because of a selection, then
364 // inform filer window.
365 if (_owner) _owner->handle_menus_deleted();
366 }
367
368 void filer_menu::show(wimp_pointer& block)
369 {
370 menu::show(block);
371 }
372
373 void filer_menu::update_layout()
374 {
375 // Iterate over the list of layout methods.
376 // Tick the one which matches the window, untick the others.
377 layout_method& method=_owner->options().layout();
378 for (unsigned int i=0;i!=_layout_count;++i)
379 _display_menu[i].tick(_layout_methods[i]==&method);
380 }
381
382 void filer_menu::update_sort()
383 {
384 sort_method& method=_owner->options().sort();
385 for (unsigned int i=0;i!=_sort_count;++i)
386 _display_menu[4+i].tick(_sort_methods[i]==&method);
387 }
388
389 void filer_menu::update_selection()
390 {
391 // Fetch selection and temporary selection.
392 directory& objects=_owner->selection();
393 unsigned int temp_selection=_owner->temp_selection();
394
395 // Iterate over selection, counting objects and distinct filetypes
396 // (in the latter case, distinguishing only between 0, 1 and some).
397 // If there is only one selected object then record its identity.
398 // Similarly if there is only one distinct filetype.
399 osgbpb_info* object=0;
400 unsigned int count=0;
401 bits filetype_last=(bits)-1;
402 int filetype_count=0;
403 for (int i=0,i_end=objects.size();i!=i_end;++i)
404 {
405 if (objects[i].selected())
406 {
407 // Count selected objects.
408 osgbpb_info& info=*objects[i];
409 switch (info.obj_type)
410 {
411 case fileswitch_IS_FILE:
412 case fileswitch_IS_DIR:
413 case fileswitch_IS_IMAGE:
414 object=&info;
415 ++count;
416 break;
417 }
418
419 // Count distinct filetypes.
420 // In fact what is recorded is the number of times the filetype
421 // changes whilst iterating through the selection, which is
422 // useful enough but easier to compute.
423 bits filetype=(bits)-1;
424 xosfscontrol_info_to_file_type(info.name,info.load_addr,
425 info.exec_addr,info.size,info.attr,info.obj_type,&filetype);
426 if (filetype!=filetype_last)
427 {
428 filetype_last=filetype;
429 ++filetype_count;
430 }
431 }
432 }
433
434 // Determine how many objects were counted because they are part of
435 // a temporary selection. (This will be either 0 or 1.)
436 unsigned int temp_count=0;
437 if ((temp_selection!=directory::npos)&&
438 (objects[temp_selection].selected())) ++temp_count;
439
440 // Update text of menu entry leading to selection submenu.
441 if (count==0)
442 {
443 // No objects selected.
444 _object=0;
445 std::sprintf(_selection_text,"File ''");
446 52 gdshaw@RISCPKG.ORG _access_dbox.update(0);
447 31 gdshaw@RISCPKG.ORG }
448 else if (count==1)
449 {
450 // One object selected.
451 // Text depends on whether it is a file, directory, image file,
452 // or application directory.
453 _object=object;
454 const char* fmt="";
455 switch (object->obj_type)
456 {
457 case fileswitch_IS_FILE:
458 fmt="File '%.*s'";
459 break;
460 case fileswitch_IS_IMAGE:
461 if (object->name[0]=='!') fmt="App. '%.*s'";
462 else fmt="Image '%.*s'";
463 break;
464 case fileswitch_IS_DIR:
465 if (object->name[0]=='!') fmt="App. '%.*s'";
466 else fmt="Dir. '%.*s'";
467 break;
468 }
469 std::sprintf(_selection_text,fmt,max_name_length,object->name);
470 41 gdshaw@RISCPKG.ORG
471 // Update dialogue boxes.
472 49 gdshaw@RISCPKG.ORG _copy_dbox.update(*object);
473 52 gdshaw@RISCPKG.ORG _access_dbox.update(object);
474 41 gdshaw@RISCPKG.ORG _info_dbox.update(*object);
475 31 gdshaw@RISCPKG.ORG }
476 else
477 {
478 // More than one object selected.
479 _object=0;
480 std::sprintf(_selection_text,"Selection");
481 52 gdshaw@RISCPKG.ORG _access_dbox.update(0);
482 31 gdshaw@RISCPKG.ORG }
483
484 // Disable selection submenu if nothing selected.
485 // Disable select all if everything already selected.
486 // Disable clear selection if nothing selected.
487 // (Disregard any temporary selection.)
488 (*this)[1].disabled(count==0);
489 (*this)[2].disabled(count-temp_count==objects.size());
490 (*this)[3].disabled(count-temp_count==0);
491
492 // Disable copy and rename options unless exactly one object selected.
493 _selection_menu[0].disabled(count!=1);
494 _selection_menu[1].disabled(count!=1);
495
496 // Disable help option unless exactly one application selected.
497 _selection_menu[5].disabled(!(count==1&&
498 (object->obj_type&fileswitch_IS_DIR)&&object->name[0]=='!'));
499
500 // Disable info option unless exactly one object selected.
501 _selection_menu[6].disabled(count!=1);
502
503 // Populate filetype field.
504 if ((filetype_count==1)&&(filetype_last<0x1000))
505 {
506 union
507 {
508 bits words[2];
509 char chars[9];
510 };
511 xosfscontrol_read_file_type(filetype_last,&words[0],&words[1]);
512 int i=8;
513 while (i&&chars[i-1]==' ') --i;
514 chars[i]=0;
515 std::strcpy(_filetype_field,chars);
516 }
517 else _filetype_field[0]=0;
518 }
519
520 void filer_menu::update_pathname()
521 {
522 // Disable open parent option if already at root.
523 char* pathname=(char*)_owner->pathname();
524 char* lastdot=strrchr(pathname,'.');
525 int isroot=!(lastdot&&strcmp(lastdot+1,"$"));
526 (*this)[7].disabled(isroot);
527 }
528
529 43 gdshaw@RISCPKG.ORG void filer_menu::update_options()
530 {
531 fileraction_flags flags=_owner->options().flags();
532 _options_menu[0].tick(flags&fileraction_CONFIRM);
533 _options_menu[1].tick(flags&fileraction_CONFIRM_DELETES_ONLY);
534 _options_menu[2].tick(flags&fileraction_VERBOSE);
535 _options_menu[3].tick(flags&fileraction_FORCE);
536 _options_menu[4].tick(flags&fileraction_NEWER);
537 _options_menu[5].tick(flags&fileraction_FASTER);
538 }
539
540 31 gdshaw@RISCPKG.ORG void filer_menu::update(filer_window* owner)
541 {
542 _owner=owner;
543 if (_owner)
544 {
545 49 gdshaw@RISCPKG.ORG _new_dir_dbox.owner(_owner);
546 _copy_dbox.owner(_owner);
547 52 gdshaw@RISCPKG.ORG _access_dbox.owner(_owner);
548 31 gdshaw@RISCPKG.ORG update_layout();
549 update_sort();
550 update_selection();
551 update_pathname();
552 43 gdshaw@RISCPKG.ORG update_options();
553 31 gdshaw@RISCPKG.ORG }
554 }