rbhex-core 1.0.0

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