rbcurse 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/CHANGELOG +1570 -0
  2. data/History.txt +6 -0
  3. data/Manifest.txt +54 -0
  4. data/README.txt +304 -0
  5. data/Rakefile +28 -0
  6. data/examples/qdfilechooser.rb +68 -0
  7. data/examples/rfe.rb +853 -0
  8. data/examples/rfe_renderer.rb +69 -0
  9. data/examples/test1.rb +242 -0
  10. data/examples/test2.rb +498 -0
  11. data/examples/testcombo.rb +95 -0
  12. data/examples/testkeypress.rb +61 -0
  13. data/examples/testmenu.rb +105 -0
  14. data/examples/testtable.rb +266 -0
  15. data/examples/testtabp.rb +106 -0
  16. data/examples/testtodo.rb +532 -0
  17. data/examples/viewtodo.rb +512 -0
  18. data/lib/rbcurse/action.rb +31 -0
  19. data/lib/rbcurse/applicationheader.rb +57 -0
  20. data/lib/rbcurse/celleditor.rb +120 -0
  21. data/lib/rbcurse/checkboxcellrenderer.rb +69 -0
  22. data/lib/rbcurse/colormap.rb +133 -0
  23. data/lib/rbcurse/comboboxcellrenderer.rb +45 -0
  24. data/lib/rbcurse/defaultlistselectionmodel.rb +49 -0
  25. data/lib/rbcurse/keylabelprinter.rb +143 -0
  26. data/lib/rbcurse/listcellrenderer.rb +99 -0
  27. data/lib/rbcurse/listkeys.rb +33 -0
  28. data/lib/rbcurse/listscrollable.rb +216 -0
  29. data/lib/rbcurse/listselectable.rb +67 -0
  30. data/lib/rbcurse/mapper.rb +108 -0
  31. data/lib/rbcurse/orderedhash.rb +77 -0
  32. data/lib/rbcurse/rcombo.rb +243 -0
  33. data/lib/rbcurse/rdialogs.rb +183 -0
  34. data/lib/rbcurse/rform.rb +845 -0
  35. data/lib/rbcurse/rinputdataevent.rb +36 -0
  36. data/lib/rbcurse/rlistbox.rb +804 -0
  37. data/lib/rbcurse/rmenu.rb +666 -0
  38. data/lib/rbcurse/rmessagebox.rb +325 -0
  39. data/lib/rbcurse/rpopupmenu.rb +754 -0
  40. data/lib/rbcurse/rtabbedpane.rb +259 -0
  41. data/lib/rbcurse/rtable.rb +1296 -0
  42. data/lib/rbcurse/rtextarea.rb +673 -0
  43. data/lib/rbcurse/rtextview.rb +335 -0
  44. data/lib/rbcurse/rwidget.rb +1731 -0
  45. data/lib/rbcurse/scrollable.rb +301 -0
  46. data/lib/rbcurse/selectable.rb +94 -0
  47. data/lib/rbcurse/table/tablecellrenderer.rb +85 -0
  48. data/lib/rbcurse/table/tabledatecellrenderer.rb +102 -0
  49. data/lib/rbcurse.rb +7 -0
  50. data/lib/ver/keyboard.rb +150 -0
  51. data/lib/ver/keyboard2.rb +170 -0
  52. data/lib/ver/ncurses.rb +102 -0
  53. data/lib/ver/window.rb +369 -0
  54. data/test/test_rbcurse.rb +0 -0
  55. metadata +118 -0
@@ -0,0 +1,105 @@
1
+ #$LOAD_PATH << "/Users/rahul/work/projects/rbcurse/"
2
+ # this program tests out various widgets.
3
+ require 'rubygems'
4
+ require 'ncurses'
5
+ require 'logger'
6
+ require 'rbcurse'
7
+ #require 'lib/rbcurse/rform'
8
+ require 'rbcurse/rpopupmenu'
9
+ if $0 == __FILE__
10
+ include RubyCurses
11
+
12
+ begin
13
+ # Initialize curses
14
+ VER::start_ncurses # this is initializing colors via ColorMap.setup
15
+ $log = Logger.new("view.log")
16
+ $log.level = Logger::DEBUG
17
+
18
+ @window = VER::Window.root_window
19
+ # Initialize few color pairs
20
+ # Create the window to be associated with the form
21
+ # Un post form and free the memory
22
+
23
+ catch(:close) do
24
+ colors = Ncurses.COLORS
25
+ $log.debug "START #{colors} colors #{$datacolor} ---------"
26
+ @form = Form.new @window
27
+ @form.window.printstring 0, 25, "Demo of Ruby Curses PopupMenu", $normalcolor, 'reverse'
28
+ r = 1; fc = 12;
29
+ row = 10; col = 10
30
+ colorlabel = Label.new @form, {'text' => "A label:", "row" => row, "col" => col, "color"=>"cyan", "mnemonic" => 'A'}
31
+
32
+ #@mb = RubyCurses::MenuBar.new
33
+ #filemenu = RubyCurses::Menu.new "File"
34
+ filemenu = RubyCurses::PopupMenu.new "File"
35
+ filemenu.add(item = RubyCurses::MenuItem.new("Open",'O'))
36
+
37
+ filemenu.insert_separator 1
38
+ filemenu.add(RubyCurses::MenuItem.new "New",'N')
39
+ filemenu.add(item = RubyCurses::MenuItem.new("Save",'S'))
40
+ filemenu.add(item = RubyCurses::MenuItem.new("Test",'T'))
41
+ filemenu.add(item = RubyCurses::MenuItem.new("Wrap Text",'W'))
42
+ filemenu.add(item = RubyCurses::MenuItem.new("Exit",'X'))
43
+ item.command() {
44
+ #throw(:menubarclose);
45
+ throw(:close)
46
+ }
47
+ item = RubyCurses::CheckBoxMenuItem.new "Reverse"
48
+ #filemenu.create_window
49
+ # item.onvalue="On"
50
+ # item.offvalue="Off"
51
+ #item.checkbox.text "Labelcb"
52
+ #item.text="Labelcb"
53
+ # in next line, an explicit repaint is required since label is on another form.
54
+ #item.command(colorlabel){|it, label| att = it.getvalue ? 'reverse' : nil; label.attr(att); label.repaint}
55
+
56
+
57
+ filemenu.add(item)
58
+ #@mb.add(filemenu)
59
+ editmenu = RubyCurses::Menu.new "Edit"
60
+ item = RubyCurses::MenuItem.new "Cut"
61
+ editmenu.add(item)
62
+ item.accelerator = "Ctrl-X"
63
+ item=RubyCurses::MenuItem.new "Copy"
64
+ editmenu.add(item)
65
+ item.accelerator = "Ctrl-C"
66
+ item=RubyCurses::MenuItem.new "Paste"
67
+ editmenu.add(item)
68
+ item.accelerator = "Ctrl-V"
69
+ #@mb.add(editmenu)
70
+ #@mb.add(
71
+ menu=RubyCurses::Menu.new("Others")
72
+ filemenu.add(menu)
73
+ #item=RubyCurses::MenuItem.new "Save","S"
74
+ item = RubyCurses::MenuItem.new "Config"
75
+ menu.add(item)
76
+ item = RubyCurses::MenuItem.new "Tables"
77
+ menu.add(item)
78
+ savemenu = RubyCurses::Menu.new "EditM"
79
+ item = RubyCurses::MenuItem.new "CutM"
80
+ savemenu.add(item)
81
+ item = RubyCurses::MenuItem.new "DeleteM"
82
+ savemenu.add(item)
83
+ item = RubyCurses::MenuItem.new "PasteM"
84
+ savemenu.add(item)
85
+ menu.add(savemenu)
86
+ # 2008-12-20 13:06 no longer hardcoding toggle key of menu_bar.
87
+ #@mb.toggle_key = KEY_F2
88
+ #@form.set_menu_bar @mb
89
+ #@cell = CellRenderer.new "Hello", {"col" => 1, "row"=>29, "justify"=>:right, "display_length" => 30}
90
+ # END
91
+ @form.repaint
92
+ filemenu.show colorlabel, 0,1
93
+ @window.wrefresh
94
+ Ncurses::Panel.update_panels
95
+ end
96
+ rescue => ex
97
+ ensure
98
+ @window.destroy if !@window.nil?
99
+ VER::stop_ncurses
100
+ p ex if ex
101
+ p(ex.backtrace.join("\n")) if ex
102
+ $log.debug( ex) if ex
103
+ $log.debug(ex.backtrace.join("\n")) if ex
104
+ end
105
+ end
@@ -0,0 +1,266 @@
1
+ #$LOAD_PATH << "/Users/rahul/work/projects/rbcurse/"
2
+ require 'rubygems'
3
+ require 'ncurses'
4
+ require 'logger'
5
+ #require 'ver/keyboard'
6
+ require 'rbcurse'
7
+ require 'rbcurse/rcombo'
8
+ require 'rbcurse/rtable'
9
+ require 'rbcurse/celleditor'
10
+ #require 'rbcurse/table/tablecellrenderer'
11
+ require 'rbcurse/comboboxcellrenderer'
12
+ require 'rbcurse/action'
13
+
14
+ ##
15
+ # a renderer which paints alternate lines with
16
+ # another color, for people with poor taste.
17
+ class MyRenderer < TableCellRenderer
18
+ def initialize text="", config={}, &block
19
+ super
20
+ @orig_bgcolor = @bgcolor
21
+ @orig_color = @color
22
+ end
23
+ def repaint graphic, r=@row,c=@col, value=@text, focussed=false, selected=false
24
+ @bgcolor = @orig_bgcolor
25
+ @color = @orig_color
26
+ if !focussed and !selected
27
+ @bgcolor = r % 2 == 0 ? "green" : @orig_bgcolor
28
+ @color = r % 2 == 0 ? "black" : @orig_color
29
+ end
30
+ super
31
+ end
32
+ end
33
+ if $0 == __FILE__
34
+ include RubyCurses
35
+ include RubyCurses::Utils
36
+
37
+ begin
38
+ # Initialize curses
39
+ VER::start_ncurses # this is initializing colors via ColorMap.setup
40
+ $log = Logger.new("view.log")
41
+ $log.level = Logger::DEBUG
42
+
43
+ @window = VER::Window.root_window
44
+
45
+ catch(:close) do
46
+ colors = Ncurses.COLORS
47
+ $log.debug "START #{colors} colors ---------"
48
+ @form = Form.new @window
49
+ @window.printstring 0,30,"Demo of Ruby Curses Table: Edit, Resize, Insert, Move, Delete Row/Col", $datacolor
50
+ r = 1; c = 30;
51
+ data = [["You're beautiful",3,"James Blunt",3.21, true, "WIP"],
52
+ ["Where are you",3,"London Beat",3.47, true, "WIP"],
53
+ ["I'll always love my mama",92,"Intruders",412, true, "Fin"],
54
+ ["I believe in love",4,"Paula Cole",110.0, false, "Cancel"],
55
+ ["Red Sky at night",4,"Dave Gilmour",102.72, false, "Postp"],
56
+ ["Midnight and you",8,"Barry White",12.72, false, "Todo"],
57
+ ["Let the music play",9,"Barry White",12.2, false, "WIP"],
58
+ ["I swear",nil,"Boyz II Men",112.7, true, "Cancel"],
59
+ ["Believe",9,"Elton John",12.2, false, "Todo"],
60
+ ["Private Dancer",9,"Tina Turner",12.2, false, "Todo"],
61
+ ["Liberian Girl",9,"Michael Jackson",12.2, false, "Todo"],
62
+ ["Like a prayer",163,"Charlotte Perrelli",5.4, false, "WIP"]]
63
+
64
+ colnames = %w[ Song Cat Artist Ratio Flag Status]
65
+ statuses = ["Todo", "WIP", "Fin", "Cancel", "Postp"]
66
+
67
+ texta = Table.new @form do
68
+ name "mytext"
69
+ row r
70
+ col c
71
+ width 78
72
+ height 15
73
+ #title "A Table"
74
+ #title_attrib (Ncurses::A_REVERSE | Ncurses::A_BOLD)
75
+ cell_editing_allowed true
76
+ editing_policy :EDITING_AUTO
77
+ set_data data, colnames
78
+ end
79
+ sel_col = Variable.new 0
80
+ sel_col.value = 0
81
+ tcm = texta.get_table_column_model
82
+ selcolname = texta.get_column_name sel_col.value
83
+ #
84
+ ## key bindings fo texta
85
+ # column widths
86
+ $log.debug " tcm #{tcm.inspect}"
87
+ $log.debug " tcms #{tcm.columns}"
88
+ $log.debug " tcm0 #{tcm.column(0).identifier}"
89
+ $log.debug " tcm0 #{tcm.column(0).width}"
90
+ tcm.column(0).width 24
91
+ tcm.column(1).width 5
92
+ tcm.column(2).width 18
93
+ #tcm.column(2).editable false
94
+ tcm.column(3).width 7
95
+ tcm.column(4).width 5
96
+ tcm.column(5).width 8
97
+ texta.configure() do
98
+ bind_key(330) { texta.remove_column(tcm.column(texta.focussed_col)) rescue "" }
99
+ bind_key(?+) {
100
+ acolumn = texta.get_column selcolname
101
+ w = acolumn.width + 1
102
+ acolumn.width w
103
+ #texta.table_structure_changed
104
+ }
105
+ bind_key(?-) {
106
+ acolumn = texta.get_column selcolname
107
+ w = acolumn.width - 1
108
+ if w > 3
109
+ acolumn.width w
110
+ #texta.table_structure_changed
111
+ end
112
+ }
113
+ bind_key(?>) {
114
+ colcount = tcm.column_count-1
115
+ #texta.move_column sel_col.value, sel_col.value+1 unless sel_col.value == colcount
116
+ col = texta.focussed_col
117
+ texta.move_column col, col+1 unless col == colcount
118
+ }
119
+ bind_key(?<) {
120
+ col = texta.focussed_col
121
+ texta.move_column col, col-1 unless col == 0
122
+ #texta.move_column sel_col.value, sel_col.value-1 unless sel_col.value == 0
123
+ }
124
+ #bind_key(KEY_RIGHT) { sel_col.value = sel_col.value+1; current_column sel_col.value}
125
+ #bind_key(KEY_LEFT) { sel_col.value = sel_col.value-1;current_column sel_col.value}
126
+ end
127
+ keylabel = RubyCurses::Label.new @form, {'text' => "", "row" => r+16, "col" => c, "color" => "yellow", "bgcolor"=>"blue", "display_length"=>60, "height"=>2}
128
+ eventlabel = RubyCurses::Label.new @form, {'text' => "Events:", "row" => r+19, "col" => c, "color" => "white", "bgcolor"=>"blue", "display_length"=>60, "height"=>2}
129
+
130
+ # report some events
131
+ texta.table_model.bind(:TABLE_MODEL_EVENT){|e| eventlabel.text = "Event: #{e}"}
132
+ texta.get_table_column_model.bind(:TABLE_COLUMN_MODEL_EVENT){|e| eventlabel.text = "Event: #{e}"}
133
+ texta.bind(:TABLE_TRAVERSAL_EVENT){|e| eventlabel.text = "Event: #{e}"}
134
+
135
+ @help = "C-q to quit. M-Tab (next col) C-n (Pg Dn), C-p (Pg Up), M-0 Top, M-9 End, C-x (select). Columns:- Narrow, + expand, > < switch"
136
+ RubyCurses::Label.new @form, {'text' => @help, "row" => Ncurses.LINES-3, "col" => 2, "color" => "yellow", "height"=>2}
137
+
138
+ str_renderer = TableCellRenderer.new ""
139
+ num_renderer = TableCellRenderer.new "", { "justify" => :right }
140
+ bool_renderer = CheckBoxCellRenderer.new "", {"parent" => texta, "display_length"=>5}
141
+ combo_renderer = RubyCurses::ComboBoxCellRenderer.new nil, {"parent" => texta, "display_length"=> 8}
142
+ combo_editor = RubyCurses::CellEditor.new(RubyCurses::ComboBox.new nil, {"focusable"=>false, "visible"=>false, "list"=>statuses, "display_length"=>8})
143
+ texta.set_default_cell_renderer_for_class "String", str_renderer
144
+ texta.set_default_cell_renderer_for_class "Fixnum", num_renderer
145
+ texta.set_default_cell_renderer_for_class "Float", num_renderer
146
+ texta.set_default_cell_renderer_for_class "TrueClass", bool_renderer
147
+ texta.set_default_cell_renderer_for_class "FalseClass", bool_renderer
148
+ texta.get_table_column_model.column(5).cell_editor = combo_editor
149
+ =begin
150
+ field = Field.new @form do
151
+ name "value"
152
+ row r+18
153
+ col c
154
+ display_length 30
155
+ bgcolor "cyan"
156
+ set_label Label.new @form, {'text' => "Value", 'mnemonic'=> 'V'}
157
+ # bind :ENTER do $editing = true end
158
+ # bind :LEAVE do $editing = false end
159
+ end
160
+ =end
161
+ buttrow = r+21 #Ncurses.LINES-4
162
+ b_newrow = Button.new @form do
163
+ text "&New"
164
+ row buttrow
165
+ col c
166
+ bind(:ENTER) { eventlabel.text "New button adds a new row at the bottom " }
167
+ end
168
+ tm = texta.table_model
169
+ b_newrow.command {
170
+ cc = texta.get_table_column_model.column_count
171
+ # need to get datatypes etc, this is just a junk test
172
+ tmp=[]
173
+ #0.upto(cc-1) { tmp << "" }
174
+ 0.upto(cc-1) { tmp << nil }
175
+ tm << tmp
176
+ #texta.table_data_changed
177
+ keylabel.text = "Added a row"
178
+ alert("Added a row at bottom of table")
179
+
180
+ }
181
+
182
+ # using ampersand to set mnemonic
183
+ b_delrow = Button.new @form do
184
+ text "&Delete"
185
+ row buttrow
186
+ col c+10
187
+ bind(:ENTER) { eventlabel.text "Deletes focussed row" }
188
+ end
189
+ b_delrow.command { |form|
190
+ row = texta.focussed_row
191
+ if confirm("Do your really want to delete row #{row}?")== :YES
192
+ tm.delete_at row
193
+ #texta.table_data_changed
194
+ else
195
+ #$message.value = "Quit aborted"
196
+ end
197
+ }
198
+ b_change = Button.new @form do
199
+ text "&Lock"
200
+ row buttrow
201
+ col c+20
202
+ command {
203
+ r = texta.focussed_row
204
+ c = sel_col.value
205
+ #$log.debug " Update gets #{field.getvalue.class}"
206
+ #texta.set_value_at(r, c, field.getvalue)
207
+ toggle = texta.column(texta.focussed_col()).editable
208
+ if toggle.nil? or toggle==true
209
+ toggle = false
210
+ text "Un&lock"
211
+ else
212
+ toggle = true
213
+ text "&Lock "
214
+ end
215
+ eventlabel.text "Set column #{texta.focussed_col()} editable to #{toggle}"
216
+ texta.column(texta.focussed_col()).editable toggle
217
+ alert("Set column #{texta.focussed_col()} editable to #{toggle}")
218
+ }
219
+ bind(:ENTER) { eventlabel.text "Toggles editable state of current column " }
220
+ end
221
+ b_insert = Button.new @form do
222
+ text "&Insert"
223
+ row buttrow
224
+ col c+32
225
+ command {
226
+ # this does not trigger a data change since we are not updating model. so update
227
+ # on pressing up or down
228
+ #0.upto(100) { |i| data << ["test", rand(100), "abc:#{i}", rand(100)/2.0]}
229
+ #texta.table_data_changed
230
+ }
231
+ bind(:ENTER) { eventlabel.text "Does nothing " }
232
+ end
233
+
234
+
235
+ @form.repaint
236
+ @window.wrefresh
237
+ Ncurses::Panel.update_panels
238
+ while((ch = @window.getchar()) != ?\C-q )
239
+ colcount = tcm.column_count-1
240
+ s = keycode_tos ch
241
+ keylabel.text = "Pressed #{ch} , #{s}"
242
+ @form.handle_key(ch)
243
+
244
+ sel_col.value = tcm.column_count-1 if sel_col.value > tcm.column_count-1
245
+ sel_col.value = 0 if sel_col.value < 0
246
+ selcolname = texta.get_column_name sel_col.value
247
+ keylabel.text = "Pressed #{ch} , #{s}. Column selected #{texta.focussed_col}: Width:#{tcm.column(sel_col.value).width} #{selcolname}. Focussed Row: #{texta.focussed_row}, Rows: #{texta.table_model.row_count}, Cols: #{colcount}"
248
+ s = texta.get_value_at(texta.focussed_row, texta.focussed_col)
249
+ #s = s.to_s
250
+ ## $log.debug " updating Field #{s}, #{s.class}"
251
+ ## field.set_buffer s unless field.state == :HIGHLIGHTED # $editing
252
+
253
+ @form.repaint
254
+ @window.wrefresh
255
+ end
256
+ end
257
+ rescue => ex
258
+ ensure
259
+ @window.destroy if !@window.nil?
260
+ VER::stop_ncurses
261
+ p ex if ex
262
+ p(ex.backtrace.join("\n")) if ex
263
+ $log.debug( ex) if ex
264
+ $log.debug(ex.backtrace.join("\n")) if ex
265
+ end
266
+ end
@@ -0,0 +1,106 @@
1
+ # this is a test program, tests out tabbed panes. type F1 to exit
2
+ #
3
+ #$LOAD_PATH << "/Users/rahul/work/projects/rbcurse/"
4
+ require 'rubygems'
5
+ require 'ncurses'
6
+ require 'logger'
7
+ require 'rbcurse'
8
+ require 'rbcurse/rtabbedpane'
9
+
10
+ class TestTabbedPane
11
+ def initialize
12
+ acolor = $reversecolor
13
+ #$config_hash ||= {}
14
+ end
15
+ def run
16
+ $config_hash ||= Variable.new Hash.new
17
+ #configvar.update_command(){ |v| $config_hash[v.source()] = v.value }
18
+ @tp = RubyCurses::TabbedPane.new nil do
19
+ height 12
20
+ width 50
21
+ row 5
22
+ col 10
23
+ end
24
+ @tab1 = @tp.add_tab "&Language"
25
+ f1 = @tab1.form
26
+ #$radio = Variable.new
27
+ radio1 = RadioButton.new f1 do
28
+ #variable $radio
29
+ variable $config_hash
30
+ name "radio1"
31
+ text "ruby"
32
+ value "ruby"
33
+ color "red"
34
+ row 4
35
+ col 2
36
+ end
37
+ radio2 = RadioButton.new f1 do
38
+ #variable $radio
39
+ variable $config_hash
40
+ name "radio1"
41
+ text "jruby"
42
+ value "jruby"
43
+ color "green"
44
+ row 5
45
+ col 2
46
+ end
47
+ radio3 = RadioButton.new f1 do
48
+ #variable $radio
49
+ variable $config_hash
50
+ name "radio1"
51
+ text "macruby"
52
+ value "macruby"
53
+ color "cyan"
54
+ row 6
55
+ col 2
56
+ end
57
+ @tab2 = @tp.add_tab "&Settings"
58
+ f2 = @tab2.form
59
+ r = 3
60
+ butts = [ "Use &HTTP/1.0", "Use &frames", "&Use SSL" ]
61
+ bcodes = %w[ HTTP, FRAMES, SSL ]
62
+ butts.each_with_index do |t, i|
63
+ RubyCurses::CheckBox.new f2 do
64
+ text butts[i]
65
+ variable $config_hash
66
+ name bcodes[i]
67
+ row r+i
68
+ col 4
69
+ end
70
+ end
71
+ @tab3 = @tp.add_tab "&Editors"
72
+ f3 = @tab3.form
73
+ butts = %w[ &Vim E&macs &Jed &Other ]
74
+ bcodes = %w[ VIM EMACS JED OTHER]
75
+ row = 3
76
+ butts.each_with_index do |name, i|
77
+ RubyCurses::CheckBox.new f3 do
78
+ text name
79
+ variable $config_hash
80
+ name bcodes[i]
81
+ row row
82
+ col 4
83
+ end
84
+ row +=1
85
+ end
86
+ @tp.show
87
+ @tp.handle_keys
88
+ end
89
+ end
90
+ if $0 == __FILE__
91
+ # Initialize curses
92
+ begin
93
+ # XXX update with new color and kb
94
+ VER::start_ncurses # this is initializing colors via ColorMap.setup
95
+ $log = Logger.new("view.log")
96
+ $log.level = Logger::DEBUG
97
+ n = TestTabbedPane.new
98
+ rescue => ex
99
+ ensure
100
+ VER::stop_ncurses
101
+ p ex if ex
102
+ p(ex.backtrace.join("\n")) if ex
103
+ $log.debug( ex) if ex
104
+ $log.debug(ex.backtrace.join("\n")) if ex
105
+ end
106
+ end