ncumbra 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53f2260b5bcdb4fece6ddca02d985adba2f742340079d37d422230e47e7a1f26
4
- data.tar.gz: e1ddc50d55672c99787d3fbcf7617184f64c43995b6249c050f6022679902bb5
3
+ metadata.gz: 6154c2af0ffac0eba6eb146efb61ee935b01b95299f828231e62ece1aa52760b
4
+ data.tar.gz: bb37af7243be9f5ba18252592a9a1a95b74157e66d5bb07d1ca1c3fd7cdb46fe
5
5
  SHA512:
6
- metadata.gz: 0aea1415f138232ac97f9903ee82debd45eb1038562c792964ba6a4e01317fd70bfe7081ff6fa4d89fa221b75cfb660ea33de27d944ba4602b174c167c426fc2
7
- data.tar.gz: 844a11a40aff0903b8aa98089994aeaf55798552504f151426abe89e652368645a4bd64ffe9bcc9dd0fbdb07b57b11ba485c7160d65cd0e7d2e17738cf6df032
6
+ metadata.gz: cae7cbbc5818c5831f02bf7aa94d44c2912420f2ddb4dea18ff35fc0fff011e557ca5de83fb98e9d950086fa75f01fa160f91eceb02c8b9a118e62dffa72afb2
7
+ data.tar.gz: 39bf8e78fddcde77125371be0a8b9a71bd70f59c242c2328c54ea1cf26d031218e532f41dacdbfd3fd34d521ee9869d7ffb856bfbf7d3f1b6ef7b0539b16c76f
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ 2018-06-01
2
+ - for 0.1.2
3
+ - much cleaning up and some renaming of methods and properties
4
+ - LIST_SELECTION_EVENT renamed to SELECT_ROW
5
+ - ItemEvent removed. ToggleButton sends self on :PRESS event
6
+
1
7
  2018-05-23
2
8
  - for 0.1.1
3
9
  - cleanup of Messagebox
data/README.md CHANGED
@@ -23,7 +23,6 @@ This is a stripped version of `canis` gem (ncurses ruby).
23
23
 
24
24
  ## Future versions
25
25
  - Ampersand in Label and Button to signify shortcut/mnemonic.
26
- - table (0.1.1 has it)
27
26
  - combo list
28
27
  - 256 colors
29
28
  - tree (maybe)
@@ -33,7 +32,7 @@ This is a stripped version of `canis` gem (ncurses ruby).
33
32
  Add this line to your application's Gemfile:
34
33
 
35
34
  ```ruby
36
- gem 'umbra'
35
+ gem 'ncumbra'
37
36
  ```
38
37
 
39
38
  And then execute:
@@ -44,18 +43,725 @@ Or install it yourself as:
44
43
 
45
44
  $ gem install ncumbra
46
45
 
46
+ ## Verify your install
47
+
48
+ To save time, it is recommended that you verify that the pre-requisites are working fine.
49
+
50
+ 1. Installing this gem, should have installed the dependency `ffi-ncurses`. First, go to the examples directory of `ffi-ncurses` and run the sample programs. If all if fine, then you have a proper ncurses and ffi-ncurses installation.
51
+
52
+ If this step fails, you may have to either install ffi-ncurses manually:
53
+
54
+ gem install ffi-ncurses
55
+
56
+ or you may not have ncurses installed:
57
+
58
+ brew install ncurses
59
+
60
+ 2. Now check that the samples in Umbra's `examples` directory are working fine. You can run:
61
+
62
+ ruby examples/ex1.rb
63
+
64
+
65
+ ruby examples/ex2.rb
66
+
67
+ If these are running fine, then you have a working copy of `umbra`. The `examples` folder has working examples of labels, fields, listboxes, textboxes and table. There is also a `tut` folder that has simple examples that are shown below.
68
+
47
69
  ## Usage
48
70
 
49
- See examples directory for code samples.
71
+ ### Printing Hello World in a window
72
+
73
+ ```ruby
74
+ require 'umbra'
75
+
76
+ ## Basic hello world program
77
+
78
+ begin
79
+ include Umbra
80
+ init_curses
81
+ win = Window.new
82
+ win.printstring(10,10, "Hello World!");
83
+ win.wrefresh
84
+
85
+ win.getchar
86
+
87
+ ensure
88
+ win.destroy
89
+ FFI::NCurses.endwin
90
+ end
91
+ ```
92
+
93
+ Following is a brief explanation of the lines above.
94
+
95
+ The `require umbra` is required to include some minimal functionality.
96
+
97
+ `include Umbra` is not required, but makes the samples easier to type so that one does not need to prepend objects with `Umbra::`
98
+
99
+ `init_curses` - sets up the ncurses environment. Please check the examples in case the name has changed by the final version.
100
+
101
+ `win = Window.new` - creates a root window. Since no dimensions are specified, a full-screen window is created.
102
+
103
+ Dimension may be specified as follows:
104
+
105
+ win = Window.new _height, _width, _top, _left
106
+
107
+ When windows are created in this manner, it is essential to call `window.destroy` in the ensure block of the program.
108
+ One may also use the block style of creating a window as follows:
109
+
110
+ ```ruby
111
+ Window.create 0,0,0,0 do |win|
112
+ win.printstring 0,0, "Hello World"
113
+ win.getchar
114
+ end
115
+ ```
116
+
117
+ This takes care of destroying the window at the end of the block.
118
+
119
+ Although ncurses provides methods for moving the cursor to a location, and printing at that location, there is a convenience method for doing the same.
120
+
121
+ win.printstring( row, column, string, color_pair, attribute).
122
+
123
+ In order to pause the screen, the program pauses to accept a keystroke.
124
+
125
+ win.getchar
126
+
127
+ Right now we are not interesting in evaluating the key, we just want the display to pause. Press a key and the window will clear, and you will return to the prompt, and your screen should be clear. In this simple program, we avoided checking for exceptions, which will be included in programs later.
128
+
129
+ The `getchar` method waits for a keystroke. Usually, the examples use `getkey` (aka `getch`) which does not pause for a keystroke.
130
+ Try replacing `getchar` with `getch` and run the program. The program closes after a second when `getch` returned a `-1`. This has been used so that forms can have continuous updates without waiting for a keystroke.
131
+
132
+
133
+
134
+ One can create color pairs or used some of the pre-created ones from `init_curses` in `window.rb`.
135
+
136
+ win.printstring( 1, 10, "Hello Ruby", CP_YELLOW, REVERSE).
137
+
138
+
139
+ ### Important Window methods:
140
+
141
+ - `Window.new`
142
+ - `Window.new 0,0, 80, 20`
143
+ - `Window.create(h, w, top, left) {|win| .... }`
144
+ - `win.destroy`
145
+ - `win.printstring(row, col, string, color_pair, attribute)`
146
+ - `win.wrefresh`
147
+ - `win.box`
148
+ - `win.getch` (alias getkey)
149
+ - `win.getchar` (waits for keystroke)
150
+
151
+ In later examples, we will not print using the `window.printstring` method, but will instead create a `label`.
152
+
153
+ ### Creating a Form
154
+
155
+ In order to create a user-interface we need to create a `Form` object. A form manages various widgets or controls such as labels, entry fields, lists, boxes, tables etc. It manages traversal and printing of the same. It handles events. Widgets created must be associated with a form, for them to be operational.
156
+
157
+ ```ruby
158
+ form = Form.new win
159
+ form.add_widget title
160
+ ```
161
+
162
+ The above block creates a `Form` passing a window object. This is required as the Form will use the window for display. This gem does NOT write onto `stdscr`, all writes go to a window.
163
+ A widget is then added to the Form so it can be displayed. Before we create a widget let us visit the important methods of a Form object:
164
+
165
+ - `add_widget` (or `add`) used to register a widget with the form. May take a comma-separated list of widgets.
166
+ - `remove_widget` - remove given widget (rarely used)
167
+ - `pack` - this method is to be called after creating all the widgets before the screen is to be painted. It carries out various functions such as registering shortcuts/hotkeys, creating a list of focusable objects, and laying out objects (layout are in a future version).
168
+ - `repaint` - paints all the registered widgets. In most cases, dimensions are calculated at the time or painting and not at creation time. Note that widgets are only repainted if changed. This minimizes processing and painting.
169
+ - `handle_key(ch)` - the form handles the key for traversal or hands it to the currently focussed field. This is the key that was received by the `window.getkey` method.
170
+
171
+ There are other form methods that one may or may not use such as `select_first_field`, `select_next_field`, `current_widget` (find out which widget is focussed), put focus on a widget (`select_field` aka `select_widget`)
172
+
173
+ At the time of writing (v 0.1.1), `pack` no longer calls `repaint`. It may do so in the future, if found to always happen.
174
+
175
+ Form registers only one event `:RESIZE` which is triggered when the window is resized. You may use this to recalculate widgets. For example:
176
+
177
+ @form.bind(:RESIZE) { resize } ## resize is a user-defined method that recalculates positions and dimensions
178
+
179
+ #### Traversal
180
+
181
+ Traversal between focusable objects may be done using the TAB or Backtab keys. Arrow keys also work.
182
+
183
+ ### Widget
184
+
185
+ Widget is the common superclass of all user-interface controls. It is never instantiated directly.
186
+
187
+ Its properties include:
188
+
189
+ - `text` - text related to a button, field, label, textbox, etc. May be changed at any time, and will immediately reflect
190
+ - `row` - vertical position on screen (0 to FFI::NCurses.LINES-1). Can be negative for relative position.
191
+ - `col` - horizontal position on screen (0 to FFI::NCurses.COLS-1)
192
+ - `width` - defaults to length of `text` but can be larger or smaller. Can be negative.
193
+ - `height` - Height of multiline widgets or boxes. Can be negative.
194
+ - `color_pair` - Combination of foreground and background color. see details for creating colors.
195
+ - `attr` : visual attribute of text in widget. Can be `BOLD` , `NORMAL` , `REVERSE` or `UNDERLINE`
196
+ - `highlight_color_pair` - color pair to use when the widget gets focus
197
+ - `highlight_attr` - attribute to use when the widget gets focus
198
+ - `focusable` - whether the widget may take focus.
199
+ - `visible` - whether the widget is visible.
200
+ - `state` - :NORMAL or :HIGHLIGHTED. Highlighted refers to focussed.
201
+
202
+ If `row` is negative, then the position will be recalculated whenever the window is resized. Similarly, if `width` and `height` are negative, then the width is stretched to the end of the window. If the window is resized, this will be recalculated. This enables some simple resizing and placing of screen components. For complex resizing and repositioning, the Form's `:RESIZE` event should be used.
203
+
204
+ #### attr_property
205
+
206
+ This is a variation of `attr_accessor`. It refers to attributes of an object that should result in the object being repainted, when the attribute is changed. However, whenever such attributes are modified, a `:PROPERTY_CHANGE` event is also fired, so that processing can be attached to such changes.
207
+
208
+ ### Creating a Label
209
+
210
+ The simplest widget in `Umbra` is the Label. Labels are used for a single line of text . The `text` of a label specifies the text to display. Other methods of a label are row, col, width and justify (alignment). Width is important for clearing space, and for right and center alignment.
211
+
212
+ title = Label.new( :text => "Demo of Labels", :row => 0, :col => 0 , :width => FFI::NCurses.COLS-1,
213
+ :justify => :center, :color_pair => 0)
214
+
215
+ A `mnemonic` and related widget may be associated with a label. This `mnemonic` is a shortcut or hotkey for jumping directly to another which is specified by `related_widget`. The `related_widget` must be a focusable object such as a `Field` or `Listbox`. The `mnemonic` is displayed with bold and underlined attribute since underline may not work on some terminals. The Alt-key is to be pressed to jump directly to the field.
216
+
217
+ ```ruby
218
+ title.mnemonic = "n"
219
+ title.related_widget = name
220
+ ```
221
+
222
+ Modify the previous example and create a label as above. Create a `Form` and use `add_widget` to associate the two.
223
+ The `width` has been specified as the size of the current screen. You may use a value such as `20` or `40`. Stretch the window to increase the width. What happens ?
224
+
225
+ Now change the `width` to `-1`. Run the program again and stretch the window's width. What happens ? Negative widths and heights are re-calculated at the time of printing, so a change in width of the screen will immediately reflect in the label's width. A negative value for width or height means that the object must stretch or extend to that row or column from the end. Negative widths are thus relative to the right end of the window. Positive widths are absolute.
226
+
227
+ The important methods of `Label` are:
228
+
229
+ - `text` - may be changed at any time, and will immediately reflect
230
+ - `row` - vertical position on screen (0 to FFI::NCurses.LINES-1). Can be negative for relative position.
231
+ - `col` - horizontal position on screen (0 to FFI::NCurses.COLS-1)
232
+ - `width` - defaults to length of `text` but can be larger or smaller. Can be negative.
233
+ - `color_pair` - see details for creating colors.
234
+ - `attr` : maybe `BOLD` , `NORMAL` , `REVERSE` or `UNDERLINE`
235
+ - `justify` - `:right`, `:left` or `:center`
236
+ - `related_widget` - editable or focusable widget associated with this label.
237
+ - `mnemonic` - short-cut used to shift access to `related_widget`
238
+ - `print_label` - override the usual printing of a label. A label usually prints in one colour and attribute (or combination of attributes. However, for any customized printing of a label, one can override this method at the instance level.
239
+
240
+ ### Field
241
+
242
+ This is an entry field. Text may be edited in a `Field`. Various validations are possible. Custom validations may be specified.
243
+
244
+ ```ruby
245
+ w = Field.new( :name => "name", :row => 1, :col => 1 , :width => 50)
246
+ w.color_pair = CP_CYAN
247
+ w.attr = FFI::NCurses::A_REVERSE
248
+ w.highlight_color_pair = CP_YELLOW
249
+ w.highlight_attr = REVERSE
250
+ w.null_allowed = true
251
+ ```
252
+
253
+ The above example shows creation of an editable field. The field has been further customized to have a different color when it is in focus (highlighted).
254
+
255
+
256
+ Other customizations of field are as follows:
257
+ ```ruby
258
+ w.chars_allowed = /[\w\+\.\@]/
259
+ email.valid_regex = /\w+\@\w+\.\w+/
260
+ age.valid_range = (18..100)
261
+ w.type = :integer
262
+ comment.maxlen = 100
263
+ ```
264
+ Validations are executed when the user exits a field, and a failed validation will throw a `FieldValidationException`
265
+ A custom validation can be given as a block to the `:CHANGED` event. More about this in events.
266
+
267
+ Field (like all focusable widgets) has events such as `:ON_LEAVE` `ON_ENTER` `:CHANGED` . Field also has an event`:CHANGE`.
268
+ - `:CHANGE` is called for each character inserted or removed from the buffer. This allows for processing to be attached to each character entered in the field.
269
+ - `:CHANGED` is called upon leaving the field, if the contents were changed.
270
+ - `:PROPERTY_CHANGE` - all widgets have certain properties which when changed result in immediate redrawing of the widget. At the same time, a program may attach processing to that change. A property may be disallowed to change by throwing a `PropertyVetoException`.
271
+
272
+ Some methods of `Field` are:
273
+
274
+ - `text` (or `default`) for setting starting value of field.
275
+ - `maxlen` - maximum length allowed during entry
276
+ - `values` - list of valid values
277
+ - `valid_range` - valid numeric range
278
+ - `valid_regex` - valid regular expression for text entered
279
+ - `above` - lower limit for numeric value (value should be above this)
280
+ - `below` - upper limit for numeric value (value should be below this)
281
+ - `mask` - character to show for each character entered (useful for password entry)
282
+ - `null_allowed` - true or false. Can field be left blank.
283
+ - `type` - specify what characters may be entered in the field. Can be:
284
+ :integer, :float, :alpha, :alnum, Float, Integer, Numeric. A regexp may also be passed in.
285
+
286
+ > ##### Exercise
287
+ >
288
+ >Make a program with a label and a field. Do not add any validations or ranges to it. Get it to work.
289
+ >
290
+ >Try various validations on it. At the time of writing this (0.1.1) on_leave is not triggered as there is only one field. FIXME. So make a second field. What happens when you enter data that fails the validation ?
291
+ >
292
+ >Add a `rescue` block after the `form.handle_key`. How can you display the error to the user ? See umbra.rb for ways to popup the exception string.
293
+ >
294
+ >Make a second label and field. Use mnemonics and try out the hotkeys.
295
+ >
296
+ >A minimal sample is present as tut/field.rb.
297
+
298
+
299
+
300
+ ### LabeledField
301
+
302
+ A labeled field associates a label and a field. This helps in printing a label and its associated field side by side. Also, a mnemonic will automatically change focus to its related field. `LabeledField` extends Field and so has all the properties of a `Field`. In addition, it has the following:
303
+
304
+ - `label` - String to print
305
+ - `lrow` and `lcol` - labels position
306
+ - `label_color_pair`- color pair of label
307
+ - `label_attr` - attribute of label
308
+ - `label_highlight_color_pair` - color pair of label when field is in focus
309
+ - `label_highlight_attr` - attribute of label when field is in focus.
310
+ - `mnemonic` - shortcut key for moving focus to this field.
311
+
312
+
313
+
314
+
315
+ ```ruby
316
+ lf = LabeledField.new( :name => "name", :row => 1, :col => 15 , :width => 20,
317
+ label: "Name: ", :label_highlight_attr => BOLD
318
+ )
319
+ ```
320
+
321
+ >#### Exercise
322
+ >
323
+ >Create a form with two labeled fields.
324
+ >
325
+ >Try out different color_pairs and highlight_color_pairs and attributes for the field and label.
326
+ >
327
+ >What happens when you specify `lcol` and when you don't ?
328
+ >
329
+ >Place a label on the bottom of the screen and try printing the number of characters typed in the current field. The number must change as the user types. (Hint 1 below)
330
+ >
331
+ >Place another label on the screen and print the time on it. The time should update even when the user does not type. (Hint 2 below).
332
+ >
333
+ >
334
+ >Hint 1: Use `:CHANGE` event. It passes an object of class `InputDataEvent`. You might use `text` or `source` (returns the Field object).
335
+ >
336
+ >Hint 2: You can do this inside the key loop when ch is -1. Use the `text` method of the Label. Is is not updating ?
337
+ >You will need to call `form.repaint`.
338
+ >
339
+ >
340
+ >A minimal sample is present as tut/labfield.rb. You can also see examples/ex21.rb.
341
+
342
+
343
+ ### Buttons
344
+
345
+ Button is a action related widget with a label and an action that fires when a user presses SPACE on it. The `:PRESS` event is associated with the space bar key. A button may also have a mnemonic that fires it's event from anywhere on the form.
346
+
347
+ In addition to the properties of the `Widget` superclass, button also has:
348
+
349
+ - `mnemonic`
350
+ - `surround_chars` - the characters on the two sides of the button, by default square brackets.
351
+
352
+ ```ruby
353
+ ok_butt = Button.new( :name => 'ok', :text => 'Ok', :row => 2, :col => 10, :width => 10 ,
354
+ :color_pair => 0, :mnemonic => 'O')
355
+ ```
356
+
357
+ > ##### Exercise
358
+ >
359
+ >Create a button with text "Cancel" which closes the window.
360
+ >Attach a code block to the Ok button to write the contents of each field to the log file and then close the window.
361
+ >
362
+ >You may see examples/ex3.rb.
363
+
364
+ `Button` is the superclass of `ToggleButton,` `RadioButton` and `Checkbox`.
365
+
366
+ ### Togglebutton
367
+
368
+ This button has an on and off state.
369
+
370
+ - `onvalue` and `offvalue` - set the values for on and off state
371
+ - `value` - get which of onvalue and offvalue is current (boolean)
372
+ - `checked?` - returns true if onvalue, false if offvalue
373
+ - `checked` - programmatically set value to true or false
374
+
375
+
376
+ ```ruby
377
+
378
+ togglebutton = ToggleButton.new()
379
+ togglebutton.value = true
380
+ togglebutton.onvalue = " Toggle Down "
381
+ togglebutton.offvalue =" Untoggle "
382
+ togglebutton.row = row
383
+ togglebutton.col = col
384
+
385
+ togglebutton.command do
386
+ if togglebutton.value
387
+ message_label.text = "Toggle button was pressed"
388
+ else
389
+ message_label.text = "UNToggle button was pressed"
390
+ end
391
+ end
392
+
393
+ togglebutton.checked(true) ## simulate keypress
394
+ togglebutton.checked? ## => true
395
+ togglebutton.value ## => true
396
+
397
+ ```
398
+
399
+ `Widget` the common ancestor to all user-interface controls defined a method `command`, which takes a block. That block is executed when a button is fired. For other widgets, it is fired when the `:CHANGED` event is called.
400
+
401
+ ### Checkbox
402
+
403
+ A checkbox is a button containing some text with a square on the left (or right). The square may be checked or unchecked.
404
+ Checkbox extends `ToggleButton`.
405
+
406
+
407
+ It adds the following properties to ToggleButton.
408
+
409
+ - `align_right` - boolean, show the button on the right. Default is false.
410
+
411
+ `value` may be used to set the initial value, or retrieve the value at any time.
412
+
413
+ ```ruby
414
+ row = 10
415
+ col = 10
416
+ check = Checkbox.new text: "No Frames", value: true, row: row+1, col: col, mnemonic: "N"
417
+ check1 = Checkbox.new text: "Use https", value: false, row: row+2, col: col, mnemonic: "U"
418
+ ```
419
+
420
+ A code block may be attached to the clicking of checkboxes either using `command` or binding to `:PRESS`.
421
+ In this example, a previously created label is updated whenever the checkboxes are clicked.
422
+
423
+
424
+ ```ruby
425
+ form.add_widget check, check1
426
+ [ check, check1 ].each do |cb|
427
+ cb.command do
428
+ message_label.text = "#{cb.text} is now #{cb.value}"
429
+ end
430
+ end
431
+ ```
432
+
433
+ The above is similar to:
434
+
435
+ ```ruby
436
+ check.bind_event(:PRESS) { |cb|
437
+ message_label.text = "#{cb.text} is now #{cb.value}"
438
+ }
439
+ ```
440
+
441
+ ### RadioButton
442
+
443
+
444
+
445
+
446
+ ### Multiline
447
+
448
+ Multiline is a parent class for all widgets that display multiple rows/lines and allow scrolling. It has the following attributes:
449
+
450
+ - `current_index` - get the index of row the cursor is on
451
+ - `list` - get or set the array of String being displayed
452
+ - `row_count` - get size of array
453
+ - `current_row` - get the row having focus
454
+
455
+ Multiline allows customizing display of each row displayed by the following methods:
456
+
457
+ - `state_of_row(index)` - customize state of row based on index. One may add a new state.
458
+ - `color_of_row(index, state)` - customize color of row based on index and state. By default, the current row is highlighted whereas all other rows use NORMAL attribute.
459
+ - `value_of_row(line, index, state)` - if the array contains data other than strings (such as an Array),
460
+ then customize how the data is to be converted to text.
461
+ - `print_row` - completely customize the printing of the row if the above are not sufficient.
462
+
463
+
464
+ Multiline exposes three events: `:ENTER_ROW`, `:LEAVE_ROW` and `:PRESS`. Press is triggered when the `RETURN` key is pressed on a row. This is not the same as selection. One may get `current_index` and `curpos` (cursor position) from the object.
465
+
466
+ A row may have one of three states.
467
+
468
+ - :HIGHLIGHTED - the focus is inside the listbox and the cursor is on this row
469
+ - :CURRENT - the focus is NOT inside the listbox but the current row had focus.
470
+ - :NORMAL - all other rows
471
+
472
+ Only one row can have :HIGHLIGHTED or :CURRENT.
473
+
474
+
475
+ ```ruby
476
+ obj.command do |o|
477
+ o.current_index ## => index under cursor
478
+ o.current_row ## => row under cursor (converted to text)
479
+ o.curpos ## => position of cursor (if you want to determine word under cursor)
480
+ end
481
+ ```
482
+
483
+ Passing a code block to the `command` method is identical to attaching it to the `:PRESS` event handler.
484
+
485
+ The `:CHANGED` event is fired whenever an array is passed to the `list=` method.
486
+
487
+ #### Traversal
488
+
489
+ In addition to arrow keys, one may use "j" and "k" for down and up. Other keys are:
490
+
491
+ - "g" - first row
492
+ - "G" - last row
493
+ - C-d - scroll down
494
+ - C-u - scroll up
495
+ - C-b - scroll backward
496
+ - C-f - scroll forward
497
+ - C-a - beginning of row
498
+ - C-e - end of row
499
+ - C-l - scroll right
500
+ - C-j - scroll left (C-h not working ??)
501
+ - Spacebar - scroll forward (same as C-f)
502
+
503
+ ### Listbox
504
+
505
+ Listbox is an extension of `Multiline` (parent class of all widgets that contain multiple lines of text such as listbox and tree and textbox). It displays an array of Strings, and allows scrolling. It adds the capability of selection to `Multiline`. At present, only single selection is allowed.
506
+
507
+ It adds various visual elements to `Multiline` such as a mark on the left of the item/line denoting whether an item is selected or not, and whether a item/row is current (focussed) or not. By default, a selected row displays an "x" on the left. The current row displays a greater than symbol ">".
508
+
509
+ Listbox adds the following attributes to Multiline.
510
+
511
+ - `selected_index` - get index of row selected (can be nil)
512
+ - `selected_mark` - character to be displayed for selected row (default is "x")
513
+ - `unselected_mark` - character to be displayed for other rows (default blank)
514
+ - `current_mark` - character to be displayed for current row (default is ">")
515
+ - `selection_key` - key that selects current row (currently the default is "s")
516
+ - `selected_color_pair`
517
+ - `selected_attr`
518
+
519
+ Listbox adds the `:SELECT_ROW` which is fired upon selection or deselection of a row. Use `selected_index` to determine which row has been selected. A value of nil implies the current row was deselected.
520
+
521
+ ```ruby
522
+ alist = []
523
+ (1..50).each do |i|
524
+ alist << "#{i} entry"
525
+ end
526
+
527
+ lb = Listbox.new list: alist, row: 1, col: 1, width: 20, col: -2
528
+
529
+ form.add_widget lb
530
+ ```
531
+
532
+ Listbox adds the `:SELECTED` state to the existing states a row may have (:CURRENT, :HIGHLIGHTED, :NORMAL).
533
+
534
+ Listboxes allow further customization of the display of each row through the following:
535
+
536
+ - `mark_of_row(index, state)` - this returns the mark to be used for the row offset or state. Typically, this returns a single character. A `:SELECTED` row by default has an 'X' mark, a `:CURRENT` row has a '&gt;'.
537
+
538
+ Listbox adds an attribute for SELECTED rows.
539
+
540
+ Some of the methods of listboxes are:
541
+
542
+ - `list=` - supply array of values to populate listbox
543
+ - `select_row(n)` - select given row
544
+ - `unselect_row(n)` - unselect given row
545
+ - `toggle_selection` - toggle selection status of given row
546
+ - `clear_selection` - clear selected index/es.
547
+
548
+ Inherited from Multiline:
549
+
550
+ - `current_index` - get the index of current row
551
+ - `current_row` - get the value of current row
552
+
553
+
554
+
555
+ ### Box
556
+
557
+ A Box is a container for one or more widgets. It paints a border around its periphery, and can place its components horizontally or vertically.
558
+
559
+ - `visible` - get or set visible property of border
560
+ - `title` - title to display on top line
561
+ - `justify` - alignment of title
562
+ - `widgets` - returns array of components
563
+ - `widget` - returns single widget if only one set
564
+
565
+ Objects are placed inside the box using either of these methods:
566
+
567
+ - `fill` - fill the box with given widget (single)
568
+ - `stack` - stack the given variable list of widgets horizontally (alias `add`)
569
+ - `flow` - stack the given variable list of widgets vertically
570
+
571
+ Those who have used the `canis` gem, will recall that multiline widgets had the option of drawing their own border. This has been simplified in `umbra` by using the Box widget which does the same thing.
572
+
573
+ A box is created by giving its four coordinates.
574
+
575
+ box = Box.new row: 4, col: 2, width: 80, height: 20
576
+
577
+ Negative width and height can be given to stretch the box to those many rows or columns from the end.
578
+ In the example below, a listbox has been created without dimensions, since the box will size it.
579
+
580
+
581
+ lb = Listbox.new list: alist
582
+ box.fill lb
583
+
50
584
 
51
- ## Development
52
585
 
53
- After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
586
+ ### Textbox
54
587
 
55
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
588
+ Textbox extends Multiline and offers simple text display facility.
589
+
590
+ It adds `:CURSOR_MOVE` event which reports cursor movement laterally in addition to Multilines vertical movement.
591
+
592
+ Additional methods:
593
+
594
+ - `file_name(String) - name of file to load and display
595
+
596
+ Additional keystrokes:
597
+
598
+ - `w` - move to next word TODO
599
+ - `b` - move to previous word TODO
600
+
601
+ Textbox doesn't support row selection, but its always possible to use the :PRESS event as a row selection.
602
+
603
+
604
+ ```ruby
605
+ filename = "readme.md"
606
+ box = Box.new row: 4, col: 2, width: 50, height: 20
607
+
608
+ tb = Textbox.new file_name: filename
609
+
610
+ box.fill tb
611
+ box.title = filename
612
+ ```
613
+ ### Tabular
614
+
615
+ Tabular is a data model, not a widget. It takes an array of arrays. It can render the same as an array of strings and may thus be used to convert a database resultset to a format that may be used as input to a Textbox or even a list.
616
+
617
+ ```ruby
618
+ t = Tabular.new(['a', 'b'], [1, 2], [3, 4], [5,6])
619
+ t.column_width(0, 3)
620
+ t.column_align(1, :right)
621
+
622
+ lb = Listbox.new list: t.render
623
+ box.fill lb
624
+ ```
625
+
626
+
627
+ ```ruby
628
+ t = Tabular.new ['a', 'b']
629
+ t << [1, 2]
630
+ t << [3, 4]
631
+ t << [4, 6]
632
+ t << [8, 6]
633
+ t << [2, 6]
634
+ lb1 = Textbox.new list: t.render
635
+ box1.fill lb1
636
+ ```
637
+
638
+ Tabular allows for customizing columns as follows:
639
+
640
+ - `column_width(n, w)` - specify width of given column
641
+ - `column_align(n, symbol)` - specify alignment of given column ( `:left` `:right` `:center` )
642
+ - `column_hidden(n, boolean)` - hide or unhide given column (true or false)
643
+ - `column_count` - returns count of visible columns
644
+ - `each_column` - yields visible columns
645
+ - `visible_columns(row)` - yields visible column data for given row
646
+ - `visible_column_names` - yields visible column names or returns array
647
+ - `add` (aliased to `add_row` and `<<`) - add a row to tabular
648
+
649
+ ### Table
650
+
651
+ Table uses `Tabular` as its data model, and maintains column header and column data information. Thus, it is column-aware.
652
+
653
+ ```ruby
654
+ table = Table.new(columns: ['a', 'b'], data: [[1, 2], [3, 4], [5,6]])
655
+ box.fill table
656
+
657
+ table1 = Table.new columns: ['a', 'b']
658
+ table1 << [8, 6]
659
+ table1 << [1, 2]
660
+ table1 << [3, 4]
661
+ table1 << [4, 6]
662
+ ```
663
+
664
+
665
+ Table may either take a pre-created Tabular object using `:tabular`, or else if will create a Tabular object from `columns` and `data` provided.
666
+
667
+ Table provides the following attributes:
668
+
669
+ - `tabular` - set a tabular object as the Table's data
670
+ - `header_color_pair`
671
+ - `header_attr`
672
+
673
+ Others:
674
+ - `data` retrieve data portion of table
675
+ - `row_count` - number of rows of data
676
+ - `current_id` - return identifier of current row (assuming first column is rowid from table)
677
+ - `current_row_as_array` - return current row as array
678
+ - `current_row_as_hash` - return current row as hash with column name as key
679
+ - `next_column` - moves cursor to next column (mapped to w)
680
+ - `prev_column` - moves cursor to previous column (mapped to b)
681
+ - `header_row?` - is cursor on header row, boolean
682
+ - `color_of_data_row(index, state, data_index)` - customize color of data row
683
+ - `color_of_header_row(index, state)` - customize color of header row
684
+ - `convert_value_to_text(current_row, format_string, index)` - customize conversion of current row to String
685
+
686
+ Table forwards several methods to its `Tabular` data model such as `add`, `<<`, `column_width`, `column_align` and `column_hidden`.
687
+
688
+ > ##### Exercise
689
+ >
690
+ >Create a window with two tables. Populate one with the output of `ls -l` and another with the process info (using the `ps` command with appropriate options).
691
+ >Create a button which refreshes the processes upon clicking.
692
+ >You may also map a key on the form level (say F5) to refresh the process info.
693
+ >
694
+ >Assign different colors to the columns of the process lister.
695
+ >Color the rows of the directory lister based on file type, or any other logic (file size).
696
+
697
+
698
+ ### Colors
699
+
700
+ This library defines a few color pairs based on ncurses defaults color constants:
701
+
702
+ CP_BLACK = 0
703
+ CP_RED = 1
704
+ CP_GREEN = 2
705
+ CP_YELLOW = 3
706
+ CP_BLUE = 4
707
+ CP_MAGENTA = 5
708
+ CP_CYAN = 6
709
+ CP_WHITE = 7
710
+
711
+ These color pairs use the color mentioned as the foreground and the terminal background color as the background color.
712
+ This expects the background color to be black or very dark.
713
+
714
+ Beyond this you may create your color pairs. Usually a color pair is created in this manner.
715
+
716
+ FFI::NCurses.init_pair(10, FFI::NCurses::BLACK, FFI::NCurses::CYAN)
717
+
718
+ However, there is the option to use the following method to create a color_pair.
719
+
720
+ create_color_pair(bgcolor, fgcolor)
721
+
722
+ This will return an Integer which can be used wherever a color pair is required, such as when specifying color_pair of a widget. It will always return the same Integer for the same combination of two colors. Thus there should be no need for you to cache this.
723
+
724
+
725
+
726
+ ### Event Handling
727
+
728
+ Various events for an instance of a widget may be subscribed to. A code block attached to the event will be called when the event takes place. Some of the common events are:
729
+
730
+ - `ON_ENTER` - executed when focus enters a widget
731
+ - `ON_LEAVE` - executed when focus leaves a widget
732
+ - `CHANGED` - executed when the data is changed. In the case of a Field, this is when user exits after changing.
733
+ In the case of `Multiline` widgets such as `Listbox` and `Table` this is whenever the list if changed.
734
+ - `PROPERTY_CHANGE` - executed whenever a property is changed. Properties are defined using `attr_property`. Properties such as color_pair, attr, width, title, alignment fire this event when changed _after_ the object is first displayed.
735
+ - `ENTER_ROW` - In `Multiline` widgets, whenever user enters a row.
736
+ - `LEAVE_ROW` - In `Multiline` widgets, whenever user leaves a row.
737
+
738
+ An object's `bind_event` is used to attach a code block to an event.
739
+
740
+ field.bind_event(:CHANGED) { |f| do_some_validation(f) }
741
+
742
+ list.bind_event(:ENTER_ROW) { |l| display some data related to current row in status line .... }
743
+
744
+
745
+ ### Key Bindings
746
+
747
+ For an object, or for the form, keys may be bound to a code block. All functionality in the system is bound to a code block, making it possible to override provided behavior, although that is not recommended. Tab, backtab and Escape may not be over-ridden.
748
+
749
+ form.bind_key(KEY_F1, "Help") { help() }
750
+
751
+ table.bind_key(?s, "search") { search }
752
+
753
+ list.bind_key(FFI::NCurses::KEY_CTRL_A, 'cursor home') { cursor_home }
754
+
755
+
756
+ ## More examples
757
+
758
+ See examples directory for code samples for all widgets. Be sure the run all the examples to see the capabilities of the library and the widgets.
56
759
 
57
760
  ## Contributing
58
761
 
762
+ Please go through the source, and suggest improvements to the design and code.
763
+ How can we make this simpler, clearer ?
764
+
59
765
  Bug reports and pull requests are welcome on GitHub at https://github.com/mare-imbrium/umbra.
60
766
 
61
767
  ## License