rbcurse 1.1.5 → 1.2.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. data/CHANGELOG +45 -0
  2. data/Makefile +1 -1
  3. data/Manifest.txt +91 -0
  4. data/NOTES +349 -2
  5. data/README.markdown +12 -0
  6. data/VERSION +1 -1
  7. data/examples/abasiclist.rb +25 -0
  8. data/examples/alpmenu.rb +42 -0
  9. data/examples/app.rb +883 -0
  10. data/examples/appcombo.rb +17 -0
  11. data/examples/appdirtree.rb +73 -0
  12. data/examples/appemail.rb +164 -0
  13. data/examples/appemaillb.rb +308 -0
  14. data/examples/appgcompose.rb +303 -0
  15. data/examples/appgmail.rb +951 -0
  16. data/examples/atree.rb +56 -0
  17. data/examples/dirtree.rb +78 -0
  18. data/examples/focusmanager.rb +31 -0
  19. data/examples/imap.rb +48 -0
  20. data/examples/menu1.rb +79 -0
  21. data/examples/multispl.rb +86 -0
  22. data/examples/rfe.rb +3 -4
  23. data/examples/rmail.rb +188 -0
  24. data/examples/s.rb +10 -0
  25. data/examples/scrollbar.rb +104 -0
  26. data/examples/splitp.rb +56 -0
  27. data/examples/table1.rb +30 -0
  28. data/examples/term.rb +48 -0
  29. data/examples/term2.rb +54 -0
  30. data/examples/test1.rb +4 -2
  31. data/examples/test2.rb +9 -9
  32. data/examples/testapp.rb +44 -0
  33. data/examples/testapp2.rb +51 -0
  34. data/examples/testcombo.rb +2 -2
  35. data/examples/testgmail.rb +46 -0
  36. data/examples/testlistbox.rb +0 -1
  37. data/examples/testmultispl.rb +199 -0
  38. data/examples/testree.rb +127 -0
  39. data/examples/testscroller.rb +0 -1
  40. data/examples/testscrolllb.rb +1 -1
  41. data/examples/testscrollp.rb +2 -1
  42. data/examples/testscrollta.rb +1 -1
  43. data/examples/testscrolltable.rb +1 -2
  44. data/examples/testsplit.rb +1 -1
  45. data/examples/testsplit2.rb +1 -1
  46. data/examples/testsplit3.rb +1 -1
  47. data/examples/testsplit3_1.rb +1 -1
  48. data/examples/testsplit3a.rb +1 -1
  49. data/examples/testsplit3b.rb +1 -1
  50. data/examples/testsplitta.rb +1 -1
  51. data/examples/testsplittv.rb +1 -1
  52. data/examples/testsplittvv.rb +1 -1
  53. data/examples/testtodo.rb +491 -488
  54. data/examples/testvimsplit.rb +111 -0
  55. data/examples/todo.db +0 -0
  56. data/examples/todocsv.csv +28 -0
  57. data/examples/viewtodo.rb +408 -403
  58. data/lib/rbcurse/action.rb +1 -0
  59. data/lib/rbcurse/app.rb +1294 -0
  60. data/lib/rbcurse/applicationheader.rb +7 -2
  61. data/lib/rbcurse/checkboxcellrenderer.rb +0 -12
  62. data/lib/rbcurse/colormap.rb +34 -8
  63. data/lib/rbcurse/comboboxcellrenderer.rb +0 -11
  64. data/lib/rbcurse/defaultlistselectionmodel.rb +23 -7
  65. data/lib/rbcurse/extras/bottomline.rb +1681 -0
  66. data/lib/rbcurse/extras/directorylist.rb +445 -0
  67. data/lib/rbcurse/extras/directorytree.rb +69 -0
  68. data/lib/rbcurse/extras/divider.rb +310 -0
  69. data/lib/rbcurse/extras/focusmanager.rb +31 -0
  70. data/lib/rbcurse/extras/listselectable.rb +222 -0
  71. data/lib/rbcurse/extras/masterdetail.rb +164 -0
  72. data/lib/rbcurse/extras/menutree.rb +63 -0
  73. data/lib/rbcurse/extras/rlink.rb +27 -0
  74. data/lib/rbcurse/extras/rmenulink.rb +21 -0
  75. data/lib/rbcurse/extras/scrollbar.rb +134 -0
  76. data/lib/rbcurse/extras/stdscrwindow.rb +247 -0
  77. data/lib/rbcurse/extras/tabular.rb +258 -0
  78. data/lib/rbcurse/extras/tabularwidget.rb +1070 -0
  79. data/lib/rbcurse/extras/viewer.rb +106 -0
  80. data/lib/rbcurse/io.rb +137 -80
  81. data/lib/rbcurse/keylabelprinter.rb +4 -0
  82. data/lib/rbcurse/listcellrenderer.rb +91 -59
  83. data/lib/rbcurse/listscrollable.rb +93 -95
  84. data/lib/rbcurse/listselectable.rb +60 -7
  85. data/lib/rbcurse/ractionevent.rb +67 -0
  86. data/lib/rbcurse/rbasiclistbox.rb +688 -0
  87. data/lib/rbcurse/rcombo.rb +5 -5
  88. data/lib/rbcurse/rcommandwindow.rb +555 -0
  89. data/lib/rbcurse/rinputdataevent.rb +12 -0
  90. data/lib/rbcurse/rlistbox.rb +305 -124
  91. data/lib/rbcurse/rmenu.rb +99 -46
  92. data/lib/rbcurse/rmessagebox.rb +13 -6
  93. data/lib/rbcurse/rmulticontainer.rb +54 -93
  94. data/lib/rbcurse/rmultisplit.rb +731 -0
  95. data/lib/rbcurse/rmultitextview.rb +3 -2
  96. data/lib/rbcurse/rpopupmenu.rb +0 -1
  97. data/lib/rbcurse/rprogress.rb +117 -0
  98. data/lib/rbcurse/rscrollpane.rb +2 -1
  99. data/lib/rbcurse/rsplitpane.rb +94 -20
  100. data/lib/rbcurse/rsplitpane2.rb +1009 -0
  101. data/lib/rbcurse/rtabbedpane.rb +3 -2
  102. data/lib/rbcurse/rtabbedwindow.rb +0 -1
  103. data/lib/rbcurse/rtable.rb +92 -64
  104. data/lib/rbcurse/rtextarea.rb +91 -57
  105. data/lib/rbcurse/rtextview.rb +223 -70
  106. data/lib/rbcurse/rtree.rb +723 -0
  107. data/lib/rbcurse/rviewport.rb +2 -1
  108. data/lib/rbcurse/rvimsplit.rb +768 -0
  109. data/lib/rbcurse/rwidget.rb +524 -325
  110. data/lib/rbcurse/table/tablecellrenderer.rb +1 -1
  111. data/lib/rbcurse/table/tabledatecellrenderer.rb +0 -1
  112. data/lib/rbcurse/tree/treecellrenderer.rb +137 -0
  113. data/lib/rbcurse/tree/treemodel.rb +428 -0
  114. data/lib/rbcurse/vieditable.rb +14 -13
  115. data/lib/ver/ncurses.rb +6 -0
  116. data/lib/ver/window.rb +67 -32
  117. metadata +99 -23
  118. data/bin/rbcurse +0 -0
  119. data/examples/rvimsplit.rb +0 -376
  120. data/examples/todo.rb +0 -1
  121. data/lib/rbcurse/rform.rb +0 -845
  122. data/lib/rbcurse/selectable.rb +0 -94
  123. data/rbcurse.gemspec +0 -188
@@ -0,0 +1,445 @@
1
+ require 'rbcurse'
2
+ require 'fileutils'
3
+ require 'rbcurse/rlistbox'
4
+ require 'rbcurse/vieditable'
5
+ require 'rbcurse/undomanager'
6
+ ##
7
+ # Created on : Wed Sep 22 22:30:13 IST 2010
8
+ # (c) rkumar (arunachalesha)
9
+ #
10
+ module RubyCurses
11
+ # Display a directory listing, allowing user to drill down on pressing Enter
12
+ # on a directory, or sort when pressing enter on header row.
13
+ class DirectoryList < Listbox
14
+
15
+ include ViEditable
16
+
17
+ def initialize form, config={}, &block
18
+ @hide_dot_files = false
19
+ @hide_others = false
20
+ @curpos = 0
21
+ super
22
+
23
+ #@current_path ||= Dir.getwd # setting it causes selection not to fire if same one
24
+ # is selected first
25
+ @_header_array = [ "Attr", "Size", "Modified" , "Name" , "<Order_by_Extension>" ]
26
+ @_header = " %s %8s %19s %s %s " % @_header_array
27
+ end
28
+ def init_vars
29
+ # which rows are not data, thus don't fire, or give error
30
+ @_non_data_indices = []
31
+ @_header_row_index = 0
32
+ @_first_data_index = 1
33
+ @one_key_selection = false # this allows us to map keys to methods
34
+ vieditable_init_listbox
35
+ undom = SimpleUndo.new self
36
+ bind_key(?\M-h, :scroll_left)
37
+ bind_key(?\M-l, :scroll_right)
38
+ bind_key(KEY_RIGHT, :cursor_forward)
39
+ bind_key(KEY_LEFT, :cursor_backward)
40
+ bind_key(?$, :end_of_line)
41
+ bind_key(?\C-e, :end_of_line)
42
+ bind_key(?\C-a, :start_of_line)
43
+ bind_key(?a, :select_all)
44
+ bind_key(?*, :invert_selection)
45
+ bind_key(?u, :clear_selection)
46
+ bind_key(?+, :ask_select)
47
+ bind_key(?-, :ask_unselect)
48
+ bind_key(?I) { @hide_dot_files = !@hide_dot_files; prune_entries; }
49
+ super
50
+ end
51
+ # changing the current path, refreshes files
52
+ def current_path(*val)
53
+ if val.empty?
54
+ return @current_path
55
+ else
56
+ raise ArgumentError, "current_path should be a directory:#{val[0]}." unless File.directory? val[0]
57
+ oldvalue = @current_path
58
+ if oldvalue != val[0]
59
+ @current_path = val[0]
60
+ populate @current_path
61
+ fire_property_change(:current_path, oldvalue, @current_path)
62
+ end
63
+ end
64
+ self
65
+ end
66
+ # populate the list with file names
67
+ # @param [String, Array] string is the path name to populate with
68
+ # Array is a list of files
69
+ def populate path
70
+ case path
71
+ when String
72
+ @current_path = path
73
+ @entries = Dir.new(path).entries
74
+ @entries.delete(".")
75
+ # isn;t it a bit late here. it needs to happen to what's already there
76
+ @entries.delete_if {|x| x =~ /^\./} if @hide_dot_files
77
+ @entries.delete_if {|x| x =~ /\.bak$/ || x=~/\.swp$/} if @hide_others
78
+ when Array
79
+ path = @current_path
80
+ # we've been passed @entries so we don't need to put it again ??? XXX
81
+ end
82
+ # TODO K M etc
83
+ list @entries
84
+ @list.insert 0, @_header
85
+ @title = @current_path
86
+ @current_index = @_first_data_index
87
+ end
88
+ # called by parent's repaint
89
+ def convert_value_to_text file, crow
90
+ if @short
91
+ file
92
+ else
93
+ if crow == @_header_row_index
94
+ return file
95
+ else
96
+ # OUCH i don't know if its a header !!
97
+ fullname = File.join(@current_path, file)
98
+ fname = file
99
+ stat = File::Stat.new fullname
100
+ time = stat.mtime.to_s[0..18]
101
+ attr = stat.directory? ? "d" : "-"
102
+ attr << (stat.writable? ? "w" : "-")
103
+ attr << (stat.readable? ? "r" : "-")
104
+ attr << (stat.executable? ? "x" : "-")
105
+ fname << "/" if stat.directory? && fname[-1] != "/"
106
+ value = " %s %8d %s %s" % [attr, stat.size, time, fname]
107
+ return value
108
+ end
109
+ end
110
+ end
111
+
112
+ def ___create_default_cell_renderer
113
+ cell_renderer( RubyCurses::DirectoryListCellRenderer.new "", {:color=>@color, :bgcolor=>@bgcolor, :parent => self, :display_length=> @width-2-@left_margin})
114
+ end
115
+ def _get_word_under_cursor line=@_header, pos=@curpos
116
+ finish = line.index(" ", pos)
117
+ start = line.rindex(" ",pos)
118
+ finish = -1 if finish.nil?
119
+ start = 0 if start.nil?
120
+ return line[start..finish]
121
+ end
122
+ # sorts entries by various parameters.
123
+ # Not optimal since it creates file stat objects each time rather than cacheing, but this is a demo of widgets
124
+ # not a real directory lister!
125
+ def sort_by key, reverse=false
126
+ # remove parent before sorting, keep at top
127
+ first = @entries.delete_at(0) if @entries[0]==".."
128
+ #key ||= @sort_key
129
+ sort_keys = { 'Name' => :name, 'Modified' => :mtime, "Size" => :size, "<Order_by_Extension>" => :ext, 'Attr' => :attr, "Accessed" => :atime }
130
+ key = sort_keys[key] if sort_keys.has_key? key
131
+ #if key == @sort_key
132
+ #reverse = true
133
+ #end
134
+ cdir=@current_path+"/"
135
+ case key
136
+ when :size
137
+ @entries.sort! {|x,y| xs = File.stat(cdir+x); ys = File.stat(cdir+y);
138
+ if reverse
139
+ xs.size <=> ys.size
140
+ else
141
+ ys.size <=> xs.size
142
+ end
143
+ }
144
+ when :mtime
145
+ @entries.sort! {|x,y| xs = File.stat(cdir+x); ys = File.stat(cdir+y);
146
+ if reverse
147
+ xs.mtime <=> ys.mtime
148
+ else
149
+ ys.mtime <=> xs.mtime
150
+ end
151
+ }
152
+ when :atime
153
+ @entries.sort! {|x,y| xs = File.stat(cdir+x); ys = File.stat(cdir+y);
154
+ if reverse
155
+ xs.atime <=> ys.atime
156
+ else
157
+ ys.atime <=> xs.atime
158
+ end
159
+ }
160
+ when :name
161
+ @entries.sort! {|x,y| x <=> y
162
+ if reverse
163
+ x <=> y
164
+ else
165
+ y <=> x
166
+ end
167
+ }
168
+ when :ext
169
+ @entries.sort! {|x,y|
170
+ if reverse
171
+ File.extname(cdir+x) <=> File.extname(cdir+y)
172
+ else
173
+ File.extname(cdir+y) <=> File.extname(cdir+x)
174
+ end
175
+ }
176
+ when :attr
177
+ @entries.sort! {|x,y| xs = File.stat(cdir+x); ys = File.stat(cdir+y);
178
+ x = xs.directory? ? "d" : "D"
179
+ y = ys.directory? ? "d" : "D"
180
+ if reverse
181
+ x <=> y
182
+ else
183
+ y <=> x
184
+ end
185
+ }
186
+ end
187
+ @sort_key = key
188
+ @entries.insert 0, first unless first.nil? # keep parent on top
189
+ populate @entries
190
+ end
191
+ GIGA_SIZE = 1073741824.0
192
+ MEGA_SIZE = 1048576.0
193
+ KILO_SIZE = 1024.0
194
+
195
+ # Return the file size with a readable style.
196
+ def readable_file_size(size, precision)
197
+ case
198
+ #when size == 1 : "1 B"
199
+ when size < KILO_SIZE then "%d B" % size
200
+ when size < MEGA_SIZE then "%.#{precision}f K" % (size / KILO_SIZE)
201
+ when size < GIGA_SIZE then "%.#{precision}f M" % (size / MEGA_SIZE)
202
+ else "%.#{precision}f G" % (size / GIGA_SIZE)
203
+ end
204
+ end
205
+ def date_format t
206
+ t.strftime "%Y/%m/%d"
207
+ end
208
+
209
+ # on pressing Enter, execute this action.
210
+ # Here we take the current file and if its a directory, we step into it.
211
+ def fire_action_event
212
+ if @_non_data_indices.include? @current_index
213
+ Ncurses.error
214
+ return
215
+ end
216
+ if @_header_row_index == @current_index
217
+ # user hit enter on the header row. we determine column and sort.
218
+ header = _get_word_under_cursor
219
+ #@reverse = false
220
+ if header == @_last_header_sorted
221
+ @reverse = !@reverse # clicking 2 times won't reverse again
222
+ else
223
+ @reverse = false
224
+ end
225
+ sort_by header.strip, @reverse
226
+ @_last_header_sorted = header
227
+ return
228
+ end
229
+ value = current_value
230
+ value = value.split.last
231
+ if value == ".."
232
+ _path = File.dirname(@current_path)
233
+ else
234
+ _path = File.join(@current_path, value)
235
+ end
236
+ if File.directory? _path
237
+ populate _path
238
+ end
239
+
240
+ super
241
+ end
242
+ def addcol num
243
+ #@repaint_required = true
244
+ @repaint_footer_required = true # 2010-01-23 22:41
245
+ if @form
246
+ @form.addcol num
247
+ else
248
+ @parent_component.form.addcol num
249
+ end
250
+ end
251
+ # set cursor column position
252
+ # if i set col1 to @curpos, i can move around left right if key mapped
253
+ def set_form_col col1=@curpos
254
+ col1 ||= 0
255
+ @cols_panned ||= 0 # RFED16 2010-02-17 23:40
256
+ win_col = 0 # 2010-02-17 23:19 RFED16
257
+ col2 = win_col + @col + @col_offset + col1 + @cols_panned + @left_margin
258
+ setrowcol nil, col2 # 2010-02-17 23:19 RFED16
259
+ end
260
+ def start_of_line
261
+ @repaint_required = true if @pcol > 0 # tried other things but did not work
262
+ set_form_col 0
263
+ @pcol = 0
264
+ end
265
+ # this does not work, since the value in list is not the printed value
266
+ def end_of_line
267
+ blen = current_value.rstrip.length
268
+ set_form_col blen
269
+ end
270
+
271
+ def cursor_backward
272
+ repeatm {
273
+ if @curpos > 0
274
+ @curpos -= 1
275
+ set_form_col
276
+ #addcol -1
277
+ elsif @pcol > 0
278
+ @pcol -= 1
279
+ end
280
+ }
281
+ #@repaint_required = true
282
+ @repaint_footer_required = true # 2010-01-23 22:41
283
+ end
284
+
285
+
286
+ def cursor_forward
287
+ @curpos ||= 0
288
+ maxlen = @maxlen || @width-2
289
+ repeatm {
290
+ if @curpos < @width and @curpos < maxlen-1 # else it will do out of box
291
+ @curpos += 1
292
+ addcol 1
293
+ else
294
+ #@pcol += 1 if @pcol <= @buffer.length
295
+ # buffer not eixstent FIXME
296
+ end
297
+ }
298
+ set_form_col
299
+ #@repaint_required = true
300
+ @repaint_footer_required = true # 2010-01-23 22:41
301
+ end
302
+ def [](index)
303
+ @entries[index]
304
+ end
305
+
306
+ @private
307
+ def longest_line=(l)
308
+ @longest_line = l
309
+ end
310
+ def longest_line
311
+ @longest_line
312
+ end
313
+ # modify a file name.
314
+ # call using 'C'
315
+ def edit_line lineno=@current_index
316
+ line = @list[lineno]
317
+ fullname = File.join(@current_path, line)
318
+ prompt = "Rename file [#{line}] to: "
319
+ maxlen = 80
320
+ config={};
321
+ oldline = line.dup
322
+ config[:default] = line
323
+ ret, str = rbgetstr(@form.window, $error_message_row, $error_message_col, prompt, maxlen, config)
324
+ $log.debug " rbgetstr returned #{ret} , #{str} "
325
+ return if ret != 0
326
+ @list[lineno].replace(str)
327
+ FileUtils.mv fullname, File.join(@current_path, str)
328
+ fire_handler :CHANGE, InputDataEvent.new(0,oldline.length, self, :DELETE_LINE, lineno, oldline)
329
+ fire_handler :CHANGE, InputDataEvent.new(0,str.length, self, :INSERT_LINE, lineno, str)
330
+ @repaint_required = true
331
+ end
332
+ def ask_select
333
+ prompt = "Select Files: "
334
+ maxlen = 80
335
+ config={}
336
+ config[:default] = @file_pattern if @file_pattern
337
+ ret, str = rbgetstr(@form.window, $error_message_row, $error_message_col, prompt, maxlen, config)
338
+ return if ret != 0
339
+ @file_pattern = str
340
+ values = Dir.glob(str)
341
+ select_values values unless values.empty?
342
+ @repaint_required = true
343
+ end
344
+ def ask_unselect
345
+ prompt = "Unselect Files: "
346
+ maxlen = 80
347
+ config={}
348
+ config[:default] = @file_pattern if @file_pattern
349
+ ret, str = rbgetstr(@form.window, $error_message_row, $error_message_col, prompt, maxlen, config)
350
+ return if ret != 0
351
+ @file_pattern = str
352
+ values = Dir.glob(str)
353
+ unselect_values values unless values.empty?
354
+ @repaint_required = true
355
+ end
356
+ def select_all
357
+ # don't select header row
358
+ add_row_selection_interval 1, row_count()
359
+ end
360
+ def invert_selection
361
+ 1.upto(row_count()){|i| toggle_row_selection i }
362
+ end
363
+ # selects all rows with the values given, leaving existing selections
364
+ # intact
365
+ def select_values values
366
+ return unless values
367
+ values.each do |val|
368
+ row = @list.index val
369
+ add_row_selection_interval row, row unless row.nil?
370
+ end
371
+ end
372
+ # unselects all rows with the values given, leaving all other rows intact
373
+ def unselect_values values
374
+ return unless values
375
+ values.each do |val|
376
+ row = @list.index val
377
+ remove_row_selection_interval row, row unless row.nil?
378
+ end
379
+ end
380
+ private
381
+ # was meant to filter rows, but no point. since we can't undo the delete
382
+ # so we just call populate again. this method could get axed
383
+ def prune_entries
384
+ #@entries.delete_if {|x| x =~ /^\./} if @hide_dot_files
385
+ #@entries.delete_if {|x| x =~ /\.bak$/ || x=~/\.swp$/} if @hide_others
386
+ populate @current_path
387
+ set_form_row
388
+ end
389
+ ##
390
+ end # class
391
+
392
+ ##
393
+ # A cell renderer should not changed the length of a line otherwise scrolling etc goes for a toss.
394
+ # The calling repaint method does trimming.
395
+ #
396
+ # A cell renderer can do small modifications, or color changing to data.
397
+ # Here we should color directories or bak files or hidden files, swap files etc differently
398
+ # Should this class do the trimming of data, else its hard to figure out what the extension
399
+ # is if its trimeed out. But then it would have to handle the panning too.
400
+ require 'rbcurse/listcellrenderer'
401
+ class DirectoryListCellRenderer < ListCellRenderer
402
+ #def initialize text="", config={}, &block
403
+ #super
404
+ #end
405
+ # sets @color_pair and @attr
406
+
407
+ ##
408
+ # paint a list box cell
409
+ # 2010-09-02 15:38 changed focussed to take true, false and :SOFT_FOCUS
410
+ # SOFT_FOCUS means the form focus is no longer on this field, but this row
411
+ # was focussed when use was last on this field. This row will take focus
412
+ # when field is focussed again
413
+ #
414
+ # @param [Buffer] window or buffer object used for printing
415
+ # @param [Fixnum] row
416
+ # @param [Fixnum] column
417
+ # @param [Fixnum] actual index into data, some lists may have actual data elsewhere and
418
+ # display data separate. e.g. rfe_renderer (directory listing)
419
+ # @param [String] text to print in cell
420
+ # @param [Boolean, :SOFT_FOCUS] cell focussed, not focussed, cell focussed but field is not focussed
421
+ # @param [Boolean] cell selected or not
422
+ def repaint graphic, r=@row,c=@col, row_index=-1,value=@text, focussed=false, selected=false
423
+
424
+ prepare_default_colors focussed, selected
425
+ if row_index == 0
426
+ #nvalue = " %s %8s %19s %s %s " % value.split(",")
427
+ graphic.printstring r, c, "%-s" % value, @color_pair,@attr
428
+ else
429
+ $log.debug " CELL XXX GTETING #{row_index} ,#{value}"
430
+
431
+
432
+ # ensure we do not exceed
433
+ if !@display_length.nil?
434
+ if value.length > @display_length
435
+ value = value[0..@display_length-1]
436
+ end
437
+ end
438
+ len = @display_length || value.length
439
+ graphic.printstring r, c, "%-*s" % [len, value], @color_pair,@attr
440
+ end
441
+ end
442
+ # ADD HERE
443
+ end
444
+ end # module
445
+ # set filetype=ruby
@@ -0,0 +1,69 @@
1
+ require 'rbcurse/rtree'
2
+ #require 'forwardable'
3
+ # we can extend from Tree but lets just try forwarding
4
+ module RubyCurses
5
+ # this class shows a tree of directories. Pressing ENTER expands or collapses
6
+ # the node. Pressing ENTER selects a node.
7
+ # Should we give options for displaying filenames also ? TODO
8
+ class DirectoryTree < Tree
9
+ attr_reader :selected_path
10
+ #@t = tree :data => model, :height => ht, :border_attrib => borderattrib, :suppress_borders => true
11
+ def _directories wd
12
+ d = Dir.new(wd)
13
+ ent = d.entries.reject{|e| !File.directory? File.join(wd,e)}
14
+ ent.delete(".");ent.delete("..")
15
+ return ent
16
+ end
17
+
18
+ def init_vars
19
+ super
20
+
21
+ one_key_selection = false
22
+ bind :TREE_WILL_EXPAND_EVENT do |node|
23
+ will_expand_action node
24
+ end
25
+
26
+ bind :TREE_SELECTION_EVENT do |ev|
27
+ selection_event ev
28
+ end
29
+ end # init_v
30
+
31
+ # populate this node with child directories
32
+ # this gives user application a chance to override or extend this action
33
+ def will_expand_action node
34
+ path = File.join(*node.user_object_path)
35
+ dirs = _directories path
36
+ ch = node.children
37
+ # add only children that may not be there
38
+ ch.each do |e|
39
+ o = e.user_object
40
+ if dirs.include? o
41
+ dirs.delete o
42
+ else
43
+ # delete this child since its no longer present TODO
44
+ end
45
+ end
46
+ node.add dirs
47
+ path_expanded path
48
+ end
49
+ # notify applications of path expanded so they may do any
50
+ # related action
51
+ # # NOTE: this is not the cleanest way, since you will need objects from your app
52
+ # scope here. User will have to add objects into the config hash after object creation
53
+ # and access them here. (See appdirtree.rb in examples)
54
+ def path_expanded path
55
+ end
56
+ def selection_event ev
57
+ if ev.state == :SELECTED
58
+ node = ev.node
59
+ path = File.join(*node.user_object_path)
60
+ @selected_path = path
61
+ selected_path_changed path
62
+ end
63
+ end
64
+ # inform applications that path has changed
65
+ # gives the user application a place to effect changes elsewhere in app
66
+ def selected_path_changed path
67
+ end
68
+ end # class
69
+ end