From c7304e54e40cd9151e6d00a8441aaf48b68c9bcc Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 3 Sep 2014 15:50:08 -0700 Subject: single pref win: move eip preferences to new combined preference window. --- Makefile | 16 +- data/resources/flags.qrc | 1 + src/leap/bitmask/gui/mainwindow.py | 38 +- src/leap/bitmask/gui/preferences_account_page.py | 165 ++++++++ src/leap/bitmask/gui/preferences_email_page.py | 38 ++ src/leap/bitmask/gui/preferences_vpn_page.py | 150 ++++++++ src/leap/bitmask/gui/preferenceswindow.py | 419 +++------------------ src/leap/bitmask/gui/ui/eippreferences.ui | 102 ----- src/leap/bitmask/gui/ui/mainwindow.ui | 8 +- src/leap/bitmask/gui/ui/preferences.ui | 234 +++++------- .../bitmask/gui/ui/preferences_account_page.ui | 95 +++++ src/leap/bitmask/gui/ui/preferences_email_page.ui | 32 ++ src/leap/bitmask/gui/ui/preferences_vpn_page.ui | 89 +++++ src/leap/bitmask/services/eip/eipconfig.py | 43 ++- 14 files changed, 760 insertions(+), 670 deletions(-) create mode 100644 src/leap/bitmask/gui/preferences_account_page.py create mode 100644 src/leap/bitmask/gui/preferences_email_page.py create mode 100644 src/leap/bitmask/gui/preferences_vpn_page.py delete mode 100644 src/leap/bitmask/gui/ui/eippreferences.ui create mode 100644 src/leap/bitmask/gui/ui/preferences_account_page.ui create mode 100644 src/leap/bitmask/gui/ui/preferences_email_page.ui create mode 100644 src/leap/bitmask/gui/ui/preferences_vpn_page.ui diff --git a/Makefile b/Makefile index bbeebc36..e7173c80 100644 --- a/Makefile +++ b/Makefile @@ -19,9 +19,19 @@ TRANSLAT_DIR = data/translations #Project file, used for translations PROJFILE = data/bitmask.pro -#UI files to compile -UI_FILES = loggerwindow.ui mainwindow.ui wizard.ui login.ui preferences.ui eip_status.ui mail_status.ui eippreferences.ui advanced_key_management.ui -#Qt resource files to compile +# UI files to compile +UI_FILES = \ + loggerwindow.ui \ + wizard.ui \ + mainwindow.ui login.ui eip_status.ui mail_status.ui \ + preferences.ui \ + preferences_account_page.ui \ + preferences_vpn_page.ui \ + preferences_email_page.ui \ + password_change.ui \ + advanced_key_management.ui + +# Qt resource files to compile RESOURCES = icons.qrc flags.qrc locale.qrc loggerwindow.qrc #pyuic4 and pyrcc4 binaries diff --git a/data/resources/flags.qrc b/data/resources/flags.qrc index 8bdc9c4c..aeecc54f 100644 --- a/data/resources/flags.qrc +++ b/data/resources/flags.qrc @@ -58,6 +58,7 @@ ../images/countries/us.png ../images/countries/ve.png ../images/countries/vn.png + ../images/countries/xx.png ../images/countries/za.png \ No newline at end of file diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 916e7c1f..243fe117 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -37,7 +37,6 @@ from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement -from leap.bitmask.gui.eip_preferenceswindow import EIPPreferencesWindow from leap.bitmask.gui.eip_status import EIPStatusWidget from leap.bitmask.gui.loggerwindow import LoggerWindow from leap.bitmask.gui.login import LoginWidget @@ -97,6 +96,9 @@ class MainWindow(QtGui.QMainWindow): # We give the services some time to a halt before forcing quit. SERVICES_STOP_TIMEOUT = 3000 # in milliseconds + # Preferences window + preferences = None + def __init__(self, start_hidden=False, backend_pid=None): """ Constructor for the client main window @@ -213,8 +215,6 @@ class MainWindow(QtGui.QMainWindow): self._backend_connect() self.ui.action_preferences.triggered.connect(self._show_preferences) - self.ui.action_eip_preferences.triggered.connect( - self._show_eip_preferences) self.ui.action_about_leap.triggered.connect(self._about) self.ui.action_quit.triggered.connect(self.quit) self.ui.action_wizard.triggered.connect(self._launch_wizard) @@ -247,7 +247,6 @@ class MainWindow(QtGui.QMainWindow): # disable buttons for now, may come back later. # self.ui.btnPreferences.clicked.connect(self._show_preferences) - # self.ui.btnEIPPreferences.clicked.connect(self._show_eip_preferences) self._enabled_services = [] self._ui_mx_visible = True @@ -601,16 +600,13 @@ class MainWindow(QtGui.QMainWindow): """ user = self._logged_user domain = self._providers.get_selected_provider() - mx_provided = False - if self._provider_details is not None: - mx_provided = MX_SERVICE in self._provider_details['services'] - preferences = PreferencesWindow(self, user, domain, self._backend, - self._soledad_started, mx_provided, - self._leap_signaler) - - self.soledad_ready.connect(preferences.set_soledad_ready) - preferences.show() - preferences.preferences_saved.connect(self._update_eip_enabled_status) + if self.preferences is not None: + self.preferences.close() + self.preferences = PreferencesWindow(self, user, domain, + self._backend, + self._soledad_started, + self._leap_signaler) + self.preferences.show() @QtCore.Slot() def _update_eip_enabled_status(self): @@ -718,20 +714,6 @@ class MainWindow(QtGui.QMainWindow): """ self._eip_status.missing_helpers = True - @QtCore.Slot() - def _show_eip_preferences(self): - """ - TRIGGERS: - self.ui.btnEIPPreferences.clicked - self.ui.action_eip_preferences (disabled for now) - - Display the EIP preferences window. - """ - domain = self._providers.get_selected_provider() - pref = EIPPreferencesWindow(self, domain, - self._backend, self._leap_signaler) - pref.show() - # # updates # diff --git a/src/leap/bitmask/gui/preferences_account_page.py b/src/leap/bitmask/gui/preferences_account_page.py new file mode 100644 index 00000000..9cc94482 --- /dev/null +++ b/src/leap/bitmask/gui/preferences_account_page.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Widget for "account" preferences +""" +import logging + +from functools import partial + +from PySide import QtCore, QtGui +from ui_preferences_account_page import Ui_PreferencesAccountPage + +logger = logging.getLogger(__name__) + +class PreferencesAccountPage(QtGui.QWidget): + """ + + """ + + def __init__(self, parent): + """ + """ + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_PreferencesAccountPage() + self.ui.setupUi(self) + self.show() + + self._selected_services = set() + #self._leap_signaler.prov_get_supported_services.connect(self._load_services) + + @QtCore.Slot() + def set_soledad_ready(self): + """ + TRIGGERS: + parent.soledad_ready + + It notifies when the soledad object as ready to use. + """ + #self.ui.lblPasswordChangeStatus.setVisible(False) + #self.ui.gbPasswordChange.setEnabled(True) + + @QtCore.Slot(str, int) + def _service_selection_changed(self, service, state): + """ + TRIGGERS: + service_checkbox.stateChanged + + Adds the service to the state if the state is checked, removes + it otherwise + + :param service: service to handle + :type service: str + :param state: state of the checkbox + :type state: int + """ + if state == QtCore.Qt.Checked: + self._selected_services = \ + self._selected_services.union(set([service])) + else: + self._selected_services = \ + self._selected_services.difference(set([service])) + + # We hide the maybe-visible status label after a change + self.ui.lblProvidersServicesStatus.setVisible(False) + + @QtCore.Slot(str) + def _populate_services(self, domain): + """ + TRIGGERS: + self.ui.cbProvidersServices.currentIndexChanged[unicode] + + Fill the services list with the selected provider's services. + + :param domain: the domain of the provider to load services from. + :type domain: str + """ + # We hide the maybe-visible status label after a change + self.ui.lblProvidersServicesStatus.setVisible(False) + + if not domain: + return + + # set the proper connection for the 'save' button + try: + self.ui.pbSaveServices.clicked.disconnect() + except RuntimeError: + pass # Signal was not connected + + save_services = partial(self._save_enabled_services, domain) + self.ui.pbSaveServices.clicked.connect(save_services) + + self._backend.provider_get_supported_services(domain=domain) + + @QtCore.Slot(str) + def _load_services(self, services): + """ + TRIGGERS: + self.ui.cbProvidersServices.currentIndexChanged[unicode] + + Loads the services that the provider provides into the UI for + the user to enable or disable. + + :param domain: the domain of the provider to load services from. + :type domain: str + """ + domain = self.ui.cbProvidersServices.currentText() + services_conf = self._settings.get_enabled_services(domain) + + # discard changes if other provider is selected + self._selected_services = set() + + # from: http://stackoverflow.com/a/13103617/687989 + # remove existing checkboxes + layout = self.ui.vlServices + for i in reversed(range(layout.count())): + layout.itemAt(i).widget().setParent(None) + + # add one checkbox per service and set the current configured value + for service in services: + try: + checkbox = QtGui.QCheckBox(self) + service_label = get_service_display_name(service) + checkbox.setText(service_label) + + self.ui.vlServices.addWidget(checkbox) + checkbox.stateChanged.connect( + partial(self._service_selection_changed, service)) + + checkbox.setChecked(service in services_conf) + except ValueError: + logger.error("Something went wrong while trying to " + "load service %s" % (service,)) + + @QtCore.Slot(str) + def _save_enabled_services(self, provider): + """ + TRIGGERS: + self.ui.pbSaveServices.clicked + + Saves the new enabled services settings to the configuration file. + + :param provider: the provider config that we need to save. + :type provider: str + """ + services = list(self._selected_services) + self._settings.set_enabled_services(provider, services) + + msg = self.tr( + "Services settings for provider '{0}' saved.".format(provider)) + logger.debug(msg) + self._set_providers_services_status(msg, success=True) + self.preferences_saved.emit() diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py new file mode 100644 index 00000000..08ff5463 --- /dev/null +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Widget for "email" preferences +""" +import logging + +from PySide import QtCore, QtGui +from ui_preferences_email_page import Ui_PreferencesEmailPage + +logger = logging.getLogger(__name__) + +class PreferencesEmailPage(QtGui.QWidget): + """ + + """ + + def __init__(self, parent): + """ + """ + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_PreferencesEmailPage() + self.ui.setupUi(self) + self.show() + diff --git a/src/leap/bitmask/gui/preferences_vpn_page.py b/src/leap/bitmask/gui/preferences_vpn_page.py new file mode 100644 index 00000000..ba1366e4 --- /dev/null +++ b/src/leap/bitmask/gui/preferences_vpn_page.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Widget for "vpn" preferences +""" + +from PySide import QtCore, QtGui +from ui_preferences_vpn_page import Ui_PreferencesVpnPage + +from leap.bitmask.config.leapsettings import LeapSettings + + +class PreferencesVpnPage(QtGui.QWidget): + """ + Page in the preferences window that shows VPN settings + """ + + def __init__(self, parent, domain, backend, leap_signaler): + """ + :param parent: parent object of the EIPPreferencesWindow. + :type parent: QWidget + + :param domain: the selected by default domain. + :type domain: unicode + + :param backend: Backend being used + :type backend: Backend + """ + QtGui.QWidget.__init__(self, parent) + self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") + + self._settings = LeapSettings() + self._leap_signaler = leap_signaler + self._backend = backend + + # Load UI + self.ui = Ui_PreferencesVpnPage() + self.ui.setupUi(self) + self.ui.flash_label.setVisible(False) + + # Connections + self.ui.gateways_list.clicked.connect(self._save_selected_gateway) + + self._domain = domain + self._backend_connect() + self._backend.eip_get_gateways_list(domain=domain) + + def _flash_error(self, message): + """ + Sets string for the flash message. + + :param message: the text to be displayed + :type message: str + """ + message = "%s" % (message,) + self.ui.flash_label.setVisible(True) + self.ui.flash_label.setText(message) + + # def _flash_success(self, message): + # """ + # Sets string for the flash message. + # + # :param message: the text to be displayed + # :type message: str + # """ + # message = "%s" % (message,) + # self.ui.flash_label.setVisible(True) + # self.ui.flash_label.setText(message) + + @QtCore.Slot(str) + def _save_selected_gateway(self, index): + """ + TRIGGERS: + self.ui.gateways_list.clicked + + Saves the new gateway setting to the configuration file. + + :param index: the current index of the selection. + :type current_item: QModelIndex + """ + item = self.ui.gateways_list.currentItem() + + if item.text() == self.AUTOMATIC_GATEWAY_LABEL: + gateway = self._settings.GATEWAY_AUTOMATIC + else: + gateway = item.data(QtCore.Qt.UserRole) + self._settings.set_selected_gateway(self._domain, gateway) + self._backend.settings_set_selected_gateway(provider=self._domain, + gateway=gateway) + + @QtCore.Slot(list) + def _update_gateways_list(self, gateways): + """ + TRIGGERS: + Signaler.eip_get_gateways_list + + :param gateways: a list of gateways + :type gateways: list of unicode + + Add the available gateways and select the one stored in + configuration file. + """ + self.ui.gateways_list.clear() + self.ui.gateways_list.addItem(self.AUTOMATIC_GATEWAY_LABEL) + + selected_gateway = self._settings.get_selected_gateway( + self._domain) + + index = 0 + for idx, (gw_name, gw_ip, gw_country) in enumerate(gateways): + gateway_text = "{0} ({1})".format(gw_name, gw_ip) + item = QtGui.QListWidgetItem(self.ui.gateways_list) + item.setText(gateway_text) + item.setIcon(QtGui.QIcon( + ":/images/countries/%s.png" % (gw_country.lower(),))) + item.setData(QtCore.Qt.UserRole, gw_ip) + if gw_ip == selected_gateway: + index = idx + 1 + self.ui.gateways_list.setCurrentRow(index) + + @QtCore.Slot() + def _gateways_list_error(self): + """ + TRIGGERS: + Signaler.eip_get_gateways_list_error + + An error has occurred retrieving the gateway list + so we inform the user. + """ + self._flash_error( + self.tr("Error loading configuration file.")) + self.ui.gateways_list.setEnabled(False) + + def _backend_connect(self): + sig = self._leap_signaler + sig.eip_get_gateways_list.connect(self._update_gateways_list) + sig.eip_get_gateways_list_error.connect(self._gateways_list_error) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 3c9cd5d0..a9c301c4 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -20,13 +20,16 @@ Preferences window """ import logging -from functools import partial - from PySide import QtCore, QtGui from leap.bitmask.config.leapsettings import LeapSettings + from leap.bitmask.gui.ui_preferences import Ui_Preferences -from leap.bitmask.util.credentials import password_checks + +from leap.bitmask.gui.preferences_account_page import PreferencesAccountPage +from leap.bitmask.gui.preferences_vpn_page import PreferencesVpnPage +from leap.bitmask.gui.preferences_email_page import PreferencesEmailPage + from leap.bitmask.services import get_service_display_name, MX_SERVICE logger = logging.getLogger(__name__) @@ -38,8 +41,7 @@ class PreferencesWindow(QtGui.QDialog): """ preferences_saved = QtCore.Signal() - def __init__(self, parent, username, domain, backend, soledad_started, mx, - leap_signaler): + def __init__(self, parent, username, domain, backend, soledad_started, leap_signaler): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget @@ -51,397 +53,94 @@ class PreferencesWindow(QtGui.QDialog): :type backend: Backend :param soledad_started: whether soledad has started or not :type soledad_started: bool - :param mx: whether the current provider provides mx or not. - :type mx: bool + :param leap_signaler: signal server + :type leap_signaler: LeapSignaler """ QtGui.QDialog.__init__(self, parent) - self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") + self._parent = parent self._username = username self._domain = domain self._leap_signaler = leap_signaler self._backend = backend self._soledad_started = soledad_started - self._mx_provided = mx self._settings = LeapSettings() - self._backend_connect() # Load UI self.ui = Ui_Preferences() self.ui.setupUi(self) - self.ui.lblPasswordChangeStatus.setVisible(False) - self.ui.lblProvidersServicesStatus.setVisible(False) - - self._selected_services = set() - - # Connections - self.ui.pbChangePassword.clicked.connect(self._change_password) - self.ui.cbProvidersServices.currentIndexChanged[unicode].connect( - self._populate_services) - - if not self._settings.get_configured_providers(): - self.ui.gbEnabledServices.setEnabled(False) - else: - self._add_configured_providers() - if self._username is None: - self._not_logged_in() - else: - self.ui.gbPasswordChange.setEnabled(True) - if self._mx_provided: - self._provides_mx() + self.ui.close_button.clicked.connect(self.close) - self._select_provider_by_name(domain) + self._add_icons() + self._add_pages() - def _not_logged_in(self): + def _add_icons(self): """ - Actions to perform if the user is not logged in. - """ - msg = self.tr( - "In order to change your password you need to be logged in.") - self._set_password_change_status(msg) - self.ui.gbPasswordChange.setEnabled(False) + Adds all the icons for the different configuration categories. + Icons are QListWidgetItems added to the nav_widget on the side + of the preferences window. - def _provides_mx(self): - """ - Actions to perform if the provider provides MX. + A note on sizing of QListWidgetItems + icon_width = list_widget.width - (2 x nav_widget.spacing) - 2 + icon_height = 56 seems to look ok """ - pw_enabled = True - enabled_services = self._settings.get_enabled_services(self._domain) - mx_name = get_service_display_name(MX_SERVICE) + account_button = QtGui.QListWidgetItem(self.ui.nav_widget) + account_button.setIcon(QtGui.QIcon(":/images/black/32/user.png")) + account_button.setText(self.tr("Account")) + account_button.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) + account_button.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + account_button.setSizeHint(QtCore.QSize(98,56)) - if MX_SERVICE not in enabled_services: - msg = self.tr("You need to enable {0} in order to change " - "the password.".format(mx_name)) - self._set_password_change_status(msg, error=True) - pw_enabled = False - else: - # check if Soledad is bootstrapped - if not self._soledad_started: - msg = self.tr( - "You need to wait until {0} is ready in " - "order to change the password.".format(mx_name)) - self._set_password_change_status(msg) - pw_enabled = False + vpn_button = QtGui.QListWidgetItem(self.ui.nav_widget) + vpn_button.setIcon(QtGui.QIcon(":/images/black/32/earth.png")) + vpn_button.setText(self.tr("VPN")) + vpn_button.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) + vpn_button.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + vpn_button.setSizeHint(QtCore.QSize(98,56)) - self.ui.gbPasswordChange.setEnabled(pw_enabled) + email_button = QtGui.QListWidgetItem(self.ui.nav_widget) + email_button.setIcon(QtGui.QIcon(":/images/black/32/email.png")) + email_button.setText(self.tr("Email")) + email_button.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) + email_button.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + email_button.setSizeHint(QtCore.QSize(98,56)) - @QtCore.Slot() - def set_soledad_ready(self): - """ - TRIGGERS: - parent.soledad_ready + self.ui.nav_widget.currentItemChanged.connect(self._change_page) + self.ui.nav_widget.setCurrentRow(0) - It notifies when the soledad object as ready to use. + def _add_pages(self): """ - self.ui.lblPasswordChangeStatus.setVisible(False) - self.ui.gbPasswordChange.setEnabled(True) - - def _set_password_change_status(self, status, error=False, success=False): + Adds the pages for the different configuration categories. """ - Sets the status label for the password change. + self.ui.pages_widget.addWidget(PreferencesAccountPage(self)) + self.ui.pages_widget.addWidget(PreferencesVpnPage(self, self._domain, self._backend, self._leap_signaler)) + self.ui.pages_widget.addWidget(PreferencesEmailPage(self)) - :param status: status message to display, can be HTML - :type status: str - """ - if error: - status = "%s" % (status,) - elif success: - status = "%s" % (status,) - - if not self.ui.gbPasswordChange.isEnabled(): - status = "%s" % (status,) - - self.ui.lblPasswordChangeStatus.setVisible(True) - self.ui.lblPasswordChangeStatus.setText(status) - - def _set_changing_password(self, disable): - """ - Enables or disables the widgets in the password change group box. - - :param disable: True if the widgets should be disabled and - it displays the status label that shows that is - changing the password. - False if they should be enabled. - :type disable: bool - """ - if disable: - self._set_password_change_status(self.tr("Changing password...")) - - self.ui.leCurrentPassword.setEnabled(not disable) - self.ui.leNewPassword.setEnabled(not disable) - self.ui.leNewPassword2.setEnabled(not disable) - self.ui.pbChangePassword.setEnabled(not disable) + # + # Slots + # @QtCore.Slot() - def _change_password(self): + def close(self): """ TRIGGERS: - self.ui.pbChangePassword.clicked + self.ui.close_button.clicked - Changes the user's password if the inputboxes are correctly filled. + Close this dialog """ - username = self._username - current_password = self.ui.leCurrentPassword.text() - new_password = self.ui.leNewPassword.text() - new_password2 = self.ui.leNewPassword2.text() - - ok, msg = password_checks(username, new_password, new_password2) - - if not ok: - self._set_changing_password(False) - self._set_password_change_status(msg, error=True) - self.ui.leNewPassword.setFocus() - return - - self._set_changing_password(True) - self._backend.user_change_password(current_password=current_password, - new_password=new_password) + self._parent.preferences = None + self.hide() @QtCore.Slot() - def _srp_change_password_ok(self): - """ - TRIGGERS: - self._backend.signaler.srp_password_change_ok - - Callback used to display a successfully changed password. - """ - new_password = self.ui.leNewPassword.text() - logger.debug("SRP password changed successfully.") - - if self._mx_provided: - self._backend.soledad_change_password(new_password=new_password) - else: - self._change_password_success() - - @QtCore.Slot(unicode) - def _srp_change_password_problem(self, msg): - """ - TRIGGERS: - self._backend.signaler.srp_password_change_error - self._backend.signaler.srp_password_change_badpw - - Callback used to display an error on changing password. - - :param msg: the message to show to the user. - :type msg: unicode - """ - logger.error("Error changing password") - self._set_password_change_status(msg, error=True) - self._set_changing_password(False) - - @QtCore.Slot() - def _soledad_change_password_ok(self): - """ - TRIGGERS: - Signaler.soledad_password_change_ok - - Soledad password change went OK. - """ - logger.debug("Soledad password changed successfully.") - self._change_password_success() - - def _change_password_success(self): - """ - Callback used to display a successfully changed password. - """ - logger.debug("Soledad password changed successfully.") - - self._set_password_change_status( - self.tr("Password changed successfully."), success=True) - self._clear_password_inputs() - self._set_changing_password(False) - - @QtCore.Slot(unicode) - def _soledad_change_password_problem(self, msg): - """ - TRIGGERS: - Signaler.soledad_password_change_error - - Callback used to display an error on changing password. - - :param msg: the message to show to the user. - :type msg: unicode - """ - logger.error("Error changing soledad password") - self._set_password_change_status(msg, error=True) - self._set_changing_password(False) - - def _clear_password_inputs(self): - """ - Clear the contents of the inputs. - """ - self.ui.leCurrentPassword.setText("") - self.ui.leNewPassword.setText("") - self.ui.leNewPassword2.setText("") - - def _set_providers_services_status(self, status, success=False): - """ - Sets the status label for the password change. - - :param status: status message to display, can be HTML - :type status: str - :param success: is set to True if we should display the - message as green - :type success: bool - """ - if success: - status = "%s" % (status,) - - self.ui.lblProvidersServicesStatus.setVisible(True) - self.ui.lblProvidersServicesStatus.setText(status) - - def _add_configured_providers(self): - """ - Add the client's configured providers to the providers combo boxes. - """ - self.ui.cbProvidersServices.clear() - for provider in self._settings.get_configured_providers(): - self.ui.cbProvidersServices.addItem(provider) - - def _select_provider_by_name(self, name): - """ - Given a provider name/domain, selects it in the combobox. - - :param name: name or domain for the provider - :type name: str - """ - provider_index = self.ui.cbProvidersServices.findText(name) - self.ui.cbProvidersServices.setCurrentIndex(provider_index) - - @QtCore.Slot(str, int) - def _service_selection_changed(self, service, state): + def _change_page(self, current, previous): """ TRIGGERS: - service_checkbox.stateChanged + self.ui.nav_widget.currentItemChanged - Adds the service to the state if the state is checked, removes - it otherwise - - :param service: service to handle - :type service: str - :param state: state of the checkbox - :type state: int - """ - if state == QtCore.Qt.Checked: - self._selected_services = \ - self._selected_services.union(set([service])) - else: - self._selected_services = \ - self._selected_services.difference(set([service])) - - # We hide the maybe-visible status label after a change - self.ui.lblProvidersServicesStatus.setVisible(False) - - @QtCore.Slot(str) - def _populate_services(self, domain): + Changes what page is displayed. """ - TRIGGERS: - self.ui.cbProvidersServices.currentIndexChanged[unicode] - - Fill the services list with the selected provider's services. - - :param domain: the domain of the provider to load services from. - :type domain: str - """ - # We hide the maybe-visible status label after a change - self.ui.lblProvidersServicesStatus.setVisible(False) - - if not domain: - return - - # set the proper connection for the 'save' button - try: - self.ui.pbSaveServices.clicked.disconnect() - except RuntimeError: - pass # Signal was not connected - - save_services = partial(self._save_enabled_services, domain) - self.ui.pbSaveServices.clicked.connect(save_services) - - self._backend.provider_get_supported_services(domain=domain) - - @QtCore.Slot(str) - def _load_services(self, services): - """ - TRIGGERS: - self.ui.cbProvidersServices.currentIndexChanged[unicode] - - Loads the services that the provider provides into the UI for - the user to enable or disable. - - :param domain: the domain of the provider to load services from. - :type domain: str - """ - domain = self.ui.cbProvidersServices.currentText() - services_conf = self._settings.get_enabled_services(domain) - - # discard changes if other provider is selected - self._selected_services = set() - - # from: http://stackoverflow.com/a/13103617/687989 - # remove existing checkboxes - layout = self.ui.vlServices - for i in reversed(range(layout.count())): - layout.itemAt(i).widget().setParent(None) - - # add one checkbox per service and set the current configured value - for service in services: - try: - checkbox = QtGui.QCheckBox(self) - service_label = get_service_display_name(service) - checkbox.setText(service_label) - - self.ui.vlServices.addWidget(checkbox) - checkbox.stateChanged.connect( - partial(self._service_selection_changed, service)) - - checkbox.setChecked(service in services_conf) - except ValueError: - logger.error("Something went wrong while trying to " - "load service %s" % (service,)) - - @QtCore.Slot(str) - def _save_enabled_services(self, provider): - """ - TRIGGERS: - self.ui.pbSaveServices.clicked - - Saves the new enabled services settings to the configuration file. - - :param provider: the provider config that we need to save. - :type provider: str - """ - services = list(self._selected_services) - self._settings.set_enabled_services(provider, services) - - msg = self.tr( - "Services settings for provider '{0}' saved.".format(provider)) - logger.debug(msg) - self._set_providers_services_status(msg, success=True) - self.preferences_saved.emit() - - def _backend_connect(self): - """ - Helper to connect to backend signals - """ - sig = self._leap_signaler - - sig.prov_get_supported_services.connect(self._load_services) - - sig.srp_password_change_ok.connect(self._srp_change_password_ok) - - pwd_change_error = lambda: self._srp_change_password_problem( - self.tr("There was a problem changing the password.")) - sig.srp_password_change_error.connect(pwd_change_error) - - pwd_change_badpw = lambda: self._srp_change_password_problem( - self.tr("You did not enter a correct current password.")) - sig.srp_password_change_badpw.connect(pwd_change_badpw) - - sig.soledad_password_change_ok.connect( - self._soledad_change_password_ok) - - sig.soledad_password_change_error.connect( - self._soledad_change_password_problem) + if not current: + current = previous + self.ui.pages_widget.setCurrentIndex(self.ui.nav_widget.row(current)) diff --git a/src/leap/bitmask/gui/ui/eippreferences.ui b/src/leap/bitmask/gui/ui/eippreferences.ui deleted file mode 100644 index 1a5fcd24..00000000 --- a/src/leap/bitmask/gui/ui/eippreferences.ui +++ /dev/null @@ -1,102 +0,0 @@ - - - EIPPreferences - - - - 0 - 0 - 435 - 144 - - - - Encrypted Internet Preferences - - - - :/images/mask-icon.png:/images/mask-icon.png - - - - - - true - - - Select gateway for provider - - - false - - - - - - Select &provider: - - - cbProvidersGateway - - - - - - - - <Select provider> - - - - - - - - &Save this provider settings - - - - - - - < Providers Gateway Status > - - - Qt::AlignCenter - - - - - - - Select &gateway: - - - cbGateways - - - - - - - - Automatic - - - - - - - - - - - cbProvidersGateway - cbGateways - pbSaveGateway - - - - - - diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index f7570ee6..2e8aea8c 100644 --- a/src/leap/bitmask/gui/ui/mainwindow.ui +++ b/src/leap/bitmask/gui/ui/mainwindow.ui @@ -317,7 +317,6 @@ - @@ -338,12 +337,7 @@ true - Account Preferences... - - - - - Internet Preferences... + Preferences... diff --git a/src/leap/bitmask/gui/ui/preferences.ui b/src/leap/bitmask/gui/ui/preferences.ui index cd4d3a77..5e30ea57 100644 --- a/src/leap/bitmask/gui/ui/preferences.ui +++ b/src/leap/bitmask/gui/ui/preferences.ui @@ -6,8 +6,8 @@ 0 0 - 503 - 401 + 520 + 439 @@ -17,159 +17,97 @@ :/images/mask-icon.png:/images/mask-icon.png - - - - - Qt::Vertical + + + 6 + + + + + + 75 + true + - - - 20 - 40 - + + user@example.org - + - - - - false - - - Password Change + + + + Qt::Horizontal - - - QFormLayout::ExpandingFieldsGrow - - - - - &Current password: - - - leCurrentPassword - - - - - - - QLineEdit::Password - - - - - - - &New password: - - - leNewPassword - - - - - - - QLineEdit::Password - - - - - - - &Re-enter new password: - - - leNewPassword2 - - - - - - - QLineEdit::Password - - - - - - - Change - - - - - - - <Password change status> - - - Qt::AlignCenter - - - - - - - - Enabled services + + + + 12 - - - - - Save this provider settings - - - - - - - Services - - - false - - - - - - - - - - - - - <Select provider> - - - - - - - - Select provider: - - - - - - - < Providers Services Status > - - - Qt::AlignCenter - - - - - + + + + + 0 + 0 + + + + + 120 + 16777215 + + + + + 32 + 32 + + + + QListView::Static + + + 10 + + + QListView::IconMode + + + true + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Close + + + + diff --git a/src/leap/bitmask/gui/ui/preferences_account_page.ui b/src/leap/bitmask/gui/ui/preferences_account_page.ui new file mode 100644 index 00000000..4e58b2ab --- /dev/null +++ b/src/leap/bitmask/gui/ui/preferences_account_page.ui @@ -0,0 +1,95 @@ + + + PreferencesAccountPage + + + + 0 + 0 + 462 + 371 + + + + Form + + + + + + + 0 + 0 + + + + Change Password + + + + + + + <change password status> + + + + + + + Qt::Vertical + + + QSizePolicy::Preferred + + + + 20 + 15 + + + + + + + + Services + + + false + + + + + + + + + < Providers Services Status > + + + Qt::AlignCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/leap/bitmask/gui/ui/preferences_email_page.ui b/src/leap/bitmask/gui/ui/preferences_email_page.ui new file mode 100644 index 00000000..41b3c28d --- /dev/null +++ b/src/leap/bitmask/gui/ui/preferences_email_page.ui @@ -0,0 +1,32 @@ + + + PreferencesEmailPage + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + 250 + 210 + 98 + 27 + + + + PushButton + + + + + + diff --git a/src/leap/bitmask/gui/ui/preferences_vpn_page.ui b/src/leap/bitmask/gui/ui/preferences_vpn_page.ui new file mode 100644 index 00000000..85a0dc60 --- /dev/null +++ b/src/leap/bitmask/gui/ui/preferences_vpn_page.ui @@ -0,0 +1,89 @@ + + + PreferencesVpnPage + + + + 0 + 0 + 400 + 362 + + + + Form + + + + + + <flash_label> + + + Qt::AlignCenter + + + + + + + Default VPN Gateway: + + + gateways_list + + + + + + + + New Item + + + + :/images/countries/us.png + + + + + + New Item + + + + :/images/countries/br.png + + + + + + + + + + 0 + 0 + + + + You must reconnect for changes to take effect. + + + false + + + true + + + gateways_list + + + + + + + + + + diff --git a/src/leap/bitmask/services/eip/eipconfig.py b/src/leap/bitmask/services/eip/eipconfig.py index 37c0c8ae..5b51d12e 100644 --- a/src/leap/bitmask/services/eip/eipconfig.py +++ b/src/leap/bitmask/services/eip/eipconfig.py @@ -113,39 +113,38 @@ class VPNGatewaySelector(object): """ Return the existing gateways, sorted by timezone proximity. - :rtype: list of tuples (location, ip) - (str, IPv4Address or IPv6Address object) + :rtype: list of tuples (label, ip, country_code) + (str, IPv4Address or IPv6Address object, str) """ gateways_timezones = [] locations = self._eipconfig.get_locations() gateways = self._eipconfig.get_gateways() for idx, gateway in enumerate(gateways): - gateway_location = gateway.get('location') - gateway_distance = 99 # if hasn't location -> should go last - - if gateway_location is not None: - timezone = locations[gateway['location']]['timezone'] - gateway_name = locations[gateway['location']].get('name', None) - if gateway_name is not None: - gateway_location = gateway_name - - gw_offset = int(timezone) - if gw_offset in self.equivalent_timezones: - gw_offset = self.equivalent_timezones[gw_offset] - - gateway_distance = self._get_timezone_distance(gw_offset) + distance = 99 # if hasn't location -> should go last + location = locations.get(gateway.get('location')) + label = gateway.get('location', 'Unknown') + country = 'XX' + if location is not None: + country = location.get('country_code', 'XX') + label = location.get('name', label) + timezone = location.get('timezone') + if timezone is not None: + offset = int(timezone) + if offset in self.equivalent_timezones: + offset = self.equivalent_timezones[offset] + distance = self._get_timezone_distance(offset) ip = self._eipconfig.get_gateway_ip(idx) - gateways_timezones.append((ip, gateway_distance, gateway_location)) + gateways_timezones.append((ip, distance, label, country)) gateways_timezones = sorted(gateways_timezones, key=lambda gw: gw[1]) - gateways = [] - for ip, distance, location in gateways_timezones: - gateways.append((location, ip)) + result = [] + for ip, distance, label, country in gateways_timezones: + result.append((label, ip, country)) - return gateways + return result def get_gateways(self): """ @@ -153,7 +152,7 @@ class VPNGatewaySelector(object): :rtype: list of IPv4Address or IPv6Address object. """ - gateways = [ip for location, ip in self.get_gateways_list()][:4] + gateways = [gateway[1] for gateway in self.get_gateways_list()][:4] return gateways def get_gateways_country_code(self): -- cgit v1.2.3 From 4e7c4b48b4255ceac06900fa9e65824c52e15ba7 Mon Sep 17 00:00:00 2001 From: elijah Date: Thu, 4 Sep 2014 17:09:29 -0700 Subject: single pref win: enabled/disable services via preferences window, account page. --- src/leap/bitmask/gui/account.py | 43 +++++++++ src/leap/bitmask/gui/app.py | 68 ++++++++++++++ src/leap/bitmask/gui/mainwindow.py | 87 +++++++---------- src/leap/bitmask/gui/preferences_account_page.py | 103 ++++++--------------- src/leap/bitmask/gui/preferences_email_page.py | 7 +- src/leap/bitmask/gui/preferences_vpn_page.py | 54 ++++++----- src/leap/bitmask/gui/preferenceswindow.py | 90 ++++++++++-------- .../bitmask/gui/ui/preferences_account_page.ui | 67 ++++++++------ src/leap/bitmask/services/eip/conductor.py | 6 ++ 9 files changed, 304 insertions(+), 221 deletions(-) create mode 100644 src/leap/bitmask/gui/account.py create mode 100644 src/leap/bitmask/gui/app.py diff --git a/src/leap/bitmask/gui/account.py b/src/leap/bitmask/gui/account.py new file mode 100644 index 00000000..b08053a9 --- /dev/null +++ b/src/leap/bitmask/gui/account.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +A frontend GUI object to hold the current username and domain. +""" + +from leap.bitmask.util import make_address +from leap.bitmask.config.leapsettings import LeapSettings + +class Account(): + + def __init__(self, username, domain): + self._settings = LeapSettings() + self.username = username + self.domain = domain + + if self.username is not None: + self.address = make_address(self.username, self.domain) + else: + self.address = self.domain + + def services(self): + """ + returns a list of service name strings + + TODO: this should depend not just on the domain + """ + return self._settings.get_enabled_services(self.domain) + + diff --git a/src/leap/bitmask/gui/app.py b/src/leap/bitmask/gui/app.py new file mode 100644 index 00000000..7fcf69af --- /dev/null +++ b/src/leap/bitmask/gui/app.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +A single App instances holds the signals that are shared among different +frontend UI components. The App also keeps a reference to the backend object +and the signaler get signals from the backend. +""" +import logging + +from functools import partial +from PySide import QtCore, QtGui + +from leap.bitmask.config.leapsettings import LeapSettings +from leap.bitmask.backend.backend_proxy import BackendProxy +from leap.bitmask.backend.leapsignaler import LeapSignaler + +logger = logging.getLogger(__name__) + +class App(QtGui.QWidget): + + #preferences_saved = QtCore.Signal() + + # the user has changed which services are enabled for a particular account + # args: account (Account), active services (list of str) + service_selection_changed = QtCore.Signal(object, list) + + def __init__(self): + QtGui.QWidget.__init__(self) + + self.settings = LeapSettings() + self.backend = BackendProxy() + self.signaler = LeapSignaler() + self.signaler.start() + + # periodically check if the backend is alive + self._backend_checker = QtCore.QTimer(self) + self._backend_checker.timeout.connect(self._check_backend_status) + self._backend_checker.start(2000) + + + @QtCore.Slot() + def _check_backend_status(self): + """ + TRIGGERS: + self._backend_checker.timeout + + Check that the backend is running. Otherwise show an error to the user. + """ + if not self.backend.online: + logger.critical("Backend is not online.") + QtGui.QMessageBox.critical( + self, self.tr("Application error"), + self.tr("There is a problem contacting the backend, please " + "restart Bitmask.")) + self._backend_checker.stop() diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 243fe117..b106364d 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -45,6 +45,8 @@ from leap.bitmask.gui.preferenceswindow import PreferencesWindow from leap.bitmask.gui.systray import SysTray from leap.bitmask.gui.wizard import Wizard from leap.bitmask.gui.providers import Providers +from leap.bitmask.gui.account import Account +from leap.bitmask.gui.app import App from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX from leap.bitmask.platform_init import locks @@ -126,19 +128,10 @@ class MainWindow(QtGui.QMainWindow): self.ui.setupUi(self) self.menuBar().setNativeMenuBar(not IS_LINUX) - self._backend = BackendProxy() - - # periodically check if the backend is alive - self._backend_checker = QtCore.QTimer(self) - self._backend_checker.timeout.connect(self._check_backend_status) - self._backend_checker.start(2000) - - self._leap_signaler = LeapSignaler() - self._leap_signaler.start() - - self._settings = LeapSettings() - # gateway = self._settings.get_selected_gateway(provider) - # self._backend.settings_set_selected_gateway(provider, gateway) + self.app = App() + self._backend = self.app.backend + self._leap_signaler = self.app.signaler + self._settings = self.app.settings # Login Widget self._login_widget = LoginWidget(self._settings, self) @@ -154,6 +147,7 @@ class MainWindow(QtGui.QMainWindow): # Qt Signal Connections ##################################### # TODO separate logic from ui signals. + self.app.service_selection_changed.connect(self._update_eip_enabled_status) self._login_widget.login.connect(self._login) self._login_widget.cancel_login.connect(self._cancel_login) self._login_widget.logout.connect(self._logout) @@ -245,9 +239,6 @@ class MainWindow(QtGui.QMainWindow): self._action_visible = QtGui.QAction(self.tr("Show Main Window"), self) self._action_visible.triggered.connect(self._ensure_visible) - # disable buttons for now, may come back later. - # self.ui.btnPreferences.clicked.connect(self._show_preferences) - self._enabled_services = [] self._ui_mx_visible = True self._ui_eip_visible = True @@ -342,23 +333,6 @@ class MainWindow(QtGui.QMainWindow): logger.error("Bad call to the backend:") logger.error(data) - @QtCore.Slot() - def _check_backend_status(self): - """ - TRIGGERS: - self._backend_checker.timeout - - Check that the backend is running. Otherwise show an error to the user. - """ - online = self._backend.online - if not online: - logger.critical("Backend is not online.") - QtGui.QMessageBox.critical( - self, self.tr("Application error"), - self.tr("There is a problem contacting the backend, please " - "restart Bitmask.")) - self._backend_checker.stop() - def _backend_connect(self, only_tracked=False): """ Connect to backend signals. @@ -598,21 +572,18 @@ class MainWindow(QtGui.QMainWindow): Display the preferences window. """ - user = self._logged_user - domain = self._providers.get_selected_provider() + account = Account(self._logged_user, + self._providers.get_selected_provider()) if self.preferences is not None: self.preferences.close() - self.preferences = PreferencesWindow(self, user, domain, - self._backend, - self._soledad_started, - self._leap_signaler) + self.preferences = PreferencesWindow(self, account, self.app) self.preferences.show() - @QtCore.Slot() - def _update_eip_enabled_status(self): + @QtCore.Slot(object, list) + def _update_eip_enabled_status(self, account=None, services=None): """ TRIGGER: - PreferencesWindow.preferences_saved + App.service_selection_changed Enable or disable the EIP start/stop actions and stop EIP if the user disabled that service. @@ -620,24 +591,35 @@ class MainWindow(QtGui.QMainWindow): :returns: if the eip actions were enabled or disabled :rtype: bool """ - settings = self._settings - default_provider = settings.get_defaultprovider() + if account is not None: + domain = account.domain + else: + # I am not sure why, but asking for the currently selected + # provider here give you the WRONG provider + domain = self.app.settings.get_defaultprovider() - if default_provider is None: + if domain is None: logger.warning("Trying to update eip enabled status but there's no" " default provider. Disabling EIP for the time" " being...") self._backend_cannot_start_eip() return - self._trying_to_start_eip = settings.get_autostart_eip() - self._backend.eip_can_start(domain=default_provider) + if not EIP_SERVICE in self.app.settings.get_enabled_services(domain): + self._eip_conductor.terminate() + def hide(): + self.app.backend.eip_can_start(domain=domain) + QtDelayedCall(100, hide) + # ^^ VERY VERY Hacky, but with the simple state machine, + # there is no way to signal 'disconnect and then disable' + + else: + self._trying_to_start_eip = self.app.settings.get_autostart_eip() + if not self._trying_to_start_eip: + self._backend.eip_setup(provider=domain, skip_network=True) + # check if EIP can start (will trigger widget update) + self.app.backend.eip_can_start(domain=domain) - # If we don't want to start eip, we leave everything - # initialized to quickly start it - if not self._trying_to_start_eip: - self._backend.eip_setup(provider=default_provider, - skip_network=True) def _backend_can_start_eip(self): """ @@ -657,7 +639,6 @@ class MainWindow(QtGui.QMainWindow): enabled_services = [] if default_provider is not None: enabled_services = settings.get_enabled_services(default_provider) - eip_enabled = False if EIP_SERVICE in enabled_services: eip_enabled = True diff --git a/src/leap/bitmask/gui/preferences_account_page.py b/src/leap/bitmask/gui/preferences_account_page.py index 9cc94482..bb90aab5 100644 --- a/src/leap/bitmask/gui/preferences_account_page.py +++ b/src/leap/bitmask/gui/preferences_account_page.py @@ -22,35 +22,29 @@ from functools import partial from PySide import QtCore, QtGui from ui_preferences_account_page import Ui_PreferencesAccountPage +from leap.bitmask.services import get_service_display_name +from leap.bitmask.config.leapsettings import LeapSettings logger = logging.getLogger(__name__) class PreferencesAccountPage(QtGui.QWidget): - """ - """ - - def __init__(self, parent): + def __init__(self, parent, account, app): """ """ QtGui.QWidget.__init__(self, parent) self.ui = Ui_PreferencesAccountPage() self.ui.setupUi(self) - self.show() - self._selected_services = set() - #self._leap_signaler.prov_get_supported_services.connect(self._load_services) + self.account = account + self.app = app - @QtCore.Slot() - def set_soledad_ready(self): - """ - TRIGGERS: - parent.soledad_ready + self._selected_services = set() + self.ui.change_password_label.setVisible(False) + self.ui.provider_services_label.setVisible(False) - It notifies when the soledad object as ready to use. - """ - #self.ui.lblPasswordChangeStatus.setVisible(False) - #self.ui.gbPasswordChange.setEnabled(True) + app.signaler.prov_get_supported_services.connect(self._load_services) + app.backend.provider_get_supported_services(domain=account.domain) @QtCore.Slot(str, int) def _service_selection_changed(self, service, state): @@ -72,94 +66,49 @@ class PreferencesAccountPage(QtGui.QWidget): else: self._selected_services = \ self._selected_services.difference(set([service])) + services = list(self._selected_services) # We hide the maybe-visible status label after a change - self.ui.lblProvidersServicesStatus.setVisible(False) + self.ui.provider_services_label.setVisible(False) - @QtCore.Slot(str) - def _populate_services(self, domain): - """ - TRIGGERS: - self.ui.cbProvidersServices.currentIndexChanged[unicode] - - Fill the services list with the selected provider's services. - - :param domain: the domain of the provider to load services from. - :type domain: str - """ - # We hide the maybe-visible status label after a change - self.ui.lblProvidersServicesStatus.setVisible(False) + # write to config + self.app.settings.set_enabled_services(self.account.domain, services) - if not domain: - return + # emit signal alerting change + self.app.service_selection_changed.emit(self.account, services) - # set the proper connection for the 'save' button - try: - self.ui.pbSaveServices.clicked.disconnect() - except RuntimeError: - pass # Signal was not connected - - save_services = partial(self._save_enabled_services, domain) - self.ui.pbSaveServices.clicked.connect(save_services) - - self._backend.provider_get_supported_services(domain=domain) @QtCore.Slot(str) def _load_services(self, services): """ TRIGGERS: - self.ui.cbProvidersServices.currentIndexChanged[unicode] + prov_get_supported_services Loads the services that the provider provides into the UI for the user to enable or disable. - :param domain: the domain of the provider to load services from. - :type domain: str + :param services: list of supported service names + :type services: list of str """ - domain = self.ui.cbProvidersServices.currentText() - services_conf = self._settings.get_enabled_services(domain) + services_conf = self.account.services() - # discard changes if other provider is selected self._selected_services = set() - # from: http://stackoverflow.com/a/13103617/687989 # remove existing checkboxes - layout = self.ui.vlServices + layout = self.ui.provider_services_layout for i in reversed(range(layout.count())): layout.itemAt(i).widget().setParent(None) - # add one checkbox per service and set the current configured value + # add one checkbox per service and set the current value + # from what is saved in settings. for service in services: try: - checkbox = QtGui.QCheckBox(self) - service_label = get_service_display_name(service) - checkbox.setText(service_label) - - self.ui.vlServices.addWidget(checkbox) + checkbox = QtGui.QCheckBox( + get_service_display_name(service), self) + self.ui.provider_services_layout.addWidget(checkbox) checkbox.stateChanged.connect( partial(self._service_selection_changed, service)) - checkbox.setChecked(service in services_conf) except ValueError: logger.error("Something went wrong while trying to " "load service %s" % (service,)) - - @QtCore.Slot(str) - def _save_enabled_services(self, provider): - """ - TRIGGERS: - self.ui.pbSaveServices.clicked - - Saves the new enabled services settings to the configuration file. - - :param provider: the provider config that we need to save. - :type provider: str - """ - services = list(self._selected_services) - self._settings.set_enabled_services(provider, services) - - msg = self.tr( - "Services settings for provider '{0}' saved.".format(provider)) - logger.debug(msg) - self._set_providers_services_status(msg, success=True) - self.preferences_saved.emit() diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index 08ff5463..da902802 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -28,11 +28,14 @@ class PreferencesEmailPage(QtGui.QWidget): """ - def __init__(self, parent): + def __init__(self, parent, account, app): """ """ QtGui.QWidget.__init__(self, parent) self.ui = Ui_PreferencesEmailPage() self.ui.setupUi(self) - self.show() + + self.parent = parent + self.account = account + self.app = app diff --git a/src/leap/bitmask/gui/preferences_vpn_page.py b/src/leap/bitmask/gui/preferences_vpn_page.py index ba1366e4..a8f074d2 100644 --- a/src/leap/bitmask/gui/preferences_vpn_page.py +++ b/src/leap/bitmask/gui/preferences_vpn_page.py @@ -28,23 +28,22 @@ class PreferencesVpnPage(QtGui.QWidget): Page in the preferences window that shows VPN settings """ - def __init__(self, parent, domain, backend, leap_signaler): + def __init__(self, parent, account, app): """ :param parent: parent object of the EIPPreferencesWindow. :type parent: QWidget - :param domain: the selected by default domain. - :type domain: unicode + :param account: the currently active account + :type account: Account - :param backend: Backend being used - :type backend: Backend + :param app: shared App instance + :type app: App """ QtGui.QWidget.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") - self._settings = LeapSettings() - self._leap_signaler = leap_signaler - self._backend = backend + self.account = account + self.app = app # Load UI self.ui = Ui_PreferencesVpnPage() @@ -53,10 +52,14 @@ class PreferencesVpnPage(QtGui.QWidget): # Connections self.ui.gateways_list.clicked.connect(self._save_selected_gateway) + sig = self.app.signaler + sig.eip_get_gateways_list.connect(self._update_gateways_list) + sig.eip_get_gateways_list_error.connect(self._gateways_list_error) + sig.eip_uninitialized_provider.connect( + self._gateways_list_uninitialized) - self._domain = domain - self._backend_connect() - self._backend.eip_get_gateways_list(domain=domain) + # Trigger update + self.app.backend.eip_get_gateways_list(domain=self.account.domain) def _flash_error(self, message): """ @@ -94,12 +97,13 @@ class PreferencesVpnPage(QtGui.QWidget): item = self.ui.gateways_list.currentItem() if item.text() == self.AUTOMATIC_GATEWAY_LABEL: - gateway = self._settings.GATEWAY_AUTOMATIC + gateway = self.app.settings.GATEWAY_AUTOMATIC else: gateway = item.data(QtCore.Qt.UserRole) - self._settings.set_selected_gateway(self._domain, gateway) - self._backend.settings_set_selected_gateway(provider=self._domain, - gateway=gateway) + self.app.settings.set_selected_gateway(self.account.domain, gateway) + self.app.backend.settings_set_selected_gateway( + provider=self.account.domain, + gateway=gateway) @QtCore.Slot(list) def _update_gateways_list(self, gateways): @@ -116,8 +120,8 @@ class PreferencesVpnPage(QtGui.QWidget): self.ui.gateways_list.clear() self.ui.gateways_list.addItem(self.AUTOMATIC_GATEWAY_LABEL) - selected_gateway = self._settings.get_selected_gateway( - self._domain) + selected_gateway = self.app.settings.get_selected_gateway( + self.account.domain) index = 0 for idx, (gw_name, gw_ip, gw_country) in enumerate(gateways): @@ -144,7 +148,15 @@ class PreferencesVpnPage(QtGui.QWidget): self.tr("Error loading configuration file.")) self.ui.gateways_list.setEnabled(False) - def _backend_connect(self): - sig = self._leap_signaler - sig.eip_get_gateways_list.connect(self._update_gateways_list) - sig.eip_get_gateways_list_error.connect(self._gateways_list_error) + @QtCore.Slot() + def _gateways_list_uninitialized(self): + """ + TRIGGERS: + Signaler.eip_uninitialized_provider + + The requested provider in not initialized yet, so we give the user an + error msg. + """ + self._flash_error( + self.tr("This is an uninitialized provider, please log in first.")) + self.ui.gateways_list.setEnabled(False) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index a9c301c4..35a875fa 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -22,16 +22,13 @@ import logging from PySide import QtCore, QtGui -from leap.bitmask.config.leapsettings import LeapSettings +from leap.bitmask.services import EIP_SERVICE, MX_SERVICE from leap.bitmask.gui.ui_preferences import Ui_Preferences - from leap.bitmask.gui.preferences_account_page import PreferencesAccountPage from leap.bitmask.gui.preferences_vpn_page import PreferencesVpnPage from leap.bitmask.gui.preferences_email_page import PreferencesEmailPage -from leap.bitmask.services import get_service_display_name, MX_SERVICE - logger = logging.getLogger(__name__) @@ -39,9 +36,8 @@ class PreferencesWindow(QtGui.QDialog): """ Window that displays the preferences. """ - preferences_saved = QtCore.Signal() - def __init__(self, parent, username, domain, backend, soledad_started, leap_signaler): + def __init__(self, parent, account, app): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget @@ -51,30 +47,26 @@ class PreferencesWindow(QtGui.QDialog): :type domain: unicode :param backend: Backend being used :type backend: Backend - :param soledad_started: whether soledad has started or not - :type soledad_started: bool :param leap_signaler: signal server :type leap_signaler: LeapSignaler """ QtGui.QDialog.__init__(self, parent) self._parent = parent - self._username = username - self._domain = domain - self._leap_signaler = leap_signaler - self._backend = backend - self._soledad_started = soledad_started - - self._settings = LeapSettings() + self.account = account + self.app = app - # Load UI self.ui = Ui_Preferences() self.ui.setupUi(self) self.ui.close_button.clicked.connect(self.close) + self.ui.account_label.setText(account.address) + + self.app.service_selection_changed.connect(self._update_icons) self._add_icons() self._add_pages() + self._update_icons(self.account, self.account.services()) def _add_icons(self): """ @@ -86,26 +78,31 @@ class PreferencesWindow(QtGui.QDialog): icon_width = list_widget.width - (2 x nav_widget.spacing) - 2 icon_height = 56 seems to look ok """ - account_button = QtGui.QListWidgetItem(self.ui.nav_widget) - account_button.setIcon(QtGui.QIcon(":/images/black/32/user.png")) - account_button.setText(self.tr("Account")) - account_button.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) - account_button.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - account_button.setSizeHint(QtCore.QSize(98,56)) - - vpn_button = QtGui.QListWidgetItem(self.ui.nav_widget) - vpn_button.setIcon(QtGui.QIcon(":/images/black/32/earth.png")) - vpn_button.setText(self.tr("VPN")) - vpn_button.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) - vpn_button.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - vpn_button.setSizeHint(QtCore.QSize(98,56)) - - email_button = QtGui.QListWidgetItem(self.ui.nav_widget) - email_button.setIcon(QtGui.QIcon(":/images/black/32/email.png")) - email_button.setText(self.tr("Email")) - email_button.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) - email_button.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - email_button.setSizeHint(QtCore.QSize(98,56)) + account_item = QtGui.QListWidgetItem(self.ui.nav_widget) + account_item.setIcon(QtGui.QIcon(":/images/black/32/user.png")) + account_item.setText(self.tr("Account")) + account_item.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) + account_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + account_item.setSizeHint(QtCore.QSize(98,56)) + self._account_item = account_item + + vpn_item = QtGui.QListWidgetItem(self.ui.nav_widget) + vpn_item.setHidden(True) + vpn_item.setIcon(QtGui.QIcon(":/images/black/32/earth.png")) + vpn_item.setText(self.tr("VPN")) + vpn_item.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) + vpn_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + vpn_item.setSizeHint(QtCore.QSize(98,56)) + self._vpn_item = vpn_item + + email_item = QtGui.QListWidgetItem(self.ui.nav_widget) + email_item.setHidden(True) + email_item.setIcon(QtGui.QIcon(":/images/black/32/email.png")) + email_item.setText(self.tr("Email")) + email_item.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) + email_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + email_item.setSizeHint(QtCore.QSize(98,56)) + self._email_item = email_item self.ui.nav_widget.currentItemChanged.connect(self._change_page) self.ui.nav_widget.setCurrentRow(0) @@ -114,9 +111,9 @@ class PreferencesWindow(QtGui.QDialog): """ Adds the pages for the different configuration categories. """ - self.ui.pages_widget.addWidget(PreferencesAccountPage(self)) - self.ui.pages_widget.addWidget(PreferencesVpnPage(self, self._domain, self._backend, self._leap_signaler)) - self.ui.pages_widget.addWidget(PreferencesEmailPage(self)) + self.ui.pages_widget.addWidget(PreferencesAccountPage(self, self.account, self.app)) + self.ui.pages_widget.addWidget(PreferencesVpnPage(self, self.account, self.app)) + self.ui.pages_widget.addWidget(PreferencesEmailPage(self, self.account, self.app)) # # Slots @@ -144,3 +141,18 @@ class PreferencesWindow(QtGui.QDialog): if not current: current = previous self.ui.pages_widget.setCurrentIndex(self.ui.nav_widget.row(current)) + + @QtCore.Slot(object, list) + def _update_icons(self, account, services): + """ + TRIGGERS: + self.app.service_selection_changed + + Change which icons are visible. + """ + if account != self.account: + return + + self._vpn_item.setHidden(not EIP_SERVICE in services) + #self._email_item.setHidden(not MX_SERVICE in services) + # ^^ disable email for now, there is nothing there yet. diff --git a/src/leap/bitmask/gui/ui/preferences_account_page.ui b/src/leap/bitmask/gui/ui/preferences_account_page.ui index 4e58b2ab..9b6d885b 100644 --- a/src/leap/bitmask/gui/ui/preferences_account_page.ui +++ b/src/leap/bitmask/gui/ui/preferences_account_page.ui @@ -15,23 +15,28 @@ - - - - 0 - 0 - - - - Change Password + + + Services - - - - - - <change password status> + + false + + + + + + + + <provider_services_label> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + @@ -51,24 +56,28 @@ - + - Services + Password - - false - - - - - - - + + + + + + 0 + 0 + + - < Providers Services Status > + Change Password - - Qt::AlignCenter + + + + + + <change_password_label> diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py index 0ee56628..01dd7449 100644 --- a/src/leap/bitmask/services/eip/conductor.py +++ b/src/leap/bitmask/services/eip/conductor.py @@ -124,6 +124,12 @@ class EIPConductor(object): """ self._backend.tear_fw_down() + def terminate(self): + """ + Turn off VPN + """ + self.qtsigs.do_disconnect_signal.emit() + @QtCore.Slot() def _start_eip(self): """ -- cgit v1.2.3 From 6166ffedcae0763f3c00076c79e74847f5c80823 Mon Sep 17 00:00:00 2001 From: elijah Date: Mon, 8 Sep 2014 02:01:14 -0700 Subject: single pref win: moved password change UI to a separate window, opened from account page in preferences. --- src/leap/bitmask/gui/account.py | 16 +- src/leap/bitmask/gui/flashable.py | 75 +++++++ src/leap/bitmask/gui/passwordwindow.py | 269 +++++++++++++++++++++++ src/leap/bitmask/gui/preferences_account_page.py | 15 ++ src/leap/bitmask/gui/ui/password_change.ui | 182 +++++++++++++++ src/leap/bitmask/gui/wizard.py | 2 +- src/leap/bitmask/util/credentials.py | 24 +- 7 files changed, 568 insertions(+), 15 deletions(-) create mode 100644 src/leap/bitmask/gui/flashable.py create mode 100644 src/leap/bitmask/gui/passwordwindow.py create mode 100644 src/leap/bitmask/gui/ui/password_change.ui diff --git a/src/leap/bitmask/gui/account.py b/src/leap/bitmask/gui/account.py index b08053a9..ae8127c0 100644 --- a/src/leap/bitmask/gui/account.py +++ b/src/leap/bitmask/gui/account.py @@ -19,6 +19,7 @@ A frontend GUI object to hold the current username and domain. from leap.bitmask.util import make_address from leap.bitmask.config.leapsettings import LeapSettings +from leap.bitmask.services import EIP_SERVICE, MX_SERVICE class Account(): @@ -33,11 +34,16 @@ class Account(): self.address = self.domain def services(self): - """ - returns a list of service name strings + """ + returns a list of service name strings - TODO: this should depend not just on the domain - """ - return self._settings.get_enabled_services(self.domain) + TODO: this should depend not just on the domain + """ + return self._settings.get_enabled_services(self.domain) + def is_email_enabled(self): + MX_SERVICE in self.services() + + def is_eip_enabled(self): + EIP_SERVICE in self.services() diff --git a/src/leap/bitmask/gui/flashable.py b/src/leap/bitmask/gui/flashable.py new file mode 100644 index 00000000..94e3ab60 --- /dev/null +++ b/src/leap/bitmask/gui/flashable.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +class Flashable(object): + """ + An abstract super class to give a QWidget handy methods for diplaying + alert messages inline. The widget inheriting from this class must have + label named 'flash_label' available at self.ui.flash_label, or pass + the QLabel object in the constructor. + """ + + def __init__(self, widget=None): + self._setup(widget) + + def _setup(self, widget=None): + if not hasattr(self, 'widget'): + if widget: + self.widget = widget + else: + self.widget = self.ui.flash_label + self.widget.setVisible(False) + + def flash_error(self, message): + """ + Sets string for the flash message. + + :param message: the text to be displayed + :type message: str + """ + self._setup() + message = "%s" % (message,) + self.widget.setVisible(True) + self.widget.setText(message) + + def flash_success(self, message): + """ + Sets string for the flash message. + + :param message: the text to be displayed + :type message: str + """ + self._setup() + message = "%s" % (message,) + self.widget.setVisible(True) + self.widget.setText(message) + + def flash_message(self, message): + """ + Sets string for the flash message. + + :param message: the text to be displayed + :type message: str + """ + self._setup() + message = "%s" % (message,) + self.widget.setVisible(True) + self.widget.setText(message) + + def hide_flash(self): + self._setup() + self.widget.setVisible(False) + diff --git a/src/leap/bitmask/gui/passwordwindow.py b/src/leap/bitmask/gui/passwordwindow.py new file mode 100644 index 00000000..9946febe --- /dev/null +++ b/src/leap/bitmask/gui/passwordwindow.py @@ -0,0 +1,269 @@ +# -*- coding: utf-8 -*- +# passwordwindow.py +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +Change password dialog window +""" + +from PySide import QtCore, QtGui +from leap.bitmask.util.credentials import password_checks + +from leap.bitmask.gui.ui_password_change import Ui_PasswordChange +from leap.bitmask.gui.flashable import Flashable + +import logging +logger = logging.getLogger(__name__) + +class PasswordWindow(QtGui.QDialog, Flashable): + + def __init__(self, parent, account, app): + """ + :param parent: parent object of the PreferencesWindow. + :parent type: QWidget + + :param account: the user set in the login widget + :type account: Account + + :param app: App instance + :type app: App + """ + QtGui.QDialog.__init__(self, parent) + + self.account = account + self.app = app + self._backend_connect() + + self.ui = Ui_PasswordChange() + self.ui.setupUi(self) + + self.hide_flash() + self.ui.ok_button.clicked.connect(self._change_password) + self.ui.cancel_button.clicked.connect(self._close) + self.ui.username_lineedit.setText(account.address) + + self._disabled = False # if set to True, never again enable widgets. + + if account.username is None: + # should not ever happen, but just in case + self._disabled = True + self._enable_password_widgets(False) + self.ui.cancel_button.setEnabled(True) + self.flash_error(self.tr("Please log in to change your password.")) + + if self.is_soledad_needed() and not self._soledad_ready: + self._enable_password_widgets(False) + self.ui.cancel_button.setEnabled(True) + self.flash_message( + self.tr("Please wait for data storage to be ready.")) + + def is_soledad_needed(self): + """ + Returns true if the current account needs to change the soledad + password as well as the SRP password. + """ + return self.account.is_email_enabled() + + # + # MANAGE WIDGETS + # + + def _enable_password_widgets(self, enabled): + """ + Enables or disables the widgets in the password change group box. + + :param enabled: True if the widgets should be enabled. + False if widgets should be disabled and + display the status label that shows that is + changing the password. + :type enabled: bool + """ + if self._disabled: + return + + if enabled: + self.hide_flash() + else: + self.flash_message(self.tr("Changing password...")) + + self.ui.current_password_lineedit.setEnabled(enabled) + self.ui.new_password_lineedit.setEnabled(enabled) + self.ui.new_password_confirmation_lineedit.setEnabled(enabled) + self.ui.ok_button.setEnabled(enabled) + self.ui.cancel_button.setEnabled(enabled) + + def _change_password_success(self): + """ + Callback used to display a successfully changed password. + """ + logger.debug("Password changed successfully.") + self._clear_password_inputs() + self._enable_password_widgets(True) + self.flash_success(self.tr("Password changed successfully.")) + + def _clear_password_inputs(self): + """ + Clear the contents of the inputs. + """ + self.ui.current_password_lineedit.setText("") + self.ui.new_password_lineedit.setText("") + self.ui.new_password_confirmation_lineedit.setText("") + + # + # SLOTS + # + + def _backend_connect(self): + """ + Helper to connect to backend signals + """ + sig = self.app.signaler + + sig.srp_password_change_ok.connect(self._srp_change_password_ok) + + pwd_change_error = lambda: self._srp_change_password_problem( + self.tr("There was a problem changing the password."), + None) + sig.srp_password_change_error.connect(pwd_change_error) + + pwd_change_badpw = lambda: self._srp_change_password_problem( + self.tr("You did not enter a correct current password."), + 'current_password') + sig.srp_password_change_badpw.connect(pwd_change_badpw) + + sig.soledad_password_change_ok.connect( + self._soledad_change_password_ok) + + sig.soledad_password_change_error.connect( + self._soledad_change_password_problem) + + self._soledad_ready = False + sig.soledad_bootstrap_finished.connect(self._on_soledad_ready) + + + @QtCore.Slot() + def _change_password(self): + """ + TRIGGERS: + self.ui.buttonBox.accepted + + Changes the user's password if the inputboxes are correctly filled. + """ + current_password = self.ui.current_password_lineedit.text() + new_password = self.ui.new_password_lineedit.text() + new_password2 = self.ui.new_password_confirmation_lineedit.text() + + self._enable_password_widgets(True) + + if len(current_password) == 0: + self.flash_error(self.tr("Password is empty.")) + self.ui.current_password_lineedit.setFocus() + return + + ok, msg, field = password_checks(self.account.username, new_password, + new_password2) + if not ok: + self.flash_error(msg) + if field == 'new_password': + self.ui.new_password_lineedit.setFocus() + elif field == 'new_password_confirmation': + self.ui.new_password_confirmation_lineedit.setFocus() + return + + self._enable_password_widgets(False) + self.app.backend.user_change_password( + current_password=current_password, + new_password=new_password) + + @QtCore.Slot() + def _close(self): + """ + TRIGGERS: + self.ui.buttonBox.rejected + + Close this dialog + """ + self.hide() + + @QtCore.Slot() + def _srp_change_password_ok(self): + """ + TRIGGERS: + self._backend.signaler.srp_password_change_ok + + Callback used to display a successfully changed password. + """ + new_password = self.ui.new_password_lineedit.text() + logger.debug("SRP password changed successfully.") + + if self.is_soledad_needed(): + self._backend.soledad_change_password(new_password=new_password) + else: + self._change_password_success() + + @QtCore.Slot(unicode) + def _srp_change_password_problem(self, msg, field): + """ + TRIGGERS: + self._backend.signaler.srp_password_change_error + self._backend.signaler.srp_password_change_badpw + + Callback used to display an error on changing password. + + :param msg: the message to show to the user. + :type msg: unicode + """ + logger.error("Error changing password: %s" % (msg,)) + self._enable_password_widgets(True) + self.flash_error(msg) + if field == 'current_password': + self.ui.current_password_lineedit.setFocus() + + @QtCore.Slot() + def _soledad_change_password_ok(self): + """ + TRIGGERS: + Signaler.soledad_password_change_ok + + Soledad password change went OK. + """ + logger.debug("Soledad password changed successfully.") + self._change_password_success() + + @QtCore.Slot(unicode) + def _soledad_change_password_problem(self, msg): + """ + TRIGGERS: + Signaler.soledad_password_change_error + + Callback used to display an error on changing password. + + :param msg: the message to show to the user. + :type msg: unicode + """ + logger.error("Error changing soledad password: %s" % (msg,)) + self._enable_password_widgets(True) + self.flash_error(msg) + + + @QtCore.Slot() + def _on_soledad_ready(self): + """ + TRIGGERS: + Signaler.soledad_bootstrap_finished + """ + self._enable_password_widgets(True) + self._soledad_ready = True diff --git a/src/leap/bitmask/gui/preferences_account_page.py b/src/leap/bitmask/gui/preferences_account_page.py index bb90aab5..895d84b5 100644 --- a/src/leap/bitmask/gui/preferences_account_page.py +++ b/src/leap/bitmask/gui/preferences_account_page.py @@ -22,6 +22,7 @@ from functools import partial from PySide import QtCore, QtGui from ui_preferences_account_page import Ui_PreferencesAccountPage +from passwordwindow import PasswordWindow from leap.bitmask.services import get_service_display_name from leap.bitmask.config.leapsettings import LeapSettings @@ -43,9 +44,17 @@ class PreferencesAccountPage(QtGui.QWidget): self.ui.change_password_label.setVisible(False) self.ui.provider_services_label.setVisible(False) + self.ui.change_password_button.clicked.connect( + self._show_change_password) app.signaler.prov_get_supported_services.connect(self._load_services) app.backend.provider_get_supported_services(domain=account.domain) + if account.username is None: + self.ui.change_password_label.setText( + self.tr('You must be logged in to change your password.')) + self.ui.change_password_label.setVisible(True) + self.ui.change_password_button.setEnabled(False) + @QtCore.Slot(str, int) def _service_selection_changed(self, service, state): """ @@ -112,3 +121,9 @@ class PreferencesAccountPage(QtGui.QWidget): except ValueError: logger.error("Something went wrong while trying to " "load service %s" % (service,)) + + @QtCore.Slot() + def _show_change_password(self): + change_password_window = PasswordWindow(self, self.account, self.app) + change_password_window.show() + diff --git a/src/leap/bitmask/gui/ui/password_change.ui b/src/leap/bitmask/gui/ui/password_change.ui new file mode 100644 index 00000000..b7ceac38 --- /dev/null +++ b/src/leap/bitmask/gui/ui/password_change.ui @@ -0,0 +1,182 @@ + + + PasswordChange + + + + 0 + 0 + 459 + 231 + + + + + 0 + 0 + + + + Change Password + + + + + + + + Username: + + + + + + + New password: + + + new_password_lineedit + + + + + + + QLineEdit::Password + + + + + + + Re-enter new password: + + + new_password_confirmation_lineedit + + + + + + + Current password: + + + current_password_lineedit + + + + + + + QLineEdit::Password + + + + + + + QLineEdit::Password + + + + + + + Qt::Vertical + + + + 20 + 10 + + + + + + + + + + false + + + + + + + + + + + <flash_label> + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Close + + + false + + + + + + + OK + + + false + + + true + + + + + + + + + username_lineedit + current_password_lineedit + new_password_lineedit + new_password_confirmation_lineedit + + + + diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 8182228d..4d55a39e 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -317,7 +317,7 @@ class Wizard(QtGui.QWizard): user_ok, msg = username_checks(username) if user_ok: - pass_ok, msg = password_checks(username, password, password2) + pass_ok, msg, field = password_checks(username, password, password2) if user_ok and pass_ok: self._set_register_status(self.tr("Starting registration...")) diff --git a/src/leap/bitmask/util/credentials.py b/src/leap/bitmask/util/credentials.py index 757ce10c..dfc78a09 100644 --- a/src/leap/bitmask/util/credentials.py +++ b/src/leap/bitmask/util/credentials.py @@ -38,7 +38,7 @@ def username_checks(username): valid = USERNAME_VALIDATOR.validate(username, 0) valid_username = valid[0] == QtGui.QValidator.State.Acceptable if message is None and not valid_username: - message = _tr("Invalid username") + message = _tr("That username is not allowed. Try another.") return message is None, message @@ -54,28 +54,34 @@ def password_checks(username, password, password2): :param password2: second password from the registration form :type password: str - :returns: True and empty message if all the checks pass, - False and an error message otherwise - :rtype: tuple(bool, str) + :returns: (True, None, None) if all the checks pass, + (False, message, field name) otherwise + :rtype: tuple(bool, str, str) """ # translation helper _tr = QtCore.QObject().tr message = None + field = None if message is None and password != password2: message = _tr("Passwords don't match") + field = 'new_password_confirmation' if message is None and not password: - message = _tr("You can't use an empty password") + message = _tr("Password is empty") + field = 'new_password' if message is None and len(password) < 8: - message = _tr("Password too short") + message = _tr("Password is too short") + field = 'new_password' if message is None and password in WEAK_PASSWORDS: - message = _tr("Password too easy") + message = _tr("Password is too easy") + field = 'new_password' if message is None and username == password: - message = _tr("Password equal to username") + message = _tr("Password can't be the same as username") + field = 'new_password' - return message is None, message + return message is None, message, field -- cgit v1.2.3 From c29b1dd9345e01e761b9891728ecd0b8d964a02d Mon Sep 17 00:00:00 2001 From: elijah Date: Tue, 16 Sep 2014 16:40:52 -0700 Subject: single pref win: gets the autopep8 beauty scrub. --- src/leap/bitmask/gui/account.py | 6 ++--- src/leap/bitmask/gui/app.py | 4 +-- src/leap/bitmask/gui/flashable.py | 3 ++- src/leap/bitmask/gui/passwordwindow.py | 7 +++-- src/leap/bitmask/gui/preferences_account_page.py | 3 +-- src/leap/bitmask/gui/preferences_email_page.py | 3 ++- src/leap/bitmask/gui/preferences_vpn_page.py | 1 + src/leap/bitmask/gui/preferenceswindow.py | 33 +++++++++++++++--------- src/leap/bitmask/gui/wizard.py | 4 ++- 9 files changed, 37 insertions(+), 27 deletions(-) diff --git a/src/leap/bitmask/gui/account.py b/src/leap/bitmask/gui/account.py index ae8127c0..c941c3fa 100644 --- a/src/leap/bitmask/gui/account.py +++ b/src/leap/bitmask/gui/account.py @@ -21,6 +21,7 @@ from leap.bitmask.util import make_address from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.services import EIP_SERVICE, MX_SERVICE + class Account(): def __init__(self, username, domain): @@ -29,9 +30,9 @@ class Account(): self.domain = domain if self.username is not None: - self.address = make_address(self.username, self.domain) + self.address = make_address(self.username, self.domain) else: - self.address = self.domain + self.address = self.domain def services(self): """ @@ -46,4 +47,3 @@ class Account(): def is_eip_enabled(self): EIP_SERVICE in self.services() - diff --git a/src/leap/bitmask/gui/app.py b/src/leap/bitmask/gui/app.py index 7fcf69af..eb1a58d5 100644 --- a/src/leap/bitmask/gui/app.py +++ b/src/leap/bitmask/gui/app.py @@ -29,9 +29,8 @@ from leap.bitmask.backend.leapsignaler import LeapSignaler logger = logging.getLogger(__name__) -class App(QtGui.QWidget): - #preferences_saved = QtCore.Signal() +class App(QtGui.QWidget): # the user has changed which services are enabled for a particular account # args: account (Account), active services (list of str) @@ -50,7 +49,6 @@ class App(QtGui.QWidget): self._backend_checker.timeout.connect(self._check_backend_status) self._backend_checker.start(2000) - @QtCore.Slot() def _check_backend_status(self): """ diff --git a/src/leap/bitmask/gui/flashable.py b/src/leap/bitmask/gui/flashable.py index 94e3ab60..a26d1ec6 100644 --- a/src/leap/bitmask/gui/flashable.py +++ b/src/leap/bitmask/gui/flashable.py @@ -14,7 +14,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . + class Flashable(object): + """ An abstract super class to give a QWidget handy methods for diplaying alert messages inline. The widget inheriting from this class must have @@ -72,4 +74,3 @@ class Flashable(object): def hide_flash(self): self._setup() self.widget.setVisible(False) - diff --git a/src/leap/bitmask/gui/passwordwindow.py b/src/leap/bitmask/gui/passwordwindow.py index 9946febe..5354ab86 100644 --- a/src/leap/bitmask/gui/passwordwindow.py +++ b/src/leap/bitmask/gui/passwordwindow.py @@ -28,6 +28,7 @@ from leap.bitmask.gui.flashable import Flashable import logging logger = logging.getLogger(__name__) + class PasswordWindow(QtGui.QDialog, Flashable): def __init__(self, parent, account, app): @@ -55,7 +56,7 @@ class PasswordWindow(QtGui.QDialog, Flashable): self.ui.cancel_button.clicked.connect(self._close) self.ui.username_lineedit.setText(account.address) - self._disabled = False # if set to True, never again enable widgets. + self._disabled = False # if set to True, never again enable widgets. if account.username is None: # should not ever happen, but just in case @@ -153,7 +154,6 @@ class PasswordWindow(QtGui.QDialog, Flashable): self._soledad_ready = False sig.soledad_bootstrap_finished.connect(self._on_soledad_ready) - @QtCore.Slot() def _change_password(self): """ @@ -174,7 +174,7 @@ class PasswordWindow(QtGui.QDialog, Flashable): return ok, msg, field = password_checks(self.account.username, new_password, - new_password2) + new_password2) if not ok: self.flash_error(msg) if field == 'new_password': @@ -258,7 +258,6 @@ class PasswordWindow(QtGui.QDialog, Flashable): self._enable_password_widgets(True) self.flash_error(msg) - @QtCore.Slot() def _on_soledad_ready(self): """ diff --git a/src/leap/bitmask/gui/preferences_account_page.py b/src/leap/bitmask/gui/preferences_account_page.py index 895d84b5..00dbe626 100644 --- a/src/leap/bitmask/gui/preferences_account_page.py +++ b/src/leap/bitmask/gui/preferences_account_page.py @@ -28,6 +28,7 @@ from leap.bitmask.config.leapsettings import LeapSettings logger = logging.getLogger(__name__) + class PreferencesAccountPage(QtGui.QWidget): def __init__(self, parent, account, app): @@ -86,7 +87,6 @@ class PreferencesAccountPage(QtGui.QWidget): # emit signal alerting change self.app.service_selection_changed.emit(self.account, services) - @QtCore.Slot(str) def _load_services(self, services): """ @@ -126,4 +126,3 @@ class PreferencesAccountPage(QtGui.QWidget): def _show_change_password(self): change_password_window = PasswordWindow(self, self.account, self.app) change_password_window.show() - diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index da902802..0535762a 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -23,7 +23,9 @@ from ui_preferences_email_page import Ui_PreferencesEmailPage logger = logging.getLogger(__name__) + class PreferencesEmailPage(QtGui.QWidget): + """ """ @@ -38,4 +40,3 @@ class PreferencesEmailPage(QtGui.QWidget): self.parent = parent self.account = account self.app = app - diff --git a/src/leap/bitmask/gui/preferences_vpn_page.py b/src/leap/bitmask/gui/preferences_vpn_page.py index a8f074d2..e3417f89 100644 --- a/src/leap/bitmask/gui/preferences_vpn_page.py +++ b/src/leap/bitmask/gui/preferences_vpn_page.py @@ -24,6 +24,7 @@ from leap.bitmask.config.leapsettings import LeapSettings class PreferencesVpnPage(QtGui.QWidget): + """ Page in the preferences window that shows VPN settings """ diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 35a875fa..ccddb764 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -33,6 +33,7 @@ logger = logging.getLogger(__name__) class PreferencesWindow(QtGui.QDialog): + """ Window that displays the preferences. """ @@ -81,27 +82,32 @@ class PreferencesWindow(QtGui.QDialog): account_item = QtGui.QListWidgetItem(self.ui.nav_widget) account_item.setIcon(QtGui.QIcon(":/images/black/32/user.png")) account_item.setText(self.tr("Account")) - account_item.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) - account_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - account_item.setSizeHint(QtCore.QSize(98,56)) + account_item.setTextAlignment( + QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) + account_item.setFlags( + QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + account_item.setSizeHint(QtCore.QSize(98, 56)) self._account_item = account_item vpn_item = QtGui.QListWidgetItem(self.ui.nav_widget) vpn_item.setHidden(True) vpn_item.setIcon(QtGui.QIcon(":/images/black/32/earth.png")) vpn_item.setText(self.tr("VPN")) - vpn_item.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) + vpn_item.setTextAlignment( + QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) vpn_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - vpn_item.setSizeHint(QtCore.QSize(98,56)) + vpn_item.setSizeHint(QtCore.QSize(98, 56)) self._vpn_item = vpn_item email_item = QtGui.QListWidgetItem(self.ui.nav_widget) email_item.setHidden(True) email_item.setIcon(QtGui.QIcon(":/images/black/32/email.png")) email_item.setText(self.tr("Email")) - email_item.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) - email_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - email_item.setSizeHint(QtCore.QSize(98,56)) + email_item.setTextAlignment( + QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) + email_item.setFlags( + QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + email_item.setSizeHint(QtCore.QSize(98, 56)) self._email_item = email_item self.ui.nav_widget.currentItemChanged.connect(self._change_page) @@ -111,9 +117,12 @@ class PreferencesWindow(QtGui.QDialog): """ Adds the pages for the different configuration categories. """ - self.ui.pages_widget.addWidget(PreferencesAccountPage(self, self.account, self.app)) - self.ui.pages_widget.addWidget(PreferencesVpnPage(self, self.account, self.app)) - self.ui.pages_widget.addWidget(PreferencesEmailPage(self, self.account, self.app)) + self.ui.pages_widget.addWidget( + PreferencesAccountPage(self, self.account, self.app)) + self.ui.pages_widget.addWidget( + PreferencesVpnPage(self, self.account, self.app)) + self.ui.pages_widget.addWidget( + PreferencesEmailPage(self, self.account, self.app)) # # Slots @@ -154,5 +163,5 @@ class PreferencesWindow(QtGui.QDialog): return self._vpn_item.setHidden(not EIP_SERVICE in services) - #self._email_item.setHidden(not MX_SERVICE in services) + # self._email_item.setHidden(not MX_SERVICE in services) # ^^ disable email for now, there is nothing there yet. diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 4d55a39e..ff9cae55 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -42,6 +42,7 @@ logger = logging.getLogger(__name__) class Wizard(QtGui.QWizard): + """ First run wizard to register a user and setup a provider """ @@ -317,7 +318,8 @@ class Wizard(QtGui.QWizard): user_ok, msg = username_checks(username) if user_ok: - pass_ok, msg, field = password_checks(username, password, password2) + pass_ok, msg, field = password_checks( + username, password, password2) if user_ok and pass_ok: self._set_register_status(self.tr("Starting registration...")) -- cgit v1.2.3 From 1d331478a431047bf59fc6249a93e127450bff24 Mon Sep 17 00:00:00 2001 From: elijah Date: Tue, 16 Sep 2014 17:07:12 -0700 Subject: single pref win: clean up vpn pref error displaying & don't show dummy gateways in case of error. --- src/leap/bitmask/gui/preferences_vpn_page.py | 31 ++++--------------------- src/leap/bitmask/gui/ui/preferences_vpn_page.ui | 20 ---------------- 2 files changed, 5 insertions(+), 46 deletions(-) diff --git a/src/leap/bitmask/gui/preferences_vpn_page.py b/src/leap/bitmask/gui/preferences_vpn_page.py index e3417f89..f3fa1ecc 100644 --- a/src/leap/bitmask/gui/preferences_vpn_page.py +++ b/src/leap/bitmask/gui/preferences_vpn_page.py @@ -21,9 +21,9 @@ from PySide import QtCore, QtGui from ui_preferences_vpn_page import Ui_PreferencesVpnPage from leap.bitmask.config.leapsettings import LeapSettings +from leap.bitmask.gui.flashable import Flashable - -class PreferencesVpnPage(QtGui.QWidget): +class PreferencesVpnPage(QtGui.QWidget, Flashable): """ Page in the preferences window that shows VPN settings @@ -50,6 +50,7 @@ class PreferencesVpnPage(QtGui.QWidget): self.ui = Ui_PreferencesVpnPage() self.ui.setupUi(self) self.ui.flash_label.setVisible(False) + self.hide_flash() # Connections self.ui.gateways_list.clicked.connect(self._save_selected_gateway) @@ -62,28 +63,6 @@ class PreferencesVpnPage(QtGui.QWidget): # Trigger update self.app.backend.eip_get_gateways_list(domain=self.account.domain) - def _flash_error(self, message): - """ - Sets string for the flash message. - - :param message: the text to be displayed - :type message: str - """ - message = "%s" % (message,) - self.ui.flash_label.setVisible(True) - self.ui.flash_label.setText(message) - - # def _flash_success(self, message): - # """ - # Sets string for the flash message. - # - # :param message: the text to be displayed - # :type message: str - # """ - # message = "%s" % (message,) - # self.ui.flash_label.setVisible(True) - # self.ui.flash_label.setText(message) - @QtCore.Slot(str) def _save_selected_gateway(self, index): """ @@ -145,7 +124,7 @@ class PreferencesVpnPage(QtGui.QWidget): An error has occurred retrieving the gateway list so we inform the user. """ - self._flash_error( + self.flash_error( self.tr("Error loading configuration file.")) self.ui.gateways_list.setEnabled(False) @@ -158,6 +137,6 @@ class PreferencesVpnPage(QtGui.QWidget): The requested provider in not initialized yet, so we give the user an error msg. """ - self._flash_error( + self.flash_error( self.tr("This is an uninitialized provider, please log in first.")) self.ui.gateways_list.setEnabled(False) diff --git a/src/leap/bitmask/gui/ui/preferences_vpn_page.ui b/src/leap/bitmask/gui/ui/preferences_vpn_page.ui index 85a0dc60..1bf3a060 100644 --- a/src/leap/bitmask/gui/ui/preferences_vpn_page.ui +++ b/src/leap/bitmask/gui/ui/preferences_vpn_page.ui @@ -36,26 +36,6 @@ - - - New Item - - - - :/images/countries/us.png - - - - - - New Item - - - - :/images/countries/br.png - - - -- cgit v1.2.3 From 5f56629884da77c3f1427ef5ceb8a830654eb424 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 17 Sep 2014 16:15:56 -0700 Subject: single pref win: move preference window tracking to PreferencesWindow --- src/leap/bitmask/gui/mainwindow.py | 9 ++------- src/leap/bitmask/gui/preferenceswindow.py | 24 ++++++++++++++---------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index b106364d..cc4ede09 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -98,9 +98,6 @@ class MainWindow(QtGui.QMainWindow): # We give the services some time to a halt before forcing quit. SERVICES_STOP_TIMEOUT = 3000 # in milliseconds - # Preferences window - preferences = None - def __init__(self, start_hidden=False, backend_pid=None): """ Constructor for the client main window @@ -574,10 +571,8 @@ class MainWindow(QtGui.QMainWindow): """ account = Account(self._logged_user, self._providers.get_selected_provider()) - if self.preferences is not None: - self.preferences.close() - self.preferences = PreferencesWindow(self, account, self.app) - self.preferences.show() + pref_win = PreferencesWindow(self, account, self.app) + pref_win.show() @QtCore.Slot(object, list) def _update_eip_enabled_status(self, account=None, services=None): diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index ccddb764..32651d5c 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -38,22 +38,21 @@ class PreferencesWindow(QtGui.QDialog): Window that displays the preferences. """ + _current_window = None # currently visible preferences window + def __init__(self, parent, account, app): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget - :param username: the user set in the login widget - :type username: unicode - :param domain: the selected domain in the login widget - :type domain: unicode - :param backend: Backend being used - :type backend: Backend - :param leap_signaler: signal server - :type leap_signaler: LeapSignaler + + :param account: the user or provider + :type account: Account + + :param app: the current App object + :type app: App """ QtGui.QDialog.__init__(self, parent) - self._parent = parent self.account = account self.app = app @@ -69,6 +68,11 @@ class PreferencesWindow(QtGui.QDialog): self._add_pages() self._update_icons(self.account, self.account.services()) + # only allow a single preferrences window at a time. + if PreferencesWindow._current_window is not None: + PreferencesWindow._current_window.close() + PreferencesWindow._current_window = self + def _add_icons(self): """ Adds all the icons for the different configuration categories. @@ -136,7 +140,7 @@ class PreferencesWindow(QtGui.QDialog): Close this dialog """ - self._parent.preferences = None + PreferencesWindow._current_window = None self.hide() @QtCore.Slot() -- cgit v1.2.3 From d8105d53e3aa66448094df3f34eda54c3dcab865 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 17 Sep 2014 16:42:51 -0700 Subject: single pref win: minor linting & add changes file --- changes/feature-combined-preferences-window | 1 + src/leap/bitmask/gui/preferences_account_page.py | 19 +++++++++++++++---- src/leap/bitmask/gui/preferences_email_page.py | 9 +-------- src/leap/bitmask/gui/preferences_vpn_page.py | 3 ++- src/leap/bitmask/gui/preferenceswindow.py | 6 ++++++ 5 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 changes/feature-combined-preferences-window diff --git a/changes/feature-combined-preferences-window b/changes/feature-combined-preferences-window new file mode 100644 index 00000000..146ac923 --- /dev/null +++ b/changes/feature-combined-preferences-window @@ -0,0 +1 @@ +- single combined preferences window (closes #4704, #4119, #5885) \ No newline at end of file diff --git a/src/leap/bitmask/gui/preferences_account_page.py b/src/leap/bitmask/gui/preferences_account_page.py index 00dbe626..ec6a7716 100644 --- a/src/leap/bitmask/gui/preferences_account_page.py +++ b/src/leap/bitmask/gui/preferences_account_page.py @@ -21,10 +21,9 @@ import logging from functools import partial from PySide import QtCore, QtGui -from ui_preferences_account_page import Ui_PreferencesAccountPage -from passwordwindow import PasswordWindow +from leap.bitmask.gui.ui_preferences_account_page import Ui_PreferencesAccountPage +from leap.bitmask.gui.passwordwindow import PasswordWindow from leap.bitmask.services import get_service_display_name -from leap.bitmask.config.leapsettings import LeapSettings logger = logging.getLogger(__name__) @@ -33,6 +32,14 @@ class PreferencesAccountPage(QtGui.QWidget): def __init__(self, parent, account, app): """ + :param parent: parent object of the PreferencesWindow. + :parent type: QWidget + + :param account: user account (user + provider or just provider) + :type account: Account + + :param app: the current App object + :type app: App """ QtGui.QWidget.__init__(self, parent) self.ui = Ui_PreferencesAccountPage() @@ -103,7 +110,11 @@ class PreferencesAccountPage(QtGui.QWidget): self._selected_services = set() - # remove existing checkboxes + # Remove existing checkboxes + # (the new widget is deleted when its parent is deleted. + # We need to loop backwards because removing things from the + # beginning shifts items and changes the order of items in the layout. + # Using `QObject.deleteLater` doesn't seem to work.) layout = self.ui.provider_services_layout for i in reversed(range(layout.count())): layout.itemAt(i).widget().setParent(None) diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index 0535762a..80e8d93e 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -19,24 +19,17 @@ Widget for "email" preferences import logging from PySide import QtCore, QtGui -from ui_preferences_email_page import Ui_PreferencesEmailPage +from leap.bitmask.gui.ui_preferences_email_page import Ui_PreferencesEmailPage logger = logging.getLogger(__name__) class PreferencesEmailPage(QtGui.QWidget): - """ - - """ - def __init__(self, parent, account, app): - """ - """ QtGui.QWidget.__init__(self, parent) self.ui = Ui_PreferencesEmailPage() self.ui.setupUi(self) - self.parent = parent self.account = account self.app = app diff --git a/src/leap/bitmask/gui/preferences_vpn_page.py b/src/leap/bitmask/gui/preferences_vpn_page.py index f3fa1ecc..901116b4 100644 --- a/src/leap/bitmask/gui/preferences_vpn_page.py +++ b/src/leap/bitmask/gui/preferences_vpn_page.py @@ -18,11 +18,12 @@ Widget for "vpn" preferences """ from PySide import QtCore, QtGui -from ui_preferences_vpn_page import Ui_PreferencesVpnPage +from leap.bitmask.gui.ui_preferences_vpn_page import Ui_PreferencesVpnPage from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.gui.flashable import Flashable + class PreferencesVpnPage(QtGui.QWidget, Flashable): """ diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 32651d5c..f9b7ddf6 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -150,6 +150,12 @@ class PreferencesWindow(QtGui.QDialog): self.ui.nav_widget.currentItemChanged Changes what page is displayed. + + :param current: the currently selected item (might be None?) + :type current: PySide.QtGui.QListWidgetItem + + :param previous: the previously selected item (might be None) + :type previous: PySide.QtGui.QListWidgetItem """ if not current: current = previous -- cgit v1.2.3 From 92fc4c1d9d60a213a4be21db459efc3bfc3e205f Mon Sep 17 00:00:00 2001 From: elijah Date: Thu, 18 Sep 2014 15:17:46 -0700 Subject: single pref win: added shortcut to preferences --- src/leap/bitmask/gui/ui/mainwindow.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index 2e8aea8c..b1d68c4a 100644 --- a/src/leap/bitmask/gui/ui/mainwindow.ui +++ b/src/leap/bitmask/gui/ui/mainwindow.ui @@ -337,7 +337,7 @@ true - Preferences... + Pr&eferences... -- cgit v1.2.3 From 0ad8c8ea3f8d5130f44aa90b55da59622d0048c7 Mon Sep 17 00:00:00 2001 From: elijah Date: Thu, 18 Sep 2014 15:18:46 -0700 Subject: single pref win: ensure proper deletion of preference window pages. --- src/leap/bitmask/gui/preferenceswindow.py | 41 +++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index f9b7ddf6..e18be976 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -38,7 +38,7 @@ class PreferencesWindow(QtGui.QDialog): Window that displays the preferences. """ - _current_window = None # currently visible preferences window + _current_window = None # currently visible preferences window def __init__(self, parent, account, app): """ @@ -59,7 +59,7 @@ class PreferencesWindow(QtGui.QDialog): self.ui = Ui_Preferences() self.ui.setupUi(self) - self.ui.close_button.clicked.connect(self.close) + self.ui.close_button.clicked.connect(self.close_window) self.ui.account_label.setText(account.address) self.app.service_selection_changed.connect(self._update_icons) @@ -68,9 +68,9 @@ class PreferencesWindow(QtGui.QDialog): self._add_pages() self._update_icons(self.account, self.account.services()) - # only allow a single preferrences window at a time. + # only allow a single preferences window at a time. if PreferencesWindow._current_window is not None: - PreferencesWindow._current_window.close() + PreferencesWindow._current_window.close_window() PreferencesWindow._current_window = self def _add_icons(self): @@ -121,27 +121,42 @@ class PreferencesWindow(QtGui.QDialog): """ Adds the pages for the different configuration categories. """ - self.ui.pages_widget.addWidget( - PreferencesAccountPage(self, self.account, self.app)) - self.ui.pages_widget.addWidget( - PreferencesVpnPage(self, self.account, self.app)) - self.ui.pages_widget.addWidget( - PreferencesEmailPage(self, self.account, self.app)) + self._account_page = PreferencesAccountPage(self, self.account, self.app) + self._vpn_page = PreferencesVpnPage(self, self.account, self.app) + self._email_page = PreferencesEmailPage(self, self.account, self.app) + + self.ui.pages_widget.addWidget(self._account_page) + self.ui.pages_widget.addWidget(self._vpn_page) + self.ui.pages_widget.addWidget(self._email_page) + + def closeEvent(self, e): + """ + Override closeEvent to capture when user closes the window. + """ + self.close_window() # # Slots # @QtCore.Slot() - def close(self): + def close_window(self): """ TRIGGERS: self.ui.close_button.clicked - Close this dialog + Close this dialog and destroy it. """ PreferencesWindow._current_window = None - self.hide() + self.close() + + # deleteLater does not seem to cascade to items in stackLayout + # (even with QtCore.Qt.WA_DeleteOnClose attribute). + # so, here we call deleteLater() explicitly: + self._account_page.deleteLater() + self._vpn_page.deleteLater() + self._email_page.deleteLater() + self.deleteLater() @QtCore.Slot() def _change_page(self, current, previous): -- cgit v1.2.3 From d47adca6cb7494e55c4a9fbc88896c62c06affa5 Mon Sep 17 00:00:00 2001 From: elijah Date: Fri, 19 Sep 2014 14:22:42 -0700 Subject: single pref win: fix problems with cleaning up closed windows (lambdas were keeping the python object from getting garbage collected, and this keeps the old signal connections active) --- src/leap/bitmask/gui/passwordwindow.py | 61 +++++++++++++++++-------------- src/leap/bitmask/gui/preferenceswindow.py | 14 ++----- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/src/leap/bitmask/gui/passwordwindow.py b/src/leap/bitmask/gui/passwordwindow.py index 5354ab86..f7ef079e 100644 --- a/src/leap/bitmask/gui/passwordwindow.py +++ b/src/leap/bitmask/gui/passwordwindow.py @@ -31,6 +31,8 @@ logger = logging.getLogger(__name__) class PasswordWindow(QtGui.QDialog, Flashable): + _current_window = None # currently visible password window + def __init__(self, parent, account, app): """ :param parent: parent object of the PreferencesWindow. @@ -53,9 +55,13 @@ class PasswordWindow(QtGui.QDialog, Flashable): self.hide_flash() self.ui.ok_button.clicked.connect(self._change_password) - self.ui.cancel_button.clicked.connect(self._close) + self.ui.cancel_button.clicked.connect(self.close) self.ui.username_lineedit.setText(account.address) + if PasswordWindow._current_window is not None: + PasswordWindow._current_window.close() + PasswordWindow._current_window = self + self._disabled = False # if set to True, never again enable widgets. if account.username is None: @@ -132,22 +138,11 @@ class PasswordWindow(QtGui.QDialog, Flashable): Helper to connect to backend signals """ sig = self.app.signaler - sig.srp_password_change_ok.connect(self._srp_change_password_ok) - - pwd_change_error = lambda: self._srp_change_password_problem( - self.tr("There was a problem changing the password."), - None) - sig.srp_password_change_error.connect(pwd_change_error) - - pwd_change_badpw = lambda: self._srp_change_password_problem( - self.tr("You did not enter a correct current password."), - 'current_password') - sig.srp_password_change_badpw.connect(pwd_change_badpw) - + sig.srp_password_change_error.connect(self._srp_password_change_error) + sig.srp_password_change_badpw.connect(self._srp_password_change_badpw) sig.soledad_password_change_ok.connect( self._soledad_change_password_ok) - sig.soledad_password_change_error.connect( self._soledad_change_password_problem) @@ -188,15 +183,16 @@ class PasswordWindow(QtGui.QDialog, Flashable): current_password=current_password, new_password=new_password) - @QtCore.Slot() - def _close(self): + def closeEvent(self, event=None): """ TRIGGERS: - self.ui.buttonBox.rejected + cancel_button (indirectly via self.close()) + or when window is closed - Close this dialog + Close this dialog & delete ourselves to clean up signals. """ - self.hide() + PasswordWindow._current_window = None + self.deleteLater() @QtCore.Slot() def _srp_change_password_ok(self): @@ -214,23 +210,32 @@ class PasswordWindow(QtGui.QDialog, Flashable): else: self._change_password_success() - @QtCore.Slot(unicode) - def _srp_change_password_problem(self, msg, field): + @QtCore.Slot() + def _srp_password_change_error(self): """ TRIGGERS: self._backend.signaler.srp_password_change_error - self._backend.signaler.srp_password_change_badpw - Callback used to display an error on changing password. + Unknown problem changing password + """ + msg = self.tr("There was a problem changing the password.") + logger.error(msg) + self._enable_password_widgets(True) + self.flash_error(msg) - :param msg: the message to show to the user. - :type msg: unicode + @QtCore.Slot() + def _srp_password_change_badpw(self): """ - logger.error("Error changing password: %s" % (msg,)) + TRIGGERS: + self._backend.signaler.srp_password_change_badpw + + The password the user entered was wrong. + """ + msg = self.tr("You did not enter a correct current password.") + logger.error(msg) self._enable_password_widgets(True) self.flash_error(msg) - if field == 'current_password': - self.ui.current_password_lineedit.setFocus() + self.ui.current_password_lineedit.setFocus() @QtCore.Slot() def _soledad_change_password_ok(self): diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index e18be976..f1252301 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -59,7 +59,7 @@ class PreferencesWindow(QtGui.QDialog): self.ui = Ui_Preferences() self.ui.setupUi(self) - self.ui.close_button.clicked.connect(self.close_window) + self.ui.close_button.clicked.connect(self.close) self.ui.account_label.setText(account.address) self.app.service_selection_changed.connect(self._update_icons) @@ -129,26 +129,20 @@ class PreferencesWindow(QtGui.QDialog): self.ui.pages_widget.addWidget(self._vpn_page) self.ui.pages_widget.addWidget(self._email_page) - def closeEvent(self, e): - """ - Override closeEvent to capture when user closes the window. - """ - self.close_window() - # # Slots # - @QtCore.Slot() - def close_window(self): + def closeEvent(self, e): """ TRIGGERS: self.ui.close_button.clicked + (since self.close() will trigger closeEvent) + whenever the window is closed Close this dialog and destroy it. """ PreferencesWindow._current_window = None - self.close() # deleteLater does not seem to cascade to items in stackLayout # (even with QtCore.Qt.WA_DeleteOnClose attribute). -- cgit v1.2.3 From 668a9f9aa327c3c055b9e0f641f8c33d59e1c452 Mon Sep 17 00:00:00 2001 From: elijah Date: Mon, 22 Sep 2014 09:56:48 -0700 Subject: country flags: add default fallback xx.png flag. --- data/images/countries/xx.png | Bin 0 -> 254 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 data/images/countries/xx.png diff --git a/data/images/countries/xx.png b/data/images/countries/xx.png new file mode 100644 index 00000000..abd36d39 Binary files /dev/null and b/data/images/countries/xx.png differ -- cgit v1.2.3