summaryrefslogtreecommitdiff
path: root/onionshare_gui
diff options
context:
space:
mode:
authorMiguel Jacq <mig@mig5.net>2018-02-25 08:27:46 +1100
committerGitHub <noreply@github.com>2018-02-25 08:27:46 +1100
commitdface51dd0e00e4316d5384d14fe77568e51bf41 (patch)
tree38400a42f9d8db3cc19eecacaf046a2f76dcd9c2 /onionshare_gui
parent6b7a6a9ec49ea9b33f9046526e8284f665eb6369 (diff)
parent560f27fc76f8b33c1d6e62c02db181b0c6ce8079 (diff)
downloadonionshare-dface51dd0e00e4316d5384d14fe77568e51bf41.tar.gz
onionshare-dface51dd0e00e4316d5384d14fe77568e51bf41.zip
Merge pull request #588 from micahflee/ux-update
Major user experience update
Diffstat (limited to 'onionshare_gui')
-rw-r--r--onionshare_gui/downloads.py8
-rw-r--r--onionshare_gui/file_selection.py238
-rw-r--r--onionshare_gui/onionshare_gui.py291
-rw-r--r--onionshare_gui/server_status.py208
-rw-r--r--onionshare_gui/settings_dialog.py18
5 files changed, 566 insertions, 197 deletions
diff --git a/onionshare_gui/downloads.py b/onionshare_gui/downloads.py
index 60bd59ac..166f14a4 100644
--- a/onionshare_gui/downloads.py
+++ b/onionshare_gui/downloads.py
@@ -34,13 +34,15 @@ class Download(object):
# make a new progress bar
cssStyleData ="""
QProgressBar {
- border: 2px solid grey;
- border-radius: 5px;
+ border: 1px solid #4e064f;
+ background-color: #ffffff !important;
text-align: center;
+ color: #9b9b9b;
+ font-size: 12px;
}
QProgressBar::chunk {
- background: qlineargradient(x1: 0.5, y1: 0, x2: 0.5, y2: 1, stop: 0 #b366ff, stop: 1 #d9b3ff);
+ background-color: #4e064f;
width: 10px;
}"""
self.progress_bar = QtWidgets.QProgressBar()
diff --git a/onionshare_gui/file_selection.py b/onionshare_gui/file_selection.py
index da03d24d..29bcc592 100644
--- a/onionshare_gui/file_selection.py
+++ b/onionshare_gui/file_selection.py
@@ -23,6 +23,50 @@ from .alert import Alert
from onionshare import strings, common
+class DropHereLabel(QtWidgets.QLabel):
+ """
+ When there are no files or folders in the FileList yet, display the
+ 'drop files here' message and graphic.
+ """
+ def __init__(self, parent, image=False):
+ self.parent = parent
+ super(DropHereLabel, self).__init__(parent=parent)
+ self.setAcceptDrops(True)
+ self.setAlignment(QtCore.Qt.AlignCenter)
+
+ if image:
+ self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(common.get_resource_path('images/logo_transparent.png'))))
+ else:
+ self.setText(strings._('gui_drag_and_drop', True))
+ self.setStyleSheet('color: #999999;')
+
+ self.hide()
+
+ def dragEnterEvent(self, event):
+ self.parent.drop_here_image.hide()
+ self.parent.drop_here_text.hide()
+ event.accept()
+
+
+class DropCountLabel(QtWidgets.QLabel):
+ """
+ While dragging files over the FileList, this counter displays the
+ number of files you're dragging.
+ """
+ def __init__(self, parent):
+ self.parent = parent
+ super(DropCountLabel, self).__init__(parent=parent)
+ self.setAcceptDrops(True)
+ self.setAlignment(QtCore.Qt.AlignCenter)
+ self.setText(strings._('gui_drag_and_drop', True))
+ self.setStyleSheet('color: #ffffff; background-color: #f44449; font-weight: bold; padding: 5px 10px; border-radius: 10px;')
+ self.hide()
+
+ def dragEnterEvent(self, event):
+ self.hide()
+ event.accept()
+
+
class FileList(QtWidgets.QListWidget):
"""
The list of files and folders in the GUI.
@@ -35,63 +79,82 @@ class FileList(QtWidgets.QListWidget):
self.setAcceptDrops(True)
self.setIconSize(QtCore.QSize(32, 32))
self.setSortingEnabled(True)
- self.setMinimumHeight(200)
+ self.setMinimumHeight(205)
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
-
- class DropHereLabel(QtWidgets.QLabel):
- """
- When there are no files or folders in the FileList yet, display the
- 'drop files here' message and graphic.
- """
- def __init__(self, parent, image=False):
- self.parent = parent
- super(DropHereLabel, self).__init__(parent=parent)
- self.setAcceptDrops(True)
- self.setAlignment(QtCore.Qt.AlignCenter)
-
- if image:
- self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(common.get_resource_path('images/drop_files.png'))))
- else:
- self.setText(strings._('gui_drag_and_drop', True))
- self.setStyleSheet('color: #999999;')
-
- self.hide()
-
- def dragEnterEvent(self, event):
- self.parent.drop_here_image.hide()
- self.parent.drop_here_text.hide()
- event.ignore()
-
self.drop_here_image = DropHereLabel(self, True)
self.drop_here_text = DropHereLabel(self, False)
-
- self.filenames = []
- self.update()
+ self.drop_count = DropCountLabel(self)
+ self.resizeEvent(None)
+ self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
def update(self):
"""
Update the GUI elements based on the current state.
"""
# file list should have a background image if empty
- if len(self.filenames) == 0:
+ if self.count() == 0:
self.drop_here_image.show()
self.drop_here_text.show()
else:
self.drop_here_image.hide()
self.drop_here_text.hide()
+ def server_started(self):
+ """
+ Update the GUI when the server starts, by hiding delete buttons.
+ """
+ self.setAcceptDrops(False)
+ self.setCurrentItem(None)
+ self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
+ for index in range(self.count()):
+ self.item(index).item_button.hide()
+
+ def server_stopped(self):
+ """
+ Update the GUI when the server stops, by showing delete buttons.
+ """
+ self.setAcceptDrops(True)
+ self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
+ for index in range(self.count()):
+ self.item(index).item_button.show()
+
def resizeEvent(self, event):
"""
When the widget is resized, resize the drop files image and text.
"""
- self.drop_here_image.setGeometry(0, 0, self.width(), self.height())
- self.drop_here_text.setGeometry(0, 0, self.width(), self.height())
+ offset = 70
+ self.drop_here_image.setGeometry(0, 0, self.width(), self.height() - offset)
+ self.drop_here_text.setGeometry(0, offset, self.width(), self.height() - offset)
+
+ if self.count() > 0:
+ # Add and delete an empty item, to force all items to get redrawn
+ # This is ugly, but the only way I could figure out how to proceed
+ item = QtWidgets.QListWidgetItem('fake item')
+ self.addItem(item)
+ self.takeItem(self.row(item))
+ self.update()
+
+ # Extend any filenames that were truncated to fit the window
+ # We use 200 as a rough guess at how wide the 'file size + delete button' widget is
+ # and extend based on the overall width minus that amount.
+ for index in range(self.count()):
+ metrics = QtGui.QFontMetrics(self.item(index).font())
+ elided = metrics.elidedText(self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200)
+ self.item(index).setText(elided)
+
def dragEnterEvent(self, event):
"""
dragEnterEvent for dragging files and directories into the widget.
"""
if event.mimeData().hasUrls:
+ self.setStyleSheet('FileList { border: 3px solid #538ad0; }')
+ count = len(event.mimeData().urls())
+ self.drop_count.setText('+{}'.format(count))
+
+ size_hint = self.drop_count.sizeHint()
+ self.drop_count.setGeometry(self.width() - size_hint.width() - 10, self.height() - size_hint.height() - 10, size_hint.width(), size_hint.height())
+ self.drop_count.show()
event.accept()
else:
event.ignore()
@@ -100,6 +163,8 @@ class FileList(QtWidgets.QListWidget):
"""
dragLeaveEvent for dragging files and directories into the widget.
"""
+ self.setStyleSheet('FileList { border: none; }')
+ self.drop_count.hide()
event.accept()
self.update()
@@ -125,36 +190,84 @@ class FileList(QtWidgets.QListWidget):
self.add_file(filename)
else:
event.ignore()
+
+ self.setStyleSheet('border: none;')
+ self.drop_count.hide()
+
self.files_dropped.emit()
def add_file(self, filename):
"""
Add a file or directory to this widget.
"""
- if filename not in self.filenames:
+ filenames = []
+ for index in range(self.count()):
+ filenames.append(self.item(index).filename)
+
+ if filename not in filenames:
if not os.access(filename, os.R_OK):
Alert(strings._("not_a_readable_file", True).format(filename))
return
- self.filenames.append(filename)
- # Re-sort the list internally
- self.filenames.sort()
-
fileinfo = QtCore.QFileInfo(filename)
- basename = os.path.basename(filename.rstrip('/'))
ip = QtWidgets.QFileIconProvider()
icon = ip.icon(fileinfo)
if os.path.isfile(filename):
- size = common.human_readable_filesize(fileinfo.size())
+ size_bytes = fileinfo.size()
+ size_readable = common.human_readable_filesize(size_bytes)
else:
- size = common.human_readable_filesize(common.dir_size(filename))
- item_name = '{0:s} ({1:s})'.format(basename, size)
- item = QtWidgets.QListWidgetItem(item_name)
- item.setToolTip(size)
+ size_bytes = common.dir_size(filename)
+ size_readable = common.human_readable_filesize(size_bytes)
+ # Create a new item
+ item = QtWidgets.QListWidgetItem()
item.setIcon(icon)
+ item.size_bytes = size_bytes
+
+ # Item's filename attribute and size labels
+ item.filename = filename
+ item_size = QtWidgets.QLabel(size_readable)
+ item_size.setStyleSheet('QLabel { color: #666666; font-size: 11px; }')
+
+ item.basename = os.path.basename(filename.rstrip('/'))
+ # Use the basename as the method with which to sort the list
+ metrics = QtGui.QFontMetrics(item.font())
+ elided = metrics.elidedText(item.basename, QtCore.Qt.ElideRight, self.sizeHint().width())
+ item.setData(QtCore.Qt.DisplayRole, elided)
+
+ # Item's delete button
+ def delete_item():
+ itemrow = self.row(item)
+ self.takeItem(itemrow)
+ self.files_updated.emit()
+
+ item.item_button = QtWidgets.QPushButton()
+ item.item_button.setDefault(False)
+ item.item_button.setFlat(True)
+ item.item_button.setIcon( QtGui.QIcon(common.get_resource_path('images/file_delete.png')) )
+ item.item_button.clicked.connect(delete_item)
+ item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
+
+ # Item info widget, with a white background
+ item_info_layout = QtWidgets.QHBoxLayout()
+ item_info_layout.addWidget(item_size)
+ item_info_layout.addWidget(item.item_button)
+ item_info = QtWidgets.QWidget()
+ item_info.setObjectName('item-info')
+ item_info.setLayout(item_info_layout)
+
+ # Create the item's widget and layouts
+ item_hlayout = QtWidgets.QHBoxLayout()
+ item_hlayout.addStretch()
+ item_hlayout.addWidget(item_info)
+ widget = QtWidgets.QWidget()
+ widget.setLayout(item_hlayout)
+
+ item.setSizeHint(widget.sizeHint())
+
self.addItem(item)
+ self.setItemWidget(item, widget)
self.files_updated.emit()
@@ -168,21 +281,23 @@ class FileSelection(QtWidgets.QVBoxLayout):
super(FileSelection, self).__init__()
self.server_on = False
- # file list
+ # File list
self.file_list = FileList()
- self.file_list.currentItemChanged.connect(self.update)
+ self.file_list.itemSelectionChanged.connect(self.update)
self.file_list.files_dropped.connect(self.update)
+ self.file_list.files_updated.connect(self.update)
- # buttons
+ # Buttons
self.add_button = QtWidgets.QPushButton(strings._('gui_add', True))
self.add_button.clicked.connect(self.add)
self.delete_button = QtWidgets.QPushButton(strings._('gui_delete', True))
self.delete_button.clicked.connect(self.delete)
button_layout = QtWidgets.QHBoxLayout()
+ button_layout.addStretch()
button_layout.addWidget(self.add_button)
button_layout.addWidget(self.delete_button)
- # add the widgets
+ # Add the widgets
self.addWidget(self.file_list)
self.addLayout(button_layout)
@@ -192,21 +307,20 @@ class FileSelection(QtWidgets.QVBoxLayout):
"""
Update the GUI elements based on the current state.
"""
- # all buttons should be disabled if the server is on
+ # All buttons should be hidden if the server is on
if self.server_on:
- self.add_button.setEnabled(False)
- self.delete_button.setEnabled(False)
+ self.add_button.hide()
+ self.delete_button.hide()
else:
- self.add_button.setEnabled(True)
+ self.add_button.show()
- # delete button should be disabled if item isn't selected
- current_item = self.file_list.currentItem()
- if not current_item:
- self.delete_button.setEnabled(False)
+ # Delete button should be hidden if item isn't selected
+ if len(self.file_list.selectedItems()) == 0:
+ self.delete_button.hide()
else:
- self.delete_button.setEnabled(True)
+ self.delete_button.show()
- # update the file list
+ # Update the file list
self.file_list.update()
def add(self):
@@ -218,6 +332,7 @@ class FileSelection(QtWidgets.QVBoxLayout):
for filename in file_dialog.selectedFiles():
self.file_list.add_file(filename)
+ self.file_list.setCurrentItem(None)
self.update()
def delete(self):
@@ -227,9 +342,10 @@ class FileSelection(QtWidgets.QVBoxLayout):
selected = self.file_list.selectedItems()
for item in selected:
itemrow = self.file_list.row(item)
- self.file_list.filenames.pop(itemrow)
self.file_list.takeItem(itemrow)
self.file_list.files_updated.emit()
+
+ self.file_list.setCurrentItem(None)
self.update()
def server_started(self):
@@ -237,7 +353,7 @@ class FileSelection(QtWidgets.QVBoxLayout):
Gets called when the server starts.
"""
self.server_on = True
- self.file_list.setAcceptDrops(False)
+ self.file_list.server_started()
self.update()
def server_stopped(self):
@@ -245,14 +361,14 @@ class FileSelection(QtWidgets.QVBoxLayout):
Gets called when the server stops.
"""
self.server_on = False
- self.file_list.setAcceptDrops(True)
+ self.file_list.server_stopped()
self.update()
def get_num_files(self):
"""
Returns the total number of files and folders in the list.
"""
- return len(self.file_list.filenames)
+ return len(range(self.file_list.count()))
def setFocus(self):
"""
diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py
index 582ebdb3..e6987cfb 100644
--- a/onionshare_gui/onionshare_gui.py
+++ b/onionshare_gui/onionshare_gui.py
@@ -56,6 +56,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.setWindowTitle('OnionShare')
self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
+ self.setMinimumWidth(430)
# Load settings
self.config = config
@@ -72,20 +73,31 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.server_status = ServerStatus(self.qtapp, self.app, web, self.file_selection, self.settings)
self.server_status.server_started.connect(self.file_selection.server_started)
self.server_status.server_started.connect(self.start_server)
+ self.server_status.server_started.connect(self.update_server_status_indicator)
self.server_status.server_stopped.connect(self.file_selection.server_stopped)
self.server_status.server_stopped.connect(self.stop_server)
+ self.server_status.server_stopped.connect(self.update_server_status_indicator)
+ self.server_status.server_stopped.connect(self.update_primary_action)
+ self.server_status.server_canceled.connect(self.cancel_server)
+ self.server_status.server_canceled.connect(self.file_selection.server_stopped)
+ self.server_status.server_canceled.connect(self.update_primary_action)
self.start_server_finished.connect(self.clear_message)
self.start_server_finished.connect(self.server_status.start_server_finished)
+ self.start_server_finished.connect(self.update_server_status_indicator)
self.stop_server_finished.connect(self.server_status.stop_server_finished)
+ self.stop_server_finished.connect(self.update_server_status_indicator)
self.file_selection.file_list.files_updated.connect(self.server_status.update)
+ self.file_selection.file_list.files_updated.connect(self.update_primary_action)
self.server_status.url_copied.connect(self.copy_url)
self.server_status.hidservauth_copied.connect(self.copy_hidservauth)
self.starting_server_step2.connect(self.start_server_step2)
self.starting_server_step3.connect(self.start_server_step3)
self.starting_server_error.connect(self.start_server_error)
+ self.server_status.button_clicked.connect(self.clear_message)
# Filesize warning
self.filesize_warning = QtWidgets.QLabel()
+ self.filesize_warning.setWordWrap(True)
self.filesize_warning.setStyleSheet('padding: 10px 0; font-weight: bold; color: #333333;')
self.filesize_warning.hide()
@@ -99,38 +111,95 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.vbar = self.downloads_container.verticalScrollBar()
self.downloads_container.hide() # downloads start out hidden
self.new_download = False
+ self.downloads_in_progress = 0
+ self.downloads_completed = 0
- # Status bar
- self.status_bar = QtWidgets.QStatusBar()
- self.status_bar.setSizeGripEnabled(False)
- self.status_bar.setStyleSheet(
- "QStatusBar::item { border: 0px; }")
- version_label = QtWidgets.QLabel('v{0:s}'.format(common.get_version()))
- version_label.setStyleSheet('color: #666666')
+ # Info label along top of screen
+ self.info_layout = QtWidgets.QHBoxLayout()
+ self.info_label = QtWidgets.QLabel()
+ self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
+
+ self.info_in_progress_download_count = QtWidgets.QLabel()
+ self.info_in_progress_download_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
+
+ self.info_completed_downloads_count = QtWidgets.QLabel()
+ self.info_completed_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
+
+ self.update_downloads_completed(self.downloads_in_progress)
+ self.update_downloads_in_progress(self.downloads_in_progress)
+
+ self.info_layout.addWidget(self.info_label)
+ self.info_layout.addStretch()
+ self.info_layout.addWidget(self.info_in_progress_download_count)
+ self.info_layout.addWidget(self.info_completed_downloads_count)
+
+ self.info_widget = QtWidgets.QWidget()
+ self.info_widget.setLayout(self.info_layout)
+ self.info_widget.hide()
+
+ # Settings button on the status bar
self.settings_button = QtWidgets.QPushButton()
self.settings_button.setDefault(False)
self.settings_button.setFlat(True)
+ self.settings_button.setFixedWidth(40)
self.settings_button.setIcon( QtGui.QIcon(common.get_resource_path('images/settings.png')) )
self.settings_button.clicked.connect(self.open_settings)
- self.status_bar.addPermanentWidget(version_label)
+
+ # Server status indicator on the status bar
+ self.server_status_image_stopped = QtGui.QImage(common.get_resource_path('images/server_stopped.png'))
+ self.server_status_image_working = QtGui.QImage(common.get_resource_path('images/server_working.png'))
+ self.server_status_image_started = QtGui.QImage(common.get_resource_path('images/server_started.png'))
+ self.server_status_image_label = QtWidgets.QLabel()
+ self.server_status_image_label.setFixedWidth(20)
+ self.server_status_label = QtWidgets.QLabel()
+ self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; }')
+ server_status_indicator_layout = QtWidgets.QHBoxLayout()
+ server_status_indicator_layout.addWidget(self.server_status_image_label)
+ server_status_indicator_layout.addWidget(self.server_status_label)
+ self.server_status_indicator = QtWidgets.QWidget()
+ self.server_status_indicator.setLayout(server_status_indicator_layout)
+ self.update_server_status_indicator()
+
+ # Status bar
+ self.status_bar = QtWidgets.QStatusBar()
+ self.status_bar.setSizeGripEnabled(False)
+ statusBar_cssStyleData ="""
+ QStatusBar {
+ font-style: italic;
+ color: #666666;
+ }
+
+ QStatusBar::item {
+ border: 0px;
+ }"""
+
+ self.status_bar.setStyleSheet(statusBar_cssStyleData)
+ self.status_bar.addPermanentWidget(self.server_status_indicator)
self.status_bar.addPermanentWidget(self.settings_button)
self.setStatusBar(self.status_bar)
# Status bar, zip progress bar
self._zip_progress_bar = None
-
- # Persistent URL notification
- self.persistent_url_label = QtWidgets.QLabel(strings._('persistent_url_in_use', True))
- self.persistent_url_label.setStyleSheet('padding: 10px 0; font-weight: bold; color: #333333;')
- self.persistent_url_label.hide()
+ # Status bar, sharing messages
+ self.server_share_status_label = QtWidgets.QLabel('')
+ self.server_share_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; padding: 2px; }')
+ self.status_bar.insertWidget(0, self.server_share_status_label)
+
+ # Primary action layout
+ primary_action_layout = QtWidgets.QVBoxLayout()
+ primary_action_layout.addWidget(self.server_status)
+ primary_action_layout.addWidget(self.filesize_warning)
+ primary_action_layout.addWidget(self.downloads_container)
+ self.primary_action = QtWidgets.QWidget()
+ self.primary_action.setLayout(primary_action_layout)
+ self.primary_action.hide()
+ self.update_primary_action()
# Main layout
self.layout = QtWidgets.QVBoxLayout()
+ self.layout.addWidget(self.info_widget)
self.layout.addLayout(self.file_selection)
- self.layout.addLayout(self.server_status)
- self.layout.addWidget(self.filesize_warning)
- self.layout.addWidget(self.persistent_url_label)
- self.layout.addWidget(self.downloads_container)
+ self.layout.addWidget(self.primary_action)
central_widget = QtWidgets.QWidget()
central_widget.setLayout(self.layout)
self.setCentralWidget(central_widget)
@@ -158,6 +227,46 @@ class OnionShareGui(QtWidgets.QMainWindow):
# After connecting to Tor, check for updates
self.check_for_updates()
+ def update_primary_action(self):
+ # 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()
+
+ # Update the file count in the info label
+ total_size_bytes = 0
+ for index in range(self.file_selection.file_list.count()):
+ item = self.file_selection.file_list.item(index)
+ total_size_bytes += item.size_bytes
+ total_size_readable = 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))
+ else:
+ self.info_label.setText(strings._('gui_file_info_single', True).format(file_count, total_size_readable))
+
+ else:
+ self.primary_action.hide()
+ self.info_widget.hide()
+
+ # Resize window
+ self.adjustSize()
+
+ def update_server_status_indicator(self):
+ common.log('OnionShareGui', 'update_server_status_indicator')
+
+ # Set the status image
+ if self.server_status.status == self.server_status.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_stopped', True))
+ elif self.server_status.status == self.server_status.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_working', True))
+ elif self.server_status.status == self.server_status.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_started', True))
+
def _initSystemTray(self):
system = common.get_platform()
@@ -238,11 +347,17 @@ class OnionShareGui(QtWidgets.QMainWindow):
if self.server_status.file_selection.get_num_files() > 0:
self.server_status.server_button.setEnabled(True)
self.status_bar.clearMessage()
+ # If we switched off the shutdown timeout setting, ensure the widget is hidden.
+ if not self.settings.get('shutdown_timeout'):
+ self.server_status.shutdown_timeout_container.hide()
d = SettingsDialog(self.onion, self.qtapp, self.config)
d.settings_saved.connect(reload_settings)
d.exec_()
+ # When settings close, refresh the server status UI
+ self.server_status.update()
+
def start_server(self):
"""
Start the onionshare server. This uses multiple threads to start the Tor onion
@@ -257,7 +372,9 @@ class OnionShareGui(QtWidgets.QMainWindow):
# Hide and reset the downloads if we have previously shared
self.downloads_container.hide()
self.downloads.reset_downloads()
+ self.reset_info_counters()
self.status_bar.clearMessage()
+ self.server_share_status_label.setText('')
# Reset web counters
web.download_count = 0
@@ -284,9 +401,10 @@ class OnionShareGui(QtWidgets.QMainWindow):
# wait for modules in thread to load, preventing a thread-related cx_Freeze crash
time.sleep(0.2)
- t = threading.Thread(target=start_onion_service, kwargs={'self': self})
- t.daemon = True
- t.start()
+ common.log('OnionshareGui', 'start_server', 'Starting an onion thread')
+ self.t = OnionThread(function=start_onion_service, kwargs={'self': self})
+ self.t.daemon = True
+ self.t.start()
def start_server_step2(self):
"""
@@ -296,8 +414,11 @@ class OnionShareGui(QtWidgets.QMainWindow):
# add progress bar to the status bar, indicating the crunching of files.
self._zip_progress_bar = ZipProgressBar(0)
- self._zip_progress_bar.total_files_size = OnionShareGui._compute_total_size(
- self.file_selection.file_list.filenames)
+ self.filenames = []
+ for index in range(self.file_selection.file_list.count()):
+ self.filenames.append(self.file_selection.file_list.item(index).filename)
+
+ self._zip_progress_bar.total_files_size = OnionShareGui._compute_total_size(self.filenames)
self.status_bar.insertWidget(0, self._zip_progress_bar)
# prepare the files for sending in a new thread
@@ -307,7 +428,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
if self._zip_progress_bar != None:
self._zip_progress_bar.update_processed_size_signal.emit(x)
try:
- web.set_file_info(self.file_selection.file_list.filenames, processed_size_callback=_set_processed_size)
+ web.set_file_info(self.filenames, processed_size_callback=_set_processed_size)
self.app.cleanup_filenames.append(web.zip_filename)
self.starting_server_step3.emit()
@@ -317,7 +438,6 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.starting_server_error.emit(e.strerror)
return
- #self.status_bar.showMessage(strings._('gui_starting_server2', True))
t = threading.Thread(target=finish_starting_server, kwargs={'self': self})
t.daemon = True
t.start()
@@ -339,7 +459,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.filesize_warning.setText(strings._("large_filesize", True))
self.filesize_warning.show()
- if self.server_status.timer_enabled:
+ if self.settings.get('shutdown_timeout'):
# Convert the date value to seconds between now and then
now = QtCore.QDateTime.currentDateTime()
self.timeout = now.secsTo(self.server_status.timeout)
@@ -352,9 +472,6 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.stop_server()
self.start_server_error(strings._('gui_server_started_after_timeout'))
- if self.settings.get('save_private_key'):
- self.persistent_url_label.show()
-
def start_server_error(self, error):
"""
If there's an error when trying to start the onion service
@@ -370,6 +487,14 @@ class OnionShareGui(QtWidgets.QMainWindow):
self._zip_progress_bar = None
self.status_bar.clearMessage()
+ def cancel_server(self):
+ """
+ Cancel the server while it is preparing to start
+ """
+ if self.t:
+ self.t.terminate()
+ self.stop_server()
+
def stop_server(self):
"""
Stop the onionshare server.
@@ -386,10 +511,13 @@ class OnionShareGui(QtWidgets.QMainWindow):
# Remove ephemeral service, but don't disconnect from Tor
self.onion.cleanup(stop_tor=False)
self.filesize_warning.hide()
- self.persistent_url_label.hide()
- self.stop_server_finished.emit()
+ self.downloads_in_progress = 0
+ self.downloads_completed = 0
+ self.update_downloads_in_progress(0)
+ self.file_selection.file_list.adjustSize()
self.set_server_active(False)
+ self.stop_server_finished.emit()
def check_for_updates(self):
"""
@@ -455,6 +583,8 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.downloads_container.show() # show the downloads layout
self.downloads.add_download(event["data"]["id"], web.zip_filesize)
self.new_download = True
+ self.downloads_in_progress += 1
+ self.update_downloads_in_progress(self.downloads_in_progress)
if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
self.systemTray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True))
@@ -469,16 +599,30 @@ class OnionShareGui(QtWidgets.QMainWindow):
if event["data"]["bytes"] == web.zip_filesize:
if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
self.systemTray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True))
+ # Update the total 'completed downloads' info
+ self.downloads_completed += 1
+ self.update_downloads_completed(self.downloads_completed)
+ # Update the 'in progress downloads' info
+ self.downloads_in_progress -= 1
+ self.update_downloads_in_progress(self.downloads_in_progress)
+
# close on finish?
if not web.get_stay_open():
self.server_status.stop_server()
- self.status_bar.showMessage(strings._('closing_automatically', True))
+ self.status_bar.clearMessage()
+ self.server_share_status_label.setText(strings._('closing_automatically', True))
else:
if self.server_status.status == self.server_status.STATUS_STOPPED:
self.downloads.cancel_download(event["data"]["id"])
+ self.downloads_in_progress = 0
+ self.update_downloads_in_progress(self.downloads_in_progress)
+
elif event["type"] == web.REQUEST_CANCELED:
self.downloads.cancel_download(event["data"]["id"])
+ # Update the 'in progress downloads' info
+ self.downloads_in_progress -= 1
+ self.update_downloads_in_progress(self.downloads_in_progress)
if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
self.systemTray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True))
@@ -487,30 +631,37 @@ class OnionShareGui(QtWidgets.QMainWindow):
# If the auto-shutdown timer has stopped, stop the server
if self.server_status.status == self.server_status.STATUS_STARTED:
- if self.app.shutdown_timer and self.server_status.timer_enabled:
+ if self.app.shutdown_timer and self.settings.get('shutdown_timeout'):
if self.timeout > 0:
+ now = QtCore.QDateTime.currentDateTime()
+ seconds_remaining = now.secsTo(self.server_status.timeout)
+ self.server_status.server_button.setText(strings._('gui_stop_server_shutdown_timeout', True).format(seconds_remaining))
if not self.app.shutdown_timer.is_alive():
# If there were no attempts to download the share, or all downloads are done, we can stop
if web.download_count == 0 or web.done:
self.server_status.stop_server()
- self.status_bar.showMessage(strings._('close_on_timeout', True))
+ self.status_bar.clearMessage()
+ self.server_share_status_label.setText(strings._('close_on_timeout', True))
# A download is probably still running - hold off on stopping the share
else:
- self.status_bar.showMessage(strings._('timeout_download_still_running', True))
+ self.status_bar.clearMessage()
+ self.server_share_status_label.setText(strings._('timeout_download_still_running', True))
def copy_url(self):
"""
When the URL gets copied to the clipboard, display this in the status bar.
"""
common.log('OnionShareGui', 'copy_url')
- self.status_bar.showMessage(strings._('gui_copied_url', True), 2000)
+ if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
+ self.systemTray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True))
def copy_hidservauth(self):
"""
When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar.
"""
common.log('OnionShareGui', 'copy_hidservauth')
- self.status_bar.showMessage(strings._('gui_copied_hidservauth', True), 2000)
+ if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
+ self.systemTray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True))
def clear_message(self):
"""
@@ -522,22 +673,52 @@ class OnionShareGui(QtWidgets.QMainWindow):
"""
Disable the Settings button while an OnionShare server is active.
"""
- self.settings_button.setEnabled(not active)
if active:
- self.settings_button.setIcon( QtGui.QIcon(common.get_resource_path('images/settings_inactive.png')) )
+ self.settings_button.hide()
else:
- self.settings_button.setIcon( QtGui.QIcon(common.get_resource_path('images/settings.png')) )
+ self.settings_button.show()
# Disable settings menu action when server is active
self.settingsAction.setEnabled(not active)
+ def reset_info_counters(self):
+ """
+ Set the info counters back to zero.
+ """
+ self.update_downloads_completed(0)
+ self.update_downloads_in_progress(0)
+
+ def update_downloads_completed(self, count):
+ """
+ Update the 'Downloads completed' info widget.
+ """
+ if count == 0:
+ self.info_completed_downloads_image = common.get_resource_path('images/download_completed_none.png')
+ else:
+ self.info_completed_downloads_image = common.get_resource_path('images/download_completed.png')
+ self.info_completed_downloads_count.setText('<img src={0:s} /> {1:d}'.format(self.info_completed_downloads_image, count))
+ self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count))
+
+ def update_downloads_in_progress(self, count):
+ """
+ Update the 'Downloads in progress' info widget.
+ """
+ if count == 0:
+ self.info_in_progress_download_image = common.get_resource_path('images/download_in_progress_none.png')
+ else:
+ self.info_in_progress_download_image = common.get_resource_path('images/download_in_progress.png')
+ self.info_in_progress_download_count.setText('<img src={0:s} /> {1:d}'.format(self.info_in_progress_download_image, count))
+ self.info_in_progress_download_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count))
+
def closeEvent(self, e):
common.log('OnionShareGui', 'closeEvent')
try:
if self.server_status.status != self.server_status.STATUS_STOPPED:
+ common.log('OnionShareGui', 'closeEvent, opening warning dialog')
dialog = QtWidgets.QMessageBox()
- dialog.setWindowTitle("OnionShare")
+ dialog.setWindowTitle(strings._('gui_quit_title', True))
dialog.setText(strings._('gui_quit_warning', True))
+ 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)
dialog.setDefaultButton(dont_quit_button)
@@ -566,14 +747,15 @@ class ZipProgressBar(QtWidgets.QProgressBar):
self.setFormat(strings._('zip_progress_bar_format'))
cssStyleData ="""
QProgressBar {
- background-color: rgba(255, 255, 255, 0.0) !important;
- border: 0px;
+ border: 1px solid #4e064f;
+ background-color: #ffffff !important;
text-align: center;
+ color: #9b9b9b;
}
QProgressBar::chunk {
border: 0px;
- background: qlineargradient(x1: 0.5, y1: 0, x2: 0.5, y2: 1, stop: 0 #b366ff, stop: 1 #d9b3ff);
+ background-color: #4e064f;
width: 10px;
}"""
self.setStyleSheet(cssStyleData)
@@ -607,3 +789,26 @@ class ZipProgressBar(QtWidgets.QProgressBar):
self.setValue(100)
else:
self.setValue(0)
+
+
+class OnionThread(QtCore.QThread):
+ """
+ A QThread for starting our Onion Service.
+ By using QThread rather than threading.Thread, we are able
+ to call quit() or terminate() on the startup if the user
+ decided to cancel (in which case do not proceed with obtaining
+ the Onion address and starting the web server).
+ """
+ def __init__(self, function, kwargs=None):
+ super(OnionThread, self).__init__()
+ common.log('OnionThread', '__init__')
+ self.function = function
+ if not kwargs:
+ self.kwargs = {}
+ else:
+ self.kwargs = kwargs
+
+ def run(self):
+ common.log('OnionThread', 'run')
+
+ self.function(**self.kwargs)
diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py
index 442ae440..03540415 100644
--- a/onionshare_gui/server_status.py
+++ b/onionshare_gui/server_status.py
@@ -23,12 +23,14 @@ from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings, common, settings
-class ServerStatus(QtWidgets.QVBoxLayout):
+class ServerStatus(QtWidgets.QWidget):
"""
The server status chunk of the GUI.
"""
server_started = QtCore.pyqtSignal()
server_stopped = QtCore.pyqtSignal()
+ server_canceled = QtCore.pyqtSignal()
+ button_clicked = QtCore.pyqtSignal()
url_copied = QtCore.pyqtSignal()
hidservauth_copied = QtCore.pyqtSignal()
@@ -47,100 +49,103 @@ class ServerStatus(QtWidgets.QVBoxLayout):
self.settings = settings
- # Helper boolean as this is used in a few places
- self.timer_enabled = False
# Shutdown timeout layout
- self.server_shutdown_timeout_checkbox = QtWidgets.QCheckBox()
- self.server_shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Unchecked)
- self.server_shutdown_timeout_checkbox.toggled.connect(self.shutdown_timeout_toggled)
- self.server_shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_choice", True))
- self.server_shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True))
- self.server_shutdown_timeout = QtWidgets.QDateTimeEdit()
+ self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True))
+ self.shutdown_timeout = QtWidgets.QDateTimeEdit()
# Set proposed timeout to be 5 minutes into the future
- self.server_shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
+ self.shutdown_timeout.setDisplayFormat("hh:mm A MMM d, yy")
+ self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
# Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 2 min from now
- self.server_shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120))
- self.server_shutdown_timeout.setCurrentSectionIndex(4)
- self.server_shutdown_timeout_label.hide()
- self.server_shutdown_timeout.hide()
- shutdown_timeout_layout_group = QtWidgets.QHBoxLayout()
- shutdown_timeout_layout_group.addWidget(self.server_shutdown_timeout_checkbox)
- shutdown_timeout_layout_group.addWidget(self.server_shutdown_timeout_label)
- shutdown_timeout_layout_group.addWidget(self.server_shutdown_timeout)
- # server layout
- self.status_image_stopped = QtGui.QImage(common.get_resource_path('images/server_stopped.png'))
- self.status_image_working = QtGui.QImage(common.get_resource_path('images/server_working.png'))
- self.status_image_started = QtGui.QImage(common.get_resource_path('images/server_started.png'))
- self.status_image_label = QtWidgets.QLabel()
- self.status_image_label.setFixedWidth(30)
+ self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120))
+ self.shutdown_timeout.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection)
+ shutdown_timeout_layout = QtWidgets.QHBoxLayout()
+ shutdown_timeout_layout.addWidget(self.shutdown_timeout_label)
+ shutdown_timeout_layout.addWidget(self.shutdown_timeout)
+
+ # Shutdown timeout container, so it can all be hidden and shown as a group
+ shutdown_timeout_container_layout = QtWidgets.QVBoxLayout()
+ shutdown_timeout_container_layout.addLayout(shutdown_timeout_layout)
+ self.shutdown_timeout_container = QtWidgets.QWidget()
+ self.shutdown_timeout_container.setLayout(shutdown_timeout_container_layout)
+ self.shutdown_timeout_container.hide()
+
+
+ # Server layout
self.server_button = QtWidgets.QPushButton()
self.server_button.clicked.connect(self.server_button_clicked)
- server_layout = QtWidgets.QHBoxLayout()
- server_layout.addWidget(self.status_image_label)
- server_layout.addWidget(self.server_button)
- # url layout
+ # URL layout
url_font = QtGui.QFont()
- self.url_label = QtWidgets.QLabel()
- self.url_label.setFont(url_font)
- self.url_label.setWordWrap(False)
- self.url_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.url_description = QtWidgets.QLabel(strings._('gui_url_description', True))
+ 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(60)
+ self.url.setMinimumSize(self.url.sizeHint())
+ self.url.setStyleSheet('QLabel { background-color: #ffffff; color: #000000; padding: 10px; border: 1px solid #666666; }')
+
+ url_buttons_style = 'QPushButton { color: #3f7fcf; }'
self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url', True))
+ self.copy_url_button.setFlat(True)
+ self.copy_url_button.setStyleSheet(url_buttons_style)
self.copy_url_button.clicked.connect(self.copy_url)
self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True))
+ self.copy_hidservauth_button.setFlat(True)
+ self.copy_hidservauth_button.setStyleSheet(url_buttons_style)
self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth)
- url_layout = QtWidgets.QHBoxLayout()
- url_layout.addWidget(self.url_label)
- url_layout.addWidget(self.copy_url_button)
- url_layout.addWidget(self.copy_hidservauth_button)
-
- # add the widgets
- self.addLayout(shutdown_timeout_layout_group)
- self.addLayout(server_layout)
- self.addLayout(url_layout)
+ url_buttons_layout = QtWidgets.QHBoxLayout()
+ url_buttons_layout.addWidget(self.copy_url_button)
+ url_buttons_layout.addWidget(self.copy_hidservauth_button)
+ url_buttons_layout.addStretch()
+
+ url_layout = QtWidgets.QVBoxLayout()
+ url_layout.addWidget(self.url_description)
+ url_layout.addWidget(self.url)
+ url_layout.addLayout(url_buttons_layout)
+
+ # Add the widgets
+ layout = QtWidgets.QVBoxLayout()
+ layout.addWidget(self.server_button)
+ layout.addLayout(url_layout)
+ layout.addWidget(self.shutdown_timeout_container)
+ self.setLayout(layout)
self.update()
- def shutdown_timeout_toggled(self, checked):
- """
- Shutdown timer option was toggled. If checked, show the timer settings.
- """
- if checked:
- self.timer_enabled = True
- # Hide the checkbox, show the options
- self.server_shutdown_timeout_label.show()
- # Reset the default timer to 5 minutes into the future after toggling the option on
- self.server_shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
- self.server_shutdown_timeout.show()
- else:
- self.timer_enabled = False
- self.server_shutdown_timeout_label.hide()
- self.server_shutdown_timeout.hide()
-
def shutdown_timeout_reset(self):
"""
Reset the timeout in the UI after stopping a share
"""
- self.server_shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Unchecked)
- self.server_shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
- self.server_shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120))
+ self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
+ self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120))
def update(self):
"""
Update the GUI elements based on the current state.
"""
- # set the status image
- if self.status == self.STATUS_STOPPED:
- self.status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.status_image_stopped))
- elif self.status == self.STATUS_WORKING:
- self.status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.status_image_working))
- elif self.status == self.STATUS_STARTED:
- self.status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.status_image_started))
-
- # set the URL fields
+ # Set the URL fields
if self.status == self.STATUS_STARTED:
- self.url_label.setText('http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug))
- self.url_label.show()
+ self.url_description.show()
+
+ info_image = common.get_resource_path('images/info.png')
+ self.url_description.setText(strings._('gui_url_description', True).format(info_image))
+ # Show a Tool Tip explaining the lifecycle of this URL
+ if self.settings.get('save_private_key'):
+ if self.settings.get('close_after_first_download'):
+ self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent', True))
+ else:
+ self.url_description.setToolTip(strings._('gui_url_label_persistent', True))
+ else:
+ if self.settings.get('close_after_first_download'):
+ self.url_description.setToolTip(strings._('gui_url_label_onetime', True))
+ else:
+ self.url_description.setToolTip(strings._('gui_url_label_stay_open', True))
+
+ self.url.setText('http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug))
+ self.url.show()
+
self.copy_url_button.show()
if self.settings.get('save_private_key'):
@@ -148,53 +153,63 @@ class ServerStatus(QtWidgets.QVBoxLayout):
self.settings.set('slug', self.web.slug)
self.settings.save()
+ if self.settings.get('shutdown_timeout'):
+ self.shutdown_timeout_container.hide()
+
if self.app.stealth:
self.copy_hidservauth_button.show()
else:
self.copy_hidservauth_button.hide()
-
- # resize parent widget
- p = self.parentWidget()
- p.resize(p.sizeHint())
else:
- self.url_label.hide()
+ self.url_description.hide()
+ self.url.hide()
self.copy_url_button.hide()
self.copy_hidservauth_button.hide()
- # button
+ # Button
+ button_stopped_style = 'QPushButton { background-color: #5fa416; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }'
+ button_working_style = 'QPushButton { background-color: #4c8211; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; font-style: italic; }'
+ button_started_style = 'QPushButton { background-color: #d0011b; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }'
if self.file_selection.get_num_files() == 0:
- self.server_button.setEnabled(False)
- self.server_button.setText(strings._('gui_start_server', True))
+ self.server_button.hide()
else:
+ self.server_button.show()
+
if self.status == self.STATUS_STOPPED:
+ self.server_button.setStyleSheet(button_stopped_style)
self.server_button.setEnabled(True)
self.server_button.setText(strings._('gui_start_server', True))
- self.server_shutdown_timeout.setEnabled(True)
- self.server_shutdown_timeout_checkbox.setEnabled(True)
+ self.server_button.setToolTip('')
+ if self.settings.get('shutdown_timeout'):
+ self.shutdown_timeout_container.show()
elif self.status == self.STATUS_STARTED:
+ self.server_button.setStyleSheet(button_started_style)
self.server_button.setEnabled(True)
self.server_button.setText(strings._('gui_stop_server', True))
- self.server_shutdown_timeout.setEnabled(False)
- self.server_shutdown_timeout_checkbox.setEnabled(False)
+ if self.settings.get('shutdown_timeout'):
+ self.shutdown_timeout_container.hide()
+ self.server_button.setToolTip(strings._('gui_stop_server_shutdown_timeout_tooltip', True).format(self.timeout))
elif self.status == self.STATUS_WORKING:
- self.server_button.setEnabled(False)
+ self.server_button.setStyleSheet(button_working_style)
+ self.server_button.setEnabled(True)
self.server_button.setText(strings._('gui_please_wait'))
- self.server_shutdown_timeout.setEnabled(False)
- self.server_shutdown_timeout_checkbox.setEnabled(False)
+ if self.settings.get('shutdown_timeout'):
+ self.shutdown_timeout_container.hide()
else:
+ self.server_button.setStyleSheet(button_working_style)
self.server_button.setEnabled(False)
self.server_button.setText(strings._('gui_please_wait'))
- self.server_shutdown_timeout.setEnabled(False)
- self.server_shutdown_timeout_checkbox.setEnabled(False)
+ if self.settings.get('shutdown_timeout'):
+ self.shutdown_timeout_container.hide()
def server_button_clicked(self):
"""
Toggle starting or stopping the server.
"""
if self.status == self.STATUS_STOPPED:
- if self.timer_enabled:
+ if self.settings.get('shutdown_timeout'):
# Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen
- self.timeout = self.server_shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0)
+ 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(strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning))
@@ -204,6 +219,9 @@ class ServerStatus(QtWidgets.QVBoxLayout):
self.start_server()
elif self.status == self.STATUS_STARTED:
self.stop_server()
+ elif self.status == self.STATUS_WORKING:
+ self.cancel_server()
+ self.button_clicked.emit()
def start_server(self):
"""
@@ -230,6 +248,16 @@ class ServerStatus(QtWidgets.QVBoxLayout):
self.update()
self.server_stopped.emit()
+ def cancel_server(self):
+ """
+ Cancel the server.
+ """
+ common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup')
+ self.status = self.STATUS_WORKING
+ self.shutdown_timeout_reset()
+ self.update()
+ self.server_canceled.emit()
+
def stop_server_finished(self):
"""
The server has finished stopping.
diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py
index 424f2589..a279723e 100644
--- a/onionshare_gui/settings_dialog.py
+++ b/onionshare_gui/settings_dialog.py
@@ -60,6 +60,11 @@ class SettingsDialog(QtWidgets.QDialog):
self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Checked)
self.systray_notifications_checkbox.setText(strings._("gui_settings_systray_notifications", True))
+ # Whether or not to use a shutdown 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))
+
# Whether or not to save the Onion private key for reuse
self.save_private_key_checkbox = QtWidgets.QCheckBox()
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked)
@@ -69,6 +74,7 @@ class SettingsDialog(QtWidgets.QDialog):
sharing_group_layout = QtWidgets.QVBoxLayout()
sharing_group_layout.addWidget(self.close_after_first_download_checkbox)
sharing_group_layout.addWidget(self.systray_notifications_checkbox)
+ sharing_group_layout.addWidget(self.shutdown_timeout_checkbox)
sharing_group_layout.addWidget(self.save_private_key_checkbox)
sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True))
sharing_group.setLayout(sharing_group_layout)
@@ -80,12 +86,14 @@ class SettingsDialog(QtWidgets.QDialog):
stealth_details.setWordWrap(True)
stealth_details.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
stealth_details.setOpenExternalLinks(True)
+ stealth_details.setMinimumSize(stealth_details.sizeHint())
self.stealth_checkbox = QtWidgets.QCheckBox()
self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True))
hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True))
hidservauth_details.setWordWrap(True)
+ hidservauth_details.setMinimumSize(hidservauth_details.sizeHint())
hidservauth_details.hide()
self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True))
@@ -317,9 +325,12 @@ class SettingsDialog(QtWidgets.QDialog):
self.save_button.clicked.connect(self.save_clicked)
self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel', True))
self.cancel_button.clicked.connect(self.cancel_clicked)
+ version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(common.get_version()))
+ version_label.setStyleSheet('color: #666666')
self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help', True))
self.help_button.clicked.connect(self.help_clicked)
buttons_layout = QtWidgets.QHBoxLayout()
+ buttons_layout.addWidget(version_label)
buttons_layout.addWidget(self.help_button)
buttons_layout.addStretch()
buttons_layout.addWidget(self.save_button)
@@ -371,6 +382,12 @@ class SettingsDialog(QtWidgets.QDialog):
else:
self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Unchecked)
+ shutdown_timeout = self.old_settings.get('shutdown_timeout')
+ if shutdown_timeout:
+ self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked)
+ else:
+ self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Unchecked)
+
save_private_key = self.old_settings.get('save_private_key')
if save_private_key:
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked)
@@ -723,6 +740,7 @@ class SettingsDialog(QtWidgets.QDialog):
settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked())
settings.set('systray_notifications', self.systray_notifications_checkbox.isChecked())
+ settings.set('shutdown_timeout', self.shutdown_timeout_checkbox.isChecked())
if self.save_private_key_checkbox.isChecked():
settings.set('save_private_key', True)
settings.set('private_key', self.old_settings.get('private_key'))