canis 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +45 -0
  3. data/CHANGES +52 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +24 -0
  7. data/Rakefile +2 -0
  8. data/canis.gemspec +25 -0
  9. data/examples/alpmenu.rb +46 -0
  10. data/examples/app.sample +19 -0
  11. data/examples/appemail.rb +191 -0
  12. data/examples/atree.rb +105 -0
  13. data/examples/bline.rb +181 -0
  14. data/examples/common/devel.rb +319 -0
  15. data/examples/common/file.rb +93 -0
  16. data/examples/data/README.markdown +9 -0
  17. data/examples/data/brew.txt +38 -0
  18. data/examples/data/color.2 +37 -0
  19. data/examples/data/gemlist.txt +59 -0
  20. data/examples/data/lotr.txt +12 -0
  21. data/examples/data/ports.txt +136 -0
  22. data/examples/data/table.txt +37 -0
  23. data/examples/data/tasks.csv +88 -0
  24. data/examples/data/tasks.txt +27 -0
  25. data/examples/data/todo.txt +16 -0
  26. data/examples/data/todocsv.csv +28 -0
  27. data/examples/data/unix1.txt +21 -0
  28. data/examples/data/unix2.txt +11 -0
  29. data/examples/dbdemo.rb +506 -0
  30. data/examples/dirtree.rb +177 -0
  31. data/examples/newtabbedwindow.rb +100 -0
  32. data/examples/newtesttabp.rb +92 -0
  33. data/examples/tabular.rb +212 -0
  34. data/examples/tasks.rb +179 -0
  35. data/examples/term2.rb +88 -0
  36. data/examples/testbuttons.rb +307 -0
  37. data/examples/testcombo.rb +102 -0
  38. data/examples/testdb.rb +182 -0
  39. data/examples/testfields.rb +208 -0
  40. data/examples/testflowlayout.rb +43 -0
  41. data/examples/testkeypress.rb +98 -0
  42. data/examples/testlistbox.rb +187 -0
  43. data/examples/testlistbox1.rb +199 -0
  44. data/examples/testmessagebox.rb +144 -0
  45. data/examples/testprogress.rb +116 -0
  46. data/examples/testree.rb +107 -0
  47. data/examples/testsplitlayout.rb +53 -0
  48. data/examples/testsplitlayout1.rb +49 -0
  49. data/examples/teststacklayout.rb +48 -0
  50. data/examples/testwsshortcuts.rb +68 -0
  51. data/examples/testwsshortcuts2.rb +129 -0
  52. data/lib/canis.rb +16 -0
  53. data/lib/canis/core/docs/index.txt +104 -0
  54. data/lib/canis/core/docs/list.txt +16 -0
  55. data/lib/canis/core/docs/style_help.yml +34 -0
  56. data/lib/canis/core/docs/tabbedpane.txt +15 -0
  57. data/lib/canis/core/docs/table.txt +31 -0
  58. data/lib/canis/core/docs/textpad.txt +48 -0
  59. data/lib/canis/core/docs/tree.txt +23 -0
  60. data/lib/canis/core/include/.DS_Store +0 -0
  61. data/lib/canis/core/include/action.rb +83 -0
  62. data/lib/canis/core/include/actionmanager.rb +49 -0
  63. data/lib/canis/core/include/appmethods.rb +179 -0
  64. data/lib/canis/core/include/bordertitle.rb +49 -0
  65. data/lib/canis/core/include/canisparser.rb +100 -0
  66. data/lib/canis/core/include/colorparser.rb +437 -0
  67. data/lib/canis/core/include/defaultfilerenderer.rb +64 -0
  68. data/lib/canis/core/include/io.rb +320 -0
  69. data/lib/canis/core/include/layouts/SplitLayout.rb +161 -0
  70. data/lib/canis/core/include/layouts/abstractlayout.rb +213 -0
  71. data/lib/canis/core/include/layouts/flowlayout.rb +104 -0
  72. data/lib/canis/core/include/layouts/stacklayout.rb +109 -0
  73. data/lib/canis/core/include/listbindings.rb +89 -0
  74. data/lib/canis/core/include/listeditable.rb +319 -0
  75. data/lib/canis/core/include/listoperations.rb +61 -0
  76. data/lib/canis/core/include/listselectionmodel.rb +388 -0
  77. data/lib/canis/core/include/multibuffer.rb +173 -0
  78. data/lib/canis/core/include/ractionevent.rb +73 -0
  79. data/lib/canis/core/include/rchangeevent.rb +27 -0
  80. data/lib/canis/core/include/rhistory.rb +95 -0
  81. data/lib/canis/core/include/rinputdataevent.rb +47 -0
  82. data/lib/canis/core/include/textdocument.rb +111 -0
  83. data/lib/canis/core/include/vieditable.rb +175 -0
  84. data/lib/canis/core/include/widgetmenu.rb +66 -0
  85. data/lib/canis/core/system/colormap.rb +165 -0
  86. data/lib/canis/core/system/keydefs.rb +32 -0
  87. data/lib/canis/core/system/ncurses.rb +237 -0
  88. data/lib/canis/core/system/panel.rb +129 -0
  89. data/lib/canis/core/system/window.rb +1081 -0
  90. data/lib/canis/core/util/ansiparser.rb +119 -0
  91. data/lib/canis/core/util/app.rb +696 -0
  92. data/lib/canis/core/util/basestack.rb +412 -0
  93. data/lib/canis/core/util/defaultcolorparser.rb +84 -0
  94. data/lib/canis/core/util/extras/README +5 -0
  95. data/lib/canis/core/util/extras/bottomline.rb +1815 -0
  96. data/lib/canis/core/util/extras/padreader.rb +192 -0
  97. data/lib/canis/core/util/focusmanager.rb +31 -0
  98. data/lib/canis/core/util/helpmanager.rb +160 -0
  99. data/lib/canis/core/util/oldwidgetshortcuts.rb +304 -0
  100. data/lib/canis/core/util/promptmenu.rb +235 -0
  101. data/lib/canis/core/util/rcommandwindow.rb +933 -0
  102. data/lib/canis/core/util/rdialogs.rb +520 -0
  103. data/lib/canis/core/util/textutils.rb +74 -0
  104. data/lib/canis/core/util/viewer.rb +238 -0
  105. data/lib/canis/core/util/widgetshortcuts.rb +508 -0
  106. data/lib/canis/core/widgets/applicationheader.rb +103 -0
  107. data/lib/canis/core/widgets/box.rb +58 -0
  108. data/lib/canis/core/widgets/divider.rb +310 -0
  109. data/lib/canis/core/widgets/extras/README.md +12 -0
  110. data/lib/canis/core/widgets/extras/rtextarea.rb +960 -0
  111. data/lib/canis/core/widgets/extras/stackflow.rb +474 -0
  112. data/lib/canis/core/widgets/keylabelprinter.rb +194 -0
  113. data/lib/canis/core/widgets/listbox.rb +326 -0
  114. data/lib/canis/core/widgets/listfooter.rb +86 -0
  115. data/lib/canis/core/widgets/rcombo.rb +210 -0
  116. data/lib/canis/core/widgets/rcontainer.rb +415 -0
  117. data/lib/canis/core/widgets/rlink.rb +30 -0
  118. data/lib/canis/core/widgets/rmenu.rb +970 -0
  119. data/lib/canis/core/widgets/rmenulink.rb +30 -0
  120. data/lib/canis/core/widgets/rmessagebox.rb +400 -0
  121. data/lib/canis/core/widgets/rprogress.rb +118 -0
  122. data/lib/canis/core/widgets/rtabbedpane.rb +631 -0
  123. data/lib/canis/core/widgets/rtabbedwindow.rb +70 -0
  124. data/lib/canis/core/widgets/rwidget.rb +3634 -0
  125. data/lib/canis/core/widgets/scrollbar.rb +147 -0
  126. data/lib/canis/core/widgets/statusline.rb +113 -0
  127. data/lib/canis/core/widgets/table.rb +1072 -0
  128. data/lib/canis/core/widgets/tabular.rb +264 -0
  129. data/lib/canis/core/widgets/textpad.rb +1674 -0
  130. data/lib/canis/core/widgets/tree.rb +690 -0
  131. data/lib/canis/core/widgets/tree/treecellrenderer.rb +150 -0
  132. data/lib/canis/core/widgets/tree/treemodel.rb +432 -0
  133. data/lib/canis/version.rb +3 -0
  134. metadata +229 -0
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env ruby -w
2
+ =begin
3
+ * Name : A Quick take on tabular data. Readonly.
4
+ * Description : To show tabular data inside a control, rather than going by the huge
5
+ Table object, I want to create a simple, minimal table data generator.
6
+ This will be thrown into a TextView for the user to navigate, select
7
+ etc.
8
+ I would use this applications where the tabular data is fairly fixed
9
+ not where i want the user to select columns, move them, expand etc.
10
+ * :
11
+ * Author : jkepler
12
+ * Date :
13
+ * License :
14
+ Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
15
+
16
+ =end
17
+
18
+ #
19
+ # A simple tabular data generator. Given table data in arrays and a column heading row in arrays, it
20
+ # quickely generates tabular data. It only takes left and right alignment of columns into account.
21
+ # You may specify individual column widths. Else it will take the widths of the column names you supply
22
+ # in the startup array. You are encouraged to supply column widths.
23
+ # If no columns are specified, and no widths are given, it take the widths of the first row
24
+ # as a model to determine column widths.
25
+ #
26
+ module Canis
27
+
28
+ class Tabular
29
+ GUESSCOLUMNS = 20
30
+
31
+ def yield_or_eval &block
32
+ return unless block
33
+ if block.arity > 0
34
+ yield self
35
+ else
36
+ self.instance_eval(&block)
37
+ end
38
+ end
39
+ # stores column info internally
40
+ class ColumnInfo < Struct.new(:name, :w, :align)
41
+ end
42
+ # an array of column titles
43
+ attr_reader :columns
44
+ # boolean, does user want lines numbered
45
+ attr_accessor :numbering
46
+ # x is the + character used a field delim in separators
47
+ # y is the field delim used in data rows, default is pipe or bar
48
+ attr_accessor :x, :y
49
+
50
+ # takes first optional argument as array of column names
51
+ # second optional argument as array of data arrays
52
+ # @yield self
53
+ #
54
+ def initialize cols=nil, *args, &block
55
+ @chash = {}
56
+ @cw = {}
57
+ @calign = {}
58
+ @separ = @columns = @numbering = nil
59
+ @y = '|'
60
+ @x = '+'
61
+ self.columns = cols if cols
62
+ if !args.empty?
63
+ #puts "ARGS after shift #{args} "
64
+ if !args.empty?
65
+ self.data = args
66
+ end
67
+ end
68
+ yield_or_eval(&block) if block_given?
69
+ end
70
+ #
71
+ # set columns names
72
+ # @param [Array<String>] column names, preferably padded out to width for column
73
+ def columns=(array)
74
+ $log.debug "tabular got columns #{array.count} #{array.inspect} " if $log
75
+ @columns = array
76
+ @columns.each_with_index { |c,i|
77
+ @chash[i] = ColumnInfo.new(c, c.to_s.length)
78
+ @cw[i] ||= c.to_s.length
79
+ #@calign[i] ||= :left # 2011-09-27 prevent setting later on
80
+ }
81
+ end
82
+ alias :headings= :columns=
83
+ #
84
+ # set data as an array of arrays
85
+ # @param [Array<Array>] data as array of arrays
86
+ def data=(list)
87
+ #puts "got data: #{list.size} " if !$log
88
+ #puts list if !$log
89
+ @list = list
90
+ end
91
+
92
+ # add a row of data
93
+ # @param [Array] an array containing entries for each column
94
+ def add array
95
+ $log.debug "tabular got add #{array.count} #{array.inspect} " if $log
96
+ @list ||= []
97
+ @list << array
98
+ end
99
+ alias :<< :add
100
+ alias :add_row :add
101
+
102
+ # set width of a given column
103
+ # @param [Number] column offset, starting 0
104
+ # @param [Number] width
105
+ def column_width colindex, width
106
+ @cw[colindex] ||= width
107
+ if @chash[colindex].nil?
108
+ @chash[colindex] = ColumnInfo.new("", width)
109
+ else
110
+ @chash[colindex].w = width
111
+ end
112
+ @chash
113
+ end
114
+
115
+ # set alignment of given column offset
116
+ # @param [Number] column offset, starting 0
117
+ # @param [Symbol] :left, :right
118
+ def align_column colindex, lrc
119
+ raise ArgumentError, "wrong alignment value sent" if ![:right, :left, :center].include? lrc
120
+ @calign[colindex] ||= lrc
121
+ if @chash[colindex].nil?
122
+ @chash[colindex] = ColumnInfo.new("", nil, lrc)
123
+ else
124
+ @chash[colindex].align = lrc
125
+ end
126
+ @chash
127
+ end
128
+
129
+ #
130
+ # Now returns an array with formatted data
131
+ # @return [Array<String>] array of formatted data
132
+ def render
133
+ buffer = []
134
+ _guess_col_widths
135
+ rows = @list.size.to_s.length
136
+ @rows = rows
137
+ _prepare_format
138
+
139
+ str = ""
140
+ if @numbering
141
+ str = " "*(rows+1)+@y
142
+ end
143
+ str << @fmstr % @columns
144
+ buffer << str
145
+ #puts "-" * str.length
146
+ buffer << separator
147
+ if @list
148
+ if @numbering
149
+ @fmstr = "%#{rows}d "+ @y + @fmstr
150
+ end
151
+ #@list.each { |e| puts e.join(@y) }
152
+ count = 0
153
+ @list.each_with_index { |r,i|
154
+ value = convert_value_to_text r, count
155
+ buffer << value
156
+ count += 1
157
+ }
158
+ end
159
+ buffer
160
+ end
161
+ def convert_value_to_text r, count
162
+ if r == :separator
163
+ return separator
164
+ end
165
+ if @numbering
166
+ r.insert 0, count+1
167
+ end
168
+ return @fmstr % r;
169
+ end
170
+ # use this for printing out on terminal
171
+ # @example
172
+ # puts t.to_s
173
+ def to_s
174
+ render().join "\n"
175
+ end
176
+ def add_separator
177
+ @list << :separator
178
+ end
179
+ def separator
180
+ return @separ if @separ
181
+ str = ""
182
+ if @numbering
183
+ str = "-"*(@rows+1)+@x
184
+ end
185
+ @cw.each_pair { |k,v| str << "-" * (v+1) + @x }
186
+ @separ = str.chop
187
+ end
188
+ private
189
+ def _guess_col_widths #:nodoc:
190
+ @list.each_with_index { |r, i|
191
+ break if i > GUESSCOLUMNS
192
+ next if r == :separator
193
+ r.each_with_index { |c, j|
194
+ x = c.to_s.length
195
+ if @cw[j].nil?
196
+ @cw[j] = x
197
+ else
198
+ @cw[j] = x if x > @cw[j]
199
+ end
200
+ }
201
+ }
202
+ end
203
+ def _prepare_format #:nodoc:
204
+ @fmtstr = nil
205
+ fmt = []
206
+ @cw.each_with_index { |c, i|
207
+ w = @cw[i]
208
+ case @calign[i]
209
+ when :right
210
+ fmt << "%#{w}s "
211
+ else
212
+ fmt << "%-#{w}s "
213
+ end
214
+ }
215
+ @fmstr = fmt.join(@y)
216
+ #puts "format: #{@fmstr} " # 2011-12-09 23:09:57
217
+ end
218
+ end
219
+ end
220
+
221
+ if __FILE__ == $PROGRAM_NAME
222
+ include Canis
223
+ $log = nil
224
+ t = Tabular.new(['a', 'b'], [1, 2], [3, 4])
225
+ puts t.to_s
226
+ puts
227
+ t = Tabular.new([" Name ", " Number ", " Email "])
228
+ t.add %w{ rahul 32 r@ruby.org }
229
+ t << %w{ _why 133 j@gnu.org }
230
+ t << %w{ Jane 1331 jane@gnu.org }
231
+ t.column_width 1, 10
232
+ t.align_column 1, :right
233
+ puts t.to_s
234
+ puts
235
+
236
+ s = Tabular.new do |b|
237
+ b.columns = %w{ country continent text }
238
+ b << ["india","asia","a warm country" ]
239
+ b << ["japan","asia","a cool country" ]
240
+ b << ["russia","europe","a hot country" ]
241
+ b.column_width 2, 30
242
+ end
243
+ puts s.to_s
244
+ puts
245
+ puts "::::"
246
+ puts
247
+ s = Tabular.new do |b|
248
+ b.columns = %w{ place continent text }
249
+ b << ["india","asia","a warm country" ]
250
+ b << ["japan","asia","a cool country" ]
251
+ b << ["russia","europe","a hot country" ]
252
+ b << ["sydney","australia","a dry country" ]
253
+ b << ["canberra","australia","a dry country" ]
254
+ b << ["ross island","antarctica","a dry country" ]
255
+ b << ["mount terror","antarctica","a windy country" ]
256
+ b << ["mt erebus","antarctica","a cold place" ]
257
+ b << ["siberia","russia","an icy city" ]
258
+ b << ["new york","USA","a fun place" ]
259
+ b.column_width 0, 12
260
+ b.column_width 1, 12
261
+ b.numbering = true
262
+ end
263
+ puts s.to_s
264
+ end
@@ -0,0 +1,1674 @@
1
+ #!/usr/bin/env ruby
2
+ # header {{{
3
+ # vim: set foldlevel=0 foldmethod=marker :
4
+ # ----------------------------------------------------------------------------- #
5
+ # File: textpad.rb
6
+ # Description: A class that displays text using a pad.
7
+ # The motivation for this is to put formatted text and not care about truncating and
8
+ # stuff. Also, there will be only one write, not each time scrolling happens.
9
+ # I found textview code for repaint being more complex than required.
10
+ # Author: jkepler http://github.com/mare-imbrium/mancurses/
11
+ # Date: 2011-11-09 - 16:59
12
+ # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
13
+ # Last update: 2014-07-08 13:04
14
+ #
15
+ # == CHANGES
16
+ # - changed @content to @list since all multirow widgets use that and so do utils etc
17
+ # == TODO
18
+ # Take care of 3 cases:
19
+ # 1. complete data change, then recreate pad, and call init_vars resetting row, col and curpos etc
20
+ # This is done by method text().
21
+ # 2. row added or minor change - recreate pad, repaint data but don't call initvars. must maintain cursor
22
+ # ignore recreate of pad if width or ht is less than w and h of container.
23
+ # 3. only rewrite a row - row data changed, no recreate pad or anything else
24
+ #
25
+ # ----------------------------------------------------------------------------- #
26
+ # header }}}
27
+ #
28
+ require 'canis'
29
+ require 'canis/core/include/bordertitle'
30
+ require 'canis/core/include/textdocument'
31
+ require 'forwardable'
32
+
33
+ include Canis
34
+ module Canis
35
+ extend self
36
+ class TextPad < Widget #
37
+ include BorderTitle
38
+ extend Forwardable
39
+
40
+ # ---- Section initialization start ----- {{{
41
+ # boolean, whether border to be suppressed or not, default false
42
+ dsl_accessor :suppress_borders
43
+ # boolean, whether footer is to be printed or not, default true
44
+ dsl_accessor :print_footer
45
+ #dsl_accessor :list_footer # attempt at making a footer object
46
+ # index of focussed row, starting 0, index into the data supplied
47
+ attr_reader :current_index
48
+ # rows is the actual number of rows the pad has which is slightly less than height (taking
49
+ # into account borders. Same for cols and width.
50
+ attr_reader :rows , :cols
51
+ dsl_accessor :footer_attrib # bold, reverse, normal
52
+ # adding these only for debugging table, to see where cursor is.
53
+ attr_reader :lastrow, :lastcol
54
+ # for external methods or classes to advance cursor
55
+ #attr_accessor :curpos
56
+ # the object that handles keys that are sent to this object by the form.
57
+ # This widget creates its own default handler if not overridden by user.
58
+ attr_accessor :key_handler
59
+
60
+ # an array of 4 items for h w t and l which can be nil, padrefresh will check
61
+ # its bounds against this to ensure no caller messes up. I don't use this any longer it was experimental.
62
+ dsl_accessor :fixed_bounds
63
+ # You may pass height, width, row and col for creating a window otherwise a fullscreen window
64
+ # will be created. If you pass a window from caller then that window will be used.
65
+ # Some keys are trapped, jkhl space, pgup, pgdown, end, home, t b
66
+ # This is currently very minimal and was created to get me started to integrating
67
+ # pads into other classes such as textview.
68
+
69
+ # a map of symbols and patterns, used currently in jumping to next or prev occurence of that
70
+ # pattern. Default contains :word. Callers may add patterns, or modify existing ones and
71
+ # create key bindings for the same.
72
+ attr_reader :text_patterns
73
+ # type of content in case parsing is required. Values :tmux, :ansi, :none
74
+ # trying to put content+type into document
75
+ #attr_accessor :content_type
76
+ # path of yaml file which contains conversion of style names to color, bgcolor and attrib
77
+ #attr_accessor :stylesheet
78
+ attr_accessor :pad
79
+ attr_reader :document
80
+
81
+ def initialize form=nil, config={}, &block
82
+
83
+ @editable = false
84
+ @focusable = true
85
+ @config = config
86
+ @row = @col = 0
87
+ @prow = @pcol = 0
88
+ @startrow = 0
89
+ @startcol = 0
90
+ register_events( [:ENTER_ROW, :PRESS, :DIMENSION_CHANGED, :ROW_CHANGED])
91
+ @text_patterns = {}
92
+ # FIXME we need to be compatible with vim's word
93
+ @text_patterns[:word] = /[[:punct:][:space:]]\S/
94
+ # we need to be compatible with vim's WORD
95
+ @text_patterns[:WORD] = /[[:space:]]\S/
96
+ super
97
+
98
+ init_vars
99
+ end
100
+ def init_vars
101
+ $multiplier = 0
102
+ @oldindex = @current_index = 0
103
+ # column cursor
104
+ @prow = @pcol = @curpos = 0
105
+ if @row && @col
106
+ @lastrow = @row + @row_offset
107
+ @lastcol = @col + @col_offset
108
+ end
109
+ # next 4 are required because in some cases a descendant may not use methods such as list or text
110
+ # to populate. +tree+ has an option of using +root()+.
111
+ @repaint_required = true
112
+ @repaint_all = true
113
+ @_populate_needed = true
114
+ map_keys unless @mapped_keys
115
+
116
+ end
117
+
118
+ # calculates the dimensions of the pad which will be used when the pad refreshes, taking into account
119
+ # whether borders are printed or not. This must be called whenever there is a change in height or width
120
+ # otherwise @rows will not be recalculated.
121
+ # Internal.
122
+ def __calc_dimensions
123
+ ## NOTE
124
+ # ---------------------------------------------------
125
+ # Since we are using pads, you need to get your height, width and rows correct
126
+ # Make sure the height factors in the row, else nothing may show
127
+ # ---------------------------------------------------
128
+
129
+
130
+ raise " CALC inside #{@name} h or w is nil #{@height} , #{@width} " if @height.nil? or @width.nil?
131
+ @rows = @height
132
+ @cols = @width
133
+ # NOTE XXX if cols is > COLS then padrefresh can fail
134
+ @startrow = @row
135
+ @startcol = @col
136
+ unless @suppress_borders
137
+ @row_offset = @col_offset = 1
138
+ @startrow += 1
139
+ @startcol += 1
140
+ @rows -=3 # 3 is since print_border_only reduces one from width, to check whether this is correct
141
+ @cols -=3
142
+ @scrollatrows = @height - 3
143
+ else
144
+ # no borders printed
145
+ @rows -= 1 # 3 is since print_border_only reduces one from width, to check whether this is correct
146
+ ## if next is 0 then padrefresh doesn't print, gives -1 sometimes.
147
+ ## otoh, if we reduce 1, then there is a blank or white left at 128 since clear_pad clears 128
148
+ # but this only writes 127 2014-05-01 - 12:31 CLEAR_PAD
149
+ #@cols -=0
150
+ @cols -=1
151
+ @scrollatrows = @height - 1 # check this out 0 or 1
152
+ @row_offset = @col_offset = 0
153
+ end
154
+ @top = @row
155
+ @left = @col
156
+ @lastrow = @row + @row_offset
157
+ @lastcol = @col + @col_offset
158
+ $log.debug " CALC_DIMENSION #{@rows} , #{@cols}, #{@height} , #{@width} , #{@top} , #{@left} "
159
+ end
160
+ def scrollatrows
161
+ unless @suppress_borders
162
+ @height - 3
163
+ else
164
+ @height - 1
165
+ end
166
+ end
167
+ alias :rows :scrollatrows
168
+ # returns the row and col where the cursor is initially placed, and where printing starts from.
169
+ def rowcol #:nodoc:
170
+ return @row+@row_offset, @col+@col_offset
171
+ end
172
+
173
+ # update the height
174
+ # This also calls fire_dimension_changed so that the dimensions can be recalculated
175
+ def height=(val)
176
+ super
177
+ fire_dimension_changed :height
178
+ end
179
+ # set the width
180
+ # This also calls fire_dimension_changed so that the dimensions can be recalculated
181
+ def width=(val)
182
+ super
183
+ fire_dimension_changed :width
184
+ end
185
+ # ---- Section initialization end ----- }}}
186
+ # ---- Section pad related start ----------- {{{
187
+
188
+ private
189
+ ## creates the pad
190
+ def create_pad
191
+ destroy if @pad
192
+ #$log.debug "XXXCP: create_pad #{@content_rows} #{@content_cols} , w:#{@width} c #{@cols} , r: #{@rows}"
193
+
194
+ @content_rows = @content_cols = nil
195
+ @content_rows = pad_rows()
196
+ @content_cols = pad_cols()
197
+ $log.debug "XXXCP: create_pad :#{@content_rows} , #{@content_cols} . w:#{@width} c #{@cols} , r: #{@rows}"
198
+ raise "create_pad content_rows is nil " unless @content_rows
199
+ raise "create_pad content_cols is nil " unless @content_cols
200
+
201
+ @pad = @window.get_pad(@content_rows, @content_cols )
202
+
203
+ end
204
+ # content_rows can be more than size of pad, but never less. Same for cols.
205
+ # height of pad, or number of row, earlier called @content_rows
206
+ public
207
+ def pad_rows
208
+ # content_rows can be more than size of pad, but never less. Same for cols.
209
+ return @content_rows if @content_rows
210
+ content_rows = @list.count
211
+ content_rows = @rows if content_rows < @rows
212
+ return content_rows
213
+ end
214
+ # content_rows can be more than size of pad, but never less. Same for cols.
215
+ # width or cols of pad, earlier called @content_cols
216
+ public
217
+ def pad_cols
218
+ return @content_cols if @content_cols
219
+ _content_cols = content_cols()
220
+ _content_cols = @cols if _content_cols < @cols
221
+ return _content_cols
222
+ end
223
+
224
+ private
225
+ # creates pad and sets repaint_all so populate happens
226
+ # FIXME - earlier used to create and populate, but now i have decoupled populate
227
+ # since that was forcing create to happen.
228
+ def populate_pad
229
+ @_populate_needed = false
230
+
231
+ create_pad
232
+
233
+ # clearstring is the string required to clear the pad to background color
234
+ @clearstring = nil
235
+ $log.debug " populate pad color = #{@color} , bg = #{@bgcolor} "
236
+ #cp = get_color($datacolor, color(), bgcolor())
237
+ # commenting off next line meant that textdialog had a black background 2014-05-01 - 23:37
238
+ #@cp = FFI::NCurses.COLOR_PAIR(cp)
239
+ # we seem to be clearing always since a pad is often reused. so making the variable whenever pad created.
240
+
241
+ @repaint_all = true
242
+
243
+ end
244
+
245
+ # clear the pad.
246
+ # There seem to be some cases when previous content of a pad remains
247
+ # in the last row or last col. So we clear.
248
+ # WARNING : pad can only clear the portion of the component placed on the window.
249
+ # As of 2014-05-01 - 16:07 this is no longer called since it messes with messagboxes.
250
+ # If you make this operational, pls test testmessageboxes.rb and look for black areas
251
+ # and see if the left-most column is missing.
252
+ public
253
+ def clear_pad
254
+ # this still doesn't work since somehow content_rows is less than height.
255
+ # this is ineffectual if the rest of the code is functioning.
256
+ # But REQUIRED for listbox which has its own clear_row needed in cases of white background.
257
+ # as in testmessagebox.rb 5
258
+
259
+ # if called directly then cp does not reflect changes to bgcolor
260
+ _color = color()
261
+ _bgcolor = bgcolor()
262
+ cp = get_color($datacolor, _color, _bgcolor)
263
+ @cp = FFI::NCurses.COLOR_PAIR(cp)
264
+
265
+ raise "pad not created" unless @pad
266
+ $log.debug " clear pad content Rows is #{@content_rows} "
267
+ raise "content_rows is nil" unless @content_rows
268
+ (0..@content_rows).each do |n|
269
+ clear_row @pad, n
270
+ end
271
+ # REST IS REQUIRED otherwise sometimes last line of window is not cleared
272
+ # Happens in bline.rb. i think the above clears the new pad size in the window
273
+ # which if it is smaller then does not clear complete window.
274
+ ## TRYING OUT COMMENTING OFF THE REMAINDER 2014-05-31 - 14:35
275
+ # next part is messing up messageboxes which have a white background
276
+ # so i use this copied from print_border
277
+ # In messageboxes the border is more inside. but pad cannot clear the entire
278
+ # window. The component may be just a part of the window.
279
+ r,c = rowcol
280
+ # width - 2 was okay if we started from @col + 1 as print_border does in window
281
+ # but here we start from @col so reduce only 1 2014-06-20 - 01:13 TEST THIS XXX
282
+ ww=width-2
283
+ ww=width-1
284
+ _col = @col + 0
285
+ startcol = 1
286
+ startcol = 0 if @suppress_borders
287
+ # need to account for borders. in col+1 and ww
288
+ ww=@width-0 if @suppress_borders
289
+ color = @cp || $datacolor # check this out XXX @cp is already converted to COLOR_PAIR !!
290
+ color = get_color($datacolor, _color, _bgcolor)
291
+ att = attr() || NORMAL
292
+ sp = " " * ww
293
+ #if color == $datacolor
294
+ $log.debug " clear_pad: colors #{@cp}, ( #{_bgcolor} #{_color} ) #{$datacolor} , attrib #{att} . r #{r} w #{ww}, h #{@height} top #{@window.top} "
295
+ # 2014-05-15 - 11:01 seems we were clearing an extra row at bottom.
296
+ # earlier it was r+1 but that was missing the first row, so now made it r+0 2014-06-20 - 01:15 XXX
297
+ (r+0).upto(r+@height-startcol-1) do |rr|
298
+ @window.printstring( rr, _col ,sp , color, att)
299
+ end
300
+ #end
301
+ end
302
+ # destroy the pad, this needs to be called from somewhere, like when the app
303
+ # closes or the current window closes , or else we could have a seg fault
304
+ # or some ugliness on the screen below this one (if nested).
305
+
306
+ # Now since we use get_pad from window, upon the window being destroyed,
307
+ # it will call this. Else it will destroy pad
308
+ def destroy
309
+ FFI::NCurses.delwin(@pad) if @pad # when do i do this ? FIXME
310
+ @pad = nil
311
+ end
312
+ # write pad onto window
313
+ #private
314
+ # padrefresh can fail if width is greater than NCurses.COLS
315
+ # padrefresh can fail if height (@rows + sr) is greater than NCurses.LINES or tput lines
316
+ # try reducing height when creating textpad.
317
+ public
318
+ def padrefresh
319
+ # sometimes padref is called directly from somewhere but dimensions have changed.
320
+ # 2014-05-27 - 11:42
321
+ unless @__first_time
322
+ __calc_dimensions
323
+ @__first_time = true
324
+ end
325
+ # startrow is the row of TP plus 1 for border
326
+ top = @window.top
327
+ left = @window.left
328
+ sr = @startrow + top
329
+ sc = @startcol + left
330
+ ser = @rows + sr
331
+ sec = @cols + sc
332
+
333
+ if @fixed_bounds
334
+ #retval = FFI::NCurses.prefresh(@pad,@prow,@pcol, sr , sc , ser , sec );
335
+ $log.debug "PAD going into fixed_bounds with #{@fixed_bounds}"
336
+ sr = @fixed_bounds[0] if @fixed_bounds[0]
337
+ sc = @fixed_bounds[1] if @fixed_bounds[1]
338
+ ser = @fixed_bounds[2] if @fixed_bounds[2]
339
+ sec = @fixed_bounds[3] if @fixed_bounds[3]
340
+ end
341
+
342
+ # this is a fix, but the entire popup is not moved up. title and borders are still
343
+ # drawn in wrong positions, and left there after popup is off.
344
+ maxr = FFI::NCurses.LINES - 1
345
+ maxc = FFI::NCurses.COLS
346
+ if ser > maxr
347
+ $log.warn "XXX PADRE ser > max. sr= #{sr} and ser #{ser}. sr:#{@startrow}+ #{top} , sc:#{@startcol}+ #{left}, rows:#{@rows}+ #{sr} cols:#{@cols}+ #{sc} top #{top} left #{left} "
348
+ #_t = ser - maxr
349
+ #ser = maxr
350
+ #sr -= _t
351
+ #$log.warn "XXX PADRE after correcting ser #{sr} and #{ser} "
352
+ end
353
+ # there are some freak cases where prow or pcol comes as -1, but prefresh does not return a -1. However, this
354
+ # could affect some other calculation somewhere.
355
+
356
+ retval = FFI::NCurses.prefresh(@pad,@prow,@pcol, sr , sc , ser , sec );
357
+ $log.warn "XXXPADREFRESH #{retval} #{self.class}:#{self.name}, #{@prow}, #{@pcol}, #{sr}, #{sc}, #{ser}, #{sec}." if retval == -1
358
+ # remove next debug statement after some testing DELETE
359
+ $log.debug "0PADREFRESH #{retval} #{self.class}, #{@prow}, #{@pcol}, #{sr}, #{sc}, #{ser}, #{sec}." if retval == 0
360
+ if retval < 0
361
+ Ncurses.beep
362
+ if sr > maxr
363
+ $log.warn "XXXPADREF #{sr} should be <= #{maxr} "
364
+ end
365
+ if sc < 0 || sc >= maxc
366
+ $log.warn "XXXPADREF #{sc} should be less than #{maxc} "
367
+ end
368
+ if ser > maxr || ser < sr
369
+ $log.warn "XXXPADREF #{ser} should be less than #{maxr} and gt #{sr} "
370
+ end
371
+ if sec > maxc || sec < sc
372
+ $log.warn "XXXPADREF #{sec} should be less than #{maxc} and gt #{sc} "
373
+ end
374
+ $log.warn "XXXPADRE sr= #{sr} and ser #{ser}. sr:#{@startrow}+ #{top} , sc:#{@startcol}+ #{left}, rows:#{@rows}+ #{sr} cols:#{@cols}+ #{sc} top #{top} left #{left} "
375
+ end
376
+ #$log.debug "XXX: PADREFRESH #{retval} #{self.class}, #{@prow}, #{@pcol}, #{sr}, #{sc}, #{ser}, #{sec}." if retval == 0
377
+ # padrefresh can fail if width is greater than NCurses.COLS
378
+ # or if height exceeds tput lines. As long as content is less, it will work
379
+ # the moment content_rows exceeds then this issue happens.
380
+ # @rows + sr < tput lines
381
+ #FFI::NCurses.prefresh(@pad,@prow,@pcol, @startrow + top, @startcol + left, @rows + @startrow + top, @cols+@startcol + left);
382
+ end
383
+ # length of longest string in array
384
+ # This will give a 'wrong' max length if the array has ansi color escape sequences in it
385
+ # which inc the length but won't be printed. Such lines actually have less length when printed
386
+ # So in such cases, give more space to the pad.
387
+ def content_cols
388
+ longest = @list.max_by(&:length)
389
+ ## 2013-03-06 - 20:41 crashes here for some reason when man gives error message no man entry
390
+ return 0 unless longest
391
+ longest.length
392
+ end
393
+ public
394
+ # to be called with program / user has added a row or changed column widths so that
395
+ # the pad needs to be recreated. However, cursor positioning is maintained since this
396
+ # is considered to be a minor change.
397
+ # We do not call `init_vars` since user is continuing to do some work on a row/col.
398
+ # NOTE : if height and width are changed then only render_all is required
399
+ # not a reparse since content has not changed.
400
+ def fire_dimension_changed _method=nil
401
+ # recreate pad since width or ht has changed (row count or col width changed)
402
+ @_populate_needed = true
403
+ @repaint_required = true
404
+ @repaint_all = true
405
+ fire_handler :DIMENSION_CHANGED, _method
406
+ @__first_time = nil
407
+ end
408
+ # repaint only one row since content of that row has changed.
409
+ # No recreate of pad is done.
410
+ def fire_row_changed ix
411
+ return if ix >= @list.length
412
+ clear_row @pad, ix
413
+ # allow documents to reparse that line
414
+ fire_handler :ROW_CHANGED, ix
415
+ _arr = _getarray
416
+ render @pad, ix, _arr[ix]
417
+
418
+ end
419
+ # ---- end pad related ----- }}}
420
+ # ---- Section render related ----- {{{
421
+ #
422
+ # iterate through content rendering each row
423
+ # 2013-03-27 - 01:51 separated so that widgets with headers such as tables can
424
+ # override this for better control
425
+ def render_all
426
+ _arr = _getarray
427
+ raise "textpad:render_all: array is nil " unless _arr
428
+ @renderer.source ||= self
429
+ @renderer.render_all @pad, _arr
430
+ end
431
+
432
+ public
433
+ # supply a custom renderer that implements +render()+
434
+ # @see render
435
+ def renderer r
436
+ @renderer = r
437
+ end
438
+ # This is to render a row, for those overriding classes who have overridden
439
+ # render_all, but not +render+. e.g. +Table+. THis is also required
440
+ # for row modifications.
441
+ def render pad, lineno, text
442
+ @renderer.render pad, lineno, text
443
+ end
444
+ #
445
+ ## ---- the next 2 methods deal with printing chunks
446
+ # we should put it int a common module and include it
447
+ # in Window and Pad stuff and perhaps include it conditionally.
448
+
449
+
450
+ # before updating a single row in a table
451
+ # we need to clear the row otherwise previous contents can show through
452
+ def clear_row pad, lineno
453
+ if @renderer and @renderer.respond_to? :clear_row
454
+ @renderer.clear_row pad, lineno
455
+ else
456
+ # need pad width not window width, the other clearstring uses width of
457
+ # widget to paint on window.
458
+ @_clearstring ||= " " * @content_cols
459
+ # what about bg color ??? XXX, left_margin and internal width
460
+ #cp = get_color($datacolor, @color, @bgcolor)
461
+ cp = @cp || FFI::NCurses.COLOR_PAIR($datacolor)
462
+ att = attr() || NORMAL
463
+ FFI::NCurses.wattron(pad,cp | att)
464
+ FFI::NCurses.mvwaddstr(pad,lineno, 0, @_clearstring)
465
+ FFI::NCurses.wattroff(pad,cp | att)
466
+ end
467
+ end
468
+
469
+ # print footer containing line and position
470
+ def print_foot #:nodoc:
471
+ return unless @print_footer
472
+ return unless @suppress_borders
473
+ footer = "R: #{@current_index+1}, C: #{@curpos+@pcol}, #{@list.length} lines "
474
+ @graphic.printstring( @row + @height -1 , @col+2, footer, @color_pair || $datacolor, @footer_attrib)
475
+ =begin
476
+ if @list_footer
477
+ if false
478
+ # if we want to print ourselves
479
+ footer = @list_footer.text(self)
480
+ footer_attrib = @list_footer.config[:attrib] || Ncurses::A_REVERSE
481
+ #footer = "R: #{@current_index+1}, C: #{@curpos+@pcol}, #{@list.length} lines "
482
+ $log.debug " print_foot calling printstring with #{@row} + #{@height} -1, #{@col}+2"
483
+ @graphic.printstring( @row + @height -1 , @col+2, footer, @color_pair || $datacolor, footer_attrib)
484
+ end
485
+ # use default print method which only prints on left
486
+ @list_footer.print self
487
+ end
488
+ =end
489
+ @repaint_footer_required = false # 2010-01-23 22:55
490
+ end
491
+
492
+ # ---- Section render related end ----- }}}
493
+ # ---- Section data related start {{{
494
+
495
+ # supply a filename as source for textpad
496
+ # Reads up file into @list
497
+ # One can optionally send in a method which takes a filename and returns an array of data
498
+ # This is required if you are processing files which are binary such as zip/archives and wish
499
+ # to print the contents. (e.g. cygnus gem sends in :get_file_contents).
500
+ # filename("a.c", method(:get_file_contents))
501
+ #
502
+ # TODO: i think this should be sent to +text+ in case content_type has been set. FIXME
503
+ #
504
+ def filename(filename, reader=nil)
505
+ @file = filename
506
+ unless File.exists? filename
507
+ alert "#{filename} does not exist"
508
+ return
509
+ end
510
+ @filetype = File.extname filename
511
+ if reader
512
+ @list = reader.call(filename)
513
+ else
514
+ @list = File.open(filename,"r").read.split("\n")
515
+ end
516
+ if @filetype == ""
517
+ if @list.first.index("ruby")
518
+ @filetype = ".rb"
519
+ end
520
+ end
521
+ init_vars
522
+ @repaint_all = true
523
+ @_populate_needed = true
524
+ end
525
+
526
+ ## NOTE this breaks widgets and everyone's text which returns text of object
527
+ # also list by itself should return the list as in listbox, not just set
528
+ # Supply an array of string to be displayed
529
+ # This will replace existing text
530
+
531
+ # display text given in an array format. This is the principal way of giving content
532
+ # to a textpad, other than filename().
533
+ # @param Array of lines
534
+ # @param format (optional) can be :tmux :ansi or :none
535
+ # If a format other than :none is given, then formatted_text is called.
536
+ def text(*val)
537
+ if val.empty?
538
+ return @list
539
+ end
540
+ $log.debug " TEXTPAD inside text() with #{val.class} "
541
+ case val
542
+ when Array
543
+ # either its an array of strings
544
+ # or its an array, and val[0] is an array of strings, and val[1] is a hash / symbol TODO
545
+ case val[0]
546
+ when String
547
+ # This is the usual simple case of an array of strings
548
+ @list = val
549
+ $log.debug " creating TEXTDOC 0 with String"
550
+ when TextDocument
551
+ # this is repeating it seems FIXME
552
+ $log.debug " creating TEXTDOC 04 with TextDocu #{val[0].content_type} "
553
+
554
+ @document = val[0]
555
+ @document.source ||= self
556
+ @list = @document.text
557
+ when Array
558
+ # This is the complex case which i would like to phase out.
559
+ # Earlier this was what was used where the second arg was an optional hash
560
+ @list = val[0]
561
+ if val[1].is_a? Symbol
562
+ content_type = val[1]
563
+ hsh = { :text => @list, :content_type => content_type }
564
+ $log.debug " creating TEXTDOC 1 with #{content_type} "
565
+ @document = TextDocument.new hsh
566
+ @document.source = self
567
+ elsif val[1].is_a? Hash
568
+ # this is hack for those cases where ct is there, but the caller may not
569
+ # pass it in config
570
+ if val[1].key? :content_type and val[1][:content_type].nil?
571
+ ;
572
+ else
573
+ # content type comes nil from viewer/help which sets it later using block yield
574
+ content_type = val[1][:content_type]
575
+ stylesheet = val[1][:stylesheet]
576
+ @title = val[1][:title] if val[1].key? :title
577
+ $log.debug " creating TEXTDOC 2 with ct=#{content_type}, #{val[1]} "
578
+ @document = TextDocument.new val[1]
579
+ @document.text = @list
580
+ @document.source = self
581
+ end
582
+ else
583
+ #raise "val_1 Unable to do anything with #{val[1].class} "
584
+ $log.debug " val_1 Unable to do anything with #{val[1].class} in textpad text()"
585
+ end
586
+ else
587
+ raise "val_0 Unable to do anything with #{val[0].class} "
588
+ end
589
+ when Hash
590
+ $log.debug " creating TEXTDOC 3 with #{val[:content_type]} "
591
+ @document = TextDocument.new val
592
+ @document.source ||= self
593
+ @list = @document.text
594
+ when TextDocument
595
+ $log.debug " creating TEXTDOC 4 with TextDocu #{val.content_type} "
596
+
597
+ @document = val
598
+ @document.source ||= self
599
+ @list = @document.text
600
+ end
601
+ @_populate_needed = true
602
+ @repaint_all = true
603
+ @repaint_required = true
604
+ init_vars
605
+ # i don't want the whole thing going into the event 2014-06-04
606
+ fire_property_change :text, "dummmy", "text has changed"
607
+ self
608
+ end
609
+ # old text {{{
610
+ def ORIG_text(*val)
611
+ if val.empty?
612
+ return @list
613
+ end
614
+ lines = val[0]
615
+ raise "Textpad: text() received nil" unless lines
616
+ fmt = val.size == 2 ? val[1] : nil
617
+ case fmt
618
+ when Hash
619
+ #raise "textpad.text expected content_type in Hash : #{fmt}"
620
+ c = fmt[:content_type]
621
+ t = fmt[:title]
622
+ @title = t if t
623
+ @content_type = c if c
624
+ @stylesheet = fmt[:stylesheet] if fmt.key? :stylesheet
625
+ $log.debug " TEXTPAD text() got #{@content_type} and #{@stylesheet} "
626
+ fmt = c
627
+ #raise "textpad.text expected content_type in Hash : #{fmt}" unless fmt
628
+ when Symbol
629
+ @content_type = fmt
630
+ when NilClass
631
+ else
632
+ raise "textpad.text expected symbol or content_type in Hash, got #{fmt.class} "
633
+ end
634
+
635
+ ## some programs like testlistbox which uses multibuffers calls this with a config
636
+ # in arg2 containing :content_type and :title
637
+
638
+
639
+ # added so callers can have one interface and avoid an if condition
640
+ #return formatted_text(lines, fmt) unless @content_type == :none
641
+ # 2014-05-20 - 13:21 change and simplication of conversion process
642
+ # We maintain original text in @list
643
+ # but use another variable for native format (chunks).
644
+ @parse_required = true
645
+ @list = lines
646
+
647
+ # 2014-06-15 - 13:39 i am commenting this off, since a user can change or set content_type
648
+ # at any time, and also content handler.
649
+ if @content_type
650
+ # this will create a default content handler if not yet specified
651
+ # which is wrong since user may specify it after setting text. he may not have
652
+ # set the content type yet too. Too much dependent on order !
653
+ #preprocess_text
654
+ #parse_formatted_text lines, :content_type => @content_type, :stylesheet => @stylesheet
655
+ end
656
+
657
+ return @list if lines.empty?
658
+ #@list = lines
659
+ #@native_text ||= @list
660
+ @_populate_needed = true
661
+ @repaint_all = true
662
+ @repaint_required = true
663
+ init_vars
664
+ # i don't want the whole thing going into the event 2014-06-04
665
+ fire_property_change :text, "dummmy", "text has changed"
666
+ self
667
+ end # ORIG_text
668
+ # old text }}}
669
+ alias :list :text
670
+ # for compat with textview, FIXME keep one consistent name for this
671
+ alias :set_content :text
672
+ # this is returning the original content
673
+ # Who is using this, should it return native ?
674
+ def content
675
+ raise "content is nil " unless @list
676
+ return @list
677
+ end
678
+ alias :get_content :content
679
+ #
680
+ # internal method to return the correct list.
681
+ # Rather than trying to synch list and native text for those who do not use the latter
682
+ # let us just use the correct array
683
+ # NOTE there are some cases where document can return a nil since native_text has not been
684
+ # calculated yet. Happens in back button of help. Earlier preprocess was done from +text+
685
+ # not it is only done from +repaint+
686
+ def _getarray
687
+ if @document.nil?
688
+ return @list
689
+ else
690
+ return @document.native_text
691
+ end
692
+ end
693
+ # textpad's preprocess
694
+ def preprocess_text
695
+ if @document
696
+ @document.preprocess_text @list
697
+ end
698
+ end
699
+ #
700
+ # returns focussed value (what cursor is on)
701
+ # This may not be a string. A tree may return a node, a table an array or row
702
+ def current_value
703
+ # many descendants do not set native_text - note that list and tree and table use just @list.
704
+ #@native_text[@current_index]
705
+ _getarray[@current_index]
706
+ end
707
+ ## NOTE : 2014-04-09 - 14:05 i think this does not have line wise operations since we deal with
708
+ # formatting of data
709
+ # But what if data is not formatted. This imposes a severe limitation. listbox does have linewise
710
+ # operations, so lets try them
711
+ #
712
+ ## append a row to the list
713
+ # @deprecated pls use << or push as per Array semantics
714
+ def append text
715
+ raise "append: deprecated pls use << or push as per Array semantics"
716
+ @list ||= []
717
+ @list.push text
718
+ fire_dimension_changed :append
719
+ self
720
+ end
721
+ # @deprecated : row_count used just for compat, use length or size
722
+ def row_count ; @list.length ; end
723
+
724
+ ## ------ LIST / ARRAY OPERATIONS ----
725
+ # All multirow widgets must use Array semantics 2014-04-10 - 17:29
726
+ # NOTE some operations will make selected indices in selection modules invalid
727
+ # clear will need to clear indices, delete_at and insert may need to also adjust
728
+ # selection or focus index/es.
729
+ #
730
+ # delegate some operations to Array
731
+ # ---- operations that reference Array, no modifications
732
+ def_delegators :@list, :include?, :each, :values_at, :size, :length, :[]
733
+
734
+ # ---- operations that modify data
735
+ # delegate some modify operations to Array: insert, clear, delete_at, []= <<
736
+ # However, we should check if content array is nil ?
737
+ # fire_dim is called, although it is not required in []=
738
+ %w[ insert delete_at << push].each { |e|
739
+ eval %{
740
+ def #{e}(*args)
741
+ @list ||= []
742
+ fire_dimension_changed :#{e}
743
+ @list.send(:#{e}, *args)
744
+ self
745
+ end
746
+ }
747
+ }
748
+ # clear all items in the object.
749
+ # NOTE: requires to be separate since init_vars is called to reset index of focus etc.
750
+ # Also, listbox will extend this to clear selected_indices
751
+ def clear
752
+ return unless @list
753
+ @list.clear
754
+ @native_text.clear
755
+ fire_dimension_changed :clear
756
+ init_vars
757
+ end
758
+ # update the value at index with given value, returning self
759
+ def []=(index, val)
760
+ @list[index]=val
761
+ fire_row_changed index
762
+ self
763
+ end
764
+ # ---- Section data related end }}}
765
+
766
+
767
+
768
+
769
+ #---- Section: movement -----# {{{
770
+ # goto first line of file
771
+ public
772
+ def goto_start
773
+ $multiplier ||= 0
774
+ if $multiplier > 0
775
+ goto_line $multiplier - 1
776
+ return
777
+ end
778
+ @current_index = 0
779
+ @curpos = @pcol = @prow = 0
780
+ @prow = 0
781
+ $multiplier = 0
782
+ end
783
+
784
+ # goto last line of file
785
+ def goto_end
786
+ $multiplier ||= 0
787
+ if $multiplier > 0
788
+ goto_line $multiplier - 1
789
+ return
790
+ end
791
+ @current_index = @list.count() - 1
792
+ @prow = @current_index - @scrollatrows
793
+ $multiplier = 0
794
+ end
795
+ def goto_line line
796
+ ## we may need to calculate page, zfm style and place at right position for ensure visible
797
+ #line -= 1
798
+ @current_index = line
799
+ ensure_visible line
800
+ bounds_check
801
+ $multiplier = 0
802
+ end
803
+ def top_of_window
804
+ @current_index = @prow
805
+ $multiplier ||= 0
806
+ if $multiplier > 0
807
+ @current_index += $multiplier
808
+ $multiplier = 0
809
+ end
810
+ end
811
+ def bottom_of_window
812
+ @current_index = @prow + @scrollatrows
813
+ $multiplier ||= 0
814
+ if $multiplier > 0
815
+ @current_index -= $multiplier
816
+ $multiplier = 0
817
+ end
818
+ end
819
+
820
+ def middle_of_window
821
+ @current_index = @prow + (@scrollatrows/2)
822
+ $multiplier = 0
823
+ end
824
+
825
+ # move down a line mimicking vim's j key
826
+ # @param [int] multiplier entered prior to invoking key
827
+ def down num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
828
+ #@oldindex = @current_index if num > 10
829
+ @current_index += num
830
+ # no , i don't like this here. it scrolls up too much making prow = current_index
831
+ unless is_visible? @current_index
832
+ #alert "#{@current_index} not visible prow #{@prow} #{@scrollatrows} "
833
+ @prow += num
834
+ end
835
+ #ensure_visible
836
+ $multiplier = 0
837
+ end
838
+
839
+ # move up a line mimicking vim's k key
840
+ # @param [int] multiplier entered prior to invoking key
841
+ def up num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
842
+ #@oldindex = @current_index if num > 10
843
+ @current_index -= num
844
+ #unless is_visible? @current_index
845
+ #if @prow > @current_index
846
+ ##$status_message.value = "1 #{@prow} > #{@current_index} "
847
+ #@prow -= 1
848
+ #else
849
+ #end
850
+ #end
851
+ $multiplier = 0
852
+ end
853
+
854
+ # scrolls window down mimicking vim C-e
855
+ # @param [int] multiplier entered prior to invoking key
856
+ def scroll_window_down num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
857
+ @prow += num
858
+ if @prow > @current_index
859
+ @current_index += 1
860
+ end
861
+ #check_prow
862
+ $multiplier = 0
863
+ end
864
+
865
+ # scrolls window up mimicking vim C-y
866
+ # @param [int] multiplier entered prior to invoking key
867
+ def scroll_window_up num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
868
+ @prow -= num
869
+ unless is_visible? @current_index
870
+ # one more check may be needed here TODO
871
+ @current_index -= num
872
+ end
873
+ $multiplier = 0
874
+ end
875
+
876
+ # scrolls lines a window full at a time, on pressing SPACE or C-d or pagedown
877
+ def scroll_forward
878
+ #@oldindex = @current_index
879
+ @current_index += @scrollatrows
880
+ @prow = @current_index - @scrollatrows
881
+ end
882
+
883
+ # scrolls lines backward a window full at a time, on pressing pageup
884
+ # C-u may not work since it is trapped by form earlier. Need to fix
885
+ def scroll_backward
886
+ #@oldindex = @current_index
887
+ @current_index -= @scrollatrows
888
+ @prow = @current_index - @scrollatrows
889
+ end
890
+ def goto_last_position
891
+ return unless @oldindex
892
+ tmp = @current_index
893
+ @current_index = @oldindex
894
+ @oldindex = tmp
895
+ bounds_check
896
+ end
897
+ def scroll_right
898
+ # I don't think it will ever be less since we've increased it to cols
899
+ if @content_cols <= @cols
900
+ maxpcol = 0
901
+ @pcol = 0
902
+ else
903
+ maxpcol = @content_cols - @cols - 1
904
+ @pcol += 1
905
+ @pcol = maxpcol if @pcol > maxpcol
906
+ end
907
+ # to prevent right from retaining earlier painted values
908
+ # padreader does not do a clear, yet works fine.
909
+ # OK it has an update_panel after padrefresh, that clears it seems.
910
+ #this clears entire window not just the pad
911
+ #FFI::NCurses.wclear(@window.get_window)
912
+ # so border and title is repainted after window clearing
913
+ #
914
+ # Next line was causing all sorts of problems when scrolling with ansi formatted text
915
+ #@repaint_all = true
916
+ end
917
+ def scroll_left
918
+ @pcol -= 1
919
+ end
920
+ #
921
+ # jumps cursor to next word, like vim's w key
922
+ #
923
+ def forward_word
924
+ #forward_regex(/[[:punct:][:space:]]\w/)
925
+ forward_regex(:word)
926
+ end
927
+ # jump to the next occurence of given regex in the current line.
928
+ # It only jumps to next line after exhausting current.
929
+ # @param [Regexp] passed to String.index
930
+ def forward_regex regex
931
+ if regex.is_a? Symbol
932
+ regex = @text_patterns[regex]
933
+ raise "Pattern specified #{regex} does not exist in text_patterns " unless regex
934
+ end
935
+ $multiplier = 1 if !$multiplier || $multiplier == 0
936
+ line = @current_index
937
+ _arr = _getarray
938
+ buff = _arr[line].to_s
939
+ return unless buff
940
+ pos = @curpos || 0 # list does not have curpos
941
+ $multiplier.times {
942
+ found = buff.index(regex, pos)
943
+ if !found
944
+ # if not found, we've lost a counter
945
+ if line+1 < _arr.length
946
+ line += 1
947
+ else
948
+ return
949
+ end
950
+ pos = 0
951
+ else
952
+ pos = found + 1
953
+ end
954
+ $log.debug " forward_word: pos #{pos} line #{line} buff: #{buff}"
955
+ }
956
+ $multiplier = 0
957
+ @current_index = line
958
+ @curpos = pos
959
+ ensure_visible
960
+ @repaint_required = true
961
+ end
962
+ # jump to previous word, like vim's "b"
963
+ def backward_word
964
+ #backward_regex(/[[:punct:][:space:]]\w/)
965
+ backward_regex(:word)
966
+ end
967
+ # jump to previous occurence of given regexp within a line, and then to previous line
968
+ # This is more line 'w' or 'b' in vim, not like '/'
969
+ # @param [Regexp] pattern to go back to.
970
+ def backward_regex regex
971
+ if regex.is_a? Symbol
972
+ regex = @text_patterns[regex]
973
+ raise "Pattern specified #{regex} does not exist in text_patterns " unless regex
974
+ end
975
+ $multiplier = 1 if !$multiplier || $multiplier == 0
976
+ _arr = _getarray
977
+ pos = @curpos || 0 # list does not have curpos
978
+ line = @current_index
979
+ #buff = _arr[line].to_s
980
+ #return unless buff
981
+ # if curpos is at zero , we should be checking previous line !
982
+ $multiplier.times {
983
+ # if at start of line, go to previous line
984
+ if pos == 0
985
+ if @current_index > 0
986
+ line -= 1
987
+ pos = _arr[line].to_s.size
988
+ else
989
+ # we are on first line of file at start
990
+ break
991
+ end
992
+ end
993
+ buff = _arr[line].to_s
994
+ return unless buff
995
+ pos2 = pos - 2
996
+ pos2 = 0 if pos2 < 0
997
+ found = buff.rindex(regex, pos2)
998
+ $log.debug " backward: pos #{pos} , found #{found}, pos2 = #{pos2} "
999
+ if !found || found == 0
1000
+ # if not found, we've lost a counter
1001
+ if pos > 0
1002
+ pos = 0
1003
+ elsif line > 0
1004
+ #line -= 1
1005
+ #pos = _arr[line].to_s.size
1006
+ else
1007
+ break
1008
+ end
1009
+ else
1010
+ pos = found + 1
1011
+ end
1012
+ $log.debug " backward_word: pos #{pos} line #{line} buff: #{buff}"
1013
+ }
1014
+ $multiplier = 0
1015
+ @current_index = line
1016
+ @curpos = pos
1017
+ ensure_visible
1018
+ @repaint_required = true
1019
+ end
1020
+ #
1021
+ # move cursor forward by one char (currently will not pan)
1022
+ def cursor_forward
1023
+ $multiplier = 1 if $multiplier == 0
1024
+ if @curpos < @cols
1025
+ @curpos += $multiplier
1026
+ if @curpos > @cols
1027
+ @curpos = @cols
1028
+ end
1029
+ @repaint_required = true
1030
+ end
1031
+ $multiplier = 0
1032
+ end
1033
+ #
1034
+ # move cursor backward by one char (currently will not pan)
1035
+ def cursor_backward
1036
+ $multiplier = 1 if $multiplier == 0
1037
+ if @curpos > 0
1038
+ @curpos -= $multiplier
1039
+ @curpos = 0 if @curpos < 0
1040
+ @repaint_required = true
1041
+ end
1042
+ $multiplier = 0
1043
+ end
1044
+ # moves cursor to end of line also panning window if necessary
1045
+ # NOTE: if one line on another page (not displayed) is way longer than any
1046
+ # displayed line, then this will pan way ahead, so may not be very intelligent
1047
+ # in such situations.
1048
+ def cursor_eol
1049
+ # pcol is based on max length not current line's length
1050
+ @pcol = @content_cols - @cols - 1
1051
+ _arr = _getarray
1052
+ @curpos = _arr[@current_index].size
1053
+ @repaint_required = true
1054
+ end
1055
+ #
1056
+ # moves cursor to start of line, panning if required
1057
+ def cursor_bol
1058
+ # copy of C-a - start of line
1059
+ @repaint_required = true if @pcol > 0
1060
+ @pcol = 0
1061
+ @curpos = 0
1062
+ end
1063
+ #
1064
+ # return true if the given row is visible
1065
+ def is_visible? index
1066
+ j = index - @prow #@toprow
1067
+ j >= 0 && j <= scrollatrows()
1068
+ end
1069
+ #---- Section: movement end -----# }}}
1070
+ #---- Section: internal stuff start -----# {{{
1071
+ public
1072
+ def create_default_renderer
1073
+ @renderer = DefaultRenderer.new self
1074
+ end
1075
+ def create_default_keyhandler
1076
+ @key_handler = DefaultKeyHandler.new self
1077
+ end
1078
+ #
1079
+ def handle_key ch
1080
+ return :UNHANDLED unless @list
1081
+
1082
+ unless @key_handler
1083
+ create_default_keyhandler
1084
+ end
1085
+ @oldrow = @prow
1086
+ @oldcol = @pcol
1087
+ $log.debug "XXX: TEXTPAD got #{ch} prow = #{@prow}"
1088
+ ret = @key_handler.handle_key(ch)
1089
+ end
1090
+ # this is a barebones handler to be used only if an overriding key handler
1091
+ # wishes to fall back to default processing after it has handled some keys.
1092
+ # The complete version is in Defaultkeyhandler.
1093
+ # BUT the key will be executed again.
1094
+ def _handle_key ch
1095
+ begin
1096
+ ret = process_key ch, self
1097
+ $multiplier = 0
1098
+ bounds_check
1099
+ rescue => err
1100
+ $log.error " TEXTPAD ERROR _handle_key #{err} "
1101
+ $log.debug(err.backtrace.join("\n"))
1102
+ alert "#{err}"
1103
+ #textdialog ["Error in TextPad: #{err} ", *err.backtrace], :title => "Exception"
1104
+ ensure
1105
+ padrefresh
1106
+ Ncurses::Panel.update_panels
1107
+ end
1108
+ return 0
1109
+ end
1110
+
1111
+
1112
+ #
1113
+ # event when user hits ENTER on a row, user would bind :PRESS
1114
+ # callers may use +text()+ to get the value of the row, +source+ to get parent object.
1115
+ #
1116
+ # obj.bind :PRESS { |eve| eve.text }
1117
+ #
1118
+ def fire_action_event
1119
+ return if @list.nil? || @list.size == 0
1120
+ require 'canis/core/include/ractionevent'
1121
+ aev = text_action_event
1122
+ fire_handler :PRESS, aev
1123
+ end
1124
+ # creates and returns a textactionevent object with current_value , line number and cursor position
1125
+ def text_action_event
1126
+ aev = TextActionEvent.new self, :PRESS, current_value().to_s, @current_index, @curpos
1127
+ end
1128
+ #
1129
+ # execute binding when a row is entered, used more in lists to display some text
1130
+ # in a header or footer as one traverses
1131
+ #
1132
+ def on_enter_row arow
1133
+ return nil if @list.nil? || @list.size == 0
1134
+
1135
+ @repaint_footer_required = true
1136
+ #alert "on_enter rr #{@repaint_required}, #{@repaint_all} oi #{@oldindex}, ci #{@current_index}, or #{@oldrow} "
1137
+
1138
+ ## can this be done once and stored, and one instance used since a lot of traversal will be done
1139
+ require 'canis/core/include/ractionevent'
1140
+ aev = TextActionEvent.new self, :ENTER_ROW, current_value().to_s, @current_index, @curpos
1141
+ fire_handler :ENTER_ROW, aev
1142
+ #@repaint_required = true
1143
+ end
1144
+
1145
+ #
1146
+ # called when this widget is entered, by form
1147
+ # 2014-05-27 - 17:02 we were not calling super, so :ENTER was not triggered !!!
1148
+ def on_enter
1149
+ super
1150
+ set_form_row
1151
+ end
1152
+ # called by form
1153
+ def set_form_row
1154
+ setrowcol @lastrow, @lastcol
1155
+ end
1156
+ # called by form
1157
+ def set_form_col
1158
+ end
1159
+
1160
+ private
1161
+
1162
+ # check that current_index and prow are within correct ranges
1163
+ # sets row (and someday col too)
1164
+ # sets repaint_required
1165
+
1166
+ public
1167
+ def bounds_check
1168
+ raise "@list is empty in textpad. Bounds_check." unless @list
1169
+ r,c = rowcol
1170
+ @current_index = 0 if @current_index < 0
1171
+ @current_index = @list.count()-1 if @current_index > @list.count()-1
1172
+ ensure_visible
1173
+
1174
+ check_prow
1175
+ @crow = @current_index + r - @prow
1176
+ @crow = r if @crow < r
1177
+ #$log.debug "XXX: PAD BOUNDS ci:#{@current_index} , old #{@oldrow},pr #{@prow}, crow #{@crow}, max #{@maxrow} pcol #{@pcol} maxcol #{@maxcol}"
1178
+ # 2 depends on whetehr suppress_borders
1179
+ if @suppress_borders
1180
+ @crow = @row + @height -1 if @crow >= r + @height -1
1181
+ else
1182
+ @crow = @row + @height -2 if @crow >= r + @height -2
1183
+ end
1184
+ #$log.debug " PAD BOUNDS CROW #{@crow} calling setrowcol"
1185
+ setrowcol @crow, @curpos+c
1186
+ lastcurpos @crow, @curpos+c
1187
+ if @oldindex != @current_index
1188
+ on_leave_row @oldindex if respond_to? :on_leave_row
1189
+ on_enter_row @current_index
1190
+ @oldindex = @current_index
1191
+ end
1192
+ if @oldrow != @prow || @oldcol != @pcol
1193
+ # only if scrolling has happened.
1194
+ @repaint_required = true
1195
+ end
1196
+ end
1197
+ #
1198
+ # save last cursor position so when reentering, cursor can be repositioned
1199
+ def lastcurpos r,c
1200
+ @lastrow = r
1201
+ @lastcol = c
1202
+ end
1203
+
1204
+
1205
+ # check that prow and pcol are within bounds
1206
+ #
1207
+ def check_prow
1208
+ @prow = 0 if @prow < 0
1209
+ @pcol = 0 if @pcol < 0
1210
+
1211
+ cc = @list.count
1212
+ @rows = rows()
1213
+
1214
+ #$log.debug " check_prow prow #{@prow} , list count #{cc}, rows #{@rows} "
1215
+ # 2014-05-28 - 22:41 changed < to <= otherwise prow became -1 when equal
1216
+ if cc <= @rows
1217
+ @prow = 0
1218
+ else
1219
+ maxrow = cc - @rows - 1
1220
+ if @prow > maxrow
1221
+ @prow = maxrow
1222
+ end
1223
+ end
1224
+ #$log.debug " check_prow after prow #{@prow} , list count #{cc} "
1225
+ # we still need to check the max that prow can go otherwise
1226
+ # the pad shows earlier stuff.
1227
+ #
1228
+ return
1229
+ end
1230
+ public
1231
+ def repaint
1232
+ unless @__first_time
1233
+ __calc_dimensions
1234
+ @__first_time = true
1235
+ end
1236
+ return unless @list # trying out since it goes into padrefresh even when no data 2014-04-10 - 00:32
1237
+ @graphic = @form.window unless @graphic
1238
+ @window ||= @graphic
1239
+ raise "Window not set in textpad" unless @window
1240
+ unless @renderer
1241
+ create_default_renderer
1242
+ end
1243
+
1244
+ ## 2013-03-08 - 21:01 This is the fix to the issue of form callign an event like ? or F1
1245
+ # which throws up a messagebox which leaves a black rect. We have no place to put a refresh
1246
+ # However, form does call repaint for all objects, so we can do a padref here. Otherwise,
1247
+ # it would get rejected. UNfortunately this may happen more often we want, but we never know
1248
+ # when something pops up on the screen.
1249
+ #$log.debug " repaint textpad RR #{@repaint_required} #{@window.top} "
1250
+ unless @repaint_required
1251
+ print_foot if @repaint_footer_required # set in on_enter_row
1252
+ # trying out removing this, since too many refreshes 2014-05-01 - 12:45
1253
+ #padrefresh
1254
+ return
1255
+ end
1256
+ # if repaint is required, print_foot not called. unless repaint_all is set, and that
1257
+ # is rarely set.
1258
+
1259
+ preprocess_text
1260
+
1261
+ # in textdialog, @window was nil going into create_pad 2014-04-15 - 01:28
1262
+
1263
+ # creates pad and calls render_all
1264
+ populate_pad if !@pad or @_populate_needed
1265
+ if @repaint_all
1266
+ clear_pad
1267
+ Ncurses::Panel.update_panels
1268
+ render_all
1269
+ end
1270
+
1271
+ raise "PAD IS NIL -- populate_pad was not called ??" unless @pad
1272
+
1273
+ _do_borders
1274
+ print_foot if @repaint_footer_required # if still not done
1275
+
1276
+ padrefresh
1277
+ # in some cases next line prevents overlapped window from refreshing again, leaving black rows.
1278
+ # removing it causes problems in other cases. (tasks.rb, confirm window. dbdemo, F2 closing)
1279
+ Ncurses::Panel.update_panels
1280
+ @repaint_required = false
1281
+ @repaint_all = false
1282
+ end
1283
+
1284
+ def _do_borders
1285
+ unless @suppress_borders
1286
+ if @repaint_all
1287
+ ## XXX im not getting the background color.
1288
+ #@window.print_border_only @top, @left, @height-1, @width, $datacolor
1289
+ clr = get_color $datacolor, color(), bgcolor()
1290
+ #@window.print_border @top, @left, @height-1, @width, clr
1291
+ @window.print_border_only @top, @left, @height-1, @width, clr
1292
+ print_title
1293
+
1294
+ # oldrow changed to oldindex 2014-04-13 - 16:55
1295
+ @repaint_footer_required = true if @oldindex != @current_index
1296
+ print_foot if @print_footer && !@suppress_borders && @repaint_footer_required
1297
+
1298
+ @window.wrefresh
1299
+ end
1300
+ end
1301
+ end
1302
+
1303
+ #
1304
+ # key mappings
1305
+ #
1306
+ # TODO take from listbindings so that emacs and vim can be selected. also user can change in one place.
1307
+ def map_keys
1308
+ @mapped_keys = true
1309
+ require 'canis/core/include/listbindings'
1310
+ bindings
1311
+ =begin
1312
+ bind_key([?g,?g], 'goto_start'){ goto_start } # mapping double keys like vim
1313
+ bind_key(279, 'goto_start'){ goto_start }
1314
+ bind_keys([?G,277], 'goto end'){ goto_end }
1315
+ bind_keys([?k,KEY_UP], "Up"){ up }
1316
+ bind_keys([?j,KEY_DOWN], "Down"){ down }
1317
+ bind_key(?\C-e, "Scroll Window Down"){ scroll_window_down }
1318
+ bind_key(?\C-y, "Scroll Window Up"){ scroll_window_up }
1319
+ bind_keys([32,338, ?\C-d], "Scroll Forward"){ scroll_forward }
1320
+ # adding CTRL_SPACE as back scroll 2014-04-14
1321
+ bind_keys([0,?\C-b,339], "Scroll Backward"){ scroll_backward }
1322
+ # the next one invalidates the single-quote binding for bookmarks
1323
+ #bind_key([?',?']){ goto_last_position } # vim , goto last row position (not column)
1324
+ bind_key(?/, :ask_search)
1325
+ bind_key(?n, :find_more)
1326
+ bind_key([?\C-x, ?>], :scroll_right)
1327
+ bind_key([?\C-x, ?<], :scroll_left)
1328
+ bind_key(?\M-l, :scroll_right)
1329
+ bind_key(?\M-h, :scroll_left)
1330
+ bind_key(?L, :bottom_of_window)
1331
+ bind_key(?M, :middle_of_window)
1332
+ bind_key(?H, :top_of_window)
1333
+ bind_key(?w, :forward_word)
1334
+ bind_key(?b, :backward_word)
1335
+ bind_key(?l, :cursor_forward)
1336
+ bind_key(?h, :cursor_backward)
1337
+ bind_key(?$, :cursor_eol)
1338
+ bind_key(KEY_ENTER, :fire_action_event)
1339
+ =end
1340
+ end
1341
+ # convenience method to return byte -- is it used ???
1342
+ private
1343
+ def key x
1344
+ x.getbyte(0)
1345
+ end
1346
+
1347
+ # ----------- end internal stuff --------------- }}}
1348
+ public
1349
+ # ---- Section search related start ----- {{{
1350
+ ##
1351
+ # Ask user for string to search for
1352
+ # This uses the dialog, but what if user wants the old style.
1353
+ # Isn't there a cleaner way to let user override style, or allow user
1354
+ # to use own UI for getting pattern and then passing here.
1355
+ # @param str default nil. If not passed, then user is prompted using get_string dialog
1356
+ # This allows caller to use own method to prompt for string such as 'get_line' or 'rbgetstr' /
1357
+ # 'ask()'
1358
+ def ask_search str=nil
1359
+ # the following is a change that enables callers to prompt for the string
1360
+ # using some other style, basically the classical style and send the string in
1361
+ str = get_string("Enter pattern: ", :title => "Find pattern") unless str
1362
+ return if str.nil?
1363
+ str = @last_regex if str == ""
1364
+ return if !str or str == ""
1365
+ str = Regexp.new str if str.is_a? String
1366
+ ix = next_match str
1367
+ return unless ix
1368
+ @last_regex = str
1369
+
1370
+ #@oldindex = @current_index
1371
+ @current_index = ix[0]
1372
+ @curpos = ix[1]
1373
+ ensure_visible
1374
+ end
1375
+ ##
1376
+ # Find next matching row for string accepted in ask_search
1377
+ #
1378
+ def find_more
1379
+ return unless @last_regex
1380
+ ix = next_match @last_regex
1381
+ return unless ix
1382
+ #@oldindex = @current_index
1383
+ @current_index = ix[0]
1384
+ @curpos = ix[1]
1385
+ ensure_visible
1386
+ end
1387
+
1388
+ ##
1389
+ # Find the next row that contains given string
1390
+ # @return row and col offset of match, or nil
1391
+ # FIXME: 2014-05-26 - 01:26 currently if there are two matches in a row skips the second
1392
+ # one, which is a pain if two matches only on same row.
1393
+ # Ok, now it goes to second but won't come back to first, if only two matches, and both in one row.
1394
+ # @param String to find
1395
+ def ORIGnext_match str
1396
+ return unless str
1397
+ first = nil
1398
+ ## content can be string or Chunkline, so we had to write <tt>index</tt> for this.
1399
+ ## =~ does not give an error, but it does not work.
1400
+ @native_text.each_with_index do |line, ix|
1401
+ offset = 0
1402
+ # next line just a hack and not correct if only one match in file FIXME
1403
+ offset = @curpos + 1 if ix == @current_index
1404
+ _col = line.index str, offset
1405
+ if _col
1406
+ first ||= [ ix, _col ]
1407
+ if ix > @current_index || ( ix == @current_index && _col > @curpos)
1408
+ return [ix, _col]
1409
+ end
1410
+ end
1411
+ end
1412
+ # if first is nil, then none found in current line also, so don't increment offset in current line
1413
+ # next time. FIXME TODO
1414
+ return first
1415
+ end
1416
+ # since 2014-05-26 - 12:13 new logic to take into account multiple matches in one line
1417
+ #
1418
+ # First time, starts searching current line from cursor position onwards to end of file
1419
+ # If no match, then checks from start of file to current line.
1420
+ # @param [String, Regex] pattern to search (uses +:index+)
1421
+ # @param [Fixnum] line number to start with. Optional. +nil+ means start search from current position.
1422
+ # @param [Fixnum] line number to end with. Optional. +nil+ means end search at end of array
1423
+ # @return [Array<Fixnum, Fixnum>] index of line where pattern found, index of offset in line.
1424
+ # +nil+ returned if no match.
1425
+ # NOTE:
1426
+ # startline and endline are more for internal purposes, externally we would call this only with
1427
+ # the pattern.
1428
+ # @example
1429
+ # pos = next_match /abc/
1430
+ # # pos is nil or [line, col]
1431
+ #
1432
+ # 2014-05-28 - Added to_s before index() so that other descendants can use, such as treemodel or tablemodel
1433
+ def next_match str, startline=nil, endline=nil
1434
+ # 1. look in current row after the curpos
1435
+ # 2. if no match look in rest of array
1436
+ # 3. if no match and you started from current_index, then search
1437
+ # from start of file to current_index. call _next_match with 0.
1438
+ _arr = _getarray
1439
+ if !startline
1440
+ startline = @current_index
1441
+ pos = @curpos + 1
1442
+ # FIXME you could be at end of line
1443
+ #_line = _arr[startline]
1444
+ _line = to_searchable(startline)
1445
+ _col = _line.index(str, pos) if _line
1446
+ if _col
1447
+ return [startline, _col]
1448
+ end
1449
+ startline += 1 # FIXME check this end of file
1450
+ end
1451
+ # FIXME iterate only through the ones we need, not all
1452
+ _arr.each_with_index do |line, ix|
1453
+ next if ix < startline
1454
+ break if endline && ix > endline
1455
+ # next line just a hack and not correct if only one match in file FIXME
1456
+ line = to_searchable(ix)
1457
+ _col = line.index str
1458
+ if _col
1459
+ return [ix, _col]
1460
+ end
1461
+ end
1462
+ if startline > 0
1463
+ return next_match str, 0, @current_index
1464
+ end
1465
+ return nil
1466
+
1467
+ end
1468
+ # search for the next occurence of given regexp. Returns line and col if found, else nil.
1469
+ # @param [String, Regexp] pattern to search for (uses :index)
1470
+ # @return [nil] return value of no consequence
1471
+ def next_regex regex
1472
+ if regex.is_a? Symbol
1473
+ regex = @text_patterns[regex]
1474
+ raise "Pattern specified #{regex} does not exist in text_patterns " unless regex
1475
+ end
1476
+ @last_regex = regex
1477
+ find_more
1478
+ end
1479
+ # convert the row to something we can run +index+ on. it should be exactly
1480
+ # as the display will be, so find offsets are correct. Required for descendants such as Table.
1481
+ def to_searchable index
1482
+ _getarray[index].to_s
1483
+ end
1484
+ ##
1485
+ # Ensure current row is visible, if not make it first row
1486
+ # NOTE - need to check if its at end and then reduce scroll at rows, check_prow does that
1487
+ #
1488
+ # @param current_index (default if not given)
1489
+ #
1490
+ def ensure_visible row = @current_index
1491
+ unless is_visible? row
1492
+ @prow = row
1493
+ end
1494
+ end
1495
+
1496
+ # returns the row offset of the focussed row, based on what is visible
1497
+ # this takes into account scrolling, and is useful if some caller needs to know
1498
+ # where the current index is actually being displayed (example if it wishes to display
1499
+ # a popup at that row)
1500
+ # An argument is not being taken since the index should be visible.
1501
+ def visual_index
1502
+ row = @current_index
1503
+ row - @prow
1504
+ end
1505
+
1506
+
1507
+
1508
+ # ---- Section search related end ----- }}}
1509
+ ##---- dead unused {{{
1510
+ ## some general methods for highlighting a row or changing attribute. However, these
1511
+ # will change the moment panning is done, or a repaint happens.
1512
+ # If these should be maintained then they should be called from the repaint method
1513
+ #
1514
+ # This was just indicative, and is not used anywhere
1515
+ def DEADhighlight_row index = @current_index, cfg={}
1516
+ return unless index
1517
+ c = 0 # we are using pads so no col except for left_margin if present
1518
+ # in a pad we don't need to convert index to printable
1519
+ r = index
1520
+ defcolor = cfg[:defaultcolor] || $promptcolor
1521
+ acolor ||= get_color defcolor, cfg[:color], cfg[:bgcolor]
1522
+ att = FFI::NCurses::A_REVERSE
1523
+ att = get_attrib(cfg[:attrib]) if cfg[:attrib]
1524
+ #@graphic.mvchgat(y=r, x=c, @width-2, att , acolor , nil)
1525
+ FFI::NCurses.mvwchgat(@pad, y=r, x=c, @width-2, att, acolor, nil)
1526
+ end
1527
+ ##---- dead unused }}}
1528
+
1529
+ end # class textpad
1530
+
1531
+ # renderer {{{
1532
+ # Very basic renderer that only prints based on color pair of the textpad
1533
+ class AbstractTextPadRenderer
1534
+ # attribute for row, color_pair, and the Ncurses int for the colorpair
1535
+ attr_accessor :attr, :color_pair, :cp
1536
+ # content cols is the width in columns of pad
1537
+ # list is the data array
1538
+ attr_accessor :content_cols, :list
1539
+ # the widget this is associated with.
1540
+ attr_accessor :source
1541
+
1542
+ def initialize source=nil
1543
+ @source = source
1544
+ end
1545
+ # have the renderer get the latest colors from the widget.
1546
+ # Override this if for some reason the renderer wishes to hardcode its own.
1547
+ # But then the widgets colors would be changed ?
1548
+ def pre_render
1549
+ @attr = @source.attr
1550
+ cp = get_color($datacolor, @source.color(), @source.bgcolor())
1551
+ @color_pair = @source.color_pair || cp
1552
+ @cp = FFI::NCurses.COLOR_PAIR(cp)
1553
+ end
1554
+ alias :update_colors :pre_render
1555
+
1556
+ # derived classes may choose to override this.
1557
+ # However, they should set size and color and attrib at the start since these
1558
+ # can change after the object has been created depending on the application.
1559
+ def render_all pad, arr
1560
+ pre_render
1561
+ @content_cols = @source.pad_cols
1562
+ @clearstring = " " * @content_cols
1563
+ @list = arr
1564
+
1565
+ att = @attr || NORMAL
1566
+ FFI::NCurses.wattron(pad, @cp | att)
1567
+
1568
+ arr.each_with_index { |line, ix|
1569
+ render pad, ix, line
1570
+ }
1571
+ FFI::NCurses.wattroff(pad, @cp | att)
1572
+ end
1573
+ # concrete / derived classes should override this for their specific uses.
1574
+ # Called if only a row is changed.
1575
+ def render pad, lineno, text
1576
+ FFI::NCurses.mvwaddstr(pad,lineno, 0, @clearstring) if @clearstring
1577
+ FFI::NCurses.mvwaddstr(pad,lineno, 0, text)
1578
+ end
1579
+ end
1580
+
1581
+ # An extension of Abstracttextpadrenderer which takes care of AbstractChunkLine objects
1582
+ # calling their +print+ method.
1583
+ class DefaultRenderer < AbstractTextPadRenderer
1584
+
1585
+ #
1586
+ # default method for rendering a line
1587
+ # If it is a chunkline, then we take care of it.
1588
+ # Only if it is a String do we pass to renderer.
1589
+ # Should a renderer be allowed to handle chunks. Or be yielded chunks?
1590
+ #
1591
+ def render pad, lineno, text
1592
+ if text.is_a? AbstractChunkLine
1593
+ text.print pad, lineno, 0, @content_cols, color_pair, attr
1594
+ return
1595
+ end
1596
+ ## messabox does have a method to paint the whole window in bg color its in rwidget.rb
1597
+ att = NORMAL
1598
+ FFI::NCurses.wattron(pad, @cp | att)
1599
+ FFI::NCurses.mvwaddstr(pad,lineno, 0, @clearstring) if @clearstring
1600
+ FFI::NCurses.mvwaddstr(pad,lineno, 0, @list[lineno])
1601
+
1602
+ #FFI::NCurses.mvwaddstr(pad, lineno, 0, text)
1603
+ FFI::NCurses.wattroff(pad, @cp | att)
1604
+ end
1605
+ end
1606
+ # renderer }}}
1607
+ # This is the default key handler.
1608
+ # It takes care of catching numbers so that vim's movement can use numeric args.
1609
+ # That is taken care of by multiplier. Other than that it has the key_map process the key.
1610
+ #
1611
+ class DefaultKeyHandler # ---- {{{
1612
+ def initialize source
1613
+ @source = source
1614
+ end
1615
+
1616
+ def handle_key ch
1617
+ begin
1618
+ case ch
1619
+ when ?0.getbyte(0)..?9.getbyte(0)
1620
+ if ch == ?0.getbyte(0) && $multiplier == 0
1621
+ @source.cursor_bol
1622
+ @source.bounds_check
1623
+ return 0
1624
+ end
1625
+ # storing digits entered so we can multiply motion actions
1626
+ $multiplier *= 10 ; $multiplier += (ch-48)
1627
+ return 0
1628
+ when ?\C-c.getbyte(0)
1629
+ $multiplier = 0
1630
+ return 0
1631
+ else
1632
+ # check for bindings, these cannot override above keys since placed at end
1633
+ begin
1634
+ ret = @source.process_key ch, self
1635
+ $multiplier = 0
1636
+ @source.bounds_check
1637
+ ## If i press C-x > i get an alert from rwidgets which blacks the screen
1638
+ # if i put a padrefresh here it becomes okay but only for one pad,
1639
+ # i still need to do it for all pads.
1640
+ rescue => err
1641
+ $log.error " TEXTPAD ERROR handle_key #{err} "
1642
+ $log.debug(err.backtrace.join("\n"))
1643
+ alert "#{err}"
1644
+ #textdialog ["Error in TextPad: #{err} ", *err.backtrace], :title => "Exception"
1645
+ end
1646
+ # --- NOTE ABOUT BLACK RECT LEFT on SCREEN {{{
1647
+ ## NOTE if textpad does not handle the event and it goes to form which pops
1648
+ # up a messagebox, then padrefresh does not happen, since control does not
1649
+ # come back here, so a black rect is left on screen
1650
+ # please note that a bounds check will not happen for stuff that
1651
+ # is triggered by form, so you'll have to to it yourself or
1652
+ # call setrowcol explicity if the cursor is not updated
1653
+ # --- }}}
1654
+
1655
+ return :UNHANDLED if ret == :UNHANDLED
1656
+ end
1657
+ rescue => err
1658
+ $log.error " TEXTPAD ERROR 591 #{err} "
1659
+ $log.debug( err) if err
1660
+ $log.debug(err.backtrace.join("\n")) if err
1661
+ # NOTE: textdialog itself is based on this class.
1662
+ alert "#{err}"
1663
+ #textdialog ["Error in TextPad: #{err} ", *err.backtrace], :title => "Exception"
1664
+ $error_message.value = ""
1665
+ ensure
1666
+ # this means that even unhandled will trigger a refresh
1667
+ @source.padrefresh
1668
+ Ncurses::Panel.update_panels
1669
+ end
1670
+ return 0
1671
+ end # def
1672
+ end # class }}}
1673
+
1674
+ end # mod