diff options
author | Miguel Jacq <mig@mig5.net> | 2018-11-13 14:45:40 +1100 |
---|---|---|
committer | Miguel Jacq <mig@mig5.net> | 2018-11-13 14:45:40 +1100 |
commit | d3b5e1e256e7b122e33b98f79bf498408e1233db (patch) | |
tree | 5ac985a9da2638256b4061a90559a17e9b1dd631 /onionshare_gui | |
parent | db8548c35bd5d0236c3083e9e860b82a1dbca808 (diff) | |
parent | a39cd08083f4443e498611bbc1c086a05f5cffde (diff) | |
download | onionshare-d3b5e1e256e7b122e33b98f79bf498408e1233db.tar.gz onionshare-d3b5e1e256e7b122e33b98f79bf498408e1233db.zip |
Merge develop branch and fix conflicts
Diffstat (limited to 'onionshare_gui')
-rw-r--r-- | onionshare_gui/__init__.py | 16 | ||||
-rw-r--r-- | onionshare_gui/mode/__init__.py (renamed from onionshare_gui/mode.py) | 33 | ||||
-rw-r--r-- | onionshare_gui/mode/history.py | 564 | ||||
-rw-r--r-- | onionshare_gui/mode/receive_mode/__init__.py | 206 | ||||
-rw-r--r-- | onionshare_gui/mode/share_mode/__init__.py (renamed from onionshare_gui/share_mode/__init__.py) | 173 | ||||
-rw-r--r-- | onionshare_gui/mode/share_mode/file_selection.py (renamed from onionshare_gui/share_mode/file_selection.py) | 17 | ||||
-rw-r--r-- | onionshare_gui/mode/share_mode/threads.py (renamed from onionshare_gui/share_mode/threads.py) | 5 | ||||
-rw-r--r-- | onionshare_gui/onionshare_gui.py | 74 | ||||
-rw-r--r-- | onionshare_gui/receive_mode/__init__.py | 236 | ||||
-rw-r--r-- | onionshare_gui/receive_mode/uploads.py | 28 | ||||
-rw-r--r-- | onionshare_gui/server_status.py | 40 | ||||
-rw-r--r-- | onionshare_gui/settings_dialog.py | 180 | ||||
-rw-r--r-- | onionshare_gui/share_mode/downloads.py | 8 | ||||
-rw-r--r-- | onionshare_gui/tor_connection_dialog.py | 6 |
14 files changed, 1084 insertions, 502 deletions
diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 99db635a..675bb52d 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -59,7 +59,15 @@ def main(): common = Common() common.define_css() + # Load the default settings and strings early, for the sake of being able to parse options. + # These won't be in the user's chosen locale necessarily, but we need to parse them + # early in order to even display the option to pass alternate settings (which might + # contain a preferred locale). + # If an alternate --config is passed, we'll reload strings later. + common.load_settings() strings.load_strings(common) + + # Display OnionShare banner print(strings._('version_string').format(common.version)) # Allow Ctrl-C to smoothly quit the program instead of throwing an exception @@ -84,6 +92,10 @@ def main(): filenames[i] = os.path.abspath(filenames[i]) config = args.config + if config: + # Re-load the strings, in case the provided config has changed locale + common.load_settings(config) + strings.load_strings(common) local_only = bool(args.local_only) debug = bool(args.debug) @@ -96,10 +108,10 @@ def main(): valid = True for filename in filenames: if not os.path.isfile(filename) and not os.path.isdir(filename): - Alert(common, strings._("not_a_file", True).format(filename)) + Alert(common, strings._("not_a_file").format(filename)) valid = False if not os.access(filename, os.R_OK): - Alert(common, strings._("not_a_readable_file", True).format(filename)) + Alert(common, strings._("not_a_readable_file").format(filename)) valid = False if not valid: sys.exit() diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode/__init__.py index 6b156f7e..5110289f 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode/__init__.py @@ -22,9 +22,9 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings from onionshare.common import ShutdownTimer -from .server_status import ServerStatus -from .threads import OnionThread -from .widgets import Alert +from ..server_status import ServerStatus +from ..threads import OnionThread +from ..widgets import Alert class Mode(QtWidgets.QWidget): """ @@ -49,8 +49,6 @@ class Mode(QtWidgets.QWidget): self.filenames = filenames - self.setMinimumWidth(450) - # The web object gets created in init() self.web = None @@ -72,24 +70,17 @@ class Mode(QtWidgets.QWidget): self.starting_server_step3.connect(self.start_server_step3) self.starting_server_error.connect(self.start_server_error) - # Primary action layout + # Primary action + # Note: It's up to the downstream Mode to add this to its layout self.primary_action_layout = QtWidgets.QVBoxLayout() self.primary_action_layout.addWidget(self.server_status) self.primary_action = QtWidgets.QWidget() self.primary_action.setLayout(self.primary_action_layout) - # Layout - self.layout = QtWidgets.QVBoxLayout() - self.layout.addWidget(self.primary_action) - # Hack to allow a minimum width on self.layout - min_width_widget = QtWidgets.QWidget() - min_width_widget.setMinimumWidth(450) - self.layout.addWidget(min_width_widget) - - self.horizontal_layout_wrapper = QtWidgets.QHBoxLayout() - self.horizontal_layout_wrapper.addLayout(self.layout) - - self.setLayout(self.horizontal_layout_wrapper) + # Hack to allow a minimum width on the main layout + # Note: It's up to the downstream Mode to add this to its layout + self.min_width_widget = QtWidgets.QWidget() + self.min_width_widget.setMinimumWidth(600) def init(self): """ @@ -333,6 +324,12 @@ class Mode(QtWidgets.QWidget): """ pass + def handle_request_upload_set_dir(self, event): + """ + Handle REQUEST_UPLOAD_SET_DIR event. + """ + pass + def handle_request_upload_finished(self, event): """ Handle REQUEST_UPLOAD_FINISHED event. diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py new file mode 100644 index 00000000..38e0fed3 --- /dev/null +++ b/onionshare_gui/mode/history.py @@ -0,0 +1,564 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com> + +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 <http://www.gnu.org/licenses/>. +""" +import time +import subprocess +import os +from datetime import datetime +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings +from ..widgets import Alert + + +class HistoryItem(QtWidgets.QWidget): + """ + The base history item + """ + def __init__(self): + super(HistoryItem, self).__init__() + + def update(self): + pass + + def cancel(self): + pass + + +class DownloadHistoryItem(HistoryItem): + """ + Download history item, for share mode + """ + def __init__(self, common, id, total_bytes): + super(DownloadHistoryItem, self).__init__() + self.common = common + + self.id = id + self.total_bytes = total_bytes + self.downloaded_bytes = 0 + self.started = time.time() + self.started_dt = datetime.fromtimestamp(self.started) + + # Label + self.label = QtWidgets.QLabel(strings._('gui_download_in_progress').format(self.started_dt.strftime("%b %d, %I:%M%p"))) + + # Progress bar + self.progress_bar = QtWidgets.QProgressBar() + self.progress_bar.setTextVisible(True) + self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter) + self.progress_bar.setMinimum(0) + self.progress_bar.setMaximum(total_bytes) + self.progress_bar.setValue(0) + self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) + self.progress_bar.total_bytes = total_bytes + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label) + layout.addWidget(self.progress_bar) + self.setLayout(layout) + + # Start at 0 + self.update(0) + + def update(self, downloaded_bytes): + self.downloaded_bytes = downloaded_bytes + + self.progress_bar.setValue(downloaded_bytes) + if downloaded_bytes == self.progress_bar.total_bytes: + pb_fmt = strings._('gui_download_upload_progress_complete').format( + self.common.format_seconds(time.time() - self.started)) + else: + elapsed = time.time() - self.started + if elapsed < 10: + # Wait a couple of seconds for the download rate to stabilize. + # This prevents a "Windows copy dialog"-esque experience at + # the beginning of the download. + pb_fmt = strings._('gui_download_upload_progress_starting').format( + self.common.human_readable_filesize(downloaded_bytes)) + else: + pb_fmt = strings._('gui_download_upload_progress_eta').format( + self.common.human_readable_filesize(downloaded_bytes), + self.estimated_time_remaining) + + self.progress_bar.setFormat(pb_fmt) + + def cancel(self): + self.progress_bar.setFormat(strings._('gui_canceled')) + + @property + def estimated_time_remaining(self): + return self.common.estimated_time_remaining(self.downloaded_bytes, + self.total_bytes, + self.started) + + +class UploadHistoryItemFile(QtWidgets.QWidget): + def __init__(self, common, filename): + super(UploadHistoryItemFile, self).__init__() + self.common = common + + self.common.log('UploadHistoryItemFile', '__init__', 'filename: {}'.format(filename)) + + self.filename = filename + self.dir = None + self.started = datetime.now() + + # Filename label + self.filename_label = QtWidgets.QLabel(self.filename) + self.filename_label_width = self.filename_label.width() + + # File size label + self.filesize_label = QtWidgets.QLabel() + self.filesize_label.setStyleSheet(self.common.css['receive_file_size']) + self.filesize_label.hide() + + # Folder button + folder_pixmap = QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/open_folder.png'))) + folder_icon = QtGui.QIcon(folder_pixmap) + self.folder_button = QtWidgets.QPushButton() + self.folder_button.clicked.connect(self.open_folder) + self.folder_button.setIcon(folder_icon) + self.folder_button.setIconSize(folder_pixmap.rect().size()) + self.folder_button.setFlat(True) + self.folder_button.hide() + + # Layouts + layout = QtWidgets.QHBoxLayout() + layout.addWidget(self.filename_label) + layout.addWidget(self.filesize_label) + layout.addStretch() + layout.addWidget(self.folder_button) + self.setLayout(layout) + + def update(self, uploaded_bytes, complete): + self.filesize_label.setText(self.common.human_readable_filesize(uploaded_bytes)) + self.filesize_label.show() + + if complete: + self.folder_button.show() + + def rename(self, new_filename): + self.filename = new_filename + self.filename_label.setText(self.filename) + + def set_dir(self, dir): + self.dir = dir + + def open_folder(self): + """ + Open the downloads folder, with the file selected, in a cross-platform manner + """ + self.common.log('UploadHistoryItemFile', 'open_folder') + + if not self.dir: + self.common.log('UploadHistoryItemFile', 'open_folder', "dir has not been set yet, can't open folder") + return + + abs_filename = os.path.join(self.dir, self.filename) + + # Linux + if self.common.platform == 'Linux' or self.common.platform == 'BSD': + try: + # If nautilus is available, open it + subprocess.Popen(['nautilus', abs_filename]) + except: + Alert(self.common, strings._('gui_open_folder_error_nautilus').format(abs_filename)) + + # macOS + elif self.common.platform == 'Darwin': + # TODO: Implement opening folder with file selected in macOS + # This seems helpful: https://stackoverflow.com/questions/3520493/python-show-in-finder + self.common.log('UploadHistoryItemFile', 'open_folder', 'not implemented for Darwin yet') + + # Windows + elif self.common.platform == 'Windows': + # TODO: Implement opening folder with file selected in Windows + # This seems helpful: https://stackoverflow.com/questions/6631299/python-opening-a-folder-in-explorer-nautilus-mac-thingie + self.common.log('UploadHistoryItemFile', 'open_folder', 'not implemented for Windows yet') + + +class UploadHistoryItem(HistoryItem): + def __init__(self, common, id, content_length): + super(UploadHistoryItem, self).__init__() + self.common = common + self.id = id + self.content_length = content_length + self.started = datetime.now() + + # Label + self.label = QtWidgets.QLabel(strings._('gui_upload_in_progress').format(self.started.strftime("%b %d, %I:%M%p"))) + + # Progress bar + self.progress_bar = QtWidgets.QProgressBar() + self.progress_bar.setTextVisible(True) + self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter) + self.progress_bar.setMinimum(0) + self.progress_bar.setValue(0) + self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) + + # This layout contains file widgets + self.files_layout = QtWidgets.QVBoxLayout() + self.files_layout.setContentsMargins(0, 0, 0, 0) + files_widget = QtWidgets.QWidget() + files_widget.setStyleSheet(self.common.css['receive_file']) + files_widget.setLayout(self.files_layout) + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label) + layout.addWidget(self.progress_bar) + layout.addWidget(files_widget) + layout.addStretch() + self.setLayout(layout) + + # We're also making a dictionary of file widgets, to make them easier to access + self.files = {} + + def update(self, data): + """ + Using the progress from Web, update the progress bar and file size labels + for each file + """ + if data['action'] == 'progress': + total_uploaded_bytes = 0 + for filename in data['progress']: + total_uploaded_bytes += data['progress'][filename]['uploaded_bytes'] + + # Update the progress bar + self.progress_bar.setMaximum(self.content_length) + self.progress_bar.setValue(total_uploaded_bytes) + + elapsed = datetime.now() - self.started + if elapsed.seconds < 10: + pb_fmt = strings._('gui_download_upload_progress_starting').format( + self.common.human_readable_filesize(total_uploaded_bytes)) + else: + estimated_time_remaining = self.common.estimated_time_remaining( + total_uploaded_bytes, + self.content_length, + self.started.timestamp()) + pb_fmt = strings._('gui_download_upload_progress_eta').format( + self.common.human_readable_filesize(total_uploaded_bytes), + estimated_time_remaining) + + # Using list(progress) to avoid "RuntimeError: dictionary changed size during iteration" + for filename in list(data['progress']): + # Add a new file if needed + if filename not in self.files: + self.files[filename] = UploadHistoryItemFile(self.common, filename) + self.files_layout.addWidget(self.files[filename]) + + # Update the file + self.files[filename].update(data['progress'][filename]['uploaded_bytes'], data['progress'][filename]['complete']) + + elif data['action'] == 'rename': + self.files[data['old_filename']].rename(data['new_filename']) + self.files[data['new_filename']] = self.files.pop(data['old_filename']) + + elif data['action'] == 'set_dir': + self.files[data['filename']].set_dir(data['dir']) + + elif data['action'] == 'finished': + # Hide the progress bar + self.progress_bar.hide() + + # Change the label + self.ended = self.started = datetime.now() + if self.started.year == self.ended.year and self.started.month == self.ended.month and self.started.day == self.ended.day: + if self.started.hour == self.ended.hour and self.started.minute == self.ended.minute: + text = strings._('gui_upload_finished').format( + self.started.strftime("%b %d, %I:%M%p") + ) + else: + text = strings._('gui_upload_finished_range').format( + self.started.strftime("%b %d, %I:%M%p"), + self.ended.strftime("%I:%M%p") + ) + else: + text = strings._('gui_upload_finished_range').format( + self.started.strftime("%b %d, %I:%M%p"), + self.ended.strftime("%b %d, %I:%M%p") + ) + self.label.setText(text) + + +class HistoryItemList(QtWidgets.QScrollArea): + """ + List of items + """ + def __init__(self, common): + super(HistoryItemList, self).__init__() + self.common = common + + self.items = {} + + # The layout that holds all of the items + self.items_layout = QtWidgets.QVBoxLayout() + self.items_layout.setContentsMargins(0, 0, 0, 0) + self.items_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + + # Wrapper layout that also contains a stretch + wrapper_layout = QtWidgets.QVBoxLayout() + wrapper_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + wrapper_layout.addLayout(self.items_layout) + wrapper_layout.addStretch() + + # The internal widget of the scroll area + widget = QtWidgets.QWidget() + widget.setLayout(wrapper_layout) + self.setWidget(widget) + self.setWidgetResizable(True) + + # Other scroll area settings + self.setBackgroundRole(QtGui.QPalette.Light) + self.verticalScrollBar().rangeChanged.connect(self.resizeScroll) + + def resizeScroll(self, minimum, maximum): + """ + Scroll to the bottom of the window when the range changes. + """ + self.verticalScrollBar().setValue(maximum) + + def add(self, id, item): + """ + Add a new item. Override this method. + """ + self.items[id] = item + self.items_layout.addWidget(item) + + def update(self, id, data): + """ + Update an item. Override this method. + """ + self.items[id].update(data) + + def cancel(self, id): + """ + Cancel an item. Override this method. + """ + self.items[id].cancel() + + def reset(self): + """ + Reset all items, emptying the list. Override this method. + """ + for item in self.items.values(): + self.items_layout.removeWidget(item) + item.close() + self.items = {} + + +class History(QtWidgets.QWidget): + """ + A history of what's happened so far in this mode. This contains an internal + object full of a scrollable list of items. + """ + def __init__(self, common, empty_image, empty_text, header_text): + super(History, self).__init__() + self.common = common + + self.setMinimumWidth(350) + + # In progress and completed counters + self.in_progress_count = 0 + self.completed_count = 0 + + # In progress and completed labels + self.in_progress_label = QtWidgets.QLabel() + self.in_progress_label.setStyleSheet(self.common.css['mode_info_label']) + self.completed_label = QtWidgets.QLabel() + self.completed_label.setStyleSheet(self.common.css['mode_info_label']) + + # Header + self.header_label = QtWidgets.QLabel(header_text) + self.header_label.setStyleSheet(self.common.css['downloads_uploads_label']) + clear_button = QtWidgets.QPushButton(strings._('gui_clear_history')) + clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) + clear_button.setFlat(True) + clear_button.clicked.connect(self.reset) + header_layout = QtWidgets.QHBoxLayout() + header_layout.addWidget(self.header_label) + header_layout.addStretch() + header_layout.addWidget(self.in_progress_label) + header_layout.addWidget(self.completed_label) + header_layout.addWidget(clear_button) + + # When there are no items + self.empty_image = QtWidgets.QLabel() + self.empty_image.setAlignment(QtCore.Qt.AlignCenter) + self.empty_image.setPixmap(empty_image) + self.empty_text = QtWidgets.QLabel(empty_text) + self.empty_text.setAlignment(QtCore.Qt.AlignCenter) + self.empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text']) + empty_layout = QtWidgets.QVBoxLayout() + empty_layout.addStretch() + empty_layout.addWidget(self.empty_image) + empty_layout.addWidget(self.empty_text) + empty_layout.addStretch() + self.empty = QtWidgets.QWidget() + self.empty.setStyleSheet(self.common.css['downloads_uploads_empty']) + self.empty.setLayout(empty_layout) + + # When there are items + self.item_list = HistoryItemList(self.common) + self.not_empty_layout = QtWidgets.QVBoxLayout() + self.not_empty_layout.addLayout(header_layout) + self.not_empty_layout.addWidget(self.item_list) + self.not_empty = QtWidgets.QWidget() + self.not_empty.setLayout(self.not_empty_layout) + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.empty) + layout.addWidget(self.not_empty) + self.setLayout(layout) + + # Reset once at the beginning + self.reset() + + def add(self, id, item): + """ + Add a new item. + """ + self.common.log('History', 'add', 'id: {}, item: {}'.format(id, item)) + + # Hide empty, show not empty + self.empty.hide() + self.not_empty.show() + + # Add it to the list + self.item_list.add(id, item) + + def update(self, id, data): + """ + Update an item. + """ + self.item_list.update(id, data) + + def cancel(self, id): + """ + Cancel an item. + """ + self.item_list.cancel(id) + + def reset(self): + """ + Reset all items. + """ + self.item_list.reset() + + # Hide not empty, show empty + self.not_empty.hide() + self.empty.show() + + # Reset counters + self.completed_count = 0 + self.in_progress_count = 0 + self.update_completed() + self.update_in_progress() + + def update_completed(self): + """ + Update the 'completed' widget. + """ + if self.completed_count == 0: + image = self.common.get_resource_path('images/share_completed_none.png') + else: + image = self.common.get_resource_path('images/share_completed.png') + self.completed_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.completed_count)) + self.completed_label.setToolTip(strings._('history_completed_tooltip').format(self.completed_count)) + + def update_in_progress(self): + """ + Update the 'in progress' widget. + """ + if self.in_progress_count == 0: + image = self.common.get_resource_path('images/share_in_progress_none.png') + else: + image = self.common.get_resource_path('images/share_in_progress.png') + self.in_progress_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count)) + self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count)) + + +class ToggleHistory(QtWidgets.QPushButton): + """ + Widget for toggling showing or hiding the history, as well as keeping track + of the indicator counter if it's hidden + """ + def __init__(self, common, current_mode, history_widget, icon, selected_icon): + super(ToggleHistory, self).__init__() + self.common = common + self.current_mode = current_mode + self.history_widget = history_widget + self.icon = icon + self.selected_icon = selected_icon + + # Toggle button + self.setDefault(False) + self.setFixedWidth(35) + self.setFixedHeight(30) + self.setFlat(True) + self.setIcon(icon) + self.clicked.connect(self.toggle_clicked) + + # Keep track of indicator + self.indicator_count = 0 + self.indicator_label = QtWidgets.QLabel(parent=self) + self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) + self.update_indicator() + + def update_indicator(self, increment=False): + """ + Update the display of the indicator count. If increment is True, then + only increment the counter if Downloads is hidden. + """ + if increment and not self.history_widget.isVisible(): + self.indicator_count += 1 + + self.indicator_label.setText("{}".format(self.indicator_count)) + + if self.indicator_count == 0: + self.indicator_label.hide() + else: + size = self.indicator_label.sizeHint() + self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) + self.indicator_label.show() + + def toggle_clicked(self): + """ + Toggle showing and hiding the history widget + """ + self.common.log('ToggleHistory', 'toggle_clicked') + + if self.history_widget.isVisible(): + self.history_widget.hide() + self.setIcon(self.icon) + self.setFlat(True) + else: + self.history_widget.show() + self.setIcon(self.selected_icon) + self.setFlat(False) + + # Reset the indicator count + self.indicator_count = 0 + self.update_indicator() diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py new file mode 100644 index 00000000..d6c0c351 --- /dev/null +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -0,0 +1,206 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com> + +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 <http://www.gnu.org/licenses/>. +""" +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings +from onionshare.web import Web + +from ..history import History, ToggleHistory, UploadHistoryItem +from .. import Mode + +class ReceiveMode(Mode): + """ + Parts of the main window UI for receiving files. + """ + def init(self): + """ + Custom initialization for ReceiveMode. + """ + # Create the Web object + self.web = Web(self.common, True, 'receive') + + # Server status + self.server_status.set_mode('receive') + self.server_status.server_started_finished.connect(self.update_primary_action) + self.server_status.server_stopped.connect(self.update_primary_action) + self.server_status.server_canceled.connect(self.update_primary_action) + + # Tell server_status about web, then update + self.server_status.web = self.web + self.server_status.update() + + # Upload history + self.history = History( + self.common, + QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/uploads_transparent.png'))), + strings._('gui_no_uploads'), + strings._('gui_uploads') + ) + self.history.hide() + + # Toggle history + self.toggle_history = ToggleHistory( + self.common, self, self.history, + QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')), + QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle_selected.png')) + ) + + # Receive mode warning + receive_warning = QtWidgets.QLabel(strings._('gui_receive_mode_warning')) + receive_warning.setMinimumHeight(80) + receive_warning.setWordWrap(True) + + # Top bar + top_bar_layout = QtWidgets.QHBoxLayout() + top_bar_layout.addStretch() + top_bar_layout.addWidget(self.toggle_history) + + # Main layout + self.main_layout = QtWidgets.QVBoxLayout() + self.main_layout.addLayout(top_bar_layout) + self.main_layout.addWidget(receive_warning) + self.main_layout.addWidget(self.primary_action) + self.main_layout.addStretch() + self.main_layout.addWidget(self.min_width_widget) + + # Wrapper layout + self.wrapper_layout = QtWidgets.QHBoxLayout() + self.wrapper_layout.addLayout(self.main_layout) + self.wrapper_layout.addWidget(self.history) + self.setLayout(self.wrapper_layout) + + def get_stop_server_shutdown_timeout_text(self): + """ + Return the string to put on the stop server button, if there's a shutdown timeout + """ + return strings._('gui_receive_stop_server_shutdown_timeout') + + def timeout_finished_should_stop_server(self): + """ + The shutdown timer expired, should we stop the server? Returns a bool + """ + # TODO: wait until the final upload is done before stoppign the server? + return True + + def start_server_custom(self): + """ + Starting the server. + """ + # Reset web counters + self.web.receive_mode.upload_count = 0 + self.web.error404_count = 0 + + # Hide and reset the uploads if we have previously shared + self.reset_info_counters() + + def start_server_step2_custom(self): + """ + Step 2 in starting the server. + """ + # Continue + self.starting_server_step3.emit() + self.start_server_finished.emit() + + def handle_tor_broke_custom(self): + """ + Connection to Tor broke. + """ + self.primary_action.hide() + + def handle_request_load(self, event): + """ + Handle REQUEST_LOAD event. + """ + self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_upload_page_loaded_message')) + + def handle_request_started(self, event): + """ + Handle REQUEST_STARTED event. + """ + item = UploadHistoryItem(self.common, event["data"]["id"], event["data"]["content_length"]) + self.history.add(event["data"]["id"], item) + self.toggle_history.update_indicator(True) + self.history.in_progress_count += 1 + self.history.update_in_progress() + + self.system_tray.showMessage(strings._('systray_upload_started_title'), strings._('systray_upload_started_message')) + + def handle_request_progress(self, event): + """ + Handle REQUEST_PROGRESS event. + """ + self.history.update(event["data"]["id"], { + 'action': 'progress', + 'progress': event["data"]["progress"] + }) + + def handle_request_close_server(self, event): + """ + Handle REQUEST_CLOSE_SERVER event. + """ + self.stop_server() + self.system_tray.showMessage(strings._('systray_close_server_title'), strings._('systray_close_server_message')) + + def handle_request_upload_file_renamed(self, event): + """ + Handle REQUEST_UPLOAD_FILE_RENAMED event. + """ + self.history.update(event["data"]["id"], { + 'action': 'rename', + 'old_filename': event["data"]["old_filename"], + 'new_filename': event["data"]["new_filename"] + }) + + def handle_request_upload_set_dir(self, event): + """ + Handle REQUEST_UPLOAD_SET_DIR event. + """ + self.history.update(event["data"]["id"], { + 'action': 'set_dir', + 'filename': event["data"]["filename"], + 'dir': event["data"]["dir"] + }) + + def handle_request_upload_finished(self, event): + """ + Handle REQUEST_UPLOAD_FINISHED event. + """ + self.history.update(event["data"]["id"], { + 'action': 'finished' + }) + self.history.completed_count += 1 + self.history.in_progress_count -= 1 + self.history.update_completed() + self.history.update_in_progress() + + def on_reload_settings(self): + """ + We should be ok to re-enable the 'Start Receive Mode' button now. + """ + self.primary_action.show() + + def reset_info_counters(self): + """ + Set the info counters back to zero. + """ + self.history.reset() + + def update_primary_action(self): + self.common.log('ReceiveMode', 'update_primary_action') diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 90fce49a..436d42f7 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -26,10 +26,11 @@ from onionshare.common import Common from onionshare.web import Web from .file_selection import FileSelection -from .downloads import Downloads from .threads import CompressThread -from ..mode import Mode -from ..widgets import Alert +from .. import Mode +from ..history import History, ToggleHistory, DownloadHistoryItem +from ...widgets import Alert + class ShareMode(Mode): """ @@ -70,33 +71,31 @@ class ShareMode(Mode): self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning']) self.filesize_warning.hide() - # Downloads - self.downloads = Downloads(self.common) - self.downloads_in_progress = 0 - self.downloads_completed = 0 + # Download history + self.history = History( + self.common, + QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/downloads_transparent.png'))), + strings._('gui_no_downloads'), + strings._('gui_downloads') + ) + self.history.hide() - # Information about share, and show downloads button + # Info label self.info_label = QtWidgets.QLabel() - self.info_label.setStyleSheet(self.common.css['mode_info_label']) - - self.info_in_progress_downloads_count = QtWidgets.QLabel() - self.info_in_progress_downloads_count.setStyleSheet(self.common.css['mode_info_label']) - - self.info_completed_downloads_count = QtWidgets.QLabel() - self.info_completed_downloads_count.setStyleSheet(self.common.css['mode_info_label']) - - self.update_downloads_completed() - self.update_downloads_in_progress() + self.info_label.hide() - self.info_layout = QtWidgets.QHBoxLayout() - self.info_layout.addWidget(self.info_label) - self.info_layout.addStretch() - self.info_layout.addWidget(self.info_in_progress_downloads_count) - self.info_layout.addWidget(self.info_completed_downloads_count) + # Toggle history + self.toggle_history = ToggleHistory( + self.common, self, self.history, + QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')), + QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle_selected.png')) + ) - self.info_widget = QtWidgets.QWidget() - self.info_widget.setLayout(self.info_layout) - self.info_widget.hide() + # Top bar + top_bar_layout = QtWidgets.QHBoxLayout() + top_bar_layout.addWidget(self.info_label) + top_bar_layout.addStretch() + top_bar_layout.addWidget(self.toggle_history) # Primary action layout self.primary_action_layout.addWidget(self.filesize_warning) @@ -106,10 +105,18 @@ class ShareMode(Mode): # Status bar, zip progress bar self._zip_progress_bar = None - # Layout - self.layout.insertLayout(0, self.file_selection) - self.layout.insertWidget(0, self.info_widget) - self.horizontal_layout_wrapper.addWidget(self.downloads) + # Main layout + self.main_layout = QtWidgets.QVBoxLayout() + self.main_layout.addLayout(top_bar_layout) + self.main_layout.addLayout(self.file_selection) + self.main_layout.addWidget(self.primary_action) + self.main_layout.addWidget(self.min_width_widget) + + # Wrapper layout + self.wrapper_layout = QtWidgets.QHBoxLayout() + self.wrapper_layout.addLayout(self.main_layout) + self.wrapper_layout.addWidget(self.history) + self.setLayout(self.wrapper_layout) # Always start with focus on file selection self.file_selection.setFocus() @@ -118,7 +125,7 @@ class ShareMode(Mode): """ Return the string to put on the stop server button, if there's a shutdown timeout """ - return strings._('gui_share_stop_server_shutdown_timeout', True) + return strings._('gui_share_stop_server_shutdown_timeout') def timeout_finished_should_stop_server(self): """ @@ -127,11 +134,11 @@ class ShareMode(Mode): # If there were no attempts to download the share, or all downloads are done, we can stop if self.web.share_mode.download_count == 0 or self.web.done: self.server_status.stop_server() - self.server_status_label.setText(strings._('close_on_timeout', True)) + self.server_status_label.setText(strings._('close_on_timeout')) return True # A download is probably still running - hold off on stopping the share else: - self.server_status_label.setText(strings._('timeout_download_still_running', True)) + self.server_status_label.setText(strings._('timeout_download_still_running')) return False def start_server_custom(self): @@ -178,7 +185,7 @@ class ShareMode(Mode): # Warn about sending large files over Tor if self.web.share_mode.download_filesize >= 157286400: # 150mb - self.filesize_warning.setText(strings._("large_filesize", True)) + self.filesize_warning.setText(strings._("large_filesize")) self.filesize_warning.show() def start_server_error_custom(self): @@ -199,9 +206,9 @@ class ShareMode(Mode): self._zip_progress_bar = None self.filesize_warning.hide() - self.downloads_in_progress = 0 - self.downloads_completed = 0 - self.update_downloads_in_progress() + self.history.in_progress_count = 0 + self.history.completed_count = 0 + self.history.update_in_progress() self.file_selection.file_list.adjustSize() def cancel_server_custom(self): @@ -209,7 +216,7 @@ class ShareMode(Mode): Stop the compression thread on cancel """ if self.compress_thread: - self.common.log('OnionShareGui', 'cancel_server: quitting compress thread') + self.common.log('ShareMode', 'cancel_server: quitting compress thread') self.compress_thread.quit() def handle_tor_broke_custom(self): @@ -217,13 +224,12 @@ class ShareMode(Mode): Connection to Tor broke. """ self.primary_action.hide() - self.info_widget.hide() def handle_request_load(self, event): """ Handle REQUEST_LOAD event. """ - self.system_tray.showMessage(strings._('systray_page_loaded_title', True), strings._('systray_download_page_loaded_message', True)) + self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_download_page_loaded_message')) def handle_request_started(self, event): """ @@ -233,50 +239,52 @@ class ShareMode(Mode): filesize = self.web.share_mode.gzip_filesize else: filesize = self.web.share_mode.download_filesize - self.downloads.add(event["data"]["id"], filesize) - self.downloads_in_progress += 1 - self.update_downloads_in_progress() - self.system_tray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) + item = DownloadHistoryItem(self.common, event["data"]["id"], filesize) + self.history.add(event["data"]["id"], item) + self.toggle_history.update_indicator(True) + self.history.in_progress_count += 1 + self.history.update_in_progress() + + self.system_tray.showMessage(strings._('systray_download_started_title'), strings._('systray_download_started_message')) def handle_request_progress(self, event): """ Handle REQUEST_PROGRESS event. """ - self.downloads.update(event["data"]["id"], event["data"]["bytes"]) + self.history.update(event["data"]["id"], event["data"]["bytes"]) # Is the download complete? if event["data"]["bytes"] == self.web.share_mode.filesize: - self.system_tray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) + self.system_tray.showMessage(strings._('systray_download_completed_title'), strings._('systray_download_completed_message')) - # Update the total 'completed downloads' info - self.downloads_completed += 1 - self.update_downloads_completed() - # Update the 'in progress downloads' info - self.downloads_in_progress -= 1 - self.update_downloads_in_progress() + # Update completed and in progress labels + self.history.completed_count += 1 + self.history.in_progress_count -= 1 + self.history.update_completed() + self.history.update_in_progress() # Close on finish? if self.common.settings.get('close_after_first_download'): self.server_status.stop_server() self.status_bar.clearMessage() - self.server_status_label.setText(strings._('closing_automatically', True)) + self.server_status_label.setText(strings._('closing_automatically')) else: if self.server_status.status == self.server_status.STATUS_STOPPED: - self.downloads.cancel(event["data"]["id"]) - self.downloads_in_progress = 0 - self.update_downloads_in_progress() + self.history.cancel(event["data"]["id"]) + self.history.in_progress_count = 0 + self.history.update_in_progress() def handle_request_canceled(self, event): """ Handle REQUEST_CANCELED event. """ - self.downloads.cancel(event["data"]["id"]) + self.history.cancel(event["data"]["id"]) - # Update the 'in progress downloads' info - self.downloads_in_progress -= 1 - self.update_downloads_in_progress() - self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) + # Update in progress count + self.history.in_progress_count -= 1 + self.history.update_in_progress() + self.system_tray.showMessage(strings._('systray_download_canceled_title'), strings._('systray_download_canceled_message')) def on_reload_settings(self): """ @@ -285,14 +293,16 @@ class ShareMode(Mode): """ if self.server_status.file_selection.get_num_files() > 0: self.primary_action.show() - self.info_widget.show() + self.info_label.show() def update_primary_action(self): + self.common.log('ShareMode', 'update_primary_action') + # Show or hide primary action layout file_count = self.file_selection.file_list.count() if file_count > 0: self.primary_action.show() - self.info_widget.show() + self.info_label.show() # Update the file count in the info label total_size_bytes = 0 @@ -302,48 +312,19 @@ class ShareMode(Mode): total_size_readable = self.common.human_readable_filesize(total_size_bytes) if file_count > 1: - self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable)) + self.info_label.setText(strings._('gui_file_info').format(file_count, total_size_readable)) else: - self.info_label.setText(strings._('gui_file_info_single', True).format(file_count, total_size_readable)) + self.info_label.setText(strings._('gui_file_info_single').format(file_count, total_size_readable)) else: self.primary_action.hide() - self.info_widget.hide() - - # Resize window - self.adjustSize() + self.info_label.hide() def reset_info_counters(self): """ Set the info counters back to zero. """ - self.downloads_completed = 0 - self.downloads_in_progress = 0 - self.update_downloads_completed() - self.update_downloads_in_progress() - self.downloads.reset() - - def update_downloads_completed(self): - """ - Update the 'Downloads completed' info widget. - """ - if self.downloads_completed == 0: - image = self.common.get_resource_path('images/share_completed_none.png') - else: - image = self.common.get_resource_path('images/share_completed.png') - self.info_completed_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(image, self.downloads_completed)) - self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(self.downloads_completed)) - - def update_downloads_in_progress(self): - """ - Update the 'Downloads in progress' info widget. - """ - if self.downloads_in_progress == 0: - image = self.common.get_resource_path('images/share_in_progress_none.png') - else: - image = self.common.get_resource_path('images/share_in_progress.png') - self.info_in_progress_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(image, self.downloads_in_progress)) - self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.downloads_in_progress)) + self.history.reset() @staticmethod def _compute_total_size(filenames): diff --git a/onionshare_gui/share_mode/file_selection.py b/onionshare_gui/mode/share_mode/file_selection.py index 628ad5ef..ec3b5ea5 100644 --- a/onionshare_gui/share_mode/file_selection.py +++ b/onionshare_gui/mode/share_mode/file_selection.py @@ -22,7 +22,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings -from ..widgets import Alert, AddFileDialog +from ...widgets import Alert, AddFileDialog class DropHereLabel(QtWidgets.QLabel): """ @@ -41,7 +41,7 @@ class DropHereLabel(QtWidgets.QLabel): if image: self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png')))) else: - self.setText(strings._('gui_drag_and_drop', True)) + self.setText(strings._('gui_drag_and_drop')) self.setStyleSheet(self.common.css['share_file_selection_drop_here_label']) self.hide() @@ -65,7 +65,7 @@ class DropCountLabel(QtWidgets.QLabel): self.setAcceptDrops(True) self.setAlignment(QtCore.Qt.AlignCenter) - self.setText(strings._('gui_drag_and_drop', True)) + self.setText(strings._('gui_drag_and_drop')) self.setStyleSheet(self.common.css['share_file_selection_drop_count_label']) self.hide() @@ -89,7 +89,7 @@ class FileList(QtWidgets.QListWidget): self.setAcceptDrops(True) self.setIconSize(QtCore.QSize(32, 32)) self.setSortingEnabled(True) - self.setMinimumHeight(205) + self.setMinimumHeight(160) self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) self.drop_here_image = DropHereLabel(self.common, self, True) self.drop_here_text = DropHereLabel(self.common, self, False) @@ -216,7 +216,7 @@ class FileList(QtWidgets.QListWidget): if filename not in filenames: if not os.access(filename, os.R_OK): - Alert(self.common, strings._("not_a_readable_file", True).format(filename)) + Alert(self.common, strings._("not_a_readable_file").format(filename)) return fileinfo = QtCore.QFileInfo(filename) @@ -261,6 +261,7 @@ class FileList(QtWidgets.QListWidget): # Item info widget, with a white background item_info_layout = QtWidgets.QHBoxLayout() + item_info_layout.setContentsMargins(0, 0, 0, 0) item_info_layout.addWidget(item_size) item_info_layout.addWidget(item.item_button) item_info = QtWidgets.QWidget() @@ -301,9 +302,9 @@ class FileSelection(QtWidgets.QVBoxLayout): self.file_list.files_updated.connect(self.update) # Buttons - self.add_button = QtWidgets.QPushButton(strings._('gui_add', True)) + self.add_button = QtWidgets.QPushButton(strings._('gui_add')) self.add_button.clicked.connect(self.add) - self.delete_button = QtWidgets.QPushButton(strings._('gui_delete', True)) + self.delete_button = QtWidgets.QPushButton(strings._('gui_delete')) self.delete_button.clicked.connect(self.delete) button_layout = QtWidgets.QHBoxLayout() button_layout.addStretch() @@ -340,7 +341,7 @@ class FileSelection(QtWidgets.QVBoxLayout): """ Add button clicked. """ - file_dialog = AddFileDialog(self.common, caption=strings._('gui_choose_items', True)) + file_dialog = AddFileDialog(self.common, caption=strings._('gui_choose_items')) if file_dialog.exec_() == QtWidgets.QDialog.Accepted: for filename in file_dialog.selectedFiles(): self.file_list.add_file(filename) diff --git a/onionshare_gui/share_mode/threads.py b/onionshare_gui/mode/share_mode/threads.py index d6022746..24e2c242 100644 --- a/onionshare_gui/share_mode/threads.py +++ b/onionshare_gui/mode/share_mode/threads.py @@ -56,5 +56,8 @@ class CompressThread(QtCore.QThread): # Let the Web and ZipWriter objects know that we're canceling compression early self.mode.web.cancel_compression = True - if self.mode.web.zip_writer: + try: self.mode.web.zip_writer.cancel_compression = True + except AttributeError: + # we never made it as far as creating a ZipWriter object + pass diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 83f3a7e0..eab3261e 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -23,8 +23,8 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings from onionshare.web import Web -from .share_mode import ShareMode -from .receive_mode import ReceiveMode +from .mode.share_mode import ShareMode +from .mode.receive_mode import ReceiveMode from .tor_connection_dialog import TorConnectionDialog from .settings_dialog import SettingsDialog @@ -45,6 +45,8 @@ class OnionShareGui(QtWidgets.QMainWindow): self.common = common self.common.log('OnionShareGui', '__init__') + self.setMinimumWidth(820) + self.setMinimumHeight(660) self.onion = onion self.qtapp = qtapp @@ -55,19 +57,19 @@ class OnionShareGui(QtWidgets.QMainWindow): self.setWindowTitle('OnionShare') self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) - self.setMinimumWidth(850) - # Load settings + # Load settings, if a custom config was passed in self.config = config - self.common.load_settings(self.config) + if self.config: + self.common.load_settings(self.config) # System tray menu = QtWidgets.QMenu() - self.settings_action = menu.addAction(strings._('gui_settings_window_title', True)) + self.settings_action = menu.addAction(strings._('gui_settings_window_title')) self.settings_action.triggered.connect(self.open_settings) - help_action = menu.addAction(strings._('gui_settings_button_help', True)) + help_action = menu.addAction(strings._('gui_settings_button_help')) help_action.triggered.connect(SettingsDialog.help_clicked) - exit_action = menu.addAction(strings._('systray_menu_exit', True)) + exit_action = menu.addAction(strings._('systray_menu_exit')) exit_action.triggered.connect(self.close) self.system_tray = QtWidgets.QSystemTrayIcon(self) @@ -80,10 +82,10 @@ class OnionShareGui(QtWidgets.QMainWindow): self.system_tray.show() # Mode switcher, to switch between share files and receive files - self.share_mode_button = QtWidgets.QPushButton(strings._('gui_mode_share_button', True)); + self.share_mode_button = QtWidgets.QPushButton(strings._('gui_mode_share_button')); self.share_mode_button.setFixedHeight(50) self.share_mode_button.clicked.connect(self.share_mode_clicked) - self.receive_mode_button = QtWidgets.QPushButton(strings._('gui_mode_receive_button', True)); + self.receive_mode_button = QtWidgets.QPushButton(strings._('gui_mode_receive_button')); self.receive_mode_button.setFixedHeight(50) self.receive_mode_button.clicked.connect(self.receive_mode_clicked) self.settings_button = QtWidgets.QPushButton() @@ -153,7 +155,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # Layouts contents_layout = QtWidgets.QVBoxLayout() - contents_layout.setContentsMargins(10, 10, 10, 10) + contents_layout.setContentsMargins(10, 0, 10, 0) contents_layout.addWidget(self.receive_mode) contents_layout.addWidget(self.share_mode) @@ -194,8 +196,8 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) - self.share_mode.show() self.receive_mode.hide() + self.share_mode.show() else: self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) @@ -223,24 +225,24 @@ class OnionShareGui(QtWidgets.QMainWindow): # Share mode if self.share_mode.server_status.status == ServerStatus.STATUS_STOPPED: self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) - self.server_status_label.setText(strings._('gui_status_indicator_share_stopped', True)) + self.server_status_label.setText(strings._('gui_status_indicator_share_stopped')) elif self.share_mode.server_status.status == ServerStatus.STATUS_WORKING: self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) - self.server_status_label.setText(strings._('gui_status_indicator_share_working', True)) + self.server_status_label.setText(strings._('gui_status_indicator_share_working')) elif self.share_mode.server_status.status == ServerStatus.STATUS_STARTED: self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) - self.server_status_label.setText(strings._('gui_status_indicator_share_started', True)) + self.server_status_label.setText(strings._('gui_status_indicator_share_started')) else: # Receive mode if self.receive_mode.server_status.status == ServerStatus.STATUS_STOPPED: self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) - self.server_status_label.setText(strings._('gui_status_indicator_receive_stopped', True)) + self.server_status_label.setText(strings._('gui_status_indicator_receive_stopped')) elif self.receive_mode.server_status.status == ServerStatus.STATUS_WORKING: self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) - self.server_status_label.setText(strings._('gui_status_indicator_receive_working', True)) + self.server_status_label.setText(strings._('gui_status_indicator_receive_working')) elif self.receive_mode.server_status.status == ServerStatus.STATUS_STARTED: self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) - self.server_status_label.setText(strings._('gui_status_indicator_receive_started', True)) + self.server_status_label.setText(strings._('gui_status_indicator_receive_started')) def stop_server_finished(self): # When the server stopped, cleanup the ephemeral onion service @@ -254,9 +256,9 @@ class OnionShareGui(QtWidgets.QMainWindow): self.common.log('OnionShareGui', '_tor_connection_canceled') def ask(): - a = Alert(self.common, strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) - settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings', True)) - quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit', True)) + a = Alert(self.common, strings._('gui_tor_connection_ask'), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) + settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings')) + quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit')) a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole) a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole) a.setDefaultButton(settings_button) @@ -327,7 +329,7 @@ class OnionShareGui(QtWidgets.QMainWindow): if self.common.platform == 'Windows' or self.common.platform == 'Darwin': if self.common.settings.get('use_autoupdate'): def update_available(update_url, installed_version, latest_version): - Alert(self.common, strings._("update_available", True).format(update_url, installed_version, latest_version)) + Alert(self.common, strings._("update_available").format(update_url, installed_version, latest_version)) self.update_thread = UpdateThread(self.common, self.onion, self.config) self.update_thread.update_available.connect(update_available) @@ -344,8 +346,8 @@ class OnionShareGui(QtWidgets.QMainWindow): # Have we lost connection to Tor somehow? if not self.onion.is_authenticated(): self.timer.stop() - self.status_bar.showMessage(strings._('gui_tor_connection_lost', True)) - self.system_tray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) + self.status_bar.showMessage(strings._('gui_tor_connection_lost')) + self.system_tray.showMessage(strings._('gui_tor_connection_lost'), strings._('gui_tor_connection_error_settings')) self.share_mode.handle_tor_broke() self.receive_mode.handle_tor_broke() @@ -388,18 +390,18 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == Web.REQUEST_UPLOAD_FILE_RENAMED: mode.handle_request_upload_file_renamed(event) + elif event["type"] == Web.REQUEST_UPLOAD_SET_DIR: + mode.handle_request_upload_set_dir(event) + elif event["type"] == Web.REQUEST_UPLOAD_FINISHED: mode.handle_request_upload_finished(event) if event["type"] == Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE: - Alert(self.common, strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) - - if event["type"] == Web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE: - Alert(self.common, strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) + Alert(self.common, strings._('error_cannot_create_downloads_dir').format(event["data"]["receive_mode_dir"])) if event["type"] == Web.REQUEST_OTHER: if event["path"] != '/favicon.ico' and event["path"] != "/{}/shutdown".format(mode.web.shutdown_slug): - self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.error404_count, strings._('other_page_loaded', True), event["path"])) + self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.error404_count, strings._('other_page_loaded'), event["path"])) mode.timer_callback() @@ -408,14 +410,14 @@ class OnionShareGui(QtWidgets.QMainWindow): When the URL gets copied to the clipboard, display this in the status bar. """ self.common.log('OnionShareGui', 'copy_url') - self.system_tray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True)) + self.system_tray.showMessage(strings._('gui_copied_url_title'), strings._('gui_copied_url')) def copy_hidservauth(self): """ When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. """ self.common.log('OnionShareGui', 'copy_hidservauth') - self.system_tray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True)) + self.system_tray.showMessage(strings._('gui_copied_hidservauth_title'), strings._('gui_copied_hidservauth')) def clear_message(self): """ @@ -453,14 +455,14 @@ class OnionShareGui(QtWidgets.QMainWindow): if server_status.status != server_status.STATUS_STOPPED: self.common.log('OnionShareGui', 'closeEvent, opening warning dialog') dialog = QtWidgets.QMessageBox() - dialog.setWindowTitle(strings._('gui_quit_title', True)) + dialog.setWindowTitle(strings._('gui_quit_title')) if self.mode == OnionShareGui.MODE_SHARE: - dialog.setText(strings._('gui_share_quit_warning', True)) + dialog.setText(strings._('gui_share_quit_warning')) else: - dialog.setText(strings._('gui_receive_quit_warning', True)) + dialog.setText(strings._('gui_receive_quit_warning')) dialog.setIcon(QtWidgets.QMessageBox.Critical) - quit_button = dialog.addButton(strings._('gui_quit_warning_quit', True), QtWidgets.QMessageBox.YesRole) - dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit', True), QtWidgets.QMessageBox.NoRole) + quit_button = dialog.addButton(strings._('gui_quit_warning_quit'), QtWidgets.QMessageBox.YesRole) + dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit'), QtWidgets.QMessageBox.NoRole) dialog.setDefaultButton(dont_quit_button) reply = dialog.exec_() diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py deleted file mode 100644 index c10622e4..00000000 --- a/onionshare_gui/receive_mode/__init__.py +++ /dev/null @@ -1,236 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com> - -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 <http://www.gnu.org/licenses/>. -""" -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings -from onionshare.web import Web - -from .uploads import Uploads -from ..mode import Mode - -class ReceiveMode(Mode): - """ - Parts of the main window UI for receiving files. - """ - def init(self): - """ - Custom initialization for ReceiveMode. - """ - # Create the Web object - self.web = Web(self.common, True, 'receive') - - # Server status - self.server_status.set_mode('receive') - self.server_status.server_started_finished.connect(self.update_primary_action) - self.server_status.server_stopped.connect(self.update_primary_action) - self.server_status.server_canceled.connect(self.update_primary_action) - - # Tell server_status about web, then update - self.server_status.web = self.web - self.server_status.update() - - # Uploads - self.uploads = Uploads(self.common) - self.uploads_in_progress = 0 - self.uploads_completed = 0 - self.new_upload = False # For scrolling to the bottom of the uploads list - self.can_stop_server = False # for communicating to the auto-stop timer - - # Information about share, and show uploads button - self.info_in_progress_uploads_count = QtWidgets.QLabel() - self.info_in_progress_uploads_count.setStyleSheet(self.common.css['mode_info_label']) - - self.info_completed_uploads_count = QtWidgets.QLabel() - self.info_completed_uploads_count.setStyleSheet(self.common.css['mode_info_label']) - - self.update_uploads_completed() - self.update_uploads_in_progress() - - self.info_layout = QtWidgets.QHBoxLayout() - self.info_layout.addStretch() - self.info_layout.addWidget(self.info_in_progress_uploads_count) - self.info_layout.addWidget(self.info_completed_uploads_count) - - self.info_widget = QtWidgets.QWidget() - self.info_widget.setLayout(self.info_layout) - self.info_widget.hide() - - # Receive mode info - self.receive_info = QtWidgets.QLabel(strings._('gui_receive_mode_warning', True)) - self.receive_info.setMinimumHeight(80) - self.receive_info.setWordWrap(True) - - # Layout - self.layout.insertWidget(0, self.receive_info) - self.layout.insertWidget(0, self.info_widget) - self.layout.addStretch() - self.horizontal_layout_wrapper.addWidget(self.uploads) - - def get_stop_server_shutdown_timeout_text(self): - """ - Return the string to put on the stop server button, if there's a shutdown timeout - """ - return strings._('gui_receive_stop_server_shutdown_timeout', True) - - def timeout_finished_should_stop_server(self): - """ - The shutdown timer expired, should we stop the server? Returns a bool - """ - # If there were no attempts to upload files, or all uploads are done, we can stop - if self.web.receive_mode.upload_count == 0 or self.can_stop_server: - self.server_status.stop_server() - self.server_status_label.setText(strings._('close_on_timeout', True)) - return True - # An upload is probably still running - hold off on stopping the share, but block new shares. - else: - self.server_status_label.setText(strings._('timeout_upload_still_running', True)) - self.web.receive_mode.can_upload = False - return False - - return True - - def start_server_custom(self): - """ - Starting the server. - """ - # Reset web counters - self.can_stop_server = False - self.web.receive_mode.upload_count = 0 - self.web.receive_mode.can_upload = True - self.web.error404_count = 0 - - # Hide and reset the uploads if we have previously shared - self.reset_info_counters() - - def start_server_step2_custom(self): - """ - Step 2 in starting the server. - """ - # Continue - self.starting_server_step3.emit() - self.start_server_finished.emit() - - def handle_tor_broke_custom(self): - """ - Connection to Tor broke. - """ - self.primary_action.hide() - self.info_widget.hide() - - def handle_request_load(self, event): - """ - Handle REQUEST_LOAD event. - """ - self.system_tray.showMessage(strings._('systray_page_loaded_title', True), strings._('systray_upload_page_loaded_message', True)) - - def handle_request_started(self, event): - """ - Handle REQUEST_STARTED event. - """ - self.uploads.add(event["data"]["id"], event["data"]["content_length"]) - self.uploads_in_progress += 1 - self.update_uploads_in_progress() - self.can_stop_server = False - - self.system_tray.showMessage(strings._('systray_upload_started_title', True), strings._('systray_upload_started_message', True)) - - def handle_request_progress(self, event): - """ - Handle REQUEST_PROGRESS event. - """ - self.uploads.update(event["data"]["id"], event["data"]["progress"]) - - def handle_request_close_server(self, event): - """ - Handle REQUEST_CLOSE_SERVER event. - """ - self.stop_server() - self.system_tray.showMessage(strings._('systray_close_server_title', True), strings._('systray_close_server_message', True)) - - def handle_request_upload_file_renamed(self, event): - """ - Handle REQUEST_UPLOAD_FILE_RENAMED event. - """ - self.uploads.rename(event["data"]["id"], event["data"]["old_filename"], event["data"]["new_filename"]) - - def handle_request_upload_finished(self, event): - """ - Handle REQUEST_UPLOAD_FINISHED event. - """ - self.uploads.finished(event["data"]["id"]) - # Update the total 'completed uploads' info - self.uploads_completed += 1 - self.update_uploads_completed() - # Update the 'in progress uploads' info - self.uploads_in_progress -= 1 - self.update_uploads_in_progress() - if self.uploads_in_progress == 0: - self.can_stop_server = True - - def on_reload_settings(self): - """ - We should be ok to re-enable the 'Start Receive Mode' button now. - """ - self.primary_action.show() - self.info_widget.show() - - def reset_info_counters(self): - """ - Set the info counters back to zero. - """ - self.uploads_completed = 0 - self.uploads_in_progress = 0 - self.update_uploads_completed() - self.update_uploads_in_progress() - self.uploads.reset() - - def update_uploads_completed(self): - """ - Update the 'Uploads completed' info widget. - """ - if self.uploads_completed == 0: - image = self.common.get_resource_path('images/share_completed_none.png') - else: - image = self.common.get_resource_path('images/share_completed.png') - self.info_completed_uploads_count.setText('<img src="{0:s}" /> {1:d}'.format(image, self.uploads_completed)) - self.info_completed_uploads_count.setToolTip(strings._('info_completed_uploads_tooltip', True).format(self.uploads_completed)) - - def update_uploads_in_progress(self): - """ - Update the 'Uploads in progress' info widget. - """ - if self.uploads_in_progress == 0: - image = self.common.get_resource_path('images/share_in_progress_none.png') - else: - image = self.common.get_resource_path('images/share_in_progress.png') - self.info_in_progress_uploads_count.setText('<img src="{0:s}" /> {1:d}'.format(image, self.uploads_in_progress)) - self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_uploads_tooltip', True).format(self.uploads_in_progress)) - - def update_primary_action(self): - self.common.log('ReceiveMode', 'update_primary_action') - - # Show the info widget when the server is active - if self.server_status.status == self.server_status.STATUS_STARTED: - self.info_widget.show() - else: - self.info_widget.hide() - - # Resize window - self.adjustSize() diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 48574cc7..68c94b1c 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -35,6 +35,7 @@ class File(QtWidgets.QWidget): self.common.log('File', '__init__', 'filename: {}'.format(filename)) self.filename = filename + self.dir = None self.started = datetime.now() # Filename label @@ -71,6 +72,9 @@ class File(QtWidgets.QWidget): if complete: self.folder_button.show() + def set_dir(self, dir): + self.dir = dir + def rename(self, new_filename): self.filename = new_filename self.filename_label.setText(self.filename) @@ -81,7 +85,10 @@ class File(QtWidgets.QWidget): """ self.common.log('File', 'open_folder') - abs_filename = os.path.join(self.common.settings.get('downloads_dir'), self.filename) + if not self.dir: + return + + abs_filename = os.path.join(self.dir, self.filename) # Linux if self.common.platform == 'Linux' or self.common.platform == 'BSD': @@ -113,7 +120,7 @@ class Upload(QtWidgets.QWidget): self.started = datetime.now() # Label - self.label = QtWidgets.QLabel(strings._('gui_upload_in_progress', True).format(self.started.strftime("%b %d, %I:%M%p"))) + self.label = QtWidgets.QLabel(strings._('gui_upload_in_progress').format(self.started.strftime("%b %d, %I:%M%p"))) # Progress bar self.progress_bar = QtWidgets.QProgressBar() @@ -182,6 +189,9 @@ class Upload(QtWidgets.QWidget): self.files[old_filename].rename(new_filename) self.files[new_filename] = self.files.pop(old_filename) + def set_dir(self, filename, dir): + self.files[filename].set_dir(dir) + def finished(self): # Hide the progress bar self.progress_bar.hide() @@ -190,16 +200,16 @@ class Upload(QtWidgets.QWidget): self.ended = self.started = datetime.now() if self.started.year == self.ended.year and self.started.month == self.ended.month and self.started.day == self.ended.day: if self.started.hour == self.ended.hour and self.started.minute == self.ended.minute: - text = strings._('gui_upload_finished', True).format( + text = strings._('gui_upload_finished').format( self.started.strftime("%b %d, %I:%M%p") ) else: - text = strings._('gui_upload_finished_range', True).format( + text = strings._('gui_upload_finished_range').format( self.started.strftime("%b %d, %I:%M%p"), self.ended.strftime("%I:%M%p") ) else: - text = strings._('gui_upload_finished_range', True).format( + text = strings._('gui_upload_finished_range').format( self.started.strftime("%b %d, %I:%M%p"), self.ended.strftime("%b %d, %I:%M%p") ) @@ -220,7 +230,7 @@ class Uploads(QtWidgets.QScrollArea): self.uploads = {} - self.setWindowTitle(strings._('gui_uploads', True)) + self.setWindowTitle(strings._('gui_uploads')) self.setWidgetResizable(True) self.setMinimumHeight(150) self.setMinimumWidth(350) @@ -229,10 +239,10 @@ class Uploads(QtWidgets.QScrollArea): self.vbar = self.verticalScrollBar() self.vbar.rangeChanged.connect(self.resizeScroll) - uploads_label = QtWidgets.QLabel(strings._('gui_uploads', True)) + uploads_label = QtWidgets.QLabel(strings._('gui_uploads')) uploads_label.setStyleSheet(self.common.css['downloads_uploads_label']) - self.no_uploads_label = QtWidgets.QLabel(strings._('gui_no_uploads', True)) - self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) + self.no_uploads_label = QtWidgets.QLabel(strings._('gui_no_uploads')) + self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history')) self.clear_history_button.clicked.connect(self.reset) self.clear_history_button.hide() diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 32135ca4..e34a3d16 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -61,7 +61,7 @@ class ServerStatus(QtWidgets.QWidget): self.resizeEvent(None) # Shutdown timeout layout - self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) + self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout')) self.shutdown_timeout = QtWidgets.QDateTimeEdit() self.shutdown_timeout.setDisplayFormat("hh:mm A MMM d, yy") if self.local_only: @@ -90,22 +90,22 @@ class ServerStatus(QtWidgets.QWidget): self.server_button.clicked.connect(self.server_button_clicked) # URL layout - url_font = QtGui.QFont() + url_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont) self.url_description = QtWidgets.QLabel() self.url_description.setWordWrap(True) self.url_description.setMinimumHeight(50) self.url = QtWidgets.QLabel() self.url.setFont(url_font) self.url.setWordWrap(True) - self.url.setMinimumHeight(65) self.url.setMinimumSize(self.url.sizeHint()) self.url.setStyleSheet(self.common.css['server_status_url']) - self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url', True)) + self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url')) self.copy_url_button.setFlat(True) self.copy_url_button.setStyleSheet(self.common.css['server_status_url_buttons']) + self.copy_url_button.setMinimumHeight(65) self.copy_url_button.clicked.connect(self.copy_url) - self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True)) + self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth')) self.copy_hidservauth_button.setFlat(True) self.copy_hidservauth_button.setStyleSheet(self.common.css['server_status_url_buttons']) self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth) @@ -142,12 +142,12 @@ class ServerStatus(QtWidgets.QWidget): When the widget is resized, try and adjust the display of a v3 onion URL. """ try: - self.get_url() + # Wrap the URL label url_length=len(self.get_url()) if url_length > 60: width = self.frameGeometry().width() if width < 530: - wrapped_onion_url = textwrap.fill(self.get_url(), 50) + wrapped_onion_url = textwrap.fill(self.get_url(), 46) self.url.setText(wrapped_onion_url) else: self.url.setText(self.get_url()) @@ -174,21 +174,21 @@ class ServerStatus(QtWidgets.QWidget): info_image = self.common.get_resource_path('images/info.png') if self.mode == ServerStatus.MODE_SHARE: - self.url_description.setText(strings._('gui_share_url_description', True).format(info_image)) + self.url_description.setText(strings._('gui_share_url_description').format(info_image)) else: - self.url_description.setText(strings._('gui_receive_url_description', True).format(info_image)) + self.url_description.setText(strings._('gui_receive_url_description').format(info_image)) # Show a Tool Tip explaining the lifecycle of this URL if self.common.settings.get('save_private_key'): if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'): - self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent', True)) + self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent')) else: - self.url_description.setToolTip(strings._('gui_url_label_persistent', True)) + self.url_description.setToolTip(strings._('gui_url_label_persistent')) else: if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'): - self.url_description.setToolTip(strings._('gui_url_label_onetime', True)) + self.url_description.setToolTip(strings._('gui_url_label_onetime')) else: - self.url_description.setToolTip(strings._('gui_url_label_stay_open', True)) + self.url_description.setToolTip(strings._('gui_url_label_stay_open')) self.url.setText(self.get_url()) self.url.show() @@ -223,9 +223,9 @@ class ServerStatus(QtWidgets.QWidget): self.server_button.setStyleSheet(self.common.css['server_status_button_stopped']) self.server_button.setEnabled(True) if self.mode == ServerStatus.MODE_SHARE: - self.server_button.setText(strings._('gui_share_start_server', True)) + self.server_button.setText(strings._('gui_share_start_server')) else: - self.server_button.setText(strings._('gui_receive_start_server', True)) + self.server_button.setText(strings._('gui_receive_start_server')) self.server_button.setToolTip('') if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.show() @@ -233,15 +233,15 @@ class ServerStatus(QtWidgets.QWidget): self.server_button.setStyleSheet(self.common.css['server_status_button_started']) self.server_button.setEnabled(True) if self.mode == ServerStatus.MODE_SHARE: - self.server_button.setText(strings._('gui_share_stop_server', True)) + self.server_button.setText(strings._('gui_share_stop_server')) else: - self.server_button.setText(strings._('gui_receive_stop_server', True)) + self.server_button.setText(strings._('gui_receive_stop_server')) if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.hide() if self.mode == ServerStatus.MODE_SHARE: - self.server_button.setToolTip(strings._('gui_share_stop_server_shutdown_timeout_tooltip', True).format(self.timeout)) + self.server_button.setToolTip(strings._('gui_share_stop_server_shutdown_timeout_tooltip').format(self.timeout)) else: - self.server_button.setToolTip(strings._('gui_receive_stop_server_shutdown_timeout_tooltip', True).format(self.timeout)) + self.server_button.setToolTip(strings._('gui_receive_stop_server_shutdown_timeout_tooltip').format(self.timeout)) elif self.status == self.STATUS_WORKING: self.server_button.setStyleSheet(self.common.css['server_status_button_working']) @@ -269,7 +269,7 @@ class ServerStatus(QtWidgets.QWidget): self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0) # If the timeout has actually passed already before the user hit Start, refuse to start the server. if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout: - Alert(self.common, strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning)) + Alert(self.common, strings._('gui_server_timeout_expired'), QtWidgets.QMessageBox.Warning) else: self.start_server() else: diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 1cec527b..958d49fd 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -47,7 +47,7 @@ class SettingsDialog(QtWidgets.QDialog): self.local_only = local_only self.setModal(True) - self.setWindowTitle(strings._('gui_settings_window_title', True)) + self.setWindowTitle(strings._('gui_settings_window_title')) self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) self.system = platform.system() @@ -57,8 +57,8 @@ class SettingsDialog(QtWidgets.QDialog): # Use a slug or not ('public mode') self.public_mode_checkbox = QtWidgets.QCheckBox() self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True)) - public_mode_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Public-Mode")) + self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox")) + public_mode_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Public-Mode")) public_mode_label.setStyleSheet(self.common.css['settings_whats_this']) public_mode_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) public_mode_label.setOpenExternalLinks(True) @@ -74,8 +74,8 @@ class SettingsDialog(QtWidgets.QDialog): # Whether or not to use a shutdown ('auto-stop') timer self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) - self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) - shutdown_timeout_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer")) + self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox")) + shutdown_timeout_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer")) shutdown_timeout_label.setStyleSheet(self.common.css['settings_whats_this']) shutdown_timeout_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) shutdown_timeout_label.setOpenExternalLinks(True) @@ -91,9 +91,9 @@ class SettingsDialog(QtWidgets.QDialog): # Whether or not to use legacy v2 onions self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.use_legacy_v2_onions_checkbox.setText(strings._("gui_use_legacy_v2_onions_checkbox", True)) + self.use_legacy_v2_onions_checkbox.setText(strings._("gui_use_legacy_v2_onions_checkbox")) self.use_legacy_v2_onions_checkbox.clicked.connect(self.use_legacy_v2_onions_checkbox_clicked) - use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Legacy-Addresses")) + use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Legacy-Addresses")) use_legacy_v2_onions_label.setStyleSheet(self.common.css['settings_whats_this']) use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_legacy_v2_onions_label.setOpenExternalLinks(True) @@ -108,9 +108,9 @@ class SettingsDialog(QtWidgets.QDialog): # Whether or not to save the Onion private key for reuse (persistent URL mode) self.save_private_key_checkbox = QtWidgets.QCheckBox() self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox", True)) + self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox")) self.save_private_key_checkbox.clicked.connect(self.save_private_key_checkbox_clicked) - save_private_key_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Using-a-Persistent-URL")) + save_private_key_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-a-Persistent-URL")) save_private_key_label.setStyleSheet(self.common.css['settings_whats_this']) save_private_key_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) save_private_key_label.setOpenExternalLinks(True) @@ -125,9 +125,9 @@ class SettingsDialog(QtWidgets.QDialog): # Stealth self.stealth_checkbox = QtWidgets.QCheckBox() self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) + self.stealth_checkbox.setText(strings._("gui_settings_stealth_option")) self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) - use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services")) + use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services")) use_stealth_label.setStyleSheet(self.common.css['settings_whats_this']) use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_stealth_label.setOpenExternalLinks(True) @@ -140,12 +140,12 @@ class SettingsDialog(QtWidgets.QDialog): self.use_stealth_widget = QtWidgets.QWidget() self.use_stealth_widget.setLayout(use_stealth_layout) - hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True)) + hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string')) hidservauth_details.setWordWrap(True) hidservauth_details.setMinimumSize(hidservauth_details.sizeHint()) hidservauth_details.hide() - self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True)) + self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth')) self.hidservauth_copy_button.clicked.connect(self.hidservauth_copy_button_clicked) self.hidservauth_copy_button.hide() @@ -159,7 +159,7 @@ class SettingsDialog(QtWidgets.QDialog): general_group_layout.addWidget(hidservauth_details) general_group_layout.addWidget(self.hidservauth_copy_button) - general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label", True)) + general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label")) general_group.setLayout(general_group_layout) # Sharing options @@ -167,19 +167,19 @@ class SettingsDialog(QtWidgets.QDialog): # Close after first download self.close_after_first_download_checkbox = QtWidgets.QCheckBox() self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) - self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option", True)) + self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option")) # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget(self.close_after_first_download_checkbox) - sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True)) + sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label")) sharing_group.setLayout(sharing_group_layout) # Downloads dir - downloads_label = QtWidgets.QLabel(strings._('gui_settings_downloads_label', True)); + downloads_label = QtWidgets.QLabel(strings._('gui_settings_downloads_label')); self.downloads_dir_lineedit = QtWidgets.QLineEdit() self.downloads_dir_lineedit.setReadOnly(True) - downloads_button = QtWidgets.QPushButton(strings._('gui_settings_downloads_button', True)) + downloads_button = QtWidgets.QPushButton(strings._('gui_settings_downloads_button')) downloads_button.clicked.connect(self.downloads_button_clicked) downloads_layout = QtWidgets.QHBoxLayout() downloads_layout.addWidget(downloads_label) @@ -189,7 +189,7 @@ class SettingsDialog(QtWidgets.QDialog): # Receiving options layout receiving_group_layout = QtWidgets.QVBoxLayout() receiving_group_layout.addLayout(downloads_layout) - receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label", True)) + receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label")) receiving_group.setLayout(receiving_group_layout) # Automatic updates options @@ -197,13 +197,13 @@ class SettingsDialog(QtWidgets.QDialog): # Autoupdate self.autoupdate_checkbox = QtWidgets.QCheckBox() self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.autoupdate_checkbox.setText(strings._("gui_settings_autoupdate_option", True)) + self.autoupdate_checkbox.setText(strings._("gui_settings_autoupdate_option")) # Last update time self.autoupdate_timestamp = QtWidgets.QLabel() # Check for updates button - self.check_for_updates_button = QtWidgets.QPushButton(strings._('gui_settings_autoupdate_check_button', True)) + self.check_for_updates_button = QtWidgets.QPushButton(strings._('gui_settings_autoupdate_check_button')) self.check_for_updates_button.clicked.connect(self.check_for_updates) # We can't check for updates if not connected to Tor if not self.onion.connected_to_tor: @@ -214,17 +214,32 @@ class SettingsDialog(QtWidgets.QDialog): autoupdate_group_layout.addWidget(self.autoupdate_checkbox) autoupdate_group_layout.addWidget(self.autoupdate_timestamp) autoupdate_group_layout.addWidget(self.check_for_updates_button) - autoupdate_group = QtWidgets.QGroupBox(strings._("gui_settings_autoupdate_label", True)) + autoupdate_group = QtWidgets.QGroupBox(strings._("gui_settings_autoupdate_label")) autoupdate_group.setLayout(autoupdate_group_layout) # Autoupdate is only available for Windows and Mac (Linux updates using package manager) if self.system != 'Windows' and self.system != 'Darwin': autoupdate_group.hide() + # Language settings + language_label = QtWidgets.QLabel(strings._("gui_settings_language_label")) + self.language_combobox = QtWidgets.QComboBox() + # Populate the dropdown with all of OnionShare's available languages + language_names_to_locales = {v: k for k, v in self.common.settings.available_locales.items()} + language_names = list(language_names_to_locales) + language_names.sort() + for language_name in language_names: + locale = language_names_to_locales[language_name] + self.language_combobox.addItem(language_name, QtCore.QVariant(locale)) + language_layout = QtWidgets.QHBoxLayout() + language_layout.addWidget(language_label) + language_layout.addWidget(self.language_combobox) + language_layout.addStretch() + # Connection type: either automatic, control port, or socket file # Bundled Tor - self.connection_type_bundled_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_bundled_option', True)) + self.connection_type_bundled_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_bundled_option')) self.connection_type_bundled_radio.toggled.connect(self.connection_type_bundled_toggled) # Bundled Tor doesn't work on dev mode in Windows or Mac @@ -234,27 +249,27 @@ class SettingsDialog(QtWidgets.QDialog): # Bridge options for bundled tor # No bridges option radio - self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_no_bridges_radio_option', True)) + self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_no_bridges_radio_option')) self.tor_bridges_no_bridges_radio.toggled.connect(self.tor_bridges_no_bridges_radio_toggled) # obfs4 option radio # if the obfs4proxy binary is missing, we can't use obfs4 transports (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths() if not os.path.isfile(self.obfs4proxy_file_path): - self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy', True)) + self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy')) self.tor_bridges_use_obfs4_radio.setEnabled(False) else: - self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option', True)) + self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option')) self.tor_bridges_use_obfs4_radio.toggled.connect(self.tor_bridges_use_obfs4_radio_toggled) # meek_lite-azure option radio # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths() if not os.path.isfile(self.obfs4proxy_file_path): - self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy', True)) + self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy')) self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False) else: - self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option', True)) + self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option')) self.tor_bridges_use_meek_lite_azure_radio.toggled.connect(self.tor_bridges_use_meek_lite_azure_radio_toggled) # meek_lite currently not supported on the version of obfs4proxy bundled with TorBrowser @@ -262,10 +277,10 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_bridges_use_meek_lite_azure_radio.hide() # Custom bridges radio and textbox - self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_custom_radio_option', True)) + self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_custom_radio_option')) self.tor_bridges_use_custom_radio.toggled.connect(self.tor_bridges_use_custom_radio_toggled) - self.tor_bridges_use_custom_label = QtWidgets.QLabel(strings._('gui_settings_tor_bridges_custom_label', True)) + self.tor_bridges_use_custom_label = QtWidgets.QLabel(strings._('gui_settings_tor_bridges_custom_label')) self.tor_bridges_use_custom_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) self.tor_bridges_use_custom_label.setOpenExternalLinks(True) self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() @@ -292,14 +307,14 @@ class SettingsDialog(QtWidgets.QDialog): self.bridges.setLayout(bridges_layout) # Automatic - self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option', True)) + self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option')) self.connection_type_automatic_radio.toggled.connect(self.connection_type_automatic_toggled) # Control port - self.connection_type_control_port_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_control_port_option', True)) + self.connection_type_control_port_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_control_port_option')) self.connection_type_control_port_radio.toggled.connect(self.connection_type_control_port_toggled) - connection_type_control_port_extras_label = QtWidgets.QLabel(strings._('gui_settings_control_port_label', True)) + connection_type_control_port_extras_label = QtWidgets.QLabel(strings._('gui_settings_control_port_label')) self.connection_type_control_port_extras_address = QtWidgets.QLineEdit() self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() @@ -312,10 +327,10 @@ class SettingsDialog(QtWidgets.QDialog): self.connection_type_control_port_extras.hide() # Socket file - self.connection_type_socket_file_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_socket_file_option', True)) + self.connection_type_socket_file_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_socket_file_option')) self.connection_type_socket_file_radio.toggled.connect(self.connection_type_socket_file_toggled) - connection_type_socket_file_extras_label = QtWidgets.QLabel(strings._('gui_settings_socket_file_label', True)) + connection_type_socket_file_extras_label = QtWidgets.QLabel(strings._('gui_settings_socket_file_label')) self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() connection_type_socket_file_extras_layout.addWidget(connection_type_socket_file_extras_label) @@ -326,7 +341,7 @@ class SettingsDialog(QtWidgets.QDialog): self.connection_type_socket_file_extras.hide() # Tor SOCKS address and port - gui_settings_socks_label = QtWidgets.QLabel(strings._('gui_settings_socks_label', True)) + gui_settings_socks_label = QtWidgets.QLabel(strings._('gui_settings_socks_label')) self.connection_type_socks_address = QtWidgets.QLineEdit() self.connection_type_socks_port = QtWidgets.QLineEdit() connection_type_socks_layout = QtWidgets.QHBoxLayout() @@ -341,14 +356,14 @@ class SettingsDialog(QtWidgets.QDialog): # Authentication options # No authentication - self.authenticate_no_auth_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_no_auth_option', True)) + self.authenticate_no_auth_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_no_auth_option')) self.authenticate_no_auth_radio.toggled.connect(self.authenticate_no_auth_toggled) # Password - self.authenticate_password_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_password_option', True)) + self.authenticate_password_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_password_option')) self.authenticate_password_radio.toggled.connect(self.authenticate_password_toggled) - authenticate_password_extras_label = QtWidgets.QLabel(strings._('gui_settings_password_label', True)) + authenticate_password_extras_label = QtWidgets.QLabel(strings._('gui_settings_password_label')) self.authenticate_password_extras_password = QtWidgets.QLineEdit('') authenticate_password_extras_layout = QtWidgets.QHBoxLayout() authenticate_password_extras_layout.addWidget(authenticate_password_extras_label) @@ -363,7 +378,7 @@ class SettingsDialog(QtWidgets.QDialog): authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) authenticate_group_layout.addWidget(self.authenticate_password_radio) authenticate_group_layout.addWidget(self.authenticate_password_extras) - self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label", True)) + self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label")) self.authenticate_group.setLayout(authenticate_group_layout) # Put the radios into their own group so they are exclusive @@ -372,18 +387,18 @@ class SettingsDialog(QtWidgets.QDialog): connection_type_radio_group_layout.addWidget(self.connection_type_automatic_radio) connection_type_radio_group_layout.addWidget(self.connection_type_control_port_radio) connection_type_radio_group_layout.addWidget(self.connection_type_socket_file_radio) - connection_type_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label", True)) + connection_type_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label")) connection_type_radio_group.setLayout(connection_type_radio_group_layout) # The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges) connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_bridges_radio_group_layout.addWidget(self.bridges) - self.connection_type_bridges_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_tor_bridges", True)) + self.connection_type_bridges_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_tor_bridges")) self.connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout) self.connection_type_bridges_radio_group.hide() # Test tor settings button - self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button', True)) + self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button')) self.connection_type_test_button.clicked.connect(self.test_tor_clicked) connection_type_test_button_layout = QtWidgets.QHBoxLayout() connection_type_test_button_layout.addWidget(self.connection_type_test_button) @@ -399,13 +414,13 @@ class SettingsDialog(QtWidgets.QDialog): connection_type_layout.addLayout(connection_type_test_button_layout) # Buttons - self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save', True)) + self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save')) self.save_button.clicked.connect(self.save_clicked) - self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel', True)) + self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel')) self.cancel_button.clicked.connect(self.cancel_clicked) version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(self.common.version)) version_label.setStyleSheet(self.common.css['settings_version']) - self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help', True)) + self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help')) self.help_button.clicked.connect(self.help_clicked) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addWidget(version_label) @@ -425,6 +440,7 @@ class SettingsDialog(QtWidgets.QDialog): left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(receiving_group) left_col_layout.addWidget(autoupdate_group) + left_col_layout.addLayout(language_layout) left_col_layout.addStretch() right_col_layout = QtWidgets.QVBoxLayout() @@ -512,6 +528,10 @@ class SettingsDialog(QtWidgets.QDialog): autoupdate_timestamp = self.old_settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp) + locale = self.old_settings.get('locale') + locale_index = self.language_combobox.findData(QtCore.QVariant(locale)) + self.language_combobox.setCurrentIndex(locale_index) + connection_type = self.old_settings.get('connection_type') if connection_type == 'bundled': if self.connection_type_bundled_radio.isEnabled(): @@ -591,7 +611,7 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_bridges_use_custom_textbox_options.hide() # Alert the user about meek's costliness if it looks like they're turning it on if not self.old_settings.get('tor_bridges_use_meek_lite_azure'): - Alert(self.common, strings._('gui_settings_meek_lite_expensive_warning', True), QtWidgets.QMessageBox.Warning) + Alert(self.common, strings._('gui_settings_meek_lite_expensive_warning'), QtWidgets.QMessageBox.Warning) def tor_bridges_use_custom_radio_toggled(self, checked): """ @@ -704,7 +724,7 @@ class SettingsDialog(QtWidgets.QDialog): """ downloads_dir = self.downloads_dir_lineedit.text() selected_dir = QtWidgets.QFileDialog.getExistingDirectory(self, - strings._('gui_settings_downloads_label', True), downloads_dir) + strings._('gui_settings_downloads_label'), downloads_dir) if selected_dir: self.common.log('SettingsDialog', 'downloads_button_clicked', 'selected dir: {}'.format(selected_dir)) @@ -734,7 +754,7 @@ class SettingsDialog(QtWidgets.QDialog): onion.connect(custom_settings=settings, config=self.config, tor_status_update_func=tor_status_update_func) # If an exception hasn't been raised yet, the Tor settings work - Alert(self.common, strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth, onion.supports_next_gen_onions)) + Alert(self.common, strings._('settings_test_success').format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth, onion.supports_next_gen_onions)) # Clean up onion.cleanup() @@ -770,19 +790,19 @@ class SettingsDialog(QtWidgets.QDialog): # Check for updates def update_available(update_url, installed_version, latest_version): - Alert(self.common, strings._("update_available", True).format(update_url, installed_version, latest_version)) + Alert(self.common, strings._("update_available").format(update_url, installed_version, latest_version)) close_forced_update_thread() def update_not_available(): - Alert(self.common, strings._('update_not_available', True)) + Alert(self.common, strings._('update_not_available')) close_forced_update_thread() def update_error(): - Alert(self.common, strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning) + Alert(self.common, strings._('update_error_check_error'), QtWidgets.QMessageBox.Warning) close_forced_update_thread() def update_invalid_version(latest_version): - Alert(self.common, strings._('update_error_invalid_latest_version', True).format(latest_version), QtWidgets.QMessageBox.Warning) + Alert(self.common, strings._('update_error_invalid_latest_version').format(latest_version), QtWidgets.QMessageBox.Warning) close_forced_update_thread() forced_update_thread = UpdateThread(self.common, self.onion, self.config, force=True) @@ -798,8 +818,29 @@ class SettingsDialog(QtWidgets.QDialog): """ self.common.log('SettingsDialog', 'save_clicked') + def changed(s1, s2, keys): + """ + Compare the Settings objects s1 and s2 and return true if any values + have changed for the given keys. + """ + for key in keys: + if s1.get(key) != s2.get(key): + return True + return False + settings = self.settings_from_fields() if settings: + # If language changed, inform user they need to restart OnionShare + if changed(settings, self.old_settings, ['locale']): + # Look up error message in different locale + new_locale = settings.get('locale') + if new_locale in strings.translations and 'gui_settings_language_changed_notice' in strings.translations[new_locale]: + notice = strings.translations[new_locale]['gui_settings_language_changed_notice'] + else: + notice = strings._('gui_settings_language_changed_notice') + Alert(self.common, notice, QtWidgets.QMessageBox.Information) + + # Save the new settings settings.save() # If Tor isn't connected, or if Tor settings have changed, Reinitialize @@ -808,15 +849,6 @@ class SettingsDialog(QtWidgets.QDialog): if not self.local_only: if self.onion.is_authenticated(): self.common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') - def changed(s1, s2, keys): - """ - Compare the Settings objects s1 and s2 and return true if any values - have changed for the given keys. - """ - for key in keys: - if s1.get(key) != s2.get(key): - return True - return False if changed(settings, self.old_settings, [ 'connection_type', 'control_port_address', @@ -861,7 +893,7 @@ class SettingsDialog(QtWidgets.QDialog): """ self.common.log('SettingsDialog', 'cancel_clicked') if not self.local_only and not self.onion.is_authenticated(): - Alert(self.common, strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning) + Alert(self.common, strings._('gui_tor_connection_canceled'), QtWidgets.QMessageBox.Warning) sys.exit() else: self.close() @@ -871,8 +903,12 @@ class SettingsDialog(QtWidgets.QDialog): Help button clicked. """ self.common.log('SettingsDialog', 'help_clicked') - help_site = 'https://github.com/micahflee/onionshare/wiki' - QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_site)) + SettingsDialog.open_help() + + @staticmethod + def open_help(): + help_url = 'https://github.com/micahflee/onionshare/wiki' + QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_url)) def settings_from_fields(self): """ @@ -923,6 +959,12 @@ class SettingsDialog(QtWidgets.QDialog): if not self.stealth_checkbox.isChecked(): settings.set('hidservauth_string', '') + # Language + locale_index = self.language_combobox.currentIndex() + locale = self.language_combobox.itemData(locale_index) + settings.set('locale', locale) + + # Tor connection if self.connection_type_bundled_radio.isChecked(): settings.set('connection_type', 'bundled') if self.connection_type_automatic_radio.isChecked(): @@ -996,7 +1038,7 @@ class SettingsDialog(QtWidgets.QDialog): new_bridges = ''.join(new_bridges) settings.set('tor_bridges_use_custom_bridges', new_bridges) else: - Alert(self.common, strings._('gui_settings_tor_bridges_invalid', True)) + Alert(self.common, strings._('gui_settings_tor_bridges_invalid')) settings.set('no_bridges', True) return False @@ -1020,11 +1062,11 @@ class SettingsDialog(QtWidgets.QDialog): dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) last_checked = dt.strftime('%B %d, %Y %H:%M') else: - last_checked = strings._('gui_settings_autoupdate_timestamp_never', True) - self.autoupdate_timestamp.setText(strings._('gui_settings_autoupdate_timestamp', True).format(last_checked)) + last_checked = strings._('gui_settings_autoupdate_timestamp_never') + self.autoupdate_timestamp.setText(strings._('gui_settings_autoupdate_timestamp').format(last_checked)) def _tor_status_update(self, progress, summary): - self.tor_status.setText('<strong>{}</strong><br>{}% {}'.format(strings._('connecting_to_tor', True), progress, summary)) + self.tor_status.setText('<strong>{}</strong><br>{}% {}'.format(strings._('connecting_to_tor'), progress, summary)) self.qtapp.processEvents() if 'Done' in summary: self.tor_status.hide() diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index a34796f1..fe1e91b8 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -89,7 +89,7 @@ class Downloads(QtWidgets.QScrollArea): self.downloads = {} - self.setWindowTitle(strings._('gui_downloads', True)) + self.setWindowTitle(strings._('gui_downloads')) self.setWidgetResizable(True) self.setMinimumHeight(150) self.setMinimumWidth(350) @@ -98,10 +98,10 @@ class Downloads(QtWidgets.QScrollArea): self.vbar = self.verticalScrollBar() self.vbar.rangeChanged.connect(self.resizeScroll) - downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) + downloads_label = QtWidgets.QLabel(strings._('gui_downloads')) downloads_label.setStyleSheet(self.common.css['downloads_uploads_label']) - self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads', True)) - self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) + self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads')) + self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history')) self.clear_history_button.clicked.connect(self.reset) self.clear_history_button.hide() diff --git a/onionshare_gui/tor_connection_dialog.py b/onionshare_gui/tor_connection_dialog.py index b3bd1fe5..2bcbf1a6 100644 --- a/onionshare_gui/tor_connection_dialog.py +++ b/onionshare_gui/tor_connection_dialog.py @@ -51,7 +51,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): self.setFixedSize(400, 150) # Label - self.setLabelText(strings._('connecting_to_tor', True)) + self.setLabelText(strings._('connecting_to_tor')) # Progress bar ticks from 0 to 100 self.setRange(0, 100) @@ -81,7 +81,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): def _tor_status_update(self, progress, summary): self.setValue(int(progress)) - self.setLabelText("<strong>{}</strong><br>{}".format(strings._('connecting_to_tor', True), summary)) + self.setLabelText("<strong>{}</strong><br>{}".format(strings._('connecting_to_tor'), summary)) def _connected_to_tor(self): self.common.log('TorConnectionDialog', '_connected_to_tor') @@ -104,7 +104,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): def alert_and_open_settings(): # Display the exception in an alert box - Alert(self.common, "{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings', True)), QtWidgets.QMessageBox.Warning) + Alert(self.common, "{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings')), QtWidgets.QMessageBox.Warning) # Open settings self.open_settings.emit() |