diff --git a/.gitignore b/.gitignore index 8395262..946edea 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ var/ *.egg-info/ .installed.cfg *.egg +nsis/output/ # Installer logs pip-log.txt diff --git a/LICENSE b/LICENSE index e72bfdd..a42f0f9 100644 --- a/LICENSE +++ b/LICENSE @@ -132,7 +132,7 @@ implementation is available to the public in source code form. A produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable +the source code needed to preview, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free diff --git a/README.md b/README.md index b63b1d0..6778af9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- skippy + skippy

@@ -8,10 +8,15 @@ +

- + +

+ +

+

@@ -22,7 +27,7 @@ Skippy - Simple. Comfortable. Powerful. notepad for www.scpwiki.com Installation

-If you have Python you can install Skippy using pip: +If you have Python 3.7 or later you can install Skippy using pip: ``` $ python -m pip install skippy-pad @@ -52,7 +57,7 @@ Features * Support all wikidot sites * Autoupdating system * Multi language interface -* Advanced autocomplete, suggestion and syntax checking engine to the Wikidot syntax +* Advanced autocomplete, suggestion and syntax engine to the Wikidot syntax * and many other features!

@@ -92,9 +97,19 @@ Author Donating

+* Buy me a coffee: + +Buy Me A Coffee + * Cryptocurrencies: * Binance Smart Chain (BSC) - 0x581cc7acbe921b1fa9fccbbfe008c86f68bc097d (BTC, ETH, DOGE and etc.) +## Sponsors + +Skippy would like to thank the following organizations for graciously permitting us to use their services for free: + +* [JetBrains](https://www.jetbrains.com/pycharm/) ([info](https://www.jetbrains.com/community/opensource/#support)) +

License

diff --git a/img/banner.png b/img/banner.png new file mode 100644 index 0000000..17fb6b3 Binary files /dev/null and b/img/banner.png differ diff --git a/img/skippy.png b/img/skippy.png deleted file mode 100644 index f4f8f2a..0000000 Binary files a/img/skippy.png and /dev/null differ diff --git a/img/skippy.svg b/img/skippy.svg new file mode 100644 index 0000000..73a23bf --- /dev/null +++ b/img/skippy.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/nsis/compile.bat b/nsis/compile.bat index 148624e..4b7176d 100644 --- a/nsis/compile.bat +++ b/nsis/compile.bat @@ -1,3 +1,5 @@ +@ECHO OFF + python -m venv venv venv\Scripts\python -m pip install ../../pyscp diff --git a/requirements.txt b/requirements.txt index b768641..506c840 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,8 @@ pypresence==4.2.0 requests==2.25.1 prettytable==2.0.0 -pyscp>=1.1.19 -pyftml>=0.1.2 +pyscp==1.1.19 +pyftml==0.1.2 PyQt5==5.15.5 -PyQtWebEngine==5.15.4 +PyQtWebEngine==5.15.5 +toml==0.10.2 diff --git a/setup.py b/setup.py index 557470a..fd2e21a 100644 --- a/setup.py +++ b/setup.py @@ -55,13 +55,15 @@ def _get_constant(name: str) -> Any: url="https://github.com/skippy-dev/skippy/", author=_get_constant("author"), author_email=_get_constant("email"), - python_requires=">=3.9", + python_requires=">=3.7", license=_get_constant("license"), classifiers=[ "Development Status :: 3 - Alpha", "Intended Audience :: Other Audience", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Operating System :: OS Independent", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", ], keywords=["scp", "wikidot", "pyscp", "skippy"], @@ -72,7 +74,7 @@ def _get_constant(name: str) -> Any: "skippy": [ "resources/*", "resources/*/*", - "lang/*.json", + "lang/*.toml" ] }, entry_points={ diff --git a/skippy/__init__.py b/skippy/__init__.py index 92d86ab..163542f 100644 --- a/skippy/__init__.py +++ b/skippy/__init__.py @@ -1,5 +1,5 @@ __author__ = "MrNereof" __license__ = "GPLv3" __email__ = "mrnereof@gmail.com" -__version__ = "1.2.3" +__version__ = "1.2.4" __description__ = "Simple. Comfortable. Powerful. notepad for www.scpwiki.com." diff --git a/skippy/api/types.py b/skippy/api/types.py index 625f9a4..4a5b506 100644 --- a/skippy/api/types.py +++ b/skippy/api/types.py @@ -1,16 +1,22 @@ -from typing import NamedTuple, TypedDict, Callable, Optional, Tuple, Dict, List, Any +from typing import MutableMapping, NamedTuple, TypedDict, Callable, Optional, Tuple, Dict, List, Any class Language(NamedTuple): code: str - dictionary: Dict[str, str] + dictionary: MutableMapping[str, Any] + + +class Field(NamedTuple): + tag: str + name: str + description: str class Action(NamedTuple): label: str statusTip: str func: Callable[..., Any] - img: str + img: Optional[str] = None class InlineSyntax(NamedTuple): @@ -33,4 +39,4 @@ class PageData(TypedDict): link: Optional[Tuple[str, str]] -__all__ = ["Language", "Action", "InlineSyntax", "BlockSyntax", "PageData"] +__all__ = ["Language", "Field", "Action", "InlineSyntax", "BlockSyntax", "PageData"] diff --git a/skippy/app.py b/skippy/app.py index f40e60f..d91ba87 100644 --- a/skippy/app.py +++ b/skippy/app.py @@ -91,8 +91,11 @@ def plugins(self): print(table) - def start_ui(self): + def start_ui(self) -> int: """Initialize everything and run the application. + + Returns: + int: Exit code """ logger.log.setLevel(logger.LOG_LEVELS[self.args.logging_level]) @@ -108,10 +111,10 @@ def start_ui(self): logger.log.info("Start plugins...") pluginLoader.start_plugins() - logger.log.info("Initializing Discord RPC..") + logger.log.info("Initializing Discord RPC...") rpc = discord_rpc.DiscordRPC() - logger.log.info("Connect Discord RPC..") + logger.log.info("Connect Discord RPC...") rpc.connect() logger.log.info("Initializing application...") @@ -120,7 +123,7 @@ def start_ui(self): logger.log.info("Stop plugins...") pluginLoader.stop_plugins() - logger.log.info("Stop Discord RPC..") + logger.log.info("Stop Discord RPC...") rpc.close() return exit_code diff --git a/skippy/config.py b/skippy/config.py index 59f4c42..b9fa786 100644 --- a/skippy/config.py +++ b/skippy/config.py @@ -35,3 +35,7 @@ PROPERTY_FOLDER = os.path.join(APPDATA_FOLDER, "property") LOGS_FOLDER = os.path.join(APPDATA_FOLDER, "logs") + + PLUGINS_SETTINGS_FOLDER = os.path.join(APPDATA_FOLDER, "plugins") + +PLUGINS_SETTINGS_FOLDER = os.path.join(PLUGINS_FOLDER, "settings") diff --git a/skippy/core/elements.py b/skippy/core/elements.py new file mode 100644 index 0000000..0bd1da5 --- /dev/null +++ b/skippy/core/elements.py @@ -0,0 +1,202 @@ +"""Elements core module + +Attributes: + elements (List[AbstractElement]): List of elements +""" +from skippy.api import Field + +from abc import ABCMeta, abstractmethod +from typing import Dict, List + + +class AbstractElement(metaclass=ABCMeta): + + """Abstract element class + + Attributes: + required_fields (List[Field]): List of element fields + """ + + __alias__: str + __description__: str + + base: str + + def __init__(self): + """Initializing element + """ + self.required_fields: List[Field] = [] + self._init_fields() + + def check_args(self, args: Dict[str, str]): + """Check required arguments + + Args: + args (Dict[str, str]): Arguments for fields + + Raises: + TypeError: Don't received required argument + """ + for field in self.required_fields: + if field.tag not in args: + raise TypeError( + f"{self.__class__.__name__}.preview() don't received required argument: {field.tag}" + ) + + @abstractmethod + def _init_fields(self): + """Abstract init fields method + """ + pass + + def add_field(self, tag: str, name: str = "", description: str = ""): + """Add field to element + + Args: + tag (str): Field tag + name (str, optional): Field name + description (str, optional): Field description + """ + self.required_fields.append(Field(tag, name, description)) + + def prepare_args(self, args: Dict[str, str]) -> Dict[str, str]: + """Prepare arguments + + Args: + args (Dict[str, str]): Arguments for fields + + Returns: + Dict[str, str]: Prepared arguments + """ + return args + + def preview(self, args: Dict[str, str]) -> str: + """Preview element using arguments + + Args: + args (Dict[str, str]): Arguments for fields + + Returns: + str: Description + """ + self.check_args(args) + args = self.prepare_args(args) + + data = self.base + for name in args: + data = data.replace(f"<<{name}>>", args[name]) + return data + + +class AbstractComponent(AbstractElement): + + """Abstract included component + """ + + component: str + base: str = "[[include <> <>]]" + + multiline: bool = True + + def prepare_args(self, args: Dict[str, str]) -> Dict[str, str]: + """Prepare arguments + + Args: + args (Dict[str, str]): Arguments for fields + + Returns: + Dict[str, str]: Prepared arguments + """ + next_line = "\n" if self.multiline else " " + return { + "component": self.component, + "args": f" |{next_line}".join([f"{arg}={args[arg]}" for arg in args if args[arg]]), + } + + +class BaseImageBlock(AbstractComponent): + + """Base image block + """ + + component: str = "component:image-block" + + multiline: bool = False + + align: str + + def _init_fields(self): + """Initializing fields + """ + self.add_field("name", "ELEMENTS.BASE_IMAGE.NAME.NAME", "ELEMENTS.BASE_IMAGE.NAME.DESC") + self.add_field("caption", "ELEMENTS.BASE_IMAGE.CAPTION.NAME", "ELEMENTS.BASE_IMAGE.CAPTION.DESC") + self.add_field("width", "ELEMENTS.BASE_IMAGE.WIDTH.NAME", "ELEMENTS.BASE_IMAGE.WIDTH.DESC") + self.add_field("link", "ELEMENTS.BASE_IMAGE.LINK.NAME", "ELEMENTS.BASE_IMAGE.LINK.DESC") + + def prepare_args(self, args: Dict[str, str]) -> Dict[str, str]: + """Prepare arguments + + Args: + args (Dict[str, str]): Arguments for fields + + Returns: + Dict[str, str]: Prepared arguments + """ + args["align"] = self.align + return super(BaseImageBlock, self).prepare_args(args) + + +class RightImageBlock(BaseImageBlock): + + """Right image block + """ + + __alias__: str = "ELEMENTS.RIGHT_IMAGE_BLOCK.ALIAS" + __description__: str = "ELEMENTS.RIGHT_IMAGE_BLOCK.DESC" + + align: str = "" + + +class LeftImageBlock(BaseImageBlock): + + """Left image block + """ + + __alias__: str = "ELEMENTS.LEFT_IMAGE_BLOCK.ALIAS" + __description__: str = "ELEMENTS.LEFT_IMAGE_BLOCK.DESC" + + align: str = "left" + + +class CenterImageBlock(BaseImageBlock): + + """Center image block + """ + + __alias__: str = "ELEMENTS.CENTER_IMAGE_BLOCK.ALIAS" + __description__: str = "ELEMENTS.CENTER_IMAGE_BLOCK.DESC" + + align: str = "center" + + +class ACSBarComponent(AbstractComponent): + + """Anomaly Classification Bar component + """ + + __alias__: str = "ELEMENTS.ACS_BAR.ALIAS" + __description__: str = "ELEMENTS.ACS_BAR.DESC" + + component: str = "component:anomaly-class-bar-source" + + def _init_fields(self): + self.add_field("item-number", "ELEMENTS.ACS_BAR.ITEM.NAME", "ELEMENTS.ACS_BAR.ITEM.DESC") + self.add_field("clearance", "ELEMENTS.ACS_BAR.CLEARANCE.NAME", "ELEMENTS.ACS_BAR.CLEARANCE.DESC") + self.add_field("container-class", "ELEMENTS.ACS_BAR.CONTAINER.NAME", "ELEMENTS.ACS_BAR.CONTAINER.DESC") + self.add_field("disruption-class", "ELEMENTS.ACS_BAR.DISRUPTION.NAME", "ELEMENTS.ACS_BAR.DISRUPTION.DESC") + self.add_field("risk-class", "ELEMENTS.ACS_BAR.RISK.NAME", "ELEMENTS.ACS_BAR.RISK.DESC") + self.add_field("secondary-class", "ELEMENTS.ACS_BAR.SEC_CLASS.NAME", "ELEMENTS.ACS_BAR.SEC_CLASS.DESC") + self.add_field("secondary-icon", "ELEMENTS.ACS_BAR.SEC_ICON.NAME", "ELEMENTS.ACS_BAR.SEC_ICON.DESC") + + +elements: List[AbstractElement] = [RightImageBlock, LeftImageBlock, CenterImageBlock, ACSBarComponent] diff --git a/skippy/core/plugins.py b/skippy/core/plugins.py index 9ff8b8e..c05705b 100644 --- a/skippy/core/plugins.py +++ b/skippy/core/plugins.py @@ -9,7 +9,7 @@ from typing import Iterator, Optional, Callable, Union, Dict, List, Any from abc import ABCMeta, abstractmethod import importlib -import json +import toml import sys import os @@ -61,7 +61,9 @@ class AbstractPlugin(metaclass=ABCMeta): __version__: str def __init__(self): - self._settingsPath: str = os.path.join(skippy.config.PLUGINS_FOLDER, f"{self.__alias__}.json") + self._settingsPath: str = os.path.join( + skippy.config.PLUGINS_SETTINGS_FOLDER, f"{self.__alias__}.toml" + ) self._settings: Dict[str, AbstractSetting] = {} def addSetting(self, name: str, setting: AbstractSetting): @@ -69,16 +71,22 @@ def addSetting(self, name: str, setting: AbstractSetting): def load_settings(self): if self._settings and os.path.exists(self._settingsPath): - with open(self._settingsPath, "r") as f: - settings = json.loads(f.read()) + with open(self._settingsPath) as f: + settings = toml.load(f) for setting in settings: if setting in self._settings: - self._settings[setting].fromJson(settings[setting]) + self._settings[setting].from_toml(settings[setting]) def save_settings(self): if self._settings: with open(self._settingsPath, "w") as f: - f.write(json.dumps({setting: self._settings[setting].toJson() for setting in self._settings})) + toml.dump( + { + setting: self._settings[setting].to_toml() + for setting in self._settings + }, + f, + ) @abstractmethod def start(self): diff --git a/skippy/core/preview.py b/skippy/core/preview.py index 84968ed..a0f897f 100644 --- a/skippy/core/preview.py +++ b/skippy/core/preview.py @@ -1,4 +1,4 @@ -"""Summary +"""Wikidot syntax previewer """ from skippy.api import PageData @@ -15,13 +15,13 @@ def render(pdata: PageData) -> str: - """Summary + """Render page by page data Args: - pdata (PageData): Description + pdata (PageData): Page data (title, source, tags and files) Returns: - str: Description + str: Rendered HTML """ preprocess = PreProcessorsHandler(pdata).process() htmlprocess = HTMLProcessorsHandler(preprocess, pdata).process() @@ -33,26 +33,26 @@ def render(pdata: PageData) -> str: ################################################# class AbstractProcessor(metaclass=ABCMeta): - """Summary""" + """Abstract processor class""" pattern: str def __init__(self, source: str, pdata: PageData): - """Summary + """Initializing Processor Args: - source (str): Description - pdata (PageData): Description + source (str): Page source + pdata (PageData): Page data """ self.source: str = source self.pdata: PageData = pdata @property def matches(self) -> List[Union[Tuple[str, ...], str]]: - """Summary + """Get all matches in source by pattern Returns: - List[Union[Tuple[str, ...], str]]: Description + List[Union[Tuple[str, ...], str]]: List of matches """ return re.findall( self.pattern, @@ -61,42 +61,38 @@ def matches(self) -> List[Union[Tuple[str, ...], str]]: @abstractmethod def process(self): - """Summary""" + """Abstract process method""" pass -class ProcessorsHandler(metaclass=ABCMeta): +class ProcessorsHandlerBase: - """Summary - - Attributes: - source (str): Description - """ + """Abstract processors handler class""" def __init__(self, source: str, pdata: PageData): """Summary Args: - source (str): Description - pdata (PageData): Description + source (str): Page source + pdata (PageData): Page data """ self.source: str = source self.pdata: PageData = pdata self.processors: List[AbstractProcessor] = [] - def register(self, processor: AbstractProcessor): - """Summary + def register(self, processor: AbstractProcessor) -> object: + """Register a processor Args: - processor (AbstractProcessor): Description + processor (AbstractProcessor): Processor class """ self.processors.append(processor) def process(self) -> str: - """Summary + """Run all processor Returns: - str: Description + str: Processed source """ for processor in self.processors: try: @@ -109,46 +105,42 @@ def process(self) -> str: ################################################# # ProcessorsHandlers ################################################# -class PreProcessorsHandler(ProcessorsHandler): +class PreProcessorsHandler(ProcessorsHandlerBase): - """Summary""" + """Handler of preprocessors""" def __init__(self, pdata: PageData): - """Summary + """Initializing preprocessors handler Args: - pdata (PageData): Description + pdata (PageData): Page data """ super(PreProcessorsHandler, self).__init__(pdata["source"], pdata) self.register(IncludesProcessor) self.register(IftagsProcessor) -class HTMLProcessorsHandler(ProcessorsHandler): +class HTMLProcessorsHandler(ProcessorsHandlerBase): - """Summary - - Attributes: - html (str): Description - """ + """Handler of HTML processors""" def __init__(self, source: str, pdata: PageData): - """Summary + """Initializing HTML processors handler Args: - source (str): Description - pdata (PageData): Description + source (str): Page source + pdata (PageData): Page data """ super(HTMLProcessorsHandler, self).__init__(source, pdata) self.register(MarkdownProcessor) self.register(InsertDataProcessor) self.register(ModuleCSSProcessor) - def process(self): - """Summary + def process(self) -> str: + """Run all processor Returns: - TYPE: Description + str: Processed source """ self.html = self.processors[0](self.source, self.pdata).process() for processor in self.processors[1:]: @@ -159,16 +151,16 @@ def process(self): return self.html -class PostProcessorsHandler(ProcessorsHandler): +class PostProcessorsHandler(ProcessorsHandlerBase): - """Summary""" + """Handler of postprocessors""" def __init__(self, source: str, pdata: PageData): - """Summary + """Initializing postprocessors handler Args: - source (str): Description - pdata (PageData): Description + source (str): Page source + pdata (PageData): Page data """ super(PostProcessorsHandler, self).__init__(source, pdata) self.register(LocalImagesProcessor) @@ -180,22 +172,18 @@ def __init__(self, source: str, pdata: PageData): ################################################# class IncludesProcessor(AbstractProcessor): - """Summary - - Attributes: - source (str): Description - """ + """Include processor""" pattern: str = r"(\[\[include\s(?::.+?:|)(?:.+?:|)(?:.+)(?:\s((?:.|\n)+?)|)]])" def process(self, iteration: int = 0) -> str: - """Summary - - Returns: - str: Description + """Replace all include tags with included page source Args: - iteration (int, optional): Description + iteration (int, optional): Include iteration (max=5) + + Returns: + str: Processed source """ for include in self.matches: path = re.findall( @@ -234,19 +222,15 @@ def process(self, iteration: int = 0) -> str: class IftagsProcessor(AbstractProcessor): - """Summary - - Attributes: - source (str): Description - """ + """Iftags processor""" pattern: str = r"(\[\[iftags (.+?)]]((?:(?:.|\n|\s)+?))\[\[\/iftags]])" def process(self) -> str: - """Summary + """Remove iftags if it not matches with page tags Returns: - str: Description + str: Processed source """ for iftag in self.matches: match = True @@ -267,31 +251,31 @@ def process(self) -> str: ################################################# class MarkdownProcessor(AbstractProcessor): - """Summary""" + """Markdown processor""" wiki: pyscp.wikidot.Wiki = pyscp.wikidot.Wiki("www.wikidot.com") def _wikidot(self) -> str: - """Summary + """If connected to internet, get previewed HTML from Wikidot Returns: - str: Description + str: Previewed source """ return self.wiki._module("edit/PagePreviewModule", source=self.source)["body"] def _ftml(self) -> str: - """Summary + """If don't have connection to internet, get previewed HTML from local FTML previewer Returns: - str: Description + str: Previewed source """ return pyftml.render_html(self.source)["body"] def process(self) -> str: - """Summary + """Get previewed page with online/offline methods Returns: - str: Description + str: Processed source """ try: return self._wikidot() @@ -301,7 +285,7 @@ def process(self) -> str: class InsertDataProcessor(AbstractProcessor): - """Summary""" + """InsertData processor""" html_base: str = """ @@ -374,21 +358,21 @@ class InsertDataProcessor(AbstractProcessor): """ def __init__(self, source: str, pdata: PageData, html: str): - """Summary + """Initializing InsertData processor Args: - source (str): Description - pdata (PageData): Description - html (str): Description + source (str): Page source + pdata (PageData): Page data + html (str): Previewed page html """ super(InsertDataProcessor, self).__init__(source, pdata) self.html: str = html def process(self) -> str: - """Summary + """Insert page data to HTML template Returns: - str: Description + str: Processed HTML page """ self.source = self.html_base.replace( "<>", html.escape(self.pdata["title"]) @@ -403,30 +387,26 @@ def process(self) -> str: class ModuleCSSProcessor(AbstractProcessor): - """Summary - - Attributes: - html (str): Description - """ + """Module CSS processor""" pattern: str = r"(?:(?:\[\[module) (?:CSS|css)(?:(.+?)|)\]\]\n)((.|\n)+?)(?:\n\[\[\/module\]\])" def __init__(self, source: str, pdata: PageData, html: str): - """Summary + """Initializing Module CSS processor Args: - source (str): Description - pdata (PageData): Description - html (str): Description + source (str): Page source + pdata (PageData): Page data + html (str): Previewed page html """ super(ModuleCSSProcessor, self).__init__(source, pdata) self.html: str = html def process(self) -> str: - """Summary + """Convert [[module CSS]] block to <style> tag Returns: - str: Description + str: Processed HTML page """ styles = "" for style in self.matches: @@ -441,19 +421,15 @@ def process(self) -> str: ################################################# class LocalImagesProcessor(AbstractProcessor): - """Summary - - Attributes: - source (str): Description - """ + """Local images processor""" pattern: str = r'<img src="http://www.wdfiles.com/local--files//(.+?)"' def process(self) -> str: - """Summary + """Insert local images to page Returns: - str: Description + str: Processed HTML page """ files = self.pdata["files"] for img in self.matches: @@ -467,19 +443,15 @@ def process(self) -> str: class HTMLTagsProcessor(AbstractProcessor): - """Summary - - Attributes: - source (str): Description - """ + """HTML tags processor""" pattern: str = r"(\[\[html(?:(?:.+?)| |)]]((?:.|\n|\s)+?)\[\[\/html]])" def process(self) -> str: - """Summary + """Unescape [[html]] tags Returns: - str: Description + str: Processed HTML page """ for tag in self.matches: self.source = self.source.replace(tag[0], html.unescape(tag[1])) diff --git a/skippy/core/settings.py b/skippy/core/settings.py index 26ec401..21358ed 100644 --- a/skippy/core/settings.py +++ b/skippy/core/settings.py @@ -22,9 +22,9 @@ def value(self, value: Any): pass @abstractmethod - def fromJson(self, text: str): + def from_toml(self, text: str): pass @abstractmethod - def toJson(self) -> str: + def to_toml(self) -> str: pass diff --git a/skippy/gui/actionbar.py b/skippy/gui/actionbar.py index 67b699b..a08d034 100644 --- a/skippy/gui/actionbar.py +++ b/skippy/gui/actionbar.py @@ -2,10 +2,12 @@ from skippy.api import Action -from skippy.gui.dialogs import previewer +from skippy.core.elements import elements as elems + +from skippy.gui.dialogs import previewer, elements, finder from skippy.gui import settings, utils -from skippy.utils import translator +from skippy.utils.translator import Translator import skippy.config from typing import Optional, Union @@ -19,157 +21,165 @@ def initActions(self): mainwindow = utils.getMainWindow() self.new_action = Action( - translator.Translator().translate("MENU_BAR_NEW_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_NEW_ACTION_STATUS_TIP"), + Translator().translate("MENU_BAR.ACTION.FILE.NEW_NAME"), + Translator().translate("MENU_BAR.ACTION.FILE.NEW_STATUS_TIP"), lambda: mainwindow.tab.newTab(), "new", ) self.open_action = Action( - translator.Translator().translate("MENU_BAR_OPEN_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_OPEN_ACTION_STATUS_TIP"), + Translator().translate("MENU_BAR.ACTION.FILE.OPEN_NAME"), + Translator().translate("MENU_BAR.ACTION.FILE.OPEN_STATUS_TIP"), lambda: mainwindow.download(), "open", ) self.upload_action = Action( - translator.Translator().translate("MENU_BAR_UPLOAD_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_UPLOAD_ACTION_STATUS_TIP"), + Translator().translate("MENU_BAR.ACTION.FILE.UPLOAD_NAME"), + Translator().translate("MENU_BAR.ACTION.FILE.UPLOAD_STATUS_TIP"), lambda: mainwindow.upload(mainwindow.tab.currentWidget().pdata), "upload", ) self.upload_as_action = Action( - translator.Translator().translate("MENU_BAR_UPLOAD_AS_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_UPLOAD_AS_ACTION_STATUS_TIP"), + Translator().translate("MENU_BAR.ACTION.FILE.UPLOAD_AS_NAME"), + Translator().translate("MENU_BAR.ACTION.FILE.UPLOAD_AS_STATUS_TIP"), lambda: mainwindow.upload_as(mainwindow.tab.currentWidget().pdata), "upload_as", ) self.close_action = Action( - translator.Translator().translate("MENU_BAR_CLOSE_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_CLOSE_ACTION_STATUS_TIP"), - lambda: mainwindow.tab.remove_tab(mainwindow.tab.currentIndex()), + Translator().translate("MENU_BAR.ACTION.FILE.CLOSE_NAME"), + Translator().translate("MENU_BAR.ACTION.FILE.CLOSE_STATUS_TIP"), + lambda: mainwindow.tab.removeTab(mainwindow.tab.currentIndex()), "close", ) self.load_files_action = Action( - translator.Translator().translate("MENU_BAR_LOAD_FILES_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_LOAD_FILES_ACTION_STATUS_TIP"), + Translator().translate("MENU_BAR.ACTION.FILE.LOAD_FILES_NAME"), + Translator().translate("MENU_BAR.ACTION.FILE.LOAD_FILES_STATUS_TIP"), lambda: mainwindow.load_files(), "load_files", ) self.load_session_action = Action( - translator.Translator().translate("MENU_BAR_LOAD_SESSION_ACTION_NAME"), - translator.Translator().translate( - "MENU_BAR_LOAD_SESSION_ACTION_STATUS_TIP" - ), + Translator().translate("MENU_BAR.ACTION.FILE.LOAD_SESSION_NAME"), + Translator().translate("MENU_BAR.ACTION.FILE.LOAD_SESSION_STATUS_TIP"), lambda: mainwindow.load_session(), "load_session", ) self.save_session_action = Action( - translator.Translator().translate("MENU_BAR_SAVE_SESSION_ACTION_NAME"), - translator.Translator().translate( - "MENU_BAR_SAVE_SESSION_ACTION_STATUS_TIP" - ), + Translator().translate("MENU_BAR.ACTION.FILE.SAVE_SESSION_NAME"), + Translator().translate("MENU_BAR.ACTION.FILE.SAVE_SESSION_STATUS_TIP"), lambda: mainwindow.save_session(), "save_session", ) self.exit_action = Action( - translator.Translator().translate("MENU_BAR_EXIT_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_EXIT_ACTION_STATUS_TIP"), + Translator().translate("MENU_BAR.ACTION.FILE.EXIT_NAME"), + Translator().translate("MENU_BAR.ACTION.FILE.EXIT_STATUS_TIP"), lambda: mainwindow.close(), "exit", ) self.undo_action = Action( - translator.Translator().translate("MENU_BAR_UNDO_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_UNDO_ACTION_STATUS_TIP"), + Translator().translate("MENU_BAR.ACTION.EDIT.UNDO_NAME"), + Translator().translate("MENU_BAR.ACTION.EDIT.UNDO_STATUS_TIP"), lambda: mainwindow.tab.currentWidget().editor.undo(), "undo", ) self.redo_action = Action( - translator.Translator().translate("MENU_BAR_REDO_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_REDO_ACTION_STATUS_TIP"), + Translator().translate("MENU_BAR.ACTION.EDIT.REDO_NAME"), + Translator().translate("MENU_BAR.ACTION.EDIT.REDO_STATUS_TIP"), lambda: mainwindow.tab.currentWidget().editor.redo(), "redo", ) self.cut_action = Action( - translator.Translator().translate("MENU_BAR_CUT_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_CUT_ACTION_STATUS_TIP"), + Translator().translate("MENU_BAR.ACTION.EDIT.CUT_NAME"), + Translator().translate("MENU_BAR.ACTION.EDIT.CUT_STATUS_TIP"), lambda: mainwindow.tab.currentWidget().editor.cut(), "cut", ) self.copy_action = Action( - translator.Translator().translate("MENU_BAR_COPY_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_COPY_ACTION_STATUS_TIP"), + Translator().translate("MENU_BAR.ACTION.EDIT.COPY_NAME"), + Translator().translate("MENU_BAR.ACTION.EDIT.COPY_STATUS_TIP"), lambda: mainwindow.tab.currentWidget().editor.copy(), "copy", ) self.paste_action = Action( - translator.Translator().translate("MENU_BAR_PASTE_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_PASTE_ACTION_STATUS_TIP"), + Translator().translate("MENU_BAR.ACTION.EDIT.PASTE_NAME"), + Translator().translate("MENU_BAR.ACTION.EDIT.PASTE_STATUS_TIP"), lambda: mainwindow.tab.currentWidget().editor.paste(), "paste", ) self.select_all_action = Action( - translator.Translator().translate("MENU_BAR_SELECT_ALL_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_SELECT_ALL_ACTION_STATUS_TIP"), + Translator().translate("MENU_BAR.ACTION.EDIT.SELECT_ALL_NAME"), + Translator().translate("MENU_BAR.ACTION.EDIT.SELECT_ALL_STATUS_TIP"), lambda: mainwindow.tab.currentWidget().editor.selectAll(), "select_all", ) + self.find_action = Action( + Translator().translate("MENU_BAR.ACTION.EDIT.FIND_NAME"), + Translator().translate("MENU_BAR.ACTION.EDIT.FIND_STATUS_TIP"), + lambda: finder.FinderDialog(mainwindow), + "find", + ) + self.preview_action = Action( - translator.Translator().translate("MENU_BAR_PREVIEW_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_PREVIEW_ACTION_STATUS_TIP"), - lambda: previewer.Previewer(mainwindow.tab.currentWidget().pdata, self), + Translator().translate("MENU_BAR.ACTION.EDIT.PREVIEW_NAME"), + Translator().translate("MENU_BAR.ACTION.EDIT.PREVIEW_STATUS_TIP"), + lambda: previewer.Previewer(mainwindow.tab.currentWidget().pdata, mainwindow).deleteLater(), "preview", ) + self.elements_menu = QtWidgets.QMenu(Translator().translate("MENU_BAR.ACTION.EDIT.INSERT_MENU")) + for elem in elems: + element = elem() + self.addAction( + Action( + Translator().translate(element.__alias__), + Translator().translate(element.__description__), + partial(elements.ElementDialog, element, mainwindow) + ), + self.elements_menu + ) + self.toggle_theme_action = Action( - translator.Translator().translate("MENU_BAR_TOGGLE_THEME_ACTION_NAME"), - translator.Translator().translate( - "MENU_BAR_TOGGLE_THEME_ACTION_STATUS_TIP" - ), + Translator().translate("MENU_BAR.ACTION.SETTINGS.TOGGLE_THEME_NAME"), + Translator().translate("MENU_BAR.ACTION.SETTINGS.TOGGLE_THEME_STATUS_TIP"), lambda: mainwindow.toggle_theme(), "toggle", ) self.login_action = Action( - translator.Translator().translate("MENU_BAR_LOGIN_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_LOGIN_ACTION_STATUS_TIP"), + Translator().translate("MENU_BAR.ACTION.SETTINGS.LOGIN_NAME"), + Translator().translate("MENU_BAR.ACTION.SETTINGS.LOGIN_STATUS_TIP"), lambda: mainwindow.login(), "login", ) self.logout_action = Action( - translator.Translator().translate("MENU_BAR_LOGOUT_ACTION_NAME"), - translator.Translator().translate("MENU_BAR_LOGOUT_ACTION_STATUS_TIP"), + Translator().translate("MENU_BAR.ACTION.SETTINGS.LOGOUT_NAME"), + Translator().translate("MENU_BAR.ACTION.SETTINGS.LOGOUT_STATUS_TIP"), lambda: mainwindow.logout(), "logout", ) self.language_menu = QtWidgets.QMenu( - translator.Translator().translate("MENU_BAR_LANGUAGES_MENU") + Translator().translate("MENU_BAR.ACTION.SETTINGS.LANGUAGES_MENU") ) - for lang in translator.Translator().languages(): - name = translator.Translator().getLangName(lang) - self.addAction( - Action(name, name, partial(mainwindow.update_translate, lang), None), - self.language_menu, - ) + for lang in Translator().languages(): + name = Translator().getLangName(lang) + self.addAction(Action(name, name, partial(mainwindow.update_translate, lang)), self.language_menu) - def addAction( - self, action: Action, menu: Optional[QtWidgets.QMenu] = None - ) -> QtWidgets.QAction: + def addAction(self, action: Action, menu: Optional[QtWidgets.QMenu] = None) -> QtWidgets.QAction: qaction = QtWidgets.QAction(action.label, self) if action.img: @@ -184,23 +194,21 @@ def addAction( qaction.setStatusTip(action.statusTip) qaction.triggered.connect(action.func) - if not menu: - super().addAction(qaction) - else: + if menu: menu.addAction(qaction) + else: + super().addAction(qaction) return qaction - def addMenu( - self, qmenu: Union[str, QtWidgets.QMenu], menu: Optional[QtWidgets.QMenu] = None - ) -> QtWidgets.QMenu: + def addMenu(self, qmenu: Union[str, QtWidgets.QMenu], menu: Optional[QtWidgets.QMenu] = None) -> QtWidgets.QMenu: if type(qmenu) == str: qmenu = QtWidgets.QMenu(qmenu, self) - if not menu: - super().addMenu(qmenu) - else: + if menu: menu.addMenu(qmenu) + else: + super().addMenu(qmenu) return qmenu @@ -211,7 +219,7 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None): self.initActions() self.file_menu = self.addMenu( - translator.Translator().translate("MENU_BAR_FILE_MENU") + Translator().translate("MENU_BAR.FILE_MENU") ) self.addAction(self.new_action, self.file_menu) self.addAction(self.open_action, self.file_menu) @@ -227,7 +235,7 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None): self.addAction(self.exit_action, self.file_menu) self.edit_menu = self.addMenu( - translator.Translator().translate("MENU_BAR_EDIT_MENU") + Translator().translate("MENU_BAR.EDIT_MENU") ) self.addAction(self.undo_action, self.edit_menu) self.addAction(self.redo_action, self.edit_menu) @@ -237,10 +245,13 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None): self.addAction(self.paste_action, self.edit_menu) self.addAction(self.select_all_action, self.edit_menu) self.edit_menu.addSeparator() + self.addAction(self.find_action, self.edit_menu) + self.edit_menu.addSeparator() self.addAction(self.preview_action, self.edit_menu) + self.addMenu(self.elements_menu, self.edit_menu) self.settings_menu = self.addMenu( - translator.Translator().translate("MENU_BAR_SETTINGS_MENU") + Translator().translate("MENU_BAR.SETTINGS_MENU") ) self.addAction(self.toggle_theme_action, self.settings_menu) self.settings_menu.addMenu(self.language_menu) @@ -288,3 +299,4 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None): self.addAction(self.select_all_action) self.addSeparator() self.addAction(self.preview_action) + self.addMenu(self.elements_menu) diff --git a/skippy/gui/dialogs/criticalmessage.py b/skippy/gui/dialogs/criticalmessage.py index b1f8a2e..16fbcbb 100644 --- a/skippy/gui/dialogs/criticalmessage.py +++ b/skippy/gui/dialogs/criticalmessage.py @@ -19,8 +19,6 @@ def __init__(self, error: str, traceback: str): self.setLayout(self._layout) self.setWindowTitle(f"Error: {error}") - self.setWindowIcon( - QtGui.QIcon(os.path.join(skippy.config.RESOURCES_FOLDER, "skippy.ico")) - ) + self.setWindowIcon(QtGui.QIcon(os.path.join(skippy.config.RESOURCES_FOLDER, "skippy.ico"))) self.resize(530, 390) diff --git a/skippy/gui/dialogs/download.py b/skippy/gui/dialogs/download.py index 2ea3610..5962868 100644 --- a/skippy/gui/dialogs/download.py +++ b/skippy/gui/dialogs/download.py @@ -30,18 +30,18 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None): self._layout = QtWidgets.QVBoxLayout(self) self.label = QtWidgets.QLabel( - translator.Translator().translate("DIALOG_ENTER_PAGE_LABEL"), self + translator.Translator().translate("DIALOG.ENTER_PAGE_LABEL"), self ) self.site_box = sitebox.SiteBox(self) self.page_box = QtWidgets.QLineEdit() self.page_box.setPlaceholderText( - translator.Translator().translate("DIALOG_PAGE_BOX_PLACEHOLDER") + translator.Translator().translate("DIALOG.PAGE_BOX_PLACEHOLDER") ) self.button = QtWidgets.QPushButton( - translator.Translator().translate("DIALOG_OK_BUTTON"), self + translator.Translator().translate("DIALOG.OK_BUTTON"), self ) self.button.clicked.connect(self.download) @@ -53,9 +53,7 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None): self.setLayout(self._layout) self.setWindowTitle(f"Skippy - {skippy.config.version}") - self.setWindowIcon( - QtGui.QIcon(os.path.join(skippy.config.RESOURCES_FOLDER, "skippy.ico")) - ) + self.setWindowIcon(QtGui.QIcon(os.path.join(skippy.config.RESOURCES_FOLDER, "skippy.ico"))) self.move(300, 300) self.resize(200, 100) @@ -67,7 +65,7 @@ def download(self): self._thread = thread.Thread(DownloadWorker(site, page)) self._thread.worker.finished.connect( - lambda pageData: utils.getMainWindow().tab.newTab(*pageData.values()) + lambda page_data: utils.getMainWindow().tab.newTab(*page_data.values()) ) self._thread.start() diff --git a/skippy/gui/dialogs/elements.py b/skippy/gui/dialogs/elements.py new file mode 100644 index 0000000..612b51e --- /dev/null +++ b/skippy/gui/dialogs/elements.py @@ -0,0 +1,89 @@ +from PyQt5 import QtWidgets, QtCore + +from skippy.core.elements import AbstractElement + +from skippy.gui.utils import getMainWindow + +from skippy.utils.translator import Translator + +import skippy.config + +from typing import Optional + + +class ElementDialog(QtWidgets.QDialog): + def __init__(self, element: AbstractElement, parent: Optional[QtWidgets.QWidget] = None): + super(ElementDialog, self).__init__(parent) + self.element = element + + self._layout = QtWidgets.QVBoxLayout(self) + + self.element_data = QtWidgets.QLabel(Translator().translate(self.element.__alias__) + " - " + + Translator().translate(self.element.__description__), self) + self.element_data.setWordWrap(True) + + self._layout.addWidget(self.element_data) + self._layout.addWidget(QHLine()) + + self.scroll = QtWidgets.QScrollArea() + self.widget = QtWidgets.QWidget() + self.vbox = QtWidgets.QVBoxLayout() + self.fields = {} + for field in self.element.required_fields: + label = QtWidgets.QLabel(Translator().translate(field.name) if field.name else field.tag, self) + + line = QtWidgets.QPlainTextEdit(self) + line.setFixedHeight(25) + line.setPlaceholderText(Translator().translate(field.description)) + line.textChanged.connect(self.updatePreview) + self.fields[field.tag] = line + + self.vbox.addWidget(label) + self.vbox.addWidget(line) + + self.widget.setLayout(self.vbox) + + self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.scroll.setWidgetResizable(True) + self.scroll.setWidget(self.widget) + + self._layout.addWidget(self.scroll) + + self.previewer = QtWidgets.QPlainTextEdit(self) + self.previewer.setReadOnly(True) + + self._layout.addWidget(self.previewer) + + self.button = QtWidgets.QPushButton(Translator().translate("DIALOG.GENERATE_BUTTON"), self) + self.button.clicked.connect(self.process) + self._layout.addWidget(self.button, alignment=QtCore.Qt.AlignRight) + + self.setLayout(self._layout) + + self.setWindowTitle(f"{Translator().translate(self.element.__alias__)} | skippy - {skippy.config.version}") + self.move(300, 300) + self.resize(500, 400) + + self.updatePreview() + + self.show() + + def updatePreview(self): + self.previewer.setPlainText(self.preview()) + + def process(self): + getMainWindow().tab.currentWidget().editor.insertPlainText( + self.preview() + ) + self.close() + + def preview(self): + return self.element.preview({field: self.fields[field].toPlainText() for field in self.fields}) + + +class QHLine(QtWidgets.QFrame): + def __init__(self, parent: Optional[QtWidgets.QWidget] = None): + super(QHLine, self).__init__(parent) + self.setFrameShape(QtWidgets.QFrame.HLine) + self.setFrameShadow(QtWidgets.QFrame.Sunken) diff --git a/skippy/gui/dialogs/filesdialog.py b/skippy/gui/dialogs/filesdialog.py index ee7b282..1d8db6b 100644 --- a/skippy/gui/dialogs/filesdialog.py +++ b/skippy/gui/dialogs/filesdialog.py @@ -15,9 +15,7 @@ def __init__(self, files: Dict[str, str], parent: Optional[QtWidgets.QWidget] = super(FilesDialog, self).__init__(parent) self._layout = QtWidgets.QVBoxLayout(self) - self.label = QtWidgets.QLabel( - translator.Translator().translate("FILES_DIALOG_FILES_LIST_LABEL"), self - ) + self.label = QtWidgets.QLabel(translator.Translator().translate("DIALOG.FILES_LIST_LABEL"), self) self.label.setFont(QtGui.QFont("Arial", 10)) self.scrollArea = QtWidgets.QScrollArea() @@ -80,11 +78,7 @@ def __init__(self, title: str, parent: Optional[QtWidgets.QWidget] = None): self._prevText = title self.button = QtWidgets.QToolButton(self) - self.button.setIcon( - QtGui.QIcon( - os.path.join(skippy.config.RESOURCES_FOLDER, theme, "close.png") - ) - ) + self.button.setIcon(QtGui.QIcon(os.path.join(skippy.config.RESOURCES_FOLDER, theme, "close.png"))) self.button.setStyleSheet("padding: 0px;") self.button.setCursor(QtCore.Qt.ArrowCursor) self.button.clicked.connect(lambda: self.fileRemoved.emit(self.text())) @@ -94,11 +88,7 @@ def __init__(self, title: str, parent: Optional[QtWidgets.QWidget] = None): self.textEdited.connect(self.textEditedEvent) - self.setStyleSheet( - "QLineEdit {padding-right: " - + str(buttonSize.width() + frameWidth + 1) - + "px; }" - ) + self.setStyleSheet("QLineEdit {padding-right: "+ str(buttonSize.width() + frameWidth + 1)+ "px; }") self.setFixedSize(200, 40) def textEditedEvent(self, new_text: str): diff --git a/skippy/gui/dialogs/finder.py b/skippy/gui/dialogs/finder.py new file mode 100644 index 0000000..49cae5f --- /dev/null +++ b/skippy/gui/dialogs/finder.py @@ -0,0 +1,102 @@ +from PyQt5 import QtWidgets, QtGui + +from skippy.gui.utils import getMainWindow + +from skippy.utils import translator + +import skippy.config + +from typing import Optional +import re +import os + + +class FinderDialog(QtWidgets.QDialog): + def __init__(self, parent: Optional[QtWidgets.QWidget] = None): + super(FinderDialog, self).__init__(parent) + self.textEditor = getMainWindow().tab.currentWidget().editor + + self.lastStart = 0 + + findButton = QtWidgets.QPushButton(translator.Translator().translate("DIALOG.FINDER_FIND_BUTTON"), self) + findButton.clicked.connect(self.find) + + replaceButton = QtWidgets.QPushButton(translator.Translator().translate("DIALOG.FINDER_REPLACE_BUTTON"), self) + replaceButton.clicked.connect(self.replace) + + allButton = QtWidgets.QPushButton(translator.Translator().translate("DIALOG.FINDER_REPLACE_ALL_BUTTON"), self) + allButton.clicked.connect(self.replaceAll) + + self.normalRadio = QtWidgets.QRadioButton(translator.Translator().translate("FINDER_NORMAL_MODE"), self) + + regexRadio = QtWidgets.QRadioButton(translator.Translator().translate("FINDER_REGEX_MODE"), self) + + self.findField = QtWidgets.QTextEdit(self) + self.findField.resize(250, 50) + + self.replaceField = QtWidgets.QTextEdit(self) + self.replaceField.resize(250, 50) + + layout = QtWidgets.QGridLayout() + + layout.addWidget(self.findField, 1, 0, 1, 4) + layout.addWidget(self.normalRadio, 2, 2) + layout.addWidget(regexRadio, 2, 3) + layout.addWidget(findButton, 2, 0, 1, 2) + + layout.addWidget(self.replaceField, 3, 0, 1, 4) + layout.addWidget(replaceButton, 4, 0, 1, 2) + layout.addWidget(allButton, 4, 2, 1, 2) + + self.setGeometry(300, 300, 360, 250) + self.setWindowTitle(f"Skippy - {skippy.config.version}") + self.setWindowIcon(QtGui.QIcon(os.path.join(skippy.config.RESOURCES_FOLDER, "skippy.ico"))) + self.setLayout(layout) + + self.normalRadio.setChecked(True) + self.show() + + def find(self): + text = self.textEditor.toPlainText() + query = self.findField.toPlainText() + + if self.normalRadio.isChecked(): + self.lastStart = text.find(query, self.lastStart+1) + + if self.lastStart >= 0: + end = self.lastStart + len(query) + self.moveCursor(self.lastStart, end) + else: + self.lastStart = 0 + else: + pattern = re.compile(query) + + match = pattern.search(text, self.lastStart + 1) + if match: + self.lastStart = match.start() + self.moveCursor(self.lastStart, match.end()) + else: + self.lastStart = 0 + + def replace(self): + self.find() + cursor = self.textEditor.textCursor() + + if cursor.hasSelection(): + cursor.insertText(self.replaceField.toPlainText()) + cursor.select(QtGui.QTextCursor.WordUnderCursor) + self.textEditor.setTextCursor(cursor) + + def replaceAll(self): + self.lastStart = 0 + self.replace() + + while self.lastStart: + self.replace() + + def moveCursor(self, start, end): + cursor = self.textEditor.textCursor() + cursor.setPosition(start) + cursor.movePosition(QtGui.QTextCursor.Right, QtGui.QTextCursor.KeepAnchor, end-start) + + self.textEditor.setTextCursor(cursor) diff --git a/skippy/gui/dialogs/login.py b/skippy/gui/dialogs/login.py index d61f780..706d9f4 100644 --- a/skippy/gui/dialogs/login.py +++ b/skippy/gui/dialogs/login.py @@ -17,19 +17,19 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None): self._layout = QtWidgets.QVBoxLayout(self) self.label = QtWidgets.QLabel( - translator.Translator().translate("DIALOG_SIGN_IN_TO_WIKIDOT_LABEL"), self + translator.Translator().translate("DIALOG.SIGN_IN_TO_WIKIDOT_LABEL"), self ) self.login_box = QtWidgets.QLineEdit() self.login_box.setPlaceholderText( - translator.Translator().translate("DIALOG_LOGIN_PLACEHOLDER") + translator.Translator().translate("DIALOG.LOGIN_PLACEHOLDER") ) self.password_box = QtWidgets.QLineEdit() self.password_box.setEchoMode(QtWidgets.QLineEdit.Password) self.password_box.setPlaceholderText( - translator.Translator().translate("DIALOG_PASSWORD_PLACEHOLDER") + translator.Translator().translate("DIALOG.PASSWORD_PLACEHOLDER") ) self.button = QtWidgets.QPushButton( - translator.Translator().translate("DIALOG_OK_BUTTON"), self + translator.Translator().translate("DIALOG.OK_BUTTON"), self ) self.button.clicked.connect(self.login) @@ -42,9 +42,7 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None): self.setLayout(self._layout) self.setWindowTitle(f"Skippy - {skippy.config.version}") - self.setWindowIcon( - QtGui.QIcon(os.path.join(skippy.config.RESOURCES_FOLDER, "skippy.ico")) - ) + self.setWindowIcon(QtGui.QIcon(os.path.join(skippy.config.RESOURCES_FOLDER, "skippy.ico"))) self.move(300, 300) self.resize(200, 100) diff --git a/skippy/gui/dialogs/previewer.py b/skippy/gui/dialogs/previewer.py index 51cfda3..59cc029 100644 --- a/skippy/gui/dialogs/previewer.py +++ b/skippy/gui/dialogs/previewer.py @@ -16,7 +16,7 @@ class PreviewerWorker(thread.AbstractWorker): finished = QtCore.pyqtSignal(str) - def __init__(self, pdata: PageData, parent: Optional[QtWidgets.QWidget] = None): + def __init__(self, pdata: PageData): super(PreviewerWorker, self).__init__() self.pdata = pdata @@ -31,7 +31,7 @@ def __init__(self, pdata: PageData, parent: Optional[QtWidgets.QWidget] = None): self.webEngineView = QtWebEngineWidgets.QWebEngineView(self) - self._thread = thread.Thread(PreviewerWorker(pdata, self)) + self._thread = thread.Thread(PreviewerWorker(pdata)) self._thread.worker.finished.connect(self.load) self._thread.start() @@ -40,9 +40,7 @@ def __init__(self, pdata: PageData, parent: Optional[QtWidgets.QWidget] = None): self.setLayout(self._layout) self.setWindowTitle(f"Skippy - {skippy.config.version}") - self.setWindowIcon( - QtGui.QIcon(os.path.join(skippy.config.RESOURCES_FOLDER, "skippy.ico")) - ) + self.setWindowIcon(QtGui.QIcon(os.path.join(skippy.config.RESOURCES_FOLDER, "skippy.ico"))) mainwindow = utils.getMainWindow() @@ -50,7 +48,7 @@ def __init__(self, pdata: PageData, parent: Optional[QtWidgets.QWidget] = None): self.resize(mainwindow.width(), mainwindow.height()) self.setWindowState(mainwindow.windowState()) - self.show() + self.exec_() def load(self, html: str): with tempfile.NamedTemporaryFile( diff --git a/skippy/gui/dialogs/updater.py b/skippy/gui/dialogs/updater.py index 2f68bc8..afa34dd 100644 --- a/skippy/gui/dialogs/updater.py +++ b/skippy/gui/dialogs/updater.py @@ -10,28 +10,22 @@ def UpdaterDialog(version: str, update_func: Callable[[], None]): msgBox = QtWidgets.QMessageBox() - msgBox.setText( - translator.Translator() - .translate("DIALOG_NEW_VERSION_AVAILABLE_LABEL") - .format(version) - ) + msgBox.setText(translator.Translator().translate("DIALOG.NEW_VERSION_AVAILABLE_LABEL").format(version)) msgBox.setInformativeText( - translator.Translator().translate("DIALOG_YOU_CAN_DOWNLOAD_IT_LABEL") + translator.Translator().translate("DIALOG.YOU_CAN_DOWNLOAD_IT_LABEL") ) msgBox.addButton( - translator.Translator().translate("DIALOG_CANCEL_BUTTON"), + translator.Translator().translate("DIALOG.CANCEL_BUTTON"), QtWidgets.QMessageBox.RejectRole, ) installButton = msgBox.addButton( - translator.Translator().translate("DIALOG_INSTALL_BUTTON"), + translator.Translator().translate("DIALOG.INSTALL_BUTTON"), QtWidgets.QMessageBox.ActionRole, ) msgBox.setWindowTitle(f"Skippy - {skippy.config.version}") - msgBox.setWindowIcon( - QtGui.QIcon(os.path.join(skippy.config.RESOURCES_FOLDER, "skippy.ico")) - ) + msgBox.setWindowIcon(QtGui.QIcon(os.path.join(skippy.config.RESOURCES_FOLDER, "skippy.ico"))) msgBox.exec_() diff --git a/skippy/gui/dialogs/upload.py b/skippy/gui/dialogs/upload.py index 15f56ab..8795627 100644 --- a/skippy/gui/dialogs/upload.py +++ b/skippy/gui/dialogs/upload.py @@ -19,7 +19,7 @@ def __init__(self, pdata: PageData, parent: Optional[QtWidgets.QWidget] = None): self._layout = QtWidgets.QVBoxLayout(self) self.label = QtWidgets.QLabel( - translator.Translator().translate("DIALOG_ENTER_PAGE_LABEL"), self + translator.Translator().translate("DIALOG.ENTER_PAGE_LABEL"), self ) self.site_box = sitebox.SiteBox( @@ -28,20 +28,20 @@ def __init__(self, pdata: PageData, parent: Optional[QtWidgets.QWidget] = None): self.page_box = QtWidgets.QLineEdit() self.page_box.setPlaceholderText( - translator.Translator().translate("DIALOG_PAGE_BOX_PLACEHOLDER") + translator.Translator().translate("DIALOG.PAGE_BOX_PLACEHOLDER") ) self.page_box.setText(self.pdata["link"][1] if self.pdata["link"] else "") self.comment_box = QtWidgets.QLineEdit() self.comment_box.setText( - translator.Translator().translate("DIALOG_COMMENT_BOX_TEXT") + translator.Translator().translate("DIALOG.COMMENT_BOX_TEXT") ) self.comment_box.setPlaceholderText( - translator.Translator().translate("DIALOG_COMMENT_BOX_PLACEHOLDER") + translator.Translator().translate("DIALOG.COMMENT_BOX_PLACEHOLDER") ) self.button = QtWidgets.QPushButton( - translator.Translator().translate("DIALOG_OK_BUTTON"), self + translator.Translator().translate("DIALOG.OK_BUTTON"), self ) self.button.clicked.connect(self.upload) diff --git a/skippy/gui/editor.py b/skippy/gui/editor.py index a2c30ee..48e8622 100644 --- a/skippy/gui/editor.py +++ b/skippy/gui/editor.py @@ -473,9 +473,7 @@ def completeInlineBrackets(self, event: QtCore.QEvent): if ( event.text() == pattern.opening[-1:] and left[-len(pattern.opening) :] == pattern.opening - and ( - right[: len(pattern.closing)] != pattern.closing or pattern.multiple - ) + and (right[: len(pattern.closing)] != pattern.closing or pattern.multiple) ): self.insertPlainText(pattern.closing) cursor = self.textCursor() diff --git a/skippy/gui/loginstatus.py b/skippy/gui/loginstatus.py index dea72a7..def63f2 100644 --- a/skippy/gui/loginstatus.py +++ b/skippy/gui/loginstatus.py @@ -14,7 +14,7 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None): def updateStatus(self, username: str): if username: self.setText( - translator.Translator().translate("SIGNED_IN_AS_LABEL").format(username) + translator.Translator().translate("MAIN.SIGNED_IN_AS_LABEL").format(username) ) else: self.setText("") diff --git a/skippy/gui/mainwindow.py b/skippy/gui/mainwindow.py index 071742f..1b3e507 100644 --- a/skippy/gui/mainwindow.py +++ b/skippy/gui/mainwindow.py @@ -16,7 +16,7 @@ utils, ) -from skippy.utils import translator, filehandlers, discord_rpc +from skippy.utils import translator, filehandlers import skippy.config @@ -32,7 +32,7 @@ class Skippy(QtWidgets.QMainWindow): def __init__(self): """Create a new main window.""" super(Skippy, self).__init__() - self.rpc = discord_rpc.DiscordRPC() + self._threads = [] self.settings = settings.Settings() @@ -44,8 +44,8 @@ def __init__(self): self.addToolBar(self.settings.toolbarArea, self.toolBar) self.tab = tabwidget.ProjectList(self) + self.tab.statsChanged.connect(self.update_rpc) self.tab.titleChanged.connect(self.update_title) - self.tab.statsChanged.connect(self.rpc.update) self.tab.load() self.loginStatus = loginstatus.LoginStatus(self) @@ -55,9 +55,7 @@ def __init__(self): self.status = QtWidgets.QStatusBar(self) self.setStatusBar(self.status) - self.setWindowIcon( - QtGui.QIcon(os.path.join(skippy.config.RESOURCES_FOLDER, "skippy.ico")) - ) + self.setWindowIcon(QtGui.QIcon(os.path.join(skippy.config.RESOURCES_FOLDER, "skippy.ico"))) self.resize(self.settings.size) self.move(self.settings.pos) self.setWindowState(QtCore.Qt.WindowState(self.settings.state)) @@ -69,9 +67,6 @@ def __init__(self): else: styles.dark() - self._uploadPageThreads = [] - self._loadFileThreads = [] - def update_title(self, title: str): """Update title for Skippy window.""" self.setWindowTitle(f"{title} | Skippy - {skippy.config.version}") @@ -89,7 +84,8 @@ def upload(self, pdata: PageData): """ if pdata["link"]: uploadPageThread = thread.Thread(workers.UploadWorker(pdata)) - self._uploadPageThreads.append(uploadPageThread) + uploadPageThread.finished.connect(lambda: self.removeThread(uploadPageThread)) + self._threads.append(uploadPageThread) uploadPageThread.start() else: self.upload_as(pdata) @@ -135,10 +131,20 @@ def load_files(self): self, "Load files", "", "All files (*.*)" ) loadFileThread = thread.Thread(workers.FileWorker(files)) - self._loadFileThreads.append(loadFileThread) + loadFileThread.finished.connect(lambda: self.removeThread(loadFileThread)) + self._threads.append(loadFileThread) loadFileThread.worker.progress.connect(self.tab.currentWidget().uploadFile) loadFileThread.start() + def update_rpc(self, title: str, words: int, letters: int): + for thr in self._threads: + if type(thr.worker) == workers.DiscordRPCWorker: + return + updateRPCThread = thread.Thread(workers.DiscordRPCWorker(title, words, letters)) + updateRPCThread.finished.connect(lambda: self.removeThread(updateRPCThread)) + self._threads.append(updateRPCThread) + updateRPCThread.start() + def toggle_theme(self): """Toggle current theme.""" self.settings.theme = "dark" if self.settings.theme == "light" else "light" @@ -158,6 +164,10 @@ def restart(cls): """Restart window.""" utils.getApplication().exit(cls.EXIT_CODE_REBOOT) + def removeThread(self, thr: thread.Thread): + if thr in self._threads: + self._threads.remove(thr) + def resizeEvent(self, event: QtCore.QEvent): """Change size of login status widget when window resized. diff --git a/skippy/gui/sitebox.py b/skippy/gui/sitebox.py index 4a29569..62f87bf 100644 --- a/skippy/gui/sitebox.py +++ b/skippy/gui/sitebox.py @@ -35,9 +35,7 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None, default: str = "" self.default = default self._lineEdit = QtWidgets.QLineEdit(self) - self._lineEdit.setPlaceholderText( - translator.Translator().translate("SITE_BOX_LINEEDIT") - ) + self._lineEdit.setPlaceholderText(translator.Translator().translate("DIALOG.SITE_BOX_LINEEDIT")) self.setLineEdit(self._lineEdit) self.setEditable(True) diff --git a/skippy/gui/startui.py b/skippy/gui/startui.py index 14c4022..a222dca 100644 --- a/skippy/gui/startui.py +++ b/skippy/gui/startui.py @@ -14,7 +14,8 @@ @critical def start_ui(): - """Start Skippy application.""" + """Start Skippy application. + """ logger.log.info("Skippy was started...") scpclient.SCPClient(*filehandlers.ProfileHandler().load()) diff --git a/skippy/gui/tabwidget.py b/skippy/gui/tabwidget.py index 0ffa51e..d773b7f 100644 --- a/skippy/gui/tabwidget.py +++ b/skippy/gui/tabwidget.py @@ -20,17 +20,9 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None): resources.qInitResources() self.tabCloseRequested.connect(self.removeTab) - self.currentChanged.connect( - lambda: self.titleChanged.emit(self.currentTabText()) - ) - self.currentChanged.connect( - lambda: self.currentWidget().statusBarStats() - if self.currentWidget() - else None - ) - self.titleChanged.connect( - lambda title: self.statsChanged.emit(title, *self.currentEditorStats()) - ) + self.currentChanged.connect(lambda: self.titleChanged.emit(self.currentTitle())) + self.currentChanged.connect(lambda: self.currentWidget().statusBarStats() if self.currentWidget() else None) + self.titleChanged.connect(lambda title: self.statsChanged.emit(title, *self.currentEditorStats())) self.setStyleSheet( "QTabBar{font-family: Arial; font-size:10pt;} QTabBar::close-button {image: url(:" @@ -55,7 +47,7 @@ def newTab( tab.sourceChanged.connect(self.save) tab.sourceChanged.connect( lambda: self.statsChanged.emit( - self.currentTabText(), *self.currentEditorStats() + self.currentTitle(), *self.currentEditorStats() ) ) @@ -64,7 +56,7 @@ def newTab( self.save() - def currentTabText(self) -> str: + def currentTitle(self) -> str: return self.tabText(self.currentIndex()) @ignore(AttributeError, (0, 0)) @@ -108,7 +100,7 @@ def load(self, path: Optional[str] = None): self.checkCount() self.setCurrentIndex(session["pos"] if "pos" in session else self.count() - 1) - self.titleChanged.emit(self.currentTabText()) + self.titleChanged.emit(self.currentTitle()) self.save() @@ -145,23 +137,15 @@ def __init__( self.editor = editor.AdvancedEditor(self) self.editor.setPlainText(source) self.editor.textChanged.connect(self.statusBarStats) - self.editor.textChanged.connect( - lambda: self.setData("source", self.editor.toPlainText()) - ) + self.editor.textChanged.connect(lambda: self.setData("source", self.editor.toPlainText())) self.editor.fileDragAndDroped.connect(self.uploadFile) self.tags_box = QtWidgets.QLineEdit() self.tags_box.setText(" ".join(tags)) - self.tags_box.textChanged.connect( - lambda text: self.setData("tags", text.split()) - ) + self.tags_box.textChanged.connect(lambda text: self.setData("tags", text.split())) - self.files_button = QtWidgets.QPushButton( - translator.Translator().translate("FILES_BUTTON"), self - ) - self.files_button.clicked.connect( - lambda: filesdialog.FilesDialog(self.pdata["files"], self) - ) + self.files_button = QtWidgets.QPushButton(translator.Translator().translate("MAIN.FILES_BUTTON"), self) + self.files_button.clicked.connect(lambda: filesdialog.FilesDialog(self.pdata["files"], self)) self._layout.addWidget(self.title_box) self._layout.addWidget(self.editor) @@ -169,9 +153,7 @@ def __init__( self._layout.addWidget(self.files_button) self.setLayout(self._layout) - self.setStyleSheet( - "QLineEdit, QPlainTextEdit {font-family: Arial; font-size:12pt;}" - ) + self.setStyleSheet("QLineEdit, QPlainTextEdit {font-family: Arial; font-size:12pt;}") def editorStats(self) -> Tuple[int, int]: content = self.editor.toPlainText() diff --git a/skippy/gui/workers.py b/skippy/gui/workers.py index c5f172e..14712d6 100644 --- a/skippy/gui/workers.py +++ b/skippy/gui/workers.py @@ -4,7 +4,9 @@ from skippy.core import scpclient -from skippy.gui import thread +from skippy.gui import thread, utils + +from skippy.utils import discord_rpc from typing import List import os @@ -34,3 +36,18 @@ def __init__(self, pdata: PageData, comment: str = "Edit using Skippy"): def run(self): scpclient.SCPClient().upload(self.pdata, self.comment) self.finished.emit() + + +class DiscordRPCWorker(thread.AbstractWorker): + def __init__(self, title: str, words: int, letters: int): + super(DiscordRPCWorker, self).__init__() + self.rpc = discord_rpc.DiscordRPC() + + self.title = title + self.words = words + self.letters = letters + + def run(self): + self.rpc.update(self.title, self.words, self.letters) + + self.finished.emit() diff --git a/skippy/lang/en.json b/skippy/lang/en.json deleted file mode 100644 index 37473e7..0000000 --- a/skippy/lang/en.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "LANGUAGE": "English", - - "MENU_BAR_FILE_MENU": "&File", - "MENU_BAR_FILE_TOOLBAE": "File", - "MENU_BAR_NEW_ACTION_NAME": "New", - "MENU_BAR_NEW_ACTION_STATUS_TIP": "New", - "MENU_BAR_OPEN_ACTION_NAME": "Open", - "MENU_BAR_OPEN_ACTION_STATUS_TIP": "Open page", - "MENU_BAR_UPLOAD_ACTION_NAME": "Upload", - "MENU_BAR_UPLOAD_ACTION_STATUS_TIP": "Upload page", - "MENU_BAR_UPLOAD_AS_ACTION_NAME": "Upload as...", - "MENU_BAR_UPLOAD_AS_ACTION_STATUS_TIP": "Upload page as...", - "MENU_BAR_CLOSE_ACTION_NAME": "Close", - "MENU_BAR_CLOSE_ACTION_STATUS_TIP": "Close", - "MENU_BAR_LOAD_FILES_ACTION_NAME": "Load files...", - "MENU_BAR_LOAD_FILES_ACTION_STATUS_TIP": "Load files to the current page", - "MENU_BAR_LOAD_SESSION_ACTION_NAME": "Load Session...", - "MENU_BAR_LOAD_SESSION_ACTION_STATUS_TIP": "Load Session...", - "MENU_BAR_SAVE_SESSION_ACTION_NAME": "Save Session as...", - "MENU_BAR_SAVE_SESSION_ACTION_STATUS_TIP": "Save Session as...", - "MENU_BAR_EXIT_ACTION_NAME": "Exit", - "MENU_BAR_EXIT_ACTION_STATUS_TIP": "Exit", - - "MENU_BAR_EDIT_MENU": "&Edit", - "MENU_BAR_EDIT_TOOLBAE": "Edit", - "MENU_BAR_UNDO_ACTION_NAME": "Undo", - "MENU_BAR_UNDO_ACTION_STATUS_TIP": "Undo last change", - "MENU_BAR_REDO_ACTION_NAME": "Redo", - "MENU_BAR_REDO_ACTION_STATUS_TIP": "Redo last change", - "MENU_BAR_CUT_ACTION_NAME": "Cut", - "MENU_BAR_CUT_ACTION_STATUS_TIP": "Cut selected text", - "MENU_BAR_COPY_ACTION_NAME": "Copy", - "MENU_BAR_COPY_ACTION_STATUS_TIP": "Copy selected text", - "MENU_BAR_PASTE_ACTION_NAME": "Paste", - "MENU_BAR_PASTE_ACTION_STATUS_TIP": "Paste from clipboard", - "MENU_BAR_SELECT_ALL_ACTION_NAME": "Select all", - "MENU_BAR_SELECT_ALL_ACTION_STATUS_TIP": "Select all text", - "MENU_BAR_PREVIEW_ACTION_NAME": "Preview", - "MENU_BAR_PREVIEW_ACTION_STATUS_TIP": "Preview", - "MENU_BAR_INSERT_MENU": "Insert element...", - - "MENU_BAR_SETTINGS_MENU": "&Settings", - "MENU_BAR_TOGGLE_THEME_ACTION_NAME": "Toggle Theme", - "MENU_BAR_TOGGLE_THEME_ACTION_STATUS_TIP": "Toggle Theme", - "MENU_BAR_LANGUAGES_MENU": "Languages...", - "MENU_BAR_LOGIN_ACTION_NAME": "Login", - "MENU_BAR_LOGIN_ACTION_STATUS_TIP": "Login", - "MENU_BAR_LOGOUT_ACTION_NAME": "Logout", - "MENU_BAR_LOGOUT_ACTION_STATUS_TIP": "Logout", - - "SIGNED_IN_AS_LABEL": "Signed in as {}", - - "FILES_BUTTON": "Files", - "FILES_DIALOG_FILES_LIST_LABEL": "Files list", - "FILES_DIALOG_EMPTY_FILES_LIST_LABEL": "Files list is empty...", - - "DIALOG_NEW_VERSION_AVAILABLE_LABEL": "New Skippy version available: v{}!", - "DIALOG_YOU_CAN_DOWNLOAD_IT_LABEL": "You can install it by press Install button.", - "DIALOG_SIGN_IN_TO_WIKIDOT_LABEL": "Sign in to your Wikidot account", - "DIALOG_LOGIN_PLACEHOLDER": "Login", - "DIALOG_PASSWORD_PLACEHOLDER": "Password", - "DIALOG_ENTER_PAGE_LABEL": "Enter page url", - "DIALOG_PAGE_BOX_PLACEHOLDER": "Page name", - "DIALOG_COMMENT_BOX_PLACEHOLDER": "Comment", - "DIALOG_COMMENT_BOX_TEXT": "Edit using Skippy", - "DIALOG_GENERATE_BUTTON": "Generate", - "DIALOG_INSTALL_BUTTON": "Install", - "DIALOG_CANCEL_BUTTON": "Cancel", - "DIALOG_OK_BUTTON": "Ok", - - "SITE_BOX_LINEEDIT": "Enter wiki url" -} \ No newline at end of file diff --git a/skippy/lang/en.toml b/skippy/lang/en.toml new file mode 100644 index 0000000..1e57918 --- /dev/null +++ b/skippy/lang/en.toml @@ -0,0 +1,152 @@ +LANGUAGE = "English" + +[MAIN] +SIGNED_IN_AS_LABEL = "Signed in as {}" +FILES_BUTTON = "Files" + +[MENU_BAR] +FILE_MENU = "&File" +EDIT_MENU = "&Edit" +SETTINGS_MENU = "&Settings" + +[MENU_BAR.ACTION] + +[MENU_BAR.ACTION.FILE] +NEW_NAME = "New" +NEW_STATUS_TIP = "New" +OPEN_NAME = "Open" +OPEN_STATUS_TIP = "Open page" +UPLOAD_NAME = "Upload" +UPLOAD_STATUS_TIP = "Upload page" +UPLOAD_AS_NAME = "Upload as..." +UPLOAD_AS_STATUS_TIP = "Upload page as..." +CLOSE_NAME = "Close" +CLOSE_STATUS_TIP = "Close" +LOAD_FILES_NAME = "Load files..." +LOAD_FILES_STATUS_TIP = "Load files to the current page" +LOAD_SESSION_NAME = "Load Session..." +LOAD_SESSION_STATUS_TIP = "Load Session..." +SAVE_SESSION_NAME = "Save Session as..." +SAVE_SESSION_STATUS_TIP = "Save Session as..." +EXIT_NAME = "Exit" +EXIT_STATUS_TIP = "Exit" + +[MENU_BAR.ACTION.EDIT] +UNDO_NAME = "Undo" +UNDO_STATUS_TIP = "Undo last change" +REDO_NAME = "Redo" +REDO_STATUS_TIP = "Redo last change" +CUT_NAME = "Cut" +CUT_STATUS_TIP = "Cut selected text" +COPY_NAME = "Copy" +COPY_STATUS_TIP = "Copy selected text" +PASTE_NAME = "Paste" +PASTE_STATUS_TIP = "Paste from clipboard" +SELECT_ALL_NAME = "Select all" +SELECT_ALL_STATUS_TIP = "Select all text" +FIND_NAME = "Find..." +FIND_STATUS_TIP = "Find in text" +PREVIEW_NAME = "Preview" +PREVIEW_STATUS_TIP = "Preview" +INSERT_MENU = "Insert element..." + +[MENU_BAR.ACTION.SETTINGS] +TOGGLE_THEME_NAME = "Toggle Theme" +TOGGLE_THEME_STATUS_TIP = "Toggle Theme" +LANGUAGES_MENU = "Languages..." +LOGIN_NAME = "Login" +LOGIN_STATUS_TIP = "Login" +LOGOUT_NAME = "Logout" +LOGOUT_STATUS_TIP = "Logout" + +[DIALOG] +FILES_LIST_LABEL = "Files list" +EMPTY_FILES_LIST_LABEL = "Files list is empty..." + +FINDER_FIND_BUTTON = "Find" +FINDER_REPLACE_BUTTON = "Replace" +FINDER_REPLACE_ALL_BUTTON = "Replace all" +FINDER_NORMAL_MODE = "Normal" +FINDER_REGEX_MODE = "RegEx" + +NEW_VERSION_AVAILABLE_LABEL = "New Skippy version available: v{}!" +YOU_CAN_DOWNLOAD_IT_LABEL = "You can install it by press Install button." +SIGN_IN_TO_WIKIDOT_LABEL = "Sign in to your Wikidot account" +LOGIN_PLACEHOLDER = "Login" +PASSWORD_PLACEHOLDER = "Password" +ENTER_PAGE_LABEL = "Enter page url" +PAGE_BOX_PLACEHOLDER = "Page name" +COMMENT_BOX_PLACEHOLDER = "Comment" +COMMENT_BOX_TEXT = "Edit using Skippy" +GENERATE_BUTTON = "Generate" +INSTALL_BUTTON = "Install" +CANCEL_BUTTON = "Cancel" +OK_BUTTON = "Ok" + +SITE_BOX_LINEEDIT = "Enter wiki url" + +[ELEMENTS] + +[ELEMENTS.BASE_IMAGE] + +[ELEMENTS.BASE_IMAGE.NAME] +NAME = "Name" +DESC = "Image url or local name" + +[ELEMENTS.BASE_IMAGE.CAPTION] +NAME = "Caption" +DESC = "Image caption" + +[ELEMENTS.BASE_IMAGE.WIDTH] +NAME = "Width" +DESC = "Image block width" + +[ELEMENTS.BASE_IMAGE.LINK] +NAME = "Link" +DESC = "Link on clicking" + +[ELEMENTS.RIGHT_IMAGE_BLOCK] +ALIAS = "Right image block" +DESC = "right-aligned image block" + +[ELEMENTS.LEFT_IMAGE_BLOCK] +ALIAS = "Left image block" +DESC = "left-aligned image block" + +[ELEMENTS.CENTER_IMAGE_BLOCK] +ALIAS = "Center image block" +DESC = "centred image block" + +[ELEMENTS.ACS_BAR] +ALIAS = "ACS Bar" +DESC = "Anomaly Classification System Bar component" + +[ELEMENTS.ACS_BAR.ITEM] +NAME = "Item #" +DESC = "Item number of SCP" + +[ELEMENTS.ACS_BAR.CLEARANCE] +NAME = "Clearance" +DESC = "Clearance Level (5,4,3,2,1,0)" + +[ELEMENTS.ACS_BAR.CONTAINER] +NAME = "Container Class" +DESC = "Container Class (Also known as Object Class)" + +[ELEMENTS.ACS_BAR.DISRUPTION] +NAME = "Disruption Class" +DESC = "Object disruption Class" + +[ELEMENTS.ACS_BAR.RISK] +NAME = "Risk Class" +DESC = "Object risk Class" + +[ELEMENTS.ACS_BAR.SEC_CLASS] +NAME = "Secondary class" +DESC = "OPTIONAL Secondary Class (If Using, set Container Class to Esoteric)" + +[ELEMENTS.ACS_BAR.SEC_ICON] +NAME = "Secondary class icon" +DESC = "If secondary class is used, place URL to Class Icon" + + diff --git a/skippy/lang/ru.json b/skippy/lang/ru.json deleted file mode 100644 index 7256efd..0000000 --- a/skippy/lang/ru.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "LANGUAGE": "Русский", - - "MENU_BAR_FILE_MENU": "&Файл", - "MENU_BAR_FILE_TOOLBAE": "Файл", - "MENU_BAR_NEW_ACTION_NAME": "Создать", - "MENU_BAR_NEW_ACTION_STATUS_TIP": "Создать новую страницу", - "MENU_BAR_OPEN_ACTION_NAME": "Открыть", - "MENU_BAR_OPEN_ACTION_STATUS_TIP": "Открыть страницу", - "MENU_BAR_UPLOAD_ACTION_NAME": "Сохранить", - "MENU_BAR_UPLOAD_ACTION_STATUS_TIP": "Сохранить страницу", - "MENU_BAR_UPLOAD_AS_ACTION_NAME": "Сохранить страницу как...", - "MENU_BAR_UPLOAD_AS_ACTION_STATUS_TIP": "Сохранить страницу как...", - "MENU_BAR_CLOSE_ACTION_NAME": "Закрыть", - "MENU_BAR_CLOSE_ACTION_STATUS_TIP": "Закрыть", - "MENU_BAR_LOAD_FILES_ACTION_NAME": "Загрузить файлы...", - "MENU_BAR_LOAD_FILES_ACTION_STATUS_TIP": "Загрузить файлы на текущую страницу", - "MENU_BAR_LOAD_SESSION_ACTION_NAME": "Загрузить сессию...", - "MENU_BAR_LOAD_SESSION_ACTION_STATUS_TIP": "Загрузить сессию...", - "MENU_BAR_SAVE_SESSION_ACTION_NAME": "Сохранить сессию как...", - "MENU_BAR_SAVE_SESSION_ACTION_STATUS_TIP": "Сохранить сессию как...", - "MENU_BAR_EXIT_ACTION_NAME": "Выйти", - "MENU_BAR_EXIT_ACTION_STATUS_TIP": "Выйти", - - "MENU_BAR_EDIT_MENU": "&Редактирование", - "MENU_BAR_EDIT_TOOLBAE": "Редактирование", - "MENU_BAR_UNDO_ACTION_NAME": "Отменить", - "MENU_BAR_UNDO_ACTION_STATUS_TIP": "Отменить последнее изменение", - "MENU_BAR_REDO_ACTION_NAME": "Вернуть", - "MENU_BAR_REDO_ACTION_STATUS_TIP": "Вернуть последнее изменение", - "MENU_BAR_CUT_ACTION_NAME": "Вырезать", - "MENU_BAR_CUT_ACTION_STATUS_TIP": "Вырезать выбранный текст", - "MENU_BAR_COPY_ACTION_NAME": "Скопировать", - "MENU_BAR_COPY_ACTION_STATUS_TIP": "Скопировать выбранный текст", - "MENU_BAR_PASTE_ACTION_NAME": "Вставить", - "MENU_BAR_PASTE_ACTION_STATUS_TIP": "Вставить из буфера обмена", - "MENU_BAR_SELECT_ALL_ACTION_NAME": "Выбрать все", - "MENU_BAR_SELECT_ALL_ACTION_STATUS_TIP": "Выбрать весь текст", - "MENU_BAR_PREVIEW_ACTION_NAME": "Предпросмотр", - "MENU_BAR_PREVIEW_ACTION_STATUS_TIP": "Предпросмотр", - "MENU_BAR_INSERT_MENU": "Вставить элемент...", - - "MENU_BAR_SETTINGS_MENU": "&Настройки", - "MENU_BAR_TOGGLE_THEME_ACTION_NAME": "Переключить тему", - "MENU_BAR_TOGGLE_THEME_ACTION_STATUS_TIP": "Переключить тему", - "MENU_BAR_LANGUAGES_MENU": "Выбрать язык...", - "MENU_BAR_LOGIN_ACTION_NAME": "Войти", - "MENU_BAR_LOGIN_ACTION_STATUS_TIP": "Войти в аккаунт", - "MENU_BAR_LOGOUT_ACTION_NAME": "Выйти", - "MENU_BAR_LOGOUT_ACTION_STATUS_TIP": "Выйти из аккаунта", - - "SIGNED_IN_AS_LABEL": "Вошел как {}", - - "FILES_BUTTON": "Файлы", - "FILES_DIALOG_FILES_LIST_LABEL": "Список файлов", - "FILES_DIALOG_EMPTY_FILES_LIST_LABEL": "Здесь пусто", - - "DIALOG_NEW_VERSION_AVAILABLE_LABEL": "Доступна новая версия Skippy: v{}!", - "DIALOG_YOU_CAN_DOWNLOAD_IT_LABEL": "Вы можете установить его нажав на кнопку Установить.", - "DIALOG_SIGN_IN_TO_WIKIDOT_LABEL": "Войдите в ваш аккаунт Wikidot", - "DIALOG_LOGIN_PLACEHOLDER": "Логин", - "DIALOG_PASSWORD_PLACEHOLDER": "Пароль", - "DIALOG_ENTER_PAGE_LABEL": "Введите адрес страницы", - "DIALOG_PAGE_BOX_PLACEHOLDER": "Имя страницы", - "DIALOG_COMMENT_BOX_PLACEHOLDER": "Комментарий", - "DIALOG_COMMENT_BOX_TEXT": "Edit using Skippy", - "DIALOG_GENERATE_BUTTON": "Сгенерировать", - "DIALOG_INSTALL_BUTTON": "Установить", - "DIALOG_CANCEL_BUTTON": "Отменить", - "DIALOG_OK_BUTTON": "Ок", - - "SITE_BOX_LINEEDIT": "Введите адрес сайта" -} \ No newline at end of file diff --git a/skippy/lang/ru.toml b/skippy/lang/ru.toml new file mode 100644 index 0000000..0271f2f --- /dev/null +++ b/skippy/lang/ru.toml @@ -0,0 +1,151 @@ +LANGUAGE = "Русский" + +[MAIN] +SIGNED_IN_AS_LABEL = "Вошел как {}" +FILES_BUTTON = "Файлы" + +[MENU_BAR] +FILE_MENU = "&Файл" +EDIT_MENU = "&Редактирование" +SETTINGS_MENU = "&Настройки" + +[MENU_BAR.ACTION] + +[MENU_BAR.ACTION.FILE] +NEW_NAME = "Создать" +NEW_STATUS_TIP = "Создать новую страницу" +OPEN_NAME = "Открыть" +OPEN_STATUS_TIP = "Открыть страницу" +UPLOAD_NAME = "Сохранить" +UPLOAD_STATUS_TIP = "Сохранить страницу" +UPLOAD_AS_NAME = "Сохранить как..." +UPLOAD_AS_STATUS_TIP = "Сохранить страницу как..." +CLOSE_NAME = "Закрыть" +CLOSE_STATUS_TIP = "Закрыть страницу" +LOAD_FILES_NAME = "Загрузить файлы..." +LOAD_FILES_STATUS_TIP = "Загрузить файлы на текущую страницу" +LOAD_SESSION_NAME = "Загрузить сессию..." +LOAD_SESSION_STATUS_TIP = "Загрузить сессию..." +SAVE_SESSION_NAME = "Сохранить сессию как..." +SAVE_SESSION_STATUS_TIP = "Сохранить сессию как..." +EXIT_NAME = "Выйти" +EXIT_STATUS_TIP = "Выйти" + +[MENU_BAR.ACTION.EDIT] +UNDO_NAME = "Отменить" +UNDO_STATUS_TIP = "Отменить последнее изменение" +REDO_NAME = "Вернуть" +REDO_STATUS_TIP = "Вернуть последнее изменение" +CUT_NAME = "Вырезать" +CUT_STATUS_TIP = "Вырезать выбранный текст" +COPY_NAME = "Скопировать" +COPY_STATUS_TIP = "Скопировать выбранный текст" +PASTE_NAME = "Вставить" +PASTE_STATUS_TIP = "Вставить из буфера обмена" +SELECT_ALL_NAME = "Выбрать все" +SELECT_ALL_STATUS_TIP = "Выбрать весь текст" +FIND_NAME = "Найти..." +FIND_STATUS_TIP = "Найти в тексте" +PREVIEW_NAME = "Предпросмотр" +PREVIEW_STATUS_TIP = "Предпросмотр" +INSERT_MENU = "Вставить элемент..." + +[MENU_BAR.ACTION.SETTINGS] +TOGGLE_THEME_NAME = "Переключить тему" +TOGGLE_THEME_STATUS_TIP = "Переключить тему" +LANGUAGES_MENU = "Выбрать язык..." +LOGIN_NAME = "Войти" +LOGIN_STATUS_TIP = "Войти в аккаунт" +LOGOUT_NAME = "Выйти" +LOGOUT_STATUS_TIP = "Выйти из аккаунта" + +[DIALOG] +FILES_LIST_LABEL = "Список файлов" +EMPTY_FILES_LIST_LABEL = "Здесь пусто" + +FINDER_FIND_BUTTON = "Найти" +FINDER_REPLACE_BUTTON = "Заменить" +FINDER_REPLACE_ALL_BUTTON = "Заменить всё" +FINDER_NORMAL_MODE = "Обычный" +FINDER_REGEX_MODE = "RegEx" + +NEW_VERSION_AVAILABLE_LABEL = "Доступна новая версия Skippy: v{}!" +YOU_CAN_DOWNLOAD_IT_LABEL = "Вы можете установить его нажав на кнопку Установить." +SIGN_IN_TO_WIKIDOT_LABEL = "Войдите в ваш аккаунт Wikidot" +LOGIN_PLACEHOLDER = "Логин" +PASSWORD_PLACEHOLDER = "Пароль" +ENTER_PAGE_LABEL = "Введите адрес страницы" +PAGE_BOX_PLACEHOLDER = "Имя страницы" +COMMENT_BOX_PLACEHOLDER = "Комментарий" +COMMENT_BOX_TEXT = "Edit using Skippy" +GENERATE_BUTTON = "Сгенерировать" +INSTALL_BUTTON = "Установить" +CANCEL_BUTTON = "Отменить" +OK_BUTTON = "Ок" + +SITE_BOX_LINEEDIT = "Введите адрес сайта" + +[ELEMENTS] + +[ELEMENTS.BASE_IMAGE] + +[ELEMENTS.BASE_IMAGE.NAME] +NAME = "Название" +DESC = "Локальное название картинки или ссылка на него" + +[ELEMENTS.BASE_IMAGE.CAPTION] +NAME = "Подпись" +DESC = "Подпись под картинкой" + +[ELEMENTS.BASE_IMAGE.WIDTH] +NAME = "Ширина" +DESC = "Ширина блока с картинкой" + +[ELEMENTS.BASE_IMAGE.LINK] +NAME = "Ссылка" +DESC = "Ссылка при нажатии" + +[ELEMENTS.RIGHT_IMAGE_BLOCK] +ALIAS = "Правый блок изображения" +DESC = "блок изображения с выравниванием по правому краю" + +[ELEMENTS.LEFT_IMAGE_BLOCK] +ALIAS = "Левый блок изображения" +DESC = "блок изображения с выравниванием по левому краю" + +[ELEMENTS.CENTER_IMAGE_BLOCK] +ALIAS = "Центральный блок изображения" +DESC = "блок изображения по центру" + +[ELEMENTS.ACS_BAR] +ALIAS = "Плашка ACS" +DESC = "компонент плашки Системы Классификации Аномалий" + +[ELEMENTS.ACS_BAR.ITEM] +NAME = "Объект №" +DESC = "Номер объекта" + +[ELEMENTS.ACS_BAR.CLEARANCE] +NAME = "Уровень допуска" +DESC = "Уровень допуска (5,4,3,2,1,0)" + +[ELEMENTS.ACS_BAR.CONTAINER] +NAME = "Класс содержания" +DESC = "Класс содержания (Также известен как Класс объекта)" + +[ELEMENTS.ACS_BAR.DISRUPTION] +NAME = "Класс нарушения" +DESC = "Класс нарушения объекта" + +[ELEMENTS.ACS_BAR.RISK] +NAME = "Класс риска" +DESC = "Класс риска объекта" + +[ELEMENTS.ACS_BAR.SEC_CLASS] +NAME = "Вторичный класс" +DESC = "Вторичный класс (поставьте Классом содержания \"Нестандартный\")" + +[ELEMENTS.ACS_BAR.SEC_ICON] +NAME = "Иконка вторичного класса" +DESC = "Если используется вторичный класс, вставьте ссылку на его иконку" + diff --git a/skippy/utils/discord_rpc.py b/skippy/utils/discord_rpc.py index 55aed20..e1b5891 100644 --- a/skippy/utils/discord_rpc.py +++ b/skippy/utils/discord_rpc.py @@ -17,7 +17,7 @@ def __init__(self): self._words = 0 self._letters = 0 - self._time = time.mktime(time.localtime()) + self._time = int(time.mktime(time.localtime())) @ignore((PyPresenceException, StructError)) def connect(self): @@ -29,7 +29,7 @@ def connect(self): def update(self, title: str, words: int = 0, letters: int = 0): super(DiscordRPC, self).update( state=f"Words: {str(words)}, Letters: {str(letters)}", - details=f'Writing an "{title}"', + details=f"Writing an \"{title}\"", start=self._time, small_text="Skippy", small_image="none", diff --git a/skippy/utils/standarddir.py b/skippy/utils/standarddir.py index a2af7ef..265bcbb 100644 --- a/skippy/utils/standarddir.py +++ b/skippy/utils/standarddir.py @@ -13,6 +13,7 @@ def initdirs(): makedir(skippy.config.APPDATA_FOLDER) makedir(skippy.config.LOGS_FOLDER) makedir(skippy.config.PROPERTY_FOLDER) + makedir(skippy.config.PLUGINS_SETTINGS_FOLDER) def makedir(path: str): diff --git a/skippy/utils/translator.py b/skippy/utils/translator.py index 1f714ce..825c36e 100644 --- a/skippy/utils/translator.py +++ b/skippy/utils/translator.py @@ -4,7 +4,7 @@ import skippy.config from typing import List -import json +import toml import os @@ -27,7 +27,7 @@ def languages() -> List[str]: os.path.splitext(file)[0] for file in os.listdir(skippy.config.LANG_FOLDER) if os.path.isfile(os.path.join(skippy.config.LANG_FOLDER, file)) - if os.path.splitext(file)[1] == ".json" + if os.path.splitext(file)[1] == ".toml" ] @staticmethod @@ -40,9 +40,9 @@ def getLangName(lang: str) -> str: Returns: str: Language name """ - path = os.path.join(skippy.config.LANG_FOLDER, f"{lang}.json") + path = os.path.join(skippy.config.LANG_FOLDER, f"{lang}.toml") with open(path, encoding="utf-8") as f: - return json.loads(f.read())["LANGUAGE"] + return toml.load(f)["LANGUAGE"] def load(self, lang: str = "en") -> Language: """Load language by code @@ -53,9 +53,9 @@ def load(self, lang: str = "en") -> Language: Returns: Language: Language namedtuple, with language code and dictionary """ - path = os.path.join(skippy.config.LANG_FOLDER, f"{lang}.json") + path = os.path.join(skippy.config.LANG_FOLDER, f"{lang}.toml") with open(path, encoding="utf-8") as f: - dictionary = json.loads(f.read()) + dictionary = toml.load(f) self._language = Language(lang, dictionary) return self._language @@ -70,6 +70,9 @@ def translate(self, context: str) -> str: str: Translated string if available, else return input context """ try: - return self._language.dictionary[context] + out = self._language.dictionary + for text in context.split("."): + out = out[text] + return out except KeyError: return context