qol
This commit is contained in:
108
gui.py
108
gui.py
@@ -23,13 +23,41 @@ def normalize_file_path(path_str: str) -> str:
|
|||||||
else:
|
else:
|
||||||
path_str = urllib.parse.unquote(path_str[7:])
|
path_str = urllib.parse.unquote(path_str[7:])
|
||||||
return os.path.abspath(path_str)
|
return os.path.abspath(path_str)
|
||||||
|
|
||||||
|
def copy_to_clipboard(text: str):
|
||||||
|
if not text:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
cb = QApplication.clipboard()
|
||||||
|
if cb:
|
||||||
|
cb.setText(text, QClipboard.Mode.Clipboard)
|
||||||
|
cb.setText(text, QClipboard.Mode.Selection)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Qt clipboard error: {e}")
|
||||||
|
|
||||||
|
# Fallback to system CLI clipboard utilities for Wayland/X11 persistence
|
||||||
|
if shutil.which("wl-copy"):
|
||||||
|
try:
|
||||||
|
subprocess.run(["wl-copy", text], check=False)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"wl-copy error: {e}")
|
||||||
|
elif shutil.which("xclip"):
|
||||||
|
try:
|
||||||
|
subprocess.run(["xclip", "-selection", "clipboard"], input=text.encode("utf-8"), check=False)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"xclip error: {e}")
|
||||||
|
elif shutil.which("xsel"):
|
||||||
|
try:
|
||||||
|
subprocess.run(["xsel", "--clipboard", "--input"], input=text.encode("utf-8"), check=False)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"xsel error: {e}")
|
||||||
from PySide6.QtCore import Qt, QThread, QObject, Signal, QUrl, QFile, QIODevice, QTimer, QMimeData
|
from PySide6.QtCore import Qt, QThread, QObject, Signal, QUrl, QFile, QIODevice, QTimer, QMimeData
|
||||||
from PySide6.QtGui import QIcon, QAction, QPixmap, QPainter, QColor, QFont, QPen, QImage, QDesktopServices, QDrag
|
from PySide6.QtGui import QIcon, QAction, QPixmap, QPainter, QColor, QFont, QPen, QImage, QDesktopServices, QDrag, QClipboard
|
||||||
from PySide6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QHttpMultiPart, QHttpPart, QNetworkReply, QLocalServer, QLocalSocket
|
from PySide6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QHttpMultiPart, QHttpPart, QNetworkReply, QLocalServer, QLocalSocket
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QApplication, QSystemTrayIcon, QMenu, QDialog, QVBoxLayout, QHBoxLayout,
|
QApplication, QSystemTrayIcon, QMenu, QDialog, QVBoxLayout, QHBoxLayout,
|
||||||
QLabel, QLineEdit, QComboBox, QCheckBox, QPushButton, QGroupBox,
|
QLabel, QLineEdit, QComboBox, QCheckBox, QPushButton, QGroupBox,
|
||||||
QFormLayout, QMessageBox, QFileDialog, QSlider, QScrollArea, QFrame, QGridLayout, QWidget
|
QFormLayout, QMessageBox, QFileDialog, QSlider, QScrollArea, QFrame, QGridLayout, QWidget, QSizePolicy
|
||||||
)
|
)
|
||||||
|
|
||||||
# Configuration paths
|
# Configuration paths
|
||||||
@@ -70,7 +98,8 @@ DEFAULT_CONFIG = {
|
|||||||
"enable_notifications": True,
|
"enable_notifications": True,
|
||||||
"show_progress_dialog": True,
|
"show_progress_dialog": True,
|
||||||
"autostart": False,
|
"autostart": False,
|
||||||
"icon_theme": "dark"
|
"icon_theme": "dark",
|
||||||
|
"clipboard_url_type": "post"
|
||||||
}
|
}
|
||||||
|
|
||||||
def load_config():
|
def load_config():
|
||||||
@@ -353,7 +382,17 @@ class SettingsDialog(QDialog):
|
|||||||
theme_layout.addWidget(self.cb_icon_theme)
|
theme_layout.addWidget(self.cb_icon_theme)
|
||||||
theme_layout.addStretch()
|
theme_layout.addStretch()
|
||||||
behavior_layout.addLayout(theme_layout)
|
behavior_layout.addLayout(theme_layout)
|
||||||
|
|
||||||
|
# Copied Link Format Selection
|
||||||
|
url_type_layout = QHBoxLayout()
|
||||||
|
url_type_label = QLabel("Copied Link Format:")
|
||||||
|
self.cb_url_type = QComboBox()
|
||||||
|
self.cb_url_type.addItems(["Post Page URL (e.g. /v/123)", "Direct File URL (e.g. /files/123.png)"])
|
||||||
|
url_type_layout.addWidget(url_type_label)
|
||||||
|
url_type_layout.addWidget(self.cb_url_type)
|
||||||
|
url_type_layout.addStretch()
|
||||||
|
behavior_layout.addLayout(url_type_layout)
|
||||||
|
|
||||||
main_layout.addWidget(grp_behavior)
|
main_layout.addWidget(grp_behavior)
|
||||||
|
|
||||||
# Section 4: Sound Notification
|
# Section 4: Sound Notification
|
||||||
@@ -442,6 +481,9 @@ class SettingsDialog(QDialog):
|
|||||||
icon_theme = config.get("icon_theme", "dark").lower()
|
icon_theme = config.get("icon_theme", "dark").lower()
|
||||||
self.cb_icon_theme.setCurrentIndex(1 if icon_theme == "light" else 0)
|
self.cb_icon_theme.setCurrentIndex(1 if icon_theme == "light" else 0)
|
||||||
|
|
||||||
|
url_type = config.get("clipboard_url_type", "post").lower()
|
||||||
|
self.cb_url_type.setCurrentIndex(1 if url_type == "direct" else 0)
|
||||||
|
|
||||||
self.chk_sound.setChecked(config.get("play_success_sound", False))
|
self.chk_sound.setChecked(config.get("play_success_sound", False))
|
||||||
self.txt_sound_path.setText(config.get("success_audio_path", ""))
|
self.txt_sound_path.setText(config.get("success_audio_path", ""))
|
||||||
|
|
||||||
@@ -520,6 +562,7 @@ class SettingsDialog(QDialog):
|
|||||||
"show_progress_dialog": self.chk_progress.isChecked(),
|
"show_progress_dialog": self.chk_progress.isChecked(),
|
||||||
"autostart": self.chk_autostart.isChecked(),
|
"autostart": self.chk_autostart.isChecked(),
|
||||||
"icon_theme": "light" if self.cb_icon_theme.currentIndex() == 1 else "dark",
|
"icon_theme": "light" if self.cb_icon_theme.currentIndex() == 1 else "dark",
|
||||||
|
"clipboard_url_type": "direct" if self.cb_url_type.currentIndex() == 1 else "post",
|
||||||
"play_success_sound": self.chk_sound.isChecked(),
|
"play_success_sound": self.chk_sound.isChecked(),
|
||||||
"success_audio_path": self.txt_sound_path.text().strip(),
|
"success_audio_path": self.txt_sound_path.text().strip(),
|
||||||
"success_audio_volume": self.slider_volume.value()
|
"success_audio_volume": self.slider_volume.value()
|
||||||
@@ -575,7 +618,9 @@ class GalleryCard(QFrame):
|
|||||||
self.item = item
|
self.item = item
|
||||||
self.on_delete = on_delete
|
self.on_delete = on_delete
|
||||||
self.setFrameShape(QFrame.StyledPanel)
|
self.setFrameShape(QFrame.StyledPanel)
|
||||||
self.setFixedHeight(195)
|
self.setFixedHeight(190)
|
||||||
|
self.setMinimumWidth(160)
|
||||||
|
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
self.setStyleSheet("""
|
self.setStyleSheet("""
|
||||||
GalleryCard {
|
GalleryCard {
|
||||||
background-color: #1e1e24;
|
background-color: #1e1e24;
|
||||||
@@ -595,7 +640,8 @@ class GalleryCard(QFrame):
|
|||||||
# Thumbnail with Drag & Drop support
|
# Thumbnail with Drag & Drop support
|
||||||
file_path = item.get("file_path", "")
|
file_path = item.get("file_path", "")
|
||||||
self.thumb_label = DraggableImageLabel(file_path)
|
self.thumb_label = DraggableImageLabel(file_path)
|
||||||
self.thumb_label.setFixedSize(160, 100)
|
self.thumb_label.setFixedHeight(95)
|
||||||
|
self.thumb_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
self.thumb_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
self.thumb_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
self.thumb_label.setCursor(Qt.CursorShape.OpenHandCursor)
|
self.thumb_label.setCursor(Qt.CursorShape.OpenHandCursor)
|
||||||
self.thumb_label.setToolTip("Click & Drag to drop file into other apps")
|
self.thumb_label.setToolTip("Click & Drag to drop file into other apps")
|
||||||
@@ -664,7 +710,7 @@ class GalleryCard(QFrame):
|
|||||||
def on_copy_link(self):
|
def on_copy_link(self):
|
||||||
url = self.item.get("url", "")
|
url = self.item.get("url", "")
|
||||||
if url:
|
if url:
|
||||||
QApplication.clipboard().setText(url)
|
copy_to_clipboard(url)
|
||||||
|
|
||||||
def on_open_url(self):
|
def on_open_url(self):
|
||||||
url = self.item.get("url", "")
|
url = self.item.get("url", "")
|
||||||
@@ -715,25 +761,42 @@ class GalleryWindow(QDialog):
|
|||||||
# Scroll Area for Gallery Cards
|
# Scroll Area for Gallery Cards
|
||||||
self.scroll_area = QScrollArea()
|
self.scroll_area = QScrollArea()
|
||||||
self.scroll_area.setWidgetResizable(True)
|
self.scroll_area.setWidgetResizable(True)
|
||||||
|
self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||||
|
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||||
self.scroll_area.setStyleSheet("QScrollArea { border: 1px solid #333340; background: #141418; border-radius: 6px; }")
|
self.scroll_area.setStyleSheet("QScrollArea { border: 1px solid #333340; background: #141418; border-radius: 6px; }")
|
||||||
|
|
||||||
self.scroll_content = QWidget()
|
self.scroll_content = QWidget()
|
||||||
self.grid_layout = QGridLayout(self.scroll_content)
|
self.grid_layout = QGridLayout(self.scroll_content)
|
||||||
self.grid_layout.setContentsMargins(12, 12, 12, 12)
|
self.grid_layout.setContentsMargins(12, 12, 12, 12)
|
||||||
self.grid_layout.setSpacing(12)
|
self.grid_layout.setSpacing(12)
|
||||||
self.grid_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
self.grid_layout.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft)
|
||||||
|
|
||||||
self.scroll_area.setWidget(self.scroll_content)
|
self.scroll_area.setWidget(self.scroll_content)
|
||||||
main_layout.addWidget(self.scroll_area)
|
main_layout.addWidget(self.scroll_area)
|
||||||
|
|
||||||
|
self.current_cols = 0
|
||||||
self.load_items()
|
self.load_items()
|
||||||
|
|
||||||
|
def get_column_count(self):
|
||||||
|
w = self.scroll_area.viewport().width()
|
||||||
|
if w <= 0:
|
||||||
|
w = self.width() - 40
|
||||||
|
card_min_w = 160
|
||||||
|
spacing = 12
|
||||||
|
margin_padding = 40
|
||||||
|
cols = max(1, (w - margin_padding + spacing) // (card_min_w + spacing))
|
||||||
|
return cols
|
||||||
|
|
||||||
def load_items(self):
|
def load_items(self):
|
||||||
while self.grid_layout.count():
|
while self.grid_layout.count():
|
||||||
child = self.grid_layout.takeAt(0)
|
child = self.grid_layout.takeAt(0)
|
||||||
if child.widget():
|
if child.widget():
|
||||||
child.widget().deleteLater()
|
child.widget().deleteLater()
|
||||||
|
|
||||||
|
# Reset column stretches
|
||||||
|
for c in range(20):
|
||||||
|
self.grid_layout.setColumnStretch(c, 0)
|
||||||
|
|
||||||
history = load_history()
|
history = load_history()
|
||||||
query = self.txt_search.text().strip().lower()
|
query = self.txt_search.text().strip().lower()
|
||||||
if query:
|
if query:
|
||||||
@@ -746,13 +809,24 @@ class GalleryWindow(QDialog):
|
|||||||
self.grid_layout.addWidget(lbl_empty, 0, 0)
|
self.grid_layout.addWidget(lbl_empty, 0, 0)
|
||||||
return
|
return
|
||||||
|
|
||||||
cols = 4
|
cols = self.get_column_count()
|
||||||
|
self.current_cols = cols
|
||||||
|
|
||||||
|
for c in range(cols):
|
||||||
|
self.grid_layout.setColumnStretch(c, 1)
|
||||||
|
|
||||||
for idx, item in enumerate(history):
|
for idx, item in enumerate(history):
|
||||||
card = GalleryCard(item, on_delete=self.delete_item)
|
card = GalleryCard(item, on_delete=self.delete_item)
|
||||||
row = idx // cols
|
row = idx // cols
|
||||||
col = idx % cols
|
col = idx % cols
|
||||||
self.grid_layout.addWidget(card, row, col)
|
self.grid_layout.addWidget(card, row, col)
|
||||||
|
|
||||||
|
def resizeEvent(self, event):
|
||||||
|
super().resizeEvent(event)
|
||||||
|
cols = self.get_column_count()
|
||||||
|
if cols != getattr(self, "current_cols", 0):
|
||||||
|
self.load_items()
|
||||||
|
|
||||||
def delete_item(self, item):
|
def delete_item(self, item):
|
||||||
history = load_history()
|
history = load_history()
|
||||||
history = [h for h in history if h.get("url") != item.get("url")]
|
history = [h for h in history if h.get("url") != item.get("url")]
|
||||||
@@ -1240,9 +1314,19 @@ class SystemTrayApp(QObject):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
res = json.loads(response_text)
|
res = json.loads(response_text)
|
||||||
if res.get("success") and res.get("url"):
|
if res.get("success") and (res.get("url") or res.get("file_url") or res.get("direct_url") or res.get("target_url") or res.get("file")):
|
||||||
item_url = res.get("url")
|
config = get_env_config()
|
||||||
QApplication.clipboard().setText(item_url)
|
url_type = config.get("clipboard_url_type", "post").lower()
|
||||||
|
|
||||||
|
post_url = res.get("url") or res.get("post_url") or ""
|
||||||
|
direct_url = res.get("file_url") or res.get("direct_url") or res.get("target_url") or res.get("file") or ""
|
||||||
|
|
||||||
|
if url_type == "direct" and direct_url:
|
||||||
|
item_url = direct_url
|
||||||
|
else:
|
||||||
|
item_url = post_url or direct_url or res.get("url", "")
|
||||||
|
|
||||||
|
copy_to_clipboard(item_url)
|
||||||
|
|
||||||
# Add to history gallery
|
# Add to history gallery
|
||||||
thumb = generate_thumbnail(file_path)
|
thumb = generate_thumbnail(file_path)
|
||||||
|
|||||||
Reference in New Issue
Block a user