示例#1
0
 def setUp(self) -> None:
     self.myapp = AppWindow()
     self.myapp.running_inside_unit_test = True
     self.myapp.ui.HAtomsCheckBox.setChecked(False)
     self.myapp.ui.ReportTextCheckBox.setChecked(False)
     self.myapp.ui.PictureWidthDoubleSpinBox.setValue(0.0)
     self.myapp.hide()
示例#2
0
 def setUp(self) -> None:
     os.chdir(Path(__file__).resolve().parent.parent)
     self.testcif = Path('tests/examples/1979688.cif').resolve()
     self.myapp = AppWindow(self.testcif)
     self.myapp.running_inside_unit_test = True
     self.myapp.hide()  # For full screen view
     self.myapp.ui.LoopsPushButton.click()
示例#3
0
    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)
示例#4
0
 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)
示例#5
0
 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)
示例#6
0
    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.ws_sidebar,
                                    self.hosts_sidebar,
                                    self.terminal,
                                    self.console_log,
                                    self.statusbar,
                                    application=self,
                                    title="Faraday " + str(CONF.getVersion()))

        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)

        if not CouchDbManager.testCouch(CONF.getCouchURI()):
            self.lost_db_connection(
                handle_connection_lost=self.handle_connection_lost,
                connect_to_a_different_couch=self.force_change_couch_url)
示例#7
0
 def setUp(self) -> None:
     self.myapp = AppWindow()
     self.myapp.running_inside_unit_test = True
     self.myapp.setWindowIcon(QIcon('./icon/multitable.png'))
     self.myapp.setWindowTitle('FinalCif v{}'.format(VERSION))
     self.status = StatusBar(self.myapp.ui)
     self.myapp.hide()
示例#8
0
 def setUp(self) -> None:
     os.chdir(Path(__file__).absolute().parent.parent)
     self.testcif = Path(
         'tests/examples/work/cu_BruecknerJK_153F40_0m.cif').absolute()
     self.myapp = AppWindow(self.testcif)
     self.myapp.hide()
     self.myapp.setWindowIcon(QIcon('./icon/multitable.png'))
     self.myapp.setWindowTitle('FinalCif v{}'.format(VERSION))
示例#9
0
 def setUp(self) -> None:
     os.chdir(Path(__file__).absolute().parent.parent)
     self.myapp = AppWindow()
     self.myapp.running_inside_unit_test = True
     self.myapp.hide()
     self.myapp.setWindowIcon(QIcon('./icon/multitable.png'))
     self.myapp.setWindowTitle('FinalCif v{}'.format(VERSION))
     Path('foo.cif').unlink(missing_ok=True)
     Path('cu_BruecknerJK_153F40_0m-finalcif.cif').unlink(missing_ok=True)
示例#10
0
class TestOptions(unittest.TestCase):
    def setUp(self) -> None:
        self.myapp = AppWindow()
        self.myapp.running_inside_unit_test = True
        self.myapp.ui.HAtomsCheckBox.setChecked(False)
        self.myapp.ui.ReportTextCheckBox.setChecked(False)
        self.myapp.ui.PictureWidthDoubleSpinBox.setValue(0.0)
        self.myapp.hide()

    def tearDown(self) -> None:
        self.myapp.ui.HAtomsCheckBox.setChecked(False)
        self.myapp.ui.ReportTextCheckBox.setChecked(False)
        self.myapp.ui.PictureWidthDoubleSpinBox.setValue(0.0)
        self.myapp.close()

    def test_save_picture_with(self):
        self.assertEqual(self.myapp.ui.PictureWidthDoubleSpinBox.value(), 0.0)
        self.myapp.ui.PictureWidthDoubleSpinBox.setValue(7.6)
        self.assertEqual(self.myapp.options.picture_width, 7.6)
        self.myapp.ui.PictureWidthDoubleSpinBox.setValue(12.5)
        self.assertEqual(self.myapp.options.picture_width, 12.5)

    def test_without_h(self):
        self.assertEqual(self.myapp.ui.HAtomsCheckBox.isChecked(), False)
        self.myapp.ui.HAtomsCheckBox.setChecked(True)
        self.assertEqual(self.myapp.options.without_h, True)
        self.myapp.ui.HAtomsCheckBox.setChecked(False)
        self.assertEqual(self.myapp.options.without_h, False)

    def test_report_text(self):
        self.assertEqual(self.myapp.ui.ReportTextCheckBox.isChecked(), False)
        self.myapp.ui.ReportTextCheckBox.setChecked(True)
        self.assertEqual(self.myapp.options.report_text, False)
        self.myapp.ui.ReportTextCheckBox.setChecked(False)
        self.assertEqual(self.myapp.options.report_text, True)

    def test_checkcif_url(self):
        self.assertEqual(
            self.myapp.ui.CheckCIFServerURLTextedit.text(),
            'https://checkcif.iucr.org/cgi-bin/checkcif_with_hkl')
        self.myapp.ui.CheckCIFServerURLTextedit.setText('foobar')
        self.assertEqual(self.myapp.options.checkcif_url, 'foobar')

    def test_option_picture_width(self):
        self.myapp.ui.PictureWidthDoubleSpinBox.setValue(0.0)
        # a width od < 0.001 defaults to 7.5 cm
        self.assertEqual(self.myapp.options.picture_width, 7.5)
        #
        self.myapp.ui.PictureWidthDoubleSpinBox.setValue(7.6)
        self.assertEqual(self.myapp.options.picture_width, 7.6)

    def test_without_H_option(self):
        self.myapp.ui.HAtomsCheckBox.setChecked(False)
        self.assertEqual(self.myapp.options.without_h, False)
示例#11
0
    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.ws_sidebar,
                                    self.hosts_sidebar,
                                    self.terminal,
                                    self.console_log,
                                    self.statusbar,
                                    application=self,
                                    title="Faraday " + str(CONF.getVersion()))

        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)

        if self.serverIO.server_info() is None:

            should_login = self.lost_db_connection(
                handle_connection_lost=self.handle_connection_lost,
                connect_to_a_different_couch=self.force_change_couch_url)

            if not should_login:
                return

        if not is_authenticated(CONF.getServerURI(),
                                CONF.getDBSessionCookies()):
            loginDialog = ForceLoginDialog(self.window,
                                           self.exit_faraday_without_confirm)
            loginDialog.run(3, CONF.getServerURI(), self.window)
            self.reload_workspaces()

        workspace_argument_set = self.open_workspace_from_args()
        if not workspace_argument_set:
            self.open_last_workspace()
示例#12
0
 def setUp(self) -> None:
     current_file_path()
     self.testcif = Path('tests/examples/1979688.cif').resolve()
     self.app = AppWindow(self.testcif)
     self.app.running_inside_unit_test = True
     self.app.hide()
     self.author = {
         'address': 'address',
         'footnote': 'footnote',
         'email': 'email',
         'name': 'name',
         'orcid': 'orcid',
         'phone': 'phone',
         'contact': True
     }
示例#13
0
 def setUp(self) -> None:
     os.chdir(Path(__file__).absolute().parent.parent)
     self.testcif = Path('tests/examples/1979688.cif').absolute()
     self.myapp = AppWindow(self.testcif)
     self.myapp.ui.HAtomsCheckBox.setChecked(False)
     self.myapp.ui.ReportTextCheckBox.setChecked(False)
     self.myapp.ui.PictureWidthDoubleSpinBox.setValue(0.0)
     # make sure to use no template:
     self.myapp.ui.TemplatesListWidget.setCurrentRow(0)
     self.myapp.running_inside_unit_test = True
     self.myapp.hide()
     self.reportdoc = Path('report_' + self.testcif.stem + '-finalcif.docx')
     self.report_zip = Path(self.testcif.stem + '-finalcif.zip')
     self.myapp.hide()
     app.processEvents()
示例#14
0
 def setUp(self) -> None:
     self.myapp = AppWindow()
     os.chdir(Path(__file__).absolute().parent.parent)
     self.testcif = Path('tests/examples/1979688.cif').absolute()
     self.myapp.load_cif_file(self.testcif.absolute())
     self.myapp.running_inside_unit_test = True
     self.myapp.ui.HAtomsCheckBox.setChecked(False)
     self.myapp.ui.ReportTextCheckBox.setChecked(False)
     self.myapp.ui.PictureWidthDoubleSpinBox.setValue(7.43)
     self.import_templates()
     self.myapp.ui.TemplatesListWidget.setCurrentRow(2)
     self.reportdoc = Path('report_' + self.testcif.stem + '-finalcif.docx')
     self.report_zip = Path(self.testcif.stem + '-finalcif.zip')
     self.myapp.set_report_picture(Path('../../icon/finalcif.png'))
     self.myapp.hide()
示例#15
0
    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 __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(self):
     self.path.bset('icon_dir',  'base_dir', os.path.join('Gtk','Icons'))                
     register_all_png_icons(self.path.get('icon_dir'), 'sloppy-')
     
     self.window = AppWindow(self)
     self._clipboard = gtk.Clipboard()  # not implemented yet
     self._current_plot = None
示例#18
0
    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.ws_sidebar,
                                    self.hosts_sidebar,
                                    self.terminal,
                                    self.console_log,
                                    self.statusbar,
                                    application=self,
                                    title="Faraday " + str(CONF.getVersion()))

        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)

        if not CouchDbManager.testCouch(CONF.getCouchURI()):
            self.lost_db_connection(
                handle_connection_lost=self.handle_connection_lost,
                connect_to_a_different_couch=self.force_change_couch_url)
示例#19
0
class TestCheckCifHTML(unittest.TestCase):

    def setUp(self) -> None:
        os.chdir(Path(__file__).absolute().parent.parent)
        self.myapp = AppWindow(Path('tests/examples/work/cu_BruecknerJK_153F40_0m.cif').absolute())
        self.myapp.hide()  # For full screen view
        self.resobj = Path('checkcif-' + strip_finalcif_of_name(self.myapp.cif.fileobj.stem) + '-finalcif.html')

    def tearDown(self) -> None:
        self.resobj.unlink(missing_ok=True)
        self.myapp.final_cif_file_name.unlink(missing_ok=True)
        Path('platon.out').unlink(missing_ok=True)
        Path('check.def').unlink(missing_ok=True)
        Path('cu_BruecknerJK_153F40_0m-finalcif.chk').unlink(missing_ok=True)
        Path('cu_BruecknerJK_153F40_0m-finalcif.gif').unlink(missing_ok=True)
        Path('cu_BruecknerJK_153F40_0m-finalcif.ins').unlink(missing_ok=True)
        Path('cu_BruecknerJK_153F40_0m-finalcif.lst').unlink(missing_ok=True)
        Path('checkcif-cu_BruecknerJK_153F40_0m-finalcif.html').unlink(missing_ok=True)
        Path('checkcif-cu_BruecknerJK_153F40_0m-finalcif.pdf').unlink(missing_ok=True)
        Path('checkpdf-cu_BruecknerJK_153F40_0m-finalcif.html').unlink(missing_ok=True)
        self.myapp.close()

    def equipment_click(self, field: str):
        self.myapp.ui.EquipmentTemplatesStackedWidget.setCurrentIndex(0)
        item = self.myapp.ui.EquipmentTemplatesListWidget.findItems(field, Qt.MatchStartsWith)[0]
        self.myapp.ui.EquipmentTemplatesListWidget.setCurrentItem(item)
        self.myapp.equipment.load_selected_equipment()

    @unittest.skip('temporary skip')
    def test_checkcif_html(self):
        """Runs a html checkcif without hkl and compares the result with the html file."""
        self.maxDiff = None
        self.equipment_click('D8 VENTURE')
        self.equipment_click('Crystallographer Details')
        self.myapp.ui.cif_main_table.setText(key='_chemical_absolute_configuration', txt='ad', column=COL_EDIT)
        # Remember: This test is without structure factors!
        self.myapp.ui.structfactCheckBox.setChecked(True)
        QTest.mouseClick(self.myapp.ui.CheckcifHTMLOnlineButton, Qt.LeftButton, Qt.NoModifier)
        time.sleep(5)
        # this is the file on github:
        html_as_it_is_expected = Path('checkcif-' + strip_finalcif_of_name(self.myapp.cif.fileobj.stem) + '-test.html')
        htmlfile = html_as_it_is_expected.read_text().splitlines()[28:-13]
        # this is the new downloadad file
        result = self.resobj.read_text().splitlines()[28:-13]
        self.assertEqual(htmlfile, result)
示例#20
0
 def do_activate(self):
     # We only allow a single window and raise any existing ones
     if not self.window:
         app_window = AppWindow(application=self, title="Sistema Básico")
         # Windows are associated with the application
         # when the last one is closed the application shuts down
         self.window = app_window.window
         self.window.set_application(self)
     self.window.present()
    def init(self):
        #self.path.bset('icon_dir',  'system_prefix_dir', os.path.join('share', 'pixmaps', 'sloppyplot'))
        
        self.window = AppWindow(self)
        self._clipboard = gtk.Clipboard()  # not implemented yet
        self._current_plot = None

        self.path.bset('icon_dir', 'base_dir', os.path.join('Gtk','Icons'))
        self.register_stock()      
示例#22
0
class TestPlatonCheckCIF_with_CIF_without_hkl_data(unittest.TestCase):
    def tearDown(self) -> None:
        print('running teardown')
        os.chdir(Path(__file__).resolve().parent.parent)
        for file in filenames:
            Path(file).unlink(missing_ok=True)
        self.myapp.close()

    def setUp(self) -> None:
        os.chdir(Path(__file__).resolve().parent.parent)
        self.myapp = AppWindow(Path('./test-data/1000007.cif').resolve())
        self.myapp.hide()
        self.myapp.ui.structfactCheckBox.setChecked(True)
        self.myapp.running_inside_unit_test = True

    def test_checkcif_offline(self):
        self.myapp.hide()
        self.myapp.ui.CheckcifButton.click()
        timediff = int(Path('./1000007-finalcif.chk').stat().st_mtime) - int(
            time.time())
        self.assertLess(timediff,
                        5)  # .chk file was modified less than 5 seconds ago
示例#23
0
    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.ws_sidebar,
                                    self.hosts_sidebar,
                                    self.terminal,
                                    self.console_log,
                                    self.statusbar,
                                    application=self,
                                    title="Faraday " + str(CONF.getVersion()))

        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)

        if self.serverIO.server_info() is None:

            should_login = self.lost_db_connection(
                handle_connection_lost=self.handle_connection_lost,
                connect_to_a_different_couch=self.force_change_couch_url)

            if not should_login:
                return

        if not is_authenticated(CONF.getServerURI(), CONF.getDBSessionCookies()):
            loginDialog = ForceLoginDialog(self.window,
                                           self.exit_faraday_without_confirm)
            loginDialog.run(3, CONF.getServerURI(), self.window)
            self.reload_workspaces()

        workspace_argument_set = self.open_workspace_from_args()
        if not workspace_argument_set:
            self.open_last_workspace()
示例#24
0
class TestStausBarWithGraphics(unittest.TestCase):
    def setUp(self) -> None:
        self.myapp = AppWindow()
        self.myapp.running_inside_unit_test = True
        self.myapp.setWindowIcon(QIcon('./icon/multitable.png'))
        self.myapp.setWindowTitle('FinalCif v{}'.format(VERSION))
        self.status = StatusBar(self.myapp.ui)
        self.myapp.hide()

    def tearDown(self) -> None:
        self.myapp.close()

    def test_show_hello(self):
        self.status.show_message('Hello!')
        self.assertEqual('Hello!', self.status.current_message)

    def test_show_list(self):
        self.status.show_message(['Hello', 'world!'])
        self.assertEqual('Hello world!', self.status.current_message)

    def test_show_2s(self):
        self.status.show_message('foobar', timeout=1)
        self.assertEqual('foobar', self.status.current_message)
示例#25
0
class TestPlatonCheckCIF(unittest.TestCase):
    def tearDown(self) -> None:
        print('running teardown')
        os.chdir(Path(__file__).resolve().parent.parent)
        for file in filenames:
            Path(file).unlink(missing_ok=True)
        self.myapp.close()

    def setUp(self) -> None:
        os.chdir(Path(__file__).resolve().parent.parent)
        self.myapp = AppWindow(Path('tests/examples/1979688.cif').resolve())
        self.myapp.hide()
        self.myapp.running_inside_unit_test = True

    def test_checkcif_offline(self):
        self.myapp.hide()
        self.myapp.ui.CheckcifButton.click()
        timediff = int(Path('1979688-finalcif.chk').stat().st_mtime) - int(
            time.time())
        self.assertLess(timediff,
                        5)  # .chk file was modified less than 5 seconds ago

    def test_checkdef_contains_text(self):
        self.myapp.hide()
        self.myapp.ui.CheckcifButton.click()
        time.sleep(0.3)
        self.assertEqual(
            'FINALCIF V{}'.format(VERSION)
            in Path('1979688-finalcif.chk').read_text(), True)
        self.assertEqual(
            'SumFormula C77 H80 O25'
            in Path('1979688-finalcif.chk').read_text(), True)

    def test_offline_checkcif_writes_gif(self):
        self.myapp.hide()
        self.myapp.ui.CheckcifButton.click()
        self.assertFalse(Path('1979688-finalcif.gif').exists())
示例#26
0
 def setUp(self) -> None:
     os.chdir(Path(__file__).resolve().parent.parent)
     self.myapp = AppWindow(Path('./test-data/1000007.cif').resolve())
     self.myapp.hide()
     self.myapp.ui.structfactCheckBox.setChecked(True)
     self.myapp.running_inside_unit_test = True
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):
        #self.path.bset('icon_dir',  'system_prefix_dir', os.path.join('share', 'pixmaps', 'sloppyplot'))
        
        self.window = AppWindow(self)
        self._clipboard = gtk.Clipboard()  # not implemented yet
        self._current_plot = None

        self.path.bset('icon_dir', 'base_dir', os.path.join('Gtk','Icons'))
        self.register_stock()      


    def register_stock(self):
        uihelper.register_stock_icons(self.path.get('icon_dir'), prefix='sloppy-')

        # register stock items
        items = [('sloppy-rename', '_Rename', 0, 0, None)]
        aliases = [('sloppy-rename', 'gtk-edit')]

        gtk.stock_add(items)

        factory = gtk.IconFactory()
        factory.add_default()
        style = self.window.get_style()
        for new_stock, alias in aliases:
            icon_set = style.lookup_icon_set(alias)
            factory.add(new_stock, icon_set)
            

    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.

        @param confirm: Ask user 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()
        self.window._refresh_recentfiles()


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

        if filename is None:
            # TODO
            # maybe we could have application.load_project
            # just request the file name and we simply
            # create a method for this dialog.
            
            # 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( self.path.get('current_dir') )
            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 = self.path.get('example_dir')
            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):
        """ Save project under another filename. """
        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( self.path.get('example_dir') )
            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 = self.path.get('example_dir')
            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()

        self.recent_files.append(os.path.abspath(filename))
        self.sig_emit('update-recent-files')


    def quit(self):
        """ Quit Application and gtk main loop. """
        try:
            Application.quit(self)
            gtk.main_quit()
        except error.UserCancel:
            return

    # ----------------------------------------------------------------------
    # Callbacks
    #
    
    # delete-event/destroy/quit application
            
    def _cb_quit_application(self, action): self.quit()       

    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)        

                        
    # --- 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])
        


        
    # --- 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='matplotlib'):
        
        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 Toolbox ???
        
#             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)
                ##window.set_transient_for(self.window)
                self.window.subwindow_add(window)
                # TESTING
                # Of course, the toolbox might decide to not use the backend,
                # e.g. if there is an 'auto' button and it is not pressed. So I will
                # need to change that then.
                window.connect("focus-in-event", (lambda a,b: self.window.toolbox.set_backend(window.get_backend())))

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

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


    def on_action_export_via_gnuplot(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('ps', 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(),
                               title="Options Postscript Export",
                               parent=app.window)
        #dialog.set_size_request(320,520)

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


        #
        # 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['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(self.path.get('current_dir'))
        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
        #
        
        # Each item in importer_registry is a class derived from
        # dataio.Importer.  By using IOTemplate objects we can
        # customize the default values for these templates.
        for (key, template) in dataio.import_templates.iteritems():
            ext_list = template.extensions.split(',')
            if len(ext_list) == 0:
                continue
            extensions = ';'.join(map(lambda ext: '*.'+ext, ext_list))
            blurb = "%s (%s)" % (template.blurb, extensions)

            filter = gtk.FileFilter()
            filter.set_name(blurb)
            for ext in ext_list:
                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 = self.path.get('data_dir')
        if os.path.exists(shortcut_folder):
            chooser.add_shortcut_folder(shortcut_folder)

        
        #
        # prepare extra widget
        #
        
        # The custom widget `combobox` lets the user choose,
        # which ImporterTemplate is to be used.
        
        # 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, template in dataio.import_templates.iteritems():
                model.append( (key, template.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()

        label = gtk.Label("Use Template: ")
        label.show()
            
        hbox = gtk.HBox()       
        hbox.pack_end(combobox,False)
        hbox.pack_end(label,False)
        hbox.show()        
        
        vbox = gtk.VBox()
        vbox.pack_start(hbox,False)
        #vbox.pack_start(pbar,False)
        vbox.show()
        
        chooser.set_extra_widget(vbox)

        #
        # run dialog
        #        
        try:
            response = chooser.run()
            if response == gtk.RESPONSE_OK:
                filenames = chooser.get_filenames()
                if len(filenames) == 0:
                    return
                
                template_key = model[combobox.get_active()][0]
                if template_key is None: # auto
                    f = chooser.get_filter()
                    template_key = filter_keys[f.get_name()]
            else:
                return
        finally:
            chooser.destroy()

        self.do_import(pj, filenames, template_key)



    def do_import(self, project, filenames, template_key=None):

        # try to determine template key if it is not given
        if template_key is None or template_key=='auto':
            matches = dataio.importer_template_from_filename(filenames[0])
            if len(matches) > 0:
                template_key = matches[0]
            else:
                template_key = 'ASCII'                            
                  
        #
        # Request import options
        #

        # Note that if 'skip_option' is set in the template, then
        # there will be no user options dialog.

        if dataio.import_templates[template_key].skip_options is False:
            dialog = import_dialog.ImportOptions(template_key, previewfile=filenames[0])
            try:
                result = dialog.run()
                if result == gtk.RESPONSE_ACCEPT:
                    # save template as 'recently used'
                    template = dataio.IOTemplate()
                    template.defaults = dialog.importer.get_values(include=dialog.importer.public_props)
                    template.blurb = "Recently used Template"
                    template.importer_key = dialog.template.importer_key
                    template.write_config = True
                    template.is_internal = True
                    dataio.import_templates['recently used'] = template
                else:
                    return
            finally:
                dialog.destroy()
        else:
            template = template_key

        # The progress bar displays which file is currently being imported.
        Application.import_datasets(self, project, filenames, template)
                



    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()
        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()


    #----------------------------------------------------------------------
    # MISC CALLBACKS

    def _cb_recent_files_clear(self, action):
        self.clear_recent_files()


    def on_action_Preferences(self, action):
        dlg = config.ConfigurationDialog()
        try:
            dlg.run()
        finally:
            dlg.destroy()

        return
    
            

    #----------------------------------------------------------------------
    # Simple user I/O, inherited from Base.Application
    #

    def ask_yes_no(self, msg):
        dialog = gtk.MessageDialog(parent=self.window,
                                   flags=0,
                                   type=gtk.MESSAGE_WARNING,
                                   buttons=gtk.BUTTONS_YES_NO,
                                   message_format=msg)
            
        result = dialog.run()
        dialog.destroy()

        return result == gtk.RESPONSE_YES


    def error_msg(self, msg):
        dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
                                   buttons=gtk.BUTTONS_OK,
                                   message_format=unicode(msg))
        dialog.run()
        dialog.destroy()


    def status_msg(self, msg):
        sb = self.window.statusbar
        context = sb.get_context_id("main")
        id = sb.push(context, msg)

        def remove_msg(statusbar, a_context, a_id):
            statusbar.remove(a_context, a_id)
            return False            
        gobject.timeout_add(3000, remove_msg, sb, context, id)


    def progress(self, fraction):
        pb = self.window.progressbar
        
        if fraction == -1:
            pb.hide()
        else:
            pb.show()
            pb.set_fraction(fraction)

        # Calling main_iteration is definitely not the only way to
        # update the progressbar; see FAQ 23.20 for details.  But
        # I refrain from using generators for the import function.
        while gtk.events_pending():
            gtk.main_iteration()
示例#28
0
        errortext += time.asctime(time.localtime(time.time())) + '\n'
        errortext += "Finalcif crashed during the following operation:" + '\n'
        errortext += '-' * 80 + '\n'
        errortext += ''.join(traceback.format_tb(error_traceback)) + '\n'
        errortext += str(exctype.__name__) + ': '
        errortext += str(value) + '\n'
        errortext += '-' * 80 + '\n'
        logfile = Path.home().joinpath(Path(r'finalcif-crash.txt'))
        try:
            logfile.write_text(errortext)
        except PermissionError:
            pass
        sys.__excepthook__(exctype, value, error_traceback)
        # Hier Fenster für meldung öffnen
        bug_found_warning(logfile)
        sys.exit(1)


    if not DEBUG:
        sys.excepthook = my_exception_hook

    app = QApplication(sys.argv)
    # windows_style = QStyleFactory.create('Fusion')
    # app.setStyle(windows_style)
    w = AppWindow()
    app.setWindowIcon(QIcon(os.path.join(application_path, r'icon/finalcif2.png')))
    w.setWindowTitle('FinalCif v{}'.format(VERSION))
    # w.showMaximized()  # For full screen view
    w.setBaseSize(1200, 780)
    sys.exit(app.exec_())
示例#29
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.

    Please respect the following structure:
    TOP: __init__
    UPPER-MIDDLE: all logic mostly not inherited fom Gtk.Application
    LOWER-MIDDLE: all do_ starting, gtk related methods
    BOTTOM: all on_ starting, dialog opener methods

    """

    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.lost_connection_dialog_raised = None
        self.workspace_dialogs_raised = None
        self.loading_dialog_raised = 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

    @property
    def active_ws_name(self):
        return self.get_active_workspace().name

    def get_active_workspace(self):
        """Return the currently active workspace"""
        return self.workspace_manager.getActiveWorkspace()

    def getMainWindow(self):
        """Useless mostly, but guiapi uses this method to access the main
        window."""
        return self.window

    def createWorkspace(self, name, description=""):
        """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.workspace_manager.getWorkspacesNames():
            error_str = "A workspace with name %s already exists" % name
            model.api.log(error_str, "ERROR")
            errorDialog(self.window, error_str)
            creation_ok = False
        else:
            model.api.log("Creating workspace '%s'" % name)
            model.api.devlog("Looking for the delegation class")
            manager = self.getWorkspaceManager()
            try:
                name = manager.createWorkspace(name, description)
                self.change_workspace(name)
                creation_ok = True
            except Exception as e:
                model.guiapi.notification_center.showDialog(str(e))
                creation_ok = False

        return creation_ok

    def remove_workspace(self, button, ws_name):
        """Removes a workspace. If the workspace to be deleted is the one
        selected, it moves you to the one above it on the list. If there
        aren't more workspaces left, you will be forced to create one.
        The clears and refreshes
        sidebar"""
        model.api.log("Removing Workspace: %s" % ws_name)
        server_response = self.getWorkspaceManager().removeWorkspace(ws_name)
        if server_response:
            self.ws_sidebar.clear_sidebar()
            self.ws_sidebar.refresh_sidebar()
            available_workspaces = self.serverIO.get_workspaces_names()
            if available_workspaces:
                self.select_last_workspace_in_list(available_workspaces)
            else:
                self.handle_no_active_workspace()

    def lost_db_connection(self, explanatory_message=None,
                                   handle_connection_lost=None,
                                   connect_to_a_different_couch=None):
        """Creates a simple dialog with an error message to inform the user
        some kind of problem has happened and the connection was lost.
        """

        # NOTE: if we start faraday without CouchDB, both the signal coming
        # from CouchDB manager AND our test in do_activate will try
        # to raise the dialog. This avoids more than one dialog to be raised.
        if self.lost_connection_dialog_raised:
            return False

        def do_nothing_on_key_stroke(event, key):
            """Do nothing except return True"""
            return True

        self.lost_connection_dialog_raised = True

        if explanatory_message and isinstance(explanatory_message, basestring):
            explanation = "\n The specific error was: " + explanatory_message
        else:
            explanation = ""

        dialog = Gtk.MessageDialog(self.window, 0,
                                   Gtk.MessageType.ERROR,
                                   Gtk.ButtonsType.NONE,
                                   "The client can't connect to Faraday Server. "
                                   "You can try to reconnect to the last URL "
                                   "you set up, change it or exit Faraday "
                                   "until you fix the problem. \n"
                                   "For more information about Faraday Server "
                                   "please refer to the Faraday Github Wiki. \n "
                                   + explanation)

        dialog.set_deletable(False)

        dialog.set_modal(True)
        dialog.connect("key_press_event", do_nothing_on_key_stroke)

        retry_button = dialog.add_button("Retry connection?", 42)
        retry_button.connect("clicked", handle_connection_lost, dialog)

        change_couch_url = dialog.add_button("Change server IP?", 43)
        change_couch_url.connect("clicked", connect_to_a_different_couch, dialog)

        cancel_button = dialog.add_button("Exit Faraday", 0)
        cancel_button.connect("clicked", self.on_quit)

        response = dialog.run()
        if response == Gtk.ResponseType.DELETE_EVENT:
            GObject.idle_add(self.exit_faraday_without_confirm)

    def handle_no_active_workspace(self):
        """If there's been a problem opening a workspace or for some reason
        we suddenly find our selves without one, force the user
        to select one if possible, or if not, to create one.
        """
        def change_flag(widget):
            self.workspace_dialogs_raised = not self.workspace_dialogs_raised

        if self.workspace_dialogs_raised:
            return False

        if not self.serverIO.is_server_up():
            # make sure it is not because we're not connected to Couch
            # there's another whole strategy for that.
            return False

        self.workspace_dialogs_raised = True
        self.ws_sidebar.refresh_sidebar()

        available_workspaces = self.serverIO.get_workspaces_names()
        workspace_model = self.ws_sidebar.workspace_model

        if available_workspaces:
            dialog = ForceChooseWorkspaceDialog(self.window,
                                                workspace_model,
                                                self.change_workspace)

        else:
            dialog = ForceNewWorkspaceDialog(self.window,
                                             self.createWorkspace,
                                             self.workspace_manager,
                                             self.ws_sidebar,
                                             self.exit_faraday)

        dialog.connect("destroy", change_flag)
        dialog.show_all()

    def select_active_workspace(self):
        """Selects on the sidebar the currently active workspace."""
        self.ws_sidebar.select_ws_by_name(self.active_ws_name)

    def select_last_workspace_in_list(self, ws_names_list):
        self.ws_sidebar.select_ws_by_name(ws_names_list[-1])

    def exit_faraday(self, button=None, parent=None):
        """A simple exit which will ask for confirmation."""
        if not self.window.do_delete_event(parent):
            if parent is not None:
                GObject.idle_add(parent.destroy)
            GObject.idle_add(self.window.destroy)

    def exit_faraday_without_confirm(self, widget=None):
        """Exits faraday without confirm. Used as a middle-man between
        connect callbacks (which will send the widget as an argument and
        self.window.destroy, which takes none.
        """
        getLogger(self).error("Faraday exited because you didn't connect "
                              "to a valid Faraday Server.")
        GObject.idle_add(self.window.destroy)
        GObject.idle_add(self.on_quit)

    def force_change_couch_url(self, button=None, dialog=None):
        """Forces the user to change the couch URL. You **will** ended up
        connected to CouchDB or you will exit my application, cowboy.
        """

        # destroy the ugly dialog that got us here
        if dialog is not None:
            dialog.destroy()

        preference_window = ForcePreferenceWindowDialog(self.reload_workspaces,
                                                        self.connect_to_couch,
                                                        self.window,
                                                        self.exit_faraday)

        preference_window.run()

    def connect_to_couch(self, server_uri, parent=None):
        """Tries to connect to a CouchDB on a specified Couch URI.
        Returns the success status of the operation, False for not successful,
        True for successful
        """
        if parent is None:
            parent = self.window

        if not self.serverIO.test_server_url(server_uri):
            errorDialog(parent, "Could not connect to Faraday Server.",
                        ("Are you sure it is running and that you can "
                         "connect to it? \n Make sure your username and "
                         "password are still valid."))
            success = False
        elif server_uri.startswith("https://"):
            if not checkSSL(server_uri):
                errorDialog(self.window,
                            "The SSL certificate validation has failed")
            success = False
        else:
            CONF.setCouchUri(server_uri)
            CONF.saveConfig()
            self.reload_workspaces()
            self.open_last_workspace()
            success = True
            self.lost_connection_dialog_raised = False
        return success

    def handle_connection_lost(self, button=None, dialog=None):
        """Tries to connect to Couch using the same URI"""
        couch_uri = CONF.getCouchURI()
        if self.connect_to_couch(couch_uri, parent=dialog):
            reconnected = True
            if dialog is not None:
                dialog.destroy()
                self.open_last_workspace()
                self.lost_connection_dialog_raised = False
        else:
            reconnected = False
        return reconnected

    def update_counts(self):
        """Returns the counts of hosts, services and vulns on the current
        workspace."""
        hosts, interfaces, services, vulns = self.serverIO.get_workspace_numbers()
        return hosts, services, vulns

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

        Return True if everything went OK, False if there was a problem
        looking for the host."""
        current_ws_name = self.get_active_workspace().name

        #for host in self.model_controller.getAllHosts():
        host = self.serverIO.get_hosts(couchid=host_id)
        if not host:
            self.show_normal_error("The host you clicked isn't accessible. "
                                   "This is most probably due to an internal "
                                   "error.")
            return False

        info_window = HostInfoDialog(self.window, current_ws_name, host[0])
        info_window.show_all()
        return True

    def reload_workspaces_no_connection(self):
        """Very similar to reload_workspaces, but doesn't resource the
        workspace_manager to avoid asking for information to a database
        we can't access."""
        self.workspace_manager.closeWorkspace()
        self.ws_sidebar.clear_sidebar()

    def reload_workspaces(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.ws_sidebar.clear_sidebar()
        self.ws_sidebar.refresh_sidebar()

    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()
        GObject.idle_add(self.statusbar.set_default_notif_label)

    def change_workspace(self, workspace_name):
        """Changes workspace in a separate thread. Emits a signal
        to present a 'Loading workspace' dialog while Faraday processes
        the change. If there are conflict present in the workspace, it will
        show a warning before changing the workspaces."""

        def loading_workspace(action):
            """Function to be called via GObject.idle_add by the background
            process.  Preconditions: show must have been called before destroy
            can be called.
            """

            if action == "show" and not self.loading_dialog_raised:
                message_string = ("Loading workspace {0}. Please wait. \n"
                                  "To cancel, press Alt+F4 or a similar shorcut."
                                  .format(workspace_name))

                self.loading_dialog_raised = True
                self.loading_dialog = Gtk.MessageDialog(self.window, 0,
                                                        Gtk.MessageType.INFO,
                                                        Gtk.ButtonsType.NONE,
                                                        message_string)

                self.loading_dialog.set_modal(True)

                # on every key stroke just return true, wont allow user
                # to press scape
                self.loading_dialog.connect("key_press_event",
                                            lambda _, __: True)

                self.loading_dialog.connect("delete_event",
                                            lambda _, __: self.handle_no_active_workspace())

                self.loading_dialog.show_all()

            if action == "destroy":
                self.loading_dialog.destroy()
                self.loading_dialog_raised = False

        def background_process():
            """Change workspace. This function runs on a separated thread
            created by the parent function. DO NOT call any Gtk methods
            withing its scope, except by emiting signals to the window
            """
            GObject.idle_add(loading_workspace, 'show')
            try:
                ws = super(GuiApp, self).openWorkspace(workspace_name)
                GObject.idle_add(CONF.setLastWorkspace, ws.name)
                GObject.idle_add(CONF.saveConfig)
            except Exception as e:
                GObject.idle_add(self.handle_no_active_workspace)
                getLogger("GTK").error(e)

            GObject.idle_add(loading_workspace, 'destroy')
            return True

        self.ws_sidebar.select_ws_by_name(workspace_name)
        if self.statusbar.conflict_button_label_int > 0:
            response = self.window.show_conflicts_warning()
            if response == Gtk.ResponseType.NO:
                self.select_active_workspace()
                return False

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

    def open_workspace_from_args(self):
        """Opens the workspace specified in the arguemnts, if possible.
        Return True if args.workspace is set, False if not."""
        if self.args.workspace:
            workspace_name = self.args.workspace
            self.change_workspace(workspace_name)
            return True
        else:
            return False

    def open_last_workspace(self):
        """Tries to open the last workspace the user had opened. Return
        None."""
        workspace_name = CONF.getLastWorkspace()
        self.change_workspace(workspace_name)

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

    ##########################################################################
    # NOTE: uninteresting part below. do not touch unless you have a very    #
    # good reason, or you want to connect a new button on the toolbar,       #
    # or, maybe most probably, you wanna register a new signal on            #
    # postEvent().                                                           #
    # Remember! -- even the best advice must sometimes not be heeded.        #
    ##########################################################################

    def postEvent(self, _, event):
        """Handles the events from gui/customevents. The second
        argument is the 'receiver', but as this was made for QT3 it is now
        deprecated and we must manually set the receiver until the
        events module is updated.

        DO NOT, AND I REPEAT, DO NOT REDRAW *ANYTHING* FROM THE GUI
        FROM HERE. If you must do it, you should to it sing GObject.idle_add,
        a misterious function with outdated documentation. Good luck."""

        def new_log_event():
            GObject.idle_add(self.console_log.customEvent, event.text)

        def new_conflict_event():
            GObject.idle_add(self.statusbar.update_conflict_button_label,
                             event.nconflicts)

        def new_notification_event():
            self.notificationsModel.prepend([str(event)])
            GObject.idle_add(self.statusbar.inc_notif_button_label)
            host_count, service_count, vuln_count = self.update_counts()
            GObject.idle_add(self.statusbar.update_ws_info, host_count,
                             service_count, vuln_count)

        def workspace_changed_event():
            self.serverIO.active_workspace = event.workspace.name
            host_count, service_count, vuln_count = self.update_counts()
            total_host_amount = self.serverIO.get_hosts_number()
            first_host_page = self.serverIO.get_hosts(page='0', page_size='20', sort='vulns', sort_dir='desc')
            GObject.idle_add(self.statusbar.set_workspace_label, event.workspace.name)
            GObject.idle_add(self.hosts_sidebar.redo, first_host_page, total_host_amount)
            GObject.idle_add(self.statusbar.update_ws_info, host_count,
                             service_count, vuln_count)
            GObject.idle_add(self.statusbar.set_default_conflict_label)
            GObject.idle_add(self.statusbar.set_default_conflict_label)
            GObject.idle_add(self.select_active_workspace)

        def normal_error_event():
            GObject.idle_add(self.show_normal_error, event.text)

        def important_error_event():
            GObject.idle_add(self.show_important_error, event)

        def lost_connection_to_server_event():
            GObject.idle_add(self.lost_db_connection, event.problem,
                             self.handle_connection_lost,
                             self.force_change_couch_url)
            GObject.idle_add(self.reload_workspaces_no_connection)

        def workspace_not_accessible_event():
            GObject.idle_add(self.handle_no_active_workspace)

        def add_object():
            GObject.idle_add(self.hosts_sidebar.add_object, event.new_obj)
            host_count, service_count, vuln_count = self.update_counts()
            GObject.idle_add(self.statusbar.update_ws_info, host_count,
                             service_count, vuln_count)

        def delete_object():
            GObject.idle_add(self.hosts_sidebar.remove_object, event.obj_id)
            host_count, service_count, vuln_count = self.update_counts()
            GObject.idle_add(self.statusbar.update_ws_info, host_count,
                             service_count, vuln_count)

        def update_object():
            GObject.idle_add(self.hosts_sidebar.update_object, event.obj)
            host_count, service_count, vuln_count = self.update_counts()
            GObject.idle_add(self.statusbar.update_ws_info, host_count,
                             service_count, vuln_count)

        dispatch = {3131:  new_log_event,
                    3141:  new_conflict_event,
                    5100:  new_notification_event,
                    3140:  workspace_changed_event,
                    3132:  normal_error_event,
                    3134:  important_error_event,
                    42424: lost_connection_to_server_event,
                    24242: workspace_not_accessible_event,
                    7777:  add_object,
                    8888:  delete_object,
                    9999:  update_object}

        function = dispatch.get(event.type())
        if function is not None:
            function()

    def show_normal_error(self, dialog_text):
        """Just a simple, normal, ignorable error"""
        dialog = Gtk.MessageDialog(self.window, 0,
                                   Gtk.MessageType.ERROR,
                                   Gtk.ButtonsType.OK,
                                   dialog_text)
        dialog.run()
        dialog.destroy()

    def show_important_error(self, event):
        """Creates an importan error dialog with a callback to send
        the developers the error traceback.
        """
        dialog_text = event.text
        dialog = ImportantErrorDialog(self.window, dialog_text)
        response = dialog.run()
        if response == 42:
            error = event.error_name
            event.callback(error, *event.exception_objects)
        dialog.destroy()

    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.serverIO = ServerIO(CONF.getLastWorkspace())
        self.serverIO.continously_check_server_connection()

        self.ws_sidebar = WorkspaceSidebar(self.serverIO,
                                           self.change_workspace,
                                           self.remove_workspace,
                                           self.on_new_button,
                                           CONF.getLastWorkspace())

        # the dummy values here will be updated as soon as the ws is loaded.
        self.hosts_sidebar = HostsSidebar(self.show_host_info, self.serverIO.get_hosts,
                                          self.icons)
        default_model = self.hosts_sidebar.create_model([]) # dummy empty list
        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 = 0, 0, 0  # dummy values
        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_to_method = {"about": self.on_about,
                            "quit": self.on_quit,
                            "preferences": self.on_preferences,
                            "pluginOptions": self.on_plugin_options,
                            "new": self.on_new_button,
                            "new_terminal": self.on_new_terminal_button,
                            "open_report": self.on_open_report_button,
                            "go_to_web_ui": self.on_click_go_to_web_ui_button,
                            "go_to_documentation": self.on_help_dispatch,
                            "go_to_faq": self.on_help_dispatch,
                            "go_to_troubleshooting": self.on_help_dispatch,
                            "go_to_demos": self.on_help_dispatch,
                            "go_to_issues": self.on_help_dispatch,
                            "go_to_forum": self.on_help_dispatch,
                            "go_to_irc": self.on_help_dispatch,
                            "go_to_twitter": self.on_help_dispatch,
                            "go_to_googlegroup": self.on_help_dispatch
                            }

        for action, method in action_to_method.items():
            gio_action = Gio.SimpleAction.new(action, None)
            gio_action.connect("activate", method)
            self.add_action(gio_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.ws_sidebar,
                                    self.hosts_sidebar,
                                    self.terminal,
                                    self.console_log,
                                    self.statusbar,
                                    application=self,
                                    title="Faraday " + str(CONF.getVersion()))

        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)

        if not self.serverIO.is_server_up():
            self.lost_db_connection(
                handle_connection_lost=self.handle_connection_lost,
                connect_to_a_different_couch=self.force_change_couch_url)

        workspace_argument_set = self.open_workspace_from_args()
        if not workspace_argument_set:
            self.open_last_workspace()

    def on_quit(self, action=None, param=None):
        self.quit()

    def on_plugin_options(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)
        terminal_scrolled = new_terminal.create_scrollable_terminal()
        self.window.new_tab(terminal_scrolled)

    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.
        """
        conflicts = self.model_controller.getConflicts()
        if conflicts:
            dialog = ConflictsDialog(conflicts,
                                     self.window)
            dialog.show_all()

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

    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()
            combo_box.set_wrap_width(3)
            for plugin_id in plugins_id:
                combo_box.append_text(plugin_id)
            combo_box.show()

            dialog.vbox.pack_start(combo_box, False, 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.FileChooserDialog(title="Import a report",
                                               parent=self.window,
                                               action=Gtk.FileChooserAction.OPEN,
                                               buttons=("Open", Gtk.ResponseType.ACCEPT,
                                                        "Cancel", Gtk.ResponseType.CANCEL)
                                               )
                dialog.set_modal(True)

                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_preferences(self, action=None, param=None):
        """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.reload_workspaces,
                                                   self.connect_to_couch,
                                                   self.window)
        preference_window.show_all()

    def on_click_go_to_web_ui_button(self, action=None, param=None):
        """Opens the dashboard of the current workspace on a new tab of
        the user's default browser
        """
        couch_url = CONF.getCouchURI()
        ws_name = self.workspace_manager.getActiveWorkspace().name
        ws_url = couch_url + "/_ui/#/dashboard/ws/" + ws_name
        webbrowser.open(ws_url, new=2)

    def on_help_dispatch(self, action, param=None):
        """Open the url contained in "action" in the user's browser."""
        urls = {"go_to_documentation":  "https://faradaysec.com/help/docs",
                "go_to_faq": "https://faradaysec.com/help/faq",
                "go_to_troubleshooting": "https://faradaysec.com/help/troubleshooting",
                "go_to_demos": "https://faradaysec.com/help/demos",
                "go_to_issues": "https://faradaysec.com/help/issues",
                "go_to_forum": "https://forum.faradaysec.com",
                "go_to_irc": "https://faradaysec.com/help/irc",
                "go_to_twitter": "https://faradaysec.com/help/twitter",
                "go_to_googlegroup": "https://faradaysec.com/help/googlegroup"
        }
        url = urls.get(action.get_name(), "https://faradaysec.com")
        webbrowser.open(url, new=2)
示例#30
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()
示例#31
0
class MyTestCase(unittest.TestCase):
    def setUp(self) -> None:
        current_file_path()
        self.testcif = Path('tests/examples/1979688.cif').resolve()
        self.app = AppWindow(self.testcif)
        self.app.running_inside_unit_test = True
        self.app.hide()
        self.author = {
            'address': 'address',
            'footnote': 'footnote',
            'email': 'email',
            'name': 'name',
            'orcid': 'orcid',
            'phone': 'phone',
            'contact': True
        }

    def tearDown(self) -> None:
        current_file_path()
        Path('tests/other_templates/testexport_author.cif').unlink(
            missing_ok=True)
        self.app.close()

    def _import_testauthor(self):
        # To be used in other tests
        self.app.authors.import_author('../other_templates/AATest_Author.cif')
        self.app.ui.LoopTemplatesListWidget.setCurrentRow(0)

    def test_selected_loop_without_selection(self):
        self.assertEqual(self.app.authors.get_selected_loop_name(), '')

    def test_selected_loop_with_selection(self):
        self._delete_test_author()
        self._import_testauthor()
        self.assertEqual('AATest Author',
                         self.app.authors.get_selected_loop_name())

    def _delete_test_author(self, name='AATest Author'):
        self._select_row_of_author(name)
        self.app.ui.DeleteLoopAuthorTemplateButton.click()
        self.assertNotEqual('AATest Author',
                            self.app.authors.get_selected_loop_name())

    def _select_row_of_author(self, name):
        listw = self.app.ui.LoopTemplatesListWidget
        for row in range(listw.count()):
            listw.setCurrentRow(row)
            if listw.currentItem().text().startswith(name):
                break

    def test_export_selected_author(self):
        self._delete_test_author()
        self._import_testauthor()
        self.app.authors.export_author_template(
            '../other_templates/testexport_author.cif')
        self.assertEqual(
            True,
            Path('../other_templates/testexport_author.cif').exists())
        self.assertEqual(
            Path('../other_templates/testexport_author.cif').read_text(),
            Path('../other_templates/testexport_author.cif').read_text())

    def test_set_name(self):
        self.app.ui.FullNameLineEdit.setText('test')
        self.assertEqual('test',
                         self.app.authors.get_author_info().get('name'))

    def test_set_contact_author(self):
        self.assertEqual(False, self.app.ui.ContactAuthorCheckBox.isChecked())
        self.app.ui.ContactAuthorCheckBox.setChecked(True)
        self.assertEqual(True,
                         self.app.authors.get_author_info().get('contact'))

    def test_set_address(self):
        self.app.ui.AddressTextedit.setText('Eine Adresse 1')
        self.assertEqual("'Eine Adresse 1'",
                         self.app.authors.get_author_info().get('address'))

    def test_set_footnote(self):
        self.app.ui.FootNoteLineEdit.setText('notex')
        self.assertEqual('notex',
                         self.app.authors.get_author_info().get('footnote'))

    def test_set_email(self):
        self.app.ui.EMailLineEdit.setText('*****@*****.**')
        self.assertEqual('*****@*****.**',
                         self.app.authors.get_author_info().get('email'))

    def test_set_orcid(self):
        self.app.ui.ORCIDLineEdit.setText('12345a')
        self.assertEqual('12345a',
                         self.app.authors.get_author_info().get('orcid'))

    def test_set_phone(self):
        self.app.ui.PhoneLineEdit.setText('12345a')
        self.assertEqual('12345a',
                         self.app.authors.get_author_info().get('phone'))

    def test_set_foo(self):
        self.assertEqual(None, self.app.authors.get_author_info().get('foo'))

    def test_set_author_info(self):
        self.app.authors.set_author_info(self.author)
        self.assertEqual('name', self.app.ui.FullNameLineEdit.text())
        self.assertEqual('address', self.app.ui.AddressTextedit.toPlainText())
        self.assertEqual('email', self.app.ui.EMailLineEdit.text())
        self.assertEqual('orcid', self.app.ui.ORCIDLineEdit.text())
        self.assertEqual('footnote', self.app.ui.FootNoteLineEdit.text())
        self.assertEqual('phone', self.app.ui.PhoneLineEdit.text())
        self.assertEqual(True, self.app.ui.ContactAuthorCheckBox.isChecked())

    def test_set_author_info_and_clear(self):
        self.app.authors.set_author_info(self.author)
        self.app.authors.clear_fields()
        self.assertEqual('', self.app.ui.FullNameLineEdit.text())
        self.assertEqual('', self.app.ui.AddressTextedit.toPlainText())
        self.assertEqual('', self.app.ui.EMailLineEdit.text())
        self.assertEqual('', self.app.ui.ORCIDLineEdit.text())
        self.assertEqual('', self.app.ui.FootNoteLineEdit.text())
        self.assertEqual('', self.app.ui.PhoneLineEdit.text())
        self.assertEqual(False, self.app.ui.ContactAuthorCheckBox.isChecked())

    def test_save_author(self):
        pass
 def init(self):
     register_all_png_icons(const.internal_path(const.PATH_ICONS), 'sloppy-')
     
     self.window = AppWindow(self)
     self._clipboard = gtk.Clipboard()  # not implemented yet
     self._current_plot = None
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):
        register_all_png_icons(const.internal_path(const.PATH_ICONS), 'sloppy-')
        
        self.window = AppWindow(self)
        self._clipboard = gtk.Clipboard()  # not implemented yet
        self._current_plot = None
        

    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.

        @param confirm: Ask user 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()
        self.window._refresh_recentfiles()


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

        if filename is None:
            # TODO
            # maybe we could have application.load_project
            # just request the file name and we simply
            # create a method for this dialog.
            
            # 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):
        """ Save project under another filename. """
        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()

        self.recent_files.append(os.path.abspath(filename))
        Signals.emit(self, 'update-recent-files')


    def quit(self):
        """ Quit Application and gtk main loop. """
        try:
            Application.quit(self)
            gtk.main_quit()
        except error.UserCancel:
            return


    #------------------------------------------------------------------------------
    # Current plot
    #

    def set_current_plot(self, plot):
        print "APP: set_current_plto to ", plot
        self._current_plot = plot
        Signals.emit(self, "notify::current_plot", plot)

    def get_current_plot(self):
        return self._current_plot

    current_plot = property(get_current_plot, set_current_plot)

    
    # ----------------------------------------------------------------------
    # Callbacks
    #
    
    # delete-event/destroy/quit application
            
    def _cb_quit_application(self, action): self.quit()       

    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)        

                        
    # --- 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])
        


        
    # --- 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)
                ##window.set_transient_for(self.window)
                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 on_action_export_via_gnuplot(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('ps', 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(),
                               title="Options Postscript Export",
                               parent=app.window)
        #dialog.set_size_request(320,520)

        # determine requested postscript mode (ps or eps) from extension
        path, ext = os.path.splitext(filename)
        ext = ext.lower()
        if ext == '.eps':
            dialog.owner.mode = 'eps'
        elif ext == '.ps':
            dialog.owner.mode = 'landscape'
            
        try:
            result = dialog.run()
            if response == gtk.RESPONSE_ACCEPT:            
                dialog.check_out()
            else:
                return
            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['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, importer) in ImporterRegistry.iteritems():
            extensions = ';'.join(map(lambda ext: '*.'+ext, importer.extensions))
            blurb = "%s (%s)" % (importer.blurb, extensions)

            filter = gtk.FileFilter()
            filter.set_name(blurb)
            for ext in importer.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)

        #
        # prepare extra widget
        #
        
        # The custom widget `combobox` lets the user choose,
        # which Importer is to be used.
        
        # 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, importer in ImporterRegistry.iteritems():
            model.append( (key, importer.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()

        label = gtk.Label("Importer")
        label.show()
            
        hbox = gtk.HBox()       
        hbox.pack_end(combobox,False)
        hbox.pack_end(label,False)
        hbox.show()        

        # The progress bar display which file is currently being imported.
        pbar = gtk.ProgressBar()
        
        vbox = gtk.VBox()
        vbox.pack_start(hbox,False)
        vbox.pack_start(pbar,False)
        vbox.show()
        
        chooser.set_extra_widget(vbox)


        #
        # 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

            # TODO
            #chooser.set_active(False)
            pbar.show()
            
            # request import options
            importer = ImporterRegistry[importer_key]()

            try:
                dialog = OptionsDialog(importer, parent=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()
                    if result == gtk.RESPONSE_ACCEPT:
                        dialog.check_out
                    else:
                        return
                finally:
                    dialog.destroy()


            def set_text(queue):
                while True:
                    try:
                        text, fraction = queue.get()
                        if text == -1:
                            pbar.hide()
                        elif text is not None:
                            pbar.set_text(text)                                        
                        if fraction is not None:
                            pbar.set_fraction(fraction)
                    except QueueEmpty:
                        pass
                    yield None

            queue = Queue()
            thread_progress = GIdleThread(set_text(queue))
            thread_progress.start()

            thread_import = GIdleThread(self.import_datasets(pj, filenames, importer), queue)
            thread_import.start()
            thread_import.wait()

        finally:
            chooser.destroy()



    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()
        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()


    #----------------------------------------------------------------------
    # MISC CALLBACKS

    def _cb_recent_files_clear(self, action):
        self.clear_recent_files()


    #----------------------------------------------------------------------
    # Simple user I/O
    #

    def ask_yes_no(self, msg):
        dialog = gtk.MessageDialog(parent=self.window,
                                   flags=0,
                                   type=gtk.MESSAGE_WARNING,
                                   buttons=gtk.BUTTONS_YES_NO,
                                   message_format=msg)
            
        result = dialog.run()
        dialog.destroy()

        return result == gtk.RESPONSE_YES


    def error_message(self, msg):
        dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
                                   buttons=gtk.BUTTONS_OK,
                                   message_format=unicode(msg))
        dialog.run()
        dialog.destroy()
示例#34
0
 def setUp(self) -> None:
     os.chdir(Path(__file__).absolute().parent.parent)
     self.testcif = Path('tests/examples/1979688.cif').absolute()
     self.myapp = AppWindow(self.testcif)
     self.myapp.running_inside_unit_test = True
     self.myapp.hide()  # For full screen view
示例#35
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.

    Please respect the following structure:
    TOP: __init__
    UPPER-MIDDLE: all logic mostly not inherited fom Gtk.Application
    LOWER-MIDDLE: all do_ starting, gtk related methods
    BOTTOM: all on_ starting, dialog opener methods

    """

    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.lost_connection_dialog_raised = None
        self.workspace_dialogs_raised = None
        self.loading_dialog_raised = 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

    @property
    def active_ws_name(self):
        return self.get_active_workspace().name

    def get_active_workspace(self):
        """Return the currently active workspace"""
        return self.workspace_manager.getActiveWorkspace()

    def getMainWindow(self):
        """Useless mostly, but guiapi uses this method to access the main
        window."""
        return self.window

    def createWorkspace(self, name, description=""):
        """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.workspace_manager.getWorkspacesNames():
            error_str = "A workspace with name %s already exists" % name
            model.api.log(error_str, "ERROR")
            errorDialog(self.window, error_str)
            creation_ok = False
        else:
            model.api.log("Creating workspace '%s'" % name)
            model.api.devlog("Looking for the delegation class")
            manager = self.getWorkspaceManager()
            try:
                name = manager.createWorkspace(name, description)
                self.change_workspace(name)
                creation_ok = True
            except Exception as e:
                model.guiapi.notification_center.showDialog(str(e))
                creation_ok = False

        return creation_ok

    def remove_workspace(self, button, ws_name):
        """Removes a workspace. If the workspace to be deleted is the one
        selected, it moves you to the one above it on the list. If there
        aren't more workspaces left, you will be forced to create one.
        The clears and refreshes
        sidebar"""
        model.api.log("Removing Workspace: %s" % ws_name)
        server_response = self.getWorkspaceManager().removeWorkspace(ws_name)
        if server_response:
            self.ws_sidebar.clear_sidebar()
            self.ws_sidebar.refresh_sidebar()
            available_workspaces = self.serverIO.get_workspaces_names()
            if available_workspaces:
                self.select_last_workspace_in_list(available_workspaces)
            else:
                self.handle_no_active_workspace()

    def lost_db_connection(self, explanatory_message=None,
                           handle_connection_lost=None,
                           connect_to_a_different_couch=None):
        """Creates a simple dialog with an error message to inform the user
        some kind of problem has happened and the connection was lost.
        """

        # NOTE: if we start faraday without CouchDB, both the signal coming
        # from CouchDB manager AND our test in do_activate will try
        # to raise the dialog. This avoids more than one dialog to be raised.
        if self.lost_connection_dialog_raised:
            return False

        def do_nothing_on_key_stroke(event, key):
            """Do nothing except return True"""
            return True

        self.lost_connection_dialog_raised = True

        if explanatory_message and isinstance(explanatory_message, basestring):
            explanation = "\n The specific error was: " + explanatory_message
        else:
            explanation = ""

        dialog = Gtk.MessageDialog(self.window, 0,
                                   Gtk.MessageType.ERROR,
                                   Gtk.ButtonsType.NONE,
                                   "The client can't connect to Faraday Server. "
                                   "You can try to reconnect to the last URL "
                                   "you set up, change it or exit Faraday "
                                   "until you fix the problem. \n"
                                   "For more information about Faraday Server "
                                   "please refer to the Faraday Github Wiki. \n "
                                   + explanation)

        dialog.set_deletable(False)

        dialog.set_modal(True)
        dialog.connect("key_press_event", do_nothing_on_key_stroke)

        retry_button = dialog.add_button("Retry connection?", 42)
        retry_button.connect("clicked", handle_connection_lost, dialog)

        change_couch_url = dialog.add_button("Change server IP?", 43)
        change_couch_url.connect("clicked", connect_to_a_different_couch, dialog)

        cancel_button = dialog.add_button("Exit Faraday", 0)
        cancel_button.connect("clicked", self.on_quit)

        response = dialog.run()
        if response == Gtk.ResponseType.DELETE_EVENT:
            GObject.idle_add(self.exit_faraday_without_confirm)

    def handle_no_active_workspace(self):
        """If there's been a problem opening a workspace or for some reason
        we suddenly find our selves without one, force the user
        to select one if possible, or if not, to create one.
        """

        def change_flag(widget):
            self.workspace_dialogs_raised = not self.workspace_dialogs_raised

        if self.workspace_dialogs_raised:
            return False

        if self.serverIO.server_info() is None:
            # make sure it is not because we're not connected to Couch
            # there's another whole strategy for that.
            return False

        self.workspace_dialogs_raised = True
        self.ws_sidebar.refresh_sidebar()

        available_workspaces = self.serverIO.get_workspaces_names()
        workspace_model = self.ws_sidebar.workspace_model

        if available_workspaces:
            dialog = ForceChooseWorkspaceDialog(self.window,
                                                workspace_model,
                                                self.change_workspace)

        else:
            dialog = ForceNewWorkspaceDialog(self.window,
                                             self.createWorkspace,
                                             self.workspace_manager,
                                             self.ws_sidebar,
                                             self.exit_faraday)

        dialog.connect("destroy", change_flag)
        dialog.show_all()

    def select_active_workspace(self):
        """Selects on the sidebar the currently active workspace."""
        self.ws_sidebar.select_ws_by_name(self.active_ws_name)

    def select_last_workspace_in_list(self, ws_names_list):
        self.ws_sidebar.select_ws_by_name(ws_names_list[-1])

    def exit_faraday(self, button=None, parent=None):
        """A simple exit which will ask for confirmation."""
        if not self.window.do_delete_event(parent):
            if parent is not None:
                GObject.idle_add(parent.destroy)
            GObject.idle_add(self.window.destroy)

    def exit_faraday_without_confirm(self, widget=None):
        """Exits faraday without confirm. Used as a middle-man between
        connect callbacks (which will send the widget as an argument and
        self.window.destroy, which takes none.
        """
        getLogger(self).error("Faraday exited because you didn't connect "
                              "to a valid Faraday Server.")
        GObject.idle_add(self.window.destroy)
        GObject.idle_add(self.on_quit)

    def force_change_couch_url(self, button=None, dialog=None):
        """Forces the user to change the couch URL. You **will** ended up
        connected to CouchDB or you will exit my application, cowboy.
        """

        # destroy the ugly dialog that got us here
        if dialog is not None:
            dialog.destroy()

        preference_window = ForcePreferenceWindowDialog(self.reload_workspaces,
                                                        self.connect_to_couch,
                                                        self.window,
                                                        self.exit_faraday)

        preference_window.run()

    def connect_to_couch(self, server_uri, parent=None):
        """Tries to connect to a CouchDB on a specified Couch URI.
        Returns the success status of the operation, False for not successful,
        True for successful
        """
        if parent is None:
            parent = self.window

        if not self.serverIO.test_server_url(server_uri):
            errorDialog(parent, "Could not connect to Faraday Server.",
                        ("Are you sure it is running and that you can "
                         "connect to it? \n Make sure your username and "
                         "password are still valid."))
            success = False
        elif server_uri.startswith("https://"):
            if not checkSSL(server_uri):
                errorDialog(self.window,
                            "The SSL certificate validation has failed")
            success = False
        else:
            try:
                check_faraday_version()
            except RuntimeError:
                errorDialog(parent,
                            "The server ir running a different Faraday version then the "
                            "client you are runnung. Version numbers must match!")
                success = False
                return success
            CONF.setCouchUri(server_uri)
            CONF.saveConfig()
            self.reload_workspaces()
            self.open_last_workspace()
            success = True
            self.lost_connection_dialog_raised = False
        return success

    def handle_connection_lost(self, button=None, dialog=None):
        """Tries to connect to Couch using the same URI"""
        couch_uri = CONF.getCouchURI()
        if self.connect_to_couch(couch_uri, parent=dialog):
            reconnected = True
            if dialog is not None:
                dialog.destroy()
                self.open_last_workspace()
                self.lost_connection_dialog_raised = False
        else:
            reconnected = False
        return reconnected

    def update_counts(self):
        """Returns the counts of hosts, services and vulns on the current
        workspace."""
        hosts, interfaces, services, vulns = self.serverIO.get_workspace_numbers()
        return hosts, services, vulns

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

        Return True if everything went OK, False if there was a problem
        looking for the host."""
        current_ws_name = self.get_active_workspace().name

        # for host in self.model_controller.getAllHosts():
        host = self.serverIO.get_hosts(couchid=host_id)
        if not host:
            self.show_normal_error("The host you clicked isn't accessible. "
                                   "This is most probably due to an internal "
                                   "error.")
            return False

        info_window = HostInfoDialog(self.window, current_ws_name, host[0])
        info_window.show_all()
        return True

    def reload_workspaces_no_connection(self):
        """Very similar to reload_workspaces, but doesn't resource the
        workspace_manager to avoid asking for information to a database
        we can't access."""
        self.workspace_manager.closeWorkspace()
        self.ws_sidebar.clear_sidebar()

    def reload_workspaces(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.ws_sidebar.clear_sidebar()
        self.ws_sidebar.refresh_sidebar()

    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()
        GObject.idle_add(self.statusbar.set_default_notif_label)

    def change_workspace(self, workspace_name):
        """Changes workspace in a separate thread. Emits a signal
        to present a 'Loading workspace' dialog while Faraday processes
        the change. If there are conflict present in the workspace, it will
        show a warning before changing the workspaces."""

        def loading_workspace(action):
            """Function to be called via GObject.idle_add by the background
            process.  Preconditions: show must have been called before destroy
            can be called.
            """

            if action == "show" and not self.loading_dialog_raised:
                message_string = ("Loading workspace {0}. Please wait. \n"
                                  "To cancel, press Alt+F4 or a similar shorcut."
                                  .format(workspace_name))

                self.loading_dialog_raised = True
                self.loading_dialog = Gtk.MessageDialog(self.window, 0,
                                                        Gtk.MessageType.INFO,
                                                        Gtk.ButtonsType.NONE,
                                                        message_string)

                self.loading_dialog.set_modal(True)

                # on every key stroke just return true, wont allow user
                # to press scape
                self.loading_dialog.connect("key_press_event",
                                            lambda _, __: True)

                self.loading_dialog.connect("delete_event",
                                            lambda _, __: self.handle_no_active_workspace())

                self.loading_dialog.show_all()

            if action == "destroy":
                self.loading_dialog.destroy()
                self.loading_dialog_raised = False

        def background_process():
            """Change workspace. This function runs on a separated thread
            created by the parent function. DO NOT call any Gtk methods
            withing its scope, except by emiting signals to the window
            """
            GObject.idle_add(loading_workspace, 'show')
            try:
                ws = super(GuiApp, self).openWorkspace(workspace_name)
                GObject.idle_add(CONF.setLastWorkspace, ws.name)
                GObject.idle_add(CONF.saveConfig)
            except Exception as e:
                GObject.idle_add(self.handle_no_active_workspace)
                getLogger("GTK").error(e)

            GObject.idle_add(loading_workspace, 'destroy')
            return True

        self.ws_sidebar.select_ws_by_name(workspace_name)
        if self.statusbar.conflict_button_label_int > 0:
            response = self.window.show_conflicts_warning()
            if response == Gtk.ResponseType.NO:
                self.select_active_workspace()
                return False

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

    def open_workspace_from_args(self):
        """Opens the workspace specified in the arguemnts, if possible.
        Return True if args.workspace is set, False if not."""
        if self.args.workspace:
            workspace_name = self.args.workspace
            self.change_workspace(workspace_name)
            return True
        else:
            return False

    def open_last_workspace(self):
        """Tries to open the last workspace the user had opened. Return
        None."""
        workspace_name = CONF.getLastWorkspace()
        self.change_workspace(workspace_name)

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

    ##########################################################################
    # NOTE: uninteresting part below. do not touch unless you have a very    #
    # good reason, or you want to connect a new button on the toolbar,       #
    # or, maybe most probably, you wanna register a new signal on            #
    # postEvent().                                                           #
    # Remember! -- even the best advice must sometimes not be heeded.        #
    ##########################################################################

    def postEvent(self, _, event):
        """Handles the events from gui/customevents. The second
        argument is the 'receiver', but as this was made for QT3 it is now
        deprecated and we must manually set the receiver until the
        events module is updated.

        DO NOT, AND I REPEAT, DO NOT REDRAW *ANYTHING* FROM THE GUI
        FROM HERE. If you must do it, you should to it sing GObject.idle_add,
        a misterious function with outdated documentation. Good luck."""

        def new_log_event():
            GObject.idle_add(self.console_log.customEvent, event.text)

        def new_conflict_event():
            GObject.idle_add(self.statusbar.update_conflict_button_label,
                             event.nconflicts)

        def new_notification_event():
            self.notificationsModel.prepend([str(event)])
            GObject.idle_add(self.statusbar.inc_notif_button_label)
            host_count, service_count, vuln_count = self.update_counts()
            GObject.idle_add(self.statusbar.update_ws_info, host_count,
                             service_count, vuln_count)

        def workspace_changed_event():
            self.serverIO.active_workspace = event.workspace.name
            host_count, service_count, vuln_count = self.update_counts()
            total_host_amount = self.serverIO.get_hosts_number()
            first_host_page = self.serverIO.get_hosts(page='0', page_size='20',
                                                      sort='vulns', sort_dir='desc')
            total_host_amount = self.serverIO.get_workspace_numbers()[0]
            GObject.idle_add(self.statusbar.set_workspace_label, event.workspace.name)
            GObject.idle_add(self.hosts_sidebar.reset_model_after_workspace_changed,
                             first_host_page, total_host_amount)
            GObject.idle_add(self.statusbar.update_ws_info, host_count,
                             service_count, vuln_count)
            GObject.idle_add(self.statusbar.set_default_conflict_label)
            GObject.idle_add(self.statusbar.set_default_conflict_label)
            GObject.idle_add(self.select_active_workspace)

        def normal_error_event():
            GObject.idle_add(self.show_normal_error, event.text)

        def important_error_event():
            GObject.idle_add(self.show_important_error, event)

        def lost_connection_to_server_event():
            GObject.idle_add(self.lost_db_connection, event.problem,
                             self.handle_connection_lost,
                             self.force_change_couch_url)
            GObject.idle_add(self.reload_workspaces_no_connection)

        def workspace_not_accessible_event():
            GObject.idle_add(self.handle_no_active_workspace)

        def add_object():
            if event.new_obj:
                GObject.idle_add(self.hosts_sidebar.add_object, event.new_obj)
                host_count, service_count, vuln_count = self.update_counts()
                GObject.idle_add(self.statusbar.update_ws_info, host_count,
                                 service_count, vuln_count)

        def delete_object():
            if event.obj_id:
                GObject.idle_add(self.hosts_sidebar.remove_object, event.obj_id)
                host_count, service_count, vuln_count = self.update_counts()
                GObject.idle_add(self.statusbar.update_ws_info, host_count,
                                 service_count, vuln_count)

        def update_object():
            if event.obj:
                GObject.idle_add(self.hosts_sidebar.update_object, event.obj)
                host_count, service_count, vuln_count = self.update_counts()
                GObject.idle_add(self.statusbar.update_ws_info, host_count,
                                 service_count, vuln_count)

        dispatch = {3131: new_log_event,
                    3141: new_conflict_event,
                    5100: new_notification_event,
                    3140: workspace_changed_event,
                    3132: normal_error_event,
                    3134: important_error_event,
                    42424: lost_connection_to_server_event,
                    24242: workspace_not_accessible_event,
                    7777: add_object,
                    8888: delete_object,
                    9999: update_object}

        function = dispatch.get(event.type())
        if function is not None:
            function()

    def show_normal_error(self, dialog_text):
        """Just a simple, normal, ignorable error"""
        dialog = Gtk.MessageDialog(self.window, 0,
                                   Gtk.MessageType.ERROR,
                                   Gtk.ButtonsType.OK,
                                   dialog_text)
        dialog.run()
        dialog.destroy()

    def show_important_error(self, event):
        """Creates an importan error dialog with a callback to send
        the developers the error traceback.
        """
        dialog_text = event.text
        dialog = ImportantErrorDialog(self.window, dialog_text)
        response = dialog.run()
        if response == 42:
            error = event.error_name
            event.callback(error, *event.exception_objects)
        dialog.destroy()

    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.serverIO = ServerIO(CONF.getLastWorkspace())
        self.serverIO.continously_check_server_connection()

        self.ws_sidebar = WorkspaceSidebar(self.serverIO,
                                           self.change_workspace,
                                           self.remove_workspace,
                                           self.on_new_button,
                                           CONF.getLastWorkspace())

        # the dummy values here will be updated as soon as the ws is loaded.
        self.hosts_sidebar = HostsSidebar(self.show_host_info, self.serverIO.get_hosts,
                                          self.serverIO.get_host, self.icons)
        self.sidebar = Sidebar(self.ws_sidebar.get_box(),
                               self.hosts_sidebar.get_box())

        host_count, service_count, vuln_count = 0, 0, 0  # dummy values
        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_to_method = {"about": self.on_about,
                            "quit": self.on_quit,
                            "preferences": self.on_preferences,
                            "pluginOptions": self.on_plugin_options,
                            "faradayPlugin": self.on_faraday_plugin,
                            "new": self.on_new_button,
                            "new_terminal": self.on_new_terminal_button,
                            "open_report": self.on_open_report_button,
                            "go_to_web_ui": self.on_click_go_to_web_ui_button,
                            "go_to_documentation": self.on_help_dispatch,
                            "go_to_faq": self.on_help_dispatch,
                            "go_to_troubleshooting": self.on_help_dispatch,
                            "go_to_demos": self.on_help_dispatch,
                            "go_to_issues": self.on_help_dispatch,
                            "go_to_forum": self.on_help_dispatch,
                            "go_to_irc": self.on_help_dispatch,
                            "go_to_twitter": self.on_help_dispatch,
                            "go_to_googlegroup": self.on_help_dispatch
                            }

        for action, method in action_to_method.items():
            gio_action = Gio.SimpleAction.new(action, None)
            gio_action.connect("activate", method)
            self.add_action(gio_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)

        topmenu = Gio.Menu()
        pluginmenu = Gio.Menu()

        topmenu.append('Faraday Plugin...', 'app.faradayPlugin')

        plugins = fplugin_utils.get_available_plugins()

        for plugin in sorted(plugins.iterkeys()):
            gio_action = Gio.SimpleAction.new('fplugin_%s' % plugin, None)
            gio_action.connect("activate", self.type_faraday_plugin_command)
            self.add_action(gio_action)

            item = Gio.MenuItem.new(plugins[plugin]['prettyname'], 'app.fplugin_%s' % plugin)

            pluginmenu.append_item(item)

        fmenu = Gio.Menu()

        fmenu.append_section(None, topmenu)
        fmenu.append_section(None, pluginmenu)

        appmenu.insert_submenu(1, "Faraday Plugin", fmenu)

        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.ws_sidebar,
                                    self.hosts_sidebar,
                                    self.terminal,
                                    self.console_log,
                                    self.statusbar,
                                    application=self,
                                    title="Faraday " + str(CONF.getVersion()))

        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)

        if self.serverIO.server_info() is None:
            self.lost_db_connection(
                handle_connection_lost=self.handle_connection_lost,
                connect_to_a_different_couch=self.force_change_couch_url)

        workspace_argument_set = self.open_workspace_from_args()
        if not workspace_argument_set:
            self.open_last_workspace()

    def on_quit(self, action=None, param=None):
        self.quit()

    def on_plugin_options(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_faraday_plugin(self, action, param):
        """Defines what happens when you press "Faraday Plugin..." on the menu"""
        pluginsOption_window = FaradayPluginsDialog(self.window.get_current_focused_terminal(),
                                                    self.get_active_workspace().getName(),
                                                    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)
        terminal_scrolled = new_terminal.create_scrollable_terminal()
        self.window.new_tab(terminal_scrolled)

    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.
        """
        conflicts = self.model_controller.getConflicts()
        if conflicts:
            dialog = ConflictsDialog(conflicts,
                                     self.window)
            dialog.show_all()

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

    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, key=lambda s: s.lower())
            dialog = Gtk.Dialog("Select plugin", self.window, 0)

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

            dialog.vbox.pack_start(combo_box, False, 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.FileChooserDialog(title="Import a report",
                                               parent=self.window,
                                               action=Gtk.FileChooserAction.OPEN,
                                               buttons=("Open", Gtk.ResponseType.ACCEPT,
                                                        "Cancel", Gtk.ResponseType.CANCEL)
                                               )
                dialog.set_modal(True)

                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_preferences(self, action=None, param=None):
        """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.reload_workspaces,
                                                   self.connect_to_couch,
                                                   self.window)
        preference_window.show_all()

    def on_click_go_to_web_ui_button(self, action=None, param=None):
        """Opens the dashboard of the current workspace on a new tab of
        the user's default browser
        """
        couch_url = CONF.getCouchURI()
        ws_name = self.workspace_manager.getActiveWorkspace()
        if not ws_name:
            ws_url = couch_url + "/_ui/"
        else:
            ws_url = couch_url + "/_ui/#/dashboard/ws/" + ws_name.name
        webbrowser.open(ws_url, new=2)

    def on_help_dispatch(self, action, param=None):
        """Open the url contained in "action" in the user's browser."""
        urls = {"go_to_documentation": "https://faradaysec.com/help/docs",
                "go_to_faq": "https://faradaysec.com/help/faq",
                "go_to_troubleshooting": "https://faradaysec.com/help/troubleshooting",
                "go_to_demos": "https://faradaysec.com/help/demos",
                "go_to_issues": "https://faradaysec.com/help/issues",
                "go_to_forum": "https://forum.faradaysec.com",
                "go_to_irc": "https://faradaysec.com/help/irc",
                "go_to_twitter": "https://faradaysec.com/help/twitter",
                "go_to_googlegroup": "https://faradaysec.com/help/googlegroup"
                }
        url = urls.get(action.get_name(), "https://faradaysec.com")
        webbrowser.open(url, new=2)

    def type_faraday_plugin_command(self, action, param=None):
        """
        Types the faraday plugin command on the command line.
        """

        plugin = "_".join(action.get_name().split('_')[1:])
        terminal = self.window.get_current_focused_terminal()

        command = fplugin_utils.build_faraday_plugin_command(plugin, self.get_active_workspace().getName())
        fd = terminal.get_pty().get_fd()

        os.write(fd, command)
 def init(self):    
     self.window = AppWindow(self)
     self._clipboard = gtk.Clipboard()  # not implemented yet
     self.progresslist = GtkProgressList        
示例#37
0
 def setUp(self) -> None:
     os.chdir(Path(__file__).absolute().parent.parent)
     self.myapp = AppWindow(Path('tests/examples/work/cu_BruecknerJK_153F40_0m.cif').absolute())
     self.myapp.hide()  # For full screen view
     self.resobj = Path('checkcif-' + strip_finalcif_of_name(self.myapp.cif.fileobj.stem) + '-finalcif.html')
 def init(self):
     register_all_png_icons(const.internal_path(const.PATH_ICONS), 'sloppy-')
     
     self.window = AppWindow(self)
     self._clipboard = gtk.Clipboard()  # not implemented yet
     self.progresslist = GtkProgressList        
示例#39
0
class TestLoops(unittest.TestCase):

    def setUp(self) -> None:
        os.chdir(Path(__file__).resolve().parent.parent)
        self.testcif = Path('tests/examples/1979688.cif').resolve()
        self.myapp = AppWindow(self.testcif)
        self.myapp.running_inside_unit_test = True
        self.myapp.hide()  # For full screen view
        self.myapp.ui.LoopsPushButton.click()

    def tearDown(self) -> None:
        self.myapp.final_cif_file_name.unlink(missing_ok=True)
        self.myapp.close()

    def get_index_of(self, loopkey: str = ''):
        tabw: QTabWidget = self.myapp.ui.LoopsTabWidget
        tab = -1
        for tab in range(tabw.count()):
            if tabw.tabText(tab).startswith(loopkey):
                break
            else:
                tab = -1
        return tab

    def test_get_index_of_tab(self):
        self.assertEqual(2, self.get_index_of('CIF Author'))

    def test_loop_data(self):
        index = self.myapp.ui.LoopsTabWidget.widget(self.get_index_of('Citations')).model().index(0, 1)
        self.assertEqual('10.1021/acs.orglett.0c01078', index.data())

    def test_loop_data_atoms_aniso(self):
        index = self.myapp.ui.LoopsTabWidget.widget(self.get_index_of('Displacement Para')).model().index(0, 1)
        self.assertEqual('0.0161(10)', index.data())

    def test_loop_audit_author_name(self):
        index = self.myapp.ui.LoopsTabWidget.widget(self.get_index_of('CIF Author')).model().index(0, 0)
        self.assertEqual('Daniel Kratzert', index.data())

    def test_loop_audit_author_address(self):
        index = self.myapp.ui.LoopsTabWidget.widget(self.get_index_of('CIF Author')).model().index(0, 1)
        self.assertEqual(unify_line_endings('University of Freiburg\nGermany'), unify_line_endings(index.data()))

    def test_loop_dots(self):
        index = self.myapp.ui.LoopsTabWidget.widget(self.get_index_of('Bonds')).model().index(1, 3)
        self.assertEqual('.', index.data())

    def test_loop_question_marks(self):
        index = self.myapp.ui.LoopsTabWidget.widget(self.get_index_of('Bonds')).model().index(1, 4)
        self.assertEqual('?', index.data())

    def test_loop_no_edit(self):
        self.myapp.ui.SaveCifButton.click()
        c = CifContainer(self.myapp.final_cif_file_name)
        self.assertEqual('0.0181', c.loops[3].val(0, 2))

    def test_loop_edit_one_single_field(self):
        model = self.myapp.ui.LoopsTabWidget.widget(self.get_index_of('Scattering')).model()
        print(self.myapp.ui.LoopsTabWidget.tabText(4))
        model.setData(model.index(0, 2), 'foo bar', role=Qt.EditRole)
        self.myapp.ui.SaveCifButton.click()
        c = CifContainer(self.myapp.final_cif_file_name)
        self.assertEqual('foo bar', as_string(c.loops[3].val(0, 2)))
示例#40
0
 def setUp(self) -> None:
     os.chdir(Path(__file__).resolve().parent.parent)
     self.myapp = AppWindow(Path('tests/examples/1979688.cif').resolve())
     self.myapp.hide()
     self.myapp.running_inside_unit_test = True
示例#41
0
class TestMainTableFieldBehavior(unittest.TestCase):
    def setUp(self) -> None:
        os.chdir(Path(__file__).absolute().parent.parent)
        self.testcif = Path('tests/examples/1979688.cif').absolute()
        self.myapp = AppWindow(self.testcif)
        self.myapp.running_inside_unit_test = True
        self.myapp.hide()  # For full screen view

    def tearDown(self) -> None:
        self.myapp.final_cif_file_name.unlink(missing_ok=True)
        self.myapp.close()

    def key_row(self, key: str) -> int:
        return self.myapp.ui.cif_main_table.row_from_key(key)

    def cell_widget(self, row: int, col: int) -> str:
        return str(self.myapp.ui.cif_main_table.cellWidget(row, col).__class__)

    def cell_text(self, key: str, col: int) -> str:
        return unify_line_endings(
            self.myapp.ui.cif_main_table.getTextFromKey(key, col))

    def equipment_click(self, field: str):
        self.myapp.ui.EquipmentTemplatesStackedWidget.setCurrentIndex(0)
        item = self.myapp.ui.EquipmentTemplatesListWidget.findItems(
            field, Qt.MatchStartsWith)[0]
        self.myapp.ui.EquipmentTemplatesListWidget.setCurrentItem(item)
        self.myapp.equipment.load_selected_equipment()

    def get_background_color(self, key: str, col: int) -> QColor:
        return self.myapp.ui.cif_main_table.itemFromKey(
            key, col).background().color()

    ######

    def test_rowcounts(self):
        self.assertEqual(130, self.myapp.ui.cif_main_table.rowCount())

    def test_delete_row(self):
        self.myapp.ui.cif_main_table.delete_row(
            self.key_row('_audit_update_record'))
        self.assertEqual(129, self.myapp.ui.cif_main_table.rowCount())

    def test_delete_and_reappear(self):
        self.myapp.ui.cif_main_table.delete_row(16)
        # cline count stays the same:
        self.assertEqual(130, self.myapp.ui.cif_main_table.rowCount())
        self.assertEqual(
            '?', self.cell_text('_atom_sites_solution_primary', COL_CIF))
        # method comes from solution program now:
        self.assertEqual(
            'direct', self.cell_text('_atom_sites_solution_primary', COL_DATA))
        # This is an essential key, it reappears after reload:
        self.assertEqual(0, self.key_row('_atom_sites_solution_primary'))

    def test_get_text_from_item(self):
        self.assertEqual('geom',
                         self.myapp.ui.cif_main_table.item(15, COL_CIF).text())
        self.assertEqual(
            '',
            self.myapp.ui.cif_main_table.item(15, COL_DATA).text())
        self.assertEqual(
            '',
            self.myapp.ui.cif_main_table.item(15, COL_EDIT).text())

    def test_get_text_by_key(self):
        self.assertEqual(
            'geom', self.cell_text('_atom_sites_solution_hydrogens', COL_CIF))
        self.assertEqual(
            '', self.cell_text('_atom_sites_solution_hydrogens', COL_DATA))
        self.assertEqual(
            '', self.cell_text('_atom_sites_solution_hydrogens', COL_EDIT))

    def test_CCDC_in_equipment_list(self):
        self.assertEqual(
            'CCDC number',
            self.myapp.ui.EquipmentTemplatesListWidget.item(1).text())

    def test_load_equipment(self):
        # make sure contact author is selected
        self.equipment_click('Crystallographer Details')
        # It is important here, that the first column has '*****@*****.**' in it:
        self.assertEqual(
            '?',
            self.myapp.ui.cif_main_table.getTextFromKey(
                '_audit_contact_author_email', COL_CIF))
        self.assertEqual(
            '*****@*****.**',
            self.myapp.ui.cif_main_table.getTextFromKey(
                '_audit_contact_author_email', COL_DATA))
        self.assertEqual(
            '*****@*****.**',
            self.myapp.ui.cif_main_table.getTextFromKey(
                '_audit_contact_author_email', COL_EDIT))

    def test_field_types(self):
        self.assertEqual(
            "<class 'gui.custom_classes.MyTableWidgetItem'>",
            str(
                self.myapp.ui.cif_main_table.itemFromKey(
                    '_atom_sites_solution_hydrogens', COL_CIF).__class__))
        self.assertEqual(
            "<class 'NoneType'>",
            str(
                self.myapp.ui.cif_main_table.widget_from_key(
                    '_atom_sites_solution_hydrogens', COL_CIF).__class__))

    def test_combobox_field(self):
        self.assertEqual(
            "<class 'gui.custom_classes.MyComboBox'>",
            str(
                self.myapp.ui.cif_main_table.widget_from_key(
                    '_atom_sites_solution_hydrogens', COL_EDIT).__class__))

    def test_plaintextedit_field(self):
        self.assertEqual(
            "<class 'gui.custom_classes.MyQPlainTextEdit'>",
            str(
                self.myapp.ui.cif_main_table.widget_from_key(
                    '_audit_contact_author_address', COL_CIF).__class__))
        self.assertEqual(
            "<class 'gui.custom_classes.MyQPlainTextEdit'>",
            str(
                self.myapp.ui.cif_main_table.widget_from_key(
                    '_audit_contact_author_address', COL_DATA).__class__))
        self.assertEqual(
            "<class 'gui.custom_classes.MyQPlainTextEdit'>",
            str(
                self.myapp.ui.cif_main_table.widget_from_key(
                    '_audit_contact_author_address', COL_EDIT).__class__))
示例#42
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()
示例#43
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()
示例#44
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()
示例#45
0
class TemplateReportTestCase(unittest.TestCase):
    def setUp(self) -> None:
        self.myapp = AppWindow()
        os.chdir(Path(__file__).absolute().parent.parent)
        self.testcif = Path('tests/examples/1979688.cif').absolute()
        self.myapp.load_cif_file(self.testcif.absolute())
        self.myapp.running_inside_unit_test = True
        self.myapp.ui.HAtomsCheckBox.setChecked(False)
        self.myapp.ui.ReportTextCheckBox.setChecked(False)
        self.myapp.ui.PictureWidthDoubleSpinBox.setValue(7.43)
        self.import_templates()
        self.myapp.ui.TemplatesListWidget.setCurrentRow(2)
        self.reportdoc = Path('report_' + self.testcif.stem + '-finalcif.docx')
        self.report_zip = Path(self.testcif.stem + '-finalcif.zip')
        self.myapp.set_report_picture(Path('../../icon/finalcif.png'))
        self.myapp.hide()

    def tearDown(self) -> None:
        self.myapp.final_cif_file_name.unlink(missing_ok=True)
        self.reportdoc.unlink(missing_ok=True)
        self.report_zip.unlink(missing_ok=True)
        self.myapp.ui.ReportTextCheckBox.setChecked(False)
        self.myapp.ui.HAtomsCheckBox.setChecked(False)
        self.myapp.ui.PictureWidthDoubleSpinBox.setValue(7.5)

    def import_templates(self):
        # blocking signals, because signal gets fired after delete and crashes:
        self.myapp.ui.TemplatesListWidget.blockSignals(True)
        for num in range(1, self.myapp.ui.TemplatesListWidget.count()):
            self.myapp.ui.TemplatesListWidget.setCurrentRow(num)
            self.myapp.templates.remove_current_template()
        self.myapp.templates.add_new_template(
            str(Path('../../template/template_text.docx').absolute()))
        self.myapp.templates.add_new_template(
            str(Path('../../template/template_without_text.docx').absolute()))
        app.processEvents()
        print('imported templates')
        self.myapp.ui.TemplatesListWidget.blockSignals(False)

    #@unittest.skip('')
    def test_with_report_text(self):
        self.myapp.ui.SaveFullReportButton.click()
        doc = Document(self.reportdoc.absolute())
        self.assertEqual('The following text is only',
                         doc.paragraphs[2].text[:26])

    @unittest.skip(
        'I am unable to access the respective paragraph, they are in w:ins')
    def test_citations(self):
        self.myapp.ui.SaveFullReportButton.click()
        doc = Document(self.reportdoc.absolute())
        self.assertEqual(
            '[6] 	D. Kratzert, FinalCif, V{}, https://www.xs3.uni-freiburg.de/research/finalcif.'
            .format(VERSION), doc.paragraphs[-1].text)

    def test_ccdc_num_in_table(self):
        self.myapp.ui.SaveFullReportButton.click()
        doc = Document(self.reportdoc.absolute())
        table: Table = doc.tables[0]
        self.assertEqual('CCDC 1979688', table.cell(row_idx=0, col_idx=1).text)

    def test_picture_has_correct_size(self):
        self.myapp.ui.SaveFullReportButton.click()
        doc = Document(self.reportdoc.absolute())
        shapes: InlineShapes = doc.inline_shapes
        self.assertEqual(WD_INLINE_SHAPE.PICTURE, shapes[0].type)
        self.assertEqual(Cm(7.43).emu, shapes[0].width)