hilfer 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,805 @@
1
+ # local files
2
+ # local files
3
+ require 'hilfer/scite_editor.rb'
4
+ require 'hilfer/rails_locator.rb'
5
+ require 'hilfer/svn_colours.rb'
6
+ require 'pathname'
7
+
8
+ =begin
9
+ This is the tree viewer object. It contains a GtkTreeView
10
+ and references an editor object. It also contains a HilferItem
11
+ which is the root node of the tree, but it's never displayed
12
+ =end
13
+ class TreeViewer
14
+ include SvnColours
15
+
16
+ attr_reader :view, :editor, :root_item
17
+
18
+ # Can pass in $options[:terminal] => some_terminal
19
+ # to use different terminal types. See GnomeTerminal
20
+ # for an example
21
+ def initialize( path, editor, parent_window, location )
22
+ @terminal = $options[:terminal]
23
+ @parent_window = parent_window
24
+ @location = location
25
+ @location.activates_default = false
26
+
27
+ # these need to be window-local
28
+ @expecting_synchronize_path = false
29
+ @auto_synchronize_path = $options[:auto_sync]
30
+ @root_item = HilferItem.new( '', path )
31
+ # TODO this shuold be in set_root or something like that
32
+ @location.text = @root_item.path
33
+ @location.select_region(0,0)
34
+ @location.position = -1
35
+ @hilfer_items = {}
36
+
37
+ # locators are things that provide a set of shortcut keys
38
+ # to jump to specific directories, files, or related locations
39
+ @locators = []
40
+ @locators << RailsLocator.new( self )
41
+
42
+ # pre-populate model from filesystem, only 1 level deep
43
+ #model = Gtk::TreeStore.new( HilferItem )
44
+ model = HilferModel.new( HilferItem )
45
+ populate( model, @root_item, nil )
46
+
47
+ # Create view
48
+ @view = Gtk::TreeView.new( model )
49
+ @view.headers_visible = false
50
+ @view.selection.mode = Gtk::SELECTION_MULTIPLE
51
+
52
+ # register with the editor, and remember it
53
+ editor.register_view( self )
54
+ @editor = editor
55
+
56
+ # handle key presses
57
+ init_keys
58
+
59
+ # handle enter for location bar
60
+ @location.signal_connect( 'activate' ) do |widget|
61
+ new_path = File.expand_path( widget.text )
62
+ if File.exists? new_path
63
+ go_into( new_path )
64
+ else
65
+ dialog = Gtk::MessageDialog.new(
66
+ @window,
67
+ Gtk::Dialog::Flags::MODAL,
68
+ Gtk::MessageDialog::Type::INFO,
69
+ Gtk::MessageDialog::BUTTONS_OK,
70
+ "Directory #{widget.text} does not exist"
71
+ )
72
+ dialog.run
73
+ dialog.destroy
74
+ end
75
+ end
76
+
77
+ @location.signal_connect( 'focus-out-event' ) do |widget,event|
78
+ #make sure everything is unselected, and put the cursor at the end
79
+ widget.select_region(0,0)
80
+ widget.position = -1
81
+ end
82
+
83
+ # handle double-clicks so they don't activate the
84
+ # cell editing
85
+ @view.signal_connect( 'button-press-event' ) do
86
+ |view, event|
87
+ if event.button == 1 && event.event_type == Gdk::Event::BUTTON2_PRESS
88
+ path, column, cell_x, cell_y = view.get_path( *event.coords )
89
+ if iter = view.model.get_iter( path )
90
+ open_action( [ iter[0] ] )
91
+ true
92
+ end
93
+ end
94
+ end
95
+
96
+ # handle collapse and expand are cached in HilferItems
97
+ @view.signal_connect( 'row-collapsed' ) do
98
+ |view, iter, path|
99
+ iter[0].status = :collapsed
100
+ true
101
+ end
102
+
103
+ @view.signal_connect( 'row-expanded' ) do
104
+ |view, iter, path|
105
+ print "expanding row: #{iter[0].path}, #{iter[0].populated?}\n" if $options[:debug]
106
+ iter[0].status = :expanded
107
+ child = iter.first_child
108
+ loop do
109
+ print "expanded child: #{child[0].path}, #{child[0].populated?}\n" if $options[:debug]
110
+ populate( @view.model, child[0], child ) unless child[0].populated?
111
+ break if !child.next!
112
+ end
113
+
114
+ # remember expanded status
115
+ setstatus
116
+ true
117
+ end
118
+
119
+ # add widgets for the columns and cells
120
+ init_renderers
121
+
122
+ # all columns must be fixed for this to work
123
+ @view.fixed_height_mode = true
124
+
125
+ init_searchable
126
+
127
+ # Allow drag and drop reordering of rows
128
+ # would need to be hooked into fs move, and maybe
129
+ # the refresh so that items aren't put back in their original
130
+ # places. That's hard.
131
+ #@view.set_reorderable( true)
132
+
133
+ # set up refresh thread
134
+ #~ Thread.new( self ) do |arg|
135
+ #~ loop do
136
+ #~ sleep 2
137
+ #~ arg.refresh
138
+ #~ print "refreshed\n"
139
+ #~ end
140
+ #~ end
141
+ end
142
+
143
+ def refresh
144
+ go_into( @root_item.path )
145
+ if $options[:debug]
146
+ load 'rails_locator.rb'
147
+ load 'tree_viewer.rb'
148
+ end
149
+ end
150
+
151
+ def init_renderers
152
+ # The column to display everything in
153
+ column = Gtk::TreeViewColumn.new( "icon and name" )
154
+ column.resizable = false
155
+ column.sizing = Gtk::TreeViewColumn::FIXED
156
+
157
+ # display the icon
158
+ pixbuf_renderer = Gtk::CellRendererPixbuf.new
159
+ column.pack_start( pixbuf_renderer, false )
160
+ column.set_cell_data_func( pixbuf_renderer ) do
161
+ |column, renderer, model, iter|
162
+ item = iter[0]
163
+
164
+ # figure out which icon to use
165
+ stock =
166
+ if item.dir?
167
+ if renderer.expanded?
168
+ Gtk::Stock::OPEN
169
+ else
170
+ Gtk::Stock::OPEN
171
+ end
172
+ else
173
+ Gtk::Stock::FILE
174
+ end
175
+
176
+ # set the icon
177
+ renderer.pixbuf = @view.render_icon( stock, Gtk::IconSize::MENU, 'cell_renderer' )
178
+ end
179
+
180
+ # display the file name
181
+ @text_renderer = Gtk::CellRendererText.new
182
+ @text_renderer.ypad = 0
183
+ column.pack_start( @text_renderer, true )
184
+ column.set_cell_data_func( @text_renderer ) do
185
+ |column, renderer, model, iter|
186
+ item = iter[0]
187
+ renderer.text = item.item
188
+ renderer.foreground = item.colour
189
+ end
190
+
191
+ # Allow file renames
192
+ @text_renderer.signal_connect("editing-started") do |widget,editable,path|
193
+ editable && editable.signal_connect("editing-done") do |widget|
194
+ # Fetch the HilferItem
195
+ item = @view.model.get_iter(path)[0]
196
+
197
+ # make the new file name, in full
198
+ new_path = ( Pathname.new( item.path ).parent + editable.text )
199
+
200
+ # rename the file if it's renameable
201
+ if editable.text != item.item && !File.exists?( new_path.to_s )
202
+ # rename the file
203
+ begin
204
+ File.rename( item.path, new_path.to_s )
205
+ item.item = editable.text
206
+ item.path = new_path.to_s
207
+ rescue Exception => e
208
+ print "couldn't rename #{item.path} to #{new_path.to_s}\n"
209
+ print e, "\n"
210
+ e.backtrace.each { | line | print line, "\n" }
211
+ end
212
+ end
213
+ true
214
+ end
215
+
216
+ true
217
+ end
218
+
219
+ @view.append_column( column )
220
+ end
221
+
222
+ # Set the interactive search box
223
+ def init_searchable
224
+ # allow tree to be searchable using regular expressions
225
+ @view.search_column = 0
226
+ @view.enable_search = true
227
+ @view.set_search_equal_func do
228
+ # - model: a Gtk::TreeModel
229
+ # - column: a Column number
230
+ # - key: a String
231
+ # - iter: a Gtk::TreeIter
232
+ # - Returns: true to continue, or false to quit(the word is matched).
233
+ |model, column, key, iter|
234
+ begin
235
+ iter[0].item !~ /#{key}/
236
+ rescue
237
+ true
238
+ end
239
+ end
240
+ end
241
+
242
+ # display a dialog and delete the files if the
243
+ # response was yes. Unless the don't ask for file
244
+ # deletion flag is switched off
245
+ def confirm_delete_files( iters )
246
+ msg = "Are you sure you want to delete\n"
247
+ iters.each { |it| msg += it[0].path + "\n" }
248
+
249
+ confirm = Gtk::MessageDialog.new(
250
+ @window,
251
+ Gtk::Dialog::Flags::MODAL,
252
+ Gtk::MessageDialog::Type::QUESTION,
253
+ Gtk::MessageDialog::BUTTONS_YES_NO,
254
+ msg
255
+ )
256
+ confirm.default_response = Gtk::Dialog::RESPONSE_YES
257
+ confirm.signal_connect( 'key-press-event' ) do
258
+ |widget,event|
259
+ case event.hardware_keycode
260
+ when 57 # n pressed
261
+ confirm.response( Gtk::Dialog::RESPONSE_NO )
262
+ true
263
+ when 29 # y pressed
264
+ confirm.response( Gtk::Dialog::RESPONSE_YES )
265
+ true
266
+ end
267
+ end
268
+
269
+ # actually delete the files
270
+ confirm.run do |response|
271
+ delete_files( iters ) if response == Gtk::Dialog::RESPONSE_YES
272
+ confirm.destroy
273
+ end
274
+ end
275
+
276
+ def delete_files( iters )
277
+ iters.each do |it|
278
+ begin
279
+ File.delete it[0].path
280
+ @view.model.remove(it)
281
+ rescue
282
+ print "Can't delete #{it[0].path}"
283
+ end
284
+ end
285
+ end
286
+
287
+ # handle keypresses
288
+ def init_keys
289
+ @view.signal_connect( 'key-press-event' ) do
290
+ |widget,event|
291
+ #puts event.inspect
292
+ retval = true
293
+ case
294
+ # enter - go into directory
295
+ # or open all selected files
296
+ when event.hardware_keycode == 36 && event.state.empty?
297
+ items = []
298
+
299
+ # fetch all selected items
300
+ widget.selection.selected_each do |model, path, iter|
301
+ items << model.get_value( iter, 0 )
302
+ end
303
+
304
+ # go into if the first item is a directory
305
+ open_action( items )
306
+
307
+ # ctrl-enter opens new window with the selected root(s)
308
+ when event.hardware_keycode == 36 && event.state.control_mask?
309
+ widget.selection.selected_each do |model, path, iter|
310
+ new_path =
311
+ if !iter[0].dir?
312
+ # open the directory this file lives in
313
+ # unless we're already in that directory
314
+ iter.parent[0].path unless iter.parent.nil?
315
+ else
316
+ # open this directory
317
+ iter[0].path
318
+ end
319
+ TreeViewerWindow.new( new_path, @editor ) unless new_path.nil?
320
+ end
321
+
322
+ # backspace - go up to parent directory
323
+ when event.hardware_keycode == 22
324
+ # go up one directory
325
+ temp = Pathname.new( @root_item.path )
326
+ @root_item.path = temp.parent.realpath.to_s
327
+ go_into( @root_item.path )
328
+
329
+ # del on main keyboard or keypad deletes a file
330
+ when [91,107].include?( event.hardware_keycode )
331
+ iters = []
332
+ widget.selection.selected_each do |model, path, iter|
333
+ iters << iter
334
+ end
335
+ confirm_delete_files( iters )
336
+
337
+ # ctrl-e and F2 edits the file name
338
+ when ( event.hardware_keycode == 26 && event.state.control_mask? ) || event.hardware_keycode == 68
339
+ # make sure all the files are selected
340
+ rows = widget.selection.selected_rows
341
+ rows.each do |path|
342
+ # seems it not necessary to set this
343
+ # @text_renderer.mode = Gtk::CellRendererText::MODE_EDITABLE
344
+ @text_renderer.editable = true
345
+ # true is to start editing
346
+ view.set_cursor( path, view.get_column(0), true )
347
+ # the rest of the work is done in the renderer's handler.
348
+ @text_renderer.editable = false
349
+ end
350
+
351
+ # ctrl-d sends some debug/info commands to scite
352
+ when event.hardware_keycode == 40 && event.state.control_mask? && !event.state.shift_mask?
353
+ @editor.dump
354
+
355
+ # ctrl-b sends the current selection to the editor
356
+ when event.hardware_keycode == 56 && event.state.control_mask? && !event.state.shift_mask?
357
+ paths = []
358
+ widget.selection.selected_each do |model, path, iter|
359
+ # note use of single quotes
360
+ paths << model.get_value( iter, 0 ).path
361
+ end
362
+ @editor.insert paths
363
+
364
+ # ctrl-c copies current selections as text
365
+ when event.hardware_keycode == 54 && event.state.control_mask? && !event.state.shift_mask?
366
+ paths = ""
367
+
368
+ # fetch all selected items
369
+ # don't use a join here so we get a trailing newline
370
+ widget.selection.selected_each do |model, path, iter|
371
+ paths << model.get_value( iter, 0 ).path << "\n"
372
+ end
373
+
374
+ Gtk::Clipboard.get( Gdk::Selection::CLIPBOARD ).text = paths
375
+
376
+ # ctrl-v selects the files in the clipboard
377
+ when event.hardware_keycode == 55 && event.state.control_mask? && !event.state.shift_mask?
378
+ text = Gtk::Clipboard.get( Gdk::Selection::CLIPBOARD ).wait_for_text
379
+ paths = text.strip.split( /\s*\n\s*/ )
380
+ location_path = Pathname.new( @root_item.path )
381
+ paths = paths.map do |x|
382
+ # check for path names relative to the current location
383
+ fullpath = location_path + x
384
+ ( fullpath.exist? ? fullpath : x ).to_s
385
+ end
386
+ select_fs_paths( paths )
387
+
388
+ # ctrl-n opens new window with same root
389
+ when event.hardware_keycode == 57 && event.state.control_mask?
390
+ TreeViewerWindow.new( @root_item.path, @editor )
391
+
392
+ # ctrl-y synchronizes with current editor file
393
+ when event.hardware_keycode == 29 && event.state.control_mask?
394
+ # make sure other windows don't synchronize
395
+ @expecting_synchronize_path = true
396
+ # ask editor for current file and sync
397
+ @editor.synchronize_path
398
+
399
+ # alt-y toggles automatic synchronization
400
+ when event.hardware_keycode == 29 && event.state.mod1_mask?
401
+ @auto_synchronize_path = !@auto_synchronize_path
402
+
403
+ # ctrl-r refreshes from filesystem
404
+ when event.hardware_keycode == 27 && event.state.control_mask?
405
+ refresh
406
+
407
+ # ctrl-t opens a terminal window on the given directorties.
408
+ # Uses gnome-terminal and opens directories in tabs
409
+ when event.hardware_keycode == 28 && event.state.control_mask? && !event.state.shift_mask?
410
+ # collect all unique directories
411
+ dirs = []
412
+ widget.selection.selected_each do |model, path, iter|
413
+ dirs << if iter[0].dir?
414
+ iter[0].path
415
+ else
416
+ if iter.parent != nil
417
+ iter.parent[0].path
418
+ else
419
+ @root_item.path
420
+ end
421
+ end
422
+ end
423
+ terminal.launch( dirs )
424
+
425
+ # alt-l toggles the location bar
426
+ when event.hardware_keycode == 46 && event.state.mod1_mask?
427
+ @location.no_show_all = true
428
+ @location.visible = !@location.visible?
429
+
430
+ # ctrl-* on keypad means expand the entire tree
431
+ when event.hardware_keycode == 63 && event.state.control_mask?
432
+ # 100 levels of directories should be enough...
433
+ populate( @view.model, @root_item, nil, 100 )
434
+ @view.expand_all
435
+
436
+ # * on keypad means expand subtree
437
+ when event.hardware_keycode == 63 && event.state.empty?
438
+ widget.selection.selected_rows.each do |path|
439
+ iter = @view.model.get_iter( path )
440
+ if iter[0].dir?
441
+ expand_dir( widget, path, iter )
442
+ end
443
+ end
444
+
445
+ # shift-/ on keypad means collapse the entire tree
446
+ when event.hardware_keycode == 112 && event.state.shift_mask?
447
+ @view.collapse_all
448
+
449
+ # ctrl-left means go to parent
450
+ when event.hardware_keycode == 100 && event.state.control_mask?
451
+ widget.selection.selected_each do |model, path, iter|
452
+ if iter.parent != nil
453
+ iter.parent[0].last_child_used = path
454
+ select_iter( iter.parent )
455
+ end
456
+
457
+ # only do the first one
458
+ break
459
+ end
460
+
461
+ # ctrl-right means go to last used path, or first child
462
+ when event.hardware_keycode == 102 && event.state.control_mask?
463
+ widget.selection.selected_each do |model, path, iter|
464
+ if iter[0].dir?
465
+ widget.expand_row( iter.path, false )
466
+ if iter[0].last_child_used != nil
467
+ select_path( iter[0].last_child_used )
468
+ else
469
+ select_iter( iter.first_child )
470
+ end
471
+ end
472
+
473
+ # only do the first one
474
+ break
475
+ end
476
+
477
+ # ctrl-up means go to previous sibling with children
478
+ when event.hardware_keycode == 98 && event.state.control_mask?
479
+ widget.selection.selected_each do |model, path, iter|
480
+ if iter[0].dir?
481
+ while path.prev!
482
+ if model.get_iter( path ).has_child?
483
+ select_path( path )
484
+ break
485
+ end
486
+ end
487
+ else
488
+ select_iter iter.parent unless iter.parent.nil?
489
+ end
490
+ break
491
+ end
492
+
493
+ # ctrl-down means go to next sibling with children
494
+ when event.hardware_keycode == 104 && event.state.control_mask?
495
+ widget.selection.selected_each do |model, path, iter|
496
+ if iter[0].dir?
497
+ while iter.next!
498
+ if iter[0].dir?
499
+ select_iter( iter )
500
+ break
501
+ end
502
+ end
503
+ else
504
+ # go up to parent and then down
505
+ # to parent's following sibling
506
+ temp = iter.parent
507
+ if temp != nil
508
+ temp.next!
509
+ select_iter temp
510
+ end
511
+ end
512
+ # only do the first selection
513
+ break
514
+ end
515
+
516
+ # shift-left means select parent
517
+ when event.hardware_keycode == 100 && event.state.shift_mask?
518
+ widget.selection.selected_each do |model, path, iter|
519
+ if iter.parent != nil
520
+ select_children( widget, iter.parent )
521
+ end
522
+ end
523
+
524
+ # shift-right means select children
525
+ when event.hardware_keycode == 102 && event.state.shift_mask?
526
+ widget.selection.selected_each do |model, path, iter|
527
+ if iter[0].dir?
528
+ select_children( widget, iter )
529
+ end
530
+ end
531
+
532
+ # left means close level(s)
533
+ when event.hardware_keycode == 100 && event.state.empty?
534
+ widget.selection.selected_each do |model, path, iter|
535
+ widget.collapse_row( path ) if iter[0].dir?
536
+ end
537
+
538
+ # right means open level(s)
539
+ when event.hardware_keycode == 102
540
+ widget.selection.selected_each do
541
+ |model, path, iter|
542
+ # false means don't expand children
543
+ widget.expand_row( path, false )
544
+ end
545
+
546
+ else
547
+ # pass keypress to all locators
548
+ @locators.each {|x| retval &&= x.handle_keypress( widget, event ) }
549
+ # indicate signal not handled
550
+ if $options[:debug] && !retval
551
+ print "hardware_keycode: #{event.hardware_keycode}\n"
552
+ end
553
+ end
554
+ retval
555
+ end
556
+ end
557
+
558
+ def expand_dir( widget, path, iter )
559
+ # make sure all directories in subtree are populated
560
+ populate( @view.model, iter[0], iter, 100 )
561
+
562
+ # expand row, with children
563
+ widget.expand_row( path, true )
564
+ end
565
+
566
+ def expand_parents_of( path )
567
+ cur = path.clone
568
+ cur.up!
569
+ paths = []
570
+ while !@view.row_expanded?( cur ) && cur.depth > 0
571
+ paths << cur.clone
572
+ cur.up!
573
+ end
574
+
575
+ # now open the necessary paths from the top down
576
+ # false is don't open all children
577
+ paths.reverse.each do |x|
578
+ @view.expand_row( x, false )
579
+ @view.selection.unselect_path( x )
580
+ end
581
+ end
582
+
583
+ def unselect_all
584
+ @view.selection.unselect_all
585
+ end
586
+
587
+ def select_children( widget, iter )
588
+ widget.expand_row( iter.path, false )
589
+ (0...iter.n_children).each do |index|
590
+ select_iter( iter.nth_child( index ), false, false )
591
+ end
592
+ end
593
+
594
+ # set selection and cursor to the given GtkTreeModel path
595
+ def select_path( path, unselect = true, set_cursor = true )
596
+ unselect_all if unselect
597
+ expand_parents_of( path )
598
+ @view.selection.select_path( path )
599
+ @view.set_cursor( path, nil, false ) if set_cursor
600
+ # path, column, use_align, row_align, col_align
601
+ @view.scroll_to_cell( path, nil, true, 0.5, 0.0 )
602
+ end
603
+
604
+ # set selection and cursor to the given GtkTreeModel iter
605
+ def select_iter( iter, unselect = true, set_cursor = true )
606
+ unselect_all if unselect
607
+ expand_parents_of( iter.path )
608
+ @view.selection.select_iter( iter )
609
+ @view.set_cursor( iter.path, nil, false ) if set_cursor
610
+ # path, column, use_align, row_align, col_align
611
+ @view.scroll_to_cell( iter.path, nil, true, 0.5, 0.0 )
612
+ end
613
+
614
+ # Set selection to the given filesystem path.
615
+ # TODO could optimise this to select faster if we
616
+ # use directory names. Possibly also use select_iter if it's faster
617
+ def select_fs_path( fs_path, open = false )
618
+ @view.model.each do
619
+ |model, path, iter|
620
+ if iter[0].path == fs_path
621
+ select_path( path )
622
+ @view.expand_row( path, false ) if open
623
+ break
624
+ end
625
+ end
626
+ end
627
+
628
+ def select_fs_paths( fs_paths, open = false )
629
+ cursor_set = false
630
+ @view.model.each do
631
+ |model, path, iter|
632
+ if fs_paths.include?( iter[0].path )
633
+ @view.expand_row( path, false ) if open
634
+ # set the cursor at the beginning because
635
+ # selection seems to follow it and we want
636
+ # a multiple selection with the cursor on the first
637
+ if !cursor_set
638
+ @view.set_cursor( iter.path, nil, false )
639
+ cursor_set = true
640
+ end
641
+
642
+ select_iter( iter, false, false )
643
+ end
644
+ end
645
+
646
+ end
647
+
648
+ def select_first
649
+ # set first item as selected
650
+ @view.selection.select_path Gtk::TreePath.new( '0' )
651
+ end
652
+
653
+ # callback for the editor classes
654
+ # First checks the values of
655
+ # @expecting_synchronize_path and @auto_synchronize_path
656
+ # and then calls select_fs_path
657
+ def synchronise_editor_path( fs_path )
658
+ if ( @expecting_synchronize_path || @auto_synchronize_path )
659
+ select_fs_path fs_path
660
+ @expecting_synchronize_path = false
661
+ end
662
+ end
663
+
664
+ # called from open_action when we don't want the auto-centering
665
+ # to do its thing.
666
+ def no_synchronize
667
+ save = @auto_synchronize_path
668
+ @auto_synchronize_path = false
669
+ yield @editor
670
+ @auto_synchronize_path = save
671
+ end
672
+
673
+ # called from the keyboard handler on enter
674
+ # and the double-click handler to
675
+ # open files or go into a directory
676
+ def open_action( files )
677
+ if files.size == 1 && files.first.dir?
678
+ go_into( files.first.path )
679
+ else
680
+ nodirs = files.find_all{ |x| !x.dir? }
681
+ no_synchronize do |editor|
682
+ editor.open_action( nodirs )
683
+ end
684
+ end
685
+ end
686
+
687
+ def terminal
688
+ @terminal ||= GnomeTerminal.new
689
+ end
690
+
691
+ # Move the displayed tree to the given directory
692
+ def go_into( fs_path )
693
+ last_select = []
694
+ @view.selection.selected_each do |model, path, iter|
695
+ last_select << path
696
+ end
697
+
698
+ last_select = [ Gtk::TreePath.new( '0' ) ] if last_select.empty?
699
+
700
+ @view.model.clear
701
+ @hilfer_items.values.each { |x| x.populated = false }
702
+ # this waits forever when called by the refresh thread
703
+ @root_item.path = fs_path
704
+ @root_item.populated = false
705
+ # show location as relative to home
706
+ @location.text = @root_item.path.sub( ENV['HOME'], '~' )
707
+ populate( @view.model, @root_item, nil )
708
+ setstatus
709
+ last_select.each { |x| select_path( x ) }
710
+ end
711
+
712
+ # fetch or create the HilferItem. Attemps a lookup from the item_path
713
+ # first if not found creates it from the item_name and item_path
714
+ def fetch_hilfer_item( item_name, item_path )
715
+ if @hilfer_items.has_key?( item_path )
716
+ @hilfer_items[item_path]
717
+ else
718
+ # create new item, add to column 0
719
+ tmp = HilferItem.new( item_name, item_path )
720
+ @hilfer_items[item_path] = tmp
721
+ tmp
722
+ end
723
+ end
724
+
725
+ def do_compute_colours( item )
726
+ if respond_to? :compute_colours
727
+ compute_colours( item.path )
728
+ else
729
+ {}
730
+ end
731
+ end
732
+
733
+ # populate the model from the filesystem
734
+ # by default populate only 1 level down
735
+ def populate( model, item, parent_iter, levels_to_populate = 1, level = 0 )
736
+ iter = nil
737
+ path_colours = do_compute_colours( item )
738
+
739
+ Dir.new( item.path ).entries.sort.each do |x|
740
+ begin
741
+ # ignore directory entries
742
+ next if x =~ /^\.\.?$/
743
+ # ignore hidden files unless the flag is set
744
+ next if x =~ /^\./ && !$options[:hidden]
745
+ # ignore generated files
746
+ next if x =~ /.os$/ || x =~ /.pot$/
747
+
748
+ # the path of the new item to consider
749
+ next_item_path = item.path + '/' + x
750
+
751
+ if item.populated?
752
+ # already have iters for this item
753
+ if iter.nil?
754
+ iter = parent_iter ? parent_iter.first_child : model.iter_first
755
+ else
756
+ iter.next!
757
+ end
758
+ else
759
+ # generate new iters
760
+ iter = model.append( parent_iter )
761
+ end
762
+
763
+ # insert the HilferItem objects, from cache if
764
+ # that exists.
765
+ iter[0] = current_item = fetch_hilfer_item( x, next_item_path )
766
+ if ( colour = path_colours[next_item_path] )
767
+ current_item.colour = colour
768
+ end
769
+
770
+ # recursively populate files, so the TreeView
771
+ # knows to draw the expanders
772
+ if ( ( File.directory?( next_item_path ) && level < levels_to_populate ) || iter[0].expanded? )
773
+ print "populating level #{level} #{iter[0].path}\n" if $options[:debug]
774
+ # populate children
775
+ populate( model, iter[0], iter, levels_to_populate, level + 1 )
776
+ end
777
+
778
+ rescue Errno::EACCES
779
+ # mostly to protect against permissions errors
780
+ rescue Exception => ex
781
+ print "caught exception: #{ex} #{ex.class}\n"
782
+ ex.backtrace.each { |x| print x, "\n" }
783
+ end
784
+
785
+ end
786
+ # OK, this dir is now populated
787
+ item.populated = true
788
+
789
+ # clean deleted entries. Doesn't seem necessary?
790
+ #model.remove iter while iter != model.iter_first
791
+ end
792
+
793
+ # go through and expand / collapse based on HilferItem status
794
+ def setstatus
795
+ @view.model.each do |model, path, iter|
796
+ case iter[0].status
797
+ when :collapsed
798
+ @view.collapse_row( iter.path )
799
+ when :expanded
800
+ @view.expand_row( iter.path, false )
801
+ end
802
+ end
803
+ end
804
+
805
+ end