ppcurses 0.0.25 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +50 -11
  3. data/lib/ppcurses/Screen.rb +128 -15
  4. data/lib/ppcurses/actions/BaseAction.rb +1 -2
  5. data/lib/ppcurses/actions/GetBooleanAction.rb +6 -6
  6. data/lib/ppcurses/actions/GetDataAction.rb +4 -4
  7. data/lib/ppcurses/actions/GetEnumeratedStringAction.rb +6 -6
  8. data/lib/ppcurses/actions/GetIntegerAction.rb +2 -2
  9. data/lib/ppcurses/actions/GetStringAction.rb +2 -0
  10. data/lib/ppcurses/actions/PromptAction.rb +2 -2
  11. data/lib/ppcurses/application.rb +219 -0
  12. data/lib/ppcurses/date/meta_month.rb +139 -0
  13. data/lib/ppcurses/form/button.rb +120 -0
  14. data/lib/ppcurses/form/combo_box.rb +93 -0
  15. data/lib/ppcurses/form/date_picker.rb +86 -0
  16. data/lib/ppcurses/form/form.rb +96 -0
  17. data/lib/ppcurses/form/input_element.rb +189 -0
  18. data/lib/ppcurses/form/radio_button_group.rb +56 -0
  19. data/lib/ppcurses/geometry.rb +23 -0
  20. data/lib/ppcurses/menu/BaseMenu.rb +8 -14
  21. data/lib/ppcurses/menu/Menu.rb +5 -8
  22. data/lib/ppcurses/menu/RadioMenu.rb +2 -5
  23. data/lib/ppcurses/menu/choice_menu.rb +33 -0
  24. data/lib/ppcurses/menu/date_menu.rb +97 -0
  25. data/lib/ppcurses/menu_bar.rb +102 -0
  26. data/lib/ppcurses/table_view.rb +58 -0
  27. data/lib/ppcurses/view.rb +45 -0
  28. data/lib/ppcurses/window/pp_window.rb +42 -0
  29. data/lib/ppcurses.rb +57 -3
  30. data/test/application/create_application.rb +23 -0
  31. data/test/date/printMetaMonth.rb +30 -0
  32. data/test/form/menu_opens_form.rb +15 -0
  33. data/test/form/simple_form.rb +48 -0
  34. data/test/form/test_combo.rb +20 -0
  35. data/test/form/test_date_picker.rb +19 -0
  36. data/test/getBooleanAction.rb +1 -1
  37. data/test/getDataAction.rb +6 -6
  38. data/test/getEnumStringAction.rb +4 -4
  39. data/test/getIntegerAction.rb +4 -4
  40. data/test/menu/changeMenuBorder.rb +1 -1
  41. data/test/menu/compositeMenu.rb +2 -2
  42. data/test/menu/displayMenu.rb +1 -1
  43. data/test/raw_screen/display_colours.rb +69 -0
  44. data/test/raw_screen/press_a_key.rb +18 -0
  45. data/test/table_view/testTableView.rb +16 -0
  46. data/test/threads/block_test.rb +63 -0
  47. data/test/threads/handle_resize.rb +43 -0
  48. metadata +37 -12
  49. data/lib/ppcurses/Constants.rb +0 -8
@@ -0,0 +1,139 @@
1
+
2
+ def group(xs, n)
3
+ (0..xs.size / n - 1).collect { |i| xs[i * n, n] } # / This comment fixes a Visual Studio Code highlighting bug
4
+ end
5
+
6
+ # --------------------------------------------------------------------------
7
+ # This algorithm is based on ref/cal.rb by Tadayoshi Funaba
8
+ #
9
+ # Differences:
10
+ # - Only supports a monthly calendar type; no yearly calendar.
11
+ #
12
+ # - Returns a secondary day position array that specifies the row
13
+ # and column for each day of the month. Useful for highlighting a
14
+ # specific day.
15
+ #
16
+ # Example day position array for May 2015. The 1st of February
17
+ # occurs in the second row and the 16th column of the returned
18
+ # string calendar array
19
+ #
20
+ # day_pos = [ nil, [2,16], [2,19],
21
+ # [3,1], [3,4], [3,7], [3,10], [3,13], [3,16], [3,19],
22
+ # [4,0], [4,3], [4,6], [4,9], [4,12], [4,15], [4,18],
23
+ # [5,0], [5,3], [5,6], [5,9], [5,12], [5,15], [5,18],
24
+ # [6,0], [6,3], [6,6], [6,9], [6,12], [6,15], [6,18],
25
+ # [7,0] ]
26
+ #
27
+ #
28
+ # - Takes a Date object as initial parameter
29
+ #
30
+ # - K variable eliminated
31
+ #
32
+ # - Does not support the transformation option
33
+ #
34
+ #
35
+ def pict(day)
36
+
37
+ year = day.year
38
+ month = day.month
39
+
40
+ dw = 2 # Day width
41
+ mw = (dw + 1) * 7 - 1 # Month width
42
+ day_pos = [ nil ] # Create an array indexed by 1 by putting nil in slot zero.
43
+
44
+ # First day of the month e.g. 2015-05-01
45
+ fi = Date.new(year, month, 1, Date::GREGORIAN)
46
+ # Go backwards to the earliest closest day divisible by 7 date, e.g. 2015-04-26,
47
+ # using the Julian day number and modulus.
48
+ fi -= (fi.jd + 1) % 7
49
+
50
+ ve = (fi..fi + 6).collect{ |cu|
51
+ %w(S M Tu W Th F S)[cu.wday]
52
+ }
53
+
54
+ row = 1
55
+ pos = -1
56
+ # Creates an array of values ["S", " M", ... "31"]
57
+ ve += (fi..fi + 41).collect{ |cu| # of type Date
58
+
59
+ pos += 1 # Needed for meta info
60
+ if (cu.jd + 1) % 7 == 0 then
61
+ row += 1
62
+ pos = 0
63
+ end
64
+
65
+ if cu.mon == month then
66
+
67
+ # -------- Save meta information in day_pos array
68
+ i = pos * 3
69
+ if cu.mday < 10 then i += 1 end
70
+
71
+ day_pos.push [row, i]
72
+ # --------- End Meta info ------------------
73
+
74
+ cu.mday
75
+ end.to_s
76
+
77
+ }
78
+
79
+ # adds padding based on day width "1" --> " 1"
80
+ ve = ve.collect{|e| e.rjust(dw)}
81
+
82
+ # Creates an array of arrays. 7 for the days in the week
83
+ gr = group(ve, 7)
84
+
85
+ # Converts inner arrays to strings
86
+ ta = gr.collect{|xs| xs.join(' ')}
87
+
88
+ # Adds month/year header to top
89
+ ca = %w(January February March April May June July
90
+ August September October November December)[month - 1]
91
+ ca = ca + ' ' + year.to_s
92
+ ca = ca.center(mw)
93
+ ta.unshift(ca)
94
+
95
+ return ta, day_pos
96
+ end
97
+
98
+
99
+
100
+ module PPCurses
101
+
102
+
103
+ # A container object that holds:
104
+ #
105
+ # - A date object
106
+ # - The month the day belongs to in string array format
107
+ # - The row and column position of the date in the string array
108
+ #
109
+ # This container is used for displaying dates in the datepicker.
110
+ # The row and column position are necessary so that the day
111
+ # selected can be highlighted.
112
+ #
113
+ class MetaMonth
114
+
115
+ attr_accessor :day
116
+ attr_accessor :month_str_array
117
+ attr_accessor :day_pos
118
+
119
+ def initialize(day=Date.today)
120
+ self.day = day
121
+ end
122
+
123
+ def day=(new_day)
124
+ @day = new_day
125
+ @month_str_array, @day_pos = pict(@day)
126
+ end
127
+
128
+ def day_row
129
+ @day_pos[ @day.day ][0]
130
+ end
131
+
132
+ def day_col
133
+ @day_pos[ @day.day ][1]
134
+ end
135
+
136
+ end
137
+
138
+
139
+ end
@@ -0,0 +1,120 @@
1
+ module PPCurses
2
+
3
+ class Button < View
4
+
5
+ attr_accessor :label
6
+ attr_accessor :selected
7
+ attr_accessor :pushed
8
+
9
+ attr_accessor :action
10
+
11
+ def initialize(label)
12
+ @label = label
13
+ @selected = false
14
+ @pushed = false
15
+ end
16
+
17
+ #
18
+ # Screen should be of type Curses::Window
19
+ #
20
+ def show(screen)
21
+ screen.attron(Curses::A_REVERSE) if @selected
22
+ screen.addstr("< #{@label} >")
23
+ screen.attroff(Curses::A_REVERSE) if @selected
24
+ end
25
+
26
+ def width
27
+ @label.length + 4
28
+ end
29
+
30
+ def height
31
+ 1
32
+ end
33
+
34
+ def set_curs_pos(screen)
35
+ Curses.curs_set(INVISIBLE)
36
+ end
37
+
38
+ def key_down( key )
39
+
40
+ if key == ENTER
41
+ @action.call unless action.nil?
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
48
+
49
+
50
+
51
+ # For grouping two buttons together, i.e. SUBMIT/CANCEL
52
+ class ButtonPair < View
53
+
54
+ attr_accessor :selected
55
+ attr_accessor :selected_element
56
+
57
+ attr_reader :button1
58
+ attr_reader :button2
59
+
60
+ def initialize(button1_label, button2_label)
61
+ @button1 = Button.new(button1_label)
62
+ @button2 = Button.new(button2_label)
63
+
64
+ @selected_button = @button1
65
+
66
+ @selected = false
67
+ end
68
+
69
+ def selected=(val)
70
+ @selected_button.selected = val
71
+ @selected=val
72
+ end
73
+
74
+ def height
75
+ 1
76
+ end
77
+
78
+ def set_curs_pos(screen)
79
+ Curses.curs_set(INVISIBLE)
80
+ end
81
+
82
+ def key_down( key )
83
+ if key == KEY_RIGHT or key == KEY_LEFT
84
+ @selected_button.selected=false
85
+
86
+ if @selected_button == @button1
87
+ @selected_button = @button2
88
+ else
89
+ @selected_button = @button1
90
+ end
91
+
92
+ @selected_button.selected=true
93
+ return
94
+ end
95
+
96
+ @selected_button.key_down(key)
97
+
98
+ end
99
+
100
+ def show(screen)
101
+
102
+ p = screen.cur_point
103
+ p.y+=1
104
+ screen.set_pos_by_point(p)
105
+
106
+
107
+ @button1.show(screen)
108
+
109
+ p.x += @button1.width + 2
110
+ screen.set_pos_by_point(p)
111
+
112
+ @button2.show(screen)
113
+
114
+ end
115
+
116
+ end
117
+
118
+
119
+
120
+ end
@@ -0,0 +1,93 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ module PPCurses
4
+
5
+ class ComboBox < View
6
+
7
+ DOWN_ARROW = '∇'
8
+
9
+ # Does the element have focus in the form?
10
+ attr_accessor :selected
11
+
12
+
13
+ def initialize(label, options)
14
+ @label = label
15
+ @options = options
16
+
17
+ @display_width = 13
18
+ @choice_made = false
19
+
20
+ @options.each do |option|
21
+ if option.length > @display_width
22
+ @display_width = option.length
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ def show(screen)
29
+ screen.attron(A_REVERSE) if @selected
30
+ screen.addstr("#{@label}:")
31
+
32
+ @combo_display_point = screen.cur_point
33
+
34
+ screen.attroff(A_REVERSE) if @selected
35
+
36
+ screen.addstr(display_string)
37
+ end
38
+
39
+ def height
40
+ 1
41
+ end
42
+
43
+ def set_curs_pos(screen)
44
+ screen.curs_set(INVISIBLE)
45
+ end
46
+
47
+ # Return Value
48
+ # The object in the receiver's internal item list corresponding to the last item selected
49
+ # from the pop-up list, or nil if no item is selected.
50
+ def object_value_of_selected_item
51
+ if @options_menu.nil?
52
+ return nil
53
+ end
54
+
55
+ @options[@options_menu.selection]
56
+ end
57
+
58
+ def key_down(key)
59
+
60
+ if key == ENTER
61
+
62
+ if @options_menu.nil?
63
+ @options_menu = PPCurses::ChoiceMenu.new( @options )
64
+ end
65
+
66
+ @options_menu.set_origin(@combo_display_point)
67
+
68
+ @options_menu.show
69
+ @options_menu.menu_selection
70
+
71
+ if @options_menu.pressed_enter
72
+ @choice_made = true
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+
79
+ #------------------------------------------------
80
+ protected
81
+ def display_string
82
+ if @choice_made
83
+ return ' '+ "#{@options[@options_menu.selection].center(@display_width,'-')} #{DOWN_ARROW}"
84
+ end
85
+
86
+ ' ' + ' select '.center(@display_width,'-') + " #{DOWN_ARROW}"
87
+ end
88
+
89
+
90
+
91
+ end
92
+
93
+ end
@@ -0,0 +1,86 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ module PPCurses
4
+
5
+
6
+ # Ruby date objects are immutable
7
+ # ---------------------------------
8
+ # 09-15-1950
9
+ # 05-04-2015 = May 4th 2015
10
+ class DatePicker < View
11
+
12
+ DOWN_ARROW = '∇'
13
+
14
+ # Does the element have focus in the form?
15
+ attr_accessor :selected
16
+
17
+
18
+ def initialize(label, initial_date = Date.today)
19
+ @label = label
20
+ @display_width = 13
21
+ @date = initial_date
22
+ end
23
+
24
+
25
+ def show(screen)
26
+ screen.attron(A_REVERSE) if @selected
27
+ screen.addstr("#{@label}:")
28
+
29
+ @combo_display_point = screen.cur_point
30
+
31
+ screen.attroff(A_REVERSE) if @selected
32
+
33
+ screen.addstr(display_string)
34
+ end
35
+
36
+ def height
37
+ 1
38
+ end
39
+
40
+ def set_curs_pos(screen)
41
+ screen.curs_set(INVISIBLE)
42
+ end
43
+
44
+ # Return Value
45
+ # The object in the receiver's internal item list corresponding to the last item selected
46
+ # from the pop-up list, or nil if no item is selected.
47
+ def object_value_of_selected_item
48
+ if @options_menu.nil?
49
+ return nil
50
+ end
51
+
52
+ @options[@options_menu.selection]
53
+ end
54
+
55
+ def key_down(key)
56
+
57
+ if key == ENTER
58
+
59
+ if @date_menu.nil?
60
+ @date_menu = PPCurses::DateMenu.new(@date)
61
+ end
62
+
63
+ @date_menu.set_origin(@combo_display_point)
64
+
65
+ @date_menu.show
66
+ @date_menu.menu_selection
67
+
68
+ if @date_menu.pressed_enter
69
+ @date = @date_menu.day
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+
76
+ #------------------------------------------------
77
+ protected
78
+ def display_string
79
+ " #{@date.month}-#{@date.day}-#{@date.year} #{DOWN_ARROW}"
80
+ end
81
+
82
+
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,96 @@
1
+ module PPCurses
2
+
3
+ class Form < View
4
+
5
+ attr_accessor :selected_element
6
+
7
+ #
8
+ # Screen should be of type Curses::Window
9
+ #
10
+ def initialize
11
+ @elements = []
12
+ end
13
+
14
+ # Formal protocol required
15
+ # Adds an element to the form. Elements are rendered in the order they are added.
16
+ #
17
+ # An element must implement the following protocol:
18
+ #
19
+ # - def show(screen)
20
+ # - def height
21
+ # - def set_curs_pos(screen)
22
+ # - def key_down(key)
23
+ # - def selected=
24
+ #
25
+ def add (element)
26
+ PPCurses.implements_protocol( element, %w(show height set_curs_pos key_down selected=))
27
+ @elements.push(element)
28
+
29
+ if @selected_element.nil?
30
+ set_selected_element(@elements[0])
31
+ end
32
+
33
+ end
34
+
35
+
36
+ def submitted?
37
+ @button_pair.button1.pushed
38
+ end
39
+
40
+
41
+ def key_down( key )
42
+
43
+ if key == KEY_UP or key == KEY_DOWN or key == TAB
44
+ selected_index = @elements.index(@selected_element)
45
+ n_choices = @elements.length
46
+
47
+ if key == KEY_DOWN or key == TAB
48
+ (selected_index == n_choices-1) ? next_selection = 0 : next_selection = selected_index + 1
49
+ else
50
+ (selected_index == 0) ? next_selection = n_choices - 1 : next_selection = selected_index - 1
51
+ end
52
+
53
+ set_selected_element(@elements[next_selection])
54
+ return
55
+ end
56
+
57
+ @selected_element.key_down(key)
58
+
59
+ end
60
+
61
+
62
+ # TODO -- call display of subview???
63
+ def display(screen)
64
+ y = 1
65
+ x = 1
66
+
67
+ for i in 0..@elements.length - 1
68
+ element = @elements[i]
69
+ screen.setpos(y, x)
70
+ element.show(screen)
71
+ y += element.height
72
+ end
73
+
74
+
75
+ @selected_element.set_curs_pos(screen) unless @selected_element.nil?
76
+ end
77
+
78
+ # --------------------------------------------------------------------------------
79
+ protected
80
+
81
+
82
+
83
+ def set_selected_element(new_element)
84
+
85
+ unless @selected_element.nil?
86
+ @selected_element.selected=false
87
+ end
88
+
89
+ @selected_element = new_element
90
+ @selected_element.selected=true
91
+
92
+ end
93
+
94
+ end
95
+
96
+ end
@@ -0,0 +1,189 @@
1
+ module PPCurses
2
+
3
+ class InputElement < View
4
+
5
+ attr_accessor :label
6
+ attr_accessor :value
7
+ attr_accessor :size
8
+ attr_accessor :selected
9
+
10
+ attr_accessor :filter
11
+
12
+ attr_accessor :value_start_point
13
+
14
+
15
+ # Stores the X location of the cursor relative to the
16
+ # value being displayed. If the cursor is in
17
+ # the middle of the string then subsequent keys
18
+ # will be added from this location, etc.
19
+ attr_accessor :cursor_location
20
+
21
+
22
+ def initialize(label, size )
23
+ @label = label
24
+ @size = size
25
+ @selected = false
26
+ @value = ''
27
+ @cursor_location = 0
28
+ @filter = nil
29
+ end
30
+
31
+ # Creates an InputElement that only allows integer input
32
+ #
33
+ def InputElement.new_integer_only( label, size)
34
+ i_only = PPCurses::InputElement.new(label, size)
35
+ i_only.filter =PPCurses::NumberFilter.new
36
+ i_only
37
+ end
38
+
39
+
40
+ def show(screen)
41
+ print_label( screen )
42
+
43
+ @value_start_point = screen.cur_point
44
+
45
+ print_value( screen )
46
+ end
47
+
48
+
49
+
50
+ def key_down( key )
51
+ if key == DELETE
52
+ handle_delete
53
+ return
54
+ end
55
+
56
+ if key == KEY_LEFT
57
+ @cursor_location -= 1 unless @cursor_location == 0
58
+ return
59
+ end
60
+
61
+ if key == KEY_RIGHT
62
+ @cursor_location += 1 unless @cursor_location == @value.length
63
+ return
64
+ end
65
+
66
+
67
+ # Ignore control characters
68
+ if key.is_a?(Fixnum)
69
+ return
70
+ end
71
+
72
+ # Adding new characters to the string
73
+
74
+ # Check size of string before adding another character
75
+ if @value.length >= @size
76
+ # Ignore input
77
+ return
78
+ end
79
+
80
+ unless passes_filter(key)
81
+ return
82
+ end
83
+
84
+ add_character(key)
85
+
86
+ end
87
+
88
+
89
+ def passes_filter( key )
90
+ if @filter.nil?
91
+ return true
92
+ end
93
+
94
+ @filter.passes_filter( key )
95
+ end
96
+
97
+
98
+ def set_curs_pos(screen)
99
+ Curses.curs_set(VISIBLE)
100
+ x = @value_start_point.x + @cursor_location
101
+
102
+ screen.setpos( @value_start_point.y, x )
103
+ end
104
+
105
+
106
+ def height
107
+ 1
108
+ end
109
+
110
+ # --------------------------------------------------------------------------------
111
+ protected
112
+
113
+
114
+ def print_label( screen )
115
+ screen.attron(Curses::A_REVERSE) if @selected
116
+ screen.addstr("#{@label}:")
117
+ screen.attroff(Curses::A_REVERSE) if @selected
118
+ screen.addstr(' ')
119
+ end
120
+
121
+
122
+ def print_value( screen )
123
+ screen.attron(Curses::A_UNDERLINE)
124
+ screen.addstr(@value.ljust(@size))
125
+ screen.attroff(Curses::A_UNDERLINE)
126
+ end
127
+
128
+
129
+ def handle_delete
130
+ # Cursor is at the front of the string, nothing in
131
+ # front of it to delete, or there is nothing to delete
132
+ if @cursor_location == 0 or @value.length == 0
133
+ return
134
+ end
135
+
136
+ if @value.length == 1
137
+ @value = ''
138
+ @cursor_location = 0
139
+ return
140
+ end
141
+
142
+ # Cursor is at the end of the string, remove the last character
143
+ if @cursor_location == @value.length
144
+ @value = @value.slice(0..@cursor_location-2)
145
+ elsif @cursor_location == 1
146
+ # cursor is right after the first character
147
+ @value = @value.slice(1..@value.length-1)
148
+ else
149
+ # Cursor is in the middle of the string, remove the character at the cursor location
150
+ # Example:
151
+ #
152
+ # abcdefg
153
+ # 1234567
154
+ # ^ -> cursor-location = 3
155
+ #
156
+ @value = @value.slice(0..@cursor_location-2) + @value.slice(@cursor_location..@value.length-1)
157
+ end
158
+
159
+ @cursor_location -= 1
160
+ end
161
+
162
+
163
+ def add_character ( char )
164
+ if @cursor_location == @value.length
165
+ @value += char
166
+ else
167
+ @value = @value.slice(0..@cursor_location-1) + char + @value.slice(@cursor_location..@value.length-1)
168
+ end
169
+
170
+ @cursor_location += 1
171
+ end
172
+
173
+ end
174
+
175
+
176
+
177
+ class NumberFilter
178
+
179
+ def passes_filter( key )
180
+ if key =~ /^\d+$/
181
+ return true
182
+ end
183
+
184
+ false
185
+ end
186
+
187
+ end
188
+
189
+ end