canis 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +45 -0
  3. data/CHANGES +52 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +24 -0
  7. data/Rakefile +2 -0
  8. data/canis.gemspec +25 -0
  9. data/examples/alpmenu.rb +46 -0
  10. data/examples/app.sample +19 -0
  11. data/examples/appemail.rb +191 -0
  12. data/examples/atree.rb +105 -0
  13. data/examples/bline.rb +181 -0
  14. data/examples/common/devel.rb +319 -0
  15. data/examples/common/file.rb +93 -0
  16. data/examples/data/README.markdown +9 -0
  17. data/examples/data/brew.txt +38 -0
  18. data/examples/data/color.2 +37 -0
  19. data/examples/data/gemlist.txt +59 -0
  20. data/examples/data/lotr.txt +12 -0
  21. data/examples/data/ports.txt +136 -0
  22. data/examples/data/table.txt +37 -0
  23. data/examples/data/tasks.csv +88 -0
  24. data/examples/data/tasks.txt +27 -0
  25. data/examples/data/todo.txt +16 -0
  26. data/examples/data/todocsv.csv +28 -0
  27. data/examples/data/unix1.txt +21 -0
  28. data/examples/data/unix2.txt +11 -0
  29. data/examples/dbdemo.rb +506 -0
  30. data/examples/dirtree.rb +177 -0
  31. data/examples/newtabbedwindow.rb +100 -0
  32. data/examples/newtesttabp.rb +92 -0
  33. data/examples/tabular.rb +212 -0
  34. data/examples/tasks.rb +179 -0
  35. data/examples/term2.rb +88 -0
  36. data/examples/testbuttons.rb +307 -0
  37. data/examples/testcombo.rb +102 -0
  38. data/examples/testdb.rb +182 -0
  39. data/examples/testfields.rb +208 -0
  40. data/examples/testflowlayout.rb +43 -0
  41. data/examples/testkeypress.rb +98 -0
  42. data/examples/testlistbox.rb +187 -0
  43. data/examples/testlistbox1.rb +199 -0
  44. data/examples/testmessagebox.rb +144 -0
  45. data/examples/testprogress.rb +116 -0
  46. data/examples/testree.rb +107 -0
  47. data/examples/testsplitlayout.rb +53 -0
  48. data/examples/testsplitlayout1.rb +49 -0
  49. data/examples/teststacklayout.rb +48 -0
  50. data/examples/testwsshortcuts.rb +68 -0
  51. data/examples/testwsshortcuts2.rb +129 -0
  52. data/lib/canis.rb +16 -0
  53. data/lib/canis/core/docs/index.txt +104 -0
  54. data/lib/canis/core/docs/list.txt +16 -0
  55. data/lib/canis/core/docs/style_help.yml +34 -0
  56. data/lib/canis/core/docs/tabbedpane.txt +15 -0
  57. data/lib/canis/core/docs/table.txt +31 -0
  58. data/lib/canis/core/docs/textpad.txt +48 -0
  59. data/lib/canis/core/docs/tree.txt +23 -0
  60. data/lib/canis/core/include/.DS_Store +0 -0
  61. data/lib/canis/core/include/action.rb +83 -0
  62. data/lib/canis/core/include/actionmanager.rb +49 -0
  63. data/lib/canis/core/include/appmethods.rb +179 -0
  64. data/lib/canis/core/include/bordertitle.rb +49 -0
  65. data/lib/canis/core/include/canisparser.rb +100 -0
  66. data/lib/canis/core/include/colorparser.rb +437 -0
  67. data/lib/canis/core/include/defaultfilerenderer.rb +64 -0
  68. data/lib/canis/core/include/io.rb +320 -0
  69. data/lib/canis/core/include/layouts/SplitLayout.rb +161 -0
  70. data/lib/canis/core/include/layouts/abstractlayout.rb +213 -0
  71. data/lib/canis/core/include/layouts/flowlayout.rb +104 -0
  72. data/lib/canis/core/include/layouts/stacklayout.rb +109 -0
  73. data/lib/canis/core/include/listbindings.rb +89 -0
  74. data/lib/canis/core/include/listeditable.rb +319 -0
  75. data/lib/canis/core/include/listoperations.rb +61 -0
  76. data/lib/canis/core/include/listselectionmodel.rb +388 -0
  77. data/lib/canis/core/include/multibuffer.rb +173 -0
  78. data/lib/canis/core/include/ractionevent.rb +73 -0
  79. data/lib/canis/core/include/rchangeevent.rb +27 -0
  80. data/lib/canis/core/include/rhistory.rb +95 -0
  81. data/lib/canis/core/include/rinputdataevent.rb +47 -0
  82. data/lib/canis/core/include/textdocument.rb +111 -0
  83. data/lib/canis/core/include/vieditable.rb +175 -0
  84. data/lib/canis/core/include/widgetmenu.rb +66 -0
  85. data/lib/canis/core/system/colormap.rb +165 -0
  86. data/lib/canis/core/system/keydefs.rb +32 -0
  87. data/lib/canis/core/system/ncurses.rb +237 -0
  88. data/lib/canis/core/system/panel.rb +129 -0
  89. data/lib/canis/core/system/window.rb +1081 -0
  90. data/lib/canis/core/util/ansiparser.rb +119 -0
  91. data/lib/canis/core/util/app.rb +696 -0
  92. data/lib/canis/core/util/basestack.rb +412 -0
  93. data/lib/canis/core/util/defaultcolorparser.rb +84 -0
  94. data/lib/canis/core/util/extras/README +5 -0
  95. data/lib/canis/core/util/extras/bottomline.rb +1815 -0
  96. data/lib/canis/core/util/extras/padreader.rb +192 -0
  97. data/lib/canis/core/util/focusmanager.rb +31 -0
  98. data/lib/canis/core/util/helpmanager.rb +160 -0
  99. data/lib/canis/core/util/oldwidgetshortcuts.rb +304 -0
  100. data/lib/canis/core/util/promptmenu.rb +235 -0
  101. data/lib/canis/core/util/rcommandwindow.rb +933 -0
  102. data/lib/canis/core/util/rdialogs.rb +520 -0
  103. data/lib/canis/core/util/textutils.rb +74 -0
  104. data/lib/canis/core/util/viewer.rb +238 -0
  105. data/lib/canis/core/util/widgetshortcuts.rb +508 -0
  106. data/lib/canis/core/widgets/applicationheader.rb +103 -0
  107. data/lib/canis/core/widgets/box.rb +58 -0
  108. data/lib/canis/core/widgets/divider.rb +310 -0
  109. data/lib/canis/core/widgets/extras/README.md +12 -0
  110. data/lib/canis/core/widgets/extras/rtextarea.rb +960 -0
  111. data/lib/canis/core/widgets/extras/stackflow.rb +474 -0
  112. data/lib/canis/core/widgets/keylabelprinter.rb +194 -0
  113. data/lib/canis/core/widgets/listbox.rb +326 -0
  114. data/lib/canis/core/widgets/listfooter.rb +86 -0
  115. data/lib/canis/core/widgets/rcombo.rb +210 -0
  116. data/lib/canis/core/widgets/rcontainer.rb +415 -0
  117. data/lib/canis/core/widgets/rlink.rb +30 -0
  118. data/lib/canis/core/widgets/rmenu.rb +970 -0
  119. data/lib/canis/core/widgets/rmenulink.rb +30 -0
  120. data/lib/canis/core/widgets/rmessagebox.rb +400 -0
  121. data/lib/canis/core/widgets/rprogress.rb +118 -0
  122. data/lib/canis/core/widgets/rtabbedpane.rb +631 -0
  123. data/lib/canis/core/widgets/rtabbedwindow.rb +70 -0
  124. data/lib/canis/core/widgets/rwidget.rb +3634 -0
  125. data/lib/canis/core/widgets/scrollbar.rb +147 -0
  126. data/lib/canis/core/widgets/statusline.rb +113 -0
  127. data/lib/canis/core/widgets/table.rb +1072 -0
  128. data/lib/canis/core/widgets/tabular.rb +264 -0
  129. data/lib/canis/core/widgets/textpad.rb +1674 -0
  130. data/lib/canis/core/widgets/tree.rb +690 -0
  131. data/lib/canis/core/widgets/tree/treecellrenderer.rb +150 -0
  132. data/lib/canis/core/widgets/tree/treemodel.rb +432 -0
  133. data/lib/canis/version.rb +3 -0
  134. metadata +229 -0
@@ -0,0 +1,690 @@
1
+ #!/usr/bin/env ruby
2
+ # header {{{
3
+ # vim: set foldlevel=0 foldmethod=marker :
4
+ # ----------------------------------------------------------------------------- #
5
+ # File: tree.rb
6
+ # Description: A tabular widget based on textpad
7
+ # Author: jkepler http://github.com/mare-imbrium/canis/
8
+ # Date: 2014-04-16 13:56
9
+ # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
10
+ # Last update: 2014-07-05 16:09
11
+ # ----------------------------------------------------------------------------- #
12
+ # tree.rb Copyright (C) 2012-2014 kepler
13
+
14
+ # == CHANGES:
15
+ # - changed @content to @list since all multirow wids and utils expect @list
16
+ # - changed name from tablewidget to table
17
+ # - Since parent textpad now uses native_text in a lot of places, and tree does not
18
+ # therefore whenever @list changes we need to update @native_text too. :(
19
+ #
20
+ # == TODO
21
+ # [ ] if no columns, then init_model is called so chash is not cleared.
22
+ # _ compare to tabular_widget and see what's missing
23
+ # _ filtering rows without losing data
24
+ # . selection stuff
25
+ # x test with resultset from sqlite to see if we can use Array or need to make model
26
+ # should we use a datamodel so resultsets can be sent in, what about tabular
27
+ # _ header to handle events ?
28
+ # header }}}
29
+
30
+ require 'logger'
31
+ require 'canis'
32
+ require 'canis/core/widgets/textpad'
33
+
34
+ ##
35
+ # The motivation to create yet another table widget is because tabular_widget
36
+ # is based on textview etc which have a lot of complex processing and rendering
37
+ # whereas textpad is quite simple. It is easy to just add one's own renderer
38
+ # making the code base simpler to understand and maintain.
39
+ #
40
+ #
41
+ module Canis
42
+ # structures {{{
43
+ TreeSelectionEvent = Struct.new(:node, :tree, :state, :previous_node, :row_first)
44
+ # structures }}}
45
+ # renderer {{{
46
+ #
47
+ # TODO see how jtable does the renderers and columns stuff.
48
+ #
49
+ # perhaps we can combine the two but have different methods or some flag
50
+ # that way oter methods can be shared
51
+ class DefaultTreeRenderer < AbstractTextPadRenderer
52
+
53
+ attr_accessor :icon_can_collapse, :icon_can_expand, :icon_not_visited, :icon_no_children
54
+ attr_accessor :row_selected_attr
55
+
56
+ PLUS_PLUS = "++"
57
+ PLUS_MINUS = "+-"
58
+ PLUS_Q = "+?"
59
+ # source is the textpad or extending widget needed so we can call show_colored_chunks
60
+ # if the user specifies column wise colors
61
+ def initialize source
62
+ @source = source
63
+ @color = :white
64
+ @bgcolor = :black
65
+ @color_pair = $datacolor
66
+ @attrib = NORMAL
67
+ @_check_coloring = nil
68
+ @icon_can_collapse = "+-"
69
+ @icon_can_expand = "++"
70
+ @icon_not_visited = "+?"
71
+ @icon_no_children = "+-"
72
+
73
+ # adding setting column_model auto on 2014-04-10 - 10:53 why wasn;t this here already
74
+ #tree_model(source.tree_model)
75
+ end
76
+ # set fg and bg color of content rows, default is $datacolor (white on black).
77
+ def content_colors fg, bg
78
+ @color = fg
79
+ @bgcolor = bg
80
+ @color_pair = get_color($datacolor, fg, bg)
81
+ end
82
+ def content_attrib att
83
+ @attrib = att
84
+ end
85
+ #
86
+ # @param pad for calling print methods on
87
+ # @param lineno the line number on the pad to print on
88
+ # @param text data to print
89
+ def render pad, lineno, treearraynode
90
+ parent = @source
91
+ level = treearraynode.level
92
+ node = treearraynode
93
+ if parent.node_expanded? node
94
+ icon = @icon_can_collapse # can collapse
95
+ else
96
+ icon = @icon_can_expand # can expand
97
+ end
98
+ if node.children.size == 0
99
+ icon = @icon_not_visited # either no children or not visited yet
100
+ if parent.has_been_expanded node
101
+ icon = @icon_no_children # definitely no children, we've visited
102
+ end
103
+ end
104
+ # adding 2 to level, that's the size of icon
105
+ # XXX FIXME if we put the icon here, then when we scroll right, the icon will show, it shoud not
106
+ # FIXME we ignore truncation etc on previous level and take the object as is !!!
107
+ _value = "%*s %s" % [ level+2, icon, node.user_object ]
108
+ len = _value.length
109
+ #graphic.printstring r, c, "%-*s" % [len, _value], @color_pair,@attr
110
+ cp = @color_pair
111
+ att = @attrib
112
+ raise "attrib is nil in tree render 104" unless att
113
+ raise "color pair is nil in tree render 104" unless cp
114
+ # added for selection, but will crash if selection is not extended !!! XXX
115
+ if @source.is_row_selected? lineno
116
+ att = @row_selected_attr || $row_selected_attr
117
+ # FIXME currentl this overflows into next row
118
+ end
119
+
120
+ FFI::NCurses.wattron(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
121
+ FFI::NCurses.mvwaddstr(pad, lineno, 0, _value)
122
+ FFI::NCurses.wattroff(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
123
+
124
+ end
125
+ # check if we need to individually color columns or we can do the entire
126
+ # row in one shot
127
+ end
128
+ # renderer }}}
129
+
130
+ #--
131
+ # If we make a pad of the whole thing then the columns will also go out when scrolling
132
+ # So then there's no point storing columns separately. Might as well keep in content
133
+ # so scrolling works fine, otherwise textpad will have issues scrolling.
134
+ # Making a pad of the content but not column header complicates stuff,
135
+ # do we make a pad of that, or print it like the old thing.
136
+ #++
137
+ # A table widget containing rows and columns and the ability to resize and hide or align
138
+ # columns. Also may have first row as column names.
139
+ #
140
+ # == NOTE
141
+ # The most important methods to use probably are `text()` or `resultset` or `filename` to load
142
+ # data. With `text` you will want to first specify column names with `columns()`.
143
+ #
144
+ # +@current_index+ inherited from +Textpad+ continues to be the index of the list that has user's
145
+ # focus, and should be used for row operations.
146
+ #
147
+ # In order to use Textpad easily, the first row of the table model is the column names. Data is maintained
148
+ # in an Array. Several operations are delegated to Array, or have the same name. You can get the list
149
+ # using `list()` to run other Array operations on it.
150
+ #
151
+ # If you modify the Array directly, you may have to use `fire_row_changed(index)` to reflect the update to
152
+ # a single row. If you delete or add a row, you will have to use `fire_dimension_changed()`. However,
153
+ # internal functions do this automatically.
154
+ #
155
+ #require 'canis/core/include/listselectionmodel'
156
+ require 'canis/core/widgets/tree/treemodel'
157
+ require 'canis/core/include/listoperations'
158
+
159
+ class Tree < TextPad
160
+
161
+ include Canis::ListOperations
162
+
163
+ dsl_accessor :print_footer
164
+ attr_reader :treemodel # returns treemodel for further actions 2011-10-2
165
+ dsl_accessor :default_value # node to show as selected - what if user doesn't have it?
166
+
167
+ def initialize form = nil, config={}, &block
168
+
169
+ @_header_adjustment = 0 #1
170
+ @col_min_width = 3
171
+
172
+ @expanded_state = {}
173
+ register_events([:ENTER_ROW, :LEAVE_ROW, :TREE_COLLAPSED_EVENT, :TREE_EXPANDED_EVENT, :TREE_SELECTION_EVENT, :TREE_WILL_COLLAPSE_EVENT, :TREE_WILL_EXPAND_EVENT])
174
+ super
175
+ #@_events.push(*[:ENTER_ROW, :LEAVE_ROW, :TREE_COLLAPSED_EVENT, :TREE_EXPANDED_EVENT, :TREE_SELECTION_EVENT, :TREE_WILL_COLLAPSE_EVENT, :TREE_WILL_EXPAND_EVENT])
176
+ create_default_renderer unless @renderer # 2014-04-10 - 11:01
177
+ init_vars
178
+ #set_default_selection_model unless @list_selection_model
179
+ end
180
+
181
+ # set the default selection model as the operational one
182
+ def set_default_selection_model
183
+ @list_selection_model = nil
184
+ @list_selection_model = Canis::DefaultListSelectionModel.new self
185
+ end
186
+ def create_default_renderer
187
+ renderer( DefaultTreeRenderer.new(self) )
188
+ end
189
+ def init_vars
190
+ # show_selector and symbol etc unused
191
+ if @show_selector
192
+ @row_selected_symbol ||= '>'
193
+ @row_unselected_symbol ||= ' '
194
+ @left_margin ||= @row_selected_symbol.length
195
+ end
196
+ @left_margin ||= 0
197
+ #@one_key_selection = true if @one_key_selection.nil?
198
+ @row_offset = @col_offset = 0 if @suppress_borders
199
+ @internal_width = 2 # taking into account borders accounting for 2 cols
200
+ @internal_width = 0 if @suppress_borders # should it be 0 ???
201
+ super
202
+
203
+ end
204
+ # maps keys to methods
205
+ # checks @key_map can be :emacs or :vim.
206
+ def map_keys
207
+ super
208
+ @keys_mapped = true
209
+ bind_key($row_selector, 'toggle row selection'){ toggle_row_selection() }
210
+ bind_key(KEY_ENTER, 'toggle expanded state') { toggle_expanded_state() }
211
+ bind_key(?o, 'toggle expanded state') { toggle_expanded_state() }
212
+ bind_key(?f, 'first row starting with char'){ set_selection_for_char() }
213
+ #bind_key(?\M-v, 'one key selection toggle'){ @one_key_selection = !@one_key_selection }
214
+ bind_key(?O, 'expand children'){ expand_children() }
215
+ bind_key(?X, 'collapse children'){ collapse_children() }
216
+ bind_key(?>, :scroll_right)
217
+ bind_key(?<, :scroll_left)
218
+ # TODO
219
+ bind_key(?x, 'collapse parent'){ collapse_parent() }
220
+ bind_key(?p, 'goto parent'){ goto_parent() }
221
+ # # commented since textpad is now calling this
222
+ # this can be brought back into include and used from other textpad too.
223
+ #require 'canis/core/include/listbindings'
224
+ #bindings
225
+ end
226
+ # Returns root if no argument given.
227
+ # Now we return root if already set
228
+ # Made node nillable so we can return root.
229
+ #
230
+ # @raise ArgumentError if setting a root after its set
231
+ # or passing nil if its not been set.
232
+ def root node=nil, asks_allow_children=false, &block
233
+ if @treemodel
234
+ return @treemodel.root unless node
235
+ raise ArgumentError, "Root already set"
236
+ end
237
+
238
+ raise ArgumentError, "root: node cannot be nil" unless node
239
+ @treemodel = Canis::DefaultTreeModel.new(node, asks_allow_children, &block)
240
+ end
241
+
242
+ # pass data to create this tree model
243
+ # used to be list
244
+ def data alist=nil
245
+
246
+ # if nothing passed, print an empty root, rather than crashing
247
+ alist = [] if alist.nil?
248
+ @data = alist # data given by user
249
+ case alist
250
+ when Array
251
+ @treemodel = Canis::DefaultTreeModel.new("/")
252
+ @treemodel.root.add alist
253
+ when Hash
254
+ @treemodel = Canis::DefaultTreeModel.new("/")
255
+ @treemodel.root.add alist
256
+ when TreeNode
257
+ # this is a root node
258
+ @treemodel = Canis::DefaultTreeModel.new(alist)
259
+ when DefaultTreeModel
260
+ @treemodel = alist
261
+ else
262
+ if alist.is_a? DefaultTreeModel
263
+ @treemodel = alist
264
+ else
265
+ raise ArgumentError, "Tree does not know how to handle #{alist.class} "
266
+ end
267
+ end
268
+ # we now have a tree
269
+ raise "I still don't have a tree" unless @treemodel
270
+ set_expanded_state(@treemodel.root, true)
271
+ convert_to_list @treemodel
272
+
273
+ # added on 2009-01-13 23:19 since updates are not automatic now
274
+ #@list.bind(:LIST_DATA_EVENT) { |e| list_data_changed() }
275
+ #create_default_list_selection_model TODO
276
+ fire_dimension_changed
277
+ self
278
+ end
279
+ # private, for use by repaint
280
+ def _list
281
+ if @_structure_changed
282
+ @list = nil
283
+ @_structure_changed = false
284
+ end
285
+ unless @list
286
+ $log.debug " XXX recreating _list"
287
+ convert_to_list @treemodel
288
+ #$log.debug " XXXX list: #{@list.size} : #{@list} "
289
+ $log.debug " XXXX list: #{@list.size} "
290
+ end
291
+ return @list
292
+ end
293
+ # repaint whenever a change heppens
294
+ # 2014-04-16 - 22:31 - I need to put a call to _list somewhere whenever a change happens
295
+ # (i.e. recreate list from the tree model object)..
296
+ def repaint
297
+ # we need to see if structure changed then regenerate @list
298
+ _list()
299
+ super
300
+ end
301
+ def convert_to_list tree
302
+ @list = @native_text = get_expanded_descendants(tree.root)
303
+ #$log.debug "XXX convert #{tree.root.children.size} "
304
+ #$log.debug " converted tree to list. #{@list.size} "
305
+ end
306
+ def traverse node, level=0, &block
307
+ raise "disuse"
308
+ #icon = node.is_leaf? ? "-" : "+"
309
+ #puts "%*s %s" % [ level+1, icon, node.user_object ]
310
+ yield node, level if block_given?
311
+ node.children.each do |e|
312
+ traverse e, level+1, &block
313
+ end
314
+ end
315
+ # return object under cursor
316
+ # Note: this should not be confused with selected row/s. User may not have selected this.
317
+ # This is only useful since in some demos we like to change a status bar as a user scrolls down
318
+ # @since 1.2.0 2010-09-06 14:33 making life easier for others.
319
+ def current_row
320
+ @list[@current_index]
321
+ end
322
+ # commendte off on 2014-05-27 - 14:27
323
+ #alias :text :current_row # thanks to shoes, not sure how this will impact since widget has text.
324
+ alias :current_value :current_row # thanks to shoes, not sure how this will impact since widget has text.
325
+
326
+ # show default value as selected and fire handler for it
327
+ # This is called in repaint, so can raise an error if called on creation
328
+ # or before repaint. Just set @default_value, and let us handle the rest.
329
+ # Suggestions are welcome.
330
+ def select_default_values
331
+ return if @default_value.nil?
332
+ # NOTE list not yet created
333
+ raise "list has not yet been created" unless @list
334
+ index = node_to_row @default_value
335
+ raise "could not find node #{@default_value}, #{@list} " unless index
336
+ return unless index
337
+ @current_index = index
338
+ toggle_row_selection
339
+ @default_value = nil
340
+ end
341
+ ### START FOR scrollable ###
342
+ def get_content
343
+ #@list 2008-12-01 23:13
344
+ #@list_variable && @list_variable.value || @list
345
+ # called by next_match in listscrollable
346
+ @list
347
+ end
348
+ def get_window
349
+ @graphic
350
+ end
351
+ ### END FOR scrollable ###
352
+ # override widgets text
353
+ def getvalue
354
+ selected_row()
355
+ end
356
+
357
+ # supply a custom renderer that implements +render()+
358
+ # @see render
359
+ def renderer *val
360
+ if val.empty?
361
+ return @renderer
362
+ end
363
+ @renderer = val[0]
364
+ end
365
+
366
+
367
+ #------- data modification methods ------#
368
+
369
+
370
+ #
371
+
372
+ ## add a row to the table
373
+ # The name add will be removed soon, pls use << instead.
374
+ def <<( array)
375
+ unless @list
376
+ # columns were not added, this most likely is the title
377
+ @list ||= []
378
+ _init_model array
379
+ end
380
+ @list << array
381
+ @native_text = @list # 2014-05-27 - 16:34
382
+ fire_dimension_changed
383
+ self
384
+ end
385
+
386
+ # delete a data row at index
387
+ #
388
+ # NOTE : This does not adjust for header_adjustment. So zero will refer to the header if there is one.
389
+ # This is to keep consistent with textpad which does not know of header_adjustment and uses the actual
390
+ # index. Usually, programmers will be dealing with +@current_index+
391
+ #
392
+ def delete_at ix
393
+ return unless @list
394
+ raise ArgumentError, "Argument must be within 0 and #{@list.length}" if ix < 0 or ix >= @list.length
395
+ fire_dimension_changed
396
+ #@list.delete_at(ix + @_header_adjustment)
397
+ _ret = @list.delete_at(ix)
398
+ @native_text = @list # 2014-05-27 - 16:34
399
+ return _ret
400
+ end
401
+
402
+ ##
403
+ # refresh pad onto window
404
+ # overrides super due to header_adjustment and the header too
405
+ def XXXpadrefresh
406
+ top = @window.top
407
+ left = @window.left
408
+ sr = @startrow + top
409
+ sc = @startcol + left
410
+ # first do header always in first row
411
+ retval = FFI::NCurses.prefresh(@pad,0,@pcol, sr , sc , 2 , @cols+ sc );
412
+ # now print rest of data
413
+ # h is header_adjustment
414
+ h = 1
415
+ retval = FFI::NCurses.prefresh(@pad,@prow + h,@pcol, sr + h , sc , @rows + sr , @cols+ sc );
416
+ $log.warn "XXX: PADREFRESH #{retval}, #{@prow}, #{@pcol}, #{sr}, #{sc}, #{@rows+sr}, #{@cols+sc}." if retval == -1
417
+ # padrefresh can fail if width is greater than NCurses.COLS
418
+ end
419
+
420
+ # print footer containing line and total, overriding textpad which prints column offset also
421
+ # This is called internally by +repaint()+ but can be overridden for more complex printing.
422
+ def print_foot
423
+ return unless @print_footer
424
+ ha = @_header_adjustment
425
+ # ha takes into account whether there are headers or not
426
+ footer = "#{@current_index+1-ha} of #{@list.length-ha} "
427
+ @graphic.printstring( @row + @height -1 , @col+2, footer, @color_pair || $datacolor, @footer_attrib)
428
+ @repaint_footer_required = false
429
+ end
430
+ def is_row_selected? row=@current_index
431
+ row == @selected_index
432
+ end
433
+ def selected_row
434
+ @list[@selected_index].node
435
+ end
436
+
437
+ # An event is thrown when a row is selected or deselected.
438
+ # Please note that when a row is selected, another one is automatically deselected.
439
+ # An event is not thrown for that since your may not want to collapse that.
440
+ # Only clicking on a selected row, will send a DESELECT on it since you may want to collapse it.
441
+ # However, the previous selection is also present in the event object, so you can act upon it.
442
+ # This is not used for expanding or collapsing, only for application to show some data in another
443
+ # window or pane based on selection. Maybe there should not be a deselect for current row ?
444
+ def toggle_row_selection
445
+ node = @list[@current_index]
446
+ previous_node = nil
447
+ previous_node = @list[@selected_index] if @selected_index
448
+ previous_index = nil
449
+ if @selected_index == @current_index
450
+ @selected_index = nil
451
+ previous_index = @current_index
452
+ else
453
+ previous_index = @selected_index
454
+ @selected_index = @current_index
455
+ end
456
+ state = @selected_index.nil? ? :DESELECTED : :SELECTED
457
+ #TreeSelectionEvent = Struct.new(:node, :tree, :state, :previous_node, :row_first)
458
+ @tree_selection_event = TreeSelectionEvent.new(node, self, state, previous_node, @current_index) #if @item_event.nil?
459
+ fire_handler :TREE_SELECTION_EVENT, @tree_selection_event # should the event itself be ITEM_EVENT
460
+ $log.debug " XXX tree selected #{@selected_index}/ #{@current_index} , #{state} "
461
+ fire_row_changed @current_index if @current_index
462
+ fire_row_changed previous_index if previous_index
463
+ @repaint_required = true
464
+ end
465
+ def toggle_expanded_state row=@current_index
466
+ state = row_expanded? row
467
+ node = row_to_node
468
+ if node.nil?
469
+ Ncurses.beep
470
+ $log.debug " No such node on row #{row} "
471
+ return
472
+ end
473
+ $log.debug " toggle XXX state #{state} #{node} "
474
+ if state
475
+ collapse_node node
476
+ else
477
+ expand_node node
478
+ end
479
+ end
480
+ def row_to_node row=@current_index
481
+ @list[row]
482
+ end
483
+ # convert a given node to row
484
+ def node_to_row node
485
+ crow = nil
486
+ @list.each_with_index { |e,i|
487
+ if e == node
488
+ crow = i
489
+ break
490
+ end
491
+ }
492
+ crow
493
+ end
494
+ # private
495
+ # related to index in representation, not tree
496
+ def row_selected? row
497
+ @selected_index == row
498
+ end
499
+ # @return [TreeNode, nil] returns selected node or nil
500
+
501
+ def row_expanded? row
502
+ node = @list[row]
503
+ node_expanded? node
504
+ end
505
+ def row_collapsed? row
506
+ !row_expanded? row
507
+ end
508
+ def set_expanded_state(node, state)
509
+ @expanded_state[node] = state
510
+ @repaint_required = true
511
+ _structure_changed true
512
+ end
513
+ def expand_node(node)
514
+ #$log.debug " expand called on #{node.user_object} "
515
+ state = true
516
+ fire_handler :TREE_WILL_EXPAND_EVENT, node
517
+ set_expanded_state(node, state)
518
+ fire_handler :TREE_EXPANDED_EVENT, node
519
+ end
520
+ def collapse_node(node)
521
+ $log.debug " collapse called on #{node.user_object} "
522
+ state = false
523
+ fire_handler :TREE_WILL_COLLAPSE_EVENT, node
524
+ set_expanded_state(node, state)
525
+ fire_handler :TREE_COLLAPSED_EVENT, node
526
+ end
527
+ # this is required to make a node visible, if you wish to start from a node that is not root
528
+ # e.g. you are loading app in a dir somewhere but want to show path from root down.
529
+ # NOTE this sucks since you have to click 2 times to expand it.
530
+ def mark_parents_expanded node
531
+ # i am setting parents as expanded, but NOT firing handlers - XXX separate this into expand_parents
532
+ _path = node.tree_path
533
+ _path.each do |e|
534
+ # if already expanded parent then break we should break
535
+ set_expanded_state(e, true)
536
+ end
537
+ end
538
+ # goes up to root of this node, and expands down to this node
539
+ # this is often required to make a specific node visible such
540
+ # as in a dir listing when current dir is deep in heirarchy.
541
+ def expand_parents node
542
+ _path = node.tree_path
543
+ _path.each do |e|
544
+ # if already expanded parent then break we should break
545
+ #set_expanded_state(e, true)
546
+ expand_node(e)
547
+ end
548
+ end
549
+ # this expands all the children of a node, recursively
550
+ # we can't use multiplier concept here since we are doing a preorder enumeration
551
+ # we need to do a breadth first enumeration to use a multiplier
552
+ #
553
+ def expand_children node=:current_index
554
+ $multiplier = 999 if !$multiplier || $multiplier == 0
555
+ node = row_to_node if node == :current_index
556
+ return if node.children.empty? # or node.is_leaf?
557
+ #node.children.each do |e|
558
+ #expand_node e # this will keep expanding parents
559
+ #expand_children e
560
+ #end
561
+ node.breadth_each($multiplier) do |e|
562
+ expand_node e
563
+ end
564
+ $multiplier = 0
565
+ _structure_changed true
566
+ end
567
+ def collapse_children node=:current_index
568
+ $multiplier = 999 if !$multiplier || $multiplier == 0
569
+ $log.debug " CCCC IINSIDE COLLLAPSE"
570
+ node = row_to_node if node == :current_index
571
+ return if node.children.empty? # or node.is_leaf?
572
+ #node.children.each do |e|
573
+ #expand_node e # this will keep expanding parents
574
+ #expand_children e
575
+ #end
576
+ node.breadth_each($multiplier) do |e|
577
+ $log.debug "CCC collapsing #{e.user_object} "
578
+ collapse_node e
579
+ end
580
+ $multiplier = 0
581
+ _structure_changed true
582
+ end
583
+ # collapse parent
584
+ # can use multiplier.
585
+ # # we need to move up also
586
+ def collapse_parent node=:current_index
587
+ node = row_to_node if node == :current_index
588
+ parent = node.parent
589
+ return if parent.nil?
590
+ goto_parent node
591
+ collapse_node parent
592
+ end
593
+ def goto_parent node=:current_index
594
+ node = row_to_node if node == :current_index
595
+ parent = node.parent
596
+ return if parent.nil?
597
+ crow = @current_index
598
+ @list.each_with_index { |e,i|
599
+ if e == parent
600
+ crow = i
601
+ break
602
+ end
603
+ }
604
+ @repaint_required = true
605
+ #set_form_row # will not work if off form
606
+ #set_focus_on crow
607
+ goto_line crow
608
+ end
609
+
610
+ def has_been_expanded node
611
+ @expanded_state.has_key? node
612
+ end
613
+ def node_expanded? node
614
+ @expanded_state[node] == true
615
+ end
616
+ def node_collapsed? node
617
+ !node_expanded?(node)
618
+ end
619
+ def get_expanded_descendants(node)
620
+ nodes = []
621
+ # 2014-07-04 - 11:55 trying out making the root invisible, we don't insert it into the list
622
+ if @treemodel.root_visible
623
+ nodes << node
624
+ end
625
+ traverse_expanded node, nodes
626
+ $log.debug " def get_expanded_descendants(node) #{nodes.size} "
627
+ return nodes
628
+ end
629
+ def traverse_expanded node, nodes
630
+ return if !node_expanded? node
631
+ #nodes << node
632
+ node.children.each do |e|
633
+ nodes << e
634
+ if node_expanded? e
635
+ traverse_expanded e, nodes
636
+ else
637
+ next
638
+ end
639
+ end
640
+ end
641
+
642
+ #
643
+ # To retrieve the node corresponding to a path specified as an array or string
644
+ # Do not mention the root.
645
+ # e.g. "ruby/1.9.2/io/console"
646
+ # or %w[ ruby 1.9.3 io console ]
647
+ # @since 1.4.0 2011-10-2
648
+ def get_node_for_path(user_path)
649
+ case user_path
650
+ when String
651
+ user_path = user_path.split "/"
652
+ when Array
653
+ else
654
+ raise ArgumentError, "Should be Array or String delimited with /"
655
+ end
656
+ $log.debug "TREE #{user_path} " if $log.debug?
657
+ root = @treemodel.root
658
+ found = nil
659
+ user_path.each { |e|
660
+ success = false
661
+ root.children.each { |c|
662
+ if c.user_object == e
663
+ found = c
664
+ success = true
665
+ root = c
666
+ break
667
+ end
668
+ }
669
+ return false unless success
670
+
671
+ }
672
+ return found
673
+ end
674
+ # default block
675
+ # @since 1.5.0 2011-11-22
676
+ def command *args, &block
677
+ bind :TREE_WILL_EXPAND_EVENT, *args, &block
678
+ end
679
+ private
680
+ # please do not rely on this yet, name could change
681
+ def _structure_changed tf=true
682
+ @_structure_changed = tf
683
+ @repaint_required = true
684
+ fire_dimension_changed
685
+ #@list = nil
686
+ end
687
+
688
+ end # class Tree
689
+
690
+ end # module