ncumbra 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/umbra/listbox.rb CHANGED
@@ -1,178 +1,121 @@
1
- require 'umbra/widget'
1
+ require 'umbra/multiline'
2
2
  # ----------------------------------------------------------------------------- #
3
3
  # File: listbox.rb
4
- # Description: list widget that displays a list of items
5
- # Author: j kepler http://github.com/mare-imbrium/canis/
4
+ # Description: list widget that displays a scrollable list of items that is selectable.
5
+ # Author: j kepler http://github.com/mare-imbrium/umbra
6
6
  # Date: 2018-03-19
7
7
  # License: MIT
8
- # Last update: 2018-04-20 12:35
8
+ # Last update: 2018-05-18 14:16
9
9
  # ----------------------------------------------------------------------------- #
10
10
  # listbox.rb Copyright (C) 2012-2018 j kepler
11
11
  # == TODO
12
- # currently only do single selection, we may do multiple at a later date.
12
+ # currently only do single selection, we may do multiple at a later date. TODO
13
13
  # insert/delete a row ??
14
+ ## Other selection functions: select_all, select(n), deselect(n), selected?(n), select(m,n,o...), select(String),
15
+ ## select(Range). Same with deselect.
14
16
  # ----------------
15
17
  module Umbra
16
- class Listbox < Widget
17
- attr_reader :list # list containing data
18
- #
19
- # index of focussed row, starting 0, index into the list supplied
20
- attr_reader :current_index
21
18
 
22
- attr_accessor :selection_key # key used to select a row
23
- attr_accessor :selected_index # row selected, may change to plural
24
- attr_accessor :selected_color_pair # row selected color_pair
25
- attr_accessor :selected_attr # row selected color_pair
26
- attr_accessor :selected_mark # row selected character
27
- attr_accessor :unselected_mark # row unselected character (usually blank)
28
- attr_accessor :current_mark # row current character (default is >)
19
+ ## Display a list of items.
20
+ ## Adds selection capability to the Scrollable widget.
21
+ #
22
+ class Listbox < Multiline
29
23
 
24
+ attr_accessor :selection_allowed # does this class allow row selection (should be class level)
25
+ attr_accessor :selection_key # key used to select a row
26
+ attr_accessor :selected_index # row selected, may change to plural
27
+ attr_property :selected_color_pair # row selected color_pair
28
+ attr_property :selected_attr # row selected color_pair
29
+ attr_accessor :selected_mark # row selected character
30
+ attr_accessor :unselected_mark # row unselected character (usually blank)
31
+ attr_accessor :current_mark # row current character (default is >)
30
32
 
31
- def initialize config={}, &block
32
- @focusable = false
33
- @editable = false
34
- @pstart = 0 # which row does printing start from
35
- @current_index = 0 # index of row on which cursor is
36
- @selected_index = nil # index of row selected
37
- @selection_key = ?s.getbyte(0) # 's' used to select/deselect
38
- @selected_color_pair = CP_RED
39
- @selected_attr = REVERSE
40
- @row_offset = 0
41
- @selected_mark = 'x' # row selected character
42
- @unselected_mark = ' ' # row unselected character (usually blank)
43
- @current_mark = '>' # row current character (default is >)
44
- register_events([:LEAVE_ROW, :ENTER_ROW, :LIST_SELECTION_EVENT])
45
- super
33
+ def initialize config={}, &block
46
34
 
47
- map_keys
48
- @pcol = 0
49
- @repaint_required = true
50
- end
51
- # set list of data to be displayed.
52
- # NOTE this can be called again and again, so we need to take care of change in size of data
53
- # as well as things like current_index and selected_index or indices.
54
- # clear the listbox is list is smaller or empty FIXME
55
- def list=(alist)
56
- if !alist or alist.size == 0
57
- $log.debug " setting focusable to false in listbox "
58
- self.focusable=(false)
59
- # should we return here
60
- else
61
- $log.debug " setting focusable to true in listbox #{alist.count} "
62
- self.focusable=(true)
35
+ @selection_allowed = true # does this class allow selection of row
36
+ @selected_index = nil # index of row selected
37
+ @selection_key = ?s.getbyte(0) # 's' used to select/deselect
38
+ @selected_color_pair = CP_RED
39
+ @selected_attr = REVERSE
40
+ @selected_mark = 'x' # row selected character
41
+ @unselected_mark = ' ' # row unselected character (usually blank)
42
+ @current_mark = '>' # row current character (default is >)
43
+ register_events([:LIST_SELECTION_EVENT])
44
+ super
63
45
  end
64
- @list = alist
65
- @repaint_required = true
66
- @pstart = @current_index = 0
67
- @selected_index = nil
68
- @pcol = 0
69
- fire_handler(:CHANGED, alist)
70
- end
71
- # Calculate dimensions as late as possible, since we can have some other container such as a box,
72
- # determine the dimensions after creation.
73
- private def _calc_dimensions
74
- raise "Dimensions not supplied to listbox" if @row.nil? or @col.nil? or @width.nil? or @height.nil?
75
- @_calc_dimensions = true
76
- @int_width = @width # internal width NOT USED ELSEWHERE
77
- @int_height = @height # internal height USED HERE ONLy REDUNDANT FIXME
78
- @scroll_lines ||= @int_height/2
79
- @page_lines = @int_height
80
- end
81
- # Each row can be in one of the following states:
82
- # 1. HIGHLIGHTED: cursor is on the row, and the list is focussed (user is in it)
83
- # 2. CURRENT : cursor was on this row, now user has exited the list
84
- # 3. SELECTED : user has selected this row (this can also have above two states actually)
85
- # 4. NORMAL : All other rows: not selected, not under cursor
86
- # returns color, attrib and left marker for given row
87
- # @param index of row in the list
88
- # @param state of row in the list (see above states)
89
- def _format_color index, state
90
- arr = case state
91
- when :SELECTED
92
- [@selected_color_pair, @selected_attr]
93
- when :HIGHLIGHTED
94
- [@highlight_color_pair || CP_WHITE, @highlight_attr || REVERSE]
95
- when :CURRENT
96
- [@color_pair, @attr]
97
- when :NORMAL
98
- #@alt_color_pair ||= create_color_pair(COLOR_BLUE, COLOR_WHITE)
99
- _color = CP_CYAN
100
- _color = CP_WHITE if index % 2 == 0
101
- #_color = @alt_color_pair if index % 2 == 0
102
- [@color_pair || _color, @attr || NORMAL]
46
+
47
+
48
+ def list=(alist)
49
+ super
50
+ clear_selection
103
51
  end
104
- return arr
105
- end
106
- # do the actual printing of the row, depending on index and state
107
- # This method starts with underscore since it is only required to be overriden
108
- # if an object has special printing needs.
109
- def _print_row(win, row, col, str, index, state)
110
- arr = _format_color index, state
111
- win.printstring(row, col, str, arr[0], arr[1])
112
- end
113
- def _format_mark index, state
114
- mark = case state
115
- when :SELECTED
116
- @selected_mark
117
- when :HIGHLIGHTED, :CURRENT
118
- @current_mark
119
- else
120
- @unselected_mark
121
- end
122
- end
123
52
 
124
- def repaint
125
- _calc_dimensions unless @_calc_dimensions
126
53
 
127
- return unless @repaint_required
128
- win = @graphic
129
- r,c = @row, @col
130
- _attr = @attr || NORMAL
131
- _color = @color_pair || CP_WHITE
132
- curpos = 1
133
- coffset = 0
134
- width = @width
135
- #files = @list
136
- files = getvalue
137
-
138
- ht = @height
139
- cur = @current_index
140
- st = pstart = @pstart # previous start
141
- pend = pstart + ht -1 # previous end
142
- if cur > pend
143
- st = (cur -ht) + 1
144
- elsif cur < pstart
145
- st = cur
54
+ def clear_selection
55
+ @selected_index = nil
146
56
  end
147
- $log.debug "LISTBOX: cur = #{cur} st = #{st} pstart = #{pstart} pend = #{pend} listsize = #{@list.size} "
148
- hl = cur
149
- y = 0
150
- ctr = 0
151
- filler = " "*(width)
152
- files.each_with_index {|_f, y|
153
- next if y < st
154
- f = _format_value(_f)
155
-
156
- # determine state of this row: NORMAL CURRENT HIGHLIGHTED SELECTED {{{
157
- _st = :NORMAL
158
- if y == hl # current row, row on which cursor is or was
159
- # highlight only if object is focussed, otherwise just show mark
160
- if @state == :HIGHLIGHTED
161
- _st = :HIGHLIGHTED
162
- else
163
- # cursor was on this row, but now user has tabbed out
164
- _st = :CURRENT
165
- end
166
- curpos = ctr
57
+
58
+ def map_keys
59
+ return if @keys_mapped
60
+ if @selection_allowed and @selection_key
61
+ bind_key(@selection_key, 'toggle_selection') { toggle_selection }
62
+ end
63
+ super
64
+ end
65
+
66
+ ## Toggle current row's selection status.
67
+ def toggle_selection
68
+ @repaint_required = true
69
+ if @selected_index == @current_index
70
+ @selected_index = nil
71
+ else
72
+ @selected_index = @current_index
167
73
  end
168
- if y == @selected_index
74
+ fire_handler :LIST_SELECTION_EVENT, self # use selected_index to know which one
75
+ end
76
+
77
+ ## Paint the row.
78
+ ## For any major customization of Listbox output, this method would be overridden.
79
+ ## This method determines state, mark, slice of line item to show.
80
+ ## listbox adds a mark on the side, whether a row is selected or not, and whether it is current.
81
+ ## @param win - window pointer for printing
82
+ ## @param [Integer] - row offset on screen
83
+ ## @param [Integer] - col offset on screen
84
+ ## @param [String] - line to print
85
+ ## @param [Integer] - offset in List array
86
+ def paint_row(win, row, col, line, index)
87
+
88
+ state = state_of_row(index)
89
+
90
+ f = value_of_row(line, index, state)
91
+
92
+ mark = mark_of_row(index, state)
93
+ ff = "#{mark}#{f}"
94
+
95
+ ff = _truncate_to_width( ff ) ## truncate and handle panning
96
+
97
+ print_row(win, row, col, ff, index, state)
98
+ end
99
+
100
+ ## Determine state of the row
101
+ ## Listbox adds :SELECTED state to Multiline.
102
+ ## @param [Integer] offset of row in data
103
+ def state_of_row index
104
+ _st = super
105
+ if index == @selected_index
169
106
  _st = :SELECTED
170
- end # }}}
171
- #colr, attr, mark = _format_color y, _st
107
+ end #
108
+ _st
109
+ end
172
110
 
173
- mark = _format_mark(y, _st)
174
- =begin
175
- mark = case _st
111
+
112
+ ## Determine the mark on the left of the row.
113
+ ## The mark depends on the state: :SELECTED :HIGHLIGHTED :CURRENT :NORMAL
114
+ ## Listbox adds :SELECTED state to Multiline.
115
+ ## @param [Integer] offset of row in data
116
+ ## @return character to be displayed inside left margin
117
+ def mark_of_row index, state
118
+ mark = case state
176
119
  when :SELECTED
177
120
  @selected_mark
178
121
  when :HIGHLIGHTED, :CURRENT
@@ -180,131 +123,26 @@ class Listbox < Widget
180
123
  else
181
124
  @unselected_mark
182
125
  end
183
- =end
184
-
185
- ff = "#{mark} #{f}"
186
- # truncate string to width, and handle panning {{{
187
- if ff
188
- if ff.size > width
189
- # pcol can be greater than width then we get null
190
- if @pcol < ff.size
191
- ff = ff[@pcol..@pcol+width-1]
192
- else
193
- ff = ""
194
- end
195
- else
196
- if @pcol < ff.size
197
- ff = ff[@pcol..-1]
198
- else
199
- ff = ""
200
- end
201
- end
202
- end # }}}
203
- ff = "" unless ff
204
-
205
- win.printstring(ctr + r, coffset+c, filler, _color ) # print filler
206
- #win.printstring(ctr + r, coffset+c, ff, colr, attr)
207
- _print_row(win, ctr + r, coffset+c, ff, y, _st)
208
- ctr += 1
209
- @pstart = st
210
- break if ctr >= ht
211
- }
212
- ## if counter < ht then we need to clear the rest in case there was data earlier {{{
213
- if ctr < ht
214
- while ctr < ht
215
- win.printstring(ctr + r, coffset+c, filler, _color )
216
- ctr += 1
217
- end
218
- end # }}}
219
- @row_offset = curpos #+ border_offset
220
- @col_offset = coffset
221
- @repaint_required = false
222
- end
223
-
224
- def getvalue
225
- @list
226
- end
227
-
228
- #
229
- # how to convert the line of the array to a simple String.
230
- # This is only required to be overridden if the list passed in is not an array of Strings.
231
- # @param the current row which could be a string or array or whatever was passed in in +list=()+.
232
- # @return [String] string to print. A String must be returned.
233
- def _format_value line
234
- line
235
- end
236
- #alias :_format_value :getvalue_for_paint
237
-
126
+ end
127
+ alias :_format_mark :mark_of_row
238
128
 
239
- def map_keys
240
- bind_keys([?k,FFI::NCurses::KEY_UP], "Up") { cursor_up }
241
- bind_keys([?j,FFI::NCurses::KEY_DOWN], "Down") { cursor_down }
242
- bind_keys([?l,FFI::NCurses::KEY_RIGHT], "Right") { cursor_forward }
243
- bind_keys([?h,FFI::NCurses::KEY_LEFT], "Left") { cursor_backward }
244
- bind_key(?g, 'goto_start') { goto_start }
245
- bind_key(?G, 'goto_end') { goto_end }
246
- bind_key(FFI::NCurses::KEY_CTRL_A, 'cursor_home') { cursor_home }
247
- bind_key(FFI::NCurses::KEY_CTRL_E, 'cursor_end') { cursor_end }
248
- bind_key(FFI::NCurses::KEY_CTRL_F, 'page_forward') { page_forward }
249
- bind_key(32, 'page_forward') { page_forward }
250
- bind_key(FFI::NCurses::KEY_CTRL_B, 'page_backward'){ page_backward }
251
- bind_key(FFI::NCurses::KEY_CTRL_U, 'scroll_up') { scroll_up }
252
- bind_key(FFI::NCurses::KEY_CTRL_D, 'scroll_down') { scroll_down }
253
- return if @keys_mapped
254
- end
255
129
 
256
- def on_enter
257
- super
258
- on_enter_row @current_index
259
- # basically I need to only highlight the current index, not repaint all OPTIMIZE
260
- touch ; repaint
261
- end
262
- def on_leave
263
- super
264
- on_leave_row @current_index
265
- # basically I need to only unhighlight the current index, not repaint all OPTIMIZE
266
- touch ; repaint
267
- end
268
- # called when object leaves a row and when object is exited.
269
- def on_leave_row index
270
- fire_handler(:LEAVE_ROW, [index]) # 2018-03-26 - improve this
271
- end
272
- # called whenever a row entered.
273
- # Call when object entered, also.
274
- def on_enter_row index
275
- fire_handler(:ENTER_ROW, [@current_index]) # 2018-03-26 - improve this
276
- end
277
- def cursor_up
278
- @current_index -= 1
279
- end
280
- # go to next row
281
- def cursor_down
282
- @current_index += 1
283
- end
284
- # position cursor at start of field
285
- def cursor_home
286
- @curpos = 0 # UNUSED RIGHT NOW
287
- @pcol = 0
288
- end
289
- # goto end of line.
290
- # This should be consistent with moving the cursor to the end of the row with right arrow
291
- def cursor_end
292
- blen = current_row().length
293
- if blen < @width
294
- @pcol = 0
295
- else
296
- @pcol = blen-@width+2 # 2 is due to mark and space
130
+ ## Determine color and attribute of row.
131
+ ## Overriding this allows application to have customized row colors based on data
132
+ ## which can be determined using +index+.
133
+ ## Listbox adds :SELECTED state to +Multiline+.
134
+ ## @param [Integer] offset of row in data
135
+ ## @return [Array] color_pair and attrib constant
136
+ def color_of_row index, state
137
+ arr = super
138
+ if state == :SELECTED
139
+ arr = [@selected_color_pair, @selected_attr]
297
140
  end
298
- @curpos = blen # this is position in array where editing or motion is to happen regardless of what you see
299
- # regardless of pcol (panning)
300
- end
301
- # returns current row as String
302
- # 2018-04-11 - NOTE this may not be a String so we convert it to string before returning
303
- # @return [String] row the cursor/user is on
304
- def current_row
305
- s = @list[@current_index]
306
- _format_value s
141
+ arr
307
142
  end
143
+
144
+
145
+ =begin
308
146
  def cursor_forward
309
147
  blen = current_row().size-1
310
148
  @pcol += 1 if @pcol < blen
@@ -312,73 +150,8 @@ class Listbox < Widget
312
150
  def cursor_backward
313
151
  @pcol -= 1 if @pcol > 0
314
152
  end
315
- # go to start of file (first line)
316
- def goto_start
317
- @current_index = 0
318
- @pcol = @curpos = 0
319
- end
320
- # go to end of file (last line)
321
- def goto_end
322
- @current_index = @list.size-1
323
- @pcol = @curpos = 0
324
- end
325
- def scroll_down
326
- @current_index += @scroll_lines
327
- end
328
- def scroll_up
329
- @current_index -= @scroll_lines
330
- end
331
- def page_backward
332
- @current_index -= @page_lines
333
- end
334
- def page_forward
335
- @current_index += @page_lines
336
- end
337
- # listbox key handling
338
- def handle_key ch
339
- old_current_index = @current_index
340
- old_pcol = @pcol
341
- case ch
342
- when @selection_key
343
- @repaint_required = true
344
- if @selected_index == @current_index
345
- @selected_index = nil
346
- else
347
- @selected_index = @current_index
348
- end
349
- fire_handler :LIST_SELECTION_EVENT, self # use selected_index to know which one
350
- else
351
- ret = super
352
- return ret
353
- end
354
- ensure
355
- @current_index = 0 if @current_index < 0
356
- @current_index = @list.size-1 if @current_index >= @list.size
357
- if @current_index != old_current_index
358
- on_leave_row old_current_index
359
- on_enter_row @current_index
360
- @repaint_required = true
361
- end
362
- @repaint_required = true if old_pcol != @pcol
363
- end
153
+ =end
364
154
 
365
- def command *args, &block
366
- bind_event :ENTER_ROW, *args, &block
367
- end
368
- def print_border row, col, height, width, color, att=FFI::NCurses::A_NORMAL
369
- raise "deprecated"
370
- pointer = @graphic.pointer
371
- FFI::NCurses.wattron(pointer, FFI::NCurses.COLOR_PAIR(color) | att)
372
- FFI::NCurses.mvwaddch pointer, row, col, FFI::NCurses::ACS_ULCORNER
373
- FFI::NCurses.mvwhline( pointer, row, col+1, FFI::NCurses::ACS_HLINE, width-2)
374
- FFI::NCurses.mvwaddch pointer, row, col+width-1, FFI::NCurses::ACS_URCORNER
375
- FFI::NCurses.mvwvline( pointer, row+1, col, FFI::NCurses::ACS_VLINE, height-2)
376
155
 
377
- FFI::NCurses.mvwaddch pointer, row+height-1, col, FFI::NCurses::ACS_LLCORNER
378
- FFI::NCurses.mvwhline(pointer, row+height-1, col+1, FFI::NCurses::ACS_HLINE, width-2)
379
- FFI::NCurses.mvwaddch pointer, row+height-1, col+width-1, FFI::NCurses::ACS_LRCORNER
380
- FFI::NCurses.mvwvline( pointer, row+1, col+width-1, FFI::NCurses::ACS_VLINE, height-2)
381
- FFI::NCurses.wattroff(pointer, FFI::NCurses.COLOR_PAIR(color) | att)
382
- end
383
- end
156
+ end
384
157
  end # module