require 'clevic/search_dialog.rb' require 'clevic/ui/browser_ui.rb' module Clevic =begin rdoc The main application class. Each model for display should have a self.ui method which returns a Clevic::TableView instance, usually in conjunction with a ModelBuilder. Clevic::TableView.new( Entry, parent ).create_model.new( Entry, parent ).create_model . . . end Model instances may also implement self.key_press_event( event, current_index, view ) and self.data_changed( top_left_index, bottom_right_index, view ) methods so that they can respond to editing events and do Neat Stuff. =end class Browser < Qt::Widget slots *%w{dump() refresh_table() filter_by_current(bool) next_tab() previous_tab() current_changed(int)} def initialize( main_window ) super( main_window ) # do menus and widgets @layout = Ui::Browser.new @layout.setup_ui( main_window ) # set icon. MUST come after call to setup_ui icon_path = Pathname.new( __FILE__ ).parent + "ui/icon.png" raise "icon.png not found" unless icon_path.file? main_window.window_icon = Qt::Icon.new( icon_path.realpath.to_s ) # add the tables tab @tables_tab = Qt::TabWidget.new( @layout.main_widget ) @layout.main_widget.layout.add_widget @tables_tab @tables_tab.tab_bar.focus_policy = Qt::NoFocus # connect slots @layout.action_dump.connect SIGNAL( 'triggered()' ), &method( :dump ) @layout.action_refresh.connect SIGNAL( 'triggered()' ), &method( :refresh_table ) @layout.action_filter.connect SIGNAL( 'triggered(bool)' ), &method( :filter_by_current ) @layout.action_next.connect SIGNAL( 'triggered()' ), &method( :next_tab ) @layout.action_previous.connect SIGNAL( 'triggered()' ), &method( :previous_tab ) @layout.action_find.connect SIGNAL( 'triggered()' ), &method( :find ) @layout.action_find_next.connect SIGNAL( 'triggered()' ), &method( :find_next ) @layout.action_new_row.connect SIGNAL( 'triggered()' ), &method( :new_row ) tables_tab.connect SIGNAL( 'currentChanged(int)' ), &method( :current_changed ) # as an example #~ tables_tab.connect SIGNAL( 'currentChanged(int)' ) { |index| puts "other current_changed: #{index}" } end # activated by Ctrl-D for debugging def dump puts "table_view.model: #{table_view.model.inspect}" if table_view.class == Clevic::TableView end # return the Clevic::TableView object in the currently displayed tab def table_view tables_tab.current_widget end def tables_tab @tables_tab end # display a search dialog, and find the entered text def find @search_dialog ||= SearchDialog.new result = @search_dialog.exec( table_view.current_index.gui_value ) override_cursor( Qt::BusyCursor ) do case result when Qt::Dialog::Accepted search_for = @search_dialog.search_text table_view.search( @search_dialog ) when Qt::Dialog::Rejected puts "Don't search" else puts "unknown dialog code #{result}" end end end def find_next if @search_dialog.nil? @layout.statusbar.show_message( 'No previous find' ) else override_cursor( Qt::BusyCursor ) do save_from_start = @search_dialog.from_start? @search_dialog.from_start = false table_view.search( @search_dialog ) @search_dialog.from_start = save_from_start end end end # force a complete reload of the current tab's data def refresh_table override_cursor( Qt::BusyCursor ) do table_view.model.reload_data end end # toggle the filter, based on current selection. def filter_by_current( bool_filter ) # TODO if there's no selection, use the current index instead table_view.filter_by_indexes( table_view.selection_model.selected_indexes ) # set the checkbox in the menu item @layout.action_filter.checked = table_view.filtered # update the tab, so there's a visual indication of filtering tab_title = table_view.filtered ? translate( '| ' + table_view.model_class.name.humanize ) : translate( table_view.model_class.name.humanize ) tables_tab.set_tab_text( tables_tab.current_index, tab_title ) end # slot to handle Ctrl-Tab and move to next tab, or wrap around def next_tab tables_tab.current_index = if tables_tab.current_index >= tables_tab.count - 1 0 else tables_tab.current_index + 1 end end # slot to handle Ctrl-Backtab and move to previous tab, or wrap around def previous_tab tables_tab.current_index = if tables_tab.current_index <= 0 tables_tab.count - 1 else tables_tab.current_index - 1 end end def new_row table_view.model.add_new_item end # slot to handle the currentChanged signal from tables_tab, and # set focus on the grid def current_changed( current_tab_index ) tables_tab.current_widget.setFocus @layout.action_filter.checked = table_view.filtered end # shortcut for the Qt translate call def translate( st ) Qt::Application.translate("Browser", st, nil, Qt::Application::UnicodeUTF8) end # return the list of models in $options[:models] or find them # as descendants of ActiveRecord::Base def find_models( models = $options[:models] ) if models.nil? || models.empty? models = [] ObjectSpace.each_object( Class ) {|x| models << x if x.superclass == ActiveRecord::Base } models else models end end # Create the tabs, each with a collection for a particular model class. # # models parameter can be an array of Model objects, in order of display. # if models is nil, find_models is called def open( *models ) models = $options[:models] if models.empty? # Add all existing model objects as tabs, one each find_models( models ).each do |model| if model.respond_to?( :ui ) tab = model.ui( tables_tab ) tab.connect( SIGNAL( 'status_text(QString)' ) ) { |msg| @layout.statusbar.show_message( msg, 20000 ) } else raise "Can't build ui for #{model.name}. Provide a self.ui method." end tables_tab.add_tab( tab, translate( model.name.humanize ) ) end end # make sure all outstanding records are saved def save_all tables_tab.each {|x| x.save_row( x.current_index ) } end end end