canis 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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