mirror of
https://github.com/onionshare/onionshare.git
synced 2025-08-09 23:02:54 -04:00
Merge pull request #588 from micahflee/ux-update
Major user experience update
This commit is contained in:
commit
6b91b90bc6
30 changed files with 849 additions and 315 deletions
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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'))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue