Class: RubyCurses::TabbedPane

Inherits:
Widget
  • Object
show all
Defined in:
lib/rbcurse/rtabbedpane.rb

Overview

extending Widget from 2009-10-08 18:45 It should extend Widget so we can pop it in a form. In fact it should be in a form,

we should not have tried to make it standalone like messagebox.
This is the main TabbedPane widget that will be slung into a form

Defined Under Namespace

Classes: Tab

Constant Summary collapse

TAB_ROW_OFFSET =

what row should tab start on (was 4 when printing subheader)

3
TAB_COL_OFFSET =

what col should tab start on (to save space, flush on left)

0

Constants included from Io

Io::ERROR_COLOR_PAIR, Io::FOOTER_COLOR_PAIR, Io::LINEONE, Io::MAIN_WINDOW_COLOR_PAIR

Instance Attribute Summary collapse

Attributes inherited from Widget

#col_offset, #cols_panned, #config, #curpos, #ext_col_offset, #ext_row_offset, #id, #parent_component, #row_offset, #rows_panned, #should_create_buffer, #state

Instance Method Summary collapse

Methods inherited from Widget

#OLDbind_key, #buffer_to_screen, #buffer_to_window, #create_buffer, #destroy_buffer, #focus, #get_buffer, #get_color, #get_preferred_size, #getvalue, #getvalue_for_paint, #height, #height=, #hide, #init_vars, #is_double_buffered?, #modified?, #move, #on_enter, #on_leave, #override_graphic, #printstring, #process_key, #remove, #repaint_all, #repaint_required, #rowcol, #safe_create_buffer, #set_buffer_modified, #set_buffering, #set_form, #set_form_col, #set_form_row, #set_modified, #setformrowcol, #setrowcol, #text_variable, #unbind_key, #width, #width=

Methods included from Io

#askchoice, #askyesno, #askyesnocancel, #clear_error, #clear_this, #get_string, #newaskyesno, #old_print_header, #old_print_top_right, #print_action, #print_error, #print_footer_help, #print_header, #print_headers, #print_help, #print_help_page, #print_in_middle, #print_key_labels, #print_key_labels_row, #print_screen_labels, #print_status, #print_this, #print_top_right, #rbgetstr

Methods included from Utils

#_process_key, #bind_key, #clean_string!, #get_color, #keycode_tos, #repeatm, #wrap_text

Methods included from ConfigSetup

#cget, #config_setup

Methods included from EventHandler

#bind, #fire_handler, #fire_property_change

Methods included from DSL

#OLD_method_missing

Constructor Details

#initialize(form, aconfig = {}, &block) ⇒ TabbedPane

Returns a new instance of TabbedPane.



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/rbcurse/rtabbedpane.rb', line 148

def initialize form, aconfig={}, &block
  super
  @parent = form
  @parentwin = form.window
  @visible = true
  @focusable= true
  @tabs ||= []
  @forms ||= []
  @attr = nil
  @current_form = nil
  @current_tab = nil
  @config = aconfig
  @col_offset = 2;  @row_offset = 1 # added 2010-01-10 22:54 
  @recreate_buttons = true
  install_keys
end

Instance Attribute Details

#current_tabObject (readonly)

Returns the value of attribute current_tab.



146
147
148
# File 'lib/rbcurse/rtabbedpane.rb', line 146

def current_tab
  @current_tab
end

#selected_indexObject (readonly)

Returns the value of attribute selected_index.



145
146
147
# File 'lib/rbcurse/rtabbedpane.rb', line 145

def selected_index
  @selected_index
end

#windowObject (readonly)

Returns the value of attribute window.



147
148
149
# File 'lib/rbcurse/rtabbedpane.rb', line 147

def window
  @window
end

Instance Method Details

#_recreate_buttonsObject

recreate all buttons We call this if even one is added : adv is we can space out accordinagly if the numbers increase We could also expand the pad here. Test it out with removing tabs to. XXX have to remove buttons from the form



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/rbcurse/rtabbedpane.rb', line 350

def _recreate_buttons
  $log.debug " inside recreate_buttons: #{@tabs.size} "
  r = @row
  col = @col + 1
  @buttons ||= []
  if !@buttons.empty?
    @buttons.each {|e| @form.remove_widget(e) }
  end
  @buttons = []
  button_gap = 4
  # the next line necessitates a clear on the pad
  #  button_gap = 1 if @tabs.size > 6 # quick dirty fix, we need something that checks fit
  # we may also need to truncate text to fit

  @buttonpad.wclear
  ## create a button for each tab
  $tabradio = Variable.new # so we know which is highlighted
  @tabs.each do |tab|
    text = tab.text
    $log.debug " TABS EACH #{text} "
    @buttons << RubyCurses::TabbedButton.new(@form) do
      variable $tabradio
      text text
      name text
      value text
      row r + 1
      col col
    end
    col += text.length + button_gap
    # if col exceeds pad_w then we need to expand pad
    # but here we don't know that a pad is being used
    $log.debug " button col #{col} " 
    form = tab.form
    form.set_parent_buffer(@window) if form

    b = @buttons.last
    b.command(b) { 
      $log.debug " calling display form from button press #{b.name} #{b.state} "
      # form.rep essentially sees that buttons get correct attributes
      # when triggering M-<char>. This button should get highlighted.
      tab.repaint
      button_form_repaint #( b.state == :HIGHLIGHTED )
      if @display_tab_on_traversal
        # set as old tab so ONLY on going down this becomes current_tab
        @old_tab = tab
      else
        # next line means next key is IMMED  taken by the tab not main form
        @current_tab = tab
      end
      fire_event tab, tab.index, :OPEN
    }
  end
  @recreate_buttons = false
  # make the buttons visible now, not after next handle_key
  @form.repaint
end

#add_tab(text, component = nil, aconfig = {}, &block) ⇒ Object Also known as: add

This is a public, user called method for appending a new tab This will be called several times for one TP. when adding tabs, you may use ampersand in text to create hotkey XXX adding a tab later does not influence buttons array,



177
178
179
180
181
# File 'lib/rbcurse/rtabbedpane.rb', line 177

def add_tab text, component = nil, aconfig={}, &block
  index = @tabs.size
  tab = insert_tab text, component, index, aconfig, &block
  return tab
end

#button_form_repaint(flag = true) ⇒ Object



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
# File 'lib/rbcurse/rtabbedpane.rb', line 444

def button_form_repaint flag = true
  $log.debug " INSIDE button_form_repaint #{flag} "
  #if flag
    #@form.repaint if flag  #  This paints the outer form not inner
    #@buttonpad.mvwaddch(2, 0, Ncurses::ACS_LTEE) # beautify the corner 2010-02-06 19:35 
    #@buttonpad.mvwaddch(2, @width-1, Ncurses::ACS_RTEE)
  #end
  #ret = @buttonpad.prefresh(0,0, @row+0, @col+0, @row+@height, @col+@width)
  #$log.debug " prefresh error buttonpad 2 " if ret < 0
  #@buttonpad.modified = false
  if flag
    # repaint form and refresh pad
    @form.repaint
  else
    # only refresh pad
    @form.prefresh
  end
end

#center_column(textlen) ⇒ Object



612
613
614
615
# File 'lib/rbcurse/rtabbedpane.rb', line 612

def center_column textlen
  width = @layout[:width]
  return (width-textlen)/2
end

#change_labelObject

prompts for a new label for a tab - taking care of mnemonics if ampersand present Currently, mapped to ‘C’ and ‘cw’ when cursor is on a label Perhaps some of this kind of utility stuff needs to go into a util class.



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/rbcurse/rtabbedpane.rb', line 235

def change_label
  ix = highlighted_tab_index
  return if ix < 0
  prompt = "Enter new label: "
  label = @buttons[ix].text
  config = {}
  config[:default] = label.dup
  maxlen = 10
  ret, str = rbgetstr(@graphic, $error_message_row, $error_message_col, prompt, maxlen, config)
  if ret == 0 and str != "" and str != label
    @tabs[ix].text = str
    @buttons[ix].text(str)
    @recreate_buttons = true
  end
end

#configure(*val, &block) ⇒ Object

private



320
321
322
323
324
325
326
327
328
329
# File 'lib/rbcurse/rtabbedpane.rb', line 320

def configure(*val , &block)
  case val.size
  when 1
    return @config[val[0]]
  when 2
    @config[val[0]] = val[1]
    variable_set(val[0], val[1]) 
  end
  instance_eval &block if block_given?
end

#configure_component(component) ⇒ Object

return a form for use by program - if you want to put multiple items Otherwise just use add_component private - can’t use externally



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/rbcurse/rtabbedpane.rb', line 278

def configure_component component
    #component.set_form @parent <<--- definitely NOT
    component.form = @parent
    component.rows_panned = component.cols_panned = 0
    component.parent_component = self # added 2010-02-27  so offsets can go down ?
    component.should_create_buffer = true 
    component.row = @row + TAB_ROW_OFFSET # 2
    component.col = @col + TAB_COL_OFFSET

    # current_form likely to be nil XXX
    scr_top = component.row # for Pad, if Pad passed as in SplitPane
    scr_left = component.col # for Pad, if Pad passed as in SplitPane
    ho = TAB_ROW_OFFSET + 2 # 5
    component.set_buffering(:target_window => @target_window || @parentwin, :form => @current_form, :bottom => @height-ho, :right => @width-2, :screen_top => scr_top, :screen_left => scr_left)
    # if left nil, then we expand the comp
    component.height ||= @height - (ho - 1) # 1 keeps lower border inside by 1
    component.width ||= @width - 0 # 0 keeps it flush on right border


end

#create_buttonsObject



568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
# File 'lib/rbcurse/rtabbedpane.rb', line 568

def create_buttons
  case @button_type.to_s.downcase
  when "ok"
    make_buttons ["&OK"]
  when "ok_cancel" #, "input", "list", "field_list"
    make_buttons %w[&OK &Cancel]
  when "yes_no"
    make_buttons %w[&Yes &No]
  when "yes_no_cancel"
    make_buttons ["&Yes", "&No", "&Cancel"]
  when "custom"
    raise "Blank list of buttons passed to custom" if @buttons.nil? or @buttons.size == 0
    make_buttons @buttons
  else
    $log.debug "No buttontype passed for creating tabbedpane. Using default (OK)"
    make_buttons ["&OK"]
  end
end

#create_tab_form(tab) ⇒ Object

This creates a form for the tab, in case we wish to put many components in it. Else just pass single components in add_tab.

Returns:

  • form - a pad based form



467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
# File 'lib/rbcurse/rtabbedpane.rb', line 467

def create_tab_form tab
    mtop = 0
    mleft = 0
    bottom_offset = 2 # 0 will overwrite bottom line, 1 will make another line for inner form
  layout = { :height => @height-(mtop+bottom_offset), :width => @width, :top => mtop, :left => mleft } 
  # create a pad but it must behave like a window at all times 2009-10-25 12:25 
  window = VER::Pad.create_with_layout(layout)

  form = RubyCurses::Form.new window
  $log.debug " pad created in TP create_tab_form: #{window.name} , form #{form.name}  "
  $log.debug " hwtl: #{layout[:height]} #{layout[:width]} #{layout[:top]} #{layout[:left]} "
  ## added 2010-01-21 15:46 to pass cursor up
  form.parent_form = @parent
  form.navigation_policy = :NON_CYCLICAL
  window.bkgd(Ncurses.COLOR_PAIR($datacolor));
  window.box( 0, 0);
  window.mvwaddch(0, 0, Ncurses::ACS_LTEE) # beautify the corner 2010-02-06 19:35 
  window.mvwaddch(0, @width-1, Ncurses::ACS_RTEE)

  # XXX TODO this wastes space we should ditch it.
  ## this prints the tab name on top left
  window.mvprintw(1,1, tab.text.tr('&', '')) if @print_subheader
  window.name = "Tab::TAB-#{tab.text}" # 2010-02-02 19:59 
  form.name = "Form::TAB-#{tab.text}" # 2010-02-02 19:59 
  form.add_cols=@col
  form.add_rows=@row
  return form
end

#create_windowObject

This form is for the tabbed buttons on top



407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/rbcurse/rtabbedpane.rb', line 407

def create_window
  set_buffer_modified() # required still ??
  # first create the main top window with the tab buttons on it.
  $log.debug " TPane create_buff Top #{@row}, Left #{@col} H #{@height} W #{@width} "
  #$log.debug " parentwin #{@parentwin.left} #{@parentwin.top} "

  r = @row
  c = @col
  @form = ScrollForm.new(@parentwin)
  @form.set_layout(1, @width, @row+1, @col+1)
  @form.display_h = 1
  @form.display_w = @width-3
  @buttonpad = @form.create_pad


  ## We will use the parent window, and not a pad. We will write absolute coordinates.
  @window = @parentwin
  color = $datacolor
  # border around button bar. should this not be in scrollform as a border ? XXX
  @window.print_border @row, @col, 2, @width, color #, Ncurses::A_REVERSE
  @buttonpad.name = "Window::TPTOPPAD" # 2010-02-02 20:01 
  @form.name = "Form::TPTOPFORM"
  $log.debug("TP WINDOW TOP ? PAD MAIN FORM W:#{@window.name},  F:#{@form.name} ")
  @form.parent_form = @parent ## 2010-01-21 15:55 TRYING OUT BUFFERED
  @form.navigation_policy = :NON_CYCLICAL
  #xx @current_form = @form
 #xx     color = $datacolor
 #xx     @window.print_border @row, @col, @height-1, @width, color #, Ncurses::A_REVERSE
  
  Ncurses::Panel.update_panels
  _recreate_buttons
 
  button_form_repaint true
  @window.wrefresh ## ADDED  2009-11-02 23:29 
  @old_tab = @tabs.first
  @buttons.first().fire unless @buttons.empty? # make the first form active to start with.
end

#destroyObject

ensure that the pads are being destroyed, although we’ve not found a way.



564
565
566
567
# File 'lib/rbcurse/rtabbedpane.rb', line 564

def destroy
  @window.destroy
  @forms.each { |f| w = f.window; w.destroy unless w.nil? }
end

#fire_event(tab, index, event) ⇒ Object



616
617
618
619
620
621
622
623
624
625
# File 'lib/rbcurse/rtabbedpane.rb', line 616

def fire_event tab, index, event
  # experimenting with structs, earlier we've used classes
  if @tabbedpane_event.nil?
    @tabbedpane_event = Event.new
  end
  @tabbedpane_event.tab = tab
  @tabbedpane_event.index = index
  @tabbedpane_event.event = event
  fire_handler event, @tabbedpane_event
end

#form(tab) ⇒ Object

create a form for tab, if multiple components are to be placed inside tab.

Tabbedpane has no control over placement and width etc of what's inside a form


300
301
302
303
304
305
306
# File 'lib/rbcurse/rtabbedpane.rb', line 300

def form tab
  if tab.form.nil?
    @forms << create_tab_form(tab)
    tab.form = @forms.last
  end
  return tab.form
end

#handle_key(ch) ⇒ Object

added 2009-10-08 19:39 so it can be placed in a form XXX stop this nonsense about current_form and current_tab TP should only be concerned with tabs. what happens inside is none of its business



500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
# File 'lib/rbcurse/rtabbedpane.rb', line 500

def handle_key(ch)
  @current_tab ||= @form # first we cycle buttons
      $log.debug " handle_key in tabbed pane got : #{ch}"
      # needs to go to component
      ret = @current_tab.handle_key(ch)
      $log.debug " -- form.handle_key in tabbed pane got ret : #{ret} , #{@current_tab} , #{ch} "

      # components will usually return UNHANDLED for a tab or btab
      # We need to convert it so the main form can use it
      if @current_tab != @form
      if ret == :UNHANDLED
        if ch == 9 #or ch == KEY_DOWN
          ret = :NO_NEXT_FIELD
        elsif ch == 353 #or ch == KEY_UP # btab
          ret = :NO_PREV_FIELD
        end
      end
      else
        # key down pressed in top form, go to tab
        if ch == KEY_DOWN
          ret = :NO_NEXT_FIELD
        end
      end

    case ret
    when :NO_NEXT_FIELD
      if @current_tab != @form
        ## if no next field on a subform go to first button of main form
        @old_tab = @current_tab
        @current_tab = @form
        @form.req_first_field
        #@form.select_next_field
      else
        # on top button panel - no more buttons, go to tabs first field
        if @old_tab # in case of empty tabbed pane old_tab was nil
          @current_tab = @old_tab
          @current_tab.set_focus :FIRST
        end
      end
    when :NO_PREV_FIELD
      if @current_tab != @form
        $log.debug "TP 1 no prev field - going to last button "
        @old_tab = @current_tab
        @current_tab = @form
        @form.req_last_field
      else
        # on top button panel - no prev buttons, go to tabs last field
        if @old_tab # in case of one tab
          @current_tab = @old_tab
          @current_tab.set_focus :LAST
        end
      end
    when :UNHANDLED
      $log.debug " unhandled in tabbed pane #{ch}"
      ret = @form.process_key ch, self # field

      return ret if ret == :UNHANDLED
    end
    if @buttonpad.modified
      button_form_repaint
    end
end

#highlighted_tab_index0..

returns the index of the tab cursor is on (not the one that is selected)

Returns:

  • (0..)

    index, or -1 if some error



253
254
255
256
257
258
# File 'lib/rbcurse/rtabbedpane.rb', line 253

def highlighted_tab_index
  @form.widgets.each_with_index{ |w, ix| 
    return ix if w.state == :HIGHLIGHTED
  }
  return -1
end

#insert_tab(text, component, index, aconfig = {}, &block) ⇒ Object

insert a component at given index index cannnot be greater than size of tab count



185
186
187
188
189
190
191
192
193
194
# File 'lib/rbcurse/rtabbedpane.rb', line 185

def insert_tab text, component, index, aconfig={}, &block
  $log.debug " TAB insert #{text} at #{index} "
  @tabs[index] = Tab.new(text, self, aconfig, &block)
  tab = @tabs[index]
  tab.component = component unless component.nil?
  tab.index = index # so i can undelete !!!
  fire_event tab, index, :INSERT
  @recreate_buttons = true
  return tab
end

#install_keysObject



164
165
166
167
168
169
170
171
# File 'lib/rbcurse/rtabbedpane.rb', line 164

def install_keys
  @form.bind_key([?d, ?d]) { ix = highlighted_tab_index; repeatm { remove_tab(ix) } }
  @form.bind_key(?u) { undelete_tab; }
  @form.bind_key(?p) { paste_tab 0; } # paste before or at position
  @form.bind_key(?P) { paste_tab 1; } # paste deleted tab after this one
  @form.bind_key([?c, ?w]) { change_label }
  @form.bind_key(?C) { change_label }
end

#make_buttons(names) ⇒ Object



586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
# File 'lib/rbcurse/rtabbedpane.rb', line 586

def make_buttons names
  total = names.inject(0) {|total, item| total + item.length + 4}
  bcol = center_column total

  brow = @layout[:height]-2
  button_ct=0
  names.each_with_index do |bname, ix|
    text = bname
    #underline = @underlines[ix] if [email protected]?

    button = Button.new @form do
      text text
      name bname
      row brow
      col bcol
      #underline underline
      highlight_background $reversecolor 
      color $datacolor
      bgcolor $datacolor
    end
    index = button_ct
    button.command { |form| @selected_index = index; @stop = true; $log.debug "Pressed Button #{bname}";}
    button_ct += 1
    bcol += text.length+6
  end
end

#paste_tab(pos) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
# File 'lib/rbcurse/rtabbedpane.rb', line 218

def paste_tab pos
  return unless @deleted_tab
  ix = highlighted_tab_index
  return if ix == -1
  @recreate_buttons = true
  @deleted_tab.index = ix + pos
  @tabs.insert(@deleted_tab.index, @deleted_tab)
  fire_event @deleted_tab, @deleted_tab.index, :INSERT
  @deleted_tab = nil
  $log.debug " paste over #{@tabs.size} #{ix} + #{pos} "
end

#remove_allObject

remove all tabs



266
267
268
269
270
271
272
273
# File 'lib/rbcurse/rtabbedpane.rb', line 266

def remove_all
  if !@buttons.empty?
    @buttons.each {|e| @form.remove_widget(e) }
  end
  @buttons = []
  @tabs = []
  @recreate_buttons = true
end

#remove_tab(index) ⇒ Object

remove given tab based on index This does not unbind the key mapping, FIXME Currently, can be invoked by ‘dd’ over highlighted button XXX can append to deleted_tabs, then on insert or paste insert with splat.



199
200
201
202
203
204
205
# File 'lib/rbcurse/rtabbedpane.rb', line 199

def remove_tab index
  @recreate_buttons = true
  $log.debug " inside remove_tab with #{index}, #{@tabs.size} "
  @deleted_tab = @tabs.delete_at(index) unless @tabs.size < index
  # note this is the index it was at. 
  fire_event @deleted_tab, index, :DELETE
end

#repaintObject

this is a really wierd repaint method. First time it creates the TP window/form which contains the buttons. In future calls it really doesn’t do anything. Deos it have nothing to paint, no borders to redraw, no repaint_required ???



334
335
336
337
338
339
340
341
# File 'lib/rbcurse/rtabbedpane.rb', line 334

def repaint
  $log.debug " tabbedpane repaint "
  @window || create_window
  _recreate_buttons if @recreate_buttons
  $log.debug " tabbedpane repaint #{@window.name} "
  @window.show
  #x set_buffer_modified()
end

#selected_tab_indexObject

returns the index of the current / selected tab



310
311
312
313
314
315
# File 'lib/rbcurse/rtabbedpane.rb', line 310

def selected_tab_index
  @form.widgets.each_with_index{ |w, ix| 
    return ix if w.selected?
  }
  return -1
end

#showObject

x set_buffer_modified()



342
343
344
# File 'lib/rbcurse/rtabbedpane.rb', line 342

def show
  repaint
end

#undelete_tabObject

Move this fun stuff to a util class. TODO If tab deleted accidentally, undelete it Okay, i just can stop myself from having a little fun



210
211
212
213
214
215
216
217
# File 'lib/rbcurse/rtabbedpane.rb', line 210

def undelete_tab
  return unless @deleted_tab
  @recreate_buttons = true
  @tabs.insert(@deleted_tab.index, @deleted_tab)
  fire_event @deleted_tab, @deleted_tab.index, :INSERT
  @deleted_tab = nil
  $log.debug " undelete over #{@tabs.size} "
end

#variable_set(var, val) ⇒ Object

private



315
316
317
318
# File 'lib/rbcurse/rtabbedpane.rb', line 315

def variable_set var, val
    var = "@#{var}"
    instance_variable_set(var, val) 
end