canis 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|