Merge pull request #588 from micahflee/ux-update

Major user experience update
This commit is contained in:
Miguel Jacq 2018-02-25 08:27:46 +11:00 committed by GitHub
commit 6b91b90bc6
30 changed files with 849 additions and 315 deletions

View file

@ -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()

View file

@ -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):
"""

View file

@ -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
# 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)
# 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)
self.status_bar.setStyleSheet(
"QStatusBar::item { border: 0px; }")
version_label = QtWidgets.QLabel('v{0:s}'.format(common.get_version()))
version_label.setStyleSheet('color: #666666')
self.settings_button = QtWidgets.QPushButton()
self.settings_button.setDefault(False)
self.settings_button.setFlat(True)
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)
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
# 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)
# 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()
# 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)

View file

@ -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)
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()
# add the widgets
self.addLayout(shutdown_timeout_layout_group)
self.addLayout(server_layout)
self.addLayout(url_layout)
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.

View file

@ -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'))