Exemplo n.º 1
0
class MainWindow(gtk.Window):
    """Application main window.

    Instance attributes:
      menubar: Menu bar.

    :Signals:
    active-editor-changed
      ``def callback(instance, editor)``
      Emitted when the active editor has changed. editor is either an
      editor instance or None.
    """

    __gsignals__ = {
        'active-editor-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                                  (gobject.TYPE_PYOBJECT, )),
        'editor-created': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                           (gobject.TYPE_PYOBJECT, )),
    }

    def __init__(self, app, create_editor=True):
        gtk.Window.__init__(self)
        self.app = app
        self._editor = None
        self._editor_conn_tag = None  # Signal ID
        self._tracked_conn = None  # Tracked connection
        self._editors = []
        self.clipboard = gtk.clipboard_get()
        self.ui = gtk.UIManager()
        # See: http://www.daa.com.au/pipermail/pygtk/2006-May/012267.html
        self.ui.connect('connect-proxy', self.on_ui_connect_proxy)
        self.ui.connect('disconnect-proxy', self.on_ui_disconnect_proxy)
        self._init_actions()
        self.ui.add_ui_from_string(UI)
        self.ui.ensure_update()
        self.set_title("CrunchyFrog %s" % release.version)
        self.set_icon_list(*utils.get_logo_icon_list())
        self._init_elements()
        self.connect('window-state-event', self.on_window_state_event)
        self.connect('delete-event', self.on_quit)
        # XXX disabled: it results in unexpected behavior...
        if create_editor and 1 == 2:
            self.editor_create()
        self.state_restore()

    def _init_actions(self):
        group = gtk.ActionGroup('instance')
        group.set_data('cf::label', _(u'Application'))
        entries = (
            # File
            ('file-menu-action', None, _(u'_File')),
            ('instance-quit', gtk.STOCK_QUIT, None, '<control>Q',
             _(u'Quit the program'), self.on_quit),
            ('file-new-menu-action', None, _(u'_New')),
            ('instance-new-editor', gtk.STOCK_NEW, _(u'_Query'), '<control>N',
             _(u'New SQL editor'), self.on_editor_new),
            ('instance-new-mainwin', None, _(u'_Window'), '<shift><control>N',
             _('New application window.'), self.on_instance_new),
            ('file-open', gtk.STOCK_OPEN, None, '<control>O',
             _(u'Open a file'), self.on_open_file),
            # Edit
            ('edit-menu-action', None, _(u'_Edit')),
            ('instance-plugins', None, _(u'_Plugins'), None,
             _(u'Configure plugins'),
             lambda action: self.app.preferences_show(self, 'plugins')),
            ('instance-datasourcemanager', None, _(u'_Data Sources'), None,
             _(u'Add, edit and delete data sources'),
             self.on_datasource_manager),
            ('instance-preferences', gtk.STOCK_PREFERENCES, None, None,
             _(u'Configure the application'),
             lambda action: self.app.preferences_show(self)),
            # Query
            ('query-menu-action', None, _(u'_Query')),
            # View
            ('view-menu-action', None, _(u'_View')),
            # Help
            ('help-menu-action', None, _(u'_Help')),
            ('help-help', gtk.STOCK_HELP, None, 'F1',
             _(u'Open the CrunchyFrog manual'),
             lambda *a: self.app.show_help()),
            (
                'help-translate',
                None,
                _(u'Help translate...'),
                None,
                _(u'Help to translate this application'),
                lambda a: webbrowser.open(
                    'https://translations.launchpad.net/crunchyfrog'),
            ),
            ('help-bugs', None, _(u'Report a problem'), None,
             _((u'Help to improve this application by reporting a bug '
                u'or a feature request.')), lambda a: webbrowser.open(
                    ('http://code.google.com/p/crunchyfrog/'
                     'issues/entry'))),
            ('help-about', gtk.STOCK_ABOUT, None, None,
             _(u'About this application'), self.on_about),
        )
        group.add_actions(entries)
        toggle_entries = (
            ('instance-toggle-toolbar', None, _(u'_Toolbar'), None,
             _(u'Show or hide the toolbar in the current window'),
             self.on_toggle_toolbar),
            ('instance-toggle-statusbar', None, _(u'_Statusbar'), None,
             _(u'Show or hide the statusbar in the current window'),
             self.on_toggle_statusbar),
        )
        group.add_toggle_actions(toggle_entries)
        # Editor switcher
        entries = []
        for i in range(1, 11):
            if i == 10:
                i = 0
            entry = ('activate-editor%d' % i, None, '', '<Alt>%d' % i, None,
                     i - 1)
            entries.append(entry)
        group.add_radio_actions(entries, 0, self.on_editor_set_focus)
        self.ui.insert_action_group(group, -1)
        group = gtk.ActionGroup('editor')
        group.set_data('cf::label', _(u'Editor'))
        entries = (
            ('editor-close', gtk.STOCK_CLOSE, None, '<control>W',
             _(u'Close the current editor'), self.on_editor_close),
            ('editor-close-all', None, _(u'Close _all'), '<control><shift>W',
             _(u'Close all editors'), lambda action: self.close_all_editors()),
            # Query
            ('query-connection-menu-action', None, _(u'_Connection')),
            ('query-connection-disconnect',
             gtk.STOCK_DISCONNECT, None, '<shift>F8',
             _(u'Disconnect current editor'), self.on_editor_disconnect),
            ('query-show-connections', None, _(u'Show connections'),
             '<shift>F5', _(u'Open or close connections in a dialog'),
             lambda *a: self.app.manage_connections(self)),
            ('query-next-statement', None,
             _(u'Next Statement'), '<control><alt>j',
             _(u'Move cursor to next statement'), self.on_jump_next_statement),
            ('query-prev-statement', None, _(u'Previous Statement'),
             '<control><alt>k', _(u'Move cursor to previous statement'),
             self.on_jump_prev_statement),
            ('query-frmt-menu', None, _(u'_Format')),
            ('query-frmt-comment', None,
             _(u'_Comment / Uncomment'), '<shift><control>space',
             _(u'Comment / uncomment selected lines'), self.on_frmt_comment),
            ('query-frmt-format', None, _(u'_Format'), '<shift><control>f',
             _(u'Beautify selected lines or whole buffer'),
             self.on_frmt_format),
            ('query-frm-uppercase-kw', None, _(u'_Uppercase Keywords'),
             '<shift><control>u',
             _(u'Uppercase keywords in selected lines or in whole buffer'),
             self.on_frmt_uppercase_kw),
            ('query-toggle-results', None,
             _(u'Show / Hide Results'), '<shift>F9',
             _(u'Show or hide results pane'), self.on_editor_toggle_results),
            ('file-save', gtk.STOCK_SAVE, None, '<control>S',
             _(u'Save the current file'), self.on_file_save),
            ('file-save-as', gtk.STOCK_SAVE_AS, None, '<shift><control>S',
             _(u'Save the current file with a different name'),
             self.on_file_save_as),
            ('editor-print', gtk.STOCK_PRINT, None, '<control>P',
             _(u'Print the current page'), self.on_editor_print),
            ('editor-printpreview',
             gtk.STOCK_PRINT_PREVIEW, None, '<shift><control>P',
             _(u'Print preview'), self.on_editor_print_preview),
        )
        group.add_actions(entries)
        group.set_sensitive(False)
        self.ui.insert_action_group(group, 0)
        # Clipboard
        group = gtk.ActionGroup('clipboard')
        entries = (
            ('clipboard-cut', gtk.STOCK_CUT, None, '<control>X',
             _(u'Cut the selection'), self.on_clipboard_cut),
            ('clipboard-copy', gtk.STOCK_COPY, None, '<control>C',
             _(u'Copy the selection'), self.on_clipboard_copy),
            ('clipboard-paste', gtk.STOCK_PASTE, None, '<control>V',
             _(u'Paste the clipboard'), self.on_clipboard_paste),
        )
        group.add_actions(entries)
        self.ui.insert_action_group(group, 0)
        group = gtk.ActionGroup('query')
        group.set_data('cf::label', _(u'Queries'))
        entries = (
            ('query-execute', gtk.STOCK_EXECUTE, None, 'F5',
             _(u'Execute statements in SQL editor'), self.on_query_execute),
            ('query-execute-current', None, _(u'Exec_ute Current Statement'),
             '<control>F5', _(u'Executes statement at cursor'),
             self.on_query_execute_current),
            ('query-begin', gtk.STOCK_INDENT, _(u'Transaction'), 'F6',
             _(u'Begin transaction on current connection'),
             self.on_begin_transaction),
            ('query-commit', gtk.STOCK_APPLY, _(u'Commit'), 'F7',
             _(u'Commit current transaction'), self.on_commit),
            ('query-rollback', gtk.STOCK_UNDO, _(u'Rollback'), 'F8',
             _(u'Rollback current transaction'), self.on_rollback),
        )
        group.add_actions(entries)
        group.set_sensitive(False)
        self.ui.insert_action_group(group, -1)
        self.add_accel_group(self.ui.get_accel_group())

    def _init_elements(self):
        """Initialize main window elements."""
        self._init_statusbar()
        self._init_panes()
        vbox = gtk.VBox()
        self.add(vbox)
        vbox.pack_start(self._init_menubar(), False, False)
        vbox.pack_start(self._init_toolbar(), False, False)
        hpaned = gtk.HPaned()
        vbox.pack_start(hpaned, True, True)
        hpaned.show()
        vpaned = gtk.VPaned()
        hpaned.add2(vpaned)
        vpaned.show()
        hpaned.add1(self.side_pane)
        vpaned.add2(self.bottom_pane)
        self.queries = pane.CenterPane(self)
        vpaned.add1(self.queries)
        self.queries.show()
        # Connect to realize to set paned position when window is ready.
        self.connect('realize', self.on_set_paned_position, vpaned)
        self.connect('realize', self.on_set_paned_position, hpaned)
        vbox.pack_start(self.statusbar, False, False)
        vbox.show()
        self.browser = Browser(self.app, self)
        self.side_pane.add_item(self.browser)
        self.side_pane.set_active_item('navigator')
        menu = self.ui.get_widget('/MenuBar/Query/Connection')
        menu.connect('activate', self.on_connection_menu_activate)
        menu = self.ui.get_widget('/MenuBar/Query')
        menu.connect('activate', self.on_query_menu_activate)
        self._init_file_open()

    def _init_file_open(self):
        """Initialize file open menu item and toolbar button."""
        ph = self.ui.get_widget("/ToolBar/OpenFile")
        btn = gtk.MenuToolButton(gtk.STOCK_OPEN)
        btn.show()
        btn.connect('clicked', self.on_open_file)
        btn.set_menu(self._get_recent_menu())
        idx = self.toolbar.get_item_index(ph)
        self.toolbar.insert(btn, idx)
        ph = self.ui.get_widget('/MenuBar/File/RecentFiles')
        menu = self._get_recent_menu()
        menuitem = gtk.MenuItem(_(u'_Recent Files'))
        menuitem.show()
        menuitem.set_submenu(menu)
        file_menu = self.ui.get_widget('/MenuBar/File').get_submenu()
        for idx, child in enumerate(file_menu.get_children()):
            if child == ph:
                file_menu.insert(menuitem, idx)
                return

    def _init_menubar(self):
        """Create and return the applications menubar."""
        self.menubar = self.ui.get_widget('/MenuBar')
        self.menubar.show_all()
        return self.menubar

    def _init_panes(self):
        """Init side and bottom pane."""
        self.side_pane = pane.SidePane(self)
        self.bottom_pane = pane.BottomPane(self)

    def _init_statusbar(self):
        """Create and return the statusbar."""
        self.statusbar = CrunchyStatusbar(self.app, self)
        self.statusbar.show()
        return self.statusbar

    def _init_toolbar(self):
        """Create and return the toolbar."""
        self.toolbar = self.ui.get_widget("/ToolBar")
        self.toolbar.show_all()
        ph = self.ui.get_widget("/ToolBar/EditorConnection")
        self.tb_conn_chooser = widgets.ConnectionButton(self)
        idx = self.toolbar.get_item_index(ph)
        self.toolbar.insert(self.tb_conn_chooser, idx)
        self.tb_conn_chooser.show_all()
        separator = gtk.SeparatorToolItem()
        self.toolbar.insert(separator, idx + 1)
        separator.show()
        return self.toolbar

    def _get_action(self, action_name):
        """Return a action regardless of groups."""
        for group in self.ui.get_action_groups():
            action = group.get_action(action_name)
            if action is not None:
                return action
        return None

    def _get_clipboard(self):
        return self.clipboard

    def _get_recent_menu(self, limit=None, recent_menu=None):
        """Return a recent file menu."""
        if recent_menu is None:
            recent_menu = gtk.RecentChooserMenu(self.app.recent_manager)
        filter_ = gtk.RecentFilter()
        filter_.add_mime_type("text/x-sql")
        recent_menu.add_filter(filter_)
        recent_menu.set_filter(filter_)
        recent_menu.set_show_not_found(False)
        recent_menu.set_sort_type(gtk.RECENT_SORT_MRU)
        recent_menu.connect("item-activated", self.on_recent_item_activated)
        if limit is not None:
            recent_menu.set_limit(limit)
        return recent_menu

    # ---
    # Callbacks
    # ---
    def on_about(self, *args):
        def open_url(dialog, url):
            gtk.show_uri(dialog.get_screen(), url,
                         gtk.gdk.x11_get_server_time(dialog.window))

        gtk.about_dialog_set_url_hook(open_url)
        dlg = gtk.AboutDialog()
        dlg.set_name(release.name)
        dlg.set_version(release.version)
        dlg.set_copyright(release.copyright)
        dlg.set_license(release.license)
        dlg.set_website(release.url)
        dlg.set_website_label(release.url)
        dlg.set_logo(utils.get_logo_icon(96))
        dlg.set_program_name(release.appname)
        dlg.set_translator_credits(release.translators)
        dlg.run()
        dlg.destroy()

    def on_begin_transaction(self, *args):
        if not self._editor:
            return
        gobject.idle_add(self._editor.begin_transaction)

    def on_clipboard_copy(self, *args):
        if not self._editor:
            return
        self._editor.clipboard_copy(self._get_clipboard())

    def on_clipboard_cut(self, *args):
        if not self._editor:
            return
        self._editor.clipboard_cut(self._get_clipboard())

    def on_clipboard_paste(self, *args):
        if not self._editor:
            return
        self._editor.clipboard_paste(self._get_clipboard())

    def on_commit(self, *args):
        if not self._editor:
            return
        gobject.idle_add(self._editor.commit)

    def on_connection_menu_activate(self, menuitem):
        widgets.rebuild_connection_menu(menuitem.get_submenu(), self,
                                        self.get_active_editor())

    def on_connection_notify(self, connection, property):
        if property.name == 'transaction-state':
            value = connection.get_property(property.name)
            gobject.idle_add(self.set_transaction_state, value, connection)

    def on_datasource_manager(self, *args):
        dlg = DatasourcesDialog(self.app, self)
        dlg.run()
        dlg.destroy()

    def on_editor_close(self, *args):
        editor = self.get_active_editor()
        if editor is not None:
            editor.close()

    def on_editor_connection_changed(self, editor, connection):
        if self._tracked_conn:
            sig = self._tracked_conn.get_data('cf::mainwin::notify')
            if sig:
                self._tracked_conn.disconnect(sig)
            self._tracked_conn = None
        for group in self.ui.get_action_groups():
            if group.get_name() == 'query':
                group.set_sensitive(connection is not None)
        if connection:
            self.set_title(connection.get_label() + ' - CrunchyFrog')
        else:
            self.set_title('CrunchyFrog %s' % release.version)
        # Set transaction state
        if connection:
            self._tracked_conn = connection
            sig = self._tracked_conn.connect('notify',
                                             self.on_connection_notify)
            self._tracked_conn.set_data('cf::mainwin::notify', sig)
            state = connection.get_property('transaction-state')
            self.set_transaction_state(state, connection)

    def on_editor_disconnect(self, action):
        editor = self.get_active_editor()
        if editor is None or editor.connection is None:
            return
        editor.set_connection(None)

    def on_editor_new(self, *args):
        editor = self.editor_create()
        # FIXME(andi): Updating editor switcher action should be in a
        #              separate function.
        self.on_query_menu_activate(None)

    def on_editor_print(self, action):
        self.get_active_editor().print_contents()

    def on_editor_print_preview(self, action):
        self.get_active_editor().print_contents(preview=True)

    def on_editor_set_focus(self, first_action, current):
        queries_idx = current.get_current_value()
        if queries_idx == -1:
            queries_idx = 9
        if self.queries.get_n_pages() - 1 < queries_idx:
            return
        self.queries.set_current_page(queries_idx)
        editor = self.queries.get_nth_page(queries_idx)
        focus_child = editor.get_focus_child()
        if focus_child is not None:
            focus_child.grab_focus()

    def on_editor_toggle_results(self, action):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.toggle_results_pane()

    def on_frmt_comment(self, action):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.selected_lines_toggle_comment()

    def on_frmt_format(self, action):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.selected_lines_quick_format()

    def on_frmt_uppercase_kw(self, action):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.selected_lines_quick_format(keyword_case='upper')

    def on_file_save(self, *args):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.save_file(self)

    def on_file_save_as(self, *args):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.save_file_as(self)

    def on_instance_new(self, action):
        self.app.new_instance()

    def on_jump_next_statement(self, action):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.rjump_to_statement(1)

    def on_jump_prev_statement(self, action):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.rjump_to_statement(-1)

    def on_menu_item_deselect(self, menuitem):
        self.statusbar.pop(100)

    def on_menu_item_select(self, menuitem, tooltip):
        self.statusbar.push(100, tooltip)

    def on_open_file(self, *args):
        dlg = gtk.FileChooserDialog(_(u"Select file"), self,
                                    gtk.FILE_CHOOSER_ACTION_OPEN,
                                    (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                     gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        dlg.set_current_folder(self.app.config.get("editor.recent_folder", ""))
        dlg.set_select_multiple(True)
        filter = gtk.FileFilter()
        filter.set_name(_(u"All files (*)"))
        filter.add_pattern("*")
        dlg.add_filter(filter)
        filter = gtk.FileFilter()
        filter.set_name(_(u"SQL files (*.sql)"))
        filter.add_pattern("*.sql")
        dlg.add_filter(filter)
        dlg.set_filter(filter)
        recent_chooser = gtk.RecentChooserWidget(self.app.recent_manager)
        filter = gtk.RecentFilter()
        filter.add_mime_type("text/x-sql")
        filter.set_name(_(u"SQL files (*.sql)"))
        recent_chooser.add_filter(filter)
        recent_chooser.set_filter(filter)
        recent_chooser.set_show_not_found(False)
        recent_chooser.set_sort_type(gtk.RECENT_SORT_MRU)
        recent_chooser.set_show_icons(True)
        recent_chooser.set_show_tips(True)

        def dlg_set_uri(chooser, dlg):
            if chooser.get_current_uri():
                dlg.set_uri(chooser.get_current_uri())

        recent_chooser.connect("selection-changed", dlg_set_uri, dlg)
        recent_chooser.connect("item-activated",
                               lambda chooser: dlg.response(gtk.RESPONSE_OK))
        exp = gtk.Expander(_(u"_Recent files:"))
        exp.add(recent_chooser)
        exp.set_use_underline(True)
        dlg.set_extra_widget(exp)
        recent_chooser.show()
        if dlg.run() == gtk.RESPONSE_OK:
            [self.editor_create(fname) for fname in dlg.get_filenames()]
            self.app.config.set("editor.recent_folder",
                                dlg.get_current_folder())
        dlg.destroy()

    def on_preferences(self, *args):
        self.app.preferences_show()

    def on_query_execute(self, *args):
        self.get_active_editor().execute_query()

    def on_query_execute_current(self, action):
        self.get_active_editor().execute_query(True)

    def on_query_menu_activate(self, menuitem):
        self._rebuild_activate_editor_actions()

    def _rebuild_activate_editor_actions(self):
        for idx in range(1, 11):
            if idx == 10:
                idx = 0
                page_idx = 9
            else:
                page_idx = idx - 1
            action = self._get_action('activate-editor%d' % idx)
            page = self.queries.get_nth_page(page_idx)
            if page is None:
                action.set_visible(False)
                action.set_sensitive(False)
            else:
                action.set_visible(True)
                action.set_sensitive(True)
                tab = self.queries.get_tab_label(page)
                lbl = tab.label.get_text()
                if len(lbl) > 16:
                    lbl = lbl[:15] + '...'
                action.props.label = lbl.replace('_', '__')
                is_active = bool(page_idx == self.queries.get_current_page())
                action.set_active(is_active)

    def on_quit(self, *args):
        if not self.close_all_editors():
            return False
        if len(self.app.get_instances()) <= 1:
            self.state_save()
        self.destroy()

    def on_recent_item_activated(self, chooser):
        self.editor_create(chooser.get_current_uri())

    def on_rollback(self, *args):
        if not self._editor:
            return
        gobject.idle_add(self._editor.rollback)

    def on_set_paned_position(self, win, paned):
        if isinstance(paned, gtk.VPaned):
            opt = 'win.bottompane_position'
        else:
            opt = 'win.sidepane_position'
        w, h = win.get_size()
        stored_pos = self.app.config.get(opt, None)
        if stored_pos is None:
            if isinstance(paned, gtk.VPaned):
                stored_pos = h - 250
            else:
                stored_pos = 250
        paned.set_position(stored_pos)
        paned.connect(
            'notify::position',
            lambda paned, prop: self.app.config.set(opt, paned.get_position()))

    def on_toggle_statusbar(self, toggle_action):
        if toggle_action.get_active():
            self.statusbar.show()
        else:
            self.statusbar.hide()
        self.app.config.set('win.statusbar_visible',
                            toggle_action.get_active())

    def on_toggle_toolbar(self, toggle_action):
        if toggle_action.get_active():
            self.toolbar.show()
        else:
            self.toolbar.hide()
        self.app.config.set('win.toolbar_visible', toggle_action.get_active())

    def on_ui_connect_proxy(self, ui, action, widget):
        tooltip = action.get_property('tooltip')
        if isinstance(widget, gtk.MenuItem) and tooltip:
            cid = widget.connect('select', self.on_menu_item_select, tooltip)
            cid2 = widget.connect('deselect', self.on_menu_item_deselect)
            widget.set_data('cf::cids', (cid, cid2))

    def on_ui_disconnect_proxy(self, ui, action, widget):
        cids = widget.get_data('cf::cids') or ()
        for cid in cids:
            widget.disconnect(cid)

    def on_window_state_event(self, window, event):
        val_names = event.new_window_state.value_names
        c = self.app.config
        c.set('gui.maximized', gtk.gdk.WINDOW_STATE_MAXIMIZED.value_names[0]
              in val_names)

    # ---
    # Public methods
    # ---

    def close_all_editors(self):
        """Closes all editors but checks for changes first.

        Returns:
          False if action was cancelled, otherwise True.
        """
        changed = []
        for item in self.queries.get_all_editors():
            if isinstance(item, Editor) and item.contents_changed():
                changed.append(item)
        if changed:
            dlg = ConfirmSaveDialog(self, changed)
            proceed = dlg.run()
            if proceed == 1:
                dlg.hide()
                if not dlg.save_files():
                    proceed = 0
            dlg.destroy()
            if proceed == 0:
                return False
        for item in self.queries.get_all_editors():
            item.close(force=True)
        return True

    def get_editors(self):
        """Return a list of all SQL editors."""
        return [
            item for item in self.queries.get_all_editors()
            if isinstance(item, Editor)
        ]

    def editor_create(self, fname=None):
        """Creates a new SQL editor.

        Arguments:
          fname: If given, the file fname is opened with this editor.

        Returns:
          An Editor instance.
        """
        last_conn = None
        if self.app.config.get('editor.reuse_connection'):
            cur_editor = self.get_active_editor()
            if cur_editor is not None:
                last_conn = cur_editor.get_connection()
        editor = Editor(self)
        if last_conn is not None:
            editor.set_connection(last_conn)
        if fname:
            scheme, _, path, _, _, _ = urlparse.urlparse(fname)
            fname = path
            editor.set_filename(fname)
        self.editor_append(editor)
        editor.show_all()
        editor.textview.grab_focus()
        return editor

    def editor_append(self, editor):
        """Adds an editor to the editors notebook.

        Arguments:
          editor: Editor instance.
        """
        # Cleanup other instances
        for instance in self.app.get_instances():
            if instance != self and editor in instance._editors:
                instance._editors.remove(editor)
        self.queries.add_item(editor)
        self._editors.append(editor)

    def editor_remove(self, editor):
        """Removes an editor from this instance."""
        if editor in self._editors:
            self._editors.remove(editor)

    def get_active_editor(self):
        """Returns the active editor.

        Returns:
          Editor instance or None.
        """
        if isinstance(self._editor, Editor):
            return self._editor
        return None

    def set_editor_active(self, editor, active):
        """Called whenever an editor receives or looses focus."""
        if not active:
            editor = None
        if self._editor_conn_tag and self._editor:
            self._editor.disconnect(self._editor_conn_tag)
            self._editor_conn_tag = None
        if self._editor:
            handler_id = self._editor.get_data('cf::sig_editor_buffer_changed')
            if handler_id:
                self._editor.disconnect(handler_id)
                self._editor.set_data('cf::sig_editor_buffer_changed', None)
        self._editor = editor
        if self._editor and isinstance(self._editor, Editor):
            self._editor_conn_tag = self._editor.connect(
                "connection-changed", self.on_editor_connection_changed)
            self.on_editor_connection_changed(self._editor,
                                              self._editor.connection)
            conn = self._editor.connection
            if conn:
                prop = conn.get_property('transaction-state')
                self.set_transaction_state(prop, conn)
            # XXX add buffer selection changed cb to update copy&paste btns
        else:
            self.set_title('CrunchyFrog %s' % release.version)
        for group in self.ui.get_action_groups():
            if group.get_name() == 'editor':
                group.set_sensitive(isinstance(self._editor, Editor))
            elif group.get_name() == 'query':
                group.set_sensitive(
                    bool(
                        isinstance(self._editor, Editor)
                        and self._editor.connection))
        sensitive = bool((self._editor and isinstance(self._editor, Editor)))
        action = self._get_action('file-save')
        action.set_sensitive(sensitive)
        action = self._get_action('file-save-as')
        action.set_sensitive(sensitive)
        self.tb_conn_chooser.set_editor(editor)
        self.app.plugins.editor_notify(editor, self)
        self.emit('active-editor-changed', editor)
        if editor is not None and isinstance(editor, Editor):
            editor.textview.grab_focus()

    def set_transaction_state(self, value, connection):
        """Adjusts the transactions state in the UI."""
        # A regression: If value is None that means we have no connection,
        # but this is already handled by setting the whole action group
        # insensitive.
        if value is None or connection is None:
            return
        commit = self._get_action('query-commit')
        rollback = self._get_action('query-rollback')
        begin = self._get_action('query-begin')
        commit.set_sensitive((value & TRANSACTION_COMMIT_ENABLED) != 0)
        rollback.set_sensitive((value & TRANSACTION_ROLLBACK_ENABLED) != 0)
        begin.set_sensitive((value & TRANSACTION_IDLE) != 0)

    def state_restore(self):
        """Restore window state."""
        conf = self.app.config
        action = self._get_action('instance-toggle-statusbar')
        action.set_active(conf.get('win.statusbar_visible', True))
        self.on_toggle_statusbar(action)
        action = self._get_action('instance-toggle-toolbar')
        action.set_active(conf.get('win.toolbar_visible', True))
        self.on_toggle_toolbar(action)
        self.side_pane.state_restore()
        self.bottom_pane.state_restore()
        width = conf.get('gui.width')
        height = conf.get('gui.height')
        if not width or width == -1:
            width = 800
        if not height or height == -1:
            height = 600
        self.resize(width, height)
        if conf.get('gui.maximized', False):
            self.maximize()
        fname = os.path.join(USER_CONFIG_DIR, 'shortcuts.map')
        if os.path.isfile(fname):
            gtk.accel_map_load(fname)

    def state_save(self):
        """Save window state to config."""
        c = self.app.config
        w, h = self.get_size()
        c.set('gui.width', w)
        c.set('gui.height', h)
        for pane in [self.side_pane, self.bottom_pane]:
            c.set('win.%s_visible' % pane.__class__.__name__.lower(),
                  pane.get_property('visible'))
        fname = os.path.join(USER_CONFIG_DIR, 'shortcuts.map')
        gtk.accel_map_save(fname)
Exemplo n.º 2
0
class MainWindow(gtk.Window):
    """Application main window.

    Instance attributes:
      menubar: Menu bar.

    :Signals:
    active-editor-changed
      ``def callback(instance, editor)``
      Emitted when the active editor has changed. editor is either an
      editor instance or None.
    """

    __gsignals__ = {
        'active-editor-changed': (gobject.SIGNAL_RUN_LAST,
                                  gobject.TYPE_NONE,
                                  (gobject.TYPE_PYOBJECT,)),
        'editor-created': (gobject.SIGNAL_RUN_LAST,
                           gobject.TYPE_NONE,
                           (gobject.TYPE_PYOBJECT,)),
    }

    def __init__(self, app, create_editor=True):
        gtk.Window.__init__(self)
        self.app = app
        self._editor = None
        self._editor_conn_tag = None  # Signal ID
        self._tracked_conn = None  # Tracked connection
        self._editors = []
        self.clipboard = gtk.clipboard_get()
        self.ui = gtk.UIManager()
        # See: http://www.daa.com.au/pipermail/pygtk/2006-May/012267.html
        self.ui.connect('connect-proxy', self.on_ui_connect_proxy)
        self.ui.connect('disconnect-proxy', self.on_ui_disconnect_proxy)
        self._init_actions()
        self.ui.add_ui_from_string(UI)
        self.ui.ensure_update()
        self.set_title("CrunchyFrog %s" % release.version)
        self.set_icon_list(*utils.get_logo_icon_list())
        self._init_elements()
        self.connect('window-state-event', self.on_window_state_event)
        self.connect('delete-event', self.on_quit)
        # XXX disabled: it results in unexpected behavior...
        if create_editor and 1 == 2:
            self.editor_create()
        self.state_restore()

    def _init_actions(self):
        group = gtk.ActionGroup('instance')
        group.set_data('cf::label', _(u'Application'))
        entries = (
            # File
            ('file-menu-action', None, _(u'_File')),
            ('instance-quit', gtk.STOCK_QUIT,
             None, '<control>Q', _(u'Quit the program'),
             self.on_quit),
            ('file-new-menu-action', None, _(u'_New')),
            ('instance-new-editor', gtk.STOCK_NEW,
             _(u'_Query'), '<control>N', _(u'New SQL editor'),
               self.on_editor_new),
            ('instance-new-mainwin', None,
             _(u'_Window'), '<shift><control>N',
             _('New application window.'),
             self.on_instance_new),
            ('file-open', gtk.STOCK_OPEN,
             None, '<control>O', _(u'Open a file'),
             self.on_open_file),
            # Edit
            ('edit-menu-action', None, _(u'_Edit')),
            ('instance-plugins', None,
             _(u'_Plugins'), None, _(u'Configure plugins'),
             lambda action: self.app.preferences_show(self, 'plugins')),
            ('instance-datasourcemanager', None, _(u'_Data Sources'),
             None, _(u'Add, edit and delete data sources'),
             self.on_datasource_manager),
            ('instance-preferences', gtk.STOCK_PREFERENCES,
             None, None, _(u'Configure the application'),
             lambda action: self.app.preferences_show(self)),
            # Query
            ('query-menu-action', None, _(u'_Query')),
            # View
            ('view-menu-action', None, _(u'_View')),
            # Help
            ('help-menu-action', None, _(u'_Help')),
            ('help-help', gtk.STOCK_HELP,
             None, 'F1', _(u'Open the CrunchyFrog manual'),
             lambda *a: self.app.show_help()),
            ('help-translate', None,
             _(u'Help translate...'), None,
             _(u'Help to translate this application'),
             lambda a:
             webbrowser.open('https://translations.launchpad.net/crunchyfrog'),
             ),
            ('help-bugs', None,
             _(u'Report a problem'), None,
             _((u'Help to improve this application by reporting a bug '
                u'or a feature request.')),
             lambda a:
             webbrowser.open(('http://code.google.com/p/crunchyfrog/'
                              'issues/entry'))),
            ('help-about', gtk.STOCK_ABOUT,
             None, None, _(u'About this application'),
             self.on_about),
        )
        group.add_actions(entries)
        toggle_entries = (
            ('instance-toggle-toolbar', None,
             _(u'_Toolbar'), None,
             _(u'Show or hide the toolbar in the current window'),
             self.on_toggle_toolbar),
            ('instance-toggle-statusbar', None,
             _(u'_Statusbar'), None,
             _(u'Show or hide the statusbar in the current window'),
             self.on_toggle_statusbar),
        )
        group.add_toggle_actions(toggle_entries)
        # Editor switcher
        entries = []
        for i in range(1, 11):
            if i == 10:
                i = 0
            entry = ('activate-editor%d' % i, None, '', '<Alt>%d' % i, None,
                     i-1)
            entries.append(entry)
        group.add_radio_actions(entries, 0, self.on_editor_set_focus)
        self.ui.insert_action_group(group, -1)
        group = gtk.ActionGroup('editor')
        group.set_data('cf::label', _(u'Editor'))
        entries = (
            ('editor-close', gtk.STOCK_CLOSE,
             None, '<control>W', _(u'Close the current editor'),
             self.on_editor_close),
            ('editor-close-all', None,
             _(u'Close _all'), '<control><shift>W', _(u'Close all editors'),
             lambda action: self.close_all_editors()),
            # Query
            ('query-connection-menu-action', None, _(u'_Connection')),
            ('query-connection-disconnect', gtk.STOCK_DISCONNECT,
             None, '<shift>F8',
             _(u'Disconnect current editor'),
             self.on_editor_disconnect),
            ('query-show-connections', None,
             _(u'Show connections'), '<shift>F5',
             _(u'Open or close connections in a dialog'),
             lambda *a: self.app.manage_connections(self)),
            ('query-next-statement', None,
             _(u'Next Statement'), '<control><alt>j',
             _(u'Move cursor to next statement'),
             self.on_jump_next_statement),
            ('query-prev-statement', None,
             _(u'Previous Statement'), '<control><alt>k',
             _(u'Move cursor to previous statement'),
             self.on_jump_prev_statement),
            ('query-frmt-menu', None, _(u'_Format')),
            ('query-frmt-comment', None,
             _(u'_Comment / Uncomment'), '<shift><control>space',
             _(u'Comment / uncomment selected lines'),
             self.on_frmt_comment),
            ('query-frmt-format', None,
             _(u'_Format'), '<shift><control>f',
             _(u'Beautify selected lines or whole buffer'),
             self.on_frmt_format),
            ('query-frm-uppercase-kw', None,
             _(u'_Uppercase Keywords'), '<shift><control>u',
             _(u'Uppercase keywords in selected lines or in whole buffer'),
             self.on_frmt_uppercase_kw),
            ('query-toggle-results', None,
             _(u'Show / Hide Results'), '<shift>F9',
             _(u'Show or hide results pane'),
             self.on_editor_toggle_results),
            ('file-save', gtk.STOCK_SAVE,
             None, '<control>S', _(u'Save the current file'),
             self.on_file_save),
            ('file-save-as', gtk.STOCK_SAVE_AS,
             None, '<shift><control>S',
             _(u'Save the current file with a different name'),
             self.on_file_save_as),
            ('editor-print', gtk.STOCK_PRINT,
             None, '<control>P', _(u'Print the current page'),
             self.on_editor_print),
            ('editor-printpreview', gtk.STOCK_PRINT_PREVIEW,
             None, '<shift><control>P', _(u'Print preview'),
             self.on_editor_print_preview),
        )
        group.add_actions(entries)
        group.set_sensitive(False)
        self.ui.insert_action_group(group, 0)
        # Clipboard
        group = gtk.ActionGroup('clipboard')
        entries = (
            ('clipboard-cut', gtk.STOCK_CUT,
             None, '<control>X', _(u'Cut the selection'),
             self.on_clipboard_cut),
            ('clipboard-copy', gtk.STOCK_COPY,
             None, '<control>C', _(u'Copy the selection'),
             self.on_clipboard_copy),
            ('clipboard-paste', gtk.STOCK_PASTE,
             None, '<control>V', _(u'Paste the clipboard'),
             self.on_clipboard_paste),
            )
        group.add_actions(entries)
        self.ui.insert_action_group(group, 0)
        group = gtk.ActionGroup('query')
        group.set_data('cf::label', _(u'Queries'))
        entries = (
            ('query-execute', gtk.STOCK_EXECUTE,
             None, 'F5', _(u'Execute statements in SQL editor'),
             self.on_query_execute),
            ('query-execute-current', None,
             _(u'Exec_ute Current Statement'), '<control>F5',
             _(u'Executes statement at cursor'),
             self.on_query_execute_current),
            ('query-begin', gtk.STOCK_INDENT,
             _(u'Transaction'), 'F6',
             _(u'Begin transaction on current connection'),
             self.on_begin_transaction),
            ('query-commit', gtk.STOCK_APPLY,
             _(u'Commit'), 'F7',
             _(u'Commit current transaction'),
             self.on_commit),
            ('query-rollback', gtk.STOCK_UNDO,
             _(u'Rollback'), 'F8',
             _(u'Rollback current transaction'),
             self.on_rollback),
        )
        group.add_actions(entries)
        group.set_sensitive(False)
        self.ui.insert_action_group(group, -1)
        self.add_accel_group(self.ui.get_accel_group())

    def _init_elements(self):
        """Initialize main window elements."""
        self._init_statusbar()
        self._init_panes()
        vbox = gtk.VBox()
        self.add(vbox)
        vbox.pack_start(self._init_menubar(), False, False)
        vbox.pack_start(self._init_toolbar(), False, False)
        hpaned = gtk.HPaned()
        vbox.pack_start(hpaned, True, True)
        hpaned.show()
        vpaned = gtk.VPaned()
        hpaned.add2(vpaned)
        vpaned.show()
        hpaned.add1(self.side_pane)
        vpaned.add2(self.bottom_pane)
        self.queries = pane.CenterPane(self)
        vpaned.add1(self.queries)
        self.queries.show()
        # Connect to realize to set paned position when window is ready.
        self.connect('realize', self.on_set_paned_position, vpaned)
        self.connect('realize', self.on_set_paned_position, hpaned)
        vbox.pack_start(self.statusbar, False, False)
        vbox.show()
        self.browser = Browser(self.app, self)
        self.side_pane.add_item(self.browser)
        self.side_pane.set_active_item('navigator')
        menu = self.ui.get_widget('/MenuBar/Query/Connection')
        menu.connect('activate', self.on_connection_menu_activate)
        menu = self.ui.get_widget('/MenuBar/Query')
        menu.connect('activate', self.on_query_menu_activate)
        self._init_file_open()

    def _init_file_open(self):
        """Initialize file open menu item and toolbar button."""
        ph = self.ui.get_widget("/ToolBar/OpenFile")
        btn = gtk.MenuToolButton(gtk.STOCK_OPEN)
        btn.show()
        btn.connect('clicked', self.on_open_file)
        btn.set_menu(self._get_recent_menu())
        idx = self.toolbar.get_item_index(ph)
        self.toolbar.insert(btn, idx)
        ph = self.ui.get_widget('/MenuBar/File/RecentFiles')
        menu = self._get_recent_menu()
        menuitem = gtk.MenuItem(_(u'_Recent Files'))
        menuitem.show()
        menuitem.set_submenu(menu)
        file_menu = self.ui.get_widget('/MenuBar/File').get_submenu()
        for idx, child in enumerate(file_menu.get_children()):
            if child == ph:
                file_menu.insert(menuitem, idx)
                return

    def _init_menubar(self):
        """Create and return the applications menubar."""
        self.menubar = self.ui.get_widget('/MenuBar')
        self.menubar.show_all()
        return self.menubar

    def _init_panes(self):
        """Init side and bottom pane."""
        self.side_pane = pane.SidePane(self)
        self.bottom_pane = pane.BottomPane(self)

    def _init_statusbar(self):
        """Create and return the statusbar."""
        self.statusbar = CrunchyStatusbar(self.app, self)
        self.statusbar.show()
        return self.statusbar

    def _init_toolbar(self):
        """Create and return the toolbar."""
        self.toolbar = self.ui.get_widget("/ToolBar")
        self.toolbar.show_all()
        ph = self.ui.get_widget("/ToolBar/EditorConnection")
        self.tb_conn_chooser = widgets.ConnectionButton(self)
        idx = self.toolbar.get_item_index(ph)
        self.toolbar.insert(self.tb_conn_chooser, idx)
        self.tb_conn_chooser.show_all()
        separator = gtk.SeparatorToolItem()
        self.toolbar.insert(separator, idx+1)
        separator.show()
        return self.toolbar

    def _get_action(self, action_name):
        """Return a action regardless of groups."""
        for group in self.ui.get_action_groups():
            action = group.get_action(action_name)
            if action is not None:
                return action
        return None

    def _get_clipboard(self):
        return self.clipboard

    def _get_recent_menu(self, limit=None, recent_menu=None):
        """Return a recent file menu."""
        if recent_menu is None:
            recent_menu = gtk.RecentChooserMenu(self.app.recent_manager)
        filter_ = gtk.RecentFilter()
        filter_.add_mime_type("text/x-sql")
        recent_menu.add_filter(filter_)
        recent_menu.set_filter(filter_)
        recent_menu.set_show_not_found(False)
        recent_menu.set_sort_type(gtk.RECENT_SORT_MRU)
        recent_menu.connect("item-activated", self.on_recent_item_activated)
        if limit is not None:
            recent_menu.set_limit(limit)
        return recent_menu

    # ---
    # Callbacks
    # ---
    def on_about(self, *args):
        def open_url(dialog, url):
            gtk.show_uri(dialog.get_screen(), url,
                         gtk.gdk.x11_get_server_time(dialog.window))
        gtk.about_dialog_set_url_hook(open_url)
        dlg = gtk.AboutDialog()
        dlg.set_name(release.name)
        dlg.set_version(release.version)
        dlg.set_copyright(release.copyright)
        dlg.set_license(release.license)
        dlg.set_website(release.url)
        dlg.set_website_label(release.url)
        dlg.set_logo(utils.get_logo_icon(96))
        dlg.set_program_name(release.appname)
        dlg.set_translator_credits(release.translators)
        dlg.run()
        dlg.destroy()


    def on_begin_transaction(self, *args):
        if not self._editor:
            return
        gobject.idle_add(self._editor.begin_transaction)

    def on_clipboard_copy(self, *args):
        if not self._editor:
            return
        self._editor.clipboard_copy(self._get_clipboard())

    def on_clipboard_cut(self, *args):
        if not self._editor:
            return
        self._editor.clipboard_cut(self._get_clipboard())

    def on_clipboard_paste(self, *args):
        if not self._editor:
            return
        self._editor.clipboard_paste(self._get_clipboard())

    def on_commit(self, *args):
        if not self._editor:
            return
        gobject.idle_add(self._editor.commit)

    def on_connection_menu_activate(self, menuitem):
        widgets.rebuild_connection_menu(menuitem.get_submenu(), self,
                                        self.get_active_editor())

    def on_connection_notify(self, connection, property):
        if property.name == 'transaction-state':
            value = connection.get_property(property.name)
            gobject.idle_add(self.set_transaction_state, value, connection)

    def on_datasource_manager(self, *args):
        dlg = DatasourcesDialog(self.app, self)
        dlg.run()
        dlg.destroy()

    def on_editor_close(self, *args):
        editor = self.get_active_editor()
        if editor is not None:
            editor.close()

    def on_editor_connection_changed(self, editor, connection):
        if self._tracked_conn:
            sig = self._tracked_conn.get_data('cf::mainwin::notify')
            if sig:
                self._tracked_conn.disconnect(sig)
            self._tracked_conn = None
        for group in self.ui.get_action_groups():
            if group.get_name() == 'query':
                group.set_sensitive(connection is not None)
        if connection:
            self.set_title(connection.get_label()+' - CrunchyFrog')
        else:
            self.set_title('CrunchyFrog %s' % release.version)
        # Set transaction state
        if connection:
            self._tracked_conn = connection
            sig = self._tracked_conn.connect('notify',
                                             self.on_connection_notify)
            self._tracked_conn.set_data('cf::mainwin::notify', sig)
            state = connection.get_property('transaction-state')
            self.set_transaction_state(state, connection)

    def on_editor_disconnect(self, action):
        editor = self.get_active_editor()
        if editor is None or editor.connection is None:
            return
        editor.set_connection(None)

    def on_editor_new(self, *args):
        editor = self.editor_create()
        # FIXME(andi): Updating editor switcher action should be in a
        #              separate function.
        self.on_query_menu_activate(None)

    def on_editor_print(self, action):
        self.get_active_editor().print_contents()

    def on_editor_print_preview(self, action):
        self.get_active_editor().print_contents(preview=True)

    def on_editor_set_focus(self, first_action, current):
        queries_idx = current.get_current_value()
        if queries_idx == -1:
            queries_idx = 9
        if self.queries.get_n_pages()-1 < queries_idx:
            return
        self.queries.set_current_page(queries_idx)
        editor = self.queries.get_nth_page(queries_idx)
        focus_child = editor.get_focus_child()
        if focus_child is not None:
            focus_child.grab_focus()

    def on_editor_toggle_results(self, action):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.toggle_results_pane()

    def on_frmt_comment(self, action):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.selected_lines_toggle_comment()

    def on_frmt_format(self, action):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.selected_lines_quick_format()

    def on_frmt_uppercase_kw(self, action):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.selected_lines_quick_format(keyword_case='upper')

    def on_file_save(self, *args):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.save_file(self)

    def on_file_save_as(self, *args):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.save_file_as(self)

    def on_instance_new(self, action):
        self.app.new_instance()

    def on_jump_next_statement(self, action):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.rjump_to_statement(1)

    def on_jump_prev_statement(self, action):
        editor = self.get_active_editor()
        if editor is None:
            return
        editor.rjump_to_statement(-1)

    def on_menu_item_deselect(self, menuitem):
        self.statusbar.pop(100)

    def on_menu_item_select(self, menuitem, tooltip):
        self.statusbar.push(100, tooltip)

    def on_open_file(self, *args):
        dlg = gtk.FileChooserDialog(_(u"Select file"),
                            self,
                            gtk.FILE_CHOOSER_ACTION_OPEN,
                            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                             gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        dlg.set_current_folder(self.app.config.get("editor.recent_folder", ""))
        dlg.set_select_multiple(True)
        filter = gtk.FileFilter()
        filter.set_name(_(u"All files (*)"))
        filter.add_pattern("*")
        dlg.add_filter(filter)
        filter = gtk.FileFilter()
        filter.set_name(_(u"SQL files (*.sql)"))
        filter.add_pattern("*.sql")
        dlg.add_filter(filter)
        dlg.set_filter(filter)
        recent_chooser = gtk.RecentChooserWidget(self.app.recent_manager)
        filter = gtk.RecentFilter()
        filter.add_mime_type("text/x-sql")
        filter.set_name(_(u"SQL files (*.sql)"))
        recent_chooser.add_filter(filter)
        recent_chooser.set_filter(filter)
        recent_chooser.set_show_not_found(False)
        recent_chooser.set_sort_type(gtk.RECENT_SORT_MRU)
        recent_chooser.set_show_icons(True)
        recent_chooser.set_show_tips(True)
        def dlg_set_uri(chooser, dlg):
            if chooser.get_current_uri():
                dlg.set_uri(chooser.get_current_uri())
        recent_chooser.connect("selection-changed", dlg_set_uri, dlg)
        recent_chooser.connect("item-activated",
                               lambda chooser: dlg.response(gtk.RESPONSE_OK))
        exp = gtk.Expander(_(u"_Recent files:"))
        exp.add(recent_chooser)
        exp.set_use_underline(True)
        dlg.set_extra_widget(exp)
        recent_chooser.show()
        if dlg.run() == gtk.RESPONSE_OK:
            [self.editor_create(fname) for fname in dlg.get_filenames()]
            self.app.config.set("editor.recent_folder",
                                dlg.get_current_folder())
        dlg.destroy()

    def on_preferences(self, *args):
        self.app.preferences_show()

    def on_query_execute(self, *args):
        self.get_active_editor().execute_query()

    def on_query_execute_current(self, action):
        self.get_active_editor().execute_query(True)

    def on_query_menu_activate(self, menuitem):
        self._rebuild_activate_editor_actions()

    def _rebuild_activate_editor_actions(self):
        for idx in range(1, 11):
            if idx == 10:
                idx = 0
                page_idx = 9
            else:
                page_idx = idx - 1
            action = self._get_action('activate-editor%d' % idx)
            page = self.queries.get_nth_page(page_idx)
            if page is None:
                action.set_visible(False)
                action.set_sensitive(False)
            else:
                action.set_visible(True)
                action.set_sensitive(True)
                tab = self.queries.get_tab_label(page)
                lbl = tab.label.get_text()
                if len(lbl) > 16:
                    lbl = lbl[:15]+'...'
                action.props.label = lbl.replace('_', '__')
                is_active = bool(page_idx == self.queries.get_current_page())
                action.set_active(is_active)

    def on_quit(self, *args):
        if not self.close_all_editors():
            return False
        if len(self.app.get_instances()) <= 1:
            self.state_save()
        self.destroy()

    def on_recent_item_activated(self, chooser):
        self.editor_create(chooser.get_current_uri())

    def on_rollback(self, *args):
        if not self._editor:
            return
        gobject.idle_add(self._editor.rollback)

    def on_set_paned_position(self, win, paned):
        if isinstance(paned, gtk.VPaned):
            opt = 'win.bottompane_position'
        else:
            opt = 'win.sidepane_position'
        w, h = win.get_size()
        stored_pos = self.app.config.get(opt, None)
        if stored_pos is None:
            if isinstance(paned, gtk.VPaned):
                stored_pos = h-250
            else:
                stored_pos = 250
        paned.set_position(stored_pos)
        paned.connect('notify::position',
                      lambda paned, prop:
                      self.app.config.set(opt, paned.get_position()))

    def on_toggle_statusbar(self, toggle_action):
        if toggle_action.get_active():
            self.statusbar.show()
        else:
            self.statusbar.hide()
        self.app.config.set('win.statusbar_visible',
                            toggle_action.get_active())

    def on_toggle_toolbar(self, toggle_action):
        if toggle_action.get_active():
            self.toolbar.show()
        else:
            self.toolbar.hide()
        self.app.config.set('win.toolbar_visible',
                            toggle_action.get_active())

    def on_ui_connect_proxy(self, ui, action, widget):
        tooltip = action.get_property('tooltip')
        if isinstance(widget, gtk.MenuItem) and tooltip:
            cid = widget.connect('select', self.on_menu_item_select, tooltip)
            cid2 = widget.connect('deselect', self.on_menu_item_deselect)
            widget.set_data('cf::cids', (cid, cid2))

    def on_ui_disconnect_proxy(self, ui, action, widget):
        cids = widget.get_data('cf::cids') or ()
        for cid in cids:
            widget.disconnect(cid)

    def on_window_state_event(self, window, event):
        val_names = event.new_window_state.value_names
        c = self.app.config
        c.set('gui.maximized',
              gtk.gdk.WINDOW_STATE_MAXIMIZED.value_names[0] in val_names)

    # ---
    # Public methods
    # ---

    def close_all_editors(self):
        """Closes all editors but checks for changes first.

        Returns:
          False if action was cancelled, otherwise True.
        """
        changed = []
        for item in self.queries.get_all_editors():
            if isinstance(item, Editor) and item.contents_changed():
                changed.append(item)
        if changed:
            dlg = ConfirmSaveDialog(self, changed)
            proceed = dlg.run()
            if proceed == 1:
                dlg.hide()
                if not dlg.save_files():
                    proceed = 0
            dlg.destroy()
            if proceed == 0:
                return False
        for item in self.queries.get_all_editors():
            item.close(force=True)
        return True

    def get_editors(self):
        """Return a list of all SQL editors."""
        return [item for item in self.queries.get_all_editors()
                if isinstance(item, Editor)]

    def editor_create(self, fname=None):
        """Creates a new SQL editor.

        Arguments:
          fname: If given, the file fname is opened with this editor.

        Returns:
          An Editor instance.
        """
        last_conn = None
        if self.app.config.get('editor.reuse_connection'):
            cur_editor = self.get_active_editor()
            if cur_editor is not None:
                last_conn = cur_editor.get_connection()
        editor = Editor(self)
        if last_conn is not None:
            editor.set_connection(last_conn)
        if fname:
            scheme, _, path, _, _, _ = urlparse.urlparse(fname)
            fname = path
            editor.set_filename(fname)
        self.editor_append(editor)
        editor.show_all()
        editor.textview.grab_focus()
        return editor

    def editor_append(self, editor):
        """Adds an editor to the editors notebook.

        Arguments:
          editor: Editor instance.
        """
        # Cleanup other instances
        for instance in self.app.get_instances():
            if instance != self and editor in instance._editors:
                instance._editors.remove(editor)
        self.queries.add_item(editor)
        self._editors.append(editor)

    def editor_remove(self, editor):
        """Removes an editor from this instance."""
        if editor in self._editors:
            self._editors.remove(editor)

    def get_active_editor(self):
        """Returns the active editor.

        Returns:
          Editor instance or None.
        """
        if isinstance(self._editor, Editor):
            return self._editor
        return None

    def set_editor_active(self, editor, active):
        """Called whenever an editor receives or looses focus."""
        if not active:
            editor = None
        if self._editor_conn_tag and self._editor:
            self._editor.disconnect(self._editor_conn_tag)
            self._editor_conn_tag = None
        if self._editor:
            handler_id = self._editor.get_data('cf::sig_editor_buffer_changed')
            if handler_id:
                self._editor.disconnect(handler_id)
                self._editor.set_data('cf::sig_editor_buffer_changed', None)
        self._editor = editor
        if self._editor and isinstance(self._editor, Editor):
            self._editor_conn_tag = self._editor.connect(
                "connection-changed", self.on_editor_connection_changed)
            self.on_editor_connection_changed(
                self._editor, self._editor.connection)
            conn = self._editor.connection
            if conn:
                prop =conn.get_property('transaction-state')
                self.set_transaction_state(prop, conn)
            # XXX add buffer selection changed cb to update copy&paste btns
        else:
            self.set_title('CrunchyFrog %s' % release.version)
        for group in self.ui.get_action_groups():
            if group.get_name() == 'editor':
                group.set_sensitive(isinstance(self._editor, Editor))
            elif group.get_name() == 'query':
                group.set_sensitive(bool(isinstance(self._editor, Editor)
                                         and self._editor.connection))
        sensitive = bool((self._editor
                          and isinstance(self._editor, Editor)))
        action = self._get_action('file-save')
        action.set_sensitive(sensitive)
        action = self._get_action('file-save-as')
        action.set_sensitive(sensitive)
        self.tb_conn_chooser.set_editor(editor)
        self.app.plugins.editor_notify(editor, self)
        self.emit('active-editor-changed', editor)
        if editor is not None and isinstance(editor, Editor):
            editor.textview.grab_focus()

    def set_transaction_state(self, value, connection):
        """Adjusts the transactions state in the UI."""
        # A regression: If value is None that means we have no connection,
        # but this is already handled by setting the whole action group
        # insensitive.
        if value is None or connection is None:
            return
        commit = self._get_action('query-commit')
        rollback = self._get_action('query-rollback')
        begin = self._get_action('query-begin')
        commit.set_sensitive((value & TRANSACTION_COMMIT_ENABLED) != 0)
        rollback.set_sensitive((value & TRANSACTION_ROLLBACK_ENABLED) != 0)
        begin.set_sensitive((value & TRANSACTION_IDLE) != 0)

    def state_restore(self):
        """Restore window state."""
        conf = self.app.config
        action = self._get_action('instance-toggle-statusbar')
        action.set_active(conf.get('win.statusbar_visible', True))
        self.on_toggle_statusbar(action)
        action = self._get_action('instance-toggle-toolbar')
        action.set_active(conf.get('win.toolbar_visible', True))
        self.on_toggle_toolbar(action)
        self.side_pane.state_restore()
        self.bottom_pane.state_restore()
        width = conf.get('gui.width')
        height = conf.get('gui.height')
        if not width or width == -1:
            width = 800
        if not height or height == -1:
            height = 600
        self.resize(width, height)
        if conf.get('gui.maximized', False):
            self.maximize()
        fname = os.path.join(USER_CONFIG_DIR, 'shortcuts.map')
        if os.path.isfile(fname):
            gtk.accel_map_load(fname)

    def state_save(self):
        """Save window state to config."""
        c = self.app.config
        w, h = self.get_size()
        c.set('gui.width', w)
        c.set('gui.height', h)
        for pane in [self.side_pane, self.bottom_pane]:
            c.set('win.%s_visible' % pane.__class__.__name__.lower(),
                  pane.get_property('visible'))
        fname = os.path.join(USER_CONFIG_DIR, 'shortcuts.map')
        gtk.accel_map_save(fname)
Exemplo n.º 3
0
 def _init_statusbar(self):
     """Create and return the statusbar."""
     self.statusbar = CrunchyStatusbar(self.app, self)
     self.statusbar.show()
     return self.statusbar
Exemplo n.º 4
0
 def _init_statusbar(self):
     """Create and return the statusbar."""
     self.statusbar = CrunchyStatusbar(self.app, self)
     self.statusbar.show()
     return self.statusbar