canis 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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,319 @@
|
|
1
|
+
# Some methods for manipulating lists
|
2
|
+
# Different components may bind different keys to these
|
3
|
+
# Currently will be called by TextArea and the editable version
|
4
|
+
# of TextView (vieditable).
|
5
|
+
#
|
6
|
+
require 'canis/core/include/rinputdataevent'
|
7
|
+
module ListEditable
|
8
|
+
|
9
|
+
def remove_all
|
10
|
+
# don't create a new object, other dependents like selection model may suffer 2014-04-08 - 20:00
|
11
|
+
#@list = []
|
12
|
+
@list.clear
|
13
|
+
set_modified # added 2009-02-13 22:28 so repaints
|
14
|
+
end
|
15
|
+
# current behav is a mix of vim's D and C-k from alpine, i don;t know how i screwed it up like this
|
16
|
+
# Should be:
|
17
|
+
# 1. do not take cursor back by 1 (this is vims D behavior)
|
18
|
+
# 2. retain EOL, we need to evaluate at undo
|
19
|
+
# 3. if nothing coming in delete buffer then join next line here
|
20
|
+
# 4. if line is blank, it will go to delete line (i think).
|
21
|
+
# Earlier, a C-k at pos 0 would blank the line and not delete it (copied from alpine).
|
22
|
+
# The next C-k would delete. emacs deletes if C-k at pos 0.
|
23
|
+
def delete_eol
|
24
|
+
return -1 unless @editable
|
25
|
+
pos = @curpos -1 # retain from 0 till prev char
|
26
|
+
@delete_buffer = @buffer[@curpos..-1]
|
27
|
+
# currently eol is there in delete_buff often. Should i maintain it ? 2010-03-08 18:29 UNDO
|
28
|
+
#@delete_buffer.chomp! # new 2010-03-08 18:29 UNDO - this worked but hope does not have othe impact
|
29
|
+
|
30
|
+
# if pos is 0, pos-1 becomes -1, end of line!
|
31
|
+
@list[@current_index] = pos == -1 ? "" : @buffer[0..pos]
|
32
|
+
$log.debug "delete EOL :pos=#{pos}, #{@delete_buffer}: row: #{@list[@current_index]}:"
|
33
|
+
@buffer = @list[@current_index]
|
34
|
+
if @delete_buffer == ""
|
35
|
+
$log.debug " TA: DELETE going to join next "
|
36
|
+
join_next_line # pull next line in
|
37
|
+
end
|
38
|
+
oldcur = @curpos
|
39
|
+
#x cursor_backward if @curpos > 0 # this was vims behavior -- knoecked off
|
40
|
+
#fire_handler :CHANGE, self # 2008-12-09 14:56
|
41
|
+
fire_handler :CHANGE, InputDataEvent.new(oldcur,oldcur+@delete_buffer.length, self, :DELETE, @current_index, @delete_buffer) # 2008-12-24 18:34
|
42
|
+
set_modified
|
43
|
+
return @delete_buffer
|
44
|
+
end
|
45
|
+
def join_next_line
|
46
|
+
# return if last line TODO
|
47
|
+
buff = @list.delete_at(@current_index + 1)
|
48
|
+
if buff
|
49
|
+
$log.debug " TA: DELETE inside to join next #{buff} "
|
50
|
+
fire_handler :CHANGE, InputDataEvent.new(0,0+buff.length, self, :DELETE_LINE, @current_index+1, buff)
|
51
|
+
@buffer << buff
|
52
|
+
end
|
53
|
+
end
|
54
|
+
# deletes given line or current
|
55
|
+
# now fires DELETE_LINE so no guessing by undo manager
|
56
|
+
def delete_line line=@current_index
|
57
|
+
return -1 unless @editable
|
58
|
+
if !$multiplier || $multiplier == 0
|
59
|
+
@delete_buffer = @list.delete_at line
|
60
|
+
else
|
61
|
+
@delete_buffer = @list.slice!(line, $multiplier)
|
62
|
+
end
|
63
|
+
@curpos ||= 0 # rlist has no such var
|
64
|
+
$multiplier = 0
|
65
|
+
add_to_kill_ring @delete_buffer
|
66
|
+
@buffer = @list[@current_index]
|
67
|
+
if @buffer.nil?
|
68
|
+
up
|
69
|
+
setrowcol @row + 1, nil # @form.col
|
70
|
+
end
|
71
|
+
# warning: delete buffer can now be an array
|
72
|
+
fire_handler :CHANGE, InputDataEvent.new(@curpos,@curpos+@delete_buffer.length, self, :DELETE_LINE, line, @delete_buffer) # 2008-12-24 18:34
|
73
|
+
set_modified
|
74
|
+
# next line being called from textarea which is old style and thus bombs
|
75
|
+
fire_dimension_changed if respond_to? :fire_dimension_changed
|
76
|
+
end
|
77
|
+
def delete_curr_char num=($multiplier == 0 ? 1 : $multiplier)
|
78
|
+
return -1 unless @editable
|
79
|
+
delete_at @curpos, num # changed so only one event, and one undo
|
80
|
+
set_modified
|
81
|
+
$multiplier = 0
|
82
|
+
end
|
83
|
+
#
|
84
|
+
# 2010-03-08 23:30 does not seem to be working well when backspacing at first char of line
|
85
|
+
# FIXME should work as a unit, so one undo and one fire_handler, at least if on one line.
|
86
|
+
def delete_prev_char num=($multiplier == 0 ? 1 : $multiplier)
|
87
|
+
return -1 if !@editable
|
88
|
+
num.times do
|
89
|
+
if @curpos <= 0
|
90
|
+
join_to_prev_line
|
91
|
+
return
|
92
|
+
end
|
93
|
+
@curpos -= 1 if @curpos > 0
|
94
|
+
delete_at
|
95
|
+
set_modified
|
96
|
+
addcol -1
|
97
|
+
end
|
98
|
+
$multiplier = 0
|
99
|
+
end
|
100
|
+
# open a new line and add chars to it.
|
101
|
+
# FIXME does not fire handler, thus won't undo
|
102
|
+
def append_row lineno=@current_index, chars=""
|
103
|
+
$log.debug "append row sapce:#{chars}."
|
104
|
+
@list.insert lineno+1, chars
|
105
|
+
end
|
106
|
+
##
|
107
|
+
# delete character/s on current line
|
108
|
+
def delete_at index=@curpos, howmany=1
|
109
|
+
return -1 if !@editable
|
110
|
+
$log.debug "delete_at (characters) : #{@current_index} #{@buffer} #{index}"
|
111
|
+
char = @buffer.slice!(@curpos,howmany) # changed added ,1 and take char for event
|
112
|
+
# if no newline at end of this then bring up prev character/s till maxlen
|
113
|
+
# NO WE DON'T DO THIS ANYLONGER 2008-12-26 21:09 lets see
|
114
|
+
=begin
|
115
|
+
if @buffer[-1,1]!="\r"
|
116
|
+
@buffer[-1]=" " if @buffer[-1,1]=="\n"
|
117
|
+
if !next_line.nil? and next_line.length > 0
|
118
|
+
move_chars_up
|
119
|
+
end
|
120
|
+
end
|
121
|
+
=end
|
122
|
+
set_modified true
|
123
|
+
fire_handler :CHANGE, InputDataEvent.new(@curpos,@curpos+howmany, self, :DELETE, @current_index, char) # 2008-12-24 18:34
|
124
|
+
end
|
125
|
+
def undo_handler(uh)
|
126
|
+
@undo_handler = uh
|
127
|
+
end
|
128
|
+
## THIS ONE SHOULD BE IN TEXTVIEW ALSO
|
129
|
+
# saves current or n lines into kill ring, appending to earlier contents
|
130
|
+
# Use yank (paste) or yank-pop to retrieve
|
131
|
+
def kill_ring_save
|
132
|
+
pointer = @current_index
|
133
|
+
list = []
|
134
|
+
repeatm {
|
135
|
+
line = @list[pointer]
|
136
|
+
list << line unless line.nil?
|
137
|
+
pointer += 1
|
138
|
+
}
|
139
|
+
add_to_kill_ring list
|
140
|
+
end
|
141
|
+
## THIS ONE SHOULD BE IN TEXTVIEW ALSO
|
142
|
+
# add given line or lines to kill_ring
|
143
|
+
def add_to_kill_ring list
|
144
|
+
# directly referenceing kill_ring. We need to OO it a bit, so we can change internals w'o breaking all.
|
145
|
+
# FIXME
|
146
|
+
if $append_next_kill
|
147
|
+
# user requested this kill to be appened to last kill, so it can be yanked as one
|
148
|
+
#$kill_ring.last << list
|
149
|
+
last = $kill_ring.pop
|
150
|
+
$log.debug "YANK: addto : last= #{last} , list= #{list} "
|
151
|
+
case list
|
152
|
+
when Array
|
153
|
+
#list.insert 0, last
|
154
|
+
list.insert 0, *last # 2011-10-10 changed as it was wrong in textarea
|
155
|
+
$kill_ring << list
|
156
|
+
when String
|
157
|
+
$kill_ring << [last, list]
|
158
|
+
end
|
159
|
+
else
|
160
|
+
$kill_ring << list
|
161
|
+
end
|
162
|
+
$kill_ring_pointer = $kill_ring.size
|
163
|
+
$append_next_kill = false
|
164
|
+
$log.debug "YANK: kill_ring: #{$kill_ring} "
|
165
|
+
end
|
166
|
+
|
167
|
+
# pastes recent (last) entry of kill_ring.
|
168
|
+
# This can be one or more lines. Please note that for us vimmer's yank means copy
|
169
|
+
# but for emacsers it seems to mean paste. Aargh!!
|
170
|
+
# earlier it was not +1, it was pasting before not after
|
171
|
+
def yank where=@current_index+1
|
172
|
+
return -1 if !@editable
|
173
|
+
return if $kill_ring.empty?
|
174
|
+
row = $kill_ring.last
|
175
|
+
$log.debug "YANK: row #{row} "
|
176
|
+
index = where
|
177
|
+
case row
|
178
|
+
when Array
|
179
|
+
#index = @current_index
|
180
|
+
row.each{ |r|
|
181
|
+
@list.insert index, r.dup
|
182
|
+
index += 1
|
183
|
+
}
|
184
|
+
$kill_last_pop_size = row.size
|
185
|
+
when String
|
186
|
+
#@list[@current_index].insert row.dup
|
187
|
+
#@list.insert @current_index, row.dup
|
188
|
+
@list.insert index, row.dup
|
189
|
+
$kill_last_pop_size = 1
|
190
|
+
else
|
191
|
+
raise "textarea yank got uncertain datatype from kill_ring #{row.class} "
|
192
|
+
end
|
193
|
+
$kill_ring_pointer = $kill_ring.size - 1
|
194
|
+
$kill_ring_index = @current_index # pops will replace data in this row, never an insert
|
195
|
+
@repaint_required = true
|
196
|
+
@widget_scrolled = true
|
197
|
+
# XXX not firing anything here, so i can't undo. yet, i don't know whether a yank will
|
198
|
+
# be followed by a yank-pop, in which case it will not be undone.
|
199
|
+
# object row can be string or array - time to use INSERT_LINE so we are clear
|
200
|
+
# row.length can be array's size or string length - beware
|
201
|
+
fire_handler :CHANGE, InputDataEvent.new(0,row.length, self, :INSERT_LINE, @current_index, row)
|
202
|
+
return 0 # don't want a UNHANDLED or NO_BLOCK going back
|
203
|
+
end
|
204
|
+
|
205
|
+
# paste previous entries from kill ring
|
206
|
+
# I am not totally clear on this, not being an emacs user. but seems you have to do C-y
|
207
|
+
# once (yank) before you can do a yank pop.
|
208
|
+
def yank_pop
|
209
|
+
return -1 if !@editable
|
210
|
+
return if $kill_ring.empty?
|
211
|
+
mapped_key = @current_key # we are mapped to this
|
212
|
+
# checking that user has done a yank on this row. We only replace on the given row, never
|
213
|
+
# insert. But what if user edited after yank, Sheesh ! XXX
|
214
|
+
if $kill_ring_index != @current_index
|
215
|
+
Ncurses.beep
|
216
|
+
return # error message required that user must yank first
|
217
|
+
end
|
218
|
+
# the real reason i put this into a loop is so that i can properly undo the
|
219
|
+
# action later if required. I only need to store the final selection.
|
220
|
+
# This also ensures the user doesn't wander off in between and come back.
|
221
|
+
row = nil
|
222
|
+
while true
|
223
|
+
# remove lines from last replace, then insert
|
224
|
+
index = @current_index
|
225
|
+
$kill_last_pop_size.times {
|
226
|
+
del = @list.delete_at index
|
227
|
+
}
|
228
|
+
row = $kill_ring[$kill_ring_pointer-$multiplier]
|
229
|
+
$multiplier = 0
|
230
|
+
index = @current_index
|
231
|
+
case row
|
232
|
+
when Array
|
233
|
+
row.each{ |r|
|
234
|
+
@list.insert index, r.dup
|
235
|
+
index += 1
|
236
|
+
}
|
237
|
+
$kill_last_pop_size = row.size
|
238
|
+
when String
|
239
|
+
@list.insert index, row.dup
|
240
|
+
$kill_last_pop_size = 1
|
241
|
+
else
|
242
|
+
raise "textarea yank_pop got uncertain datatype from kill_ring #{row.class} "
|
243
|
+
end
|
244
|
+
|
245
|
+
$kill_ring_pointer -= 1
|
246
|
+
if $kill_ring_pointer < 0
|
247
|
+
# should be size, but that'll give an error. need to find a way!
|
248
|
+
$kill_ring_pointer = $kill_ring.size - 1
|
249
|
+
end
|
250
|
+
@repaint_required = true
|
251
|
+
@widget_scrolled = true
|
252
|
+
my_win = @form || @parent_component.form # 2010-02-12 12:51
|
253
|
+
my_win.repaint
|
254
|
+
ch = @graphic.getchar
|
255
|
+
if ch != mapped_key
|
256
|
+
@graphic.ungetch ch # seems to work fine
|
257
|
+
return ch # XXX to be picked up by handle_key loop and processed
|
258
|
+
end
|
259
|
+
end
|
260
|
+
# object row can be string or array - time to use INSERT_LINE so we are clear
|
261
|
+
# row.length can be array's size or string length - beware
|
262
|
+
fire_handler :CHANGE, InputDataEvent.new(0,row.length, self, :INSERT_LINE, @current_index, row)
|
263
|
+
return 0
|
264
|
+
end
|
265
|
+
def append_next_kill
|
266
|
+
$append_next_kill = true
|
267
|
+
end
|
268
|
+
# deletes count words on current line
|
269
|
+
# Does not at this point go beyond the line
|
270
|
+
def delete_word
|
271
|
+
return -1 unless @editable
|
272
|
+
$multiplier = 1 if !$multiplier || $multiplier == 0
|
273
|
+
line = @current_index
|
274
|
+
pos = @curpos
|
275
|
+
@delete_buffer = ""
|
276
|
+
# currently only look in current line
|
277
|
+
$multiplier.times {
|
278
|
+
found = @buffer.index(/[[:punct:][:space:]]/, pos)
|
279
|
+
break if !found
|
280
|
+
$log.debug " delete_word: pos #{pos} found #{found} buff: #{@buffer} "
|
281
|
+
@delete_buffer << @buffer.slice!(pos..found)
|
282
|
+
}
|
283
|
+
return if @delete_buffer == ""
|
284
|
+
$log.debug " delete_word: delbuff #{@delete_buffer} "
|
285
|
+
add_to_kill_ring @delete_buffer
|
286
|
+
fire_handler :CHANGE, InputDataEvent.new(@curpos,@curpos+@delete_buffer.length, self, :DELETE, line, @delete_buffer) # 2008-12-24 18:34
|
287
|
+
set_modified
|
288
|
+
end
|
289
|
+
##
|
290
|
+
# deletes forward till the occurence of a character
|
291
|
+
# it gets the char from the user
|
292
|
+
# Should we pass in the character (and accept it as a separate func) ???
|
293
|
+
def delete_forward
|
294
|
+
return -1 unless @editable
|
295
|
+
ch = @graphic.getchar
|
296
|
+
return if ch < 0 || ch > 255
|
297
|
+
char = ch.chr
|
298
|
+
$multiplier = 1 if !$multiplier || $multiplier == 0
|
299
|
+
line = @current_index
|
300
|
+
pos = @curpos
|
301
|
+
tmpbuf = ""
|
302
|
+
# currently only look in current line
|
303
|
+
$multiplier.times {
|
304
|
+
found = @buffer.index(char, pos)
|
305
|
+
break if !found
|
306
|
+
#$log.debug " delete_forward: pos #{pos} found #{found} buff: #{@buffer} "
|
307
|
+
# ideally do this in one shot outside loop, but its okay here for now
|
308
|
+
tmpbuf << @buffer.slice!(pos..found)
|
309
|
+
}
|
310
|
+
return if tmpbuf == ""
|
311
|
+
@delete_buffer = tmpbuf
|
312
|
+
$log.debug " delete_forward: delbuff #{@delete_buffer} "
|
313
|
+
add_to_kill_ring @delete_buffer
|
314
|
+
fire_handler :CHANGE, InputDataEvent.new(@curpos,@curpos+@delete_buffer.length, self, :DELETE, line, @delete_buffer) # 2008-12-24 18:34
|
315
|
+
set_modified
|
316
|
+
$multiplier = 0
|
317
|
+
end
|
318
|
+
|
319
|
+
end # end module
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# Some methods for traversing list like widgets such as tree, listbox and maybe table
|
2
|
+
# Different components may bind different keys to these
|
3
|
+
#
|
4
|
+
module Canis
|
5
|
+
module ListOperations
|
6
|
+
|
7
|
+
# get a char ensure it is a char or number
|
8
|
+
# In this state, it could accept control and other chars.
|
9
|
+
private
|
10
|
+
def _ask_a_char
|
11
|
+
ch = @graphic.getch
|
12
|
+
#message "achar is #{ch}"
|
13
|
+
if ch < 26 || ch > 255
|
14
|
+
@graphic.ungetch ch
|
15
|
+
return :UNHANDLED
|
16
|
+
end
|
17
|
+
return ch.chr
|
18
|
+
end
|
19
|
+
public
|
20
|
+
# sets the selection to the next row starting with char
|
21
|
+
# Trying to return unhandled is having no effect right now. if only we could pop it into a
|
22
|
+
# stack or unget it.
|
23
|
+
def set_selection_for_char char=nil
|
24
|
+
char = _ask_a_char unless char
|
25
|
+
#alert "got #{char} "
|
26
|
+
return :UNHANDLED if char == :UNHANDLED
|
27
|
+
@oldrow = @current_index
|
28
|
+
@last_regex = /^#{char}/
|
29
|
+
ix = next_regex @last_regex
|
30
|
+
#alert "next returned #{ix}"
|
31
|
+
return unless ix
|
32
|
+
@current_index = ix[0]
|
33
|
+
@search_found_ix = @current_index
|
34
|
+
@curpos = ix[1]
|
35
|
+
ensure_visible
|
36
|
+
return @current_index
|
37
|
+
end
|
38
|
+
# Find the next row that contains given string
|
39
|
+
# @return row and col offset of match, or nil
|
40
|
+
# @param String to find
|
41
|
+
def next_regex str
|
42
|
+
first = nil
|
43
|
+
## content can be string or Chunkline, so we had to write <tt>index</tt> for this.
|
44
|
+
## =~ does not give an error, but it does not work.
|
45
|
+
@list.each_with_index do |line, ix|
|
46
|
+
#col = line.index str
|
47
|
+
# for treemodel which will give us user_object.to_s
|
48
|
+
col = line.to_s.index str
|
49
|
+
if col
|
50
|
+
first ||= [ ix, col ]
|
51
|
+
if ix > @current_index
|
52
|
+
return [ix, col]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
return first
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
end # end module
|
61
|
+
end # end module
|
@@ -0,0 +1,388 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# ----------------------------------------------------------------------------- #
|
3
|
+
# File: listselectionmodel.rb
|
4
|
+
# Description: Used by textpad derivates to give selection of rows
|
5
|
+
# Author: j kepler http://github.com/mare-imbrium/canis/
|
6
|
+
# Date: 2014-04-10 - 21:04
|
7
|
+
# License: Same as ruby license
|
8
|
+
# Last update: 2014-07-07 00:36
|
9
|
+
# ----------------------------------------------------------------------------- #
|
10
|
+
# listselectionmodel.rb Copyright (C) 2012-2014 j kepler
|
11
|
+
# ----------------------------------------------------------------------------- #
|
12
|
+
#
|
13
|
+
require 'forwardable'
|
14
|
+
|
15
|
+
# The +DefaultListSelection+ mixin provides Textpad derived classes with
|
16
|
+
# selection methods and bindings.
|
17
|
+
# == Example
|
18
|
+
# Inside the constructor of the multiline object use the following line, before the call to `super()`
|
19
|
+
#
|
20
|
+
# self.extend DefaultListSelection
|
21
|
+
#
|
22
|
+
# At any other portion, for example after the call to `super()` you may set the
|
23
|
+
# default model if the user has not done so in the calling block
|
24
|
+
#
|
25
|
+
# @list_selection_model ||= Canis::DefaultListSelectionModel.new self
|
26
|
+
#
|
27
|
+
# When clearing data, as in `clear`, +selected_indices+ should also be cleared.
|
28
|
+
#
|
29
|
+
# == Note
|
30
|
+
#
|
31
|
+
# This module does not take care of rendering a selected row. This must still be handled
|
32
|
+
# by the default or custom renderer using `is_row_selected?`.
|
33
|
+
#
|
34
|
+
# Note that changing the order of data, or deleting, inserting etc will not correct the selection
|
35
|
+
# indices. Indices are assumed to be stable, and they may be cleared using `clear` on +@selected_indices+
|
36
|
+
# if the data indices change.
|
37
|
+
#
|
38
|
+
module Canis
|
39
|
+
extend self
|
40
|
+
module DefaultListSelection
|
41
|
+
def self.extended(obj)
|
42
|
+
extend Forwardable
|
43
|
+
# selection modes may be :multiple, :single or :none
|
44
|
+
dsl_accessor :selection_mode
|
45
|
+
# color of selected rows, and attribute of selected rows
|
46
|
+
dsl_accessor :selected_color, :selected_bgcolor, :selected_attr
|
47
|
+
# indices of selected rows
|
48
|
+
dsl_accessor :selected_indices
|
49
|
+
# model that takes care of selection operations
|
50
|
+
dsl_accessor :list_selection_model
|
51
|
+
#
|
52
|
+
# all operations of selection are delegated to the ListSelectionModel
|
53
|
+
def_delegators :@list_selection_model, :is_row_selected?, :toggle_row_selection, :select, :unselect, :is_selection_empty?, :clear_selection, :selected_rows, :select_all, :selected_values, :selected_value
|
54
|
+
|
55
|
+
|
56
|
+
obj.instance_exec {
|
57
|
+
@selected_indices = []
|
58
|
+
@selection_mode = :multiple # default is multiple intervals
|
59
|
+
#@list_selection_model = DefaultListSelectionModel.new obj
|
60
|
+
}
|
61
|
+
|
62
|
+
end
|
63
|
+
end # mod DefaultListSelection
|
64
|
+
# Whenever user selects one or more rows, this object is sent via event
|
65
|
+
# giving start row and last row of selection, object
|
66
|
+
# and type which is :INSERT :DELETE :CLEAR
|
67
|
+
class ListSelectionEvent < Struct.new(:firstrow, :lastrow, :source, :type)
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Object that takes care of selection of rows.
|
72
|
+
# This may be replace with a custom object at time of instantiation of list
|
73
|
+
# Note that there are only two selection modes: single and multiple.
|
74
|
+
# Multiple refers to multiple intervals. There is also a multiple row selection
|
75
|
+
# mode, single interval, which only allows one range to be selected, much like a
|
76
|
+
# text object, i.e. any text editor.
|
77
|
+
#
|
78
|
+
## I am copying this from listselectable. that was a module so was included and shared variables
|
79
|
+
# but now this is a class, and cannot access state as directly
|
80
|
+
|
81
|
+
class DefaultListSelectionModel
|
82
|
+
|
83
|
+
def initialize component
|
84
|
+
raise "Components passed to DefaultListSelectionModel is nil" unless component
|
85
|
+
@obj = component
|
86
|
+
|
87
|
+
@selected_indices = @obj.selected_indices
|
88
|
+
# in this case since it is called immediately upon extend, user cannot change this
|
89
|
+
# Need a method to let user change after extending
|
90
|
+
@selection_mode = @obj.selection_mode
|
91
|
+
list_bindings
|
92
|
+
end
|
93
|
+
|
94
|
+
# change selection of current row on pressing space bar (or keybinding)
|
95
|
+
# If mode is multiple, then this row is added to previous selections
|
96
|
+
# @example
|
97
|
+
# bind_key(32) { toggle_row_selection }
|
98
|
+
#
|
99
|
+
#
|
100
|
+
def toggle_row_selection crow=@obj.current_index
|
101
|
+
@last_clicked = crow
|
102
|
+
@repaint_required = true
|
103
|
+
case @obj.selection_mode
|
104
|
+
when :multiple
|
105
|
+
if @selected_indices.include? crow
|
106
|
+
@selected_indices.delete crow
|
107
|
+
lse = ListSelectionEvent.new(crow, crow, @obj, :DELETE)
|
108
|
+
@obj.fire_handler :LIST_SELECTION_EVENT, lse
|
109
|
+
else
|
110
|
+
@selected_indices << crow
|
111
|
+
lse = ListSelectionEvent.new(crow, crow, @obj, :INSERT)
|
112
|
+
@obj.fire_handler :LIST_SELECTION_EVENT, lse
|
113
|
+
end
|
114
|
+
else
|
115
|
+
# single - now change to use array only
|
116
|
+
@selected_index = @selected_indices[0]
|
117
|
+
if @selected_index == crow
|
118
|
+
@old_selected_index = @selected_index # 2011-10-15 so we can unhighlight
|
119
|
+
@selected_index = nil
|
120
|
+
@selected_indices.clear
|
121
|
+
lse = ListSelectionEvent.new(crow, crow, @obj, :DELETE)
|
122
|
+
@obj.fire_handler :LIST_SELECTION_EVENT, lse
|
123
|
+
else
|
124
|
+
@selected_indices[0] = crow
|
125
|
+
@obj.fire_row_changed(@old_selected_index) if @old_selected_index
|
126
|
+
@old_selected_index = crow # 2011-10-15 so we can unhighlight
|
127
|
+
lse = ListSelectionEvent.new(crow, crow, @obj, :INSERT)
|
128
|
+
@obj.fire_handler :LIST_SELECTION_EVENT, lse
|
129
|
+
end
|
130
|
+
end
|
131
|
+
@obj.fire_row_changed crow
|
132
|
+
#alert "toggling #{@selected_indices.join(',')}"
|
133
|
+
end
|
134
|
+
#
|
135
|
+
# Range select.
|
136
|
+
# Only for multiple mode.
|
137
|
+
# Uses the last row clicked on, till the current one.
|
138
|
+
# If user clicks inside a selcted range, then deselect from last click till current (remove from earlier)
|
139
|
+
# If user clicks outside selected range, then select from last click till current (add to earlier)
|
140
|
+
# typically bound to Ctrl-Space (0)
|
141
|
+
#
|
142
|
+
# @example
|
143
|
+
#
|
144
|
+
# bind_key(0) { range_select }
|
145
|
+
#
|
146
|
+
def range_select crow=@obj.current_index
|
147
|
+
#alert "add to selection fired #{@last_clicked}"
|
148
|
+
@last_clicked ||= crow
|
149
|
+
min = [@last_clicked, crow].min
|
150
|
+
max = [@last_clicked, crow].max
|
151
|
+
case @obj.selection_mode
|
152
|
+
when :multiple
|
153
|
+
if @selected_indices.include? crow
|
154
|
+
# delete from last_clicked until this one in any direction
|
155
|
+
min.upto(max){ |i| @selected_indices.delete i
|
156
|
+
@obj.fire_row_changed i
|
157
|
+
}
|
158
|
+
lse = ListSelectionEvent.new(min, max, @obj, :DELETE)
|
159
|
+
@obj.fire_handler :LIST_SELECTION_EVENT, lse
|
160
|
+
else
|
161
|
+
# add to selection from last_clicked until this one in any direction
|
162
|
+
min.upto(max){ |i| @selected_indices << i unless @selected_indices.include?(i)
|
163
|
+
@obj.fire_row_changed i
|
164
|
+
}
|
165
|
+
lse = ListSelectionEvent.new(min, max, @obj, :INSERT)
|
166
|
+
@obj.fire_handler :LIST_SELECTION_EVENT, lse
|
167
|
+
end
|
168
|
+
else
|
169
|
+
end
|
170
|
+
@last_clicked = crow # 2014-04-08 - 01:21 this was missing, i think it is required
|
171
|
+
self
|
172
|
+
end
|
173
|
+
# clears selected indices, typically called when multiple select
|
174
|
+
# Key binding is application specific
|
175
|
+
def clear_selection
|
176
|
+
return if @selected_indices.nil? || @selected_indices.empty?
|
177
|
+
arr = @selected_indices.dup # to un highlight
|
178
|
+
@selected_indices.clear
|
179
|
+
arr.each {|i| @obj.fire_row_changed(i) }
|
180
|
+
@selected_index = nil
|
181
|
+
@old_selected_index = nil
|
182
|
+
# User should ignore first two params
|
183
|
+
lse = ListSelectionEvent.new(0, arr.size, @obj, :CLEAR)
|
184
|
+
@obj.fire_handler :LIST_SELECTION_EVENT, lse
|
185
|
+
arr = nil
|
186
|
+
end
|
187
|
+
|
188
|
+
# returns +true+ if given row has been selected
|
189
|
+
# Now that we use only the array, the multiple check is good enough
|
190
|
+
def is_row_selected? crow
|
191
|
+
case @obj.selection_mode
|
192
|
+
when :multiple
|
193
|
+
@selected_indices.include? crow
|
194
|
+
else
|
195
|
+
@selected_index = @selected_indices[0]
|
196
|
+
crow == @selected_index
|
197
|
+
end
|
198
|
+
end
|
199
|
+
alias :is_selected? is_row_selected?
|
200
|
+
|
201
|
+
# after selecting, traverse selections forward
|
202
|
+
def goto_next_selection
|
203
|
+
return if selected_rows().length == 0
|
204
|
+
row = selected_rows().sort.find { |i| i > @obj.current_index }
|
205
|
+
row ||= @obj.current_index
|
206
|
+
#@obj.current_index = row
|
207
|
+
@obj.goto_line row
|
208
|
+
end
|
209
|
+
|
210
|
+
# after selecting, traverse selections backward
|
211
|
+
def goto_prev_selection
|
212
|
+
return if selected_rows().length == 0
|
213
|
+
row = selected_rows().sort{|a,b| b <=> a}.find { |i| i < @obj.current_index }
|
214
|
+
row ||= @obj.current_index
|
215
|
+
#@obj.current_index = row
|
216
|
+
@obj.goto_line row
|
217
|
+
end
|
218
|
+
# add the following range to selected items, unless already present
|
219
|
+
# should only be used if multiple selection interval
|
220
|
+
def add_row_selection_interval ix0, ix1
|
221
|
+
return if @obj.selection_mode != :multiple
|
222
|
+
@anchor_selection_index = ix0
|
223
|
+
@lead_selection_index = ix1
|
224
|
+
ix0.upto(ix1) {|i|
|
225
|
+
@selected_indices << i unless @selected_indices.include? i
|
226
|
+
@obj.fire_row_changed i
|
227
|
+
}
|
228
|
+
lse = ListSelectionEvent.new(ix0, ix1, @obj, :INSERT)
|
229
|
+
@obj.fire_handler :LIST_SELECTION_EVENT, lse
|
230
|
+
#$log.debug " DLSM firing LIST_SELECTION EVENT #{lse}"
|
231
|
+
end
|
232
|
+
|
233
|
+
# remove selected indices between given indices inclusive
|
234
|
+
def remove_row_selection_interval ix0, ix1
|
235
|
+
@anchor_selection_index = ix0
|
236
|
+
@lead_selection_index = ix1
|
237
|
+
arr = @selected_indices.dup # to un highlight
|
238
|
+
@selected_indices.delete_if {|x| x >= ix0 and x <= ix1 }
|
239
|
+
arr.each {|i| @obj.fire_row_changed(i) }
|
240
|
+
lse = ListSelectionEvent.new(ix0, ix1, @obj, :DELETE)
|
241
|
+
@obj.fire_handler :LIST_SELECTION_EVENT, lse
|
242
|
+
end
|
243
|
+
# convenience method to select next len rows
|
244
|
+
def insert_index_interval ix0, len
|
245
|
+
@anchor_selection_index = ix0
|
246
|
+
@lead_selection_index = ix0+len
|
247
|
+
add_row_selection_interval @anchor_selection_index, @lead_selection_index
|
248
|
+
end
|
249
|
+
# select all rows, you may specify starting row.
|
250
|
+
# if header row, then 1 else should be 0. Actually we should have a way to determine
|
251
|
+
# this, and the default should be zero.
|
252
|
+
def select_all start_row=0 #+@_header_adjustment
|
253
|
+
# don't select header row - need to make sure this works for all cases. we may
|
254
|
+
# need a variable instead of hardoded value
|
255
|
+
add_row_selection_interval start_row, @obj.list.count()-1
|
256
|
+
end
|
257
|
+
|
258
|
+
# toggle selection of entire list
|
259
|
+
# Requires application specific key binding
|
260
|
+
def invert_selection start_row=0 #+@_header_adjustment
|
261
|
+
start_row.upto(@obj.list.count()-1){|i| invert_row_selection i }
|
262
|
+
end
|
263
|
+
|
264
|
+
# toggles selection for given row
|
265
|
+
# Typically called by invert_selection
|
266
|
+
def invert_row_selection row=@obj.current_index
|
267
|
+
@repaint_required = true
|
268
|
+
if is_selected? row
|
269
|
+
remove_row_selection_interval(row, row)
|
270
|
+
else
|
271
|
+
add_row_selection_interval(row, row)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
# selects all rows with the values given, leaving existing selections
|
275
|
+
# intact. Typically used after accepting search criteria, and getting a list of values
|
276
|
+
# to select (such as file names). Will not work with tables (array or array)
|
277
|
+
# TODO is this even needed, scrap
|
278
|
+
def select_values values
|
279
|
+
return unless values
|
280
|
+
values.each do |val|
|
281
|
+
row = @list.index val
|
282
|
+
add_row_selection_interval row, row unless row.nil?
|
283
|
+
end
|
284
|
+
end
|
285
|
+
# TODO is this even needed, scrap
|
286
|
+
# unselects all rows with the values given, leaving all other rows intact
|
287
|
+
# You can map "-" to ask_select and call this from there.
|
288
|
+
# bind_key(?+, :ask_select) # --> calls select_values
|
289
|
+
# bind_key(?-, :ask_unselect)
|
290
|
+
def unselect_values values
|
291
|
+
return unless values
|
292
|
+
values.each do |val|
|
293
|
+
row = @list.index val
|
294
|
+
remove_row_selection_interval row, row unless row.nil?
|
295
|
+
end
|
296
|
+
end
|
297
|
+
#
|
298
|
+
# Asks user to enter a string or pattern for selecting rows
|
299
|
+
# Selects rows based on pattern, leaving other selections as-is
|
300
|
+
def ask_select prompt="Enter selection pattern: "
|
301
|
+
ret = get_string prompt
|
302
|
+
return if ret.nil? || ret == ""
|
303
|
+
indices = get_matching_indices ret
|
304
|
+
#$log.debug "listselectionmodel: ask_select got matches#{@indices} "
|
305
|
+
return if indices.nil? || indices.empty?
|
306
|
+
indices.each { |e|
|
307
|
+
# will not work if single select !! FIXME
|
308
|
+
add_row_selection_interval e,e
|
309
|
+
}
|
310
|
+
end
|
311
|
+
# returns a list of matching indices using a simple regex match on given pattern
|
312
|
+
# returns an empty list if no match
|
313
|
+
def get_matching_indices pattern
|
314
|
+
matches = []
|
315
|
+
@obj.content.each_with_index { |e,i|
|
316
|
+
# convert to string for tables
|
317
|
+
e = e.to_s unless e.is_a? String
|
318
|
+
if e =~ /#{pattern}/
|
319
|
+
matches << i
|
320
|
+
end
|
321
|
+
}
|
322
|
+
return matches
|
323
|
+
end
|
324
|
+
# Asks user to enter a string or pattern for UNselecting rows
|
325
|
+
# UNSelects rows based on pattern, leaving other selections as-is
|
326
|
+
def ask_unselect prompt="Enter selection pattern: "
|
327
|
+
ret = get_string prompt
|
328
|
+
return if ret.nil? || ret == ""
|
329
|
+
indices = get_matching_indices ret
|
330
|
+
return if indices.nil? || indices.empty?
|
331
|
+
indices.each { |e|
|
332
|
+
# will not work if single select !! FIXME
|
333
|
+
remove_row_selection_interval e,e
|
334
|
+
}
|
335
|
+
end
|
336
|
+
|
337
|
+
##
|
338
|
+
# bindings related to selection
|
339
|
+
#
|
340
|
+
def list_bindings
|
341
|
+
# freeing space for paging, now trying out 'v' as selector. 2014-04-14 - 18:57
|
342
|
+
@obj.bind_key($row_selector || 'v'.ord, 'toggle selection') { toggle_row_selection }
|
343
|
+
|
344
|
+
# the mode may be set to single after the constructor, so this would have taken effect.
|
345
|
+
if @obj.selection_mode == :multiple
|
346
|
+
# freeing ctrl_space for back paging, now trying out 'V' as selector. 2014-04-14 - 18:57
|
347
|
+
@obj.bind_key($range_selector || 'V'.ord, 'range select') { range_select }
|
348
|
+
@obj.bind_key(?+, 'ask_select') { ask_select }
|
349
|
+
@obj.bind_key(?-, 'ask_unselect') { ask_unselect }
|
350
|
+
@obj.bind_key(?a, 'select_all') {select_all}
|
351
|
+
@obj.bind_key(?*, 'invert_selection') { invert_selection }
|
352
|
+
@obj.bind_key(?u, 'clear_selection') { clear_selection }
|
353
|
+
@obj.bind_key([?g,?n], 'goto next selection'){ goto_next_selection } # mapping double keys like vim
|
354
|
+
@obj.bind_key([?g,?p], 'goto prev selection'){ goto_prev_selection } # mapping double keys like vim
|
355
|
+
end
|
356
|
+
@_header_adjustment ||= 0 # incase caller does not use
|
357
|
+
#@obj._events << :LIST_SELECTION_EVENT unless @obj._events.include? :LIST_SELECTION_EVENT
|
358
|
+
end
|
359
|
+
def list_init_vars
|
360
|
+
# uncommenting since link with obj will be broken
|
361
|
+
#@selected_indices = []
|
362
|
+
@selected_index = nil
|
363
|
+
@old_selected_index = nil
|
364
|
+
#@row_selected_symbol = ''
|
365
|
+
## FIXME we are not doing selectors at present. should we, else remove this
|
366
|
+
if @show_selector
|
367
|
+
@row_selected_symbol ||= '*'
|
368
|
+
@row_unselected_symbol ||= ' '
|
369
|
+
@left_margin ||= @row_selected_symbol.length
|
370
|
+
end
|
371
|
+
end
|
372
|
+
# return the indices selected
|
373
|
+
def selected_rows
|
374
|
+
@selected_indices
|
375
|
+
end
|
376
|
+
# return the values selected
|
377
|
+
def selected_values
|
378
|
+
@obj.values_at(*@selected_indices)
|
379
|
+
end
|
380
|
+
# returns first selection, meant for convenience of single select listboxes
|
381
|
+
# earlier called selected_item
|
382
|
+
def selected_value
|
383
|
+
return nil if @selected_indices.empty?
|
384
|
+
@obj[@selected_indices.first]
|
385
|
+
#selected_values.first
|
386
|
+
end
|
387
|
+
end # class
|
388
|
+
end # mod Canis
|