require 'rbcurse/app' #include Ncurses # FFI 2011-09-8 include RubyCurses # TODO : We can consider making it independent of objects, or allow for a margin so it does not write # over the object. Then it will be always visible. # TODO: if lists and tables, can without borders actually adjust then putting this independent # would make even more sense, since it won't eat an extra line. # # @example # lb = list_box .... # rb = Divider.new @form, :parent => lb, :side => :right # # At a later stage, we will integrate this with lists and tables, so it will happen automatically. # # @since 1.2.0 module RubyCurses class DragEvent < Struct.new(:source, :type); end # This is a horizontal or vertical bar (like a scrollbar), at present attached to a # widget that is focusable, and allows user to press arrow keys. # It highlights on focus, the caller can expand and contract components in a container # or even screen, based on arrow movements. This allows for a visual resizing of components. # @example # lb = list_box .... # rb = Divider.new @form, :parent => lb, :side => :right # # NOTE: since this can be deactivated, containers need to check focusable before passing # focus in # 2010-10-07 23:56 made focusable false by default. Add divider to # FocusManager when creating, so F3 can be used to set focusable # See rvimsplit.rb for example class Divider < Widget # row to start, same as listbox, required. dsl_property :row # column to start, same as listbox, required. dsl_property :col # how many rows is this (should be same as listboxes height, required. dsl_property :length # vertical or horizontal currently only VERTICAL dsl_property :side # initialize based on parent's values dsl_property :parent # which row is focussed, current_index of listbox, required. # how many total rows of data does the list have, same as @list.length, required. dsl_accessor :next_component # 'next' bombing in dsl_accessor 2011-10-2 PLS CHANGE ELSEWHERE # TODO: if parent passed, we shold bind to ON_ENTER and get current_index, so no extra work is required. def initialize form, config={}, &block # setting default first or else Widget will place its BW default #@color, @bgcolor = ColorMap.get_colors_for_pair $bottomcolor super @height = 1 @color_pair = get_color $datacolor, @color, @bgcolor @scroll_pair = get_color $bottomcolor, :green, :white #@window = form.window @editable = false # you can set to true upon creation, or use F3 on vimsplit to # toggle focusable @focusable = false @repaint_required = true @_events.push(:DRAG_EVENT) map_keys unless @parent raise ArgumentError, "row col and length should be provided" if !@row || !@col || !@length end #if @parent #@parent.bind :ENTER_ROW do |p| ## parent must implement row_count, and have a @current_index #raise StandardError, "Parent must implement row_count" unless p.respond_to? :row_count #self.current_index = p.current_index #@repaint_required = true #requred otherwise at end when same value sent, prop handler ## will not be fired (due to optimization). #end #end end def map_keys if !defined? $deactivate_dividers $deactivate_dividers = false end # deactivate only this bar bind_key(?f) {@focusable=false; } # deactivate all bars, i've had nuff! bind_key(?F) {deactivate_all(true)} end ## # repaint the scrollbar # Taking the data from parent as late as possible in case parent resized, or # moved around by a container. # NOTE: sometimes if this is inside another object, the divider repaints but then # is wiped out when that objects print_border is called. So such an obkect (e.g. # vimsplit) should call repaint after its has done its own repaint. that does mean # the repaint happens twice during movement def repaint woffset = 2 coffset = 1 if @parent woffset = 0 if @parent.suppress_borders @border_attrib ||= @parent.border_attrib case @side when :right @row = @parent.row+1 @col = @parent.col + @parent.width - 0 @length = @parent.height - woffset when :left @row = @parent.row+1 @col = @parent.col+0 #+ @parent.width - 1 @length = @parent.height - woffset when :top @row = @parent.row+0 @col = @parent.col + @parent.col_offset #+ @parent.width - 1 @length = @parent.width - woffset when :bottom @row = @parent.row+@parent.height-0 #1 @col = @parent.col+@parent.col_offset #+ @parent.width - 1 @length = @parent.width - woffset end else # row, col and length should be passed end my_win = @form ? @form.window : @target_window @graphic = my_win unless @graphic raise "graphic is nil in divider, perhaps form was nil when creating" unless @graphic return unless @repaint_required # first print a right side vertical line #bc = $bottomcolor # dark blue bc = $datacolor bordercolor = @border_color || bc borderatt = @border_attrib || Ncurses::A_REVERSE if @focussed bordercolor = $promptcolor || bordercolor end borderatt = convert_attrib_to_sym(borderatt) if borderatt.is_a? Symbol @graphic.attron(Ncurses.COLOR_PAIR(bordercolor) | borderatt) $log.debug " XXX DIVIDER #{@row} #{@col} #{@length} " case @side when :right, :left @graphic.mvvline(@row, @col, 1, @length) when :top, :bottom @graphic.mvhline(@row, @col, 1, @length) end @graphic.attroff(Ncurses.COLOR_PAIR(bordercolor) | borderatt) _paint_marker #alert "divider repaint at #{row} #{col} " @repaint_required = false end def convert_attrib_to_sym attr case attr when 'reverse' Ncurses::A_REVERSE when 'bold' Ncurses::A_BOLD when 'normal' Ncurses::A_NORMAL when 'blink' Ncurses::A_BLINK when 'underline' Ncurses::A_UNDERLINE else Ncurses::A_REVERSE end end # deactivate all dividers # The application has to provide a key or button to activate all # or just this one. def deactivate_all tf=true $deactivate_dividers = tf @focusable = !tf end def handle_key ch # all dividers have been deactivated if $deactivate_dividers || !@focusable @focusable = false return :UNHANDLED end case @side when :right, :left case ch when KEY_RIGHT fire_handler :DRAG_EVENT, DragEvent.new(self, ch) when KEY_LEFT fire_handler :DRAG_EVENT, DragEvent.new(self, ch) else ret = process_key ch, self return ret if ret == :UNHANDLED end set_form_col when :top, :bottom case ch when KEY_UP fire_handler :DRAG_EVENT, DragEvent.new(self, ch) when KEY_DOWN fire_handler :DRAG_EVENT, DragEvent.new(self, ch) else ret = process_key ch, self return ret if ret == :UNHANDLED end set_form_col else end @repaint_required = true return 0 end def on_enter if $deactivate_dividers || !@focusable @focusable = false return :UNHANDLED end # since it is over border of component, we need to repaint @focussed = true @repaint_required = true repaint end def on_leave @focussed = false @repaint_required = true repaint # TODO: we should review this since its not over the parent any longer if @parent # since it is over border of component, we need to clear @parent.repaint_required # if we don't paint now, parent paints over other possible dividers @parent.repaint end end def set_form_row return unless @focusable r,c = rowcol setrowcol r, c end # set the cursor on first point of bar def set_form_col return unless @focusable # need to set it to first point, otherwise it could be off the widget r,c = rowcol setrowcol r, c end # is this a vertical divider def v? @side == :top || @side == :bottom end # is this a horizontal divider def h? @side == :right || @side == :left end private def _paint_marker #:nodoc: r,c = rowcol if @focussed @graphic.mvwaddch r,c, Ncurses::ACS_DIAMOND if v? @graphic.mvwaddch r,c+1, Ncurses::ACS_UARROW @graphic.mvwaddch r,c+2, Ncurses::ACS_DARROW else @graphic.mvwaddch r+1,c, Ncurses::ACS_LARROW @graphic.mvwaddch r+2,c, Ncurses::ACS_RARROW end else #@graphic.mvwaddch r,c, Ncurses::ACS_CKBOARD end end ## ## # ADD HERE end # class end # module if __FILE__ == $PROGRAM_NAME App.new do r = 5 len = 20 list = [] 0.upto(100) { |v| list << "#{v} scrollable data" } lb = list_box "A list", :list => list, :row => 2, :col => 2 #sb = Scrollbar.new @form, :row => r, :col => 20, :length => len, :list_length => 50, :current_index => 0 rb = Divider.new @form, :parent => lb, :side => :right rb.bind :DRAG_EVENT do |e| message "got an event #{e.type} " case e.type when KEY_RIGHT lb.width += 1 when KEY_LEFT lb.width -= 1 end lb.repaint_required end rb1 = Divider.new @form, :parent => lb, :side => :bottom rb.focusable(true) rb1.focusable(true) rb1.bind :DRAG_EVENT do |e| message " 2 got an event #{e.type} " end #hline :width => 20, :row => len+r #keypress do |ch| #case ch #when :down #sb.current_index += 1 #when :up #sb.current_index -= 1 #end #end end end