ncumbra 0.1.0 → 0.1.1

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.
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