summaryrefslogtreecommitdiff
path: root/onionshare_gui
diff options
context:
space:
mode:
authorMicah Lee <micah@micahflee.com>2020-06-30 10:56:25 -0700
committerMicah Lee <micah@micahflee.com>2020-06-30 10:56:25 -0700
commit17b0af98f27c1550c115226450ac244a45832b56 (patch)
tree04560f5f13eeb19a3eb3f82fc10e0c358b2c86ba /onionshare_gui
parent40b43b66ab788c60fe3753f9c44207e360ff8fad (diff)
parentf65e50f399f1716162343d84b6003957adc46185 (diff)
downloadonionshare-17b0af98f27c1550c115226450ac244a45832b56.tar.gz
onionshare-17b0af98f27c1550c115226450ac244a45832b56.zip
Merge branch 'develop' into 910_flatpak
Diffstat (limited to 'onionshare_gui')
-rw-r--r--onionshare_gui/gui_common.py5
-rw-r--r--onionshare_gui/tab/mode/__init__.py11
-rw-r--r--onionshare_gui/tab/mode/file_selection.py12
-rw-r--r--onionshare_gui/tab/mode/mode_settings_widget.py10
-rw-r--r--onionshare_gui/tab/mode/share_mode/__init__.py28
-rw-r--r--onionshare_gui/tab/mode/website_mode/__init__.py28
-rw-r--r--onionshare_gui/tab/server_status.py40
-rw-r--r--onionshare_gui/tab/tab.py15
-rw-r--r--onionshare_gui/tab_widget.py19
-rw-r--r--onionshare_gui/widgets.py64
10 files changed, 199 insertions, 33 deletions
diff --git a/onionshare_gui/gui_common.py b/onionshare_gui/gui_common.py
index 18edded1..4b8fe5d4 100644
--- a/onionshare_gui/gui_common.py
+++ b/onionshare_gui/gui_common.py
@@ -211,6 +211,11 @@ class GuiCommon:
color: #cc0000;
}""",
# Share mode and child widget styles
+ "share_delete_all_files_button": """
+ QPushButton {
+ color: #3f7fcf;
+ }
+ """,
"share_zip_progess_bar": """
QProgressBar {
border: 1px solid #4e064f;
diff --git a/onionshare_gui/tab/mode/__init__.py b/onionshare_gui/tab/mode/__init__.py
index 7696f79e..fc79d0ad 100644
--- a/onionshare_gui/tab/mode/__init__.py
+++ b/onionshare_gui/tab/mode/__init__.py
@@ -138,7 +138,7 @@ class Mode(QtWidgets.QWidget):
"""
# If this is a scheduled share, display the countdown til the share starts
if self.server_status.status == ServerStatus.STATUS_WORKING:
- if self.server_status.autostart_timer_datetime:
+ if self.settings.get("general", "autostart_timer"):
now = QtCore.QDateTime.currentDateTime()
if self.server_status.local_only:
seconds_remaining = now.secsTo(
@@ -227,13 +227,8 @@ class Mode(QtWidgets.QWidget):
# Start the onion thread. If this share was scheduled for a future date,
# the OnionThread will start and exit 'early' to obtain the port, password
# and onion address, but it will not start the WebThread yet.
- if self.server_status.autostart_timer_datetime:
+ if self.settings.get("general", "autostart_timer"):
self.start_onion_thread(obtain_onion_early=True)
- else:
- self.start_onion_thread()
-
- # If scheduling a share, delay starting the real share
- if self.server_status.autostart_timer_datetime:
self.common.log("Mode", "start_server", "Starting auto-start timer")
self.startup_thread = AutoStartTimer(self)
# Once the timer has finished, start the real share, with a WebThread
@@ -241,6 +236,8 @@ class Mode(QtWidgets.QWidget):
self.startup_thread.error.connect(self.start_server_error)
self.startup_thread.canceled = False
self.startup_thread.start()
+ else:
+ self.start_onion_thread()
def start_onion_thread(self, obtain_onion_early=False):
self.common.log("Mode", "start_server", "Starting an onion thread")
diff --git a/onionshare_gui/tab/mode/file_selection.py b/onionshare_gui/tab/mode/file_selection.py
index baa460f3..5a9e5d59 100644
--- a/onionshare_gui/tab/mode/file_selection.py
+++ b/onionshare_gui/tab/mode/file_selection.py
@@ -338,8 +338,8 @@ class FileSelection(QtWidgets.QVBoxLayout):
else:
self.add_button = QtWidgets.QPushButton(strings._("gui_add"))
self.add_button.clicked.connect(self.add)
- self.delete_button = QtWidgets.QPushButton(strings._("gui_delete"))
- self.delete_button.clicked.connect(self.delete)
+ self.remove_button = QtWidgets.QPushButton(strings._("gui_remove"))
+ self.remove_button.clicked.connect(self.delete)
button_layout = QtWidgets.QHBoxLayout()
button_layout.addStretch()
if self.common.platform == "Darwin":
@@ -347,7 +347,7 @@ class FileSelection(QtWidgets.QVBoxLayout):
button_layout.addWidget(self.add_folder_button)
else:
button_layout.addWidget(self.add_button)
- button_layout.addWidget(self.delete_button)
+ button_layout.addWidget(self.remove_button)
# Add the widgets
self.addWidget(self.file_list)
@@ -366,7 +366,7 @@ class FileSelection(QtWidgets.QVBoxLayout):
self.add_folder_button.hide()
else:
self.add_button.hide()
- self.delete_button.hide()
+ self.remove_button.hide()
else:
if self.common.platform == "Darwin":
self.add_files_button.show()
@@ -376,9 +376,9 @@ class FileSelection(QtWidgets.QVBoxLayout):
# Delete button should be hidden if item isn't selected
if len(self.file_list.selectedItems()) == 0:
- self.delete_button.hide()
+ self.remove_button.hide()
else:
- self.delete_button.show()
+ self.remove_button.show()
# Update the file list
self.file_list.update()
diff --git a/onionshare_gui/tab/mode/mode_settings_widget.py b/onionshare_gui/tab/mode/mode_settings_widget.py
index 34335494..ed224589 100644
--- a/onionshare_gui/tab/mode/mode_settings_widget.py
+++ b/onionshare_gui/tab/mode/mode_settings_widget.py
@@ -76,7 +76,10 @@ class ModeSettingsWidget(QtWidgets.QWidget):
self.autostart_timer_widget.setCurrentSection(
QtWidgets.QDateTimeEdit.MinuteSection
)
- self.autostart_timer_widget.hide()
+ if self.settings.get("general", "autostart_timer"):
+ self.autostart_timer_widget.show()
+ else:
+ self.autostart_timer_widget.hide()
# Autostart timer layout
autostart_timer_layout = QtWidgets.QHBoxLayout()
@@ -104,7 +107,10 @@ class ModeSettingsWidget(QtWidgets.QWidget):
self.autostop_timer_widget.setCurrentSection(
QtWidgets.QDateTimeEdit.MinuteSection
)
- self.autostop_timer_widget.hide()
+ if self.settings.get("general", "autostop_timer"):
+ self.autostop_timer_widget.show()
+ else:
+ self.autostop_timer_widget.hide()
# Autostop timer layout
autostop_timer_layout = QtWidgets.QHBoxLayout()
diff --git a/onionshare_gui/tab/mode/share_mode/__init__.py b/onionshare_gui/tab/mode/share_mode/__init__.py
index 1423d60a..a81f83d4 100644
--- a/onionshare_gui/tab/mode/share_mode/__init__.py
+++ b/onionshare_gui/tab/mode/share_mode/__init__.py
@@ -111,6 +111,17 @@ class ShareMode(Mode):
self.info_label = QtWidgets.QLabel()
self.info_label.hide()
+ # Delete all files button
+ self.remove_all_button = QtWidgets.QPushButton(
+ strings._("gui_file_selection_remove_all")
+ )
+ self.remove_all_button.setFlat(True)
+ self.remove_all_button.setStyleSheet(
+ self.common.gui.css["share_delete_all_files_button"]
+ )
+ self.remove_all_button.clicked.connect(self.delete_all)
+ self.remove_all_button.hide()
+
# Toggle history
self.toggle_history = ToggleHistory(
self.common,
@@ -126,6 +137,7 @@ class ShareMode(Mode):
top_bar_layout = QtWidgets.QHBoxLayout()
top_bar_layout.addWidget(self.info_label)
top_bar_layout.addStretch()
+ top_bar_layout.addWidget(self.remove_all_button)
top_bar_layout.addWidget(self.toggle_history)
# Primary action layout
@@ -198,6 +210,8 @@ class ShareMode(Mode):
# Hide and reset the downloads if we have previously shared
self.reset_info_counters()
+ self.remove_all_button.hide()
+
def start_server_step2_custom(self):
"""
Step 2 in starting the server. Zipping up files.
@@ -257,6 +271,8 @@ class ShareMode(Mode):
self.history.update_in_progress()
self.file_selection.file_list.adjustSize()
+ self.remove_all_button.show()
+
def cancel_server_custom(self):
"""
Stop the compression thread on cancel
@@ -343,6 +359,7 @@ class ShareMode(Mode):
if self.server_status.file_selection.get_num_files() > 0:
self.primary_action.show()
self.info_label.show()
+ self.remove_all_button.show()
def update_primary_action(self):
self.common.log("ShareMode", "update_primary_action")
@@ -352,6 +369,7 @@ class ShareMode(Mode):
if file_count > 0:
self.primary_action.show()
self.info_label.show()
+ self.remove_all_button.show()
# Update the file count in the info label
total_size_bytes = 0
@@ -374,6 +392,7 @@ class ShareMode(Mode):
else:
self.primary_action.hide()
self.info_label.hide()
+ self.remove_all_button.hide()
def reset_info_counters(self):
"""
@@ -383,6 +402,15 @@ class ShareMode(Mode):
self.toggle_history.indicator_count = 0
self.toggle_history.update_indicator()
+ def delete_all(self):
+ """
+ Delete All button clicked
+ """
+ self.file_selection.file_list.clear()
+ self.file_selection.file_list.files_updated.emit()
+
+ self.file_selection.file_list.setCurrentItem(None)
+
@staticmethod
def _compute_total_size(filenames):
total_size = 0
diff --git a/onionshare_gui/tab/mode/website_mode/__init__.py b/onionshare_gui/tab/mode/website_mode/__init__.py
index db8dbf09..6199812c 100644
--- a/onionshare_gui/tab/mode/website_mode/__init__.py
+++ b/onionshare_gui/tab/mode/website_mode/__init__.py
@@ -114,6 +114,17 @@ class WebsiteMode(Mode):
self.info_label = QtWidgets.QLabel()
self.info_label.hide()
+ # Delete all files button
+ self.remove_all_button = QtWidgets.QPushButton(
+ strings._("gui_file_selection_remove_all")
+ )
+ self.remove_all_button.setFlat(True)
+ self.remove_all_button.setStyleSheet(
+ self.common.gui.css["share_delete_all_files_button"]
+ )
+ self.remove_all_button.clicked.connect(self.delete_all)
+ self.remove_all_button.hide()
+
# Toggle history
self.toggle_history = ToggleHistory(
self.common,
@@ -129,6 +140,7 @@ class WebsiteMode(Mode):
top_bar_layout = QtWidgets.QHBoxLayout()
top_bar_layout.addWidget(self.info_label)
top_bar_layout.addStretch()
+ top_bar_layout.addWidget(self.remove_all_button)
top_bar_layout.addWidget(self.toggle_history)
# Primary action layout
@@ -191,6 +203,8 @@ class WebsiteMode(Mode):
# Hide and reset the downloads if we have previously shared
self.reset_info_counters()
+ self.remove_all_button.hide()
+
def start_server_step2_custom(self):
"""
Step 2 in starting the server. Zipping up files.
@@ -228,6 +242,8 @@ class WebsiteMode(Mode):
self.history.completed_count = 0
self.file_selection.file_list.adjustSize()
+ self.remove_all_button.show()
+
def cancel_server_custom(self):
"""
Log that the server has been cancelled
@@ -248,6 +264,7 @@ class WebsiteMode(Mode):
if self.server_status.file_selection.get_num_files() > 0:
self.primary_action.show()
self.info_label.show()
+ self.remove_all_button.show()
def update_primary_action(self):
self.common.log("WebsiteMode", "update_primary_action")
@@ -257,6 +274,7 @@ class WebsiteMode(Mode):
if file_count > 0:
self.primary_action.show()
self.info_label.show()
+ self.remove_all_button.show()
# Update the file count in the info label
total_size_bytes = 0
@@ -279,6 +297,7 @@ class WebsiteMode(Mode):
else:
self.primary_action.hide()
self.info_label.hide()
+ self.remove_all_button.hide()
def reset_info_counters(self):
"""
@@ -288,6 +307,15 @@ class WebsiteMode(Mode):
self.toggle_history.indicator_count = 0
self.toggle_history.update_indicator()
+ def delete_all(self):
+ """
+ Delete All button clicked
+ """
+ self.file_selection.file_list.clear()
+ self.file_selection.file_list.files_updated.emit()
+
+ self.file_selection.file_list.setCurrentItem(None)
+
@staticmethod
def _compute_total_size(filenames):
total_size = 0
diff --git a/onionshare_gui/tab/server_status.py b/onionshare_gui/tab/server_status.py
index 0fbc11b6..9bfbce78 100644
--- a/onionshare_gui/tab/server_status.py
+++ b/onionshare_gui/tab/server_status.py
@@ -20,10 +20,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import platform
import textwrap
from PyQt5 import QtCore, QtWidgets, QtGui
+from PyQt5.QtCore import Qt
from onionshare import strings
from ..widgets import Alert
+from ..widgets import QRCodeDialog
class ServerStatus(QtWidgets.QWidget):
@@ -85,17 +87,31 @@ class ServerStatus(QtWidgets.QWidget):
self.url.setWordWrap(True)
self.url.setMinimumSize(self.url.sizeHint())
self.url.setStyleSheet(self.common.gui.css["server_status_url"])
+ self.url.setTextInteractionFlags(
+ Qt.TextSelectableByMouse | Qt.TextSelectableByKeyboard
+ )
self.copy_url_button = QtWidgets.QPushButton(strings._("gui_copy_url"))
self.copy_url_button.setFlat(True)
self.copy_url_button.setStyleSheet(
self.common.gui.css["server_status_url_buttons"]
)
- self.copy_url_button.setMinimumHeight(65)
self.copy_url_button.clicked.connect(self.copy_url)
self.copy_hidservauth_button = QtWidgets.QPushButton(
strings._("gui_copy_hidservauth")
)
+ self.show_url_qr_code_button = QtWidgets.QPushButton(
+ strings._("gui_show_url_qr_code")
+ )
+ self.show_url_qr_code_button.hide()
+ self.show_url_qr_code_button.clicked.connect(
+ self.show_url_qr_code_button_clicked
+ )
+ self.show_url_qr_code_button.setFlat(True)
+ self.show_url_qr_code_button.setStyleSheet(
+ self.common.gui.css["server_status_url_buttons"]
+ )
+
self.copy_hidservauth_button.setFlat(True)
self.copy_hidservauth_button.setStyleSheet(
self.common.gui.css["server_status_url_buttons"]
@@ -103,6 +119,7 @@ class ServerStatus(QtWidgets.QWidget):
self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth)
url_buttons_layout = QtWidgets.QHBoxLayout()
url_buttons_layout.addWidget(self.copy_url_button)
+ url_buttons_layout.addWidget(self.show_url_qr_code_button)
url_buttons_layout.addWidget(self.copy_hidservauth_button)
url_buttons_layout.addStretch()
@@ -190,6 +207,8 @@ class ServerStatus(QtWidgets.QWidget):
self.url.show()
self.copy_url_button.show()
+ self.show_url_qr_code_button.show()
+
if self.settings.get("general", "client_auth"):
self.copy_hidservauth_button.show()
else:
@@ -269,7 +288,7 @@ class ServerStatus(QtWidgets.QWidget):
self.common.gui.css["server_status_button_working"]
)
self.server_button.setEnabled(True)
- if self.autostart_timer_datetime:
+ if self.settings.get("general", "autostart_timer"):
self.server_button.setToolTip(
strings._("gui_start_server_autostart_timer_tooltip").format(
self.mode_settings_widget.autostart_timer_widget.dateTime().toString(
@@ -279,15 +298,6 @@ class ServerStatus(QtWidgets.QWidget):
)
else:
self.server_button.setText(strings._("gui_please_wait"))
-
- if self.settings.get("general", "autostart_timer"):
- self.server_button.setToolTip(
- strings._("gui_start_server_autostart_timer_tooltip").format(
- self.mode_settings_widget.autostart_timer_widget.dateTime().toString(
- "h:mm AP, MMMM dd, yyyy"
- )
- )
- )
else:
self.server_button.setStyleSheet(
self.common.gui.css["server_status_button_working"]
@@ -364,6 +374,12 @@ class ServerStatus(QtWidgets.QWidget):
self.cancel_server()
self.button_clicked.emit()
+ def show_url_qr_code_button_clicked(self):
+ """
+ Show a QR code of the onion URL.
+ """
+ self.qr_code_dialog = QRCodeDialog(self.common, self.get_url())
+
def start_server(self):
"""
Start the server.
@@ -377,7 +393,7 @@ class ServerStatus(QtWidgets.QWidget):
The server has finished starting.
"""
self.status = self.STATUS_STARTED
- self.copy_url()
+ # self.copy_url()
self.update()
self.server_started_finished.emit()
diff --git a/onionshare_gui/tab/tab.py b/onionshare_gui/tab/tab.py
index aa4518b5..5d1fde55 100644
--- a/onionshare_gui/tab/tab.py
+++ b/onionshare_gui/tab/tab.py
@@ -287,7 +287,7 @@ class Tab(QtWidgets.QWidget):
strings._("gui_status_indicator_share_stopped")
)
elif self.share_mode.server_status.status == ServerStatus.STATUS_WORKING:
- if self.share_mode.server_status.autostart_timer_datetime:
+ if self.settings.get("general", "autostart_timer"):
self.set_server_status_indicator_working(
strings._("gui_status_indicator_share_scheduled")
)
@@ -306,9 +306,14 @@ class Tab(QtWidgets.QWidget):
strings._("gui_status_indicator_share_stopped")
)
elif self.website_mode.server_status.status == ServerStatus.STATUS_WORKING:
- self.set_server_status_indicator_working(
- strings._("gui_status_indicator_share_working")
- )
+ if self.website_mode.server_status.autostart_timer_datetime:
+ self.set_server_status_indicator_working(
+ strings._("gui_status_indicator_share_scheduled")
+ )
+ else:
+ self.set_server_status_indicator_working(
+ strings._("gui_status_indicator_share_working")
+ )
elif self.website_mode.server_status.status == ServerStatus.STATUS_STARTED:
self.set_server_status_indicator_started(
strings._("gui_status_indicator_share_started")
@@ -320,7 +325,7 @@ class Tab(QtWidgets.QWidget):
strings._("gui_status_indicator_receive_stopped")
)
elif self.receive_mode.server_status.status == ServerStatus.STATUS_WORKING:
- if self.receive_mode.server_status.autostart_timer_datetime:
+ if self.settings.get("general", "autostart_timer"):
self.set_server_status_indicator_working(
strings._("gui_status_indicator_receive_scheduled")
)
diff --git a/onionshare_gui/tab_widget.py b/onionshare_gui/tab_widget.py
index e14d1f27..1ef82cad 100644
--- a/onionshare_gui/tab_widget.py
+++ b/onionshare_gui/tab_widget.py
@@ -59,6 +59,7 @@ class TabWidget(QtWidgets.QTabWidget):
# Use a custom tab bar
tab_bar = TabBar()
tab_bar.move_new_tab_button.connect(self.move_new_tab_button)
+ tab_bar.currentChanged.connect(self.tab_changed)
self.setTabBar(tab_bar)
# Set up the tab widget
@@ -106,6 +107,24 @@ class TabWidget(QtWidgets.QTabWidget):
self.new_tab_button.move(pos)
self.new_tab_button.raise_()
+ def tab_changed(self):
+ # Active tab was changed
+ tab_id = self.currentIndex()
+ self.common.log("TabWidget", "tab_changed", f"Tab was changed to {tab_id}")
+ try:
+ mode = self.tabs[tab_id].get_mode()
+ if mode:
+ # Update the server status indicator to reflect that of the current tab
+ self.tabs[tab_id].update_server_status_indicator()
+ else:
+ # If this tab doesn't have a mode set yet, blank the server status indicator
+ self.status_bar.server_status_image_label.clear()
+ self.status_bar.server_status_label.clear()
+ except KeyError:
+ # When all current tabs are closed, index briefly drops to -1 before resetting to 0
+ # which will otherwise trigger a KeyError on tab.get_mode() above.
+ pass
+
def new_tab_clicked(self):
# Create a new tab
self.add_tab()
diff --git a/onionshare_gui/widgets.py b/onionshare_gui/widgets.py
index 74ef2c88..58ddd1b5 100644
--- a/onionshare_gui/widgets.py
+++ b/onionshare_gui/widgets.py
@@ -18,7 +18,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from PyQt5 import QtCore, QtWidgets, QtGui
-
+from onionshare import strings
+import qrcode
class Alert(QtWidgets.QMessageBox):
"""
@@ -90,3 +91,64 @@ class MinimumWidthWidget(QtWidgets.QWidget):
super(MinimumWidthWidget, self).__init__()
self.setMinimumWidth(width)
+
+class Image(qrcode.image.base.BaseImage):
+ """
+ A custom Image class, for use with the QR Code pixmap.
+ """
+
+ def __init__(self, border, width, box_size):
+ self.border = border
+ self.width = width
+ self.box_size = box_size
+ size = (width + border * 2) * box_size
+ self._image = QtGui.QImage(
+ size, size, QtGui.QImage.Format_RGB16)
+ self._image.fill(QtCore.Qt.white)
+
+ def pixmap(self):
+ return QtGui.QPixmap.fromImage(self._image)
+
+ def drawrect(self, row, col):
+ painter = QtGui.QPainter(self._image)
+ painter.fillRect(
+ (col + self.border) * self.box_size,
+ (row + self.border) * self.box_size,
+ self.box_size, self.box_size,
+ QtCore.Qt.black)
+
+ def save(self, stream, kind=None):
+ pass
+
+
+class QRCodeDialog(QtWidgets.QDialog):
+ """
+ A dialog showing a QR code.
+ """
+
+ def __init__(self, common, text):
+ super(QRCodeDialog, self).__init__()
+
+ self.common = common
+ self.text = text
+
+ self.common.log("QrCode", "__init__")
+
+ self.qr_label = QtWidgets.QLabel(self)
+ self.qr_label.setPixmap(
+ qrcode.make(self.text, image_factory=Image).pixmap())
+
+ self.qr_label_description = QtWidgets.QLabel(self)
+ self.qr_label_description.setText(strings._("gui_qr_code_description"))
+ self.qr_label_description.setWordWrap(True)
+
+ self.setWindowTitle(strings._("gui_qr_code_dialog_title"))
+ self.setWindowIcon(
+ QtGui.QIcon(self.common.get_resource_path("images/logo.png"))
+ )
+ layout = QtWidgets.QVBoxLayout(self)
+ layout.addWidget(self.qr_label)
+ layout.addWidget(self.qr_label_description)
+
+ self.exec_()
+