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