コード例 #1
0
ファイル: __init__.py プロジェクト: rustforfuture/gridsync
 def __init__(self, core):
     self.core = core
     self.welcome_dialog = WelcomeDialog(self)
     self.main_window = MainWindow(self)
     self.preferences_window = PreferencesWindow()
     self.systray = SystemTrayIcon(self)
     self.debug_exporter = DebugExporter(core)
コード例 #2
0
ファイル: __init__.py プロジェクト: rustforfuture/gridsync
class Gui():
    def __init__(self, core):
        self.core = core
        self.welcome_dialog = WelcomeDialog(self)
        self.main_window = MainWindow(self)
        self.preferences_window = PreferencesWindow()
        self.systray = SystemTrayIcon(self)
        self.debug_exporter = DebugExporter(core)

    def show_message(self, title, message, duration=5000):
        notify(self.systray, title, message, duration)

    def show_welcome_dialog(self):
        self.welcome_dialog.show()
        self.welcome_dialog.raise_()

    def show_main_window(self):
        self.main_window.show()
        self.main_window.raise_()

    def show_preferences_window(self):
        self.preferences_window.show()
        self.preferences_window.raise_()

    def show_systray(self):
        if self.systray.isSystemTrayAvailable():
            self.systray.show()
        else:
            self.show_main_window()

    def show(self):
        self.systray.show()
        if self.main_window.gateways:
            self.show_main_window()
        else:
            self.show_welcome_dialog()

    def hide(self):
        self.systray.hide()
        self.main_window.hide()
        self.preferences_window.hide()

    def toggle(self):
        if self.main_window.isVisible():
            self.main_window.hide()
        else:
            self.show_main_window()

    def populate(self, gateways):
        self.main_window.populate(gateways)

    def show_debug_exporter(self):
        self.debug_exporter.show()
        self.debug_exporter.raise_()
        self.debug_exporter.load()
コード例 #3
0
class Gui(object):
    def __init__(self, core):
        self.core = core
        self.welcome_dialog = WelcomeDialog(self)
        self.main_window = MainWindow(self)
        self.systray = SystemTrayIcon(self)

    def show_message(self, title, message, duration=5000):
        notify(self.systray, title, message, duration)

    def show_welcome_dialog(self):
        self.welcome_dialog.show()
        self.welcome_dialog.raise_()

    def show_main_window(self):
        self.main_window.show()
        self.main_window.raise_()

    def show_systray(self):
        if self.systray.isSystemTrayAvailable():
            self.systray.show()
        else:
            self.show_main_window()

    def show(self):
        self.systray.show()
        self.show_main_window()

    def hide(self):
        self.systray.hide()
        self.main_window.hide()

    def toggle(self):
        if self.main_window.isVisible():
            self.main_window.hide()
        else:
            self.show_main_window()

    def populate(self, gateways):
        self.main_window.populate(gateways)
コード例 #4
0
ファイル: main_window.py プロジェクト: purplesparkle/gridsync
class MainWindow(QMainWindow):
    def __init__(self, gui):  # noqa: max-complexity
        super().__init__()
        self.gui = gui
        self.gateways = []
        self.welcome_dialog = None
        self.recovery_key_exporter = None

        self.setWindowTitle(APP_NAME)
        self.setMinimumSize(QSize(600, 400))
        self.setUnifiedTitleAndToolBarOnMac(True)
        self.setContextMenuPolicy(Qt.NoContextMenu)

        if sys.platform == "darwin":
            # To disable the broken/buggy "full screen" mode on macOS.
            # See https://github.com/gridsync/gridsync/issues/241
            self.setWindowFlags(Qt.Dialog)

        grid_invites_enabled = True
        invites_enabled = True
        multiple_grids_enabled = True
        features_settings = settings.get("features")
        if features_settings:
            grid_invites = features_settings.get("grid_invites")
            if grid_invites and grid_invites.lower() == "false":
                grid_invites_enabled = False
            invites = features_settings.get("invites")
            if invites and invites.lower() == "false":
                invites_enabled = False
            multiple_grids = features_settings.get("multiple_grids")
            if multiple_grids and multiple_grids.lower() == "false":
                multiple_grids_enabled = False

        if multiple_grids_enabled:
            self.shortcut_new = QShortcut(QKeySequence.New, self)
            self.shortcut_new.activated.connect(self.show_welcome_dialog)

        self.shortcut_open = QShortcut(QKeySequence.Open, self)
        self.shortcut_open.activated.connect(self.select_folder)

        self.shortcut_preferences = QShortcut(QKeySequence.Preferences, self)
        self.shortcut_preferences.activated.connect(
            self.gui.show_preferences_window)

        self.shortcut_close = QShortcut(QKeySequence.Close, self)
        self.shortcut_close.activated.connect(self.close)

        self.shortcut_quit = QShortcut(QKeySequence.Quit, self)
        self.shortcut_quit.activated.connect(self.confirm_quit)

        self.central_widget = CentralWidget(self.gui)
        self.setCentralWidget(self.central_widget)

        font = Font(8)

        folder_icon_default = QFileIconProvider().icon(QFileInfo(config_dir))
        folder_icon_composite = CompositePixmap(
            folder_icon_default.pixmap(256, 256), resource("green-plus.png"))
        folder_icon = QIcon(folder_icon_composite)

        folder_action = QAction(folder_icon, "Add Folder", self)
        folder_action.setToolTip("Add a Folder...")
        folder_action.setFont(font)
        folder_action.triggered.connect(self.select_folder)

        if grid_invites_enabled:
            invites_action = QAction(QIcon(resource("invite.png")), "Invites",
                                     self)
            invites_action.setToolTip("Enter or Create an Invite Code")
            invites_action.setFont(font)

            enter_invite_action = QAction(QIcon(), "Enter Invite Code...",
                                          self)
            enter_invite_action.setToolTip("Enter an Invite Code...")
            enter_invite_action.triggered.connect(self.open_invite_receiver)

            create_invite_action = QAction(QIcon(), "Create Invite Code...",
                                           self)
            create_invite_action.setToolTip("Create on Invite Code...")
            create_invite_action.triggered.connect(
                self.open_invite_sender_dialog)

            invites_menu = QMenu(self)
            invites_menu.addAction(enter_invite_action)
            invites_menu.addAction(create_invite_action)

            invites_button = QToolButton(self)
            invites_button.setDefaultAction(invites_action)
            invites_button.setMenu(invites_menu)
            invites_button.setPopupMode(2)
            invites_button.setStyleSheet(
                "QToolButton::menu-indicator { image: none }")
            invites_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)

        elif invites_enabled:
            invite_action = QAction(QIcon(resource("invite.png")),
                                    "Enter Code", self)
            invite_action.setToolTip("Enter an Invite Code...")
            invite_action.setFont(font)
            invite_action.triggered.connect(self.open_invite_receiver)

        spacer_left = QWidget()
        spacer_left.setSizePolicy(QSizePolicy.Expanding, 0)

        self.combo_box = ComboBox()
        self.combo_box.currentIndexChanged.connect(self.on_grid_selected)
        if not multiple_grids_enabled:
            self.combo_box.hide()

        spacer_right = QWidget()
        spacer_right.setSizePolicy(QSizePolicy.Expanding, 0)

        history_action = QAction(QIcon(resource("time.png")), "History", self)
        history_action.setToolTip("Show/Hide History")
        history_action.setFont(font)
        history_action.triggered.connect(self.on_history_button_clicked)

        self.history_button = QToolButton(self)
        self.history_button.setDefaultAction(history_action)
        self.history_button.setCheckable(True)
        self.history_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)

        recovery_action = QAction(QIcon(resource("key.png")), "Recovery", self)
        recovery_action.setToolTip("Import or Export a Recovery Key")
        recovery_action.setFont(font)

        import_action = QAction(QIcon(), "Import Recovery Key...", self)
        import_action.setToolTip("Import Recovery Key...")
        import_action.triggered.connect(self.import_recovery_key)

        export_action = QAction(QIcon(), "Export Recovery Key...", self)
        export_action.setToolTip("Export Recovery Key...")
        export_action.setShortcut(QKeySequence.Save)
        export_action.triggered.connect(self.export_recovery_key)

        recovery_menu = QMenu(self)
        recovery_menu.addAction(import_action)
        recovery_menu.addAction(export_action)

        recovery_button = QToolButton(self)
        recovery_button.setDefaultAction(recovery_action)
        recovery_button.setMenu(recovery_menu)
        recovery_button.setPopupMode(2)
        recovery_button.setStyleSheet(
            "QToolButton::menu-indicator { image: none }")
        recovery_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)

        self.toolbar = self.addToolBar("")
        p = self.palette()
        dimmer_grey = BlendedColor(p.windowText().color(),
                                   p.window().color(), 0.7).name()
        if sys.platform != "darwin":
            self.toolbar.setStyleSheet("""
                QToolBar {{ border: 0px }}
                QToolButton {{ color: {} }}
            """.format(dimmer_grey))
        else:
            self.toolbar.setStyleSheet(
                "QToolButton {{ color: {} }}".format(dimmer_grey))
        self.toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        self.toolbar.setIconSize(QSize(24, 24))
        self.toolbar.setMovable(False)
        self.toolbar.addAction(folder_action)
        if grid_invites_enabled:
            self.toolbar.addWidget(invites_button)
        elif invites_enabled:
            self.toolbar.addAction(invite_action)
        self.toolbar.addWidget(spacer_left)
        self.toolbar.addWidget(self.combo_box)
        self.toolbar.addWidget(spacer_right)
        self.toolbar.addWidget(self.history_button)
        self.toolbar.addWidget(recovery_button)

        if sys.platform != "win32":  # Text is getting clipped on Windows 10
            for action in self.toolbar.actions():
                widget = self.toolbar.widgetForAction(action)
                if isinstance(widget, QToolButton):
                    widget.setMaximumWidth(68)

        self.active_invite_sender_dialogs = []
        self.active_invite_receiver_dialogs = []

        self.pending_news_message = ()

    def populate(self, gateways):
        for gateway in gateways:
            if gateway not in self.gateways:
                self.central_widget.add_folders_view(gateway)
                self.central_widget.add_history_view(gateway)
                self.combo_box.add_gateway(gateway)
                self.gateways.append(gateway)
                gateway.newscap_checker.message_received.connect(
                    self.on_message_received)
                gateway.newscap_checker.upgrade_required.connect(
                    self.on_upgrade_required)

    def show_news_message(self, gateway, title, message):
        msgbox = QMessageBox(self)
        msgbox.setWindowModality(Qt.WindowModal)
        icon_filepath = os.path.join(gateway.nodedir, "icon")
        if os.path.exists(icon_filepath):
            msgbox.setIconPixmap(QIcon(icon_filepath).pixmap(64, 64))
        elif os.path.exists(resource("tahoe-lafs.png")):
            msgbox.setIconPixmap(
                QIcon(resource("tahoe-lafs.png")).pixmap(64, 64))
        else:
            msgbox.setIcon(QMessageBox.Information)
        if sys.platform == "darwin":
            msgbox.setText(title)
            msgbox.setInformativeText(message)
        else:
            msgbox.setWindowTitle(title)
            msgbox.setText(message)
        msgbox.show()
        try:
            self.gui.unread_messages.remove((gateway, title, message))
        except ValueError:
            return
        self.gui.systray.update()

    def _maybe_show_news_message(self, gateway, title, message):
        self.gui.unread_messages.append((gateway, title, message))
        self.gui.systray.update()
        if self.isVisible():
            self.show_news_message(gateway, title, message)
        else:
            self.pending_news_message = (gateway, title, message)

    def on_message_received(self, gateway, message):
        title = "New message from {}".format(gateway.name)
        self.gui.show_message(title,
                              strip_html_tags(message.replace("<p>", "\n\n")))
        self._maybe_show_news_message(gateway, title, message)

    def on_upgrade_required(self, gateway):
        title = "Upgrade required"
        message = (
            "A message was received from {} in an unsupported format. This "
            "suggests that you are running an out-of-date version of {}.\n\n"
            "To avoid seeing this warning, please upgrade to the latest "
            "version.".format(gateway.name, APP_NAME))
        self._maybe_show_news_message(gateway, title, message)

    def current_view(self):
        try:
            w = self.central_widget.folders_views[self.combo_box.currentData()]
        except KeyError:
            return None
        return w.layout().itemAt(0).widget()

    def select_folder(self):
        self.show_folders_view()
        view = self.current_view()
        if view:
            view.select_folder()

    def set_current_grid_status(self):
        current_view = self.current_view()
        if not current_view:
            return
        self.gui.systray.update()

    def show_folders_view(self):
        try:
            self.central_widget.setCurrentWidget(
                self.central_widget.folders_views[
                    self.combo_box.currentData()])
        except KeyError:
            pass
        self.set_current_grid_status()

    def show_history_view(self):
        try:
            self.central_widget.setCurrentWidget(
                self.central_widget.history_views[
                    self.combo_box.currentData()])
        except KeyError:
            pass
        self.set_current_grid_status()

    def show_welcome_dialog(self):
        if self.welcome_dialog:
            self.welcome_dialog.close()
        self.welcome_dialog = WelcomeDialog(self.gui, self.gateways)
        self.welcome_dialog.show()
        self.welcome_dialog.raise_()

    def on_grid_selected(self, index):
        if index == self.combo_box.count() - 1:
            self.show_welcome_dialog()
        if not self.combo_box.currentData():
            return
        if self.history_button.isChecked():
            self.show_history_view()
        else:
            self.show_folders_view()
        self.setWindowTitle("{} - {}".format(
            APP_NAME,
            self.combo_box.currentData().name))

    def confirm_export(self, path):
        if os.path.isfile(path):
            logging.info("Recovery Key successfully exported")
            info(
                self,
                "Export successful",
                "Recovery Key successfully exported to {}".format(path),
            )
        else:
            logging.error("Error exporting Recovery Key; file not found.")
            error(
                self,
                "Error exporting Recovery Key",
                "Destination file not found after export: {}".format(path),
            )

    def export_recovery_key(self, gateway=None):
        self.show_folders_view()
        if not gateway:
            gateway = self.combo_box.currentData()
        self.recovery_key_exporter = RecoveryKeyExporter(self)
        self.recovery_key_exporter.done.connect(self.confirm_export)
        self.recovery_key_exporter.do_export(gateway)

    def import_recovery_key(self):
        # XXX Quick hack for user-testing; change later
        self.welcome_dialog = WelcomeDialog(self.gui, self.gateways)
        self.welcome_dialog.on_restore_link_activated()

    def on_history_button_clicked(self):
        if not self.history_button.isChecked():
            self.history_button.setChecked(True)
            self.show_history_view()
        else:
            self.history_button.setChecked(False)
            self.show_folders_view()

    def on_invite_received(self, gateway):
        self.populate([gateway])
        for view in self.central_widget.views:
            view.model().monitor.scan_rootcap("star.png")

    def on_invite_closed(self, obj):
        try:
            self.active_invite_receiver_dialogs.remove(obj)
        except ValueError:
            pass

    def open_invite_receiver(self):
        invite_receiver_dialog = InviteReceiverDialog(self.gateways)
        invite_receiver_dialog.done.connect(self.on_invite_received)
        invite_receiver_dialog.closed.connect(self.on_invite_closed)
        invite_receiver_dialog.show()
        self.active_invite_receiver_dialogs.append(invite_receiver_dialog)

    def open_invite_sender_dialog(self):
        gateway = self.combo_box.currentData()
        if gateway:
            view = self.current_view()
            if view:
                invite_sender_dialog = InviteSenderDialog(
                    gateway, self.gui, view.get_selected_folders())
            else:
                invite_sender_dialog = InviteSenderDialog(gateway, self.gui)
            invite_sender_dialog.closed.connect(
                self.active_invite_sender_dialogs.remove)
            invite_sender_dialog.show()
            self.active_invite_sender_dialogs.append(invite_sender_dialog)

    def confirm_quit(self):
        folder_loading = False
        folder_syncing = False
        for model in [view.model() for view in self.central_widget.views]:
            for row in range(model.rowCount()):
                status = model.item(row, 1).data(Qt.UserRole)
                mtime = model.item(row, 2).data(Qt.UserRole)
                if not status and not mtime:  # "Loading..." and not yet synced
                    folder_loading = True
                    break
                if status == 1:  # "Syncing"
                    folder_syncing = True
                    break
        msg = QMessageBox(self)
        if folder_loading:
            msg.setIcon(QMessageBox.Warning)
            informative_text = (
                "One or more folders have not finished loading. If these "
                "folders were recently added, you may need to add them again.")
        elif folder_syncing:
            msg.setIcon(QMessageBox.Warning)
            informative_text = (
                "One or more folders are currently syncing. If you quit, any "
                "pending upload or download operations will be cancelled "
                "until you launch {} again.".format(APP_NAME))
        else:
            msg.setIcon(QMessageBox.Question)
            informative_text = (
                "If you quit, {} will stop synchronizing your folders until "
                "you launch it again.".format(APP_NAME))
        if sys.platform == "darwin":
            msg.setText("Are you sure you wish to quit?")
            msg.setInformativeText(informative_text)
        else:
            msg.setWindowTitle("Exit {}?".format(APP_NAME))
            msg.setText(
                "Are you sure you wish to quit? {}".format(informative_text))
        msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        msg.setDefaultButton(QMessageBox.No)
        if msg.exec_() == QMessageBox.Yes:
            if sys.platform == "win32":
                self.gui.systray.hide()
            reactor.stop()

    def keyPressEvent(self, event):
        key = event.key()
        if key in (Qt.Key_Backspace, Qt.Key_Delete):
            view = self.current_view()
            selected = view.selectedIndexes() if view else None
            if selected:
                view.confirm_stop_syncing(view.get_selected_folders())
        if key == Qt.Key_Escape:
            view = self.current_view()
            selected = view.selectedIndexes() if view else None
            if selected:
                for index in selected:
                    view.selectionModel().select(index,
                                                 QItemSelectionModel.Deselect)
            elif self.gui.systray.isSystemTrayAvailable():
                self.hide()

    def closeEvent(self, event):
        if self.gui.systray.isSystemTrayAvailable():
            event.accept()
        else:
            event.ignore()
            self.confirm_quit()

    def showEvent(self, _):
        if self.pending_news_message:
            gateway, title, message = self.pending_news_message
            self.pending_news_message = ()
            QTimer.singleShot(
                0, lambda: self.show_news_message(gateway, title, message))
コード例 #5
0
ファイル: main_window.py プロジェクト: purplesparkle/gridsync
 def import_recovery_key(self):
     # XXX Quick hack for user-testing; change later
     self.welcome_dialog = WelcomeDialog(self.gui, self.gateways)
     self.welcome_dialog.on_restore_link_activated()
コード例 #6
0
ファイル: main_window.py プロジェクト: purplesparkle/gridsync
 def show_welcome_dialog(self):
     if self.welcome_dialog:
         self.welcome_dialog.close()
     self.welcome_dialog = WelcomeDialog(self.gui, self.gateways)
     self.welcome_dialog.show()
     self.welcome_dialog.raise_()
コード例 #7
0
 def __init__(self, core):
     self.core = core
     self.welcome_dialog = WelcomeDialog(self)
     self.main_window = MainWindow(self)
     self.systray = SystemTrayIcon(self)
コード例 #8
0
class MainWindow(QMainWindow):
    def __init__(self, gui):
        super(MainWindow, self).__init__()
        self.gui = gui
        self.gateways = []
        self.progress = None
        self.animation = None
        self.crypter = None
        self.crypter_thread = None
        self.export_data = None
        self.export_dest = None
        self.welcome_dialog = None

        self.setWindowTitle(APP_NAME)
        self.setMinimumSize(QSize(500, 300))

        self.shortcut_new = QShortcut(QKeySequence.New, self)
        self.shortcut_new.activated.connect(self.show_welcome_dialog)

        self.shortcut_open = QShortcut(QKeySequence.Open, self)
        self.shortcut_open.activated.connect(self.select_folder)

        self.shortcut_close = QShortcut(QKeySequence.Close, self)
        self.shortcut_close.activated.connect(self.close)

        self.shortcut_quit = QShortcut(QKeySequence.Quit, self)
        self.shortcut_quit.activated.connect(self.confirm_quit)

        self.combo_box = ComboBox()
        self.combo_box.activated[int].connect(self.on_grid_selected)

        self.central_widget = CentralWidget(self.gui)
        self.setCentralWidget(self.central_widget)

        invite_action = QAction(QIcon(resource('invite.png')),
                                'Enter an Invite Code...', self)
        invite_action.setStatusTip('Enter an Invite Code...')
        invite_action.triggered.connect(self.open_invite_receiver)

        folder_icon_default = QFileIconProvider().icon(QFileInfo(config_dir))
        folder_icon_composite = CompositePixmap(
            folder_icon_default.pixmap(256, 256), resource('green-plus.png'))
        folder_icon = QIcon(folder_icon_composite)

        folder_action = QAction(folder_icon, "Add folder...", self)
        folder_action.setStatusTip("Add folder...")

        folder_from_local_action = QAction(QIcon(resource('laptop.png')),
                                           "From local computer...", self)
        folder_from_local_action.setStatusTip("Add folder from local computer")
        folder_from_local_action.setToolTip("Add folder from local computer")
        #self.from_local_action.setShortcut(QKeySequence.Open)
        folder_from_local_action.triggered.connect(self.select_folder)

        folder_from_invite_action = QAction(QIcon(resource('invite.png')),
                                            "From Invite Code...", self)
        folder_from_invite_action.setStatusTip("Add folder from Invite Code")
        folder_from_invite_action.setToolTip("Add folder from Invite Code")
        folder_from_invite_action.triggered.connect(self.open_invite_receiver)

        folder_menu = QMenu(self)
        folder_menu.addAction(folder_from_local_action)
        folder_menu.addAction(folder_from_invite_action)

        folder_button = QToolButton(self)
        folder_button.setDefaultAction(folder_action)
        folder_button.setMenu(folder_menu)
        folder_button.setPopupMode(2)
        folder_button.setStyleSheet(
            'QToolButton::menu-indicator { image: none }')

        pair_action = QAction(
            QIcon(
                CompositePixmap(
                    QIcon(resource('laptop.png')).pixmap(256, 256),
                    resource('green-plus.png'))), "Connect another device...",
            self)
        pair_action.setStatusTip('Connect another device...')
        pair_action.triggered.connect(self.open_pair_widget)

        export_action = QAction(QIcon(resource('export.png')),
                                'Export Recovery Key', self)
        export_action.setStatusTip('Export Recovery Key...')
        export_action.setShortcut(QKeySequence.Save)
        export_action.triggered.connect(self.export_recovery_key)

        preferences_action = QAction(QIcon(resource('preferences.png')),
                                     'Preferences', self)
        preferences_action.setStatusTip('Preferences')
        preferences_action.setShortcut(QKeySequence.Preferences)
        preferences_action.triggered.connect(self.toggle_preferences_widget)

        spacer_left = QWidget()
        spacer_left.setSizePolicy(QSizePolicy.Expanding, 0)

        spacer_right = QWidget()
        spacer_right.setSizePolicy(QSizePolicy.Expanding, 0)

        self.toolbar = self.addToolBar('')
        #self.toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        #self.toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.toolbar.setIconSize(QSize(24, 24))
        self.toolbar.setMovable(False)
        self.toolbar.addWidget(folder_button)
        #self.toolbar.addAction(invite_action)
        self.toolbar.addAction(pair_action)
        self.toolbar.addWidget(spacer_left)
        self.toolbar.addWidget(self.combo_box)
        self.toolbar.addWidget(spacer_right)
        self.toolbar.addAction(export_action)
        self.toolbar.addAction(preferences_action)

        self.status_bar = self.statusBar()
        self.status_bar_label = QLabel('Loading...')
        self.status_bar.addPermanentWidget(self.status_bar_label)

        self.preferences_widget = PreferencesWidget()
        self.preferences_widget.accepted.connect(self.show_selected_grid_view)

        self.active_pair_widgets = []
        self.active_invite_receivers = []

    def populate(self, gateways):
        for gateway in gateways:
            if gateway not in self.gateways:
                self.gateways.append(gateway)
        self.combo_box.populate(self.gateways)
        self.central_widget.populate(self.gateways)
        self.central_widget.addWidget(self.preferences_widget)
        self.gui.systray.menu.populate()
        self.preferences_widget.load_preferences()

    def current_view(self):
        current_widget = self.central_widget.currentWidget()
        if current_widget:
            view = current_widget.layout().itemAt(0).widget()
            if isinstance(view, View):
                return view
        return None

    def select_folder(self):
        try:
            view = self.current_view()
        except AttributeError:
            return
        view.select_folder()

    def set_current_grid_status(self):
        if self.central_widget.currentWidget() == self.preferences_widget:
            return
        self.status_bar_label.setText(self.current_view().model().grid_status)
        self.gui.systray.update()

    def show_welcome_dialog(self):
        if self.welcome_dialog:
            self.welcome_dialog.close()
        self.welcome_dialog = WelcomeDialog(self.gui, self.gateways)
        self.welcome_dialog.show()
        self.welcome_dialog.raise_()

    def on_grid_selected(self, index):
        if index == self.combo_box.count() - 1:
            self.show_welcome_dialog()
        else:
            self.central_widget.setCurrentIndex(index)
            self.status_bar.show()
            self.set_current_grid_status()

    def show_selected_grid_view(self):
        for i in range(self.central_widget.count()):
            widget = self.central_widget.widget(i)
            try:
                gateway = widget.layout().itemAt(0).widget().gateway
            except AttributeError:
                continue
            if gateway == self.combo_box.currentData():
                self.central_widget.setCurrentIndex(i)
                self.status_bar.show()
                self.set_current_grid_status()
                return
        self.combo_box.setCurrentIndex(0)  # Fallback to 0 if none selected
        self.on_grid_selected(0)

    def show_error_msg(self, title, text):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Critical)
        msg.setWindowTitle(str(title))
        msg.setText(str(text))
        msg.exec_()

    def show_info_msg(self, title, text):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Information)
        msg.setWindowTitle(str(title))
        msg.setText(str(text))
        msg.exec_()

    def confirm_export(self, path):
        if os.path.isfile(path):  # TODO: Confirm contents?
            self.show_info_msg(
                "Export successful",
                "Recovery Key successfully exported to {}".format(path))
        else:
            self.show_error_msg(
                "Error exporting Recovery Key",
                "Destination file not found after export: {}".format(path))

    def on_encryption_succeeded(self, ciphertext):
        self.crypter_thread.quit()
        if self.export_dest:
            with open(self.export_dest, 'wb') as f:
                f.write(ciphertext)
            self.confirm_export(self.export_dest)
            self.export_dest = None
        else:
            self.export_data = ciphertext
        self.crypter_thread.wait()

    def on_encryption_failed(self, message):
        self.crypter_thread.quit()
        self.show_error_msg("Error encrypting data",
                            "Encryption failed: " + message)
        self.crypter_thread.wait()

    def export_encrypted_recovery(self, gateway, password):
        settings = gateway.get_settings(include_rootcap=True)
        data = json.dumps(settings)
        self.progress = QProgressDialog("Encrypting...", None, 0, 100)
        self.progress.show()
        self.animation = QPropertyAnimation(self.progress, b'value')
        self.animation.setDuration(5000)  # XXX
        self.animation.setStartValue(0)
        self.animation.setEndValue(99)
        self.animation.start()
        self.crypter = Crypter(data.encode(), password.encode())
        self.crypter_thread = QThread()
        self.crypter.moveToThread(self.crypter_thread)
        self.crypter.succeeded.connect(self.animation.stop)
        self.crypter.succeeded.connect(self.progress.close)
        self.crypter.succeeded.connect(self.on_encryption_succeeded)
        self.crypter.failed.connect(self.animation.stop)
        self.crypter.failed.connect(self.progress.close)
        self.crypter.failed.connect(self.on_encryption_failed)
        self.crypter_thread.started.connect(self.crypter.encrypt)
        self.crypter_thread.start()
        dest, _ = QFileDialog.getSaveFileName(
            self, "Select a destination",
            os.path.join(os.path.expanduser('~'),
                         gateway.name + ' Recovery Key.json.encrypted'))
        if not dest:
            return
        if self.export_data:
            with open(dest, 'wb') as f:
                f.write(self.export_data)
            self.confirm_export(dest)
            self.export_data = None
        else:
            self.export_dest = dest

    def export_plaintext_recovery(self, gateway):
        dest, _ = QFileDialog.getSaveFileName(
            self, "Select a destination",
            os.path.join(os.path.expanduser('~'),
                         gateway.name + ' Recovery Key.json'))
        if not dest:
            return
        try:
            gateway.export(dest, include_rootcap=True)
        except Exception as e:  # pylint: disable=broad-except
            self.show_error_msg("Error exporting Recovery Key", str(e))
            return
        self.confirm_export(dest)

    def export_recovery_key(self, gateway=None):
        self.show_selected_grid_view()
        if not gateway:
            gateway = self.current_view().gateway
        password, ok = PasswordDialog.get_password(
            self, "Encryption passphrase (optional):",
            "A long passphrase will help keep your files safe in the event "
            "that your Recovery Key is ever compromised.")
        if ok and password:
            self.export_encrypted_recovery(gateway, password)
        elif ok:
            self.export_plaintext_recovery(gateway)

    def toggle_preferences_widget(self):
        if self.central_widget.currentWidget() == self.preferences_widget:
            self.show_selected_grid_view()
        else:
            self.status_bar.hide()
            for i in range(self.central_widget.count()):
                if self.central_widget.widget(i) == self.preferences_widget:
                    self.central_widget.setCurrentIndex(i)

    def on_invite_received(self, _):
        for view in self.central_widget.views:
            view.model().monitor.scan_rootcap('star.png')

    def on_invite_closed(self, obj):
        try:
            self.active_invite_receivers.remove(obj)
        except ValueError:
            pass

    def open_invite_receiver(self):
        invite_receiver = InviteReceiver(self.gateways)
        invite_receiver.done.connect(self.on_invite_received)
        invite_receiver.closed.connect(self.on_invite_closed)
        invite_receiver.show()
        self.active_invite_receivers.append(invite_receiver)

    def open_pair_widget(self):
        gateway = self.combo_box.currentData()
        if gateway:
            pair_widget = ShareWidget(gateway, self.gui)
            pair_widget.closed.connect(self.active_pair_widgets.remove)
            pair_widget.show()
            self.active_pair_widgets.append(pair_widget)

    def confirm_quit(self):
        reply = QMessageBox.question(
            self, "Exit {}?".format(APP_NAME),
            "Are you sure you wish to quit? If you quit, {} will stop "
            "synchronizing your folders until you run it again.".format(
                APP_NAME), QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            reactor.stop()

    def keyPressEvent(self, event):
        key = event.key()
        if key == Qt.Key_Escape:
            view = self.current_view()
            selected = (view.selectedIndexes() if view else None)
            if selected:
                for index in selected:
                    view.selectionModel().select(index,
                                                 QItemSelectionModel.Deselect)
            elif self.gui.systray.isSystemTrayAvailable():
                self.hide()

    def closeEvent(self, event):
        if self.gui.systray.isSystemTrayAvailable():
            event.accept()
        else:
            event.ignore()
            self.confirm_quit()
コード例 #9
0
ファイル: main_window.py プロジェクト: joppich/gridsync
class MainWindow(QMainWindow):
    def __init__(self, gui):
        super(MainWindow, self).__init__()
        self.gui = gui
        self.gateways = []
        self.welcome_dialog = None
        self.recovery_key_exporter = None

        self.setWindowTitle(APP_NAME)
        self.setMinimumSize(QSize(600, 400))
        self.setUnifiedTitleAndToolBarOnMac(True)

        self.shortcut_new = QShortcut(QKeySequence.New, self)
        self.shortcut_new.activated.connect(self.show_welcome_dialog)

        self.shortcut_open = QShortcut(QKeySequence.Open, self)
        self.shortcut_open.activated.connect(self.select_folder)

        self.shortcut_close = QShortcut(QKeySequence.Close, self)
        self.shortcut_close.activated.connect(self.close)

        self.shortcut_quit = QShortcut(QKeySequence.Quit, self)
        self.shortcut_quit.activated.connect(self.confirm_quit)

        self.central_widget = CentralWidget(self.gui)
        self.setCentralWidget(self.central_widget)

        font = QFont()
        if sys.platform == 'darwin':
            font.setPointSize(11)
        else:
            font.setPointSize(8)

        folder_icon_default = QFileIconProvider().icon(QFileInfo(config_dir))
        folder_icon_composite = CompositePixmap(
            folder_icon_default.pixmap(256, 256), resource('green-plus.png'))
        folder_icon = QIcon(folder_icon_composite)

        folder_action = QAction(folder_icon, "Add folder", self)
        folder_action.setToolTip("Add a folder...")
        folder_action.setFont(font)
        folder_action.triggered.connect(self.select_folder)

        invite_action = QAction(
            QIcon(resource('invite.png')), "Enter Code", self)
        invite_action.setToolTip("Enter an Invite Code...")
        invite_action.setFont(font)
        invite_action.triggered.connect(self.open_invite_receiver)

        history_action = QAction(
            QIcon(resource('time.png')), 'History', self)
        history_action.setToolTip("View history")
        history_action.setFont(font)
        history_action.triggered.connect(self.on_history_button_clicked)

        self.history_button = QToolButton(self)
        self.history_button.setDefaultAction(history_action)
        self.history_button.setCheckable(True)
        self.history_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)

        spacer_left = QWidget()
        spacer_left.setSizePolicy(QSizePolicy.Expanding, 0)

        self.combo_box = ComboBox()
        self.combo_box.currentIndexChanged.connect(self.on_grid_selected)

        spacer_right = QWidget()
        spacer_right.setSizePolicy(QSizePolicy.Expanding, 0)

        share_action = QAction(QIcon(resource('share.png')), "Share", self)
        share_action.setToolTip("Share...")
        share_action.setFont(font)
        share_action.triggered.connect(self.open_invite_sender_dialog)

        recovery_action = QAction(
            QIcon(resource('key.png')), "Recovery", self)
        recovery_action.setToolTip("Import/Export Recovery Key...")
        recovery_action.setFont(font)

        import_action = QAction(QIcon(), "Import Recovery Key...", self)
        import_action.setToolTip("Import Recovery Key...")
        import_action.triggered.connect(self.import_recovery_key)

        export_action = QAction(QIcon(), "Export Recovery Key...", self)
        export_action.setToolTip("Export Recovery Key...")
        export_action.setShortcut(QKeySequence.Save)
        export_action.triggered.connect(self.export_recovery_key)

        recovery_menu = QMenu(self)
        recovery_menu.addAction(import_action)
        recovery_menu.addAction(export_action)

        recovery_button = QToolButton(self)
        recovery_button.setDefaultAction(recovery_action)
        recovery_button.setMenu(recovery_menu)
        recovery_button.setPopupMode(2)
        recovery_button.setStyleSheet(
            'QToolButton::menu-indicator { image: none }')
        recovery_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)

        preferences_action = QAction(
            QIcon(resource('preferences.png')), "Preferences", self)
        preferences_action.setStatusTip("Preferences")
        preferences_action.setToolTip("Preferences")
        preferences_action.setFont(font)
        preferences_action.setShortcut(QKeySequence.Preferences)
        preferences_action.triggered.connect(self.gui.show_preferences_window)

        self.toolbar = self.addToolBar('')
        if sys.platform != 'darwin':
            self.toolbar.setStyleSheet("""
                QToolBar { border: 0px }
                QToolButton { color: rgb(50, 50, 50) }
            """)
        else:
            self.toolbar.setStyleSheet(
                "QToolButton { color: rgb(50, 50, 50) }")
        self.toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        self.toolbar.setIconSize(QSize(24, 24))
        self.toolbar.setMovable(False)
        self.toolbar.addAction(folder_action)
        self.toolbar.addAction(invite_action)
        self.toolbar.addWidget(self.history_button)
        self.toolbar.addWidget(spacer_left)
        self.toolbar.addWidget(self.combo_box)
        self.toolbar.addWidget(spacer_right)
        self.toolbar.addAction(share_action)
        self.toolbar.addWidget(recovery_button)
        self.toolbar.addAction(preferences_action)

        if sys.platform != 'win32':  # Text is getting clipped on Windows 10
            for action in self.toolbar.actions():
                widget = self.toolbar.widgetForAction(action)
                if isinstance(widget, QToolButton):
                    widget.setMaximumWidth(68)

        self.active_invite_sender_dialogs = []
        self.active_invite_receiver_dialogs = []

    def populate(self, gateways):
        for gateway in gateways:
            if gateway not in self.gateways:
                self.central_widget.add_folders_view(gateway)
                self.central_widget.add_history_view(gateway)
                self.combo_box.add_gateway(gateway)
                self.gateways.append(gateway)
        self.gui.systray.menu.populate()

    def current_view(self):
        try:
            w = self.central_widget.folders_views[self.combo_box.currentData()]
        except KeyError:
            return None
        return w.layout().itemAt(0).widget()

    def select_folder(self):
        self.show_folders_view()
        view = self.current_view()
        if view:
            view.select_folder()

    def set_current_grid_status(self):
        current_view = self.current_view()
        if not current_view:
            return
        self.gui.systray.update()

    def show_folders_view(self):
        try:
            self.central_widget.setCurrentWidget(
                self.central_widget.folders_views[self.combo_box.currentData()]
            )
        except KeyError:
            pass
        self.set_current_grid_status()

    def show_history_view(self):
        try:
            self.central_widget.setCurrentWidget(
                self.central_widget.history_views[self.combo_box.currentData()]
            )
        except KeyError:
            pass
        self.set_current_grid_status()

    def show_welcome_dialog(self):
        if self.welcome_dialog:
            self.welcome_dialog.close()
        self.welcome_dialog = WelcomeDialog(self.gui, self.gateways)
        self.welcome_dialog.show()
        self.welcome_dialog.raise_()

    def on_grid_selected(self, index):
        if index == self.combo_box.count() - 1:
            self.show_welcome_dialog()
        if not self.combo_box.currentData():
            return
        if self.history_button.isChecked():
            self.show_history_view()
        else:
            self.show_folders_view()
        self.setWindowTitle(
            "{} - {}".format(APP_NAME, self.combo_box.currentData().name)
        )

    def confirm_export(self, path):
        if os.path.isfile(path):
            info(
                self,
                "Export successful",
                "Recovery Key successfully exported to {}".format(path))
        else:
            error(
                self,
                "Error exporting Recovery Key",
                "Destination file not found after export: {}".format(path))

    def export_recovery_key(self, gateway=None):
        self.show_folders_view()
        if not gateway:
            gateway = self.combo_box.currentData()
        self.recovery_key_exporter = RecoveryKeyExporter(self)
        self.recovery_key_exporter.done.connect(self.confirm_export)
        self.recovery_key_exporter.do_export(gateway)

    def import_recovery_key(self):
        # XXX Quick hack for user-testing; change later
        self.welcome_dialog = WelcomeDialog(self.gui, self.gateways)
        self.welcome_dialog.on_restore_link_activated()

    def on_history_button_clicked(self):
        if not self.history_button.isChecked():
            self.history_button.setChecked(True)
            self.show_history_view()
        else:
            self.history_button.setChecked(False)
            self.show_folders_view()

    def on_invite_received(self, gateway):
        self.populate([gateway])
        for view in self.central_widget.views:
            view.model().monitor.scan_rootcap('star.png')

    def on_invite_closed(self, obj):
        try:
            self.active_invite_receiver_dialogs.remove(obj)
        except ValueError:
            pass

    def open_invite_receiver(self):
        invite_receiver_dialog = InviteReceiverDialog(self.gateways)
        invite_receiver_dialog.done.connect(self.on_invite_received)
        invite_receiver_dialog.closed.connect(self.on_invite_closed)
        invite_receiver_dialog.show()
        self.active_invite_receiver_dialogs.append(invite_receiver_dialog)

    def open_invite_sender_dialog(self):
        gateway = self.combo_box.currentData()
        if gateway:
            view = self.current_view()
            if view:
                invite_sender_dialog = InviteSenderDialog(
                    gateway, self.gui, view.get_selected_folders())
            else:
                invite_sender_dialog = InviteSenderDialog(gateway, self.gui)
            invite_sender_dialog.closed.connect(
                self.active_invite_sender_dialogs.remove)
            invite_sender_dialog.show()
            self.active_invite_sender_dialogs.append(invite_sender_dialog)

    def confirm_quit(self):
        folder_loading = False
        folder_syncing = False
        for model in [view.model() for view in self.central_widget.views]:
            for row in range(model.rowCount()):
                status = model.item(row, 1).data(Qt.UserRole)
                mtime = model.item(row, 2).data(Qt.UserRole)
                if not status and not mtime:  # "Loading..." and not yet synced
                    folder_loading = True
                    break
                elif status == 1:  # "Syncing"
                    folder_syncing = True
                    break
        msg = QMessageBox(self)
        if folder_loading:
            msg.setIcon(QMessageBox.Warning)
            informative_text = (
                "One or more folders have not finished loading. If these "
                "folders were recently added, you may need to add them again.")
        elif folder_syncing:
            msg.setIcon(QMessageBox.Warning)
            informative_text = (
                "One or more folders are currently syncing. If you quit, any "
                "pending upload or download operations will be cancelled "
                "until you launch {} again.".format(APP_NAME))
        else:
            msg.setIcon(QMessageBox.Question)
            informative_text = (
                "If you quit, {} will stop synchronizing your folders until "
                "you launch it again.".format(APP_NAME))
        if sys.platform == 'darwin':
            msg.setText("Are you sure you wish to quit?")
            msg.setInformativeText(informative_text)
        else:
            msg.setWindowTitle("Exit {}?".format(APP_NAME))
            msg.setText(
                "Are you sure you wish to quit? {}".format(informative_text))
        msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        msg.setDefaultButton(QMessageBox.No)
        if msg.exec_() == QMessageBox.Yes:
            if sys.platform == 'win32':
                self.gui.systray.hide()
            reactor.stop()

    def keyPressEvent(self, event):
        key = event.key()
        if key in (Qt.Key_Backspace, Qt.Key_Delete):
            view = self.current_view()
            selected = (view.selectedIndexes() if view else None)
            if selected:
                view.confirm_remove(view.get_selected_folders())
        if key == Qt.Key_Escape:
            view = self.current_view()
            selected = (view.selectedIndexes() if view else None)
            if selected:
                for index in selected:
                    view.selectionModel().select(
                        index, QItemSelectionModel.Deselect)
            elif self.gui.systray.isSystemTrayAvailable():
                self.hide()

    def closeEvent(self, event):
        if self.gui.systray.isSystemTrayAvailable():
            event.accept()
        else:
            event.ignore()
            self.confirm_quit()
コード例 #10
0
ファイル: test_welcome.py プロジェクト: testimx62/gridsync
def test_init_welcome_dialog():
    welcome_dialog = WelcomeDialog(None)
    assert welcome_dialog
コード例 #11
0
def test_init_welcome_dialog():
    welcome_dialog = WelcomeDialog(MagicMock())
    assert welcome_dialog