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,235 @@
1
+ # ----------------------------------------------------------------------------- #
2
+ # File: promptmenu.rb
3
+ # Description: a simple 'most' type menu at bottom of screen.
4
+ # Moved from io.rb
5
+ # Author: j kepler http://github.com/mare-imbrium/canis/
6
+ # Date: 2014-04-25 - 12:32
7
+ # License: MIT
8
+ # Last update: 2014-04-27 00:10
9
+ # ----------------------------------------------------------------------------- #
10
+ # promptmenu.rb Copyright (C) 2012-2014 j kepler
11
+ # Depends on rcommandwindow for display_menu
12
+
13
+ module Canis
14
+
15
+ ## A *simple* way of creating menus that will appear in a single row.
16
+ # This copies the menu at the bottom of "most" upon pressing ":".
17
+ # hotkey is the key to invoke an item (a single digit letter)
18
+ #
19
+ # label is an action name
20
+ #
21
+ # desc is a description displayed after an item is chosen. Usually, its like:
22
+ #+ "Folding has been enabled" or "Searches will now be case sensitive"
23
+ #
24
+ # action may be a Proc or a symbol which will be called if item selected
25
+ #+ action may be another menu, so recursive menus can be built, but each
26
+ #+ should fit in a line, its a simple system.
27
+
28
+ CMenuItem = Struct.new( :hotkey, :label, :desc, :action )
29
+
30
+
31
+ ## An encapsulated form of yesterday's Most Menu
32
+ # It keeps the internals away from the user.
33
+ # Its not really OOP in the sense that the PromptMenu is not a MenuItem. That's how it is in
34
+ # our Menu system, and that led to a lot of painful coding (at least for me). This is quite
35
+ # simple. A submenu contains a PromptMenu in its action object and is evaluated in a switch.
36
+ # A recursive loop handles submenus.
37
+ #
38
+ # Prompting of menu options with suboptions etc.
39
+ # A block of code or symbol or proc is executed for any leaf node
40
+ # This allows us to define different menus for different objects on the screen, and not have to map
41
+ # all kinds of control keys for operations, and have the user remember them. Only one key invokes the menu
42
+ # and the rest are ordinary characters.
43
+ #
44
+ # == Example
45
+ # menu = PromptMenu.new self do
46
+ # item :s, :goto_start
47
+ # item :b, :goto_bottom
48
+ # item :r, :scroll_backward
49
+ # item :l, :scroll_forward
50
+ # submenu :m, "submenu" do
51
+ # item :p, :goto_last_position
52
+ # item :r, :scroll_backward
53
+ # item :l, :scroll_forward
54
+ # end
55
+ # end
56
+ # menu.display_new :title => 'window title', :prompt => "Choose:"
57
+
58
+ class PromptMenu
59
+ include Io
60
+ attr_reader :text
61
+ attr_reader :options
62
+ def initialize caller, text="Choose:", &block
63
+ @caller = caller
64
+ @text = text
65
+ @options = []
66
+ yield_or_eval &block if block_given?
67
+ end
68
+ def add *menuitem
69
+ item = nil
70
+ case menuitem.first
71
+ when CMenuItem
72
+ item = menuitem.first
73
+ @options << item
74
+ else
75
+ case menuitem.size
76
+ when 4
77
+ item = CMenuItem.new(*menuitem.flatten)
78
+ when 2
79
+ # if user only sends key and symbol
80
+ menuitem[3] = menuitem[1]
81
+ item = CMenuItem.new(*menuitem.flatten)
82
+ when 1
83
+ if menuitem.first.is_a? Action
84
+ item = menuitem.first
85
+ else
86
+ raise ArgumentError, "Don't know how to handle #{menuitem.size} : #{menuitem} "
87
+ end
88
+ else
89
+ raise ArgumentError, "Don't know how to handle #{menuitem.size} : #{menuitem} "
90
+ end
91
+ @options << item
92
+ end
93
+ return item
94
+ end
95
+ alias :item :add
96
+ def create_mitem *args
97
+ item = CMenuItem.new(*args.flatten)
98
+ end
99
+ # Added this, since actually it could have been like this 2011-12-22
100
+ def self.create_menuitem *args
101
+ item = CMenuItem.new(*args.flatten)
102
+ end
103
+ # create the whole thing using a MenuTree which has minimal information.
104
+ # It uses a hotkey and a code only. We are supposed to resolve the display text
105
+ # and actual proc from the caller using this code.
106
+ def menu_tree mt, pm = self
107
+ mt.each_pair { |ch, code|
108
+ if code.is_a? Canis::MenuTree
109
+ item = pm.add(ch, code.value, "")
110
+ current = PromptMenu.new @caller, code.value
111
+ item.action = current
112
+ menu_tree code, current
113
+ else
114
+ item = pm.add(ch, code.to_s, "", code)
115
+ end
116
+ }
117
+ end
118
+ #
119
+ # To allow a more rubyesque way of defining menus and submenus
120
+ def submenu key, label, &block
121
+ item = CMenuItem.new(key, label)
122
+ @options << item
123
+ item.action = PromptMenu.new @caller, label, &block
124
+ end
125
+ #
126
+ # Display prompt_menu in columns using commandwindow
127
+ # This is an improved way of showing the "most" like menu. The earlier
128
+ # format would only print in one row.
129
+ #
130
+ def display_columns config={}
131
+ prompt = config[:prompt] || "Choose: "
132
+ require 'canis/core/util/rcommandwindow'
133
+ layout = { :height => 5, :width => Ncurses.COLS-0, :top => Ncurses.LINES-6, :left => 0 }
134
+ rc = CommandWindow.new nil, :layout => layout, :box => true, :title => config[:title] || "Menu"
135
+ w = rc.window
136
+ r = 4
137
+ c = 1
138
+ color = $datacolor
139
+ begin
140
+ menu = @options
141
+ $log.debug " DISP MENU "
142
+ ret = 0
143
+ len = 80
144
+ while true
145
+ h = {}
146
+ valid = []
147
+ labels = []
148
+ menu.each{ |item|
149
+ if item.respond_to? :hotkey
150
+ hk = item.hotkey.to_s
151
+ else
152
+ raise ArgumentError, "Promptmenu needs hotkey or mnemonic"
153
+ end
154
+ # 187compat 2013-03-20 - 19:00 throws up
155
+ labels << "%c. %s " % [ hk.getbyte(0), item.label ]
156
+ h[hk] = item
157
+ valid << hk
158
+ }
159
+ #$log.debug " valid are #{valid} "
160
+ color = $datacolor
161
+ #print_this(win, str, color, r, c)
162
+ rc.display_menu labels, :indexing => :custom
163
+ ch=w.getchar()
164
+ rc.clear
165
+ #$log.debug " got ch #{ch} "
166
+ next if ch < 0 or ch > 255
167
+ if ch == 3 || ch == ?\C-g.getbyte(0)
168
+ clear_this w, r, c, color, len
169
+ print_this(w, "Aborted.", color, r,c)
170
+ break
171
+ end
172
+ ch = ch.chr
173
+ index = valid.index ch
174
+ if index.nil?
175
+ clear_this w, r, c, color, len
176
+ print_this(w, "Not valid. Valid are #{valid}. C-c/C-g to abort.", color, r,c)
177
+ sleep 1
178
+ next
179
+ end
180
+ #$log.debug " index is #{index} "
181
+ item = h[ch]
182
+ # I don;t think this even shows now, its useless
183
+ if item.respond_to? :desc
184
+ desc = item.desc
185
+ #desc ||= "Could not find desc for #{ch} "
186
+ desc ||= ""
187
+ clear_this w, r, c, color, len
188
+ print_this(w, desc, color, r,c)
189
+ end
190
+ action = item.action
191
+ case action
192
+ #when Array
193
+ when PromptMenu
194
+ # submenu
195
+ menu = action.options
196
+ title = rc.title
197
+ rc.title title +" => " + action.text # set title of window to submenu
198
+ when Proc
199
+ #rc.destroy
200
+ ##bottom needs to be refreshed somehow
201
+ #FFI::NCurses.ungetch ?j
202
+ rc.hide
203
+ ret = action.call
204
+ break
205
+ when Symbol
206
+ if @caller.respond_to?(action, true)
207
+ rc.hide
208
+ $log.debug "XXX: IO caller responds to action #{action} "
209
+ ret = @caller.send(action)
210
+ elsif @caller.respond_to?(:execute_this, true)
211
+ rc.hide
212
+ ret = @caller.send(:execute_this, action)
213
+ else
214
+ alert "PromptMenu: unidentified action #{action} for #{@caller.class} "
215
+ raise "PromptMenu: unidentified action #{action} for #{@caller.class} "
216
+ end
217
+
218
+ break
219
+ else
220
+ $log.debug " Unidentified flying class #{action.class} "
221
+ break
222
+ end
223
+ end # while
224
+ ensure
225
+ rc.destroy
226
+ rc = nil
227
+ end
228
+ end
229
+ alias :display_new :display_columns
230
+ alias :display :display_columns
231
+
232
+ end # class PromptMenu
233
+
234
+
235
+ end # module
@@ -0,0 +1,933 @@
1
+ =begin
2
+ * Name: rcommandwindow: pops up a status message at bottom of screen
3
+ creating a new window, so we don't have to worry about having window
4
+ handle.
5
+
6
+ * Description
7
+ * Author: jkepler (ABCD)
8
+ * Date: 2008-11-19 12:49
9
+ * License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
10
+ * file separated on 2009-01-13 22:39
11
+
12
+ == Changes
13
+ Have removed a lot of stuff that was either a duplication of other stuff, or that was not really
14
+ adding any value.
15
+
16
+ == Issues
17
+
18
+ Only display_menu is of any use, and can be taken out and placed in some util or extra.
19
+ It is called by numbered_menu which can be retained.
20
+ display_menu is used by Promptmenu too.
21
+
22
+ =end
23
+ require 'canis'
24
+
25
+ module Canis
26
+
27
+ # this is taken from view and replaces the call to view, since we were
28
+ # modifying view a bit too much to fit it into the needs here.
29
+ #
30
+ def command_list content, config={}, &block #:yield: textpad
31
+ wt = 0 # top margin
32
+ wl = 0 # left margin
33
+ wh = Ncurses.LINES-wt # height, goes to bottom of screen
34
+ ww = Ncurses.COLS-wl # width, goes to right end
35
+ layout = { :height => wh, :width => ww, :top => wt, :left => wl }
36
+ if config.has_key? :layout
37
+ layout = config[:layout]
38
+ case layout
39
+ when Array
40
+ wh, ww, wt, wl = layout
41
+ layout = { :height => wh, :width => ww, :top => wt, :left => wl }
42
+ when Hash
43
+ # okay
44
+ end
45
+ end
46
+
47
+ fp = config[:title] || ""
48
+ pf = config.fetch(:print_footer, false)
49
+ ta = config.fetch(:title_attrib, 'bold')
50
+ fa = config.fetch(:footer_attrib, 'bold')
51
+ b_ah = config[:app_header]
52
+ type = config[:content_type]
53
+
54
+ v_window = Canis::Window.new(layout)
55
+ v_form = Canis::Form.new v_window
56
+ v_window.name = "command-list"
57
+ colors = Ncurses.COLORS
58
+ back = :blue
59
+ back = 235 if colors >= 256
60
+ blue_white = get_color($datacolor, :white, back)
61
+
62
+ tprow = 0
63
+ ah = nil
64
+ if b_ah
65
+ ah = ApplicationHeader.new v_form, "", :text_center => fp
66
+ tprow += 1
67
+ end
68
+
69
+ textview = TextPad.new v_form do
70
+ name "CommandList"
71
+ row tprow
72
+ col 0
73
+ width ww
74
+ height wh-tprow # earlier 2 but seems to be leaving space.
75
+ title fp
76
+ title_attrib ta
77
+ print_footer pf
78
+ footer_attrib fa
79
+ #border_attrib :reverse
80
+ border_color blue_white
81
+ end
82
+
83
+ t = textview
84
+ items = {:header => ah}
85
+ begin
86
+ textview.set_content content, :content_type => type
87
+ if block_given?
88
+ if block.arity > 0
89
+ yield textview, items
90
+ else
91
+ textview.instance_eval(&block)
92
+ end
93
+ end
94
+ v_form.repaint
95
+ v_window.wrefresh
96
+ Ncurses::Panel.update_panels
97
+ retval = ""
98
+ # allow closing using q and Ctrl-q in addition to any key specified
99
+ # user should not need to specify key, since that becomes inconsistent across usages
100
+ # NOTE: no longer can we close with just a q since often apps using this trap char keys
101
+ # NOTE: 2727 is no longer operational, so putting just ESC
102
+ while((ch = v_window.getchar()) != ?\C-q.getbyte(0) )
103
+ # ideally we should be throwing a close rather than this since called will need keys.
104
+ retval = textview.current_value() if ch == config[:close_key]
105
+ break if ch == config[:close_key] || ch == 3|| ch == 27 # removed double esc 2014-05-04 - 17:30
106
+ # if you've asked for ENTER then i also check for 10 and 13
107
+ retval = textview.current_value() if (ch == 10 || ch == 13) && config[:close_key] == KEY_ENTER
108
+ break if (ch == 10 || ch == 13) && config[:close_key] == KEY_ENTER
109
+ v_form.handle_key ch
110
+ v_form.repaint
111
+ end
112
+ rescue => err
113
+ $log.error " command-list ERROR #{err} "
114
+ $log.debug(err.backtrace.join("\n"))
115
+ alert "#{err}"
116
+ #textdialog ["Error in command-list: #{err} ", *err.backtrace], :title => "Exception"
117
+ ensure
118
+ v_window.destroy if !v_window.nil?
119
+ end
120
+ return retval
121
+ end
122
+ ##
123
+ # Creates a window at the bottom of the screen for some operations.
124
+ # Used for some operations such as:
125
+ # - display a menu
126
+ # - display some interactive text
127
+ # - display some text
128
+ #
129
+ class CommandWindow
130
+ include Canis::Utils
131
+ dsl_accessor :box
132
+ dsl_accessor :title
133
+ attr_reader :config
134
+ attr_reader :layout
135
+ attr_reader :window # required for keyboard or printing
136
+ dsl_accessor :height, :width, :top, :left # 2009-01-06 00:05 after removing meth missing
137
+
138
+ def initialize form=nil, aconfig={}, &block # --- {{{
139
+ @config = aconfig
140
+ @config.each_pair { |k,v| instance_variable_set("@#{k}",v) }
141
+ instance_eval &block if block_given?
142
+ if @layout.nil?
143
+ set_layout(1,Ncurses.COLS, -1, 0)
144
+ end
145
+ @height = @layout[:height]
146
+ @width = @layout[:width]
147
+ @window = Canis::Window.new(@layout)
148
+ @start = 0 # row for display of text with paging
149
+ @list = []
150
+ draw_box
151
+ @window.wrefresh
152
+ @panel = @window.panel
153
+ Ncurses::Panel.update_panels
154
+ @window.wrefresh
155
+ @row_offset = 0
156
+ if @box
157
+ @row_offset = 1
158
+ end
159
+ end # --- }}}
160
+
161
+ # draw the box, needed to redo this upon clear since clearing of windows
162
+ # was removing the top border 2014-05-04 - 20:14
163
+ def draw_box
164
+ if @box == :border
165
+ @window.box 0,0
166
+ elsif @box
167
+ @window.attron(Ncurses.COLOR_PAIR($normalcolor) | Ncurses::A_REVERSE)
168
+ @window.mvhline 0,0,1,@width
169
+ @window.printstring 0,0,@title, $normalcolor #, 'normal' if @title
170
+ @window.attroff(Ncurses.COLOR_PAIR($normalcolor) | Ncurses::A_REVERSE)
171
+ else
172
+ #@window.printstring 0,0,@title, $normalcolor, 'reverse' if @title
173
+ title @title
174
+ end
175
+ end
176
+
177
+ # not sure if this is really required. print_string is just fine.
178
+ # print a string.
179
+ # config can be :x :y :color_pair
180
+ def print_str text, config={}
181
+ win = config.fetch(:window, @window) # assuming its in App
182
+ x = config.fetch :x, 0
183
+ y = config.fetch :y, 0
184
+ color = config[:color_pair] || $datacolor
185
+ raise "no window for ask print in #{self.class} name: #{name} " unless win
186
+ color=Ncurses.COLOR_PAIR(color);
187
+ win.attron(color);
188
+ win.mvprintw(x, y, "%s" % text);
189
+ win.attroff(color);
190
+ win.refresh
191
+ end
192
+
193
+
194
+
195
+ # ---- windowing functions {{{
196
+ ##
197
+ ## message box
198
+ def stopping?
199
+ @stop
200
+ end
201
+
202
+ # todo handle mappings, so user can map keys TODO
203
+ def handle_keys
204
+ begin
205
+ while((ch = @window.getchar()) != 999 )
206
+ case ch
207
+ when -1
208
+ next
209
+ else
210
+ press ch
211
+ break if @stop
212
+ yield ch if block_given?
213
+ end
214
+ end
215
+ ensure
216
+ destroy
217
+ end
218
+ return #@selected_index
219
+ end
220
+
221
+ # handles a key, commandline
222
+ def press ch
223
+ ch = ch.getbyte(0) if ch.class==String ## 1.9
224
+ $log.debug " XXX press #{ch} " if $log.debug?
225
+ case ch
226
+ when -1
227
+ return
228
+ when KEY_F1, 27, ?\C-q.getbyte(0)
229
+ @stop = true
230
+ return
231
+ when KEY_ENTER, 10, 13
232
+ #$log.debug "popup ENTER : #{@selected_index} "
233
+ #$log.debug "popup ENTER : #{field.name}" if !field.nil?
234
+ @stop = true
235
+ return
236
+ when ?\C-d.getbyte(0)
237
+ @start += @height-1
238
+ bounds_check
239
+ when KEY_UP
240
+ @start -= 1
241
+ @start = 0 if @start < 0
242
+ when KEY_DOWN
243
+ @start += 1
244
+ bounds_check
245
+ when ?\C-b.getbyte(0)
246
+ @start -= @height-1
247
+ @start = 0 if @start < 0
248
+ when 0
249
+ @start = 0
250
+ end
251
+ Ncurses::Panel.update_panels();
252
+ Ncurses.doupdate();
253
+ @window.wrefresh
254
+ end
255
+
256
+ # might as well add more keys for paging.
257
+ def configure(*val , &block)
258
+ case val.size
259
+ when 1
260
+ return @config[val[0]]
261
+ when 2
262
+ @config[val[0]] = val[1]
263
+ instance_variable_set("@#{val[0]}", val[1])
264
+ end
265
+ instance_eval &block if block_given?
266
+ end
267
+ def cget param
268
+ @config[param]
269
+ end
270
+
271
+ def set_layout(height=0, width=0, top=0, left=0)
272
+ # negative means top should be n rows from last line. -1 is last line
273
+ if top < 0
274
+ top = Ncurses.LINES-top
275
+ end
276
+ @layout = { :height => height, :width => width, :top => top, :left => left }
277
+ @height = height
278
+ @width = width
279
+ end
280
+ def show
281
+ @window.show
282
+ end
283
+ # this really helps if we are creating another window over this and we find the lower window
284
+ # still showing through. destroy does not often work so this clears current window.
285
+ # However, lower window may still have a black region. FIXME
286
+ def hide
287
+ @window.hide
288
+ Window.refresh_all
289
+ end
290
+ def destroy
291
+ @window.destroy
292
+ end
293
+ def OLDdestroy
294
+ $log.debug "DESTROY : rcommandwindow"
295
+ if @window
296
+ begin
297
+ panel = @window.panel
298
+ Ncurses::Panel.del_panel(panel.pointer) if panel
299
+ @window.delwin
300
+ rescue => exc
301
+ end
302
+ end
303
+ end
304
+ # refresh whatevers painted onto the window
305
+ def refresh
306
+ Ncurses::Panel.update_panels();
307
+ Ncurses.doupdate();
308
+ @window.wrefresh
309
+ end
310
+ # clears the window, leaving the title line as is, from row 1 onwards
311
+ def clear
312
+ @window.wmove 1,1
313
+ @window.wclrtobot
314
+ #@window.box 0,0 if @box == :border
315
+ draw_box
316
+ # lower line of border will get erased currently since we are writing to
317
+ # last line FIXME
318
+ end
319
+ # ---- windowing functions }}}
320
+
321
+ # modify the window title, or get it if no params passed.
322
+ def title t=nil # --- {{{
323
+ return @title unless t
324
+ @title = t
325
+ @window.printstring 0,0,@title, $normalcolor, 'reverse' if @title
326
+ end # --- }}}
327
+
328
+ #
329
+ # Displays list in a window at bottom of screen, if large then 2 or 3 columns.
330
+ # @param [Array] list of string to be displayed
331
+ # @param [Hash] configuration options: indexing and indexcolor
332
+ # indexing - can be letter or number. Anything else will be ignored, however
333
+ # it will result in first letter being highlighted in indexcolor
334
+ # indexcolor - color of mnemonic, default green
335
+ def display_menu list, options={} # --- {{{
336
+ indexing = options[:indexing]
337
+ #indexcolor = options[:indexcolor] || get_color($normalcolor, :yellow, :black)
338
+ indexcolor = $datacolor || 7 # XXX above line crashing on choose()
339
+ indexatt = Ncurses::A_BOLD
340
+ #
341
+ # the index to start from (used when scrolling a long menu such as file list)
342
+ startindex = options[:startindex] || 0
343
+
344
+ max_cols = 3 # maximum no of columns, we will reduce based on data size
345
+ l_succ = "`"
346
+ act_height = @height
347
+ if @box
348
+ act_height = @height - 2
349
+ end
350
+ lh = list.size
351
+ if lh < act_height
352
+ $log.debug "DDD inside one window" if $log.debug?
353
+ list.each_with_index { |e, i|
354
+ text = e
355
+ case e
356
+ when Array
357
+ text = e.first + " ..."
358
+ end
359
+ if indexing == :number
360
+ mnem = i+1
361
+ text = "%d. %s" % [i+1, text]
362
+ elsif indexing == :letter
363
+ mnem = l_succ.succ!
364
+ text = "%s. %s" % [mnem, text]
365
+ end
366
+ @window.printstring i+@row_offset, 1, text, $normalcolor
367
+ if indexing
368
+ @window.mvchgat(y=i+@row_offset, x=1, max=1, indexatt, indexcolor, nil)
369
+ end
370
+ }
371
+ else
372
+ $log.debug "DDD inside two window" if $log.debug?
373
+ row = 0
374
+ h = act_height
375
+ cols = (lh*1.0 / h).ceil
376
+ cols = max_cols if cols > max_cols
377
+ # sometimes elements are large like directory paths, so check size
378
+ datasize = list.first.length
379
+ if datasize > @width/3 # keep safety margin since checking only first row
380
+ cols = 1
381
+ elsif datasize > @width/2
382
+ cols = [2,cols].min
383
+ end
384
+ adv = (@width/cols).to_i
385
+ colct = 0
386
+ col = 1
387
+ $log.debug "DDDcols #{cols}, adv #{adv} size: #{lh} h: #{act_height} w #{@width} " if $log.debug?
388
+ list.each_with_index { |e, i|
389
+ text = e
390
+ # signify that there's a deeper level
391
+ case e
392
+ when Array
393
+ text = e.first + "..."
394
+ end
395
+ if indexing == :number
396
+ mnem = i+1
397
+ text = "%d. %s" % [mnem, text]
398
+ elsif indexing == :letter
399
+ mnem = l_succ.succ!
400
+ text = "%s. %s" % [mnem, text]
401
+ end
402
+ # print only within range and window height
403
+ #if i >= startindex && row < @window.actual_height
404
+ if i >= startindex && row < @window.height
405
+ $log.debug "XXX: MENU #{i} > #{startindex} row #{row} col #{col} "
406
+ @window.printstring row+@row_offset, col, text, $normalcolor
407
+ if indexing
408
+ @window.mvchgat(y=row+@row_offset, x=col, max=1, indexatt, indexcolor, nil)
409
+ end
410
+ colct += 1
411
+ if colct == cols
412
+ col = 1
413
+ row += 1
414
+ colct = 0
415
+ else
416
+ col += adv
417
+ end
418
+ end # startindex
419
+ }
420
+ end
421
+ Ncurses::Panel.update_panels();
422
+ Ncurses.doupdate();
423
+ @window.wrefresh
424
+ end # --- }}}
425
+
426
+
427
+ end # class CommandWindow
428
+
429
+ # a generic key dispatcher that can be used in various classes for handling keys,
430
+ # setting key_map and processing keys.
431
+ module KeyDispatcher # --- {{{
432
+
433
+ # key handler of Controlphandler
434
+ # This sets +@keyint+ with the value read by window.
435
+ # This sets +@keychr+ with the +chr+ value of +ch+ if ch between 32 and 127 exclusive.
436
+ # @param [Fixnum] ch is key read by window.
437
+ def handle_key ch
438
+ $log.debug " KeyDispatcher GOT KEY #{ch} "
439
+ @keyint = ch
440
+ @keychr = nil
441
+ chr = nil
442
+ chr = ch.chr if ch > 32 and ch < 127
443
+ @keychr = chr
444
+
445
+ ret = process_key ch
446
+ # revert to the basic handling of key_map and refreshing pad.
447
+ #####
448
+ # NOTE
449
+ # this is being done where we are creating some kind of front for a textpad by using +view+
450
+ # so we steal some keys, and pass the rest to +view+. Otherwise, next line is not needed.
451
+ # +@source+ typically would be handle to textpad yielded by +view+.
452
+ #####
453
+ @source._handle_key(ch) if ret == :UNHANDLED and @source
454
+ end
455
+
456
+ # checks the key against +@key_map+ if its set
457
+ # @param [Fixnum] ch character read by +Window+
458
+ # @return [0, :UNHANDLED] 0 if processed, :UNHANDLED if not processed so higher level can process
459
+ def process_key ch
460
+ chr = nil
461
+ if ch > 0 and ch < 256
462
+ chr = ch.chr
463
+ end
464
+ return :UNHANDLED unless @key_map
465
+ @key_map.each_pair do |k,p|
466
+ #$log.debug "KKK: processing key #{ch} #{chr} "
467
+ if (k == ch || k == chr)
468
+ #$log.debug "KKK: checking match == #{k}: #{ch} #{chr} "
469
+ # compare both int key and chr
470
+ #$log.debug "KKK: found match 1 #{ch} #{chr} "
471
+ p.call(self, ch)
472
+ return 0
473
+ elsif k.respond_to? :include?
474
+ #$log.debug "KKK: checking match include #{k}: #{ch} #{chr} "
475
+ # this bombs if its a String and we check for include of a ch.
476
+ if !k.is_a?( String ) && (k.include?( ch ) || k.include?(chr))
477
+ #$log.debug "KKK: found match include #{ch} #{chr} "
478
+ p.call(self, ch)
479
+ return 0
480
+ end
481
+ elsif k.is_a? Regexp
482
+ if k.match(chr)
483
+ #$log.debug "KKK: found match regex #{ch} #{chr} "
484
+ p.call(self, ch)
485
+ return 0
486
+ end
487
+ end
488
+ end
489
+ return :UNHANDLED
490
+ end
491
+ # setting up some keys
492
+ # This is currently an insertion key map, if you want a String named +@buffer+ updated.
493
+ # Expects buffer_changed and set_buffer to exist as well as +buffer()+.
494
+ # TODO add left and right arrow keys for changing insertion point. And other keys.
495
+ # XXX Why are we trying to duplicate a Field here ??
496
+ def default_string_key_map
497
+ require 'canis/core/include/action'
498
+ @key_map ||= {}
499
+ @key_map[ Regexp.new('[a-zA-Z0-9_\.\/]') ] = Action.new("Append to pattern") { |obj, ch|
500
+ obj.buffer << ch.chr
501
+ obj.buffer_changed
502
+ }
503
+ @key_map[ [127, ?\C-h.getbyte(0)] ] = Action.new("Delete Prev Char") { |obj, ch|
504
+ # backspace
505
+ buff = obj.buffer
506
+ buff = buff[0..-2] unless buff == ""
507
+ obj.set_buffer buff
508
+ }
509
+ end
510
+
511
+ # convenience method to bind a key or array /range of keys, or regex to a block
512
+ # @param [int, String, #include?, Regexp] keycode If the user presses this key, then execute given block
513
+ # @param [String, Action] descr is either a textual description of the key
514
+ # or an Action object
515
+ # @param [block] unless an Action object has been passed, a block is passed for execution
516
+ #
517
+ # @example
518
+ # bind_key '%', 'Do something' {|obj, ch| actions ... }
519
+ # bind_key [?\C-h.getbyte(0), 127], 'Delete something' {|obj, ch| actions ... }
520
+ # bind_key Regexp.new('[a-zA-Z_\.]'), 'Append char' {|obj, ch| actions ... }
521
+ # TODO test this
522
+ def bind_key keycode, descr, &block
523
+ if descr.is_a? Action
524
+ @key_map[keycode] = descr
525
+ else
526
+ @key_map[keycode] = Action.new(descr), block
527
+ end
528
+ end
529
+ end # module }}}
530
+
531
+ # presents given list in numbered format in a window above last line
532
+ # and accepts input on last line
533
+ # The list is a list of strings. e.g.
534
+ # %w{ ruby perl python haskell }
535
+ # Multiple levels can be given as:
536
+ # list = %w{ ruby perl python haskell }
537
+ # list[0] = %w{ ruby ruby1.9 ruby 1.8 rubinius jruby }
538
+ # In this case, "ruby" is the first level option. The others are used
539
+ # in the second level. This might make it clearer. first3 has 2 choices under it.
540
+ # [ "first1" , "first2", ["first3", "second1", "second2"], "first4"]
541
+ #
542
+ # Currently, we return an array containing each selected level
543
+ #
544
+ # @return [Array] selected option/s from list
545
+ def numbered_menu list1, config={}
546
+ if list1.nil? || list1.empty?
547
+ #say_with_pause "empty list passed to numbered_menu" # 2014-04-25
548
+ # remove bottomline
549
+ print_error_message "Empty list passed to numbered_menu"
550
+ return nil
551
+ end
552
+ prompt = config[:prompt] || "Select one: "
553
+ require 'canis/core/util/rcommandwindow'
554
+ layout = { :height => 5, :width => Ncurses.COLS-1, :top => Ncurses.LINES-6, :left => 0 }
555
+ rc = CommandWindow.new nil, :layout => layout, :box => true, :title => config[:title]
556
+ w = rc.window
557
+ # should we yield rc, so user can bind keys or whatever
558
+ # attempt a loop so we do levels.
559
+ retval = []
560
+ begin
561
+ while true
562
+ rc.display_menu list1, :indexing => :number
563
+ #ret = ask(prompt, Integer ) { |q| q.in = 1..list1.size }
564
+ # if class is specifited then update type in Field
565
+ ret = rb_gets(prompt) {|f| f.datatype = 1.class ; f.type :integer; f.valid_range(1..list1.size)}
566
+ val = list1[ret-1]
567
+ if val.is_a? Array
568
+ retval << val[0]
569
+ $log.debug "NL: #{retval} "
570
+ list1 = val[1..-1]
571
+ rc.clear
572
+ else
573
+ retval << val
574
+ $log.debug "NL1: #{retval} "
575
+ break
576
+ end
577
+ end
578
+ ensure
579
+ rc.destroy
580
+ rc = nil
581
+ end
582
+ #list1[ret-1]
583
+ $log.debug "NL2: #{retval} , #{retval.class} "
584
+ retval
585
+ end
586
+
587
+ # This is a variation of display_list which is more for selecting a file, or dir.
588
+ # It maps some keys to go up to parent dir, and to step into directory under cursor
589
+ # if you are in directory mode.
590
+ # @param [String] (optional) glob is a glob to apply when creating a listing
591
+ # @param [Hash] config options for configuring the listing
592
+ # @option config [Boolean] :recursive Should listing recurse, default true
593
+ # @option config [Boolean] :dirs Should list directories only, default false
594
+ # @option config [String] :startdir Directory to use as current
595
+ # You may also add other config pairs to be passed to textpad such as title
596
+ #
597
+ # NOTE: if you pass a glob, then :recursive will not apply. You must specify
598
+ # recursive by prepending "**/" or inserting it in the appropriate place such
599
+ # as "a/b/c/**/*rb". We would not know where to place the "**/".
600
+ #
601
+ # @example list directories recursively
602
+ #
603
+ # str = choose_file :title => "Select a file",
604
+ # :recursive => true,
605
+ # :dirs => true,
606
+ #
607
+ def choose_file glob, config={}
608
+ if glob.is_a? Hash
609
+ config = glob
610
+ glob = nil
611
+ end
612
+ frec = true
613
+ frec = config.delete :recursive if config.key? :recursive
614
+ fdir = config.delete :dirs
615
+ if glob.nil?
616
+ glob = "*"
617
+ if frec
618
+ glob = "**/*"
619
+ end
620
+ if fdir
621
+ glob << "/"
622
+ end
623
+ end
624
+ maxh = 15
625
+ # i am not going through that route, since going up and down a dir will be difficult in a generic
626
+ # case the glob has the dir in it, or i pass directory to the handler.
627
+ # why not Dir.pwd in next line ?? XXX
628
+ #directory = Pathname.new(File.expand_path(File.dirname($0)))
629
+ #_d = config.delete :directory
630
+ #if _d
631
+ #directory = Pathname.new(File.expand_path(_d))
632
+ #end
633
+ directory = config.delete :directory
634
+ # this keeps going up with each invocation if I send ".."
635
+ Dir.chdir(directory) if directory
636
+ command = config.delete(:command)
637
+ text = Dir.glob(glob)
638
+ #text = Dir[File.join(directory.to_s, glob)]
639
+ if !text or text.empty?
640
+ text = ["No entries"]
641
+ end
642
+ if text.size < maxh
643
+ config[:height] = text.size + 1
644
+ end
645
+ # calc window coords
646
+ _update_default_settings config
647
+ default_layout = config[:layout]
648
+ config[:close_key] = 1001
649
+ command_list(text, config) do |t, hash|
650
+ t.suppress_borders true
651
+ t.print_footer false
652
+ #t.fixed_bounds config.delete(:fixed_bounds)
653
+ t.key_handler = ControlPHandler.new(t)
654
+ t.key_handler.maxht = maxh
655
+ t.key_handler.default_layout = default_layout
656
+ t.key_handler.header = hash[:header]
657
+ t.key_handler.recursive_search(glob)
658
+ t.key_handler.directory_key_map
659
+ end
660
+ end
661
+
662
+ # NOTE: moved from bottomline since it used commandwindow but now we;ve
663
+ # moved away from ListObject to view. and i wonder what this really
664
+ # gives ?
665
+ # WARNING: if you actually use this, please copy it to your app, since
666
+ # it may be removed. I don;t see what real purpose it achieves. It is
667
+ # now a wrapper over Canis::Viewer.view
668
+ #
669
+ # Displays text at the bottom of the screen, close the screen with
670
+ # ENTER.
671
+ # @param text can be file name or Array of Strings.
672
+ # @param config is a Hash. :height which will be from bottom of screen
673
+ # and defaults to 15.
674
+ # All other config elements are passed to +view+. See viewer.rb.
675
+ def display_text text, config={}
676
+ _update_default_settings config
677
+ if text.is_a? String
678
+ if File.exists? text
679
+ text = File.open(text, 'r').read.split("\n")
680
+ end
681
+ end
682
+ command_list(text, config) do |t|
683
+ t.suppress_borders true
684
+ t.print_footer false
685
+ end
686
+ end
687
+ # update given hash with layout, close_key and app_header
688
+ # so this is shared across various methods.
689
+ # The layout created is weighted to the bottom, so it is always ending at the second last row
690
+ def _update_default_settings config={}
691
+ ht = config[:height] || 15
692
+ sh = Ncurses.LINES-1
693
+ sc = Ncurses.COLS-0
694
+ layout = [ ht, sc, sh-ht ,0]
695
+ config[:layout] = layout
696
+ config[:close_key] = KEY_ENTER
697
+ config[:app_header] = true
698
+ # repeated resetting seems to play with left and other things.
699
+ # let's say we know that my window will always have certain settings, then let me do a check for them in padrefresh
700
+ # in this window the only thing changing is the top (based on rows). all else is same.
701
+ #config[:fixed_bounds] = [nil, 0, sh, sc]
702
+ end
703
+ # create a new layout array based on size of data given
704
+ # The layout created is weighted to the bottom, so it is always ending at the second last row
705
+ # The +top+ keeps changing based on height.
706
+ def _new_layout size
707
+ ht = size
708
+ #ht = 15
709
+ sh = Ncurses.LINES-1
710
+ sc = Ncurses.COLS-0
711
+ layout = [ ht, sc, sh-ht ,0]
712
+ end
713
+ # return a blank if user quits list, or the value
714
+ # can we prevent a user from quitting, he must select ?
715
+ #
716
+ # Display a list of valies given in +text+ and allows user to shrink the list based on keys entered
717
+ # much like control-p.
718
+ #
719
+ # @param [Array<String>] array of Strings to print
720
+ # @param [ Hash ] config hash passed to Viewer.view
721
+ # May contain +:command+ which is a Proc that replenishes the list everytime user
722
+ # enters a key (updates the search string). The proc is supplied user-entered string.
723
+ # The following example is a proc that matches all the files returned by Dir.glob
724
+ # with the user entered string.
725
+ #
726
+ # Proc.new {|str| Dir.glob("**/*").select do |p| p.index str; end }
727
+ #
728
+ # @return [ String ] text of line user pressed ENTER on, or "" if user pressed ESC or C-c
729
+ # x show keys entered
730
+ # x shrink the pad based on results
731
+ # x if no results show somethinf like "No entries". or don't change
732
+ # TODO take left and right arrow key and adjust insert point
733
+ # TODO have some proc so user can keep querying, rather than passing a list. this way if the
734
+ # list is really long, all values don't need to be passed.
735
+ def display_list text, config={}
736
+ maxh = 15
737
+ _update_default_settings config
738
+ default_layout = config[:layout].dup
739
+ command = config.delete(:command)
740
+ command_list(text, config) do |t, hash|
741
+ t.suppress_borders true
742
+ t.print_footer false
743
+ #t.fixed_bounds config.delete(:fixed_bounds)
744
+ t.key_handler = ControlPHandler.new(t)
745
+ t.key_handler.maxht = maxh
746
+ t.key_handler.default_layout = default_layout
747
+ t.key_handler.header = hash[:header]
748
+ t.key_handler.command = command if command
749
+ end
750
+ end
751
+
752
+ # This is a keyhandler that traps some keys, much like control-p which filters down a list
753
+ # based on some alpha numeric chars. The rest, such as arrow-keys, are passed to the key_map
754
+ #
755
+ class ControlPHandler # --- {{{
756
+ require 'canis/core/include/action'
757
+ include Canis::KeyDispatcher
758
+
759
+ attr_accessor :maxht
760
+ attr_accessor :default_layout
761
+ # string the user is currently entering in (pattern to filter on)
762
+ attr_accessor :buffer
763
+ # application_header object whose text can be changed
764
+ attr_accessor :header
765
+ attr_accessor :key_map
766
+ # specify command to requery data
767
+ attr_accessor :command
768
+ attr_reader :source
769
+ attr_reader :keyint
770
+ attr_reader :keychr
771
+ def initialize source
772
+ @source = source
773
+ @list = source.text
774
+ # backup of data to refilter from if no command given to update
775
+ @__list = @list
776
+ @buffer = ""
777
+ @maxht ||=15
778
+ default_string_key_map
779
+ default_key_map
780
+ @no_match = false
781
+ end
782
+
783
+ # a default proc to requery data based on glob supplied and the pattern user enters
784
+ def recursive_search glob="**/*"
785
+ @command = Proc.new {|str| Dir.glob(glob).select do |p| p.index str; end }
786
+ end
787
+
788
+ # specify command to requery data
789
+ #def command &block
790
+ #@command = block
791
+ #end
792
+ #alias :command= :command
793
+
794
+ # signal that the data has changed and should be redisplayed
795
+ # with window resizing etc.
796
+ def data_changed list
797
+ sz = list.size
798
+ @source.text(list)
799
+ wh = @source.form.window.height
800
+ @source.form.window.hide
801
+ th = @source.height
802
+ sh = Ncurses.LINES-1
803
+ if sz < @maxht
804
+ # rows is less than tp size so reduce tp and window
805
+ @source.height = sz
806
+ nl = _new_layout sz+1
807
+ $log.debug "XXX: adjust ht to #{sz} layout is #{nl} size is #{sz}"
808
+ @source.form.window.resize_with(nl)
809
+ #Window.refresh_all
810
+ else
811
+ # expand the window ht to maxht
812
+ tt = @maxht-1
813
+ @source.height = tt
814
+ nl = _new_layout tt+1
815
+ $log.debug "XXX: increase ht to #{tt} def layout is #{nl} size is #{sz}"
816
+ @source.form.window.resize_with(nl)
817
+ end
818
+
819
+ @source.fire_dimension_changed
820
+
821
+ @source.init_vars # do if rows is less than current_index.
822
+ @source.set_form_row
823
+ @source.form.window.show
824
+
825
+ #Window.refresh_all
826
+ @source.form.window.wrefresh
827
+ Ncurses::Panel.update_panels();
828
+ Ncurses.doupdate();
829
+ end
830
+ # modify the pattern (used if some procs are trying to change using handle to self)
831
+ def set_buffer str
832
+ @buffer = str
833
+ buffer_changed
834
+ end
835
+ # signal that the user has added or deleted a char from the pattern
836
+ # and data should be requeried, etc
837
+ #
838
+ def buffer_changed
839
+ # display the pattern on the header
840
+ @header.text1(">>>#{@buffer}_") if @header
841
+ @header.text_right(Dir.pwd) if @header
842
+ @no_match = false
843
+
844
+ if @command
845
+ @list = @command.call(@buffer)
846
+ else
847
+ @list = @__list.select do |line|
848
+ line.index @buffer
849
+ end
850
+ end
851
+ sz = @list.size
852
+ if sz == 0
853
+ Ncurses.beep
854
+ #return 1
855
+ #this should make ENTER and arrow keys unusable except for BS or Esc,
856
+ @list = ["No entries"]
857
+ @no_match = true
858
+ end
859
+ data_changed @list
860
+ 0
861
+ end
862
+
863
+ # key handler of Controlphandler which overrides KeyDispatcher since we need to
864
+ # intercept KEY_ENTER
865
+ # @param [Fixnum] ch is key read by window.
866
+ # WARNING: Please note that if this is used in +Viewer.view+, that +view+
867
+ # has already trapped CLOSE_KEY which is KEY_ENTER/13 for closing, so we won't get 13
868
+ # anywhere
869
+ def handle_key ch
870
+ $log.debug " HANDLER GOT KEY #{ch} "
871
+ @keyint = ch
872
+ @keychr = nil
873
+ # accumulate keys in a string
874
+ # need to track insertion point if user uses left and right arrow
875
+ @buffer ||= ""
876
+ chr = nil
877
+ chr = ch.chr if ch > 47 and ch < 127
878
+ @keychr = chr
879
+ # Don't let user hit enter or keys if no match
880
+ if [13,10, KEY_ENTER, KEY_UP, KEY_DOWN].include? ch
881
+ if @no_match
882
+ $log.warn "XXX: KEY GOT WAS #{ch}, #{chr} "
883
+ # viewer has already blocked KEY_ENTER !
884
+ return 0 if [13,10, KEY_ENTER, KEY_UP, KEY_DOWN].include? ch
885
+ else
886
+ if [13,10, KEY_ENTER].include? ch
887
+ @source.form.window.ungetch(1001)
888
+ return 0
889
+ end
890
+ end
891
+ end
892
+ ret = process_key ch
893
+ # revert to the basic handling of key_map and refreshing pad.
894
+ # but this will rerun the keys and may once again run a mapping.
895
+ @source._handle_key(ch) if ret == :UNHANDLED
896
+ end
897
+
898
+ # setting up some keys
899
+ def default_key_map
900
+ tp = source
901
+ source.bind_key(?\M-n.getbyte(0), 'goto_end'){ tp.goto_end }
902
+ source.bind_key(?\M-p.getbyte(0), 'goto_start'){ tp.goto_start }
903
+ end
904
+
905
+ # specific actions for directory listers
906
+ # currently for stepping into directory under cursor
907
+ # and going to parent dir.
908
+ def directory_key_map
909
+ @key_map["<"] = Action.new("Goto Parent Dir") { |obj|
910
+ # go to parent dir
911
+ $log.debug "KKK: called proc for <"
912
+ Dir.chdir("..")
913
+ obj.buffer_changed
914
+ }
915
+ @key_map[">"] = Action.new("Change Dir"){ |obj|
916
+ $log.debug "KKK: called proc for > : #{obj.current_value} "
917
+ # step into directory
918
+
919
+ dir = obj.current_value
920
+ if File.directory? dir
921
+ Dir.chdir dir
922
+ obj.buffer_changed
923
+ end
924
+ }
925
+ end
926
+
927
+ # the line on which focus is.
928
+ def current_value
929
+ @source.current_value
930
+ end
931
+ end # -- class }}}
932
+
933
+ end # module