class SheetInterface
TODO: provide conversion for column designators to int vv, also for AA to ZZ !
Public Class Methods
# File lib/sheetinterface.rb, line 61 def initialize(wb, num) init_logger(STDOUT, Logger::INFO) @workbook = wb.sheet(num) @menu = nil @status = Status.new construct end
Private Instance Methods
# File lib/sheetinterface.rb, line 154 def ask_cell_coord() coord = nil until coord print 'Which cell (col:row)? ' begin coord = STDIN.gets().chomp! rescue Interrupt return nil end if('' == coord || !coord.include?(':') || "\e" == coord) return nil else col, row = coord.split(':') row = row.to_i col = col.strip end return [col, row] end end
Asks the user to enter a number, then verifies ageinst eventual constraints.
# File lib/sheetinterface.rb, line 80 def ask_number(question, &constraint) num = nil 3.times do print question.dup << " " begin num = STDIN.gets() rescue Interrupt return nil end num = num.to_i if (constraint && !yield(num)) num = nil end return num if num end if !num @status.message = "Inacceptable value, doing nothing." return nil end end
Shortcutting from “user inputs bull” to “program interrupts” without the need for the program to actually run havoc, first.
# File lib/sheetinterface.rb, line 103 def ask_string(question, &constraint) str = nil until str print question.dup << " " begin str = STDIN.gets().chomp! rescue Interrupt return nil end if("" == str || "\e" == str) return nil end if(constraint && !yield(str)) str = nil end end if !str @status.message = "Inacceptable value, doing nothing." end return str end
The function which is called to recreate the view. It prepares the table-generation by transforming the Roo-instance to objects of the classes Cell
, Row
and Column
, then calls draw(), above and adds the status- and menu-lines. The menu waits for user input (blocking) before construct() returns, no other thread or “event-loop” involved there.
# File lib/sheetinterface.rb, line 406 def construct(on_screen = true) puts "... reading spreadsheet data (%s), PSE wait" %[@workbook.default_sheet] bi = BusyIndicator.new() if(!@rows || !@columns) @rows = Array.new if !@rows @columns = Array.new if !@columns # create for each column num_columns = @workbook.last_column if(num_columns && num_columns > 0) num_columns.times do |column| # ... a column, then @columns[column] = Column.new(column) if !@columns[column] cur_col = @columns[column] # ... create for each row in each column (@workbook.last_row).times do |row| # ... a row then @rows[row] = Row.new(row) if !@rows[row] cur_row = @rows[row] # ... create for each cell in each row cur_cell = cur_row.cells.detect {|cell| column == cell.col } if !cur_cell # ... a cell cell = @workbook.cell(row + 1, column + 1) cur_cell = Cell.new(cur_row, cur_col, cell.to_s) cur_row << cur_cell cur_col << cur_cell end end end end end bi.stop("\n") if(on_screen) @table_view = draw() if @table_view @table_view.extend(Scrollable) @table_view.fixed_rows = 4 @table_view.fixed_cols = 4 refresh() end end end
Adds concrete actions to the menu. Note the calls to ask_number
and ask_string
with constraints.
# File lib/sheetinterface.rb, line 209 def define_actions(menu) sm_Menu = Menu.new(:name => 'sheet', :key => 's') sm_Menu.add_action(Action.new(:name => 'sheet number', :key => '#') {open_sheet(:number)}) sm_Menu.add_action(Action.new(:name => 'sheet name', :key => 'n') {open_sheet(:name)}) save_Menu = Menu.new(:name => 'save to file', :key => 'f') save_Menu.add_action(Action.new(:name => 'current sheet', :key => 'c') do file = new_file() if(file) File::open(file, 'w+') {|out| draw(out) } @status.message = green('... done') end construct end) save_Menu.add_action(Action.new(:name => 'all sheets', :key => 'a') do current_sheet = @workbook.default_sheet @status.message = "\nATTN! If saving takes a long time, your spreadsheet may contain useless data or many empty cells. Check the visible width and height of your tables.\n" file = new_file() if(file) bi = BusyIndicator.new(true) begin File::open(file, 'w+') do |out| @workbook.sheets.each do |s| @log.debug('writing sheet ' << s) set_sheet(s) construct(false) draw(out) end end rescue Interrupt puts bold(red("Interrupted by user. Bye!")) exit true end bi.stop('... done') end set_sheet(current_sheet) construct end) col_Menu = Menu.new(:name=>'column', :key => 'c') col_Menu.add_action(Action.new(:name => 'column width', :key => 'w') do |num| num = ask_number('column width (min. 4) ?'){|w| w > 3} Column::col_width = num if num construct end) menu.add_action(Action.new(:name => 'value', :key => 'v') do cell_coord = ask_cell_coord if(cell_coord && cell_coord.length == 2) row = cell_coord[1] - 1 col = cell_coord[0].upcase.bytes[0] - 65 cell = nil if(@rows[row] && @columns[col]) cell = @rows[row].cells.detect {|c| c.col == col} if(cell) @status.message = "#{cell_coord[0].upcase}:#{cell_coord[1]} is \"" << cell.value.to_s << "\" (%d)" %cell.value.to_s.length else @status.message = red('invalid cell') end else @status.message = red(cell_coord.join(':') << ': out of range') end elsif(cell_coord && cell_coord.length > 0) @status.message = red('invalid input') end construct end) menu.add_menu(save_Menu) menu.add_menu(sm_Menu) menu.add_menu(col_Menu) menu.add_action(Action.new(:name => "⇧", :key => "i") {@table_view.up; refresh()}) menu.add_action(Action.new(:name => "⇩", :key => "k") {@table_view.down; refresh()}) menu.add_action(Action.new(:name => "⇦", :key => "j") {@table_view.left; refresh()}) menu.add_action(Action.new(:name => "⇨", :key => "l") {@table_view.right; refresh()}) menu.add_action(Action.new(:global => true, :hidden => true, :name => '', :key => "\e") {refresh}) menu.add_action(Action.new(:global => true, :name => 'quit', :key => 'q') {puts 'Bye ';exit 0}) end
This is hard to describe. The function basically draws a table on screen.
# File lib/sheetinterface.rb, line 290 def draw(out = STDOUT) if(@rows && !@rows.empty?) # Welcome to hell. on_screen = STDOUT == out col_w = Column::col_width num_c = @rows[0].cells.length() num_r = @rows.length ch = nil hline = nil #----- table_view = '' #----- table_view << @workbook.default_sheet << "\n" #out.puts @workbook.default_sheet table_view << ('┌' << '─' * num_r.to_s.length << '┬' << (('─' * col_w) << '┬' ) * (num_c -1) ) << (('─' * col_w) << '┐' ) << "\n" # out.puts ('┌' << '─' * num_r.to_s.length << '┬' << (('─' * col_w) << '┬' ) * (num_c -1) ) << (('─' * col_w) << '┐' ) lh = 0 @rows.each do |row| if(row == @rows.first) ch = 'A' table_view << "│%#{num_r.to_s.length}s" %" " #out.print "│%#{num_r.to_s.length}s" %" " table_view << '│' # out.print '│' row.each do |c| head = "%#{col_w}s" %ch head = bold(head) if(on_screen) # out.print(head << "|") table_view << (head << "│") ch = ch.next end # out.puts table_view << "\n" hline = ('├' << ('─' * (num_r.to_s.length) ) <<'┼' << (('─' * col_w) << '┼' ) * (num_c - 1) ) << (('─' * col_w) << '┤')if !hline table_view << hline << "\n" # out.puts hline end lh += 1 table_view << "│" # out.print "│" line_head = "%#{num_r.to_s.length}s" %lh.to_s line_head = bold(line_head) if on_screen table_view << line_head # out.print line_head # out.print "│" << bold("%#{num_r.to_s.length}s" %line_head) row.height.times do |li| table_view << "│%#{num_r.to_s.length}s" %" " if li > 0 # out.print "│%#{num_r.to_s.length}s" %" " if li > 0 table_view << '│' # out.print '│' row.each do |cell| line = cell.line(li) content = "%#{col_w}s│" %line if(on_screen && line && line.end_with?('...')) content = red(content) end table_view << content # out.print content end table_view << "\n" # out.puts end hlline = ('│' << (('─' * col_w) << '│' ) * num_c ) if !hline table_view << hline << "\n" # out.puts hline # Some of the following code may go to a function as it repeats the # procedure from the beginning of the table. I am currently unwilling to # do it, because all the rest must stay so verbose that another # function-call at this position cannot contribute to a better # understanding of this ... table-generating MESS!! if(row == @rows.last) ch = 'A' table_view << "│%#{num_r.to_s.length}s" %" " # out.print "│%#{num_r.to_s.length}s" %" " table_view << '│' # out.print '│' row.each do |c| head = "%#{col_w}s" %ch head = bold(head) if on_screen table_view << ( head << '│') # out.print( head << '│') ch = ch.next end table_view << "\n" # out.puts hline = ('└' << ('─' * (num_r.to_s.length) ) << '┴' << (('─' * col_w) << '┴' ) * (num_c - 1) ) << (('─' * col_w) << '┘' ) table_view << hline << "\n" # out.puts hline end end =begin table_width = hline.length tw = terminal_size[0] if(on_screen && table_width > tw) @status.message = red("ATTN! Terminal is to narrow to display the whole table (#{tw} characters for #{table_width}). Diminish column-width!") end =end return table_view else return nil end end
Write content to a new file
# File lib/sheetinterface.rb, line 126 def new_file() file = nil until file print 'file path:' << ' ' begin file = STDIN.gets().chomp! rescue Interrupt return nil end if(file && File::exist?(file) ) if(File::writable?(file) ) print "File #{file} exists! Do you want to overwrite its content, now (j/N)? " answer = "%c" %wait_for_user if('j' != answer.downcase) file = nil else puts end else puts "File #{file} is not writable! Please name a different file path" file = nil end end end return file end
# File lib/sheetinterface.rb, line 187 def open_sheet(designator = :number) sn = nil if designator == :number sn = ask_number("Sheet number (max #{@workbook.sheets.size() - 1})?") do |num| num < @workbook.sheets.size end else sn = ask_string("Sheet name (#{@workbook.sheets.join(', ')})?") do |name| @workbook.sheets.include?(name) end end if sn && !sn.to_s.empty? @log.debug('calling workbook.sheet with ' << sn.to_s) @log.debug('@workbook is of type ' << @workbook.class.name) set_sheet(sn) end construct end
# File lib/sheetinterface.rb, line 452 def refresh() @table_view.width=terminal_size[0] - 1 @table_view.height=terminal_size[1] # @table_view.box = true @table_view.show @status.show if !@menu @menu = Menu.new(:name => 'main') define_actions(@menu) end @menu.call end
# File lib/sheetinterface.rb, line 174 def set_sheet(sheet) current_sheet = @workbook.default_sheet @workbook = @workbook.sheet(sheet) columns = @workbook.last_column if columns && columns > 0 @rows = nil @columns = nil else @workbook = @workbook.sheet(current_sheet) @status.message = red("Sheet #{sheet} contains no data!") end end
returns [width, height] in columns and lines of the current terminal-window.
# File lib/sheetinterface.rb, line 74 def terminal_size size = `stty size`.split.map { |x| x.to_i }.reverse return size end