rbcurse-experimental 0.0.0
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.
- data/README.markdown +75 -0
- data/VERSION +1 -0
- data/examples/teststackflow.rb +113 -0
- data/lib/rbcurse/experimental/widgets/directorylist.rb +467 -0
- data/lib/rbcurse/experimental/widgets/directorytree.rb +69 -0
- data/lib/rbcurse/experimental/widgets/masterdetail.rb +166 -0
- data/lib/rbcurse/experimental/widgets/multiform.rb +330 -0
- data/lib/rbcurse/experimental/widgets/resultsetbrowser.rb +281 -0
- data/lib/rbcurse/experimental/widgets/resultsettextview.rb +586 -0
- data/lib/rbcurse/experimental/widgets/rscrollform.rb +418 -0
- data/lib/rbcurse/experimental/widgets/stackflow.rb +478 -0
- data/lib/rbcurse/experimental/widgets/undomanager.rb +188 -0
- metadata +89 -0
@@ -0,0 +1,281 @@
|
|
1
|
+
require 'rbcurse/experimental/widgets/rscrollform'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
# See tabbed pane, and add_cols nad add_rows, on_entr and set_form_row to see cursor issue
|
5
|
+
# remove main form
|
6
|
+
# don't make a widget just an object
|
7
|
+
# let it create own form.
|
8
|
+
|
9
|
+
# NOTE: experimental, not yet firmed up
|
10
|
+
# If you use in application, please copy to some application folder in case i change this.
|
11
|
+
# Can be used for print_help_page
|
12
|
+
# SUGGESTIONS WELCOME.
|
13
|
+
# @since 1.4.1
|
14
|
+
module RubyCurses
|
15
|
+
|
16
|
+
class ResultsetBrowser #< Widget
|
17
|
+
#include EventHandler
|
18
|
+
include ConfigSetup
|
19
|
+
include RubyCurses::Utils
|
20
|
+
#dsl_property :xxx
|
21
|
+
# I don't think width is actually used, windows width matters. What of height ?
|
22
|
+
# THATS WRONG , i cannot eat up window. I should use object dimentsion for pad,
|
23
|
+
# not window dimensions
|
24
|
+
dsl_accessor :row, :col, :height, :width
|
25
|
+
dsl_accessor :should_print_border
|
26
|
+
|
27
|
+
def initialize win, config={}, &block
|
28
|
+
@should_print_border = true
|
29
|
+
@v_window = win #form.window
|
30
|
+
@window = @v_window
|
31
|
+
@focusable = true
|
32
|
+
@editable = true
|
33
|
+
@old_index = @current_index = 0
|
34
|
+
@fields = nil
|
35
|
+
config_setup config
|
36
|
+
instance_eval &block if block_given?
|
37
|
+
init_vars
|
38
|
+
end
|
39
|
+
def map_keys
|
40
|
+
@v_form.bind_key(?\C-n) { @current_index += 1 if @current_index < @rows.count-1 }
|
41
|
+
@v_form.bind_key(?\C-p) { @current_index -= 1 if @current_index > 0 }
|
42
|
+
@mapped = true
|
43
|
+
end
|
44
|
+
def init_vars
|
45
|
+
@row ||= 0
|
46
|
+
@col ||= 0
|
47
|
+
@height ||= 15
|
48
|
+
@width ||= 50
|
49
|
+
@field_offset = 14 # where actual Field should start (leaving space for label)
|
50
|
+
@row_offset ||= 0
|
51
|
+
@col_offset ||= 1
|
52
|
+
@border_offset = 0
|
53
|
+
if @should_print_border
|
54
|
+
@border_offset = 1
|
55
|
+
end
|
56
|
+
@v_form = RubyCurses::ScrollForm.new @v_window
|
57
|
+
@v_form.display_h(@height-1-@border_offset*2) if @height
|
58
|
+
@v_form.display_w(@width-1-@border_offset*2) if @width
|
59
|
+
end
|
60
|
+
def data=(rows)
|
61
|
+
@rows = rows
|
62
|
+
end
|
63
|
+
def columns=(columns)
|
64
|
+
@columns = columns
|
65
|
+
h = @columns.count + 2
|
66
|
+
w = 150
|
67
|
+
#row = 1
|
68
|
+
#col = 1
|
69
|
+
row = @row + @border_offset
|
70
|
+
col = @col + @border_offset
|
71
|
+
@v_form.set_pad_dimensions(row, col, h, w)
|
72
|
+
@v_form.should_print_border(false) # this should use dimensions of object not window. # 2011-10-12 15:48:14
|
73
|
+
# currently I don't have space for any buttons or anything. The form takes all space of the window
|
74
|
+
# not of the object defined.
|
75
|
+
# I should be able to tell Scrollform to use only thismuch of window.
|
76
|
+
end
|
77
|
+
def set_form_row
|
78
|
+
f = @v_form.get_current_field
|
79
|
+
f.set_form_row
|
80
|
+
end
|
81
|
+
def handle_key ch
|
82
|
+
map_keys unless @mapped
|
83
|
+
$log.debug "XXX: RB HK got ch "
|
84
|
+
ret = @v_form.handle_key ch
|
85
|
+
#set_form_row
|
86
|
+
if ret == :UNHANDLED
|
87
|
+
@v_form.process_key ch, self
|
88
|
+
end
|
89
|
+
repaint
|
90
|
+
@v_window.wrefresh
|
91
|
+
end
|
92
|
+
def repaint
|
93
|
+
@fields ||= _create_fields
|
94
|
+
#alert "old #{@old_index} , #{@current_index} "
|
95
|
+
if @old_index != @current_index
|
96
|
+
#alert "index change"
|
97
|
+
row = @rows[@current_index]
|
98
|
+
@columns.each_with_index { |e, i|
|
99
|
+
value = row[i]
|
100
|
+
len = value.to_s.length
|
101
|
+
type=@rows[0].types[i]
|
102
|
+
if type == "TEXT"
|
103
|
+
value = value.gsub(/\n/," ") if value
|
104
|
+
end
|
105
|
+
f = @fields[i]
|
106
|
+
@fields[i].set_buffer(value)
|
107
|
+
if f.display_length < len && len < (@width - @field_offset)
|
108
|
+
@fields[i].display_length len
|
109
|
+
end
|
110
|
+
}
|
111
|
+
@v_form.repaint
|
112
|
+
@window.wrefresh
|
113
|
+
Ncurses::Panel.update_panels
|
114
|
+
@old_index = @current_index
|
115
|
+
end
|
116
|
+
end
|
117
|
+
# maybe not required since we don't have 2 forms now
|
118
|
+
def unused_on_enter
|
119
|
+
if $current_key == KEY_BTAB
|
120
|
+
c = @v_form.widgets.count-1
|
121
|
+
@v_form.select_field c
|
122
|
+
else
|
123
|
+
@v_form.select_field 0
|
124
|
+
end
|
125
|
+
end
|
126
|
+
private
|
127
|
+
def _create_fields
|
128
|
+
color = $datacolor
|
129
|
+
if @should_print_border
|
130
|
+
@v_window.print_border @row, @col, @height-1, @width, color #, Ncurses::A_REVERSE
|
131
|
+
@row_offset += 1
|
132
|
+
@col_offset += 1
|
133
|
+
end
|
134
|
+
$log.debug "XXX: ROWS#{@rows}"
|
135
|
+
$log.debug "XXX: COLS#{@columns}"
|
136
|
+
$log.debug "XXX: row#{@rows[@current_index]}"
|
137
|
+
fields = []
|
138
|
+
r = @row + @row_offset # row was for where to print the total object, not this
|
139
|
+
c = @col + @col_offset + @field_offset # 14 is to leave space for labels
|
140
|
+
v_form = @v_form
|
141
|
+
@columns.each_with_index { |e, index|
|
142
|
+
#break if index >= @height-1 # create only as much space we have, SUCKS but just trying till be scroll
|
143
|
+
#$log.debug "XXX: #{r} #{c} EACH #{e}, #{index}, #{@rows[@current_index][index]}"
|
144
|
+
value=@rows[@current_index][index]
|
145
|
+
type=@rows[0].types[index]
|
146
|
+
if type == "TEXT"
|
147
|
+
value = value.gsub(/\n/," ") if value
|
148
|
+
end
|
149
|
+
len = [value.to_s.length, (@width - @field_offset)].min
|
150
|
+
f = Field.new v_form do
|
151
|
+
name e
|
152
|
+
row r
|
153
|
+
col c
|
154
|
+
bgcolor 'blue'
|
155
|
+
highlight_background 'cyan'
|
156
|
+
set_buffer value
|
157
|
+
display_length len
|
158
|
+
set_label Label.new v_form, {'text' => e, 'color'=>'cyan'}
|
159
|
+
end
|
160
|
+
fields << f
|
161
|
+
r += 1
|
162
|
+
}
|
163
|
+
@v_form.repaint
|
164
|
+
@window.wrefresh
|
165
|
+
Ncurses::Panel.update_panels
|
166
|
+
#$log.debug "XXX: created fields "
|
167
|
+
return fields
|
168
|
+
end
|
169
|
+
|
170
|
+
# ADD HERE
|
171
|
+
|
172
|
+
end # class
|
173
|
+
end # module
|
174
|
+
module RubyCurses
|
175
|
+
# a data viewer for viewing some text or filecontents
|
176
|
+
# view filename, :close_key => KEY_RETURN
|
177
|
+
# send data in an array
|
178
|
+
# view Array, :close_key => KEY_RETURN, :layout => [0,0,23,80]
|
179
|
+
# when passing layout reserve 4 rows for window and border. So for 2 lines of text
|
180
|
+
# give 6 rows.
|
181
|
+
class Browser
|
182
|
+
def self.browse_sql dbname, tablename, sql, config={} #:yield: ???
|
183
|
+
raise "file not found" unless File.exist? dbname
|
184
|
+
require 'sqlite3'
|
185
|
+
db = SQLite3::Database.new(dbname)
|
186
|
+
columns, *rows = db.execute2(sql)
|
187
|
+
#$log.debug "XXX COLUMNS #{sql} "
|
188
|
+
content = rows
|
189
|
+
return nil if content.nil? or content[0].nil?
|
190
|
+
datatypes = content[0].types
|
191
|
+
self.browse db, tablename, columns, rows, config
|
192
|
+
end
|
193
|
+
# @param filename as string or content as array
|
194
|
+
# @yield textview object for further configuration before display
|
195
|
+
# NOTE: i am experimentally yielding textview object so i could supress borders
|
196
|
+
# just for kicks, but on can also bind_keys or events if one wanted.
|
197
|
+
#def self.view what, config={} #:yield: textview
|
198
|
+
def self.browse dbconn, tablename, columns, rows, config={} #:yield: ???
|
199
|
+
wt = 0 # top margin
|
200
|
+
wl = 0 # left margin
|
201
|
+
wh = Ncurses.LINES-wt-3 # height, goes to bottom of screen
|
202
|
+
ww = Ncurses.COLS-wl-3 # width, goes to right end
|
203
|
+
wt, wl, wh, ww = config[:layout] if config.has_key? :layout
|
204
|
+
|
205
|
+
fp = config[:title] || ""
|
206
|
+
pf = config.fetch(:print_footer, true)
|
207
|
+
ta = config.fetch(:title_attrib, 'bold')
|
208
|
+
fa = config.fetch(:footer_attrib, 'bold')
|
209
|
+
|
210
|
+
wh = Ncurses.LINES-0
|
211
|
+
ww = Ncurses.COLS-0
|
212
|
+
layout = { :height => wh, :width => ww, :top => wt, :left => wl }
|
213
|
+
#v_window = config[:window] # using previous window cause crash seg fault
|
214
|
+
#v_window ||= VER::Window.new(layout) # copywin gives -1 and prints nothing
|
215
|
+
v_window = VER::Window.root_window
|
216
|
+
v_window.printstring 0, 30, "Database Browser Demo", $datacolor
|
217
|
+
@form = RubyCurses::Form.new v_window # only for some widgets that are not editable
|
218
|
+
header = app_header "rbcurse ", :text_center => "ResultsetBrowser Demo", :text_right =>"New Improved!", :color => :black, :bgcolor => :white, :attr => :bold
|
219
|
+
sl = status_line :row => v_window.height == 0 ? Ncurses.LINES-1 : v.window.height-1
|
220
|
+
sl.command { "Record Navigation: C-n C-p. Scrolling M-n, M-p, M-l, M-h" }
|
221
|
+
|
222
|
+
@form.repaint
|
223
|
+
|
224
|
+
#rb = ResultsetBrowser.new v_form, :row => 2, :col => 2
|
225
|
+
rb = ResultsetBrowser.new v_window, :row => 2, :col => 2, :height => 20, :width => 95
|
226
|
+
rb.columns = columns
|
227
|
+
rb.data = rows
|
228
|
+
rb.repaint
|
229
|
+
|
230
|
+
|
231
|
+
# yielding textview so you may further configure or bind keys or events
|
232
|
+
begin
|
233
|
+
#v_form.repaint
|
234
|
+
v_window.wrefresh
|
235
|
+
Ncurses::Panel.update_panels
|
236
|
+
# allow closing using q and Ctrl-q in addition to any key specified
|
237
|
+
# user should not need to specify key, since that becomes inconsistent across usages
|
238
|
+
while((ch = v_window.getchar()) != ?\C-q.getbyte(0) )
|
239
|
+
break if ch == config[:close_key]
|
240
|
+
rb.handle_key ch
|
241
|
+
#v_form.handle_key ch
|
242
|
+
end
|
243
|
+
rescue => err
|
244
|
+
$log.error err.to_s
|
245
|
+
$log.error err.backtrace.join("\n")
|
246
|
+
alert err.to_s
|
247
|
+
|
248
|
+
ensure
|
249
|
+
v_window.destroy if !v_window.nil?
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end # class
|
253
|
+
end # module
|
254
|
+
if __FILE__ == $PROGRAM_NAME
|
255
|
+
require 'rbcurse/core/util/app'
|
256
|
+
|
257
|
+
App.new do
|
258
|
+
header = app_header "rbcurse ", :text_center => "ResultsetBrowser Demo", :text_right =>"New Improved!", :color => :black, :bgcolor => :white, :attr => :bold
|
259
|
+
message "Press F10 to exit from here"
|
260
|
+
columns = ["Name","Age","City", "Country"]
|
261
|
+
data = [
|
262
|
+
[ "Rahul",31, "Delhi","India"],
|
263
|
+
[ "Dev",35, "Mumbai","India"],
|
264
|
+
[ "Jobs",56, "L.A","U.S.A"],
|
265
|
+
[ "Matz",40, "Tokyo","Nippon"]
|
266
|
+
]
|
267
|
+
|
268
|
+
#RubyCurses::Browser.browse("dummy", "atable", columns, data, :close_key => FFI::NCurses::KEY_F10, :title => "Enter to close") do |t|
|
269
|
+
sql = "select id, type, priority, title from bugs"
|
270
|
+
sql = "select * from bugs"
|
271
|
+
RubyCurses::Browser.browse_sql("../../../bugzy.sqlite", "bugs", sql, :close_key => FFI::NCurses::KEY_F10, :title => "Enter to close", :window => @window) do |t|
|
272
|
+
# you may configure textview further here.
|
273
|
+
#t.suppress_borders true
|
274
|
+
#t.color = :black
|
275
|
+
#t.bgcolor = :white
|
276
|
+
# or
|
277
|
+
#t.attr = :reverse
|
278
|
+
end
|
279
|
+
|
280
|
+
end # app
|
281
|
+
end
|
@@ -0,0 +1,586 @@
|
|
1
|
+
=begin
|
2
|
+
* Name: ResultsetTextView
|
3
|
+
* Description View text in this widget.
|
4
|
+
* Author: rkumar (arunachalesha)
|
5
|
+
* file created 2009-01-08 15:23
|
6
|
+
* major change: 2010-02-10 19:43 simplifying the buffer stuff.
|
7
|
+
* major change: 2011-10-14 reducing repaint, calling only if scrolled
|
8
|
+
also, printing row focus and selection outside of repaint so only 2 rows affected.
|
9
|
+
--------
|
10
|
+
* License:
|
11
|
+
Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
12
|
+
|
13
|
+
=end
|
14
|
+
require 'rbcurse/core/widgets/rtextview'
|
15
|
+
require 'rbcurse/core/util/bottomline' # for ask()
|
16
|
+
|
17
|
+
include RubyCurses
|
18
|
+
module RubyCurses
|
19
|
+
extend self
|
20
|
+
|
21
|
+
##
|
22
|
+
# An extension of textview that allows viewing a resultset, one record
|
23
|
+
# at a time, paging records.
|
24
|
+
|
25
|
+
class ResultsetTextView < TextView
|
26
|
+
|
27
|
+
# The offset of the current record in the resultset, starting 0
|
28
|
+
attr_accessor :current_record
|
29
|
+
|
30
|
+
# not used, and may not be used since i would have to reserve on more column for this
|
31
|
+
#dsl_accessor :row_selected_symbol
|
32
|
+
# Should the row focussed on show a highlight or not, default is false
|
33
|
+
# By default, the cursor will be placed on focussed row.
|
34
|
+
dsl_accessor :should_show_focus
|
35
|
+
#
|
36
|
+
# Attribute of selected row 'reverse' 'bold'
|
37
|
+
# By default, it is reverse.
|
38
|
+
dsl_accessor :selected_attrib
|
39
|
+
# Attribute of focussed row 'reverse' 'bold'
|
40
|
+
# By default, it is not set.
|
41
|
+
dsl_accessor :focussed_attrib # attribute of focussed row 'bold' 'underline'
|
42
|
+
dsl_accessor :editing_allowed # can user edit values and store in database
|
43
|
+
|
44
|
+
def initialize form = nil, config={}, &block
|
45
|
+
@row_offset = @col_offset = 1
|
46
|
+
@row = 0
|
47
|
+
@col = 0
|
48
|
+
@should_show_focus = false # don;t show focus and unfocus by default
|
49
|
+
@list = [] # this is only the currently visible record
|
50
|
+
@rows = nil # this is the entire resultset
|
51
|
+
@old_record = @current_record = 0 # index of currently displayed record from resultset
|
52
|
+
@editing_allowed = true
|
53
|
+
super
|
54
|
+
@win = @graphic
|
55
|
+
@datatypes = nil; # to be set when we query data, an array one for each column
|
56
|
+
|
57
|
+
@widget_scrolled = true
|
58
|
+
@record_changed = false
|
59
|
+
|
60
|
+
@help_text = "C to edit a column, Navigation: M-lhjk,jk gg G, Space. Next Record M-period or period, Previous M-comma or comma. Last Record '>' First Record '<' <Enter> View sortable tabular view"
|
61
|
+
bind(:PRESS){ |eve|
|
62
|
+
s = eve.source
|
63
|
+
r = s.current_record
|
64
|
+
col = @columns[@current_index]
|
65
|
+
#alert "You clicked on #{r} , #{col} , #{eve.text} "
|
66
|
+
#edit_record
|
67
|
+
}
|
68
|
+
#@selected_attrib = 'standout'
|
69
|
+
#@focussed_attrib = 'underline'
|
70
|
+
end
|
71
|
+
def edit_record
|
72
|
+
unless @editing_allowed
|
73
|
+
say "You clicked on #{r} , #{col} , #{eve.text}. If editing_allowed was true you could have modified the db "
|
74
|
+
return
|
75
|
+
end
|
76
|
+
col = @columns[@current_index]
|
77
|
+
text = @rows[@current_record][@current_index]
|
78
|
+
value = ask("Edit #{col}: "){ |q| q.default = text }
|
79
|
+
if value && value != "" && value != text
|
80
|
+
@rows[@current_record][@current_index] = value
|
81
|
+
@widget_scrolled = true # force repaint of data
|
82
|
+
begin
|
83
|
+
sql_update @tablename, id=@rows[@current_record][0], col, value
|
84
|
+
say_with_wait "Update to database successful"
|
85
|
+
rescue => err
|
86
|
+
alert "UPDATE ERROR:#{err.to_s} "
|
87
|
+
end
|
88
|
+
else
|
89
|
+
say_with_pause "Editing aborted", :color_pair => $errorcolor
|
90
|
+
end
|
91
|
+
end
|
92
|
+
##
|
93
|
+
# update a row from bugs based on id, giving one fieldname and value
|
94
|
+
# @param [Fixnum] id unique key
|
95
|
+
# @param [String] fieldname
|
96
|
+
# @param [String] value to update
|
97
|
+
# @example sql_update "bugs", 9, :name, "Roger"
|
98
|
+
# I need to know keyfields for 2 reasons , disallow update and use in update XXX
|
99
|
+
def sql_update table, id, field, value
|
100
|
+
# 2010-09-12 11:42 added to_s to now, due to change in sqlite3 1.3.x
|
101
|
+
alert "No database connection" unless @db
|
102
|
+
return unless @db
|
103
|
+
ret = @db.execute( "update #{table} set #{field} = ? where id = ?", [value, id])
|
104
|
+
$log.debug "SQLITE ret value : #{ret}, #{table} #{field} #{id} #{value} "
|
105
|
+
end
|
106
|
+
|
107
|
+
def repaint_all tf
|
108
|
+
super
|
109
|
+
@widget_scrolled = true
|
110
|
+
end
|
111
|
+
def next_record num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
|
112
|
+
@old_record = @current_record
|
113
|
+
@record_changed = true
|
114
|
+
num.times {
|
115
|
+
@current_record += 1 if @current_record < @rows.count-1;
|
116
|
+
}
|
117
|
+
|
118
|
+
@repaint_required = true
|
119
|
+
$multiplier = 0
|
120
|
+
end
|
121
|
+
def previous_record num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
|
122
|
+
@old_record = @current_record
|
123
|
+
@record_changed = true
|
124
|
+
num.times {
|
125
|
+
@current_record -= 1 if @current_record > 0 ;
|
126
|
+
}
|
127
|
+
@repaint_required = true
|
128
|
+
end
|
129
|
+
def last_record
|
130
|
+
@old_record = @current_record
|
131
|
+
@record_changed = true
|
132
|
+
@current_record = @rows.count-1; @repaint_required = true
|
133
|
+
end
|
134
|
+
def first_record
|
135
|
+
@old_record = @current_record
|
136
|
+
@record_changed = true
|
137
|
+
@current_record = 0; @repaint_required = true
|
138
|
+
end
|
139
|
+
def map_keys
|
140
|
+
super
|
141
|
+
bind_key([?\C-x, ?6], :scroll_backward)
|
142
|
+
bind_key([?\C-x, ?v], :scroll_forward)
|
143
|
+
bind_key([?\C-x, ?n], :next_record)
|
144
|
+
bind_key([?\C-x, ?p], :previous_record)
|
145
|
+
bind_keys([?\M-,,?,], :previous_record )
|
146
|
+
bind_keys([?\M-.,?.], :next_record)
|
147
|
+
bind_keys([?\M-<,?<], :first_record )
|
148
|
+
bind_keys([?\M->,?>], :last_record)
|
149
|
+
bind_key('C', :edit_record)
|
150
|
+
#bind_key([?\C-x, ?>], :scroll_right)
|
151
|
+
#bind_key([?\C-x, ?<], :scroll_left)
|
152
|
+
#bind_key([?\C-x, ?\C-s], :saveas)
|
153
|
+
#bind_key(?r) { getstr("Enter a word: ") }
|
154
|
+
#bind_key(?m, :disp_menu)
|
155
|
+
end
|
156
|
+
# connect to database, run sql and set data, columns and datatypes
|
157
|
+
# Similar can be done with another database
|
158
|
+
def sqlite dbname, table, sql
|
159
|
+
raise "App error: file not found: #{dbname} " unless File.exist? dbname
|
160
|
+
require 'sqlite3'
|
161
|
+
db = SQLite3::Database.new(dbname)
|
162
|
+
columns, *rows = db.execute2(sql)
|
163
|
+
@db = db # for update
|
164
|
+
@dbname = dbname
|
165
|
+
@tablename = table
|
166
|
+
$log.debug "XXX sql #{sql}, #{rows.count} "
|
167
|
+
content = rows
|
168
|
+
return nil if content.nil? or content[0].nil?
|
169
|
+
self.datatypes = content[0].types
|
170
|
+
set_content rows, columns
|
171
|
+
end
|
172
|
+
##
|
173
|
+
# send in a dataset (array of arrays) and array of column names
|
174
|
+
# e.g. set_content File.open("README.txt","r").readlines
|
175
|
+
# set wrap at time of passing :WRAP_NONE :WRAP_WORD
|
176
|
+
# XXX if we widen the textview later, as in a vimsplit that data
|
177
|
+
# will still be wrapped at this width !!
|
178
|
+
def set_content list, columns
|
179
|
+
@rows = list
|
180
|
+
@columns = columns
|
181
|
+
@current_record = 0
|
182
|
+
init_vars
|
183
|
+
self
|
184
|
+
end
|
185
|
+
def data=(list)
|
186
|
+
@rows = list
|
187
|
+
end
|
188
|
+
def columns=(list)
|
189
|
+
@columns = list
|
190
|
+
end
|
191
|
+
# set array of datatypes, one per column
|
192
|
+
def datatypes=(list)
|
193
|
+
@datatypes = list
|
194
|
+
end
|
195
|
+
def remove_all
|
196
|
+
$log.warn "ResultsetTextView remove_all not yet tested XXX"
|
197
|
+
@list = []
|
198
|
+
@rows = []
|
199
|
+
init_vars
|
200
|
+
@repaint_required = true
|
201
|
+
end
|
202
|
+
|
203
|
+
def repaint # textview :nodoc:
|
204
|
+
#$log.debug "TEXTVIEW repaint r c #{@row}, #{@col} "
|
205
|
+
$log.debug "RESULTSETTEXTVIEW repaint r c #{@row}, #{@col}, key: #{$current_key}, reqd #{@repaint_required} "
|
206
|
+
|
207
|
+
# TRYING OUT dangerous 2011-10-13
|
208
|
+
@repaint_required = false
|
209
|
+
@repaint_required = true if @widget_scrolled || @pcol != @old_pcol || @record_changed
|
210
|
+
|
211
|
+
|
212
|
+
paint if @repaint_required
|
213
|
+
|
214
|
+
print_foot if @print_footer && !@suppress_borders && @repaint_footer_required
|
215
|
+
end
|
216
|
+
def print_foot #:nodoc:
|
217
|
+
return unless @rows
|
218
|
+
@footer_attrib ||= Ncurses::A_DIM
|
219
|
+
gb = get_color($datacolor, 'green','black')
|
220
|
+
footer = "%15s" % " [#{@current_record+1}/ #{@rows.length} ]"
|
221
|
+
$log.debug " print_foot calling printstring with #{@row} + #{@height} -1, #{@col}+2"
|
222
|
+
pos = @col + 2
|
223
|
+
right = true
|
224
|
+
if right
|
225
|
+
pos = @col + @width - footer.length - 1
|
226
|
+
end
|
227
|
+
@graphic.printstring( @row + @height -1 , pos, footer, gb, @footer_attrib)
|
228
|
+
@repaint_footer_required = false # 2010-01-23 22:55
|
229
|
+
end
|
230
|
+
def getvalue
|
231
|
+
@list
|
232
|
+
end
|
233
|
+
# not sure what to return, returning data value
|
234
|
+
def current_value
|
235
|
+
return nil if @list.nil? || @rows.nil?
|
236
|
+
#@list[@current_record][@current_index]
|
237
|
+
@rows[@current_record][@current_index]
|
238
|
+
end
|
239
|
+
def fire_action_event
|
240
|
+
if @current_index == @selected_index
|
241
|
+
@old_selected_index = @current_index
|
242
|
+
unhighlight_row @current_index
|
243
|
+
color_field @current_index
|
244
|
+
@selected_index = nil
|
245
|
+
return
|
246
|
+
end
|
247
|
+
unhighlight_row @selected_index
|
248
|
+
color_field @selected_index
|
249
|
+
@selected_index = @current_index
|
250
|
+
highlight_selected_row
|
251
|
+
@old_selected_index = @selected_index
|
252
|
+
@repaint_required = true
|
253
|
+
super
|
254
|
+
tabular
|
255
|
+
end
|
256
|
+
def handle_key ch #:nodoc:
|
257
|
+
return :UNHANDLED if @rows.nil?
|
258
|
+
super
|
259
|
+
end
|
260
|
+
def tabular
|
261
|
+
require 'rbcurse/core/widgets/tabularwidget'
|
262
|
+
w = Ncurses.COLS
|
263
|
+
h = Ncurses.LINES-1
|
264
|
+
v_window = VER::Window.new(h,w,0,0)
|
265
|
+
v_form = RubyCurses::Form.new v_window
|
266
|
+
tabula = TabularWidget.new v_form, :width => w, :height => h-1, :print_footer => true
|
267
|
+
begin
|
268
|
+
tabula.set_content @rows, @columns
|
269
|
+
yield tabula if block_given?
|
270
|
+
v_form.repaint
|
271
|
+
v_window.wrefresh
|
272
|
+
Ncurses::Panel.update_panels
|
273
|
+
# allow closing using q and Ctrl-q in addition to any key specified
|
274
|
+
# user should not need to specify key, since that becomes inconsistent across usages
|
275
|
+
while((ch = v_window.getchar()) != ?\C-q.getbyte(0) )
|
276
|
+
break if ch == config[:close_key] || ch == ?q.ord
|
277
|
+
# if you've asked for RETURN then i also check for 10 and 13
|
278
|
+
break if (ch == 10 || ch == 13) && config[:close_key] == KEY_RETURN
|
279
|
+
v_form.handle_key ch
|
280
|
+
#v_form.repaint
|
281
|
+
v_window.wrefresh
|
282
|
+
end
|
283
|
+
rescue => err
|
284
|
+
$log.error " Tabularwidget Resultsetview ERROR #{err} "
|
285
|
+
$log.error(err.backtrace.join("\n"))
|
286
|
+
alert err.to_s
|
287
|
+
|
288
|
+
ensure
|
289
|
+
v_window.destroy if !v_window.nil?
|
290
|
+
end
|
291
|
+
end
|
292
|
+
# newly added to check curpos when moving up or down
|
293
|
+
# set cursor on correct column tview
|
294
|
+
def set_form_col col1=@curpos #:nodoc:
|
295
|
+
@cols_panned ||= 0
|
296
|
+
@pad_offset ||= 0 # added 2010-02-11 21:54 since padded widgets get an offset.
|
297
|
+
@curpos = col1
|
298
|
+
maxlen = @maxlen || @width-@internal_width
|
299
|
+
#@curpos = maxlen if @curpos > maxlen
|
300
|
+
if @curpos > maxlen
|
301
|
+
@pcol = @curpos - maxlen
|
302
|
+
@curpos = maxlen - 1
|
303
|
+
@repaint_required = true # this is required so C-e can pan screen
|
304
|
+
else
|
305
|
+
@pcol = 0
|
306
|
+
end
|
307
|
+
# the rest only determines cursor placement
|
308
|
+
win_col = 0 # 2010-02-07 23:19 new cursor stuff
|
309
|
+
col2 = win_col + @col + @col_offset + @curpos + @cols_panned + @pad_offset
|
310
|
+
$log.debug "TV SFC #{@name} setting c to #{col2} #{win_col} #{@col} #{@col_offset} #{@curpos} "
|
311
|
+
#@form.setrowcol @form.row, col
|
312
|
+
setrowcol nil, col2
|
313
|
+
@repaint_footer_required = true
|
314
|
+
end
|
315
|
+
#
|
316
|
+
# prepares row data for paint to print
|
317
|
+
# Creates a string for each row, which is great for textview operation, all of them
|
318
|
+
# work just fine. But does not allow paint to know what part is title and what is
|
319
|
+
# data
|
320
|
+
#
|
321
|
+
def get_content
|
322
|
+
return nil unless @rows
|
323
|
+
id = @current_record
|
324
|
+
|
325
|
+
row = @rows[id]
|
326
|
+
@lens = []
|
327
|
+
a = []
|
328
|
+
f = "%14s %-*s"
|
329
|
+
#f = "%14s %-20s"
|
330
|
+
@columns.each_with_index { |e, i|
|
331
|
+
value = row[i]
|
332
|
+
len = value.to_s.length
|
333
|
+
type = @datatypes[i]
|
334
|
+
if type == "TEXT"
|
335
|
+
value = value.gsub(/\n/," ") if value
|
336
|
+
end
|
337
|
+
@lens << len
|
338
|
+
a << f % [e, len, value]
|
339
|
+
}
|
340
|
+
@list = a # this keeps it compatible with textview operations.
|
341
|
+
return a
|
342
|
+
end
|
343
|
+
|
344
|
+
## NOTE: earlier print_border was called only once in constructor, but when
|
345
|
+
##+ a window is resized, and destroyed, then this was never called again, so the
|
346
|
+
##+ border would not be seen in splitpane unless the width coincided exactly with
|
347
|
+
##+ what is calculated in divider_location.
|
348
|
+
def paint #:nodoc:
|
349
|
+
|
350
|
+
$log.debug "XXX TEXTVIEW PAINT HAPPENING #{@current_index} "
|
351
|
+
#@left_margin ||= @row_selected_symbol.length
|
352
|
+
@left_margin = 0
|
353
|
+
@fieldbgcolor ||= get_color($datacolor,@bgcolor, 'cyan')
|
354
|
+
my_win = nil
|
355
|
+
if @form
|
356
|
+
my_win = @form.window
|
357
|
+
else
|
358
|
+
my_win = @target_window
|
359
|
+
end
|
360
|
+
@graphic = my_win unless @graphic
|
361
|
+
@win_left = my_win.left
|
362
|
+
@win_top = my_win.top
|
363
|
+
|
364
|
+
print_borders if (@suppress_borders == false && @repaint_all) # do this once only, unless everything changes
|
365
|
+
maxlen = @maxlen || @width-@internal_width
|
366
|
+
#$log.debug " #{@name} textview repaint width is #{@width}, height is #{@height} , maxlen #{maxlen}/ #{@maxlen}, #{@graphic.name} roff #{@row_offset} coff #{@col_offset}"
|
367
|
+
tm = get_content
|
368
|
+
return unless tm # no data
|
369
|
+
rc = tm.size # row_count
|
370
|
+
tr = @toprow
|
371
|
+
acolor = get_color $datacolor
|
372
|
+
h = scrollatrow()
|
373
|
+
r,c = rowcol
|
374
|
+
@longest_line = @width-@internal_width #maxlen
|
375
|
+
$log.debug "XXX: SELECTED ROW IS #{@selected_index} "
|
376
|
+
0.upto(h) do |hh|
|
377
|
+
crow = tr+hh
|
378
|
+
if crow < rc
|
379
|
+
focussed = @current_index == crow # row focussed ?
|
380
|
+
selected = is_row_selected crow
|
381
|
+
content = tm[crow]
|
382
|
+
content = content.dup
|
383
|
+
sanitize content if @sanitization_required
|
384
|
+
truncate content
|
385
|
+
|
386
|
+
@graphic.printstring r+hh, c+@left_margin, "%-*s" % [@width-@internal_width,content], acolor, @attr
|
387
|
+
|
388
|
+
if selected
|
389
|
+
#print_selected_row r+hh, c+@left_margin, content, acolor
|
390
|
+
highlight_selected_row r+hh, c
|
391
|
+
#@graphic.printstring r+hh, c+@left_margin, "%-*s" % [@width-@internal_width,content], acolor, @focussed_attrib || 'reverse'
|
392
|
+
elsif focussed
|
393
|
+
# i am keeping this here just since sometimes repaint gets called
|
394
|
+
highlight_focussed_row :FOCUSSED, r+hh, c
|
395
|
+
end
|
396
|
+
#print_focussed_row :FOCUSSED, nil, nil, content, acolor
|
397
|
+
#@graphic.printstring r+hh, c+@left_margin, "%-*s" % [@width-@internal_width,content], acolor, @focussed_attrib || 'bold'
|
398
|
+
|
399
|
+
color_field crow
|
400
|
+
=begin
|
401
|
+
# paint field portion separately, take care of when panned
|
402
|
+
# hl only field length, not whole thing.
|
403
|
+
startpoint = [c+14+1-@pcol,c].max # don't let it go < 0
|
404
|
+
clen = @lens[crow]
|
405
|
+
# take into account when we've scrolled off right
|
406
|
+
clen -= @pcol-14-1 if 14+1-@pcol < 0
|
407
|
+
hlwidth = [clen,@width-@internal_width-14-1+@pcol, @width-@internal_width].min
|
408
|
+
hlwidth = 0 if hlwidth < 0
|
409
|
+
|
410
|
+
@graphic.mvchgat(y=r+hh, x=startpoint, hlwidth, Ncurses::A_NORMAL, @fieldbgcolor, nil)
|
411
|
+
#@graphic.mvchgat(y=r+hh, x=startpoint, hlwidth, Ncurses::A_BOLD, acolor, nil)
|
412
|
+
=end
|
413
|
+
|
414
|
+
# highlighting search results.
|
415
|
+
if @search_found_ix == tr+hh
|
416
|
+
if !@find_offset.nil?
|
417
|
+
# handle exceed bounds, and if scrolling
|
418
|
+
if @find_offset1 < maxlen+@pcol and @find_offset > @pcol
|
419
|
+
@graphic.mvchgat(y=r+hh, x=c+@find_offset-@pcol, @find_offset1-@find_offset, Ncurses::A_NORMAL, $reversecolor, nil)
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
else
|
424
|
+
# clear rows
|
425
|
+
@graphic.printstring r+hh, c, " " * (@width-@internal_width), acolor,@attr
|
426
|
+
end
|
427
|
+
|
428
|
+
end
|
429
|
+
@repaint_required = false
|
430
|
+
@repaint_footer_required = true
|
431
|
+
@repaint_all = false
|
432
|
+
# 2011-10-13
|
433
|
+
@widget_scrolled = false
|
434
|
+
@record_changed = false
|
435
|
+
@old_pcol = @pcol
|
436
|
+
|
437
|
+
end
|
438
|
+
def color_field index
|
439
|
+
return unless index
|
440
|
+
_r,c = rowcol
|
441
|
+
r = _convert_index_to_printable_row index
|
442
|
+
return unless r
|
443
|
+
# paint field portion separately, take care of when panned
|
444
|
+
# hl only field length, not whole thing.
|
445
|
+
startpoint = [c+14+1-@pcol,c].max # don't let it go < 0
|
446
|
+
clen = @lens[index]
|
447
|
+
# take into account when we've scrolled off right
|
448
|
+
clen -= @pcol-14-1 if 14+1-@pcol < 0
|
449
|
+
hlwidth = [clen,@width-@internal_width-14-1+@pcol, @width-@internal_width].min
|
450
|
+
hlwidth = 0 if hlwidth < 0
|
451
|
+
|
452
|
+
@graphic.mvchgat(y=r, x=startpoint, hlwidth, Ncurses::A_NORMAL, @fieldbgcolor, nil)
|
453
|
+
end
|
454
|
+
# the idea here is to be able to call externally or from loop
|
455
|
+
# However, for that content has to be truncated here, not in loop
|
456
|
+
def DELprint_focussed_row type, r=nil, c=nil, content=nil, acolor=nil
|
457
|
+
return unless @should_show_focus
|
458
|
+
case type
|
459
|
+
when :FOCUSSED
|
460
|
+
r = _convert_index_to_printable_row() unless r
|
461
|
+
attrib = @focussed_attrib || 'bold'
|
462
|
+
ix = @current_index
|
463
|
+
|
464
|
+
when :UNFOCUSSED
|
465
|
+
return if @oldrow.nil? || @oldrow == @current_index
|
466
|
+
r = _convert_index_to_printable_row(@oldrow) unless r
|
467
|
+
return unless r # row is not longer visible
|
468
|
+
ix = @oldrow
|
469
|
+
attrib = @attr
|
470
|
+
end
|
471
|
+
unless c
|
472
|
+
_r, c = rowcol
|
473
|
+
end
|
474
|
+
if content.nil?
|
475
|
+
content = @list[ix]
|
476
|
+
content = content.dup
|
477
|
+
sanitize content if @sanitization_required
|
478
|
+
truncate content
|
479
|
+
end
|
480
|
+
acolor ||= get_color $datacolor
|
481
|
+
#@graphic.printstring r+hh, c+@left_margin, "%-*s" % [@width-@internal_width,content], acolor, @focussed_attrib || 'bold'
|
482
|
+
@graphic.printstring r, c+@left_margin, "%-*s" % [@width-@internal_width, content], acolor, attrib
|
483
|
+
end
|
484
|
+
|
485
|
+
# this only highlights the selcted row, does not print data again
|
486
|
+
# so its safer and should be used instead of print_selected_row
|
487
|
+
def highlight_selected_row r=nil, c=nil, acolor=nil
|
488
|
+
return unless @selected_index # no selection
|
489
|
+
r = _convert_index_to_printable_row(@selected_index) unless r
|
490
|
+
return unless r # not on screen
|
491
|
+
unless c
|
492
|
+
_r, c = rowcol
|
493
|
+
end
|
494
|
+
acolor ||= get_color $datacolor
|
495
|
+
att = FFI::NCurses::A_REVERSE
|
496
|
+
att = get_attrib(@selected_attrib) if @selected_attrib
|
497
|
+
@graphic.mvchgat(y=r, x=c, @width-@internal_width, att , acolor , nil)
|
498
|
+
end
|
499
|
+
def highlight_focussed_row type, r=nil, c=nil, acolor=nil
|
500
|
+
return unless @should_show_focus
|
501
|
+
case type
|
502
|
+
when :FOCUSSED
|
503
|
+
r = _convert_index_to_printable_row() unless r
|
504
|
+
attrib = @focussed_attrib || 'bold'
|
505
|
+
ix = @current_index
|
506
|
+
|
507
|
+
when :UNFOCUSSED
|
508
|
+
return if @oldrow.nil? || @oldrow == @current_index
|
509
|
+
r = _convert_index_to_printable_row(@oldrow) unless r
|
510
|
+
return unless r # row is not longer visible
|
511
|
+
ix = @oldrow
|
512
|
+
attrib = @attr
|
513
|
+
end
|
514
|
+
unless c
|
515
|
+
_r, c = rowcol
|
516
|
+
end
|
517
|
+
acolor ||= get_color $datacolor
|
518
|
+
att = get_attrib(attrib) #if @focussed_attrib
|
519
|
+
@graphic.mvchgat(y=r, x=c, @width-@internal_width, att , acolor , nil)
|
520
|
+
#@graphic.printstring r, c+@left_margin, "%-*s" % [@width-@internal_width,content], acolor, @focussed_attrib || 'reverse'
|
521
|
+
end
|
522
|
+
def unhighlight_row index, r=nil, c=nil, acolor=nil
|
523
|
+
return unless index # no selection
|
524
|
+
r = _convert_index_to_printable_row(index) unless r
|
525
|
+
return unless r # not on screen
|
526
|
+
unless c
|
527
|
+
_r, c = rowcol
|
528
|
+
end
|
529
|
+
acolor ||= get_color $datacolor
|
530
|
+
att = FFI::NCurses::A_NORMAL
|
531
|
+
att = get_attrib(@normal_attrib) if @normal_attrib
|
532
|
+
@graphic.mvchgat(y=r, x=c, @width-@internal_width, att , acolor , nil)
|
533
|
+
end
|
534
|
+
def is_row_selected row
|
535
|
+
@selected_index == row
|
536
|
+
end
|
537
|
+
def on_enter_row arow
|
538
|
+
if @should_show_focus
|
539
|
+
highlight_focussed_row :FOCUSSED
|
540
|
+
unless @oldrow == @selected_index
|
541
|
+
highlight_focussed_row :UNFOCUSSED
|
542
|
+
color_field @oldrow
|
543
|
+
end
|
544
|
+
end
|
545
|
+
super
|
546
|
+
end
|
547
|
+
# no such method in superclass !!! XXX FIXME no such event too
|
548
|
+
def on_leave_row arow
|
549
|
+
#print_focussed_row :UNFOCUSSED
|
550
|
+
#print_normal_row
|
551
|
+
#super
|
552
|
+
end
|
553
|
+
# this is just a test of the simple "most" menu
|
554
|
+
# How can application add to this, or override
|
555
|
+
def disp_menu #:nodoc:
|
556
|
+
require 'rbcurse/extras/widgets/menutree'
|
557
|
+
# we need to put this into data-structure so that i can be manipulated by calling apps
|
558
|
+
# This should not be at the widget level, too many types of menus. It should be at the app
|
559
|
+
# level only if the user wants his app to use this kind of menu.
|
560
|
+
|
561
|
+
@menu = RubyCurses::MenuTree.new "Main", { s: :goto_start, r: :scroll_right, l: :scroll_left, m: :submenu }
|
562
|
+
@menu.submenu :m, "submenu", {s: :noignorecase, t: :goto_last_position, f: :next3 }
|
563
|
+
menu = PromptMenu.new self
|
564
|
+
menu.menu_tree @menu
|
565
|
+
|
566
|
+
=begin
|
567
|
+
menu = PromptMenu.new self
|
568
|
+
menu.add( menu.create_mitem( 's', "Goto start ", "Going to start", Proc.new { goto_start} ))
|
569
|
+
menu.add(menu.create_mitem( 'r', "scroll right", "I have scrolled ", :scroll_right ))
|
570
|
+
menu.add(menu.create_mitem( 'l', "scroll left", "I have scrolled ", :scroll_left ))
|
571
|
+
item = menu.create_mitem( 'm', "submenu", "submenu options" )
|
572
|
+
menu1 = PromptMenu.new( self, "Submenu Options")
|
573
|
+
menu1.add(menu1.create_mitem( 's', "CASE sensitive", "Ignoring Case in search" ))
|
574
|
+
menu1.add(menu1.create_mitem( 't', "goto last position", "moved to previous position", Proc.new { goto_last_position} ))
|
575
|
+
item.action = menu1
|
576
|
+
menu.add(item)
|
577
|
+
# how do i know what's available. the application or window should know where to place
|
578
|
+
#menu.display @form.window, 23, 1, $datacolor #, menu
|
579
|
+
=end
|
580
|
+
menu.display @form.window, $error_message_row, $error_message_col, $datacolor #, menu
|
581
|
+
end
|
582
|
+
|
583
|
+
|
584
|
+
end # class textview
|
585
|
+
|
586
|
+
end # modul
|