cygnus 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,846 @@
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: 2013-03-16 17:41
12
+ #
13
+ # == CHANGES
14
+ # == TODO
15
+ # _ in popup case allowing scrolling when it should not so you get an extra char at end
16
+ # _ The list one needs a f-char like functionality.
17
+ # x handle putting data again and overwriting existing
18
+ # When reputting data, the underlying pad needs to be properly cleared
19
+ # esp last row and last col
20
+ #
21
+ # x add mappings and process key in handle_keys and other widget things
22
+ # - can pad movement and other ops be abstracted into module for reuse
23
+ # / get scrolling like in vim (C-f e y b d)
24
+ #
25
+ # == TODO 2013-03-07 - 20:34
26
+ # _ key bindings not showing up -- bind properly
27
+ # ----------------------------------------------------------------------------- #
28
+ #
29
+ require 'rbcurse'
30
+ require 'rbcurse/core/include/bordertitle'
31
+
32
+ include RubyCurses
33
+ module Cygnus
34
+ extend self
35
+ class TextPad < Widget
36
+ include BorderTitle
37
+
38
+ dsl_accessor :suppress_border
39
+ attr_reader :current_index
40
+ attr_reader :rows , :cols
41
+ # You may pass height, width, row and col for creating a window otherwise a fullscreen window
42
+ # will be created. If you pass a window from caller then that window will be used.
43
+ # Some keys are trapped, jkhl space, pgup, pgdown, end, home, t b
44
+ # This is currently very minimal and was created to get me started to integrating
45
+ # pads into other classes such as textview.
46
+ def initialize form=nil, config={}, &block
47
+
48
+ @editable = false
49
+ @focusable = true
50
+ @config = config
51
+ @row = @col = 0
52
+ @prow = @pcol = 0
53
+ @startrow = 0
54
+ @startcol = 0
55
+ @list = []
56
+ super
57
+
58
+ ## NOTE
59
+ # ---------------------------------------------------
60
+ # Since we are using pads, you need to get your height, width and rows correct
61
+ # Make sure the height factors in the row, else nothing may show
62
+ # ---------------------------------------------------
63
+ #@height = @height.ifzero(FFI::NCurses.LINES)
64
+ #@width = @width.ifzero(FFI::NCurses.COLS)
65
+ @rows = @height
66
+ @cols = @width
67
+ # NOTE XXX if cols is > COLS then padrefresh can fail
68
+ @startrow = @row
69
+ @startcol = @col
70
+ #@suppress_border = config[:suppress_border]
71
+ @row_offset = @col_offset = 1
72
+ unless @suppress_border
73
+ @startrow += 1
74
+ @startcol += 1
75
+ @rows -=3 # 3 is since print_border_only reduces one from width, to check whether this is correct
76
+ @cols -=3
77
+ else
78
+ # seeing why nothing is printing
79
+ @rows -=0 # 3 is since print_border_only reduces one from width, to check whether this is correct
80
+ ## if next is 0 then padrefresh doesn't print
81
+ @cols -=1
82
+ end
83
+ @row_offset = @col_offset = 0 if @suppress_borders
84
+ @top = @row
85
+ @left = @col
86
+ @lastrow = @row + 1
87
+ @lastcol = @col + 1
88
+ @_events << :PRESS
89
+ @_events << :ENTER_ROW
90
+ @scrollatrows = @height - 3
91
+ init_vars
92
+ end
93
+ def init_vars
94
+ $multiplier = 0
95
+ @oldindex = @current_index = 0
96
+ # column cursor
97
+ @prow = @pcol = @curpos = 0
98
+ if @row && @col
99
+ @lastrow = @row + 1
100
+ @lastcol = @col + 1
101
+ end
102
+ @repaint_required = true
103
+ end
104
+ def rowcol #:nodoc:
105
+ return @row+@row_offset, @col+@col_offset
106
+ end
107
+
108
+ private
109
+ ## XXX in list text returns the selected row, list returns the full thing, keep consistent
110
+ def create_pad
111
+ destroy if @pad
112
+ #@pad = FFI::NCurses.newpad(@content_rows, @content_cols)
113
+ @pad = @window.get_pad(@content_rows, @content_cols )
114
+ end
115
+
116
+ private
117
+ # create and populate pad
118
+ def populate_pad
119
+ @_populate_needed = false
120
+ # how can we make this more sensible ? FIXME
121
+ #@renderer ||= DefaultFileRenderer.new #if ".rb" == @filetype
122
+ @content_rows = @content.count
123
+ @content_cols = content_cols()
124
+ # this should be explicit and not "intelligent"
125
+ #@title += " [ #{@content_rows},#{@content_cols}] " if @cols > 50
126
+ @content_rows = @rows if @content_rows < @rows
127
+ @content_cols = @cols if @content_cols < @cols
128
+ #$log.debug "XXXX content_cols = #{@content_cols}"
129
+
130
+ create_pad
131
+
132
+ # clearstring is the string required to clear the pad to backgroud color
133
+ @clearstring = nil
134
+ cp = get_color($datacolor, @color, @bgcolor)
135
+ @cp = FFI::NCurses.COLOR_PAIR(cp)
136
+ if cp != $datacolor
137
+ @clearstring ||= " " * @width
138
+ end
139
+
140
+ Ncurses::Panel.update_panels
141
+ @content.each_index { |ix|
142
+ #FFI::NCurses.mvwaddstr(@pad,ix, 0, @content[ix])
143
+ render @pad, ix, @content[ix]
144
+ }
145
+
146
+ end
147
+
148
+ public
149
+ # supply a custom renderer that implements +render()+
150
+ # @see render
151
+ def renderer r
152
+ @renderer = r
153
+ end
154
+ #
155
+ # default method for rendering a line
156
+ #
157
+ def render pad, lineno, text
158
+ if text.is_a? Chunks::ChunkLine
159
+ FFI::NCurses.wmove @pad, lineno, 0
160
+ a = get_attrib @attrib
161
+
162
+ show_colored_chunks text, nil, a
163
+ return
164
+ end
165
+ if @renderer
166
+ @renderer.render @pad, lineno, text
167
+ else
168
+ ## messabox does have a method to paint the whole window in bg color its in rwidget.rb
169
+ att = NORMAL
170
+ FFI::NCurses.wattron(@pad, @cp | att)
171
+ FFI::NCurses.mvwaddstr(@pad,lineno, 0, @clearstring) if @clearstring
172
+ FFI::NCurses.mvwaddstr(@pad,lineno, 0, @content[lineno])
173
+
174
+ #FFI::NCurses.mvwaddstr(pad, lineno, 0, text)
175
+ FFI::NCurses.wattroff(@pad, @cp | att)
176
+ end
177
+ end
178
+
179
+ # supply a filename as source for textpad
180
+ # Reads up file into @content
181
+
182
+ def filename(filename)
183
+ @file = filename
184
+ unless File.exists? filename
185
+ alert "#{filename} does not exist"
186
+ return
187
+ end
188
+ @filetype = File.extname filename
189
+ @content = File.open(filename,"r").readlines
190
+ if @filetype == ""
191
+ if @content.first.index("ruby")
192
+ @filetype = ".rb"
193
+ end
194
+ end
195
+ init_vars
196
+ @repaint_all = true
197
+ @_populate_needed = true
198
+ end
199
+
200
+ # Supply an array of string to be displayed
201
+ # This will replace existing text
202
+
203
+ ## XXX in list text returns the selected row, list returns the full thing, keep consistent
204
+ def text lines
205
+ raise "text() receiving null content" unless lines
206
+ @content = lines
207
+ @_populate_needed = true
208
+ @repaint_all = true
209
+ init_vars
210
+ end
211
+ alias :list :text
212
+ def content
213
+ raise "content is nil " unless @content
214
+ return @content
215
+ end
216
+
217
+ ## ---- the next 2 methods deal with printing chunks
218
+ # we should put it int a common module and include it
219
+ # in Window and Pad stuff and perhaps include it conditionally.
220
+
221
+ ## 2013-03-07 - 19:57 changed width to @content_cols since data not printing
222
+ # in some cases fully when ansi sequences were present int some line but not in others
223
+ # lines without ansi were printing less by a few chars.
224
+ # This was prolly copied from rwindow, where it is okay since its for a specific width
225
+ def print(string, _width = @content_cols)
226
+ #return unless visible?
227
+ w = _width == 0? Ncurses.COLS : _width
228
+ FFI::NCurses.waddnstr(@pad,string.to_s, w) # changed 2011 dts
229
+ end
230
+
231
+ def show_colored_chunks(chunks, defcolor = nil, defattr = nil)
232
+ #return unless visible?
233
+ chunks.each do |chunk| #|color, chunk, attrib|
234
+ case chunk
235
+ when Chunks::Chunk
236
+ color = chunk.color
237
+ attrib = chunk.attrib
238
+ text = chunk.text
239
+ when Array
240
+ # for earlier demos that used an array
241
+ color = chunk[0]
242
+ attrib = chunk[2]
243
+ text = chunk[1]
244
+ end
245
+
246
+ color ||= defcolor
247
+ attrib ||= defattr || NORMAL
248
+
249
+ #cc, bg = ColorMap.get_colors_for_pair color
250
+ #$log.debug "XXX: CHUNK textpad #{text}, cp #{color} , attrib #{attrib}. #{cc}, #{bg} "
251
+ FFI::NCurses.wcolor_set(@pad, color,nil) if color
252
+ FFI::NCurses.wattron(@pad, attrib) if attrib
253
+ print(text)
254
+ FFI::NCurses.wattroff(@pad, attrib) if attrib
255
+ end
256
+ end
257
+
258
+ def formatted_text text, fmt
259
+ require 'rbcurse/core/include/chunk'
260
+ @formatted_text = text
261
+ @color_parser = fmt
262
+ @repaint_required = true
263
+ # don't know if start is always required. so putting in caller
264
+ #goto_start
265
+ #remove_all
266
+ end
267
+
268
+ # write pad onto window
269
+ #private
270
+ def padrefresh
271
+ top = @window.top
272
+ left = @window.left
273
+ sr = @startrow + top
274
+ sc = @startcol + left
275
+ retval = FFI::NCurses.prefresh(@pad,@prow,@pcol, sr , sc , @rows + sr , @cols+ sc );
276
+ $log.warn "XXX: PADREFRESH #{retval}, #{@prow}, #{@pcol}, #{sr}, #{sc}, #{@rows+sr}, #{@cols+sc}." if retval == -1
277
+ # padrefresh can fail if width is greater than NCurses.COLS
278
+ #FFI::NCurses.prefresh(@pad,@prow,@pcol, @startrow + top, @startcol + left, @rows + @startrow + top, @cols+@startcol + left);
279
+ end
280
+
281
+ # convenience method to return byte
282
+ private
283
+ def key x
284
+ x.getbyte(0)
285
+ end
286
+
287
+ # length of longest string in array
288
+ # This will give a 'wrong' max length if the array has ansi color escape sequences in it
289
+ # which inc the length but won't be printed. Such lines actually have less length when printed
290
+ # So in such cases, give more space to the pad.
291
+ def content_cols
292
+ longest = @content.max_by(&:length)
293
+ ## 2013-03-06 - 20:41 crashes here for some reason when man gives error message no man entry
294
+ return 0 unless longest
295
+ longest.length
296
+ end
297
+
298
+ public
299
+ def repaint
300
+ ## 2013-03-08 - 21:01 This is the fix to the issue of form callign an event like ? or F1
301
+ # which throws up a messagebox which leaves a black rect. We have no place to put a refresh
302
+ # However, form does call repaint for all objects, so we can do a padref here. Otherwise,
303
+ # it would get rejected. UNfortunately this may happen more often we want, but we never know
304
+ # when something pops up on the screen.
305
+ padrefresh unless @repaint_required
306
+ return unless @repaint_required
307
+ if @formatted_text
308
+ $log.debug "XXX: INSIDE FORMATTED TEXT "
309
+
310
+ l = RubyCurses::Utils.parse_formatted_text(@color_parser,
311
+ @formatted_text)
312
+
313
+ text(l)
314
+ @formatted_text = nil
315
+ end
316
+
317
+ ## moved this line up or else create_p was crashing
318
+ @window ||= @graphic
319
+ populate_pad if @_populate_needed
320
+ #HERE we need to populate once so user can pass a renderer
321
+ unless @suppress_border
322
+ if @repaint_all
323
+ ## XXX im not getting the background color.
324
+ #@window.print_border_only @top, @left, @height-1, @width, $datacolor
325
+ clr = get_color $datacolor, @color, @bgcolor
326
+ #@window.print_border @top, @left, @height-1, @width, clr
327
+ @window.print_border_only @top, @left, @height-1, @width, clr
328
+ print_title
329
+ @window.wrefresh
330
+ end
331
+ end
332
+
333
+ padrefresh
334
+ Ncurses::Panel.update_panels
335
+ @repaint_required = false
336
+ @repaint_all = false
337
+ end
338
+
339
+ #
340
+ # key mappings
341
+ #
342
+ def map_keys
343
+ @mapped_keys = true
344
+ bind_key([?g,?g], 'goto_start'){ goto_start } # mapping double keys like vim
345
+ bind_key(279, 'goto_start'){ goto_start }
346
+ bind_keys([?G,277], 'goto end'){ goto_end }
347
+ bind_keys([?k,KEY_UP], "Up"){ up }
348
+ bind_keys([?j,KEY_DOWN], "Down"){ down }
349
+ bind_key(?\C-e, "Scroll Window Down"){ scroll_window_down }
350
+ bind_key(?\C-y, "Scroll Window Up"){ scroll_window_up }
351
+ bind_keys([32,338, ?\C-d], "Scroll Forward"){ scroll_forward }
352
+ bind_keys([?\C-b,339]){ scroll_backward }
353
+ # the next one invalidates the single-quote binding for bookmarks
354
+ #bind_key([?',?']){ goto_last_position } # vim , goto last row position (not column)
355
+ bind_key(?/, :ask_search)
356
+ bind_key(?n, :find_more)
357
+ bind_key([?\C-x, ?>], :scroll_right)
358
+ bind_key([?\C-x, ?<], :scroll_left)
359
+ bind_key(?\M-l, :scroll_right)
360
+ bind_key(?\M-h, :scroll_left)
361
+ bind_key(?L, :bottom_of_window)
362
+ bind_key(?M, :middle_of_window)
363
+ bind_key(?H, :top_of_window)
364
+ bind_key(?w, :forward_word)
365
+ bind_key(KEY_ENTER, :fire_action_event)
366
+ end
367
+
368
+ # goto first line of file
369
+ def goto_start
370
+ #@oldindex = @current_index
371
+ $multiplier ||= 0
372
+ if $multiplier > 0
373
+ goto_line $multiplier - 1
374
+ return
375
+ end
376
+ @current_index = 0
377
+ @curpos = @pcol = @prow = 0
378
+ @prow = 0
379
+ $multiplier = 0
380
+ end
381
+
382
+ # goto last line of file
383
+ def goto_end
384
+ #@oldindex = @current_index
385
+ $multiplier ||= 0
386
+ if $multiplier > 0
387
+ goto_line $multiplier - 1
388
+ return
389
+ end
390
+ @current_index = @content.count() - 1
391
+ @prow = @current_index - @scrollatrows
392
+ $multiplier = 0
393
+ end
394
+ def goto_line line
395
+ ## we may need to calculate page, zfm style and place at right position for ensure visible
396
+ #line -= 1
397
+ @current_index = line
398
+ ensure_visible line
399
+ bounds_check
400
+ $multiplier = 0
401
+ end
402
+ def top_of_window
403
+ @current_index = @prow
404
+ $multiplier ||= 0
405
+ if $multiplier > 0
406
+ @current_index += $multiplier
407
+ $multiplier = 0
408
+ end
409
+ end
410
+ def bottom_of_window
411
+ @current_index = @prow + @scrollatrows
412
+ $multiplier ||= 0
413
+ if $multiplier > 0
414
+ @current_index -= $multiplier
415
+ $multiplier = 0
416
+ end
417
+ end
418
+ def middle_of_window
419
+ @current_index = @prow + (@scrollatrows/2)
420
+ $multiplier = 0
421
+ end
422
+
423
+ # move down a line mimicking vim's j key
424
+ # @param [int] multiplier entered prior to invoking key
425
+ def down num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
426
+ #@oldindex = @current_index if num > 10
427
+ @current_index += num
428
+ ensure_visible
429
+ $multiplier = 0
430
+ end
431
+
432
+ # move up a line mimicking vim's k key
433
+ # @param [int] multiplier entered prior to invoking key
434
+ def up num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
435
+ #@oldindex = @current_index if num > 10
436
+ @current_index -= num
437
+ #unless is_visible? @current_index
438
+ #if @prow > @current_index
439
+ ##$status_message.value = "1 #{@prow} > #{@current_index} "
440
+ #@prow -= 1
441
+ #else
442
+ #end
443
+ #end
444
+ $multiplier = 0
445
+ end
446
+
447
+ # scrolls window down mimicking vim C-e
448
+ # @param [int] multiplier entered prior to invoking key
449
+ def scroll_window_down num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
450
+ @prow += num
451
+ if @prow > @current_index
452
+ @current_index += 1
453
+ end
454
+ #check_prow
455
+ $multiplier = 0
456
+ end
457
+
458
+ # scrolls window up mimicking vim C-y
459
+ # @param [int] multiplier entered prior to invoking key
460
+ def scroll_window_up num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
461
+ @prow -= num
462
+ unless is_visible? @current_index
463
+ # one more check may be needed here TODO
464
+ @current_index -= num
465
+ end
466
+ $multiplier = 0
467
+ end
468
+
469
+ # scrolls lines a window full at a time, on pressing ENTER or C-d or pagedown
470
+ def scroll_forward
471
+ #@oldindex = @current_index
472
+ @current_index += @scrollatrows
473
+ @prow = @current_index - @scrollatrows
474
+ end
475
+
476
+ # scrolls lines backward a window full at a time, on pressing pageup
477
+ # C-u may not work since it is trapped by form earlier. Need to fix
478
+ def scroll_backward
479
+ #@oldindex = @current_index
480
+ @current_index -= @scrollatrows
481
+ @prow = @current_index - @scrollatrows
482
+ end
483
+ def goto_last_position
484
+ return unless @oldindex
485
+ tmp = @current_index
486
+ @current_index = @oldindex
487
+ @oldindex = tmp
488
+ bounds_check
489
+ end
490
+ def scroll_right
491
+ # I don't think it will ever be less since we've increased it to cols
492
+ if @content_cols <= @cols
493
+ maxpcol = 0
494
+ @pcol = 0
495
+ else
496
+ maxpcol = @content_cols - @cols - 1
497
+ @pcol += 1
498
+ @pcol = maxpcol if @pcol > maxpcol
499
+ end
500
+ # to prevent right from retaining earlier painted values
501
+ # padreader does not do a clear, yet works fine.
502
+ # OK it has an update_panel after padrefresh, that clears it seems.
503
+ #this clears entire window not just the pad
504
+ #FFI::NCurses.wclear(@window.get_window)
505
+ # so border and title is repainted after window clearing
506
+ #
507
+ # Next line was causing all sorts of problems when scrolling with ansi formatted text
508
+ #@repaint_all = true
509
+ end
510
+ def scroll_left
511
+ @pcol -= 1
512
+ end
513
+ #
514
+ #
515
+ #
516
+ # NOTE : if advancing pcol one has to clear the pad or something or else
517
+ # there'll be older content on the right side.
518
+ #
519
+ def handle_key ch
520
+ return :UNHANDLED unless @content
521
+ map_keys unless @mapped_keys
522
+
523
+ @maxrow = @content_rows - @rows
524
+ @maxcol = @content_cols - @cols
525
+
526
+ # need to understand the above line, can go below zero.
527
+ # all this seems to work fine in padreader.rb in util.
528
+ # somehow maxcol going to -33
529
+ @oldrow = @prow
530
+ @oldcol = @pcol
531
+ $log.debug "XXX: PAD got #{ch} prow = #{@prow}"
532
+ begin
533
+ case ch
534
+ when key(?l)
535
+ # TODO take multipler
536
+ #@pcol += 1
537
+ if @curpos < @cols
538
+ @curpos += 1
539
+ end
540
+ when key(?$)
541
+ #@pcol = @maxcol - 1
542
+ @curpos = [@content[@current_index].size, @cols].min
543
+ when key(?h)
544
+ # TODO take multipler
545
+ if @curpos > 0
546
+ @curpos -= 1
547
+ end
548
+ #when key(?0)
549
+ #@curpos = 0
550
+ when ?0.getbyte(0)..?9.getbyte(0)
551
+ if ch == ?0.getbyte(0) && $multiplier == 0
552
+ # copy of C-a - start of line
553
+ @repaint_required = true if @pcol > 0 # tried other things but did not work
554
+ @pcol = 0
555
+ @curpos = 0
556
+ return 0
557
+ end
558
+ # storing digits entered so we can multiply motion actions
559
+ $multiplier *= 10 ; $multiplier += (ch-48)
560
+ return 0
561
+ when ?\C-c.getbyte(0)
562
+ $multiplier = 0
563
+ return 0
564
+ else
565
+ # check for bindings, these cannot override above keys since placed at end
566
+ begin
567
+ ret = process_key ch, self
568
+ $multiplier = 0
569
+ bounds_check
570
+ ## If i press C-x > i get an alert from rwidgets which blacks the screen
571
+ # if i put a padrefresh here it becomes okay but only for one pad,
572
+ # i still need to do it for all pads.
573
+ rescue => err
574
+ $log.error " TEXTPAD ERROR INS #{err} "
575
+ $log.debug(err.backtrace.join("\n"))
576
+ textdialog ["Error in TextPad: #{err} ", *err.backtrace], :title => "Exception"
577
+ end
578
+ ## NOTE if textpad does not handle the event and it goes to form which pops
579
+ # up a messagebox, then padrefresh does not happen, since control does not
580
+ # come back here, so a black rect is left on screen
581
+ # please note that a bounds check will not happen for stuff that
582
+ # is triggered by form, so you'll have to to it yourself or
583
+ # call setrowcol explicity if the cursor is not updated
584
+ return :UNHANDLED if ret == :UNHANDLED
585
+ end
586
+ rescue => err
587
+ $log.error " TEXTPAD ERROR 111 #{err} "
588
+ $log.debug( err) if err
589
+ $log.debug(err.backtrace.join("\n")) if err
590
+ textdialog ["Error in TextPad: #{err} ", *err.backtrace], :title => "Exception"
591
+ $error_message.value = ""
592
+ ensure
593
+ padrefresh
594
+ Ncurses::Panel.update_panels
595
+ end
596
+ return 0
597
+ end # while loop
598
+ def fire_action_event
599
+ return if @content.nil? || @content.size == 0
600
+ require 'rbcurse/core/include/ractionevent'
601
+ aev = TextActionEvent.new self, :PRESS, current_value().to_s, @current_index, @curpos
602
+ fire_handler :PRESS, aev
603
+ end
604
+ def current_value
605
+ @content[@current_index]
606
+ end
607
+ #
608
+ def on_enter_row arow
609
+ return if @content.nil? || @content.size == 0
610
+ require 'rbcurse/core/include/ractionevent'
611
+ aev = TextActionEvent.new self, :ENTER_ROW, current_value().to_s, @current_index, @curpos
612
+ fire_handler :ENTER_ROW, aev
613
+ @repaint_required = true
614
+ end
615
+
616
+ # destroy the pad, this needs to be called from somewhere, like when the app
617
+ # closes or the current window closes , or else we could have a seg fault
618
+ # or some ugliness on the screen below this one (if nested).
619
+
620
+ # Now since we use get_pad from window, upon the window being destroyed,
621
+ # it will call this. Else it will destroy pad
622
+ def destroy
623
+ FFI::NCurses.delwin(@pad) if @pad # when do i do this ? FIXME
624
+ @pad = nil
625
+ end
626
+ def is_visible? index
627
+ j = index - @prow #@toprow
628
+ j >= 0 && j <= @scrollatrows
629
+ end
630
+ def on_enter
631
+ set_form_row
632
+ end
633
+ def set_form_row
634
+ setrowcol @lastrow, @lastcol
635
+ end
636
+ def set_form_col
637
+ end
638
+
639
+ private
640
+
641
+ # check that current_index and prow are within correct ranges
642
+ # sets row (and someday col too)
643
+ # sets repaint_required
644
+
645
+ def bounds_check
646
+ r,c = rowcol
647
+ @current_index = 0 if @current_index < 0
648
+ @current_index = @content.count()-1 if @current_index > @content.count()-1
649
+ ensure_visible
650
+
651
+ #$status_message.value = "visible #{@prow} , #{@current_index} "
652
+ #unless is_visible? @current_index
653
+ #if @prow > @current_index
654
+ ##$status_message.value = "1 #{@prow} > #{@current_index} "
655
+ #@prow -= 1
656
+ #else
657
+ #end
658
+ #end
659
+ #end
660
+ check_prow
661
+ #$log.debug "XXX: PAD BOUNDS ci:#{@current_index} , old #{@oldrow},pr #{@prow}, max #{@maxrow} pcol #{@pcol} maxcol #{@maxcol}"
662
+ @crow = @current_index + r - @prow
663
+ @crow = r if @crow < r
664
+ # 2 depends on whetehr suppressborders
665
+ @crow = @row + @height -2 if @crow >= r + @height -2
666
+ setrowcol @crow, @curpos+c
667
+ lastcurpos @crow, @curpos+c
668
+ if @oldindex != @current_index
669
+ on_enter_row @current_index
670
+ @oldindex = @current_index
671
+ end
672
+ if @oldrow != @prow || @oldcol != @pcol
673
+ # only if scrolling has happened.
674
+ @repaint_required = true
675
+ end
676
+ end
677
+ def lastcurpos r,c
678
+ @lastrow = r
679
+ @lastcol = c
680
+ end
681
+
682
+
683
+ # check that prow and pcol are within bounds
684
+
685
+ def check_prow
686
+ @prow = 0 if @prow < 0
687
+ @pcol = 0 if @pcol < 0
688
+
689
+ cc = @content.count
690
+
691
+ if cc < @rows
692
+ @prow = 0
693
+ else
694
+ maxrow = cc - @rows - 1
695
+ if @prow > maxrow
696
+ @prow = maxrow
697
+ end
698
+ end
699
+ # we still need to check the max that prow can go otherwise
700
+ # the pad shows earlier stuff.
701
+ #
702
+ return
703
+ # the following was causing bugs if content was smaller than pad
704
+ if @prow > @maxrow-1
705
+ @prow = @maxrow-1
706
+ end
707
+ if @pcol > @maxcol-1
708
+ @pcol = @maxcol-1
709
+ end
710
+ end
711
+ public
712
+ ##
713
+ # Ask user for string to search for
714
+ def ask_search
715
+ str = get_string("Enter pattern: ")
716
+ return if str.nil?
717
+ str = @last_regex if str == ""
718
+ return if str == ""
719
+ ix = next_match str
720
+ return unless ix
721
+ @last_regex = str
722
+
723
+ #@oldindex = @current_index
724
+ @current_index = ix[0]
725
+ @curpos = ix[1]
726
+ ensure_visible
727
+ end
728
+ ##
729
+ # Find next matching row for string accepted in ask_search
730
+ #
731
+ def find_more
732
+ return unless @last_regex
733
+ ix = next_match @last_regex
734
+ return unless ix
735
+ #@oldindex = @current_index
736
+ @current_index = ix[0]
737
+ @curpos = ix[1]
738
+ ensure_visible
739
+ end
740
+
741
+ ##
742
+ # Find the next row that contains given string
743
+ # @return row and col offset of match, or nil
744
+ # @param String to find
745
+ def next_match str
746
+ first = nil
747
+ ## content can be string or Chunkline, so we had to write <tt>index</tt> for this.
748
+ ## =~ does not give an error, but it does not work.
749
+ @content.each_with_index do |line, ix|
750
+ col = line.index str
751
+ if col
752
+ first ||= [ ix, col ]
753
+ if ix > @current_index
754
+ return [ix, col]
755
+ end
756
+ end
757
+ end
758
+ return first
759
+ end
760
+ ##
761
+ # Ensure current row is visible, if not make it first row
762
+ # NOTE - need to check if its at end and then reduce scroll at rows, check_prow does that
763
+ #
764
+ # @param current_index (default if not given)
765
+ #
766
+ def ensure_visible row = @current_index
767
+ unless is_visible? row
768
+ @prow = @current_index
769
+ end
770
+ end
771
+ def forward_word
772
+ $multiplier = 1 if !$multiplier || $multiplier == 0
773
+ line = @current_index
774
+ buff = @content[line].to_s
775
+ return unless buff
776
+ pos = @curpos || 0 # list does not have curpos
777
+ $multiplier.times {
778
+ found = buff.index(/[[:punct:][:space:]]\w/, pos)
779
+ if !found
780
+ # if not found, we've lost a counter
781
+ if line+1 < @content.length
782
+ line += 1
783
+ else
784
+ return
785
+ end
786
+ pos = 0
787
+ else
788
+ pos = found + 1
789
+ end
790
+ $log.debug " forward_word: pos #{pos} line #{line} buff: #{buff}"
791
+ }
792
+ $multiplier = 0
793
+ @current_index = line
794
+ @curpos = pos
795
+ ensure_visible
796
+ #@buffer = @list[@current_index].to_s
797
+ #set_form_row
798
+ #set_form_col pos
799
+ @repaint_required = true
800
+ end
801
+
802
+ end # class textpad
803
+
804
+ # a test renderer to see how things go
805
+ class DefaultFileRenderer
806
+ def render pad, lineno, text
807
+ bg = :black
808
+ fg = :white
809
+ att = NORMAL
810
+ #cp = $datacolor
811
+ cp = get_color($datacolor, fg, bg)
812
+ ## XXX believe it or not, the next line can give you "invalid byte sequence in UTF-8
813
+ # even when processing filename at times.
814
+ if text =~ /^\s*# / || text =~ /^\s*## /
815
+ fg = :red
816
+ #att = BOLD
817
+ cp = get_color($datacolor, fg, bg)
818
+ elsif text =~ /^\s*#/
819
+ fg = :blue
820
+ cp = get_color($datacolor, fg, bg)
821
+ elsif text =~ /^\s*(class|module) /
822
+ fg = :cyan
823
+ att = BOLD
824
+ cp = get_color($datacolor, fg, bg)
825
+ elsif text =~ /^\s*def / || text =~ /^\s*function /
826
+ fg = :yellow
827
+ att = BOLD
828
+ cp = get_color($datacolor, fg, bg)
829
+ elsif text =~ /^\s*(end|if |elsif|else|begin|rescue|ensure|include|extend|while|unless|case |when )/
830
+ fg = :magenta
831
+ att = BOLD
832
+ cp = get_color($datacolor, fg, bg)
833
+ elsif text =~ /^\s*=/
834
+ # rdoc case
835
+ fg = :blue
836
+ bg = :white
837
+ cp = get_color($datacolor, fg, bg)
838
+ att = REVERSE
839
+ end
840
+ FFI::NCurses.wattron(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
841
+ FFI::NCurses.mvwaddstr(pad, lineno, 0, text)
842
+ FFI::NCurses.wattroff(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
843
+
844
+ end
845
+ end
846
+ end