rbcurse 1.4.0 → 1.4.1.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. data/NOTES +68 -0
  2. data/README.markdown +73 -330
  3. data/TODO2.txt +2 -2
  4. data/VERSION +1 -1
  5. data/examples/abasiclist.rb +8 -2
  6. data/examples/alpmenu.rb +1 -1
  7. data/examples/app.rb +1 -24
  8. data/examples/appdirtree.rb +1 -1
  9. data/examples/appemail.rb +8 -14
  10. data/examples/appemaillb.rb +2 -2
  11. data/examples/appgcompose.rb +7 -5
  12. data/examples/common/file.rb +40 -0
  13. data/examples/{rmail.rb → common/rmail.rb} +0 -0
  14. data/examples/data/README.markdown +9 -0
  15. data/examples/data/brew.txt +38 -0
  16. data/examples/data/color.2 +37 -0
  17. data/examples/data/gemlist.txt +60 -0
  18. data/examples/data/lotr.txt +12 -0
  19. data/examples/data/ports.txt +136 -0
  20. data/examples/data/tasks.txt +27 -0
  21. data/examples/{todocsv.csv → data/todocsv.csv} +0 -0
  22. data/examples/data/unix1.txt +21 -0
  23. data/examples/data/unix2.txt +11 -0
  24. data/examples/dbdemo.rb +49 -14
  25. data/examples/{appgmail.rb → deprecated/appgmail.rb} +1 -1
  26. data/examples/{splitp.rb → deprecated/splitp.rb} +0 -0
  27. data/examples/{testscrolllb.rb → deprecated/testscrolllb.rb} +0 -0
  28. data/examples/{testscrollp.rb → deprecated/testscrollp.rb} +0 -0
  29. data/examples/{testscrollta.rb → deprecated/testscrollta.rb} +0 -0
  30. data/examples/{testscrolltable.rb → deprecated/testscrolltable.rb} +0 -0
  31. data/examples/{testsplit.rb → deprecated/testsplit.rb} +0 -0
  32. data/examples/{testsplit2.rb → deprecated/testsplit2.rb} +0 -0
  33. data/examples/{testsplit3.rb → deprecated/testsplit3.rb} +0 -0
  34. data/examples/{testsplit3_1.rb → deprecated/testsplit3_1.rb} +0 -0
  35. data/examples/{testsplit3a.rb → deprecated/testsplit3a.rb} +0 -0
  36. data/examples/{testsplit3b.rb → deprecated/testsplit3b.rb} +0 -0
  37. data/examples/{testsplitta.rb → deprecated/testsplitta.rb} +0 -0
  38. data/examples/{testsplittv.rb → deprecated/testsplittv.rb} +0 -0
  39. data/examples/{testsplittvv.rb → deprecated/testsplittvv.rb} +0 -0
  40. data/examples/{testtpane.rb → deprecated/testtpane.rb} +0 -0
  41. data/examples/{testtpane2.rb → deprecated/testtpane2.rb} +0 -0
  42. data/examples/{testtpanetable.rb → deprecated/testtpanetable.rb} +0 -0
  43. data/examples/dirtree.rb +17 -7
  44. data/examples/experimental/resultsetdemo.rb +280 -0
  45. data/examples/experimental/testmform.rb +35 -0
  46. data/examples/{testscroller.rb → experimental/testscroller.rb} +1 -19
  47. data/examples/experimental/teststackflow.rb +111 -0
  48. data/examples/menu1.rb +1 -0
  49. data/examples/multispl.rb +4 -4
  50. data/examples/newmessagebox.rb +130 -0
  51. data/examples/newtabbedwindow.rb +100 -0
  52. data/examples/newtesttabp.rb +0 -3
  53. data/examples/qdfilechooser.rb +0 -3
  54. data/examples/rfe.rb +134 -18
  55. data/examples/rfe_renderer.rb +48 -2
  56. data/examples/sqlc.rb +2 -1
  57. data/examples/sqlm.rb +0 -2
  58. data/examples/table1.rb +1 -7
  59. data/examples/term2.rb +4 -1
  60. data/examples/test1.rb +0 -5
  61. data/examples/test2.rb +42 -31
  62. data/examples/testapp2.rb +8 -1
  63. data/examples/testchars.rb +0 -4
  64. data/examples/testcombo.rb +5 -9
  65. data/examples/testkeypress.rb +0 -3
  66. data/examples/testmenu.rb +0 -4
  67. data/examples/testmulticomp.rb +3 -5
  68. data/examples/testmulticontainer.rb +94 -0
  69. data/examples/testtable.rb +0 -5
  70. data/examples/testtabp.rb +16 -18
  71. data/examples/testtodo.rb +1 -5
  72. data/examples/testwsshortcuts.rb +64 -0
  73. data/examples/testwsshortcuts2.rb +126 -0
  74. data/examples/viewtodo.rb +1 -4
  75. data/lib/rbcurse.rb +1 -1
  76. data/lib/rbcurse/app.rb +92 -69
  77. data/lib/rbcurse/applicationheader.rb +46 -6
  78. data/lib/rbcurse/celleditor.rb +1 -9
  79. data/lib/rbcurse/comboboxcellrenderer.rb +0 -4
  80. data/lib/rbcurse/common/ansiparser.rb +117 -0
  81. data/{examples → lib/rbcurse/common}/appmethods.rb +25 -0
  82. data/lib/rbcurse/common/basestack.rb +407 -0
  83. data/lib/rbcurse/common/bordertitle.rb +41 -0
  84. data/lib/rbcurse/common/chunk.rb +177 -0
  85. data/lib/rbcurse/common/colorparser.rb +71 -0
  86. data/lib/rbcurse/common/keydefs.rb +30 -0
  87. data/lib/rbcurse/common/widgetshortcuts.rb +302 -0
  88. data/lib/rbcurse/deprecated/README.markdown +12 -0
  89. data/lib/rbcurse/{rscrollpane.rb → deprecated/rscrollpane.rb} +0 -0
  90. data/lib/rbcurse/{rsplitpane.rb → deprecated/rsplitpane.rb} +0 -0
  91. data/lib/rbcurse/{rsplitpane2.rb → deprecated/rsplitpane2.rb} +0 -0
  92. data/lib/rbcurse/{rviewport.rb → deprecated/rviewport.rb} +0 -0
  93. data/lib/rbcurse/experimental/README.markdown +14 -0
  94. data/lib/rbcurse/experimental/resultsettextview.rb +585 -0
  95. data/lib/rbcurse/experimental/stackflow.rb +478 -0
  96. data/lib/rbcurse/extras/bottomline.rb +85 -16
  97. data/lib/rbcurse/extras/box.rb +58 -0
  98. data/lib/rbcurse/extras/directorylist.rb +1 -1
  99. data/lib/rbcurse/extras/horizlist.rb +203 -0
  100. data/lib/rbcurse/extras/listselectable.rb +8 -0
  101. data/lib/rbcurse/extras/multiform.rb +330 -0
  102. data/lib/rbcurse/extras/multilinelabel.rb +142 -0
  103. data/lib/rbcurse/extras/newmessagebox.rb +328 -0
  104. data/lib/rbcurse/extras/newtabbedpane.rb +612 -0
  105. data/lib/rbcurse/extras/newtabbedwindow.rb +68 -0
  106. data/lib/rbcurse/extras/padreader.rb +189 -0
  107. data/lib/rbcurse/extras/rcomboedit.rb +256 -0
  108. data/lib/rbcurse/extras/resultsetbrowser.rb +281 -0
  109. data/lib/rbcurse/extras/statusline.rb +44 -6
  110. data/lib/rbcurse/extras/tabularwidget.rb +141 -104
  111. data/lib/rbcurse/extras/textpad.rb +516 -0
  112. data/lib/rbcurse/extras/viewer.rb +2 -0
  113. data/lib/rbcurse/io.rb +120 -3
  114. data/lib/rbcurse/listcellrenderer.rb +2 -1
  115. data/lib/rbcurse/listkeys.rb +1 -1
  116. data/lib/rbcurse/listscrollable.rb +72 -7
  117. data/lib/rbcurse/rbasiclistbox.rb +64 -12
  118. data/lib/rbcurse/rchangeevent.rb +0 -1
  119. data/lib/rbcurse/rcombo.rb +54 -59
  120. data/lib/rbcurse/rcommandwindow.rb +46 -10
  121. data/lib/rbcurse/rcontainer.rb +415 -0
  122. data/lib/rbcurse/rdialogs.rb +242 -165
  123. data/lib/rbcurse/rinputdataevent.rb +0 -1
  124. data/lib/rbcurse/rlistbox.rb +19 -9
  125. data/lib/rbcurse/rmessagebox.rb +1 -5
  126. data/lib/rbcurse/rmulticontainer.rb +64 -61
  127. data/lib/rbcurse/rmultitextview.rb +0 -2
  128. data/lib/rbcurse/rpopupmenu.rb +0 -2
  129. data/lib/rbcurse/rscrollform.rb +61 -12
  130. data/lib/rbcurse/rtabbedpane.rb +89 -73
  131. data/lib/rbcurse/rtabbedwindow.rb +53 -211
  132. data/lib/rbcurse/rtable.rb +0 -2
  133. data/lib/rbcurse/rtextarea.rb +14 -14
  134. data/lib/rbcurse/rtextview.rb +165 -50
  135. data/lib/rbcurse/rvimsplit.rb +15 -12
  136. data/lib/rbcurse/rwidget.rb +404 -150
  137. data/lib/rbcurse/table/tablecellrenderer.rb +1 -4
  138. data/lib/rbcurse/table/tabledatecellrenderer.rb +0 -3
  139. data/lib/ver/ncurses.rb +1 -9
  140. data/lib/ver/rpad.rb +375 -0
  141. data/lib/ver/window.rb +262 -440
  142. metadata +73 -31
@@ -9,7 +9,6 @@
9
9
  Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
10
10
 
11
11
  =end
12
- #require 'rubygems'
13
12
 
14
13
  # Event created when state changed (as in ViewPort)
15
14
  module RubyCurses
@@ -1,41 +1,43 @@
1
- =begin
2
- * Name: combo box
3
- * Description:
4
- * Author: rkumar
5
-
6
- --------
7
- * Date: 2008-12-16 22:03
8
- * License:
9
- Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
10
-
11
- =end
1
+ # ----------------------------------------------------------------------------- #
2
+ # File: rcombo.rb
3
+ # Description: Non-editable combo box.
4
+ # Make it dead-simple to use.
5
+ # This is a simpler version of the original ComboBox which allowed
6
+ # editing and used rlistbox. This simpler class is meant for the rbcurse
7
+ # core package and will only depend on a core class if at all.
8
+ # Author: rkumar http://github.com/rkumar/rbcurse/
9
+ # Date: 2011-11-11 - 21:42
10
+ # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
11
+ # Last update: use ,,L
12
+ # ----------------------------------------------------------------------------- #
13
+ #
12
14
  require 'rbcurse'
13
- require 'rbcurse/rlistbox'
14
15
 
15
- #include Ncurses # FFI 2011-09-8
16
16
  include RubyCurses
17
17
  module RubyCurses
18
- META_KEY = 128
19
18
  extend self
20
19
 
21
- # TODO :
22
- # i no longer use values, i now use "list" or better "list_data_model"
23
- # try to make it so values gets converted to list.
24
- # NOTE: 2010-10-01 13:25 spacebar and enter will popup in addition to Alt-Down
20
+ # the quick approach would be to use field, and just add a popup.
21
+ # Or since we are not editing, we could use a Label and a popup
22
+ # Or just display a label and a popup without using anything else.
23
+ #
24
+
25
25
  class ComboBox < Field
26
26
  include RubyCurses::EventHandler
27
27
  dsl_accessor :list_config
28
- dsl_accessor :insert_policy # NO_INSERT, INSERT_AT_TOP, INSERT_AT_BOTTOM, INSERT_AT_CURRENT
29
- # INSERT_AFTER_CURRENT, INSERT_BEFORE_CURRENT,INSERT_ALPHABETICALLY
30
28
 
31
29
  attr_accessor :current_index
32
30
  # the symbol you want to use for combos
33
31
  attr_accessor :COMBO_SYMBOL
34
32
  attr_accessor :show_symbol # show that funny symbol after a combo to signify its a combo
33
+ dsl_accessor :arrow_key_policy # :IGNORE :NEXT_ROW :POPUP
35
34
 
36
35
  def initialize form, config={}, &block
36
+ @arrow_key_policy = :ignore
37
+ @editable = false
38
+ @COMBO_SYMBOL = "v".ord # trying this out
39
+ @current_index = 0
37
40
  super
38
- @current_index ||= 0
39
41
  # added if check since it was overriding set_buffer in creation. 2009-01-18 00:03
40
42
  set_buffer @list[@current_index].dup if @buffer.nil? or @buffer.empty?
41
43
  init_vars
@@ -43,7 +45,8 @@ module RubyCurses
43
45
  end
44
46
  def init_vars
45
47
  super
46
- @show_symbol ||= true
48
+ @show_symbol = true if @show_symbol.nil? # if set to false don't touch
49
+ #@show_symbol = false if @label # 2011-11-13
47
50
  @COMBO_SYMBOL ||= FFI::NCurses::ACS_DARROW #GEQUAL
48
51
  bind_key(KEY_UP) { previous_row }
49
52
  bind_key(KEY_DOWN) { next_row }
@@ -59,13 +62,8 @@ module RubyCurses
59
62
  # convert given list to datamodel
60
63
  def list alist=nil
61
64
  return @list if alist.nil?
62
- @list = RubyCurses::ListDataModel.new(alist)
63
- end
64
- ##
65
- # set given datamodel
66
- def list_data_model ldm
67
- raise "Expecting list_data_model" unless ldm.is_a? RubyCurses::ListDataModel
68
- @list = ldm
65
+ #@list = RubyCurses::ListDataModel.new(alist)
66
+ @list = alist
69
67
  end
70
68
  ##
71
69
  # combo edit box key handling
@@ -78,26 +76,36 @@ module RubyCurses
78
76
  return :UNHANDLED
79
77
  end
80
78
  end
79
+ case @arrow_key_policy
80
+ when :ignore
81
+ if ch == KEY_DOWN or ch == KEY_UP
82
+ return :UNHANDLED
83
+ end
84
+ when :popup
85
+ if ch == KEY_DOWN or ch == KEY_UP
86
+ popup
87
+ end
88
+ end
81
89
  case ch
82
90
  #when KEY_UP # show previous value
83
91
  # previous_row
84
92
  #when KEY_DOWN # show previous value
85
93
  # next_row
86
94
  # adding spacebar to popup combo, as in microemacs 2010-10-01 13:21
87
- when 32, KEY_DOWN+ RubyCurses::META_KEY # alt down
95
+ when 32, KEY_DOWN+ META_KEY # alt down
88
96
  popup # pop up the popup
89
97
  else
90
98
  super
91
99
  end
92
100
  end
93
- def previous_row
101
+ def DEPprevious_row
94
102
  @current_index -= 1 if @current_index > 0
95
103
  set_buffer @list[@current_index].dup
96
104
  set_modified(true) ## ??? not required
97
105
  fire_handler :ENTER_ROW, self
98
106
  @list.on_enter_row self
99
107
  end
100
- def next_row
108
+ def DEPnext_row
101
109
  @current_index += 1 if @current_index < @list.length()-1
102
110
  set_buffer @list[@current_index].dup
103
111
  set_modified(true) ## ??? not required
@@ -117,6 +125,19 @@ module RubyCurses
117
125
  # added dup in PRESS since editing edit field mods this
118
126
  # on pressing ENTER, value set back and current_index updated
119
127
  def popup
128
+ @list_config ||= {}
129
+ @list_config[:row] ||= @row
130
+ @list_config[:col] ||= @col
131
+ @list_config[:relative_to] ||= self
132
+ # this does not allow us to bind to events in the list
133
+ index = popuplist @list, @list_config
134
+ if index
135
+ set_buffer @list[index].dup
136
+ set_modified(true) if @current_index != index
137
+ @current_index = index
138
+ end
139
+ end
140
+ def OLDpopup
120
141
  listconfig = (@list_config && @list_config.dup) || {}
121
142
  dm = @list
122
143
  # current item in edit box will be focussed when list pops up
@@ -199,43 +220,17 @@ module RubyCurses
199
220
  # no effect on the list, and wont trigger events.
200
221
  # Do not override.
201
222
  def on_leave
202
- if !@list.include? @buffer and !@buffer.strip.empty?
203
- _insert_policy = @insert_policy || :INSERT_AT_BOTTOM
204
- case _insert_policy
205
- when :INSERT_AT_BOTTOM, :INSERT_AT_END
206
- @list.append @buffer
207
- when :INSERT_AT_TOP
208
- @list.insert(0, @buffer)
209
- when :INSERT_AFTER_CURRENT
210
- @current_index += 1
211
- @list.insert(@current_index, @buffer)
212
-
213
- when :INSERT_BEFORE_CURRENT
214
- #_index = @current_index-1 if @current_index>0
215
- _index = @current_index
216
- @list.insert(_index, @buffer)
217
- when :INSERT_AT_CURRENT
218
- @list[@current_index]=@buffer
219
- when :NO_INSERT
220
- ; # take a break
221
- end
222
- end
223
223
  fire_handler :LEAVE, self
224
224
  end
225
225
 
226
226
  def repaint
227
227
  super
228
228
  c = @col + @display_length
229
- # @form.window.mvwvline( @row, c, ACS_VLINE, 1)
230
229
  if @show_symbol # 2009-01-11 18:47
231
230
  # i have changed c +1 to c, since we have no right to print beyond display_length
232
231
  @form.window.mvwaddch @row, c, @COMBO_SYMBOL # Ncurses::ACS_GEQUAL
232
+ @form.window.mvchgat(y=@row, x=c, max=1, Ncurses::A_REVERSE|Ncurses::A_UNDERLINE, $datacolor, nil)
233
233
  end
234
- # @form.window.mvwvline( @row, c+2, ACS_VLINE, 1)
235
- # @form.window.mvwaddch @row, c+2, Ncurses::ACS_S1
236
- # @form.window.mvwaddch @row, c+3, Ncurses::ACS_S9
237
- # @form.window.mvwaddch @row, c+4, Ncurses::ACS_LRCORNER
238
- # @form.window.mvwhline( @row, c+5, ACS_HLINE, 2)
239
234
  end
240
235
 
241
236
  end # class ComboBox
@@ -30,7 +30,7 @@ module RubyCurses
30
30
  @config.each_pair { |k,v| instance_variable_set("@#{k}",v) }
31
31
  instance_eval &block if block_given?
32
32
  if @layout.nil?
33
- set_layout(1,80, 27, 0)
33
+ set_layout(1,80, -1, 0)
34
34
  end
35
35
  @height = @layout[:height]
36
36
  @width = @layout[:width]
@@ -51,7 +51,8 @@ module RubyCurses
51
51
  @window.printstring 0,0,@title, $normalcolor #, 'normal' if @title
52
52
  @window.attroff(Ncurses.COLOR_PAIR($normalcolor) | Ncurses::A_REVERSE)
53
53
  else
54
- @window.printstring 0,0,@title, $normalcolor, 'reverse' if @title
54
+ #@window.printstring 0,0,@title, $normalcolor, 'reverse' if @title
55
+ title @title
55
56
  end
56
57
  @window.wrefresh
57
58
  @panel = @window.panel
@@ -62,6 +63,12 @@ module RubyCurses
62
63
  @row_offset = 1
63
64
  end
64
65
  end
66
+ # modify the window title, or get it if no params passed.
67
+ def title t=nil
68
+ return @title unless t
69
+ @title = t
70
+ @window.printstring 0,0,@title, $normalcolor, 'reverse' if @title
71
+ end
65
72
  ##
66
73
  ## message box
67
74
  def stopping?
@@ -135,6 +142,10 @@ module RubyCurses
135
142
  end
136
143
 
137
144
  def set_layout(height=0, width=0, top=0, left=0)
145
+ # negative means top should be n rows from last line. -1 is last line
146
+ if top < 0
147
+ top = Ncurses.LINES-top
148
+ end
138
149
  @layout = { :height => height, :width => width, :top => top, :left => left }
139
150
  @height = height
140
151
  @width = width
@@ -150,9 +161,21 @@ module RubyCurses
150
161
  end
151
162
  end
152
163
  end
153
- # do not go more than 3 columns and do not print more than window TODO FIXME
164
+ #
165
+ # Displays list in a window at bottom of screen, if large then 2 or 3 columns.
166
+ # @param [Array] list of string to be displayed
167
+ # @param [Hash] configuration options: indexing and indexcolor
168
+ # indexing - can be letter or number. Anything else will be ignored, however
169
+ # it will result in first letter being highlighted in indexcolor
170
+ # indexcolor - color of mnemonic, default green
154
171
  def display_menu list, options={}
155
172
  indexing = options[:indexing]
173
+ indexcolor = options[:indexcolor] || get_color($normalcolor, :yellow, :black)
174
+ indexatt = Ncurses::A_BOLD
175
+ #
176
+ # the index to start from (used when scrolling a long menu such as file list)
177
+ startindex = options[:startindex] || 0
178
+
156
179
  max_cols = 3 # maximum no of columns, we will reduce based on data size
157
180
  l_succ = "`"
158
181
  act_height = @height
@@ -169,11 +192,16 @@ module RubyCurses
169
192
  text = e.first + " ..."
170
193
  end
171
194
  if indexing == :number
195
+ mnem = i+1
172
196
  text = "%d. %s" % [i+1, text]
173
197
  elsif indexing == :letter
174
- text = "%s. %s" % [l_succ.succ!, text]
198
+ mnem = l_succ.succ!
199
+ text = "%s. %s" % [mnem, text]
175
200
  end
176
201
  @window.printstring i+@row_offset, 1, text, $normalcolor
202
+ if indexing
203
+ window.mvchgat(y=i+@row_offset, x=1, max=1, indexatt, indexcolor, nil)
204
+ end
177
205
  }
178
206
  else
179
207
  $log.debug "DDD inside two window" if $log.debug?
@@ -193,7 +221,6 @@ module RubyCurses
193
221
  col = 1
194
222
  $log.debug "DDDcols #{cols}, adv #{adv} size: #{lh} h: #{act_height} w #{@width} " if $log.debug?
195
223
  list.each_with_index { |e, i|
196
- # check that row + @row_offset < @top + @height or whatever TODO
197
224
  text = e
198
225
  # signify that there's a deeper level
199
226
  case e
@@ -201,11 +228,19 @@ module RubyCurses
201
228
  text = e.first + "..."
202
229
  end
203
230
  if indexing == :number
204
- text = "%d. %s" % [i+1, text]
231
+ mnem = i+1
232
+ text = "%d. %s" % [mnem, text]
205
233
  elsif indexing == :letter
206
- text = "%s. %s" % [l_succ.succ!, text]
234
+ mnem = l_succ.succ!
235
+ text = "%s. %s" % [mnem, text]
207
236
  end
208
- @window.printstring row+@row_offset, col, text, $normalcolor
237
+ # print only within range and window height
238
+ if i >= startindex && row < @window.actual_height
239
+ $log.debug "XXX: MENU #{i} > #{startindex} row #{row} col #{col} "
240
+ @window.printstring row+@row_offset, col, text, $normalcolor
241
+ if indexing
242
+ @window.mvchgat(y=row+@row_offset, x=col, max=1, indexatt, indexcolor, nil)
243
+ end
209
244
  colct += 1
210
245
  if colct == cols
211
246
  col = 1
@@ -214,6 +249,7 @@ module RubyCurses
214
249
  else
215
250
  col += adv
216
251
  end
252
+ end # startindex
217
253
  }
218
254
  end
219
255
  Ncurses::Panel.update_panels();
@@ -521,7 +557,7 @@ module RubyCurses
521
557
  @window.wmove row, c
522
558
  @window.wrefresh # FFI added to keep cursor display in synch with selection
523
559
  end
524
- def OLDbounds_check
560
+ def OLDbounds_check #:nodoc:
525
561
  @start = 0 if @start < 0
526
562
  row_offset = 1
527
563
  last = (@list.length)-(@height-row_offset-1)
@@ -529,7 +565,7 @@ module RubyCurses
529
565
  @start = last
530
566
  end
531
567
  end # bounds_check
532
- def handle_keys
568
+ def handle_keys #:nodoc:
533
569
  begin
534
570
  while((ch = @window.getchar()) != 999 )
535
571
  case ch
@@ -0,0 +1,415 @@
1
+ =begin
2
+ * Name: A container that manages components placed in it but
3
+ is not a form. Thus it can be safely placed as a widget
4
+ without all the complicatinos of a form embedded inside another.
5
+ NOTE: Still experimental
6
+ * Description
7
+ * Author: rkumar (http://github.com/rkumar/rbcurse/)
8
+ * Date: 21.10.11 - 00:29
9
+ * License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
10
+
11
+ * Last update: 23.10.11 - 00:29
12
+ == CHANGES
13
+ Focusables so we don't focus on label
14
+ == TODO
15
+ How to put blank lines in stack - use a blank label
16
+
17
+ - The contaomers and multis need to do their own on_enter and on_leave
18
+ management, they cannot rely on some other container doing it.
19
+ We can only rely on handle_key being called. HK should determine
20
+ whether any set_form row etc needs to be done.
21
+ - Should have its own stack and flow
22
+ =end
23
+
24
+ require 'rbcurse'
25
+
26
+ include RubyCurses
27
+ module RubyCurses
28
+ extend self
29
+
30
+ # This is an attempt at having a container which can contain multiple
31
+ # widgets without being a form itself. Having forms within forms
32
+ # complicates code too much, esp cursor positioning. e.g. tabbedpane
33
+
34
+ class Container < Widget
35
+
36
+ dsl_accessor :suppress_borders #to_print_borders
37
+ dsl_accessor :border_attrib, :border_color
38
+ dsl_accessor :title #set this on top
39
+ dsl_accessor :title_attrib #bold, reverse, normal
40
+ # should container stack objects ignoring users row col
41
+ # this is esp needed since App sets row and col which is too early
42
+ # This is now the default value, till i can redo things
43
+ #dsl_accessor :stack
44
+ dsl_accessor :positioning # absolute, relative, stack
45
+ attr_reader :current_component
46
+
47
+ def initialize form=nil, config={}, &block
48
+ @suppress_borders = false
49
+ @row_offset = @col_offset = 1
50
+ @_events ||= []
51
+ @stack = true
52
+ @positioning = :stack
53
+ super
54
+ @focusable = true
55
+ @editable = false
56
+ @components = [] # all components
57
+ @focusables = [] # focusable components, makes checks easier
58
+
59
+ init_vars
60
+ end
61
+ def init_vars
62
+ @repaint_required = true
63
+ @row_offset = @col_offset = 0 if @suppress_borders # FIXME supposed to use this !!
64
+
65
+ @internal_width = 2
66
+ @internal_width = 1 if @suppress_borders
67
+ @name ||= "AContainer"
68
+ @first_time = true
69
+
70
+ end
71
+
72
+ # NOTE: since we are handling the traversal, we delink the object from any
73
+ # form's widgets array that might have been added. Whenever a form is available,
74
+ # we set it (without adding widget to it) so it can print using the form's window.
75
+ #
76
+ # @param [Widget] to add
77
+ def add *items
78
+ items.each do |c|
79
+ raise ArgumentError, "Nil component passed to add" unless c
80
+ if c.is_a? Widget
81
+ if c.form && c.form != @form
82
+ $log.debug " removing widget VIMSPLIT #{c.class} wr: #{c.row} row:#{@row} ht:#{@height} "
83
+ c.form.remove_widget c
84
+ c.form = nil
85
+ # or should i just stack them myself and screw what you've asked for
86
+ end
87
+ # take it out of form's control. We will control it.
88
+ if c.form
89
+ c.form.remove_widget c
90
+ end
91
+ # shoot, what if at this point the container does not have a form
92
+ attach_form c if @form
93
+ end
94
+ # most likely if you have created both container and widgets
95
+ # inside app, it would have given row after container
96
+
97
+ @components << c
98
+ if c.focusable
99
+ @focusables << c
100
+ @current_component ||= c # only the first else cursor falls on last on enter
101
+ end
102
+
103
+ end # items each
104
+ self
105
+ end
106
+
107
+ # When we get a form, we silently attach it to this object, without the form
108
+ # knowing. We don't want form managing this object.
109
+ def attach_form c
110
+ c.form = @form
111
+ c.override_graphic @graphic
112
+ c.parent_component = self
113
+ end
114
+ alias :add_widget :add
115
+ def widgets; @components; end
116
+ # what of by_name
117
+
118
+
119
+ # correct coordinates of comp esp if App has stacked them after this
120
+ # container
121
+ # It is best to use the simple stack feature. The rest could change at any time
122
+ # and is quite arbitrary. Some folks may set absolute locations if container
123
+ # is directly on a form, others may set relative locations if it is inside a
124
+ # tabbed pane or other container. Thus, stacks are best
125
+ def correct_component c
126
+ raise "Form is still not set in Container" unless @form
127
+ attach_form(c) unless c.form
128
+ @last_row ||= @row + 1
129
+ inset = 2
130
+ # 2011-10-20 current default behaviour is to stack
131
+ if @positioning == :stack
132
+ c.row = @last_row
133
+ c.col = @col + inset
134
+
135
+ # do not advance row, save col for next row
136
+ @last_row += 1
137
+ elsif @positioning == :relative # UNTESTED NOTE
138
+ if (c.row || 0) <= 0
139
+ $log.warn "c.row in CONTAINER is #{c.row} "
140
+ c.row = @last_row
141
+ @last_row += 1
142
+ elsif c.row > @row + @height -1
143
+ $log.warn "c.row in CONTAINER exceeds container. #{c.row} "
144
+ c.row -= @height - @row_offset
145
+ else
146
+ # this is where it should come
147
+ c.row += @row + @row_offset
148
+ @last_row = c.row + 1
149
+ end
150
+ if (c.col || 0) <= 0
151
+ c.col = @col + inset + @col_offset
152
+ elsif c.col > @col + @width -1
153
+ c.col -= @width
154
+ elsif c.col == @col
155
+ c.col += @col_offset + inset
156
+ else #f c.col < @col
157
+ c.col += @col+@col_offset
158
+ end
159
+ $log.debug "XXX: CORRECT #{c.name} r:#{c.row} c:#{c.col} "
160
+ end
161
+ @first_time = false
162
+ end
163
+ def check_component c
164
+ raise "row is less than container #{c.row} #{@row} " if c.row <= @row
165
+ raise "col is less than container #{c.col} #{@col} " if c.col <= @col
166
+ end
167
+
168
+ public
169
+ # repaint object
170
+ # called by Form, and sometimes parent component (if not form).
171
+ def repaint
172
+ my_win = @form ? @form.window : @target_window
173
+ @graphic = my_win unless @graphic
174
+ raise " #{@name} NO GRAPHIC set as yet CONTAINER paint " unless @graphic
175
+ @components.each { |e| correct_component e } if @first_time
176
+ #@components.each { |e| check_component e } # seeme one if printing out
177
+
178
+ #return unless @repaint_required
179
+
180
+ # if some major change has happened then repaint everything
181
+ if @repaint_required
182
+ $log.debug " VIM repaint graphic #{@graphic} "
183
+ print_borders unless @suppress_borders # do this once only, unless everything changes
184
+ @components.each { |e| e.repaint_all(true); e.repaint }
185
+ else
186
+ @components.each { |e| e.repaint }
187
+ end # if repaint_required
188
+
189
+ @repaint_required = false
190
+ end
191
+
192
+ private
193
+ def print_borders
194
+ width = @width
195
+ height = @height-1 # 2010-01-04 15:30 BUFFERED HEIGHT
196
+ window = @graphic # 2010-01-04 12:37 BUFFERED
197
+ startcol = @col
198
+ startrow = @row
199
+ @color_pair = get_color($datacolor)
200
+ #$log.debug "rlistb #{name}: window.print_border #{startrow}, #{startcol} , h:#{height}, w:#{width} , @color_pair, @attr "
201
+ window.print_border startrow, startcol, height, width, @color_pair, @attr
202
+ print_title
203
+ end
204
+ def print_title
205
+ $log.debug "CONTAINER PRINTING TITLE at #{row} #{col} "
206
+ @graphic.printstring( @row, @col+(@width-@title.length)/2, @title, @color_pair, @title_attrib) unless @title.nil?
207
+ end
208
+
209
+ public
210
+ # called by parent or form, otherwise its private
211
+ def handle_key ch
212
+ $log.debug " CONTAINER handle_key #{ch} "
213
+ return if @components.empty?
214
+ _multiplier = ($multiplier == 0 ? 1 : $multiplier )
215
+
216
+ # should this go here 2011-10-19
217
+ unless @_entered
218
+ $log.warn "XXX WARN: calling ON_ENTER since in this situation it was not called"
219
+ on_enter
220
+ end
221
+ if ch == KEY_TAB
222
+ $log.debug "CONTAINER GOTO NEXT TAB"
223
+ return goto_next_component
224
+ elsif ch == KEY_BTAB
225
+ return goto_prev_component
226
+ end
227
+ comp = @current_component
228
+ $log.debug " CONTAINER handle_key #{ch}: #{comp}"
229
+ if comp
230
+ ret = comp.handle_key(ch)
231
+ $log.debug " CONTAINER handle_key#{ch}: #{comp} returned #{ret} "
232
+ if ret != :UNHANDLED
233
+ comp.repaint # NOTE: if we don;t do this, then it won't get repainted. I will have to repaint ALL
234
+ # in repaint of this.
235
+ return ret
236
+ end
237
+ $log.debug "XXX CONTAINER key unhandled by comp #{comp.name} "
238
+ else
239
+ $log.warn "XXX CONTAINER key unhandled NULL comp"
240
+ end
241
+ case ch
242
+ when ?\C-c.getbyte(0)
243
+ $multiplier = 0
244
+ return 0
245
+ when ?0.getbyte(0)..?9.getbyte(0)
246
+ $log.debug " VIM coming here to set multiplier #{$multiplier} "
247
+ $multiplier *= 10 ; $multiplier += (ch-48)
248
+ return 0
249
+ end
250
+ ret = process_key ch, self
251
+ # allow user to map left and right if he wants
252
+ if ret == :UNHANDLED
253
+ case ch
254
+ when KEY_UP
255
+ # form will pick this up and do needful
256
+ return goto_prev_component #unless on_first_component?
257
+ when KEY_LEFT
258
+ # if i don't check for first component, key will go back to form,
259
+ # but not be processes. so focussed remain here, but be false.
260
+ # In case of returnign an unhandled TAB, on_leave will happen and cursor will move to
261
+ # previous component outside of this.
262
+ return goto_prev_component unless on_first_component?
263
+ when KEY_RIGHT
264
+ return goto_next_component #unless on_last_component?
265
+ when KEY_DOWN
266
+ return goto_next_component #unless on_last_component?
267
+ else
268
+ @_entered = false
269
+ return :UNHANDLED
270
+ end
271
+ end
272
+
273
+ $multiplier = 0
274
+ return 0
275
+ end
276
+ # Actually we should only go to current component if it accepted
277
+ # a key stroke. if user tabbed thru it, then no point going back to
278
+ # it. Go to first or last depending on TAB or BACKTAB otherwise.
279
+ # NOTE: if user comes in using DOWN or UP, last traversed component will get the focus
280
+ #
281
+ def on_enter
282
+ # if BTAB, the last comp XXX they must be focusable FIXME
283
+ if $current_key == KEY_BTAB || $current_key == KEY_UP
284
+ @current_component = @focusables.last
285
+ else
286
+ @current_component = @focusables.first
287
+ end
288
+ return unless @current_component
289
+ $log.debug " CONTAINER came to ON_ENTER #{@current_component} "
290
+ set_form_row
291
+ @_entered = true
292
+ end
293
+ # we cannot be sure that this will be called especially if this is embedded
294
+ # inside some other component
295
+ def on_leave
296
+ @_entered = false
297
+ super
298
+ end
299
+ def goto_next_component
300
+ if @current_component != nil
301
+ leave_current_component
302
+ if on_last_component?
303
+ #@_entered = false
304
+ return :UNHANDLED
305
+ end
306
+ @current_index = @focusables.index(@current_component)
307
+ index = @current_index + 1
308
+ f = @focusables[index]
309
+ if f
310
+ @current_index = index
311
+ @current_component = f
312
+ return set_form_row
313
+ end
314
+ end
315
+ @_entered = false
316
+ return :UNHANDLED
317
+ end
318
+ def goto_prev_component
319
+ if @current_component != nil
320
+ leave_current_component
321
+ if on_first_component?
322
+ @_entered = false
323
+ return :UNHANDLED
324
+ end
325
+ @current_index = @focusables.index(@current_component)
326
+ index = @current_index -= 1
327
+ f = @focusables[i]
328
+ if f
329
+ @current_index = index
330
+ @current_component = f
331
+ return set_form_row
332
+ end
333
+ end
334
+ return :UNHANDLED
335
+ end
336
+ # private
337
+ # XXX why are we calling 3 methods in a row, why not OE manages these 3
338
+ # There's double calling going on.
339
+ def set_form_row
340
+ return :UNHANDLED if @current_component.nil?
341
+ cc = @current_component
342
+ $log.debug "CONT #{@name} set_form_row calling sfr for #{cc.name}, r #{cc.row} c: #{cc.col} "
343
+ $log.debug " CONTAINER on enter sfr #{@current_component.name} #{@current_component} "
344
+
345
+ # bug caught here. we were printing a field before it had been set, so it printed out
346
+ @components.each { |e| correct_component e } if @first_time
347
+ @current_component.on_enter
348
+ @current_component.set_form_row # why was this missing in vimsplit. is it
349
+ $log.debug "CONT2 #{@name} set_form_row calling sfr for #{cc.name}, r #{cc.row} c: #{cc.col} "
350
+ # that on_enter does a set_form_row
351
+ @current_component.set_form_col # XXX
352
+ @current_component.repaint # OMG this could happen before we've set row and col
353
+ # XXX compo should do set_form_row and col if it has that
354
+ end
355
+ #
356
+ def set_form_col
357
+ return if @current_component.nil?
358
+ $log.debug " #{@name} CONTAINER EMPTY set_form_col calling sfc for #{@current_component.name} "
359
+ # already called from above.
360
+ #@current_component.set_form_col
361
+ end
362
+ # leave the component we are on.
363
+ # This should be followed by all containers, so that the on_leave action
364
+ # of earlier comp can be displayed, such as dimming components selections
365
+ def leave_current_component
366
+ @current_component.on_leave
367
+ # NOTE this is required, since repaint will just not happen otherwise
368
+ # Some components are erroneously repainting all, after setting this to true so it is
369
+ # working there.
370
+ @current_component.repaint_required true
371
+ $log.debug " after on_leave RCONT XXX #{@current_component.focussed} #{@current_component.name}"
372
+ @current_component.repaint
373
+ end
374
+
375
+ # is focus on first component FIXME check for focusable
376
+ def on_first_component?
377
+ @current_component == @focusables.first
378
+ end
379
+ # is focus on last component FIXME check for focusable
380
+ def on_last_component?
381
+ @current_component == @focusables.last
382
+ end
383
+ # set focus on given component
384
+ # Sometimes you have the handle to component, and you want to move focus to it
385
+ def goto_component comp
386
+ return if comp == @current_component
387
+ leave_current_component
388
+ @current_component = comp
389
+ set_form_row
390
+ end
391
+
392
+ # ADD HERE ABOVe
393
+ end # class
394
+ end # module
395
+
396
+ if __FILE__ == $PROGRAM_NAME
397
+ require 'rbcurse/app'
398
+ App.new do
399
+ f1 = field "name", :maxlen => 20, :display_length => 20, :bgcolor => :white,
400
+ :color => :black, :text => "abc", :label => " Name: ", :label_color_pair => @datacolor
401
+ f2 = field "email", :display_length => 20, :bgcolor => :white,
402
+ :color => :blue, :text => "me@google.com", :label => "Email: ", :label_color_pair => @datacolor
403
+ f3 = radio :group => :grp, :text => "red", :value => "RED", :color => :red
404
+ f4 = radio :group => :grp, :text => "blue", :value => "BLUE", :color => :blue
405
+ f5 = radio :group => :grp, :text => "green", :value => "GREEN", :color => :green
406
+ stack :margin_top => 2, :margin => 2 do
407
+ r = container :row => 1, :col => 2, :width => 80, :height => 20, :title => "A container"
408
+ r.add(f1)
409
+ r.add(f2)
410
+ r.add(f3,f4,f5)
411
+ sl = status_line
412
+ end # stack
413
+
414
+ end # app
415
+ end # if