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.
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