lib/clevic/swing/table_view.rb in clevic-0.13.0.b9 vs lib/clevic/swing/table_view.rb in clevic-0.13.0.b10

- old
+ new

@@ -17,20 +17,20 @@ # TODO make sure JTable doesn't grab Ctrl-C and do its own copy routine. # TODO make sure Delegates use the correct copy routines. class ClevicTable < javax.swing.JTable attr_accessor :table_view - + def processKeyBinding( key_stroke, key_event, condition, pressed ) # don't auto-start if it's a Ctrl, or Alt-modified key # or a function key. Hopefully this doesn't get checked # for every single keystroke while editing - those should # be captured by the cell editor. if key_event.alt? || key_event.ctrl? || key_event.meta? || key_event.fx? || key_event.del? || key_event.esc? put_client_property( "JTable.autoStartsEdit", false ) end - + # do what JTable normally does with keys super rescue Exception => e puts e.message puts e.backtrace @@ -40,11 +40,11 @@ end # override to make things simpler def getCellEditor( row_index, column_index ) index = table_view.model.create_index( row_index, column_index ) - + # Basically, this is for boolean editing. Number of mouse # clicks and so on is horribly complicated, so just let the # code in javax.swing.whatever handle it. # It has to go here and not in CellEditor, otherwise # listeners and things are wrong. @@ -77,11 +77,11 @@ sm.single_cell? && sm.selected?( row, column ) end else true end - + # must call superclass here to do the edit rather than # just returning whether it should be edited. Java. tsk tsk. if edit_ok super else @@ -100,73 +100,73 @@ # - an instance of Clevic::View # - an instance of TableModel def initialize( arg, &block ) super( @jtable = ClevicTable.new ) @jtable.table_view = self - + # seems like this MUST go after the super call (or maybe the # ClevicTable constructor), otherwise Swing throws an error # somewhere deep inside something. It's not clear right now. - + # This should theoretically close editors when focus is lost # saving whatever values are in there jtable.put_client_property( "terminateEditOnFocusLost", true ) - + # cell editors get focus immediately on editor start jtable.surrendersFocusOnKeystroke = true - + # no auto-resizing of columns jtable.auto_resize_mode = javax.swing.JTable::AUTO_RESIZE_OFF - + # selection of all kinds allowed jtable.selection_mode = javax.swing.ListSelectionModel::MULTIPLE_INTERVAL_SELECTION jtable.row_selection_allowed = true jtable.column_selection_allowed = true jtable.cell_selection_enabled = true - + # appearance jtable.font = Clevic.tahoma self.font = Clevic.tahoma - + # make sure grid shows, even on mac jtable.show_grid = true # because OSX sets this to the same color as the foreground. Duh. jtable.grid_color = java.awt.SystemColor.controlHighlight - + jtable.setDefaultRenderer( java.lang.Object, CellRenderer.new( self ) ) - + fix_input_map - + framework_init( arg, &block ) - + # this must go after framework_init, because it needs the actions # which are set up in there jtable.component_popup_menu = popup_menu - + # add the row header RowHeader.new( self ) - + # make sure focus goes to the right place self.focus_traversal_policy = TableViewFocus.new( self ) end - + class EmptyAction < javax.swing.AbstractAction def actionPerformed( action_event ); end end - + def empty_action @empty_action ||= EmptyAction.new end - + def add_map( key_string, action = empty_action ) map.put( javax.swing.KeyStroke.getKeyStroke( key_string ), action ) end - + def map @map ||= jtable.getInputMap( javax.swing.JComponent::WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ) end - + # This puts empty actions in the local keyboard map so that the # generic keyboard map doesn't catch them and prevent our menu actions # from being triggered # TODO I'm sure this isn't the right way to do this. def fix_input_map @@ -174,79 +174,79 @@ add_map 'ctrl pressed V' add_map 'meta pressed V' add_map 'ctrl pressed X' add_map 'pressed DEL' end - + def popup_menu @popup_menu ||= javax.swing.JPopupMenu.new.tap do |menu| model_actions.each do |action| menu << action.clone.tap{|a| a.shortcut = nil} end - + # now do the generic edit items edit_actions.each do |action| menu << action.clone.tap{|a| a.shortcut = nil} end - + menu.pack end end - + attr_reader :jtable - + def connect_view_signals( entity_view ) # pick up model changes and pass them to the Clevic::View object model.addTableModelListener do |table_model_event| begin # pass changed events to view definitions return unless table_model_event.updated? - + # unlikely to be useful to models, and in fact causes a very very long # calculation. So don't pass it on. return if table_model_event.all_rows? - + top_left = model.create_index( table_model_event.first_row, table_model_event.column ) bottom_right = model.create_index( table_model_event.last_row, table_model_event.column ) - + entity_view.notify_data_changed( self, top_left, bottom_right ) - + to_next_index rescue Exception => e show_error e.message puts e.backtrace end end end - + # kind-of override of requestFocus, but it will probably only # work from Ruby def request_focus @jtable.request_focus end - + # return a collection of collections of SwingTableIndex objects # indicating the indices of the current selection def selected_rows @jtable.selected_rows.map do |row_index| @jtable.selected_columns.map do |column_index| SwingTableIndex.new( model, row_index, column_index ) end end end - + # called from the framework-independent part to edit a cell def edit( table_index ) # TODO keyboard focus doesn't seem to be reassigned to combo # when editing is started this way. @jtable.editCellAt( table_index.row, table_index.column ) end - + def status_text_listeners @status_text_listeners ||= Set.new end - + # If msg is provided, yield to stored block. # If block is provided, store it for later. def emit_status_text( msg = nil, &notifier_block ) if block_given? status_text_listeners << notifier_block @@ -254,55 +254,55 @@ status_text_listeners.each do |notify| notify.call( msg ) end end end - + def filter_status_listeners @filter_status_listeners ||= Set.new end - + # emit whether the view is filtered or not def emit_filter_status( bool = nil, &notifier_block ) if block_given? filter_status_listeners << notifier_block else filter_status_listeners.each do |notify| notify.call( bool ) end end end - + def confirm_dialog( question, title ) cd = ConfirmDialog.new do |dialog| dialog.parent = self dialog.question = question dialog.title = title dialog['Ok'] = :accept, :default dialog['Cancel'] = :reject end cd.show end - + # set the size of the column from the sample def auto_size_column( col, sample ) @jtable.column_model.column( col ).preferred_width = column_width( col, sample ) end # calculate the size of the column from the string value of the data def column_width( col, data ) @jtable.getFontMetrics( @jtable.font ).stringWidth( data.to_s ) + 5 end - + def trim_middle( value, max = 40 ) if value && value.length > max "#{value[0..(max/2-2)]}...#{value[-(max/2-2)..-1]}" else value end end - + # forward to @jtable # also handle model#emit_data_error def model=( model ) emitter_block = lambda do |index,value,message| show_error "#{index.rc} #{message}: #{trim_middle( value, 40 )}" @@ -310,77 +310,77 @@ @jtable.model.remove_data_error( &emitter_block ) if @jtable.model.respond_to? :remove_data_error @jtable.model = model @jtable.model.emit_data_error( &emitter_block ) if @jtable.model.respond_to? :emit_data_error resize_columns end - + def model @jtable.model end - + def show_error( msg, title = "Error" ) @pane ||= javax.swing.JOptionPane.new( '', javax.swing.JOptionPane::ERROR_MESSAGE, javax.swing.JOptionPane::DEFAULT_OPTION ) @pane.message = msg @pane.create_dialog( self, title ).show end - + def selection_model SelectionModel.new( self ) end - + # move the cursor & selection to the specified table_index def current_index=( table_index ) @jtable.selection_model.clear_selection @jtable.setColumnSelectionInterval( table_index.column, table_index.column ) @jtable.setRowSelectionInterval( table_index.row, table_index.row ) - + # x position. Should be sum of widths of all columns up to the beginning of this one # ie not including this one, hence the -1 xpos = (0..table_index.column-1).inject(0) do |sum,column_index| sum + @jtable.column_model.getColumn( column_index ).width end - + rect = java.awt.Rectangle.new( xpos, - + # y position @jtable.row_height * table_index.row, - + # width of this column @jtable.column_model.getColumn( table_index.column ).width, - + # height @jtable.row_height ) @jtable.scrollRectToVisible( rect ) end - + # return a SwingTableIndex for the current cursor position # TODO optimise so we don't keep creating a new index, only if a selection # changed event has occurred def current_index model.create_index( @jtable.selected_row, @jtable.selected_column ) end def wait_cursor @wait_cursor ||= java.awt.Cursor.new( java.awt.Cursor::WAIT_CURSOR ) end - + # show a busy cursor, do the block, back to normal cursor # return value of block def busy_cursor( &block ) save_cursor = cursor self.cursor = wait_cursor rv = yield ensure self.cursor = save_cursor rv end - + # collect actions for the popup menu def add_action( action ) ( @context_actions ||= [] ) << action end end