rbcurse 1.1.5 → 1.2.0.pre

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