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,70 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
* Name: newtabbedwindow.rb
|
|
3
|
+
* Description : This is a window that contains a tabbedpane (NewTabbedPane). This for situation
|
|
4
|
+
when you want to pop up a setup/configuration type of tabbed pane.
|
|
5
|
+
See examples/newtabbedwindow.rb for an example of usage, and test2.rb
|
|
6
|
+
which calls it from the menu (Options2 item).
|
|
7
|
+
In a short while, I will deprecate the existing complex TabbedPane and use this
|
|
8
|
+
in the lib/canis dir.
|
|
9
|
+
* Author: jkepler (http://github.com/mare-imbrium/canis/)
|
|
10
|
+
* Date: 22.10.11 - 20:35
|
|
11
|
+
* License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
|
12
|
+
* Last update: 2013-04-01 13:42
|
|
13
|
+
|
|
14
|
+
== CHANGES
|
|
15
|
+
== TODO
|
|
16
|
+
=end
|
|
17
|
+
require 'canis'
|
|
18
|
+
require 'canis/core/widgets/rtabbedpane'
|
|
19
|
+
require 'canis/core/widgets/rcontainer'
|
|
20
|
+
|
|
21
|
+
include Canis
|
|
22
|
+
module Canis
|
|
23
|
+
class TabbedWindow
|
|
24
|
+
attr_reader :tabbed_pane
|
|
25
|
+
# The given block is passed to the TabbedPane
|
|
26
|
+
# The given dimensions are used to create the window.
|
|
27
|
+
# The TabbedPane is placed at 0,0 and fills the window.
|
|
28
|
+
def initialize config={}, &block
|
|
29
|
+
|
|
30
|
+
h = config.fetch(:height, 0)
|
|
31
|
+
w = config.fetch(:width, 0)
|
|
32
|
+
t = config.fetch(:row, 0)
|
|
33
|
+
l = config.fetch(:col, 0)
|
|
34
|
+
@window = Canis::Window.new :height => h, :width => w, :top => t, :left => l
|
|
35
|
+
@form = Form.new @window
|
|
36
|
+
config[:row] = config[:col] = 0
|
|
37
|
+
@tabbed_pane = TabbedPane.new @form, config , &block
|
|
38
|
+
end
|
|
39
|
+
# returns button index
|
|
40
|
+
# Call this after instantiating the window
|
|
41
|
+
def run
|
|
42
|
+
@form.repaint
|
|
43
|
+
@window.wrefresh
|
|
44
|
+
return handle_keys
|
|
45
|
+
end
|
|
46
|
+
# returns button index
|
|
47
|
+
private
|
|
48
|
+
def handle_keys
|
|
49
|
+
buttonindex = catch(:close) do
|
|
50
|
+
while((ch = @window.getchar()) != FFI::NCurses::KEY_F10 )
|
|
51
|
+
break if ch == ?\C-q.getbyte(0)
|
|
52
|
+
begin
|
|
53
|
+
@form.handle_key(ch)
|
|
54
|
+
@window.wrefresh
|
|
55
|
+
rescue => err
|
|
56
|
+
$log.debug( err) if err
|
|
57
|
+
$log.debug(err.backtrace.join("\n")) if err
|
|
58
|
+
textdialog ["Error in TabbedWindow: #{err} ", *err.backtrace], :title => "Exception"
|
|
59
|
+
$error_message.value = ""
|
|
60
|
+
ensure
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end # while loop
|
|
64
|
+
end # close
|
|
65
|
+
$log.debug "XXX: CALLER GOT #{buttonindex} "
|
|
66
|
+
@window.destroy
|
|
67
|
+
return buttonindex
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,3634 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
* Name: rwidget: base class and then basic widgets like field, button and label
|
|
3
|
+
* Description
|
|
4
|
+
Some simple light widgets for creating ncurses applications. No reliance on ncurses
|
|
5
|
+
forms and fields.
|
|
6
|
+
I expect to pass through this world but once. Any good therefore that I can do,
|
|
7
|
+
or any kindness or ablities that I can show to any fellow creature, let me do it now.
|
|
8
|
+
Let me not defer it or neglect it, for I shall not pass this way again.
|
|
9
|
+
* Author: jkepler (ABCD)
|
|
10
|
+
* Date: 2008-11-19 12:49
|
|
11
|
+
* License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
|
12
|
+
* Last update: 2014-07-10 00:07
|
|
13
|
+
|
|
14
|
+
== CHANGES
|
|
15
|
+
* 2011-10-2 Added PropertyVetoException to rollback changes to property
|
|
16
|
+
* 2011-10-2 Returning self from dsl_accessor and dsl_property for chaining
|
|
17
|
+
* 2011-10-2 removing clutter of buffering, a lot of junk code removed too.
|
|
18
|
+
* 2014-04-23 major cleanup. removal of text_variable.
|
|
19
|
+
== TODO
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
=end
|
|
23
|
+
require 'logger'
|
|
24
|
+
require 'canis/core/system/colormap'
|
|
25
|
+
#require 'canis/core/include/orderedhash'
|
|
26
|
+
require 'canis/core/include/rinputdataevent' # for FIELD 2010-09-11 12:31
|
|
27
|
+
require 'canis/core/include/io'
|
|
28
|
+
require 'canis/core/system/keydefs'
|
|
29
|
+
|
|
30
|
+
# 2013-03-21 - 187compat removed ||
|
|
31
|
+
BOLD = FFI::NCurses::A_BOLD
|
|
32
|
+
REVERSE = FFI::NCurses::A_REVERSE
|
|
33
|
+
UNDERLINE = FFI::NCurses::A_UNDERLINE
|
|
34
|
+
NORMAL = FFI::NCurses::A_NORMAL
|
|
35
|
+
|
|
36
|
+
class Object # yeild eval {{{
|
|
37
|
+
# thanks to terminal-table for this method
|
|
38
|
+
def yield_or_eval &block
|
|
39
|
+
return unless block
|
|
40
|
+
if block.arity > 0
|
|
41
|
+
yield self
|
|
42
|
+
else
|
|
43
|
+
self.instance_eval(&block)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
# 2009-10-04 14:13 added RK after suggestion on http://www.ruby-forum.com/topic/196618#856703
|
|
48
|
+
# these are for 1.8 compatibility
|
|
49
|
+
unless "a"[0] == "a"
|
|
50
|
+
class Fixnum
|
|
51
|
+
def ord
|
|
52
|
+
self
|
|
53
|
+
end
|
|
54
|
+
## mostly for control and meta characters
|
|
55
|
+
def getbyte(n)
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
end unless "a"[0] == "a"
|
|
59
|
+
# 2013-03-21 - 187compat
|
|
60
|
+
class String
|
|
61
|
+
## mostly for control and meta characters
|
|
62
|
+
def getbyte(n)
|
|
63
|
+
self[n]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end # }}}
|
|
67
|
+
class Module # dsl_accessor {{{
|
|
68
|
+
## others may not want this, sets config, so there's a duplicate hash
|
|
69
|
+
# also creates a attr_writer so you can use =.
|
|
70
|
+
# 2011-10-2 V1.3.1 Now returning self, so i can chain calls
|
|
71
|
+
def dsl_accessor(*symbols)
|
|
72
|
+
symbols.each { |sym|
|
|
73
|
+
class_eval %{
|
|
74
|
+
def #{sym}(*val)
|
|
75
|
+
if val.empty?
|
|
76
|
+
@#{sym}
|
|
77
|
+
else
|
|
78
|
+
@#{sym} = val.size == 1 ? val[0] : val
|
|
79
|
+
self # 2011-10-2
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
# can the next bypass validations
|
|
83
|
+
attr_writer sym
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
# Besides creating getters and setters, this also fires property change handler
|
|
88
|
+
# if the value changes, and after the object has been painted once.
|
|
89
|
+
# 2011-10-2 V1.3.1 Now returning self, so i can chain calls
|
|
90
|
+
def dsl_property(*symbols)
|
|
91
|
+
symbols.each { |sym|
|
|
92
|
+
class_eval %{
|
|
93
|
+
def #{sym}(*val)
|
|
94
|
+
if val.empty?
|
|
95
|
+
@#{sym}
|
|
96
|
+
else
|
|
97
|
+
oldvalue = @#{sym}
|
|
98
|
+
tmp = val.size == 1 ? val[0] : val
|
|
99
|
+
newvalue = tmp
|
|
100
|
+
if oldvalue.nil? || @_object_created.nil?
|
|
101
|
+
@#{sym} = tmp
|
|
102
|
+
end
|
|
103
|
+
return(self) if oldvalue.nil? || @_object_created.nil?
|
|
104
|
+
|
|
105
|
+
if oldvalue != newvalue
|
|
106
|
+
# trying to reduce calls to fire, when object is being created
|
|
107
|
+
begin
|
|
108
|
+
@property_changed = true
|
|
109
|
+
fire_property_change("#{sym}", oldvalue, newvalue) if !oldvalue.nil?
|
|
110
|
+
@#{sym} = tmp
|
|
111
|
+
@config["#{sym}"]=@#{sym}
|
|
112
|
+
rescue PropertyVetoException
|
|
113
|
+
$log.warn "PropertyVetoException for #{sym}:" + oldvalue.to_s + "-> "+ newvalue.to_s
|
|
114
|
+
end
|
|
115
|
+
end # if old
|
|
116
|
+
self
|
|
117
|
+
end # if val
|
|
118
|
+
end # def
|
|
119
|
+
#attr_writer sym
|
|
120
|
+
def #{sym}=val
|
|
121
|
+
#{sym}(val)
|
|
122
|
+
end
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
# divert an = call to the dsl_property or accessor call.
|
|
127
|
+
# This is required if I am bypassing dsl_property for some extra processing as in color and bgcolor
|
|
128
|
+
# but need the rest of it.
|
|
129
|
+
def dsl_writer(*symbols)
|
|
130
|
+
symbols.each { |sym|
|
|
131
|
+
class_eval %{
|
|
132
|
+
def #{sym}=(val)
|
|
133
|
+
#{sym}(val)
|
|
134
|
+
end
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
end # }}}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
module Canis
|
|
143
|
+
extend self
|
|
144
|
+
include ColorMap
|
|
145
|
+
class FieldValidationException < RuntimeError
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# The property change is not acceptable, undo it. e.g. test2.rb
|
|
149
|
+
# @param [String] text message
|
|
150
|
+
# @param [Event] PropertyChangeEvent object
|
|
151
|
+
# @since 1.4.0
|
|
152
|
+
class PropertyVetoException < RuntimeError
|
|
153
|
+
def initialize(string, event)
|
|
154
|
+
@string = string
|
|
155
|
+
@event = event
|
|
156
|
+
super(string)
|
|
157
|
+
end
|
|
158
|
+
attr_reader :string, :event
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
module Utils # --- {{{
|
|
162
|
+
## this is the numeric argument used to repeat and action by repeatm()
|
|
163
|
+
$multiplier = 0
|
|
164
|
+
|
|
165
|
+
# 2010-03-04 18:01
|
|
166
|
+
## this may come in handy for methods to know whether they are inside a batch action or not
|
|
167
|
+
# e.g. a single call of foo() may set a var, a repeated call of foo() may append to var
|
|
168
|
+
$inside_multiplier_action = true
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# convenience func to get int value of a key
|
|
172
|
+
# added 2014-05-05
|
|
173
|
+
# instead of ?\C-a.getbyte(0)
|
|
174
|
+
# use key(?\C-a)
|
|
175
|
+
# or key(?a) or key(?\M-x)
|
|
176
|
+
def key ch
|
|
177
|
+
ch.getbyte(0)
|
|
178
|
+
end
|
|
179
|
+
# returns a string representation of a given int keycode
|
|
180
|
+
# @param [Fixnum] keycode read by window
|
|
181
|
+
# In some case, such as Meta/Alt codes, the window reads two ints, but still we are using the param
|
|
182
|
+
# as the value returned by ?\M-a.getbyte(0) and such, which is typically 128 + key
|
|
183
|
+
# @return [String] a string representation which is what is to be used when binding a key to an
|
|
184
|
+
# action or Proc. This is close to what vimrc recognizes such as <CR> <C-a> a-zA-z0-9 <SPACE>
|
|
185
|
+
# Hopefully it should be identical to what vim recognizes in the map command.
|
|
186
|
+
# If the key is not known to this program it returns "UNKNOWN:key" which means this program
|
|
187
|
+
# needs to take care of that combination. FIXME some numbers are missing in between.
|
|
188
|
+
# NOTE do we really need to cache everything ? Only the computed ones should be cached ?
|
|
189
|
+
def key_tos ch # -- {{{
|
|
190
|
+
x = $key_cache[ch]
|
|
191
|
+
return x if x
|
|
192
|
+
chr = case ch
|
|
193
|
+
when 10,13 , KEY_ENTER
|
|
194
|
+
"<CR>"
|
|
195
|
+
when 9
|
|
196
|
+
"<TAB>"
|
|
197
|
+
when 0
|
|
198
|
+
"<C-@>"
|
|
199
|
+
when 27
|
|
200
|
+
"<ESC>"
|
|
201
|
+
when 31
|
|
202
|
+
"<C-/>"
|
|
203
|
+
when 1..30
|
|
204
|
+
x= ch + 96
|
|
205
|
+
"<C-#{x.chr}>"
|
|
206
|
+
when 32
|
|
207
|
+
"<SPACE>"
|
|
208
|
+
when 41
|
|
209
|
+
"<M-CR>"
|
|
210
|
+
when 33..126
|
|
211
|
+
ch.chr
|
|
212
|
+
when 127,263
|
|
213
|
+
"<BACKSPACE>"
|
|
214
|
+
when 128..154
|
|
215
|
+
x = ch - 128
|
|
216
|
+
#"<M-C-#{x.chr}>"
|
|
217
|
+
xx = key_tos(x).gsub(/[<>]/,"")
|
|
218
|
+
"<M-#{xx}>"
|
|
219
|
+
when 160..255
|
|
220
|
+
x = ch - 128
|
|
221
|
+
xx = key_tos(x).gsub(/[<>]/,"")
|
|
222
|
+
"<M-#{xx}>"
|
|
223
|
+
when 255
|
|
224
|
+
"<M-BACKSPACE>"
|
|
225
|
+
when 2727
|
|
226
|
+
"<ESC-ESC>"
|
|
227
|
+
else
|
|
228
|
+
|
|
229
|
+
chs = FFI::NCurses::keyname(ch)
|
|
230
|
+
# remove those ugly brackets around function keys
|
|
231
|
+
if chs && chs[-1]==')'
|
|
232
|
+
chs = chs.gsub(/[()]/,'')
|
|
233
|
+
end
|
|
234
|
+
if chs
|
|
235
|
+
chs = chs.gsub("KEY_","")
|
|
236
|
+
"<#{chs}>"
|
|
237
|
+
else
|
|
238
|
+
"UNKNOWN:#{ch}"
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
$key_cache[ch] = chr
|
|
242
|
+
return chr
|
|
243
|
+
end # --- }}}
|
|
244
|
+
# only for a short while till we weed it out.
|
|
245
|
+
alias :keycode_tos :key_tos
|
|
246
|
+
# needs to move to a keystroke class
|
|
247
|
+
# please use these only for printing or debugging, not comparing
|
|
248
|
+
# I could soon return symbols instead 2010-09-07 14:14
|
|
249
|
+
# @deprecated
|
|
250
|
+
# Please move to window.key_tos
|
|
251
|
+
def ORIGkeycode_tos keycode # {{{
|
|
252
|
+
$log.warn "XXX: keycode_tos please move to window.key_tos"
|
|
253
|
+
case keycode
|
|
254
|
+
when 33..126
|
|
255
|
+
return keycode.chr
|
|
256
|
+
when ?\C-a.getbyte(0) .. ?\C-z.getbyte(0)
|
|
257
|
+
return "C-" + (keycode + ?a.getbyte(0) -1).chr
|
|
258
|
+
when ?\M-A.getbyte(0)..?\M-z.getbyte(0)
|
|
259
|
+
return "M-"+ (keycode - 128).chr
|
|
260
|
+
when ?\M-\C-A.getbyte(0)..?\M-\C-Z.getbyte(0)
|
|
261
|
+
return "M-C-"+ (keycode - 32).chr
|
|
262
|
+
when ?\M-0.getbyte(0)..?\M-9.getbyte(0)
|
|
263
|
+
return "M-"+ (keycode-?\M-0.getbyte(0)).to_s
|
|
264
|
+
when 32
|
|
265
|
+
return "space" # changed to lowercase so consistent
|
|
266
|
+
when 27
|
|
267
|
+
return "esc" # changed to lowercase so consistent
|
|
268
|
+
when ?\C-].getbyte(0)
|
|
269
|
+
return "C-]"
|
|
270
|
+
when 258
|
|
271
|
+
return "down"
|
|
272
|
+
when 259
|
|
273
|
+
return "up"
|
|
274
|
+
when 260
|
|
275
|
+
return "left"
|
|
276
|
+
when 261
|
|
277
|
+
return "right"
|
|
278
|
+
when FFI::NCurses::KEY_F1..FFI::NCurses::KEY_F12
|
|
279
|
+
return "F"+ (keycode-264).to_s
|
|
280
|
+
when 330
|
|
281
|
+
return "delete"
|
|
282
|
+
when 127
|
|
283
|
+
return "bs"
|
|
284
|
+
when 353
|
|
285
|
+
return "btab"
|
|
286
|
+
when 481
|
|
287
|
+
return "M-S-tab"
|
|
288
|
+
when 393..402
|
|
289
|
+
return "M-F"+ (keycode-392).to_s
|
|
290
|
+
when 0
|
|
291
|
+
return "C-space"
|
|
292
|
+
when 160
|
|
293
|
+
return "M-space" # at least on OSX Leopard now (don't remember this working on PPC)
|
|
294
|
+
when C_LEFT
|
|
295
|
+
return "C-left"
|
|
296
|
+
when C_RIGHT
|
|
297
|
+
return "C-right"
|
|
298
|
+
when S_F9
|
|
299
|
+
return "S_F9"
|
|
300
|
+
else
|
|
301
|
+
others=[?\M--,?\M-+,?\M-=,?\M-',?\M-",?\M-;,?\M-:,?\M-\,, ?\M-.,?\M-<,?\M->,?\M-?,?\M-/,?\M-!]
|
|
302
|
+
others.collect! {|x| x.getbyte(0) } ## added 2009-10-04 14:25 for 1.9
|
|
303
|
+
s_others=%w[M-- M-+ M-= M-' M-" M-; M-: M-, M-. M-< M-> M-? M-/ M-!]
|
|
304
|
+
if others.include? keycode
|
|
305
|
+
index = others.index keycode
|
|
306
|
+
return s_others[index]
|
|
307
|
+
end
|
|
308
|
+
# all else failed
|
|
309
|
+
return keycode.to_s
|
|
310
|
+
end
|
|
311
|
+
end # }}}
|
|
312
|
+
|
|
313
|
+
# if passed a string in second or third param, will create a color
|
|
314
|
+
# and return, else it will return default color
|
|
315
|
+
# Use this in order to create a color pair with the colors
|
|
316
|
+
# provided, however, if user has not provided, use supplied
|
|
317
|
+
# default.
|
|
318
|
+
# @param [Fixnum] color_pair created by ncurses
|
|
319
|
+
# @param [Symbol] color name such as white black cyan magenta red green yellow
|
|
320
|
+
# @param [Symbol] bgcolor name such as white black cyan magenta red green yellow
|
|
321
|
+
# @example get_color $promptcolor, :white, :cyan
|
|
322
|
+
def get_color default=$datacolor, color=color(), bgcolor=bgcolor()
|
|
323
|
+
return default if color.nil? || bgcolor.nil?
|
|
324
|
+
#raise ArgumentError, "Color not valid: #{color}: #{ColorMap.colors} " if !ColorMap.is_color? color
|
|
325
|
+
#raise ArgumentError, "Bgolor not valid: #{bgcolor} : #{ColorMap.colors} " if !ColorMap.is_color? bgcolor
|
|
326
|
+
acolor = ColorMap.get_color(color, bgcolor)
|
|
327
|
+
return acolor
|
|
328
|
+
end
|
|
329
|
+
#
|
|
330
|
+
# convert a string to integer attribute
|
|
331
|
+
# FIXME: what if user wishes to OR two attribs, this will give error
|
|
332
|
+
# @param [String] e.g. reverse bold normal underline
|
|
333
|
+
# if a Fixnum is passed, it is returned as is assuming to be
|
|
334
|
+
# an attrib
|
|
335
|
+
def get_attrib str
|
|
336
|
+
return FFI::NCurses::A_NORMAL unless str
|
|
337
|
+
# next line allows us to do a one time conversion and keep the value
|
|
338
|
+
# in the same variable
|
|
339
|
+
if str.is_a? Fixnum
|
|
340
|
+
if [
|
|
341
|
+
FFI::NCurses::A_BOLD,
|
|
342
|
+
FFI::NCurses::A_REVERSE,
|
|
343
|
+
FFI::NCurses::A_NORMAL,
|
|
344
|
+
FFI::NCurses::A_UNDERLINE,
|
|
345
|
+
FFI::NCurses::A_STANDOUT,
|
|
346
|
+
FFI::NCurses::A_DIM,
|
|
347
|
+
FFI::NCurses::A_BOLD | FFI::NCurses::A_REVERSE,
|
|
348
|
+
FFI::NCurses::A_BOLD | FFI::NCurses::A_UNDERLINE,
|
|
349
|
+
FFI::NCurses::A_REVERSE | FFI::NCurses::A_UNDERLINE,
|
|
350
|
+
FFI::NCurses::A_BLINK
|
|
351
|
+
].include? str
|
|
352
|
+
return str
|
|
353
|
+
else
|
|
354
|
+
raise ArgumentError, "get_attrib got a wrong value: #{str} "
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
att = nil
|
|
360
|
+
str = str.downcase.to_sym if str.is_a? String
|
|
361
|
+
case str #.to_s.downcase
|
|
362
|
+
when :bold
|
|
363
|
+
att = FFI::NCurses::A_BOLD
|
|
364
|
+
when :reverse
|
|
365
|
+
att = FFI::NCurses::A_REVERSE
|
|
366
|
+
when :normal
|
|
367
|
+
att = FFI::NCurses::A_NORMAL
|
|
368
|
+
when :underline
|
|
369
|
+
att = FFI::NCurses::A_UNDERLINE
|
|
370
|
+
when :standout
|
|
371
|
+
att = FFI::NCurses::A_STANDOUT
|
|
372
|
+
when :bold_reverse
|
|
373
|
+
att = FFI::NCurses::A_BOLD | FFI::NCurses::A_REVERSE
|
|
374
|
+
when :bold_underline
|
|
375
|
+
att = FFI::NCurses::A_BOLD | FFI::NCurses::A_UNDERLINE
|
|
376
|
+
when :dim
|
|
377
|
+
att = FFI::NCurses::A_DIM
|
|
378
|
+
when :blink
|
|
379
|
+
att = FFI::NCurses::A_BLINK # unlikely to work
|
|
380
|
+
else
|
|
381
|
+
att = FFI::NCurses::A_NORMAL
|
|
382
|
+
end
|
|
383
|
+
return att
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
## repeats the given action based on how value of universal numerica argument
|
|
387
|
+
##+ set using the C-u key. Or in vim-mode using numeric keys
|
|
388
|
+
def repeatm
|
|
389
|
+
$inside_multiplier_action = true
|
|
390
|
+
_multiplier = ( ($multiplier.nil? || $multiplier == 0) ? 1 : $multiplier )
|
|
391
|
+
_multiplier.times { yield }
|
|
392
|
+
$multiplier = 0
|
|
393
|
+
$inside_multiplier_action = false
|
|
394
|
+
end
|
|
395
|
+
# this is the bindkey that has been working all along. now i am trying a new approach
|
|
396
|
+
# that does not use a hash inside but keeps on key. so it can be manip easily be user
|
|
397
|
+
def ORIGbind_key keycode, *args, &blk # -- {{{
|
|
398
|
+
#$log.debug " #{@name} bind_key received #{keycode} "
|
|
399
|
+
@_key_map ||= {}
|
|
400
|
+
#
|
|
401
|
+
# added on 2011-12-4 so we can pass a description for a key and print it
|
|
402
|
+
# The first argument may be a string, it will not be removed
|
|
403
|
+
# so existing programs will remain as is.
|
|
404
|
+
@key_label ||= {}
|
|
405
|
+
if args[0].is_a?(String) || args[0].is_a?(Symbol)
|
|
406
|
+
@key_label[keycode] = args[0]
|
|
407
|
+
else
|
|
408
|
+
@key_label[keycode] = :unknown
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
if !block_given?
|
|
412
|
+
blk = args.pop
|
|
413
|
+
raise "If block not passed, last arg should be a method symbol" if !blk.is_a? Symbol
|
|
414
|
+
#$log.debug " #{@name} bind_key received a symbol #{blk} "
|
|
415
|
+
end
|
|
416
|
+
case keycode
|
|
417
|
+
when String
|
|
418
|
+
# single assignment
|
|
419
|
+
keycode = keycode.getbyte(0) #if keycode.class==String ## 1.9 2009-10-05 19:40
|
|
420
|
+
#$log.debug " #{name} Widg String called bind_key BIND #{keycode}, #{keycode_tos(keycode)} "
|
|
421
|
+
#$log.debug " assigning #{keycode} " if $log.debug?
|
|
422
|
+
@_key_map[keycode] = blk
|
|
423
|
+
when Array
|
|
424
|
+
# double assignment
|
|
425
|
+
# for starters lets try with 2 keys only
|
|
426
|
+
raise "A one key array will not work. Pass without array" if keycode.size == 1
|
|
427
|
+
a0 = keycode[0]
|
|
428
|
+
a0 = keycode[0].getbyte(0) if keycode[0].class == String
|
|
429
|
+
a1 = keycode[1]
|
|
430
|
+
a1 = keycode[1].getbyte(0) if keycode[1].class == String
|
|
431
|
+
@_key_map[a0] ||= OrderedHash.new
|
|
432
|
+
#$log.debug " assigning #{keycode} , A0 #{a0} , A1 #{a1} " if $log.debug?
|
|
433
|
+
@_key_map[a0][a1] = blk
|
|
434
|
+
#$log.debug " XX assigning #{keycode} to _key_map " if $log.debug?
|
|
435
|
+
else
|
|
436
|
+
#$log.debug " assigning #{keycode} to _key_map " if $log.debug?
|
|
437
|
+
@_key_map[keycode] = blk
|
|
438
|
+
end
|
|
439
|
+
@_key_args ||= {}
|
|
440
|
+
@_key_args[keycode] = args
|
|
441
|
+
|
|
442
|
+
end # --- }}}
|
|
443
|
+
|
|
444
|
+
##
|
|
445
|
+
# A new attempt at a flat hash 2014-05-06 - 00:16
|
|
446
|
+
# bind an action to a key, required if you create a button which has a hotkey
|
|
447
|
+
# or a field to be focussed on a key, or any other user defined action based on key
|
|
448
|
+
# e.g. bind_key ?\C-x, object, block
|
|
449
|
+
# added 2009-01-06 19:13 since widgets need to handle keys properly
|
|
450
|
+
# 2010-02-24 12:43 trying to take in multiple key bindings, TODO unbind
|
|
451
|
+
# TODO add symbol so easy to map from config file or mapping file
|
|
452
|
+
#
|
|
453
|
+
# Ideally i want to also allow a regex and array/range to be used as a key
|
|
454
|
+
# However, then how do i do multiple assignments which use an array.
|
|
455
|
+
#
|
|
456
|
+
# Currently the only difference is that there is no hash inside the value,
|
|
457
|
+
# key can be an int, or array of ints (for multiple keycode like qq or gg).
|
|
458
|
+
def bind_key keycode, *args, &blk
|
|
459
|
+
#$log.debug " #{@name} bind_key received #{keycode} "
|
|
460
|
+
@_key_map ||= {}
|
|
461
|
+
#
|
|
462
|
+
# added on 2011-12-4 so we can pass a description for a key and print it
|
|
463
|
+
# The first argument may be a string, it will not be removed
|
|
464
|
+
# so existing programs will remain as is.
|
|
465
|
+
@key_label ||= {}
|
|
466
|
+
if args[0].is_a?(String) || args[0].is_a?(Symbol)
|
|
467
|
+
@key_label[keycode] = args[0]
|
|
468
|
+
else
|
|
469
|
+
@key_label[keycode] = :unknown
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
if !block_given?
|
|
473
|
+
blk = args.pop
|
|
474
|
+
raise "If block not passed, last arg should be a method symbol" if !blk.is_a? Symbol
|
|
475
|
+
#$log.debug " #{@name} bind_key received a symbol #{blk} "
|
|
476
|
+
end
|
|
477
|
+
case keycode
|
|
478
|
+
when String
|
|
479
|
+
# single assignment
|
|
480
|
+
keycode = keycode.getbyte(0) #if keycode.class==String ## 1.9 2009-10-05 19:40
|
|
481
|
+
#@_key_map[keycode] = blk
|
|
482
|
+
when Array
|
|
483
|
+
# double assignment
|
|
484
|
+
# this means that all these keys have to be pressed in succession for this block, like "gg" or "C-x C-c"
|
|
485
|
+
raise "A one key array will not work. Pass without array" if keycode.size == 1
|
|
486
|
+
ee = []
|
|
487
|
+
keycode.each do |e|
|
|
488
|
+
e = e.getbyte(0) if e.is_a? String
|
|
489
|
+
ee << e
|
|
490
|
+
end
|
|
491
|
+
bind_composite_mapping ee, args, blk
|
|
492
|
+
return self
|
|
493
|
+
#@_key_map[a0] ||= OrderedHash.new
|
|
494
|
+
#@_key_map[a0][a1] = blk
|
|
495
|
+
#$log.debug " XX assigning #{keycode} to _key_map " if $log.debug?
|
|
496
|
+
else
|
|
497
|
+
$log.debug " assigning #{keycode} to _key_map for #{self.class}, #{@name}" if $log.debug?
|
|
498
|
+
end
|
|
499
|
+
@_key_map[keycode] = blk
|
|
500
|
+
@_key_args ||= {}
|
|
501
|
+
@_key_args[keycode] = args
|
|
502
|
+
self
|
|
503
|
+
end
|
|
504
|
+
=begin
|
|
505
|
+
# this allows us to play a bit with the map, and allocate one action to another key
|
|
506
|
+
# get_action_map()[KEY_ENTER] = get_action(32)
|
|
507
|
+
# But we will hold on this unless absolutely necessary. 2014-05-12 - 22:36 CANIS.
|
|
508
|
+
def get_action keycode
|
|
509
|
+
@_key_map[keycode]
|
|
510
|
+
end
|
|
511
|
+
def get_action_map
|
|
512
|
+
@_key_map
|
|
513
|
+
end
|
|
514
|
+
=end
|
|
515
|
+
class MapNode
|
|
516
|
+
attr_accessor :action
|
|
517
|
+
attr_accessor :map
|
|
518
|
+
def initialize arg=nil
|
|
519
|
+
@map = Hash.new {|hash, key| hash[key] = MapNode.new }
|
|
520
|
+
end
|
|
521
|
+
def put key, value
|
|
522
|
+
@map[key].action = value
|
|
523
|
+
end
|
|
524
|
+
# fetch / get returns a node, or nil. if node, then use node.action
|
|
525
|
+
def fetch key, deft=nil
|
|
526
|
+
@map.fetch(key, deft)
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
def bind_composite_mapping key, *args, action
|
|
531
|
+
@_key_composite_map ||= Hash.new {|hash, key| hash[key] = MapNode.new }
|
|
532
|
+
if key.is_a? String
|
|
533
|
+
n = @_key_composite_map[key]
|
|
534
|
+
n.action = action
|
|
535
|
+
else
|
|
536
|
+
mp = @_key_composite_map
|
|
537
|
+
n = nil
|
|
538
|
+
key.each do |e|
|
|
539
|
+
n = mp[e]
|
|
540
|
+
mp = n.map
|
|
541
|
+
end
|
|
542
|
+
n.action = action
|
|
543
|
+
end
|
|
544
|
+
val = @_key_composite_map.fetch(key[0], nil)
|
|
545
|
+
$log.debug " composite contains #{key} : #{val} "
|
|
546
|
+
end
|
|
547
|
+
def check_composite_mapping key, window
|
|
548
|
+
$log.debug "inside check with #{key} "
|
|
549
|
+
return nil if !@_key_composite_map
|
|
550
|
+
return nil if !@_key_composite_map.key? key
|
|
551
|
+
$log.debug " composite has #{key} "
|
|
552
|
+
|
|
553
|
+
# we have a match and need to loop
|
|
554
|
+
mp = @_key_composite_map
|
|
555
|
+
n = nil
|
|
556
|
+
actions = []
|
|
557
|
+
unconsumed = []
|
|
558
|
+
e = key
|
|
559
|
+
while true
|
|
560
|
+
|
|
561
|
+
# we traverse each key and get action of final.
|
|
562
|
+
# However, if at any level there is a failure, we need to go back to previous action
|
|
563
|
+
# and push the other keys back so they can be again processed.
|
|
564
|
+
if e.nil? or e == -1
|
|
565
|
+
#puts "e is nil TODO "
|
|
566
|
+
# TODO
|
|
567
|
+
#$log.debug " -1 push #{unconsumed} returning: #{actions.last}"
|
|
568
|
+
$log.debug " -1 returning: #{actions.last}"
|
|
569
|
+
# is there a reason we are pushing here ?
|
|
570
|
+
#unconsumed.each {|e| window.ungetch(e)}
|
|
571
|
+
return actions.last
|
|
572
|
+
else
|
|
573
|
+
$log.debug " in loop with #{e} "
|
|
574
|
+
unconsumed << e
|
|
575
|
+
n = mp.fetch(e, nil)
|
|
576
|
+
$log.debug " got node #{n} with #{e} "
|
|
577
|
+
# instead of just nil, we need to go back up, but since not recursive ...
|
|
578
|
+
#return nil unless n
|
|
579
|
+
$log.debug "push unconsumed:#{unconsumed} " unless n
|
|
580
|
+
unconsumed.each {|e| window.ungetch(e)} unless n
|
|
581
|
+
return actions.last unless n
|
|
582
|
+
mp = n.map
|
|
583
|
+
# there are no more keys, only an action
|
|
584
|
+
if mp.nil? or mp.empty?
|
|
585
|
+
$log.debug " mp is nil or empty returning action: #{n.action}"
|
|
586
|
+
#puts "mp is nil or empty"
|
|
587
|
+
return n.action
|
|
588
|
+
end
|
|
589
|
+
# we could have consumed keys at this point
|
|
590
|
+
actions << n.action if n.action
|
|
591
|
+
unconsumed.clear if n.action
|
|
592
|
+
#e = window.getchar
|
|
593
|
+
Ncurses::wtimeout(window.get_window, 500) # will wait a second on wgetch so we can get gg and qq
|
|
594
|
+
e = window.getch
|
|
595
|
+
Ncurses::nowtimeout(window.get_window, true)
|
|
596
|
+
# e can return -1 if timedout
|
|
597
|
+
$log.debug " getch got #{e}"
|
|
598
|
+
end
|
|
599
|
+
end
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
def xxxbind_composite_mapping keycode, *args, &blk
|
|
603
|
+
@_key_composite_map ||= {}
|
|
604
|
+
str = ""
|
|
605
|
+
keycode.each do |e|
|
|
606
|
+
s = key_tos(e)
|
|
607
|
+
str << s
|
|
608
|
+
end
|
|
609
|
+
$log.debug " composite map #{keycode} bound as #{str} "
|
|
610
|
+
@_key_composite_map[str] = blk
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
# define a key with sub-keys to which commands are attached.
|
|
614
|
+
# e.g. to attach commands to C-x a , C-x b, C-x x etc.
|
|
615
|
+
#
|
|
616
|
+
# == Example
|
|
617
|
+
#
|
|
618
|
+
# We create a map named :csmap and attach various commands to it
|
|
619
|
+
# on different keys. At this point, there is no main key that triggers it.
|
|
620
|
+
# Any key can be made the prefix command later. In this case, C-s was bound
|
|
621
|
+
# to the map. This is more organized that creating separate maps for
|
|
622
|
+
# C-s r, C-s s etc which then cannot be changed or customized by user.
|
|
623
|
+
#
|
|
624
|
+
# @form.define_prefix_command :csmap, :scope => self
|
|
625
|
+
# @form.define_key(:csmap, "r", 'refresh', :refresh )
|
|
626
|
+
# @form.define_key(:csmap, "s", 'specification') { specification }
|
|
627
|
+
# @form.bind_key ?\C-s, :csmap
|
|
628
|
+
#
|
|
629
|
+
def define_prefix_command _name, config={} #_mapvar=nil, _prompt=nil
|
|
630
|
+
$rb_prefix_map ||= {}
|
|
631
|
+
_name = _name.to_sym unless _name.is_a? Symbol
|
|
632
|
+
$rb_prefix_map[_name] ||= {}
|
|
633
|
+
scope = config[:scope] || self
|
|
634
|
+
$rb_prefix_map[_name][:scope] = scope
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
# create a variable by name _name
|
|
638
|
+
# create a method by same name to use
|
|
639
|
+
# Don;t let this happen more than once
|
|
640
|
+
instance_eval %{
|
|
641
|
+
def #{_name.to_s} *args
|
|
642
|
+
#$log.debug "XXX: came inside #{_name} "
|
|
643
|
+
h = $rb_prefix_map["#{_name}".to_sym]
|
|
644
|
+
raise "No prefix_map named #{_name}, #{$rb_prefix_map.keys} " unless h
|
|
645
|
+
ch = @window.getchar
|
|
646
|
+
if ch
|
|
647
|
+
if ch == KEY_F1
|
|
648
|
+
text = ["Options are: "]
|
|
649
|
+
h.keys.each { |e| c = keycode_tos(e); text << c + " " + @descriptions[e] }
|
|
650
|
+
textdialog text, :title => "#{_name} key bindings"
|
|
651
|
+
return
|
|
652
|
+
end
|
|
653
|
+
res = h[ch]
|
|
654
|
+
if res.is_a? Proc
|
|
655
|
+
res.call
|
|
656
|
+
elsif res.is_a? Symbol
|
|
657
|
+
scope = h[:scope]
|
|
658
|
+
scope.send(res)
|
|
659
|
+
elsif res.nil?
|
|
660
|
+
Ncurses.beep
|
|
661
|
+
return :UNHANDLED
|
|
662
|
+
end
|
|
663
|
+
else
|
|
664
|
+
:UNHANDLED
|
|
665
|
+
end
|
|
666
|
+
end
|
|
667
|
+
}
|
|
668
|
+
return _name
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
# Define a key for a prefix command.
|
|
672
|
+
# @see +define_prefix_command+
|
|
673
|
+
#
|
|
674
|
+
# == Example:
|
|
675
|
+
#
|
|
676
|
+
# @form.define_key(:csmap, "s", 'specification') { specification }
|
|
677
|
+
# @form.define_key(:csmap, "r", 'refresh', :refresh )
|
|
678
|
+
#
|
|
679
|
+
# @param _symbol prefix command symbol (already created using +define_prefix_command+
|
|
680
|
+
# @param keycode key within the prefix command for given block or action
|
|
681
|
+
# @param args arguments to be passed to block. The first is a description.
|
|
682
|
+
# The second may be a symbol for a method to be executed (if block not given).
|
|
683
|
+
# @param block action to be executed on pressing keycode
|
|
684
|
+
def define_key _symbol, _keycode, *args, &blk
|
|
685
|
+
#_symbol = @symbol
|
|
686
|
+
h = $rb_prefix_map[_symbol]
|
|
687
|
+
raise ArgumentError, "No such keymap #{_symbol} defined. Use define_prefix_command." unless h
|
|
688
|
+
_keycode = _keycode[0].getbyte(0) if _keycode[0].class == String
|
|
689
|
+
arg = args.shift
|
|
690
|
+
if arg.is_a? String
|
|
691
|
+
desc = arg
|
|
692
|
+
arg = args.shift
|
|
693
|
+
elsif arg.is_a? Symbol
|
|
694
|
+
# its a symbol
|
|
695
|
+
desc = arg.to_s
|
|
696
|
+
elsif arg.nil?
|
|
697
|
+
desc = "unknown"
|
|
698
|
+
else
|
|
699
|
+
raise ArgumentError, "Don't know how to handle #{arg.class} in PrefixManager"
|
|
700
|
+
end
|
|
701
|
+
# 2013-03-20 - 18:45 187compat gave error in 187 cannot convert string to int
|
|
702
|
+
#@descriptions ||= []
|
|
703
|
+
@descriptions ||= {}
|
|
704
|
+
@descriptions[_keycode] = desc
|
|
705
|
+
|
|
706
|
+
if !block_given?
|
|
707
|
+
blk = arg
|
|
708
|
+
end
|
|
709
|
+
h[_keycode] = blk
|
|
710
|
+
end
|
|
711
|
+
# Display key bindings for current widget and form in dialog
|
|
712
|
+
def print_key_bindings *args
|
|
713
|
+
f = get_current_field
|
|
714
|
+
#labels = [@key_label, f.key_label]
|
|
715
|
+
#labels = [@key_label]
|
|
716
|
+
#labels << f.key_label if f.key_label
|
|
717
|
+
labels = []
|
|
718
|
+
labels << (f.key_label || {}) #if f.key_label
|
|
719
|
+
labels << @key_label
|
|
720
|
+
arr = []
|
|
721
|
+
if get_current_field.help_text
|
|
722
|
+
arr << get_current_field.help_text
|
|
723
|
+
end
|
|
724
|
+
labels.each_with_index { |h, i|
|
|
725
|
+
case i
|
|
726
|
+
when 0
|
|
727
|
+
arr << " === Current widget bindings ==="
|
|
728
|
+
when 1
|
|
729
|
+
arr << " === Form bindings ==="
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
h.each_pair { |name, val|
|
|
733
|
+
if name.is_a? Fixnum
|
|
734
|
+
name = keycode_tos name
|
|
735
|
+
elsif name.is_a? String
|
|
736
|
+
name = keycode_tos(name.getbyte(0))
|
|
737
|
+
elsif name.is_a? Array
|
|
738
|
+
s = []
|
|
739
|
+
name.each { |e|
|
|
740
|
+
s << keycode_tos(e.getbyte(0))
|
|
741
|
+
}
|
|
742
|
+
name = s
|
|
743
|
+
else
|
|
744
|
+
#$log.debug "XXX: KEY #{name} #{name.class} "
|
|
745
|
+
end
|
|
746
|
+
arr << " %-30s %s" % [name ,val]
|
|
747
|
+
$log.debug "KEY: #{name} : #{val} "
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
textdialog arr, :title => "Key Bindings"
|
|
751
|
+
end
|
|
752
|
+
def bind_keys keycodes, *args, &blk
|
|
753
|
+
keycodes.each { |k| bind_key k, *args, &blk }
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
# This the new one which does not use orderedhash, it uses an array for multiple assignments
|
|
759
|
+
# and falls back to a single assignment if multiple fails.
|
|
760
|
+
# e.g. process_key ch, self
|
|
761
|
+
# returns UNHANDLED if no block for it
|
|
762
|
+
# after form handles basic keys, it gives unhandled key to current field, if current field returns
|
|
763
|
+
# unhandled, then it checks this map.
|
|
764
|
+
# added 2009-01-06 19:13 since widgets need to handle keys properly
|
|
765
|
+
# added 2009-01-18 12:58 returns ret val of blk.call
|
|
766
|
+
# so that if block does not handle, the key can still be handled
|
|
767
|
+
# e.g. table last row, last col does not handle, so it will auto go to next field
|
|
768
|
+
# 2010-02-24 13:45 handles 2 key combinations, copied from Form, must be identical in logic
|
|
769
|
+
# except maybe for window pointer. TODO not tested
|
|
770
|
+
def _process_key keycode, object, window
|
|
771
|
+
return :UNHANDLED if @_key_map.nil?
|
|
772
|
+
chr = nil
|
|
773
|
+
ch = keycode
|
|
774
|
+
if ch > 0 and ch < 256
|
|
775
|
+
chr = ch.chr
|
|
776
|
+
end
|
|
777
|
+
blk = @_key_map[keycode]
|
|
778
|
+
# i am scrappaing this since i am once again complicating too much
|
|
779
|
+
=begin
|
|
780
|
+
# if blk then we found an exact match which supercedes any ranges, arrays and regexes
|
|
781
|
+
unless blk
|
|
782
|
+
@_key_map.each_pair do |k,p|
|
|
783
|
+
$log.debug "KKK: processing key #{ch} #{chr} "
|
|
784
|
+
if (k == ch || k == chr)
|
|
785
|
+
$log.debug "KKK: checking match == #{k}: #{ch} #{chr} "
|
|
786
|
+
# compare both int key and chr
|
|
787
|
+
$log.debug "KKK: found match 1 #{ch} #{chr} "
|
|
788
|
+
#p.call(self, ch)
|
|
789
|
+
#return 0
|
|
790
|
+
blk = p
|
|
791
|
+
break
|
|
792
|
+
elsif k.respond_to? :include?
|
|
793
|
+
$log.debug "KKK: checking match include #{k}: #{ch} #{chr} "
|
|
794
|
+
# this bombs if its a String and we check for include of a ch.
|
|
795
|
+
if !k.is_a?( String ) && (k.include?( ch ) || k.include?(chr))
|
|
796
|
+
$log.debug "KKK: found match include #{ch} #{chr} "
|
|
797
|
+
#p.call(self, ch)
|
|
798
|
+
#return 0
|
|
799
|
+
blk = p
|
|
800
|
+
break
|
|
801
|
+
end
|
|
802
|
+
elsif k.is_a? Regexp
|
|
803
|
+
if k.match(chr)
|
|
804
|
+
$log.debug "KKK: found match regex #{ch} #{chr} "
|
|
805
|
+
#p.call(self, ch)
|
|
806
|
+
#return 0
|
|
807
|
+
blk = p
|
|
808
|
+
break
|
|
809
|
+
end
|
|
810
|
+
end
|
|
811
|
+
end
|
|
812
|
+
end
|
|
813
|
+
=end
|
|
814
|
+
# blk either has a proc or is nil
|
|
815
|
+
# we still need to check for a complex map. if none, then execute simple map.
|
|
816
|
+
ret = check_composite_mapping(ch, window)
|
|
817
|
+
$log.debug " composite returned (#{ret}) for #{ch} "
|
|
818
|
+
if !ret
|
|
819
|
+
return execute_mapping(blk, ch, object) if blk
|
|
820
|
+
end
|
|
821
|
+
return execute_mapping(ret, ch, object) if ret
|
|
822
|
+
return :UNHANDLED
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
def execute_mapping blk, keycode, object
|
|
826
|
+
|
|
827
|
+
if blk.is_a? Symbol
|
|
828
|
+
if respond_to? blk
|
|
829
|
+
$log.debug " RESPONDING TO BLCK #{keycode}"
|
|
830
|
+
return send(blk, *@_key_args[keycode])
|
|
831
|
+
else
|
|
832
|
+
## 2013-03-05 - 19:50 why the hell is there an alert here, nowhere else
|
|
833
|
+
alert "This ( #{self.class} ) does not respond to #{blk.to_s} [PROCESS-KEY]"
|
|
834
|
+
# added 2013-03-05 - 19:50 so called can know
|
|
835
|
+
return :UNHANDLED
|
|
836
|
+
end
|
|
837
|
+
else
|
|
838
|
+
$log.debug "rwidget BLOCK called _process_key #{keycode} " if $log.debug?
|
|
839
|
+
return blk.call object, *@_key_args[keycode]
|
|
840
|
+
end
|
|
841
|
+
end
|
|
842
|
+
|
|
843
|
+
# e.g. process_key ch, self
|
|
844
|
+
# returns UNHANDLED if no block for it
|
|
845
|
+
# after form handles basic keys, it gives unhandled key to current field, if current field returns
|
|
846
|
+
# unhandled, then it checks this map.
|
|
847
|
+
# added 2009-01-06 19:13 since widgets need to handle keys properly
|
|
848
|
+
# added 2009-01-18 12:58 returns ret val of blk.call
|
|
849
|
+
# so that if block does not handle, the key can still be handled
|
|
850
|
+
# e.g. table last row, last col does not handle, so it will auto go to next field
|
|
851
|
+
# 2010-02-24 13:45 handles 2 key combinations, copied from Form, must be identical in logic
|
|
852
|
+
# except maybe for window pointer. TODO not tested
|
|
853
|
+
def ORIG_process_key keycode, object, window
|
|
854
|
+
return :UNHANDLED if @_key_map.nil?
|
|
855
|
+
blk = @_key_map[keycode]
|
|
856
|
+
$log.debug "XXX: _process key keycode #{keycode} #{blk.class}, #{self.class} "
|
|
857
|
+
return :UNHANDLED if blk.nil?
|
|
858
|
+
if blk.is_a? OrderedHash
|
|
859
|
+
#Ncurses::nodelay(window.get_window, bf = false)
|
|
860
|
+
# if you set nodelay in ncurses.rb then this will not
|
|
861
|
+
# wait for second key press, so you then must either make it blocking
|
|
862
|
+
# here, or set a wtimeout here.
|
|
863
|
+
#
|
|
864
|
+
# This is since i have removed timeout globally since resize was happeing
|
|
865
|
+
# after a keypress. maybe we can revert to timeout and not worry about resize so much
|
|
866
|
+
Ncurses::wtimeout(window.get_window, 500) # will wait a second on wgetch so we can get gg and qq
|
|
867
|
+
ch = window.getch
|
|
868
|
+
# we should not reset here, resetting should happen in getch itself so it is consistent
|
|
869
|
+
#Ncurses::nowtimeout(window.get_window, true)
|
|
870
|
+
|
|
871
|
+
$log.debug " process_key: got #{keycode} , #{ch} "
|
|
872
|
+
# next line ignores function keys etc. C-x F1, thus commented 255 2012-01-11
|
|
873
|
+
if ch < 0 #|| ch > 255
|
|
874
|
+
return nil
|
|
875
|
+
end
|
|
876
|
+
#yn = ch.chr
|
|
877
|
+
blk1 = blk[ch]
|
|
878
|
+
# FIXME we are only returning the second key, what if form
|
|
879
|
+
# has mapped first and second combo. We should unget keycode and ch. 2011-12-23
|
|
880
|
+
# check this out first.
|
|
881
|
+
window.ungetch(ch) if blk1.nil? # trying 2011-09-27
|
|
882
|
+
return :UNHANDLED if blk1.nil? # changed nil to unhandled 2011-09-27
|
|
883
|
+
$log.debug " process_key: found block for #{keycode} , #{ch} "
|
|
884
|
+
blk = blk1
|
|
885
|
+
end
|
|
886
|
+
if blk.is_a? Symbol
|
|
887
|
+
if respond_to? blk
|
|
888
|
+
return send(blk, *@_key_args[keycode])
|
|
889
|
+
else
|
|
890
|
+
## 2013-03-05 - 19:50 why the hell is there an alert here, nowhere else
|
|
891
|
+
alert "This ( #{self.class} ) does not respond to #{blk.to_s} [PROCESS-KEY]"
|
|
892
|
+
# added 2013-03-05 - 19:50 so called can know
|
|
893
|
+
return :UNHANDLED
|
|
894
|
+
end
|
|
895
|
+
else
|
|
896
|
+
$log.debug "rwidget BLOCK called _process_key " if $log.debug?
|
|
897
|
+
return blk.call object, *@_key_args[keycode]
|
|
898
|
+
end
|
|
899
|
+
#0
|
|
900
|
+
end
|
|
901
|
+
# view a file or array of strings
|
|
902
|
+
def view what, config={}, &block # :yields: textview for further configuration
|
|
903
|
+
require 'canis/core/util/viewer'
|
|
904
|
+
Canis::Viewer.view what, config, &block
|
|
905
|
+
end
|
|
906
|
+
# create a logger giving a path.
|
|
907
|
+
def create_logger path
|
|
908
|
+
#path = File.join(ENV["LOGDIR"] || "./" ,"canis14.log")
|
|
909
|
+
file = File.open(path, File::WRONLY|File::TRUNC|File::CREAT)
|
|
910
|
+
logg = Logger.new(path)
|
|
911
|
+
raise "Could not create logger " unless logg
|
|
912
|
+
# if not set, will default to 0 which is debug. Other values are 1 - info, 2 - warn
|
|
913
|
+
logg.level = ENV["CANIS_LOG_LEVEL"].to_i
|
|
914
|
+
colors = Ncurses.COLORS
|
|
915
|
+
logg.info "START #{colors} colors -- #{$0} win: #{@window} : log level: #{logg.level}. To change log level, increase CANIS_LOG_LEVEL in your environment to 1 or 2 or 3."
|
|
916
|
+
return logg
|
|
917
|
+
end
|
|
918
|
+
end # module }}}
|
|
919
|
+
|
|
920
|
+
module EventHandler # {{{
|
|
921
|
+
# widgets may register their events prior to calling super
|
|
922
|
+
# 2014-04-17 - 20:54 Earlier they were writing directly to a data structure after +super+.
|
|
923
|
+
#
|
|
924
|
+
def register_events eves
|
|
925
|
+
@_events ||= []
|
|
926
|
+
case eves
|
|
927
|
+
when Array
|
|
928
|
+
@_events.push(*eves)
|
|
929
|
+
when Symbol
|
|
930
|
+
@_events << eves
|
|
931
|
+
else
|
|
932
|
+
raise ArgumentError "register_events: Don't know how to handle #{eves.class}"
|
|
933
|
+
end
|
|
934
|
+
end
|
|
935
|
+
##
|
|
936
|
+
# bind an event to a block, optional args will also be passed when calling
|
|
937
|
+
def bind event, *xargs, &blk
|
|
938
|
+
#$log.debug "#{self} called EventHandler BIND #{event}, args:#{xargs} "
|
|
939
|
+
if @_events
|
|
940
|
+
$log.warn "bind: #{self.class} does not support this event: #{event}. #{@_events} " if !event? event
|
|
941
|
+
#raise ArgumentError, "#{self.class} does not support this event: #{event}. #{@_events} " if !event? event
|
|
942
|
+
else
|
|
943
|
+
# it can come here if bind in initial block, since widgets add to @_event after calling super
|
|
944
|
+
# maybe we can change that.
|
|
945
|
+
$log.warn "BIND #{self.class} (#{event}) XXXXX no events defined in @_events. Please do so to avoid bugs and debugging. This will become a fatal error soon."
|
|
946
|
+
end
|
|
947
|
+
@handler ||= {}
|
|
948
|
+
@event_args ||= {}
|
|
949
|
+
@handler[event] ||= []
|
|
950
|
+
@handler[event] << blk
|
|
951
|
+
@event_args[event] ||= []
|
|
952
|
+
@event_args[event] << xargs
|
|
953
|
+
end
|
|
954
|
+
alias :add_binding :bind # temporary, needs a proper name to point out that we are adding
|
|
955
|
+
|
|
956
|
+
# NOTE: Do we have a way of removing bindings
|
|
957
|
+
# # TODO check if event is valid. Classes need to define what valid event names are
|
|
958
|
+
|
|
959
|
+
##
|
|
960
|
+
# Fire all bindings for given event
|
|
961
|
+
# e.g. fire_handler :ENTER, self
|
|
962
|
+
# The first parameter passed to the calling block is either self, or some action event
|
|
963
|
+
# The second and beyond are any objects you passed when using `bind` or `command`.
|
|
964
|
+
# Exceptions are caught here itself, or else they prevent objects from updating, usually the error is
|
|
965
|
+
# in the block sent in by application, not our error.
|
|
966
|
+
# TODO: if an object throws a subclass of VetoException we should not catch it and throw it back for
|
|
967
|
+
# caller to catch and take care of, such as prevent LEAVE or update etc.
|
|
968
|
+
def fire_handler event, object
|
|
969
|
+
$log.debug "inside def fire_handler evt:#{event}, o: #{object.class}"
|
|
970
|
+
if !@handler.nil?
|
|
971
|
+
if @_events
|
|
972
|
+
raise ArgumentError, "fire_handler: #{self.class} does not support this event: #{event}. #{@_events} " if !event? event
|
|
973
|
+
else
|
|
974
|
+
$log.debug "bIND #{self.class} XXXXX TEMPO no events defined in @_events "
|
|
975
|
+
end
|
|
976
|
+
ablk = @handler[event]
|
|
977
|
+
if !ablk.nil?
|
|
978
|
+
aeve = @event_args[event]
|
|
979
|
+
ablk.each_with_index do |blk, ix|
|
|
980
|
+
#$log.debug "#{self} called EventHandler firehander #{@name}, #{event}, obj: #{object},args: #{aeve[ix]}"
|
|
981
|
+
$log.debug "#{self} called EventHandler firehander #{@name}, #{event}"
|
|
982
|
+
begin
|
|
983
|
+
blk.call object, *aeve[ix]
|
|
984
|
+
rescue FieldValidationException => fve
|
|
985
|
+
# added 2011-09-26 1.3.0 so a user raised exception on LEAVE
|
|
986
|
+
# keeps cursor in same field.
|
|
987
|
+
raise fve
|
|
988
|
+
rescue PropertyVetoException => pve
|
|
989
|
+
# added 2011-09-26 1.3.0 so a user raised exception on LEAVE
|
|
990
|
+
# keeps cursor in same field.
|
|
991
|
+
raise pve
|
|
992
|
+
rescue => ex
|
|
993
|
+
## some don't have name
|
|
994
|
+
#$log.error "======= Error ERROR in block event #{self}: #{name}, #{event}"
|
|
995
|
+
$log.error "======= Error ERROR in block event #{self}: #{event}"
|
|
996
|
+
$log.error ex
|
|
997
|
+
$log.error(ex.backtrace.join("\n"))
|
|
998
|
+
#$error_message = "#{ex}" # changed 2010
|
|
999
|
+
$error_message.value = "#{ex.to_s}"
|
|
1000
|
+
Ncurses.beep
|
|
1001
|
+
end
|
|
1002
|
+
end
|
|
1003
|
+
else
|
|
1004
|
+
# there is no block for this key/event
|
|
1005
|
+
# we must behave exactly as processkey
|
|
1006
|
+
# NOTE this is too risky since then buttons and radio buttons
|
|
1007
|
+
# that don't have any command don;t update,so removing 2011-12-2
|
|
1008
|
+
#return :UNHANDLED
|
|
1009
|
+
return :NO_BLOCK
|
|
1010
|
+
end # if
|
|
1011
|
+
else
|
|
1012
|
+
# there is no handler
|
|
1013
|
+
# I've done this since list traps ENTER but rarely uses it.
|
|
1014
|
+
# For buttons default, we'd like to trap ENTER even when focus is elsewhere
|
|
1015
|
+
# we must behave exactly as processkey
|
|
1016
|
+
# NOTE this is too risky since then buttons and radio buttons
|
|
1017
|
+
# that don't have any command don;t update,so removing 2011-12-2
|
|
1018
|
+
#return :UNHANDLED
|
|
1019
|
+
# If caller wants, can return UNHANDLED such as list and ENTER.
|
|
1020
|
+
return :NO_BLOCK
|
|
1021
|
+
end # if
|
|
1022
|
+
end
|
|
1023
|
+
## added on 2009-01-08 00:33
|
|
1024
|
+
# goes with dsl_property
|
|
1025
|
+
# Need to inform listeners - done 2010-02-25 23:09
|
|
1026
|
+
# Can throw a FieldValidationException or PropertyVetoException
|
|
1027
|
+
def fire_property_change text, oldvalue, newvalue
|
|
1028
|
+
return if oldvalue.nil? || @_object_created.nil? # added 2010-09-16 so if called by methods it is still effective
|
|
1029
|
+
#$log.debug " FPC #{self}: #{text} #{oldvalue}, #{newvalue}"
|
|
1030
|
+
$log.debug " FPC #{self}: #{text} "
|
|
1031
|
+
if @pce.nil?
|
|
1032
|
+
@pce = PropertyChangeEvent.new(self, text, oldvalue, newvalue)
|
|
1033
|
+
else
|
|
1034
|
+
@pce.set( self, text, oldvalue, newvalue)
|
|
1035
|
+
end
|
|
1036
|
+
fire_handler :PROPERTY_CHANGE, @pce
|
|
1037
|
+
@repaint_required = true # this was a hack and shoudl go, someone wanted to set this so it would repaint (viewport line 99 fire_prop
|
|
1038
|
+
repaint_all(true) # for repainting borders, headers etc 2011-09-28 V1.3.1
|
|
1039
|
+
end
|
|
1040
|
+
|
|
1041
|
+
# returns boolean depending on whether this widget has registered the given event
|
|
1042
|
+
def event? eve
|
|
1043
|
+
@_events.include? eve
|
|
1044
|
+
end
|
|
1045
|
+
|
|
1046
|
+
# returns event list for this widget
|
|
1047
|
+
def event_list
|
|
1048
|
+
@_events
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
end # module eventh }}}
|
|
1052
|
+
|
|
1053
|
+
module ConfigSetup # {{{
|
|
1054
|
+
# private
|
|
1055
|
+
# options passed in the constructor call the relevant methods declared in dsl_accessor or dsl_property
|
|
1056
|
+
def variable_set var, val
|
|
1057
|
+
send("#{var}", val) #rescue send("#{var}=", val)
|
|
1058
|
+
end
|
|
1059
|
+
def config_setup aconfig
|
|
1060
|
+
@config = aconfig
|
|
1061
|
+
# this creates a problem in 1.9.2 since variable_set sets @config 2010-08-22 19:05 RK
|
|
1062
|
+
#@config.each_pair { |k,v| variable_set(k,v) }
|
|
1063
|
+
keys = @config.keys
|
|
1064
|
+
keys.each do |e|
|
|
1065
|
+
variable_set(e, @config[e])
|
|
1066
|
+
end
|
|
1067
|
+
end
|
|
1068
|
+
end # module config }}}
|
|
1069
|
+
|
|
1070
|
+
##
|
|
1071
|
+
# Basic widget class superclass. Anything embedded in a form should
|
|
1072
|
+
# extend this, if it wants to be repainted or wants focus. Otherwise.
|
|
1073
|
+
# form will be unaware of it.
|
|
1074
|
+
|
|
1075
|
+
|
|
1076
|
+
class Widget # {{{
|
|
1077
|
+
require 'canis/core/include/action' # added 2012-01-3 for add_action
|
|
1078
|
+
include EventHandler
|
|
1079
|
+
include ConfigSetup
|
|
1080
|
+
include Canis::Utils
|
|
1081
|
+
include Io # added 2010-03-06 13:05
|
|
1082
|
+
# common interface for text related to a field, label, textview, button etc
|
|
1083
|
+
dsl_property :text
|
|
1084
|
+
dsl_property :width, :height
|
|
1085
|
+
|
|
1086
|
+
# foreground and background colors when focussed. Currently used with buttons and field
|
|
1087
|
+
# Form checks and repaints on entry if these are set.
|
|
1088
|
+
dsl_property :highlight_color, :highlight_bgcolor # FIXME use color_pair
|
|
1089
|
+
|
|
1090
|
+
# FIXME is enabled used? is menu using it
|
|
1091
|
+
#dsl_accessor :focusable, :enabled # boolean
|
|
1092
|
+
# This means someone can change label to focusable ! there should be someting like CAN_TAKE_FOCUS
|
|
1093
|
+
dsl_property :row, :col # location of object
|
|
1094
|
+
#dsl_property :color, :bgcolor # normal foreground and background
|
|
1095
|
+
dsl_writer :color, :bgcolor # normal foreground and background
|
|
1096
|
+
# moved to a method which calculates color 2011-11-12
|
|
1097
|
+
#dsl_property :color_pair # instead of colors give just color_pair
|
|
1098
|
+
dsl_property :attr # attribute bold, normal, reverse
|
|
1099
|
+
dsl_accessor :name # name to refr to or recall object by_name
|
|
1100
|
+
attr_accessor :id #, :zorder
|
|
1101
|
+
attr_accessor :curpos # cursor position inside object - column, not row.
|
|
1102
|
+
attr_reader :config # can be used for popping user objects too
|
|
1103
|
+
attr_accessor :form # made accessor 2008-11-27 22:32 so menu can set
|
|
1104
|
+
attr_accessor :state # normal, selected, highlighted
|
|
1105
|
+
attr_reader :row_offset, :col_offset # where should the cursor be placed to start with
|
|
1106
|
+
dsl_property :visible # boolean # 2008-12-09 11:29
|
|
1107
|
+
#attr_accessor :modified # boolean, value modified or not (moved from field 2009-01-18 00:14 )
|
|
1108
|
+
dsl_accessor :help_text # added 2009-01-22 17:41 can be used for status/tooltips
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
attr_accessor :_object_created # 2010-09-16 12:12 to prevent needless property change firing when object being set
|
|
1112
|
+
|
|
1113
|
+
attr_accessor :parent_component # added 2010-01-12 23:28 BUFFERED - to bubble up
|
|
1114
|
+
|
|
1115
|
+
# sometimes inside a container there's no way of knowing if an individual comp is in focus
|
|
1116
|
+
# other than to explicitly set it and inquire . 2010-09-02 14:47 @since 1.1.5
|
|
1117
|
+
# NOTE state takes care of this and is set by form. boolean
|
|
1118
|
+
attr_accessor :focussed # is this widget in focus, so they may paint differently
|
|
1119
|
+
|
|
1120
|
+
# height percent and width percent used in stacks and flows.
|
|
1121
|
+
dsl_accessor :height_pc, :width_pc # tryin out in stacks and flows 2011-11-23
|
|
1122
|
+
|
|
1123
|
+
# descriptions for each key set in _key_map
|
|
1124
|
+
attr_reader :key_label
|
|
1125
|
+
attr_reader :handler # event handler
|
|
1126
|
+
|
|
1127
|
+
def initialize aform, aconfig={}, &block
|
|
1128
|
+
# I am trying to avoid passing the nil when you don't want to give a form.
|
|
1129
|
+
# I hope this does not create new issues 2011-11-20
|
|
1130
|
+
if aform.is_a? Hash
|
|
1131
|
+
# presumable there's nothing coming in in hash, or else we will have to merge
|
|
1132
|
+
aconfig = aform
|
|
1133
|
+
@form = nil
|
|
1134
|
+
else
|
|
1135
|
+
#raise "got a #{aform.class} "
|
|
1136
|
+
@form = aform
|
|
1137
|
+
end
|
|
1138
|
+
@row_offset ||= 0
|
|
1139
|
+
@col_offset ||= 0
|
|
1140
|
+
#@ext_row_offset = @ext_col_offset = 0 # 2010-02-07 20:18 # removed on 2011-09-29
|
|
1141
|
+
@state = :NORMAL
|
|
1142
|
+
|
|
1143
|
+
@handler = nil # we can avoid firing if nil
|
|
1144
|
+
#@event_args = {} # 2014-04-22 - 18:47 declared in bind_key
|
|
1145
|
+
# These are standard events for most widgets which will be fired by
|
|
1146
|
+
# Form. In the case of CHANGED, form fires if it's editable property is set, so
|
|
1147
|
+
# it does not apply to all widgets.
|
|
1148
|
+
register_events( [:ENTER, :LEAVE, :CHANGED, :PROPERTY_CHANGE])
|
|
1149
|
+
|
|
1150
|
+
config_setup aconfig # @config.each_pair { |k,v| variable_set(k,v) }
|
|
1151
|
+
#instance_eval &block if block_given?
|
|
1152
|
+
if block_given?
|
|
1153
|
+
if block.arity > 0
|
|
1154
|
+
yield self
|
|
1155
|
+
else
|
|
1156
|
+
self.instance_eval(&block)
|
|
1157
|
+
end
|
|
1158
|
+
end
|
|
1159
|
+
# 2010-09-20 13:12 moved down, so it does not create problems with other who want to set their
|
|
1160
|
+
# own default
|
|
1161
|
+
#@bgcolor ||= "black" # 0
|
|
1162
|
+
#@color ||= "white" # $datacolor
|
|
1163
|
+
set_form(@form) if @form
|
|
1164
|
+
end
|
|
1165
|
+
def init_vars
|
|
1166
|
+
# just in case anyone does a super. Not putting anything here
|
|
1167
|
+
# since i don't want anyone accidentally overriding
|
|
1168
|
+
end
|
|
1169
|
+
# this is supposed to be a duplicate of what dsl_property generates for cases when
|
|
1170
|
+
# we need to customise the get portion but not copy the set part. just call this.
|
|
1171
|
+
def property_set sym, val
|
|
1172
|
+
oldvalue = instance_variable_get "@#{sym}"
|
|
1173
|
+
tmp = val.size == 1 ? val[0] : val
|
|
1174
|
+
newvalue = tmp
|
|
1175
|
+
if oldvalue.nil? || @_object_created.nil?
|
|
1176
|
+
#@#{sym} = tmp
|
|
1177
|
+
instance_variable_set "@#{sym}", tmp
|
|
1178
|
+
end
|
|
1179
|
+
return(self) if oldvalue.nil? || @_object_created.nil?
|
|
1180
|
+
|
|
1181
|
+
if oldvalue != newvalue
|
|
1182
|
+
# trying to reduce calls to fire, when object is being created
|
|
1183
|
+
begin
|
|
1184
|
+
@property_changed = true
|
|
1185
|
+
fire_property_change("#{sym}", oldvalue, newvalue) if !oldvalue.nil?
|
|
1186
|
+
#@#{sym} = tmp
|
|
1187
|
+
instance_variable_set "@#{sym}", tmp
|
|
1188
|
+
#@config["#{sym}"]=@#{sym}
|
|
1189
|
+
rescue PropertyVetoException
|
|
1190
|
+
$log.warn "PropertyVetoException for #{sym}:" + oldvalue.to_s + "-> "+ newvalue.to_s
|
|
1191
|
+
end
|
|
1192
|
+
end # if old
|
|
1193
|
+
self
|
|
1194
|
+
end
|
|
1195
|
+
# returns widgets color, or if not set then app default
|
|
1196
|
+
# Ideally would have returned form's color, but it seems that form does not have color any longer.
|
|
1197
|
+
def color( *val )
|
|
1198
|
+
if val.empty?
|
|
1199
|
+
return @color if @color
|
|
1200
|
+
return @form.color if @form
|
|
1201
|
+
return $def_fg_color
|
|
1202
|
+
else
|
|
1203
|
+
@color_pair = nil
|
|
1204
|
+
return property_set :color, val
|
|
1205
|
+
end
|
|
1206
|
+
end
|
|
1207
|
+
# returns widgets bgcolor, or form's color. This ensures that all widgets use form's color
|
|
1208
|
+
# unless user has overriden the color.
|
|
1209
|
+
# This is to be used whenever a widget is rendering to check the color at this moment.
|
|
1210
|
+
def bgcolor( *val )
|
|
1211
|
+
if val.empty?
|
|
1212
|
+
return @bgcolor if @bgcolor
|
|
1213
|
+
return @form.bgcolor if @form
|
|
1214
|
+
return $def_bg_color
|
|
1215
|
+
else
|
|
1216
|
+
@color_pair = nil
|
|
1217
|
+
return property_set :bgcolor, val
|
|
1218
|
+
end
|
|
1219
|
+
end
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
# modified
|
|
1223
|
+
##
|
|
1224
|
+
# typically read will be overridden to check if value changed from what it was on enter.
|
|
1225
|
+
# getter and setter for modified (added 2009-01-18 12:31 )
|
|
1226
|
+
def modified?
|
|
1227
|
+
@modified
|
|
1228
|
+
end
|
|
1229
|
+
def set_modified tf=true
|
|
1230
|
+
@modified = tf
|
|
1231
|
+
@form.modified = true if tf
|
|
1232
|
+
end
|
|
1233
|
+
alias :modified :set_modified
|
|
1234
|
+
|
|
1235
|
+
## got left out by mistake 2008-11-26 20:20
|
|
1236
|
+
def on_enter
|
|
1237
|
+
@state = :HIGHLIGHTED # duplicating since often these are inside containers
|
|
1238
|
+
@focussed = true
|
|
1239
|
+
if @handler && @handler.has_key?(:ENTER)
|
|
1240
|
+
fire_handler :ENTER, self
|
|
1241
|
+
end
|
|
1242
|
+
end
|
|
1243
|
+
## got left out by mistake 2008-11-26 20:20
|
|
1244
|
+
def on_leave
|
|
1245
|
+
@state = :NORMAL # duplicating since often these are inside containers
|
|
1246
|
+
@focussed = false
|
|
1247
|
+
if @handler && @handler.has_key?(:LEAVE)
|
|
1248
|
+
fire_handler :LEAVE, self
|
|
1249
|
+
end
|
|
1250
|
+
end
|
|
1251
|
+
##
|
|
1252
|
+
# @return row and col of a widget where painting data actually starts
|
|
1253
|
+
# row and col is where a widget starts. offsets usually take into account borders.
|
|
1254
|
+
# the offsets typically are where the cursor should be positioned inside, upon on_enter.
|
|
1255
|
+
def rowcol
|
|
1256
|
+
# $log.debug "widgte rowcol : #{@row+@row_offset}, #{@col+@col_offset}"
|
|
1257
|
+
return @row+@row_offset, @col+@col_offset
|
|
1258
|
+
end
|
|
1259
|
+
## return the value of the widget.
|
|
1260
|
+
# In cases where selection is possible, should return selected value/s
|
|
1261
|
+
def getvalue
|
|
1262
|
+
#@text_variable && @text_variable.value || @text
|
|
1263
|
+
@text
|
|
1264
|
+
end
|
|
1265
|
+
##
|
|
1266
|
+
# Am making a separate method since often value for print differs from actual value
|
|
1267
|
+
def getvalue_for_paint
|
|
1268
|
+
getvalue
|
|
1269
|
+
end
|
|
1270
|
+
##
|
|
1271
|
+
# default repaint method. Called by form for all widgets.
|
|
1272
|
+
# widget does not have display_length.
|
|
1273
|
+
def repaint
|
|
1274
|
+
r,c = rowcol
|
|
1275
|
+
#@bgcolor ||= $def_bg_color # moved down 2011-11-5
|
|
1276
|
+
#@color ||= $def_fg_color
|
|
1277
|
+
_bgcolor = bgcolor()
|
|
1278
|
+
_color = color()
|
|
1279
|
+
$log.debug("widget repaint : r:#{r} c:#{c} col:#{_color}" )
|
|
1280
|
+
value = getvalue_for_paint
|
|
1281
|
+
len = @width || value.length
|
|
1282
|
+
acolor = @color_pair || get_color($datacolor, _color, _bgcolor)
|
|
1283
|
+
@graphic.printstring r, c, "%-*s" % [len, value], acolor, attr()
|
|
1284
|
+
# next line should be in same color but only have @att so we can change att is nec
|
|
1285
|
+
#@form.window.mvchgat(y=r, x=c, max=len, Ncurses::A_NORMAL, @bgcolor, nil)
|
|
1286
|
+
end
|
|
1287
|
+
|
|
1288
|
+
def destroy
|
|
1289
|
+
$log.debug "DESTROY : widget #{@name} "
|
|
1290
|
+
panel = @window.panel
|
|
1291
|
+
Ncurses::Panel.del_panel(panel.pointer) if !panel.nil?
|
|
1292
|
+
@window.delwin if !@window.nil?
|
|
1293
|
+
end
|
|
1294
|
+
# in those cases where we create widget without a form, and later give it to
|
|
1295
|
+
# some other program which sets the form. Dirty, we should perhaps create widgets
|
|
1296
|
+
# without forms, and add explicitly.
|
|
1297
|
+
def set_form form
|
|
1298
|
+
raise "Form is nil in set_form" if form.nil?
|
|
1299
|
+
@form = form
|
|
1300
|
+
@id = form.add_widget(self) if !form.nil? and form.respond_to? :add_widget
|
|
1301
|
+
# 2009-10-29 15:04 use form.window, unless buffer created
|
|
1302
|
+
# should not use form.window so explicitly everywhere.
|
|
1303
|
+
# added 2009-12-27 20:05 BUFFERED in case child object needs a form.
|
|
1304
|
+
# We don;t wish to overwrite the graphic object
|
|
1305
|
+
if @graphic.nil?
|
|
1306
|
+
#$log.debug " setting graphic to form window for #{self.class}, #{form} "
|
|
1307
|
+
@graphic = form.window unless form.nil? # use screen for writing, not buffer
|
|
1308
|
+
end
|
|
1309
|
+
# execute those actions delayed due to absence of form -- used internally
|
|
1310
|
+
# mostly by buttons and labels to bind hotkey to form
|
|
1311
|
+
fire_handler(:FORM_ATTACHED, self) if event? :FORM_ATTACHED
|
|
1312
|
+
end
|
|
1313
|
+
|
|
1314
|
+
# puts cursor on correct row.
|
|
1315
|
+
def set_form_row
|
|
1316
|
+
# @form.row = @row + 1 + @winrow
|
|
1317
|
+
#@form.row = @row + 1
|
|
1318
|
+
r, c = rowcol
|
|
1319
|
+
#$log.warn " empty set_form_row in widget #{self} r = #{r} , c = #{c} "
|
|
1320
|
+
#raise "trying to set 0, maybe called repaint before container has set value" if row <= 0
|
|
1321
|
+
setrowcol row, nil
|
|
1322
|
+
end
|
|
1323
|
+
# set cursor on correct column, widget
|
|
1324
|
+
# Ideally, this should be overriden, as it is not likely to be correct.
|
|
1325
|
+
# NOTE: this is okay for some widgets but NOT for containers
|
|
1326
|
+
# that will call their own components SFR and SFC
|
|
1327
|
+
def set_form_col col1=@curpos
|
|
1328
|
+
@curpos = col1 || 0 # 2010-01-14 21:02
|
|
1329
|
+
#@form.col = @col + @col_offset + @curpos
|
|
1330
|
+
c = @col + @col_offset + @curpos
|
|
1331
|
+
#$log.warn " #{@name} empty set_form_col #{c}, curpos #{@curpos} , #{@col} + #{@col_offset} #{@form} "
|
|
1332
|
+
setrowcol nil, c
|
|
1333
|
+
end
|
|
1334
|
+
def hide
|
|
1335
|
+
@visible = false
|
|
1336
|
+
end
|
|
1337
|
+
def show
|
|
1338
|
+
@visible = true
|
|
1339
|
+
end
|
|
1340
|
+
def remove
|
|
1341
|
+
@form.remove_widget(self)
|
|
1342
|
+
end
|
|
1343
|
+
# is this required can we remove
|
|
1344
|
+
def move row, col
|
|
1345
|
+
@row = row
|
|
1346
|
+
@col = col
|
|
1347
|
+
end
|
|
1348
|
+
##
|
|
1349
|
+
# moves focus to this field
|
|
1350
|
+
# we must look into running on_leave of previous field
|
|
1351
|
+
def focus
|
|
1352
|
+
return if !@focusable
|
|
1353
|
+
if @form.validate_field != -1
|
|
1354
|
+
@form.select_field @id
|
|
1355
|
+
end
|
|
1356
|
+
end
|
|
1357
|
+
# set or unset focusable (boolean). Whether a widget can get keyboard focus.
|
|
1358
|
+
def focusable(*val)
|
|
1359
|
+
return @focusable if val.empty?
|
|
1360
|
+
oldv = @focusable
|
|
1361
|
+
@focusable = val[0]
|
|
1362
|
+
|
|
1363
|
+
return self if oldv.nil? || @_object_created.nil?
|
|
1364
|
+
# once the form has been painted then any changes will trigger update of focusables.
|
|
1365
|
+
@form.update_focusables if @form
|
|
1366
|
+
# actually i should only set the forms focusable_modified flag rather than call this. FIXME
|
|
1367
|
+
self
|
|
1368
|
+
end
|
|
1369
|
+
|
|
1370
|
+
# is this widget accessible from keyboard or not.
|
|
1371
|
+
def focusable?
|
|
1372
|
+
@focusable
|
|
1373
|
+
end
|
|
1374
|
+
##
|
|
1375
|
+
# remove a binding that you don't want
|
|
1376
|
+
def unbind_key keycode
|
|
1377
|
+
@_key_args.delete keycode unless @_key_args.nil?
|
|
1378
|
+
@_key_map.delete keycode unless @_key_map.nil?
|
|
1379
|
+
end
|
|
1380
|
+
|
|
1381
|
+
# e.g. process_key ch, self
|
|
1382
|
+
# returns UNHANDLED if no block for it
|
|
1383
|
+
# after form handles basic keys, it gives unhandled key to current field, if current field returns
|
|
1384
|
+
# unhandled, then it checks this map.
|
|
1385
|
+
def process_key keycode, object
|
|
1386
|
+
return _process_key keycode, object, @graphic
|
|
1387
|
+
end
|
|
1388
|
+
##
|
|
1389
|
+
# to be added at end of handle_key of widgets so instlalled actions can be checked
|
|
1390
|
+
def handle_key(ch)
|
|
1391
|
+
ret = process_key ch, self
|
|
1392
|
+
return :UNHANDLED if ret == :UNHANDLED
|
|
1393
|
+
0
|
|
1394
|
+
end
|
|
1395
|
+
|
|
1396
|
+
|
|
1397
|
+
|
|
1398
|
+
# to give simple access to other components, (eg, parent) to tell a comp to either
|
|
1399
|
+
# paint its data, or to paint all - borders, headers, footers due to a big change (ht/width)
|
|
1400
|
+
def repaint_required(tf=true)
|
|
1401
|
+
@repaint_required = tf
|
|
1402
|
+
end
|
|
1403
|
+
def repaint_all(tf=true)
|
|
1404
|
+
@repaint_all = tf
|
|
1405
|
+
@repaint_required = tf
|
|
1406
|
+
end
|
|
1407
|
+
|
|
1408
|
+
##
|
|
1409
|
+
# When an enclosing component creates a pad (buffer) and the child component
|
|
1410
|
+
#+ should write onto the same pad, then the enclosing component should override
|
|
1411
|
+
#+ the default graphic of child. This applies mainly to editor components in
|
|
1412
|
+
#+ listboxes and tables.
|
|
1413
|
+
# @param graphic graphic object to use for writing contents
|
|
1414
|
+
# @see prepare_editor in rlistbox.
|
|
1415
|
+
# added 2010-01-05 15:25
|
|
1416
|
+
def override_graphic gr
|
|
1417
|
+
@graphic = gr
|
|
1418
|
+
end
|
|
1419
|
+
|
|
1420
|
+
## passing a cursor up and adding col and row offsets
|
|
1421
|
+
## Added 2010-01-13 13:27 I am checking this out.
|
|
1422
|
+
## I would rather pass the value down and store it than do this recursive call
|
|
1423
|
+
##+ for each cursor display
|
|
1424
|
+
# @see Form#setrowcol
|
|
1425
|
+
def setformrowcol r, c
|
|
1426
|
+
@form.row = r unless r.nil?
|
|
1427
|
+
@form.col = c unless c.nil?
|
|
1428
|
+
# this is stupid, going through this route i was losing windows top and left
|
|
1429
|
+
# And this could get repeated if there are mult objects.
|
|
1430
|
+
if !@parent_component.nil? and @parent_component != self
|
|
1431
|
+
r+= @parent_component.form.window.top unless r.nil?
|
|
1432
|
+
c+= @parent_component.form.window.left unless c.nil?
|
|
1433
|
+
$log.debug " (#{@name}) calling parents setformrowcol #{r}, #{c} pa: #{@parent_component.name} self: #{name}, #{self.class}, poff #{@parent_component.row_offset}, #{@parent_component.col_offset}, top:#{@form.window.left} left:#{@form.window.left} "
|
|
1434
|
+
@parent_component.setformrowcol r, c
|
|
1435
|
+
else
|
|
1436
|
+
# no more parents, now set form
|
|
1437
|
+
$log.debug " name NO MORE parents setting #{r}, #{c} in #{@form} "
|
|
1438
|
+
@form.setrowcol r, c
|
|
1439
|
+
end
|
|
1440
|
+
end
|
|
1441
|
+
## widget: i am putting one extra level of indirection so i can switch here
|
|
1442
|
+
# between form#setrowcol and setformrowcol, since i am not convinced either
|
|
1443
|
+
# are giving the accurate result. i am not sure what the issue is.
|
|
1444
|
+
def setrowcol r, c
|
|
1445
|
+
# 2010-02-07 21:32 is this where i should add ext_offsets
|
|
1446
|
+
#$log.debug " #{@name} w.setrowcol #{r} + #{@ext_row_offset}, #{c} + #{@ext_col_offset} "
|
|
1447
|
+
# commented off 2010-02-15 18:22
|
|
1448
|
+
#r += @ext_row_offset unless r.nil?
|
|
1449
|
+
#c += @ext_col_offset unless c.nil?
|
|
1450
|
+
if @form
|
|
1451
|
+
@form.setrowcol r, c
|
|
1452
|
+
#elsif @parent_component
|
|
1453
|
+
else
|
|
1454
|
+
raise "Parent component not defined for #{self}, #{self.class} " unless @parent_component
|
|
1455
|
+
@parent_component.setrowcol r, c
|
|
1456
|
+
end
|
|
1457
|
+
#setformrowcol r,c
|
|
1458
|
+
end
|
|
1459
|
+
|
|
1460
|
+
# returns array of events defined for this object
|
|
1461
|
+
# @deprecated, should be in eventhandler
|
|
1462
|
+
#def event_list
|
|
1463
|
+
#return @_events if defined? @_events
|
|
1464
|
+
#nil
|
|
1465
|
+
#end
|
|
1466
|
+
|
|
1467
|
+
# 2011-11-12 trying to make color setting a bit sane
|
|
1468
|
+
# You may set as a color_pair using get_color which gives a fixnum
|
|
1469
|
+
# or you may give 2 color symbols so i can update color, bgcolor and colorpair in one shot
|
|
1470
|
+
# if one of them is nil, i just use the existing value
|
|
1471
|
+
def color_pair(*val)
|
|
1472
|
+
if val.empty?
|
|
1473
|
+
#return @color_pair
|
|
1474
|
+
return @color_pair || get_color($datacolor, color(), bgcolor())
|
|
1475
|
+
end
|
|
1476
|
+
|
|
1477
|
+
oldvalue = @color_pair
|
|
1478
|
+
case val.size
|
|
1479
|
+
when 1
|
|
1480
|
+
raise ArgumentError, "Expecting fixnum for color_pair." unless val[0].is_a? Fixnum
|
|
1481
|
+
@color_pair = val[0]
|
|
1482
|
+
@color, @bgcolor = ColorMap.get_colors_for_pair @color_pair
|
|
1483
|
+
when 2
|
|
1484
|
+
@color = val.first if val.first
|
|
1485
|
+
@bgcolor = val.last if val.last
|
|
1486
|
+
@color_pair = get_color $datacolor, @color, @bgcolor
|
|
1487
|
+
end
|
|
1488
|
+
if oldvalue != @color_pair
|
|
1489
|
+
fire_property_change(:color_pair, oldvalue, @color_pair)
|
|
1490
|
+
@property_changed = true
|
|
1491
|
+
repaint_all true
|
|
1492
|
+
end
|
|
1493
|
+
self
|
|
1494
|
+
end
|
|
1495
|
+
# a general method for all widgets to override with their favorite or most meaninful event
|
|
1496
|
+
# Ideally this is where the block in the constructor should land up.
|
|
1497
|
+
# @since 1.5.0 2011-11-21
|
|
1498
|
+
def command *args, &block
|
|
1499
|
+
if event? :PRESS
|
|
1500
|
+
bind :PRESS, *args, &block
|
|
1501
|
+
else
|
|
1502
|
+
bind :CHANGED, *args, &block
|
|
1503
|
+
end
|
|
1504
|
+
end
|
|
1505
|
+
# return an object of actionmanager class, creating if required
|
|
1506
|
+
# Widgets and apps may add_action and show_menu using the same
|
|
1507
|
+
def action_manager
|
|
1508
|
+
require 'canis/core/include/actionmanager'
|
|
1509
|
+
@action_manager ||= ActionManager.new
|
|
1510
|
+
end
|
|
1511
|
+
#
|
|
1512
|
+
## ADD HERE WIDGET
|
|
1513
|
+
end # }}}
|
|
1514
|
+
|
|
1515
|
+
##
|
|
1516
|
+
#
|
|
1517
|
+
# TODO: we don't have an event for when form is entered and exited.
|
|
1518
|
+
class Form # {{{
|
|
1519
|
+
include EventHandler
|
|
1520
|
+
include Canis::Utils
|
|
1521
|
+
|
|
1522
|
+
# array of widgets
|
|
1523
|
+
attr_reader :widgets
|
|
1524
|
+
|
|
1525
|
+
# related window used for printing
|
|
1526
|
+
attr_accessor :window
|
|
1527
|
+
|
|
1528
|
+
# cursor row and col
|
|
1529
|
+
attr_accessor :row, :col
|
|
1530
|
+
# color and bgcolor for all widget, widgets that don't have color specified will inherit from form
|
|
1531
|
+
# If not mentioned, then global defaults will be taken
|
|
1532
|
+
attr_writer :color, :bgcolor
|
|
1533
|
+
attr_accessor :attr
|
|
1534
|
+
|
|
1535
|
+
# has the form been modified
|
|
1536
|
+
attr_accessor :modified
|
|
1537
|
+
|
|
1538
|
+
# index of active widget
|
|
1539
|
+
attr_accessor :active_index
|
|
1540
|
+
|
|
1541
|
+
# hash containing widgets by name for retrieval
|
|
1542
|
+
# Useful if one widget refers to second before second created.
|
|
1543
|
+
# lb = @form.by_name["listb"]
|
|
1544
|
+
attr_reader :by_name
|
|
1545
|
+
|
|
1546
|
+
# associated menubar
|
|
1547
|
+
attr_reader :menu_bar
|
|
1548
|
+
|
|
1549
|
+
# this influences whether navigation will return to first component after last or not
|
|
1550
|
+
# Default is :CYCLICAL which cycles between first and last. In some cases, where a form
|
|
1551
|
+
# or container exists inside a form with buttons or tabs, you may not want cyclical traversal.
|
|
1552
|
+
attr_accessor :navigation_policy # :CYCLICAL will cycle around. Needed to move to other tabs
|
|
1553
|
+
|
|
1554
|
+
# name given to form for debugging
|
|
1555
|
+
attr_accessor :name
|
|
1556
|
+
|
|
1557
|
+
# signify that the layout manager must calculate each widgets dimensions again since
|
|
1558
|
+
# typically the window has been resized.
|
|
1559
|
+
attr_accessor :resize_required
|
|
1560
|
+
# class that lays out objects (calculates row, col, width and height)
|
|
1561
|
+
attr_accessor :layout_manager
|
|
1562
|
+
|
|
1563
|
+
def initialize win, &block
|
|
1564
|
+
@window = win
|
|
1565
|
+
# added 2014-05-01 - 20:43 so that a window can update its form, during overlapping forms.
|
|
1566
|
+
@window.form = self if win
|
|
1567
|
+
@widgets = []
|
|
1568
|
+
@by_name = {}
|
|
1569
|
+
@active_index = -1
|
|
1570
|
+
@row = @col = -1
|
|
1571
|
+
@modified = false
|
|
1572
|
+
@resize_required = true
|
|
1573
|
+
@focusable = true
|
|
1574
|
+
# when widgets are added, add them here if focusable so traversal is easier. However,
|
|
1575
|
+
# if user changes this during the app, we need to update this somehow. FIXME
|
|
1576
|
+
@focusables = [] # added 2014-04-24 - 12:28 to make traversal easier
|
|
1577
|
+
@navigation_policy ||= :CYCLICAL
|
|
1578
|
+
# 2014-04-24 - 17:42 NO MORE ENTER LEAVE at FORM LEVEL
|
|
1579
|
+
#register_events([:ENTER, :LEAVE, :RESIZE])
|
|
1580
|
+
register_events(:RESIZE)
|
|
1581
|
+
instance_eval &block if block_given?
|
|
1582
|
+
@_firsttime = true; # added on 2010-01-02 19:21 to prevent scrolling crash !
|
|
1583
|
+
@name ||= ""
|
|
1584
|
+
|
|
1585
|
+
# related to emacs kill ring concept for copy-paste
|
|
1586
|
+
|
|
1587
|
+
$kill_ring ||= [] # 2010-03-09 22:42 so textarea and others can copy and paste emacs EMACS
|
|
1588
|
+
$kill_ring_pointer = 0 # needs to be incremented with each append, moved with yank-pop
|
|
1589
|
+
$append_next_kill = false
|
|
1590
|
+
$kill_last_pop_size = 0 # size of last pop which has to be cleared
|
|
1591
|
+
|
|
1592
|
+
$last_key = 0 # last key pressed @since 1.1.5 (not used yet)
|
|
1593
|
+
$current_key = 0 # curr key pressed @since 1.1.5 (so some containers can behave based on whether
|
|
1594
|
+
# user tabbed in, or backtabbed in (rmultisplit)
|
|
1595
|
+
|
|
1596
|
+
# for storing error message
|
|
1597
|
+
$error_message ||= Variable.new ""
|
|
1598
|
+
|
|
1599
|
+
# what kind of key-bindings do you want, :vim or :emacs
|
|
1600
|
+
$key_map_type ||= :vim ## :emacs or :vim, keys to be defined accordingly. TODO
|
|
1601
|
+
|
|
1602
|
+
bind_key(KEY_F1, 'help') { hm = help_manager(); hm.display_help }
|
|
1603
|
+
end
|
|
1604
|
+
##
|
|
1605
|
+
# set this menubar as the form's menu bar.
|
|
1606
|
+
# also bind the toggle_key for popping up.
|
|
1607
|
+
# Should this not be at application level ?
|
|
1608
|
+
def set_menu_bar mb
|
|
1609
|
+
@menu_bar = mb
|
|
1610
|
+
add_widget mb
|
|
1611
|
+
mb.toggle_key ||= Ncurses.KEY_F2
|
|
1612
|
+
if !mb.toggle_key.nil?
|
|
1613
|
+
ch = mb.toggle_key
|
|
1614
|
+
bind_key(ch, 'Menu Bar') do |_form|
|
|
1615
|
+
if !@menu_bar.nil?
|
|
1616
|
+
@menu_bar.toggle
|
|
1617
|
+
@menu_bar.handle_keys
|
|
1618
|
+
end
|
|
1619
|
+
end
|
|
1620
|
+
end
|
|
1621
|
+
end
|
|
1622
|
+
##
|
|
1623
|
+
# Add given widget to widget list and returns an incremental id.
|
|
1624
|
+
# Adding to widgets, results in it being painted, and focussed.
|
|
1625
|
+
# removing a widget and adding can give the same ID's, however at this point we are not
|
|
1626
|
+
# really using ID. But need to use an incremental int in future. (internal use)
|
|
1627
|
+
def add_widget widget
|
|
1628
|
+
# this help to access widget by a name
|
|
1629
|
+
if widget.respond_to? :name and !widget.name.nil?
|
|
1630
|
+
@by_name[widget.name] = widget
|
|
1631
|
+
end
|
|
1632
|
+
|
|
1633
|
+
@widgets << widget
|
|
1634
|
+
@focusable_modified = true
|
|
1635
|
+
|
|
1636
|
+
return @widgets.length-1
|
|
1637
|
+
end
|
|
1638
|
+
alias :add :add_widget
|
|
1639
|
+
|
|
1640
|
+
# remove a widget
|
|
1641
|
+
# (internal use)
|
|
1642
|
+
def remove_widget widget
|
|
1643
|
+
if widget.respond_to? :name and !widget.name.nil?
|
|
1644
|
+
@by_name.delete(widget.name)
|
|
1645
|
+
end
|
|
1646
|
+
@focusable_modified = true
|
|
1647
|
+
@widgets.delete widget
|
|
1648
|
+
end
|
|
1649
|
+
|
|
1650
|
+
# sets a flag that focusables should be updated
|
|
1651
|
+
# called whenever a widgets changes its focusable property
|
|
1652
|
+
def update_focusables
|
|
1653
|
+
$log.debug "XXX: inside update focusables"
|
|
1654
|
+
@focusable_modified = true
|
|
1655
|
+
end
|
|
1656
|
+
|
|
1657
|
+
private
|
|
1658
|
+
# does the actual job of updating the focusables array
|
|
1659
|
+
def _update_focusables #:nodoc:
|
|
1660
|
+
@focusable_modified = false
|
|
1661
|
+
@focusables = @widgets.select { |w| w.focusable? }
|
|
1662
|
+
end
|
|
1663
|
+
|
|
1664
|
+
public
|
|
1665
|
+
|
|
1666
|
+
# form repaint,calls repaint on each widget which will repaint it only if it has been modified since last call.
|
|
1667
|
+
# called after each keypress.
|
|
1668
|
+
def repaint
|
|
1669
|
+
$log.debug " form repaint:#{self}, #{@name} , r #{@row} c #{@col} " if $log.debug?
|
|
1670
|
+
if @resize_required && @layout_manager
|
|
1671
|
+
@layout_manager.form = self unless @layout_manager.form
|
|
1672
|
+
@layout_manager.do_layout
|
|
1673
|
+
@resize_required = false
|
|
1674
|
+
end
|
|
1675
|
+
@widgets.each do |f|
|
|
1676
|
+
next if f.visible == false # added 2008-12-09 12:17
|
|
1677
|
+
#$log.debug "XXX: FORM CALLING REPAINT OF WIDGET #{f} IN LOOP"
|
|
1678
|
+
#raise "Row or col nil #{f.row} #{f.col} for #{f}, #{f.name} " if f.row.nil? || f.col.nil?
|
|
1679
|
+
f.repaint
|
|
1680
|
+
f._object_created = true # added 2010-09-16 13:02 now prop handlers can be fired
|
|
1681
|
+
end
|
|
1682
|
+
|
|
1683
|
+
_update_focusables if @focusable_modified
|
|
1684
|
+
# this can bomb if someone sets row. We need a better way!
|
|
1685
|
+
if @row == -1 and @_firsttime == true
|
|
1686
|
+
|
|
1687
|
+
select_first_field
|
|
1688
|
+
@_firsttime = false
|
|
1689
|
+
end
|
|
1690
|
+
setpos
|
|
1691
|
+
# XXX this creates a problem if window is a pad
|
|
1692
|
+
# although this does show cursor movement etc.
|
|
1693
|
+
### @window.wrefresh
|
|
1694
|
+
#if @window.window_type == :WINDOW
|
|
1695
|
+
#$log.debug " formrepaint #{@name} calling window.wrefresh #{@window} "
|
|
1696
|
+
@window.wrefresh
|
|
1697
|
+
Ncurses::Panel.update_panels ## added 2010-11-05 00:30 to see if clears the stdscr problems
|
|
1698
|
+
#else
|
|
1699
|
+
#$log.warn " XXX formrepaint #{@name} no refresh called 2011-09-19 #{@window} "
|
|
1700
|
+
#end
|
|
1701
|
+
end
|
|
1702
|
+
##
|
|
1703
|
+
# move cursor to where the fields row and col are
|
|
1704
|
+
# private
|
|
1705
|
+
def setpos r=@row, c=@col
|
|
1706
|
+
#$log.debug "setpos : (#{self.name}) #{r} #{c} XXX"
|
|
1707
|
+
## adding just in case things are going out of bounds of a parent and no cursor to be shown
|
|
1708
|
+
return if r.nil? or c.nil? # added 2009-12-29 23:28 BUFFERED
|
|
1709
|
+
return if r<0 or c<0 # added 2010-01-02 18:49 stack too deep coming if goes above screen
|
|
1710
|
+
@window.wmove r,c
|
|
1711
|
+
end
|
|
1712
|
+
# @return [Widget, nil] current field, nil if no focusable field
|
|
1713
|
+
def get_current_field
|
|
1714
|
+
select_next_field if @active_index == -1
|
|
1715
|
+
return nil if @active_index.nil? # for forms that have no focusable field 2009-01-08 12:22
|
|
1716
|
+
@widgets[@active_index]
|
|
1717
|
+
end
|
|
1718
|
+
alias :current_widget :get_current_field
|
|
1719
|
+
# take focus to first focussable field
|
|
1720
|
+
# we shoud not send to select_next. have a separate method to avoid bugs.
|
|
1721
|
+
# but check current_field, in case called from anotehr field TODO FIXME
|
|
1722
|
+
def select_first_field
|
|
1723
|
+
# this results in on_leave of last field being executed when form starts.
|
|
1724
|
+
#@active_index = -1 # FIXME HACK
|
|
1725
|
+
#select_next_field
|
|
1726
|
+
ix = @focusables.first
|
|
1727
|
+
return unless ix # no focussable field
|
|
1728
|
+
|
|
1729
|
+
# if the user is on a field other than current then fire on_leave
|
|
1730
|
+
if @active_index.nil? || @active_index < 0
|
|
1731
|
+
elsif @active_index != ix
|
|
1732
|
+
f = @widgets[@active_index]
|
|
1733
|
+
begin
|
|
1734
|
+
#$log.debug " select first field, calling on_leave of #{f} #{@active_index} "
|
|
1735
|
+
on_leave f
|
|
1736
|
+
rescue => err
|
|
1737
|
+
$log.error " Caught EXCEPTION select_first_field on_leave #{err}"
|
|
1738
|
+
Ncurses.beep
|
|
1739
|
+
#$error_message = "#{err}"
|
|
1740
|
+
$error_message.value = "#{err}"
|
|
1741
|
+
return
|
|
1742
|
+
end
|
|
1743
|
+
end
|
|
1744
|
+
select_field ix
|
|
1745
|
+
end
|
|
1746
|
+
|
|
1747
|
+
# take focus to last field on form
|
|
1748
|
+
def select_last_field
|
|
1749
|
+
@active_index = nil
|
|
1750
|
+
select_prev_field
|
|
1751
|
+
end
|
|
1752
|
+
|
|
1753
|
+
|
|
1754
|
+
## do not override
|
|
1755
|
+
# form's trigger, fired when any widget loses focus
|
|
1756
|
+
# This wont get called in editor components in tables, since they are formless
|
|
1757
|
+
def on_leave f
|
|
1758
|
+
return if f.nil? || !f.focusable # added focusable, else label was firing
|
|
1759
|
+
f.state = :NORMAL
|
|
1760
|
+
# on leaving update text_variable if defined. Should happen on modified only
|
|
1761
|
+
# should this not be f.text_var ... f.buffer ? 2008-11-25 18:58
|
|
1762
|
+
#f.text_variable.value = f.buffer if !f.text_variable.nil? # 2008-12-20 23:36
|
|
1763
|
+
f.on_leave if f.respond_to? :on_leave
|
|
1764
|
+
# 2014-04-24 - 17:42 NO MORE ENTER LEAVE at FORM LEVEL
|
|
1765
|
+
#fire_handler :LEAVE, f
|
|
1766
|
+
## to test XXX in combo boxes the box may not be editable by be modified by selection.
|
|
1767
|
+
if f.respond_to? :editable and f.modified?
|
|
1768
|
+
$log.debug " Form about to fire CHANGED for #{f} "
|
|
1769
|
+
f.fire_handler(:CHANGED, f)
|
|
1770
|
+
end
|
|
1771
|
+
end
|
|
1772
|
+
# form calls on_enter of each object.
|
|
1773
|
+
# However, if a multicomponent calls on_enter of a widget, this code will
|
|
1774
|
+
# not be triggered. The highlighted part
|
|
1775
|
+
def on_enter f
|
|
1776
|
+
return if f.nil? || !f.focusable # added focusable, else label was firing 2010-09
|
|
1777
|
+
|
|
1778
|
+
f.state = :HIGHLIGHTED
|
|
1779
|
+
# If the widget has a color defined for focussed, set repaint
|
|
1780
|
+
# otherwise it will not be repainted unless user edits !
|
|
1781
|
+
if f.highlight_bgcolor || f.highlight_color
|
|
1782
|
+
f.repaint_required true
|
|
1783
|
+
end
|
|
1784
|
+
|
|
1785
|
+
f.modified false
|
|
1786
|
+
#f.set_modified false
|
|
1787
|
+
f.on_enter if f.respond_to? :on_enter
|
|
1788
|
+
# 2014-04-24 - 17:42 NO MORE ENTER LEAVE at FORM LEVEL
|
|
1789
|
+
#fire_handler :ENTER, f
|
|
1790
|
+
end
|
|
1791
|
+
|
|
1792
|
+
##
|
|
1793
|
+
# puts focus on the given field/widget index
|
|
1794
|
+
# @param index of field in @widgets (or can be a Widget too)
|
|
1795
|
+
# XXX if called externally will not run a on_leave of previous field
|
|
1796
|
+
def select_field ix0
|
|
1797
|
+
if ix0.is_a? Widget
|
|
1798
|
+
ix0 = @widgets.index(ix0)
|
|
1799
|
+
end
|
|
1800
|
+
return if @widgets.nil? or @widgets.empty?
|
|
1801
|
+
#$log.debug "inside select_field : #{ix0} ai #{@active_index}"
|
|
1802
|
+
f = @widgets[ix0]
|
|
1803
|
+
return if !f.focusable?
|
|
1804
|
+
if f.focusable?
|
|
1805
|
+
@active_index = ix0
|
|
1806
|
+
@row, @col = f.rowcol
|
|
1807
|
+
#$log.debug " WMOVE insdie sele nxt field : ROW #{@row} COL #{@col} "
|
|
1808
|
+
on_enter f
|
|
1809
|
+
@window.wmove @row, @col # added RK FFI 2011-09-7 = setpos
|
|
1810
|
+
|
|
1811
|
+
f.set_form_row # added 2011-10-5 so when embedded in another form it can get the cursor
|
|
1812
|
+
f.set_form_col # this can wreak havoc in containers, unless overridden
|
|
1813
|
+
|
|
1814
|
+
# next line in field changes cursor position after setting form_col
|
|
1815
|
+
# resulting in a bug. 2011-11-25
|
|
1816
|
+
# maybe it is in containers or tabbed panes and multi-containers
|
|
1817
|
+
# where previous objects col is still shown. we cannot do this after
|
|
1818
|
+
# setformcol
|
|
1819
|
+
#f.curpos = 0 # why was this, okay is it because of prev obj's cursor ?
|
|
1820
|
+
repaint
|
|
1821
|
+
@window.refresh
|
|
1822
|
+
else
|
|
1823
|
+
$log.debug "inside select field ENABLED FALSE : act #{@active_index} ix0 #{ix0}"
|
|
1824
|
+
end
|
|
1825
|
+
end
|
|
1826
|
+
##
|
|
1827
|
+
# run validate_field on a field, usually whatevers current
|
|
1828
|
+
# before transferring control
|
|
1829
|
+
# We should try to automate this so developer does not have to remember to call it.
|
|
1830
|
+
# # @param field object
|
|
1831
|
+
# @return [0, -1] for success or failure
|
|
1832
|
+
# NOTE : catches exception and sets $error_message, check if -1
|
|
1833
|
+
def validate_field f=@widgets[@active_index]
|
|
1834
|
+
begin
|
|
1835
|
+
on_leave f
|
|
1836
|
+
rescue => err
|
|
1837
|
+
$log.error "form: validate_field caught EXCEPTION #{err}"
|
|
1838
|
+
$log.error(err.backtrace.join("\n"))
|
|
1839
|
+
# $error_message = "#{err}" # changed 2010
|
|
1840
|
+
$error_message.value = "#{err}"
|
|
1841
|
+
Ncurses.beep
|
|
1842
|
+
return -1
|
|
1843
|
+
end
|
|
1844
|
+
return 0
|
|
1845
|
+
end
|
|
1846
|
+
# put focus on next field
|
|
1847
|
+
# will cycle by default, unless navigation policy not :CYCLICAL
|
|
1848
|
+
# in which case returns :NO_NEXT_FIELD.
|
|
1849
|
+
# FIXME: in the beginning it comes in as -1 and does an on_leave of last field
|
|
1850
|
+
def select_next_field
|
|
1851
|
+
return :UNHANDLED if @widgets.nil? || @widgets.empty?
|
|
1852
|
+
#$log.debug "insdie sele nxt field : #{@active_index} WL:#{@widgets.length}"
|
|
1853
|
+
if @active_index.nil? || @active_index == -1 # needs to be tested out A LOT
|
|
1854
|
+
# what is this silly hack for still here 2014-04-24 - 13:04 DELETE FIXME
|
|
1855
|
+
@active_index = -1
|
|
1856
|
+
else
|
|
1857
|
+
f = @widgets[@active_index]
|
|
1858
|
+
begin
|
|
1859
|
+
on_leave f
|
|
1860
|
+
rescue FieldValidationException => err # added 2011-10-2 v1.3.1 so we can rollback
|
|
1861
|
+
$log.error "select_next_field: caught EXCEPTION #{err}"
|
|
1862
|
+
$error_message.value = "#{err}"
|
|
1863
|
+
raise err
|
|
1864
|
+
rescue => err
|
|
1865
|
+
$log.error "select_next_field: caught EXCEPTION #{err}"
|
|
1866
|
+
$log.error(err.backtrace.join("\n"))
|
|
1867
|
+
# $error_message = "#{err}" # changed 2010
|
|
1868
|
+
$error_message.value = "#{err}"
|
|
1869
|
+
Ncurses.beep
|
|
1870
|
+
return 0
|
|
1871
|
+
end
|
|
1872
|
+
end
|
|
1873
|
+
f = @widgets[@active_index]
|
|
1874
|
+
index = @focusables.index(f)
|
|
1875
|
+
index += 1
|
|
1876
|
+
f = @focusables[index]
|
|
1877
|
+
if f
|
|
1878
|
+
select_field f
|
|
1879
|
+
return 0
|
|
1880
|
+
end
|
|
1881
|
+
#
|
|
1882
|
+
#$log.debug "insdie sele nxt field FAILED: #{@active_index} WL:#{@widgets.length}"
|
|
1883
|
+
## added on 2008-12-14 18:27 so we can skip to another form/tab
|
|
1884
|
+
if @navigation_policy == :CYCLICAL
|
|
1885
|
+
f = @focusables.first
|
|
1886
|
+
if f
|
|
1887
|
+
select_field f
|
|
1888
|
+
return 0
|
|
1889
|
+
end
|
|
1890
|
+
end
|
|
1891
|
+
$log.debug "inside sele nxt field : NO NEXT #{@active_index} WL:#{@widgets.length}"
|
|
1892
|
+
return :NO_NEXT_FIELD
|
|
1893
|
+
end
|
|
1894
|
+
##
|
|
1895
|
+
# put focus on previous field
|
|
1896
|
+
# will cycle by default, unless navigation policy not :CYCLICAL
|
|
1897
|
+
# in which case returns :NO_PREV_FIELD.
|
|
1898
|
+
# @return [nil, :NO_PREV_FIELD] nil if cyclical and it finds a field
|
|
1899
|
+
# if not cyclical, and no more fields then :NO_PREV_FIELD
|
|
1900
|
+
def select_prev_field
|
|
1901
|
+
return :UNHANDLED if @widgets.nil? or @widgets.empty?
|
|
1902
|
+
#$log.debug "insdie sele prev field : #{@active_index} WL:#{@widgets.length}"
|
|
1903
|
+
if @active_index.nil?
|
|
1904
|
+
@active_index = @widgets.length
|
|
1905
|
+
else
|
|
1906
|
+
f = @widgets[@active_index]
|
|
1907
|
+
begin
|
|
1908
|
+
on_leave f
|
|
1909
|
+
rescue => err
|
|
1910
|
+
$log.error " Caught EXCEPTION #{err}"
|
|
1911
|
+
Ncurses.beep
|
|
1912
|
+
# $error_message = "#{err}" # changed 2010
|
|
1913
|
+
$error_message.value = "#{err}"
|
|
1914
|
+
return
|
|
1915
|
+
end
|
|
1916
|
+
end
|
|
1917
|
+
|
|
1918
|
+
f = @widgets[@active_index]
|
|
1919
|
+
index = @focusables.index(f)
|
|
1920
|
+
if index > 0
|
|
1921
|
+
index -= 1
|
|
1922
|
+
f = @focusables[index]
|
|
1923
|
+
if f
|
|
1924
|
+
select_field f
|
|
1925
|
+
return
|
|
1926
|
+
end
|
|
1927
|
+
end
|
|
1928
|
+
|
|
1929
|
+
## added on 2008-12-14 18:27 so we can skip to another form/tab
|
|
1930
|
+
# 2009-01-08 12:24 no recursion, can be stack overflows if no focusable field
|
|
1931
|
+
if @navigation_policy == :CYCLICAL
|
|
1932
|
+
f = @focusables.last
|
|
1933
|
+
select_field @widgets.index(f) if f
|
|
1934
|
+
end
|
|
1935
|
+
|
|
1936
|
+
return :NO_PREV_FIELD
|
|
1937
|
+
end
|
|
1938
|
+
##
|
|
1939
|
+
# move cursor by num columns. Form
|
|
1940
|
+
def addcol num
|
|
1941
|
+
return if @col.nil? || @col == -1
|
|
1942
|
+
@col += num
|
|
1943
|
+
@window.wmove @row, @col
|
|
1944
|
+
## 2010-01-30 23:45 exchange calling parent with calling this forms setrow
|
|
1945
|
+
# since in tabbedpane with table i am not gietting this forms offset.
|
|
1946
|
+
setrowcol nil, col
|
|
1947
|
+
end
|
|
1948
|
+
##
|
|
1949
|
+
# move cursor by given rows and columns, can be negative.
|
|
1950
|
+
# 2010-01-30 23:47 FIXME, if this is called we should call setrowcol like in addcol
|
|
1951
|
+
def addrowcol row,col
|
|
1952
|
+
return if @col.nil? or @col == -1 # contradicts comment on top
|
|
1953
|
+
return if @row.nil? or @row == -1
|
|
1954
|
+
@col += col
|
|
1955
|
+
@row += row
|
|
1956
|
+
@window.wmove @row, @col
|
|
1957
|
+
|
|
1958
|
+
end
|
|
1959
|
+
|
|
1960
|
+
## Form
|
|
1961
|
+
# New attempt at setting cursor using absolute coordinates
|
|
1962
|
+
# Also, trying NOT to go up. let this pad or window print cursor.
|
|
1963
|
+
def setrowcol r, c
|
|
1964
|
+
@row = r unless r.nil?
|
|
1965
|
+
@col = c unless c.nil?
|
|
1966
|
+
end
|
|
1967
|
+
##
|
|
1968
|
+
|
|
1969
|
+
# e.g. process_key ch, self
|
|
1970
|
+
# returns UNHANDLED if no block for it
|
|
1971
|
+
# after form handles basic keys, it gives unhandled key to current field, if current field returns
|
|
1972
|
+
# unhandled, then it checks this map.
|
|
1973
|
+
# Please update widget with any changes here. TODO: match regexes as in mapper
|
|
1974
|
+
|
|
1975
|
+
def process_key keycode, object
|
|
1976
|
+
return _process_key keycode, object, @window
|
|
1977
|
+
end
|
|
1978
|
+
|
|
1979
|
+
# Defines how user can give numeric args to a command even in edit mode
|
|
1980
|
+
# User either presses universal_argument (C-u) which generates a series of 4 16 64.
|
|
1981
|
+
# Or he presses C-u and then types some numbers. Followed by the action.
|
|
1982
|
+
# @returns [0, :UNHANDLED] :UNHANDLED implies that last keystroke is still to evaluated
|
|
1983
|
+
# by system. ) implies only numeric args were obtained. This method updates $multiplier
|
|
1984
|
+
|
|
1985
|
+
def universal_argument
|
|
1986
|
+
$multiplier = ( ($multiplier.nil? || $multiplier == 0) ? 4 : $multiplier *= 4)
|
|
1987
|
+
$log.debug " inside UNIV MULT0: #{$multiplier} "
|
|
1988
|
+
# See if user enters numerics. If so discard existing varaible and take only
|
|
1989
|
+
#+ entered values
|
|
1990
|
+
_m = 0
|
|
1991
|
+
while true
|
|
1992
|
+
ch = @window.getchar()
|
|
1993
|
+
case ch
|
|
1994
|
+
when -1
|
|
1995
|
+
next
|
|
1996
|
+
when ?0.getbyte(0)..?9.getbyte(0)
|
|
1997
|
+
_m *= 10 ; _m += (ch-48)
|
|
1998
|
+
$multiplier = _m
|
|
1999
|
+
$log.debug " inside UNIV MULT #{$multiplier} "
|
|
2000
|
+
when ?\C-u.getbyte(0)
|
|
2001
|
+
if _m == 0
|
|
2002
|
+
# user is incrementally hitting C-u
|
|
2003
|
+
$multiplier *= 4
|
|
2004
|
+
else
|
|
2005
|
+
# user is terminating some numbers so he can enter a numeric command next
|
|
2006
|
+
return 0
|
|
2007
|
+
end
|
|
2008
|
+
else
|
|
2009
|
+
$log.debug " inside UNIV MULT else got #{ch} "
|
|
2010
|
+
# here is some other key that is the function key to be repeated. we must honor this
|
|
2011
|
+
# and ensure it goes to the right widget
|
|
2012
|
+
return ch
|
|
2013
|
+
#return :UNHANDLED
|
|
2014
|
+
end
|
|
2015
|
+
end
|
|
2016
|
+
return 0
|
|
2017
|
+
end
|
|
2018
|
+
|
|
2019
|
+
def digit_argument ch
|
|
2020
|
+
$multiplier = ch - ?\M-0.getbyte(0)
|
|
2021
|
+
$log.debug " inside UNIV MULT 0 #{$multiplier} "
|
|
2022
|
+
# See if user enters numerics. If so discard existing varaible and take only
|
|
2023
|
+
#+ entered values
|
|
2024
|
+
_m = $multiplier
|
|
2025
|
+
while true
|
|
2026
|
+
ch = @window.getchar()
|
|
2027
|
+
case ch
|
|
2028
|
+
when -1
|
|
2029
|
+
next
|
|
2030
|
+
when ?0.getbyte(0)..?9.getbyte(0)
|
|
2031
|
+
_m *= 10 ; _m += (ch-48)
|
|
2032
|
+
$multiplier = _m
|
|
2033
|
+
$log.debug " inside UNIV MULT 1 #{$multiplier} "
|
|
2034
|
+
when ?\M-0.getbyte(0)..?\M-9.getbyte(0)
|
|
2035
|
+
_m *= 10 ; _m += (ch-?\M-0.getbyte(0))
|
|
2036
|
+
$multiplier = _m
|
|
2037
|
+
$log.debug " inside UNIV MULT 2 #{$multiplier} "
|
|
2038
|
+
else
|
|
2039
|
+
$log.debug " inside UNIV MULT else got #{ch} "
|
|
2040
|
+
# here is some other key that is the function key to be repeated. we must honor this
|
|
2041
|
+
# and ensure it goes to the right widget
|
|
2042
|
+
return ch
|
|
2043
|
+
#return :UNHANDLED
|
|
2044
|
+
end
|
|
2045
|
+
end
|
|
2046
|
+
return 0
|
|
2047
|
+
end
|
|
2048
|
+
#
|
|
2049
|
+
# These mappings will only trigger if the current field
|
|
2050
|
+
# does not use them.
|
|
2051
|
+
#
|
|
2052
|
+
def map_keys
|
|
2053
|
+
return if @keys_mapped
|
|
2054
|
+
bind_keys([?\M-?,?\?], 'show field help') {
|
|
2055
|
+
#if get_current_field.help_text
|
|
2056
|
+
#textdialog(get_current_field.help_text, 'title' => 'Help Text', :bgcolor => 'green', :color => :white)
|
|
2057
|
+
#else
|
|
2058
|
+
print_key_bindings
|
|
2059
|
+
#end
|
|
2060
|
+
}
|
|
2061
|
+
bind_key(FFI::NCurses::KEY_F9, "Print keys", :print_key_bindings) # show bindings, tentative on F9
|
|
2062
|
+
bind_key(?\M-:, 'show menu') {
|
|
2063
|
+
fld = get_current_field
|
|
2064
|
+
am = fld.action_manager()
|
|
2065
|
+
#fld.init_menu
|
|
2066
|
+
am.show_actions
|
|
2067
|
+
}
|
|
2068
|
+
@keys_mapped = true
|
|
2069
|
+
end
|
|
2070
|
+
|
|
2071
|
+
# this forces a repaint of all visible widgets and has been added for the case of overlapping
|
|
2072
|
+
# windows, since a black rectangle is often left when a window is destroyed. This is internally
|
|
2073
|
+
# triggered whenever a window is destroyed, and currently only for root window.
|
|
2074
|
+
def repaint_all_widgets
|
|
2075
|
+
#$log.debug " REPAINT ALL in FORM called "
|
|
2076
|
+
@widgets.each do |w|
|
|
2077
|
+
next if w.visible == false
|
|
2078
|
+
next if w.class.to_s == "Canis::MenuBar"
|
|
2079
|
+
#$log.debug " ---- REPAINT ALL #{w.name} "
|
|
2080
|
+
#w.repaint_required true
|
|
2081
|
+
w.repaint_all true
|
|
2082
|
+
w.repaint
|
|
2083
|
+
end
|
|
2084
|
+
#$log.debug " REPAINT ALL in FORM complete "
|
|
2085
|
+
# place cursor on current_widget
|
|
2086
|
+
setpos
|
|
2087
|
+
end
|
|
2088
|
+
|
|
2089
|
+
## forms handle keys
|
|
2090
|
+
# mainly traps tab and backtab to navigate between widgets.
|
|
2091
|
+
# I know some widgets will want to use tab, e.g edit boxes for entering a tab
|
|
2092
|
+
# or for completion.
|
|
2093
|
+
# @throws FieldValidationException
|
|
2094
|
+
# NOTE : please rescue exceptions when you use this in your main loop and alert() user
|
|
2095
|
+
#
|
|
2096
|
+
def handle_key(ch)
|
|
2097
|
+
map_keys unless @keys_mapped
|
|
2098
|
+
handled = :UNHANDLED # 2011-10-4
|
|
2099
|
+
if ch == ?\C-u.getbyte(0)
|
|
2100
|
+
ret = universal_argument
|
|
2101
|
+
$log.debug "C-u FORM set MULT to #{$multiplier}, ret = #{ret} "
|
|
2102
|
+
return 0 if ret == 0
|
|
2103
|
+
ch = ret # unhandled char
|
|
2104
|
+
elsif ch >= ?\M-1.getbyte(0) && ch <= ?\M-9.getbyte(0)
|
|
2105
|
+
if $catch_alt_digits # emacs EMACS
|
|
2106
|
+
ret = digit_argument ch
|
|
2107
|
+
$log.debug " FORM set MULT DA to #{$multiplier}, ret = #{ret} "
|
|
2108
|
+
return 0 if ret == 0 # don't see this happening
|
|
2109
|
+
ch = ret # unhandled char
|
|
2110
|
+
end
|
|
2111
|
+
end
|
|
2112
|
+
|
|
2113
|
+
$current_key = ch
|
|
2114
|
+
case ch
|
|
2115
|
+
when -1
|
|
2116
|
+
return
|
|
2117
|
+
when 1000
|
|
2118
|
+
$log.debug " form RESIZE HK #{ch} #{self}, #{@name} "
|
|
2119
|
+
repaint_all_widgets
|
|
2120
|
+
return
|
|
2121
|
+
#when Ncurses::KEY_RESIZE # SIGWINCH
|
|
2122
|
+
when FFI::NCurses::KEY_RESIZE # SIGWINCH # FFI
|
|
2123
|
+
lines = Ncurses.LINES
|
|
2124
|
+
cols = Ncurses.COLS
|
|
2125
|
+
x = Ncurses.stdscr.getmaxy
|
|
2126
|
+
y = Ncurses.stdscr.getmaxx
|
|
2127
|
+
$log.debug " form RESIZE HK #{ch} #{self}, #{@name}, #{ch}, x #{x} y #{y} lines #{lines} , cols: #{cols} "
|
|
2128
|
+
#alert "SIGWINCH WE NEED TO RECALC AND REPAINT resize #{lines}, #{cols}: #{x}, #{y} "
|
|
2129
|
+
|
|
2130
|
+
# next line may be causing flicker, can we do without.
|
|
2131
|
+
Ncurses.endwin
|
|
2132
|
+
@window.wrefresh
|
|
2133
|
+
@window.wclear
|
|
2134
|
+
if @layout_manager
|
|
2135
|
+
@layout_manager.do_layout
|
|
2136
|
+
# we need to redo statusline and others that layout ignores
|
|
2137
|
+
else
|
|
2138
|
+
@widgets.each { |e| e.repaint_all(true) } # trying out
|
|
2139
|
+
end
|
|
2140
|
+
## added RESIZE on 2012-01-5
|
|
2141
|
+
## stuff that relies on last line such as statusline dock etc will need to be redrawn.
|
|
2142
|
+
fire_handler :RESIZE, self
|
|
2143
|
+
else
|
|
2144
|
+
field = get_current_field
|
|
2145
|
+
if $log.debug?
|
|
2146
|
+
keycode = keycode_tos(ch)
|
|
2147
|
+
$log.debug " form HK #{ch} #{self}, #{@name}, #{keycode}, field: giving to: #{field}, #{field.name} " if field
|
|
2148
|
+
end
|
|
2149
|
+
handled = :UNHANDLED
|
|
2150
|
+
handled = field.handle_key ch unless field.nil? # no field focussable
|
|
2151
|
+
$log.debug "handled inside Form #{ch} from #{field} got #{handled} "
|
|
2152
|
+
# some widgets like textarea and list handle up and down
|
|
2153
|
+
if handled == :UNHANDLED or handled == -1 or field.nil?
|
|
2154
|
+
case ch
|
|
2155
|
+
when KEY_TAB, ?\M-\C-i.getbyte(0) # tab and M-tab in case widget eats tab (such as Table)
|
|
2156
|
+
ret = select_next_field
|
|
2157
|
+
return ret if ret == :NO_NEXT_FIELD
|
|
2158
|
+
# alt-shift-tab or backtab (in case Table eats backtab)
|
|
2159
|
+
when FFI::NCurses::KEY_BTAB, 481 ## backtab added 2008-12-14 18:41
|
|
2160
|
+
ret = select_prev_field
|
|
2161
|
+
return ret if ret == :NO_PREV_FIELD
|
|
2162
|
+
when FFI::NCurses::KEY_UP
|
|
2163
|
+
ret = select_prev_field
|
|
2164
|
+
return ret if ret == :NO_PREV_FIELD
|
|
2165
|
+
when FFI::NCurses::KEY_DOWN
|
|
2166
|
+
ret = select_next_field
|
|
2167
|
+
return ret if ret == :NO_NEXT_FIELD
|
|
2168
|
+
else
|
|
2169
|
+
#$log.debug " before calling process_key in form #{ch} " if $log.debug?
|
|
2170
|
+
ret = process_key ch, self
|
|
2171
|
+
# seems we need to flushinp in case composite has pushed key
|
|
2172
|
+
$log.debug "FORM process_key #{ch} got ret #{ret} in #{self}, flushing input "
|
|
2173
|
+
# 2014-06-01 - 17:01 added flush, maybe at some point we could do it only if unhandled
|
|
2174
|
+
# in case some method wishes to actually push some keys
|
|
2175
|
+
Ncurses.flushinp
|
|
2176
|
+
return :UNHANDLED if ret == :UNHANDLED
|
|
2177
|
+
end
|
|
2178
|
+
elsif handled == :NO_NEXT_FIELD || handled == :NO_PREV_FIELD # 2011-10-4
|
|
2179
|
+
return handled
|
|
2180
|
+
end
|
|
2181
|
+
end
|
|
2182
|
+
$log.debug " form before repaint #{self} , #{@name}, ret #{ret}"
|
|
2183
|
+
repaint
|
|
2184
|
+
$last_key = ch
|
|
2185
|
+
ret || 0 # 2011-10-17
|
|
2186
|
+
end
|
|
2187
|
+
|
|
2188
|
+
# 2010-02-07 14:50 to aid in debugging and comparing log files.
|
|
2189
|
+
def to_s; @name || self; end
|
|
2190
|
+
|
|
2191
|
+
#
|
|
2192
|
+
# returns in instance of help_manager with which one may install help_text and call help.
|
|
2193
|
+
# user apps will only supply help_text, form would already have mapped F1 to help.
|
|
2194
|
+
def help_manager
|
|
2195
|
+
require 'canis/core/util/helpmanager'
|
|
2196
|
+
@help_manager ||= Canis::HelpManager.new self
|
|
2197
|
+
end
|
|
2198
|
+
# returns forms color, or if not set then app default
|
|
2199
|
+
# This is used by widget's as the color to fallback on when no color is specified for them.
|
|
2200
|
+
# This way all widgets in a form can have one color.
|
|
2201
|
+
def color
|
|
2202
|
+
@color || $def_fg_color
|
|
2203
|
+
end
|
|
2204
|
+
# returns form's bgcolor, or global default.
|
|
2205
|
+
def bgcolor
|
|
2206
|
+
@bgcolor || $def_bg_color
|
|
2207
|
+
end
|
|
2208
|
+
|
|
2209
|
+
## ADD HERE FORM
|
|
2210
|
+
end # }}}
|
|
2211
|
+
|
|
2212
|
+
|
|
2213
|
+
## Created and sent to all listeners whenever a property is changed
|
|
2214
|
+
# @see fire_property_change
|
|
2215
|
+
# @see fire_handler
|
|
2216
|
+
# @since 1.0.5 added 2010-02-25 23:06
|
|
2217
|
+
class PropertyChangeEvent # {{{
|
|
2218
|
+
attr_accessor :source, :property_name, :oldvalue, :newvalue
|
|
2219
|
+
def initialize source, property_name, oldvalue, newvalue
|
|
2220
|
+
set source, property_name, oldvalue, newvalue
|
|
2221
|
+
end
|
|
2222
|
+
def set source, property_name, oldvalue, newvalue
|
|
2223
|
+
@source, @property_name, @oldvalue, @newvalue =
|
|
2224
|
+
source, property_name, oldvalue, newvalue
|
|
2225
|
+
end
|
|
2226
|
+
def to_s
|
|
2227
|
+
"PROPERTY_CHANGE name: #{property_name}, oldval: #{@oldvalue}, newvalue: #{@newvalue}, source: #{@source}"
|
|
2228
|
+
end
|
|
2229
|
+
def inspect
|
|
2230
|
+
to_s
|
|
2231
|
+
end
|
|
2232
|
+
end # }}}
|
|
2233
|
+
|
|
2234
|
+
##
|
|
2235
|
+
# Text edit field
|
|
2236
|
+
# NOTE: +width+ is the length of the display whereas +maxlen+ is the maximum size that the value
|
|
2237
|
+
# can take. Thus, +maxlen+ can exceed +width+. Currently, +maxlen+ defaults to +width+ which
|
|
2238
|
+
# defaults to 20.
|
|
2239
|
+
# NOTE: Use +text(val)+ to set value, and +text()+ to retrieve value
|
|
2240
|
+
# == Example
|
|
2241
|
+
# f = Field.new @form, text: "Some value", row: 10, col: 2
|
|
2242
|
+
#
|
|
2243
|
+
# Field introduces an event :CHANGE which is fired for each character deleted or inserted
|
|
2244
|
+
# TODO: some methods should return self, so chaining can be done. Not sure if the return value of the
|
|
2245
|
+
# fire_handler is being checked.
|
|
2246
|
+
# NOTE: i have just added repain_required check in Field before repaint
|
|
2247
|
+
# this may mean in some places field does not paint. repaint_require will have to be set
|
|
2248
|
+
# to true in those cases. this was since field was overriding a popup window that was not modal.
|
|
2249
|
+
#
|
|
2250
|
+
class Field < Widget # {{{
|
|
2251
|
+
dsl_accessor :maxlen # maximum length allowed into field
|
|
2252
|
+
attr_reader :buffer # actual buffer being used for storage
|
|
2253
|
+
#
|
|
2254
|
+
|
|
2255
|
+
dsl_accessor :values # validate against provided list, (+include?+)
|
|
2256
|
+
dsl_accessor :valid_regex # validate against regular expression (+match()+)
|
|
2257
|
+
dsl_accessor :valid_range # validate against numeric range, should respond to +include?+
|
|
2258
|
+
# for numeric fields, specify lower or upper limit of entered value
|
|
2259
|
+
attr_accessor :below, :above
|
|
2260
|
+
|
|
2261
|
+
#dsl_accessor :chars_allowed # regex, what characters to allow entry, will ignore all else
|
|
2262
|
+
# character to show, earlier called +show+ which clashed with Widget method +show+
|
|
2263
|
+
dsl_accessor :mask # what charactr to show for each char entered (password field)
|
|
2264
|
+
dsl_accessor :null_allowed # allow nulls, don't validate if null # added , boolean
|
|
2265
|
+
|
|
2266
|
+
# any new widget that has editable should have modified also
|
|
2267
|
+
dsl_accessor :editable # allow editing
|
|
2268
|
+
|
|
2269
|
+
# +type+ is just a convenience over +chars_allowed+ and sets some basic filters
|
|
2270
|
+
# @example: :integer, :float, :alpha, :alnum
|
|
2271
|
+
# NOTE: we do not store type, only chars_allowed, so this won't return any value
|
|
2272
|
+
#attr_reader :type # datatype of field, currently only sets chars_allowed
|
|
2273
|
+
|
|
2274
|
+
# this accesses the field created or passed with set_label
|
|
2275
|
+
#attr_reader :label
|
|
2276
|
+
# this is the class of the field set in +text()+, so value is returned in same class
|
|
2277
|
+
# @example : Fixnum, Integer, Float
|
|
2278
|
+
attr_accessor :datatype # crrently set during set_buffer
|
|
2279
|
+
attr_reader :original_value # value on entering field
|
|
2280
|
+
attr_accessor :overwrite_mode # true or false INSERT OVERWRITE MODE
|
|
2281
|
+
|
|
2282
|
+
# column on which field printed, usually the same as +col+ unless +label+ used.
|
|
2283
|
+
# Required by +History+ to popup field history.
|
|
2284
|
+
attr_reader :field_col # column on which field is printed
|
|
2285
|
+
# required due to labels. Is updated after printing
|
|
2286
|
+
# # so can be nil if accessed early 2011-12-8
|
|
2287
|
+
|
|
2288
|
+
def initialize form=nil, config={}, &block
|
|
2289
|
+
@form = form
|
|
2290
|
+
@buffer = String.new
|
|
2291
|
+
#@type=config.fetch("type", :varchar)
|
|
2292
|
+
@row = 0
|
|
2293
|
+
@col = 0
|
|
2294
|
+
@editable = true
|
|
2295
|
+
@focusable = true
|
|
2296
|
+
#@event_args = {} # arguments passed at time of binding, to use when firing event
|
|
2297
|
+
map_keys
|
|
2298
|
+
init_vars
|
|
2299
|
+
register_events(:CHANGE)
|
|
2300
|
+
super
|
|
2301
|
+
@width ||= 20
|
|
2302
|
+
@maxlen ||= @width
|
|
2303
|
+
end
|
|
2304
|
+
def init_vars
|
|
2305
|
+
@pcol = 0 # needed for horiz scrolling
|
|
2306
|
+
@curpos = 0 # current cursor position in buffer
|
|
2307
|
+
# this is the index where characters are put or deleted
|
|
2308
|
+
# # when user edits
|
|
2309
|
+
@modified = false
|
|
2310
|
+
@repaint_required = true
|
|
2311
|
+
end
|
|
2312
|
+
|
|
2313
|
+
# NOTE: earlier there was some confusion over type, chars_allowed and datatype
|
|
2314
|
+
# Now type and chars_allowed are merged into one.
|
|
2315
|
+
# If you pass a symbol such as :integer, :float or Float Fixnum then some
|
|
2316
|
+
# standard chars_allowed will be used. Otherwise you may pass a regexp.
|
|
2317
|
+
#
|
|
2318
|
+
# @param symbol :integer, :float, :alpha, :alnum, Float, Fixnum, Numeric, Regexp
|
|
2319
|
+
def type *val
|
|
2320
|
+
return @chars_allowed if val.empty?
|
|
2321
|
+
|
|
2322
|
+
dtype = val[0]
|
|
2323
|
+
#return self if @chars_allowed # disallow changing
|
|
2324
|
+
if dtype.is_a? Regexp
|
|
2325
|
+
@chars_allowed = dtype
|
|
2326
|
+
return self
|
|
2327
|
+
end
|
|
2328
|
+
dtype = dtype.to_s.downcase.to_sym if dtype.is_a? String
|
|
2329
|
+
case dtype # missing to_sym would have always failed due to to_s 2011-09-30 1.3.1
|
|
2330
|
+
when :integer, Fixnum, Integer
|
|
2331
|
+
@chars_allowed = /\d/
|
|
2332
|
+
when :numeric, :float, Numeric, Float
|
|
2333
|
+
@chars_allowed = /[\d\.]/
|
|
2334
|
+
when :alpha
|
|
2335
|
+
@chars_allowed = /[a-zA-Z]/
|
|
2336
|
+
when :alnum
|
|
2337
|
+
@chars_allowed = /[a-zA-Z0-9]/
|
|
2338
|
+
else
|
|
2339
|
+
raise ArgumentError, "Field type: invalid datatype specified. Use :integer, :numeric, :float, :alpha, :alnum "
|
|
2340
|
+
end
|
|
2341
|
+
self
|
|
2342
|
+
end
|
|
2343
|
+
alias :chars_allowed :type
|
|
2344
|
+
|
|
2345
|
+
#
|
|
2346
|
+
# add a char to field, and validate
|
|
2347
|
+
# NOTE: this should return self for chaining operations and throw an exception
|
|
2348
|
+
# if disabled or exceeding size
|
|
2349
|
+
# @param [char] a character to add
|
|
2350
|
+
# @return [Fixnum] 0 if okay, -1 if not editable or exceeding length
|
|
2351
|
+
def putch char
|
|
2352
|
+
return -1 if !@editable
|
|
2353
|
+
return -1 if !@overwrite_mode && (@buffer.length >= @maxlen)
|
|
2354
|
+
blen = @buffer.length
|
|
2355
|
+
if @chars_allowed != nil
|
|
2356
|
+
return if char.match(@chars_allowed).nil?
|
|
2357
|
+
end
|
|
2358
|
+
# added insert or overwrite mode 2010-03-17 20:11
|
|
2359
|
+
oldchar = nil
|
|
2360
|
+
if @overwrite_mode
|
|
2361
|
+
oldchar = @buffer[@curpos]
|
|
2362
|
+
@buffer[@curpos] = char
|
|
2363
|
+
else
|
|
2364
|
+
@buffer.insert(@curpos, char)
|
|
2365
|
+
end
|
|
2366
|
+
oldcurpos = @curpos
|
|
2367
|
+
#$log.warn "XXX: FIELD CURPOS #{@curpos} blen #{@buffer.length} " #if @curpos > blen
|
|
2368
|
+
@curpos += 1 if @curpos < @maxlen
|
|
2369
|
+
@modified = true
|
|
2370
|
+
#$log.debug " FIELD FIRING CHANGE: #{char} at new #{@curpos}: bl:#{@buffer.length} buff:[#{@buffer}]"
|
|
2371
|
+
# i have no way of knowing what change happened and what char was added deleted or changed
|
|
2372
|
+
#fire_handler :CHANGE, self # 2008-12-09 14:51
|
|
2373
|
+
if @overwrite_mode
|
|
2374
|
+
fire_handler :CHANGE, InputDataEvent.new(oldcurpos,@curpos, self, :DELETE, 0, oldchar) # 2010-09-11 12:43
|
|
2375
|
+
end
|
|
2376
|
+
fire_handler :CHANGE, InputDataEvent.new(oldcurpos,@curpos, self, :INSERT, 0, char) # 2010-09-11 12:43
|
|
2377
|
+
0
|
|
2378
|
+
end
|
|
2379
|
+
|
|
2380
|
+
##
|
|
2381
|
+
# TODO : sending c>=0 allows control chars to go. Should be >= ?A i think.
|
|
2382
|
+
def putc c
|
|
2383
|
+
if c >= 0 and c <= 127
|
|
2384
|
+
ret = putch c.chr
|
|
2385
|
+
if ret == 0
|
|
2386
|
+
if addcol(1) == -1 # if can't go forward, try scrolling
|
|
2387
|
+
# scroll if exceeding display len but less than max len
|
|
2388
|
+
if @curpos > @width && @curpos <= @maxlen
|
|
2389
|
+
@pcol += 1 if @pcol < @width
|
|
2390
|
+
end
|
|
2391
|
+
end
|
|
2392
|
+
set_modified
|
|
2393
|
+
return 0 # 2010-09-11 12:59 else would always return -1
|
|
2394
|
+
end
|
|
2395
|
+
end
|
|
2396
|
+
return -1
|
|
2397
|
+
end
|
|
2398
|
+
def delete_at index=@curpos
|
|
2399
|
+
return -1 if !@editable
|
|
2400
|
+
char = @buffer.slice!(index,1)
|
|
2401
|
+
#$log.debug " delete at #{index}: #{@buffer.length}: #{@buffer}"
|
|
2402
|
+
@modified = true
|
|
2403
|
+
#fire_handler :CHANGE, self # 2008-12-09 14:51
|
|
2404
|
+
fire_handler :CHANGE, InputDataEvent.new(@curpos,@curpos, self, :DELETE, 0, char) # 2010-09-11 13:01
|
|
2405
|
+
end
|
|
2406
|
+
#
|
|
2407
|
+
# silently restores value without firing handlers, use if exception and you want old value
|
|
2408
|
+
# @since 1.4.0 2011-10-2
|
|
2409
|
+
def restore_original_value
|
|
2410
|
+
@buffer = @original_value.dup
|
|
2411
|
+
# earlier commented but trying again, since i am getting IndexError in insert 2188
|
|
2412
|
+
# Added next 3 lines to fix issue, now it comes back to beginning.
|
|
2413
|
+
cursor_home
|
|
2414
|
+
|
|
2415
|
+
@repaint_required = true
|
|
2416
|
+
end
|
|
2417
|
+
##
|
|
2418
|
+
# set value of Field
|
|
2419
|
+
# fires CHANGE handler
|
|
2420
|
+
# Please don't use this directly, use +text+
|
|
2421
|
+
# This name is from ncurses field, added underscore to emphasize not to use
|
|
2422
|
+
def _set_buffer value #:nodoc:
|
|
2423
|
+
@repaint_required = true
|
|
2424
|
+
@datatype = value.class
|
|
2425
|
+
@delete_buffer = @buffer.dup
|
|
2426
|
+
@buffer = value.to_s.dup
|
|
2427
|
+
# don't allow setting of value greater than maxlen
|
|
2428
|
+
@buffer = @buffer[0,@maxlen] if @maxlen && @buffer.length > @maxlen
|
|
2429
|
+
@curpos = 0
|
|
2430
|
+
# hope @delete_buffer is not overwritten
|
|
2431
|
+
fire_handler :CHANGE, InputDataEvent.new(@curpos,@curpos, self, :DELETE, 0, @delete_buffer) # 2010-09-11 13:01
|
|
2432
|
+
fire_handler :CHANGE, InputDataEvent.new(@curpos,@curpos, self, :INSERT, 0, @buffer) # 2010-09-11 13:01
|
|
2433
|
+
self # 2011-10-2
|
|
2434
|
+
end
|
|
2435
|
+
# converts back into original type
|
|
2436
|
+
# changed to convert on 2009-01-06 23:39
|
|
2437
|
+
def getvalue
|
|
2438
|
+
dt = @datatype || String
|
|
2439
|
+
case dt.to_s
|
|
2440
|
+
when "String"
|
|
2441
|
+
return @buffer
|
|
2442
|
+
when "Fixnum"
|
|
2443
|
+
return @buffer.to_i
|
|
2444
|
+
when "Float"
|
|
2445
|
+
return @buffer.to_f
|
|
2446
|
+
else
|
|
2447
|
+
return @buffer.to_s
|
|
2448
|
+
end
|
|
2449
|
+
end
|
|
2450
|
+
|
|
2451
|
+
# create a label linked to this field
|
|
2452
|
+
# Typically one passes a Label, but now we can pass just a String, a label
|
|
2453
|
+
# is created. This differs from +label+ in positioning. The +Field+ is printed on
|
|
2454
|
+
# +row+ and +col+ and the label before it. Thus, all fields are aligned on column,
|
|
2455
|
+
# however you must leave adequate columns for the label to be printed to the left of the field.
|
|
2456
|
+
#
|
|
2457
|
+
# NOTE: 2011-10-20 when field attached to some container, label won't be attached
|
|
2458
|
+
# In such cases, use just +label()+ not +set_label()+.
|
|
2459
|
+
# @param [Label, String] label object to be associated with this field
|
|
2460
|
+
# @return label created which can be further customized.
|
|
2461
|
+
# FIXME this may not work since i have disabled -1, now i do not set row and col 2011-11-5
|
|
2462
|
+
def set_label label
|
|
2463
|
+
# added case for user just using a string
|
|
2464
|
+
case label
|
|
2465
|
+
when String
|
|
2466
|
+
# what if no form at this point
|
|
2467
|
+
@label_unattached = true unless @form
|
|
2468
|
+
label = Label.new @form, {:text => label}
|
|
2469
|
+
end
|
|
2470
|
+
@label = label
|
|
2471
|
+
# in the case of app it won't be set yet FIXME
|
|
2472
|
+
# So app sets label to 0 and t his won't trigger
|
|
2473
|
+
# can this be delayed to when paint happens XXX
|
|
2474
|
+
if @row
|
|
2475
|
+
position_label
|
|
2476
|
+
else
|
|
2477
|
+
@label_unplaced = true
|
|
2478
|
+
end
|
|
2479
|
+
label
|
|
2480
|
+
end
|
|
2481
|
+
def label *val
|
|
2482
|
+
return @label if val.empty?
|
|
2483
|
+
raise "Field does not allow setting of label. Please use LabeledField instead with lcol for label column"
|
|
2484
|
+
end
|
|
2485
|
+
# FIXME this may not work since i have disabled -1, now i do not set row and col
|
|
2486
|
+
def position_label
|
|
2487
|
+
$log.debug "XXX: LABEL row #{@label.row}, #{@label.col} "
|
|
2488
|
+
@label.row @row unless @label.row #if @label.row == -1
|
|
2489
|
+
@label.col @col-(@label.name.length+1) unless @label.col #if @label.col == -1
|
|
2490
|
+
@label.label_for(self) # this line got deleted when we redid stuff !
|
|
2491
|
+
$log.debug " XXX: LABEL row #{@label.row}, #{@label.col} "
|
|
2492
|
+
end
|
|
2493
|
+
|
|
2494
|
+
## Note that some older widgets like Field repaint every time the form.repaint
|
|
2495
|
+
##+ is called, whether updated or not. I can't remember why this is, but
|
|
2496
|
+
##+ currently I've not implemented events with these widgets. 2010-01-03 15:00
|
|
2497
|
+
|
|
2498
|
+
def repaint
|
|
2499
|
+
return unless @repaint_required # 2010-11-20 13:13 its writing over a window i think TESTING
|
|
2500
|
+
if @label_unattached
|
|
2501
|
+
alert "came here unattachd"
|
|
2502
|
+
@label.set_form(@form)
|
|
2503
|
+
@label_unattached = nil
|
|
2504
|
+
end
|
|
2505
|
+
if @label_unplaced
|
|
2506
|
+
alert "came here unplaced"
|
|
2507
|
+
position_label
|
|
2508
|
+
@label_unplaced = nil
|
|
2509
|
+
end
|
|
2510
|
+
#@bgcolor ||= $def_bg_color
|
|
2511
|
+
#@color ||= $def_fg_color
|
|
2512
|
+
_color = color()
|
|
2513
|
+
_bgcolor = bgcolor()
|
|
2514
|
+
$log.debug("repaint FIELD: #{id}, #{name}, #{row} #{col},pcol:#{@pcol}, #{focusable} st: #{@state} ")
|
|
2515
|
+
@width = 1 if width == 0
|
|
2516
|
+
printval = getvalue_for_paint().to_s # added 2009-01-06 23:27
|
|
2517
|
+
printval = mask()*printval.length unless @mask.nil?
|
|
2518
|
+
if !printval.nil?
|
|
2519
|
+
if printval.length > width # only show maxlen
|
|
2520
|
+
printval = printval[@pcol..@pcol+width-1]
|
|
2521
|
+
else
|
|
2522
|
+
printval = printval[@pcol..-1]
|
|
2523
|
+
end
|
|
2524
|
+
end
|
|
2525
|
+
|
|
2526
|
+
acolor = @color_pair || get_color($datacolor, _color, _bgcolor)
|
|
2527
|
+
if @state == :HIGHLIGHTED
|
|
2528
|
+
_bgcolor = @highlight_bgcolor || _bgcolor
|
|
2529
|
+
_color = @highlight_color || _color
|
|
2530
|
+
acolor = get_color(acolor, _color, _bgcolor)
|
|
2531
|
+
end
|
|
2532
|
+
@graphic = @form.window if @graphic.nil? ## cell editor listbox hack
|
|
2533
|
+
#$log.debug " Field g:#{@graphic}. r,c,displen:#{@row}, #{@col}, #{@width} c:#{@color} bg:#{@bgcolor} a:#{@attr} :#{@name} "
|
|
2534
|
+
r = row
|
|
2535
|
+
c = col
|
|
2536
|
+
@graphic.printstring r, c, sprintf("%-*s", width, printval), acolor, attr()
|
|
2537
|
+
@field_col = c
|
|
2538
|
+
@repaint_required = false
|
|
2539
|
+
end
|
|
2540
|
+
|
|
2541
|
+
# deprecated
|
|
2542
|
+
# set or unset focusable
|
|
2543
|
+
def set_focusable(tf)
|
|
2544
|
+
$log.warn "pls don't use, deprecated. use focusable(boolean)"
|
|
2545
|
+
focusable tf
|
|
2546
|
+
end
|
|
2547
|
+
|
|
2548
|
+
|
|
2549
|
+
def map_keys
|
|
2550
|
+
return if @keys_mapped
|
|
2551
|
+
bind_key(FFI::NCurses::KEY_LEFT, :cursor_backward )
|
|
2552
|
+
bind_key(FFI::NCurses::KEY_RIGHT, :cursor_forward )
|
|
2553
|
+
bind_key(FFI::NCurses::KEY_BACKSPACE, :delete_prev_char )
|
|
2554
|
+
bind_key(127, :delete_prev_char )
|
|
2555
|
+
bind_key(330, :delete_curr_char )
|
|
2556
|
+
bind_key(?\C-a, :cursor_home )
|
|
2557
|
+
bind_key(?\C-e, :cursor_end )
|
|
2558
|
+
bind_key(?\C-k, :delete_eol )
|
|
2559
|
+
bind_key(?\C-_, :undo_delete_eol )
|
|
2560
|
+
#bind_key(27){ text @original_value }
|
|
2561
|
+
bind_key(?\C-g, 'revert'){ text @original_value } # 2011-09-29 V1.3.1 ESC did not work
|
|
2562
|
+
@keys_mapped = true
|
|
2563
|
+
end
|
|
2564
|
+
|
|
2565
|
+
# field
|
|
2566
|
+
#
|
|
2567
|
+
def handle_key ch
|
|
2568
|
+
@repaint_required = true
|
|
2569
|
+
#map_keys unless @keys_mapped # moved to init
|
|
2570
|
+
case ch
|
|
2571
|
+
when 32..126
|
|
2572
|
+
#$log.debug("FIELD: ch #{ch} ,at #{@curpos}, buffer:[#{@buffer}] bl: #{@buffer.to_s.length}")
|
|
2573
|
+
putc ch
|
|
2574
|
+
when 27 # cannot bind it
|
|
2575
|
+
#text @original_value
|
|
2576
|
+
# commented above and changed 2014-05-12 - 20:05 I think above creates positioning issues. TEST XXX
|
|
2577
|
+
restore_original_value
|
|
2578
|
+
else
|
|
2579
|
+
ret = super
|
|
2580
|
+
return ret
|
|
2581
|
+
end
|
|
2582
|
+
0 # 2008-12-16 23:05 without this -1 was going back so no repaint
|
|
2583
|
+
end
|
|
2584
|
+
# does an undo on delete_eol, not a real undo
|
|
2585
|
+
def undo_delete_eol
|
|
2586
|
+
return if @delete_buffer.nil?
|
|
2587
|
+
#oldvalue = @buffer
|
|
2588
|
+
@buffer.insert @curpos, @delete_buffer
|
|
2589
|
+
fire_handler :CHANGE, InputDataEvent.new(@curpos,@curpos+@delete_buffer.length, self, :INSERT, 0, @delete_buffer) # 2010-09-11 13:01
|
|
2590
|
+
end
|
|
2591
|
+
##
|
|
2592
|
+
# position cursor at start of field
|
|
2593
|
+
def cursor_home
|
|
2594
|
+
@curpos = 0
|
|
2595
|
+
@pcol = 0
|
|
2596
|
+
set_form_col 0
|
|
2597
|
+
end
|
|
2598
|
+
##
|
|
2599
|
+
# goto end of field, "end" is a keyword so could not use it.
|
|
2600
|
+
def cursor_end
|
|
2601
|
+
blen = @buffer.rstrip.length
|
|
2602
|
+
if blen < @width
|
|
2603
|
+
set_form_col blen
|
|
2604
|
+
else
|
|
2605
|
+
# there is a problem here FIXME.
|
|
2606
|
+
@pcol = blen-@width
|
|
2607
|
+
#set_form_col @width-1
|
|
2608
|
+
set_form_col blen
|
|
2609
|
+
end
|
|
2610
|
+
@curpos = blen # this is position in array where editing or motion is to happen regardless of what you see
|
|
2611
|
+
# regardless of pcol (panning)
|
|
2612
|
+
# $log.debug " crusor END cp:#{@curpos} pcol:#{@pcol} b.l:#{@buffer.length} d_l:#{@width} fc:#{@form.col}"
|
|
2613
|
+
#set_form_col @buffer.length
|
|
2614
|
+
end
|
|
2615
|
+
# sets the visual cursor on the window at correct place
|
|
2616
|
+
# added here since we need to account for pcol. 2011-12-7
|
|
2617
|
+
# NOTE be careful of curpos - pcol being less than 0
|
|
2618
|
+
def set_form_col col1=@curpos
|
|
2619
|
+
@curpos = col1 || 0 # NOTE we set the index of cursor here
|
|
2620
|
+
c = @col + @col_offset + @curpos - @pcol
|
|
2621
|
+
min = @col + @col_offset
|
|
2622
|
+
max = min + @width
|
|
2623
|
+
c = min if c < min
|
|
2624
|
+
c = max if c > max
|
|
2625
|
+
#$log.debug " #{@name} FIELD set_form_col #{c}, curpos #{@curpos} , #{@col} + #{@col_offset} pcol:#{@pcol} "
|
|
2626
|
+
setrowcol nil, c
|
|
2627
|
+
end
|
|
2628
|
+
def delete_eol
|
|
2629
|
+
return -1 unless @editable
|
|
2630
|
+
pos = @curpos-1
|
|
2631
|
+
@delete_buffer = @buffer[@curpos..-1]
|
|
2632
|
+
# if pos is 0, pos-1 becomes -1, end of line!
|
|
2633
|
+
@buffer = pos == -1 ? "" : @buffer[0..pos]
|
|
2634
|
+
fire_handler :CHANGE, InputDataEvent.new(@curpos,@curpos+@delete_buffer.length, self, :DELETE, 0, @delete_buffer)
|
|
2635
|
+
return @delete_buffer
|
|
2636
|
+
end
|
|
2637
|
+
def cursor_forward
|
|
2638
|
+
if @curpos < @buffer.length
|
|
2639
|
+
if addcol(1)==-1 # go forward if you can, else scroll
|
|
2640
|
+
@pcol += 1 if @pcol < @width
|
|
2641
|
+
end
|
|
2642
|
+
@curpos += 1
|
|
2643
|
+
end
|
|
2644
|
+
# $log.debug " crusor FORWARD cp:#{@curpos} pcol:#{@pcol} b.l:#{@buffer.length} d_l:#{@display_length} fc:#{@form.col}"
|
|
2645
|
+
end
|
|
2646
|
+
def cursor_backward
|
|
2647
|
+
if @curpos > 0
|
|
2648
|
+
@curpos -= 1
|
|
2649
|
+
if @pcol > 0 and @form.col == @col + @col_offset
|
|
2650
|
+
@pcol -= 1
|
|
2651
|
+
end
|
|
2652
|
+
addcol -1
|
|
2653
|
+
elsif @pcol > 0 # added 2008-11-26 23:05
|
|
2654
|
+
@pcol -= 1
|
|
2655
|
+
end
|
|
2656
|
+
# $log.debug " crusor back cp:#{@curpos} pcol:#{@pcol} b.l:#{@buffer.length} d_l:#{@display_length} fc:#{@form.col}"
|
|
2657
|
+
=begin
|
|
2658
|
+
# this is perfect if not scrolling, but now needs changes
|
|
2659
|
+
if @curpos > 0
|
|
2660
|
+
@curpos -= 1
|
|
2661
|
+
addcol -1
|
|
2662
|
+
end
|
|
2663
|
+
=end
|
|
2664
|
+
end
|
|
2665
|
+
def delete_curr_char
|
|
2666
|
+
return -1 unless @editable
|
|
2667
|
+
delete_at
|
|
2668
|
+
set_modified
|
|
2669
|
+
end
|
|
2670
|
+
def delete_prev_char
|
|
2671
|
+
return -1 if !@editable
|
|
2672
|
+
return if @curpos <= 0
|
|
2673
|
+
# if we've panned, then unpan, and don't move cursor back
|
|
2674
|
+
# Otherwise, adjust cursor (move cursor back as we delete)
|
|
2675
|
+
adjust = true
|
|
2676
|
+
if @pcol > 0
|
|
2677
|
+
@pcol -= 1
|
|
2678
|
+
adjust = false
|
|
2679
|
+
end
|
|
2680
|
+
@curpos -= 1 if @curpos > 0
|
|
2681
|
+
delete_at
|
|
2682
|
+
addcol -1 if adjust # move visual cursor back
|
|
2683
|
+
set_modified
|
|
2684
|
+
end
|
|
2685
|
+
## add a column to cursor position. Field
|
|
2686
|
+
def addcol num
|
|
2687
|
+
if num < 0
|
|
2688
|
+
if @form.col <= @col + @col_offset
|
|
2689
|
+
# $log.debug " error trying to cursor back #{@form.col}"
|
|
2690
|
+
return -1
|
|
2691
|
+
end
|
|
2692
|
+
elsif num > 0
|
|
2693
|
+
if @form.col >= @col + @col_offset + @width
|
|
2694
|
+
# $log.debug " error trying to cursor forward #{@form.col}"
|
|
2695
|
+
return -1
|
|
2696
|
+
end
|
|
2697
|
+
end
|
|
2698
|
+
@form.addcol num
|
|
2699
|
+
end
|
|
2700
|
+
# upon leaving a field
|
|
2701
|
+
# returns false if value not valid as per values or valid_regex
|
|
2702
|
+
# 2008-12-22 12:40 if null_allowed, don't validate, but do fire_handlers
|
|
2703
|
+
def on_leave
|
|
2704
|
+
val = getvalue
|
|
2705
|
+
#$log.debug " FIELD ON LEAVE:#{val}. #{@values.inspect}"
|
|
2706
|
+
valid = true
|
|
2707
|
+
if val.to_s.empty? && @null_allowed
|
|
2708
|
+
#$log.debug " empty and null allowed"
|
|
2709
|
+
else
|
|
2710
|
+
if !@values.nil?
|
|
2711
|
+
valid = @values.include? val
|
|
2712
|
+
raise FieldValidationException, "Field value (#{val}) not in values: #{@values.join(',')}" unless valid
|
|
2713
|
+
end
|
|
2714
|
+
if !@valid_regex.nil?
|
|
2715
|
+
valid = @valid_regex.match(val.to_s)
|
|
2716
|
+
raise FieldValidationException, "Field not matching regex #{@valid_regex}" unless valid
|
|
2717
|
+
end
|
|
2718
|
+
# added valid_range for numerics 2011-09-29
|
|
2719
|
+
if !in_range?(val)
|
|
2720
|
+
raise FieldValidationException, "Field not matching range #{@valid_range}, above #{@above} or below #{@below} "
|
|
2721
|
+
end
|
|
2722
|
+
end
|
|
2723
|
+
# here is where we should set the forms modified to true - 2009-01
|
|
2724
|
+
if modified?
|
|
2725
|
+
set_modified true
|
|
2726
|
+
end
|
|
2727
|
+
# if super fails we would have still set modified to true
|
|
2728
|
+
super
|
|
2729
|
+
#return valid
|
|
2730
|
+
end
|
|
2731
|
+
|
|
2732
|
+
# checks field against +valid_range+, +above+ and +below+ , returning +true+ if it passes
|
|
2733
|
+
# set attributes, +false+ if it fails any one.
|
|
2734
|
+
def in_range?( val )
|
|
2735
|
+
val = val.to_i
|
|
2736
|
+
(@above.nil? or val > @above) and
|
|
2737
|
+
(@below.nil? or val < @below) and
|
|
2738
|
+
(@valid_range.nil? or @valid_range.include?(val))
|
|
2739
|
+
end
|
|
2740
|
+
## save original value on enter, so we can check for modified.
|
|
2741
|
+
# 2009-01-18 12:25
|
|
2742
|
+
# 2011-10-9 I have changed to take @buffer since getvalue returns a datatype
|
|
2743
|
+
# and this causes a crash in set_original on cursor forward.
|
|
2744
|
+
def on_enter
|
|
2745
|
+
#@original_value = getvalue.dup rescue getvalue
|
|
2746
|
+
@original_value = @buffer.dup # getvalue.dup rescue getvalue
|
|
2747
|
+
super
|
|
2748
|
+
end
|
|
2749
|
+
##
|
|
2750
|
+
# overriding widget, check for value change
|
|
2751
|
+
# 2009-01-18 12:25
|
|
2752
|
+
def modified?
|
|
2753
|
+
getvalue() != @original_value
|
|
2754
|
+
end
|
|
2755
|
+
#
|
|
2756
|
+
# Set the value in the field.
|
|
2757
|
+
# @param if none given, returns value existing
|
|
2758
|
+
# @param value (can be int, float, String)
|
|
2759
|
+
#
|
|
2760
|
+
# @return self
|
|
2761
|
+
def text(*val)
|
|
2762
|
+
if val.empty?
|
|
2763
|
+
return getvalue()
|
|
2764
|
+
else
|
|
2765
|
+
return unless val # added 2010-11-17 20:11, dup will fail on nil
|
|
2766
|
+
return unless val[0]
|
|
2767
|
+
# 2013-04-20 - 19:02 dup failing on fixnum, set_buffer does a dup
|
|
2768
|
+
# so maybe i can do without it here
|
|
2769
|
+
#s = val[0].dup
|
|
2770
|
+
s = val[0]
|
|
2771
|
+
_set_buffer(s)
|
|
2772
|
+
end
|
|
2773
|
+
end
|
|
2774
|
+
alias :default :text
|
|
2775
|
+
def text=(val)
|
|
2776
|
+
return unless val # added 2010-11-17 20:11, dup will fail on nil
|
|
2777
|
+
# will bomb on integer or float etc !!
|
|
2778
|
+
#_set_buffer(val.dup)
|
|
2779
|
+
_set_buffer(val)
|
|
2780
|
+
end
|
|
2781
|
+
# ADD HERE FIELD
|
|
2782
|
+
end # }}}
|
|
2783
|
+
|
|
2784
|
+
# trying to take out the label thing from field to keep it as simple as possible.
|
|
2785
|
+
# 2014-07-09
|
|
2786
|
+
class LabeledField < Field
|
|
2787
|
+
# Unlike `set_label` which creates a separate +Label+
|
|
2788
|
+
# object, this stores a +String+ and prints it before the string. This is less
|
|
2789
|
+
# customizable, however, in some cases when a field is attached to some container
|
|
2790
|
+
# the label gets left out. This label is gauranteed to print to the left of the field
|
|
2791
|
+
# This label prints on +lrow+ and +lcol+ if supplied, else it will print on the left of the field
|
|
2792
|
+
# at +col+ minus the width of the label.
|
|
2793
|
+
#
|
|
2794
|
+
dsl_accessor :label # label of field
|
|
2795
|
+
dsl_accessor :lrow, :lcol # coordinates of the label
|
|
2796
|
+
dsl_property :label_color_pair # label of field Unused earlier, now will print
|
|
2797
|
+
dsl_property :label_attr # label of field Unused earlier, now will print
|
|
2798
|
+
|
|
2799
|
+
def repaint
|
|
2800
|
+
return unless @repaint_required
|
|
2801
|
+
_lrow = @lrow || @row
|
|
2802
|
+
# the next was nice, but in some cases this goes out of screen. and the container
|
|
2803
|
+
# only sets row and col for whatever is added, it does not know that lcol has to be
|
|
2804
|
+
# taken into account
|
|
2805
|
+
#_lcol = @lcol || (@col - @label.length - 2)
|
|
2806
|
+
unless @lcol
|
|
2807
|
+
@lcol = @col
|
|
2808
|
+
@col = @lcol + @label.length + 2
|
|
2809
|
+
end
|
|
2810
|
+
_lcol = @lcol
|
|
2811
|
+
@graphic = @form.window if @graphic.nil?
|
|
2812
|
+
lcolor = @label_color_pair || $datacolor # this should be the same color as window bg XXX
|
|
2813
|
+
lattr = @label_attr || NORMAL
|
|
2814
|
+
@graphic.printstring _lrow, _lcol, @label, lcolor, lattr
|
|
2815
|
+
##c += @label.length + 2
|
|
2816
|
+
#@col_offset = c-@col # required so cursor lands in right place
|
|
2817
|
+
super
|
|
2818
|
+
end
|
|
2819
|
+
end
|
|
2820
|
+
##
|
|
2821
|
+
# Like Tk's TkVariable, a simple proxy that can be passed to a widget. The widget
|
|
2822
|
+
# will update the Variable. A variable can be used to link a field with a label or
|
|
2823
|
+
# some other widget.
|
|
2824
|
+
# This is the new version of Variable. Deleting old version on 2009-01-17 12:04
|
|
2825
|
+
# == Example
|
|
2826
|
+
# x = Variable.new
|
|
2827
|
+
# If x is passed as the +variable+ for a RadioButton group, then this keeps the value of the
|
|
2828
|
+
# button that is on.
|
|
2829
|
+
# y = Variable.new false
|
|
2830
|
+
#
|
|
2831
|
+
# z = Variable.new Hash.new
|
|
2832
|
+
# If z is passed as variable to create several Checkboxes, and each has the +name+ property set,
|
|
2833
|
+
# then this hash will contain the status of each checkbox with +name+ as key.
|
|
2834
|
+
|
|
2835
|
+
class Variable # {{{
|
|
2836
|
+
|
|
2837
|
+
def initialize value=""
|
|
2838
|
+
@update_command = []
|
|
2839
|
+
@args = []
|
|
2840
|
+
@value = value
|
|
2841
|
+
@klass = value.class.to_s
|
|
2842
|
+
end
|
|
2843
|
+
|
|
2844
|
+
##
|
|
2845
|
+
# This is to ensure that change handlers for all dependent objects are called
|
|
2846
|
+
# so they are updated. This is called from text_variable property of some widgets. If you
|
|
2847
|
+
# use one text_variable across objects, all will be updated auto. User does not need to call.
|
|
2848
|
+
# NOTE: I have removed text_variable from widget to simplify, so this seems to be dead.
|
|
2849
|
+
# @ private
|
|
2850
|
+
def add_dependent obj
|
|
2851
|
+
$log.debug " ADDING DEPENDE #{obj}"
|
|
2852
|
+
@dependents ||= []
|
|
2853
|
+
@dependents << obj
|
|
2854
|
+
end
|
|
2855
|
+
##
|
|
2856
|
+
# install trigger to call whenever a value is updated
|
|
2857
|
+
# @public called by user components
|
|
2858
|
+
def update_command *args, &block
|
|
2859
|
+
$log.debug "Variable: update command set " # #{args}"
|
|
2860
|
+
@update_command << block
|
|
2861
|
+
@args << args
|
|
2862
|
+
end
|
|
2863
|
+
alias :command :update_command
|
|
2864
|
+
##
|
|
2865
|
+
# value of the variable
|
|
2866
|
+
def get_value val=nil
|
|
2867
|
+
if @klass == 'String'
|
|
2868
|
+
return @value
|
|
2869
|
+
elsif @klass == 'Hash'
|
|
2870
|
+
return @value[val]
|
|
2871
|
+
elsif @klass == 'Array'
|
|
2872
|
+
return @value[val]
|
|
2873
|
+
else
|
|
2874
|
+
return @value
|
|
2875
|
+
end
|
|
2876
|
+
end
|
|
2877
|
+
##
|
|
2878
|
+
# update the value of this variable.
|
|
2879
|
+
# 2008-12-31 18:35 Added source so one can identify multiple sources that are updating.
|
|
2880
|
+
# Idea is that mutiple fields (e.g. checkboxes) can share one var and update a hash through it.
|
|
2881
|
+
# Source would contain some code or key relatin to each field.
|
|
2882
|
+
def set_value val, key=""
|
|
2883
|
+
oldval = @value
|
|
2884
|
+
if @klass == 'String'
|
|
2885
|
+
@value = val
|
|
2886
|
+
elsif @klass == 'Hash'
|
|
2887
|
+
#$log.debug " Variable setting hash #{key} to #{val}"
|
|
2888
|
+
oldval = @value[key]
|
|
2889
|
+
@value[key]=val
|
|
2890
|
+
elsif @klass == 'Array'
|
|
2891
|
+
#$log.debug " Variable setting array #{key} to #{val}"
|
|
2892
|
+
oldval = @value[key]
|
|
2893
|
+
@value[key]=val
|
|
2894
|
+
else
|
|
2895
|
+
oldval = @value
|
|
2896
|
+
@value = val
|
|
2897
|
+
end
|
|
2898
|
+
return if @update_command.nil?
|
|
2899
|
+
@update_command.each_with_index do |comm, ix|
|
|
2900
|
+
comm.call(self, *@args[ix]) unless comm.nil?
|
|
2901
|
+
end
|
|
2902
|
+
@dependents.each {|d| d.fire_property_change(d, oldval, val) } unless @dependents.nil?
|
|
2903
|
+
end
|
|
2904
|
+
##
|
|
2905
|
+
def value= (val)
|
|
2906
|
+
raise "Please use set_value for hash/array" if @klass=='Hash' or @klass=='Array'
|
|
2907
|
+
oldval = @value
|
|
2908
|
+
@value=val
|
|
2909
|
+
return if @update_command.nil?
|
|
2910
|
+
@update_command.each_with_index do |comm, ix|
|
|
2911
|
+
comm.call(self, *@args[ix]) unless comm.nil?
|
|
2912
|
+
end
|
|
2913
|
+
@dependents.each {|d| d.fire_property_change(d, oldval, val) } unless @dependents.nil?
|
|
2914
|
+
end
|
|
2915
|
+
def value
|
|
2916
|
+
raise "Please use set_value for hash/array: #{@klass}" if @klass=='Hash' #or @klass=='Array'
|
|
2917
|
+
@value
|
|
2918
|
+
end
|
|
2919
|
+
def inspect
|
|
2920
|
+
@value.inspect
|
|
2921
|
+
end
|
|
2922
|
+
def [](key)
|
|
2923
|
+
@value[key]
|
|
2924
|
+
end
|
|
2925
|
+
##
|
|
2926
|
+
# in order to run some method we don't yet support
|
|
2927
|
+
def source
|
|
2928
|
+
@value
|
|
2929
|
+
end
|
|
2930
|
+
def to_s
|
|
2931
|
+
inspect
|
|
2932
|
+
end
|
|
2933
|
+
end # }}}
|
|
2934
|
+
##
|
|
2935
|
+
# The preferred way of printing text on screen, esp if you want to modify it at run time.
|
|
2936
|
+
# Use display_length to ensure no spillage.
|
|
2937
|
+
# This can use text or text_variable for setting and getting data (inh from Widget).
|
|
2938
|
+
# 2011-11-12 making it simpler, and single line only. The original multiline label
|
|
2939
|
+
# has moved to extras/multilinelabel.rb
|
|
2940
|
+
#
|
|
2941
|
+
class Label < Widget # {{{
|
|
2942
|
+
dsl_accessor :mnemonic # keyboard focus is passed to buddy based on this key (ALT mask)
|
|
2943
|
+
|
|
2944
|
+
# justify required a display length, esp if center.
|
|
2945
|
+
dsl_property :justify #:right, :left, :center
|
|
2946
|
+
#dsl_property :display_length #please give this to ensure the we only print this much
|
|
2947
|
+
# for consistency with others 2011-11-5
|
|
2948
|
+
#alias :width :display_length
|
|
2949
|
+
#alias :width= :display_length=
|
|
2950
|
+
|
|
2951
|
+
def initialize form, config={}, &block
|
|
2952
|
+
|
|
2953
|
+
@text = config.fetch(:text, "NOTFOUND")
|
|
2954
|
+
@editable = false
|
|
2955
|
+
@focusable = false
|
|
2956
|
+
# we have some processing for when a form is attached, registering a hotkey
|
|
2957
|
+
register_events :FORM_ATTACHED
|
|
2958
|
+
super
|
|
2959
|
+
@justify ||= :left
|
|
2960
|
+
@name ||= @text
|
|
2961
|
+
@repaint_required = true
|
|
2962
|
+
end
|
|
2963
|
+
#
|
|
2964
|
+
# get the value for the label
|
|
2965
|
+
def getvalue
|
|
2966
|
+
#@text_variable && @text_variable.value || @text
|
|
2967
|
+
@text
|
|
2968
|
+
end
|
|
2969
|
+
def label_for field
|
|
2970
|
+
@label_for = field
|
|
2971
|
+
#$log.debug " label for: #{@label_for}"
|
|
2972
|
+
if @form
|
|
2973
|
+
bind_hotkey
|
|
2974
|
+
else
|
|
2975
|
+
# we have some processing for when a form is attached, registering a hotkey
|
|
2976
|
+
bind(:FORM_ATTACHED){ bind_hotkey }
|
|
2977
|
+
end
|
|
2978
|
+
end
|
|
2979
|
+
|
|
2980
|
+
##
|
|
2981
|
+
# for a button, fire it when label invoked without changing focus
|
|
2982
|
+
# for other widgets, attempt to change focus to that field
|
|
2983
|
+
def bind_hotkey
|
|
2984
|
+
if @mnemonic
|
|
2985
|
+
ch = @mnemonic.downcase()[0].ord ## 1.9 DONE
|
|
2986
|
+
# meta key
|
|
2987
|
+
mch = ?\M-a.getbyte(0) + (ch - ?a.getbyte(0)) ## 1.9
|
|
2988
|
+
if (@label_for.is_a? Canis::Button ) && (@label_for.respond_to? :fire)
|
|
2989
|
+
@form.bind_key(mch, "hotkey for button #{@label_for.text} ") { |_form, _butt| @label_for.fire }
|
|
2990
|
+
else
|
|
2991
|
+
$log.debug " bind_hotkey label for: #{@label_for}"
|
|
2992
|
+
@form.bind_key(mch, "hotkey for label #{text} ") { |_form, _field| @label_for.focus }
|
|
2993
|
+
end
|
|
2994
|
+
end
|
|
2995
|
+
end
|
|
2996
|
+
|
|
2997
|
+
##
|
|
2998
|
+
# label's repaint - I am removing wrapping and Array stuff and making it simple 2011-11-12
|
|
2999
|
+
def repaint
|
|
3000
|
+
return unless @repaint_required
|
|
3001
|
+
raise "Label row or col nil #{@row} , #{@col}, #{@text} " if @row.nil? || @col.nil?
|
|
3002
|
+
r,c = rowcol
|
|
3003
|
+
|
|
3004
|
+
#@bgcolor ||= $def_bg_color
|
|
3005
|
+
#@color ||= $def_fg_color
|
|
3006
|
+
_bgcolor = bgcolor()
|
|
3007
|
+
_color = color()
|
|
3008
|
+
# value often nil so putting blank, but usually some application error
|
|
3009
|
+
value = getvalue_for_paint || ""
|
|
3010
|
+
|
|
3011
|
+
if value.is_a? Array
|
|
3012
|
+
value = value.join " "
|
|
3013
|
+
end
|
|
3014
|
+
# ensure we do not exceed
|
|
3015
|
+
if @width
|
|
3016
|
+
if value.length > @width
|
|
3017
|
+
value = value[0..@width-1]
|
|
3018
|
+
end
|
|
3019
|
+
end
|
|
3020
|
+
len = @width || value.length
|
|
3021
|
+
#acolor = get_color $datacolor
|
|
3022
|
+
# the user could have set color_pair, use that, else determine color
|
|
3023
|
+
# This implies that if he sets cp, then changing col and bg won't have an effect !
|
|
3024
|
+
# A general routine that only changes color will not work here.
|
|
3025
|
+
acolor = @color_pair || get_color($datacolor, _color, _bgcolor)
|
|
3026
|
+
#$log.debug "label :#{@text}, #{value}, r #{r}, c #{c} col= #{@color}, #{@bgcolor} acolor #{acolor} j:#{@justify} dlL: #{@width} "
|
|
3027
|
+
str = @justify.to_sym == :right ? "%*s" : "%-*s" # added 2008-12-22 19:05
|
|
3028
|
+
|
|
3029
|
+
@graphic ||= @form.window
|
|
3030
|
+
# clear the area
|
|
3031
|
+
@graphic.printstring r, c, " " * len , acolor, attr()
|
|
3032
|
+
if @justify.to_sym == :center
|
|
3033
|
+
padding = (@width - value.length)/2
|
|
3034
|
+
value = " "*padding + value + " "*padding # so its cleared if we change it midway
|
|
3035
|
+
end
|
|
3036
|
+
@graphic.printstring r, c, str % [len, value], acolor, attr()
|
|
3037
|
+
if @mnemonic
|
|
3038
|
+
ulindex = value.index(@mnemonic) || value.index(@mnemonic.swapcase)
|
|
3039
|
+
@graphic.mvchgat(y=r, x=c+ulindex, max=1, Ncurses::A_BOLD|Ncurses::A_UNDERLINE, acolor, nil)
|
|
3040
|
+
end
|
|
3041
|
+
@repaint_required = false
|
|
3042
|
+
end
|
|
3043
|
+
# Added 2011-10-22 to prevent some naive components from putting focus here.
|
|
3044
|
+
def on_enter
|
|
3045
|
+
raise "Cannot enter Label"
|
|
3046
|
+
end
|
|
3047
|
+
def on_leave
|
|
3048
|
+
raise "Cannot leave Label"
|
|
3049
|
+
end
|
|
3050
|
+
# ADD HERE LABEL
|
|
3051
|
+
end # }}}
|
|
3052
|
+
##
|
|
3053
|
+
# action buttons
|
|
3054
|
+
# Use +text+ to pass the string to be printed on the button
|
|
3055
|
+
# An ampersand is understaod to denote a shortcut and will map Alt-char to that button's FIRE event
|
|
3056
|
+
# Alternative, +mnemonic(char)+ can also be used.
|
|
3057
|
+
# In the config hash, ':hotkey' may be passed which maps the character itself to the button's FIRE.
|
|
3058
|
+
# This is for menulinks, and maybe a form that has only buttons.
|
|
3059
|
+
#
|
|
3060
|
+
# NOTE: When firing event, an ActionEvent will be passed as the first parameter, followed by anything
|
|
3061
|
+
# you may have passed when binding, or calling the command() method.
|
|
3062
|
+
# - Action: may have to listen to Action property changes so enabled, name etc change can be reflected
|
|
3063
|
+
# 2011-11-26 : define button as default, so it can show differently and also fire on ENTER
|
|
3064
|
+
# trying out behavior change. space to fire current button, ENTER for default button which has
|
|
3065
|
+
# > Name < look.
|
|
3066
|
+
class Button < Widget # {{{
|
|
3067
|
+
dsl_accessor :surround_chars # characters to use to surround the button, def is square brackets
|
|
3068
|
+
# char to be underlined, and bound to Alt-char
|
|
3069
|
+
dsl_accessor :mnemonic
|
|
3070
|
+
def initialize form, config={}, &block
|
|
3071
|
+
require 'canis/core/include/ractionevent'
|
|
3072
|
+
@focusable = true
|
|
3073
|
+
@editable = false
|
|
3074
|
+
# hotkey denotes we should bind the key itself not alt-key (for menulinks)
|
|
3075
|
+
@hotkey = config.delete(:hotkey)
|
|
3076
|
+
register_events([:PRESS, :FORM_ATTACHED])
|
|
3077
|
+
@default_chars = ['> ', ' <']
|
|
3078
|
+
super
|
|
3079
|
+
|
|
3080
|
+
|
|
3081
|
+
@surround_chars ||= ['[ ', ' ]']
|
|
3082
|
+
@col_offset = @surround_chars[0].length
|
|
3083
|
+
@text_offset = 0
|
|
3084
|
+
map_keys
|
|
3085
|
+
end
|
|
3086
|
+
##
|
|
3087
|
+
# set button based on Action
|
|
3088
|
+
def action a
|
|
3089
|
+
text a.name
|
|
3090
|
+
mnemonic a.mnemonic unless a.mnemonic.nil?
|
|
3091
|
+
command { a.call }
|
|
3092
|
+
end
|
|
3093
|
+
##
|
|
3094
|
+
# button: sets text, checking for ampersand, uses that for hotkey and underlines
|
|
3095
|
+
def text(*val)
|
|
3096
|
+
if val.empty?
|
|
3097
|
+
return @text
|
|
3098
|
+
else
|
|
3099
|
+
s = val[0].dup
|
|
3100
|
+
s = s.to_s if !s.is_a? String # 2009-01-15 17:32
|
|
3101
|
+
if (( ix = s.index('&')) != nil)
|
|
3102
|
+
s.slice!(ix,1)
|
|
3103
|
+
@underline = ix #unless @form.nil? # this setting a fake underline in messageboxes
|
|
3104
|
+
@text = s # mnemo needs this for setting description
|
|
3105
|
+
mnemonic s[ix,1]
|
|
3106
|
+
end
|
|
3107
|
+
@text = s
|
|
3108
|
+
end
|
|
3109
|
+
return self
|
|
3110
|
+
end
|
|
3111
|
+
|
|
3112
|
+
##
|
|
3113
|
+
# FIXME this will not work in messageboxes since no form available
|
|
3114
|
+
# if already set mnemonic, then unbind_key, ??
|
|
3115
|
+
# NOTE: Some buttons like checkbox directly call mnemonic, so if they have no form
|
|
3116
|
+
# then this processing does not happen
|
|
3117
|
+
|
|
3118
|
+
# set mnemonic for button, this is a hotkey that triggers +fire+ upon pressing Alt+char
|
|
3119
|
+
def mnemonic char=nil
|
|
3120
|
+
return @mnemonic unless char # added 2011-11-24 so caller can get mne
|
|
3121
|
+
|
|
3122
|
+
unless @form
|
|
3123
|
+
# we have some processing for when a form is attached, registering a hotkey
|
|
3124
|
+
bind(:FORM_ATTACHED) { mnemonic char }
|
|
3125
|
+
return self # added 2014-03-23 - 22:59 so that we can chain methods
|
|
3126
|
+
end
|
|
3127
|
+
@mnemonic = char
|
|
3128
|
+
ch = char.downcase()[0].ord ## 1.9
|
|
3129
|
+
# meta key
|
|
3130
|
+
ch = ?\M-a.getbyte(0) + (ch - ?a.getbyte(0)) unless @hotkey
|
|
3131
|
+
$log.debug " #{self} setting MNEMO to #{char} #{ch}, #{@hotkey} "
|
|
3132
|
+
_t = self.text || self.name || "Unnamed #{self.class} "
|
|
3133
|
+
@form.bind_key(ch, "hotkey for button #{_t} ") { |_form, _butt| self.fire }
|
|
3134
|
+
return self # added 2015-03-23 - 22:59 so that we can chain methods
|
|
3135
|
+
end
|
|
3136
|
+
|
|
3137
|
+
##
|
|
3138
|
+
# bind hotkey to form keys. added 2008-12-15 20:19
|
|
3139
|
+
# use ampersand in name or underline
|
|
3140
|
+
# IS THIS USED ??
|
|
3141
|
+
def bind_hotkey
|
|
3142
|
+
alert "bind_hotkey was called in button"
|
|
3143
|
+
if @form.nil?
|
|
3144
|
+
if @underline
|
|
3145
|
+
bind(:FORM_ATTACHED){ bind_hotkey }
|
|
3146
|
+
end
|
|
3147
|
+
return
|
|
3148
|
+
end
|
|
3149
|
+
_value = @text || getvalue # hack for Togglebutton FIXME
|
|
3150
|
+
$log.debug " bind hot #{_value} #{@underline}"
|
|
3151
|
+
ch = _value[@underline,1].downcase()[0].ord ## 1.9 2009-10-05 18:55 TOTEST
|
|
3152
|
+
@mnemonic = _value[@underline,1]
|
|
3153
|
+
# meta key
|
|
3154
|
+
mch = ?\M-a.getbyte(0) + (ch - ?a.getbyte(0))
|
|
3155
|
+
@form.bind_key(mch, "hotkey for button #{self.text}" ) { |_form, _butt| self.fire }
|
|
3156
|
+
end
|
|
3157
|
+
def default_button tf=nil
|
|
3158
|
+
return @default_button unless tf
|
|
3159
|
+
raise ArgumentError, "default button must be true or false" if ![false,true].include? tf
|
|
3160
|
+
unless @form
|
|
3161
|
+
bind(:FORM_ATTACHED){ default_button(tf) }
|
|
3162
|
+
return self
|
|
3163
|
+
end
|
|
3164
|
+
$log.debug "XXX: BUTTON DEFAULT setting to true : #{tf} "
|
|
3165
|
+
@default_button = tf
|
|
3166
|
+
if tf
|
|
3167
|
+
@surround_chars = @default_chars
|
|
3168
|
+
@form.bind_key(13, "fire #{self.text} ") { |_form, _butt| self.fire }
|
|
3169
|
+
else
|
|
3170
|
+
# i have no way of reversing the above
|
|
3171
|
+
end
|
|
3172
|
+
end
|
|
3173
|
+
|
|
3174
|
+
def getvalue
|
|
3175
|
+
#@text_variable.nil? ? @text : @text_variable.get_value(@name)
|
|
3176
|
+
@text
|
|
3177
|
+
end
|
|
3178
|
+
|
|
3179
|
+
# ensure text has been passed or action
|
|
3180
|
+
def getvalue_for_paint
|
|
3181
|
+
ret = getvalue
|
|
3182
|
+
@text_offset = @surround_chars[0].length
|
|
3183
|
+
@surround_chars[0] + ret + @surround_chars[1]
|
|
3184
|
+
end
|
|
3185
|
+
|
|
3186
|
+
# FIXME 2014-05-31 since form checks for highlight color and sets repaint on on_enter, we shoul not set it.
|
|
3187
|
+
# but what if it is set at form level ?
|
|
3188
|
+
# also it is not correct to set colors now that form's defaults are taken
|
|
3189
|
+
def repaint # button
|
|
3190
|
+
|
|
3191
|
+
#@bgcolor ||= $def_bg_color
|
|
3192
|
+
#@color ||= $def_fg_color
|
|
3193
|
+
$log.debug("BUTTON repaint : #{self} r:#{@row} c:#{@col} , #{@color} , #{@bgcolor} , #{getvalue_for_paint}" )
|
|
3194
|
+
r,c = @row, @col #rowcol include offset for putting cursor
|
|
3195
|
+
# NOTE: please override both (if using a string), or else it won't work
|
|
3196
|
+
# These are both colorpairs not colors ??? 2014-05-31 - 11:58
|
|
3197
|
+
_highlight_color = @highlight_color || $reversecolor
|
|
3198
|
+
_highlight_bgcolor = @highlight_bgcolor || 0
|
|
3199
|
+
_bgcolor = bgcolor()
|
|
3200
|
+
_color = color()
|
|
3201
|
+
if @state == :HIGHLIGHTED
|
|
3202
|
+
_bgcolor = @state==:HIGHLIGHTED ? _highlight_bgcolor : _bgcolor
|
|
3203
|
+
_color = @state==:HIGHLIGHTED ? _highlight_color : _color
|
|
3204
|
+
elsif selected? # only for certain buttons lie toggle and radio
|
|
3205
|
+
_bgcolor = @selected_bgcolor || _bgcolor
|
|
3206
|
+
_color = @selected_color || _color
|
|
3207
|
+
end
|
|
3208
|
+
$log.debug "XXX: button #{text} STATE is #{@state} color #{_color} , bg: #{_bgcolor} "
|
|
3209
|
+
if _bgcolor.is_a?( Fixnum) && _color.is_a?( Fixnum)
|
|
3210
|
+
# i think this means they are colorpairs not colors, but what if we use colors on the 256 scale ?
|
|
3211
|
+
# i don;t like this at all.
|
|
3212
|
+
else
|
|
3213
|
+
_color = get_color($datacolor, _color, _bgcolor)
|
|
3214
|
+
end
|
|
3215
|
+
value = getvalue_for_paint
|
|
3216
|
+
$log.debug("button repaint :#{self} r:#{r} c:#{c} col:#{_color} bg #{_bgcolor} v: #{value} ul #{@underline} mnem #{@mnemonic} datacolor #{$datacolor} ")
|
|
3217
|
+
len = @width || value.length
|
|
3218
|
+
@graphic = @form.window if @graphic.nil? ## cell editor listbox hack
|
|
3219
|
+
@graphic.printstring r, c, "%-*s" % [len, value], _color, attr()
|
|
3220
|
+
# @form.window.mvchgat(y=r, x=c, max=len, Ncurses::A_NORMAL, bgcolor, nil)
|
|
3221
|
+
# in toggle buttons the underline can change as the text toggles
|
|
3222
|
+
if @underline || @mnemonic
|
|
3223
|
+
uline = @underline && (@underline + @text_offset) || value.index(@mnemonic) ||
|
|
3224
|
+
value.index(@mnemonic.swapcase)
|
|
3225
|
+
# if the char is not found don't print it
|
|
3226
|
+
if uline
|
|
3227
|
+
y=r #-@graphic.top
|
|
3228
|
+
x=c+uline #-@graphic.left
|
|
3229
|
+
#
|
|
3230
|
+
# NOTE: often values go below zero since root windows are defined
|
|
3231
|
+
# with 0 w and h, and then i might use that value for calcaluting
|
|
3232
|
+
#
|
|
3233
|
+
$log.error "XXX button underline location error #{x} , #{y} " if x < 0 or c < 0
|
|
3234
|
+
raise " #{r} #{c} #{uline} button underline location error x:#{x} , y:#{y}. left #{@graphic.left} top:#{@graphic.top} " if x < 0 or c < 0
|
|
3235
|
+
@graphic.mvchgat(y, x, max=1, Ncurses::A_BOLD|Ncurses::A_UNDERLINE, _color, nil)
|
|
3236
|
+
end
|
|
3237
|
+
end
|
|
3238
|
+
end
|
|
3239
|
+
|
|
3240
|
+
## command of button (invoked on press, hotkey, space)
|
|
3241
|
+
# added args 2008-12-20 19:22
|
|
3242
|
+
def command *args, &block
|
|
3243
|
+
bind :PRESS, *args, &block
|
|
3244
|
+
end
|
|
3245
|
+
## fires PRESS event of button
|
|
3246
|
+
def fire
|
|
3247
|
+
#$log.debug "firing PRESS #{text}"
|
|
3248
|
+
fire_handler :PRESS, ActionEvent.new(self, :PRESS, text)
|
|
3249
|
+
end
|
|
3250
|
+
# for campatibility with all buttons, will apply to radio buttons mostly
|
|
3251
|
+
def selected?; false; end
|
|
3252
|
+
|
|
3253
|
+
def map_keys
|
|
3254
|
+
return if @keys_mapped
|
|
3255
|
+
bind_key(32, "fire") { fire } if respond_to? :fire
|
|
3256
|
+
if $key_map_type == :vim
|
|
3257
|
+
bind_key( key("j"), "down") { @form.window.ungetch(KEY_DOWN) }
|
|
3258
|
+
bind_key( key("k"), "up") { @form.window.ungetch(KEY_UP) }
|
|
3259
|
+
end
|
|
3260
|
+
end
|
|
3261
|
+
|
|
3262
|
+
# Button
|
|
3263
|
+
def handle_key ch
|
|
3264
|
+
super
|
|
3265
|
+
end
|
|
3266
|
+
=begin
|
|
3267
|
+
case ch
|
|
3268
|
+
when FFI::NCurses::KEY_LEFT, FFI::NCurses::KEY_UP
|
|
3269
|
+
return :UNHANDLED
|
|
3270
|
+
# @form.select_prev_field
|
|
3271
|
+
when FFI::NCurses::KEY_RIGHT, FFI::NCurses::KEY_DOWN
|
|
3272
|
+
return :UNHANDLED
|
|
3273
|
+
# @form.select_next_field
|
|
3274
|
+
# 2014-05-07 - 12:26 removed ENTER on buttons
|
|
3275
|
+
# CANIS : button only responds to SPACE, ENTER will only work on default button.
|
|
3276
|
+
#when FFI::NCurses::KEY_ENTER, 10, 13, 32 # added space bar also
|
|
3277
|
+
# I am really confused about this. Default button really confuses things in some
|
|
3278
|
+
# situations, but is great if you are not on the buttons.
|
|
3279
|
+
# shall we keep ENTER for default button
|
|
3280
|
+
when 32 # added space bar also
|
|
3281
|
+
if respond_to? :fire
|
|
3282
|
+
fire
|
|
3283
|
+
end
|
|
3284
|
+
else
|
|
3285
|
+
if $key_map_type == :vim
|
|
3286
|
+
case ch
|
|
3287
|
+
when ?j.getbyte(0)
|
|
3288
|
+
@form.window.ungetch(KEY_DOWN)
|
|
3289
|
+
return 0
|
|
3290
|
+
when ?k.getbyte(0)
|
|
3291
|
+
@form.window.ungetch(KEY_UP)
|
|
3292
|
+
return 0
|
|
3293
|
+
end
|
|
3294
|
+
|
|
3295
|
+
end
|
|
3296
|
+
return :UNHANDLED
|
|
3297
|
+
end
|
|
3298
|
+
end
|
|
3299
|
+
=end
|
|
3300
|
+
|
|
3301
|
+
# temporary method, shoud be a proper class
|
|
3302
|
+
def self.button_layout buttons, row, startcol=0, cols=Ncurses.COLS-1, gap=5
|
|
3303
|
+
col = startcol
|
|
3304
|
+
buttons.each_with_index do |b, ix|
|
|
3305
|
+
$log.debug " BUTTON #{b}: #{b.col} "
|
|
3306
|
+
b.row = row
|
|
3307
|
+
b.col col
|
|
3308
|
+
$log.debug " after BUTTON #{b}: #{b.col} "
|
|
3309
|
+
len = b.text.length + gap
|
|
3310
|
+
col += len
|
|
3311
|
+
end
|
|
3312
|
+
end
|
|
3313
|
+
end #BUTTON # }}}
|
|
3314
|
+
|
|
3315
|
+
##
|
|
3316
|
+
# an event fired when an item that can be selected is toggled/selected
|
|
3317
|
+
class ItemEvent # {{{
|
|
3318
|
+
# http://java.sun.com/javase/6/docs/api/java/awt/event/ItemEvent.html
|
|
3319
|
+
attr_reader :state # :SELECTED :DESELECTED
|
|
3320
|
+
attr_reader :item # the item pressed such as toggle button
|
|
3321
|
+
attr_reader :item_selectable # item originating event such as list or collection
|
|
3322
|
+
attr_reader :item_first # if from a list
|
|
3323
|
+
attr_reader :item_last #
|
|
3324
|
+
attr_reader :param_string # for debugging etc
|
|
3325
|
+
=begin
|
|
3326
|
+
def initialize item, item_selectable, state, item_first=-1, item_last=-1, paramstring=nil
|
|
3327
|
+
@item, @item_selectable, @state, @item_first, @item_last =
|
|
3328
|
+
item, item_selectable, state, item_first, item_last
|
|
3329
|
+
@param_string = "Item event fired: #{item}, #{state}"
|
|
3330
|
+
end
|
|
3331
|
+
=end
|
|
3332
|
+
# i think only one is needed per object, so create once only
|
|
3333
|
+
def initialize item, item_selectable
|
|
3334
|
+
@item, @item_selectable =
|
|
3335
|
+
item, item_selectable
|
|
3336
|
+
end
|
|
3337
|
+
def set state, item_first=-1, item_last=-1, param_string=nil
|
|
3338
|
+
@state, @item_first, @item_last, @param_string =
|
|
3339
|
+
state, item_first, item_last, param_string
|
|
3340
|
+
@param_string = "Item event fired: #{item}, #{state}" if param_string.nil?
|
|
3341
|
+
end
|
|
3342
|
+
end # }}}
|
|
3343
|
+
##
|
|
3344
|
+
# A button that may be switched off an on.
|
|
3345
|
+
# To be extended by RadioButton and checkbox.
|
|
3346
|
+
# WARNING, pls do not override +text+ otherwise checkboxes etc will stop functioning.
|
|
3347
|
+
# TODO: add editable here nd prevent toggling if not so.
|
|
3348
|
+
class ToggleButton < Button # {{{
|
|
3349
|
+
# text for on value and off value
|
|
3350
|
+
dsl_accessor :onvalue, :offvalue
|
|
3351
|
+
# boolean, which value to use currently, onvalue or offvalue
|
|
3352
|
+
dsl_accessor :value
|
|
3353
|
+
# characters to use for surround, array, default square brackets
|
|
3354
|
+
dsl_accessor :surround_chars
|
|
3355
|
+
dsl_accessor :variable # value linked to this variable which is a boolean
|
|
3356
|
+
# background to use when selected, if not set then default
|
|
3357
|
+
dsl_accessor :selected_bgcolor
|
|
3358
|
+
dsl_accessor :selected_color
|
|
3359
|
+
|
|
3360
|
+
def initialize form, config={}, &block
|
|
3361
|
+
super
|
|
3362
|
+
|
|
3363
|
+
@value ||= (@variable.nil? ? false : @variable.get_value(@name)==true)
|
|
3364
|
+
end
|
|
3365
|
+
def getvalue
|
|
3366
|
+
@value ? @onvalue : @offvalue
|
|
3367
|
+
end
|
|
3368
|
+
|
|
3369
|
+
# WARNING, pls do not override +text+ otherwise checkboxes etc will stop functioning.
|
|
3370
|
+
|
|
3371
|
+
# added for some standardization 2010-09-07 20:28
|
|
3372
|
+
# alias :text :getvalue # NEXT VERSION
|
|
3373
|
+
# change existing text to label
|
|
3374
|
+
##
|
|
3375
|
+
# is the button on or off
|
|
3376
|
+
# added 2008-12-09 19:05
|
|
3377
|
+
def checked?
|
|
3378
|
+
@value
|
|
3379
|
+
end
|
|
3380
|
+
alias :selected? :checked?
|
|
3381
|
+
|
|
3382
|
+
def getvalue_for_paint
|
|
3383
|
+
unless @width
|
|
3384
|
+
if @onvalue && @offvalue
|
|
3385
|
+
@width = [ @onvalue.length, @offvalue.length ].max
|
|
3386
|
+
end
|
|
3387
|
+
end
|
|
3388
|
+
buttontext = getvalue().center(@width)
|
|
3389
|
+
@text_offset = @surround_chars[0].length
|
|
3390
|
+
@surround_chars[0] + buttontext + @surround_chars[1]
|
|
3391
|
+
end
|
|
3392
|
+
|
|
3393
|
+
# toggle button handle key
|
|
3394
|
+
# @param [int] key received
|
|
3395
|
+
#
|
|
3396
|
+
def handle_key ch
|
|
3397
|
+
if ch == 32
|
|
3398
|
+
toggle
|
|
3399
|
+
else
|
|
3400
|
+
super
|
|
3401
|
+
end
|
|
3402
|
+
end
|
|
3403
|
+
|
|
3404
|
+
##
|
|
3405
|
+
# toggle the button value
|
|
3406
|
+
def toggle
|
|
3407
|
+
fire
|
|
3408
|
+
end
|
|
3409
|
+
|
|
3410
|
+
# called on :PRESS event
|
|
3411
|
+
# caller should check state of itemevent passed to block
|
|
3412
|
+
def fire
|
|
3413
|
+
checked(!@value)
|
|
3414
|
+
@item_event = ItemEvent.new self, self if @item_event.nil?
|
|
3415
|
+
@item_event.set(@value ? :SELECTED : :DESELECTED)
|
|
3416
|
+
fire_handler :PRESS, @item_event # should the event itself be ITEM_EVENT
|
|
3417
|
+
end
|
|
3418
|
+
##
|
|
3419
|
+
# set the value to true or false
|
|
3420
|
+
# user may programmatically want to check or uncheck
|
|
3421
|
+
def checked tf
|
|
3422
|
+
@value = tf
|
|
3423
|
+
if @variable
|
|
3424
|
+
if @value
|
|
3425
|
+
@variable.set_value((@onvalue || 1), @name)
|
|
3426
|
+
else
|
|
3427
|
+
@variable.set_value((@offvalue || 0), @name)
|
|
3428
|
+
end
|
|
3429
|
+
end
|
|
3430
|
+
end
|
|
3431
|
+
end # class # }}}
|
|
3432
|
+
|
|
3433
|
+
##
|
|
3434
|
+
# A checkbox, may be selected or unselected
|
|
3435
|
+
#
|
|
3436
|
+
class CheckBox < ToggleButton # {{{
|
|
3437
|
+
dsl_accessor :align_right # the button will be on the right 2008-12-09 23:41
|
|
3438
|
+
# if a variable has been defined, off and on value will be set in it (default 0,1)
|
|
3439
|
+
def initialize form, config={}, &block
|
|
3440
|
+
@surround_chars = ['[', ']'] # 2008-12-23 23:16 added space in Button so overriding
|
|
3441
|
+
super
|
|
3442
|
+
end
|
|
3443
|
+
def getvalue
|
|
3444
|
+
@value
|
|
3445
|
+
end
|
|
3446
|
+
|
|
3447
|
+
def getvalue_for_paint
|
|
3448
|
+
buttontext = getvalue() ? "X" : " "
|
|
3449
|
+
dtext = @width.nil? ? @text : "%-*s" % [@width, @text]
|
|
3450
|
+
dtext = "" if @text.nil? # added 2009-01-13 00:41 since cbcellrenderer prints no text
|
|
3451
|
+
if @align_right
|
|
3452
|
+
@text_offset = 0
|
|
3453
|
+
@col_offset = dtext.length + @surround_chars[0].length + 1
|
|
3454
|
+
return "#{dtext} " + @surround_chars[0] + buttontext + @surround_chars[1]
|
|
3455
|
+
else
|
|
3456
|
+
pretext = @surround_chars[0] + buttontext + @surround_chars[1]
|
|
3457
|
+
@text_offset = pretext.length + 1
|
|
3458
|
+
@col_offset = @surround_chars[0].length
|
|
3459
|
+
#@surround_chars[0] + buttontext + @surround_chars[1] + " #{@text}"
|
|
3460
|
+
return pretext + " #{dtext}"
|
|
3461
|
+
end
|
|
3462
|
+
end
|
|
3463
|
+
end # class # }}}
|
|
3464
|
+
|
|
3465
|
+
# This is not a visual class or a widget.
|
|
3466
|
+
# This class allows us to attach several RadioButtons to it, so it can maintain which one is the
|
|
3467
|
+
# selected one. It also allows for assigning of commands to be executed whenever a button is pressed,
|
|
3468
|
+
# akin to binding to the +fire+ of the button, except that one would not have to bind to each button,
|
|
3469
|
+
# but only once here.
|
|
3470
|
+
#
|
|
3471
|
+
# @example
|
|
3472
|
+
# group = ButtonGroup.new
|
|
3473
|
+
# group.add(r1).add(r2).add(r3)
|
|
3474
|
+
# # change the color of +somelabel+ to the color specified by the value of clicked radio.
|
|
3475
|
+
# group.command(somelabel) do |grp, label| label.color(grp.value); end
|
|
3476
|
+
#
|
|
3477
|
+
# Added on 2014-04-30
|
|
3478
|
+
class ButtonGroup # -- {{{
|
|
3479
|
+
|
|
3480
|
+
# Array of buttons that have been added.
|
|
3481
|
+
attr_reader :elements
|
|
3482
|
+
|
|
3483
|
+
# the value of the radio button that is selected. To get the button itself, use +selection+.
|
|
3484
|
+
attr_reader :value
|
|
3485
|
+
def initialize
|
|
3486
|
+
@elements = []
|
|
3487
|
+
@hash = {}
|
|
3488
|
+
end
|
|
3489
|
+
|
|
3490
|
+
def add e
|
|
3491
|
+
@elements << e
|
|
3492
|
+
@hash[e.value] = e
|
|
3493
|
+
e.variable(self)
|
|
3494
|
+
self
|
|
3495
|
+
end
|
|
3496
|
+
def remove e
|
|
3497
|
+
@elements.delete e
|
|
3498
|
+
@hash.delete e.value
|
|
3499
|
+
self
|
|
3500
|
+
end
|
|
3501
|
+
|
|
3502
|
+
# @return the radiobutton that is selected
|
|
3503
|
+
def selection
|
|
3504
|
+
@hash[@value]
|
|
3505
|
+
end
|
|
3506
|
+
|
|
3507
|
+
# @param [String, RadioButton] +value+ of a button, or +Button+ itself to check if selected.
|
|
3508
|
+
# @return [true or false] for wether the given value or button is the selected one
|
|
3509
|
+
def selected? val
|
|
3510
|
+
if val.is_a? String
|
|
3511
|
+
@value == val
|
|
3512
|
+
else
|
|
3513
|
+
@hash[@value] == val
|
|
3514
|
+
end
|
|
3515
|
+
end
|
|
3516
|
+
# install trigger to call whenever a value is updated
|
|
3517
|
+
# @public called by user components
|
|
3518
|
+
def command *args, &block
|
|
3519
|
+
@commands ||= []
|
|
3520
|
+
@args ||= []
|
|
3521
|
+
@commands << block
|
|
3522
|
+
@args << args
|
|
3523
|
+
end
|
|
3524
|
+
# select the given button or value.
|
|
3525
|
+
# This may be called by user programs to programmatically select a button
|
|
3526
|
+
def select button
|
|
3527
|
+
if button.is_a? String
|
|
3528
|
+
;
|
|
3529
|
+
else
|
|
3530
|
+
button = button.value
|
|
3531
|
+
end
|
|
3532
|
+
set_value button
|
|
3533
|
+
end
|
|
3534
|
+
|
|
3535
|
+
# when a radio button is pressed, it calls set_value giving the value of that radio.
|
|
3536
|
+
# it also gives the name (optionally) since Variables can also be passed and be used across
|
|
3537
|
+
# groups. Here, since a button group is only for one group, so we discard name.
|
|
3538
|
+
# @param [String] value (text) of radio button that is selected
|
|
3539
|
+
#
|
|
3540
|
+
# This is used by RadioButton class for backward compat with Variable.
|
|
3541
|
+
def set_value value, name=nil
|
|
3542
|
+
@value = value
|
|
3543
|
+
return unless @commands
|
|
3544
|
+
@commands.each_with_index do |comm, ix|
|
|
3545
|
+
comm.call(self, *@args[ix]) unless comm.nil?
|
|
3546
|
+
end
|
|
3547
|
+
end
|
|
3548
|
+
# returns the value of the selected button
|
|
3549
|
+
# NOTE: This is used by RadioButton class for backward compat with Variable.
|
|
3550
|
+
# User programs should use +value()+
|
|
3551
|
+
def get_value name=nil
|
|
3552
|
+
@value
|
|
3553
|
+
end
|
|
3554
|
+
end # -- }}}
|
|
3555
|
+
##
|
|
3556
|
+
# A selectable button that has a text value. It is based on a Variable that
|
|
3557
|
+
# is shared by other radio buttons. Only one is selected at a time, unlike checkbox
|
|
3558
|
+
# +text+ is the value to display, which can include an ampersand for a hotkey
|
|
3559
|
+
# +value+ is the value returned if selected, which usually is similar to text (or a short word)
|
|
3560
|
+
# +width+ is helpful if placing the brackets to right of text, used to align round brackets
|
|
3561
|
+
# By default, radio buttons place the button on the left of the text.
|
|
3562
|
+
#
|
|
3563
|
+
# Typically, the variable's update_command is passed a block to execute whenever any of the
|
|
3564
|
+
# radiobuttons of this group is fired.
|
|
3565
|
+
|
|
3566
|
+
class RadioButton < ToggleButton # {{{
|
|
3567
|
+
dsl_accessor :align_right # the button will be on the right 2008-12-09 23:41
|
|
3568
|
+
# if a variable has been defined, off and on value will be set in it (default 0,1)
|
|
3569
|
+
def initialize form, config={}, &block
|
|
3570
|
+
@surround_chars = ['(', ')'] if @surround_chars.nil?
|
|
3571
|
+
super
|
|
3572
|
+
$log.warn "XXX: FIXME Please set 'value' for radiobutton. If not sure, try setting it to the same value as 'text'" unless @value
|
|
3573
|
+
# I am setting value of value here if not set 2011-10-21
|
|
3574
|
+
@value ||= @text
|
|
3575
|
+
## trying with off since i can't do conventional style construction
|
|
3576
|
+
#raise "A single Variable must be set for a group of Radio Buttons for this to work." unless @variable
|
|
3577
|
+
end
|
|
3578
|
+
|
|
3579
|
+
# all radio buttons will return the value of the selected value, not the offered value
|
|
3580
|
+
def getvalue
|
|
3581
|
+
@variable.get_value @name
|
|
3582
|
+
end
|
|
3583
|
+
|
|
3584
|
+
def getvalue_for_paint
|
|
3585
|
+
buttontext = getvalue() == @value ? "o" : " "
|
|
3586
|
+
dtext = @width.nil? ? text : "%-*s" % [@width, text]
|
|
3587
|
+
if @align_right
|
|
3588
|
+
@text_offset = 0
|
|
3589
|
+
@col_offset = dtext.length + @surround_chars[0].length + 1
|
|
3590
|
+
return "#{dtext} " + @surround_chars[0] + buttontext + @surround_chars[1]
|
|
3591
|
+
else
|
|
3592
|
+
pretext = @surround_chars[0] + buttontext + @surround_chars[1]
|
|
3593
|
+
@text_offset = pretext.length + 1
|
|
3594
|
+
@col_offset = @surround_chars[0].length
|
|
3595
|
+
return pretext + " #{dtext}"
|
|
3596
|
+
end
|
|
3597
|
+
end
|
|
3598
|
+
|
|
3599
|
+
def toggle
|
|
3600
|
+
@variable.set_value @value, @name
|
|
3601
|
+
# call fire of button class 2008-12-09 17:49
|
|
3602
|
+
fire
|
|
3603
|
+
end
|
|
3604
|
+
|
|
3605
|
+
# added for bindkeys since that calls fire, not toggle - XXX i don't like this
|
|
3606
|
+
def fire
|
|
3607
|
+
@variable.set_value @value, @name
|
|
3608
|
+
super
|
|
3609
|
+
end
|
|
3610
|
+
|
|
3611
|
+
##
|
|
3612
|
+
# ideally this should not be used. But implemented for completeness.
|
|
3613
|
+
# it is recommended to toggle some other radio button than to uncheck this.
|
|
3614
|
+
def checked tf
|
|
3615
|
+
if tf
|
|
3616
|
+
toggle
|
|
3617
|
+
elsif !@variable.nil? and getvalue() != @value # XXX ???
|
|
3618
|
+
@variable.set_value "", ""
|
|
3619
|
+
end
|
|
3620
|
+
end
|
|
3621
|
+
end # class radio # }}}
|
|
3622
|
+
|
|
3623
|
+
# unused to my knowledge
|
|
3624
|
+
def self.startup
|
|
3625
|
+
raise "startup seems to be unused. remove this line if used, else remove method by next version"
|
|
3626
|
+
Canis::start_ncurses
|
|
3627
|
+
path = File.join(ENV["LOGDIR"] || "./" ,"canis14.log")
|
|
3628
|
+
file = File.open(path, File::WRONLY|File::TRUNC|File::CREAT)
|
|
3629
|
+
$log = Logger.new(path)
|
|
3630
|
+
$log.level = Logger::DEBUG
|
|
3631
|
+
end
|
|
3632
|
+
|
|
3633
|
+
end # module
|
|
3634
|
+
include Canis::Utils
|