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 +4 -4
- data/CHANGELOG +6 -0
- data/README.md +712 -6
- data/examples/ex1.rb +2 -3
- data/examples/ex3.rb +2 -3
- data/examples/ex4.rb +1 -2
- data/examples/ex5.rb +4 -3
- data/examples/exbox.rb +1 -1
- data/examples/extab3.rb +1 -2
- data/lib/umbra.rb +2 -0
- data/lib/umbra/box.rb +23 -9
- data/lib/umbra/button.rb +13 -14
- data/lib/umbra/checkbox.rb +3 -3
- data/lib/umbra/eventhandler.rb +2 -3
- data/lib/umbra/field.rb +23 -12
- data/lib/umbra/form.rb +19 -109
- data/lib/umbra/listbox.rb +26 -20
- data/lib/umbra/menu.rb +2 -2
- data/lib/umbra/messagebox.rb +2 -2
- data/lib/umbra/multiline.rb +95 -32
- data/lib/umbra/pad.rb +5 -3
- data/lib/umbra/radiobutton.rb +2 -2
- data/lib/umbra/tabular.rb +5 -2
- data/lib/umbra/textbox.rb +14 -153
- data/lib/umbra/togglebutton.rb +75 -112
- data/lib/umbra/version.rb +1 -1
- data/lib/umbra/widget.rb +9 -14
- data/lib/umbra/window.rb +8 -2
- data/tut/field.rb +72 -0
- data/tut/hello.rb +17 -0
- data/tut/label_hello.rb +28 -0
- data/tut/labfield.rb +63 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6154c2af0ffac0eba6eb146efb61ee935b01b95299f828231e62ece1aa52760b
|
4
|
+
data.tar.gz: bb37af7243be9f5ba18252592a9a1a95b74157e66d5bb07d1ca1c3fd7cdb46fe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cae7cbbc5818c5831f02bf7aa94d44c2912420f2ddb4dea18ff35fc0fff011e557ca5de83fb98e9d950086fa75f01fa160f91eceb02c8b9a118e62dffa72afb2
|
7
|
+
data.tar.gz: 39bf8e78fddcde77125371be0a8b9a71bd70f59c242c2328c54ea1cf26d031218e532f41dacdbfd3fd34d521ee9869d7ffb856bfbf7d3f1b6ef7b0539b16c76f
|
data/CHANGELOG
CHANGED
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 '
|
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
|
-
|
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 '>'.
|
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
|
-
|
586
|
+
### Textbox
|
54
587
|
|
55
|
-
|
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
|