Ejemplo n.º 1
0
class GuiApp(Gtk.Application, FaradayUi):
    """
    Creates the application and has the necesary callbacks to FaradayUi
    As far as the GUI goes, this handles only the menu, everything is else is
    appWindow's resposibility. All logic by the main window should be done
    here. Some of the logic on the dialogs is implemented in the dialogs own
    class. Some dialogs are shown by the appwindow to handle errors coming
    from other threads outside GTK's.
    """
    def __init__(self, model_controller, plugin_manager, workspace_manager,
                 plugin_controller):
        """Does not do much. Most of the initialization work is actually
        done by the run() method, as specified in FaradayUi."""

        FaradayUi.__init__(self, model_controller, plugin_manager,
                           workspace_manager, plugin_controller)

        Gtk.Application.__init__(self,
                                 application_id="org.infobyte.faraday",
                                 flags=Gio.ApplicationFlags.FLAGS_NONE)

        self.icons = CONF.getImagePath() + "icons/"
        faraday_icon = self.icons + "faraday_icon.png"
        self.icon = GdkPixbuf.Pixbuf.new_from_file_at_scale(
            faraday_icon, 16, 16, False)
        self.window = None
        self.model_controller = model_controller
        self.conflicts = self.model_controller.getConflicts()

    def getMainWindow(self):
        """Returns the main window. This is none only at the
        the startup, the GUI will create one as soon as do_activate() is called
        """
        return self.window

    def updateConflicts(self):
        """Reassings self.conflicts with an updated list of conflicts"""
        self.conflicts = self.model_controller.getConflicts()

    def updateHosts(self):
        """Reassings the value of self.all_hosts to a current one to
        catch workspace changes, new hosts added via plugins or any other
        external interference with out host list"""
        self.all_hosts = self.model_controller.getAllHosts()

    def createWorkspace(self, name, description="", w_type=""):
        """Pretty much copy/pasted from the QT3 GUI.
        Uses the instance of workspace manager passed into __init__ to
        get all the workspaces names and see if they don't clash with
        the one the user wrote. If everything's fine, it saves the new
        workspace and returns True. If something went wrong, return False"""

        if name in self.getWorkspaceManager().getWorkspacesNames():

            model.api.log("A workspace with name %s already exists" % name,
                          "ERROR")
            status = True
        else:
            model.api.log("Creating workspace '%s'" % name)
            model.api.devlog("Looking for the delegation class")
            manager = self.getWorkspaceManager()
            try:
                w = manager.createWorkspace(name, description,
                                            manager.namedTypeToDbType(w_type))
                CONF.setLastWorkspace(w.name)
                CONF.saveConfig()
                status = True
            except Exception as e:
                status = False
                model.guiapi.notification_center.showDialog(str(e))

        return status

    def removeWorkspace(self, button, ws_name):
        """Removes a workspace. If the workspace to be deleted is the one
        selected, it moves you first to the default. The clears and refreshes
        sidebar"""

        model.api.log("Removing Workspace: %s" % ws_name)
        if CONF.getLastWorkspace() == ws_name:
            self.openDefaultWorkspace()
        self.getWorkspaceManager().removeWorkspace(ws_name)
        self.ws_sidebar.clearSidebar()
        self.ws_sidebar.refreshSidebar()

    def do_startup(self):
        """
        GTK calls this method after Gtk.Application.run()
        Creates instances of the sidebar, terminal, console log and
        statusbar to be added to the app window.
        Sets up necesary actions on menu and toolbar buttons
        Also reads the .xml file from menubar.xml
        """
        Gtk.Application.do_startup(self)  # deep GTK magic

        self.ws_sidebar = WorkspaceSidebar(self.workspace_manager,
                                           self.changeWorkspace,
                                           self.removeWorkspace,
                                           self.on_new_button,
                                           CONF.getLastWorkspace())

        self.updateHosts()
        self.hosts_sidebar = HostsSidebar(self.show_host_info, self.icons)
        default_model = self.hosts_sidebar.create_model(self.all_hosts)
        self.hosts_sidebar.create_view(default_model)

        self.sidebar = Sidebar(self.ws_sidebar.get_box(),
                               self.hosts_sidebar.get_box())

        host_count, service_count, vuln_count = self.update_counts()

        self.terminal = Terminal(CONF)
        self.console_log = ConsoleLog()
        self.statusbar = Statusbar(self.on_click_notifications,
                                   self.on_click_conflicts, host_count,
                                   service_count, vuln_count)

        self.notificationsModel = Gtk.ListStore(str)

        action = Gio.SimpleAction.new("about", None)
        action.connect("activate", self.on_about)
        self.add_action(action)

        action = Gio.SimpleAction.new("help", None)
        action.connect("activate", self.on_help)
        self.add_action(action)

        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

        action = Gio.SimpleAction.new("preferences", None)
        action.connect("activate", self.on_preferences)
        self.add_action(action)

        action = Gio.SimpleAction.new("pluginOptions", None)
        action.connect("activate", self.on_pluginOptions)
        self.add_action(action)

        action = Gio.SimpleAction.new("new", None)
        action.connect("activate", self.on_new_button)
        self.add_action(action)

        action = Gio.SimpleAction.new("new_terminal")  # new terminal = new tab
        action.connect("activate", self.on_new_terminal_button)
        self.add_action(action)

        action = Gio.SimpleAction.new("open_report")
        action.connect("activate", self.on_open_report_button)
        self.add_action(action)

        dirname = os.path.dirname(os.path.abspath(__file__))
        builder = Gtk.Builder.new_from_file(dirname + '/menubar.xml')
        builder.connect_signals(self)
        appmenu = builder.get_object('appmenu')
        self.set_app_menu(appmenu)
        helpMenu = builder.get_object('Help')
        self.set_menubar(helpMenu)

    def do_activate(self):
        """If there's no window, create one and present it (show it to user).
        If there's a window, just present it. Also add the log handler
        and the notifier to the application"""

        # We only allow a single window and raise any existing ones
        if not self.window:
            # Windows are associated with the application
            # when the last one is closed the application shuts down
            self.window = AppWindow(self.sidebar,
                                    self.terminal,
                                    self.console_log,
                                    self.statusbar,
                                    application=self,
                                    title="Faraday")

        self.window.set_icon(self.icon)
        self.window.present()

        self.loghandler = GUIHandler()
        model.guiapi.setMainApp(self)
        addHandler(self.loghandler)
        self.loghandler.registerGUIOutput(self.window)

        notifier = model.log.getNotifier()
        notifier.widget = self.window
        model.guiapi.notification_center.registerWidget(self.window)

    def postEvent(self, receiver, event):
        """Handles the events from gui/customevents."""
        if receiver is None:
            receiver = self.getMainWindow()

        elif event.type() == 3131:  # new log event
            receiver.emit("new_log", event.text)

        elif event.type() == 3141:  # new conflict event
            receiver.emit("set_conflict_label", event.nconflicts)

        elif event.type() == 5100:  # new notification event
            self.notificationsModel.prepend([event.change.getMessage()])
            receiver.emit("new_notif")
            host_count, service_count, vuln_count = self.update_counts()
            receiver.emit("update_ws_info", host_count, service_count,
                          vuln_count)

        elif event.type() == 4100 or event.type(
        ) == 3140:  # newinfo or changews
            host_count, service_count, vuln_count = self.update_counts()
            self.updateHosts()
            self.hosts_sidebar.update(self.all_hosts)
            receiver.emit("update_ws_info", host_count, service_count,
                          vuln_count)

        elif event.type() == 3132:  # error
            self.window.emit("normal_error", event.text)

        elif event.type() == 3134:  # important error, uncaught exception
            self.window.prepare_important_error(event)
            self.window.emit("important_error")

    def update_counts(self):
        """Update the counts for host, services and vulns"""
        host_count = self.model_controller.getHostsCount()
        service_count = self.model_controller.getServicesCount()
        vuln_count = self.model_controller.getVulnsCount()
        return host_count, service_count, vuln_count

    def on_open_report_button(self, action, param):
        """What happens when the user clicks the open report button.
        A dialog will present itself with a combobox to select a plugin.
        Then a file chooser to select a report. The report will be processed
        with the selected plugin.
        """
        def select_plugin():
            """Creates a simple dialog with a combo box to select a plugin"""
            plugins_id = [_id for _id in self.plugin_manager.getPlugins()]
            plugins_id = sorted(plugins_id)
            dialog = Gtk.Dialog("Select plugin", self.window, 0)

            combo_box = Gtk.ComboBoxText()
            for plugin_id in plugins_id:
                combo_box.append_text(plugin_id)
            combo_box.show()

            dialog.vbox.pack_start(combo_box, True, True, 10)

            dialog.add_button("Cancel", Gtk.ResponseType.DELETE_EVENT)
            dialog.add_button("OK", Gtk.ResponseType.ACCEPT)

            response = dialog.run()
            selected = combo_box.get_active_text()

            dialog.destroy()
            return response, selected

        def on_file_selected(plugin_id, report):
            """Send the plugin_id and the report file to be processed"""
            self.report_manager.sendReportToPluginById(plugin_id, report)

        plugin_response, plugin_id = select_plugin()

        while plugin_response == Gtk.ResponseType.ACCEPT and plugin_id is None:
            # force user to select a plugin if he did not do it
            errorDialog(self.window,
                        "Please select a plugin to parse your report!")
            plugin_response, plugin_id = select_plugin()
        else:
            if plugin_response == Gtk.ResponseType.ACCEPT:
                dialog = Gtk.FileChooserNative()
                dialog.set_title("Import a report")
                dialog.set_modal(True)
                dialog.set_transient_for(self.window)
                dialog.set_action(Gtk.FileChooserAction.OPEN)

                res = dialog.run()
                if res == Gtk.ResponseType.ACCEPT:
                    on_file_selected(plugin_id, dialog.get_filename())
                dialog.destroy()

    def on_about(self, action, param):
        """ Defines what happens when you press 'about' on the menu"""

        about_dialog = aboutDialog(self.window)
        about_dialog.run()
        about_dialog.destroy()

    def on_help(self, action, param):
        """Defines what happens when user press 'help' on the menu"""

        help_dialog = helpDialog(self.window)
        help_dialog.run()
        help_dialog.destroy()

    def on_preferences(self, action, param):
        """Defines what happens when you press 'preferences' on the menu.
        Sends as a callback reloadWsManager, so if the user actually
        changes her Couch URL, the sidebar will reload reflecting the
        new workspaces available"""

        preference_window = PreferenceWindowDialog(self.reloadWorkspaces,
                                                   self.window)
        preference_window.show_all()

    def show_host_info(self, host_id):
        """Looks up the host selected in the HostSidebar by id and shows
        its information on the HostInfoDialog"""

        for host in self.all_hosts:
            if host_id == host.id:
                selected_host = host
                break

        info_window = HostInfoDialog(self.window, selected_host)
        info_window.show_all()

    def reloadWorkspaces(self):
        """Close workspace, resources the workspaces available,
        clears the sidebar of the old workspaces and injects all the new ones
        in there too"""
        self.workspace_manager.closeWorkspace()
        self.workspace_manager.resource()
        self.ws_sidebar.clearSidebar()
        self.ws_sidebar.refreshSidebar()

    def on_pluginOptions(self, action, param):
        """Defines what happens when you press "Plugins" on the menu"""
        pluginsOption_window = PluginOptionsDialog(self.plugin_manager,
                                                   self.window)
        pluginsOption_window.show_all()

    def on_new_button(self, action=None, params=None, title=None):
        "Defines what happens when you press the 'new' button on the toolbar"
        new_workspace_dialog = NewWorkspaceDialog(self.createWorkspace,
                                                  self.workspace_manager,
                                                  self.ws_sidebar, self.window,
                                                  title)
        new_workspace_dialog.show_all()

    def on_new_terminal_button(self, action, params):
        """When the user clicks on the new_terminal button, creates a new
        instance of the Terminal and tells the window to add it as a new tab
        for the notebook"""
        new_terminal = Terminal(CONF)
        the_new_terminal = new_terminal.getTerminal()
        AppWindow.new_tab(self.window, the_new_terminal)

    def on_click_notifications(self, button):
        """Defines what happens when the user clicks on the notifications
        button: just show a silly window with a treeview containing
        all the notifications"""

        notifications_view = Gtk.TreeView(self.notificationsModel)
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Notifications", renderer, text=0)
        notifications_view.append_column(column)
        notifications_dialog = NotificationsDialog(notifications_view,
                                                   self.delete_notifications,
                                                   self.window)
        notifications_dialog.show_all()

    def on_click_conflicts(self, button=None):
        """Doesn't use the button at all, there cause GTK likes it.
        Shows the conflict dialog.
        """
        self.updateConflicts()
        if self.conflicts:
            dialog = ConflictsDialog(self.conflicts, self.window)
            dialog.show_all()
            self.updateConflicts()

        else:
            dialog = Gtk.MessageDialog(self.window, 0, Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK,
                                       "No conflicts to fix!")
            dialog.run()
            dialog.destroy()

    def delete_notifications(self):
        """Clear the notifications model of all info, also send a signal
        to get the notification label to 0 on the main window's button
        """
        self.notificationsModel.clear()
        self.window.emit("clear_notifications")

    def changeWorkspace(self, workspaceName):
        """Changes workspace in a separate thread. Emits a signal
        to present a 'Loading workspace' dialog while Faraday processes
        the change"""
        def background_process():
            self.window.emit("loading_workspace", 'show')
            try:
                ws = super(GuiApp, self).openWorkspace(workspaceName)
            except Exception as e:
                model.guiapi.notification_center.showDialog(str(e))
                ws = self.openDefaultWorkspace()

            workspace = ws.name
            CONF.setLastWorkspace(workspace)
            CONF.saveConfig()
            self.window.emit("loading_workspace", "destroy")

            return True

        thread = threading.Thread(target=background_process)
        thread.daemon = True
        thread.start()

    def run(self, args):
        """First method to run, as defined by FaradayUi. This method is
        mandatory"""

        workspace = args.workspace
        try:
            ws = super(GuiApp, self).openWorkspace(workspace)
        except Exception as e:
            getLogger(self).error(("Your last workspace %s is not accessible, "
                                   "check configuration") % workspace)
            getLogger(self).error(str(e))
            ws = self.openDefaultWorkspace()
        workspace = ws.name

        CONF.setLastWorkspace(workspace)
        CONF.saveConfig()
        Gtk.Application.run(self)

    def on_quit(self, action, param):
        self.quit()
Ejemplo n.º 2
0
class GuiApp(Gtk.Application, FaradayUi):
    """
    Creates the application and has the necesary callbacks to FaradayUi
    As far as the GUI goes, this handles only the menu, everything is else is
    appWindow's resposibility. All logic by the main window should be done
    here. Some of the logic on the dialogs is implemented in the dialogs own
    class. Some dialogs are shown by the appwindow to handle errors coming
    from other threads outside GTK's.
    """

    def __init__(self, model_controller, plugin_manager, workspace_manager,
                 plugin_controller):
        """Does not do much. Most of the initialization work is actually
        done by the run() method, as specified in FaradayUi."""

        FaradayUi.__init__(self,
                           model_controller,
                           plugin_manager,
                           workspace_manager,
                           plugin_controller)

        Gtk.Application.__init__(self, application_id="org.infobyte.faraday",
                                 flags=Gio.ApplicationFlags.FLAGS_NONE)

        self.icons = CONF.getImagePath() + "icons/"
        faraday_icon = self.icons + "faraday_icon.png"
        self.icon = GdkPixbuf.Pixbuf.new_from_file_at_scale(faraday_icon, 16,
                                                            16, False)
        self.window = None
        self.model_controller = model_controller
        self.conflicts = self.model_controller.getConflicts()

    def getMainWindow(self):
        """Returns the main window. This is none only at the
        the startup, the GUI will create one as soon as do_activate() is called
        """
        return self.window

    def updateConflicts(self):
        """Reassings self.conflicts with an updated list of conflicts"""
        self.conflicts = self.model_controller.getConflicts()

    def updateHosts(self):
        """Reassings the value of self.all_hosts to a current one to
        catch workspace changes, new hosts added via plugins or any other
        external interference with out host list"""
        self.all_hosts = self.model_controller.getAllHosts()

    def createWorkspace(self, name, description="", w_type=""):
        """Pretty much copy/pasted from the QT3 GUI.
        Uses the instance of workspace manager passed into __init__ to
        get all the workspaces names and see if they don't clash with
        the one the user wrote. If everything's fine, it saves the new
        workspace and returns True. If something went wrong, return False"""

        if name in self.getWorkspaceManager().getWorkspacesNames():

            model.api.log("A workspace with name %s already exists"
                          % name, "ERROR")
            status = True
        else:
            model.api.log("Creating workspace '%s'" % name)
            model.api.devlog("Looking for the delegation class")
            manager = self.getWorkspaceManager()
            try:
                w = manager.createWorkspace(name, description,
                                            manager.namedTypeToDbType(w_type))
                CONF.setLastWorkspace(w.name)
                CONF.saveConfig()
                status = True
            except Exception as e:
                status = False
                model.guiapi.notification_center.showDialog(str(e))

        return status

    def removeWorkspace(self, button, ws_name):
        """Removes a workspace. If the workspace to be deleted is the one
        selected, it moves you first to the default. The clears and refreshes
        sidebar"""

        model.api.log("Removing Workspace: %s" % ws_name)
        if CONF.getLastWorkspace() == ws_name:
            self.openDefaultWorkspace()
        self.getWorkspaceManager().removeWorkspace(ws_name)
        self.ws_sidebar.clearSidebar()
        self.ws_sidebar.refreshSidebar()

    def do_startup(self):
        """
        GTK calls this method after Gtk.Application.run()
        Creates instances of the sidebar, terminal, console log and
        statusbar to be added to the app window.
        Sets up necesary actions on menu and toolbar buttons
        Also reads the .xml file from menubar.xml
        """
        Gtk.Application.do_startup(self)  # deep GTK magic

        self.ws_sidebar = WorkspaceSidebar(self.workspace_manager,
                                           self.changeWorkspace,
                                           self.removeWorkspace,
                                           self.on_new_button,
                                           CONF.getLastWorkspace())

        self.updateHosts()
        self.hosts_sidebar = HostsSidebar(self.show_host_info, self.icons)
        default_model = self.hosts_sidebar.create_model(self.all_hosts)
        self.hosts_sidebar.create_view(default_model)

        self.sidebar = Sidebar(self.ws_sidebar.get_box(),
                               self.hosts_sidebar.get_box())

        host_count, service_count, vuln_count = self.update_counts()

        self.terminal = Terminal(CONF)
        self.console_log = ConsoleLog()
        self.statusbar = Statusbar(self.on_click_notifications,
                                   self.on_click_conflicts,
                                   host_count, service_count, vuln_count)

        self.notificationsModel = Gtk.ListStore(str)

        action = Gio.SimpleAction.new("about", None)
        action.connect("activate", self.on_about)
        self.add_action(action)

        action = Gio.SimpleAction.new("help", None)
        action.connect("activate", self.on_help)
        self.add_action(action)

        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

        action = Gio.SimpleAction.new("preferences", None)
        action.connect("activate", self.on_preferences)
        self.add_action(action)

        action = Gio.SimpleAction.new("pluginOptions", None)
        action.connect("activate", self.on_pluginOptions)
        self.add_action(action)

        action = Gio.SimpleAction.new("new", None)
        action.connect("activate", self.on_new_button)
        self.add_action(action)

        action = Gio.SimpleAction.new("new_terminal")  # new terminal = new tab
        action.connect("activate", self.on_new_terminal_button)
        self.add_action(action)

        action = Gio.SimpleAction.new("open_report")
        action.connect("activate", self.on_open_report_button)
        self.add_action(action)

        dirname = os.path.dirname(os.path.abspath(__file__))
        builder = Gtk.Builder.new_from_file(dirname + '/menubar.xml')
        builder.connect_signals(self)
        appmenu = builder.get_object('appmenu')
        self.set_app_menu(appmenu)
        helpMenu = builder.get_object('Help')
        self.set_menubar(helpMenu)

    def do_activate(self):
        """If there's no window, create one and present it (show it to user).
        If there's a window, just present it. Also add the log handler
        and the notifier to the application"""

        # We only allow a single window and raise any existing ones
        if not self.window:
            # Windows are associated with the application
            # when the last one is closed the application shuts down
            self.window = AppWindow(self.sidebar,
                                    self.terminal,
                                    self.console_log,
                                    self.statusbar,
                                    application=self,
                                    title="Faraday")

        self.window.set_icon(self.icon)
        self.window.present()

        self.loghandler = GUIHandler()
        model.guiapi.setMainApp(self)
        addHandler(self.loghandler)
        self.loghandler.registerGUIOutput(self.window)

        notifier = model.log.getNotifier()
        notifier.widget = self.window
        model.guiapi.notification_center.registerWidget(self.window)

    def postEvent(self, receiver, event):
        """Handles the events from gui/customevents."""
        if receiver is None:
            receiver = self.getMainWindow()

        elif event.type() == 3131:  # new log event
            receiver.emit("new_log", event.text)

        elif event.type() == 3141:  # new conflict event
            receiver.emit("set_conflict_label", event.nconflicts)

        elif event.type() == 5100:  # new notification event
            self.notificationsModel.prepend([event.change.getMessage()])
            receiver.emit("new_notif")
            host_count, service_count, vuln_count = self.update_counts()
            receiver.emit("update_ws_info", host_count,
                          service_count, vuln_count)

        elif event.type() == 4100 or event.type() == 3140:  # newinfo or changews
            host_count, service_count, vuln_count = self.update_counts()
            self.updateHosts()
            self.hosts_sidebar.update(self.all_hosts)
            receiver.emit("update_ws_info", host_count,
                          service_count, vuln_count)

        elif event.type() == 3132:  # error
            self.window.emit("normal_error", event.text)

        elif event.type() == 3134:  # important error, uncaught exception
            self.window.prepare_important_error(event)
            self.window.emit("important_error")

    def update_counts(self):
        """Update the counts for host, services and vulns"""
        host_count = self.model_controller.getHostsCount()
        service_count = self.model_controller.getServicesCount()
        vuln_count = self.model_controller.getVulnsCount()
        return host_count, service_count, vuln_count

    def on_open_report_button(self, action, param):
        """What happens when the user clicks the open report button.
        A dialog will present itself with a combobox to select a plugin.
        Then a file chooser to select a report. The report will be processed
        with the selected plugin.
        """

        def select_plugin():
            """Creates a simple dialog with a combo box to select a plugin"""
            plugins_id = [_id for _id in self.plugin_manager.getPlugins()]
            plugins_id = sorted(plugins_id)
            dialog = Gtk.Dialog("Select plugin", self.window, 0)

            combo_box = Gtk.ComboBoxText()
            for plugin_id in plugins_id:
                combo_box.append_text(plugin_id)
            combo_box.show()

            dialog.vbox.pack_start(combo_box, True, True, 10)

            dialog.add_button("Cancel", Gtk.ResponseType.DELETE_EVENT)
            dialog.add_button("OK", Gtk.ResponseType.ACCEPT)

            response = dialog.run()
            selected = combo_box.get_active_text()

            dialog.destroy()
            return response, selected

        def on_file_selected(plugin_id, report):
            """Send the plugin_id and the report file to be processed"""
            self.report_manager.sendReportToPluginById(plugin_id, report)

        plugin_response, plugin_id = select_plugin()

        while plugin_response == Gtk.ResponseType.ACCEPT and plugin_id is None:
            # force user to select a plugin if he did not do it
            errorDialog(self.window,
                        "Please select a plugin to parse your report!")
            plugin_response, plugin_id = select_plugin()
        else:
            if plugin_response == Gtk.ResponseType.ACCEPT:
                dialog = Gtk.FileChooserNative()
                dialog.set_title("Import a report")
                dialog.set_modal(True)
                dialog.set_transient_for(self.window)
                dialog.set_action(Gtk.FileChooserAction.OPEN)

                res = dialog.run()
                if res == Gtk.ResponseType.ACCEPT:
                    on_file_selected(plugin_id, dialog.get_filename())
                dialog.destroy()

    def on_about(self, action, param):
        """ Defines what happens when you press 'about' on the menu"""

        about_dialog = aboutDialog(self.window)
        about_dialog.run()
        about_dialog.destroy()

    def on_help(self, action, param):
        """Defines what happens when user press 'help' on the menu"""

        help_dialog = helpDialog(self.window)
        help_dialog.run()
        help_dialog.destroy()

    def on_preferences(self, action, param):
        """Defines what happens when you press 'preferences' on the menu.
        Sends as a callback reloadWsManager, so if the user actually
        changes her Couch URL, the sidebar will reload reflecting the
        new workspaces available"""

        preference_window = PreferenceWindowDialog(self.reloadWorkspaces,
                                                   self.window)
        preference_window.show_all()

    def show_host_info(self, host_id):
        """Looks up the host selected in the HostSidebar by id and shows
        its information on the HostInfoDialog"""

        for host in self.all_hosts:
            if host_id == host.id:
                selected_host = host
                break

        info_window = HostInfoDialog(self.window, selected_host)
        info_window.show_all()

    def reloadWorkspaces(self):
        """Close workspace, resources the workspaces available,
        clears the sidebar of the old workspaces and injects all the new ones
        in there too"""
        self.workspace_manager.closeWorkspace()
        self.workspace_manager.resource()
        self.ws_sidebar.clearSidebar()
        self.ws_sidebar.refreshSidebar()

    def on_pluginOptions(self, action, param):
        """Defines what happens when you press "Plugins" on the menu"""
        pluginsOption_window = PluginOptionsDialog(self.plugin_manager,
                                                   self.window)
        pluginsOption_window.show_all()

    def on_new_button(self, action=None, params=None, title=None):
        "Defines what happens when you press the 'new' button on the toolbar"
        new_workspace_dialog = NewWorkspaceDialog(self.createWorkspace,
                                                  self.workspace_manager,
                                                  self.ws_sidebar, self.window,
                                                  title)
        new_workspace_dialog.show_all()

    def on_new_terminal_button(self, action, params):
        """When the user clicks on the new_terminal button, creates a new
        instance of the Terminal and tells the window to add it as a new tab
        for the notebook"""
        new_terminal = Terminal(CONF)
        the_new_terminal = new_terminal.getTerminal()
        AppWindow.new_tab(self.window, the_new_terminal)

    def on_click_notifications(self, button):
        """Defines what happens when the user clicks on the notifications
        button: just show a silly window with a treeview containing
        all the notifications"""

        notifications_view = Gtk.TreeView(self.notificationsModel)
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Notifications", renderer, text=0)
        notifications_view.append_column(column)
        notifications_dialog = NotificationsDialog(notifications_view,
                                                   self.delete_notifications,
                                                   self.window)
        notifications_dialog.show_all()

    def on_click_conflicts(self, button=None):
        """Doesn't use the button at all, there cause GTK likes it.
        Shows the conflict dialog.
        """
        self.updateConflicts()
        if self.conflicts:
            dialog = ConflictsDialog(self.conflicts,
                                     self.window)
            dialog.show_all()
            self.updateConflicts()

        else:
            dialog = Gtk.MessageDialog(self.window, 0,
                                       Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK,
                                       "No conflicts to fix!")
            dialog.run()
            dialog.destroy()

    def delete_notifications(self):
        """Clear the notifications model of all info, also send a signal
        to get the notification label to 0 on the main window's button
        """
        self.notificationsModel.clear()
        self.window.emit("clear_notifications")

    def changeWorkspace(self, workspaceName):
        """Changes workspace in a separate thread. Emits a signal
        to present a 'Loading workspace' dialog while Faraday processes
        the change"""

        def background_process():
            self.window.emit("loading_workspace", 'show')
            try:
                ws = super(GuiApp, self).openWorkspace(workspaceName)
            except Exception as e:
                model.guiapi.notification_center.showDialog(str(e))
                ws = self.openDefaultWorkspace()

            workspace = ws.name
            CONF.setLastWorkspace(workspace)
            CONF.saveConfig()
            self.window.emit("loading_workspace", "destroy")

            return True

        thread = threading.Thread(target=background_process)
        thread.daemon = True
        thread.start()

    def run(self, args):
        """First method to run, as defined by FaradayUi. This method is
        mandatory"""

        workspace = args.workspace
        try:
            ws = super(GuiApp, self).openWorkspace(workspace)
        except Exception as e:
            getLogger(self).error(
                ("Your last workspace %s is not accessible, "
                 "check configuration") % workspace)
            getLogger(self).error(str(e))
            ws = self.openDefaultWorkspace()
        workspace = ws.name

        CONF.setLastWorkspace(workspace)
        CONF.saveConfig()
        Gtk.Application.run(self)

    def on_quit(self, action, param):
        self.quit()
Ejemplo n.º 3
0
class GuiApp(Gtk.Application, FaradayUi):
    """
    Creates the application and has the necesary callbacks to FaradayUi
    Right now handles by itself only the menu, everything is else is
    appWindow's resposibility as far as the initial UI goes.
    The dialogs are found inside the dialogs module
    """

    def __init__(self, model_controller, plugin_manager, workspace_manager,
                 plugin_controller):

        FaradayUi.__init__(self,
                           model_controller,
                           plugin_manager,
                           workspace_manager,
                           plugin_controller)

        Gtk.Application.__init__(self, application_id="org.infobyte.faraday",
                                 flags=Gio.ApplicationFlags.FLAGS_NONE)

        icons = CONF.getImagePath() + "icons/"
        self.icon = GdkPixbuf.Pixbuf.new_from_file(icons + "faraday_icon.png")
        self.window = None

    def getMainWindow(self):
        """Returns the main window. This is none only at the
        the startup, the GUI will create one as soon as do_activate() is called
        """
        return self.window

    def createWorkspace(self, name, description="", w_type=""):
        """Pretty much copy/pasted from the QT3 GUI.
        Uses the instance of workspace manager passed into __init__ to
        get all the workspaces names and see if they don't clash with
        the one the user wrote. If everything's fine, it saves the new
        workspace and returns True. If something went wrong, return False"""

        if name in self.getWorkspaceManager().getWorkspacesNames():

            model.api.log("A workspace with name %s already exists"
                          % name, "ERROR")
            status = True
        else:
            model.api.log("Creating workspace '%s'" % name)
            model.api.devlog("Looking for the delegation class")
            manager = self.getWorkspaceManager()
            try:
                w = manager.createWorkspace(name, description,
                                            manager.namedTypeToDbType(w_type))
                CONF.setLastWorkspace(w.name)
                CONF.saveConfig()
                status = True
            except Exception as e:
                status = False
                model.guiapi.notification_center.showDialog(str(e))

        return status

    def removeWorkspace(self, button, ws_name):
        """Removes a workspace. If the workspace to be deleted is the one
        selected, it moves you first to the default. The clears and refreshes
        sidebar"""

        model.api.log("Removing Workspace: %s" % ws_name)
        if CONF.getLastWorkspace() == ws_name:
            self.openDefaultWorkspace()
        self.getWorkspaceManager().removeWorkspace(ws_name)
        self.sidebar.clearSidebar()
        self.sidebar.refreshSidebar()

    def do_startup(self):
        """
        GTK calls this method after Gtk.Application.run()
        Creates instances of the sidebar, terminal, console log and
        statusbar to be added to the app window.
        Sets up necesary acttions on menu and toolbar buttons
        Also reads the .xml file from menubar.xml
        """
        Gtk.Application.do_startup(self)  # deep GTK magic

        self.sidebar = Sidebar(self.workspace_manager,
                               self.changeWorkspace,
                               self.removeWorkspace,
                               CONF.getLastWorkspace())

        self.terminal = Terminal(CONF)
        self.console_log = ConsoleLog()
        self.statusbar = Statusbar(self.on_click_notifications)
        self.notificationsModel = Gtk.ListStore(str)

        action = Gio.SimpleAction.new("about", None)
        action.connect("activate", self.on_about)
        self.add_action(action)

        action = Gio.SimpleAction.new("help", None)
        action.connect("activate", self.on_help)
        self.add_action(action)

        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

        action = Gio.SimpleAction.new("preferences", None)
        action.connect("activate", self.on_preferences)
        self.add_action(action)

        action = Gio.SimpleAction.new("pluginOptions", None)
        action.connect("activate", self.on_pluginOptions)
        self.add_action(action)

        action = Gio.SimpleAction.new("new", None)
        action.connect("activate", self.on_new_button)
        self.add_action(action)

        action = Gio.SimpleAction.new("new_terminal")  # new terminal = new tab
        action.connect("activate", self.on_new_terminal_button)
        self.add_action(action)

        dirname = os.path.dirname(os.path.abspath(__file__))
        builder = Gtk.Builder.new_from_file(dirname + '/menubar.xml')
        builder.connect_signals(self)
        appmenu = builder.get_object('appmenu')
        self.set_app_menu(appmenu)
        helpMenu = builder.get_object('Help')
        self.set_menubar(helpMenu)

    def do_activate(self):
        """If there's no window, create one and present it (show it to user).
        If there's a window, just present it"""

        # We only allow a single window and raise any existing ones
        if not self.window:
            # Windows are associated with the application
            # when the last one is closed the application shuts down
            self.window = AppWindow(self.sidebar,
                                    self.terminal,
                                    self.console_log,
                                    self.statusbar,
                                    application=self,
                                    title="Faraday")

        self.window.set_icon(self.icon)
        self.window.present()

        self.loghandler = GUIHandler()
        model.guiapi.setMainApp(self)
        addHandler(self.loghandler)
        self.loghandler.registerGUIOutput(self.window)

        notifier = model.log.getNotifier()
        notifier.widget = self.window
        model.guiapi.notification_center.registerWidget(self.window)

    def postEvent(self, receiver, event):
        if receiver is None:
            receiver = self.getMainWindow()
        if event.type() == 3131:
            receiver.emit("new_log", event.text)
        if event.type() == 5100:
            self.notificationsModel.prepend([event.change.getMessage()])
            receiver.emit("new_notif")
        if event.type() == 3132:
            dialog_text = event.text
            dialog = Gtk.MessageDialog(self.window, 0,
                                       Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK,
                                       dialog_text)
            dialog.run()
            dialog.destroy()

    def on_about(self, action, param):
        """ Defines what happens when you press 'about' on the menu"""

        about_dialog = aboutDialog(self.window)
        about_dialog.run()
        about_dialog.destroy()

    def on_help(self, action, param):
        """Defines what happens when user press 'help' on the menu"""

        help_dialog = helpDialog(self.window)
        help_dialog.run()
        help_dialog.destroy()

    def on_preferences(self, action, param):
        """Defines what happens when you press 'preferences' on the menu.
        Sends as a callback reloadWsManager, so if the user actually
        changes her Couch URL, the sidebar will reload reflecting the
        new workspaces available"""

        preference_window = PreferenceWindowDialog(self.reloadWorkspaces,
                                                   self.window)
        preference_window.show_all()

    def reloadWorkspaces(self):
        """Used in conjunction with on_preferences: close workspace,
        resources the workspaces available, clears the sidebar of the old
        workspaces and injects all the new ones in there too"""
        self.workspace_manager.closeWorkspace()
        self.workspace_manager.resource()
        self.sidebar.clearSidebar()
        self.sidebar.refreshSidebar()

    def on_pluginOptions(self, action, param):
        """Defines what happens when you press "Plugins" on the menu"""
        pluginsOption_window = PluginOptionsDialog(self.plugin_manager,
                                                   self.window)
        pluginsOption_window.show_all()

    def on_new_button(self, action, params):
        "Defines what happens when you press the 'new' button on the toolbar"
        new_workspace_dialog = NewWorkspaceDialog(self.createWorkspace,
                                                  self.workspace_manager,
                                                  self.sidebar, self.window)
        new_workspace_dialog.show_all()

    def on_new_terminal_button(self, action, params):
        """When the user clicks on the new_terminal button, creates a new
        instance of the Terminal and tells the window to add it as a new tab
        for the notebook"""
        new_terminal = Terminal(CONF)
        the_new_terminal = new_terminal.getTerminal()
        AppWindow.new_tab(self.window, the_new_terminal)

    def on_click_notifications(self, button):
        """Defines what happens when the user clicks on the notifications
        button."""

        notifications_view = Gtk.TreeView(self.notificationsModel)
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Notifications", renderer, text=0)
        notifications_view.append_column(column)
        notifications_dialog = NotificationsDialog(notifications_view,
                                                   self.delete_notifications,
                                                   self.window)
        notifications_dialog.show_all()

    def delete_notifications(self):
        self.notificationsModel.clear()
        self.window.emit("clear_notifications")

    def changeWorkspace(self, selection=None):
        """Pretty much copy/pasted from QT3 GUI.
        Selection is actually used nowhere, but the connect function is
        Sidebar passes it as an argument so well there it is"""

        tree_model, treeiter = selection.get_selected()
        workspaceName = tree_model[treeiter][0]

        try:
            ws = super(GuiApp, self).openWorkspace(workspaceName)
        except Exception as e:
            model.guiapi.notification_center.showDialog(str(e))
            ws = self.openDefaultWorkspace()
        workspace = ws.name
        CONF.setLastWorkspace(workspace)
        CONF.saveConfig()
        return ws

    def run(self, args):
        """First method to run, as defined by FaradayUi. This method is
        mandatory"""

        workspace = args.workspace
        try:
            ws = super(GuiApp, self).openWorkspace(workspace)
        except Exception as e:
            getLogger(self).error(
                ("Your last workspace %s is not accessible, "
                 "check configuration") % workspace)
            getLogger(self).error(str(e))
            ws = self.openDefaultWorkspace()
        workspace = ws.name

        CONF.setLastWorkspace(workspace)
        CONF.saveConfig()
        Gtk.Application.run(self)

    def on_quit(self, action, param):
        self.quit()
Ejemplo n.º 4
0
class GuiApp(Gtk.Application, FaradayUi):
    """
    Creates the application and has the necesary callbacks to FaradayUi
    Right now handles by itself only the menu, everything is else is
    appWindow's resposibility as far as the initial UI goes.
    The dialogs are found inside the dialogs module
    """
    def __init__(self, model_controller, plugin_manager, workspace_manager,
                 plugin_controller):

        FaradayUi.__init__(self, model_controller, plugin_manager,
                           workspace_manager, plugin_controller)

        Gtk.Application.__init__(self,
                                 application_id="org.infobyte.faraday",
                                 flags=Gio.ApplicationFlags.FLAGS_NONE)

        icons = CONF.getImagePath() + "icons/"
        self.icon = GdkPixbuf.Pixbuf.new_from_file(icons + "faraday_icon.png")
        self.window = None

    def getMainWindow(self):
        """Returns the main window. This is none only at the
        the startup, the GUI will create one as soon as do_activate() is called
        """
        return self.window

    def createWorkspace(self, name, description="", w_type=""):
        """Pretty much copy/pasted from the QT3 GUI.
        Uses the instance of workspace manager passed into __init__ to
        get all the workspaces names and see if they don't clash with
        the one the user wrote. If everything's fine, it saves the new
        workspace and returns True. If something went wrong, return False"""

        if name in self.getWorkspaceManager().getWorkspacesNames():

            model.api.log("A workspace with name %s already exists" % name,
                          "ERROR")
            status = True
        else:
            model.api.log("Creating workspace '%s'" % name)
            model.api.devlog("Looking for the delegation class")
            manager = self.getWorkspaceManager()
            try:
                w = manager.createWorkspace(name, description,
                                            manager.namedTypeToDbType(w_type))
                CONF.setLastWorkspace(w.name)
                CONF.saveConfig()
                status = True
            except Exception as e:
                status = False
                model.guiapi.notification_center.showDialog(str(e))

        return status

    def removeWorkspace(self, button, ws_name):
        """Removes a workspace. If the workspace to be deleted is the one
        selected, it moves you first to the default. The clears and refreshes
        sidebar"""

        model.api.log("Removing Workspace: %s" % ws_name)
        if CONF.getLastWorkspace() == ws_name:
            self.openDefaultWorkspace()
        self.getWorkspaceManager().removeWorkspace(ws_name)
        self.sidebar.clearSidebar()
        self.sidebar.refreshSidebar()

    def do_startup(self):
        """
        GTK calls this method after Gtk.Application.run()
        Creates instances of the sidebar, terminal, console log and
        statusbar to be added to the app window.
        Sets up necesary acttions on menu and toolbar buttons
        Also reads the .xml file from menubar.xml
        """
        Gtk.Application.do_startup(self)  # deep GTK magic

        self.sidebar = Sidebar(self.workspace_manager, self.changeWorkspace,
                               self.removeWorkspace, CONF.getLastWorkspace())

        self.terminal = Terminal(CONF)
        self.console_log = ConsoleLog()
        self.statusbar = Statusbar(self.on_click_notifications)
        self.notificationsModel = Gtk.ListStore(str)

        action = Gio.SimpleAction.new("about", None)
        action.connect("activate", self.on_about)
        self.add_action(action)

        action = Gio.SimpleAction.new("help", None)
        action.connect("activate", self.on_help)
        self.add_action(action)

        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

        action = Gio.SimpleAction.new("preferences", None)
        action.connect("activate", self.on_preferences)
        self.add_action(action)

        action = Gio.SimpleAction.new("pluginOptions", None)
        action.connect("activate", self.on_pluginOptions)
        self.add_action(action)

        action = Gio.SimpleAction.new("new", None)
        action.connect("activate", self.on_new_button)
        self.add_action(action)

        action = Gio.SimpleAction.new("new_terminal")  # new terminal = new tab
        action.connect("activate", self.on_new_terminal_button)
        self.add_action(action)

        dirname = os.path.dirname(os.path.abspath(__file__))
        builder = Gtk.Builder.new_from_file(dirname + '/menubar.xml')
        builder.connect_signals(self)
        appmenu = builder.get_object('appmenu')
        self.set_app_menu(appmenu)
        helpMenu = builder.get_object('Help')
        self.set_menubar(helpMenu)

    def do_activate(self):
        """If there's no window, create one and present it (show it to user).
        If there's a window, just present it"""

        # We only allow a single window and raise any existing ones
        if not self.window:
            # Windows are associated with the application
            # when the last one is closed the application shuts down
            self.window = AppWindow(self.sidebar,
                                    self.terminal,
                                    self.console_log,
                                    self.statusbar,
                                    application=self,
                                    title="Faraday")

        self.window.set_icon(self.icon)
        self.window.present()

        self.loghandler = GUIHandler()
        model.guiapi.setMainApp(self)
        addHandler(self.loghandler)
        self.loghandler.registerGUIOutput(self.window)

        notifier = model.log.getNotifier()
        notifier.widget = self.window
        model.guiapi.notification_center.registerWidget(self.window)

    def postEvent(self, receiver, event):
        if receiver is None:
            receiver = self.getMainWindow()
        if event.type() == 3131:
            receiver.emit("new_log", event.text)
        if event.type() == 5100:
            self.notificationsModel.prepend([event.change.getMessage()])
            receiver.emit("new_notif")
        if event.type() == 3132:
            dialog_text = event.text
            dialog = Gtk.MessageDialog(self.window, 0, Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK, dialog_text)
            dialog.run()
            dialog.destroy()

    def on_about(self, action, param):
        """ Defines what happens when you press 'about' on the menu"""

        about_dialog = aboutDialog(self.window)
        about_dialog.run()
        about_dialog.destroy()

    def on_help(self, action, param):
        """Defines what happens when user press 'help' on the menu"""

        help_dialog = helpDialog(self.window)
        help_dialog.run()
        help_dialog.destroy()

    def on_preferences(self, action, param):
        """Defines what happens when you press 'preferences' on the menu.
        Sends as a callback reloadWsManager, so if the user actually
        changes her Couch URL, the sidebar will reload reflecting the
        new workspaces available"""

        preference_window = PreferenceWindowDialog(self.reloadWorkspaces,
                                                   self.window)
        preference_window.show_all()

    def reloadWorkspaces(self):
        """Used in conjunction with on_preferences: close workspace,
        resources the workspaces available, clears the sidebar of the old
        workspaces and injects all the new ones in there too"""
        self.workspace_manager.closeWorkspace()
        self.workspace_manager.resource()
        self.sidebar.clearSidebar()
        self.sidebar.refreshSidebar()

    def on_pluginOptions(self, action, param):
        """Defines what happens when you press "Plugins" on the menu"""
        pluginsOption_window = PluginOptionsDialog(self.plugin_manager,
                                                   self.window)
        pluginsOption_window.show_all()

    def on_new_button(self, action, params):
        "Defines what happens when you press the 'new' button on the toolbar"
        new_workspace_dialog = NewWorkspaceDialog(self.createWorkspace,
                                                  self.workspace_manager,
                                                  self.sidebar, self.window)
        new_workspace_dialog.show_all()

    def on_new_terminal_button(self, action, params):
        """When the user clicks on the new_terminal button, creates a new
        instance of the Terminal and tells the window to add it as a new tab
        for the notebook"""
        new_terminal = Terminal(CONF)
        the_new_terminal = new_terminal.getTerminal()
        AppWindow.new_tab(self.window, the_new_terminal)

    def on_click_notifications(self, button):
        """Defines what happens when the user clicks on the notifications
        button."""

        notifications_view = Gtk.TreeView(self.notificationsModel)
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Notifications", renderer, text=0)
        notifications_view.append_column(column)
        notifications_dialog = NotificationsDialog(notifications_view,
                                                   self.delete_notifications,
                                                   self.window)
        notifications_dialog.show_all()

    def delete_notifications(self):
        self.notificationsModel.clear()
        self.window.emit("clear_notifications")

    def changeWorkspace(self, selection=None):
        """Pretty much copy/pasted from QT3 GUI.
        Selection is actually used nowhere, but the connect function is
        Sidebar passes it as an argument so well there it is"""

        tree_model, treeiter = selection.get_selected()
        workspaceName = tree_model[treeiter][0]

        try:
            ws = super(GuiApp, self).openWorkspace(workspaceName)
        except Exception as e:
            model.guiapi.notification_center.showDialog(str(e))
            ws = self.openDefaultWorkspace()
        workspace = ws.name
        CONF.setLastWorkspace(workspace)
        CONF.saveConfig()
        return ws

    def run(self, args):
        """First method to run, as defined by FaradayUi. This method is
        mandatory"""

        workspace = args.workspace
        try:
            ws = super(GuiApp, self).openWorkspace(workspace)
        except Exception as e:
            getLogger(self).error(("Your last workspace %s is not accessible, "
                                   "check configuration") % workspace)
            getLogger(self).error(str(e))
            ws = self.openDefaultWorkspace()
        workspace = ws.name

        CONF.setLastWorkspace(workspace)
        CONF.saveConfig()
        Gtk.Application.run(self)

    def on_quit(self, action, param):
        self.quit()
Ejemplo n.º 5
0
class GtkApplication(Application):
    """    
    Application is a wrapper window for the ProjectTreeView which
    holds the information on the current project.  It adds a menu bar
    and a toolbar as described in the two attributes window_actions
    and ui_string of this module.  Furthermore, it provides basic
    functions to work with the project.
    """
    
    def __init__(self, project=None):        
        # self.window needs to be initialized first
        self.window = AppWindow(self)  # gtk        
        Application.__init__(self, project)
        
        self._clipboard = gtk.Clipboard()  # not implemented yet # gtk
        self.progresslist = GtkProgressList


    def init_plugins(self):
        Application.init_plugins(self)

        for plugin in self.plugins.itervalues():

            if hasattr(plugin, 'gtk_popup_actions'):
                action_wrappers = plugin.gtk_popup_actions()                
                # create action group
                ag = gtk.ActionGroup("Plugin")
                for item in action_wrappers:
                    ag.add_action(item.action)
                self.window.uimanager.insert_action_group(ag, -1)

                # construct plugin ui
                plugin_ui = '<popup name="popup_dataset">'
                for item in action_wrappers:
                    plugin_ui += '<menuitem action="%s"/>' % item.name
                plugin_ui += '</popup>'
                        
                # merge plugin ui
                merge_id = self.window.uimanager.add_ui_from_string(plugin_ui)


    # ----------------------------------------------------------------------
    # Project
    
    def set_project(self, project, confirm=True):
        " Assign the given project to the Application. "

        # ask for permission to close the project
        # (unless there were no changes)
        if self._project is not None:
            if self._project.journal.can_undo() and confirm is True:        
                msg = \
                """
                You are about to close the Project.
                Do you want to save your changes ?
                """
                dialog = gtk.MessageDialog(type = gtk.MESSAGE_QUESTION, message_format = msg)
                dialog.add_button("_Don't Save", gtk.RESPONSE_NO)
                btn_default = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
                dialog.add_button(gtk.STOCK_SAVE, gtk.RESPONSE_YES)

                btn_default.grab_focus()

                response = dialog.run()
                dialog.destroy()

                if response == gtk.RESPONSE_YES:
                    # yes = yes, save the file before closing
                    self.save_project()                    
                elif response == gtk.RESPONSE_NO:
                    # no = no, proceed with closing
                    pass
                else:
                    # everything else -> abort action
                    raise error.UserCancel

        # set new project
        Application.set_project(self, project)
        self.window.treeview.set_project(project)

        # assign project label to window title
        if project:
            title = project.filename or "<unnamed project>"
        else:
            title = "(no project)"
        self.window.set_title(basename(title))

        if project is not None:
            project.journal.on_change = self.window._refresh_undo_redo

        self.window._refresh_undo_redo()


    # ----------------------------------------------------------------------
    # delete-event/destroy/quit application
            
    def _cb_quit_application(self, action):
        try:
            self.quit()
        except error.UserCancel:
            return
        else:
            self.window.emit('destroy')

    def quit(self):
        try:
            self.set_project(None, confirm=True)
        except error.UserCancel:
            return True
        gtk.main_quit()
        

    #
    # callbacks
    #
    def _cb_project_close(self,widget=None):  self.close_project()
    def _cb_project_open(self,widget): self.load_project()
    def _cb_project_save(self,widget):   self.save_project()            
    def _cb_project_save_as(self,widget): self.save_project_as()                        
    def _cb_project_new(self,widget): self.new_project()

    def _cb_edit(self, action):
        plots, datasets = self.window.treeview.get_selected_plds()
        if len(plots) > 0:
            self.edit_layer(plots[0])
        else:
            for dataset in datasets:
                self.edit_dataset(dataset)
        
    # ----------------------------------------------------------------------

    def _cb_load_test_project(self,widget):
        try:
            filename = const.internal_path(const.PATH_EXAMPLE+'/example.spj')
            spj = load_project(filename)
        except IOError:
            # TODO: Message Box
            return None
        self.set_project(spj)

        
    # --- VIEW ---------------------------------------------------------------------
                
    def edit_dataset(self, ds, undolist=[]):
        assert( isinstance(ds, Dataset) )

        # reuse old DatasetWindow or create new one
        window = self.window.subwindow_match(
            (lambda win: isinstance(win, DatasetWindow) and (win.dataset == ds))) \
            or \
            self.window.subwindow_add( DatasetWindow(self, self._project, ds) )
	window.present()


    def edit_layer(self, plot, layer=None, current_page=None):
        """
        Edit the given layer of the given plot.
        If no layer is given, the method tries to edit the first Layer.
        If there is no Layer in the plot, an error will logged.

        TODO: current_page.
        """
        if layer is None:
            if len(plot.layers) > 0:
                layer = plot.layers[0]
            else:
                logger.error("The plot to be edited has not even a single layer!")
                return
            
        win = LayerWindow(self, plot, layer, current_page=current_page)
        win.set_modal(True)
        win.present()
        
    
    #----------------------------------------------------------------------

    def _cb_new_plot(self,widget):
        pj = self._check_project()
        
        plot = new_lineplot2d(key='empty plot')
        pj.add_plots([plot])
        

    def load_project(self):
        """
        Open a FileChooserDialog and let the user pick a new project
        to be loaded. The old project is replaced.
        """

        # create chooser object 
        chooser = gtk.FileChooserDialog(
            title="Open project",
            action=gtk.FILE_CHOOSER_ACTION_OPEN,
            buttons=(gtk.STOCK_CANCEL,
                     gtk.RESPONSE_CANCEL,
                     gtk.STOCK_OPEN,
                     gtk.RESPONSE_OK))
        chooser.set_default_response(gtk.RESPONSE_OK)
        chooser.set_current_folder( const.internal_path(const.PATH_EXAMPLE) )
        chooser.set_select_multiple(False)

        filter = gtk.FileFilter()
        filter.set_name("All files")
        filter.add_pattern("*")
        chooser.add_filter(filter)

        filter = gtk.FileFilter()
        filter.set_name("Sloppyplot Project files")
        filter.add_pattern("*.spj")
        filter.add_pattern("*.SPJ")
        chooser.add_filter(filter)
        chooser.set_filter(filter) # default filter

        shortcut_folder = const.internal_path(const.PATH_EXAMPLE)
        if os.path.exists(shortcut_folder):
            chooser.add_shortcut_folder( shortcut_folder )

        response = chooser.run()
        if response == gtk.RESPONSE_OK:
            filename = chooser.get_filename()
        else:
            filename = None
        chooser.destroy()


        if filename is not None:
            Application.load_project(self, filename)



    def save_project_as(self, filename = None, undolist=[]):
        " Save project under another filename -- no undo possible. "
        pj = self._check_project()

        if not filename:
            # allow user to choose a filename
            chooser = gtk.FileChooserDialog(
                title="Save Project As",
                action=gtk.FILE_CHOOSER_ACTION_SAVE,
                buttons=(gtk.STOCK_CANCEL,
                         gtk.RESPONSE_CANCEL,
                         gtk.STOCK_SAVE,
                         gtk.RESPONSE_OK))
            chooser.set_default_response(gtk.RESPONSE_OK)
            chooser.set_current_folder( const.internal_path(const.PATH_EXAMPLE) )
            chooser.set_select_multiple(False)
            chooser.set_filename(pj.filename or "unnamed.spj")

            filter = gtk.FileFilter()
            filter.set_name("All files")
            filter.add_pattern("*")
            chooser.add_filter(filter)
            chooser.set_filter(filter) # default filter

            shortcut_folder = const.internal_path(const.PATH_EXAMPLE)
            if os.path.exists(shortcut_folder):
                chooser.add_shortcut_folder(shortcut_folder)

            response = chooser.run()
            try:
                if response == gtk.RESPONSE_OK:
                    filename = chooser.get_filename()                    
                else:
                    raise error.UserCancel
            finally:
                chooser.destroy()

            # add extension if not yet there
            if filename.lower().endswith('.spj') is False:
                filename = filename + ".spj"

        self._project.filename = filename
        self.window.set_title(basename(self._project.filename))
        save_project(self._project)
        self._project.journal.clear()


        
    # --- PLOT ---------------------------------------------------------------------
    
    def _cb_plot(self,widget): self.plot_current_objects()        
    def _cb_plot_gnuplot(self,widget): self.plot_current_objects('gnuplot/x11')
    def _cb_plot_matplotlib(self,widget): self.plot_current_objects('matplotlib')


    def plot(self,plot,backend_name=const.DEFAULT_BACKEND):
        
        logger.debug("Backend name is %s" % backend_name)
        
        if backend_name == 'gnuplot/x11':
            backend = self.project.request_backend('gnuplot/x11', plot=plot)
            backend.draw()
            return

        # TODO: open Gnuplot window

        # add_subwindow
        # match_subwindow
        # request_subwindow

        # evtl. schon als ToolWindow ???
        
#             window = self.window.subwindow_match( \
#                 (lambda win: isinstance(win, GnuplotWindow) and \
#                  win.project == self.project and \
#                  win.plot == plot) )
#             if window is None:
#                 window = GnuplotWindow(self, project=self.project, plot=plot)
#                 self.window.subwindow_add( window )

        elif backend_name == 'matplotlib':

#             # as widget
#             widget = self.window.find_plotwidget(project=self.project,plot=plot)
#             if widget is None:
#                 widget = MatplotlibWidget(self, project=self.project, plot=plot)
#                 self.window.add_plotwidget(widget)
#             widget.show()

            # as window
            window = self.window.subwindow_match( \
                (lambda win: isinstance(win, MatplotlibWindow) \
                 and win.get_project() == self.project \
                 and win.get_plot() == plot) )

            if window is None:
                window = MatplotlibWindow(self, project=self.project, plot=plot)                   
                self.window.subwindow_add(window)

            window.show()
            window.present()
            
        else:
            raise RuntimeError("Unknown backend %s" % backend_name)
        

    def plot_current_objects(self, backend_name=const.DEFAULT_BACKEND, undolist=[]):
        (plots, datasets) = self.window.treeview.get_selected_plds()
        for plot in plots:
            self.plot(plot, backend_name)


    def _cb_plot_postscript(self, action):
        plots = self.window.treeview.get_selected_plots()
        if len(plots) > 0:
            self.plot_postscript(self.project, plots[0])

        
    def plot_postscript(app, project, plot):

        #
        # request filename
        #
        filename = PostscriptTerminal.build_filename('eps', project, plot)
        
        chooser = gtk.FileChooserDialog(
            title="PostScript Export",
            action=gtk.FILE_CHOOSER_ACTION_SAVE,
            buttons=(gtk.STOCK_CANCEL,
                         gtk.RESPONSE_CANCEL,
                         gtk.STOCK_SAVE,
                         gtk.RESPONSE_OK))
        chooser.set_default_response(gtk.RESPONSE_OK)        
        chooser.set_select_multiple(False)
        chooser.set_current_folder(os.path.dirname(filename))
        chooser.set_current_name(os.path.basename(filename))

        filter = gtk.FileFilter()
        filter.set_name("All files")
        filter.add_pattern("*")
        chooser.add_filter(filter)
        
        filter = gtk.FileFilter()
        filter.set_name("Postscript (.ps; .eps)")
        filter.add_pattern("*.ps")
        filter.add_pattern("*.eps")
        chooser.add_filter(filter)
        chooser.set_filter(filter) # default filter                

        response = chooser.run()
        try:
            if response == gtk.RESPONSE_OK:
                filename = chooser.get_filename()                    
            else:
                raise error.UserCancel
        finally:
            chooser.destroy()               

        #
        # request export options
        #
        ##props = ['mode', 'enhanced', 'color', 'blacktext', 'solid',
        ##         'dashlength', 'linewidth', 'duplexing', 'rounded', 'fontname',
        ##         'fontsize', 'timestamp']          
        
        dialog = OptionsDialog(PostscriptTerminal(), app.window)

        # determine requested postscript mode (ps or eps) from extension
        path, ext = os.path.splitext(filename)
        ext = ext.lower()
        if ext == '.eps':
            dialog.container.mode = 'eps'
        elif ext == '.ps':
            dialog.container.mode = 'landscape'
            
        try:
            result = dialog.run()
            terminal = dialog.container
        finally:
            dialog.destroy()

        if result != gtk.RESPONSE_ACCEPT:
            return
        

        #
        # now check if mode and filename extension match
        #

        def fix_filename(filename, mode):
            msg = "The postscript mode you selected (%s) does not match the given filename extension (%s).  Do you want to adjust the filename to match the mode? " % (mode, os.path.splitext(filename)[1])
            dialog = gtk.MessageDialog(type = gtk.MESSAGE_QUESTION, message_format = msg)
            dialog.add_button("Keep Filename", gtk.RESPONSE_NO)
            btn_default = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
            dialog.add_button("Adjust Filename", gtk.RESPONSE_YES)

            btn_default.grab_focus()

            response = dialog.run()
            dialog.destroy()

            if response == gtk.RESPONSE_YES:
                # yes = yes, adjust filename
                if mode == '.eps':  new_ext = '.eps'
                else: new_ext = '.ps'
                path, ext = os.path.splitext(filename)
                return path + new_ext
            elif response == gtk.RESPONSE_NO:
                # no = no, keep filename
                return filename
            else:
                # everything else -> abort action
                raise error.UserCancel

        if (terminal.mode == 'eps' and ext != '.eps') or \
               (terminal.mode != 'eps' and ext != '.ps'):
            filename = fix_filename(filename, terminal.mode)
        
        #
        # construct backend for output
        #
        backend = BackendRegistry.new_instance(
            'gnuplot', project=project, plot=plot,
            filename=filename,
            terminal=terminal)
        try:
            backend.draw()
        finally:
            backend.disconnect()

        

    # --- DATASET HANDLING -------------------------------------------------


    def _cb_import_dataset(self, action):
        pj = self._check_project()
        
        # allow user to choose files for import
        chooser = gtk.FileChooserDialog(
            title="Import Dataset from file",
            action=gtk.FILE_CHOOSER_ACTION_OPEN,
            buttons=(gtk.STOCK_CANCEL,
                     gtk.RESPONSE_CANCEL,
                     gtk.STOCK_OPEN,
                     gtk.RESPONSE_OK))
        chooser.set_default_response(gtk.RESPONSE_OK)
        chooser.set_current_folder(const.internal_path(const.PATH_DATA))
        chooser.set_select_multiple(True)

        filter_keys = {} # used for reference later on
        
        # add 'All Files' filter
        blurb_all_files = "All Files"
        filter = gtk.FileFilter()
        filter.set_name(blurb_all_files)
        filter.add_pattern("*")
        chooser.add_filter(filter)
        chooser.set_filter(filter)
        filter_keys[blurb_all_files] = 'auto' # default if nothing else specified
        
        # create file filters
        for (key, val) in ImporterRegistry.iteritems():            
            klass = val.klass
            extensions = ';'.join(map(lambda ext: '*.'+ext, klass.extensions))
            blurb = "%s (%s)" % (klass.blurb, extensions)

            filter = gtk.FileFilter()
            filter.set_name(blurb)
            for ext in klass.extensions:
                filter.add_pattern("*."+ext.lower())
                filter.add_pattern("*."+ext.upper())
            chooser.add_filter(filter)

            filter_keys[blurb] = key

        # add shortcut folder to example path, if such exists
        shortcut_folder = const.internal_path(const.PATH_EXAMPLE)
        if os.path.exists(shortcut_folder):
            chooser.add_shortcut_folder(shortcut_folder)

        # The custom widget `combobox` lets the user choose,
        # which Importer is to be used.
        hbox = gtk.HBox()       

        label = gtk.Label("Importer")
        label.show()
        
        # model: key, blurb
        model = gtk.ListStore(str, str)
        # add 'Same as Filter' as first choice, then add all importers
        model.append( (None, "Auto") )
        for key, val in ImporterRegistry.iteritems():
            model.append( (key, val.klass.blurb) )

        combobox = gtk.ComboBox(model)
        cell = gtk.CellRendererText()
        combobox.pack_start(cell, True)
        combobox.add_attribute(cell, 'text', 1)
        combobox.set_active(0)
        combobox.show()
        
        hbox.pack_end(combobox,False)
        hbox.pack_end(label,False)
        hbox.show()        

        chooser.set_extra_widget(hbox)

        # run dialog
        try:
            response = chooser.run()
            if response == gtk.RESPONSE_OK:
                filenames = chooser.get_filenames()
                if len(filenames) == 0:
                    return
                
                importer_key = model[combobox.get_active()][0]
                if importer_key is None: # auto
                    f = chooser.get_filter()
                    importer_key = filter_keys[f.get_name()]
                    if importer_key is 'auto':
                        matches = importer_from_filename(filenames[0])
                        if len(matches) > 0:
                            importer_key = matches[0]
                        else:
                            importer_key = 'ASCII'
            else:
                return
        finally:
            chooser.destroy()
    
        
        # request import options
        importer = ImporterRegistry.new_instance(importer_key)

        try:
            dialog = OptionsDialog(importer, self.window)
        except NoOptionsError:
            pass
        else:
            # If there are any options, construct a
            # preview widget to help the user.
            view = gtk.TextView()
            buffer = view.get_buffer()
            view.set_editable(False)
            view.show()

            tag_main = buffer.create_tag(family="Courier")
            tag_linenr = buffer.create_tag(family="Courier", weight=pango.WEIGHT_HEAVY)

            # fill preview buffer with at most 100 lines
            preview_file = filenames[0]
            try:
                fd = open(preview_file, 'r')
            except IOError:
                raise RuntimeError("Could not open file %s for preview!" % preview_file)

            iter = buffer.get_start_iter()        
            try:
                for j in range(100):
                    line = fd.readline()
                    if len(line) == 0:
                        break
                    buffer.insert_with_tags(iter, u"%3d\t" % j, tag_linenr)
                    try:
                        buffer.insert_with_tags(iter, unicode(line), tag_main)
                    except UnicodeDecodeError:
                        buffer.insert_with_tags(iter, u"<unreadable line>\n", tag_main)
            finally:
                fd.close()

            preview_widget = uihelper.add_scrollbars(view)
            preview_widget.show()
            
            dialog.vbox.add(preview_widget)
            dialog.set_size_request(480,320)

            try:
                result = dialog.run()
            finally:
                dialog.destroy()

            if result != gtk.RESPONSE_ACCEPT:
                return

        pj.import_datasets(filenames, importer)




    ###
    ### TODO: rewrite this!
    ###
    def _cb_export_dataset(self, widget):
        """
        Export the selected Dataset to a file.
        Currently, only the csv format is supported.
        TODO: Support for arbitrary export filters.
        """
        pj = self._check_project()
        objects = self.window.treeview.get_selected_objects()
        if len(objects) == 0:
            return
        object = objects[0]

        if not isinstance(object,Dataset):
            return

        # allow user to choose a filename
        chooser = gtk.FileChooserDialog(
            title="Export Dataset %s" % object.get_option('label'),
            action=gtk.FILE_CHOOSER_ACTION_SAVE,
            buttons=(gtk.STOCK_CANCEL,
                     gtk.RESPONSE_CANCEL,
                     gtk.STOCK_SAVE,
                     gtk.RESPONSE_OK))
        chooser.set_default_response(gtk.RESPONSE_OK)
        chooser.set_current_folder(const.internal_path(const.PATH_EXAMPLE))
        chooser.set_select_multiple(False)

        filter = gtk.FileFilter()
        filter.set_name("All files")
        filter.add_pattern("*")
        chooser.add_filter(filter)

        filter = gtk.FileFilter()
        filter.set_name("Data Files")
        filter.add_pattern("*.dat")
        chooser.add_filter(filter)
        chooser.set_filter(filter) # default filter

        shortcut_folder = const.internal_path(const.PATH_EXAMPLE)
        if os.path.exists(shortcut_folder):
            chooser.add_shortcut_folder(shortcut_folder)

        response = chooser.run()
        if response == gtk.RESPONSE_OK:
            filename = chooser.get_filename()
        else:
            filename = None
        chooser.destroy()

        # export file, using the filter 'f'
        filename = os.path.abspath(filename)
        logger.debug("Exporting file %s as csv..." % filename )
        f = FilterRegistry.new_instance('comma separated values')
        f.export_to_file(filename, object)


    def _cb_new_dataset(self,widget):
        " Create a new dataset and switch to its editing window. "
        pj = self._check_project()        
        ds = pj.new_dataset()
        self.edit_dataset(ds)        


    def _cb_create_plot_from_datasets(self, widget):
        pj = self._check_project()
        datasets = self.window.treeview.get_selected_datasets()
        pj.create_plot_from_datasets(datasets)
        

    def _cb_add_datasets_to_plot(self, action):
        pj = self._check_project()
        (plots, datasets) = self.window.treeview.get_selected_plds()
        if len(plots) == 1 and len(datasets) > 0:
            pj.add_datasets_to_plot(datasets, plots[0])

    def _cb_delete(self, widget):
        pj = self._check_project()

        objects = self.window.treeview.get_selected_objects()

        response = gtkutils.dialog_confirm_list(\
            title = "Remove Objects ?",
            msg =
            """
            Do you really wish to remove
            the following objects ?
            """,
            objects = objects)

        if response == gtk.RESPONSE_OK:
            print "OBJECTS", objects
            pj.remove_objects(objects)


    def _cb_experimental_plot(self, action):        
        pj = self._check_project()
        plugin = self.plugins['Default']
        plugin.add_experimental_plot(pj)



    # --- EDIT -------------------------------------------------------------

    ###
    ### TODO: implement cut/copy/paste
    ###
    def _cb_edit_cut(self, widget): pass
    def _cb_edit_copy(self, widget):  pass
    def _cb_edit_paste(self, widget):  pass
    
    # --- UNDO/REDO --------------------------------------------------------
        
    def _cb_undo(self, widget):
        pj = self._check_project()
        pj.undo()
        
    def _cb_redo(self, widget):
        pj = self._check_project()
        pj.redo()