rbcurse-core 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/README.md +69 -0
  2. data/VERSION +1 -0
  3. data/examples/abasiclist.rb +151 -0
  4. data/examples/alpmenu.rb +46 -0
  5. data/examples/app.sample +17 -0
  6. data/examples/atree.rb +100 -0
  7. data/examples/common/file.rb +45 -0
  8. data/examples/data/README.markdown +9 -0
  9. data/examples/data/brew.txt +38 -0
  10. data/examples/data/color.2 +37 -0
  11. data/examples/data/gemlist.txt +60 -0
  12. data/examples/data/lotr.txt +12 -0
  13. data/examples/data/ports.txt +136 -0
  14. data/examples/data/table.txt +37 -0
  15. data/examples/data/tasks.csv +88 -0
  16. data/examples/data/tasks.txt +27 -0
  17. data/examples/data/todo.txt +10 -0
  18. data/examples/data/todocsv.csv +28 -0
  19. data/examples/data/unix1.txt +21 -0
  20. data/examples/data/unix2.txt +11 -0
  21. data/examples/dbdemo.rb +487 -0
  22. data/examples/dirtree.rb +90 -0
  23. data/examples/newtabbedwindow.rb +100 -0
  24. data/examples/newtesttabp.rb +92 -0
  25. data/examples/tabular.rb +132 -0
  26. data/examples/tasks.rb +167 -0
  27. data/examples/term2.rb +83 -0
  28. data/examples/testkeypress.rb +72 -0
  29. data/examples/testlistbox.rb +158 -0
  30. data/examples/testmessagebox.rb +140 -0
  31. data/examples/testree.rb +106 -0
  32. data/examples/testwsshortcuts.rb +66 -0
  33. data/examples/testwsshortcuts2.rb +127 -0
  34. data/lib/rbcurse.rb +8 -0
  35. data/lib/rbcurse/core/docs/index.txt +73 -0
  36. data/lib/rbcurse/core/include/action.rb +40 -0
  37. data/lib/rbcurse/core/include/appmethods.rb +112 -0
  38. data/lib/rbcurse/core/include/bordertitle.rb +41 -0
  39. data/lib/rbcurse/core/include/chunk.rb +182 -0
  40. data/lib/rbcurse/core/include/io.rb +953 -0
  41. data/lib/rbcurse/core/include/listcellrenderer.rb +140 -0
  42. data/lib/rbcurse/core/include/listeditable.rb +317 -0
  43. data/lib/rbcurse/core/include/listscrollable.rb +590 -0
  44. data/lib/rbcurse/core/include/listselectable.rb +264 -0
  45. data/lib/rbcurse/core/include/multibuffer.rb +83 -0
  46. data/lib/rbcurse/core/include/orderedhash.rb +77 -0
  47. data/lib/rbcurse/core/include/ractionevent.rb +67 -0
  48. data/lib/rbcurse/core/include/rchangeevent.rb +27 -0
  49. data/lib/rbcurse/core/include/rhistory.rb +62 -0
  50. data/lib/rbcurse/core/include/rinputdataevent.rb +47 -0
  51. data/lib/rbcurse/core/include/vieditable.rb +170 -0
  52. data/lib/rbcurse/core/system/colormap.rb +163 -0
  53. data/lib/rbcurse/core/system/keyboard.rb +150 -0
  54. data/lib/rbcurse/core/system/keydefs.rb +30 -0
  55. data/lib/rbcurse/core/system/ncurses.rb +218 -0
  56. data/lib/rbcurse/core/system/panel.rb +162 -0
  57. data/lib/rbcurse/core/system/window.rb +901 -0
  58. data/lib/rbcurse/core/util/ansiparser.rb +117 -0
  59. data/lib/rbcurse/core/util/app.rb +1235 -0
  60. data/lib/rbcurse/core/util/basestack.rb +407 -0
  61. data/lib/rbcurse/core/util/bottomline.rb +1850 -0
  62. data/lib/rbcurse/core/util/colorparser.rb +71 -0
  63. data/lib/rbcurse/core/util/focusmanager.rb +31 -0
  64. data/lib/rbcurse/core/util/padreader.rb +189 -0
  65. data/lib/rbcurse/core/util/rcommandwindow.rb +587 -0
  66. data/lib/rbcurse/core/util/rdialogs.rb +619 -0
  67. data/lib/rbcurse/core/util/viewer.rb +149 -0
  68. data/lib/rbcurse/core/util/widgetshortcuts.rb +505 -0
  69. data/lib/rbcurse/core/widgets/applicationheader.rb +102 -0
  70. data/lib/rbcurse/core/widgets/box.rb +58 -0
  71. data/lib/rbcurse/core/widgets/divider.rb +310 -0
  72. data/lib/rbcurse/core/widgets/keylabelprinter.rb +178 -0
  73. data/lib/rbcurse/core/widgets/rcombo.rb +238 -0
  74. data/lib/rbcurse/core/widgets/rcontainer.rb +415 -0
  75. data/lib/rbcurse/core/widgets/rlink.rb +30 -0
  76. data/lib/rbcurse/core/widgets/rlist.rb +723 -0
  77. data/lib/rbcurse/core/widgets/rmenu.rb +939 -0
  78. data/lib/rbcurse/core/widgets/rmenulink.rb +22 -0
  79. data/lib/rbcurse/core/widgets/rmessagebox.rb +373 -0
  80. data/lib/rbcurse/core/widgets/rprogress.rb +118 -0
  81. data/lib/rbcurse/core/widgets/rtabbedpane.rb +615 -0
  82. data/lib/rbcurse/core/widgets/rtabbedwindow.rb +68 -0
  83. data/lib/rbcurse/core/widgets/rtextarea.rb +920 -0
  84. data/lib/rbcurse/core/widgets/rtextview.rb +780 -0
  85. data/lib/rbcurse/core/widgets/rtree.rb +787 -0
  86. data/lib/rbcurse/core/widgets/rwidget.rb +3040 -0
  87. data/lib/rbcurse/core/widgets/scrollbar.rb +143 -0
  88. data/lib/rbcurse/core/widgets/statusline.rb +94 -0
  89. data/lib/rbcurse/core/widgets/tabular.rb +264 -0
  90. data/lib/rbcurse/core/widgets/tabularwidget.rb +1211 -0
  91. data/lib/rbcurse/core/widgets/textpad.rb +516 -0
  92. data/lib/rbcurse/core/widgets/tree/treecellrenderer.rb +150 -0
  93. data/lib/rbcurse/core/widgets/tree/treemodel.rb +428 -0
  94. metadata +156 -0
@@ -0,0 +1,787 @@
1
+ =begin
2
+ * Name: rtree:
3
+ * Description : a Tree control
4
+ * Author: rkumar (arunachalesha)
5
+ * Date: 2010-09-18 12:02
6
+ * License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
7
+ * This file started on 2010-09-18 12:03 (copied from rlistbox)
8
+ TODO:
9
+ [x] load on tree will expand
10
+ [x] selected row on startup
11
+ [x] open up a node and make current on startup
12
+ [ ] find string
13
+ [/] expand all descendants
14
+ ++ +- and +?
15
+ =end
16
+ require 'rbcurse'
17
+ require 'rbcurse/core/widgets/tree/treemodel'
18
+ require 'rbcurse/core/widgets/tree/treecellrenderer'
19
+
20
+ TreeSelectionEvent = Struct.new(:node, :tree, :state, :previous_node, :row_first)
21
+
22
+ #include Ncurses # FFI 2011-09-8
23
+ module RubyCurses
24
+ extend self
25
+ # a representation of heirarchical data in outline form
26
+ # Currently supports only single selection, and does not allow editing.
27
+ # @events Events: SELECT, DESELECT, TREE_WILL_EXPAND_EVENT, TREE_COLLAPSED_EVENT
28
+ #
29
+ class Tree < Widget
30
+ require 'rbcurse/core/include/listscrollable'
31
+ # currently just use single selection
32
+ include ListScrollable
33
+ #extend Forwardable
34
+ dsl_accessor :height
35
+ dsl_accessor :title
36
+ dsl_property :title_attrib # bold, reverse, normal
37
+ dsl_accessor :border_attrib, :border_color # FIXME not used currently
38
+
39
+ attr_reader :toprow
40
+ # attr_reader :prow
41
+ # attr_reader :winrow
42
+ dsl_accessor :default_value # node to show as selected - what if user doesn't have it?
43
+ attr_accessor :current_index
44
+ dsl_accessor :selected_color, :selected_bgcolor, :selected_attr
45
+ dsl_accessor :max_visible_items # how many to display 2009-01-11 16:15
46
+ dsl_accessor :cell_editing_allowed # obsolete
47
+ dsl_accessor :suppress_borders
48
+ dsl_property :show_selector
49
+ dsl_property :row_selected_symbol # 2009-01-12 12:01 changed from selector to selected
50
+ dsl_property :row_unselected_symbol # added 2009-01-12 12:00
51
+ dsl_property :left_margin
52
+ dsl_accessor :sanitization_required # 2011-10-6
53
+ #dsl_accessor :valign # popup related
54
+ #
55
+ # will pressing a single key move to first matching row. setting it to false lets us use vim keys
56
+ attr_accessor :one_key_selection # will pressing a single key move to first matching row
57
+ # index of row selected, relates to internal representation, not tree. @see selected_row
58
+ attr_reader :selected_index # index of row that is selected. this relates to representation
59
+ attr_reader :treemodel # returns treemodel for further actions 2011-10-2
60
+
61
+ def initialize form, config={}, &block
62
+ @focusable = true
63
+ @editable = false
64
+ @row = 0
65
+ @col = 0
66
+ # array representation of tree
67
+ @list = nil
68
+ # any special attribs such as status to be printed in col1, or color (selection)
69
+ @list_attribs = {}
70
+ # hash containing nodes that are expanded or once expanded
71
+ # if value is true, then currently expanded, else once expanded
72
+ # TODO : will need purging under some situations
73
+ @expanded_state = {}
74
+ @suppress_borders = false
75
+ @row_offset = @col_offset = 1
76
+ @current_index = 0
77
+ @one_key_selection = false # use vim keys
78
+ super
79
+ #@selection_mode ||= :single # default is multiple, anything else given becomes single
80
+ @win = @graphic # 2010-01-04 12:36 BUFFERED replace form.window with graphic
81
+ @sanitization_required = true
82
+ @longest_line = 0
83
+
84
+
85
+ @win_left = 0
86
+ @win_top = 0
87
+ @_events.push(*[:ENTER_ROW, :LEAVE_ROW, :TREE_COLLAPSED_EVENT, :TREE_EXPANDED_EVENT, :TREE_SELECTION_EVENT, :TREE_WILL_COLLAPSE_EVENT, :TREE_WILL_EXPAND_EVENT])
88
+
89
+
90
+ bind(:PROPERTY_CHANGE){|e| @cell_renderer = nil } # will be recreated if anything changes 2011-09-28 V1.3.1
91
+ # FIXME the above is dangerous if user set his own renderer with some values XXX
92
+ init_vars
93
+
94
+ #if !@list.selected_index.nil?
95
+ #set_focus_on @list.selected_index # the new version
96
+ #end
97
+ @keys_mapped = false
98
+ end
99
+ def init_vars
100
+ @repaint_required = true
101
+ @toprow = @pcol = 0
102
+ if @show_selector
103
+ @row_selected_symbol ||= '>'
104
+ @row_unselected_symbol ||= ' '
105
+ @left_margin ||= @row_selected_symbol.length
106
+ end
107
+ @left_margin ||= 0
108
+ #@one_key_selection = true if @one_key_selection.nil?
109
+ @row_offset = @col_offset = 0 if @suppress_borders
110
+ @internal_width = 2 # taking into account borders accounting for 2 cols
111
+ @internal_width = 0 if @suppress_borders # should it be 0 ???
112
+
113
+ end
114
+ # maps keys to methods
115
+ # checks @key_map can be :emacs or :vim.
116
+ def map_keys
117
+ @keys_mapped = true
118
+ $log.debug " cam in XXXX map keys"
119
+ bind_key(32){ toggle_row_selection() }
120
+ bind_key(KEY_ENTER) { toggle_expanded_state() }
121
+ bind_key(?o) { toggle_expanded_state() }
122
+ bind_key(?f){ ask_selection_for_char() }
123
+ bind_key(?\M-v){ @one_key_selection = !@one_key_selection }
124
+ bind_key(KEY_DOWN){ next_row() }
125
+ bind_key(KEY_UP){ previous_row() }
126
+ bind_key(?O){ expand_children() }
127
+ bind_key(?X){ collapse_children() }
128
+ bind_key(?>, :scroll_right)
129
+ bind_key(?<, :scroll_left)
130
+ bind_key(?\M-l, :scroll_right)
131
+ bind_key(?\M-h, :scroll_left)
132
+ # TODO
133
+ bind_key(?x){ collapse_parent() }
134
+ bind_key(?p){ goto_parent() }
135
+ if $key_map == :emacs
136
+ $log.debug " EMACSam in XXXX map keys"
137
+ bind_key(?\C-v){ scroll_forward }
138
+ bind_key(?\M-v){ scroll_backward }
139
+ bind_key(?\C-s){ ask_search() }
140
+ bind_key(?\C-n){ next_row() }
141
+ bind_key(?\C-p){ previous_row() }
142
+ bind_key(?\M->){ goto_bottom() }
143
+ bind_key(?\M-<){ goto_top() }
144
+ else # :vim
145
+ $log.debug " VIM cam in XXXX map keys"
146
+ bind_key(?j){ next_row() }
147
+ bind_key(?k){ previous_row() }
148
+ bind_key(?\C-d){ scroll_forward }
149
+ bind_key(?\C-b){ scroll_backward }
150
+ bind_key(?G){ goto_bottom() }
151
+ bind_key([?g,?g]){ goto_top() }
152
+ bind_key(?/){ ask_search() }
153
+ end
154
+ end
155
+
156
+ ##
157
+ def row_count
158
+ return 0 if @list.nil?
159
+ @list.length
160
+ end
161
+ # at what row should scrolling begin
162
+ def scrollatrow
163
+ if @suppress_borders
164
+ return @height - 1
165
+ else
166
+ return @height - 3
167
+ end
168
+ end
169
+ #
170
+ # Sets the given node as root and returns treemodel.
171
+ # Returns root if no argument given.
172
+ # Now we return root if already set
173
+ # Made node nillable so we can return root.
174
+ #
175
+ # @raise ArgumentError if setting a root after its set
176
+ # or passing nil if its not been set.
177
+ def root node=nil, asks_allow_children=false, &block
178
+ if @treemodel
179
+ return @treemodel.root unless node
180
+ raise ArgumentError, "Root already set"
181
+ end
182
+
183
+ raise ArgumentError, "root: node cannot be nil" unless node
184
+ @treemodel = RubyCurses::DefaultTreeModel.new(node, asks_allow_children, &block)
185
+ end
186
+
187
+ # pass data to create this tree model
188
+ # used to be list
189
+ def data alist=nil
190
+
191
+ # if nothing passed, print an empty root, rather than crashing
192
+ alist = [] if alist.nil?
193
+ @data = alist # data given by user
194
+ case alist
195
+ when Array
196
+ @treemodel = RubyCurses::DefaultTreeModel.new("/")
197
+ @treemodel.root.add alist
198
+ when Hash
199
+ @treemodel = RubyCurses::DefaultTreeModel.new("/")
200
+ @treemodel.root.add alist
201
+ when TreeNode
202
+ # this is a root node
203
+ @treemodel = RubyCurses::DefaultTreeModel.new(alist)
204
+ when DefaultTreeModel
205
+ @treemodel = alist
206
+ else
207
+ if alist.is_a? DefaultTreeModel
208
+ @treemodel = alist
209
+ else
210
+ raise ArgumentError, "Tree does not know how to handle #{alist.class} "
211
+ end
212
+ end
213
+ # we now have a tree
214
+ raise "I still don't have a tree" unless @treemodel
215
+ set_expanded_state(@treemodel.root, true)
216
+ convert_to_list @treemodel
217
+
218
+ # added on 2009-01-13 23:19 since updates are not automatic now
219
+ #@list.bind(:LIST_DATA_EVENT) { |e| list_data_changed() }
220
+ #create_default_list_selection_model TODO
221
+ end
222
+ # private, for use by repaint
223
+ def _list
224
+ if @_structure_changed
225
+ @list = nil
226
+ @_structure_changed = false
227
+ end
228
+ unless @list
229
+ $log.debug " XXX recreating _list"
230
+ convert_to_list @treemodel
231
+ $log.debug " XXXX list: #{@list.size} : #{@list} "
232
+ end
233
+ return @list
234
+ end
235
+ def convert_to_list tree
236
+ @list = get_expanded_descendants(tree.root)
237
+ #$log.debug "XXX convert #{tree.root.children.size} "
238
+ #$log.debug " converted tree to list. #{@list.size} "
239
+ end
240
+ def traverse node, level=0, &block
241
+ raise "disuse"
242
+ #icon = node.is_leaf? ? "-" : "+"
243
+ #puts "%*s %s" % [ level+1, icon, node.user_object ]
244
+ yield node, level if block_given?
245
+ node.children.each do |e|
246
+ traverse e, level+1, &block
247
+ end
248
+ end
249
+ # return object under cursor
250
+ # Note: this should not be confused with selected row/s. User may not have selected this.
251
+ # This is only useful since in some demos we like to change a status bar as a user scrolls down
252
+ # @since 1.2.0 2010-09-06 14:33 making life easier for others.
253
+ def current_row
254
+ @list[@current_index]
255
+ end
256
+ alias :text :current_row # thanks to shoes, not sure how this will impact since widget has text.
257
+
258
+ # show default value as selected and fire handler for it
259
+ # This is called in repaint, so can raise an error if called on creation
260
+ # or before repaint. Just set @default_value, and let us handle the rest.
261
+ # Suggestions are welcome.
262
+ def select_default_values
263
+ return if @default_value.nil?
264
+ # NOTE list not yet created
265
+ raise "list has not yet been created" unless @list
266
+ index = node_to_row @default_value
267
+ raise "could not find node #{@default_value}, #{@list} " unless index
268
+ return unless index
269
+ @current_index = index
270
+ toggle_row_selection
271
+ @default_value = nil
272
+ end
273
+ def print_borders
274
+ width = @width
275
+ height = @height-1 # 2010-01-04 15:30 BUFFERED HEIGHT
276
+ window = @graphic # 2010-01-04 12:37 BUFFERED
277
+ startcol = @col
278
+ startrow = @row
279
+ @color_pair = get_color($datacolor)
280
+ # bordercolor = @border_color || $datacolor # changed 2011 dts
281
+ bordercolor = @border_color || @color_pair # 2011-09-28 V1.3.1
282
+ borderatt = @border_attrib || Ncurses::A_NORMAL
283
+
284
+ window.print_border startrow, startcol, height, width, bordercolor, borderatt
285
+ print_title
286
+ end
287
+ def print_title
288
+ return unless @title
289
+ _title = @title
290
+ if @title.length > @width - 2
291
+ _title = @title[0..@width-2]
292
+ end
293
+ @color_pair ||= get_color($datacolor)
294
+ @graphic.printstring( @row, @col+(@width-_title.length)/2, _title, @color_pair, @title_attrib) unless @title.nil?
295
+ end
296
+ ### START FOR scrollable ###
297
+ def get_content
298
+ #@list 2008-12-01 23:13
299
+ @list_variable && @list_variable.value || @list
300
+ # called by next_match in listscrollable
301
+ @list
302
+ end
303
+ def get_window
304
+ @graphic # 2010-01-04 12:37 BUFFERED
305
+ end
306
+ ### END FOR scrollable ###
307
+ # override widgets text
308
+ def getvalue
309
+ selected_row()
310
+ end
311
+ # Listbox
312
+ def handle_key(ch)
313
+ return if @list.nil? || @list.empty?
314
+ @current_index ||= 0
315
+ @toprow ||= 0
316
+ map_keys unless @keys_mapped
317
+ h = scrollatrow()
318
+ rc = row_count
319
+ $log.debug " tree got ch #{ch}"
320
+ case ch
321
+ when 27, ?\C-c.getbyte(0)
322
+ #editing_canceled @current_index if @cell_editing_allowed
323
+ #cancel_block # block
324
+ $multiplier = 0
325
+ return 0
326
+ #when ?\C-u.getbyte(0)
327
+ # multiplier. Series is 4 16 64
328
+ # TESTING @multiplier = (@multiplier == 0 ? 4 : @multiplier *= 4)
329
+ # return 0
330
+ when ?\C-c.getbyte(0)
331
+ @multiplier = 0
332
+ return 0
333
+ else
334
+ ret = :UNHANDLED
335
+ if ret == :UNHANDLED
336
+ # beware one-key eats up numbers. we'll be wondering why
337
+ if @one_key_selection
338
+ case ch
339
+ #when ?A.getbyte(0)..?Z.getbyte(0), ?a.getbyte(0)..?z.getbyte(0), ?0.getbyte(0)..?9.getbyte(0)
340
+ when ?A.getbyte(0)..?Z.getbyte(0), ?a.getbyte(0)..?z.getbyte(0)
341
+ # simple motion, key press defines motion
342
+ ret = set_selection_for_char ch.chr
343
+ else
344
+ ret = process_key ch, self
345
+ @multiplier = 0
346
+ return :UNHANDLED if ret == :UNHANDLED
347
+ end
348
+ else
349
+ # no motion on single key, we can freak out like in vim, pref f <char> for set_selection
350
+ case ch
351
+ when ?0.getbyte(0)..?9.getbyte(0)
352
+ $multiplier *= 10 ; $multiplier += (ch-48)
353
+ #$log.debug " setting mult to #{$multiplier} in list "
354
+ return 0
355
+ end
356
+ $log.debug " TREE before process key #{ch} "
357
+ ret = process_key ch, self
358
+ $log.debug " TREE after process key #{ch} #{ret} "
359
+ #$multiplier = 0 # 2010-09-02 22:35 this prevents parent from using mult
360
+ return :UNHANDLED if ret == :UNHANDLED
361
+ end
362
+ end
363
+ end
364
+ $multiplier = 0
365
+ end
366
+ # get a keystroke from user and go to first item starting with that key
367
+ def ask_selection_for_char
368
+ ch = @graphic.getch
369
+ if ch < 0 || ch > 255
370
+ return :UNHANDLED
371
+ end
372
+ ret = set_selection_for_char ch.chr
373
+ end
374
+ def ask_search_forward
375
+ regex = get_string("Enter regex to search")
376
+ ix = @list.find_match regex
377
+ if ix.nil?
378
+ alert("No matching data for: #{regex}")
379
+ else
380
+ set_focus_on(ix)
381
+ end
382
+ end
383
+ # gets string to search and calls data models find prev
384
+ def ask_search_backward
385
+ regex = get_string("Enter regex to search (backward)")
386
+ @last_regex = regex
387
+ ix = @list.find_prev regex, @current_index
388
+ if ix.nil?
389
+ alert("No matching data for: #{regex}")
390
+ else
391
+ set_focus_on(ix)
392
+ end
393
+ end
394
+ # please check for error before proceeding
395
+ # @return [Boolean] false if no data
396
+ def on_enter
397
+ if @list.size < 1
398
+ Ncurses.beep
399
+ return false
400
+ end
401
+ on_enter_row @current_index
402
+ set_form_row # added 2009-01-11 23:41
403
+ #$log.debug " ONE ENTER LIST #{@current_index}, #{@form.row}"
404
+ @repaint_required = true
405
+ super
406
+ #fire_handler :ENTER, self
407
+ true
408
+ end
409
+ def on_enter_row arow
410
+ #$log.debug " Listbox #{self} ENTER_ROW with curr #{@current_index}. row: #{arow} H: #{@handler.keys}"
411
+ #fire_handler :ENTER_ROW, arow
412
+ fire_handler :ENTER_ROW, self
413
+ #@list.on_enter_row self TODO
414
+ #edit_row_at arow
415
+ @repaint_required = true
416
+ end
417
+ ##
418
+ def on_leave_row arow
419
+ #$log.debug " Listbox #{self} leave with (cr: #{@current_index}) #{arow}: list[row]:#{@list[arow]}"
420
+ #$log.debug " Listbox #{self} leave with (cr: #{@current_index}) #{arow}: "
421
+ #fire_handler :LEAVE_ROW, arow
422
+ fire_handler :LEAVE_ROW, self
423
+ #editing_completed arow
424
+ end
425
+
426
+ ##
427
+ # getter and setter for cell_renderer
428
+ def cell_renderer(*val)
429
+ if val.empty?
430
+ @cell_renderer ||= create_default_cell_renderer
431
+ else
432
+ @cell_renderer = val[0]
433
+ end
434
+ end
435
+ def create_default_cell_renderer
436
+ return RubyCurses::TreeCellRenderer.new "", {"color"=>@color, "bgcolor"=>@bgcolor, "parent" => self, "display_length"=> @width-@internal_width-@left_margin}
437
+ end
438
+ ##
439
+ # this method chops the data to length before giving it to the
440
+ # renderer, this can cause problems if the renderer does some
441
+ # processing. also, it pans the data horizontally giving the renderer
442
+ # a section of it.
443
+ # FIXME: tree may not be clearing till end see appdirtree after divider movement
444
+ def repaint
445
+ return unless @repaint_required
446
+ @height ||= 10
447
+ @width ||= 30
448
+
449
+ my_win = @form ? @form.window : @target_window
450
+ @graphic = my_win unless @graphic
451
+
452
+ raise " #{@name} neither form, nor target window given TV paint " unless my_win
453
+ raise " #{@name} NO GRAPHIC set as yet TV paint " unless @graphic
454
+ @win_left = my_win.left
455
+ @win_top = my_win.top
456
+
457
+ $log.debug "rtree repaint #{@name} graphic #{@graphic}"
458
+ print_borders unless @suppress_borders # do this once only, unless everything changes
459
+ maxlen = @maxlen || @width-@internal_width
460
+ maxlen -= @left_margin # 2011-10-6
461
+ tm = _list()
462
+ select_default_values
463
+ rc = row_count
464
+ tr = @toprow
465
+ acolor = get_color $datacolor
466
+ h = scrollatrow()
467
+ r,c = rowcol
468
+ @longest_line = @width #maxlen
469
+ 0.upto(h) do |hh|
470
+ crow = tr+hh
471
+ if crow < rc
472
+ _focussed = @current_index == crow ? true : false # row focussed ?
473
+ focus_type = _focussed
474
+ # added 2010-09-02 14:39 so inactive fields don't show a bright focussed line
475
+ #focussed = false if focussed && !@focussed
476
+ focus_type = :SOFT_FOCUS if _focussed && !@focussed
477
+ selected = row_selected? crow
478
+ content = tm[crow] # 2009-01-17 18:37 chomp giving error in some cases says frozen
479
+ if content.is_a? TreeNode
480
+ node = content
481
+ object = content
482
+ leaf = node.is_leaf?
483
+ # content passed is rejected by treecellrenderer 2011-10-6
484
+ content = node.user_object.to_s # may need to trim or truncate
485
+ expanded = row_expanded? crow
486
+ elsif content.is_a? String
487
+ $log.warn "Removed this entire block since i don't think it was used XXX "
488
+ # this block does not set object XXX
489
+ else
490
+ raise "repaint what is the class #{content.class} "
491
+ content = content.to_s
492
+ end
493
+ # this is redundant since data is taken by renderer directly
494
+ #sanitize content if @sanitization_required
495
+ #truncate value
496
+ ## set the selector symbol if requested
497
+ selection_symbol = ''
498
+ if @show_selector
499
+ if selected
500
+ selection_symbol = @row_selected_symbol
501
+ else
502
+ selection_symbol = @row_unselected_symbol
503
+ end
504
+ @graphic.printstring r+hh, c, selection_symbol, acolor,@attr
505
+ end
506
+
507
+ renderer = cell_renderer()
508
+ renderer.display_length(@width-@internal_width-@left_margin) # just in case resizing of listbox
509
+ renderer.pcol = @pcol
510
+ #renderer.repaint @graphic, r+hh, c+@left_margin, crow, content, _focussed, selected
511
+ renderer.repaint @graphic, r+hh, c+@left_margin, crow, object, content, leaf, focus_type, selected, expanded
512
+ @longest_line = renderer.actual_length if renderer.actual_length > @longest_line
513
+ else
514
+ # clear rows
515
+ @graphic.printstring r+hh, c, " " * (@width-@internal_width), acolor,@attr
516
+ end
517
+ end
518
+ @table_changed = false
519
+ @repaint_required = false
520
+ end
521
+
522
+ def list_data_changed
523
+ if row_count == 0 # added on 2009-02-02 17:13 so cursor not hanging on last row which could be empty
524
+ init_vars
525
+ @current_index = 0
526
+ set_form_row
527
+ end
528
+ @repaint_required = true
529
+ end
530
+ def set_form_col col1=0
531
+ @cols_panned ||= 0 # RFED16 2010-02-17 23:40
532
+ win_col = 0
533
+ col2 = win_col + @col + @col_offset + col1 + @cols_panned + @left_margin
534
+ setrowcol nil, col2 # 2010-02-17 23:19 RFED16
535
+ end
536
+ def selected_row
537
+ @list[@selected_index].node
538
+ end
539
+
540
+ # An event is thrown when a row is selected or deselected.
541
+ # Please note that when a row is selected, another one is automatically deselected.
542
+ # An event is not thrown for that since your may not want to collapse that.
543
+ # Only clicking on a selected row, will send a DESELECT on it since you may want to collapse it.
544
+ # However, the previous selection is also present in the event object, so you can act upon it.
545
+ # This is not used for expanding or collapsing, only for application to show some data in another
546
+ # window or pane based on selection. Maybe there should not be a deselect for current row ?
547
+ def toggle_row_selection
548
+ node = @list[@current_index]
549
+ previous_node = nil
550
+ previous_node = @list[@selected_index] if @selected_index
551
+ if @selected_index == @current_index
552
+ @selected_index = nil
553
+ else
554
+ @selected_index = @current_index
555
+ end
556
+ state = @selected_index.nil? ? :DESELECTED : :SELECTED
557
+ #TreeSelectionEvent = Struct.new(:node, :tree, :state, :previous_node, :row_first)
558
+ @tree_selection_event = TreeSelectionEvent.new(node, self, state, previous_node, @current_index) #if @item_event.nil?
559
+ fire_handler :TREE_SELECTION_EVENT, @tree_selection_event # should the event itself be ITEM_EVENT
560
+ $log.debug " XXX tree selected #{@selected_index}/ #{@current_index} , #{state} "
561
+ @repaint_required = true
562
+ end
563
+ def toggle_expanded_state row=@current_index
564
+ state = row_expanded? row
565
+ node = row_to_node
566
+ if node.nil?
567
+ Ncurses.beep
568
+ $log.debug " No such node on row #{row} "
569
+ return
570
+ end
571
+ $log.debug " toggle XXX state #{state} #{node} "
572
+ if state
573
+ collapse_node node
574
+ else
575
+ expand_node node
576
+ end
577
+ end
578
+ def row_to_node row=@current_index
579
+ @list[row]
580
+ end
581
+ # convert a given node to row
582
+ def node_to_row node
583
+ crow = nil
584
+ @list.each_with_index { |e,i|
585
+ if e == node
586
+ crow = i
587
+ break
588
+ end
589
+ }
590
+ crow
591
+ end
592
+ # private
593
+ # related to index in representation, not tree
594
+ def row_selected? row
595
+ @selected_index == row
596
+ end
597
+ # @return [TreeNode, nil] returns selected node or nil
598
+
599
+ def row_expanded? row
600
+ node = @list[row]
601
+ node_expanded? node
602
+ end
603
+ def row_collapsed? row
604
+ !row_expanded? row
605
+ end
606
+ def set_expanded_state(node, state)
607
+ @expanded_state[node] = state
608
+ @repaint_required = true
609
+ _structure_changed true
610
+ end
611
+ def expand_node(node)
612
+ #$log.debug " expand called on #{node.user_object} "
613
+ state = true
614
+ fire_handler :TREE_WILL_EXPAND_EVENT, node
615
+ set_expanded_state(node, state)
616
+ fire_handler :TREE_EXPANDED_EVENT, node
617
+ end
618
+ def collapse_node(node)
619
+ $log.debug " collapse called on #{node.user_object} "
620
+ state = false
621
+ fire_handler :TREE_WILL_COLLAPSE_EVENT, node
622
+ set_expanded_state(node, state)
623
+ fire_handler :TREE_COLLAPSED_EVENT, node
624
+ end
625
+ # this is required to make a node visible, if you wish to start from a node that is not root
626
+ # e.g. you are loading app in a dir somewhere but want to show path from root down.
627
+ # NOTE this sucks since you have to click 2 times to expand it.
628
+ def mark_parents_expanded node
629
+ # i am setting parents as expanded, but NOT firing handlers - XXX separate this into expand_parents
630
+ _path = node.tree_path
631
+ _path.each do |e|
632
+ # if already expanded parent then break we should break
633
+ set_expanded_state(e, true)
634
+ end
635
+ end
636
+ # goes up to root of this node, and expands down to this node
637
+ # this is often required to make a specific node visible such
638
+ # as in a dir listing when current dir is deep in heirarchy.
639
+ def expand_parents node
640
+ _path = node.tree_path
641
+ _path.each do |e|
642
+ # if already expanded parent then break we should break
643
+ #set_expanded_state(e, true)
644
+ expand_node(e)
645
+ end
646
+ end
647
+ # this expands all the children of a node, recursively
648
+ # we can't use multiplier concept here since we are doing a preorder enumeration
649
+ # we need to do a breadth first enumeration to use a multiplier
650
+ #
651
+ def expand_children node=:current_index
652
+ $multiplier = 999 if !$multiplier || $multiplier == 0
653
+ node = row_to_node if node == :current_index
654
+ return if node.children.empty? # or node.is_leaf?
655
+ #node.children.each do |e|
656
+ #expand_node e # this will keep expanding parents
657
+ #expand_children e
658
+ #end
659
+ node.breadth_each($multiplier) do |e|
660
+ expand_node e
661
+ end
662
+ $multiplier = 0
663
+ _structure_changed true
664
+ end
665
+ def collapse_children node=:current_index
666
+ $multiplier = 999 if !$multiplier || $multiplier == 0
667
+ $log.debug " CCCC IINSIDE COLLLAPSE"
668
+ node = row_to_node if node == :current_index
669
+ return if node.children.empty? # or node.is_leaf?
670
+ #node.children.each do |e|
671
+ #expand_node e # this will keep expanding parents
672
+ #expand_children e
673
+ #end
674
+ node.breadth_each($multiplier) do |e|
675
+ $log.debug "CCC collapsing #{e.user_object} "
676
+ collapse_node e
677
+ end
678
+ $multiplier = 0
679
+ _structure_changed true
680
+ end
681
+ # collapse parent
682
+ # can use multiplier.
683
+ # # we need to move up also
684
+ def collapse_parent node=:current_index
685
+ node = row_to_node if node == :current_index
686
+ parent = node.parent
687
+ return if parent.nil?
688
+ goto_parent node
689
+ collapse_node parent
690
+ end
691
+ def goto_parent node=:current_index
692
+ node = row_to_node if node == :current_index
693
+ parent = node.parent
694
+ return if parent.nil?
695
+ crow = @current_index
696
+ @list.each_with_index { |e,i|
697
+ if e == parent
698
+ crow = i
699
+ break
700
+ end
701
+ }
702
+ @repaint_required = true
703
+ #set_form_row # will not work if off form
704
+ set_focus_on crow
705
+ end
706
+
707
+ def has_been_expanded node
708
+ @expanded_state.has_key? node
709
+ end
710
+ def node_expanded? node
711
+ @expanded_state[node] == true
712
+ end
713
+ def node_collapsed? node
714
+ !node_expanded?(node)
715
+ end
716
+ def get_expanded_descendants(node)
717
+ nodes = []
718
+ nodes << node
719
+ traverse_expanded node, nodes
720
+ $log.debug " def get_expanded_descendants(node) #{nodes.size} "
721
+ return nodes
722
+ end
723
+ def traverse_expanded node, nodes
724
+ return if !node_expanded? node
725
+ #nodes << node
726
+ node.children.each do |e|
727
+ nodes << e
728
+ if node_expanded? e
729
+ traverse_expanded e, nodes
730
+ else
731
+ next
732
+ end
733
+ end
734
+ end
735
+
736
+ #
737
+ # To retrieve the node corresponding to a path specified as an array or string
738
+ # Do not mention the root.
739
+ # e.g. "ruby/1.9.2/io/console"
740
+ # or %w[ ruby 1.9.3 io console ]
741
+ # @since 1.4.0 2011-10-2
742
+ def get_node_for_path(user_path)
743
+ case user_path
744
+ when String
745
+ user_path = user_path.split "/"
746
+ when Array
747
+ else
748
+ raise ArgumentError, "Should be Array or String delimited with /"
749
+ end
750
+ $log.debug "TREE #{user_path} " if $log.debug?
751
+ root = @treemodel.root
752
+ found = nil
753
+ user_path.each { |e|
754
+ success = false
755
+ root.children.each { |c|
756
+ if c.user_object == e
757
+ found = c
758
+ success = true
759
+ root = c
760
+ break
761
+ end
762
+ }
763
+ return false unless success
764
+
765
+ }
766
+ return found
767
+ end
768
+ # default block
769
+ # @since 1.5.0 2011-11-22
770
+ def command *args, &block
771
+ bind :TREE_WILL_EXPAND_EVENT, *args, &block
772
+ end
773
+ private
774
+ # please do not rely on this yet, name could change
775
+ def _structure_changed tf=true
776
+ @_structure_changed = tf
777
+ @repaint_required = true
778
+ #@list = nil
779
+ end
780
+
781
+
782
+
783
+ # ADD HERE
784
+ end # class tree
785
+
786
+
787
+ end # module