canis 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +45 -0
- data/CHANGES +52 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +24 -0
- data/Rakefile +2 -0
- data/canis.gemspec +25 -0
- data/examples/alpmenu.rb +46 -0
- data/examples/app.sample +19 -0
- data/examples/appemail.rb +191 -0
- data/examples/atree.rb +105 -0
- data/examples/bline.rb +181 -0
- data/examples/common/devel.rb +319 -0
- data/examples/common/file.rb +93 -0
- data/examples/data/README.markdown +9 -0
- data/examples/data/brew.txt +38 -0
- data/examples/data/color.2 +37 -0
- data/examples/data/gemlist.txt +59 -0
- data/examples/data/lotr.txt +12 -0
- data/examples/data/ports.txt +136 -0
- data/examples/data/table.txt +37 -0
- data/examples/data/tasks.csv +88 -0
- data/examples/data/tasks.txt +27 -0
- data/examples/data/todo.txt +16 -0
- data/examples/data/todocsv.csv +28 -0
- data/examples/data/unix1.txt +21 -0
- data/examples/data/unix2.txt +11 -0
- data/examples/dbdemo.rb +506 -0
- data/examples/dirtree.rb +177 -0
- data/examples/newtabbedwindow.rb +100 -0
- data/examples/newtesttabp.rb +92 -0
- data/examples/tabular.rb +212 -0
- data/examples/tasks.rb +179 -0
- data/examples/term2.rb +88 -0
- data/examples/testbuttons.rb +307 -0
- data/examples/testcombo.rb +102 -0
- data/examples/testdb.rb +182 -0
- data/examples/testfields.rb +208 -0
- data/examples/testflowlayout.rb +43 -0
- data/examples/testkeypress.rb +98 -0
- data/examples/testlistbox.rb +187 -0
- data/examples/testlistbox1.rb +199 -0
- data/examples/testmessagebox.rb +144 -0
- data/examples/testprogress.rb +116 -0
- data/examples/testree.rb +107 -0
- data/examples/testsplitlayout.rb +53 -0
- data/examples/testsplitlayout1.rb +49 -0
- data/examples/teststacklayout.rb +48 -0
- data/examples/testwsshortcuts.rb +68 -0
- data/examples/testwsshortcuts2.rb +129 -0
- data/lib/canis.rb +16 -0
- data/lib/canis/core/docs/index.txt +104 -0
- data/lib/canis/core/docs/list.txt +16 -0
- data/lib/canis/core/docs/style_help.yml +34 -0
- data/lib/canis/core/docs/tabbedpane.txt +15 -0
- data/lib/canis/core/docs/table.txt +31 -0
- data/lib/canis/core/docs/textpad.txt +48 -0
- data/lib/canis/core/docs/tree.txt +23 -0
- data/lib/canis/core/include/.DS_Store +0 -0
- data/lib/canis/core/include/action.rb +83 -0
- data/lib/canis/core/include/actionmanager.rb +49 -0
- data/lib/canis/core/include/appmethods.rb +179 -0
- data/lib/canis/core/include/bordertitle.rb +49 -0
- data/lib/canis/core/include/canisparser.rb +100 -0
- data/lib/canis/core/include/colorparser.rb +437 -0
- data/lib/canis/core/include/defaultfilerenderer.rb +64 -0
- data/lib/canis/core/include/io.rb +320 -0
- data/lib/canis/core/include/layouts/SplitLayout.rb +161 -0
- data/lib/canis/core/include/layouts/abstractlayout.rb +213 -0
- data/lib/canis/core/include/layouts/flowlayout.rb +104 -0
- data/lib/canis/core/include/layouts/stacklayout.rb +109 -0
- data/lib/canis/core/include/listbindings.rb +89 -0
- data/lib/canis/core/include/listeditable.rb +319 -0
- data/lib/canis/core/include/listoperations.rb +61 -0
- data/lib/canis/core/include/listselectionmodel.rb +388 -0
- data/lib/canis/core/include/multibuffer.rb +173 -0
- data/lib/canis/core/include/ractionevent.rb +73 -0
- data/lib/canis/core/include/rchangeevent.rb +27 -0
- data/lib/canis/core/include/rhistory.rb +95 -0
- data/lib/canis/core/include/rinputdataevent.rb +47 -0
- data/lib/canis/core/include/textdocument.rb +111 -0
- data/lib/canis/core/include/vieditable.rb +175 -0
- data/lib/canis/core/include/widgetmenu.rb +66 -0
- data/lib/canis/core/system/colormap.rb +165 -0
- data/lib/canis/core/system/keydefs.rb +32 -0
- data/lib/canis/core/system/ncurses.rb +237 -0
- data/lib/canis/core/system/panel.rb +129 -0
- data/lib/canis/core/system/window.rb +1081 -0
- data/lib/canis/core/util/ansiparser.rb +119 -0
- data/lib/canis/core/util/app.rb +696 -0
- data/lib/canis/core/util/basestack.rb +412 -0
- data/lib/canis/core/util/defaultcolorparser.rb +84 -0
- data/lib/canis/core/util/extras/README +5 -0
- data/lib/canis/core/util/extras/bottomline.rb +1815 -0
- data/lib/canis/core/util/extras/padreader.rb +192 -0
- data/lib/canis/core/util/focusmanager.rb +31 -0
- data/lib/canis/core/util/helpmanager.rb +160 -0
- data/lib/canis/core/util/oldwidgetshortcuts.rb +304 -0
- data/lib/canis/core/util/promptmenu.rb +235 -0
- data/lib/canis/core/util/rcommandwindow.rb +933 -0
- data/lib/canis/core/util/rdialogs.rb +520 -0
- data/lib/canis/core/util/textutils.rb +74 -0
- data/lib/canis/core/util/viewer.rb +238 -0
- data/lib/canis/core/util/widgetshortcuts.rb +508 -0
- data/lib/canis/core/widgets/applicationheader.rb +103 -0
- data/lib/canis/core/widgets/box.rb +58 -0
- data/lib/canis/core/widgets/divider.rb +310 -0
- data/lib/canis/core/widgets/extras/README.md +12 -0
- data/lib/canis/core/widgets/extras/rtextarea.rb +960 -0
- data/lib/canis/core/widgets/extras/stackflow.rb +474 -0
- data/lib/canis/core/widgets/keylabelprinter.rb +194 -0
- data/lib/canis/core/widgets/listbox.rb +326 -0
- data/lib/canis/core/widgets/listfooter.rb +86 -0
- data/lib/canis/core/widgets/rcombo.rb +210 -0
- data/lib/canis/core/widgets/rcontainer.rb +415 -0
- data/lib/canis/core/widgets/rlink.rb +30 -0
- data/lib/canis/core/widgets/rmenu.rb +970 -0
- data/lib/canis/core/widgets/rmenulink.rb +30 -0
- data/lib/canis/core/widgets/rmessagebox.rb +400 -0
- data/lib/canis/core/widgets/rprogress.rb +118 -0
- data/lib/canis/core/widgets/rtabbedpane.rb +631 -0
- data/lib/canis/core/widgets/rtabbedwindow.rb +70 -0
- data/lib/canis/core/widgets/rwidget.rb +3634 -0
- data/lib/canis/core/widgets/scrollbar.rb +147 -0
- data/lib/canis/core/widgets/statusline.rb +113 -0
- data/lib/canis/core/widgets/table.rb +1072 -0
- data/lib/canis/core/widgets/tabular.rb +264 -0
- data/lib/canis/core/widgets/textpad.rb +1674 -0
- data/lib/canis/core/widgets/tree.rb +690 -0
- data/lib/canis/core/widgets/tree/treecellrenderer.rb +150 -0
- data/lib/canis/core/widgets/tree/treemodel.rb +432 -0
- data/lib/canis/version.rb +3 -0
- 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
|