rbhex-core 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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