├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── app ├── __init__.py ├── __main__.py ├── controllers │ ├── __init__.py │ ├── config_controller.py │ ├── main_controller.py │ ├── scratch_pad_controller.py │ └── shortcut_controller.py ├── core │ ├── __init__.py │ └── str_utils.py ├── data │ ├── __init__.py │ ├── entities.py │ └── lite_data_store.py ├── events │ ├── __init__.py │ └── signals.py ├── generated │ ├── ConfigurationDialog_ui.py │ ├── MainWindow_ui.py │ ├── __init__.py │ └── resources_rc.py ├── settings │ ├── __init__.py │ ├── app_config.py │ └── app_settings.py ├── themes │ ├── __init__.py │ ├── theme_loader.py │ └── theme_provider.py ├── views │ ├── __init__.py │ ├── configuration_dialog.py │ └── main_window.py └── widgets │ └── __init__.py ├── bin └── app ├── docs └── images │ └── pyqt-boilerplate-screenshots.png ├── mk-icns.sh ├── requirements ├── base.txt ├── build.txt └── dev.txt ├── resources ├── fonts │ └── JetBrainsMono-Regular.ttf ├── icons │ ├── app.icns │ ├── app.ico │ └── app.svg ├── images │ ├── app-logo.png │ ├── app.png │ └── configure-48.png ├── resources.qrc ├── themes │ ├── dark.qss │ ├── light.qss │ ├── pyg-dark.css │ └── pyg-light.css └── views │ ├── ConfigurationDialog.ui │ └── MainWindow.ui └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # IDE 75 | target/ 76 | .idea/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 namuan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export PROJECTNAME=$(shell basename "$(PWD)") 2 | 3 | .SILENT: ; # no need for @ 4 | 5 | clean-pyc: ## remove Python file artifacts 6 | find . -name '*.pyc' -exec rm -f {} + 7 | find . -name '*.pyo' -exec rm -f {} + 8 | find . -name '*~' -exec rm -f {} + 9 | find . -name '__pycache__' -exec rm -fr {} + 10 | 11 | clean: clean-pyc ## Clean package 12 | rm -rf build dist 13 | 14 | black: ## Runs black for code formatting 15 | black app --exclude generated 16 | 17 | lint: black ## Runs Flake8 for linting 18 | flake8 app 19 | 20 | setup: clean ## Re-initiates virtualenv 21 | rm -rf venv 22 | python3 -m venv venv 23 | ./venv/bin/python3 -m pip install -r requirements/dev.txt 24 | 25 | deps: ## Reinstalls dependencies 26 | ./venv/bin/python3 -m pip install -r requirements/dev.txt 27 | 28 | uic: res ## Converts ui files in resources/views to python 29 | for i in `ls resources/views/*.ui`; do FNAME=`basename $${i} ".ui"`; ./venv/bin/pyuic5 $${i} > "app/generated/$${FNAME}_ui.py"; done 30 | 31 | res: ## Generates and compresses resource listed in resources/resources.qrc 32 | ./venv/bin/pyrcc5 -compress 9 -o app/generated/resources_rc.py resources/resources.qrc 33 | 34 | run: ## Runs the application 35 | export PYTHONPATH=`pwd`:$PYTHONPATH && ./venv/bin/python3 app/__main__.py 36 | 37 | icns: ## Generates icon files from svg 38 | echo "Run ./mk-icns.sh resources/icons/app.svg app" 39 | 40 | .PHONY: help 41 | .DEFAULT_GOAL := setup 42 | 43 | help: Makefile 44 | echo 45 | echo " Choose a command run in "$(PROJECTNAME)":" 46 | echo 47 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 48 | echo -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

PyQt Boilerplate

5 | 6 | 7 | ### Features: 8 | 9 | 👉 Runnable application 10 | 11 | 👉 Application configuration using `.ini` files 12 | 13 | 👉 Separate requirements file for development and build 14 | 15 | 👉 Manage UI files and resources from QtDesigner 16 | 17 | 👉 Resource files and application icon 18 | 19 | 👉 Make file to set up virtualenv, run and test application 20 | 21 | 👉 Make command to generate code from QtDesigner generated `.ui` files 22 | 23 | 👉 Make command to generate and compress resource files 24 | 25 | 👉 Script to generate icons from SVG file (uses Inkscape) 26 | 27 | 👉 Stylesheets for switching between Light/Dark modes 28 | 29 | 👉 Rotating log file 30 | 31 | 👉 Application data persistence support using Sqlite database 32 | 33 | ### Light/Dark Themes 34 | 35 | ![Themes](docs/images/pyqt-boilerplate-screenshots.png) 36 | 37 | ### Development 38 | 39 | You'll need Python3.6+ to run this application. 40 | 41 | The following make commands can be used for setting up virtual env and running the application. 42 | 43 | Setup a new virtual env in venv folder and install all the dependencies listed in requirements/dev.txt 44 | 45 | ``` 46 | $ make setup 47 | ``` 48 | 49 | Run application 50 | ``` 51 | $ make run 52 | ``` 53 | 54 | Other useful commands 55 | 56 | ``` 57 | $ make help 58 | 59 | Choose a command run in pyqt-boilerplate: 60 | 61 | black Runs black for code formatting 62 | clean-pyc remove Python file artifacts 63 | clean Clean package 64 | icns Generates icon files from svg 65 | lint Runs Flake8 for linting 66 | release Step to prepare a new release 67 | res Generates and compresses resource file 68 | run Runs the application 69 | setup Setup virtual environment and install dependencies 70 | uic Converts ui files to python 71 | venv Re-initiates virtualenv 72 | ``` 73 | 74 | ### Application Icon 75 | 76 | An example application icon is available in `resources/icons/app.svg`. 77 | Run the following command to generate `.ico` and `.icns` files. 78 | Note that it requires `Inkspace` to be available on the local machine. 79 | Please check the following script to change location of `Inkscape`. 80 | 81 | ``` 82 | $ ./mk-icns.sh 83 | ``` 84 | 85 | ### Credits 86 | 87 | 🙏 https://github.com/gmarull/pyside2-boilerplate 88 | 89 | 🙏 https://github.com/zenorocha/dracula-theme 90 | 91 | 🙏 https://icons8.com 92 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.4.0" 2 | __appname__ = "OnePage" 3 | __description__ = "Simple ScratchPad" 4 | __desktopid__ = "dev.deskriders.OnePage" 5 | 6 | from app.generated import resources_rc # noqa 7 | -------------------------------------------------------------------------------- /app/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from PyQt5.QtWidgets import QApplication 3 | 4 | from app import __version__, __appname__, __desktopid__ 5 | from app.themes.theme_provider import configure_theme 6 | from app.views.main_window import MainWindow 7 | 8 | 9 | def main(): 10 | app = QApplication(sys.argv) 11 | app.setApplicationVersion(__version__) 12 | app.setApplicationName(__appname__) 13 | app.setDesktopFileName(__desktopid__) 14 | 15 | window = MainWindow() 16 | configure_theme(app) 17 | window.show() 18 | sys.exit(app.exec_()) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /app/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from .config_controller import ConfigController 2 | from .main_controller import MainWindowController 3 | from .shortcut_controller import ShortcutController 4 | from .scratch_pad_controller import ScratchPadController 5 | -------------------------------------------------------------------------------- /app/controllers/config_controller.py: -------------------------------------------------------------------------------- 1 | from app.settings.app_config import AppConfig 2 | from app.views.configuration_dialog import ConfigurationDialog 3 | 4 | 5 | class ConfigController: 6 | def __init__(self, parent_view, app): 7 | self.view = ConfigurationDialog(parent_view) 8 | self.parent_view = parent_view 9 | self.app = app 10 | 11 | # ui events 12 | self.view.btn_save_configuration.pressed.connect(self.on_success) 13 | self.view.btn_cancel_configuration.pressed.connect(self.ignore_changes) 14 | 15 | def ignore_changes(self): 16 | self.view.reject() 17 | 18 | def on_success(self): 19 | app_config = self.form_to_object() 20 | self.app.save_configuration(app_config) 21 | self.view.accept() 22 | 23 | def show_dialog(self): 24 | app_config = self.app.load_configuration() 25 | self.object_to_form(app_config) 26 | self.view.show() 27 | 28 | def form_to_object(self): 29 | config = AppConfig() 30 | config.item_checked = self.view.chkAppOption.isChecked() 31 | return config 32 | 33 | def object_to_form(self, app_config: AppConfig): 34 | self.view.chkAppOption.setChecked(app_config.item_checked) 35 | -------------------------------------------------------------------------------- /app/controllers/main_controller.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | class MainWindowController: 5 | def __init__(self, parent_window, app): 6 | self.parent = parent_window 7 | self.initial_load = True 8 | self.app = app 9 | self.init_app() 10 | 11 | def init_app(self): 12 | self.app.init() 13 | self.app.init_logger() 14 | if self.app.geometry(): 15 | self.parent.restoreGeometry(self.app.geometry()) 16 | if self.app.window_state(): 17 | self.parent.restoreState(self.app.window_state()) 18 | 19 | def save_settings(self): 20 | self.app.save_window_state( 21 | geometry=self.parent.saveGeometry(), window_state=self.parent.saveState() 22 | ) 23 | logging.info("Saved settings for Main Window") 24 | 25 | def shutdown(self): 26 | self.save_settings() 27 | 28 | def after_window_loaded(self): 29 | if not self.initial_load: 30 | return 31 | 32 | self.initial_load = False 33 | self.on_first_load() 34 | 35 | def on_first_load(self): 36 | self.parent.scratch_pad_controller.init() 37 | -------------------------------------------------------------------------------- /app/controllers/scratch_pad_controller.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore 2 | from PyQt5.QtCore import QObject, QEvent 3 | 4 | 5 | class ScratchPadEvents(QObject): 6 | def __init__(self, parent, app): 7 | super().__init__(parent) 8 | self.parent = parent 9 | self.app = app 10 | 11 | def eventFilter(self, source: QObject, event: QEvent): 12 | if event.type() == QtCore.QEvent.FocusOut: 13 | self.save_scratch_pad() 14 | 15 | return super().eventFilter(source, event) 16 | 17 | def save_scratch_pad(self): 18 | scratch = self.parent.txt_scratch_pad.toPlainText() 19 | self.app.data.update_scratch_note(scratch) 20 | 21 | 22 | class ScratchPadController: 23 | def __init__(self, parent, app): 24 | self.parent = parent 25 | self.app = app 26 | self.events = ScratchPadEvents(self.parent, self.app) 27 | 28 | # installing event filter 29 | self.parent.txt_scratch_pad.installEventFilter(self.events) 30 | 31 | def init(self): 32 | scratch_note = self.app.data.get_scratch_note() 33 | self.parent.txt_scratch_pad.setPlainText(scratch_note) 34 | 35 | def save_scratch_pad(self): 36 | self.events.save_scratch_pad() 37 | -------------------------------------------------------------------------------- /app/controllers/shortcut_controller.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtGui import QKeySequence 2 | from PyQt5.QtWidgets import QShortcut 3 | 4 | 5 | class ShortcutController: 6 | def __init__(self, parent_window, app): 7 | self.parent = parent_window 8 | self.app = app 9 | 10 | def init_items(self): 11 | short = QShortcut(QKeySequence("Ctrl+S"), self.parent) 12 | short.activated.connect(self.parent.scratch_pad_controller.save_scratch_pad) 13 | config = QShortcut(QKeySequence("Ctrl+,"), self.parent) 14 | config.activated.connect(self.parent.config_controller.show_dialog) 15 | -------------------------------------------------------------------------------- /app/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/pyqt-boilerplate/a8d5ff702d200e18227afe2f3bd4035fff78c467/app/core/__init__.py -------------------------------------------------------------------------------- /app/core/str_utils.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | 4 | def str_to_bytes(input_str: str): 5 | return bytes(input_str, encoding="utf-8") 6 | 7 | 8 | def bytes_to_str(input_bytes: bytes): 9 | return input_bytes.decode(encoding="utf-8") 10 | 11 | 12 | def str_to_int(int_str, default=0): 13 | if int_str and type(int_str) is str: 14 | return int(int_str) 15 | 16 | return default 17 | 18 | 19 | def str_to_bool(bool_str): 20 | if type(bool_str) is bool: 21 | return bool_str 22 | return bool_str.lower() in ("yes", "true", "t", "1") 23 | 24 | 25 | def plain_to_b64_str(plain_str): 26 | if not plain_str: 27 | return "" 28 | 29 | return bytes_to_str(base64.standard_b64encode(str_to_bytes(plain_str))) 30 | 31 | 32 | def b64_to_plain_str(b64_str): 33 | if not b64_str: 34 | return "" 35 | 36 | return bytes_to_str(base64.standard_b64decode(str_to_bytes(b64_str))) 37 | -------------------------------------------------------------------------------- /app/data/__init__.py: -------------------------------------------------------------------------------- 1 | from .lite_data_store import LiteDataStore 2 | -------------------------------------------------------------------------------- /app/data/entities.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import attr 4 | import cattr 5 | 6 | APP_STATE_RECORD_TYPE = "app_state" 7 | 8 | 9 | @attr.s(auto_attribs=True) 10 | class AppState: 11 | record_type: str = APP_STATE_RECORD_TYPE 12 | scratch_note: str = "" 13 | 14 | @classmethod 15 | def from_json_str(cls, json_str): 16 | json_obj = json.loads(json_str) 17 | return cls.from_json(json_obj) 18 | 19 | @classmethod 20 | def from_json(cls, json_obj): 21 | if not json_obj: 22 | return cls() 23 | return cattr.structure(json_obj, cls) 24 | 25 | def to_json(self): 26 | return cattr.unstructure(self) 27 | 28 | def to_json_str(self): 29 | return json.dumps(self.to_json()) 30 | -------------------------------------------------------------------------------- /app/data/lite_data_store.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import dataset 4 | 5 | from app.core.str_utils import plain_to_b64_str, b64_to_plain_str 6 | from app.data.entities import AppState, APP_STATE_RECORD_TYPE 7 | from app.events.signals import AppEvents 8 | 9 | 10 | class LiteDataStore: 11 | app_events = AppEvents() 12 | 13 | def __init__(self, data_dir): 14 | self.data_dir = data_dir 15 | db_path = f"sqlite:///{self.data_dir}/app.db" 16 | self.db = dataset.connect(db_path) 17 | self._app_state = self.get_app_state() 18 | 19 | @property 20 | def app_state(self): 21 | return self._app_state 22 | 23 | def update_app_state_in_db(self): 24 | app_state_table = self.db[self.app_state.record_type] 25 | app_state_table.upsert( 26 | dict(name=self.app_state.record_type, object=self.app_state.to_json_str()), 27 | ["name"], 28 | ) 29 | 30 | def get_app_state(self): 31 | table = self.db[APP_STATE_RECORD_TYPE] 32 | app_state_record = table.find_one(name=APP_STATE_RECORD_TYPE) 33 | if not app_state_record: 34 | return AppState() 35 | 36 | return AppState.from_json_str(app_state_record["object"]) 37 | 38 | def update_scratch_note(self, scratch_note): 39 | logging.debug("Updating Scratch Pad: Characters: {}".format(len(scratch_note))) 40 | if not scratch_note: 41 | return 42 | 43 | self.app_state.scratch_note = plain_to_b64_str(scratch_note) 44 | self.update_app_state_in_db() 45 | 46 | def get_scratch_note(self): 47 | return b64_to_plain_str(self.app_state.scratch_note) 48 | -------------------------------------------------------------------------------- /app/events/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/pyqt-boilerplate/a8d5ff702d200e18227afe2f3bd4035fff78c467/app/events/__init__.py -------------------------------------------------------------------------------- /app/events/signals.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QObject, pyqtSignal 2 | 3 | 4 | class AppEvents(QObject): 5 | pass 6 | 7 | 8 | class AppCommands(QObject): 9 | pass 10 | -------------------------------------------------------------------------------- /app/generated/ConfigurationDialog_ui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'resources/views/ConfigurationDialog.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.3 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | 10 | from PyQt5 import QtCore, QtGui, QtWidgets 11 | 12 | 13 | class Ui_Configuration(object): 14 | def setupUi(self, Configuration): 15 | Configuration.setObjectName("Configuration") 16 | Configuration.setWindowModality(QtCore.Qt.WindowModal) 17 | Configuration.resize(486, 255) 18 | font = QtGui.QFont() 19 | font.setPointSize(10) 20 | Configuration.setFont(font) 21 | Configuration.setModal(True) 22 | self.tabWidget = QtWidgets.QTabWidget(Configuration) 23 | self.tabWidget.setGeometry(QtCore.QRect(10, 10, 451, 191)) 24 | font = QtGui.QFont() 25 | font.setPointSize(10) 26 | self.tabWidget.setFont(font) 27 | self.tabWidget.setObjectName("tabWidget") 28 | self.notes = QtWidgets.QWidget() 29 | self.notes.setObjectName("notes") 30 | self.chkAppOption = QtWidgets.QCheckBox(self.notes) 31 | self.chkAppOption.setGeometry(QtCore.QRect(20, 20, 201, 20)) 32 | self.chkAppOption.setObjectName("chkAppOption") 33 | self.tabWidget.addTab(self.notes, "") 34 | self.tab_4 = QtWidgets.QWidget() 35 | self.tab_4.setObjectName("tab_4") 36 | self.label_5 = QtWidgets.QLabel(self.tab_4) 37 | self.label_5.setGeometry(QtCore.QRect(10, 10, 421, 16)) 38 | self.label_5.setObjectName("label_5") 39 | self.tabWidget.addTab(self.tab_4, "") 40 | self.btn_save_configuration = QtWidgets.QPushButton(Configuration) 41 | self.btn_save_configuration.setGeometry(QtCore.QRect(360, 210, 113, 32)) 42 | self.btn_save_configuration.setObjectName("btn_save_configuration") 43 | self.btn_cancel_configuration = QtWidgets.QPushButton(Configuration) 44 | self.btn_cancel_configuration.setGeometry(QtCore.QRect(250, 210, 113, 32)) 45 | self.btn_cancel_configuration.setObjectName("btn_cancel_configuration") 46 | 47 | self.retranslateUi(Configuration) 48 | self.tabWidget.setCurrentIndex(0) 49 | QtCore.QMetaObject.connectSlotsByName(Configuration) 50 | 51 | def retranslateUi(self, Configuration): 52 | _translate = QtCore.QCoreApplication.translate 53 | Configuration.setWindowTitle(_translate("Configuration", "Settings")) 54 | self.chkAppOption.setText(_translate("Configuration", "App configuration option")) 55 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.notes), _translate("Configuration", "Notes")) 56 | self.label_5.setText(_translate("Configuration", "Icons by Icons8")) 57 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_4), _translate("Configuration", "Credit")) 58 | self.btn_save_configuration.setText(_translate("Configuration", "OK")) 59 | self.btn_cancel_configuration.setText(_translate("Configuration", "Cancel")) 60 | -------------------------------------------------------------------------------- /app/generated/MainWindow_ui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'resources/views/MainWindow.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.3 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | 10 | from PyQt5 import QtCore, QtGui, QtWidgets 11 | 12 | 13 | class Ui_MainWindow(object): 14 | def setupUi(self, MainWindow): 15 | MainWindow.setObjectName("MainWindow") 16 | MainWindow.resize(978, 723) 17 | self.centralwidget = QtWidgets.QWidget(MainWindow) 18 | self.centralwidget.setObjectName("centralwidget") 19 | self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget) 20 | self.horizontalLayout.setObjectName("horizontalLayout") 21 | self.txt_scratch_pad = QtWidgets.QPlainTextEdit(self.centralwidget) 22 | self.txt_scratch_pad.setPlainText("") 23 | self.txt_scratch_pad.setObjectName("txt_scratch_pad") 24 | self.horizontalLayout.addWidget(self.txt_scratch_pad) 25 | MainWindow.setCentralWidget(self.centralwidget) 26 | 27 | self.retranslateUi(MainWindow) 28 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 29 | 30 | def retranslateUi(self, MainWindow): 31 | _translate = QtCore.QCoreApplication.translate 32 | MainWindow.setWindowTitle(_translate("MainWindow", "OnePage")) 33 | self.txt_scratch_pad.setPlaceholderText(_translate("MainWindow", "Scratch Pad ...")) 34 | -------------------------------------------------------------------------------- /app/generated/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/pyqt-boilerplate/a8d5ff702d200e18227afe2f3bd4035fff78c467/app/generated/__init__.py -------------------------------------------------------------------------------- /app/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/pyqt-boilerplate/a8d5ff702d200e18227afe2f3bd4035fff78c467/app/settings/__init__.py -------------------------------------------------------------------------------- /app/settings/app_config.py: -------------------------------------------------------------------------------- 1 | from typing import ClassVar 2 | 3 | import attr 4 | 5 | 6 | @attr.s(auto_attribs=True) 7 | class AppConfig: 8 | ITEM_CHECK: ClassVar[str] = "sampleConfigItem" 9 | _item_check: bool = True 10 | 11 | @property 12 | def item_checked(self): 13 | return self._item_check 14 | 15 | @item_checked.setter 16 | def item_checked(self, val): 17 | self._item_check = val 18 | -------------------------------------------------------------------------------- /app/settings/app_settings.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import logging.handlers 3 | from pathlib import Path 4 | from typing import Any, Union 5 | 6 | from PyQt5.QtCore import QSettings, QStandardPaths 7 | from PyQt5.QtWidgets import qApp 8 | 9 | from app.data import LiteDataStore 10 | from app.settings.app_config import AppConfig 11 | 12 | 13 | class AppSettings: 14 | def __init__(self): 15 | self.settings: QSettings = None 16 | self.app_name: str = None 17 | self.app_dir: Union[Path, Any] = None 18 | self.docs_location: Path = Path( 19 | QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation) 20 | ) 21 | self.data: LiteDataStore = None 22 | 23 | def init(self): 24 | self.app_name = qApp.applicationName().lower() 25 | self.app_dir = Path( 26 | QStandardPaths.writableLocation(QStandardPaths.AppConfigLocation) 27 | ) 28 | self.app_dir.mkdir(exist_ok=True) 29 | settings_file = f"{self.app_name}.ini" 30 | self.settings = QSettings( 31 | self.app_dir.joinpath(settings_file).as_posix(), QSettings.IniFormat 32 | ) 33 | self.settings.sync() 34 | self.data = LiteDataStore(self.app_dir) 35 | 36 | def init_logger(self): 37 | log_file = f"{self.app_name}.log" 38 | handlers = [ 39 | logging.handlers.RotatingFileHandler( 40 | self.app_dir.joinpath(log_file), maxBytes=1000000, backupCount=1 41 | ), 42 | logging.StreamHandler(), 43 | ] 44 | 45 | logging.basicConfig( 46 | handlers=handlers, 47 | format="%(asctime)s - %(filename)s:%(lineno)d - %(message)s", 48 | datefmt="%Y-%m-%d %H:%M:%S", 49 | level=logging.DEBUG, 50 | ) 51 | logging.captureWarnings(capture=True) 52 | 53 | def save_window_state(self, geometry, window_state): 54 | self.settings.setValue("geometry", geometry) 55 | self.settings.setValue("windowState", window_state) 56 | self.settings.sync() 57 | 58 | def save_configuration(self, app_config: AppConfig): 59 | self.settings.setValue(AppConfig.ITEM_CHECK, app_config.item_checked) 60 | self.settings.sync() 61 | 62 | def load_configuration(self): 63 | app_config = AppConfig() 64 | app_config.item_checked = self.settings.value( 65 | AppConfig.ITEM_CHECK, 66 | app_config.item_checked, 67 | ) 68 | return app_config 69 | 70 | def geometry(self): 71 | return self.settings.value("geometry", None) 72 | 73 | def window_state(self): 74 | return self.settings.value("windowState", None) 75 | 76 | 77 | app = AppSettings() 78 | -------------------------------------------------------------------------------- /app/themes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/pyqt-boilerplate/a8d5ff702d200e18227afe2f3bd4035fff78c467/app/themes/__init__.py -------------------------------------------------------------------------------- /app/themes/theme_loader.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from PyQt5.QtWidgets import QProxyStyle, qApp 4 | from PyQt5.QtCore import QFile, QFileInfo, QTextStream 5 | 6 | 7 | def styles_from_file(filename): 8 | if QFileInfo(filename).exists(): 9 | qss_file = QFile(filename) 10 | qss_file.open(QFile.ReadOnly | QFile.Text) 11 | content = QTextStream(qss_file).readAll() 12 | return content 13 | else: 14 | return None 15 | 16 | 17 | class ThemeLoader(QProxyStyle): 18 | def __init__(self): 19 | super().__init__() 20 | 21 | def load_stylesheet(self, theme_mode): 22 | filename = ":/themes/{}.qss".format(theme_mode) 23 | 24 | styles = styles_from_file(filename) 25 | qApp.setStyleSheet(styles) if styles else self.log_error(filename) 26 | 27 | def log_error(self, styles_file): 28 | logging.error(f"Unable to read file from {styles_file}") 29 | -------------------------------------------------------------------------------- /app/themes/theme_provider.py: -------------------------------------------------------------------------------- 1 | import darkdetect 2 | from PyQt5.QtGui import QFont, QIcon, QFontDatabase 3 | 4 | from app.themes.theme_loader import ThemeLoader, styles_from_file 5 | 6 | __pyg_styles = None 7 | 8 | 9 | def is_dark(): 10 | return darkdetect.isDark() 11 | 12 | 13 | def configure_theme(app): 14 | app.setWindowIcon(QIcon(":/icons/app.svg")) 15 | 16 | app.setStyle(ThemeLoader()) 17 | theme_mode = "dark" if is_dark() else "light" 18 | app.style().load_stylesheet(theme_mode) 19 | 20 | font_db = QFontDatabase() 21 | font_db.addApplicationFont(":/fonts/JetBrainsMono-Regular.ttf") 22 | 23 | current_font: QFont = QFont("JetBrains Mono") 24 | current_font.setPointSize(14) 25 | app.setFont(current_font) 26 | 27 | 28 | def pyg_styles(): 29 | if __pyg_styles: 30 | return __pyg_styles 31 | else: 32 | pyg_theme = "dark" if is_dark() else "light" 33 | return styles_from_file(":/themes/pyg-{}.css".format(pyg_theme)) 34 | -------------------------------------------------------------------------------- /app/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/pyqt-boilerplate/a8d5ff702d200e18227afe2f3bd4035fff78c467/app/views/__init__.py -------------------------------------------------------------------------------- /app/views/configuration_dialog.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog 2 | 3 | from app.generated.ConfigurationDialog_ui import Ui_Configuration 4 | 5 | 6 | class ConfigurationDialog(QDialog, Ui_Configuration): 7 | def __init__(self, parent=None): 8 | super(ConfigurationDialog, self).__init__(parent) 9 | self.setupUi(self) 10 | -------------------------------------------------------------------------------- /app/views/main_window.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import traceback 3 | 4 | import sys 5 | from PyQt5.QtGui import QCloseEvent 6 | from PyQt5.QtWidgets import QMainWindow, qApp 7 | 8 | from app.controllers import ( 9 | MainWindowController, 10 | ConfigController, 11 | ShortcutController, 12 | ScratchPadController, 13 | ) 14 | from app.generated.MainWindow_ui import Ui_MainWindow 15 | from app.settings.app_settings import app 16 | 17 | 18 | class MainWindow(QMainWindow, Ui_MainWindow): 19 | """Main Window.""" 20 | 21 | def __init__(self): 22 | QMainWindow.__init__(self) 23 | self.setupUi(self) 24 | 25 | # Initialise controllers 26 | self.main_controller = MainWindowController(self, app) 27 | self.config_controller = ConfigController(self, app) 28 | self.shortcut_controller = ShortcutController(self, app) 29 | self.scratch_pad_controller = ScratchPadController(self, app) 30 | 31 | # Initialise components 32 | self.shortcut_controller.init_items() 33 | 34 | # Initialise Sub-Systems 35 | sys.excepthook = MainWindow.log_uncaught_exceptions 36 | 37 | # Main Window events 38 | def resizeEvent(self, event): 39 | self.main_controller.after_window_loaded() 40 | 41 | @staticmethod 42 | def log_uncaught_exceptions(cls, exc, tb) -> None: 43 | logging.critical("".join(traceback.format_tb(tb))) 44 | logging.critical("{0}: {1}".format(cls, exc)) 45 | 46 | def closeEvent(self, event: QCloseEvent): 47 | logging.info("Received close event") 48 | event.accept() 49 | self.main_controller.shutdown() 50 | try: 51 | qApp.exit(0) 52 | except: 53 | pass 54 | 55 | def replace_widget(self, selected_widget): 56 | self.clear_layout(self.toolWidgetLayout) 57 | self.toolWidgetLayout.addWidget(selected_widget) 58 | 59 | def clear_layout(self, layout): 60 | for i in reversed(range(layout.count())): 61 | widget_item = layout.takeAt(i) 62 | if widget_item: 63 | widget_item.widget().deleteLater() 64 | -------------------------------------------------------------------------------- /app/widgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/pyqt-boilerplate/a8d5ff702d200e18227afe2f3bd4035fff78c467/app/widgets/__init__.py -------------------------------------------------------------------------------- /bin/app: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from app.__main__ import main 4 | 5 | 6 | main() 7 | -------------------------------------------------------------------------------- /docs/images/pyqt-boilerplate-screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/pyqt-boilerplate/a8d5ff702d200e18227afe2f3bd4035fff78c467/docs/images/pyqt-boilerplate-screenshots.png -------------------------------------------------------------------------------- /mk-icns.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | inkscape="/Applications/Inkscape.app/Contents/Resources/bin/inkscape" 5 | svg_file="$PWD/$1" 6 | output_name=$2 7 | 8 | set -e 9 | test -d "$output_name.iconset" && rm -R "$output_name.iconset" 10 | mkdir "$output_name.iconset" 11 | $inkscape -z -e "$PWD/$output_name.iconset/icon_16x16.png" -w 16 -h 16 "$svg_file" 12 | $inkscape -z -e "$PWD/$output_name.iconset/icon_16x16@2x.png" -w 32 -h 32 "$svg_file" 13 | $inkscape -z -e "$PWD/$output_name.iconset/icon_32x32.png" -w 32 -h 32 "$svg_file" 14 | $inkscape -z -e "$PWD/$output_name.iconset/icon_32x32@2x.png" -w 64 -h 64 "$svg_file" 15 | $inkscape -z -e "$PWD/$output_name.iconset/icon_128x128.png" -w 128 -h 128 "$svg_file" 16 | $inkscape -z -e "$PWD/$output_name.iconset/icon_128x128@2x.png" -w 256 -h 256 "$svg_file" 17 | $inkscape -z -e "$PWD/$output_name.iconset/icon_256x256.png" -w 256 -h 256 "$svg_file" 18 | $inkscape -z -e "$PWD/$output_name.iconset/icon_256x256@2x.png" -w 512 -h 512 "$svg_file" 19 | $inkscape -z -e "$PWD/$output_name.iconset/icon_512x512.png" -w 512 -h 512 "$svg_file" 20 | $inkscape -z -e "$PWD/$output_name.iconset/icon_512x512@2x.png" -w 1024 -h 1024 "$svg_file" 21 | iconutil -c icns "$output_name.iconset" 22 | 23 | convert $(ls "${output_name}".iconset/icon_*.png) resources/icons/app.ico 24 | 25 | rm -R "$output_name.iconset" 26 | mv "$output_name".icns resources/icons/ 27 | 28 | $inkscape -z -e "$PWD/resources/images/${output_name}.png" -w 512 -h 512 "$svg_file" 29 | $inkscape -z -e "$PWD/resources/images/${output_name}-logo.png" -w 100 -h 80 "$svg_file" -------------------------------------------------------------------------------- /requirements/base.txt: -------------------------------------------------------------------------------- 1 | PyQt5==5.12.3 2 | darkdetect 3 | attrs 4 | cattrs 5 | dataset 6 | -------------------------------------------------------------------------------- /requirements/build.txt: -------------------------------------------------------------------------------- 1 | -r base.txt 2 | pyqt-distutils 3 | PyInstaller>=3.6 4 | -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | -r base.txt 2 | flake8 3 | black -------------------------------------------------------------------------------- /resources/fonts/JetBrainsMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/pyqt-boilerplate/a8d5ff702d200e18227afe2f3bd4035fff78c467/resources/fonts/JetBrainsMono-Regular.ttf -------------------------------------------------------------------------------- /resources/icons/app.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/pyqt-boilerplate/a8d5ff702d200e18227afe2f3bd4035fff78c467/resources/icons/app.icns -------------------------------------------------------------------------------- /resources/icons/app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/pyqt-boilerplate/a8d5ff702d200e18227afe2f3bd4035fff78c467/resources/icons/app.ico -------------------------------------------------------------------------------- /resources/icons/app.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/images/app-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/pyqt-boilerplate/a8d5ff702d200e18227afe2f3bd4035fff78c467/resources/images/app-logo.png -------------------------------------------------------------------------------- /resources/images/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/pyqt-boilerplate/a8d5ff702d200e18227afe2f3bd4035fff78c467/resources/images/app.png -------------------------------------------------------------------------------- /resources/images/configure-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/pyqt-boilerplate/a8d5ff702d200e18227afe2f3bd4035fff78c467/resources/images/configure-48.png -------------------------------------------------------------------------------- /resources/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | fonts/JetBrainsMono-Regular.ttf 4 | themes/light.qss 5 | themes/dark.qss 6 | themes/pyg-dark.css 7 | themes/pyg-light.css 8 | icons/app.icns 9 | icons/app.ico 10 | icons/app.svg 11 | images/configure-48.png 12 | 13 | 14 | -------------------------------------------------------------------------------- /resources/themes/dark.qss: -------------------------------------------------------------------------------- 1 | /* Styles for Dark theme 2 | 3 | QMainWindow { 4 | background-color: #000000; 5 | } 6 | */ -------------------------------------------------------------------------------- /resources/themes/light.qss: -------------------------------------------------------------------------------- 1 | /* Styles for Light theme 2 | 3 | QMainWindow { 4 | background-color: #000000; 5 | } 6 | */ -------------------------------------------------------------------------------- /resources/themes/pyg-dark.css: -------------------------------------------------------------------------------- 1 | a { color: #2474AB;} 2 | 3 | /* Dracula Theme v1.2.5 4 | * 5 | * https://github.com/zenorocha/dracula-theme 6 | * 7 | * Copyright 2016, All rights reserved 8 | * 9 | * Code licensed under the MIT license 10 | * http://zenorocha.mit-license.org 11 | * 12 | * @author Rob G 13 | * @author Chris Bracco 14 | * @author Zeno Rocha 15 | */ 16 | 17 | .highlight .hll { background-color: #f1fa8c } 18 | .highlight { background: #282a36; color: #f8f8f2 } 19 | .highlight .c { color: #6272a4 } /* Comment */ 20 | .highlight .err { color: #f8f8f2 } /* Error */ 21 | .highlight .g { color: #f8f8f2 } /* Generic */ 22 | .highlight .k { color: #ff79c6 } /* Keyword */ 23 | .highlight .l { color: #f8f8f2 } /* Literal */ 24 | .highlight .n { color: #f8f8f2 } /* Name */ 25 | .highlight .o { color: #ff79c6 } /* Operator */ 26 | .highlight .x { color: #f8f8f2 } /* Other */ 27 | .highlight .p { color: #f8f8f2 } /* Punctuation */ 28 | .highlight .ch { color: #6272a4 } /* Comment.Hashbang */ 29 | .highlight .cm { color: #6272a4 } /* Comment.Multiline */ 30 | .highlight .cp { color: #ff79c6 } /* Comment.Preproc */ 31 | .highlight .cpf { color: #6272a4 } /* Comment.PreprocFile */ 32 | .highlight .c1 { color: #6272a4 } /* Comment.Single */ 33 | .highlight .cs { color: #6272a4 } /* Comment.Special */ 34 | .highlight .gd { color: #8b080b } /* Generic.Deleted */ 35 | .highlight .ge { color: #f8f8f2; text-decoration: underline } /* Generic.Emph */ 36 | .highlight .gr { color: #f8f8f2 } /* Generic.Error */ 37 | .highlight .gh { color: #f8f8f2; font-weight: bold } /* Generic.Heading */ 38 | .highlight .gi { color: #f8f8f2; font-weight: bold } /* Generic.Inserted */ 39 | .highlight .go { color: #44475a } /* Generic.Output */ 40 | .highlight .gp { color: #f8f8f2 } /* Generic.Prompt */ 41 | .highlight .gs { color: #f8f8f2 } /* Generic.Strong */ 42 | .highlight .gu { color: #f8f8f2; font-weight: bold } /* Generic.Subheading */ 43 | .highlight .gt { color: #f8f8f2 } /* Generic.Traceback */ 44 | .highlight .kc { color: #ff79c6 } /* Keyword.Constant */ 45 | .highlight .kd { color: #8be9fd; font-style: italic } /* Keyword.Declaration */ 46 | .highlight .kn { color: #ff79c6 } /* Keyword.Namespace */ 47 | .highlight .kp { color: #ff79c6 } /* Keyword.Pseudo */ 48 | .highlight .kr { color: #ff79c6 } /* Keyword.Reserved */ 49 | .highlight .kt { color: #8be9fd } /* Keyword.Type */ 50 | .highlight .ld { color: #f8f8f2 } /* Literal.Date */ 51 | .highlight .m { color: #bd93f9 } /* Literal.Number */ 52 | .highlight .s { color: #f1fa8c } /* Literal.String */ 53 | .highlight .na { color: #50fa7b } /* Name.Attribute */ 54 | .highlight .nb { color: #8be9fd; font-style: italic } /* Name.Builtin */ 55 | .highlight .nc { color: #50fa7b } /* Name.Class */ 56 | .highlight .no { color: #f8f8f2 } /* Name.Constant */ 57 | .highlight .nd { color: #f8f8f2 } /* Name.Decorator */ 58 | .highlight .ni { color: #f8f8f2 } /* Name.Entity */ 59 | .highlight .ne { color: #f8f8f2 } /* Name.Exception */ 60 | .highlight .nf { color: #50fa7b } /* Name.Function */ 61 | .highlight .nl { color: #8be9fd; font-style: italic } /* Name.Label */ 62 | .highlight .nn { color: #f8f8f2 } /* Name.Namespace */ 63 | .highlight .nx { color: #f8f8f2 } /* Name.Other */ 64 | .highlight .py { color: #f8f8f2 } /* Name.Property */ 65 | .highlight .nt { color: #ff79c6 } /* Name.Tag */ 66 | .highlight .nv { color: #8be9fd; font-style: italic } /* Name.Variable */ 67 | .highlight .ow { color: #ff79c6 } /* Operator.Word */ 68 | .highlight .w { color: #f8f8f2 } /* Text.Whitespace */ 69 | .highlight .mb { color: #bd93f9 } /* Literal.Number.Bin */ 70 | .highlight .mf { color: #bd93f9 } /* Literal.Number.Float */ 71 | .highlight .mh { color: #bd93f9 } /* Literal.Number.Hex */ 72 | .highlight .mi { color: #bd93f9 } /* Literal.Number.Integer */ 73 | .highlight .mo { color: #bd93f9 } /* Literal.Number.Oct */ 74 | .highlight .sa { color: #f1fa8c } /* Literal.String.Affix */ 75 | .highlight .sb { color: #f1fa8c } /* Literal.String.Backtick */ 76 | .highlight .sc { color: #f1fa8c } /* Literal.String.Char */ 77 | .highlight .dl { color: #f1fa8c } /* Literal.String.Delimiter */ 78 | .highlight .sd { color: #f1fa8c } /* Literal.String.Doc */ 79 | .highlight .s2 { color: #f1fa8c } /* Literal.String.Double */ 80 | .highlight .se { color: #f1fa8c } /* Literal.String.Escape */ 81 | .highlight .sh { color: #f1fa8c } /* Literal.String.Heredoc */ 82 | .highlight .si { color: #f1fa8c } /* Literal.String.Interpol */ 83 | .highlight .sx { color: #f1fa8c } /* Literal.String.Other */ 84 | .highlight .sr { color: #f1fa8c } /* Literal.String.Regex */ 85 | .highlight .s1 { color: #f1fa8c } /* Literal.String.Single */ 86 | .highlight .ss { color: #f1fa8c } /* Literal.String.Symbol */ 87 | .highlight .bp { color: #f8f8f2; font-style: italic } /* Name.Builtin.Pseudo */ 88 | .highlight .fm { color: #50fa7b } /* Name.Function.Magic */ 89 | .highlight .vc { color: #8be9fd; font-style: italic } /* Name.Variable.Class */ 90 | .highlight .vg { color: #8be9fd; font-style: italic } /* Name.Variable.Global */ 91 | .highlight .vi { color: #8be9fd; font-style: italic } /* Name.Variable.Instance */ 92 | .highlight .vm { color: #8be9fd; font-style: italic } /* Name.Variable.Magic */ 93 | .highlight .il { color: #bd93f9 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /resources/themes/pyg-light.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight .c { color: #408080; font-style: italic } /* Comment */ 3 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 4 | .highlight .k { color: #008000; font-weight: bold } /* Keyword */ 5 | .highlight .o { color: #666666 } /* Operator */ 6 | .highlight .ch { color: #408080; font-style: italic } /* Comment.Hashbang */ 7 | .highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 8 | .highlight .cp { color: #BC7A00 } /* Comment.Preproc */ 9 | .highlight .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */ 10 | .highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */ 11 | .highlight .cs { color: #408080; font-style: italic } /* Comment.Special */ 12 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 13 | .highlight .ge { font-style: italic } /* Generic.Emph */ 14 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 15 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 16 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 17 | .highlight .go { color: #888888 } /* Generic.Output */ 18 | .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 19 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 20 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 21 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 22 | .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 23 | .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 24 | .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 25 | .highlight .kp { color: #008000 } /* Keyword.Pseudo */ 26 | .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 27 | .highlight .kt { color: #B00040 } /* Keyword.Type */ 28 | .highlight .m { color: #666666 } /* Literal.Number */ 29 | .highlight .s { color: #BA2121 } /* Literal.String */ 30 | .highlight .na { color: #7D9029 } /* Name.Attribute */ 31 | .highlight .nb { color: #008000 } /* Name.Builtin */ 32 | .highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 33 | .highlight .no { color: #880000 } /* Name.Constant */ 34 | .highlight .nd { color: #AA22FF } /* Name.Decorator */ 35 | .highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ 36 | .highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 37 | .highlight .nf { color: #0000FF } /* Name.Function */ 38 | .highlight .nl { color: #A0A000 } /* Name.Label */ 39 | .highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 40 | .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ 41 | .highlight .nv { color: #19177C } /* Name.Variable */ 42 | .highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 43 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 44 | .highlight .mb { color: #666666 } /* Literal.Number.Bin */ 45 | .highlight .mf { color: #666666 } /* Literal.Number.Float */ 46 | .highlight .mh { color: #666666 } /* Literal.Number.Hex */ 47 | .highlight .mi { color: #666666 } /* Literal.Number.Integer */ 48 | .highlight .mo { color: #666666 } /* Literal.Number.Oct */ 49 | .highlight .sa { color: #BA2121 } /* Literal.String.Affix */ 50 | .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ 51 | .highlight .sc { color: #BA2121 } /* Literal.String.Char */ 52 | .highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ 53 | .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 54 | .highlight .s2 { color: #BA2121 } /* Literal.String.Double */ 55 | .highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 56 | .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ 57 | .highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 58 | .highlight .sx { color: #008000 } /* Literal.String.Other */ 59 | .highlight .sr { color: #BB6688 } /* Literal.String.Regex */ 60 | .highlight .s1 { color: #BA2121 } /* Literal.String.Single */ 61 | .highlight .ss { color: #19177C } /* Literal.String.Symbol */ 62 | .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ 63 | .highlight .fm { color: #0000FF } /* Name.Function.Magic */ 64 | .highlight .vc { color: #19177C } /* Name.Variable.Class */ 65 | .highlight .vg { color: #19177C } /* Name.Variable.Global */ 66 | .highlight .vi { color: #19177C } /* Name.Variable.Instance */ 67 | .highlight .vm { color: #19177C } /* Name.Variable.Magic */ 68 | .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /resources/views/ConfigurationDialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Configuration 4 | 5 | 6 | Qt::WindowModal 7 | 8 | 9 | 10 | 0 11 | 0 12 | 486 13 | 255 14 | 15 | 16 | 17 | 18 | 10 19 | 20 | 21 | 22 | Settings 23 | 24 | 25 | true 26 | 27 | 28 | 29 | 30 | 10 31 | 10 32 | 451 33 | 191 34 | 35 | 36 | 37 | 38 | 10 39 | 40 | 41 | 42 | 0 43 | 44 | 45 | 46 | Notes 47 | 48 | 49 | 50 | 51 | 20 52 | 20 53 | 201 54 | 20 55 | 56 | 57 | 58 | App configuration option 59 | 60 | 61 | 62 | 63 | 64 | Credit 65 | 66 | 67 | 68 | 69 | 10 70 | 10 71 | 421 72 | 16 73 | 74 | 75 | 76 | Icons by <a href="https://icons8.com">Icons8</a> 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 360 85 | 210 86 | 113 87 | 32 88 | 89 | 90 | 91 | OK 92 | 93 | 94 | 95 | 96 | 97 | 250 98 | 210 99 | 113 100 | 32 101 | 102 | 103 | 104 | Cancel 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /resources/views/MainWindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 978 10 | 723 11 | 12 | 13 | 14 | OnePage 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Scratch Pad ... 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | 5 | from setuptools import setup, find_packages 6 | 7 | cmdclass = {} 8 | 9 | with open('app/__init__.py') as f: 10 | _version = re.search(r'__version__\s+=\s+\"(.*)\"', f.read()).group(1) 11 | 12 | setup(name='OnePage', 13 | version=_version, 14 | packages=find_packages(), 15 | description='Simple ScratchPad', 16 | author='Namuan', 17 | author_email='info@deskriders.dev', 18 | license='MIT', 19 | url='https://deskriders.dev', 20 | entry_points={ 21 | 'gui_scripts': ['app=app.__main__:__main__.py'], 22 | }, 23 | cmdclass=cmdclass) 24 | --------------------------------------------------------------------------------