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.
- checksums.yaml +7 -0
- data/.gitignore +45 -0
- data/CHANGES +52 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +24 -0
- data/Rakefile +2 -0
- data/canis.gemspec +25 -0
- data/examples/alpmenu.rb +46 -0
- data/examples/app.sample +19 -0
- data/examples/appemail.rb +191 -0
- data/examples/atree.rb +105 -0
- data/examples/bline.rb +181 -0
- data/examples/common/devel.rb +319 -0
- data/examples/common/file.rb +93 -0
- data/examples/data/README.markdown +9 -0
- data/examples/data/brew.txt +38 -0
- data/examples/data/color.2 +37 -0
- data/examples/data/gemlist.txt +59 -0
- data/examples/data/lotr.txt +12 -0
- data/examples/data/ports.txt +136 -0
- data/examples/data/table.txt +37 -0
- data/examples/data/tasks.csv +88 -0
- data/examples/data/tasks.txt +27 -0
- data/examples/data/todo.txt +16 -0
- data/examples/data/todocsv.csv +28 -0
- data/examples/data/unix1.txt +21 -0
- data/examples/data/unix2.txt +11 -0
- data/examples/dbdemo.rb +506 -0
- data/examples/dirtree.rb +177 -0
- data/examples/newtabbedwindow.rb +100 -0
- data/examples/newtesttabp.rb +92 -0
- data/examples/tabular.rb +212 -0
- data/examples/tasks.rb +179 -0
- data/examples/term2.rb +88 -0
- data/examples/testbuttons.rb +307 -0
- data/examples/testcombo.rb +102 -0
- data/examples/testdb.rb +182 -0
- data/examples/testfields.rb +208 -0
- data/examples/testflowlayout.rb +43 -0
- data/examples/testkeypress.rb +98 -0
- data/examples/testlistbox.rb +187 -0
- data/examples/testlistbox1.rb +199 -0
- data/examples/testmessagebox.rb +144 -0
- data/examples/testprogress.rb +116 -0
- data/examples/testree.rb +107 -0
- data/examples/testsplitlayout.rb +53 -0
- data/examples/testsplitlayout1.rb +49 -0
- data/examples/teststacklayout.rb +48 -0
- data/examples/testwsshortcuts.rb +68 -0
- data/examples/testwsshortcuts2.rb +129 -0
- data/lib/canis.rb +16 -0
- data/lib/canis/core/docs/index.txt +104 -0
- data/lib/canis/core/docs/list.txt +16 -0
- data/lib/canis/core/docs/style_help.yml +34 -0
- data/lib/canis/core/docs/tabbedpane.txt +15 -0
- data/lib/canis/core/docs/table.txt +31 -0
- data/lib/canis/core/docs/textpad.txt +48 -0
- data/lib/canis/core/docs/tree.txt +23 -0
- data/lib/canis/core/include/.DS_Store +0 -0
- data/lib/canis/core/include/action.rb +83 -0
- data/lib/canis/core/include/actionmanager.rb +49 -0
- data/lib/canis/core/include/appmethods.rb +179 -0
- data/lib/canis/core/include/bordertitle.rb +49 -0
- data/lib/canis/core/include/canisparser.rb +100 -0
- data/lib/canis/core/include/colorparser.rb +437 -0
- data/lib/canis/core/include/defaultfilerenderer.rb +64 -0
- data/lib/canis/core/include/io.rb +320 -0
- data/lib/canis/core/include/layouts/SplitLayout.rb +161 -0
- data/lib/canis/core/include/layouts/abstractlayout.rb +213 -0
- data/lib/canis/core/include/layouts/flowlayout.rb +104 -0
- data/lib/canis/core/include/layouts/stacklayout.rb +109 -0
- data/lib/canis/core/include/listbindings.rb +89 -0
- data/lib/canis/core/include/listeditable.rb +319 -0
- data/lib/canis/core/include/listoperations.rb +61 -0
- data/lib/canis/core/include/listselectionmodel.rb +388 -0
- data/lib/canis/core/include/multibuffer.rb +173 -0
- data/lib/canis/core/include/ractionevent.rb +73 -0
- data/lib/canis/core/include/rchangeevent.rb +27 -0
- data/lib/canis/core/include/rhistory.rb +95 -0
- data/lib/canis/core/include/rinputdataevent.rb +47 -0
- data/lib/canis/core/include/textdocument.rb +111 -0
- data/lib/canis/core/include/vieditable.rb +175 -0
- data/lib/canis/core/include/widgetmenu.rb +66 -0
- data/lib/canis/core/system/colormap.rb +165 -0
- data/lib/canis/core/system/keydefs.rb +32 -0
- data/lib/canis/core/system/ncurses.rb +237 -0
- data/lib/canis/core/system/panel.rb +129 -0
- data/lib/canis/core/system/window.rb +1081 -0
- data/lib/canis/core/util/ansiparser.rb +119 -0
- data/lib/canis/core/util/app.rb +696 -0
- data/lib/canis/core/util/basestack.rb +412 -0
- data/lib/canis/core/util/defaultcolorparser.rb +84 -0
- data/lib/canis/core/util/extras/README +5 -0
- data/lib/canis/core/util/extras/bottomline.rb +1815 -0
- data/lib/canis/core/util/extras/padreader.rb +192 -0
- data/lib/canis/core/util/focusmanager.rb +31 -0
- data/lib/canis/core/util/helpmanager.rb +160 -0
- data/lib/canis/core/util/oldwidgetshortcuts.rb +304 -0
- data/lib/canis/core/util/promptmenu.rb +235 -0
- data/lib/canis/core/util/rcommandwindow.rb +933 -0
- data/lib/canis/core/util/rdialogs.rb +520 -0
- data/lib/canis/core/util/textutils.rb +74 -0
- data/lib/canis/core/util/viewer.rb +238 -0
- data/lib/canis/core/util/widgetshortcuts.rb +508 -0
- data/lib/canis/core/widgets/applicationheader.rb +103 -0
- data/lib/canis/core/widgets/box.rb +58 -0
- data/lib/canis/core/widgets/divider.rb +310 -0
- data/lib/canis/core/widgets/extras/README.md +12 -0
- data/lib/canis/core/widgets/extras/rtextarea.rb +960 -0
- data/lib/canis/core/widgets/extras/stackflow.rb +474 -0
- data/lib/canis/core/widgets/keylabelprinter.rb +194 -0
- data/lib/canis/core/widgets/listbox.rb +326 -0
- data/lib/canis/core/widgets/listfooter.rb +86 -0
- data/lib/canis/core/widgets/rcombo.rb +210 -0
- data/lib/canis/core/widgets/rcontainer.rb +415 -0
- data/lib/canis/core/widgets/rlink.rb +30 -0
- data/lib/canis/core/widgets/rmenu.rb +970 -0
- data/lib/canis/core/widgets/rmenulink.rb +30 -0
- data/lib/canis/core/widgets/rmessagebox.rb +400 -0
- data/lib/canis/core/widgets/rprogress.rb +118 -0
- data/lib/canis/core/widgets/rtabbedpane.rb +631 -0
- data/lib/canis/core/widgets/rtabbedwindow.rb +70 -0
- data/lib/canis/core/widgets/rwidget.rb +3634 -0
- data/lib/canis/core/widgets/scrollbar.rb +147 -0
- data/lib/canis/core/widgets/statusline.rb +113 -0
- data/lib/canis/core/widgets/table.rb +1072 -0
- data/lib/canis/core/widgets/tabular.rb +264 -0
- data/lib/canis/core/widgets/textpad.rb +1674 -0
- data/lib/canis/core/widgets/tree.rb +690 -0
- data/lib/canis/core/widgets/tree/treecellrenderer.rb +150 -0
- data/lib/canis/core/widgets/tree/treemodel.rb +432 -0
- data/lib/canis/version.rb +3 -0
- 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
|