reterm 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +131 -0
  4. data/designer/main.rb +10 -0
  5. data/designer/src/ComponentParams.rb +120 -0
  6. data/designer/src/ComponentsList.rb +90 -0
  7. data/designer/src/CreatedList.rb +156 -0
  8. data/designer/src/Designer.rb +114 -0
  9. data/designer/src/ToggleArea.rb +67 -0
  10. data/designer/src/common.rb +1 -0
  11. data/designer/src/component_factory.rb +30 -0
  12. data/designer/src/component_map.rb +116 -0
  13. data/designer/src/glade/ComponentParams.glade +96 -0
  14. data/designer/src/glade/Designer.glade +184 -0
  15. data/designer/src/images/add.png +0 -0
  16. data/designer/src/images/asciitext.png +0 -0
  17. data/designer/src/images/blank.png +0 -0
  18. data/designer/src/images/blank_sm.png +0 -0
  19. data/designer/src/images/button.png +0 -0
  20. data/designer/src/images/dial.png +0 -0
  21. data/designer/src/images/entry.png +0 -0
  22. data/designer/src/images/hslider.png +0 -0
  23. data/designer/src/images/label.png +0 -0
  24. data/designer/src/images/matrix.png +0 -0
  25. data/designer/src/images/orig/Check.png +0 -0
  26. data/designer/src/images/orig/ascii_text.png +0 -0
  27. data/designer/src/images/orig/button.png +0 -0
  28. data/designer/src/images/orig/dial.png +0 -0
  29. data/designer/src/images/orig/entry.png +0 -0
  30. data/designer/src/images/orig/hslider.png +0 -0
  31. data/designer/src/images/orig/label.png +0 -0
  32. data/designer/src/images/orig/matrix.png +0 -0
  33. data/designer/src/images/orig/radio.png +0 -0
  34. data/designer/src/images/orig/rocker.png +0 -0
  35. data/designer/src/images/orig/slist.png +0 -0
  36. data/designer/src/images/orig/vslider.png +0 -0
  37. data/designer/src/images/radio.png +0 -0
  38. data/designer/src/images/remove.png +0 -0
  39. data/designer/src/images/rocker.png +0 -0
  40. data/designer/src/images/slist.png +0 -0
  41. data/designer/src/images/vslider.png +0 -0
  42. data/lib/reterm.rb +22 -0
  43. data/lib/reterm/color_pair.rb +66 -0
  44. data/lib/reterm/component.rb +40 -0
  45. data/lib/reterm/components.rb +32 -0
  46. data/lib/reterm/components/ascii_text.rb +46 -0
  47. data/lib/reterm/components/button.rb +31 -0
  48. data/lib/reterm/components/dial.rb +95 -0
  49. data/lib/reterm/components/dialog.rb +34 -0
  50. data/lib/reterm/components/entry.rb +38 -0
  51. data/lib/reterm/components/hslider.rb +32 -0
  52. data/lib/reterm/components/image.rb +55 -0
  53. data/lib/reterm/components/label.rb +20 -0
  54. data/lib/reterm/components/matrix.rb +43 -0
  55. data/lib/reterm/components/radio.rb +33 -0
  56. data/lib/reterm/components/rocker.rb +71 -0
  57. data/lib/reterm/components/slist.rb +32 -0
  58. data/lib/reterm/components/template.rb +23 -0
  59. data/lib/reterm/components/vslider.rb +61 -0
  60. data/lib/reterm/init.rb +95 -0
  61. data/lib/reterm/layout.rb +63 -0
  62. data/lib/reterm/layouts.rb +16 -0
  63. data/lib/reterm/layouts/horizontal.rb +19 -0
  64. data/lib/reterm/layouts/vertical.rb +19 -0
  65. data/lib/reterm/loader.rb +126 -0
  66. data/lib/reterm/menu.rb +81 -0
  67. data/lib/reterm/mixins/cdk_component.rb +45 -0
  68. data/lib/reterm/mixins/component_input.rb +37 -0
  69. data/lib/reterm/mixins/event_dispatcher.rb +20 -0
  70. data/lib/reterm/mixins/nav_input.rb +126 -0
  71. data/lib/reterm/panel.rb +37 -0
  72. data/lib/reterm/resize.rb +27 -0
  73. data/lib/reterm/terminal.rb +33 -0
  74. data/lib/reterm/version.rb +3 -0
  75. data/lib/reterm/window.rb +260 -0
  76. metadata +155 -0
@@ -0,0 +1,63 @@
1
+ module RETerm
2
+ # Provides mechanism to orgranize on screen components according
3
+ # to rules defined by subclasses.
4
+ #
5
+ # Layouts themselves are specialized types of components as they
6
+ # are intended to be associated with windows in which to be rendered
7
+ class Layout < Component
8
+ include NavInput
9
+
10
+ # TODO padding / margin support
11
+
12
+ attr_accessor :children
13
+
14
+ def initialize(args={})
15
+ @children ||= []
16
+ end
17
+
18
+ # Subclasses should override this method returning
19
+ # boolean if child window exceeds bounds of layout
20
+ def exceeds_bounds?(child)
21
+ raise "NotImplemented"
22
+ end
23
+
24
+ # Return boolean indicating if any child component
25
+ # is activatable
26
+ def activatable?
27
+ children.any? { |c| c.component.activatable? }
28
+ end
29
+
30
+ # Create new child window and add it to layout
31
+ def add_child(h={})
32
+ child = window.create_child(h)
33
+
34
+ if child.win.nil?
35
+ raise ArgumentError, "could not create child window"
36
+ end
37
+
38
+ if exceeds_bounds?(child)
39
+ window.del_child(child) unless child.win.nil?
40
+ raise ArgumentError, "child exceeds bounds"
41
+ end
42
+
43
+ children << child
44
+ update_reterm
45
+ child
46
+ end
47
+
48
+ # Draw all layout children
49
+ def draw!
50
+ children.each { |c| c.draw! }
51
+ end
52
+
53
+ # Activates layout by dispatching to navigation
54
+ # input system.
55
+ #
56
+ # @see NavInput
57
+ def activate!
58
+ draw!
59
+ update_reterm
60
+ handle_input
61
+ end
62
+ end # class Layout
63
+ end # module RETerm
@@ -0,0 +1,16 @@
1
+ module RETerm
2
+ # Encapsulates all Layouts currently implemented
3
+ module Layouts
4
+ def self.all
5
+ @a ||= Layouts.constants
6
+ end
7
+
8
+ def self.names
9
+ @n ||= all.collect { |l| l.to_s }
10
+ end
11
+ end
12
+ end
13
+
14
+ require 'reterm/layout'
15
+ require 'reterm/layouts/horizontal'
16
+ require 'reterm/layouts/vertical'
@@ -0,0 +1,19 @@
1
+ module RETerm
2
+ module Layouts
3
+ # Layout which arrainges items horizontally across screen cols
4
+ class Horizontal < Layout
5
+ def current_cols
6
+ children.sum { |c| c.cols } + 1
7
+ end
8
+
9
+ def exceeds_bounds?(child)
10
+ child.rows > window.rows || (current_cols + child.cols) > window.cols
11
+ end
12
+
13
+ def add_child(h={})
14
+ # set x/y to next appropriate location
15
+ super(h.merge(:y => 1, :x => current_cols))
16
+ end
17
+ end # class Horizontal
18
+ end # module Layouts
19
+ end # module RETerm
@@ -0,0 +1,19 @@
1
+ module RETerm
2
+ module Layouts
3
+ # Layout which arrainges items vertically down screen rows
4
+ class Vertical < Layout
5
+ def current_rows
6
+ children.sum { |c| c.rows } + 1
7
+ end
8
+
9
+ def exceeds_bounds?(child)
10
+ child.cols > window.cols || (current_rows + child.rows) > window.rows
11
+ end
12
+
13
+ def add_child(h={})
14
+ # set x/y to next appropriate location
15
+ super(h.merge(:y => current_rows, :x => 1))
16
+ end
17
+ end # class Horizontal
18
+ end # module Layouts
19
+ end # module RETerm
@@ -0,0 +1,126 @@
1
+ module RETerm
2
+ # JSON schema loader, instantiates RETerm subsystem from schema
3
+ # stored in a JSON string. Currently this schema does not have a
4
+ # formal definition, but see examples/ for what can currently be
5
+ # done.
6
+ class Loader
7
+ # Initialie new loader from JSON string
8
+ def initialize(str)
9
+ require 'json'
10
+ parse_doc(str)
11
+ end
12
+
13
+ # Return top level window in loader
14
+ def window
15
+ @win
16
+ end
17
+
18
+ private
19
+
20
+ def parse_doc(str)
21
+ @j = JSON.parse(str)
22
+
23
+ raise ArgumentError unless @j.is_a?(Hash)
24
+ raise ArgumentError unless @j.key?("window")
25
+
26
+ parse_colors(@j['colors']) if @j.key?('colors')
27
+
28
+ @win = parse_win(@j['window'])
29
+ update_reterm
30
+ end
31
+
32
+ def parse_colors(colors)
33
+ raise ArgumentError unless colors.is_a?(Array)
34
+ colors.each { |c|
35
+ raise ArgumentError unless c.is_a?(Array) && c.size >= 3
36
+ ColorPair.register(*c)
37
+ }
38
+ end
39
+
40
+ def parse_win(win)
41
+ raise ArgumentError unless win.is_a?(Hash)
42
+
43
+ args = {}
44
+
45
+ args[:rows] = win['rows'] if win.key?('rows')
46
+ args[:cols] = win['cols'] if win.key?('cols')
47
+
48
+ w = Window.new args
49
+ parse_child(w, win)
50
+
51
+ w.border! if win.key?('border') && !!win['border']
52
+ w.colors = ColorPair.for(win['colors']) if win.key?('colors')
53
+
54
+ w
55
+ end
56
+
57
+ def parse_child(win, parent)
58
+ raise ArgumentError if parent.key?('component') && parent.key?('layout')
59
+ raise ArgumentError if !parent.key?('component') && !parent.key?('layout')
60
+
61
+ if parent.key?('component')
62
+ parse_component win, parent['component']
63
+
64
+ else # if parent.key?('layout')
65
+ parse_layout win, parent['layout']
66
+ end
67
+ end
68
+
69
+ def prep_init(init)
70
+ raise ArgumentError unless init.is_a?(Hash)
71
+ init = Hash[init] # make copy
72
+
73
+ init.keys.each { |k|
74
+ init[k.intern] = init[k] # XXX unsafe intern
75
+ init.delete(k)
76
+ }
77
+
78
+ init
79
+ end
80
+
81
+ def parse_component(win, c)
82
+ raise ArgumentError unless c.is_a?(Hash)
83
+ raise ArgumentError unless c.key?("type") ||
84
+ !Components.names.include?(c["type"])
85
+
86
+ init = c.key?("init") ? prep_init(c["init"]) : {}
87
+ c = RETerm::Components.const_get(c["type"]).new(init)
88
+ win.component = c
89
+ c
90
+ end
91
+
92
+ def parse_layout(win, l)
93
+ raise ArgumentError unless l.is_a?(Hash)
94
+ raise ArgumentError unless l.key?("type") ||
95
+ !Layouts.names.include?(l["type"])
96
+
97
+ init = l.key?("init") ? prep_init(l["init"]) : {}
98
+ lc = RETerm::Layouts.const_get(l["type"]).new(init)
99
+ win.component = lc
100
+ parse_layout_children(lc, l["children"]) if l.key?("children")
101
+ lc
102
+ end
103
+
104
+ def parse_layout_children(l, children)
105
+ raise ArgumentError unless children.is_a?(Array)
106
+
107
+ children.each { |child|
108
+ raise ArgumentError unless child.is_a?(Hash)
109
+ rows = child['rows'] if child.key?('rows')
110
+ cols = child['cols'] if child.key?('cols')
111
+
112
+ cw = l.add_child :rows => rows,
113
+ :cols => cols
114
+
115
+ cw.border! if child.key?('border') && !!child['border']
116
+ cw.colors = ColorPair.for(child['colors']) if child.key?('colors')
117
+
118
+ cw.component = parse_child(cw, child)
119
+ }
120
+ end
121
+ end # class Loader
122
+
123
+ def load_reterm(str)
124
+ Loader.new(str).window
125
+ end
126
+ end # module RETerm
@@ -0,0 +1,81 @@
1
+ module RETerm
2
+ # Graphical menu rendered on screen
3
+ #
4
+ # TODO: look into working this into std component/activation subsystem
5
+ class Menu
6
+ include EventDispatcher
7
+
8
+ attr_accessor :window
9
+
10
+ # Menus should be instantiated with a hash of label/value
11
+ # pairs to assign to menu
12
+ def initialize(items={})
13
+ @items = items
14
+ @menu = Ncurses::Menu.new_menu(items.collect { |k,v|
15
+ Ncurses::Menu.new_item(k.to_s, v.to_s)
16
+ })
17
+
18
+ @menu.menu_opts_off(Ncurses::Menu::O_SHOWDESC)
19
+ end
20
+
21
+ # Return number of rows in menu
22
+ def rows
23
+ @items.size
24
+ end
25
+
26
+ # Return number of cols in menu
27
+ def cols
28
+ @cols ||= @items.keys.max { |k| k.size }.size + 3
29
+ end
30
+
31
+ # Attach menu to specified window
32
+ def attach_to(win)
33
+ @menu.set_menu_win(win.win)
34
+ win.component = self
35
+
36
+ @child_win = win.create_child :rows => rows,
37
+ :cols => cols,
38
+ :x => 1, :y => 1
39
+ @menu.set_menu_sub(@child_win.win)
40
+ @menu.post_menu
41
+
42
+ self
43
+ end
44
+
45
+ # Destroy menu subsystem
46
+ def detach
47
+ @menu.unpost_menu
48
+ end
49
+
50
+ def finalize!
51
+ end
52
+
53
+ # Static map of menu drivers to internal representations.
54
+ #
55
+ # XXX defined here (as opposed to a const) since these
56
+ # consts won't be available until after init_scr
57
+ def driver_map
58
+ @driver_map ||= {
59
+ :left => Ncurses::Menu::REQ_LEFT_ITEM,
60
+ :right => Ncurses::Menu::REQ_RIGHT_ITEM,
61
+ :up => Ncurses::Menu::REQ_UP_ITEM,
62
+ :down => Ncurses::Menu::REQ_DOWN_ITEM,
63
+ :scr_up_line => Ncurses::Menu::REQ_SCR_ULINE,
64
+ :scr_down_line => Ncurses::Menu::REQ_SCR_DLINE,
65
+ :scr_down_page => Ncurses::Menu::REQ_SCR_DPAGE,
66
+ :scr_up_page => Ncurses::Menu::REQ_SCR_UPAGE,
67
+ :first => Ncurses::Menu::REQ_FIRST_ITEM,
68
+ :last => Ncurses::Menu::REQ_LAST_ITEM,
69
+ :next => Ncurses::Menu::REQ_NEXT_ITEM,
70
+ :prev => Ncurses::Menu::REQ_PREV_ITEM,
71
+ :toggle => Ncurses::Menu::REQ_TOGGLE_ITEM
72
+ }
73
+ end
74
+
75
+ # Drive menu from input
76
+ def drive(i)
77
+ i = driver_map[i] if i.is_a?(Symbol)
78
+ Ncurses::Menu.menu_driver(@menu, i)
79
+ end
80
+ end # class Menu
81
+ end # module RETerm
@@ -0,0 +1,45 @@
1
+ module RETerm
2
+ # Mixin used by CDK based component defining cdk-specific helpers
3
+ module CDKComponent
4
+ # Should be implemented in subclass to initialize component
5
+ def _component
6
+ raise "NotImplemented"
7
+ end
8
+
9
+ # Return completely initialized CDK component
10
+ def component
11
+ enable_cdk!
12
+ @component ||= begin
13
+ c = _component
14
+ c.setBackgroundColor("</#{@colors.id}>") if colored?
15
+ c
16
+ end
17
+ end
18
+
19
+ # Assign {ColorPair} to component
20
+ def colors=(c)
21
+ super
22
+ component.setBackgroundColor("</#{@colors.id}>")
23
+ end
24
+
25
+ # Invoke CDK draw routine
26
+ def draw!
27
+ component.draw([])
28
+ end
29
+
30
+ # CDK components may be activated
31
+ def activatable?
32
+ true
33
+ end
34
+
35
+ # Invoke CDK activation routine
36
+ def activate!
37
+ component.activate([])
38
+ end
39
+
40
+ # Return stored value of cdk component
41
+ def value
42
+ component.getValue
43
+ end
44
+ end # module CDKComponent
45
+ end # module RETerm
@@ -0,0 +1,37 @@
1
+ module RETerm
2
+ # Helper mixin defining standard input controls for custom
3
+ # components. 'In House' components included in the project
4
+ # may used this to standarding their usage.
5
+ module ComponentInput
6
+ # Key which if pressed cause the component to
7
+ # lose focus / become deactivated
8
+ QUIT_CONTROLS = [10, 27] # 10 = enter, 27 = ESC
9
+
10
+ # Keys if pressed invoked the increment operation
11
+ INC_CONTROLS = ['+'.ord, Ncurses::KEY_UP, Ncurses::KEY_RIGHT]
12
+
13
+ # Keys if pressed invoked the decrement operation
14
+ DEC_CONTROLS = ['-'.ord, Ncurses::KEY_DOWN, Ncurses::KEY_LEFT]
15
+
16
+ # May be overrideen in subclass, invoked when the user requests
17
+ # an 'increment'
18
+ def on_inc
19
+ end
20
+
21
+ # May be overrideen in subclass, invoked when the user
22
+ # requests a decrement
23
+ def on_dec
24
+ end
25
+
26
+ # Helper to be internally invoked by component on activation
27
+ def handle_input
28
+ while(!QUIT_CONTROLS.include?((ch = window.getch)))
29
+ if INC_CONTROLS.include?(ch)
30
+ on_inc
31
+ elsif DEC_CONTROLS.include?(ch)
32
+ on_dec
33
+ end
34
+ end
35
+ end
36
+ end # module ComponentInput
37
+ end # module RETerm
@@ -0,0 +1,20 @@
1
+ module RETerm
2
+ # Helper mixin defining methods to register event
3
+ # handlers and dispatch events. Events are simply
4
+ # composed parameters which are passed to the
5
+ # callback blocks when dispatched
6
+ module EventDispatcher
7
+ def dispatch(e, h={})
8
+ @event_handlers ||= {}
9
+ @event_handlers[e].each { |eh|
10
+ eh.call h
11
+ } if @event_handlers.key?(e)
12
+ end
13
+
14
+ def handle(e, &bl)
15
+ @event_handlers ||= {}
16
+ @event_handlers[e] ||= []
17
+ @event_handlers[e] << bl
18
+ end
19
+ end # module EventDispatcher
20
+ end # module RETerm
@@ -0,0 +1,126 @@
1
+ module RETerm
2
+ # Helper mixin defining standard navigation controls.
3
+ # Used by layouts and top level components this tightly
4
+ # defines keyboard navigation and allows the user to
5
+ # seemlessly move between and activate/decativate
6
+ # components.
7
+ module NavInput
8
+ # Key which if pressed causes the navigation component
9
+ # to lose focus / become deactivated
10
+ QUIT_CONTROLS = [27, 'q'.ord, 'Q'.ord] # 27 = ESC
11
+
12
+ # Key if pressed focuses on / activates a component
13
+ ENTER_CONTROLS = [10] # 10 = enter , space
14
+
15
+ # Up navigation keys
16
+ UP_CONTROLS = ['k'.ord, 'K'.ord, Ncurses::KEY_UP]
17
+
18
+ # Down navigation keys
19
+ DOWN_CONTROLS = ['j'.ord, 'J'.ord, Ncurses::KEY_DOWN]
20
+
21
+ # Left navigation keys
22
+ LEFT_CONTROLS = ['h'.ord, 'H'.ord, Ncurses::KEY_BACKSPACE,
23
+ Ncurses::KEY_BTAB,
24
+ Ncurses::KEY_LEFT]
25
+
26
+ # Right navigation keys
27
+ RIGHT_CONTROLS = ['l'.ord, 'L'.ord, "\t".ord, Ncurses::KEY_RIGHT]
28
+
29
+ # Return children which are focusabled/activable
30
+ def focusable
31
+ children.select { |c| c.component.activatable? }
32
+ end
33
+
34
+ # Return boolean indicating if any children are focusable
35
+ def focusable?
36
+ !focusable.empty?
37
+ end
38
+
39
+ # Helper to be internally invoked by navigation component
40
+ # on activation
41
+ def handle_input(from_parent=false)
42
+ @focus ||= 0
43
+
44
+ ch = handle_focused
45
+
46
+ while(!QUIT_CONTROLS.include?(ch))
47
+ if ENTER_CONTROLS.include?(ch)
48
+ focused.activate!
49
+
50
+ elsif UP_CONTROLS.include?(ch)
51
+ focused.no_border!
52
+
53
+ return ch if window.component.is_a?(Layouts::Horizontal) &&
54
+ from_parent &&
55
+ !window.parent.children.index(window) != 0
56
+
57
+ @focus -= 1
58
+
59
+ elsif LEFT_CONTROLS.include?(ch)
60
+ focused.no_border!
61
+
62
+ return ch if window.component.is_a?(Layouts::Vertical) &&
63
+ from_parent &&
64
+ !window.parent.children.index(window) != 0
65
+
66
+ @focus -= 1
67
+
68
+ elsif DOWN_CONTROLS.include?(ch)
69
+ focused.no_border!
70
+
71
+ return ch if window.component.is_a?(Layouts::Horizontal) &&
72
+ from_parent &&
73
+ !window.parent.children.index(window) != (window.parent.children.size - 1)
74
+
75
+ @focus += 1
76
+
77
+ elsif RIGHT_CONTROLS.include?(ch)
78
+ focused.no_border!
79
+
80
+ return ch if window.component.is_a?(Layouts::Vertical) &&
81
+ from_parent &&
82
+ !window.parent.children.index(window) != (window.parent.children.size - 1)
83
+
84
+ @focus += 1
85
+ end
86
+
87
+ if @focus >= focusable.size
88
+ @focus = focusable.size - 1
89
+ return ch if from_parent
90
+ end
91
+
92
+ if @focus < 0
93
+ @focus = 0
94
+ return ch if from_parent
95
+ end
96
+
97
+ ch = handle_focused
98
+ end
99
+
100
+ ch
101
+ end
102
+
103
+ private
104
+
105
+ def focused
106
+ focusable[@focus]
107
+ end
108
+
109
+ def handle_focused
110
+ ch = nil
111
+
112
+ focused.border! unless focused.component.kind_of?(Layout)
113
+ update_reterm
114
+ window.draw!
115
+
116
+ if focused.component.kind_of?(Layout)
117
+ ch = focused.component.handle_input(true)
118
+
119
+ else
120
+ ch = window.getch
121
+ end
122
+
123
+ ch
124
+ end
125
+ end # module NavInput
126
+ end # module RETerm