mirror of
https://github.com/Yuvi9587/Kemono-Downloader.git
synced 2025-12-29 16:14:44 +00:00
Compare commits
6 Commits
cfd869e05a
...
9e58a9d574
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e58a9d574 | ||
|
|
d67de87a11 | ||
|
|
149f217f2f | ||
|
|
874902ad60 | ||
|
|
440cf60d90 | ||
|
|
fb446a1e28 |
5529
main_window_old.py
5529
main_window_old.py
File diff suppressed because it is too large
Load Diff
@@ -127,13 +127,22 @@ use_cookie =False ,
|
||||
cookie_text="",
|
||||
selected_cookie_file=None,
|
||||
app_base_dir=None,
|
||||
manga_filename_style_for_sort_check =None
|
||||
manga_filename_style_for_sort_check=None,
|
||||
processed_post_ids=None # --- ADD THIS ARGUMENT ---
|
||||
):
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
|
||||
# --- ADD THIS BLOCK ---
|
||||
# Ensure processed_post_ids is a set for fast lookups
|
||||
if processed_post_ids is None:
|
||||
processed_post_ids = set()
|
||||
else:
|
||||
processed_post_ids = set(processed_post_ids)
|
||||
# --- END OF ADDITION ---
|
||||
|
||||
service, user_id, target_post_id = extract_post_info(api_url_input)
|
||||
|
||||
if cancellation_event and cancellation_event.is_set():
|
||||
@@ -149,6 +158,11 @@ manga_filename_style_for_sort_check =None
|
||||
if use_cookie and app_base_dir:
|
||||
cookies_for_api = prepare_cookies_for_request(use_cookie, cookie_text, selected_cookie_file, app_base_dir, logger, target_domain=api_domain)
|
||||
if target_post_id:
|
||||
# --- ADD THIS CHECK FOR RESTORE ---
|
||||
if target_post_id in processed_post_ids:
|
||||
logger(f" Skipping already processed target post ID: {target_post_id}")
|
||||
return
|
||||
# --- END OF ADDITION ---
|
||||
direct_post_api_url = f"https://{api_domain}/api/v1/{service}/user/{user_id}/post/{target_post_id}"
|
||||
logger(f" Attempting direct fetch for target post: {direct_post_api_url}")
|
||||
try:
|
||||
@@ -234,6 +248,15 @@ manga_filename_style_for_sort_check =None
|
||||
break
|
||||
if cancellation_event and cancellation_event.is_set(): return
|
||||
if all_posts_for_manga_mode:
|
||||
# --- ADD THIS BLOCK TO FILTER POSTS IN MANGA MODE ---
|
||||
if processed_post_ids:
|
||||
original_count = len(all_posts_for_manga_mode)
|
||||
all_posts_for_manga_mode = [post for post in all_posts_for_manga_mode if post.get('id') not in processed_post_ids]
|
||||
skipped_count = original_count - len(all_posts_for_manga_mode)
|
||||
if skipped_count > 0:
|
||||
logger(f" Manga Mode: Skipped {skipped_count} already processed post(s) before sorting.")
|
||||
# --- END OF ADDITION ---
|
||||
|
||||
logger(f" Manga Mode: Fetched {len(all_posts_for_manga_mode)} total posts. Sorting by publication date (oldest first)...")
|
||||
def sort_key_tuple(post):
|
||||
published_date_str = post.get('published')
|
||||
@@ -261,8 +284,6 @@ manga_filename_style_for_sort_check =None
|
||||
yield all_posts_for_manga_mode[i:i + page_size]
|
||||
return
|
||||
|
||||
|
||||
|
||||
if manga_mode and not target_post_id and (manga_filename_style_for_sort_check == STYLE_DATE_POST_TITLE):
|
||||
logger(f" Manga Mode (Style: {STYLE_DATE_POST_TITLE}): Processing posts in default API order (newest first).")
|
||||
|
||||
@@ -305,6 +326,16 @@ manga_filename_style_for_sort_check =None
|
||||
logger(f"❌ Unexpected error fetching page {current_page_num} (offset {current_offset}): {e}")
|
||||
traceback.print_exc()
|
||||
break
|
||||
|
||||
# --- ADD THIS BLOCK TO FILTER POSTS IN STANDARD MODE ---
|
||||
if processed_post_ids:
|
||||
original_count = len(posts_batch)
|
||||
posts_batch = [post for post in posts_batch if post.get('id') not in processed_post_ids]
|
||||
skipped_count = original_count - len(posts_batch)
|
||||
if skipped_count > 0:
|
||||
logger(f" Skipped {skipped_count} already processed post(s) from page {current_page_num}.")
|
||||
# --- END OF ADDITION ---
|
||||
|
||||
if not posts_batch:
|
||||
if target_post_id and not processed_target_post_flag:
|
||||
logger(f"❌ Target post {target_post_id} not found after checking all available pages (API returned no more posts at offset {current_offset}).")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ from PyQt5.QtWidgets import (
|
||||
# --- Local Application Imports ---
|
||||
from ...i18n.translator import get_translation
|
||||
from ..main_window import get_app_icon_object
|
||||
|
||||
from ...utils.resolution import get_dark_theme
|
||||
|
||||
class CookieHelpDialog(QDialog):
|
||||
"""
|
||||
|
||||
@@ -13,7 +13,7 @@ from PyQt5.QtWidgets import (
|
||||
from ...i18n.translator import get_translation
|
||||
# get_app_icon_object is defined in the main window module in this refactoring plan.
|
||||
from ..main_window import get_app_icon_object
|
||||
|
||||
from ...utils.resolution import get_dark_theme
|
||||
|
||||
class DownloadExtractedLinksDialog(QDialog):
|
||||
"""
|
||||
@@ -144,16 +144,22 @@ class DownloadExtractedLinksDialog(QDialog):
|
||||
|
||||
def _apply_theme(self):
|
||||
"""Applies the current theme from the parent application."""
|
||||
is_dark_theme = self.parent() and hasattr(self.parent_app, 'current_theme') and self.parent_app.current_theme == "dark"
|
||||
is_dark_theme = self.parent_app and self.parent_app.current_theme == "dark"
|
||||
|
||||
if is_dark_theme and hasattr(self.parent_app, 'get_dark_theme'):
|
||||
self.setStyleSheet(self.parent_app.get_dark_theme())
|
||||
if is_dark_theme:
|
||||
# Get the scale factor from the parent app
|
||||
scale = getattr(self.parent_app, 'scale_factor', 1)
|
||||
# Call the imported function with the correct scale
|
||||
self.setStyleSheet(get_dark_theme(scale))
|
||||
else:
|
||||
# Explicitly set a blank stylesheet for light mode
|
||||
self.setStyleSheet("")
|
||||
|
||||
# Set header text color based on theme
|
||||
header_color = Qt.cyan if is_dark_theme else Qt.blue
|
||||
for i in range(self.links_list_widget.count()):
|
||||
item = self.links_list_widget.item(i)
|
||||
# Headers are not checkable
|
||||
# Headers are not checkable (they have no checkable flag)
|
||||
if not item.flags() & Qt.ItemIsUserCheckable:
|
||||
item.setForeground(header_color)
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ from PyQt5.QtWidgets import (
|
||||
# --- Local Application Imports ---
|
||||
from ...i18n.translator import get_translation
|
||||
from ..main_window import get_app_icon_object
|
||||
from ...utils.resolution import get_dark_theme
|
||||
|
||||
|
||||
class DownloadHistoryDialog (QDialog ):
|
||||
@@ -23,7 +24,7 @@ class DownloadHistoryDialog (QDialog ):
|
||||
self .last_3_downloaded_entries =last_3_downloaded_entries
|
||||
self .first_processed_entries =first_processed_entries
|
||||
self .setModal (True )
|
||||
|
||||
self._apply_theme()
|
||||
# Patch missing creator_display_name and creator_name using parent_app.creator_name_cache if available
|
||||
creator_name_cache = getattr(parent_app, 'creator_name_cache', None)
|
||||
if creator_name_cache:
|
||||
@@ -158,6 +159,14 @@ class DownloadHistoryDialog (QDialog ):
|
||||
return get_translation (self .parent_app .current_selected_language ,key ,default_text )
|
||||
return default_text
|
||||
|
||||
def _apply_theme(self):
|
||||
"""Applies the current theme from the parent application."""
|
||||
if self.parent_app and self.parent_app.current_theme == "dark":
|
||||
scale = getattr(self.parent_app, 'scale_factor', 1)
|
||||
self.setStyleSheet(get_dark_theme(scale))
|
||||
else:
|
||||
self.setStyleSheet("QDialog { background-color: #f0f0f0; }")
|
||||
|
||||
def _save_history_to_txt (self ):
|
||||
if not self .last_3_downloaded_entries and not self .first_processed_entries :
|
||||
QMessageBox .information (self ,self ._tr ("no_download_history_header","No Downloads Yet"),
|
||||
|
||||
@@ -21,6 +21,7 @@ from ...i18n.translator import get_translation
|
||||
from ..main_window import get_app_icon_object
|
||||
from ...core.api_client import download_from_api
|
||||
from ...utils.network_utils import extract_post_info, prepare_cookies_for_request
|
||||
from ...utils.resolution import get_dark_theme
|
||||
|
||||
|
||||
class PostsFetcherThread (QThread ):
|
||||
@@ -129,6 +130,7 @@ class PostsFetcherThread (QThread ):
|
||||
self .status_update .emit (self .parent_dialog ._tr ("post_fetch_finished_status","Finished fetching posts for selected creators."))
|
||||
self .finished_signal .emit ()
|
||||
|
||||
|
||||
class EmptyPopupDialog (QDialog ):
|
||||
"""A simple empty popup dialog."""
|
||||
SCOPE_CHARACTERS ="Characters"
|
||||
@@ -289,9 +291,14 @@ class EmptyPopupDialog (QDialog ):
|
||||
|
||||
self ._retranslate_ui ()
|
||||
|
||||
if self .parent_app and hasattr (self .parent_app ,'get_dark_theme')and self .parent_app .current_theme =="dark":
|
||||
self .setStyleSheet (self .parent_app .get_dark_theme ())
|
||||
|
||||
if self.parent_app and self.parent_app.current_theme == "dark":
|
||||
# Get the scale factor from the parent app
|
||||
scale = getattr(self.parent_app, 'scale_factor', 1)
|
||||
# Call the imported function with the correct scale
|
||||
self.setStyleSheet(get_dark_theme(scale))
|
||||
else:
|
||||
# Explicitly set a blank stylesheet for light mode
|
||||
self.setStyleSheet("")
|
||||
|
||||
self .resize (int ((self .original_size .width ()+50 )*scale_factor ),int ((self .original_size .height ()+100 )*scale_factor ))
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from ...i18n.translator import get_translation
|
||||
from ..assets import get_app_icon_object
|
||||
# Corrected Import: The filename uses PascalCase.
|
||||
from .ExportOptionsDialog import ExportOptionsDialog
|
||||
|
||||
from ...utils.resolution import get_dark_theme
|
||||
|
||||
class ErrorFilesDialog(QDialog):
|
||||
"""
|
||||
@@ -132,9 +132,14 @@ class ErrorFilesDialog(QDialog):
|
||||
|
||||
def _apply_theme(self):
|
||||
"""Applies the current theme from the parent application."""
|
||||
if self.parent_app and hasattr(self.parent_app, 'current_theme') and self.parent_app.current_theme == "dark":
|
||||
if hasattr(self.parent_app, 'get_dark_theme'):
|
||||
self.setStyleSheet(self.parent_app.get_dark_theme())
|
||||
if self.parent_app and self.parent_app.current_theme == "dark":
|
||||
# Get the scale factor from the parent app
|
||||
scale = getattr(self.parent_app, 'scale_factor', 1)
|
||||
# Call the imported function with the correct scale
|
||||
self.setStyleSheet(get_dark_theme(scale))
|
||||
else:
|
||||
# Explicitly set a blank stylesheet for light mode
|
||||
self.setStyleSheet("")
|
||||
|
||||
def _select_all_items(self):
|
||||
"""Checks all items in the list."""
|
||||
|
||||
@@ -10,7 +10,7 @@ from PyQt5.QtWidgets import (
|
||||
from ...i18n.translator import get_translation
|
||||
# get_app_icon_object is defined in the main window module in this refactoring plan.
|
||||
from ..main_window import get_app_icon_object
|
||||
|
||||
from ...utils.resolution import get_dark_theme
|
||||
|
||||
class ExportOptionsDialog(QDialog):
|
||||
"""
|
||||
|
||||
@@ -16,7 +16,7 @@ from ...i18n.translator import get_translation
|
||||
from ..assets import get_app_icon_object
|
||||
from ...utils.network_utils import prepare_cookies_for_request
|
||||
from .CookieHelpDialog import CookieHelpDialog
|
||||
|
||||
from ...utils.resolution import get_dark_theme
|
||||
|
||||
class FavoriteArtistsDialog (QDialog ):
|
||||
"""Dialog to display and select favorite artists."""
|
||||
|
||||
@@ -25,7 +25,7 @@ from ...utils.network_utils import prepare_cookies_for_request
|
||||
# Corrected Import: Import CookieHelpDialog directly from its own module
|
||||
from .CookieHelpDialog import CookieHelpDialog
|
||||
from ...core.api_client import download_from_api
|
||||
|
||||
from ...utils.resolution import get_dark_theme
|
||||
|
||||
class FavoritePostsFetcherThread (QThread ):
|
||||
"""Worker thread to fetch favorite posts and creator names."""
|
||||
|
||||
@@ -11,6 +11,7 @@ from PyQt5.QtWidgets import (
|
||||
# --- Local Application Imports ---
|
||||
# This assumes the new project structure is in place.
|
||||
from ...i18n.translator import get_translation
|
||||
from ...utils.resolution import get_dark_theme
|
||||
from ..main_window import get_app_icon_object
|
||||
from ...config.constants import (
|
||||
THEME_KEY, LANGUAGE_KEY, DOWNLOAD_LOCATION_KEY
|
||||
@@ -113,8 +114,9 @@ class FutureSettingsDialog(QDialog):
|
||||
|
||||
def _apply_theme(self):
|
||||
"""Applies the current theme from the parent application."""
|
||||
if self.parent_app.current_theme == "dark":
|
||||
self.setStyleSheet(self.parent_app.get_dark_theme())
|
||||
if self.parent_app and self.parent_app.current_theme == "dark":
|
||||
scale = getattr(self.parent_app, 'scale_factor', 1)
|
||||
self.setStyleSheet(get_dark_theme(scale))
|
||||
else:
|
||||
self.setStyleSheet("")
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from PyQt5.QtWidgets import (
|
||||
# --- Local Application Imports ---
|
||||
from ...i18n.translator import get_translation
|
||||
from ..main_window import get_app_icon_object
|
||||
|
||||
from ...utils.resolution import get_dark_theme
|
||||
|
||||
class TourStepWidget(QWidget):
|
||||
"""
|
||||
|
||||
@@ -8,7 +8,7 @@ from PyQt5.QtWidgets import (
|
||||
# --- Local Application Imports ---
|
||||
from ...i18n.translator import get_translation
|
||||
from ..main_window import get_app_icon_object
|
||||
|
||||
from ...utils.resolution import get_dark_theme
|
||||
|
||||
class KnownNamesFilterDialog(QDialog):
|
||||
"""
|
||||
@@ -102,8 +102,14 @@ class KnownNamesFilterDialog(QDialog):
|
||||
|
||||
def _apply_theme(self):
|
||||
"""Applies the current theme from the parent application."""
|
||||
if self.parent_app and hasattr(self.parent_app, 'get_dark_theme') and self.parent_app.current_theme == "dark":
|
||||
self.setStyleSheet(self.parent_app.get_dark_theme())
|
||||
if self.parent_app and self.parent_app.current_theme == "dark":
|
||||
# Get the scale factor from the parent app
|
||||
scale = getattr(self.parent_app, 'scale_factor', 1)
|
||||
# Call the imported function with the correct scale
|
||||
self.setStyleSheet(get_dark_theme(scale))
|
||||
else:
|
||||
# Explicitly set a blank stylesheet for light mode
|
||||
self.setStyleSheet("")
|
||||
|
||||
def _populate_list_widget(self):
|
||||
"""Populates the list widget with the known names."""
|
||||
|
||||
@@ -2,6 +2,7 @@ from PyQt5.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QRadioButton, QDialogButtonBox, QButtonGroup, QLabel, QComboBox, QHBoxLayout, QCheckBox
|
||||
)
|
||||
from PyQt5.QtCore import Qt
|
||||
from ...utils.resolution import get_dark_theme
|
||||
|
||||
class MoreOptionsDialog(QDialog):
|
||||
"""
|
||||
@@ -12,6 +13,7 @@ class MoreOptionsDialog(QDialog):
|
||||
|
||||
def __init__(self, parent=None, current_scope=None, current_format=None, single_pdf_checked=False):
|
||||
super().__init__(parent)
|
||||
self.parent_app = parent
|
||||
self.setWindowTitle("More Options")
|
||||
self.setMinimumWidth(350)
|
||||
|
||||
@@ -22,7 +24,7 @@ class MoreOptionsDialog(QDialog):
|
||||
layout.addWidget(self.description_label)
|
||||
self.radio_button_group = QButtonGroup(self)
|
||||
self.radio_content = QRadioButton("Description/Content")
|
||||
self.radio_comments = QRadioButton("Comments")
|
||||
self.radio_comments = QRadioButton("Comments (Not Working)")
|
||||
self.radio_button_group.addButton(self.radio_content)
|
||||
self.radio_button_group.addButton(self.radio_comments)
|
||||
layout.addWidget(self.radio_content)
|
||||
@@ -62,7 +64,7 @@ class MoreOptionsDialog(QDialog):
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
self.setLayout(layout)
|
||||
|
||||
self._apply_theme()
|
||||
def update_single_pdf_checkbox_state(self, text):
|
||||
"""Enable the Single PDF checkbox only if the format is PDF."""
|
||||
is_pdf = (text.upper() == "PDF")
|
||||
@@ -81,3 +83,14 @@ class MoreOptionsDialog(QDialog):
|
||||
def get_single_pdf_state(self):
|
||||
"""Returns the state of the Single PDF checkbox."""
|
||||
return self.single_pdf_checkbox.isChecked() and self.single_pdf_checkbox.isEnabled()
|
||||
|
||||
def _apply_theme(self):
|
||||
"""Applies the current theme from the parent application."""
|
||||
if self.parent_app and self.parent_app.current_theme == "dark":
|
||||
# Get the scale factor from the parent app
|
||||
scale = getattr(self.parent_app, 'scale_factor', 1)
|
||||
# Call the imported function with the correct scale
|
||||
self.setStyleSheet(get_dark_theme(scale))
|
||||
else:
|
||||
# Explicitly set a blank stylesheet for light mode
|
||||
self.setStyleSheet("")
|
||||
|
||||
93
src/ui/dialogs/SupportDialog.py
Normal file
93
src/ui/dialogs/SupportDialog.py
Normal file
@@ -0,0 +1,93 @@
|
||||
# src/ui/dialogs/SupportDialog.py
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QLabel, QFrame, QDialogButtonBox
|
||||
)
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QFont
|
||||
|
||||
# Assuming execution from project root, so we can import from utils
|
||||
from ...utils.resolution import get_dark_theme
|
||||
|
||||
class SupportDialog(QDialog):
|
||||
"""
|
||||
A dialog to show support and donation options.
|
||||
"""
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.parent_app = parent
|
||||
self.setWindowTitle("❤️ Support the Developer")
|
||||
self.setMinimumWidth(400)
|
||||
|
||||
# Main layout
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(15)
|
||||
|
||||
# Title Label
|
||||
title_label = QLabel("Thank You for Your Support!")
|
||||
font = title_label.font()
|
||||
font.setPointSize(14)
|
||||
font.setBold(True)
|
||||
title_label.setFont(font)
|
||||
title_label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(title_label)
|
||||
|
||||
# Informational Text
|
||||
info_label = QLabel(
|
||||
"If you find this application useful, please consider supporting its development. "
|
||||
"Your contribution helps cover costs and encourages future updates and features."
|
||||
)
|
||||
info_label.setWordWrap(True)
|
||||
info_label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(info_label)
|
||||
|
||||
# Separator
|
||||
line = QFrame()
|
||||
line.setFrameShape(QFrame.HLine)
|
||||
line.setFrameShadow(QFrame.Sunken)
|
||||
layout.addWidget(line)
|
||||
|
||||
# Donation Options
|
||||
options_layout = QVBoxLayout()
|
||||
options_layout.setSpacing(10)
|
||||
|
||||
# --- Ko-fi ---
|
||||
kofi_label = QLabel(
|
||||
'<a href="https://ko-fi.com/yuvi427183" style="color: #13C2C2; text-decoration: none;">'
|
||||
'☕ Buy me a Ko-fi'
|
||||
'</a>'
|
||||
)
|
||||
kofi_label.setOpenExternalLinks(True)
|
||||
kofi_label.setAlignment(Qt.AlignCenter)
|
||||
font.setPointSize(12)
|
||||
kofi_label.setFont(font)
|
||||
options_layout.addWidget(kofi_label)
|
||||
|
||||
# --- GitHub Sponsors ---
|
||||
github_label = QLabel(
|
||||
'<a href="https://github.com/sponsors/Yuvi9587" style="color: #C9D1D9; text-decoration: none;">'
|
||||
'💜 Sponsor on GitHub'
|
||||
'</a>'
|
||||
)
|
||||
github_label.setOpenExternalLinks(True)
|
||||
github_label.setAlignment(Qt.AlignCenter)
|
||||
github_label.setFont(font)
|
||||
options_layout.addWidget(github_label)
|
||||
|
||||
layout.addLayout(options_layout)
|
||||
|
||||
# Close Button
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Close)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
|
||||
self.setLayout(layout)
|
||||
self._apply_theme()
|
||||
|
||||
def _apply_theme(self):
|
||||
"""Applies the current theme from the parent application."""
|
||||
if self.parent_app and hasattr(self.parent_app, 'current_theme') and self.parent_app.current_theme == "dark":
|
||||
scale = getattr(self.parent_app, 'scale_factor', 1)
|
||||
self.setStyleSheet(get_dark_theme(scale))
|
||||
else:
|
||||
self.setStyleSheet("")
|
||||
@@ -12,6 +12,7 @@ from PyQt5.QtWidgets import (
|
||||
# --- Local Application Imports ---
|
||||
from ...i18n.translator import get_translation
|
||||
from ..main_window import get_app_icon_object
|
||||
from ...utils.resolution import get_dark_theme
|
||||
from ...config.constants import (
|
||||
CONFIG_ORGANIZATION_NAME
|
||||
)
|
||||
@@ -150,8 +151,9 @@ class TourDialog(QDialog):
|
||||
|
||||
def _apply_theme(self):
|
||||
"""Applies the current theme from the parent application."""
|
||||
if self.parent_app and hasattr(self.parent_app, 'get_dark_theme') and self.parent_app.current_theme == "dark":
|
||||
self.setStyleSheet(self.parent_app.get_dark_theme())
|
||||
if self.parent_app and self.parent_app.current_theme == "dark":
|
||||
scale = getattr(self.parent_app, 'scale_factor', 1)
|
||||
self.setStyleSheet(get_dark_theme(scale))
|
||||
else:
|
||||
self.setStyleSheet("QDialog { background-color: #f0f0f0; }")
|
||||
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
# src/ui/flow_layout.py
|
||||
|
||||
from PyQt5.QtWidgets import QLayout, QSizePolicy, QStyle
|
||||
from PyQt5.QtCore import QPoint, QRect, QSize, Qt
|
||||
|
||||
class FlowLayout(QLayout):
|
||||
"""A custom layout that arranges widgets in a flow, wrapping as necessary."""
|
||||
def __init__(self, parent=None, margin=0, spacing=-1):
|
||||
super(FlowLayout, self).__init__(parent)
|
||||
|
||||
if parent is not None:
|
||||
self.setContentsMargins(margin, margin, margin, margin)
|
||||
|
||||
self.setSpacing(spacing)
|
||||
self.itemList = []
|
||||
|
||||
def __del__(self):
|
||||
item = self.takeAt(0)
|
||||
while item:
|
||||
item = self.takeAt(0)
|
||||
|
||||
def addItem(self, item):
|
||||
self.itemList.append(item)
|
||||
|
||||
def count(self):
|
||||
return len(self.itemList)
|
||||
|
||||
def itemAt(self, index):
|
||||
if 0 <= index < len(self.itemList):
|
||||
return self.itemList[index]
|
||||
return None
|
||||
|
||||
def takeAt(self, index):
|
||||
if 0 <= index < len(self.itemList):
|
||||
return self.itemList.pop(index)
|
||||
return None
|
||||
|
||||
def expandingDirections(self):
|
||||
return Qt.Orientations(Qt.Orientation(0))
|
||||
|
||||
def hasHeightForWidth(self):
|
||||
return True
|
||||
|
||||
def heightForWidth(self, width):
|
||||
return self._do_layout(QRect(0, 0, width, 0), True)
|
||||
|
||||
def setGeometry(self, rect):
|
||||
super(FlowLayout, self).setGeometry(rect)
|
||||
self._do_layout(rect, False)
|
||||
|
||||
def sizeHint(self):
|
||||
return self.minimumSize()
|
||||
|
||||
def minimumSize(self):
|
||||
size = QSize()
|
||||
for item in self.itemList:
|
||||
size = size.expandedTo(item.minimumSize())
|
||||
|
||||
margin, _, _, _ = self.getContentsMargins()
|
||||
size += QSize(2 * margin, 2 * margin)
|
||||
return size
|
||||
|
||||
def _do_layout(self, rect, test_only):
|
||||
x = rect.x()
|
||||
y = rect.y()
|
||||
line_height = 0
|
||||
|
||||
space_x = self.spacing()
|
||||
space_y = self.spacing()
|
||||
if self.layout() is not None:
|
||||
space_x = self.spacing()
|
||||
space_y = self.spacing()
|
||||
else:
|
||||
space_x = self.spacing()
|
||||
space_y = self.spacing()
|
||||
|
||||
|
||||
for item in self.itemList:
|
||||
wid = item.widget()
|
||||
next_x = x + item.sizeHint().width() + space_x
|
||||
if next_x - space_x > rect.right() and line_height > 0:
|
||||
x = rect.x()
|
||||
y = y + line_height + space_y
|
||||
next_x = x + item.sizeHint().width() + space_x
|
||||
line_height = 0
|
||||
|
||||
if not test_only:
|
||||
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
|
||||
|
||||
x = next_x
|
||||
line_height = max(line_height, item.sizeHint().height())
|
||||
|
||||
return y + line_height - rect.y()
|
||||
File diff suppressed because it is too large
Load Diff
566
src/utils/resolution.py
Normal file
566
src/utils/resolution.py
Normal file
@@ -0,0 +1,566 @@
|
||||
# src/ui/utils/resolution.py
|
||||
|
||||
# --- Standard Library Imports ---
|
||||
import os
|
||||
|
||||
# --- PyQt5 Imports ---
|
||||
from PyQt5.QtWidgets import (
|
||||
QSplitter, QScrollArea, QFrame, QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QLineEdit, QPushButton, QStackedWidget, QButtonGroup, QRadioButton, QCheckBox,
|
||||
QListWidget, QTextEdit, QApplication
|
||||
)
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QIntValidator # <-- Import QIntValidator from here
|
||||
|
||||
# --- Local Application Imports ---
|
||||
# Assuming execution from project root
|
||||
from ..config.constants import *
|
||||
|
||||
|
||||
def setup_ui(main_app):
|
||||
"""
|
||||
Initializes and scales the user interface for the DownloaderApp.
|
||||
|
||||
Args:
|
||||
main_app: The instance of the main DownloaderApp.
|
||||
"""
|
||||
# --- START: New Scaling Logic ---
|
||||
screen = QApplication.primaryScreen()
|
||||
if screen:
|
||||
resolution = screen.size()
|
||||
if resolution.width() > 1920 and resolution.height() > 1200:
|
||||
main_app.scale_factor = 2
|
||||
else:
|
||||
main_app.scale_factor = 1
|
||||
else:
|
||||
# Fallback if a primary screen isn't detected
|
||||
main_app.scale_factor = 1
|
||||
|
||||
scale = main_app.scale_factor # Use a convenient local variable
|
||||
# --- END: New Scaling Logic ---
|
||||
|
||||
main_app.main_splitter = QSplitter(Qt.Horizontal)
|
||||
|
||||
# --- Use a scroll area for the left panel for consistency ---
|
||||
left_scroll_area = QScrollArea()
|
||||
left_scroll_area.setWidgetResizable(True)
|
||||
left_scroll_area.setFrameShape(QFrame.NoFrame)
|
||||
|
||||
left_panel_widget = QWidget()
|
||||
left_layout = QVBoxLayout(left_panel_widget)
|
||||
left_scroll_area.setWidget(left_panel_widget)
|
||||
|
||||
right_panel_widget = QWidget()
|
||||
right_layout = QVBoxLayout(right_panel_widget)
|
||||
|
||||
left_layout.setContentsMargins(10, 10, 10, 10)
|
||||
right_layout.setContentsMargins(10, 10, 10, 10)
|
||||
apply_theme_to_app(main_app, main_app.current_theme, initial_load=True)
|
||||
|
||||
# --- URL and Page Range ---
|
||||
main_app.url_input_widget = QWidget()
|
||||
url_input_layout = QHBoxLayout(main_app.url_input_widget)
|
||||
url_input_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_app.url_label_widget = QLabel()
|
||||
url_input_layout.addWidget(main_app.url_label_widget)
|
||||
main_app.link_input = QLineEdit()
|
||||
main_app.link_input.setPlaceholderText("e.g., https://kemono.su/patreon/user/12345 or .../post/98765")
|
||||
main_app.link_input.textChanged.connect(main_app.update_custom_folder_visibility)
|
||||
url_input_layout.addWidget(main_app.link_input, 1)
|
||||
main_app.empty_popup_button = QPushButton("🎨")
|
||||
main_app.empty_popup_button.setStyleSheet(f"padding: {4*scale}px {6*scale}px;")
|
||||
main_app.empty_popup_button.clicked.connect(main_app._show_empty_popup)
|
||||
url_input_layout.addWidget(main_app.empty_popup_button)
|
||||
main_app.page_range_label = QLabel(main_app._tr("page_range_label_text", "Page Range:"))
|
||||
main_app.page_range_label.setStyleSheet("font-weight: bold; padding-left: 10px;")
|
||||
url_input_layout.addWidget(main_app.page_range_label)
|
||||
main_app.start_page_input = QLineEdit()
|
||||
main_app.start_page_input.setPlaceholderText(main_app._tr("start_page_input_placeholder", "Start"))
|
||||
main_app.start_page_input.setFixedWidth(50 * scale)
|
||||
main_app.start_page_input.setValidator(QIntValidator(1, 99999))
|
||||
url_input_layout.addWidget(main_app.start_page_input)
|
||||
main_app.to_label = QLabel(main_app._tr("page_range_to_label_text", "to"))
|
||||
url_input_layout.addWidget(main_app.to_label)
|
||||
main_app.end_page_input = QLineEdit()
|
||||
main_app.end_page_input.setPlaceholderText(main_app._tr("end_page_input_placeholder", "End"))
|
||||
main_app.end_page_input.setFixedWidth(50 * scale)
|
||||
main_app.end_page_input.setToolTip(main_app._tr("end_page_input_tooltip", "For creator URLs: Specify the ending page number..."))
|
||||
main_app.end_page_input.setValidator(QIntValidator(1, 99999))
|
||||
url_input_layout.addWidget(main_app.end_page_input)
|
||||
main_app.url_placeholder_widget = QWidget()
|
||||
placeholder_layout = QHBoxLayout(main_app.url_placeholder_widget)
|
||||
placeholder_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_app.fav_mode_active_label = QLabel(main_app._tr("fav_mode_active_label_text", "⭐ Favorite Mode is active..."))
|
||||
main_app.fav_mode_active_label.setAlignment(Qt.AlignCenter)
|
||||
placeholder_layout.addWidget(main_app.fav_mode_active_label)
|
||||
main_app.url_or_placeholder_stack = QStackedWidget()
|
||||
main_app.url_or_placeholder_stack.addWidget(main_app.url_input_widget)
|
||||
main_app.url_or_placeholder_stack.addWidget(main_app.url_placeholder_widget)
|
||||
left_layout.addWidget(main_app.url_or_placeholder_stack)
|
||||
|
||||
# --- Download Location ---
|
||||
main_app.download_location_label_widget = QLabel()
|
||||
left_layout.addWidget(main_app.download_location_label_widget)
|
||||
dir_layout = QHBoxLayout()
|
||||
main_app.dir_input = QLineEdit()
|
||||
main_app.dir_input.setPlaceholderText("Select folder where downloads will be saved")
|
||||
main_app.dir_button = QPushButton("Browse...")
|
||||
main_app.dir_button.clicked.connect(main_app.browse_directory)
|
||||
dir_layout.addWidget(main_app.dir_input, 1)
|
||||
dir_layout.addWidget(main_app.dir_button)
|
||||
left_layout.addLayout(dir_layout)
|
||||
|
||||
# --- Filters and Custom Folder Container ---
|
||||
main_app.filters_and_custom_folder_container_widget = QWidget()
|
||||
filters_and_custom_folder_layout = QHBoxLayout(main_app.filters_and_custom_folder_container_widget)
|
||||
filters_and_custom_folder_layout.setContentsMargins(0, 5, 0, 0)
|
||||
filters_and_custom_folder_layout.setSpacing(10)
|
||||
main_app.character_filter_widget = QWidget()
|
||||
character_filter_v_layout = QVBoxLayout(main_app.character_filter_widget)
|
||||
character_filter_v_layout.setContentsMargins(0, 0, 0, 0)
|
||||
character_filter_v_layout.setSpacing(2)
|
||||
main_app.character_label = QLabel("🎯 Filter by Character(s) (comma-separated):")
|
||||
character_filter_v_layout.addWidget(main_app.character_label)
|
||||
char_input_and_button_layout = QHBoxLayout()
|
||||
char_input_and_button_layout.setContentsMargins(0, 0, 0, 0)
|
||||
char_input_and_button_layout.setSpacing(10)
|
||||
main_app.character_input = QLineEdit()
|
||||
main_app.character_input.setPlaceholderText("e.g., Tifa, Aerith, (Cloud, Zack)")
|
||||
char_input_and_button_layout.addWidget(main_app.character_input, 3)
|
||||
main_app.char_filter_scope_toggle_button = QPushButton()
|
||||
main_app._update_char_filter_scope_button_text()
|
||||
char_input_and_button_layout.addWidget(main_app.char_filter_scope_toggle_button, 1)
|
||||
character_filter_v_layout.addLayout(char_input_and_button_layout)
|
||||
|
||||
# --- Custom Folder Widget Definition ---
|
||||
main_app.custom_folder_widget = QWidget()
|
||||
custom_folder_v_layout = QVBoxLayout(main_app.custom_folder_widget)
|
||||
custom_folder_v_layout.setContentsMargins(0, 0, 0, 0)
|
||||
custom_folder_v_layout.setSpacing(2)
|
||||
main_app.custom_folder_label = QLabel("🗄️ Custom Folder Name (Single Post Only):")
|
||||
main_app.custom_folder_input = QLineEdit()
|
||||
main_app.custom_folder_input.setPlaceholderText("Optional: Save this post to specific folder")
|
||||
custom_folder_v_layout.addWidget(main_app.custom_folder_label)
|
||||
custom_folder_v_layout.addWidget(main_app.custom_folder_input)
|
||||
main_app.custom_folder_widget.setVisible(False)
|
||||
|
||||
filters_and_custom_folder_layout.addWidget(main_app.character_filter_widget, 1)
|
||||
filters_and_custom_folder_layout.addWidget(main_app.custom_folder_widget, 1)
|
||||
left_layout.addWidget(main_app.filters_and_custom_folder_container_widget)
|
||||
|
||||
# --- Word Manipulation Container ---
|
||||
word_manipulation_container_widget = QWidget()
|
||||
word_manipulation_outer_layout = QHBoxLayout(word_manipulation_container_widget)
|
||||
word_manipulation_outer_layout.setContentsMargins(0, 0, 0, 0)
|
||||
word_manipulation_outer_layout.setSpacing(15)
|
||||
skip_words_widget = QWidget()
|
||||
skip_words_vertical_layout = QVBoxLayout(skip_words_widget)
|
||||
skip_words_vertical_layout.setContentsMargins(0, 0, 0, 0)
|
||||
skip_words_vertical_layout.setSpacing(2)
|
||||
main_app.skip_words_label_widget = QLabel()
|
||||
skip_words_vertical_layout.addWidget(main_app.skip_words_label_widget)
|
||||
skip_input_and_button_layout = QHBoxLayout()
|
||||
skip_input_and_button_layout.setContentsMargins(0, 0, 0, 0)
|
||||
skip_input_and_button_layout.setSpacing(10)
|
||||
main_app.skip_words_input = QLineEdit()
|
||||
main_app.skip_words_input.setPlaceholderText("e.g., WM, WIP, sketch, preview")
|
||||
skip_input_and_button_layout.addWidget(main_app.skip_words_input, 1)
|
||||
main_app.skip_scope_toggle_button = QPushButton()
|
||||
main_app._update_skip_scope_button_text()
|
||||
skip_input_and_button_layout.addWidget(main_app.skip_scope_toggle_button, 0)
|
||||
skip_words_vertical_layout.addLayout(skip_input_and_button_layout)
|
||||
word_manipulation_outer_layout.addWidget(skip_words_widget, 7)
|
||||
remove_words_widget = QWidget()
|
||||
remove_words_vertical_layout = QVBoxLayout(remove_words_widget)
|
||||
remove_words_vertical_layout.setContentsMargins(0, 0, 0, 0)
|
||||
remove_words_vertical_layout.setSpacing(2)
|
||||
main_app.remove_from_filename_label_widget = QLabel()
|
||||
remove_words_vertical_layout.addWidget(main_app.remove_from_filename_label_widget)
|
||||
main_app.remove_from_filename_input = QLineEdit()
|
||||
main_app.remove_from_filename_input.setPlaceholderText("e.g., patreon, HD")
|
||||
remove_words_vertical_layout.addWidget(main_app.remove_from_filename_input)
|
||||
word_manipulation_outer_layout.addWidget(remove_words_widget, 3)
|
||||
left_layout.addWidget(word_manipulation_container_widget)
|
||||
|
||||
# --- File Filter Layout ---
|
||||
file_filter_layout = QVBoxLayout()
|
||||
file_filter_layout.setContentsMargins(0, 10, 0, 0)
|
||||
file_filter_layout.addWidget(QLabel("Filter Files:"))
|
||||
radio_button_layout = QHBoxLayout()
|
||||
radio_button_layout.setSpacing(10)
|
||||
main_app.radio_group = QButtonGroup(main_app)
|
||||
main_app.radio_all = QRadioButton("All")
|
||||
main_app.radio_images = QRadioButton("Images/GIFs")
|
||||
main_app.radio_videos = QRadioButton("Videos")
|
||||
main_app.radio_only_archives = QRadioButton("📦 Only Archives")
|
||||
main_app.radio_only_audio = QRadioButton("🎧 Only Audio")
|
||||
main_app.radio_only_links = QRadioButton("🔗 Only Links")
|
||||
main_app.radio_more = QRadioButton("More")
|
||||
|
||||
main_app.radio_all.setChecked(True)
|
||||
for btn in [main_app.radio_all, main_app.radio_images, main_app.radio_videos, main_app.radio_only_archives, main_app.radio_only_audio, main_app.radio_only_links, main_app.radio_more]:
|
||||
main_app.radio_group.addButton(btn)
|
||||
radio_button_layout.addWidget(btn)
|
||||
main_app.favorite_mode_checkbox = QCheckBox()
|
||||
main_app.favorite_mode_checkbox.setChecked(False)
|
||||
radio_button_layout.addWidget(main_app.favorite_mode_checkbox)
|
||||
radio_button_layout.addStretch(1)
|
||||
file_filter_layout.addLayout(radio_button_layout)
|
||||
left_layout.addLayout(file_filter_layout)
|
||||
|
||||
# --- Checkboxes Group ---
|
||||
checkboxes_group_layout = QVBoxLayout()
|
||||
checkboxes_group_layout.setSpacing(10)
|
||||
row1_layout = QHBoxLayout()
|
||||
row1_layout.setSpacing(10)
|
||||
main_app.skip_zip_checkbox = QCheckBox("Skip .zip")
|
||||
main_app.skip_zip_checkbox.setChecked(True)
|
||||
row1_layout.addWidget(main_app.skip_zip_checkbox)
|
||||
main_app.skip_rar_checkbox = QCheckBox("Skip .rar")
|
||||
main_app.skip_rar_checkbox.setChecked(True)
|
||||
row1_layout.addWidget(main_app.skip_rar_checkbox)
|
||||
main_app.download_thumbnails_checkbox = QCheckBox("Download Thumbnails Only")
|
||||
row1_layout.addWidget(main_app.download_thumbnails_checkbox)
|
||||
main_app.scan_content_images_checkbox = QCheckBox("Scan Content for Images")
|
||||
main_app.scan_content_images_checkbox.setChecked(main_app.scan_content_images_setting)
|
||||
row1_layout.addWidget(main_app.scan_content_images_checkbox)
|
||||
main_app.compress_images_checkbox = QCheckBox("Compress to WebP")
|
||||
main_app.compress_images_checkbox.setToolTip("Compress images > 1.5MB to WebP format (requires Pillow).")
|
||||
row1_layout.addWidget(main_app.compress_images_checkbox)
|
||||
main_app.keep_duplicates_checkbox = QCheckBox("Keep Duplicates")
|
||||
main_app.keep_duplicates_checkbox.setToolTip("If checked, downloads all files from a post even if they have the same name.")
|
||||
row1_layout.addWidget(main_app.keep_duplicates_checkbox)
|
||||
row1_layout.addStretch(1)
|
||||
checkboxes_group_layout.addLayout(row1_layout)
|
||||
|
||||
# --- Advanced Settings ---
|
||||
advanced_settings_label = QLabel("⚙️ Advanced Settings:")
|
||||
checkboxes_group_layout.addWidget(advanced_settings_label)
|
||||
advanced_row1_layout = QHBoxLayout()
|
||||
advanced_row1_layout.setSpacing(10)
|
||||
main_app.use_subfolders_checkbox = QCheckBox("Separate Folders by Name/Title")
|
||||
main_app.use_subfolders_checkbox.setChecked(True)
|
||||
main_app.use_subfolders_checkbox.toggled.connect(main_app.update_ui_for_subfolders)
|
||||
advanced_row1_layout.addWidget(main_app.use_subfolders_checkbox)
|
||||
main_app.use_subfolder_per_post_checkbox = QCheckBox("Subfolder per Post")
|
||||
main_app.use_subfolder_per_post_checkbox.toggled.connect(main_app.update_ui_for_subfolders)
|
||||
advanced_row1_layout.addWidget(main_app.use_subfolder_per_post_checkbox)
|
||||
main_app.date_prefix_checkbox = QCheckBox("Date Prefix")
|
||||
main_app.date_prefix_checkbox.setToolTip("When 'Subfolder per Post' is active, prefix the folder name with the post's upload date.")
|
||||
advanced_row1_layout.addWidget(main_app.date_prefix_checkbox)
|
||||
main_app.use_cookie_checkbox = QCheckBox("Use Cookie")
|
||||
main_app.use_cookie_checkbox.setChecked(main_app.use_cookie_setting)
|
||||
main_app.cookie_text_input = QLineEdit()
|
||||
main_app.cookie_text_input.setPlaceholderText("if no Select cookies.txt)")
|
||||
main_app.cookie_text_input.setText(main_app.cookie_text_setting)
|
||||
advanced_row1_layout.addWidget(main_app.use_cookie_checkbox)
|
||||
advanced_row1_layout.addWidget(main_app.cookie_text_input, 2)
|
||||
main_app.cookie_browse_button = QPushButton("Browse...")
|
||||
main_app.cookie_browse_button.setFixedWidth(80 * scale)
|
||||
advanced_row1_layout.addWidget(main_app.cookie_browse_button)
|
||||
advanced_row1_layout.addStretch(1)
|
||||
checkboxes_group_layout.addLayout(advanced_row1_layout)
|
||||
advanced_row2_layout = QHBoxLayout()
|
||||
advanced_row2_layout.setSpacing(10)
|
||||
multithreading_layout = QHBoxLayout()
|
||||
multithreading_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_app.use_multithreading_checkbox = QCheckBox("Use Multithreading")
|
||||
main_app.use_multithreading_checkbox.setChecked(True)
|
||||
multithreading_layout.addWidget(main_app.use_multithreading_checkbox)
|
||||
main_app.thread_count_label = QLabel("Threads:")
|
||||
multithreading_layout.addWidget(main_app.thread_count_label)
|
||||
main_app.thread_count_input = QLineEdit("4")
|
||||
main_app.thread_count_input.setFixedWidth(40 * scale)
|
||||
main_app.thread_count_input.setValidator(QIntValidator(1, MAX_THREADS))
|
||||
multithreading_layout.addWidget(main_app.thread_count_input)
|
||||
advanced_row2_layout.addLayout(multithreading_layout)
|
||||
main_app.external_links_checkbox = QCheckBox("Show External Links in Log")
|
||||
advanced_row2_layout.addWidget(main_app.external_links_checkbox)
|
||||
main_app.manga_mode_checkbox = QCheckBox("Manga/Comic Mode")
|
||||
advanced_row2_layout.addWidget(main_app.manga_mode_checkbox)
|
||||
advanced_row2_layout.addStretch(1)
|
||||
checkboxes_group_layout.addLayout(advanced_row2_layout)
|
||||
left_layout.addLayout(checkboxes_group_layout)
|
||||
|
||||
# --- Action Buttons ---
|
||||
main_app.standard_action_buttons_widget = QWidget()
|
||||
btn_layout = QHBoxLayout(main_app.standard_action_buttons_widget)
|
||||
btn_layout.setContentsMargins(0, 10, 0, 0)
|
||||
btn_layout.setSpacing(10)
|
||||
main_app.download_btn = QPushButton("⬇️ Start Download")
|
||||
main_app.download_btn.setStyleSheet("font-weight: bold;")
|
||||
main_app.download_btn.clicked.connect(main_app.start_download)
|
||||
main_app.pause_btn = QPushButton("⏸️ Pause Download")
|
||||
main_app.pause_btn.setEnabled(False)
|
||||
main_app.pause_btn.clicked.connect(main_app._handle_pause_resume_action)
|
||||
main_app.cancel_btn = QPushButton("❌ Cancel & Reset UI")
|
||||
main_app.cancel_btn.setEnabled(False)
|
||||
main_app.cancel_btn.clicked.connect(main_app.cancel_download_button_action)
|
||||
main_app.error_btn = QPushButton("Error")
|
||||
main_app.error_btn.setToolTip("View files skipped due to errors and optionally retry them.")
|
||||
main_app.error_btn.setEnabled(True)
|
||||
btn_layout.addWidget(main_app.download_btn)
|
||||
btn_layout.addWidget(main_app.pause_btn)
|
||||
btn_layout.addWidget(main_app.cancel_btn)
|
||||
btn_layout.addWidget(main_app.error_btn)
|
||||
main_app.favorite_action_buttons_widget = QWidget()
|
||||
favorite_buttons_layout = QHBoxLayout(main_app.favorite_action_buttons_widget)
|
||||
main_app.favorite_mode_artists_button = QPushButton("🖼️ Favorite Artists")
|
||||
main_app.favorite_mode_posts_button = QPushButton("📄 Favorite Posts")
|
||||
main_app.favorite_scope_toggle_button = QPushButton()
|
||||
favorite_buttons_layout.addWidget(main_app.favorite_mode_artists_button)
|
||||
favorite_buttons_layout.addWidget(main_app.favorite_mode_posts_button)
|
||||
favorite_buttons_layout.addWidget(main_app.favorite_scope_toggle_button)
|
||||
main_app.bottom_action_buttons_stack = QStackedWidget()
|
||||
main_app.bottom_action_buttons_stack.addWidget(main_app.standard_action_buttons_widget)
|
||||
main_app.bottom_action_buttons_stack.addWidget(main_app.favorite_action_buttons_widget)
|
||||
left_layout.addWidget(main_app.bottom_action_buttons_stack)
|
||||
left_layout.addSpacing(10)
|
||||
|
||||
# --- Known Names Layout ---
|
||||
known_chars_label_layout = QHBoxLayout()
|
||||
known_chars_label_layout.setSpacing(10)
|
||||
main_app.known_chars_label = QLabel("🎭 Known Shows/Characters (for Folder Names):")
|
||||
known_chars_label_layout.addWidget(main_app.known_chars_label)
|
||||
main_app.open_known_txt_button = QPushButton("Open Known.txt")
|
||||
main_app.open_known_txt_button.setFixedWidth(120 * scale)
|
||||
known_chars_label_layout.addWidget(main_app.open_known_txt_button)
|
||||
main_app.character_search_input = QLineEdit()
|
||||
main_app.character_search_input.setPlaceholderText("Search characters...")
|
||||
known_chars_label_layout.addWidget(main_app.character_search_input, 1)
|
||||
left_layout.addLayout(known_chars_label_layout)
|
||||
main_app.character_list = QListWidget()
|
||||
main_app.character_list.setSelectionMode(QListWidget.ExtendedSelection)
|
||||
left_layout.addWidget(main_app.character_list, 1)
|
||||
char_manage_layout = QHBoxLayout()
|
||||
char_manage_layout.setSpacing(10)
|
||||
main_app.new_char_input = QLineEdit()
|
||||
main_app.new_char_input.setPlaceholderText("Add new show/character name")
|
||||
main_app.add_char_button = QPushButton("➕ Add")
|
||||
main_app.add_to_filter_button = QPushButton("⤵️ Add to Filter")
|
||||
main_app.add_to_filter_button.setToolTip("Select names... to add to the 'Filter by Character(s)' field.")
|
||||
main_app.delete_char_button = QPushButton("🗑️ Delete Selected")
|
||||
main_app.delete_char_button.setToolTip("Delete the selected name(s)...")
|
||||
main_app.add_char_button.clicked.connect(main_app._handle_ui_add_new_character)
|
||||
main_app.new_char_input.returnPressed.connect(main_app.add_char_button.click)
|
||||
main_app.delete_char_button.clicked.connect(main_app.delete_selected_character)
|
||||
char_manage_layout.addWidget(main_app.new_char_input, 2)
|
||||
char_manage_layout.addWidget(main_app.add_char_button, 0)
|
||||
main_app.known_names_help_button = QPushButton("?")
|
||||
main_app.known_names_help_button.setFixedWidth(45 * scale)
|
||||
main_app.known_names_help_button.clicked.connect(main_app._show_feature_guide)
|
||||
main_app.history_button = QPushButton("📜")
|
||||
main_app.history_button.setFixedWidth(45 * scale)
|
||||
main_app.history_button.setToolTip(main_app._tr("history_button_tooltip_text", "View download history"))
|
||||
main_app.future_settings_button = QPushButton("⚙️")
|
||||
main_app.future_settings_button.setFixedWidth(45 * scale)
|
||||
main_app.future_settings_button.clicked.connect(main_app._show_future_settings_dialog)
|
||||
main_app.support_button = QPushButton("❤️ Support")
|
||||
main_app.support_button.setFixedWidth(100 * scale)
|
||||
main_app.support_button.setToolTip("Support the application developer.")
|
||||
char_manage_layout.addWidget(main_app.add_to_filter_button, 1)
|
||||
char_manage_layout.addWidget(main_app.delete_char_button, 1)
|
||||
char_manage_layout.addWidget(main_app.known_names_help_button, 0)
|
||||
char_manage_layout.addWidget(main_app.history_button, 0)
|
||||
char_manage_layout.addWidget(main_app.future_settings_button, 0)
|
||||
char_manage_layout.addWidget(main_app.support_button, 0)
|
||||
left_layout.addLayout(char_manage_layout)
|
||||
left_layout.addStretch(0)
|
||||
|
||||
# --- Right Panel (Logs) ---
|
||||
right_panel_widget.setLayout(right_layout)
|
||||
log_title_layout = QHBoxLayout()
|
||||
main_app.progress_log_label = QLabel("📜 Progress Log:")
|
||||
log_title_layout.addWidget(main_app.progress_log_label)
|
||||
log_title_layout.addStretch(1)
|
||||
main_app.link_search_input = QLineEdit()
|
||||
main_app.link_search_input.setPlaceholderText("Search Links...")
|
||||
main_app.link_search_input.setVisible(False)
|
||||
log_title_layout.addWidget(main_app.link_search_input)
|
||||
main_app.link_search_button = QPushButton("🔍")
|
||||
main_app.link_search_button.setVisible(False)
|
||||
main_app.link_search_button.setFixedWidth(30 * scale)
|
||||
log_title_layout.addWidget(main_app.link_search_button)
|
||||
main_app.manga_rename_toggle_button = QPushButton()
|
||||
main_app.manga_rename_toggle_button.setVisible(False)
|
||||
main_app.manga_rename_toggle_button.setFixedWidth(140 * scale)
|
||||
main_app._update_manga_filename_style_button_text()
|
||||
log_title_layout.addWidget(main_app.manga_rename_toggle_button)
|
||||
main_app.manga_date_prefix_input = QLineEdit()
|
||||
main_app.manga_date_prefix_input.setPlaceholderText("Prefix for Manga Filenames")
|
||||
main_app.manga_date_prefix_input.setVisible(False)
|
||||
log_title_layout.addWidget(main_app.manga_date_prefix_input)
|
||||
main_app.multipart_toggle_button = QPushButton()
|
||||
main_app.multipart_toggle_button.setToolTip("Toggle between Multi-part and Single-stream downloads for large files.")
|
||||
main_app.multipart_toggle_button.setFixedWidth(130 * scale)
|
||||
main_app._update_multipart_toggle_button_text()
|
||||
log_title_layout.addWidget(main_app.multipart_toggle_button)
|
||||
main_app.EYE_ICON = "\U0001F441"
|
||||
main_app.CLOSED_EYE_ICON = "\U0001F648"
|
||||
main_app.log_verbosity_toggle_button = QPushButton(main_app.EYE_ICON)
|
||||
main_app.log_verbosity_toggle_button.setFixedWidth(45 * scale)
|
||||
main_app.log_verbosity_toggle_button.setStyleSheet(f"font-size: {11 * scale}pt; padding: {4 * scale}px {2 * scale}px;")
|
||||
log_title_layout.addWidget(main_app.log_verbosity_toggle_button)
|
||||
main_app.reset_button = QPushButton("🔄 Reset")
|
||||
main_app.reset_button.setFixedWidth(80 * scale)
|
||||
log_title_layout.addWidget(main_app.reset_button)
|
||||
right_layout.addLayout(log_title_layout)
|
||||
main_app.log_splitter = QSplitter(Qt.Vertical)
|
||||
main_app.log_view_stack = QStackedWidget()
|
||||
main_app.main_log_output = QTextEdit()
|
||||
main_app.main_log_output.setReadOnly(True)
|
||||
main_app.main_log_output.setLineWrapMode(QTextEdit.NoWrap)
|
||||
main_app.log_view_stack.addWidget(main_app.main_log_output)
|
||||
main_app.missed_character_log_output = QTextEdit()
|
||||
main_app.missed_character_log_output.setReadOnly(True)
|
||||
main_app.missed_character_log_output.setLineWrapMode(QTextEdit.NoWrap)
|
||||
main_app.log_view_stack.addWidget(main_app.missed_character_log_output)
|
||||
main_app.external_log_output = QTextEdit()
|
||||
main_app.external_log_output.setReadOnly(True)
|
||||
main_app.external_log_output.setLineWrapMode(QTextEdit.NoWrap)
|
||||
main_app.external_log_output.hide()
|
||||
main_app.log_splitter.addWidget(main_app.log_view_stack)
|
||||
main_app.log_splitter.addWidget(main_app.external_log_output)
|
||||
main_app.log_splitter.setSizes([main_app.height(), 0])
|
||||
right_layout.addWidget(main_app.log_splitter, 1)
|
||||
export_button_layout = QHBoxLayout()
|
||||
export_button_layout.addStretch(1)
|
||||
main_app.export_links_button = QPushButton(main_app._tr("export_links_button_text", "Export Links"))
|
||||
main_app.export_links_button.setFixedWidth(100 * scale)
|
||||
main_app.export_links_button.setEnabled(False)
|
||||
main_app.export_links_button.setVisible(False)
|
||||
export_button_layout.addWidget(main_app.export_links_button)
|
||||
main_app.download_extracted_links_button = QPushButton(main_app._tr("download_extracted_links_button_text", "Download"))
|
||||
main_app.download_extracted_links_button.setFixedWidth(100 * scale)
|
||||
main_app.download_extracted_links_button.setEnabled(False)
|
||||
main_app.download_extracted_links_button.setVisible(False)
|
||||
export_button_layout.addWidget(main_app.download_extracted_links_button)
|
||||
main_app.log_display_mode_toggle_button = QPushButton()
|
||||
main_app.log_display_mode_toggle_button.setFixedWidth(120 * scale)
|
||||
main_app.log_display_mode_toggle_button.setVisible(False)
|
||||
export_button_layout.addWidget(main_app.log_display_mode_toggle_button)
|
||||
right_layout.addLayout(export_button_layout)
|
||||
main_app.progress_label = QLabel("Progress: Idle")
|
||||
main_app.progress_label.setStyleSheet("padding-top: 5px; font-style: italic;")
|
||||
right_layout.addWidget(main_app.progress_label)
|
||||
main_app.file_progress_label = QLabel("")
|
||||
main_app.file_progress_label.setToolTip("Shows the progress of individual file downloads, including speed and size.")
|
||||
main_app.file_progress_label.setWordWrap(True)
|
||||
main_app.file_progress_label.setStyleSheet("padding-top: 2px; font-style: italic; color: #A0A0A0;")
|
||||
right_layout.addWidget(main_app.file_progress_label)
|
||||
|
||||
# --- Final Assembly ---
|
||||
main_app.main_splitter.addWidget(left_scroll_area)
|
||||
main_app.main_splitter.addWidget(right_panel_widget)
|
||||
|
||||
# --- START: Resolution-based Splitter Sizing ---
|
||||
# Check screen resolution to set the initial splitter sizes
|
||||
if screen:
|
||||
resolution = screen.size()
|
||||
if resolution.width() >= 1920 and resolution.height() >= 1200:
|
||||
# For 1920x1200 and higher, set 40% left, 60% right
|
||||
main_app.main_splitter.setStretchFactor(0, 4)
|
||||
main_app.main_splitter.setStretchFactor(1, 6)
|
||||
else:
|
||||
# Default for lower resolutions
|
||||
main_app.main_splitter.setStretchFactor(0, 7)
|
||||
main_app.main_splitter.setStretchFactor(1, 3)
|
||||
else:
|
||||
# Fallback if no screen is detected
|
||||
main_app.main_splitter.setStretchFactor(0, 7)
|
||||
main_app.main_splitter.setStretchFactor(1, 3)
|
||||
# --- END: Resolution-based Splitter Sizing ---
|
||||
|
||||
top_level_layout = QHBoxLayout(main_app)
|
||||
top_level_layout.setContentsMargins(0, 0, 0, 0)
|
||||
top_level_layout.addWidget(main_app.main_splitter)
|
||||
|
||||
# --- Initial UI State Updates ---
|
||||
main_app.update_ui_for_subfolders(main_app.use_subfolders_checkbox.isChecked())
|
||||
main_app.update_external_links_setting(main_app.external_links_checkbox.isChecked())
|
||||
main_app.update_multithreading_label(main_app.thread_count_input.text())
|
||||
main_app.update_page_range_enabled_state()
|
||||
if main_app.manga_mode_checkbox:
|
||||
main_app.update_ui_for_manga_mode(main_app.manga_mode_checkbox.isChecked())
|
||||
if hasattr(main_app, 'link_input'):
|
||||
main_app.link_input.textChanged.connect(lambda: main_app.update_ui_for_manga_mode(main_app.manga_mode_checkbox.isChecked() if main_app.manga_mode_checkbox else False))
|
||||
main_app._load_creator_name_cache_from_json()
|
||||
main_app.load_known_names_from_util()
|
||||
main_app._update_cookie_input_visibility(main_app.use_cookie_checkbox.isChecked() if hasattr(main_app, 'use_cookie_checkbox') else False)
|
||||
main_app._handle_multithreading_toggle(main_app.use_multithreading_checkbox.isChecked())
|
||||
if hasattr(main_app, 'radio_group') and main_app.radio_group.checkedButton():
|
||||
main_app._handle_filter_mode_change(main_app.radio_group.checkedButton(), True)
|
||||
main_app.radio_group.buttonToggled.connect(main_app._handle_more_options_toggled)
|
||||
|
||||
main_app._update_manga_filename_style_button_text()
|
||||
main_app._update_skip_scope_button_text()
|
||||
main_app._update_char_filter_scope_button_text()
|
||||
main_app._update_multithreading_for_date_mode()
|
||||
if hasattr(main_app, 'download_thumbnails_checkbox'):
|
||||
main_app._handle_thumbnail_mode_change(main_app.download_thumbnails_checkbox.isChecked())
|
||||
if hasattr(main_app, 'favorite_mode_checkbox'):
|
||||
main_app._handle_favorite_mode_toggle(False)
|
||||
|
||||
def get_dark_theme(scale=1):
|
||||
"""
|
||||
Generates the stylesheet for the dark theme, scaled by the given factor.
|
||||
"""
|
||||
# Define base sizes
|
||||
font_size_base = 10
|
||||
font_size_small_base = 9.5
|
||||
padding_base = 5
|
||||
padding_small = 4
|
||||
button_h_padding_base = 12
|
||||
indicator_size_base = 14
|
||||
|
||||
# Apply scaling
|
||||
font_size = font_size_base * scale
|
||||
font_size_small = font_size_small_base * scale
|
||||
line_edit_padding = padding_base * scale
|
||||
button_padding_v = padding_base * scale
|
||||
button_padding_h = button_h_padding_base * scale
|
||||
tooltip_padding = padding_small * scale
|
||||
|
||||
return f"""
|
||||
QWidget {{ background-color: #2E2E2E; color: #E0E0E0; font-family: Segoe UI, Arial, sans-serif; font-size: {font_size}pt; }}
|
||||
QLineEdit, QListWidget {{ background-color: #3C3F41; border: 1px solid #5A5A5A; padding: {line_edit_padding}px; color: #F0F0F0; border-radius: 4px; }}
|
||||
QTextEdit {{ background-color: #3C3F41; border: 1px solid #5A5A5A; padding: {line_edit_padding}px;
|
||||
color: #F0F0F0; border-radius: 4px;
|
||||
font-family: Consolas, Courier New, monospace; font-size: {font_size_small}pt; }}
|
||||
QPushButton {{ background-color: #555; color: #F0F0F0; border: 1px solid #6A6A6A; padding: {button_padding_v}px {button_padding_h}px; border-radius: 4px; }}
|
||||
QPushButton:hover {{ background-color: #656565; border: 1px solid #7A7A7A; }}
|
||||
QPushButton:pressed {{ background-color: #4A4A4A; }}
|
||||
QPushButton:disabled {{ background-color: #404040; color: #888; border-color: #555; }}
|
||||
QLabel {{ font-weight: bold; padding-top: {4 * scale}px; padding-bottom: {2 * scale}px; color: #C0C0C0; }}
|
||||
QRadioButton, QCheckBox {{ spacing: {5 * scale}px; color: #E0E0E0; padding-top: {4 * scale}px; padding-bottom: {4 * scale}px; }}
|
||||
QRadioButton::indicator, QCheckBox::indicator {{ width: {indicator_size_base * scale}px; height: {indicator_size_base * scale}px; }}
|
||||
QListWidget {{ alternate-background-color: #353535; border: 1px solid #5A5A5A; }}
|
||||
QListWidget::item:selected {{ background-color: #007ACC; color: #FFFFFF; }}
|
||||
QToolTip {{ background-color: #4A4A4A; color: #F0F0F0; border: 1px solid #6A6A6A; padding: {tooltip_padding}px; border-radius: 3px; }}
|
||||
QSplitter::handle {{ background-color: #5A5A5A; }}
|
||||
QSplitter::handle:horizontal {{ width: {5 * scale}px; }}
|
||||
QSplitter::handle:vertical {{ height: {5 * scale}px; }}
|
||||
QFrame[frameShape="4"], QFrame[frameShape="5"] {{
|
||||
border: 1px solid #4A4A4A;
|
||||
border-radius: 3px;
|
||||
}}
|
||||
"""
|
||||
def apply_theme_to_app(main_app, theme_name, initial_load=False):
|
||||
"""
|
||||
Applies the selected theme and scaling to the main application window.
|
||||
"""
|
||||
main_app.current_theme = theme_name
|
||||
if not initial_load:
|
||||
main_app.settings.setValue(THEME_KEY, theme_name)
|
||||
main_app.settings.sync()
|
||||
|
||||
if theme_name == "dark":
|
||||
scale = getattr(main_app, 'scale_factor', 1)
|
||||
main_app.setStyleSheet(get_dark_theme(scale))
|
||||
if not initial_load:
|
||||
main_app.log_signal.emit("🎨 Switched to Dark Mode.")
|
||||
else:
|
||||
main_app.setStyleSheet("")
|
||||
if not initial_load:
|
||||
main_app.log_signal.emit("🎨 Switched to Light Mode.")
|
||||
main_app.update()
|
||||
2064
workers.py
Normal file
2064
workers.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user