From 36b61c06003fd2df5c5a856639fff67dfb9d92ad Mon Sep 17 00:00:00 2001 From: Axel Rafn Date: Mon, 15 Jul 2024 09:29:51 +0200 Subject: [PATCH] Update PyQt version to PyQt6 and adjust window geometry in URLManager --- README.md | 5 +- app-new.py | 510 ++++++++++++++++++++++++++++++++++++++++++++++++++ style-new.qss | 196 +++++++++++++++++++ style.qss | 500 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1208 insertions(+), 3 deletions(-) create mode 100644 app-new.py create mode 100644 style-new.qss create mode 100644 style.qss diff --git a/README.md b/README.md index 561d9bd..ea189a2 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # tabRemember -A open-source app written in Python using PyQT6 for managing links for Sims players who use custom content, and for others who need a nice, simple and little bookmark manager outside the browser. +A open-source app written in Python using PyQt6 for managing links for Sims players who use custom content, and for others who need a nice, simple and little bookmark manager outside the browser. # Wait, what? My wife is one of those people and I've seen how her browsers are, she has maxed out the tabs in the Safari browser in her iPhone and is always stressed that she might lose them at any given moment. Her computer is no better.. -So this project is to create a cross platform app written in Python, relying on PyQT6 for theming and looks. +So this project is to create a cross platform app written in Python, relying on PyQt6 for theming and looks. I plan to have it multilingual as well since not everyone uses the same language. For the time being the languages at the start and while I build this app up towards the first release, I will only offer English and Icelandic. @@ -122,4 +122,3 @@ The app supports custom filters for extracting search terms from URLs. Filters a 1. Click the delete button (🗑️) next to the URL. 2. Confirm the deletion in the dialog that appears. - diff --git a/app-new.py b/app-new.py new file mode 100644 index 0000000..61716dd --- /dev/null +++ b/app-new.py @@ -0,0 +1,510 @@ +import sys +import json +import os +from PyQt6.QtWidgets import ( + QApplication, QWidget, QVBoxLayout, QHBoxLayout, + QLineEdit, QPushButton, QTextEdit, QMessageBox, + QSpacerItem, QSizePolicy, QLabel, QFileDialog, QDialog, + QFormLayout, QTableWidget, QTableWidgetItem, QHeaderView, + QComboBox, QAbstractItemView +) +from PyQt6.QtGui import QIcon, QDesktopServices +from PyQt6.QtCore import Qt, QDateTime, QUrl +from PyQt6.QtSvgWidgets import QSvgWidget + +class URLSearch: + def __init__(self, url, search_engine_url): + self.url = url + self.search_engine_url = search_engine_url + self.filters_dir = 'filters/' + self.available_filters = self.load_filters() + + def load_filters(self): + filters = {} + for file_name in os.listdir(self.filters_dir): + if file_name.endswith('.filter.py'): + domain = '.'.join(file_name.split('.')[:-2]) # Extracting domain.tld from domain.tld.filter.py + filters[domain] = os.path.join(self.filters_dir, file_name) + print("Loaded filters:", filters) # Debug print + return filters + + def extract_terms(self): + parsed_url = QUrl(self.url) + host_parts = parsed_url.host().split('.') + domain = '.'.join(host_parts[-2:]) # Extract full domain including TLD + print("Extracted domain:", domain) # Debug print + if domain in self.available_filters: + filter_file = self.available_filters[domain] + terms = self.apply_filter(filter_file) + if terms: + return terms + return None + + def apply_filter(self, filter_file): + try: + with open(filter_file, 'r') as file: + filter_code = file.read() + local_vars = {'url': self.url, 'terms': [], 'QUrl': QUrl} + exec(filter_code, {}, local_vars) + return local_vars['terms'] + except Exception as e: + print(f"Error applying filter: {e}") + return None + + def search(self): + terms = self.extract_terms() + if terms: + search_query = '+'.join(terms) + search_url = f"{self.search_engine_url}{search_query}" + QDesktopServices.openUrl(QUrl(search_url)) + else: + QDesktopServices.openUrl(QUrl(self.url)) # Open the URL directly if no valid search term is extracted + +class URLManager(QWidget): + def __init__(self): + super().__init__() + + self.settings = self.load_settings() + self.translations = {} + self.load_translations(f'lang/translations_{self.settings["language"].lower()[:2]}.json') + self.setWindowTitle(self.get_translation('title', 'URL Manager')) + self.setGeometry(100, 100, 600, 600) + self.setWindowIcon(QIcon('assets/logo.png')) + + self.load_stylesheet("style-new.qss") # Load and apply the stylesheet + + self.layout = QVBoxLayout() + + # URL input and settings button layout + self.url_layout = QHBoxLayout() + self.url_input = QLineEdit() + self.url_input.setPlaceholderText(self.get_translation('url_placeholder', 'Enter URL')) + self.url_layout.addWidget(self.url_input) + + self.settings_button = QPushButton() + self.settings_button.setFixedSize(28, 28) + self.settings_button.setStyleSheet("padding: 5px;") + self.settings_button.clicked.connect(self.show_settings_dialog) + self.url_layout.addWidget(self.settings_button) + + self.info_button = QPushButton() + self.info_button.setFixedSize(28, 28) + self.info_button.setStyleSheet("padding: 5px;") + self.info_button.clicked.connect(self.show_info_dialog) + self.url_layout.addWidget(self.info_button) + + self.layout.addLayout(self.url_layout) + + self.update_icon_color() + + self.description_input = QTextEdit() + self.description_input.setPlaceholderText(self.get_translation('description_placeholder', 'Enter Description or Thoughts')) + self.description_input.setMaximumHeight(100) + self.layout.addWidget(self.description_input) + + self.add_button = QPushButton(self.get_translation('save_url_button', 'Save URL')) + self.add_button.clicked.connect(self.add_url) + self.layout.addWidget(self.add_button) + + self.layout.addSpacerItem(QSpacerItem(0, 10, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)) + + self.search_layout = QHBoxLayout() + self.search_input = QLineEdit() + self.search_input.setPlaceholderText(self.get_translation('search_placeholder', 'Search...')) + self.search_input.textChanged.connect(self.filter_urls) + self.search_layout.addWidget(self.search_input) + self.layout.addLayout(self.search_layout) + + self.search_input.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + + self.url_list = QTableWidget() + self.url_list.setColumnCount(3) + self.url_list.setHorizontalHeaderLabels([ + self.get_translation('column_url', 'URL'), + self.get_translation('column_date', 'Date'), + self.get_translation('column_actions', 'Actions') + ]) + self.url_list.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) + self.url_list.setColumnWidth(1, 150) + self.url_list.setColumnWidth(2, 100) + self.url_list.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection) + self.layout.addWidget(self.url_list) + + self.setLayout(self.layout) + + self.urls = [] + + self.load_urls() + + self.search_engines = { + "Bing": "https://www.bing.com/search?q=", + "Brave": "https://search.brave.com/search?q=", + "DuckDuckGo": "https://duckduckgo.com/?q=", + "Ecosia": "https://www.ecosia.org/search?q=", + "Google": "https://www.google.com/search?q=", + "Startpage": "https://www.startpage.com/do/search?q=", + "Swisscows": "https://swisscows.com/web?query=" + } + + def load_stylesheet(self, stylesheet_path): + if os.path.exists(stylesheet_path): + with open(stylesheet_path, "r") as file: + self.setStyleSheet(file.read()) + + def load_translations(self, file_path): + if os.path.exists(file_path): + with open(file_path, 'r') as file: + data = json.load(file) + self.translations = data['translations'] + + def get_translation(self, key, default): + return self.translations.get(key, default) + + def update_icon_color(self): + palette = self.palette() + if palette.color(palette.ColorRole.Window).value() < 128: + settings_icon_path = 'assets/cogwheel_dark.svg' + info_icon_path = 'assets/info_dark.svg' + self.action_icons = { + "visit": 'assets/visit_dark.svg', + "info": 'assets/info_dark.svg', + "search": 'assets/search_dark.svg', + "delete": 'assets/delete_dark.svg' + } + else: + settings_icon_path = 'assets/cogwheel_light.svg' + info_icon_path = 'assets/info_light.svg' + self.action_icons = { + "visit": 'assets/visit_light.svg', + "info": 'assets/info_light.svg', + "search": 'assets/search_light.svg', + "delete": 'assets/delete_light.svg' + } + + self.settings_button.setIcon(QIcon(settings_icon_path)) + self.info_button.setIcon(QIcon(info_icon_path)) + + def add_url(self): + url = self.url_input.text() + description = self.description_input.toPlainText() + if url: + date_added = QDateTime.currentDateTime().toString(Qt.DateFormat.ISODate) + url_data = {'url': url, 'description': description, 'date': date_added} + self.urls.append(url_data) + self.save_url(url_data) + self.update_url_list() + self.url_input.clear() + self.description_input.clear() + + def save_url(self, url_data): + url_path = os.path.join(self.settings['data_directory'], 'urls.json') + if os.path.exists(url_path): + with open(url_path, 'r') as file: + url_list = json.load(file) + else: + url_list = [] + url_list.append(url_data) + with open(url_path, 'w') as file: + json.dump(url_list, file) + + def update_url(self, row, new_url, new_description): + self.urls[row]['url'] = new_url + self.urls[row]['description'] = new_description + self.save_urls() + self.update_url_list() + + def delete_url(self, row): + reply = QMessageBox.question(self, self.get_translation('delete_confirmation_title', 'Delete URL'), self.get_translation('delete_confirmation', 'Are you sure you want to delete this URL?'), + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No) + if reply == QMessageBox.StandardButton.Yes: + del self.urls[row] + self.save_urls() + self.update_url_list() + + def filter_urls(self): + search_term = self.search_input.text().strip().lower() + if not search_term: + self.update_url_list() + return + + filtered_urls = [url for url in self.urls if search_term in url['url'].lower() or search_term in url['description'].lower()] + self.update_url_list(filtered_urls) + + def update_url_list(self, urls=None): + self.url_list.setRowCount(0) + urls_to_display = urls if urls else self.urls + for row, url in enumerate(urls_to_display): + self.url_list.insertRow(row) + self.url_list.setItem(row, 0, QTableWidgetItem(url['url'])) + formatted_date = self.format_date(url['date']) + date_item = QTableWidgetItem(formatted_date) + date_item.setTextAlignment(Qt.AlignmentFlag.AlignVCenter) + self.url_list.setItem(row, 1, date_item) + + actions_layout = QHBoxLayout() + actions_layout.setContentsMargins(0, 0, 0, 0) + actions_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) + + visit_button = QPushButton() + visit_button.setIcon(QIcon(self.action_icons["visit"])) + visit_button.setFixedSize(22, 22) + visit_button.setStyleSheet("border: none; padding: 3px;") + visit_button.clicked.connect(lambda _, url=url['url']: self.visit_url(url)) + actions_layout.addWidget(visit_button) + + info_button = QPushButton() + info_button.setIcon(QIcon(self.action_icons["info"])) + info_button.setFixedSize(22, 22) + info_button.setStyleSheet("border: none; padding: 3px;") + info_button.clicked.connect(lambda _, row=row: self.view_description(row)) + actions_layout.addWidget(info_button) + + search_button = QPushButton() + search_button.setIcon(QIcon(self.action_icons["search"])) + search_button.setFixedSize(22, 22) + search_button.setStyleSheet("border: none; padding: 3px;") + search_button.clicked.connect(lambda _, url=url['url']: self.search_url(url)) + actions_layout.addWidget(search_button) + + delete_button = QPushButton() + delete_button.setIcon(QIcon(self.action_icons["delete"])) + delete_button.setFixedSize(22, 22) + delete_button.setStyleSheet("border: none; padding: 3px;") + delete_button.clicked.connect(lambda _, row=row: self.delete_url(row)) + actions_layout.addWidget(delete_button) + + actions_widget = QWidget() + actions_widget.setLayout(actions_layout) + + self.url_list.setCellWidget(row, 2, actions_widget) + + vertical_header = self.url_list.verticalHeader() + vertical_header.setVisible(False) + + def format_date(self, date_str): + date = QDateTime.fromString(date_str, Qt.DateFormat.ISODate) + if self.settings['date_format'] == 'Nerdy': + return date.toString("yyyy-MM-ddTHH:mm:ss") + elif self.settings['date_format'] == 'Normal': + return date.toString("dd/MM/yy @ HH:mm") + elif self.settings['date_format'] == 'Murica!': + return date.toString("MM/dd/yyyy hh:mm AP") + else: + return date_str + + def visit_url(self, url): + QDesktopServices.openUrl(QUrl(url)) + + def view_description(self, row): + dialog = DescriptionDialog(self, row) + dialog.exec() + + def search_url(self, url): + search_engine_url = self.search_engines[self.settings['search_engine']] + url_search = URLSearch(url, search_engine_url) + url_search.search() + + def load_settings(self): + settings_path = 'data/settings.json' + if os.path.exists(settings_path): + with open(settings_path, 'r') as file: + settings = json.load(file) + self.data_directory = settings['data_directory'] + self.search_engine = settings['search_engine'] + self.date_format = settings.get('date_format', 'Nerdy') + self.language = settings.get('language', 'English') + return settings + else: + default_settings = { + 'data_directory': 'data/', + 'search_engine': 'Google', + 'date_format': 'Nerdy', + 'language': 'English' + } + self.save_settings(default_settings) + return default_settings + + def save_settings(self, settings=None): + settings_path = 'data/settings.json' + os.makedirs(os.path.dirname(settings_path), exist_ok=True) + if settings is None: + settings = { + 'data_directory': self.data_directory, + 'search_engine': self.search_engine, + 'date_format': self.date_format, + 'language': self.language + } + with open(settings_path, 'w') as file: + json.dump(settings, file) + + def load_urls(self): + url_path = os.path.join(self.settings['data_directory'], 'urls.json') + if os.path.exists(url_path): + with open(url_path, 'r') as file: + self.urls = json.load(file) + self.update_url_list() + + def save_urls(self): + url_path = os.path.join(self.settings['data_directory'], 'urls.json') + with open(url_path, 'w') as file: + json.dump(self.urls, file) + + def show_info_dialog(self): + dialog = QDialog(self) + dialog.setWindowTitle(self.get_translation('about_title', 'About URL Manager')) + + layout = QVBoxLayout() + + svg_widget = QSvgWidget('assets/logo.svg') + svg_widget.setFixedSize(256, 256) + layout.addWidget(svg_widget, alignment=Qt.AlignmentFlag.AlignCenter) + + written_by_label = QLabel(self.get_translation('developed_by', 'Developed by Axel Rafn')) + layout.addWidget(written_by_label, alignment=Qt.AlignmentFlag.AlignCenter) + + link_label = QLabel() + link_label.setText(f'{self.get_translation("repository_link_name", "Project Home")}') + link_label.setOpenExternalLinks(True) + layout.addWidget(link_label, alignment=Qt.AlignmentFlag.AlignCenter) + + dialog.setLayout(layout) + dialog.exec() + + def show_settings_dialog(self): + dialog = SettingsDialog(self) + if dialog.exec() == QDialog.DialogCode.Accepted: + self.settings = self.load_settings() + self.update_url_list() + +class DescriptionDialog(QDialog): + def __init__(self, parent, row): + super().__init__(parent) + self.parent = parent + self.row = row + self.setWindowTitle(self.parent.get_translation('description_title', 'URL Description')) + + layout = QVBoxLayout() + + self.url_input = QLineEdit(self.parent.urls[row]['url']) + layout.addWidget(self.url_input) + + self.description_input = QTextEdit(self.parent.urls[row]['description']) + layout.addWidget(self.description_input) + + button_layout = QHBoxLayout() + self.update_button = QPushButton(self.parent.get_translation('update_button', 'Update')) + self.update_button.clicked.connect(self.update_url) + self.close_button = QPushButton(self.parent.get_translation('close_button', 'Close')) + self.close_button.clicked.connect(self.close) + button_layout.addWidget(self.update_button) + button_layout.addWidget(self.close_button) + layout.addLayout(button_layout) + + self.setLayout(layout) + + def update_url(self): + new_url = self.url_input.text() + new_description = self.description_input.toPlainText() + self.parent.update_url(self.row, new_url, new_description) + self.accept() + +class SettingsDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle(parent.get_translation('settings_title', 'Settings')) + self.setFixedWidth(360) + + self.parent = parent + + layout = QVBoxLayout() + + directory_label = QLabel(parent.get_translation('data_directory_label', 'Data Directory:')) + layout.addWidget(directory_label) + + directory_layout = QHBoxLayout() + self.directory_input = QLineEdit(self.parent.settings['data_directory']) + self.browse_button = QPushButton(self.parent.get_translation('browse_button', 'Browse...')) + self.browse_button.clicked.connect(self.browse_directory) + directory_layout.addWidget(self.directory_input) + directory_layout.addWidget(self.browse_button) + layout.addLayout(directory_layout) + + layout.addSpacerItem(QSpacerItem(0, 4, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)) + + form_layout = QFormLayout() + + # Populate Search Engine Combobox + self.search_engine_combobox = QComboBox() + search_engines = sorted(self.parent.search_engines.keys()) + self.search_engine_combobox.addItems(search_engines) + self.search_engine_combobox.setCurrentText(self.parent.settings['search_engine']) + form_layout.addRow(self.parent.get_translation('search_engine_label', 'Search Engine:'), self.search_engine_combobox) + + # Populate Date Format Combobox + self.date_format_combobox = QComboBox() + self.date_format_combobox.addItems(["Nerdy", "Normal", "Murica!"]) + self.date_format_combobox.setCurrentText(self.parent.settings['date_format']) + form_layout.addRow(self.parent.get_translation('date_format_label', 'Date Format:'), self.date_format_combobox) + + # Populate Language Combobox + self.language_combobox = QComboBox() + self.populate_language_combobox() + current_language_code = self.parent.settings['language'] + self.set_current_language(current_language_code) + form_layout.addRow(self.parent.get_translation('language_label', 'Language:'), self.language_combobox) + + layout.addLayout(form_layout) + + self.save_button = QPushButton(self.parent.get_translation('save_button', 'Save')) + self.save_button.clicked.connect(self.save_settings) + layout.addWidget(self.save_button) + + self.setLayout(layout) + + def browse_directory(self): + directory = QFileDialog.getExistingDirectory(self, "Select Directory") + if directory: + self.directory_input.setText(directory) + + def populate_language_combobox(self): + lang_dir = 'lang/' + language_files = [f for f in os.listdir(lang_dir) if f.startswith('translations_') and f.endswith('.json')] + self.language_map = {} + + for file_name in language_files: + file_path = os.path.join(lang_dir, file_name) + with open(file_path, 'r') as file: + data = json.load(file) + language_name = data.get('language', 'Unknown') + language_code = file_name[len('translations_'):-len('.json')] + display_text = f"{language_name} ({language_code})" + self.language_combobox.addItem(display_text) + self.language_map[language_code] = display_text + + def set_current_language(self, language_code): + if language_code in self.language_map: + display_text = self.language_map[language_code] + index = self.language_combobox.findText(display_text) + if index != -1: + self.language_combobox.setCurrentIndex(index) + + def save_settings(self): + selected_language = self.language_combobox.currentText() + language_code = selected_language.split('(')[-1].strip(' )') # Extract language code from "(en)" format + old_language = self.parent.settings['language'] + self.parent.settings['data_directory'] = self.directory_input.text() + self.parent.settings['search_engine'] = self.search_engine_combobox.currentText() + self.parent.settings['date_format'] = self.date_format_combobox.currentText() + self.parent.settings['language'] = language_code + self.parent.save_settings(self.parent.settings) + + if old_language != language_code: + QMessageBox.information(self, self.parent.get_translation('restart_required_title', 'Restart Required'), self.parent.get_translation('restart_required_message', 'You need to restart the application for the language changes to take effect.')) + + self.accept() + +if __name__ == '__main__': + app = QApplication(sys.argv) + window = URLManager() + window.show() + sys.exit(app.exec()) diff --git a/style-new.qss b/style-new.qss new file mode 100644 index 0000000..a6d3370 --- /dev/null +++ b/style-new.qss @@ -0,0 +1,196 @@ +/* General application styling */ +QWidget { + color: rgb(221, 221, 221); + font: 11pt "Segoe UI"; + background-color: rgb(40, 44, 52); +} + +/* QLineEdit styling */ +QLineEdit { + background-color: rgb(33, 37, 43); + border: 2px solid rgb(33, 37, 43); + border-radius: 5px; + padding-left: 10px; + selection-color: rgb(255, 255, 255); + selection-background-color: rgb(189, 147, 249); +} +QLineEdit:hover { + border: 2px solid rgb(64, 71, 88); +} +QLineEdit:focus { + border: 2px solid rgb(91, 101, 124); +} + +/* QTextEdit styling */ +QTextEdit { + background-color: rgb(33, 37, 43); + border: 2px solid rgb(33, 37, 43); + border-radius: 5px; + padding: 10px; + selection-color: rgb(255, 255, 255); + selection-background-color: rgb(189, 147, 249); +} +QTextEdit:hover { + border: 2px solid rgb(64, 71, 88); +} +QTextEdit:focus { + border: 2px solid rgb(91, 101, 124); +} + +/* QPushButton styling */ +QPushButton { + background-color: rgb(52, 59, 72); + border: 2px solid rgb(52, 59, 72); + border-radius: 5px; + padding: 5px; + color: rgb(221, 221, 221); +} +QPushButton:hover { + background-color: rgb(57, 65, 80); + border: 2px solid rgb(61, 70, 86); +} +QPushButton:pressed { + background-color: rgb(35, 40, 49); + border: 2px solid rgb(43, 50, 61); +} + +/* QTableWidget styling */ +QTableWidget { + background-color: rgb(33, 37, 43); + border: 2px solid rgb(33, 37, 43); + border-radius: 5px; + padding: 10px; + gridline-color: rgb(44, 49, 58); +} +QTableWidget::item { + border-color: rgb(44, 49, 60); + padding-left: 5px; + padding-right: 5px; + gridline-color: rgb(44, 49, 60); +} +QTableWidget::item:selected { + background-color: rgb(189, 147, 249); +} +QHeaderView::section { + background-color: rgb(33, 37, 43); + border: 1px solid rgb(44, 49, 58); + padding: 3px; +} +QTableWidget::horizontalHeader { + background-color: rgb(33, 37, 43); +} +QHeaderView::section:horizontal { + border: 1px solid rgb(33, 37, 43); + background-color: rgb(33, 37, 43); + padding: 3px; + border-top-left-radius: 7px; + border-top-right-radius: 7px; +} +QHeaderView::section:vertical { + border: 1px solid rgb(44, 49, 60); +} + +/* QComboBox styling */ +QComboBox { + background-color: rgb(33, 37, 43); + border: 2px solid rgb(33, 37, 43); + border-radius: 5px; + padding: 5px; + padding-left: 10px; +} +QComboBox:hover { + border: 2px solid rgb(64, 71, 88); +} +QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: top right; + width: 25px; + border-left-width: 3px; + border-left-color: rgba(39, 44, 54, 150); + border-left-style: solid; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + background-image: url(:/icons/images/icons/cil-arrow-bottom.png); + background-position: center; + background-repeat: no-repeat; +} +QComboBox QAbstractItemView { + color: rgb(255, 121, 198); + background-color: rgb(33, 37, 43); + padding: 10px; + selection-background-color: rgb(39, 44, 54); +} + +/* QScrollBar styling */ +QScrollBar:horizontal { + border: none; + background: rgb(52, 59, 72); + height: 8px; + margin: 0px 21px 0 21px; + border-radius: 0px; +} +QScrollBar::handle:horizontal { + background: rgb(189, 147, 249); + min-width: 25px; + border-radius: 4px; +} +QScrollBar::add-line:horizontal { + border: none; + background: rgb(55, 63, 77); + width: 20px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + subcontrol-position: right; + subcontrol-origin: margin; +} +QScrollBar::sub-line:horizontal { + border: none; + background: rgb(55, 63, 77); + width: 20px; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + subcontrol-position: left; + subcontrol-origin: margin; +} +QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal { + background: none; +} +QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { + background: none; +} +QScrollBar:vertical { + border: none; + background: rgb(52, 59, 72); + width: 8px; + margin: 21px 0 21px 0; + border-radius: 0px; +} +QScrollBar::handle:vertical { + background: rgb(189, 147, 249); + min-height: 25px; + border-radius: 4px; +} +QScrollBar::add-line:vertical { + border: none; + background: rgb(55, 63, 77); + height: 20px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} +QScrollBar::sub-line:vertical { + border: none; + background: rgb(55, 63, 77); + height: 20px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + subcontrol-position: top; + subcontrol-origin: margin; +} +QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + background: none; +} +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; +} diff --git a/style.qss b/style.qss new file mode 100644 index 0000000..7868845 --- /dev/null +++ b/style.qss @@ -0,0 +1,500 @@ + +QWidget{ + color: rgb(221, 221, 221); + font: 10pt "Segoe UI"; +} + + +Tooltip */ +QToolTip { + color: #ffffff; + background-color: rgba(33, 37, 43, 180); + border: 1px solid rgb(44, 49, 58); + background-image: none; + background-position: left center; + background-repeat: no-repeat; + border: none; + border-left: 2px solid rgb(255, 121, 198); + text-align: left; + padding-left: 8px; + margin: 0px; +} + + +Bg App */ +#bgApp { + background-color: rgb(40, 44, 52); + border: 1px solid rgb(44, 49, 58); +} + + +Left Menu */ +#leftMenuBg { + background-color: rgb(33, 37, 43); +} +#topLogo { + background-color: rgb(33, 37, 43); + background-image: url(:/images/images/images/PyDracula.png); + background-position: centered; + background-repeat: no-repeat; +} +#titleLeftApp { font: 63 12pt "Segoe UI Semibold"; } +#titleLeftDescription { font: 8pt "Segoe UI"; color: rgb(189, 147, 249); } + +/* MENUS */ +#topMenu .QPushButton { + background-position: left center; + background-repeat: no-repeat; + border: none; + border-left: 22px solid transparent; + background-color: transparent; + text-align: left; + padding-left: 44px; +} +#topMenu .QPushButton:hover { + background-color: rgb(40, 44, 52); +} +#topMenu .QPushButton:pressed { + background-color: rgb(189, 147, 249); + color: rgb(255, 255, 255); +} +#bottomMenu .QPushButton { + background-position: left center; + background-repeat: no-repeat; + border: none; + border-left: 20px solid transparent; + background-color:transparent; + text-align: left; + padding-left: 44px; +} +#bottomMenu .QPushButton:hover { + background-color: rgb(40, 44, 52); +} +#bottomMenu .QPushButton:pressed { + background-color: rgb(189, 147, 249); + color: rgb(255, 255, 255); +} +#leftMenuFrame{ + border-top: 3px solid rgb(44, 49, 58); +} + +/* Toggle Button */ +#toggleButton { + background-position: left center; + background-repeat: no-repeat; + border: none; + border-left: 20px solid transparent; + background-color: rgb(37, 41, 48); + text-align: left; + padding-left: 44px; + color: rgb(113, 126, 149); +} +#toggleButton:hover { + background-color: rgb(40, 44, 52); +} +#toggleButton:pressed { + background-color: rgb(189, 147, 249); +} + +/* Title Menu */ +#titleRightInfo { padding-left: 10px; } + + + +Extra Tab */ +#extraLeftBox { + background-color: rgb(44, 49, 58); +} +#extraTopBg{ + background-color: rgb(189, 147, 249) +} + +/* Icon */ +#extraIcon { + background-position: center; + background-repeat: no-repeat; + background-image: url(:/icons/images/icons/icon_settings.png); +} + +/* Label */ +#extraLabel { color: rgb(255, 255, 255); } + +/* Btn Close */ +#extraCloseColumnBtn { background-color: rgba(255, 255, 255, 0); border: none; border-radius: 5px; } +#extraCloseColumnBtn:hover { background-color: rgb(196, 161, 249); border-style: solid; border-radius: 4px; } +#extraCloseColumnBtn:pressed { background-color: rgb(180, 141, 238); border-style: solid; border-radius: 4px; } + +/* Extra Content */ +#extraContent{ + border-top: 3px solid rgb(40, 44, 52); +} + +/* Extra Top Menus */ +#extraTopMenu .QPushButton { +background-position: left center; + background-repeat: no-repeat; + border: none; + border-left: 22px solid transparent; + background-color:transparent; + text-align: left; + padding-left: 44px; +} +#extraTopMenu .QPushButton:hover { + background-color: rgb(40, 44, 52); +} +#extraTopMenu .QPushButton:pressed { + background-color: rgb(189, 147, 249); + color: rgb(255, 255, 255); +} + + +Content App */ +#contentTopBg{ + background-color: rgb(33, 37, 43); +} +#contentBottom{ + border-top: 3px solid rgb(44, 49, 58); +} + +/* Top Buttons */ +#rightButtons .QPushButton { background-color: rgba(255, 255, 255, 0); border: none; border-radius: 5px; } +#rightButtons .QPushButton:hover { background-color: rgb(44, 49, 57); border-style: solid; border-radius: 4px; } +#rightButtons .QPushButton:pressed { background-color: rgb(23, 26, 30); border-style: solid; border-radius: 4px; } + +/* Theme Settings */ +#extraRightBox { background-color: rgb(44, 49, 58); } +#themeSettingsTopDetail { background-color: rgb(189, 147, 249); } + +/* Bottom Bar */ +#bottomBar { background-color: rgb(44, 49, 58); } +#bottomBar QLabel { font-size: 11px; color: rgb(113, 126, 149); padding-left: 10px; padding-right: 10px; padding-bottom: 2px; } + +/* CONTENT SETTINGS */ +/* MENUS */ +#contentSettings .QPushButton { + background-position: left center; + background-repeat: no-repeat; + border: none; + border-left: 22px solid transparent; + background-color:transparent; + text-align: left; + padding-left: 44px; +} +#contentSettings .QPushButton:hover { + background-color: rgb(40, 44, 52); +} +#contentSettings .QPushButton:pressed { + background-color: rgb(189, 147, 249); + color: rgb(255, 255, 255); +} + + +QTableWidget */ +QTableWidget { + background-color: transparent; + padding: 10px; + border-radius: 5px; + gridline-color: rgb(44, 49, 58); + border-bottom: 1px solid rgb(44, 49, 60); +} +QTableWidget::item{ + border-color: rgb(44, 49, 60); + padding-left: 5px; + padding-right: 5px; + gridline-color: rgb(44, 49, 60); +} +QTableWidget::item:selected{ + background-color: rgb(189, 147, 249); +} +QHeaderView::section{ + background-color: rgb(33, 37, 43); + max-width: 30px; + border: 1px solid rgb(44, 49, 58); + border-style: none; + border-bottom: 1px solid rgb(44, 49, 60); + border-right: 1px solid rgb(44, 49, 60); +} +QTableWidget::horizontalHeader { + background-color: rgb(33, 37, 43); +} +QHeaderView::section:horizontal +{ + border: 1px solid rgb(33, 37, 43); + background-color: rgb(33, 37, 43); + padding: 3px; + border-top-left-radius: 7px; + border-top-right-radius: 7px; +} +QHeaderView::section:vertical +{ + border: 1px solid rgb(44, 49, 60); +} + + +LineEdit */ +QLineEdit { + background-color: rgb(33, 37, 43); + border-radius: 5px; + border: 2px solid rgb(33, 37, 43); + padding-left: 10px; + selection-color: rgb(255, 255, 255); + selection-background-color: rgb(255, 121, 198); +} +QLineEdit:hover { + border: 2px solid rgb(64, 71, 88); +} +QLineEdit:focus { + border: 2px solid rgb(91, 101, 124); +} + + +PlainTextEdit */ +QPlainTextEdit { + background-color: rgb(27, 29, 35); + border-radius: 5px; + padding: 10px; + selection-color: rgb(255, 255, 255); + selection-background-color: rgb(255, 121, 198); +} +QPlainTextEdit QScrollBar:vertical { + width: 8px; + } +QPlainTextEdit QScrollBar:horizontal { + height: 8px; + } +QPlainTextEdit:hover { + border: 2px solid rgb(64, 71, 88); +} +QPlainTextEdit:focus { + border: 2px solid rgb(91, 101, 124); +} + + +ScrollBars */ +QScrollBar:horizontal { + border: none; + background: rgb(52, 59, 72); + height: 8px; + margin: 0px 21px 0 21px; + border-radius: 0px; +} +QScrollBar::handle:horizontal { + background: rgb(189, 147, 249); + min-width: 25px; + border-radius: 4px +} +QScrollBar::add-line:horizontal { + border: none; + background: rgb(55, 63, 77); + width: 20px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + subcontrol-position: right; + subcontrol-origin: margin; +} +QScrollBar::sub-line:horizontal { + border: none; + background: rgb(55, 63, 77); + width: 20px; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + subcontrol-position: left; + subcontrol-origin: margin; +} +QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal +{ + background: none; +} +QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal +{ + background: none; +} + QScrollBar:vertical { + border: none; + background: rgb(52, 59, 72); + width: 8px; + margin: 21px 0 21px 0; + border-radius: 0px; + } + QScrollBar::handle:vertical { + background: rgb(189, 147, 249); + min-height: 25px; + border-radius: 4px + } + QScrollBar::add-line:vertical { + border: none; + background: rgb(55, 63, 77); + height: 20px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + subcontrol-position: bottom; + subcontrol-origin: margin; + } + QScrollBar::sub-line:vertical { + border: none; + background: rgb(55, 63, 77); + height: 20px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + subcontrol-position: top; + subcontrol-origin: margin; + } + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + background: none; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + + +CheckBox */ +QCheckBox::indicator { + border: 3px solid rgb(52, 59, 72); + width: 15px; + height: 15px; + border-radius: 10px; + background: rgb(44, 49, 60); +} +QCheckBox::indicator:hover { + border: 3px solid rgb(58, 66, 81); +} +QCheckBox::indicator:checked { + background: 3px solid rgb(52, 59, 72); + border: 3px solid rgb(52, 59, 72); + background-image: url(:/icons/images/icons/cil-check-alt.png); +} + + +RadioButton */ +QRadioButton::indicator { + border: 3px solid rgb(52, 59, 72); + width: 15px; + height: 15px; + border-radius: 10px; + background: rgb(44, 49, 60); +} +QRadioButton::indicator:hover { + border: 3px solid rgb(58, 66, 81); +} +QRadioButton::indicator:checked { + background: 3px solid rgb(94, 106, 130); + border: 3px solid rgb(52, 59, 72); +} + + +ComboBox */ +QComboBox{ + background-color: rgb(27, 29, 35); + border-radius: 5px; + border: 2px solid rgb(33, 37, 43); + padding: 5px; + padding-left: 10px; +} +QComboBox:hover{ + border: 2px solid rgb(64, 71, 88); +} +QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: top right; + width: 25px; + border-left-width: 3px; + border-left-color: rgba(39, 44, 54, 150); + border-left-style: solid; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + background-image: url(:/icons/images/icons/cil-arrow-bottom.png); + background-position: center; + background-repeat: no-reperat; + } +QComboBox QAbstractItemView { + color: rgb(255, 121, 198); + background-color: rgb(33, 37, 43); + padding: 10px; + selection-background-color: rgb(39, 44, 54); +} + + +Sliders */ +QSlider::groove:horizontal { + border-radius: 5px; + height: 10px; + margin: 0px; + background-color: rgb(52, 59, 72); +} +QSlider::groove:horizontal:hover { + background-color: rgb(55, 62, 76); +} +QSlider::handle:horizontal { + background-color: rgb(189, 147, 249); + border: none; + height: 10px; + width: 10px; + margin: 0px; + border-radius: 5px; +} +QSlider::handle:horizontal:hover { + background-color: rgb(195, 155, 255); +} +QSlider::handle:horizontal:pressed { + background-color: rgb(255, 121, 198); +} + +QSlider::groove:vertical { + border-radius: 5px; + width: 10px; + margin: 0px; + background-color: rgb(52, 59, 72); +} +QSlider::groove:vertical:hover { + background-color: rgb(55, 62, 76); +} +QSlider::handle:vertical { + background-color: rgb(189, 147, 249); + border: none; + height: 10px; + width: 10px; + margin: 0px; + border-radius: 5px; +} +QSlider::handle:vertical:hover { + background-color: rgb(195, 155, 255); +} +QSlider::handle:vertical:pressed { + background-color: rgb(255, 121, 198); +} + + +CommandLinkButton */ +QCommandLinkButton { + color: rgb(255, 121, 198); + border-radius: 5px; + padding: 5px; +} +QCommandLinkButton:hover { + color: rgb(255, 170, 255); + background-color: rgb(44, 49, 60); +} +QCommandLinkButton:pressed { + color: rgb(189, 147, 249); + background-color: rgb(52, 58, 71); +} + + +Button */ +#pagesContainer QPushButton { + border: 2px solid rgb(52, 59, 72); + border-radius: 5px; + background-color: rgb(52, 59, 72); +} +#pagesContainer QPushButton:hover { + background-color: rgb(57, 65, 80); + border: 2px solid rgb(61, 70, 86); +} +#pagesContainer QPushButton:pressed { + background-color: rgb(35, 40, 49); + border: 2px solid rgb(43, 50, 61); +} + + +