rbhex-core 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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