Subversion Repositories Filer-Free

Rev

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

Rev Author Line No. Line
21 gdshaw@RISCPKG.ORG 1
// 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>
35 gdshaw@RISCPKG.ORG 7
#include <cstdio>
21 gdshaw@RISCPKG.ORG 8
 
35 gdshaw@RISCPKG.ORG 9
#include "oslib/osbyte.h"
10
#include "oslib/wimp.h"
11
 
21 gdshaw@RISCPKG.ORG 12
#include "template_loader.h"
25 gdshaw@RISCPKG.ORG 13
#include "layout_method.h"
31 gdshaw@RISCPKG.ORG 14
#include "filer_menu.h"
21 gdshaw@RISCPKG.ORG 15
#include "filer_window.h"
16
#include "filer_application.h"
17
 
35 gdshaw@RISCPKG.ORG 18
namespace {
19
 
20
/** The maximum length of a leafname. */
21
const size_t max_name_length=256;
22
 
23
/** The initial horizontal offset for opening a subdirectory. */
24
const int open_xstart=20;
25
 
26
/** The initial vertical offset for opening a subdirectory. */
27
const int open_ystart=-32;
28
 
29
/** The horizontal offset between subdirectories. */
30
const int open_xstep=20;
31
 
32
/** The vertical offset between subdirectories. */
33
const int open_ystep=0;
34
 
35
/** The number of distinct subdirectory offsets. */
36
const unsigned int open_count=8;
37
 
38 gdshaw@RISCPKG.ORG 38
/** The pathname for the template file. */
39
const char* template_pathname="Resources:$.Resources.Filer.Templates";
40
 
35 gdshaw@RISCPKG.ORG 41
}; /* anonymous namespace */
42
 
21 gdshaw@RISCPKG.ORG 43
filer_window::filer_window(filer_application* app,const char* pathname,
25 gdshaw@RISCPKG.ORG 44
        const os_box& box,const filer_options& options):
21 gdshaw@RISCPKG.ORG 45
        window(app),
46
        _pathname(new char[std::strlen(pathname)+1]),
25 gdshaw@RISCPKG.ORG 47
        _options(options),
48
        _directory(options.sort()),
49
        _xcsize(68),
50
        _ycsize(68),
51
        _xccount(1),
31 gdshaw@RISCPKG.ORG 52
        _yccount(1),
35 gdshaw@RISCPKG.ORG 53
        _temp_selection(directory::npos),
54
        _auto_pos(open_xstart,open_ystart,open_xstep,open_ystep,open_count)
21 gdshaw@RISCPKG.ORG 55
{
56
        // Copy pathname into buffer owned by this object.
57
        std::strcpy(_pathname,pathname);
58
 
59
        // Load directory listing.
60
        reload();
61
 
62
        // Create window from template.
38 gdshaw@RISCPKG.ORG 63
        template_loader loader("directory",template_pathname);
21 gdshaw@RISCPKG.ORG 64
        wimp_window& wblock=loader;
65
        wblock.title_flags|=wimp_ICON_INDIRECTED;
66
        wblock.title_data.indirected_text.text=_pathname;
67
        wblock.title_data.indirected_text.validation="";
68
        wblock.title_data.indirected_text.size=strlen(_pathname);
69
        wblock.visible=box;
70
        wblock.extent.x0=0;
71
        wblock.extent.y0=-(box.y1-box.y0);
72
        wblock.extent.x1=box.x1-box.x0;
73
        wblock.extent.y1=0;
28 gdshaw@RISCPKG.ORG 74
        wblock.flags|=wimp_WINDOW_IGNORE_XEXTENT;
21 gdshaw@RISCPKG.ORG 75
        wblock.icon_count=0;
76
        create_window(wblock);
77
 
78
        // Resize extent to match actual visible area.
79
        wimp_window_state wstate;
80
        get_window_state(wstate);
81
        os_box extent;
82
        extent.x0=0;
83
        extent.y0=-(wstate.visible.y1-wstate.visible.y0);
84
        extent.x1=wstate.visible.x1-wstate.visible.x0;
85
        extent.y1=0;
86
        window::set_extent(extent);
87
 
25 gdshaw@RISCPKG.ORG 88
        // Open window, but keep it hidden from view.
21 gdshaw@RISCPKG.ORG 89
        wimp_window_info winfo;
90
        get_window_info_header(winfo);
91
        wimp_open open;
92
        open.visible.x0=winfo.visible.x0;
93
        open.visible.y0=winfo.visible.y1-(winfo.extent.y1-winfo.extent.y0);
94
        open.visible.x1=winfo.visible.x0+(winfo.extent.x1-winfo.extent.x0);
95
        open.visible.y1=winfo.visible.y1;
96
        open.visible=winfo.visible;
97
        open.xscroll=0;
98
        open.yscroll=0;
25 gdshaw@RISCPKG.ORG 99
        open.next=wimp_HIDDEN;
100
        open_window(open);
101
 
102
        // Reformat content of window.
103
        reformat();
104
 
105
        // Reopen window, now making it visible.
106
        get_window_state(wstate);
107
        open.visible=wstate.visible;
108
        open.xscroll=wstate.xscroll;
109
        open.yscroll=wstate.yscroll;
21 gdshaw@RISCPKG.ORG 110
        open.next=wimp_TOP;
111
        open_window(open);
112
}
113
 
114
filer_window::~filer_window()
115
{
116
        // Delete pathname buffer.
117
        delete[] _pathname;
118
}
119
 
120
void filer_window::handle_redraw_request(wimp_draw& block)
121
{
25 gdshaw@RISCPKG.ORG 122
        // Fetch layout.
123
        layout_method& layout=_options.layout();
124
        int xgap=layout.xgap();
125
        int ygap=layout.ygap();
126
 
21 gdshaw@RISCPKG.ORG 127
        int more=0;
128
        xwimp_redraw_window(&block,&more);
129
        while (more)
130
        {
25 gdshaw@RISCPKG.ORG 131
                // Get clipping coordinates with respect to this window.
132
                int xmin=block.clip.x0-block.box.x0+block.xscroll-xgap;
133
                int ymin=block.clip.y0-block.box.y1+block.yscroll+ygap;
134
                int xmax=block.clip.x1-block.box.x0+block.xscroll-xgap;
135
                int ymax=block.clip.y1-block.box.y1+block.yscroll+ygap;
136
 
137
                // Determine which cells are within clipping box.
138
                int xcmin=(xmin)/(_xcsize+xgap);
139
                int ycmin=(-ymax)/(_ycsize+ygap);
140
                int xcmax=(xmax-1)/(_xcsize+xgap)+1;
141
                int ycmax=(-ymin-1)/(_ycsize+ygap)+1;
142
                if (xcmin<0) xcmin=0;
143
                if (ycmin<0) ycmin=0;
144
                if (xcmax>_xccount) xcmax=_xccount;
145
                if (ycmax>_yccount) ycmax=_yccount;
146
 
147
                // Iterate over cells to be redrawn.
148
                for (int yc=ycmin;yc<ycmax;++yc)
149
                {
150
                        for (int xc=xcmin;xc<xcmax;++xc)
151
                        {
152
                                // Determine whether cell is occupied.
30 gdshaw@RISCPKG.ORG 153
                                unsigned int i=xc+yc*_xccount;
154
                                if (i<_directory.size())
25 gdshaw@RISCPKG.ORG 155
                                {
156
                                        // Redraw cell.
157
                                        os_box box;
158
                                        box.x0=xc*(_xcsize+xgap)+xgap;
159
                                        box.y0=-yc*(_ycsize+ygap)-_ycsize-ygap;
160
                                        box.x1=box.x0+_xcsize;
161
                                        box.y1=box.y0+_ycsize;
162
                                        layout.plot(box,*_directory[i],
163
                                                _directory[i].selected(),_options);
164
                                }
165
                        }
166
                }
167
 
21 gdshaw@RISCPKG.ORG 168
                xwimp_get_rectangle(&block,&more);
169
        }
170
}
171
 
28 gdshaw@RISCPKG.ORG 172
void filer_window::handle_open_request(wimp_open& block)
173
{
174
        int xccount=_xccount;
175
        int yccount=_yccount;
176
        reformat(block.visible.x1-block.visible.x0);
177
        xwimp_open_window(&block);
178
        if (_xccount!=xccount||_yccount!=yccount) force_redraw();
179
}
180
 
29 gdshaw@RISCPKG.ORG 181
void filer_window::handle_close_request(wimp_close& block)
182
{
183
        wimp_pointer pointer;
184
        xwimp_get_pointer_info(&pointer);
185
 
186
        if (pointer.buttons&wimp_CLICK_ADJUST)
187
        {
188
                os_coord offset;
189
                offset.x=0;
190
                offset.y=0;
191
                open_parent(offset);
192
        }
193
 
194
        delete this;
195
}
196
 
30 gdshaw@RISCPKG.ORG 197
void filer_window::handle_mouse_click(wimp_pointer& block)
198
{
199
        // Fetch layout.
200
        layout_method& layout=options().layout();
201
 
35 gdshaw@RISCPKG.ORG 202
        // Read shift state.
203
        int shift_state=0;
204
        xosbyte1(osbyte_VAR_KEYBOARD_STATE,0,0xff,&shift_state);
205
        int shift=(shift_state&0x08)!=0;
206
 
30 gdshaw@RISCPKG.ORG 207
        // Find cell under pointer.
208
        unsigned int index=find_cell(block.pos);
209
 
35 gdshaw@RISCPKG.ORG 210
        bool close=false;
31 gdshaw@RISCPKG.ORG 211
        if (block.buttons==wimp_CLICK_MENU)
30 gdshaw@RISCPKG.ORG 212
        {
31 gdshaw@RISCPKG.ORG 213
                // Any existing menu will be deleted by this action.
214
                handle_menus_deleted();
215
 
216
                // Count number of cells selected.
217
                int count=0;
218
                for (unsigned int i=0,i_end=_directory.size();i!=i_end;++i)
219
                {
220
                        if (_directory[i].selected()) ++count;
221
                }
222
 
223
                // If pointer is over a cell and nothing has yet been selected
224
                // then temporarily select the cell under the pointer.
225
                if (index!=directory::npos&&!_directory[index].selected()&&!count)
226
                {
227
                        _directory[index].selected(true);
228
                        _temp_selection=index;
229
                        force_redraw_cell(index);
230
                }
231
 
232
                // Update then open the menu.
233
                shared_menu().update(this);
234
                shared_menu().show(block);
235
        }
35 gdshaw@RISCPKG.ORG 236
        else if (block.buttons&(wimp_DOUBLE_ADJUST|wimp_DOUBLE_SELECT))
237
        {
238
                // If double click within a cell then run that object.
239
                if (index!=directory::npos)
240
                {
241
                        select_all(0);
242
                        close=(block.buttons&wimp_DOUBLE_ADJUST)!=0;
243
                        if (close) close_window();
244
                        run(*_directory[index],close,shift);
245
                }
246
        }
31 gdshaw@RISCPKG.ORG 247
        else if (block.buttons&(wimp_SINGLE_SELECT|wimp_SINGLE_ADJUST))
248
        {
30 gdshaw@RISCPKG.ORG 249
                // If single select click then first de-select everything.
250
                if (block.buttons&wimp_SINGLE_SELECT) select_all(0);
251
 
252
                // If click within a cell then select that cell.
253
                if (index!=directory::npos)
254
                {
255
                        _directory[index].selected(!_directory[index].selected());
256
                        force_redraw_cell(index);
257
                }
258
        }
35 gdshaw@RISCPKG.ORG 259
 
260
        // If this window has closed as a result of the mouse event
261
        // then delete it.
262
        if (close) delete this;
30 gdshaw@RISCPKG.ORG 263
}
264
 
31 gdshaw@RISCPKG.ORG 265
void filer_window::handle_menus_deleted()
266
{
267
        if (_temp_selection!=directory::npos)
268
        {
269
                _directory[_temp_selection].selected(0);
270
                force_redraw_cell(_temp_selection);
271
                _temp_selection=directory::npos;
272
        }
273
}
274
 
36 gdshaw@RISCPKG.ORG 275
void filer_window::options(const filer_options& options)
276
{
277
        // Keep track of whether window needs to be reformatted, restored
278
        // and redrawn.
279
        int need_reformat=0;
280
        int need_resort=0;
281
        int need_redraw=0;
282
 
283
        // Check whether layout method has changed.
284
        if (&options.layout()!=&_options.layout())
285
        {
286
                need_reformat=1;
287
                need_redraw=1;
288
        }
289
 
290
        // Check whether sorting method has changed.
291
        if (&options.sort()!=&_options.sort())
292
        {
293
                need_resort=1;
294
                need_redraw=1;
295
        }
296
 
297
        // Update options.
298
        _options=options;
299
 
300
        // Reformat, resort and redraw window as required.
301
        if (need_resort) _directory.sort(_options.sort());
302
        if (need_redraw) force_redraw();
303
        if (need_reformat)
304
        {
305
                reformat();
306
                if (need_redraw) force_redraw();
307
        }
308
}
309
 
31 gdshaw@RISCPKG.ORG 310
filer_menu& filer_window::shared_menu() const
311
{
312
        static filer_menu* _shared_menu=0;
313
        if (!_shared_menu)
314
        {
38 gdshaw@RISCPKG.ORG 315
                wimp_open_template(template_pathname);
31 gdshaw@RISCPKG.ORG 316
                _shared_menu=new filer_menu(parent_application());
38 gdshaw@RISCPKG.ORG 317
                wimp_close_template();
31 gdshaw@RISCPKG.ORG 318
        }
319
        return *_shared_menu;
320
}
321
 
21 gdshaw@RISCPKG.ORG 322
void filer_window::reload()
323
{
324
        static int buffer[256];
325
        _directory.load(_pathname,buffer,sizeof(buffer));
326
}
25 gdshaw@RISCPKG.ORG 327
 
328
void filer_window::reformat()
329
{
330
        wimp_window_state wstate;
331
        get_window_state(wstate);
332
        reformat(wstate.visible.x1-wstate.visible.x0);
333
}
334
 
335
void filer_window::reformat(int xsize)
336
{
337
        // Fetch layout.
338
        layout_method& layout=options().layout();
339
        int xgap=layout.xgap();
340
        int ygap=layout.ygap();
341
 
342
        // Calculate cell size.  This must be able to accommodate
343
        // - the minimum cell size for the chosen layout, and
344
        // - the actual size occupied by each file to be listed.
345
        os_coord csize=layout.min_size(_options);
346
        for (unsigned int i=0;i!=_directory.size();++i)
347
        {
348
                os_coord size=layout.size(*_directory[i],_options);
349
                if (size.x>csize.x) csize.x=size.x;
350
                if (size.y>csize.y) csize.y=size.y;
351
        }
352
 
353
        // Apply the calculated cell size.
354
        _xcsize=csize.x;
355
        _ycsize=csize.y;
356
 
357
        // Determine number of rows and columns.
358
        _xccount=xsize/(_xcsize+xgap);
359
        if (_xccount<1) _xccount=1;
360
        _yccount=(_directory.size()+_xccount-1)/_xccount;
361
        if (_yccount<1) _yccount=1;
362
 
363
        // Calculate new extent.
364
        int extent_xsize=_xccount*(_xcsize+xgap)+xgap;
365
        int extent_ysize=_yccount*(_ycsize+ygap)+ygap;
366
 
28 gdshaw@RISCPKG.ORG 367
        // Do not force the window to become narrower than the width requested,
25 gdshaw@RISCPKG.ORG 368
        // unless there are too few directory entries to justify that width.
369
        if (extent_xsize<xsize)
370
        {
371
                extent_xsize=xsize;
372
                if (((extent_xsize+_xcsize-1)/(_xcsize+xgap))>
373
                        static_cast<int>(_directory.size()))
374
                {
375
                        extent_xsize=_directory.size()*(_xcsize+xgap)+xgap;
376
                }
377
        }
378
 
379
        // Apply the calculated extent.
380
        os_box extent;
381
        extent.x0=0;
382
        extent.y0=-extent_ysize;
383
        extent.x1=extent_xsize;
384
        extent.y1=0;
385
        set_extent(extent);
386
}
29 gdshaw@RISCPKG.ORG 387
 
30 gdshaw@RISCPKG.ORG 388
unsigned int filer_window::find_cell(const os_coord& p)
389
{
390
        layout_method& layout=options().layout();
391
        int xgap=layout.xgap();
392
        int ygap=layout.ygap();
393
 
394
        wimp_window_state state;
395
        get_window_state(state);
396
        int x=p.x-state.visible.x0+state.xscroll-xgap;
397
        int y=p.y-state.visible.y1+state.yscroll+ygap;
398
 
399
        int xc=x/(_xcsize+xgap);
400
        int yc=(-y)/(_ycsize+ygap);
401
        unsigned int index=xc+yc*_xccount;
402
 
403
        x-=xc*(_xcsize+xgap);
404
        y+=yc*(_ycsize+ygap);
405
 
406
        int found=(x>=0)&&(y>=-_ycsize)&&(x<_xcsize)&&(y<0)&&
407
                (xc>=0)&&(yc>=0)&&(xc<_xccount)&&(yc<_yccount)&&
408
                (index<_directory.size());
409
 
410
        return (found)?index:directory::npos;
411
}
412
 
413
void filer_window::force_redraw_cell(unsigned int index)
414
{
415
        layout_method& layout=options().layout();
416
        int xgap=layout.xgap();
417
        int ygap=layout.ygap();
418
 
419
        int xc=index%_xccount;
420
        int yc=index/_xccount;
421
 
422
        os_box box;
423
        box.x0=xgap+xc*(_xcsize+xgap);
424
        box.y0=-(yc+1)*(_ycsize+ygap);
425
        box.x1=box.x0+_xcsize;
426
        box.y1=box.y0+_ycsize;
427
        force_redraw(box);
428
}
429
 
34 gdshaw@RISCPKG.ORG 430
void filer_window::refresh()
431
{
432
        reload();
433
        reformat();
434
        force_redraw();
435
}
436
 
30 gdshaw@RISCPKG.ORG 437
void filer_window::select_all(int selected)
438
{
439
        layout_method& layout=options().layout();
440
        int xgap=layout.xgap();
441
        int ygap=layout.ygap();
442
 
443
        int xc_min=_xccount;
444
        int yc_min=_yccount;
445
        int xc_max=0;
446
        int yc_max=0;
447
 
448
        for (unsigned int i=0;i!=_directory.size();++i)
449
        {
450
                if (_directory[i].selected()!=selected)
451
                {
452
                        _directory[i].selected(selected);
453
 
454
                        int xc=i%_xccount;
455
                        int yc=i/_xccount;
456
                        if (xc<xc_min) xc_min=xc;
457
                        if (yc<yc_min) yc_min=yc;
458
                        if (xc+1>xc_max) xc_max=xc+1;
459
                        if (yc+1>yc_max) yc_max=yc+1;
460
                }
461
        }
462
 
463
        if ((xc_max>xc_min)&&(yc_max>yc_min))
464
        {
465
                os_box box;
466
                box.x0=xgap+xc_min*(_xcsize+xgap);
467
                box.y0=-yc_max*(_ycsize+ygap);
468
                box.x1=xc_max*(_xcsize+xgap);
469
                box.y1=-ygap-yc_min*(_ycsize+ygap);
470
                force_redraw(box);
471
        }
31 gdshaw@RISCPKG.ORG 472
 
473
        _temp_selection=directory::npos;
30 gdshaw@RISCPKG.ORG 474
}
475
 
29 gdshaw@RISCPKG.ORG 476
void filer_window::open_parent(const os_coord& offset) const
477
{
478
        // Determine whether there is a parent directory.
479
        char* lastdot=strrchr(_pathname,'.');
480
        if (lastdot&&strcmp(lastdot+1,"$"))
481
        {
482
                // Find top-left corner of this window.
483
                wimp_window_state state;
484
                get_window_state(state);
485
 
486
                // Add offset to give top-left corner of new window.
487
                os_box box;
488
                box.x0=state.visible.x0+offset.x;
489
                box.y1=state.visible.y1+offset.y;
490
                box.x1=box.x0;
491
                box.y0=box.y1;
492
 
493
                // Open new window.
494
                *lastdot=0;
495
                new filer_window((filer_application*)parent_application(),
496
                        _pathname,box,_options);
497
                *lastdot='.';
498
        }
499
}
35 gdshaw@RISCPKG.ORG 500
 
501
void filer_window::run(osgbpb_info& info,int close,int shift)
502
{
503
        // Need to at least partially handle shift key here, because
504
        // Filer_Run does not allow coordinates to be specified when
505
        // shift-clicking an application directory.
506
        // (... unless there is an alternative way to invoke Filer_Run?)
507
 
508
        static char buffer[12+max_name_length];
509
        if ((info.obj_type&fileswitch_IS_DIR)&&((info.name[0]!='!')||shift))
510
        {
511
                // Get dimensions of this window.
512
                wimp_window_state state;
513
                get_window_state(state);
514
 
515
                // Choose coordinates for new window.
516
                // If closing this window then open in same position,
517
                // otherwise open below and to the right.
518
                os_coord offset=_auto_pos();
519
                os_box box;
520
                box.x0=state.visible.x0+((close)?0:offset.x);
521
                box.y1=state.visible.y1+((close)?0:offset.y);
522
                box.x1=box.x0;
523
                box.y0=box.y1;
524
 
525
                // Open new window.
526
                if (std::strlen(_pathname)+std::strlen(info.name)+1<max_name_length)
527
                {
528
                        std::sprintf(buffer,"%s.%s",_pathname,info.name);
529
                        window* w=new filer_window(
530
                                (filer_application*)parent_application(),
531
                                buffer,box,_options);
532
                }
533
        }
534
        else
535
        {
536
                // Run object.
537
                if (std::strlen(_pathname)+std::strlen(info.name)+1<max_name_length)
538
                {
539
                        std::sprintf(buffer,"Run %s.%s",_pathname,info.name);
540
                        wimp_start_task(buffer);
541
                }
542
        }
543
}