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,147 @@
|
|
|
1
|
+
require 'canis/core/util/app'
|
|
2
|
+
#include Ncurses # FFI 2011-09-8
|
|
3
|
+
include Canis
|
|
4
|
+
|
|
5
|
+
# This paints a vertical white bar given row and col, and length. It also calculates and prints
|
|
6
|
+
# a small bar over this based on relaetd objects list.length and current_index.
|
|
7
|
+
# Typically, after setup one would keep updating only current_index from the repaint method
|
|
8
|
+
# of caller or in the traversal event. This would look best if the listbox also has a reverse video border, or none.
|
|
9
|
+
# @example
|
|
10
|
+
# lb = list_box ....
|
|
11
|
+
# sb = Scrollbar.new @form, :row => lb.row, :col => lb.col, :length => lb.height, :list_length => lb.row_count, :current_index => 0
|
|
12
|
+
# .... later as user traverses
|
|
13
|
+
# sb.current_index = lb.current_index
|
|
14
|
+
# sb = Scrollbar.new @form, :parent => list
|
|
15
|
+
#
|
|
16
|
+
# At a later stage, we will integrate this with lists and tables, so it will happen automatically.
|
|
17
|
+
#
|
|
18
|
+
# @since 1.2.0 UNTESTED
|
|
19
|
+
module Canis
|
|
20
|
+
class Scrollbar < Widget
|
|
21
|
+
# row to start, same as listbox, required.
|
|
22
|
+
dsl_property :row
|
|
23
|
+
# column to start, same as listbox, required.
|
|
24
|
+
dsl_property :col
|
|
25
|
+
# how many rows is this (should be same as listboxes height, required.
|
|
26
|
+
dsl_property :length
|
|
27
|
+
# vertical or horizontal currently only VERTICAL
|
|
28
|
+
dsl_property :orientation
|
|
29
|
+
# initialize based on parent's values
|
|
30
|
+
dsl_property :parent
|
|
31
|
+
# which row is focussed, current_index of listbox, required.
|
|
32
|
+
dsl_property :current_index
|
|
33
|
+
# how many total rows of data does the list have, same as @list.length, required.
|
|
34
|
+
dsl_property :list_length
|
|
35
|
+
|
|
36
|
+
# TODO: if parent passed, we shold bind to ON_ENTER and get current_index, so no extra work is required.
|
|
37
|
+
|
|
38
|
+
def initialize form, config={}, &block
|
|
39
|
+
|
|
40
|
+
# setting default first or else Widget will place its BW default
|
|
41
|
+
#@color, @bgcolor = ColorMap.get_colors_for_pair $bottomcolor
|
|
42
|
+
super
|
|
43
|
+
@color_pair = get_color $datacolor, @color, @bgcolor
|
|
44
|
+
@scroll_pair = get_color $bottomcolor, :green, :white
|
|
45
|
+
#$log.debug "SCROLLBAR COLOR cp #{@color_pair} sp #{@scroll_pair} " if $log.debug?
|
|
46
|
+
@window = form.window
|
|
47
|
+
@editable = false
|
|
48
|
+
@focusable = false
|
|
49
|
+
@repaint_required = true
|
|
50
|
+
@orientation = :V
|
|
51
|
+
if @parent
|
|
52
|
+
@parent.bind :ENTER_ROW do |p|
|
|
53
|
+
# textview sent self, textpad sends textactionevent
|
|
54
|
+
if p.instance_of? TextActionEvent
|
|
55
|
+
p = p.source
|
|
56
|
+
end
|
|
57
|
+
# parent must implement row_count, and have a @current_index
|
|
58
|
+
raise StandardError, "Parent (#{p.class.to_s}) must implement row_count" unless p.respond_to? :row_count
|
|
59
|
+
self.current_index = p.current_index
|
|
60
|
+
@repaint_required = true #requred otherwise at end when same value sent, prop handler
|
|
61
|
+
# will not be fired (due to optimization).
|
|
62
|
+
end
|
|
63
|
+
# in some cases, on leaving a listbox or other component redraws itself to reduce
|
|
64
|
+
# selected or highlighted object, so the scrollbar gets overwritten. We need to repaint it.
|
|
65
|
+
@parent.bind :LEAVE do |p|
|
|
66
|
+
@repaint_required = true
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# repaint the scrollbar
|
|
73
|
+
# Taking the data from parent as late as possible in case parent resized, or
|
|
74
|
+
# moved around by a container.
|
|
75
|
+
def repaint
|
|
76
|
+
if @parent
|
|
77
|
+
@row = @parent.row+1
|
|
78
|
+
@col = @parent.col + @parent.width - 1
|
|
79
|
+
@length = @parent.height - 2
|
|
80
|
+
@list_length = @parent.row_count
|
|
81
|
+
@current_index ||= @parent.current_index
|
|
82
|
+
@border_attrib ||= @parent.border_attrib
|
|
83
|
+
end
|
|
84
|
+
raise ArgumentError, "current_index must be provided" unless @current_index
|
|
85
|
+
raise ArgumentError, "list_length must be provided" unless @list_length
|
|
86
|
+
my_win = @form ? @form.window : @target_window
|
|
87
|
+
@graphic = my_win unless @graphic
|
|
88
|
+
return unless @repaint_required
|
|
89
|
+
|
|
90
|
+
# first print a right side vertical line
|
|
91
|
+
#bc = $bottomcolor # dark blue
|
|
92
|
+
bc = $datacolor
|
|
93
|
+
bordercolor = @border_color || bc
|
|
94
|
+
borderatt = @border_attrib || Ncurses::A_REVERSE
|
|
95
|
+
#$log.debug "SCROLL bordercolor #{bordercolor} , #{borderatt} " if $log.debug?
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@graphic.attron(Ncurses.COLOR_PAIR(bordercolor) | borderatt)
|
|
99
|
+
#$log.debug " XXX SCROLL #{@row} #{@col} #{@length} "
|
|
100
|
+
@graphic.mvvline(@row+0, @col, 1, @length-0)
|
|
101
|
+
@graphic.attroff(Ncurses.COLOR_PAIR(bordercolor) | borderatt)
|
|
102
|
+
|
|
103
|
+
# now calculate and paint the scrollbar
|
|
104
|
+
pht = @length
|
|
105
|
+
listlen = @list_length * 1.0
|
|
106
|
+
@current_index = 0 if @current_index < 0
|
|
107
|
+
@current_index = listlen-1 if @current_index >= listlen
|
|
108
|
+
sclen = (pht/listlen)* @length
|
|
109
|
+
sclen = 1 if sclen < 1 # sometimes 0.7 for large lists 100 items 2011-10-1
|
|
110
|
+
scloc = (@current_index/listlen)* @length
|
|
111
|
+
scloc = (@length - sclen) if scloc > @length - sclen # don't exceed end
|
|
112
|
+
if @current_index == @list_length - 1
|
|
113
|
+
scloc = @length - sclen + 0 # earlier 1, but removed since sclen min 1 2011-10-1
|
|
114
|
+
end
|
|
115
|
+
@graphic.attron(Ncurses.COLOR_PAIR(@scroll_pair) | borderatt)
|
|
116
|
+
r = @row + scloc
|
|
117
|
+
c = @col + 0
|
|
118
|
+
#$log.debug " XXX SCROLLBAR #{r} #{c} #{sclen} "
|
|
119
|
+
@graphic.mvvline(r, c, 1, sclen)
|
|
120
|
+
@graphic.attroff(Ncurses.COLOR_PAIR(@scroll_pair) | borderatt)
|
|
121
|
+
@repaint_required = false
|
|
122
|
+
end
|
|
123
|
+
##
|
|
124
|
+
##
|
|
125
|
+
# ADD HERE
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
if __FILE__ == $PROGRAM_NAME
|
|
129
|
+
App.new do
|
|
130
|
+
r = 5
|
|
131
|
+
len = 20
|
|
132
|
+
list = []
|
|
133
|
+
0.upto(100) { |v| list << "#{v} scrollable data" }
|
|
134
|
+
lb = list_box "A list", :list => list
|
|
135
|
+
#sb = Scrollbar.new @form, :row => r, :col => 20, :length => len, :list_length => 50, :current_index => 0
|
|
136
|
+
rb = Scrollbar.new @form, :parent => lb
|
|
137
|
+
#hline :width => 20, :row => len+r
|
|
138
|
+
#keypress do |ch|
|
|
139
|
+
#case ch
|
|
140
|
+
#when :down
|
|
141
|
+
#sb.current_index += 1
|
|
142
|
+
#when :up
|
|
143
|
+
#sb.current_index -= 1
|
|
144
|
+
#end
|
|
145
|
+
#end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
require 'canis'
|
|
2
|
+
|
|
3
|
+
module Canis
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
# A vim-like application status bar that can display time and various other statuses
|
|
7
|
+
# at the bottom, typically above the dock (3rd line from last).
|
|
8
|
+
#
|
|
9
|
+
class StatusLine < Widget
|
|
10
|
+
#attr_accessor :row_relative # lets only advertise this when we've tested it out
|
|
11
|
+
|
|
12
|
+
def initialize form, config={}, &block
|
|
13
|
+
@row_relative = -3
|
|
14
|
+
if form.window.height == 0
|
|
15
|
+
@row = Ncurses.LINES-3 # fix, what about smaller windows, use window dimensions and watch out for 0,0
|
|
16
|
+
else
|
|
17
|
+
@row = form.window.height-3 # fix, what about smaller windows, use window dimensions and watch out for 0,0
|
|
18
|
+
end
|
|
19
|
+
# in root windows FIXME
|
|
20
|
+
@col = 0
|
|
21
|
+
@name = "sl"
|
|
22
|
+
super
|
|
23
|
+
# if negativ row passed we store as relative to bottom, so we can maintain that.
|
|
24
|
+
if @row < 0
|
|
25
|
+
@row_relative = @row
|
|
26
|
+
@row = Ncurses.LINES - @row
|
|
27
|
+
else
|
|
28
|
+
@row_relative = (Ncurses.LINES - @row) * -1
|
|
29
|
+
end
|
|
30
|
+
@focusable = false
|
|
31
|
+
@editable = false
|
|
32
|
+
@command = nil
|
|
33
|
+
@repaint_required = true
|
|
34
|
+
bind(:PROPERTY_CHANGE) { |e| @color_pair = nil ; }
|
|
35
|
+
end
|
|
36
|
+
#
|
|
37
|
+
# command that returns a string that populates the status line (left aligned)
|
|
38
|
+
# @see :right
|
|
39
|
+
# See dbdemo.rb
|
|
40
|
+
# e.g.
|
|
41
|
+
# @l.command { "%-20s [DB: %-s | %-s ]" % [ Time.now, $current_db || "None", $current_table || "----"] }
|
|
42
|
+
#
|
|
43
|
+
def command *args, &blk
|
|
44
|
+
@command = blk
|
|
45
|
+
@args = args
|
|
46
|
+
end
|
|
47
|
+
alias :left :command
|
|
48
|
+
|
|
49
|
+
#
|
|
50
|
+
# Procudure for text to be right aligned in statusline
|
|
51
|
+
def right *args, &blk
|
|
52
|
+
@right_text = blk
|
|
53
|
+
@right_args = args
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# NOTE: I have not put a check of repaint_required, so this will print on each key-stroke OR
|
|
57
|
+
# rather whenever form.repaint is called.
|
|
58
|
+
def repaint
|
|
59
|
+
@color_pair ||= get_color($datacolor, @color, @bgcolor)
|
|
60
|
+
len = @form.window.getmaxx # width does not change upon resizing so useless, fix or do something
|
|
61
|
+
len = Ncurses.COLS if len == 0 || len > Ncurses.COLS
|
|
62
|
+
# this should only happen if there's a change in window
|
|
63
|
+
if @row_relative
|
|
64
|
+
@row = Ncurses.LINES+@row_relative
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# first print dashes through
|
|
68
|
+
@form.window.printstring @row, @col, "%s" % "-" * len, @color_pair, Ncurses::A_REVERSE
|
|
69
|
+
|
|
70
|
+
# now call the block to get current values
|
|
71
|
+
if @command
|
|
72
|
+
ftext = @command.call(self, @args)
|
|
73
|
+
else
|
|
74
|
+
status = $status_message ? $status_message.value : ""
|
|
75
|
+
#ftext = " %-20s | %s" % [Time.now, status] # should we print a default value just in case user doesn't
|
|
76
|
+
ftext = status # should we print a default value just in case user doesn't
|
|
77
|
+
end
|
|
78
|
+
# 2013-03-25 - 11:52 replaced $datacolor with @color_pair - how could this have been ?
|
|
79
|
+
# what if user wants to change attrib ?
|
|
80
|
+
if ftext =~ /#\[/
|
|
81
|
+
# hopefully color_pair does not clash with formatting
|
|
82
|
+
@form.window.printstring_formatted @row, @col, ftext, @color_pair, Ncurses::A_REVERSE
|
|
83
|
+
else
|
|
84
|
+
@form.window.printstring @row, @col, ftext, @color_pair, Ncurses::A_REVERSE
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if @right_text
|
|
88
|
+
ftext = @right_text.call(self, @right_args)
|
|
89
|
+
if ftext =~ /#\[/
|
|
90
|
+
# hopefully color_pair does not clash with formatting
|
|
91
|
+
@form.window.printstring_formatted_right @row, nil, ftext, @color_pair, Ncurses::A_REVERSE
|
|
92
|
+
else
|
|
93
|
+
c = len - ftext.length
|
|
94
|
+
@form.window.printstring @row, c, ftext, @color_pair, Ncurses::A_REVERSE
|
|
95
|
+
end
|
|
96
|
+
else
|
|
97
|
+
t = Time.now
|
|
98
|
+
tt = t.strftime "%F %H:%M:%S"
|
|
99
|
+
#r = Ncurses.LINES
|
|
100
|
+
# somehow the bg defined here affects the bg in left text, if left does not define
|
|
101
|
+
# a bg. The bgcolor defined of statusline is ignored in left or overriden by this
|
|
102
|
+
#ftext = "#[fg=white,bg=blue] %-20s#[/end]" % [tt] # print a default
|
|
103
|
+
@form.window.printstring_formatted_right @row, nil, tt, @color_pair, Ncurses::A_REVERSE
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
@repaint_required = false
|
|
107
|
+
end
|
|
108
|
+
def handle_keys ch
|
|
109
|
+
return :UNHANDLED
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
end # class
|
|
113
|
+
end # module
|
|
@@ -0,0 +1,1072 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# header {{{
|
|
3
|
+
# vim: set foldlevel=0 foldmethod=marker :
|
|
4
|
+
# ----------------------------------------------------------------------------- #
|
|
5
|
+
# File: table.rb
|
|
6
|
+
# Description: A tabular widget based on textpad
|
|
7
|
+
# Author: jkepler http://github.com/mare-imbrium/canis/
|
|
8
|
+
# Date: 2013-03-29 - 20:07
|
|
9
|
+
# License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
|
10
|
+
# Last update: 2014-06-01 13:10
|
|
11
|
+
# ----------------------------------------------------------------------------- #
|
|
12
|
+
# table.rb Copyright (C) 2012-2014 kepler
|
|
13
|
+
|
|
14
|
+
# == CHANGES:
|
|
15
|
+
# - changed @content to @list since all multirow wids and utils expect @list
|
|
16
|
+
# - changed name from tablewidget to table
|
|
17
|
+
#
|
|
18
|
+
# == TODO
|
|
19
|
+
# [ ] if no columns, then init_model is called so chash is not cleared.
|
|
20
|
+
# _ compare to tabular_widget and see what's missing
|
|
21
|
+
# _ filtering rows without losing data
|
|
22
|
+
# . selection stuff
|
|
23
|
+
# x test with resultset from sqlite to see if we can use Array or need to make model
|
|
24
|
+
# should we use a datamodel so resultsets can be sent in, what about tabular
|
|
25
|
+
# _ header to handle events ?
|
|
26
|
+
# header }}}
|
|
27
|
+
|
|
28
|
+
require 'logger'
|
|
29
|
+
require 'canis'
|
|
30
|
+
require 'canis/core/widgets/textpad'
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# The motivation to create yet another table widget is because tabular_widget
|
|
34
|
+
# is based on textview etc which have a lot of complex processing and rendering
|
|
35
|
+
# whereas textpad is quite simple. It is easy to just add one's own renderer
|
|
36
|
+
# making the code base simpler to understand and maintain.
|
|
37
|
+
#
|
|
38
|
+
#
|
|
39
|
+
module Canis
|
|
40
|
+
# structures {{{
|
|
41
|
+
# column data, one instance for each column
|
|
42
|
+
# index is the index in the data of this column. This index will not change.
|
|
43
|
+
# Order of printing columns is determined by the ordering of the objects.
|
|
44
|
+
class ColumnInfo < Struct.new(:name, :index, :offset, :width, :align, :hidden, :attrib, :color, :bgcolor)
|
|
45
|
+
end
|
|
46
|
+
# a structure that maintains position and gives
|
|
47
|
+
# next and previous taking max index into account.
|
|
48
|
+
# it also circles. Can be used for traversing next component
|
|
49
|
+
# in a form, or container, or columns in a table.
|
|
50
|
+
class Circular < Struct.new(:max_index, :current_index)
|
|
51
|
+
attr_reader :last_index
|
|
52
|
+
attr_reader :current_index
|
|
53
|
+
def initialize m, c=0
|
|
54
|
+
raise "max index cannot be nil" unless m
|
|
55
|
+
@max_index = m
|
|
56
|
+
@current_index = c
|
|
57
|
+
@last_index = c
|
|
58
|
+
end
|
|
59
|
+
def next
|
|
60
|
+
@last_index = @current_index
|
|
61
|
+
if @current_index + 1 > @max_index
|
|
62
|
+
@current_index = 0
|
|
63
|
+
else
|
|
64
|
+
@current_index += 1
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
def previous
|
|
68
|
+
@last_index = @current_index
|
|
69
|
+
if @current_index - 1 < 0
|
|
70
|
+
@current_index = @max_index
|
|
71
|
+
else
|
|
72
|
+
@current_index -= 1
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
def is_last?
|
|
76
|
+
@current_index == @max_index
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
# structures }}}
|
|
80
|
+
# sorter {{{
|
|
81
|
+
# This is our default table row sorter.
|
|
82
|
+
# It does a multiple sort and allows for reverse sort also.
|
|
83
|
+
# It's a pretty simple sorter and uses sort, not sort_by.
|
|
84
|
+
# Improvements welcome.
|
|
85
|
+
# Usage: provide model in constructor or using model method
|
|
86
|
+
# Call toggle_sort_order(column_index)
|
|
87
|
+
# Call sort.
|
|
88
|
+
# Currently, this sorts the provided model in-place. Future versions
|
|
89
|
+
# may maintain a copy, or use a table that provides a mapping of model to result.
|
|
90
|
+
# # TODO check if column_sortable
|
|
91
|
+
class DefaultTableRowSorter
|
|
92
|
+
attr_reader :sort_keys
|
|
93
|
+
# model is array of data
|
|
94
|
+
def initialize data_model=nil
|
|
95
|
+
self.model = data_model
|
|
96
|
+
@columns_sort = []
|
|
97
|
+
@sort_keys = nil
|
|
98
|
+
end
|
|
99
|
+
def model=(model)
|
|
100
|
+
@model = model
|
|
101
|
+
@sort_keys = nil
|
|
102
|
+
end
|
|
103
|
+
def sortable colindex, tf
|
|
104
|
+
@columns_sort[colindex] = tf
|
|
105
|
+
end
|
|
106
|
+
def sortable? colindex
|
|
107
|
+
return false if @columns_sort[colindex]==false
|
|
108
|
+
return true
|
|
109
|
+
end
|
|
110
|
+
# should to_s be used for this column
|
|
111
|
+
def use_to_s colindex
|
|
112
|
+
return true # TODO
|
|
113
|
+
end
|
|
114
|
+
# sorts the model based on sort keys and reverse flags
|
|
115
|
+
# @sort_keys contains indices to sort on
|
|
116
|
+
# @reverse_flags is an array of booleans, true for reverse, nil or false for ascending
|
|
117
|
+
def sort
|
|
118
|
+
return unless @model
|
|
119
|
+
return if @sort_keys.empty?
|
|
120
|
+
$log.debug "TABULAR SORT KEYS #{sort_keys} "
|
|
121
|
+
# first row is the header which should remain in place
|
|
122
|
+
# We could have kept column headers separate, but then too much of mucking around
|
|
123
|
+
# with textpad, this way we avoid touching it
|
|
124
|
+
header = @model.delete_at 0
|
|
125
|
+
begin
|
|
126
|
+
# next line often can give error "array within array" - i think on date fields that
|
|
127
|
+
# contain nils
|
|
128
|
+
@model.sort!{|x,y|
|
|
129
|
+
res = 0
|
|
130
|
+
@sort_keys.each { |ee|
|
|
131
|
+
e = ee.abs-1 # since we had offsetted by 1 earlier
|
|
132
|
+
abse = e.abs
|
|
133
|
+
if ee < 0
|
|
134
|
+
xx = x[abse]
|
|
135
|
+
yy = y[abse]
|
|
136
|
+
# the following checks are since nil values cause an error to be raised
|
|
137
|
+
if xx.nil? && yy.nil?
|
|
138
|
+
res = 0
|
|
139
|
+
elsif xx.nil?
|
|
140
|
+
res = 1
|
|
141
|
+
elsif yy.nil?
|
|
142
|
+
res = -1
|
|
143
|
+
else
|
|
144
|
+
res = y[abse] <=> x[abse]
|
|
145
|
+
end
|
|
146
|
+
else
|
|
147
|
+
xx = x[e]
|
|
148
|
+
yy = y[e]
|
|
149
|
+
# the following checks are since nil values cause an error to be raised
|
|
150
|
+
# whereas we want a nil to be wither treated as a zero or a blank
|
|
151
|
+
if xx.nil? && yy.nil?
|
|
152
|
+
res = 0
|
|
153
|
+
elsif xx.nil?
|
|
154
|
+
res = -1
|
|
155
|
+
elsif yy.nil?
|
|
156
|
+
res = 1
|
|
157
|
+
else
|
|
158
|
+
res = x[e] <=> y[e]
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
break if res != 0
|
|
162
|
+
}
|
|
163
|
+
res
|
|
164
|
+
}
|
|
165
|
+
ensure
|
|
166
|
+
@model.insert 0, header if header
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
# toggle the sort order if given column offset is primary sort key
|
|
170
|
+
# Otherwise, insert as primary sort key, ascending.
|
|
171
|
+
def toggle_sort_order index
|
|
172
|
+
index += 1 # increase by 1, since 0 won't multiple by -1
|
|
173
|
+
# internally, reverse sort is maintained by multiplying number by -1
|
|
174
|
+
@sort_keys ||= []
|
|
175
|
+
if @sort_keys.first && index == @sort_keys.first.abs
|
|
176
|
+
@sort_keys[0] *= -1
|
|
177
|
+
else
|
|
178
|
+
@sort_keys.delete index # in case its already there
|
|
179
|
+
@sort_keys.delete(index*-1) # in case its already there
|
|
180
|
+
@sort_keys.unshift index
|
|
181
|
+
# don't let it go on increasing
|
|
182
|
+
if @sort_keys.size > 3
|
|
183
|
+
@sort_keys.pop
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
def set_sort_keys list
|
|
188
|
+
@sort_keys = list
|
|
189
|
+
end
|
|
190
|
+
end #class
|
|
191
|
+
|
|
192
|
+
# sorter }}}
|
|
193
|
+
# renderer {{{
|
|
194
|
+
#
|
|
195
|
+
# TODO see how jtable does the renderers and columns stuff.
|
|
196
|
+
#
|
|
197
|
+
# perhaps we can combine the two but have different methods or some flag
|
|
198
|
+
# that way oter methods can be shared
|
|
199
|
+
class DefaultTableRenderer
|
|
200
|
+
|
|
201
|
+
# source is the textpad or extending widget needed so we can call show_colored_chunks
|
|
202
|
+
# if the user specifies column wise colors
|
|
203
|
+
def initialize source
|
|
204
|
+
@source = source
|
|
205
|
+
@y = '|'
|
|
206
|
+
@x = '+'
|
|
207
|
+
@coffsets = []
|
|
208
|
+
@header_color = :white
|
|
209
|
+
@header_bgcolor = :red
|
|
210
|
+
@header_attrib = NORMAL
|
|
211
|
+
@color = :white
|
|
212
|
+
@bgcolor = :black
|
|
213
|
+
@color_pair = $datacolor
|
|
214
|
+
@attrib = NORMAL
|
|
215
|
+
@_check_coloring = nil
|
|
216
|
+
# adding setting column_model auto on 2014-04-10 - 10:53 why wasn;t this here already
|
|
217
|
+
column_model(source.column_model)
|
|
218
|
+
end
|
|
219
|
+
def header_colors fg, bg
|
|
220
|
+
@header_color = fg
|
|
221
|
+
@header_bgcolor = bg
|
|
222
|
+
end
|
|
223
|
+
def header_attrib att
|
|
224
|
+
@header_attrib = att
|
|
225
|
+
end
|
|
226
|
+
# set fg and bg color of content rows, default is $datacolor (white on black).
|
|
227
|
+
def content_colors fg, bg
|
|
228
|
+
@color = fg
|
|
229
|
+
@bgcolor = bg
|
|
230
|
+
@color_pair = get_color($datacolor, fg, bg)
|
|
231
|
+
end
|
|
232
|
+
def content_attrib att
|
|
233
|
+
@attrib = att
|
|
234
|
+
end
|
|
235
|
+
# set column model (Table Renderer)
|
|
236
|
+
def column_model c
|
|
237
|
+
@chash = c
|
|
238
|
+
end
|
|
239
|
+
##
|
|
240
|
+
# Takes the array of row data and formats it using column widths
|
|
241
|
+
# and returns an array which is used for printing
|
|
242
|
+
#
|
|
243
|
+
# return an array so caller can color columns if need be
|
|
244
|
+
def convert_value_to_text r
|
|
245
|
+
str = []
|
|
246
|
+
fmt = nil
|
|
247
|
+
field = nil
|
|
248
|
+
# we need to loop through chash and get index from it and get that row from r
|
|
249
|
+
each_column {|c,i|
|
|
250
|
+
e = r[c.index]
|
|
251
|
+
w = c.width
|
|
252
|
+
l = e.to_s.length
|
|
253
|
+
# if value is longer than width, then truncate it
|
|
254
|
+
if l > w
|
|
255
|
+
fmt = "%.#{w}s "
|
|
256
|
+
else
|
|
257
|
+
case c.align
|
|
258
|
+
when :right
|
|
259
|
+
fmt = "%#{w}s "
|
|
260
|
+
else
|
|
261
|
+
fmt = "%-#{w}s "
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
field = fmt % e
|
|
265
|
+
# if we really want to print a single column with color, we need to print here itself
|
|
266
|
+
# each cell. If we want the user to use tmux formatting in the column itself ...
|
|
267
|
+
# FIXME - this must not be done for headers.
|
|
268
|
+
#if c.color
|
|
269
|
+
#field = "#[fg=#{c.color}]#{field}#[/end]"
|
|
270
|
+
#end
|
|
271
|
+
str << field
|
|
272
|
+
}
|
|
273
|
+
return str
|
|
274
|
+
end
|
|
275
|
+
# return a string representation of the row so that +index+ can be applied to it.
|
|
276
|
+
# This must take into account columns widths and offsets. This is used by textpad's
|
|
277
|
+
# next_match method
|
|
278
|
+
def to_searchable arr
|
|
279
|
+
convert_value_to_text(arr).join
|
|
280
|
+
end
|
|
281
|
+
#
|
|
282
|
+
# @param pad for calling print methods on
|
|
283
|
+
# @param lineno the line number on the pad to print on
|
|
284
|
+
# @param [String] data to print which will be an array (@list[index])
|
|
285
|
+
def render pad, lineno, str
|
|
286
|
+
#lineno += 1 # header_adjustment
|
|
287
|
+
# header_adjustment means columns have been set
|
|
288
|
+
return render_header pad, lineno, 0, str if lineno == 0 && @source.header_adjustment > 0
|
|
289
|
+
#text = str.join " | "
|
|
290
|
+
#text = @fmstr % str
|
|
291
|
+
text = convert_value_to_text str
|
|
292
|
+
if @_check_coloring
|
|
293
|
+
#$log.debug "XXX: INSIDE COLORIIN"
|
|
294
|
+
text = colorize pad, lineno, text
|
|
295
|
+
return
|
|
296
|
+
end
|
|
297
|
+
# check if any specific colors , if so then print colors in a loop with no dependence on colored chunks
|
|
298
|
+
# then we don't need source pointer
|
|
299
|
+
render_data pad, lineno, text
|
|
300
|
+
|
|
301
|
+
end
|
|
302
|
+
# passes padded data for final printing or data row
|
|
303
|
+
# this allows user to do row related coloring without having to tamper
|
|
304
|
+
# with the headers or other internal workings. This will not be called
|
|
305
|
+
# if column specific colorign is in effect.
|
|
306
|
+
# @param text is an array of strings, in the order of actual printing with hidden cols removed
|
|
307
|
+
def render_data pad, lineno, text
|
|
308
|
+
text = text.join
|
|
309
|
+
# FIXME why repeatedly getting this colorpair
|
|
310
|
+
cp = @color_pair
|
|
311
|
+
att = @attrib
|
|
312
|
+
# added for selection, but will crash if selection is not extended !!! XXX
|
|
313
|
+
if @source.is_row_selected? lineno
|
|
314
|
+
att = REVERSE
|
|
315
|
+
# FIXME currentl this overflows into next row
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
FFI::NCurses.wattron(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
|
|
319
|
+
FFI::NCurses.mvwaddstr(pad, lineno, 0, text)
|
|
320
|
+
FFI::NCurses.wattroff(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def render_header pad, lineno, col, columns
|
|
324
|
+
# I could do it once only but if user sets colors midway we can check once whenvever
|
|
325
|
+
# repainting
|
|
326
|
+
check_colors #if @_check_coloring.nil?
|
|
327
|
+
#text = columns.join " | "
|
|
328
|
+
#text = @fmstr % columns
|
|
329
|
+
text = convert_value_to_text columns
|
|
330
|
+
text = text.join
|
|
331
|
+
bg = @header_bgcolor
|
|
332
|
+
fg = @header_color
|
|
333
|
+
att = @header_attrib
|
|
334
|
+
#cp = $datacolor
|
|
335
|
+
cp = get_color($datacolor, fg, bg)
|
|
336
|
+
FFI::NCurses.wattron(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
|
|
337
|
+
FFI::NCurses.mvwaddstr(pad, lineno, col, text)
|
|
338
|
+
FFI::NCurses.wattroff(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
|
|
339
|
+
end
|
|
340
|
+
# check if we need to individually color columns or we can do the entire
|
|
341
|
+
# row in one shot
|
|
342
|
+
def check_colors
|
|
343
|
+
each_column {|c,i|
|
|
344
|
+
if c.color || c.bgcolor || c.attrib
|
|
345
|
+
@_check_coloring = true
|
|
346
|
+
return
|
|
347
|
+
end
|
|
348
|
+
@_check_coloring = false
|
|
349
|
+
}
|
|
350
|
+
end
|
|
351
|
+
def each_column
|
|
352
|
+
@chash.each_with_index { |c, i|
|
|
353
|
+
next if c.hidden
|
|
354
|
+
yield c,i if block_given?
|
|
355
|
+
}
|
|
356
|
+
end
|
|
357
|
+
def colorize pad, lineno, r
|
|
358
|
+
# the incoming data is already in the order of display based on chash,
|
|
359
|
+
# so we cannot run chash on it again, so how do we get the color info
|
|
360
|
+
_offset = 0
|
|
361
|
+
each_column {|c,i|
|
|
362
|
+
text = r[i]
|
|
363
|
+
color = c.color
|
|
364
|
+
bg = c.bgcolor
|
|
365
|
+
if color || bg
|
|
366
|
+
cp = get_color(@color_pair, color || @color, bg || @bgcolor)
|
|
367
|
+
else
|
|
368
|
+
cp = @color_pair
|
|
369
|
+
end
|
|
370
|
+
att = c.attrib || @attrib
|
|
371
|
+
FFI::NCurses.wattron(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
|
|
372
|
+
FFI::NCurses.mvwaddstr(pad, lineno, _offset, text)
|
|
373
|
+
FFI::NCurses.wattroff(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
|
|
374
|
+
_offset += text.length
|
|
375
|
+
}
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
# renderer }}}
|
|
379
|
+
|
|
380
|
+
#--
|
|
381
|
+
# If we make a pad of the whole thing then the columns will also go out when scrolling
|
|
382
|
+
# So then there's no point storing columns separately. Might as well keep in content
|
|
383
|
+
# so scrolling works fine, otherwise textpad will have issues scrolling.
|
|
384
|
+
# Making a pad of the content but not column header complicates stuff,
|
|
385
|
+
# do we make a pad of that, or print it like the old thing.
|
|
386
|
+
#++
|
|
387
|
+
# A table widget containing rows and columns and the ability to resize and hide or align
|
|
388
|
+
# columns. Also may have first row as column names.
|
|
389
|
+
#
|
|
390
|
+
# == NOTE
|
|
391
|
+
# The most important methods to use probably are `text()` or `resultset` or `filename` to load
|
|
392
|
+
# data. With `text` you will want to first specify column names with `columns()`.
|
|
393
|
+
#
|
|
394
|
+
# +@current_index+ inherited from +Textpad+ continues to be the index of the list that has user's
|
|
395
|
+
# focus, and should be used for row operations.
|
|
396
|
+
#
|
|
397
|
+
# In order to use Textpad easily, the first row of the table model is the column names. Data is maintained
|
|
398
|
+
# in an Array. Several operations are delegated to Array, or have the same name. You can get the list
|
|
399
|
+
# using `list()` to run other Array operations on it.
|
|
400
|
+
#
|
|
401
|
+
# If you modify the Array directly, you may have to use `fire_row_changed(index)` to reflect the update to
|
|
402
|
+
# a single row. If you delete or add a row, you will have to use `fire_dimension_changed()`. However,
|
|
403
|
+
# internal functions do this automatically.
|
|
404
|
+
#
|
|
405
|
+
require 'canis/core/include/listselectionmodel'
|
|
406
|
+
class Table < TextPad
|
|
407
|
+
|
|
408
|
+
dsl_accessor :print_footer
|
|
409
|
+
#attr_reader :columns
|
|
410
|
+
attr_accessor :table_row_sorter
|
|
411
|
+
|
|
412
|
+
def initialize form = nil, config={}, &block
|
|
413
|
+
|
|
414
|
+
# array of column info objects
|
|
415
|
+
@chash = []
|
|
416
|
+
# chash should be an array which is basically the order of rows to be printed
|
|
417
|
+
# it contains index, which is the offset of the row in the data @list
|
|
418
|
+
# When printing we should loop through chash and get the index in data
|
|
419
|
+
#
|
|
420
|
+
# should be zero here, but then we won't get textpad correct
|
|
421
|
+
@_header_adjustment = 0 #1
|
|
422
|
+
@col_min_width = 3
|
|
423
|
+
|
|
424
|
+
self.extend DefaultListSelection
|
|
425
|
+
super
|
|
426
|
+
create_default_renderer unless @renderer # 2014-04-10 - 11:01
|
|
427
|
+
# NOTE listselection takes + and - for ask_select
|
|
428
|
+
bind_key(?w, "next column") { self.next_column }
|
|
429
|
+
bind_key(?b, "prev column") { self.prev_column }
|
|
430
|
+
bind_key(?\M-\-, "contract column") { self.contract_column }
|
|
431
|
+
bind_key(?\M-+, "expand column") { self.expand_column }
|
|
432
|
+
bind_key(?=, "expand column to width") { self.expand_column_to_width }
|
|
433
|
+
bind_key(?\M-=, "expand column to width") { self.expand_column_to_max_width }
|
|
434
|
+
bind_key(?\C-s, "Save as") { self.save_as(nil) }
|
|
435
|
+
#@list_selection_model ||= DefaultListSelectionModel.new self
|
|
436
|
+
set_default_selection_model unless @list_selection_model
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# set the default selection model as the operational one
|
|
440
|
+
def set_default_selection_model
|
|
441
|
+
@list_selection_model = nil
|
|
442
|
+
@list_selection_model = Canis::DefaultListSelectionModel.new self
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# retrieve the column info structure for the given offset. The offset
|
|
446
|
+
# pertains to the visible offset not actual offset in data model.
|
|
447
|
+
# These two differ when we move a column.
|
|
448
|
+
# @return ColumnInfo object containing width align color bgcolor attrib hidden
|
|
449
|
+
def get_column index
|
|
450
|
+
return @chash[index] if @chash[index]
|
|
451
|
+
# create a new entry since none present
|
|
452
|
+
c = ColumnInfo.new
|
|
453
|
+
c.index = index
|
|
454
|
+
@chash[index] = c
|
|
455
|
+
return c
|
|
456
|
+
end
|
|
457
|
+
##
|
|
458
|
+
# returns collection of ColumnInfo objects
|
|
459
|
+
def column_model
|
|
460
|
+
@chash
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# calculate pad width based on widths of columns
|
|
464
|
+
def content_cols
|
|
465
|
+
total = 0
|
|
466
|
+
#@chash.each_pair { |i, c|
|
|
467
|
+
#@chash.each_with_index { |c, i|
|
|
468
|
+
#next if c.hidden
|
|
469
|
+
each_column {|c,i|
|
|
470
|
+
w = c.width
|
|
471
|
+
# if you use prepare_format then use w+2 due to separator symbol
|
|
472
|
+
total += w + 1
|
|
473
|
+
}
|
|
474
|
+
return total
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
#
|
|
478
|
+
# This calculates and stores the offset at which each column starts.
|
|
479
|
+
# Used when going to next column or doing a find for a string in the table.
|
|
480
|
+
# TODO store this inside the hash so it's not calculated again in renderer
|
|
481
|
+
#
|
|
482
|
+
def _calculate_column_offsets
|
|
483
|
+
@coffsets = []
|
|
484
|
+
total = 0
|
|
485
|
+
|
|
486
|
+
#@chash.each_pair { |i, c|
|
|
487
|
+
#@chash.each_with_index { |c, i|
|
|
488
|
+
#next if c.hidden
|
|
489
|
+
each_column {|c,i|
|
|
490
|
+
w = c.width
|
|
491
|
+
@coffsets[i] = total
|
|
492
|
+
c.offset = total
|
|
493
|
+
# if you use prepare_format then use w+2 due to separator symbol
|
|
494
|
+
total += w + 1
|
|
495
|
+
}
|
|
496
|
+
end
|
|
497
|
+
# Convert current cursor position to a table column
|
|
498
|
+
# calculate column based on curpos since user may not have
|
|
499
|
+
# user w and b keys (:next_column)
|
|
500
|
+
# @return [Fixnum] column index base 0
|
|
501
|
+
def _convert_curpos_to_column #:nodoc:
|
|
502
|
+
_calculate_column_offsets unless @coffsets
|
|
503
|
+
x = 0
|
|
504
|
+
@coffsets.each_with_index { |i, ix|
|
|
505
|
+
if @curpos < i
|
|
506
|
+
break
|
|
507
|
+
else
|
|
508
|
+
x += 1
|
|
509
|
+
end
|
|
510
|
+
}
|
|
511
|
+
x -= 1 # since we start offsets with 0, so first auto becoming 1
|
|
512
|
+
return x
|
|
513
|
+
end
|
|
514
|
+
# convert the row into something searchable so that offsets returned by +index+
|
|
515
|
+
# are exactly what is seen on the screen.
|
|
516
|
+
def to_searchable index
|
|
517
|
+
if @renderer
|
|
518
|
+
@renderer.to_searchable(@list[index])
|
|
519
|
+
else
|
|
520
|
+
@list[index].to_s
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
# jump cursor to next column
|
|
524
|
+
# TODO : if cursor goes out of view, then pad should scroll right or left and down
|
|
525
|
+
def next_column
|
|
526
|
+
# TODO take care of multipliers
|
|
527
|
+
_calculate_column_offsets unless @coffsets
|
|
528
|
+
c = @column_pointer.next
|
|
529
|
+
cp = @coffsets[c]
|
|
530
|
+
#$log.debug " next_column #{c} , #{cp} "
|
|
531
|
+
@curpos = cp if cp
|
|
532
|
+
down() if c < @column_pointer.last_index
|
|
533
|
+
fire_column_event :ENTER_COLUMN
|
|
534
|
+
end
|
|
535
|
+
# jump cursor to previous column
|
|
536
|
+
# TODO : if cursor goes out of view, then pad should scroll right or left and down
|
|
537
|
+
def prev_column
|
|
538
|
+
# TODO take care of multipliers
|
|
539
|
+
_calculate_column_offsets unless @coffsets
|
|
540
|
+
c = @column_pointer.previous
|
|
541
|
+
cp = @coffsets[c]
|
|
542
|
+
#$log.debug " prev #{c} , #{cp} "
|
|
543
|
+
@curpos = cp if cp
|
|
544
|
+
up() if c > @column_pointer.last_index
|
|
545
|
+
fire_column_event :ENTER_COLUMN
|
|
546
|
+
end
|
|
547
|
+
# a column traversal has happened.
|
|
548
|
+
# FIXME needs to be looked into. is this consistent naming wise and are we using the correct object
|
|
549
|
+
# In old system it was TABLE_TRAVERSAL_EVENT
|
|
550
|
+
def fire_column_event eve
|
|
551
|
+
require 'canis/core/include/ractionevent'
|
|
552
|
+
aev = TextActionEvent.new self, eve, get_column(@column_pointer.current_index), @column_pointer.current_index, @column_pointer.last_index
|
|
553
|
+
fire_handler eve, aev
|
|
554
|
+
end
|
|
555
|
+
def expand_column
|
|
556
|
+
x = _convert_curpos_to_column
|
|
557
|
+
w = get_column(x).width
|
|
558
|
+
column_width x, w+1 if w
|
|
559
|
+
@coffsets = nil
|
|
560
|
+
fire_dimension_changed
|
|
561
|
+
end
|
|
562
|
+
def expand_column_to_width w=nil
|
|
563
|
+
x = _convert_curpos_to_column
|
|
564
|
+
unless w
|
|
565
|
+
# expand to width of current cell
|
|
566
|
+
s = @list[@current_index][x]
|
|
567
|
+
w = s.to_s.length + 1
|
|
568
|
+
end
|
|
569
|
+
column_width x, w
|
|
570
|
+
@coffsets = nil
|
|
571
|
+
fire_dimension_changed
|
|
572
|
+
end
|
|
573
|
+
# find the width of the longest item in the current columns and expand the width
|
|
574
|
+
# to that.
|
|
575
|
+
def expand_column_to_max_width
|
|
576
|
+
x = _convert_curpos_to_column
|
|
577
|
+
w = calculate_column_width x
|
|
578
|
+
expand_column_to_width w
|
|
579
|
+
end
|
|
580
|
+
def contract_column
|
|
581
|
+
x = _convert_curpos_to_column
|
|
582
|
+
w = get_column(x).width
|
|
583
|
+
return if w <= @col_min_width
|
|
584
|
+
column_width x, w-1 if w
|
|
585
|
+
@coffsets = nil
|
|
586
|
+
fire_dimension_changed
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
#def method_missing(name, *args)
|
|
590
|
+
#@tp.send(name, *args)
|
|
591
|
+
#end
|
|
592
|
+
#
|
|
593
|
+
# supply a custom renderer that implements +render()+
|
|
594
|
+
# @see render
|
|
595
|
+
def renderer r
|
|
596
|
+
@renderer = r
|
|
597
|
+
end
|
|
598
|
+
def header_adjustment
|
|
599
|
+
@_header_adjustment
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
##
|
|
603
|
+
# getter and setter for columns
|
|
604
|
+
# 2014-04-10 - 13:49
|
|
605
|
+
# @param [Array] columns to set as Array of Strings
|
|
606
|
+
# @return if no args, returns array of column names as Strings
|
|
607
|
+
# NOTE
|
|
608
|
+
# Appends columns to array, so it must be set before data, and thus it should
|
|
609
|
+
# clear the list
|
|
610
|
+
#
|
|
611
|
+
def columns(*val)
|
|
612
|
+
if val.empty?
|
|
613
|
+
# returns array of column names as Strings
|
|
614
|
+
@list[0]
|
|
615
|
+
else
|
|
616
|
+
array = val[0]
|
|
617
|
+
@_header_adjustment = 1
|
|
618
|
+
@list ||= []
|
|
619
|
+
@list.clear
|
|
620
|
+
@list << array
|
|
621
|
+
_init_model array
|
|
622
|
+
|
|
623
|
+
# update the names in column model
|
|
624
|
+
array.each_with_index { |n,i|
|
|
625
|
+
c = get_column(i)
|
|
626
|
+
c.name = name
|
|
627
|
+
}
|
|
628
|
+
self
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
##
|
|
633
|
+
# Set column titles with given array of strings.
|
|
634
|
+
# NOTE: This is only required to be called if first row of file or content does not contain
|
|
635
|
+
# titles. In that case, this should be called before setting the data as the array passed
|
|
636
|
+
# is appended into the content array.
|
|
637
|
+
# @deprecated complicated, just use `columns()`
|
|
638
|
+
def columns=(array)
|
|
639
|
+
columns(array)
|
|
640
|
+
self
|
|
641
|
+
end
|
|
642
|
+
alias :headings= :columns=
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
# size each column based on widths of this row of data.
|
|
646
|
+
# Only changed width if no width for that column
|
|
647
|
+
def _init_model array
|
|
648
|
+
# clear the column data -- this line should be called otherwise previous tables stuff will remain.
|
|
649
|
+
@chash.clear
|
|
650
|
+
array.each_with_index { |e,i|
|
|
651
|
+
# if columns added later we could be overwriting the width
|
|
652
|
+
c = get_column(i)
|
|
653
|
+
c.width ||= 10
|
|
654
|
+
}
|
|
655
|
+
# maintains index in current pointer and gives next or prev
|
|
656
|
+
@column_pointer = Circular.new array.size()-1
|
|
657
|
+
end
|
|
658
|
+
# size each column based on widths of this row of data.
|
|
659
|
+
def model_row index
|
|
660
|
+
array = @list[index]
|
|
661
|
+
array.each_with_index { |c,i|
|
|
662
|
+
# if columns added later we could be overwriting the width
|
|
663
|
+
ch = get_column(i)
|
|
664
|
+
ch.width = c.to_s.length + 2
|
|
665
|
+
}
|
|
666
|
+
# maintains index in current pointer and gives next or prev
|
|
667
|
+
@column_pointer = Circular.new array.size()-1
|
|
668
|
+
self
|
|
669
|
+
end
|
|
670
|
+
# estimate columns widths based on data in first 10 or so rows
|
|
671
|
+
# This will override any previous widths, so put custom widths
|
|
672
|
+
# after calling this.
|
|
673
|
+
def estimate_column_widths
|
|
674
|
+
each_column {|c,i|
|
|
675
|
+
c.width = suggest_column_width(i)
|
|
676
|
+
}
|
|
677
|
+
self
|
|
678
|
+
end
|
|
679
|
+
# calculates and returns a suggested columns width for given column
|
|
680
|
+
# based on data (first 10 rows)
|
|
681
|
+
# called by +estimate_column_widths+ in a loop
|
|
682
|
+
def suggest_column_width col
|
|
683
|
+
#ret = @cw[col] || 2
|
|
684
|
+
ret = get_column(col).width || 2
|
|
685
|
+
ctr = 0
|
|
686
|
+
@list.each_with_index { |r, i|
|
|
687
|
+
#next if i < @toprow # this is also a possibility, it checks visible rows
|
|
688
|
+
break if ctr > 10
|
|
689
|
+
ctr += 1
|
|
690
|
+
next if r == :separator
|
|
691
|
+
c = r[col]
|
|
692
|
+
x = c.to_s.length
|
|
693
|
+
ret = x if x > ret
|
|
694
|
+
}
|
|
695
|
+
ret
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
#------- data modification methods ------#
|
|
699
|
+
|
|
700
|
+
# I am assuming the column has been set using +columns=+
|
|
701
|
+
# Now only data is being sent in
|
|
702
|
+
# NOTE : calling set_content sends to TP's +text()+ which resets @list
|
|
703
|
+
# @param lines is an array or arrays
|
|
704
|
+
def text lines, fmt=:none
|
|
705
|
+
# maybe we can check this out
|
|
706
|
+
# should we not erase data, will user keep one column and resetting data ?
|
|
707
|
+
# set_content assumes data is gone.
|
|
708
|
+
@list ||= [] # this would work if no columns
|
|
709
|
+
@list.concat( lines)
|
|
710
|
+
fire_dimension_changed
|
|
711
|
+
self
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
##
|
|
715
|
+
# set column array and data array in one shot
|
|
716
|
+
# Erases any existing content
|
|
717
|
+
def resultset columns, data
|
|
718
|
+
@list = []
|
|
719
|
+
columns(columns)
|
|
720
|
+
text(data)
|
|
721
|
+
end
|
|
722
|
+
# Takes the name of a file containing delimited data
|
|
723
|
+
# and load it into the table.
|
|
724
|
+
# This method will load and split the file into the table.
|
|
725
|
+
# @param name is the file name
|
|
726
|
+
# @param config is a hash containing:
|
|
727
|
+
# - :separator - field separator, default is TAB
|
|
728
|
+
# - :columns - array of column names
|
|
729
|
+
# or true - first row is column names
|
|
730
|
+
# or false - no columns.
|
|
731
|
+
#
|
|
732
|
+
# == NOTE
|
|
733
|
+
# if columns is not mentioned, then it defaults to false
|
|
734
|
+
#
|
|
735
|
+
# == Example
|
|
736
|
+
#
|
|
737
|
+
# table = Table.new ...
|
|
738
|
+
# table.filename 'contacts.tsv', :separator => '|', :columns => true
|
|
739
|
+
#
|
|
740
|
+
def filename name, _config = {}
|
|
741
|
+
arr = File.open(name,"r").read.split("\n")
|
|
742
|
+
lines = []
|
|
743
|
+
sep = _config[:separator] || _config[:delimiter] || '\t'
|
|
744
|
+
arr.each { |l| lines << l.split(sep) }
|
|
745
|
+
cc = _config[:columns]
|
|
746
|
+
if cc.is_a? Array
|
|
747
|
+
columns(cc)
|
|
748
|
+
text(lines)
|
|
749
|
+
elsif cc
|
|
750
|
+
# cc is true, use first row as column names
|
|
751
|
+
columns(lines[0])
|
|
752
|
+
text(lines[1..-1])
|
|
753
|
+
else
|
|
754
|
+
# cc is false - no columns
|
|
755
|
+
# XXX since columns() is not called, so chash is not cleared.
|
|
756
|
+
_init_model lines[0]
|
|
757
|
+
text(lines)
|
|
758
|
+
end
|
|
759
|
+
end
|
|
760
|
+
alias :load :filename
|
|
761
|
+
|
|
762
|
+
# save the table as a file
|
|
763
|
+
# @param String name of output file. If nil, user is prompted
|
|
764
|
+
# Currently, tabs are used as delimiter, but this could be based on input
|
|
765
|
+
# separator, or prompted.
|
|
766
|
+
def save_as outfile
|
|
767
|
+
_t = "(all rows)"
|
|
768
|
+
if @selected_indices.size > 0
|
|
769
|
+
_t = "(selected rows)"
|
|
770
|
+
end
|
|
771
|
+
unless outfile
|
|
772
|
+
outfile = get_string "Enter file name to save #{_t} as "
|
|
773
|
+
return unless outfile
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
# if there is a selection, then write only selected rows
|
|
777
|
+
l = nil
|
|
778
|
+
if @selected_indices.size > 0
|
|
779
|
+
l = []
|
|
780
|
+
@list.each_with_index { |v,i| l << v if @selected_indices.include? i }
|
|
781
|
+
else
|
|
782
|
+
l = @list
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
File.open(outfile, 'w') {|f|
|
|
786
|
+
l.each {|r|
|
|
787
|
+
line = r.join "\t"
|
|
788
|
+
f.puts line
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
end
|
|
792
|
+
#
|
|
793
|
+
|
|
794
|
+
## add a row to the table
|
|
795
|
+
# The name add will be removed soon, pls use << instead.
|
|
796
|
+
def add array
|
|
797
|
+
unless @list
|
|
798
|
+
# columns were not added, this most likely is the title
|
|
799
|
+
@list ||= []
|
|
800
|
+
_init_model array
|
|
801
|
+
end
|
|
802
|
+
@list << array
|
|
803
|
+
fire_dimension_changed
|
|
804
|
+
self
|
|
805
|
+
end
|
|
806
|
+
alias :<< :add
|
|
807
|
+
|
|
808
|
+
# delete a data row at index
|
|
809
|
+
#
|
|
810
|
+
# NOTE : This does not adjust for header_adjustment. So zero will refer to the header if there is one.
|
|
811
|
+
# This is to keep consistent with textpad which does not know of header_adjustment and uses the actual
|
|
812
|
+
# index. Usually, programmers will be dealing with +@current_index+
|
|
813
|
+
#
|
|
814
|
+
def delete_at ix
|
|
815
|
+
return unless @list
|
|
816
|
+
raise ArgumentError, "Argument must be within 0 and #{@list.length}" if ix < 0 or ix >= @list.length
|
|
817
|
+
fire_dimension_changed
|
|
818
|
+
#@list.delete_at(ix + @_header_adjustment)
|
|
819
|
+
@list.delete_at(ix)
|
|
820
|
+
end
|
|
821
|
+
#
|
|
822
|
+
# clear the list completely
|
|
823
|
+
def clear
|
|
824
|
+
@selected_indices.clear
|
|
825
|
+
super
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
# get the value at the cell at row and col
|
|
829
|
+
# @return String
|
|
830
|
+
def get_value_at row,col
|
|
831
|
+
actrow = row + @_header_adjustment
|
|
832
|
+
@list[actrow, col]
|
|
833
|
+
end
|
|
834
|
+
|
|
835
|
+
# set value at the cell at row and col
|
|
836
|
+
# @param int row
|
|
837
|
+
# @param int col
|
|
838
|
+
# @param String value
|
|
839
|
+
# @return self
|
|
840
|
+
def set_value_at row,col,val
|
|
841
|
+
actrow = row + @_header_adjustment
|
|
842
|
+
@list[actrow , col] = val
|
|
843
|
+
fire_row_changed actrow
|
|
844
|
+
self
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
#------- column related methods ------#
|
|
848
|
+
#
|
|
849
|
+
# convenience method to set width of a column
|
|
850
|
+
# @param index of column
|
|
851
|
+
# @param width
|
|
852
|
+
# For setting other attributes, use get_column(index)
|
|
853
|
+
def column_width colindex, width
|
|
854
|
+
get_column(colindex).width = width
|
|
855
|
+
_invalidate_width_cache
|
|
856
|
+
end
|
|
857
|
+
# convenience method to set alignment of a column
|
|
858
|
+
# @param index of column
|
|
859
|
+
# @param align - :right (any other value is taken to be left)
|
|
860
|
+
def column_align colindex, align
|
|
861
|
+
get_column(colindex).align = align
|
|
862
|
+
end
|
|
863
|
+
# convenience method to hide or unhide a column
|
|
864
|
+
# Provided since column offsets need to be recalculated in the case of a width
|
|
865
|
+
# change or visibility change
|
|
866
|
+
def column_hidden colindex, hidden
|
|
867
|
+
get_column(colindex).hidden = hidden
|
|
868
|
+
_invalidate_width_cache
|
|
869
|
+
end
|
|
870
|
+
# http://www.opensource.apple.com/source/gcc/gcc-5483/libjava/javax/swing/table/DefaultTableColumnModel.java
|
|
871
|
+
def _invalidate_width_cache #:nodoc:
|
|
872
|
+
@coffsets = nil
|
|
873
|
+
end
|
|
874
|
+
##
|
|
875
|
+
# should all this move into table column model or somepn
|
|
876
|
+
# move a column from offset ix to offset newix
|
|
877
|
+
def move_column ix, newix
|
|
878
|
+
acol = @chash.delete_at ix
|
|
879
|
+
@chash.insert newix, acol
|
|
880
|
+
_invalidate_width_cache
|
|
881
|
+
#tmce = TableColumnModelEvent.new(ix, newix, self, :MOVE)
|
|
882
|
+
#fire_handler :TABLE_COLUMN_MODEL_EVENT, tmce
|
|
883
|
+
end
|
|
884
|
+
# TODO
|
|
885
|
+
def add_column tc
|
|
886
|
+
raise "to figure out add_column"
|
|
887
|
+
_invalidate_width_cache
|
|
888
|
+
end
|
|
889
|
+
# TODO
|
|
890
|
+
def remove_column tc
|
|
891
|
+
raise "to figure out add_column"
|
|
892
|
+
_invalidate_width_cache
|
|
893
|
+
end
|
|
894
|
+
def calculate_column_width col, maxrows=99
|
|
895
|
+
ret = 3
|
|
896
|
+
ctr = 0
|
|
897
|
+
@list.each_with_index { |r, i|
|
|
898
|
+
#next if i < @toprow # this is also a possibility, it checks visible rows
|
|
899
|
+
break if ctr > maxrows
|
|
900
|
+
ctr += 1
|
|
901
|
+
#next if r == :separator
|
|
902
|
+
c = r[col]
|
|
903
|
+
x = c.to_s.length
|
|
904
|
+
ret = x if x > ret
|
|
905
|
+
}
|
|
906
|
+
ret
|
|
907
|
+
end
|
|
908
|
+
##
|
|
909
|
+
# refresh pad onto window
|
|
910
|
+
# overrides super due to header_adjustment and the header too
|
|
911
|
+
def padrefresh
|
|
912
|
+
top = @window.top
|
|
913
|
+
left = @window.left
|
|
914
|
+
sr = @startrow + top
|
|
915
|
+
sc = @startcol + left
|
|
916
|
+
# first do header always in first row
|
|
917
|
+
retval = FFI::NCurses.prefresh(@pad,0,@pcol, sr , sc , 2 , @cols+ sc );
|
|
918
|
+
# now print rest of data
|
|
919
|
+
# h is header_adjustment
|
|
920
|
+
h = 1
|
|
921
|
+
retval = FFI::NCurses.prefresh(@pad,@prow + h,@pcol, sr + h , sc , @rows + sr , @cols+ sc );
|
|
922
|
+
$log.warn "XXX: PADREFRESH #{retval}, #{@prow}, #{@pcol}, #{sr}, #{sc}, #{@rows+sr}, #{@cols+sc}." if retval == -1
|
|
923
|
+
# padrefresh can fail if width is greater than NCurses.COLS
|
|
924
|
+
end
|
|
925
|
+
|
|
926
|
+
def create_default_sorter
|
|
927
|
+
raise "Data not sent in." unless @list
|
|
928
|
+
@table_row_sorter = DefaultTableRowSorter.new @list
|
|
929
|
+
end
|
|
930
|
+
# set a default renderer
|
|
931
|
+
#--
|
|
932
|
+
# we were not doing this automatically, so repaint was going to TP and failing on mvaddstr
|
|
933
|
+
# 2014-04-10 - 10:57
|
|
934
|
+
#++
|
|
935
|
+
def create_default_renderer
|
|
936
|
+
r = DefaultTableRenderer.new self
|
|
937
|
+
renderer(r)
|
|
938
|
+
end
|
|
939
|
+
# returns true if focus is on header_row
|
|
940
|
+
def header_row?
|
|
941
|
+
#@prow == 0
|
|
942
|
+
@prow == @current_index
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
# called when ENTER is pressed.
|
|
946
|
+
# Takes into account if user is on header_row
|
|
947
|
+
def fire_action_event
|
|
948
|
+
if header_row?
|
|
949
|
+
if @table_row_sorter
|
|
950
|
+
x = _convert_curpos_to_column
|
|
951
|
+
c = @chash[x]
|
|
952
|
+
# convert to index in data model since sorter only has data_model
|
|
953
|
+
index = c.index
|
|
954
|
+
@table_row_sorter.toggle_sort_order index
|
|
955
|
+
@table_row_sorter.sort
|
|
956
|
+
fire_dimension_changed
|
|
957
|
+
end
|
|
958
|
+
end
|
|
959
|
+
super
|
|
960
|
+
end
|
|
961
|
+
##
|
|
962
|
+
# Find the next row that contains given string
|
|
963
|
+
# Overrides textpad since each line is an array
|
|
964
|
+
# NOTE does not go to next match within row
|
|
965
|
+
# NOTE: FIXME ensure_visible puts prow = current_index so in this case, the header
|
|
966
|
+
# overwrites the matched row.
|
|
967
|
+
# @return row and col offset of match, or nil
|
|
968
|
+
# @param String to find
|
|
969
|
+
#@ deprecate since it does not get second match in line. textpad does
|
|
970
|
+
# however, the offset textpad shows is wrong
|
|
971
|
+
def OLDnext_match str
|
|
972
|
+
_calculate_column_offsets unless @coffsets
|
|
973
|
+
first = nil
|
|
974
|
+
## content can be string or Chunkline, so we had to write <tt>index</tt> for this.
|
|
975
|
+
@list.each_with_index do |fields, ix|
|
|
976
|
+
#col = line.index str
|
|
977
|
+
#fields.each_with_index do |f, jx|
|
|
978
|
+
#@chash.each_with_index do |c, jx|
|
|
979
|
+
#next if c.hidden
|
|
980
|
+
each_column do |c,jx|
|
|
981
|
+
f = fields[c.index]
|
|
982
|
+
# value can be numeric
|
|
983
|
+
col = f.to_s.index str
|
|
984
|
+
if col
|
|
985
|
+
col += @coffsets[jx]
|
|
986
|
+
first ||= [ ix, col ]
|
|
987
|
+
if ix > @current_index
|
|
988
|
+
return [ix, col]
|
|
989
|
+
end
|
|
990
|
+
end
|
|
991
|
+
end
|
|
992
|
+
end
|
|
993
|
+
return first
|
|
994
|
+
end
|
|
995
|
+
# yields each column to caller method
|
|
996
|
+
# if yield returns true, collects index of row into array and returns the array
|
|
997
|
+
# @returns array of indices which can be empty
|
|
998
|
+
# Value yielded can be fixnum or date etc
|
|
999
|
+
def matching_indices
|
|
1000
|
+
raise "block required for matching_indices" unless block_given?
|
|
1001
|
+
@indices = []
|
|
1002
|
+
## content can be string or Chunkline, so we had to write <tt>index</tt> for this.
|
|
1003
|
+
@list.each_with_index do |fields, ix|
|
|
1004
|
+
flag = yield ix, fields
|
|
1005
|
+
if flag
|
|
1006
|
+
@indices << ix
|
|
1007
|
+
end
|
|
1008
|
+
end
|
|
1009
|
+
#$log.debug "XXX: INDICES found #{@indices}"
|
|
1010
|
+
if @indices.count > 0
|
|
1011
|
+
fire_dimension_changed
|
|
1012
|
+
init_vars
|
|
1013
|
+
else
|
|
1014
|
+
@indices = nil
|
|
1015
|
+
end
|
|
1016
|
+
#return @indices
|
|
1017
|
+
end
|
|
1018
|
+
def clear_matches
|
|
1019
|
+
# clear previous match so all data can show again
|
|
1020
|
+
if @indices && @indices.count > 0
|
|
1021
|
+
fire_dimension_changed
|
|
1022
|
+
init_vars
|
|
1023
|
+
end
|
|
1024
|
+
@indices = nil
|
|
1025
|
+
end
|
|
1026
|
+
##
|
|
1027
|
+
# Ensure current row is visible, if not make it first row
|
|
1028
|
+
# This overrides textpad due to header_adjustment, otherwise
|
|
1029
|
+
# during next_match, the header overrides the found row.
|
|
1030
|
+
# @param current_index (default if not given)
|
|
1031
|
+
#
|
|
1032
|
+
def ensure_visible row = @current_index
|
|
1033
|
+
unless is_visible? row
|
|
1034
|
+
@prow = @current_index - @_header_adjustment
|
|
1035
|
+
end
|
|
1036
|
+
end
|
|
1037
|
+
#
|
|
1038
|
+
# yields non-hidden columns (ColumnInfo) and the offset/index
|
|
1039
|
+
# This is the order in which columns are to be printed
|
|
1040
|
+
def each_column
|
|
1041
|
+
@chash.each_with_index { |c, i|
|
|
1042
|
+
next if c.hidden
|
|
1043
|
+
yield c,i if block_given?
|
|
1044
|
+
}
|
|
1045
|
+
end
|
|
1046
|
+
# calls the renderer for all rows of data giving them pad, lineno, and line data
|
|
1047
|
+
def render_all
|
|
1048
|
+
if @indices && @indices.count > 0
|
|
1049
|
+
@indices.each_with_index do |ix, jx|
|
|
1050
|
+
render @pad, jx, @list[ix]
|
|
1051
|
+
end
|
|
1052
|
+
else
|
|
1053
|
+
@list.each_with_index { |line, ix|
|
|
1054
|
+
#FFI::NCurses.mvwaddstr(@pad,ix, 0, @list[ix])
|
|
1055
|
+
render @pad, ix, line
|
|
1056
|
+
}
|
|
1057
|
+
end
|
|
1058
|
+
end
|
|
1059
|
+
# print footer containing line and total, overriding textpad which prints column offset also
|
|
1060
|
+
# This is called internally by +repaint()+ but can be overridden for more complex printing.
|
|
1061
|
+
def print_foot
|
|
1062
|
+
return unless @print_footer
|
|
1063
|
+
ha = @_header_adjustment
|
|
1064
|
+
# ha takes into account whether there are headers or not
|
|
1065
|
+
footer = "#{@current_index+1-ha} of #{@list.length-ha} "
|
|
1066
|
+
@graphic.printstring( @row + @height -1 , @col+2, footer, @color_pair || $datacolor, @footer_attrib)
|
|
1067
|
+
@repaint_footer_required = false
|
|
1068
|
+
end
|
|
1069
|
+
|
|
1070
|
+
end # class Table
|
|
1071
|
+
|
|
1072
|
+
end # module
|