summaryrefslogtreecommitdiff
path: root/onionshare_gui
diff options
context:
space:
mode:
authorMiguel Jacq <mig@mig5.net>2018-11-13 14:45:40 +1100
committerMiguel Jacq <mig@mig5.net>2018-11-13 14:45:40 +1100
commitd3b5e1e256e7b122e33b98f79bf498408e1233db (patch)
tree5ac985a9da2638256b4061a90559a17e9b1dd631 /onionshare_gui
parentdb8548c35bd5d0236c3083e9e860b82a1dbca808 (diff)
parenta39cd08083f4443e498611bbc1c086a05f5cffde (diff)
downloadonionshare-d3b5e1e256e7b122e33b98f79bf498408e1233db.tar.gz
onionshare-d3b5e1e256e7b122e33b98f79bf498408e1233db.zip
Merge develop branch and fix conflicts
Diffstat (limited to 'onionshare_gui')
-rw-r--r--onionshare_gui/__init__.py16
-rw-r--r--onionshare_gui/mode/__init__.py (renamed from onionshare_gui/mode.py)33
-rw-r--r--onionshare_gui/mode/history.py564
-rw-r--r--onionshare_gui/mode/receive_mode/__init__.py206
-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.py74
-rw-r--r--onionshare_gui/receive_mode/__init__.py236
-rw-r--r--onionshare_gui/receive_mode/uploads.py28
-rw-r--r--onionshare_gui/server_status.py40
-rw-r--r--onionshare_gui/settings_dialog.py180
-rw-r--r--onionshare_gui/share_mode/downloads.py8
-rw-r--r--onionshare_gui/tor_connection_dialog.py6
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()