rbhex-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/CHANGELOG +2000 -0
  4. data/LICENSE +56 -0
  5. data/README.md +44 -0
  6. data/examples/abasiclist.rb +179 -0
  7. data/examples/alpmenu.rb +50 -0
  8. data/examples/app.sample +19 -0
  9. data/examples/atree.rb +100 -0
  10. data/examples/bline.rb +136 -0
  11. data/examples/common/file.rb +45 -0
  12. data/examples/data/README.markdown +9 -0
  13. data/examples/data/brew.txt +38 -0
  14. data/examples/data/color.2 +37 -0
  15. data/examples/data/gemlist.txt +60 -0
  16. data/examples/data/lotr.txt +12 -0
  17. data/examples/data/ports.txt +136 -0
  18. data/examples/data/table.txt +37 -0
  19. data/examples/data/tasks.csv +88 -0
  20. data/examples/data/tasks.txt +27 -0
  21. data/examples/data/todo.txt +10 -0
  22. data/examples/data/todo.txt.bak +10 -0
  23. data/examples/data/todocsv.csv +28 -0
  24. data/examples/data/unix1.txt +21 -0
  25. data/examples/data/unix2.txt +11 -0
  26. data/examples/dbdemo.rb +502 -0
  27. data/examples/dirtree.rb +94 -0
  28. data/examples/newtabbedwindow.rb +100 -0
  29. data/examples/newtesttabp.rb +92 -0
  30. data/examples/tabular.rb +146 -0
  31. data/examples/tasks.rb +178 -0
  32. data/examples/term2.rb +84 -0
  33. data/examples/testbuttons.rb +296 -0
  34. data/examples/testcombo.rb +102 -0
  35. data/examples/testfields.rb +195 -0
  36. data/examples/testkeypress.rb +72 -0
  37. data/examples/testlistbox.rb +170 -0
  38. data/examples/testmessagebox.rb +140 -0
  39. data/examples/testprogress.rb +116 -0
  40. data/examples/testree.rb +106 -0
  41. data/examples/testwsshortcuts.rb +66 -0
  42. data/examples/testwsshortcuts2.rb +128 -0
  43. data/lib/rbhex.rb +6 -0
  44. data/lib/rbhex/core/docs/index.txt +73 -0
  45. data/lib/rbhex/core/include/action.rb +80 -0
  46. data/lib/rbhex/core/include/actionmanager.rb +49 -0
  47. data/lib/rbhex/core/include/appmethods.rb +214 -0
  48. data/lib/rbhex/core/include/bordertitle.rb +48 -0
  49. data/lib/rbhex/core/include/chunk.rb +203 -0
  50. data/lib/rbhex/core/include/io.rb +553 -0
  51. data/lib/rbhex/core/include/listbindings.rb +74 -0
  52. data/lib/rbhex/core/include/listcellrenderer.rb +140 -0
  53. data/lib/rbhex/core/include/listeditable.rb +317 -0
  54. data/lib/rbhex/core/include/listscrollable.rb +663 -0
  55. data/lib/rbhex/core/include/listselectable.rb +271 -0
  56. data/lib/rbhex/core/include/multibuffer.rb +83 -0
  57. data/lib/rbhex/core/include/orderedhash.rb +77 -0
  58. data/lib/rbhex/core/include/ractionevent.rb +73 -0
  59. data/lib/rbhex/core/include/rchangeevent.rb +27 -0
  60. data/lib/rbhex/core/include/rhistory.rb +95 -0
  61. data/lib/rbhex/core/include/rinputdataevent.rb +47 -0
  62. data/lib/rbhex/core/include/vieditable.rb +172 -0
  63. data/lib/rbhex/core/include/widgetmenu.rb +66 -0
  64. data/lib/rbhex/core/system/colormap.rb +165 -0
  65. data/lib/rbhex/core/system/keyboard.rb +150 -0
  66. data/lib/rbhex/core/system/keydefs.rb +30 -0
  67. data/lib/rbhex/core/system/ncurses.rb +236 -0
  68. data/lib/rbhex/core/system/panel.rb +162 -0
  69. data/lib/rbhex/core/system/window.rb +913 -0
  70. data/lib/rbhex/core/util/ansiparser.rb +119 -0
  71. data/lib/rbhex/core/util/app.rb +1228 -0
  72. data/lib/rbhex/core/util/basestack.rb +410 -0
  73. data/lib/rbhex/core/util/bottomline.rb +1859 -0
  74. data/lib/rbhex/core/util/colorparser.rb +77 -0
  75. data/lib/rbhex/core/util/focusmanager.rb +31 -0
  76. data/lib/rbhex/core/util/padreader.rb +192 -0
  77. data/lib/rbhex/core/util/rcommandwindow.rb +604 -0
  78. data/lib/rbhex/core/util/rdialogs.rb +574 -0
  79. data/lib/rbhex/core/util/viewer.rb +149 -0
  80. data/lib/rbhex/core/util/widgetshortcuts.rb +506 -0
  81. data/lib/rbhex/core/version.rb +5 -0
  82. data/lib/rbhex/core/widgets/applicationheader.rb +103 -0
  83. data/lib/rbhex/core/widgets/box.rb +58 -0
  84. data/lib/rbhex/core/widgets/divider.rb +310 -0
  85. data/lib/rbhex/core/widgets/keylabelprinter.rb +194 -0
  86. data/lib/rbhex/core/widgets/rcombo.rb +253 -0
  87. data/lib/rbhex/core/widgets/rcontainer.rb +415 -0
  88. data/lib/rbhex/core/widgets/rlink.rb +30 -0
  89. data/lib/rbhex/core/widgets/rlist.rb +696 -0
  90. data/lib/rbhex/core/widgets/rmenu.rb +958 -0
  91. data/lib/rbhex/core/widgets/rmenulink.rb +22 -0
  92. data/lib/rbhex/core/widgets/rmessagebox.rb +387 -0
  93. data/lib/rbhex/core/widgets/rprogress.rb +118 -0
  94. data/lib/rbhex/core/widgets/rtabbedpane.rb +634 -0
  95. data/lib/rbhex/core/widgets/rtabbedwindow.rb +70 -0
  96. data/lib/rbhex/core/widgets/rtextarea.rb +960 -0
  97. data/lib/rbhex/core/widgets/rtextview.rb +739 -0
  98. data/lib/rbhex/core/widgets/rtree.rb +768 -0
  99. data/lib/rbhex/core/widgets/rwidget.rb +3277 -0
  100. data/lib/rbhex/core/widgets/scrollbar.rb +143 -0
  101. data/lib/rbhex/core/widgets/statusline.rb +113 -0
  102. data/lib/rbhex/core/widgets/tabular.rb +264 -0
  103. data/lib/rbhex/core/widgets/tabularwidget.rb +1142 -0
  104. data/lib/rbhex/core/widgets/textpad.rb +995 -0
  105. data/lib/rbhex/core/widgets/tree/treecellrenderer.rb +150 -0
  106. data/lib/rbhex/core/widgets/tree/treemodel.rb +428 -0
  107. data/rbhex-core.gemspec +32 -0
  108. metadata +172 -0
@@ -0,0 +1,995 @@
1
+ #!/usr/bin/env ruby
2
+ # ----------------------------------------------------------------------------- #
3
+ # File: textpad.rb
4
+ # Description: A class that displays text using a pad.
5
+ # The motivation for this is to put formatted text and not care about truncating and
6
+ # stuff. Also, there will be only one write, not each time scrolling happens.
7
+ # I found textview code for repaint being more complex than required.
8
+ # Author: rkumar http://github.com/rkumar/mancurses/
9
+ # Date: 2011-11-09 - 16:59
10
+ # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
11
+ # Last update: 2014-04-07 00:24
12
+ #
13
+ # == CHANGES
14
+ # == TODO
15
+ # Take care of 3 cases:
16
+ # 1. complete data change, then recreate pad, and call init_vars resetting row, col and curpos etc
17
+ # This is done by method text().
18
+ # 2. row added or minor change - recreate pad, repaint data but don't call initvars. must maintain cursor
19
+ # ignore recreate of pad if width or ht is less than w and h of container.
20
+ # 3. only rewrite a row - row data changed, no recreate pad or anything else
21
+ #
22
+ #
23
+ #
24
+ # ----------------------------------------------------------------------------- #
25
+ #
26
+ require 'rbhex'
27
+ require 'rbhex/core/include/bordertitle'
28
+
29
+ include RubyCurses
30
+ module RubyCurses
31
+ extend self
32
+ class TextPad < Widget
33
+ include BorderTitle
34
+
35
+ dsl_accessor :suppress_borders
36
+ dsl_accessor :print_footer
37
+ attr_reader :current_index
38
+ attr_reader :rows , :cols
39
+ # adding these only for debugging table, to see where cursor is.
40
+ attr_reader :lastrow, :lastcol
41
+ # for external methods or classes to advance cursor
42
+ #attr_accessor :curpos
43
+ # You may pass height, width, row and col for creating a window otherwise a fullscreen window
44
+ # will be created. If you pass a window from caller then that window will be used.
45
+ # Some keys are trapped, jkhl space, pgup, pgdown, end, home, t b
46
+ # This is currently very minimal and was created to get me started to integrating
47
+ # pads into other classes such as textview.
48
+ def initialize form=nil, config={}, &block
49
+
50
+ @editable = false
51
+ @focusable = true
52
+ @config = config
53
+ @row = @col = 0
54
+ @prow = @pcol = 0
55
+ @startrow = 0
56
+ @startcol = 0
57
+ # @list is unused, think it can be removed
58
+ #@list = []
59
+ super
60
+
61
+ ## code of calc_dimensions used to be here but moved late for listbox to work in flows
62
+
63
+ @_events << :PRESS
64
+ @_events << :ENTER_ROW
65
+ init_vars
66
+ end
67
+ # calculate height width row col etc as late as possible so they need not be set on creation
68
+ # seeing if we can move this later, so flows etc can call textpad based objects
69
+ def __calc_dimensions
70
+ ## NOTE
71
+ # ---------------------------------------------------
72
+ # Since we are using pads, you need to get your height, width and rows correct
73
+ # Make sure the height factors in the row, else nothing may show
74
+ # ---------------------------------------------------
75
+ #@height = @height.ifzero(FFI::NCurses.LINES)
76
+ #@width = @width.ifzero(FFI::NCurses.COLS)
77
+ @rows = @height
78
+ @cols = @width
79
+ # NOTE XXX if cols is > COLS then padrefresh can fail
80
+ @startrow = @row
81
+ @startcol = @col
82
+ unless @suppress_borders
83
+ @row_offset = @col_offset = 1
84
+ @startrow += 1
85
+ @startcol += 1
86
+ @rows -=3 # 3 is since print_border_only reduces one from width, to check whether this is correct
87
+ @cols -=3
88
+ @scrollatrows = @height - 3
89
+ else
90
+ # no borders printed
91
+ @rows -= 1 # 3 is since print_border_only reduces one from width, to check whether this is correct
92
+ ## if next is 0 then padrefresh doesn't print
93
+ @cols -=1
94
+ @scrollatrows = @height - 1 # check this out 0 or 1
95
+ @row_offset = @col_offset = 0
96
+ end
97
+ @top = @row
98
+ @left = @col
99
+ @lastrow = @row + @row_offset
100
+ @lastcol = @col + @col_offset
101
+ end
102
+ def init_vars
103
+ $multiplier = 0
104
+ @oldindex = @current_index = 0
105
+ # column cursor
106
+ @prow = @pcol = @curpos = 0
107
+ if @row && @col
108
+ @lastrow = @row + @row_offset
109
+ @lastcol = @col + @col_offset
110
+ end
111
+ @repaint_required = true
112
+ map_keys unless @mapped_keys
113
+ end
114
+ def rowcol #:nodoc:
115
+ return @row+@row_offset, @col+@col_offset
116
+ end
117
+
118
+ private
119
+ ## XXX in list text returns the selected row, list returns the full thing, keep consistent
120
+ def create_pad
121
+ destroy if @pad
122
+ #@pad = FFI::NCurses.newpad(@content_rows, @content_cols)
123
+ @pad = @window.get_pad(@content_rows, @content_cols )
124
+ end
125
+
126
+ private
127
+ # create and populate pad
128
+ def populate_pad
129
+ @_populate_needed = false
130
+ @content_rows = @content.count
131
+ @content_cols = content_cols()
132
+ @content_rows = @rows if @content_rows < @rows
133
+ @content_cols = @cols if @content_cols < @cols
134
+
135
+ create_pad
136
+
137
+ # clearstring is the string required to clear the pad to background color
138
+ @clearstring = nil
139
+ cp = get_color($datacolor, @color, @bgcolor)
140
+ @cp = FFI::NCurses.COLOR_PAIR(cp)
141
+ if cp != $datacolor
142
+ @clearstring ||= " " * @width
143
+ end
144
+
145
+ Ncurses::Panel.update_panels
146
+ render_all
147
+
148
+ end
149
+ #
150
+ # iterate through content rendering each row
151
+ # 2013-03-27 - 01:51 separated so that widgets with headers such as tables can
152
+ # override this for better control
153
+ def render_all
154
+ @content.each_with_index { |line, ix|
155
+ #FFI::NCurses.mvwaddstr(@pad,ix, 0, @content[ix])
156
+ render @pad, ix, line
157
+ }
158
+ end
159
+
160
+ public
161
+ # supply a custom renderer that implements +render()+
162
+ # @see render
163
+ def renderer r
164
+ @renderer = r
165
+ end
166
+ #
167
+ # default method for rendering a line
168
+ #
169
+ def render pad, lineno, text
170
+ if text.is_a? Chunks::ChunkLine
171
+ FFI::NCurses.wmove @pad, lineno, 0
172
+ a = get_attrib @attrib
173
+
174
+ show_colored_chunks text, nil, a
175
+ return
176
+ end
177
+ if @renderer
178
+ @renderer.render @pad, lineno, text
179
+ else
180
+ ## messabox does have a method to paint the whole window in bg color its in rwidget.rb
181
+ att = NORMAL
182
+ FFI::NCurses.wattron(@pad, @cp | att)
183
+ FFI::NCurses.mvwaddstr(@pad,lineno, 0, @clearstring) if @clearstring
184
+ FFI::NCurses.mvwaddstr(@pad,lineno, 0, @content[lineno])
185
+
186
+ #FFI::NCurses.mvwaddstr(pad, lineno, 0, text)
187
+ FFI::NCurses.wattroff(@pad, @cp | att)
188
+ end
189
+ end
190
+
191
+ # supply a filename as source for textpad
192
+ # Reads up file into @content
193
+ # One can optionally send in a method which takes a filename and returns an array of data
194
+ # This is required if you are processing files which are binary such as zip/archives and wish
195
+ # to print the contents. (e.g. cygnus gem sends in :get_file_contents).
196
+ # filename("a.c", method(:get_file_contents))
197
+ #
198
+ def filename(filename, reader=nil)
199
+ @file = filename
200
+ unless File.exists? filename
201
+ alert "#{filename} does not exist"
202
+ return
203
+ end
204
+ @filetype = File.extname filename
205
+ if reader
206
+ @content = reader.call(filename)
207
+ else
208
+ @content = File.open(filename,"r").readlines
209
+ end
210
+ if @filetype == ""
211
+ if @content.first.index("ruby")
212
+ @filetype = ".rb"
213
+ end
214
+ end
215
+ init_vars
216
+ @repaint_all = true
217
+ @_populate_needed = true
218
+ end
219
+
220
+ # Supply an array of string to be displayed
221
+ # This will replace existing text
222
+
223
+ # display text given in an array format. This is the principal way of giving content
224
+ # to a textpad, other than filename().
225
+ # @param Array of lines
226
+ # @param format (optional) can be :tmux :ansi or :none
227
+ # If a format other than :none is given, then formatted_text is called.
228
+ def text(lines, fmt=:none)
229
+ # added so callers can have one interface and avoid an if condition
230
+ return formatted_text(lines, fmt) unless fmt == :none
231
+
232
+ return @content if lines.empty?
233
+ @content = lines
234
+ @_populate_needed = true
235
+ @repaint_all = true
236
+ init_vars
237
+ self
238
+ end
239
+ alias :list :text
240
+ # for compat with textview
241
+ alias :set_content :text
242
+ def content
243
+ raise "content is nil " unless @content
244
+ return @content
245
+ end
246
+ alias :get_content :content
247
+
248
+ # print footer containing line and position
249
+ # XXX UNTESTED TODO TESTING
250
+ def print_foot #:nodoc:
251
+ @footer_attrib ||= Ncurses::A_REVERSE
252
+ footer = "R: #{@current_index+1}, C: #{@curpos+@pcol}, #{@list.length} lines "
253
+ $log.debug " print_foot calling printstring with #{@row} + #{@height} -1, #{@col}+2"
254
+ @graphic.printstring( @row + @height -1 , @col+2, footer, @color_pair || $datacolor, @footer_attrib)
255
+ @repaint_footer_required = false # 2010-01-23 22:55
256
+ end
257
+
258
+ ## ---- the next 2 methods deal with printing chunks
259
+ # we should put it int a common module and include it
260
+ # in Window and Pad stuff and perhaps include it conditionally.
261
+
262
+ ## 2013-03-07 - 19:57 changed width to @content_cols since data not printing
263
+ # in some cases fully when ansi sequences were present int some line but not in others
264
+ # lines without ansi were printing less by a few chars.
265
+ # This was prolly copied from rwindow, where it is okay since its for a specific width
266
+ def print(string, _width = @content_cols)
267
+ #return unless visible?
268
+ w = _width == 0? Ncurses.COLS : _width
269
+ FFI::NCurses.waddnstr(@pad,string.to_s, w) # changed 2011 dts
270
+ end
271
+
272
+ def show_colored_chunks(chunks, defcolor = nil, defattr = nil)
273
+ #return unless visible?
274
+ chunks.each do |chunk| #|color, chunk, attrib|
275
+ case chunk
276
+ when Chunks::Chunk
277
+ color = chunk.color
278
+ attrib = chunk.attrib
279
+ text = chunk.text
280
+ when Array
281
+ # for earlier demos that used an array
282
+ color = chunk[0]
283
+ attrib = chunk[2]
284
+ text = chunk[1]
285
+ end
286
+
287
+ color ||= defcolor
288
+ attrib ||= defattr || NORMAL
289
+
290
+ #cc, bg = ColorMap.get_colors_for_pair color
291
+ #$log.debug "XXX: CHUNK textpad #{text}, cp #{color} , attrib #{attrib}. #{cc}, #{bg} "
292
+ FFI::NCurses.wcolor_set(@pad, color,nil) if color
293
+ FFI::NCurses.wattron(@pad, attrib) if attrib
294
+ print(text)
295
+ FFI::NCurses.wattroff(@pad, attrib) if attrib
296
+ end
297
+ end
298
+
299
+ #
300
+ # pass in formatted text along with parser (:tmux or :ansi)
301
+ # NOTE this does not call init_vars, i think it should, text() does
302
+ def formatted_text text, fmt
303
+
304
+ require 'rbhex/core/include/chunk'
305
+ @formatted_text = text
306
+ @color_parser = fmt
307
+ @repaint_required = true
308
+ _convert_formatted
309
+ # don't know if start is always required. so putting in caller
310
+ #goto_start
311
+ #remove_all
312
+ end
313
+
314
+ # write pad onto window
315
+ #private
316
+ def padrefresh
317
+ top = @window.top
318
+ left = @window.left
319
+ sr = @startrow + top
320
+ sc = @startcol + left
321
+ retval = FFI::NCurses.prefresh(@pad,@prow,@pcol, sr , sc , @rows + sr , @cols+ sc );
322
+ $log.warn "XXX: PADREFRESH #{retval}, #{@prow}, #{@pcol}, #{sr}, #{sc}, #{@rows+sr}, #{@cols+sc}." if retval == -1
323
+ # padrefresh can fail if width is greater than NCurses.COLS
324
+ #FFI::NCurses.prefresh(@pad,@prow,@pcol, @startrow + top, @startcol + left, @rows + @startrow + top, @cols+@startcol + left);
325
+ end
326
+
327
+ # convenience method to return byte
328
+ private
329
+ def key x
330
+ x.getbyte(0)
331
+ end
332
+
333
+ # length of longest string in array
334
+ # This will give a 'wrong' max length if the array has ansi color escape sequences in it
335
+ # which inc the length but won't be printed. Such lines actually have less length when printed
336
+ # So in such cases, give more space to the pad.
337
+ def content_cols
338
+ longest = @content.max_by(&:length)
339
+ ## 2013-03-06 - 20:41 crashes here for some reason when man gives error message no man entry
340
+ return 0 unless longest
341
+ longest.length
342
+ end
343
+
344
+ public
345
+ # to be called with program / user has added a row or changed column widths so that
346
+ # the pad needs to be recreated. However, cursor positioning is maintained since this
347
+ # is considered to be a minor change.
348
+ # We do not call init_vars since user is continuing to do some work on a row/col.
349
+ def fire_dimension_changed
350
+ # recreate pad since width or ht has changed (row count or col width changed)
351
+ @_populate_needed = true
352
+ @repaint_required = true
353
+ @repaint_all = true
354
+ end
355
+ # repaint only one row since content of that row has changed.
356
+ # No recreate of pad is done.
357
+ def fire_row_changed ix
358
+ render @pad, ix, @content[ix]
359
+ # may need to call padrefresh TODO TESTING
360
+ end
361
+ def repaint
362
+ if @__first_time
363
+ __calc_dimensions
364
+ @__first_time = true
365
+ end
366
+ ## 2013-03-08 - 21:01 This is the fix to the issue of form callign an event like ? or F1
367
+ # which throws up a messagebox which leaves a black rect. We have no place to put a refresh
368
+ # However, form does call repaint for all objects, so we can do a padref here. Otherwise,
369
+ # it would get rejected. UNfortunately this may happen more often we want, but we never know
370
+ # when something pops up on the screen.
371
+ unless @repaint_required
372
+ padrefresh
373
+ return
374
+ end
375
+ # I can't recall why we are doing this late. Is the rreason relevant any longer
376
+ # Some methods that expect data are crashing like tablewidgets model_row
377
+ _convert_formatted
378
+
379
+ @window ||= @graphic
380
+ populate_pad if @_populate_needed
381
+ #HERE we need to populate once so user can pass a renderer
382
+
383
+ _do_borders
384
+
385
+ padrefresh
386
+ Ncurses::Panel.update_panels
387
+ @repaint_required = false
388
+ @repaint_all = false
389
+ end
390
+
391
+ def _do_borders
392
+ unless @suppress_borders
393
+ if @repaint_all
394
+ ## XXX im not getting the background color.
395
+ #@window.print_border_only @top, @left, @height-1, @width, $datacolor
396
+ clr = get_color $datacolor, @color, @bgcolor
397
+ #@window.print_border @top, @left, @height-1, @width, clr
398
+ @window.print_border_only @top, @left, @height-1, @width, clr
399
+ print_title
400
+
401
+ @repaint_footer_required = true if @oldrow != @current_index
402
+ print_foot if @print_footer && !@suppress_borders && @repaint_footer_required
403
+
404
+ @window.wrefresh
405
+ end
406
+ end
407
+ end
408
+ def _convert_formatted
409
+ if @formatted_text
410
+
411
+ l = RubyCurses::Utils.parse_formatted_text(@color_parser,
412
+ @formatted_text)
413
+
414
+ text(l)
415
+ @formatted_text = nil
416
+ end
417
+ end
418
+
419
+ #
420
+ # key mappings
421
+ #
422
+ def map_keys
423
+ @mapped_keys = true
424
+ bind_key([?g,?g], 'goto_start'){ goto_start } # mapping double keys like vim
425
+ bind_key(279, 'goto_start'){ goto_start }
426
+ bind_keys([?G,277], 'goto end'){ goto_end }
427
+ bind_keys([?k,KEY_UP], "Up"){ up }
428
+ bind_keys([?j,KEY_DOWN], "Down"){ down }
429
+ bind_key(?\C-e, "Scroll Window Down"){ scroll_window_down }
430
+ bind_key(?\C-y, "Scroll Window Up"){ scroll_window_up }
431
+ bind_keys([32,338, ?\C-d], "Scroll Forward"){ scroll_forward }
432
+ bind_keys([?\C-b,339]){ scroll_backward }
433
+ # the next one invalidates the single-quote binding for bookmarks
434
+ #bind_key([?',?']){ goto_last_position } # vim , goto last row position (not column)
435
+ bind_key(?/, :ask_search)
436
+ bind_key(?n, :find_more)
437
+ bind_key([?\C-x, ?>], :scroll_right)
438
+ bind_key([?\C-x, ?<], :scroll_left)
439
+ bind_key(?\M-l, :scroll_right)
440
+ bind_key(?\M-h, :scroll_left)
441
+ bind_key(?L, :bottom_of_window)
442
+ bind_key(?M, :middle_of_window)
443
+ bind_key(?H, :top_of_window)
444
+ bind_key(?w, :forward_word)
445
+ bind_key(?b, :backward_word)
446
+ bind_key(?l, :cursor_forward)
447
+ bind_key(?h, :cursor_backward)
448
+ bind_key(?$, :cursor_eol)
449
+ bind_key(KEY_ENTER, :fire_action_event)
450
+ end
451
+
452
+ # goto first line of file
453
+ def goto_start
454
+ #@oldindex = @current_index
455
+ $multiplier ||= 0
456
+ if $multiplier > 0
457
+ goto_line $multiplier - 1
458
+ return
459
+ end
460
+ @current_index = 0
461
+ @curpos = @pcol = @prow = 0
462
+ @prow = 0
463
+ $multiplier = 0
464
+ end
465
+
466
+ # goto last line of file
467
+ def goto_end
468
+ #@oldindex = @current_index
469
+ $multiplier ||= 0
470
+ if $multiplier > 0
471
+ goto_line $multiplier - 1
472
+ return
473
+ end
474
+ @current_index = @content.count() - 1
475
+ @prow = @current_index - @scrollatrows
476
+ $multiplier = 0
477
+ end
478
+ def goto_line line
479
+ ## we may need to calculate page, zfm style and place at right position for ensure visible
480
+ #line -= 1
481
+ @current_index = line
482
+ ensure_visible line
483
+ bounds_check
484
+ $multiplier = 0
485
+ end
486
+ def top_of_window
487
+ @current_index = @prow
488
+ $multiplier ||= 0
489
+ if $multiplier > 0
490
+ @current_index += $multiplier
491
+ $multiplier = 0
492
+ end
493
+ end
494
+ def bottom_of_window
495
+ @current_index = @prow + @scrollatrows
496
+ $multiplier ||= 0
497
+ if $multiplier > 0
498
+ @current_index -= $multiplier
499
+ $multiplier = 0
500
+ end
501
+ end
502
+ def middle_of_window
503
+ @current_index = @prow + (@scrollatrows/2)
504
+ $multiplier = 0
505
+ end
506
+
507
+ # move down a line mimicking vim's j key
508
+ # @param [int] multiplier entered prior to invoking key
509
+ def down num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
510
+ #@oldindex = @current_index if num > 10
511
+ @current_index += num
512
+ # no , i don't like this here. it scrolls up too much making prow = current_index
513
+ unless is_visible? @current_index
514
+ @prow += num
515
+ end
516
+ #ensure_visible
517
+ $multiplier = 0
518
+ end
519
+
520
+ # move up a line mimicking vim's k key
521
+ # @param [int] multiplier entered prior to invoking key
522
+ def up num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
523
+ #@oldindex = @current_index if num > 10
524
+ @current_index -= num
525
+ #unless is_visible? @current_index
526
+ #if @prow > @current_index
527
+ ##$status_message.value = "1 #{@prow} > #{@current_index} "
528
+ #@prow -= 1
529
+ #else
530
+ #end
531
+ #end
532
+ $multiplier = 0
533
+ end
534
+
535
+ # scrolls window down mimicking vim C-e
536
+ # @param [int] multiplier entered prior to invoking key
537
+ def scroll_window_down num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
538
+ @prow += num
539
+ if @prow > @current_index
540
+ @current_index += 1
541
+ end
542
+ #check_prow
543
+ $multiplier = 0
544
+ end
545
+
546
+ # scrolls window up mimicking vim C-y
547
+ # @param [int] multiplier entered prior to invoking key
548
+ def scroll_window_up num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
549
+ @prow -= num
550
+ unless is_visible? @current_index
551
+ # one more check may be needed here TODO
552
+ @current_index -= num
553
+ end
554
+ $multiplier = 0
555
+ end
556
+
557
+ # scrolls lines a window full at a time, on pressing ENTER or C-d or pagedown
558
+ def scroll_forward
559
+ #@oldindex = @current_index
560
+ @current_index += @scrollatrows
561
+ @prow = @current_index - @scrollatrows
562
+ end
563
+
564
+ # scrolls lines backward a window full at a time, on pressing pageup
565
+ # C-u may not work since it is trapped by form earlier. Need to fix
566
+ def scroll_backward
567
+ #@oldindex = @current_index
568
+ @current_index -= @scrollatrows
569
+ @prow = @current_index - @scrollatrows
570
+ end
571
+ def goto_last_position
572
+ return unless @oldindex
573
+ tmp = @current_index
574
+ @current_index = @oldindex
575
+ @oldindex = tmp
576
+ bounds_check
577
+ end
578
+ def scroll_right
579
+ # I don't think it will ever be less since we've increased it to cols
580
+ if @content_cols <= @cols
581
+ maxpcol = 0
582
+ @pcol = 0
583
+ else
584
+ maxpcol = @content_cols - @cols - 1
585
+ @pcol += 1
586
+ @pcol = maxpcol if @pcol > maxpcol
587
+ end
588
+ # to prevent right from retaining earlier painted values
589
+ # padreader does not do a clear, yet works fine.
590
+ # OK it has an update_panel after padrefresh, that clears it seems.
591
+ #this clears entire window not just the pad
592
+ #FFI::NCurses.wclear(@window.get_window)
593
+ # so border and title is repainted after window clearing
594
+ #
595
+ # Next line was causing all sorts of problems when scrolling with ansi formatted text
596
+ #@repaint_all = true
597
+ end
598
+ def scroll_left
599
+ @pcol -= 1
600
+ end
601
+ #
602
+ #
603
+ #
604
+ #
605
+ def handle_key ch
606
+ return :UNHANDLED unless @content
607
+
608
+
609
+ @oldrow = @prow
610
+ @oldcol = @pcol
611
+ $log.debug "XXX: PAD got #{ch} prow = #{@prow}"
612
+ begin
613
+ case ch
614
+ when ?0.getbyte(0)..?9.getbyte(0)
615
+ if ch == ?0.getbyte(0) && $multiplier == 0
616
+ cursor_bol
617
+ return 0
618
+ end
619
+ # storing digits entered so we can multiply motion actions
620
+ $multiplier *= 10 ; $multiplier += (ch-48)
621
+ return 0
622
+ when ?\C-c.getbyte(0)
623
+ $multiplier = 0
624
+ return 0
625
+ else
626
+ # check for bindings, these cannot override above keys since placed at end
627
+ begin
628
+ ret = process_key ch, self
629
+ $multiplier = 0
630
+ bounds_check
631
+ ## If i press C-x > i get an alert from rwidgets which blacks the screen
632
+ # if i put a padrefresh here it becomes okay but only for one pad,
633
+ # i still need to do it for all pads.
634
+ rescue => err
635
+ $log.error " TEXTPAD ERROR INS #{err} "
636
+ $log.debug(err.backtrace.join("\n"))
637
+ textdialog ["Error in TextPad: #{err} ", *err.backtrace], :title => "Exception"
638
+ end
639
+ ## NOTE if textpad does not handle the event and it goes to form which pops
640
+ # up a messagebox, then padrefresh does not happen, since control does not
641
+ # come back here, so a black rect is left on screen
642
+ # please note that a bounds check will not happen for stuff that
643
+ # is triggered by form, so you'll have to to it yourself or
644
+ # call setrowcol explicity if the cursor is not updated
645
+ return :UNHANDLED if ret == :UNHANDLED
646
+ end
647
+ rescue => err
648
+ $log.error " TEXTPAD ERROR 591 #{err} "
649
+ $log.debug( err) if err
650
+ $log.debug(err.backtrace.join("\n")) if err
651
+ textdialog ["Error in TextPad: #{err} ", *err.backtrace], :title => "Exception"
652
+ $error_message.value = ""
653
+ ensure
654
+ padrefresh
655
+ Ncurses::Panel.update_panels
656
+ end
657
+ return 0
658
+ end # while loop
659
+
660
+ #
661
+ # event when user hits enter on a row, user would bind :PRESS
662
+ #
663
+ def fire_action_event
664
+ return if @content.nil? || @content.size == 0
665
+ require 'rbhex/core/include/ractionevent'
666
+ aev = TextActionEvent.new self, :PRESS, current_value().to_s, @current_index, @curpos
667
+ fire_handler :PRESS, aev
668
+ end
669
+ #
670
+ # returns current value (what cursor is on)
671
+ def current_value
672
+ @content[@current_index]
673
+ end
674
+ #
675
+ # execute binding when a row is entered, used more in lists to display some text
676
+ # in a header or footer as one traverses
677
+ #
678
+ def on_enter_row arow
679
+ return if @content.nil? || @content.size == 0
680
+ require 'rbhex/core/include/ractionevent'
681
+ aev = TextActionEvent.new self, :ENTER_ROW, current_value().to_s, @current_index, @curpos
682
+ fire_handler :ENTER_ROW, aev
683
+ @repaint_required = true
684
+ end
685
+
686
+ # destroy the pad, this needs to be called from somewhere, like when the app
687
+ # closes or the current window closes , or else we could have a seg fault
688
+ # or some ugliness on the screen below this one (if nested).
689
+
690
+ # Now since we use get_pad from window, upon the window being destroyed,
691
+ # it will call this. Else it will destroy pad
692
+ def destroy
693
+ FFI::NCurses.delwin(@pad) if @pad # when do i do this ? FIXME
694
+ @pad = nil
695
+ end
696
+ #
697
+ # return true if the given row is visible
698
+ def is_visible? index
699
+ j = index - @prow #@toprow
700
+ j >= 0 && j <= @scrollatrows
701
+ end
702
+ #
703
+ # called when this widget is entered, by form
704
+ def on_enter
705
+ set_form_row
706
+ end
707
+ # called by form
708
+ def set_form_row
709
+ setrowcol @lastrow, @lastcol
710
+ end
711
+ # called by form
712
+ def set_form_col
713
+ end
714
+
715
+ private
716
+
717
+ # check that current_index and prow are within correct ranges
718
+ # sets row (and someday col too)
719
+ # sets repaint_required
720
+
721
+ def bounds_check
722
+ r,c = rowcol
723
+ @current_index = 0 if @current_index < 0
724
+ @current_index = @content.count()-1 if @current_index > @content.count()-1
725
+ ensure_visible
726
+
727
+ check_prow
728
+ #$log.debug "XXX: PAD BOUNDS ci:#{@current_index} , old #{@oldrow},pr #{@prow}, max #{@maxrow} pcol #{@pcol} maxcol #{@maxcol}"
729
+ @crow = @current_index + r - @prow
730
+ @crow = r if @crow < r
731
+ # 2 depends on whetehr suppressborders
732
+ if @suppress_borders
733
+ @crow = @row + @height -1 if @crow >= r + @height -1
734
+ else
735
+ @crow = @row + @height -2 if @crow >= r + @height -2
736
+ end
737
+ setrowcol @crow, @curpos+c
738
+ lastcurpos @crow, @curpos+c
739
+ if @oldindex != @current_index
740
+ on_enter_row @current_index
741
+ @oldindex = @current_index
742
+ end
743
+ if @oldrow != @prow || @oldcol != @pcol
744
+ # only if scrolling has happened.
745
+ @repaint_required = true
746
+ end
747
+ end
748
+ #
749
+ # save last cursor position so when reentering, cursor can be repositioned
750
+ def lastcurpos r,c
751
+ @lastrow = r
752
+ @lastcol = c
753
+ end
754
+
755
+
756
+ # check that prow and pcol are within bounds
757
+ #
758
+ def check_prow
759
+ @prow = 0 if @prow < 0
760
+ @pcol = 0 if @pcol < 0
761
+
762
+ cc = @content.count
763
+
764
+ if cc < @rows
765
+ @prow = 0
766
+ else
767
+ maxrow = cc - @rows - 1
768
+ if @prow > maxrow
769
+ @prow = maxrow
770
+ end
771
+ end
772
+ # we still need to check the max that prow can go otherwise
773
+ # the pad shows earlier stuff.
774
+ #
775
+ return
776
+ end
777
+ public
778
+ ##
779
+ # Ask user for string to search for
780
+ # This uses the dialog, but what if user wants the old style.
781
+ # Isn't there a cleaner way to let user override style, or allow user
782
+ # to use own UI for getting pattern and then passing here.
783
+ # @param str default nil. If not passed, then user is prompted using get_string dialog
784
+ # This allows caller to use own method to prompt for string such as 'get_line' or 'rbgetstr' /
785
+ # 'ask()'
786
+ def ask_search str=nil
787
+ # the following is a change that enables callers to prompt for the string
788
+ # using some other style, basically the classical style and send the string in
789
+ str = get_string("Enter pattern: ") unless str
790
+ return if str.nil?
791
+ str = @last_regex if str == ""
792
+ return if str == ""
793
+ ix = next_match str
794
+ return unless ix
795
+ @last_regex = str
796
+
797
+ #@oldindex = @current_index
798
+ @current_index = ix[0]
799
+ @curpos = ix[1]
800
+ ensure_visible
801
+ end
802
+ ##
803
+ # Find next matching row for string accepted in ask_search
804
+ #
805
+ def find_more
806
+ return unless @last_regex
807
+ ix = next_match @last_regex
808
+ return unless ix
809
+ #@oldindex = @current_index
810
+ @current_index = ix[0]
811
+ @curpos = ix[1]
812
+ ensure_visible
813
+ end
814
+
815
+ ##
816
+ # Find the next row that contains given string
817
+ # @return row and col offset of match, or nil
818
+ # @param String to find
819
+ def next_match str
820
+ first = nil
821
+ ## content can be string or Chunkline, so we had to write <tt>index</tt> for this.
822
+ ## =~ does not give an error, but it does not work.
823
+ @content.each_with_index do |line, ix|
824
+ col = line.index str
825
+ if col
826
+ first ||= [ ix, col ]
827
+ if ix > @current_index
828
+ return [ix, col]
829
+ end
830
+ end
831
+ end
832
+ return first
833
+ end
834
+ ##
835
+ # Ensure current row is visible, if not make it first row
836
+ # NOTE - need to check if its at end and then reduce scroll at rows, check_prow does that
837
+ #
838
+ # @param current_index (default if not given)
839
+ #
840
+ def ensure_visible row = @current_index
841
+ unless is_visible? row
842
+ @prow = @current_index
843
+ end
844
+ end
845
+ #
846
+ # jumps cursor to next work, like vim's w key
847
+ #
848
+ def forward_word
849
+ $multiplier = 1 if !$multiplier || $multiplier == 0
850
+ line = @current_index
851
+ buff = @content[line].to_s
852
+ return unless buff
853
+ pos = @curpos || 0 # list does not have curpos
854
+ $multiplier.times {
855
+ found = buff.index(/[[:punct:][:space:]]\w/, pos)
856
+ if !found
857
+ # if not found, we've lost a counter
858
+ if line+1 < @content.length
859
+ line += 1
860
+ else
861
+ return
862
+ end
863
+ pos = 0
864
+ else
865
+ pos = found + 1
866
+ end
867
+ $log.debug " forward_word: pos #{pos} line #{line} buff: #{buff}"
868
+ }
869
+ $multiplier = 0
870
+ @current_index = line
871
+ @curpos = pos
872
+ ensure_visible
873
+ @repaint_required = true
874
+ end
875
+ def backward_word
876
+ $multiplier = 1 if !$multiplier || $multiplier == 0
877
+ line = @current_index
878
+ buff = @content[line].to_s
879
+ return unless buff
880
+ pos = @curpos || 0 # list does not have curpos
881
+ $multiplier.times {
882
+ found = buff.rindex(/[[:punct:][:space:]]\w/, pos-2)
883
+ if !found || found == 0
884
+ # if not found, we've lost a counter
885
+ if pos > 0
886
+ pos = 0
887
+ elsif line > 0
888
+ line -= 1
889
+ pos = @content[line].to_s.size
890
+ else
891
+ return
892
+ end
893
+ else
894
+ pos = found + 1
895
+ end
896
+ $log.debug " backward_word: pos #{pos} line #{line} buff: #{buff}"
897
+ }
898
+ $multiplier = 0
899
+ @current_index = line
900
+ @curpos = pos
901
+ ensure_visible
902
+ @repaint_required = true
903
+ end
904
+ #
905
+ # move cursor forward by one char (currently will not pan)
906
+ def cursor_forward
907
+ $multiplier = 1 if $multiplier == 0
908
+ if @curpos < @cols
909
+ @curpos += $multiplier
910
+ if @curpos > @cols
911
+ @curpos = @cols
912
+ end
913
+ @repaint_required = true
914
+ end
915
+ $multiplier = 0
916
+ end
917
+ #
918
+ # move cursor backward by one char (currently will not pan)
919
+ def cursor_backward
920
+ $multiplier = 1 if $multiplier == 0
921
+ if @curpos > 0
922
+ @curpos -= $multiplier
923
+ @curpos = 0 if @curpos < 0
924
+ @repaint_required = true
925
+ end
926
+ $multiplier = 0
927
+ end
928
+ # moves cursor to end of line also panning window if necessary
929
+ # NOTE: if one line on another page (not displayed) is way longer than any
930
+ # displayed line, then this will pan way ahead, so may not be very intelligent
931
+ # in such situations.
932
+ def cursor_eol
933
+ # pcol is based on max length not current line's length
934
+ @pcol = @content_cols - @cols - 1
935
+ @curpos = @content[@current_index].size
936
+ @repaint_required = true
937
+ end
938
+ #
939
+ # moves cursor to start of line, panning if required
940
+ def cursor_bol
941
+ # copy of C-a - start of line
942
+ @repaint_required = true if @pcol > 0
943
+ @pcol = 0
944
+ @curpos = 0
945
+ end
946
+
947
+ end # class textpad
948
+
949
+ # a test renderer to see how things go
950
+ class DefaultFileRenderer
951
+ #
952
+ # @param pad for calling print methods on
953
+ # @param lineno the line number on the pad to print on
954
+ # @param text data to print
955
+ def render pad, lineno, text
956
+ bg = :black
957
+ fg = :white
958
+ att = NORMAL
959
+ #cp = $datacolor
960
+ cp = get_color($datacolor, fg, bg)
961
+ ## XXX believe it or not, the next line can give you "invalid byte sequence in UTF-8
962
+ # even when processing filename at times. Or if its an mp3 or non-text file.
963
+ if text =~ /^\s*# / || text =~ /^\s*## /
964
+ fg = :red
965
+ #att = BOLD
966
+ cp = get_color($datacolor, fg, bg)
967
+ elsif text =~ /^\s*#/
968
+ fg = :blue
969
+ cp = get_color($datacolor, fg, bg)
970
+ elsif text =~ /^\s*(class|module) /
971
+ fg = :cyan
972
+ att = BOLD
973
+ cp = get_color($datacolor, fg, bg)
974
+ elsif text =~ /^\s*def / || text =~ /^\s*function /
975
+ fg = :yellow
976
+ att = BOLD
977
+ cp = get_color($datacolor, fg, bg)
978
+ elsif text =~ /^\s*(end|if |elsif|else|begin|rescue|ensure|include|extend|while|unless|case |when )/
979
+ fg = :magenta
980
+ att = BOLD
981
+ cp = get_color($datacolor, fg, bg)
982
+ elsif text =~ /^\s*=/
983
+ # rdoc case
984
+ fg = :blue
985
+ bg = :white
986
+ cp = get_color($datacolor, fg, bg)
987
+ att = REVERSE
988
+ end
989
+ FFI::NCurses.wattron(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
990
+ FFI::NCurses.mvwaddstr(pad, lineno, 0, text)
991
+ FFI::NCurses.wattroff(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
992
+
993
+ end
994
+ end
995
+ end