canis 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +45 -0
  3. data/CHANGES +52 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +24 -0
  7. data/Rakefile +2 -0
  8. data/canis.gemspec +25 -0
  9. data/examples/alpmenu.rb +46 -0
  10. data/examples/app.sample +19 -0
  11. data/examples/appemail.rb +191 -0
  12. data/examples/atree.rb +105 -0
  13. data/examples/bline.rb +181 -0
  14. data/examples/common/devel.rb +319 -0
  15. data/examples/common/file.rb +93 -0
  16. data/examples/data/README.markdown +9 -0
  17. data/examples/data/brew.txt +38 -0
  18. data/examples/data/color.2 +37 -0
  19. data/examples/data/gemlist.txt +59 -0
  20. data/examples/data/lotr.txt +12 -0
  21. data/examples/data/ports.txt +136 -0
  22. data/examples/data/table.txt +37 -0
  23. data/examples/data/tasks.csv +88 -0
  24. data/examples/data/tasks.txt +27 -0
  25. data/examples/data/todo.txt +16 -0
  26. data/examples/data/todocsv.csv +28 -0
  27. data/examples/data/unix1.txt +21 -0
  28. data/examples/data/unix2.txt +11 -0
  29. data/examples/dbdemo.rb +506 -0
  30. data/examples/dirtree.rb +177 -0
  31. data/examples/newtabbedwindow.rb +100 -0
  32. data/examples/newtesttabp.rb +92 -0
  33. data/examples/tabular.rb +212 -0
  34. data/examples/tasks.rb +179 -0
  35. data/examples/term2.rb +88 -0
  36. data/examples/testbuttons.rb +307 -0
  37. data/examples/testcombo.rb +102 -0
  38. data/examples/testdb.rb +182 -0
  39. data/examples/testfields.rb +208 -0
  40. data/examples/testflowlayout.rb +43 -0
  41. data/examples/testkeypress.rb +98 -0
  42. data/examples/testlistbox.rb +187 -0
  43. data/examples/testlistbox1.rb +199 -0
  44. data/examples/testmessagebox.rb +144 -0
  45. data/examples/testprogress.rb +116 -0
  46. data/examples/testree.rb +107 -0
  47. data/examples/testsplitlayout.rb +53 -0
  48. data/examples/testsplitlayout1.rb +49 -0
  49. data/examples/teststacklayout.rb +48 -0
  50. data/examples/testwsshortcuts.rb +68 -0
  51. data/examples/testwsshortcuts2.rb +129 -0
  52. data/lib/canis.rb +16 -0
  53. data/lib/canis/core/docs/index.txt +104 -0
  54. data/lib/canis/core/docs/list.txt +16 -0
  55. data/lib/canis/core/docs/style_help.yml +34 -0
  56. data/lib/canis/core/docs/tabbedpane.txt +15 -0
  57. data/lib/canis/core/docs/table.txt +31 -0
  58. data/lib/canis/core/docs/textpad.txt +48 -0
  59. data/lib/canis/core/docs/tree.txt +23 -0
  60. data/lib/canis/core/include/.DS_Store +0 -0
  61. data/lib/canis/core/include/action.rb +83 -0
  62. data/lib/canis/core/include/actionmanager.rb +49 -0
  63. data/lib/canis/core/include/appmethods.rb +179 -0
  64. data/lib/canis/core/include/bordertitle.rb +49 -0
  65. data/lib/canis/core/include/canisparser.rb +100 -0
  66. data/lib/canis/core/include/colorparser.rb +437 -0
  67. data/lib/canis/core/include/defaultfilerenderer.rb +64 -0
  68. data/lib/canis/core/include/io.rb +320 -0
  69. data/lib/canis/core/include/layouts/SplitLayout.rb +161 -0
  70. data/lib/canis/core/include/layouts/abstractlayout.rb +213 -0
  71. data/lib/canis/core/include/layouts/flowlayout.rb +104 -0
  72. data/lib/canis/core/include/layouts/stacklayout.rb +109 -0
  73. data/lib/canis/core/include/listbindings.rb +89 -0
  74. data/lib/canis/core/include/listeditable.rb +319 -0
  75. data/lib/canis/core/include/listoperations.rb +61 -0
  76. data/lib/canis/core/include/listselectionmodel.rb +388 -0
  77. data/lib/canis/core/include/multibuffer.rb +173 -0
  78. data/lib/canis/core/include/ractionevent.rb +73 -0
  79. data/lib/canis/core/include/rchangeevent.rb +27 -0
  80. data/lib/canis/core/include/rhistory.rb +95 -0
  81. data/lib/canis/core/include/rinputdataevent.rb +47 -0
  82. data/lib/canis/core/include/textdocument.rb +111 -0
  83. data/lib/canis/core/include/vieditable.rb +175 -0
  84. data/lib/canis/core/include/widgetmenu.rb +66 -0
  85. data/lib/canis/core/system/colormap.rb +165 -0
  86. data/lib/canis/core/system/keydefs.rb +32 -0
  87. data/lib/canis/core/system/ncurses.rb +237 -0
  88. data/lib/canis/core/system/panel.rb +129 -0
  89. data/lib/canis/core/system/window.rb +1081 -0
  90. data/lib/canis/core/util/ansiparser.rb +119 -0
  91. data/lib/canis/core/util/app.rb +696 -0
  92. data/lib/canis/core/util/basestack.rb +412 -0
  93. data/lib/canis/core/util/defaultcolorparser.rb +84 -0
  94. data/lib/canis/core/util/extras/README +5 -0
  95. data/lib/canis/core/util/extras/bottomline.rb +1815 -0
  96. data/lib/canis/core/util/extras/padreader.rb +192 -0
  97. data/lib/canis/core/util/focusmanager.rb +31 -0
  98. data/lib/canis/core/util/helpmanager.rb +160 -0
  99. data/lib/canis/core/util/oldwidgetshortcuts.rb +304 -0
  100. data/lib/canis/core/util/promptmenu.rb +235 -0
  101. data/lib/canis/core/util/rcommandwindow.rb +933 -0
  102. data/lib/canis/core/util/rdialogs.rb +520 -0
  103. data/lib/canis/core/util/textutils.rb +74 -0
  104. data/lib/canis/core/util/viewer.rb +238 -0
  105. data/lib/canis/core/util/widgetshortcuts.rb +508 -0
  106. data/lib/canis/core/widgets/applicationheader.rb +103 -0
  107. data/lib/canis/core/widgets/box.rb +58 -0
  108. data/lib/canis/core/widgets/divider.rb +310 -0
  109. data/lib/canis/core/widgets/extras/README.md +12 -0
  110. data/lib/canis/core/widgets/extras/rtextarea.rb +960 -0
  111. data/lib/canis/core/widgets/extras/stackflow.rb +474 -0
  112. data/lib/canis/core/widgets/keylabelprinter.rb +194 -0
  113. data/lib/canis/core/widgets/listbox.rb +326 -0
  114. data/lib/canis/core/widgets/listfooter.rb +86 -0
  115. data/lib/canis/core/widgets/rcombo.rb +210 -0
  116. data/lib/canis/core/widgets/rcontainer.rb +415 -0
  117. data/lib/canis/core/widgets/rlink.rb +30 -0
  118. data/lib/canis/core/widgets/rmenu.rb +970 -0
  119. data/lib/canis/core/widgets/rmenulink.rb +30 -0
  120. data/lib/canis/core/widgets/rmessagebox.rb +400 -0
  121. data/lib/canis/core/widgets/rprogress.rb +118 -0
  122. data/lib/canis/core/widgets/rtabbedpane.rb +631 -0
  123. data/lib/canis/core/widgets/rtabbedwindow.rb +70 -0
  124. data/lib/canis/core/widgets/rwidget.rb +3634 -0
  125. data/lib/canis/core/widgets/scrollbar.rb +147 -0
  126. data/lib/canis/core/widgets/statusline.rb +113 -0
  127. data/lib/canis/core/widgets/table.rb +1072 -0
  128. data/lib/canis/core/widgets/tabular.rb +264 -0
  129. data/lib/canis/core/widgets/textpad.rb +1674 -0
  130. data/lib/canis/core/widgets/tree.rb +690 -0
  131. data/lib/canis/core/widgets/tree/treecellrenderer.rb +150 -0
  132. data/lib/canis/core/widgets/tree/treemodel.rb +432 -0
  133. data/lib/canis/version.rb +3 -0
  134. metadata +229 -0
@@ -0,0 +1,30 @@
1
+ require 'canis'
2
+ ##
3
+ module Canis
4
+ class Link < Button
5
+ dsl_property :description
6
+
7
+
8
+ def initialize form, config={}, &block
9
+ super
10
+ @text_offset = 0
11
+ # haha we've never done this, pin the cursor up on 0,0
12
+ @col_offset = -1
13
+ # this won't be triggered since the shortcut does not set menmo
14
+ # unless form is there.
15
+ # Sometimes the mnemonic is not in text, such as '?'
16
+ if @mnemonic
17
+ form.bind_key(@mnemonic.downcase, self){ self.fire }
18
+ end
19
+ @width = config[:width]
20
+ end
21
+ def fire
22
+ super
23
+ self.focus
24
+ end
25
+ def getvalue_for_paint
26
+ getvalue()
27
+ end
28
+ ##
29
+ end # class
30
+ end # module
@@ -0,0 +1,970 @@
1
+ =begin
2
+ * Name: menu and related classes
3
+ * Description
4
+ * Author: jkepler
5
+ * I am redoing this totally, since this was one my first ruby programs and needs
6
+ * simplification. It was hard to maintain.
7
+ TODO
8
+ -- cursor to be on current menuitem if possible ... UNABLE TO !!
9
+ -- Number and letter indexing for item_list
10
+ -- Use Box characters and hline for separator
11
+ -- MenuSeparator and MenuItem should be common to popups and menus, so we don't need
12
+ 2 separate names, there was clobbering the same namespace.
13
+
14
+ ?? Also, we should move to Action classes as against just blokcs of code. And action class would have
15
+ a user friendly string to identifiy the action, as well as a disabled option.
16
+
17
+ --------
18
+ * Date: 2011-09-23 (old 2008-11-14 23:43 )
19
+ == Major changes v1.3.1
20
+ 2011-09-24 V1.3.1 added item_list for dynamic menuitem generation, see examples/menu1.rb
21
+ 2011-09-24 V1.3.1 added multicolumn outputs
22
+ 2011-09-24 V1.3.1 left and right keys on menua, C-g to abort
23
+
24
+ * License:
25
+ Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
26
+
27
+ =end
28
+ #require 'logger'
29
+ require 'canis'
30
+
31
+ include Canis
32
+ module Canis
33
+ extend self
34
+
35
+
36
+ # The separator that separates menuitems, helping to group them.
37
+ class MenuSeparator
38
+ attr_accessor :enabled
39
+ attr_accessor :parent
40
+ attr_accessor :row
41
+ attr_accessor :col
42
+ attr_accessor :coffset
43
+ attr_accessor :width
44
+ attr_accessor :color, :bgcolor # 2011-09-25 V1.3.1
45
+ def initialize
46
+ @enable = false
47
+ end
48
+ def repaint
49
+ acolor = get_color($reversecolor, @color, @bgcolor)
50
+ @parent.window.printstring( @row, 0, "|%s|" % ("-"*@width), acolor)
51
+ end
52
+ def destroy
53
+ end
54
+ def on_enter
55
+ end
56
+ def on_leave
57
+ end
58
+ def to_s
59
+ ""
60
+ end
61
+ end
62
+ ##
63
+ # Items in menus. These will usually result in an action which closes the entire
64
+ # menubar.
65
+ class MenuItem
66
+ attr_accessor :parent
67
+ # attr_accessor :window
68
+ attr_accessor :row
69
+ attr_accessor :col
70
+ attr_accessor :coffset
71
+ attr_accessor :width
72
+ attr_writer :accelerator
73
+ attr_accessor :enabled
74
+ attr_accessor :color, :bgcolor # 2011-09-25 V1.3.1
75
+ attr_accessor :color_pair # 2011-09-25 V1.3.1
76
+ attr_reader :active_index # 2011-09-24 V1.3.1 trying to do a right
77
+ attr_accessor :text, :mnemonic # changed reader to accessor
78
+ def initialize text, mnemonic=nil, &block
79
+ @text = text
80
+ @enabled = true
81
+ # check for mnem that is not one char, could be an accelerator
82
+ if mnemonic
83
+ if mnemonic.length != 1
84
+ $log.error "MenuItem #{text} mnemonic #{mnemonic} should be one character. Maybe you meant accelerator? "
85
+ mnemonic = nil
86
+ end
87
+ end
88
+ @mnemonic = mnemonic
89
+ instance_eval &block if block_given?
90
+ end
91
+ def to_s
92
+ "#{@text} #{@accelerator}"
93
+ end
94
+ def command *args, &block
95
+ $log.debug ">>>command : #{@text} "
96
+ @command = block if block_given?
97
+ alert "Command nil or some error! #{text} " unless @command
98
+ @args = args
99
+ end
100
+ # add accelerator for a menu item
101
+ # NOTE: accelerator means that the application has tied this string to some action, outside
102
+ # of the menu bar. It does not mean that the menu bar will trigger the action. So the app still has to
103
+ # define the action and bind a key to that accelerator. This is only informative.
104
+ # Had to do this since dsl_accessor was throwing some nilclass does not have []= nomethod error
105
+ # This allows user to put accelerator inside dsl block
106
+ # @example
107
+ # accelerator "Ctrl-X"
108
+ def accelerator(*val)
109
+ if val.empty?
110
+ return @accelerator
111
+ else
112
+ @accelerator = val[0]
113
+ end
114
+ end
115
+ def on_enter #item
116
+ highlight
117
+ #@parent.window.wmove @row, @col+1 # 2011-09-25 V1.3.1 NO EFFECT
118
+ end
119
+ def on_leave
120
+ highlight false
121
+ end
122
+ ## XXX it could be a menu again
123
+ # We should not be firing a :NO_MENUITEMS
124
+ def fire
125
+ $log.debug ">>>fire menuitem : #{@text} #{@command} "
126
+ @command.call self, *@args if !@command.nil?
127
+ @parent.clear_menus
128
+ return :CLOSE # added 2009-01-02 00:09 to close only actions, not submenus
129
+ end
130
+ def highlight tf=true
131
+ if @parent.nil? or @parent.window.nil?
132
+ #$log.debug "HL XXX #{self} parent nil"
133
+ #$log.debug "HL XXX #{self} - > #{@parent} parent nil"
134
+ end
135
+ if tf
136
+ #color = $datacolor
137
+ #@parent.window.mvchgat(y=@row, x=1, @width, Ncurses::A_NORMAL, color, nil)
138
+ # above line did not work in vt100, 200 terminals, next works.
139
+ # @parent.window.mvchgat(y=@row, x=1, @width, Ncurses::A_REVERSE, $reversecolor, nil) # changed 2011 dts 2011-09-24 multicolumn, 1 skips the border
140
+ @color_pair ||= get_color($reversecolor, @color, @bgcolor)
141
+ @parent.window.mvchgat(y=@row, x=@col+1, @width, Ncurses::A_REVERSE, @color_pair, nil)
142
+ #@parent.window.mvaddch @row, @col, "*".ord
143
+ #@parent.window.wmove @row, @col # 2011-09-25 V1.3.1 NO EFFECT
144
+ else
145
+ repaint
146
+ end
147
+ @parent.window.wrefresh unless @parent.window.nil? ## XXX 2009-01-21 22:00
148
+ end
149
+ def repaint # menuitem.repaint
150
+ if @parent.nil? or @parent.window.nil?
151
+ $log.debug "repaint #{self} parent nil"
152
+ # return
153
+ end
154
+ r = @row
155
+ c = @col
156
+ ltext = text
157
+ ltext = "* No Items *" if text == :NO_MENUITEMS
158
+ @color_pair = get_color($reversecolor, @color, @bgcolor)
159
+ #acolor = $reversecolor
160
+ acolor = @color_pair
161
+ acolor = get_color($reversecolor, 'green', @bgcolor) if !@enabled
162
+ # @parent.window.printstring( @row, 0, "|%-*s|" % [@width, ltext], acolor) # changed 2011 2011-09-24
163
+ @parent.window.printstring( @row, c, "|%-*s|" % [@width, ltext], acolor)
164
+ if @enabled # 2010-09-10 23:56
165
+ if !@accelerator.nil?
166
+ # FIXME add c earlier 0 was offset
167
+ @parent.window.printstring( r, (@width+1)-@accelerator.length, @accelerator, acolor)
168
+ elsif !@mnemonic.nil?
169
+ m = @mnemonic
170
+ ix = text.index(m) || text.index(m.swapcase)
171
+ charm = text[ix,1]
172
+ #@parent.window.printstring( r, ix+1, charm, $datacolor) if !ix.nil?
173
+ # prev line changed since not working in vt100 and vt200
174
+ @parent.window.printstring( r, ix+1, charm, $reversecolor, 'reverse') if !ix.nil?
175
+ end
176
+ #@parent.window.wmove r, c # NO EFFECT
177
+ end
178
+ end
179
+ def destroy
180
+ $log.debug "DESTROY menuitem #{@text}"
181
+ end
182
+ end
183
+ ## class Menu. Contains menuitems, and can be a menuitem itself.
184
+ # Opens out another list of menuitems.
185
+ class Menu < MenuItem
186
+ attr_accessor :parent
187
+ attr_accessor :row
188
+ attr_accessor :col
189
+ attr_accessor :coffset
190
+ attr_accessor :width
191
+ attr_accessor :enabled
192
+ attr_reader :text
193
+ attr_reader :items
194
+ attr_reader :window
195
+ attr_reader :panel
196
+ attr_reader :current_menu
197
+ attr_reader :row_margin ## 2009-01-21 12:06 NEW
198
+ ## this keeps a stack of menus. if we coud somehow put this in
199
+ # menubar would be great.
200
+ @@menus = []
201
+ @@row = 0
202
+ @@col = 0
203
+
204
+ def initialize text, &block
205
+ @text = text
206
+ @items = []
207
+ @enabled = true
208
+ @current_menu = []
209
+ super text, nil, &block
210
+ @row ||=10
211
+ @col ||=10
212
+ @coffset = 0
213
+ @@menus ||= []
214
+ @active_index = nil # 2011-09-25 V1.3.1 otherwise crashing in select_right
215
+ end
216
+ ## called upon firing so when we next show menubar there are not any left overs in here.
217
+ def clear_menus
218
+ @@menus = []
219
+ end
220
+ def to_s
221
+ @text
222
+ end
223
+ # item could be menuitem or another menu (precreated)
224
+ def add menuitem
225
+ #$log.debug " YYYY inside add menuitem #{menuitem.text} "
226
+ @items << menuitem
227
+ return self
228
+ end
229
+ alias :<< :add
230
+
231
+ # add item method which could be used from blocks
232
+ # add 2010-09-10 12:20 simplifying
233
+ def item text, mnem=nil, &block
234
+ #$log.debug "YYYY inside M: menuitem text #{text} "
235
+ m = MenuItem.new text, mnem, &block
236
+ add m
237
+ return m
238
+ end
239
+ # create a menu within a menu
240
+ # add menu method which could be used from blocks
241
+ # add 2010-09-10 12:20 simplifying
242
+ def menu text, &block
243
+ #$log.debug "YYYY inside M: menu text #{text} "
244
+ m = Menu.new text, &block
245
+ add m
246
+ return m
247
+ end
248
+ def insert_separator ix
249
+ @items.insert ix, MenuSeparator.new
250
+ end
251
+ def add_separator
252
+ @items << MenuSeparator.new
253
+ end
254
+ alias :separator :add_separator
255
+
256
+ ## added 2009-01-21 12:09 NEW
257
+ def get_item i
258
+ @items[i]
259
+ end
260
+ ## added 2009-01-21 12:09 NEW
261
+ def remove n
262
+ if n.is_a? Fixnum
263
+ @items.delete_at n
264
+ else
265
+ @items.delete n
266
+ end
267
+ end
268
+ # generate an item list at runtime for this menu
269
+ def item_list *args, &block
270
+ $log.debug ">>>item_list : #{@text} "
271
+ @item_list = block if block_given?
272
+ @item_list_args = args
273
+ end
274
+ # menu -
275
+ def fire
276
+ $log.debug "menu fire called: #{text} "
277
+ if @window.nil?
278
+ #repaint
279
+ # added 2011-09-24 adding ability to generate list of items
280
+ if @item_list
281
+ # generate a list, but we need to know what to do with that list.
282
+ @items = []
283
+ l = @item_list.call self, *@item_list_args if !@item_list.nil?
284
+ if l.nil? || l.size == 0
285
+ item(:NO_MENUITEMS)
286
+ else
287
+ # for each element returned create a menuitem, and attach the command to it.
288
+ l.each { |e| it = item(e);
289
+ if @command # there should be a command otherwise what's the point
290
+ it.command(@args) do @command.call(it, it.text) end;
291
+ else
292
+ it.command(@args) do alert("No command attached to #{it.text} ") end;
293
+ $log.warn "No command attached to item_list "
294
+ end
295
+ }
296
+ end
297
+ $log.debug "menu got items #{@items.count} "
298
+ end
299
+ if @items.empty? # user did not specify any items
300
+ item(:NO_MENUITEMS)
301
+ end
302
+ create_window
303
+ if !@parent.is_a? Canis::MenuBar
304
+ @parent.current_menu << self
305
+ @@menus << self # NEW
306
+ end
307
+ else
308
+ ### shouod this not just show ?
309
+ $log.debug "menu fire called: #{text} ELSE XXX WHEN IS THIS CALLED ? 658 #{@items[@active_index].text} "
310
+ if @active_index # sometimes no menu item specified 2011-09-24 NEWMENU
311
+ return @items[@active_index].fire # this should happen if selected. else selected()
312
+ end
313
+ end
314
+ #@action.call if !@action.nil?
315
+ end
316
+ # user has clicked down, we shoud display items
317
+ # DRAW menuitems
318
+ def repaint # menu.repaint
319
+ # OMG will not print anything if no items !
320
+ # When we do item generation this list will be empty
321
+ #return if @items.nil? or @items.empty? # commented 2011-09-24 NEWMENU
322
+ #$log.debug "menu repaint: #{text} row #{@row} col #{@col} "
323
+ @color_pair = get_color($reversecolor, @color, @bgcolor)
324
+ if !@parent.is_a? Canis::MenuBar
325
+ @parent.window.printstring( @row, 0, "|%-*s>|" % [@width-1, text], @color_pair)
326
+ @parent.window.refresh
327
+ end
328
+ if @window.nil?
329
+ #create_window
330
+ else
331
+ @window.show
332
+ select_item 0
333
+ @window.refresh
334
+ end
335
+ end
336
+ ##
337
+ # recursive if given one not enabled goes to next enabled
338
+ def select_item ix0
339
+ return if @items.nil? or @items.empty?
340
+ #$log.debug "insdie select item : #{ix0} active: #{@active_index}"
341
+ if !@active_index.nil?
342
+ @items[@active_index].on_leave
343
+ end
344
+ previtem = @active_index
345
+ @active_index = ix0
346
+ if @items[ix0].enabled
347
+ @items[ix0].on_enter
348
+ else
349
+ #$log.debug "insdie sele nxt item ENABLED FALSE : #{ix0}"
350
+ if @active_index > previtem
351
+ select_next_item
352
+ else
353
+ select_prev_item
354
+ end
355
+ end
356
+ @window.refresh
357
+ end
358
+ def select_next_item
359
+ return if @items.nil? or @items.empty?
360
+ #$log.debug "insdie sele nxt item : #{@active_index}"
361
+ @active_index = -1 if @active_index.nil?
362
+ if @active_index < @items.length-1
363
+ select_item @active_index + 1
364
+ else
365
+ # select_item 0
366
+ end
367
+ end
368
+ def select_prev_item
369
+ return if @items.nil? or @items.empty?
370
+ #$log.debug "insdie sele prv item : #{@active_index}"
371
+ if @active_index > 0
372
+ select_item @active_index - 1
373
+ else
374
+ #select_item @items.length-1
375
+ end
376
+ end
377
+ #
378
+ # If multi-column menuitems then try going to a left item (prev column same row)
379
+ # NOTE It should only come here if items are open, otherwise row and col will be blank.
380
+ # NOTE active_index nil means no items open
381
+ #
382
+ def select_left_item
383
+ return :UNHANDLED if @items.nil? or @items.empty? or @active_index.nil?
384
+ index = nil
385
+ crow = @items[@active_index].row
386
+ ccol = @items[@active_index].col
387
+ @items.each_with_index { |e, i| index = i if e.row == crow && e.col < ccol }
388
+ if index
389
+ select_item index
390
+ else
391
+ return :UNHANDLED
392
+ end
393
+ end
394
+ # @since 1.3.1 2011-09-24
395
+ # If multi-column menuitems then try going to a right item (next column same row)
396
+ # Only if items are open, not from a menubar menu
397
+ def select_right_item
398
+ return :UNHANDLED if @items.nil? or @items.empty? or @active_index.nil?
399
+ crow = @items[@active_index].row
400
+ ccol = @items[@active_index].col
401
+ #alert "inside select right with #{@items.size} #{@items[@active_index].text}: items. r #{crow} col #{ccol} "
402
+ index = nil
403
+ @items.each_with_index { |e, i|
404
+ $log.debug " select_right #{e.row} == #{crow} , #{e.col} > #{ccol} " if $log.debug?
405
+ if e.row == crow && e.col > ccol
406
+ index = i
407
+ $log.debug "YYY select_right #{e.row} == #{crow} , #{e.col} > #{ccol} FOUND #{i} " if $log.debug?
408
+ break
409
+ end
410
+ }
411
+ if index
412
+ select_item index
413
+ else
414
+ return :UNHANDLED
415
+ end
416
+ end
417
+ def on_enter # menu.on_enter
418
+ #$log.debug "menu onenter: #{text} #{@row} #{@col} "
419
+ # call parent method. XXX
420
+ #if @parent.is_a? Canis::MenuBar
421
+ #acolor = get_color($datacolor, @bgcolor, @color)
422
+ #@parent.window.printstring( @row, @col, " %s " % text, acolor)
423
+ #else
424
+ highlight
425
+ #end
426
+ if !@window.nil? #and @parent.selected
427
+ #$log.debug "menu onenter: #{text} calling window,show"
428
+ @window.show
429
+ select_item 0
430
+ elsif @parent.is_a? Canis::MenuBar and @parent.selected
431
+ # only on the top level do we open a window if a previous one was opened
432
+ #$log.debug "menu onenter: #{text} calling repaint CLASS: #{@parent.class}"
433
+ # repaint
434
+ create_window
435
+ end
436
+ end
437
+ def on_leave # menu.on_leave
438
+ #$log.debug "menu onleave: #{text} #{@row} #{@col} "
439
+ # call parent method. XXX
440
+ @color_pair ||= get_color($reversecolor, @color, @bgcolor)
441
+ if @parent.is_a? Canis::MenuBar
442
+ # @parent.window.printstring( @row, @col, " %s " % text, $reversecolor) # changed 2011 2011-09-24
443
+ @parent.window.printstring( @row, @col, " %s " % text, @color_pair)
444
+ @window.hide if !@window.nil?
445
+ else
446
+ #$log.debug "MENU SUBMEN. menu onleave: #{text} #{@row} #{@col} "
447
+ # parent is a menu
448
+ highlight false
449
+ #@parent.current_menu.pop
450
+ #@@menus.pop
451
+ #destroy
452
+ end
453
+ end
454
+ def highlight tf=true # menu
455
+ if @parent.is_a? Canis::MenuBar # top level menu
456
+ #acolor = get_color($datacolor, @bgcolor, @color)
457
+ #@parent.window.printstring( @row, @col, " %s " % text, acolor)
458
+ @color_pair ||= get_color($reversecolor, @color, @bgcolor)
459
+ att = Ncurses::A_REVERSE
460
+ @parent.window.mvchgat(y=@row, x=@col+1, text.length+1, att, @color_pair, nil)
461
+ else
462
+ #$log.debug "MENU SUBMENU menu highlight: #{text} #{@row} #{@col}, PW #{@parent.width} "
463
+ acolor = tf ? $datacolor : $reversecolor
464
+ att = tf ? Ncurses::A_REVERSE : Ncurses::A_NORMAL
465
+ #@parent.window.mvchgat(y=@row, x=1, @width, Ncurses::A_NORMAL, color, nil)
466
+ #@parent.window.mvchgat(y=@row, x=1, @parent.width, Ncurses::A_NORMAL, color, nil)
467
+ # above line did not work with vt100/vt200 next does
468
+ # @parent.window.mvchgat(y=@row, x=1, @parent.width, att, $reversecolor, nil) # changed 2011 2011-09-24
469
+ @parent.window.mvchgat(y=@row, x=1, @parent.width, att, @color_pair, nil)
470
+ @parent.window.wrefresh
471
+ end
472
+ end
473
+ def create_window # menu
474
+ margin = 2 # flush against parent
475
+ @width = array_width(@items) + 1 # adding 1 since menus append a ">" 2011-09-24
476
+ $log.debug "create window menu #{@text}: r #{@row} ,col #{@col}, wd #{@width} "
477
+ t = @row+1
478
+ h = @items.length+3
479
+ ww = @width+margin
480
+ ww1 = @width
481
+ max = Ncurses.LINES-1
482
+ if t + h > max
483
+ t = 2 # one below menubar, not touching
484
+ if h > max
485
+ i = ((h*1.0)/max).ceil
486
+ h = max - 1
487
+ ww = ww * i # FIXME we need to calculate
488
+ end
489
+ end # t + 1
490
+ $log.debug "create window menu #{@text}: t #{t} ,h #{h}, w: #{ww} , col #{@col} max #{max} "
491
+
492
+ #@layout = { :height => @items.length+3, :width => ww, :top => @row+1, :left => @col }
493
+ # earlier col had the offset to start the next level, I was not using it to print
494
+ # but with mulitple cols i am using it. So, this col will overwrite existing menu.
495
+ @layout = { :height => h-1, :width => ww, :top => t, :left => @coffset }
496
+ @win = Canis::Window.new(@layout)
497
+ @window = @win
498
+ @color_pair ||= get_color($datacolor, @color, @bgcolor)
499
+ @rev_color_pair ||= get_color($reversecolor, @color, @bgcolor)
500
+ @win.bkgd(Ncurses.COLOR_PAIR(@color_pair));
501
+ @panel = @win.panel
502
+ #@window.printstring( 0, 0, "+%s+" % ("-"*@width), $reversecolor)
503
+ @window.printstring( 0, 0, "+%s+" % ("-"*(ww1)), @rev_color_pair)
504
+ saved_r = 1
505
+ r = 1
506
+ #saved_c = @col+@width+margin # margins???
507
+ saved_c = 0 ; # actual program uses 0 in repain for col
508
+ c = saved_c
509
+ $log.debug "create window menu #{@text}: first col r #{r} ,c #{c}"
510
+ @items.each do |item|
511
+ #break if r > h # added 2011-09-24 for large number of items - causes error
512
+ if r >= h-2
513
+ @window.printstring( h-2, c, "+%s+" % ("-"*(ww1)), @rev_color_pair)
514
+ r = saved_r
515
+ c += (@width + 2)
516
+ @window.printstring( 0, c, "+%s+" % ("-"*(ww1)), @rev_color_pair)
517
+ $log.debug "create window menu #{@text}: new col r #{r} ,c #{c}, #{item.text} "
518
+ end
519
+ item.row = r
520
+ item.col = c
521
+ item.coffset = @coffset+@width+margin # margins???
522
+
523
+
524
+ item.width = @width
525
+ #item.window = @window
526
+ item.parent = self
527
+ item.color = @color; item.bgcolor = @bgcolor
528
+ item.repaint
529
+ r+=1
530
+ end
531
+ # @window.printstring( r, 0, "+%s+" % ("-"*@width), $reversecolor) # changed 2011 2011-09-24
532
+ @window.printstring( h-2, 0, "+%s+" % ("-"*(ww1)), @rev_color_pair)
533
+ # in case of multiple rows
534
+ @window.printstring( r, c, "+%s+" % ("-"*(ww1)), @rev_color_pair)
535
+ select_item 0
536
+ @window.refresh
537
+ return @window
538
+ end
539
+ # private
540
+ def array_width a
541
+ longest = a.max {|a,b| a.to_s.length <=> b.to_s.length }
542
+ #$log.debug "array width #{longest}"
543
+ longest.to_s.length
544
+ end
545
+ def destroy
546
+ $log.debug "DESTRY menu #{@text}"
547
+ return if @window.nil?
548
+ @visible = false
549
+ #2014-05-12 - 20:53 next 3 replaced with destroy since destroy refreshes root window.
550
+ #panel = @window.panel
551
+ #Ncurses::Panel.del_panel(panel.pointer) if !panel.nil?
552
+ #@window.delwin if !@window.nil?
553
+ @window.destroy
554
+ @items.each do |item|
555
+ #next if item == :SEPARATOR
556
+ item.destroy
557
+ end
558
+ @window = nil
559
+ end
560
+ # menu LEFT, RIGHT, DOWN, UP, ENTER
561
+ # item could be menuitem or another menu
562
+ #
563
+ def handle_key ch
564
+ if !@current_menu.empty?
565
+ cmenu = @current_menu.last
566
+ else
567
+ cmenu = self
568
+ end
569
+ if !@@menus.empty?
570
+ cmenu = @@menus.last
571
+ else
572
+ cmenu = self
573
+ end
574
+ case ch
575
+ when KEY_DOWN
576
+ cmenu.select_next_item
577
+ #return cmenu.fire # XXX 2010-10-16 21:39 trying out
578
+ if cmenu.is_a? Canis::Menu
579
+ #alert "is a menu" # this gets triggered even when we are on items
580
+ end
581
+ when KEY_UP
582
+ cmenu.select_prev_item
583
+ when KEY_ENTER, 10, 13, 32 # added 32 2008-11-27 23:50
584
+ return cmenu.fire
585
+ when KEY_LEFT
586
+ if cmenu.parent.is_a? Canis::Menu
587
+ #$log.debug "LEFT IN MENU : #{cmenu.parent.class} len: #{cmenu.parent.current_menu.length}"
588
+ #$log.debug "left IN MENU : #{cmenu.parent.class} len: #{cmenu.current_menu.length}"
589
+ end
590
+ ret = cmenu.select_left_item # 2011-09-24 V1.3.1 attempt to goto left item if columns
591
+ if ret == :UNHANDLED
592
+ if cmenu.parent.is_a? Canis::MenuBar #and !cmenu.parent.current_menu.empty?
593
+ #$log.debug " ABOU TO DESTROY DUE TO LEFT"
594
+ cmenu.current_menu.pop
595
+ @@menus.pop ## NEW
596
+ cmenu.destroy
597
+ return :UNHANDLED
598
+ end
599
+ # LEFT on a menu list allows me to close and return to higher level
600
+ if cmenu.parent.is_a? Canis::Menu #and !cmenu.parent.current_menu.empty?
601
+ #$log.debug " ABOU TO DESTROY DUE TO LEFT"
602
+ cmenu.current_menu.pop
603
+ @@menus.pop ## NEW
604
+ cmenu.destroy
605
+ #return :UNHANDLED
606
+ end
607
+ end
608
+ when KEY_RIGHT
609
+ $log.debug "RIGHTIN MENU : #{text} "
610
+ if cmenu.active_index
611
+ if cmenu.items[cmenu.active_index].is_a? Canis::Menu
612
+ #alert "could fire here cmenu: #{cmenu.text}, par: #{cmenu.parent.text} "
613
+ cmenu.fire
614
+ return
615
+ #$log.debug "right IN MENU : #{cmenu.parent.class} len: #{cmenu.parent.current_menu.length}"
616
+ #$log.debug "right IN MENU : #{cmenu.parent.class} len: #{cmenu.current_menu.length}"
617
+ end
618
+ end
619
+ # This introduces a bug if no open items
620
+ ret = cmenu.select_right_item # 2011-09-24 V1.3.1 attempt to goto right item if columns
621
+ #alert "attempting to select right #{ret} "
622
+ if ret == :UNHANDLED
623
+ #if cmenu.parent.is_a? Canis::Menu and !cmenu.parent.current_menu.empty?
624
+ if cmenu.parent.is_a? Canis::MenuBar #and !cmenu.current_menu.empty?
625
+ $log.debug " ABOU TO DESTROY DUE TO RIGHT"
626
+ cmenu.current_menu.pop
627
+ @@menus.pop
628
+ cmenu.destroy
629
+ return :UNHANDLED
630
+ end
631
+ end
632
+ else
633
+ ret = check_mnemonics cmenu, ch
634
+ return ret
635
+ end
636
+ end
637
+ ##
638
+ # checks given key against current menu's items and fires key if
639
+ # added on 2008-11-27 12:07
640
+ def check_mnemonics cmenu, ch
641
+ # $log.debug "inside check_mnemonics #{ch}"
642
+ key = ch.chr.downcase rescue ""
643
+ cmenu.items.each do |item|
644
+ next if !item.respond_to? :mnemonic or item.mnemonic.nil?
645
+ # $log.debug "inside check_mnemonics #{item.mnemonic}"
646
+ if key == item.mnemonic.downcase && item.enabled # 2010-09-11 00:03 enabled
647
+ ret = item.fire
648
+ return ret #0 2009-01-23 00:45
649
+ end
650
+ end
651
+ return :UNHANDLED
652
+ end
653
+ ## menu
654
+ def show # menu.show
655
+ #$log.debug "show (menu) : #{@text} "
656
+ if @window.nil?
657
+ create_window #@col+@width
658
+ end
659
+ @window.show
660
+ select_item 0
661
+ end
662
+ end
663
+ ##
664
+ # An application related menubar.
665
+ # Currently, I am adding this to a form. But should this not be application specific ?
666
+ # It should popup no matter which window you are on ?? XXX
667
+ class MenuBar
668
+ attr_reader :items
669
+ attr_reader :window
670
+ attr_reader :panel
671
+ attr_reader :selected
672
+ attr_reader :text # temp 2011-09-24 V1.3.1
673
+ attr_accessor :visible
674
+ attr_accessor :active_index
675
+ attr_accessor :state # normal, selected, highlighted
676
+ attr_accessor :toggle_key # key used to popup, should be set prior to attaching to form
677
+ attr_accessor :color, :bgcolor # 2011-09-25 V1.3.1
678
+ attr_accessor :_object_created # 2011-10-7 if visible then Form will call this
679
+ def initialize &block
680
+ @window = nil
681
+ @text = "menubar"
682
+ @items = []
683
+ init_vars
684
+ @visible = false
685
+ @cols = Ncurses.COLS-1
686
+ instance_eval &block if block_given?
687
+ end
688
+ def init_vars
689
+ @active_index = 0
690
+ @repaint_required = true
691
+ end
692
+ def focusable
693
+ false
694
+ end
695
+ alias :focusable? focusable
696
+ # add a precreated menu
697
+ def add menu
698
+ #$log.debug "YYYY inside MB: add #{menu.text} "
699
+ @items << menu
700
+ return self
701
+ end
702
+ alias :<< :add
703
+
704
+ # add a menu through the block, this would happen through instance eval
705
+ # 2010-09-10 12:07 added while simplifying the interface
706
+ # this calls add so you get the MB back, not a ref to the menu created NOTE
707
+ def menu text, &block
708
+ #$log.debug "YYYY inside MB: menu text #{text} "
709
+ m = Menu.new text, &block
710
+ m.color = @color
711
+ m.bgcolor = @bgcolor
712
+ add m
713
+ return m
714
+ end
715
+ def next_menu
716
+ #$log.debug "next meu: #{@active_index} "
717
+ if @active_index < @items.length-1
718
+ set_menu @active_index + 1
719
+ else
720
+ set_menu 0
721
+ end
722
+ end
723
+ def prev_menu
724
+ #$log.debug "prev meu: #{@active_index} "
725
+ if @active_index > 0
726
+ set_menu @active_index-1
727
+ else
728
+ set_menu @items.length-1
729
+ end
730
+ end
731
+ def set_menu index
732
+ #$log.debug "set meu: #{@active_index} #{index}"
733
+ menu = @items[@active_index]
734
+ menu.on_leave # hide its window, if open
735
+ @active_index = index
736
+ menu = @items[@active_index]
737
+ menu.on_enter #display window, if previous was displayed
738
+ @window.wmove menu.row, menu.col
739
+ # menu.show
740
+ # menu.window.wrefresh # XXX we need this
741
+ end
742
+
743
+ def keep_visible flag=nil
744
+ return @keep_visible unless flag
745
+ @keep_visible = flag
746
+ @visible = flag
747
+ self
748
+ end
749
+ # menubar LEFT, RIGHT, DOWN
750
+ def handle_keys
751
+ @selected = false
752
+ @repaint_required = true # added 2011-12-12 otherwise keeps repainting and you see a flicker
753
+ @toggle_key ||= 27 # default switch off with ESC, if nothing else defined
754
+ set_menu 0
755
+ begin
756
+ catch(:menubarclose) do
757
+ while((ch = @window.getchar()) != @toggle_key )
758
+ #$log.debug "menuubar inside handle_keys : #{ch}" if ch != -1
759
+ case ch
760
+ when -1
761
+ next
762
+ when KEY_DOWN
763
+ #$log.debug "insdie keyDOWN : #{ch}"
764
+ if !@selected
765
+ current_menu.fire
766
+ else
767
+ current_menu.handle_key ch
768
+ end
769
+
770
+ @selected = true
771
+ when KEY_ENTER, 10, 13, 32
772
+ @selected = true
773
+ #$log.debug " mb insdie ENTER : #{current_menu}"
774
+ ret = current_menu.handle_key ch
775
+ #$log.debug "ret = #{ret} mb insdie ENTER : #{current_menu}"
776
+ #break; ## 2008-12-29 18:00 This will close after firing
777
+ #anything
778
+ break if ret == :CLOSE
779
+ when KEY_UP
780
+ #$log.debug " mb insdie keyUPP : #{ch}"
781
+ current_menu.handle_key ch
782
+ when KEY_LEFT
783
+ #$log.debug " mb insdie KEYLEFT : #{ch}"
784
+ ret = current_menu.handle_key ch
785
+ prev_menu if ret == :UNHANDLED
786
+ #display_items if @selected
787
+ when KEY_RIGHT
788
+ #$log.debug " mb insdie KEYRIGHT : #{ch}"
789
+ ret = current_menu.handle_key ch
790
+ next_menu if ret == :UNHANDLED
791
+ when ?\C-g.getbyte(0) # abort
792
+ throw :menubarclose
793
+ else
794
+ #$log.debug " mb insdie ELSE : #{ch}"
795
+ ret = current_menu.handle_key ch
796
+ if ret == :UNHANDLED
797
+ Ncurses.beep
798
+ else
799
+ break # we handled a menu action, close menubar (THIS WORKS FOR MNEMONICS ONLY and always)
800
+ end
801
+ end
802
+ Ncurses::Panel.update_panels();
803
+ Ncurses.doupdate();
804
+
805
+ @window.wrefresh
806
+ end
807
+ end # catch
808
+ ensure
809
+ #ensure is required becos one can throw a :close
810
+ $log.debug " DESTROY IN ENSURE"
811
+ current_menu.clear_menus #@@menus = [] # added 2009-01-23 13:21
812
+ @repaint_required = false
813
+ destroy # Note that we destroy the menu bar upon exit
814
+ end
815
+ end
816
+ def current_menu
817
+ @items[@active_index]
818
+ end
819
+ # called by set_menu_bar in widget.rb (class Form).
820
+ def toggle
821
+ # added keeping it visible, 2011-10-7 being tested in dbdemo
822
+ if @keep_visible
823
+ init_vars
824
+ show
825
+ @items[0].highlight
826
+ @window.ungetch(KEY_DOWN)
827
+ return
828
+ end
829
+ #@items.each { |i| $log.debug " ITEM DDD : #{i.text}" }
830
+ @visible = !@visible
831
+ if !@visible
832
+ hide
833
+ else
834
+ init_vars
835
+ show
836
+ end
837
+ end
838
+ def hide
839
+ @visible = false
840
+ @window.hide if !@window.nil? # seems to cause auto-firing when we resume toggle 2011-09-26
841
+ end
842
+ def show
843
+ @visible = true
844
+ if @window.nil?
845
+ repaint # XXX FIXME
846
+ else
847
+ @window.show
848
+ end
849
+ end
850
+ ## menubar
851
+ # TODO: check for menu to be flush right (only for last one).
852
+ # TODO: repaint only if needed
853
+ def repaint
854
+ return if !@visible
855
+ return unless @repaint_required
856
+ @repaint_required = false
857
+ @color_pair = get_color($reversecolor, @color, @bgcolor)
858
+ @window ||= create_window_menubar
859
+ #@window.printstring( 0, 0, "%-*s" % [@cols," "], @color_pair) # this becomes blank in some terms
860
+ c = 1; r = 0;
861
+ @items.each do |item|
862
+ item.row = r; item.col = c; item.coffset = c; item.parent = self
863
+ item.color = @color
864
+ item.bgcolor = @bgcolor
865
+ @window.printstring( r, c, " %s " % item.text, @color_pair)
866
+ # 2011-09-26 V1.3.1 quick dirty highlighting of first menu on menubar
867
+ # on opening since calling highlight was giving bug in parent.width
868
+ #if c == 1
869
+ #att = Ncurses::A_REVERSE
870
+ #@window.mvchgat(y=r, x=c+1, item.text.length+1, att, @color_pair, nil)
871
+ #end
872
+ c += (item.text.length + 2)
873
+ end
874
+ #@items[0].on_enter # 2011-09-25 V1.3.1 caused issues when toggling, first item fired on DOWN
875
+ @items[0].highlight unless @keep_visible # 2011-09-26 V1.3.1 fixed to take both cases into account
876
+ @window.wrefresh
877
+ end
878
+ def create_window_menubar
879
+ @layout = { :height => 1, :width => 0, :top => 0, :left => 0 }
880
+ @win = Canis::Window.new(@layout)
881
+ @window = @win
882
+ att = get_attrib @attr
883
+ @win.bkgd(Ncurses.COLOR_PAIR(5)); # <---- FIXME
884
+ len = @window.width
885
+ len = Ncurses.COLS-0 if len == 0
886
+ # print a bar across the screen , which hopefully will not go blank in some terms
887
+ @window.attron(Ncurses.COLOR_PAIR(@color_pair) | att)
888
+ @window.mvhline(0, 0, 1, len)
889
+ @window.attroff(Ncurses.COLOR_PAIR(@color_pair) | att)
890
+ @panel = @win.panel
891
+ return @window
892
+ end
893
+ def destroy
894
+ $log.debug "DESTRY menubar #{@keep_visible} "
895
+
896
+ # when we close, but keep visible, we don't want menu to still be hightlighted
897
+ # added on 2011-12-12
898
+ menu = @items[@active_index]
899
+ menu.on_leave # hide its window, if open
900
+
901
+ @items.each do |item|
902
+ item.destroy
903
+ end
904
+ $log.debug " DESTROY finished with items "
905
+ # TODO the current menu should not be highlighted
906
+ # FIXME even here i think underlying windows need to be repainted.
907
+ #return if @keep_visible
908
+ if @keep_visible
909
+ Window.refresh_all
910
+ return
911
+ end
912
+ @visible = false
913
+ # replacing next 3 lines with destroy. 2014-05-12 - 17:07 CANIS
914
+ #panel = @window.panel
915
+ #Ncurses::Panel.del_panel(panel.pointer) if !panel.nil?
916
+ #@window.delwin if !@window.nil?
917
+ $log.debug " CALLING WINDOW DESTROY from menubar"
918
+ @window.destroy
919
+ @window = nil
920
+ end
921
+ end # menubar
922
+
923
+ class CheckBoxMenuItem < MenuItem
924
+ attr_reader :checkbox
925
+ def initialize text, mnemonic=nil, &block
926
+ @checkbox = CheckBox.new nil
927
+ @checkbox.text text
928
+ super
929
+ end
930
+ def onvalue
931
+ @checkbox.onvalue onvalue
932
+ end
933
+ def offvalue
934
+ @checkbox.onvalue offvalue
935
+ end
936
+ def text=(t) # stack level too deep if no = .????
937
+ @checkbox.text t
938
+ end
939
+ def to_s
940
+ " #{text} "
941
+ end
942
+ def getvalue
943
+ checkbox.getvalue
944
+ end
945
+ def getvalue_for_paint
946
+ "|%-*s|" % [@width, checkbox.getvalue_for_paint]
947
+ end
948
+ def fire
949
+ checkbox.toggle
950
+ super
951
+ repaint
952
+ highlight true
953
+ end
954
+ def repaint # checkbox
955
+ # FIXME need @color_pair here
956
+ @color_pair ||= get_color($reversecolor, @color, @bgcolor)
957
+ @parent.window.printstring( row, 0, getvalue_for_paint, @color_pair)
958
+ parent.window.wrefresh
959
+ end
960
+ def method_missing(sym, *args)
961
+ if checkbox.respond_to? sym
962
+ #$log.debug("calling CHECKBOXMENU #{sym} called #{args[0]}")
963
+ checkbox.send(sym, args)
964
+ else
965
+ $log.error("ERROR CHECKBOXMENU #{sym} called")
966
+ end
967
+ end
968
+ end # class
969
+
970
+ end # modul