diff --git a/desktop/src/onionshare/main_window.py b/desktop/src/onionshare/main_window.py index c125741c..0f11cf8e 100644 --- a/desktop/src/onionshare/main_window.py +++ b/desktop/src/onionshare/main_window.py @@ -24,8 +24,6 @@ from PySide2 import QtCore, QtWidgets, QtGui from . import strings from .tor_connection_dialog import TorConnectionDialog -from .tor_settings_dialog import TorSettingsDialog -from .settings_dialog import SettingsDialog from .widgets import Alert from .update_checker import UpdateThread from .tab_widget import TabWidget @@ -245,21 +243,22 @@ class MainWindow(QtWidgets.QMainWindow): def open_tor_settings(self): """ - Open the TorSettingsDialog. + Open the TorSettingsTab """ self.common.log("MainWindow", "open_tor_settings") - d = TorSettingsDialog(self.common) - d.settings_saved.connect(self.settings_have_changed) - d.exec_() + # d = TorSettingsDialog(self.common) + # d.settings_saved.connect(self.settings_have_changed) + # d.exec_() def open_settings(self): """ - Open the SettingsDialog. + Open the SettingsTab """ self.common.log("MainWindow", "open_settings") - d = SettingsDialog(self.common) - d.settings_saved.connect(self.settings_have_changed) - d.exec_() + self.tabs.open_settings_tab() + # d = SettingsDialog(self.common) + # d.settings_saved.connect(self.settings_have_changed) + # d.exec_() def settings_have_changed(self): self.common.log("OnionShareGui", "settings_have_changed") diff --git a/desktop/src/onionshare/settings_dialog.py b/desktop/src/onionshare/settings_tab.py similarity index 84% rename from desktop/src/onionshare/settings_dialog.py rename to desktop/src/onionshare/settings_tab.py index b1003386..251783aa 100644 --- a/desktop/src/onionshare/settings_dialog.py +++ b/desktop/src/onionshare/settings_tab.py @@ -19,57 +19,34 @@ along with this program. If not, see . """ from PySide2 import QtCore, QtWidgets, QtGui -from PySide2.QtCore import Slot, Qt -from PySide2.QtGui import QPalette, QColor import sys import platform import datetime import re import os from onionshare_cli.settings import Settings -from onionshare_cli.onion import ( - Onion, - TorErrorInvalidSetting, - TorErrorAutomatic, - TorErrorSocketPort, - TorErrorSocketFile, - TorErrorMissingPassword, - TorErrorUnreadableCookieFile, - TorErrorAuthError, - TorErrorProtocolError, - BundledTorTimeout, - BundledTorBroken, - TorTooOldEphemeral, - TorTooOldStealth, - PortNotAvailable, -) from . import strings from .widgets import Alert from .update_checker import UpdateThread -from .tor_connection_dialog import TorConnectionDialog from .gui_common import GuiCommon -class SettingsDialog(QtWidgets.QDialog): +class SettingsTab(QtWidgets.QWidget): """ Settings dialog. """ settings_saved = QtCore.Signal() - def __init__(self, common): - super(SettingsDialog, self).__init__() + def __init__(self, common, tab_id): + super(SettingsTab, self).__init__() self.common = common - - self.common.log("SettingsDialog", "__init__") - - self.setModal(True) - self.setWindowTitle(strings._("gui_settings_window_title")) - self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png"))) + self.common.log("SettingsTab", "__init__") self.system = platform.system() + self.tab_id = tab_id # Automatic updates options @@ -146,31 +123,26 @@ class SettingsDialog(QtWidgets.QDialog): # Buttons self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save")) self.save_button.clicked.connect(self.save_clicked) - self.cancel_button = QtWidgets.QPushButton( - strings._("gui_settings_button_cancel") - ) - self.cancel_button.clicked.connect(self.cancel_clicked) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addStretch() buttons_layout.addWidget(self.save_button) - buttons_layout.addWidget(self.cancel_button) # Layout layout = QtWidgets.QVBoxLayout() + layout.addStretch() layout.addWidget(autoupdate_group) if autoupdate_group.isVisible(): layout.addSpacing(20) layout.addLayout(language_layout) layout.addLayout(theme_layout) layout.addSpacing(20) - layout.addStretch() layout.addWidget(version_label) layout.addWidget(help_label) layout.addSpacing(20) layout.addLayout(buttons_layout) + layout.addStretch() self.setLayout(layout) - self.cancel_button.setFocus() self.reload_settings() @@ -199,7 +171,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Check for Updates button clicked. Manually force an update check. """ - self.common.log("SettingsDialog", "check_for_updates") + self.common.log("SettingsTab", "check_for_updates") # Disable buttons self._disable_buttons() self.common.gui.qtapp.processEvents() @@ -261,7 +233,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Save button clicked. Save current settings to disk. """ - self.common.log("SettingsDialog", "save_clicked") + self.common.log("SettingsTab", "save_clicked") def changed(s1, s2, keys): """ @@ -301,30 +273,12 @@ class SettingsDialog(QtWidgets.QDialog): self.settings_saved.emit() self.close() - def cancel_clicked(self): - """ - Cancel button clicked. - """ - self.common.log("SettingsDialog", "cancel_clicked") - if ( - not self.common.gui.local_only - and not self.common.gui.onion.is_authenticated() - ): - Alert( - self.common, - strings._("gui_tor_connection_canceled"), - QtWidgets.QMessageBox.Warning, - ) - sys.exit() - else: - self.close() - def help_clicked(self): """ Help button clicked. """ - self.common.log("SettingsDialog", "help_clicked") - SettingsDialog.open_help() + self.common.log("SettingsTab", "help_clicked") + SettingsTab.open_help() @staticmethod def open_help(): @@ -335,7 +289,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Return a Settings object that's full of values from the settings dialog. """ - self.common.log("SettingsDialog", "settings_from_fields") + self.common.log("SettingsTab", "settings_from_fields") settings = Settings(self.common) settings.load() # To get the last update timestamp @@ -351,7 +305,7 @@ class SettingsDialog(QtWidgets.QDialog): return settings def _update_autoupdate_timestamp(self, autoupdate_timestamp): - self.common.log("SettingsDialog", "_update_autoupdate_timestamp") + self.common.log("SettingsTab", "_update_autoupdate_timestamp") if autoupdate_timestamp: dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) @@ -363,18 +317,16 @@ class SettingsDialog(QtWidgets.QDialog): ) def _disable_buttons(self): - self.common.log("SettingsDialog", "_disable_buttons") + self.common.log("SettingsTab", "_disable_buttons") self.check_for_updates_button.setEnabled(False) self.save_button.setEnabled(False) - self.cancel_button.setEnabled(False) def _enable_buttons(self): - self.common.log("SettingsDialog", "_enable_buttons") + self.common.log("SettingsTab", "_enable_buttons") # We can't check for updates if we're still not connected to Tor if not self.common.gui.onion.connected_to_tor: self.check_for_updates_button.setEnabled(False) else: self.check_for_updates_button.setEnabled(True) self.save_button.setEnabled(True) - self.cancel_button.setEnabled(True) diff --git a/desktop/src/onionshare/tab_widget.py b/desktop/src/onionshare/tab_widget.py index a955ea53..daf878d7 100644 --- a/desktop/src/onionshare/tab_widget.py +++ b/desktop/src/onionshare/tab_widget.py @@ -26,6 +26,8 @@ from . import strings from .tab import Tab from .threads import EventHandlerThread from .gui_common import GuiCommon +from .tor_settings_tab import TorSettingsTab +from .settings_tab import SettingsTab class TabWidget(QtWidgets.QTabWidget): @@ -116,6 +118,11 @@ class TabWidget(QtWidgets.QTabWidget): # Active tab was changed tab_id = self.currentIndex() self.common.log("TabWidget", "tab_changed", f"Tab was changed to {tab_id}") + + # If it's Settings or Tor Settings, ignore + if self.is_settings_tab(tab_id): + return + try: mode = self.tabs[tab_id].get_mode() if mode: @@ -160,20 +167,7 @@ class TabWidget(QtWidgets.QTabWidget): # In macOS, manually create a close button because tabs don't seem to have them otherwise if self.common.platform == "Darwin": - - def close_tab(): - self.tabBar().tabCloseRequested.emit(self.indexOf(tab)) - - tab.close_button = QtWidgets.QPushButton() - tab.close_button.setFlat(True) - tab.close_button.setFixedWidth(40) - tab.close_button.setIcon( - QtGui.QIcon(GuiCommon.get_resource_path("images/close_tab.png")) - ) - tab.close_button.clicked.connect(close_tab) - self.tabBar().setTabButton( - index, QtWidgets.QTabBar.RightSide, tab.close_button - ) + self.macos_create_close_button(tab, index) tab.init(mode_settings) @@ -187,6 +181,25 @@ class TabWidget(QtWidgets.QTabWidget): # Bring the window to front, in case this is being added by an event self.bring_to_front.emit() + def open_settings_tab(self): + self.common.log("TabWidget", "open_settings_tab") + + # See if a settings tab is already open, and if so switch to it + for index in range(self.count()): + if self.is_settings_tab(index): + self.setCurrentIndex(index) + return + + settings_tab = SettingsTab(self.common, self.current_tab_id) + self.tabs[self.current_tab_id] = settings_tab + self.current_tab_id += 1 + index = self.addTab(settings_tab, strings._("gui_settings_window_title")) + self.setCurrentIndex(index) + + # In macOS, manually create a close button because tabs don't seem to have them otherwise + if self.common.platform == "Darwin": + self.macos_create_close_button(settings_tab, index) + def change_title(self, tab_id, title): shortened_title = title if len(shortened_title) > 11: @@ -224,9 +237,10 @@ class TabWidget(QtWidgets.QTabWidget): # Figure out the order of persistent tabs to save in settings persistent_tabs = [] for index in range(self.count()): - tab = self.widget(index) - if tab.settings.get("persistent", "enabled"): - persistent_tabs.append(tab.settings.id) + if not self.is_settings_tab(index): + tab = self.widget(index) + if tab.settings.get("persistent", "enabled"): + persistent_tabs.append(tab.settings.id) # Only save if tabs have actually moved if persistent_tabs != self.common.settings.get("persistent_tabs"): self.common.settings.set("persistent_tabs", persistent_tabs) @@ -235,11 +249,8 @@ class TabWidget(QtWidgets.QTabWidget): def close_tab(self, index): self.common.log("TabWidget", "close_tab", f"{index}") tab = self.widget(index) - if tab.close_tab(): - # If the tab is persistent, delete the settings file from disk - if tab.settings.get("persistent", "enabled"): - tab.settings.delete() + if self.is_settings_tab(index): # Remove the tab self.removeTab(index) del self.tabs[tab.tab_id] @@ -248,7 +259,21 @@ class TabWidget(QtWidgets.QTabWidget): if self.count() == 0: self.new_tab_clicked() - self.save_persistent_tabs() + else: + if tab.close_tab(): + # If the tab is persistent, delete the settings file from disk + if tab.settings.get("persistent", "enabled"): + tab.settings.delete() + + self.save_persistent_tabs() + + # Remove the tab + self.removeTab(index) + del self.tabs[tab.tab_id] + + # If the last tab is closed, open a new one + if self.count() == 0: + self.new_tab_clicked() def are_tabs_active(self): """ @@ -273,6 +298,28 @@ class TabWidget(QtWidgets.QTabWidget): super(TabWidget, self).resizeEvent(event) self.move_new_tab_button() + def macos_create_close_button(self, tab, index): + def close_tab(): + self.tabBar().tabCloseRequested.emit(self.indexOf(tab)) + + close_button = QtWidgets.QPushButton() + close_button.setFlat(True) + close_button.setFixedWidth(40) + close_button.setIcon( + QtGui.QIcon(GuiCommon.get_resource_path("images/close_tab.png")) + ) + close_button.clicked.connect(close_tab) + self.tabBar().setTabButton(index, QtWidgets.QTabBar.RightSide, tab.close_button) + + def is_settings_tab(self, tab_id): + if tab_id not in self.tabs: + return True + + return ( + type(self.tabs[tab_id]) is SettingsTab + or type(self.tabs[tab_id]) is TorSettingsTab + ) + class TabBar(QtWidgets.QTabBar): """ diff --git a/desktop/src/onionshare/tor_settings_dialog.py b/desktop/src/onionshare/tor_settings_tab.py similarity index 95% rename from desktop/src/onionshare/tor_settings_dialog.py rename to desktop/src/onionshare/tor_settings_tab.py index 38ff512a..279469df 100644 --- a/desktop/src/onionshare/tor_settings_dialog.py +++ b/desktop/src/onionshare/tor_settings_tab.py @@ -34,25 +34,21 @@ from .moat_dialog import MoatDialog from .gui_common import GuiCommon -class TorSettingsDialog(QtWidgets.QDialog): +class TorSettingsTab(QtWidgets.QWidget): """ Settings dialog. """ settings_saved = QtCore.Signal() - def __init__(self, common): - super(TorSettingsDialog, self).__init__() + def __init__(self, common, tab_id): + super(TorSettingsTab, self).__init__() self.common = common - - self.common.log("TorSettingsDialog", "__init__") - - self.setModal(True) - self.setWindowTitle(strings._("gui_tor_settings_window_title")) - self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png"))) + self.common.log("TorSettingsTab", "__init__") self.system = platform.system() + self.tab_id = tab_id # Connection type: either automatic, control port, or socket file @@ -443,7 +439,7 @@ class TorSettingsDialog(QtWidgets.QDialog): """ Connection type bundled was toggled """ - self.common.log("TorSettingsDialog", "connection_type_bundled_toggled") + self.common.log("TorSettingsTab", "connection_type_bundled_toggled") if checked: self.tor_settings_group.hide() self.connection_type_socks.hide() @@ -495,7 +491,7 @@ class TorSettingsDialog(QtWidgets.QDialog): """ Request new bridge button clicked """ - self.common.log("TorSettingsDialog", "bridge_moat_button_clicked") + self.common.log("TorSettingsTab", "bridge_moat_button_clicked") moat_dialog = MoatDialog(self.common) moat_dialog.got_bridges.connect(self.bridge_moat_got_bridges) @@ -505,7 +501,7 @@ class TorSettingsDialog(QtWidgets.QDialog): """ Got new bridges from moat """ - self.common.log("TorSettingsDialog", "bridge_moat_got_bridges") + self.common.log("TorSettingsTab", "bridge_moat_got_bridges") self.bridge_moat_textbox.document().setPlainText(bridges) self.bridge_moat_textbox.show() @@ -522,7 +518,7 @@ class TorSettingsDialog(QtWidgets.QDialog): """ Connection type automatic was toggled. If checked, hide authentication fields. """ - self.common.log("TorSettingsDialog", "connection_type_automatic_toggled") + self.common.log("TorSettingsTab", "connection_type_automatic_toggled") if checked: self.tor_settings_group.hide() self.connection_type_socks.hide() @@ -533,7 +529,7 @@ class TorSettingsDialog(QtWidgets.QDialog): Connection type control port was toggled. If checked, show extra fields for Tor control address and port. If unchecked, hide those extra fields. """ - self.common.log("TorSettingsDialog", "connection_type_control_port_toggled") + self.common.log("TorSettingsTab", "connection_type_control_port_toggled") if checked: self.tor_settings_group.show() self.connection_type_control_port_extras.show() @@ -547,7 +543,7 @@ class TorSettingsDialog(QtWidgets.QDialog): Connection type socket file was toggled. If checked, show extra fields for socket file. If unchecked, hide those extra fields. """ - self.common.log("TorSettingsDialog", "connection_type_socket_file_toggled") + self.common.log("TorSettingsTab", "connection_type_socket_file_toggled") if checked: self.tor_settings_group.show() self.connection_type_socket_file_extras.show() @@ -560,7 +556,7 @@ class TorSettingsDialog(QtWidgets.QDialog): """ Authentication option no authentication was toggled. """ - self.common.log("TorSettingsDialog", "authenticate_no_auth_toggled") + self.common.log("TorSettingsTab", "authenticate_no_auth_toggled") if checked: self.authenticate_password_extras.hide() else: @@ -571,7 +567,7 @@ class TorSettingsDialog(QtWidgets.QDialog): Test Tor Settings button clicked. With the given settings, see if we can successfully connect and authenticate to Tor. """ - self.common.log("TorSettingsDialog", "test_tor_clicked") + self.common.log("TorSettingsTab", "test_tor_clicked") settings = self.settings_from_fields() if not settings: return @@ -605,7 +601,7 @@ class TorSettingsDialog(QtWidgets.QDialog): """ Save button clicked. Save current settings to disk. """ - self.common.log("TorSettingsDialog", "save_clicked") + self.common.log("TorSettingsTab", "save_clicked") def changed(s1, s2, keys): """ @@ -628,7 +624,7 @@ class TorSettingsDialog(QtWidgets.QDialog): if not self.common.gui.local_only: if self.common.gui.onion.is_authenticated(): self.common.log( - "TorSettingsDialog", "save_clicked", "Connected to Tor" + "TorSettingsTab", "save_clicked", "Connected to Tor" ) if changed( @@ -654,7 +650,7 @@ class TorSettingsDialog(QtWidgets.QDialog): else: self.common.log( - "TorSettingsDialog", "save_clicked", "Not connected to Tor" + "TorSettingsTab", "save_clicked", "Not connected to Tor" ) # Tor isn't connected, so try connecting reboot_onion = True @@ -663,7 +659,7 @@ class TorSettingsDialog(QtWidgets.QDialog): if reboot_onion: # Reinitialize the Onion object self.common.log( - "TorSettingsDialog", "save_clicked", "rebooting the Onion" + "TorSettingsTab", "save_clicked", "rebooting the Onion" ) self.common.gui.onion.cleanup() @@ -671,7 +667,7 @@ class TorSettingsDialog(QtWidgets.QDialog): tor_con.start() self.common.log( - "TorSettingsDialog", + "TorSettingsTab", "save_clicked", f"Onion done rebooting, connected to Tor: {self.common.gui.onion.connected_to_tor}", ) @@ -694,7 +690,7 @@ class TorSettingsDialog(QtWidgets.QDialog): """ Cancel button clicked. """ - self.common.log("TorSettingsDialog", "cancel_clicked") + self.common.log("TorSettingsTab", "cancel_clicked") if ( not self.common.gui.local_only and not self.common.gui.onion.is_authenticated() @@ -712,7 +708,7 @@ class TorSettingsDialog(QtWidgets.QDialog): """ Return a Settings object that's full of values from the settings dialog. """ - self.common.log("TorSettingsDialog", "settings_from_fields") + self.common.log("TorSettingsTab", "settings_from_fields") settings = Settings(self.common) settings.load() # To get the last update timestamp @@ -833,13 +829,13 @@ class TorSettingsDialog(QtWidgets.QDialog): return settings def closeEvent(self, e): - self.common.log("TorSettingsDialog", "closeEvent") + self.common.log("TorSettingsTab", "closeEvent") # On close, if Tor isn't connected, then quit OnionShare altogether if not self.common.gui.local_only: if not self.common.gui.onion.is_authenticated(): self.common.log( - "TorSettingsDialog", + "TorSettingsTab", "closeEvent", "Closing while not connected to Tor", )