├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── main.yml ├── .gitignore ├── .reuse └── dep5 ├── LICENSES ├── CC0-1.0.txt ├── GPL-3.0-or-later.txt ├── LGPL-3.0-only.txt ├── MIT.txt └── Unlicense.txt ├── README.md ├── data ├── icons │ ├── hicolor │ │ ├── scalable │ │ │ └── apps │ │ │ │ ├── me.iepure.devtoolbox.Devel.svg │ │ │ │ └── me.iepure.devtoolbox.svg │ │ └── symbolic │ │ │ └── apps │ │ │ └── me.iepure.devtoolbox-symbolic.svg │ ├── meson.build │ └── symbolic │ │ ├── arrow-pointing-away-from-line-left-symbolic.svg │ │ ├── arrow-pointing-away-from-line-right-symbolic.svg │ │ ├── base64-symbolic.svg │ │ ├── brush-symbolic.svg │ │ ├── calendar-symbolic.svg │ │ ├── certificate-parser-symbolic.svg │ │ ├── certificate-symbolic.svg │ │ ├── chain-link-symbolic.svg │ │ ├── check-plain-symbolic.svg │ │ ├── check-round-outline-symbolic.svg │ │ ├── checkerboard-big-symbolic.svg │ │ ├── clock-alt-symbolic.svg │ │ ├── code-symbolic.svg │ │ ├── color-symbolic.svg │ │ ├── csr-symbolic.svg │ │ ├── css-symbolic.svg │ │ ├── database-symbolic.svg │ │ ├── dice3-symbolic.svg │ │ ├── down-symbolic.svg │ │ ├── earth-symbolic.svg │ │ ├── edit-symbolic.svg │ │ ├── encode-symbolic.svg │ │ ├── error-symbolic.svg │ │ ├── eye-open-symbolic.svg │ │ ├── fingerprint-symbolic.svg │ │ ├── floppy-symbolic.svg │ │ ├── folder-templates-symbolic.svg │ │ ├── format-indent-more-symbolic.svg │ │ ├── format-text-rich-symbolic.svg │ │ ├── general-properties-symbolic.svg │ │ ├── hash-symbolic (copy).svg │ │ ├── hash-symbolic.svg │ │ ├── hashtag-symbolic.svg │ │ ├── horizontal-arrows-symbolic.svg │ │ ├── hourglass-symbolic.svg │ │ ├── html-symbolic.svg │ │ ├── image-adjust-contrast-symbolic.svg │ │ ├── image-symbolic.svg │ │ ├── info-symbolic.svg │ │ ├── js-symbolic.svg │ │ ├── json-check-symbolic.svg │ │ ├── json-symbolic.svg │ │ ├── key-symbolic.svg │ │ ├── list-compact-symbolic.svg │ │ ├── list-large-symbolic.svg │ │ ├── loupe-symbolic.svg │ │ ├── lowercase-symbolic.svg │ │ ├── markdown-symbolic.svg │ │ ├── newspaper-symbolic.svg │ │ ├── non-starred-symbolic.svg │ │ ├── open-book-symbolic.svg │ │ ├── padlock2-symbolic.svg │ │ ├── paper-filled-symbolic.svg │ │ ├── plus-symbolic.svg │ │ ├── qr-code-symbolic.svg │ │ ├── regex-symbolic.svg │ │ ├── select-mode-symbolic.svg │ │ ├── shoe-box-symbolic.svg │ │ ├── sidebar-symbolic.svg │ │ ├── size-horizontally-symbolic.svg │ │ ├── spacebar-symbolic.svg │ │ ├── stacked-plates-symbolic.svg │ │ ├── starred-symbolic.svg │ │ ├── symbols-symbolic.svg │ │ ├── text-ab-symbolic.svg │ │ ├── text-direction-ltr-symbolic.svg │ │ ├── text-indent-symbolic.svg │ │ ├── text-inspector-symbolic.svg │ │ ├── text-symbolic.svg │ │ ├── timer-reverse-symbolic.svg │ │ ├── timer-symbolic.svg │ │ ├── today-alt-symbolic.svg │ │ ├── up-symbolic.svg │ │ ├── update-symbolic.svg │ │ ├── uppercase-symbolic.svg │ │ ├── warning-symbolic.svg │ │ ├── weight-symbolic.svg │ │ └── xml-check-symbolic.svg ├── markdown-preview │ ├── fenced-code-colors-dark.css │ ├── fenced-code-colors-light.css │ ├── github-markdown.css │ └── scrollpos-script.js ├── me.iepure.devtoolbox.desktop.in.in ├── me.iepure.devtoolbox.gschema.xml ├── me.iepure.devtoolbox.metainfo.xml.in ├── meson.build └── screenshots │ ├── 1.png │ ├── 2.png │ └── 3.png ├── install └── devtoolbox-run-script ├── me.iepure.devtoolbox.json ├── meson.build ├── po ├── LINGUAS ├── POTFILES ├── ar.po ├── bg.po ├── cs.po ├── de.po ├── devtoolbox.pot ├── es.po ├── et.po ├── fi.po ├── fr.po ├── ia.po ├── it.po ├── ja.po ├── meson.build ├── nb_NO.po ├── pt.po ├── pt_BR.po ├── ru.po ├── sk.po ├── ta.po ├── tr.po ├── ur.po ├── zh_Hans.po └── zh_Hant.po ├── pypi-dependencies.json ├── requirements.txt └── src ├── __init__.py ├── css ├── style-dark-hc.css ├── style-dark.css ├── style-hc.css └── style.css ├── devtoolbox.gresource.xml ├── devtoolbox.in ├── formatters ├── __init__.py ├── css.py ├── css_minifier.py ├── formatter.py ├── html.py ├── js.py ├── js_minifier.py ├── json.py ├── meson.build ├── sql.py └── xml.py ├── main.py ├── meson.build ├── services ├── __init__.py ├── base64_encoder.py ├── certificate_parser.py ├── colorblindness_simulator.py ├── cron_converter.py ├── gzip_compressor.py ├── hash_generator.py ├── html_encoder.py ├── image_converter.py ├── json_validator.py ├── json_yaml.py ├── jwt_decoder.py ├── lorem_generator.py ├── markdown_preview.py ├── meson.build ├── regex_tester.py ├── text_diff.py ├── text_inspector.py ├── url_encoder.py ├── uuid_generator.py └── xml_validator.py ├── ui ├── about_dialog.blp ├── gtk │ └── help-overlay.blp ├── views │ ├── base64_encoder.blp │ ├── base_converter.blp │ ├── certificate_parser.blp │ ├── certificate_request_generator.blp │ ├── chmod_calculator.blp │ ├── colorblindness_simulator.blp │ ├── contrast_checker.blp │ ├── cron_converter.blp │ ├── formatter.blp │ ├── gzip_compressor.blp │ ├── hash_generator.blp │ ├── html_encoder.blp │ ├── image_converter.blp │ ├── json_validator.blp │ ├── json_yaml.blp │ ├── jwt_decoder.blp │ ├── lorem_generator.blp │ ├── markdown_preview.blp │ ├── qrcode_generator.blp │ ├── random_generator.blp │ ├── regex_tester.blp │ ├── reverse_cron.blp │ ├── text_diff.blp │ ├── text_inspector.blp │ ├── timestamp.blp │ ├── url_encoder.blp │ ├── uuid_generator.blp │ └── xml_validator.blp ├── widgets │ ├── date_area.blp │ ├── entry_row.blp │ ├── file_view.blp │ ├── image_area.blp │ ├── spin_area.blp │ ├── text_area.blp │ ├── text_file_area.blp │ ├── theme_switcher.blp │ ├── utility_title.blp │ └── webview_area.blp └── window.blp ├── utils.py ├── views ├── __init__.py ├── base64_encoder.py ├── base_converter.py ├── certificate_parser.py ├── certificate_request_generator.py ├── chmod_calculator.py ├── colorblindness_simulator.py ├── contrast_checker.py ├── cron_converter.py ├── formatter.py ├── gzip_compressor.py ├── hash_generator.py ├── html_encoder.py ├── image_converter.py ├── json_validator.py ├── json_yaml.py ├── jwt_decoder.py ├── lorem_generator.py ├── markdown_preview.py ├── meson.build ├── qrcode_generator.py ├── random_generator.py ├── regex_tester.py ├── reverse_cron.py ├── text_diff.py ├── text_inspector.py ├── timestamp.py ├── url_encoder.py ├── uuid_generator.py └── xml_validator.py ├── widgets ├── __init__.py ├── date_area.py ├── entry_row.py ├── file_view.py ├── image_area.py ├── meson.build ├── sidebar_item.py ├── spin_area.py ├── text_area.py ├── text_file_area.py ├── theme_switcher.py ├── utility_title.py └── webview_area.py └── window.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the issue** 11 | A clear and concise description of what the issue is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Operating system (please complete the following information):** 27 | - Distro: 28 | - Version: 29 | - Desktop Environment: 30 | - Xorg/Wayland: 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | name: Build 6 | jobs: 7 | flatpak: 8 | name: "Build Flatpak" 9 | runs-on: ubuntu-latest 10 | container: 11 | image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-48 12 | options: --privileged 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 16 | with: 17 | bundle: devtoolbox-devel.flatpak 18 | manifest-path: me.iepure.devtoolbox.json 19 | cache-key: flatpak-builder-${{ github.sha }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | .flatpak-builder/ 6 | .flatpak/ 7 | .vscode/ 8 | builddir/ 9 | _build/ 10 | 11 | .pre-commit-config.yaml 12 | me.iepure.devtoolbox-release.json 13 | po/*.mo 14 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: devtoolbox 3 | Upstream-Contact: Alessandro Iepure 4 | Source: https://github.com/aleiepure/devtoolbox 5 | 6 | Files: data/icons/symbolic/* 7 | Copyright: GNOME Project 8 | License: LGPL-3.0-only 9 | 10 | Files: data/icons/symbolic/spacebar-symbolic.svg, data/icons/symbolic/base64-symbolic.svg, data/icons/symbolic/json-symbolic.svg, data/icons/symbolic/database-symbolic.svg, data/icons/symbolic/hash-symbolic.svg, data/icons/symbolic/text-inspector-symbolic.svg 11 | Copyright: 2022 - 2023 Alessandro 12 | License: GPL-3.0-or-later 13 | 14 | Files: data/icons/hicolor/scalable/apps/* 15 | Copyright: 2022 - 2023 Alessandro 16 | License: GPL-3.0-or-later 17 | 18 | Files: data/icons/hicolor/symbolic/apps/* 19 | Copyright: 2022 - 2023 Alessandro 20 | License: GPL-3.0-or-later 21 | 22 | Files: pypi-dependencies.json 23 | Copyright: 2022 - 2023 Alessandro 24 | License: GPL-3.0-or-later 25 | 26 | Files: me.iepure.devtoolbox.json 27 | Copyright: 2022 - 2023 Alessandro 28 | License: GPL-3.0-or-later 29 | 30 | Files: me.iepure.devtoolbox-release.json 31 | Copyright: 2022 - 2023 Alessandro 32 | License: GPL-3.0-or-later 33 | 34 | Files: requirements.txt 35 | Copyright: 2022 - 2023 Alessandro 36 | License: GPL-3.0-or-later 37 | 38 | Files: .pre-commit-config.yaml 39 | Copyright: 2022 - 2023 Alessandro 40 | License: GPL-3.0-or-later 41 | 42 | Files: .github/* 43 | Copyright: 2022 - 2023 Alessandro 44 | License: GPL-3.0-or-later 45 | 46 | Files: data/appstream/* 47 | Copyright: 2022 - 2023 Alessandro 48 | License: CC0-1.0 49 | 50 | Files: po/POTFILES 51 | Copyright: 2022 - 2023 Alessandro 52 | License: CC0-1.0 53 | 54 | Files: po/LINGUAS 55 | Copyright: 2022 - 2023 Alessandro 56 | License: CC0-1.0 57 | 58 | Files: po/devtoolbox.pot 59 | Copyright: 2022 - 2023 Alessandro 60 | License: CC0-1.0 61 | 62 | Files: po/*.po 63 | Copyright: see file header 64 | License: GPL-3.0-or-later 65 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /LICENSES/Unlicense.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. 4 | 5 | In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and 6 | successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | 10 | For more information, please refer to 11 | -------------------------------------------------------------------------------- /data/icons/hicolor/symbolic/apps/me.iepure.devtoolbox-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/meson.build: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | application_id = 'me.iepure.devtoolbox' 6 | 7 | if get_option('debug') == true 8 | scalable_dir = join_paths('hicolor', 'scalable', 'apps') 9 | install_data( 10 | join_paths(scalable_dir, ('@0@.Devel.svg').format(application_id)), 11 | install_dir: join_paths(get_option('datadir'), 'icons', scalable_dir), 12 | rename: [('@0@.svg').format(application_id)] 13 | ) 14 | else 15 | scalable_dir = join_paths('hicolor', 'scalable', 'apps') 16 | install_data( 17 | join_paths(scalable_dir, ('@0@.svg').format(application_id)), 18 | install_dir: join_paths(get_option('datadir'), 'icons', scalable_dir) 19 | ) 20 | endif 21 | 22 | symbolic_dir = join_paths('hicolor', 'symbolic', 'apps') 23 | install_data( 24 | join_paths(symbolic_dir, ('@0@-symbolic.svg').format(application_id)), 25 | install_dir: join_paths(get_option('datadir'), 'icons', symbolic_dir) 26 | ) 27 | -------------------------------------------------------------------------------- /data/icons/symbolic/arrow-pointing-away-from-line-left-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/icons/symbolic/arrow-pointing-away-from-line-right-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/icons/symbolic/brush-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/calendar-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/certificate-parser-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 43 | -------------------------------------------------------------------------------- /data/icons/symbolic/certificate-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /data/icons/symbolic/chain-link-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/check-plain-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/check-round-outline-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/checkerboard-big-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/clock-alt-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/icons/symbolic/code-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/color-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/css-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /data/icons/symbolic/database-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/dice3-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/icons/symbolic/down-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/icons/symbolic/earth-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/edit-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/icons/symbolic/encode-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/error-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/eye-open-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/fingerprint-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/floppy-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/folder-templates-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/format-indent-more-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/format-text-rich-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/general-properties-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/icons/symbolic/hash-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/hashtag-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/horizontal-arrows-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/hourglass-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/html-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/image-adjust-contrast-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/image-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/info-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/js-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /data/icons/symbolic/key-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/list-compact-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/list-large-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/loupe-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/icons/symbolic/lowercase-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 35 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /data/icons/symbolic/markdown-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/newspaper-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/non-starred-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/open-book-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/padlock2-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/icons/symbolic/paper-filled-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/plus-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/qr-code-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/icons/symbolic/regex-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/select-mode-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/shoe-box-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/sidebar-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/size-horizontally-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/icons/symbolic/spacebar-symbolic.svg: -------------------------------------------------------------------------------- 1 | ic_fluent_spacebar_24_filledCreated with Sketch. -------------------------------------------------------------------------------- /data/icons/symbolic/stacked-plates-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/starred-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/symbols-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 55 | -------------------------------------------------------------------------------- /data/icons/symbolic/text-ab-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/text-direction-ltr-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/text-indent-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/text-inspector-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/text-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/timer-reverse-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 35 | 39 | 40 | -------------------------------------------------------------------------------- /data/icons/symbolic/timer-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/icons/symbolic/today-alt-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/up-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/icons/symbolic/update-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/icons/symbolic/uppercase-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/icons/symbolic/warning-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/weight-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/symbolic/xml-check-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/markdown-preview/scrollpos-script.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 - 2023 Alessandro Iepure 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | document.addEventListener("DOMContentLoaded", function (event) { 8 | var scrollpos = sessionStorage.getItem('scrollpos'); 9 | if (scrollpos) { 10 | window.scrollTo(0, scrollpos); 11 | sessionStorage.removeItem('scrollpos'); 12 | } 13 | }); 14 | 15 | window.addEventListener("beforeunload", function (e) { 16 | sessionStorage.setItem('scrollpos', window.scrollY); 17 | }); 18 | -------------------------------------------------------------------------------- /data/me.iepure.devtoolbox.desktop.in.in: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | [Desktop Entry] 6 | # TRANSLATORS: do not translate 7 | Name=@app-name@ 8 | Comment=Dev tools at your fingertips 9 | Exec=devtoolbox 10 | Icon=me.iepure.devtoolbox 11 | Terminal=false 12 | Type=Application 13 | Categories=Development;GTK; 14 | Keywords=JSON;YAML;YML;Converter;CRON;Parser;GZip;Compressor;Decompressor;Hash;Regex;Markdown;Image; 15 | StartupNotify=true 16 | -------------------------------------------------------------------------------- /data/me.iepure.devtoolbox.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 1200 15 | 16 | 17 | 850 18 | 19 | 20 | false 21 | 22 | 23 | "json-yaml" 24 | 25 | 26 | true 27 | 28 | 29 | 30 | 31 | [] 32 | 33 | 34 | 35 | 36 | "auto" 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | desktop_conf = configuration_data() 6 | if get_option('debug') 7 | desktop_conf.set('app-name', 'Dev Toolbox (Development snapshot)') 8 | else 9 | desktop_conf.set('app-name', 'Dev Toolbox') 10 | endif 11 | desktop_file = i18n.merge_file( 12 | input: configure_file( 13 | output: 'me.iepure.devtoolbox.desktop.in', 14 | input: 'me.iepure.devtoolbox.desktop.in.in', 15 | configuration: desktop_conf 16 | ), 17 | output: 'me.iepure.devtoolbox.desktop', 18 | type: 'desktop', 19 | po_dir: '../po', 20 | install: true, 21 | install_dir: join_paths(get_option('datadir'), 'applications') 22 | ) 23 | 24 | desktop_utils = find_program('desktop-file-validate', required: false) 25 | if desktop_utils.found() 26 | test('Validate desktop file', desktop_utils, args: [desktop_file]) 27 | endif 28 | 29 | appstream_file = i18n.merge_file( 30 | input: 'me.iepure.devtoolbox.metainfo.xml.in', 31 | output: 'me.iepure.devtoolbox.metainfo.xml', 32 | po_dir: '../po', 33 | install: true, 34 | install_dir: get_option('datadir') / 'metainfo' 35 | ) 36 | 37 | appstreamcli = find_program('appstreamcli', required: false, disabler: true) 38 | test('Validate appstream file', appstreamcli, 39 | args: ['validate', '--no-net', '--explain', appstream_file]) 40 | 41 | install_data('me.iepure.devtoolbox.gschema.xml', 42 | install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas') 43 | ) 44 | 45 | compile_schemas = find_program('glib-compile-schemas', required: false) 46 | if compile_schemas.found() 47 | test('Validate schema file', 48 | compile_schemas, 49 | args: ['--strict', '--dry-run', meson.current_source_dir()]) 50 | endif 51 | 52 | subdir('icons') 53 | -------------------------------------------------------------------------------- /data/screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleiepure/devtoolbox/4a476cb969be983973b746040c832f1e6ae0119b/data/screenshots/1.png -------------------------------------------------------------------------------- /data/screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleiepure/devtoolbox/4a476cb969be983973b746040c832f1e6ae0119b/data/screenshots/2.png -------------------------------------------------------------------------------- /data/screenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleiepure/devtoolbox/4a476cb969be983973b746040c832f1e6ae0119b/data/screenshots/3.png -------------------------------------------------------------------------------- /install/devtoolbox-run-script: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # Copyright (C) 2022 - 2023 Alessandro Iepure 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | export TMPDIR="$XDG_RUNTIME_DIR/app/me.iepure.devtoolbox" 7 | exec /app/bin/devtoolbox-bin "$@" 8 | -------------------------------------------------------------------------------- /me.iepure.devtoolbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "me.iepure.devtoolbox", 3 | "runtime" : "org.gnome.Platform", 4 | "runtime-version" : "48", 5 | "sdk" : "org.gnome.Sdk", 6 | "command" : "devtoolbox", 7 | "separate-locales": false, 8 | "finish-args" : [ 9 | "--share=network", 10 | "--share=ipc", 11 | "--socket=fallback-x11", 12 | "--device=dri", 13 | "--socket=wayland", 14 | "--talk-name=org.gtk.vfs.*", 15 | "--filesystem=xdg-run/gvfsd" 16 | ], 17 | "cleanup" : [ 18 | "/include", 19 | "/lib/pkgconfig", 20 | "/man", 21 | "/share/doc", 22 | "/share/gtk-doc", 23 | "/share/man", 24 | "/share/pkgconfig", 25 | "*.la", 26 | "*.a" 27 | ], 28 | "modules" : [ 29 | "pypi-dependencies.json", 30 | { 31 | "name": "blueprint-compiler", 32 | "buildsystem": "meson", 33 | "cleanup": ["*"], 34 | "sources": [ 35 | { 36 | "type": "git", 37 | "url": "https://gitlab.gnome.org/jwestman/blueprint-compiler", 38 | "tag": "v0.16.0" 39 | } 40 | ] 41 | }, 42 | { 43 | "name" : "devtoolbox", 44 | "builddir" : true, 45 | "buildsystem" : "meson", 46 | "post-install": [ 47 | "mv /app/bin/devtoolbox /app/bin/devtoolbox-bin", 48 | "install -Dm755 /app/devtoolbox/devtoolbox-run-script /app/bin/devtoolbox" 49 | ], 50 | "sources": [ 51 | { 52 | "type": "dir", 53 | "path": "." 54 | } 55 | ] 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | project('devtoolbox', 6 | version: '1.2.4', 7 | meson_version: '>= 0.59.0', 8 | default_options: [ 'warning_level=2', 'werror=false', ], 9 | ) 10 | 11 | i18n = import('i18n') 12 | gnome = import('gnome') 13 | 14 | subdir('data') 15 | subdir('src') 16 | subdir('po') 17 | 18 | install_data('install/devtoolbox-run-script', install_dir: join_paths(get_option('prefix'), meson.project_name())) 19 | 20 | gnome.post_install( 21 | glib_compile_schemas: true, 22 | gtk_update_icon_cache: true, 23 | update_desktop_database: true, 24 | ) 25 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | cs 2 | it 3 | pt_BR 4 | tr 5 | nb_NO 6 | pt 7 | es 8 | ta 9 | zh_Hans 10 | fi 11 | ja 12 | zh_Hant 13 | sk 14 | fr 15 | ru 16 | de 17 | ar 18 | bg 19 | ur 20 | ia 21 | et 22 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | i18n.gettext('devtoolbox', preset: 'glib') 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ruamel.yaml 2 | python-dateutil 3 | humanize 4 | python-crontab 5 | croniter 6 | python-crontab 7 | pyjwt 8 | sqlparse 9 | lxml 10 | python-lorem 11 | uuid6 12 | textstat 13 | markdown2 14 | Pygments 15 | 16 | meson-python 17 | numpy 18 | 19 | Pillow 20 | daltonlens 21 | asn1crypto 22 | pytz 23 | tzlocal 24 | jsbeautifier 25 | qrcode 26 | jsonschema==4.17.3 27 | cssbeautifier 28 | rcssmin 29 | rjsmin -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | -------------------------------------------------------------------------------- /src/css/style-dark-hc.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 - 2023 Alessandro Iepure 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | /* Dark High contrast CSS - overrides style-dark.css */ 8 | 9 | textarea textview, textarea box.view, 10 | textfilearea textview, textfilearea box.view, textfilearea picture, textfilearea fileview, 11 | imagearea picture, imagearea box.view, 12 | webarea webkitwebview, 13 | row text.view 14 | { 15 | border: 1px solid #9a9a9a; 16 | } 17 | -------------------------------------------------------------------------------- /src/css/style-dark.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 - 2023 Alessandro Iepure 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | /* Dark mode CSS - overrides style.css */ 8 | -------------------------------------------------------------------------------- /src/css/style-hc.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 - 2023 Alessandro Iepure 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | /* Light High contrast CSS - overrides style.css */ 8 | 9 | textarea textview, textarea box.view, 10 | textfilearea textview, textfilearea box.view, textfilearea picture, textfilearea fileview, 11 | imagearea picture, imagearea box.view, 12 | webarea webkitwebview, 13 | row text.view 14 | { 15 | border: 1px solid #919191; 16 | } 17 | -------------------------------------------------------------------------------- /src/css/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 - 2023 Alessandro Iepure 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | /* Light mode CSS - Main style */ 8 | 9 | .rounded-border { 10 | border-top-right-radius: 12px; 11 | border-top-left-radius: 12px; 12 | border-bottom-right-radius: 12px; 13 | border-bottom-left-radius: 12px; 14 | } 15 | 16 | .padding-left-8 { 17 | padding-left: 8px; 18 | } 19 | 20 | .padding-5 { 21 | padding-left: 5px; 22 | padding-right: 5px; 23 | padding-bottom: 5px; 24 | padding-top: 2px; 25 | } 26 | 27 | .border-red { 28 | border: 1px solid red; 29 | } 30 | 31 | .padding-20{ 32 | padding: 20px; 33 | } 34 | 35 | dropdown, dropdown box, dropdown > entry, dropdown > button { 36 | background-color: transparent; 37 | box-shadow: none; 38 | border: none; 39 | font-weight: normal; 40 | } 41 | 42 | /* Theme Switcher */ 43 | /* Modified from https://gitlab.gnome.org/tijder/blueprintgtk */ 44 | /* Original header below */ 45 | 46 | /* Base on dialect-app */ 47 | /* https://github.com/dialect-app/dialect/blob/c0b7ca0580d4c7cfb32ff7ed0a3a08c06bbe40e0/data/resources/style.css */ 48 | 49 | .themeswitcher { 50 | margin: 9px; 51 | } 52 | 53 | .themeswitcher checkbutton { 54 | padding: 0; 55 | min-height: 44px; 56 | min-width: 44px; 57 | padding: 1px; 58 | background-clip: content-box; 59 | border-radius: 9999px; 60 | box-shadow: inset 0 0 0 1px @borders; 61 | } 62 | 63 | .themeswitcher checkbutton.system:checked, 64 | .themeswitcher checkbutton.light:checked, 65 | .themeswitcher checkbutton.dark:checked { 66 | box-shadow: inset 0 0 0 2px @theme_selected_bg_color; 67 | } 68 | 69 | .themeswitcher checkbutton.system { 70 | background-image: linear-gradient(to bottom right, #fff 49.99%, #202020 50.01%); 71 | } 72 | 73 | .themeswitcher checkbutton.light { 74 | background-color: #fff; 75 | } 76 | 77 | .themeswitcher checkbutton.dark { 78 | background-color: #202020; 79 | } 80 | 81 | .themeswitcher checkbutton radio { 82 | -gtk-icon-source: none; 83 | border: none; 84 | background: none; 85 | box-shadow: none; 86 | min-width: 12px; 87 | min-height: 12px; 88 | transform: translate(27px, 14px); 89 | padding: 2px; 90 | } 91 | 92 | .themeswitcher checkbutton.theme-selector radio:checked { 93 | -gtk-icon-source: -gtk-icontheme("object-select-symbolic"); 94 | background-color: @theme_selected_bg_color; 95 | color: @theme_selected_fg_color; 96 | } 97 | -------------------------------------------------------------------------------- /src/devtoolbox.in: -------------------------------------------------------------------------------- 1 | #!@PYTHON@ 2 | 3 | # devtoolbox.in 4 | # 5 | # Copyright (C) 2022 - 2023 Alessandro 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | # SPDX-License-Identifier: GPL-3.0-or-later 21 | 22 | import os 23 | import sys 24 | import signal 25 | import locale 26 | import gettext 27 | 28 | VERSION = '@VERSION@' 29 | pkgdatadir = '@pkgdatadir@' 30 | localedir = '@localedir@' 31 | debug = '@debug@' 32 | 33 | sys.path.insert(1, pkgdatadir) 34 | signal.signal(signal.SIGINT, signal.SIG_DFL) 35 | locale.bindtextdomain('devtoolbox', localedir) 36 | locale.textdomain('devtoolbox') 37 | gettext.bindtextdomain('devtoolbox', localedir) 38 | gettext.textdomain('devtoolbox') 39 | gettext.install('devtoolbox', localedir) 40 | 41 | if __name__ == '__main__': 42 | import gi 43 | 44 | from gi.repository import Gio 45 | resource = Gio.Resource.load(os.path.join(pkgdatadir, 'devtoolbox.gresource')) 46 | resource._register() 47 | 48 | from devtoolbox import main 49 | sys.exit(main.main(VERSION, debug)) 50 | -------------------------------------------------------------------------------- /src/formatters/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | -------------------------------------------------------------------------------- /src/formatters/css.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from .formatter import Formatter 6 | from gettext import gettext as _, pgettext as C_ 7 | from typing import List 8 | import cssbeautifier 9 | 10 | 11 | class CssFormatter(Formatter): 12 | 13 | _title = _("CSS Formatter") 14 | _description = _("Format CSS documents") 15 | _utility_name = "css-formatter" 16 | _textarea_name = _("Type CSS here") 17 | _language = "css" 18 | _extensions = ["css", "scss", "sass"] 19 | _action_btn_name = C_("verb/action", "Format") 20 | _show_options = True 21 | 22 | def _format(self, text:str, indents:int): 23 | opts = cssbeautifier.default_options() 24 | opts.indent_size = indents 25 | return cssbeautifier.beautify(text, opts) 26 | 27 | def is_correct(self, text:str) -> bool: 28 | if isinstance(text, str): 29 | return True 30 | else: 31 | try: 32 | text.decode("utf-8") 33 | return True 34 | except UnicodeError: 35 | return False 36 | 37 | def get_title(self) -> str: 38 | return self._title 39 | 40 | def get_description(self) -> str: 41 | return self._description 42 | 43 | def get_utility_name(self) -> str: 44 | return self._utility_name 45 | 46 | def get_textarea_name(self) -> str: 47 | return self._textarea_name 48 | 49 | def get_language(self) -> str: 50 | return self._language 51 | 52 | def get_file_extensions(self) -> List[str]: 53 | return self._extensions 54 | 55 | def get_action_button_name(self) -> str: 56 | return self._action_btn_name 57 | 58 | def get_show_options(self) -> bool: 59 | return self._show_options -------------------------------------------------------------------------------- /src/formatters/css_minifier.py: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (C) 2022 - 2023 Alessandro Iepure 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | from .formatter import Formatter 7 | from gettext import gettext as _, pgettext as C_ 8 | from typing import List 9 | import rcssmin 10 | 11 | 12 | class CssMinifier(Formatter): 13 | 14 | _title = _("CSS Minifier") 15 | _description = _("Minify CSS documents") 16 | _utility_name = "css-minifier" 17 | _textarea_name = _("Type CSS here") 18 | _language = "css" 19 | _extensions = ["css", "scss", "sass"] 20 | _action_btn_name = C_("verb/action", "Minify") 21 | _show_options = False 22 | 23 | def _format(self, text:str, indents:int): 24 | return rcssmin.cssmin(text) 25 | 26 | def is_correct(self, text:str) -> bool: 27 | if isinstance(text, str): 28 | return True 29 | else: 30 | try: 31 | text.decode("utf-8") 32 | return True 33 | except UnicodeError: 34 | return False 35 | 36 | def get_title(self) -> str: 37 | return self._title 38 | 39 | def get_description(self) -> str: 40 | return self._description 41 | 42 | def get_utility_name(self) -> str: 43 | return self._utility_name 44 | 45 | def get_textarea_name(self) -> str: 46 | return self._textarea_name 47 | 48 | def get_language(self) -> str: 49 | return self._language 50 | 51 | def get_file_extensions(self) -> List[str]: 52 | return self._extensions 53 | 54 | def get_action_button_name(self) -> str: 55 | return self._action_btn_name 56 | 57 | def get_show_options(self) -> bool: 58 | return self._show_options -------------------------------------------------------------------------------- /src/formatters/formatter.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | import abc 6 | from gi.repository import Gio, GObject 7 | from typing import List 8 | 9 | 10 | class Formatter(abc.ABC): 11 | 12 | def __init__(self): 13 | self._cancellable = Gio.Cancellable() 14 | 15 | def _format_thread(self, task, source_object, task_data, cancelable): 16 | if task.return_error_if_cancelled(): 17 | return 18 | outcome = self._format(self._text, self._indentations) 19 | task.return_value(outcome) 20 | 21 | def format_async(self, caller: GObject.Object, callback: callable): 22 | task = Gio.Task.new(caller, None, callback, self._cancellable) 23 | task.set_return_on_cancel(True) 24 | task.run_in_thread(self._format_thread) 25 | 26 | def format_async_finish(self, result, caller: GObject.Object): 27 | if not Gio.Task.is_valid(result, caller): 28 | return -1 29 | return result.propagate_value().value 30 | 31 | def set_input(self, text:str): 32 | self._text = text 33 | 34 | def set_indentations(self, indents:int): 35 | self._indentations = indents 36 | 37 | def get_cancellable(self): 38 | return self._cancellable 39 | 40 | @abc.abstractmethod 41 | def _format(self, text:str, indents:int): 42 | pass 43 | 44 | @abc.abstractmethod 45 | def is_correct(self, text:str) -> bool: 46 | pass 47 | 48 | @abc.abstractmethod 49 | def get_title(self) -> str: 50 | pass 51 | 52 | @abc.abstractmethod 53 | def get_description(self) -> str: 54 | pass 55 | 56 | @abc.abstractmethod 57 | def get_utility_name(self) -> str: 58 | pass 59 | 60 | @abc.abstractmethod 61 | def get_textarea_name(self) -> str: 62 | pass 63 | 64 | @abc.abstractmethod 65 | def get_language(self) -> str: 66 | pass 67 | 68 | @abc.abstractmethod 69 | def get_file_extensions(self) -> List[str]: 70 | pass 71 | 72 | @abc.abstractmethod 73 | def get_action_button_name(self) -> str: 74 | pass 75 | 76 | @abc.abstractmethod 77 | def get_show_options(self) -> bool: 78 | pass 79 | -------------------------------------------------------------------------------- /src/formatters/html.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from .formatter import Formatter 6 | from gettext import gettext as _, pgettext as C_ 7 | from typing import List 8 | 9 | from lxml import etree, html 10 | 11 | 12 | class HtmlFormatter(Formatter): 13 | 14 | _title = _("HTML Formatter") 15 | _description = _("Format HTML documents") 16 | _utility_name = "html-formatter" 17 | _textarea_name = _("Type HTML code here") 18 | _language = "html" 19 | _extensions = ["html", "htm"] 20 | _action_btn_name = C_("verb/action", "Format") 21 | _show_options = True 22 | 23 | def _format(self, text:str, indents:int): 24 | indent_str = "" 25 | for _ in range(0, indents): 26 | indent_str += " " 27 | 28 | doc_root = html.fromstring(text) 29 | etree.indent(doc_root, space=indent_str) 30 | return etree.tostring(doc_root, method='html', encoding='unicode', pretty_print=True) 31 | 32 | def is_correct(self, text:str) -> bool: 33 | if isinstance(text, str): 34 | return True 35 | else: 36 | try: 37 | text.decode("utf-8") 38 | return True 39 | except UnicodeError: 40 | return False 41 | 42 | def get_title(self) -> str: 43 | return self._title 44 | 45 | def get_description(self) -> str: 46 | return self._description 47 | 48 | def get_utility_name(self) -> str: 49 | return self._utility_name 50 | 51 | def get_textarea_name(self) -> str: 52 | return self._textarea_name 53 | 54 | def get_language(self) -> str: 55 | return self._language 56 | 57 | def get_file_extensions(self) -> List[str]: 58 | return self._extensions 59 | 60 | def get_action_button_name(self) -> str: 61 | return self._action_btn_name 62 | 63 | def get_show_options(self) -> bool: 64 | return self._show_options -------------------------------------------------------------------------------- /src/formatters/js.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from .formatter import Formatter 6 | from gettext import gettext as _, pgettext as C_ 7 | from typing import List 8 | 9 | import jsbeautifier 10 | 11 | 12 | class JsFormatter(Formatter): 13 | 14 | _title = _("JavaScript Formatter") 15 | _description = _("Format JavaScript documents") 16 | _utility_name = "js-formatter" 17 | _textarea_name = _("Type JavaScript code here") 18 | _language = "js" 19 | _extensions = ["js"] 20 | _action_btn_name = C_("verb/action", "Format") 21 | _show_options = True 22 | 23 | def _format(self, text:str, indents:int): 24 | opts = jsbeautifier.default_options() 25 | opts.indent_size = indents 26 | return jsbeautifier.beautify(text, opts) 27 | 28 | def is_correct(self, text:str) -> bool: 29 | if isinstance(text, str): 30 | return True 31 | else: 32 | try: 33 | text.decode("utf-8") 34 | return True 35 | except UnicodeError: 36 | return False 37 | 38 | def get_title(self) -> str: 39 | return self._title 40 | 41 | def get_description(self) -> str: 42 | return self._description 43 | 44 | def get_utility_name(self) -> str: 45 | return self._utility_name 46 | 47 | def get_textarea_name(self) -> str: 48 | return self._textarea_name 49 | 50 | def get_language(self) -> str: 51 | return self._language 52 | 53 | def get_file_extensions(self) -> List[str]: 54 | return self._extensions 55 | 56 | def get_action_button_name(self) -> str: 57 | return self._action_btn_name 58 | 59 | def get_show_options(self) -> bool: 60 | return self._show_options -------------------------------------------------------------------------------- /src/formatters/js_minifier.py: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (C) 2022 - 2023 Alessandro Iepure 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | from .formatter import Formatter 7 | from gettext import gettext as _, pgettext as C_ 8 | from typing import List 9 | import rjsmin 10 | 11 | 12 | class JsMinifier(Formatter): 13 | 14 | _title = _("JavaScript Minifier") 15 | _description = _("Minify JS code") 16 | _utility_name = "js-minifier" 17 | _textarea_name = _("Type JS code here") 18 | _language = "js" 19 | _extensions = ["js", "mjs", "ts", "tsx"] 20 | _action_btn_name = C_("verb/action", "Minify") 21 | _show_options = False 22 | 23 | def _format(self, text:str, indents:int): 24 | return rjsmin.jsmin(text) 25 | 26 | def is_correct(self, text:str) -> bool: 27 | if isinstance(text, str): 28 | return True 29 | else: 30 | try: 31 | text.decode("utf-8") 32 | return True 33 | except UnicodeError: 34 | return False 35 | 36 | def get_title(self) -> str: 37 | return self._title 38 | 39 | def get_description(self) -> str: 40 | return self._description 41 | 42 | def get_utility_name(self) -> str: 43 | return self._utility_name 44 | 45 | def get_textarea_name(self) -> str: 46 | return self._textarea_name 47 | 48 | def get_language(self) -> str: 49 | return self._language 50 | 51 | def get_file_extensions(self) -> List[str]: 52 | return self._extensions 53 | 54 | def get_action_button_name(self) -> str: 55 | return self._action_btn_name 56 | 57 | def get_show_options(self) -> bool: 58 | return self._show_options -------------------------------------------------------------------------------- /src/formatters/json.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from .formatter import Formatter 6 | from gettext import gettext as _, pgettext as C_ 7 | import json 8 | from typing import List 9 | 10 | 11 | class JsonFormatter(Formatter): 12 | 13 | _title = _("JSON Formatter") 14 | _description = _("Format JSON documents") 15 | _utility_name = "json-formatter" 16 | _textarea_name = _("Type JSON code here") 17 | _language = "json" 18 | _extensions = ["json"] 19 | _action_btn_name = C_("verb/action", "Format") 20 | _show_options = True 21 | 22 | def _format(self, text:str, indents:int): 23 | try: 24 | return json.dumps(json.loads(text), indent=indents) 25 | except json.JSONDecodeError: 26 | return "" 27 | 28 | def is_correct(self, text:str): 29 | try: 30 | json.loads(text) 31 | return True 32 | except json.JSONDecodeError: 33 | return False 34 | 35 | def get_title(self) -> str: 36 | return self._title 37 | 38 | def get_description(self) -> str: 39 | return self._description 40 | 41 | def get_utility_name(self) -> str: 42 | return self._utility_name 43 | 44 | def get_textarea_name(self) -> str: 45 | return self._textarea_name 46 | 47 | def get_language(self) -> str: 48 | return self._language 49 | 50 | def get_file_extensions(self) -> List[str]: 51 | return self._extensions 52 | 53 | def get_action_button_name(self) -> str: 54 | return self._action_btn_name 55 | 56 | def get_show_options(self) -> bool: 57 | return self._show_options -------------------------------------------------------------------------------- /src/formatters/meson.build: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name()) 6 | formattersdir = join_paths(pkgdatadir, 'devtoolbox/formatters') 7 | 8 | sources = [ 9 | '__init__.py', 10 | 'formatter.py', 11 | 'json.py', 12 | 'sql.py', 13 | 'xml.py', 14 | 'html.py', 15 | 'js.py', 16 | 'css.py', 17 | 'css_minifier.py', 18 | 'js_minifier.py', 19 | ] 20 | 21 | install_data(sources, install_dir: formattersdir) 22 | -------------------------------------------------------------------------------- /src/formatters/sql.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from .formatter import Formatter 6 | from gettext import gettext as _, pgettext as C_ 7 | from typing import List 8 | import sqlparse 9 | 10 | 11 | class SqlFormatter(Formatter): 12 | 13 | _title = _("SQL Formatter") 14 | _description = _("Format SQL documents") 15 | _utility_name = "sql-formatter" 16 | _textarea_name = _("Type SQL code here") 17 | _language = "sql" 18 | _extensions = ["sql"] 19 | _action_btn_name = C_("verb/action", "Format") 20 | _show_options = True 21 | 22 | def _format(self, text:str, indents:int): 23 | try: 24 | return sqlparse.format( 25 | text, 26 | indent_width=indents, 27 | keyword_case="upper", 28 | identifier_case="lower", 29 | reindent=True 30 | ) 31 | except sqlparse.exceptions.SQLParseError: 32 | return "" 33 | 34 | def is_correct(self, text:str): 35 | if isinstance(text, str): 36 | return True 37 | else: 38 | try: 39 | text.decode("utf-8") 40 | return True 41 | except UnicodeError: 42 | return False 43 | 44 | def get_title(self) -> str: 45 | return self._title 46 | 47 | def get_description(self) -> str: 48 | return self._description 49 | 50 | def get_utility_name(self) -> str: 51 | return self._utility_name 52 | 53 | def get_textarea_name(self) -> str: 54 | return self._textarea_name 55 | 56 | def get_language(self) -> str: 57 | return self._language 58 | 59 | def get_file_extensions(self) -> List[str]: 60 | return self._extensions 61 | 62 | def get_action_button_name(self) -> str: 63 | return self._action_btn_name 64 | 65 | def get_show_options(self) -> bool: 66 | return self._show_options -------------------------------------------------------------------------------- /src/formatters/xml.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from .formatter import Formatter 6 | from gettext import gettext as _, pgettext as C_ 7 | from typing import List 8 | 9 | import lxml.etree 10 | import xml.etree.ElementTree as etree 11 | 12 | 13 | class XmlFormatter(Formatter): 14 | 15 | _title = _("XML Formatter") 16 | _description = _("Format XML documents") 17 | _utility_name = "xml-formatter" 18 | _textarea_name = _("Type XML code here") 19 | _language = "xml" 20 | _extensions = ["xml", "html", "htm", "svg", "ui"] 21 | _action_btn_name = C_("verb/action", "Format") 22 | _show_options = True 23 | 24 | def _format(self, text:str, indents:int): 25 | indent_str = "" 26 | for _ in range(0, indents): 27 | indent_str += " " 28 | 29 | try: 30 | xml = etree.XML(text) 31 | etree.indent(xml, space=indent_str) 32 | return etree.tostring(xml, encoding="utf-8", xml_declaration=True).decode("utf-8") 33 | except lxml.etree.XMLSyntaxError: 34 | return "" 35 | 36 | def is_correct(self, text:str) -> bool: 37 | try: 38 | etree.XML(text) 39 | return True 40 | except etree.ParseError: 41 | return False 42 | 43 | def get_title(self) -> str: 44 | return self._title 45 | 46 | def get_description(self) -> str: 47 | return self._description 48 | 49 | def get_utility_name(self) -> str: 50 | return self._utility_name 51 | 52 | def get_textarea_name(self) -> str: 53 | return self._textarea_name 54 | 55 | def get_language(self) -> str: 56 | return self._language 57 | 58 | def get_file_extensions(self) -> List[str]: 59 | return self._extensions 60 | 61 | def get_action_button_name(self) -> str: 62 | return self._action_btn_name 63 | 64 | def get_show_options(self) -> bool: 65 | return self._show_options -------------------------------------------------------------------------------- /src/services/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | -------------------------------------------------------------------------------- /src/services/base64_encoder.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from gi.repository import Gio, GObject 3 | 4 | 5 | class Base64EncoderService: 6 | 7 | def __init__(self): 8 | self._cancellable = Gio.Cancellable() 9 | self._url_safe = False 10 | 11 | def _encode_thread(self, task: Gio.Task, source_object: GObject.Object, task_data: object, cancelable: Gio.Cancellable): 12 | if task.return_error_if_cancelled(): 13 | return 14 | outcome = self._encode(self._input) 15 | task.return_value(outcome) 16 | 17 | def _decode_thread(self, task: Gio.Task, source_object: GObject.Object, task_data: object, cancelable: Gio.Cancellable): 18 | if task.return_error_if_cancelled(): 19 | return 20 | outcome = self._decode(self._input) 21 | task.return_value(outcome) 22 | 23 | def _encode(self, input: str) -> str: 24 | if self._url_safe: 25 | return base64.urlsafe_b64encode(input.encode()).decode() 26 | return base64.b64encode(input.encode()).decode() 27 | 28 | def _decode(self, input: str) -> str: 29 | if self._url_safe: 30 | return base64.urlsafe_b64decode(input.encode()).decode() 31 | return base64.b64decode(input.encode()).decode() 32 | 33 | def encode_async(self, caller: GObject.Object, callback: callable): 34 | task = Gio.Task.new(caller, None, callback, self._cancellable) 35 | task.set_return_on_cancel(True) 36 | task.run_in_thread(self._encode_thread) 37 | 38 | def decode_async(self, caller: GObject.Object, callback: callable): 39 | task = Gio.Task.new(caller, None, callback, self._cancellable) 40 | task.set_return_on_cancel(True) 41 | task.run_in_thread(self._decode_thread) 42 | 43 | def async_finish(self, result: Gio.AsyncResult, caller: GObject.Object): 44 | if not Gio.Task.is_valid(result, caller): 45 | return -1 46 | self._input = None 47 | return result.propagate_value().value 48 | 49 | def get_cancellable(self) -> Gio.Cancellable: 50 | return self._cancellable 51 | 52 | def set_input(self, input_str: str): 53 | self._input = input_str 54 | 55 | def set_url_safe(self, url_safe: bool): 56 | self._url_safe = url_safe -------------------------------------------------------------------------------- /src/services/certificate_parser.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gio, GObject, Gcr 6 | from asn1crypto import pem 7 | 8 | 9 | class CertificateParserService: 10 | 11 | def __init__(self): 12 | self._cancellable = Gio.Cancellable() 13 | 14 | def set_path(self, path:str): 15 | self._path = path 16 | 17 | def get_cancellable(self) -> Gio.Cancellable: 18 | return self._cancellable 19 | 20 | def get_gcr_async_finish(self, result:Gio.AsyncResult, caller:GObject.Object): 21 | if not Gio.Task.is_valid(result, caller): 22 | return -1 23 | self._path = None 24 | return result.propagate_value().value 25 | 26 | def get_gcr_async(self, caller:GObject.Object, callback:callable): 27 | task = Gio.Task.new(caller, None, callback, self._cancellable) 28 | task.set_return_on_cancel(True) 29 | task.run_in_thread(self._get_gcr_thread) 30 | 31 | def _get_gcr_thread(self, task:Gio.Task, source_object:GObject.Object, task_data:object, cancelable:Gio.Cancellable): 32 | if task.return_error_if_cancelled(): 33 | return 34 | outcome = self._get_gcr(self._path) 35 | task.return_value(outcome) 36 | 37 | def _get_gcr(self, path:str): 38 | with open(path, "rb") as f: 39 | file_bytes = f.read() 40 | if pem.detect(file_bytes): 41 | _, _, file_bytes = pem.unarmor(file_bytes) 42 | return Gcr.SimpleCertificate.new(file_bytes) 43 | -------------------------------------------------------------------------------- /src/services/colorblindness_simulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gio, GObject 6 | from daltonlens import convert, simulate 7 | from PIL import Image 8 | import numpy as np 9 | 10 | 11 | class ColorblindnessSimulatorService(): 12 | 13 | def __init__(self): 14 | self._cancellable = Gio.Cancellable() 15 | self._protanopia_file = Gio.File.new_tmp("me.iepure.devtoolbox.XXXXXX.png")[0] 16 | self._deutranopia_file = Gio.File.new_tmp("me.iepure.devtoolbox.XXXXXX.png")[0] 17 | self._tritanopia_file = Gio.File.new_tmp("me.iepure.devtoolbox.XXXXXX.png")[0] 18 | 19 | def set_original_file(self, file:Gio.File): 20 | self._original_file = file 21 | 22 | def set_severity(self, severity:float): 23 | self._severity = severity 24 | 25 | def get_cancellable(self) -> Gio.Cancellable: 26 | return self._cancellable 27 | 28 | def async_finish(self, result:Gio.AsyncResult, caller:GObject.Object): 29 | if not Gio.Task.is_valid(result, caller): 30 | return -1 31 | self._original_file = None 32 | self._severity = None 33 | return result.propagate_value().value 34 | 35 | def simulate_async(self, caller:GObject.Object, callback:callable): 36 | task = Gio.Task.new(caller, None, callback, self._cancellable) 37 | task.set_return_on_cancel(True) 38 | task.run_in_thread(self._simulate_thread) 39 | 40 | def _simulate_thread(self, task:Gio.Task, source_object:GObject.Object, task_data:object, cancelable:Gio.Cancellable): 41 | if task.return_error_if_cancelled(): 42 | return 43 | outcome = self._simulate(self._original_file, self._severity) 44 | task.return_value(outcome) 45 | 46 | def _simulate(self, file:Gio.File, severity:float): 47 | 48 | original_image = np.asarray(Image.open(file.get_path()).convert('RGB')) 49 | 50 | simulator = simulate.Simulator_AutoSelect() 51 | 52 | protan_array = simulator.simulate_cvd(original_image, simulate.Deficiency.PROTAN, severity=severity) 53 | deutan_array = simulator.simulate_cvd(original_image, simulate.Deficiency.DEUTAN, severity=severity) 54 | tritan_array = simulator.simulate_cvd(original_image, simulate.Deficiency.TRITAN, severity=severity) 55 | 56 | protan_im = Image.fromarray(protan_array) 57 | deutan_im = Image.fromarray(deutan_array) 58 | tritan_im = Image.fromarray(tritan_array) 59 | 60 | protan_im.save(self._protanopia_file.get_path()) 61 | deutan_im.save(self._deutranopia_file.get_path()) 62 | tritan_im.save(self._tritanopia_file.get_path()) 63 | 64 | return self._protanopia_file, self._deutranopia_file, self._tritanopia_file 65 | -------------------------------------------------------------------------------- /src/services/cron_converter.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gio, GObject 6 | from crontab import CronTab 7 | 8 | 9 | class CronConverterService(): 10 | 11 | def __init__(self): 12 | self._cancellable = Gio.Cancellable() 13 | 14 | def _generate_dates_thread(self, task:Gio.Task, source_object:GObject.Object, task_data:object, cancelable:Gio.Cancellable): 15 | if task.return_error_if_cancelled(): 16 | return 17 | outcome = self._generate_dates(self._expression, self._format_str, self._quantity) 18 | task.return_value(outcome) 19 | 20 | 21 | def _generate_dates(self, expression:str, format_str:str, quantity:int) -> str: 22 | cron = CronTab() 23 | job = cron.new(command="/usr/bin/echo") 24 | job.setall(expression) 25 | schedule = job.schedule() 26 | string = "" 27 | for _ in range(0, quantity): 28 | string += schedule.get_next().strftime(format_str) + "\n" 29 | return string 30 | 31 | def generate_dates_async(self, caller:GObject.Object, callback:callable): 32 | task = Gio.Task.new(caller, None, callback, self._cancellable) 33 | task.set_return_on_cancel(True) 34 | task.run_in_thread(self._generate_dates_thread) 35 | 36 | def generate_dates_async_finish(self, result:Gio.AsyncResult, caller:GObject.Object): 37 | if not Gio.Task.is_valid(result, caller): 38 | return -1 39 | self._expression = None 40 | self._format_str = None 41 | self._quantity = None 42 | return result.propagate_value().value 43 | 44 | def get_cancellable(self) -> Gio.Cancellable: 45 | return self._cancellable 46 | 47 | def set_expression(self, expression:str): 48 | self._expression = expression 49 | 50 | def set_format_str(self, format_str:str): 51 | self._format_str = format_str 52 | 53 | def set_quantity(self, quantity:int): 54 | self._quantity = quantity 55 | -------------------------------------------------------------------------------- /src/services/html_encoder.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gio, GObject 6 | import html 7 | 8 | 9 | class HtmlEncoderService(): 10 | 11 | def __init__(self): 12 | self._cancellable = Gio.Cancellable() 13 | 14 | def _encode_thread(self, task:Gio.Task, source_object:GObject.Object, task_data:object, cancelable:Gio.Cancellable): 15 | if task.return_error_if_cancelled(): 16 | return 17 | outcome = self._encode(self._input) 18 | task.return_value(outcome) 19 | 20 | def _decode_thread(self, task:Gio.Task, source_object:GObject.Object, task_data:object, cancelable:Gio.Cancellable): 21 | if task.return_error_if_cancelled(): 22 | return 23 | outcome = self._decode(self._input) 24 | task.return_value(outcome) 25 | 26 | def _encode(self, input:str) -> str: 27 | return html.escape(input) 28 | 29 | def _decode(self, input:str) -> str: 30 | return html.unescape(input) 31 | 32 | def encode_async(self, caller:GObject.Object, callback:callable): 33 | task = Gio.Task.new(caller, None, callback, self._cancellable) 34 | task.set_return_on_cancel(True) 35 | task.run_in_thread(self._encode_thread) 36 | 37 | def decode_async(self, caller:GObject.Object, callback:callable): 38 | task = Gio.Task.new(caller, None, callback, self._cancellable) 39 | task.set_return_on_cancel(True) 40 | task.run_in_thread(self._decode_thread) 41 | 42 | def async_finish(self, result:Gio.AsyncResult, caller:GObject.Object): 43 | if not Gio.Task.is_valid(result, caller): 44 | return -1 45 | self._input = None 46 | return result.propagate_value().value 47 | 48 | def get_cancellable(self) -> Gio.Cancellable: 49 | return self._cancellable 50 | 51 | def set_input(self, input_str: str): 52 | self._input = input_str 53 | -------------------------------------------------------------------------------- /src/services/image_converter.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gio, GObject 6 | from PIL import Image 7 | 8 | 9 | class ImageConverterService(): 10 | 11 | def __init__(self): 12 | self._cancellable = Gio.Cancellable() 13 | 14 | def set_file(self, file:Gio.File): 15 | self._file = file 16 | 17 | def set_destination_format(self, destination_format:int): 18 | self._destination_format = destination_format 19 | 20 | def set_destination_file(self, destination_file:Gio.File): 21 | self._destination_file = destination_file 22 | 23 | def get_cancellable(self) -> Gio.Cancellable: 24 | return self._cancellable 25 | 26 | def async_finish(self, result:Gio.AsyncResult, caller:GObject.Object): 27 | if not Gio.Task.is_valid(result, caller): 28 | return -1 29 | self._file = None 30 | self._destination_format = None 31 | self._destination_file = None 32 | return result.propagate_value().value 33 | 34 | def convert_image_async(self, caller:GObject.Object, callback:callable): 35 | task = Gio.Task.new(caller, None, callback, self._cancellable) 36 | task.set_return_on_cancel(True) 37 | task.run_in_thread(self._convert_image_thread) 38 | 39 | def _convert_image_thread(self, task:Gio.Task, source_object:GObject.Object, task_data:object, cancelable:Gio.Cancellable): 40 | if task.return_error_if_cancelled(): 41 | return 42 | outcome = self._convert_image(self._file, self._destination_format, self._destination_file) 43 | task.return_value(outcome) 44 | 45 | def _convert_image(self, file:Gio.File, destination_format:int, destination_file:Gio.File): 46 | 47 | img = Image.open(file.get_path()) 48 | 49 | match destination_format: 50 | case 0: # BMP 51 | extension = "bmp" 52 | case 1: # GIF 53 | extension = "gif" 54 | case 2: # ICNS 55 | extension = "icns" 56 | case 3: # JPEG 57 | extension = "jpeg" 58 | img = img.convert('RGB') 59 | case 4: # PNG 60 | extension = "png" 61 | case 5: # TIFF 62 | extension = "tiff" 63 | case 6: # WEBP 64 | extension = "webp" 65 | 66 | img.save(destination_file.get_path(), format=extension) 67 | return destination_file.get_path() 68 | -------------------------------------------------------------------------------- /src/services/json_validator.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gio, GObject 6 | import json 7 | from jsonschema import validate, exceptions 8 | 9 | 10 | class JsonValidatorService(): 11 | 12 | def __init__(self): 13 | self._cancellable = Gio.Cancellable() 14 | 15 | def set_json(self, json:str): 16 | self._json = json 17 | 18 | def set_schema(self, schema:str): 19 | self._schema = schema 20 | 21 | def get_cancellable(self) -> Gio.Cancellable: 22 | return self._cancellable 23 | 24 | def async_finish(self, result:Gio.AsyncResult, caller:GObject.Object): 25 | if not Gio.Task.is_valid(result, caller): 26 | return -1 27 | self._json = None 28 | self._schema = None 29 | return result.propagate_value().value 30 | 31 | def check_json_async(self, caller:GObject.Object, callback:callable): 32 | task = Gio.Task.new(caller, None, callback, self._cancellable) 33 | task.set_return_on_cancel(True) 34 | task.run_in_thread(self._check_json_thread) 35 | 36 | def _check_json_thread(self, task:Gio.Task, source_object:GObject.Object, task_data:object, cancelable:Gio.Cancellable): 37 | if task.return_error_if_cancelled(): 38 | return 39 | outcome = self._check_json(self._json, self._schema) 40 | task.return_value(outcome) 41 | 42 | def _check_json(self, json_str:str, schema_str:str): 43 | try: 44 | validate(instance=json.loads(json_str), schema=json.loads(schema_str)) 45 | return True, None, None 46 | except exceptions.ValidationError as error: 47 | return False, error.message, None 48 | except exceptions.SchemaError as error: 49 | return False, None, error.message 50 | -------------------------------------------------------------------------------- /src/services/jwt_decoder.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gio, GObject 6 | import json 7 | import jwt 8 | 9 | 10 | class JwtDecoderService(): 11 | 12 | def __init__(self): 13 | self._cancellable = Gio.Cancellable() 14 | 15 | def _decode_header_thread(self, task:Gio.Task, source_object:GObject.Object, task_data:object, cancelable:Gio.Cancellable): 16 | if task.return_error_if_cancelled(): 17 | return 18 | outcome = self._decode_header(self._token) 19 | task.return_value(outcome) 20 | 21 | def _decode_payload_thread(self, task:Gio.Task, source_object:GObject.Object, task_data:object, cancelable:Gio.Cancellable): 22 | if task.return_error_if_cancelled(): 23 | return 24 | outcome = self._decode_payload(self._token) 25 | task.return_value(outcome) 26 | 27 | def _decode_header(self, token:str) -> str: 28 | return json.dumps(jwt.get_unverified_header(token), indent=4) 29 | 30 | def _decode_payload(self, token:str) -> str: 31 | return json.dumps(jwt.decode(token, options={"verify_signature": False}), indent=4) 32 | 33 | def decode_header_async(self, caller:GObject.Object, callback:callable): 34 | task = Gio.Task.new(caller, None, callback, self._cancellable) 35 | task.set_return_on_cancel(True) 36 | task.run_in_thread(self._decode_header_thread) 37 | 38 | def decode_payload_async(self, caller:GObject.Object, callback:callable): 39 | task = Gio.Task.new(caller, None, callback, self._cancellable) 40 | task.set_return_on_cancel(True) 41 | task.run_in_thread(self._decode_payload_thread) 42 | 43 | def decode_finish(self, result:Gio.AsyncResult, caller:GObject.Object): 44 | if not Gio.Task.is_valid(result, caller): 45 | return -1 46 | self._token = None 47 | return result.propagate_value().value 48 | 49 | def set_token(self, token:str): 50 | self._token = token 51 | 52 | def get_cancellable(self): 53 | return self._cancellable 54 | -------------------------------------------------------------------------------- /src/services/lorem_generator.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import GObject, Gio 6 | import lorem 7 | 8 | 9 | class LoremGeneratorService(): 10 | 11 | def __init__(self): 12 | self._cancellable = Gio.Cancellable() 13 | 14 | def async_finish(self, result:Gio.AsyncResult, caller: GObject.Object): 15 | if not Gio.Task.is_valid(result, caller): 16 | return -1 17 | self._begin_with_lorem_ipsum = None 18 | self._unit = None 19 | self._quantity = None 20 | return result.propagate_value().value 21 | 22 | def get_cancellable(self) -> Gio.Cancellable: 23 | return self._cancellable 24 | 25 | def set_beginning(self, begin_with_lorem_ipsum:bool): 26 | self._begin_with_lorem_ipsum = begin_with_lorem_ipsum 27 | 28 | def set_amount(self, unit:int, quantity:int): 29 | self._unit = unit 30 | self._quantity = quantity 31 | 32 | def generate_text_async(self, caller:GObject.Object, callback:callable): 33 | task = Gio.Task.new(caller, None, callback, self._cancellable) 34 | task.set_return_on_cancel(True) 35 | task.run_in_thread(self._generate_text_thread) 36 | 37 | def _generate_text_thread(self, task:Gio.Task, source_object:GObject.Object, task_data:object, cancelable:Gio.Cancellable): 38 | if task.return_error_if_cancelled(): 39 | return 40 | outcome = self._generate_text(self._begin_with_lorem_ipsum, self._unit, self._quantity) 41 | task.return_value(outcome) 42 | 43 | def _generate_text(self, begin_with_lorem_ipsum:bool, unit:int, quantity:int) -> str: 44 | string = "" 45 | 46 | match unit: 47 | case 0: # words 48 | string = lorem.get_word(count=quantity) 49 | case 1: # sentence 50 | string = lorem.get_sentence(count=quantity) 51 | case 2: # paragraph 52 | string = lorem.get_paragraph(count=quantity) 53 | 54 | if begin_with_lorem_ipsum: 55 | string = "Lorem ipsum dolor sit amet, " + string[0].lower() + string[1:] 56 | 57 | return string 58 | -------------------------------------------------------------------------------- /src/services/meson.build: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name()) 6 | servicesdir = join_paths(pkgdatadir, 'devtoolbox/services') 7 | 8 | sources = [ 9 | '__init__.py', 10 | 'json_yaml.py', 11 | 'cron_converter.py', 12 | 'base64_encoder.py', 13 | 'html_encoder.py', 14 | 'url_encoder.py', 15 | 'gzip_compressor.py', 16 | 'jwt_decoder.py', 17 | 'hash_generator.py', 18 | 'lorem_generator.py', 19 | 'uuid_generator.py', 20 | 'text_inspector.py', 21 | 'regex_tester.py', 22 | 'text_diff.py', 23 | 'xml_validator.py', 24 | 'markdown_preview.py', 25 | 'colorblindness_simulator.py', 26 | 'image_converter.py', 27 | 'certificate_parser.py', 28 | 'json_validator.py', 29 | ] 30 | 31 | install_data(sources, install_dir: servicesdir) 32 | -------------------------------------------------------------------------------- /src/services/regex_tester.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gio, GObject 6 | from typing import Iterator 7 | import re 8 | 9 | 10 | class RegexTesterService(): 11 | 12 | def __init__(self): 13 | self._cancellable = Gio.Cancellable() 14 | 15 | def set_regex(self, regex:str): 16 | self._regex = regex 17 | 18 | def set_text(self, text:str): 19 | self._text = text 20 | 21 | def get_cancellable(self) -> Gio.Cancellable: 22 | return self._cancellable 23 | 24 | def async_finish(self, result:Gio.AsyncResult, caller:GObject.Object): 25 | if not Gio.Task.is_valid(result, caller): 26 | return -1 27 | self._text = None 28 | self._regex = None 29 | return result.propagate_value().value 30 | 31 | def find_all_matches_async(self, caller:GObject.Object, callback:callable): 32 | task = Gio.Task.new(caller, None, callback, self._cancellable) 33 | task.set_return_on_cancel(True) 34 | task.run_in_thread(self._find_all_matches_thread) 35 | 36 | def _find_all_matches_thread(self, task:Gio.Task, source_object:GObject.Object, task_data:object, cancelable:Gio.Cancellable): 37 | if task.return_error_if_cancelled(): 38 | return 39 | outcome = self._find_all_matches(self._regex, self._text) 40 | task.return_value(outcome) 41 | 42 | def _find_all_matches(self, regex:str, text:str) -> Iterator: 43 | p = re.compile(r"{}".format(regex)) 44 | return p.finditer(text) 45 | -------------------------------------------------------------------------------- /src/services/url_encoder.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gio, GObject 6 | from urllib import parse 7 | 8 | class UrlEncoderService(): 9 | 10 | def __init__(self): 11 | self._cancellable = Gio.Cancellable() 12 | 13 | def _encode_thread(self, task:Gio.Task, source_object:GObject.Object, task_data:object, cancelable:Gio.Cancellable): 14 | if task.return_error_if_cancelled(): 15 | return 16 | outcome = self._encode(self._input, self._space_as_plus) 17 | task.return_value(outcome) 18 | 19 | def _decode_thread(self, task:Gio.Task, source_object:GObject.Object, task_data:object, cancelable:Gio.Cancellable): 20 | if task.return_error_if_cancelled(): 21 | return 22 | outcome = self._decode(self._input) 23 | task.return_value(outcome) 24 | 25 | def _encode(self, input_str:str, space_as_plus:bool) -> str: 26 | if space_as_plus: 27 | return parse.quote_plus(input_str, safe=":/?#[]@!$&'()*+,;=") 28 | 29 | return parse.quote(input_str, safe=":/?#[]@!$&'()*+,;=") 30 | 31 | def _decode(self, input_str:str) -> str: 32 | return parse.unquote_plus(input_str) 33 | 34 | def encode_async(self, caller:GObject.Object, callback:callable): 35 | task = Gio.Task.new(caller, None, callback, self._cancellable) 36 | task.set_return_on_cancel(True) 37 | task.run_in_thread(self._encode_thread) 38 | 39 | def decode_async(self, caller:GObject.Object, callback:callable): 40 | task = Gio.Task.new(caller, None, callback, self._cancellable) 41 | task.set_return_on_cancel(True) 42 | task.run_in_thread(self._decode_thread) 43 | 44 | def async_finish(self, result:Gio.AsyncResult, caller:GObject.Object): 45 | if not Gio.Task.is_valid(result, caller): 46 | return -1 47 | self._input = None 48 | self._space_as_plus = None 49 | return result.propagate_value().value 50 | 51 | def get_cancellable(self) -> Gio.Cancellable: 52 | return self._cancellable 53 | 54 | def set_input(self, input_str:str): 55 | self._input = input_str 56 | 57 | def set_space_as_plus(self, space_as_plus:bool): 58 | self._space_as_plus = space_as_plus 59 | -------------------------------------------------------------------------------- /src/services/uuid_generator.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gio, GObject 6 | from uuid6 import uuid6, uuid7 7 | import uuid 8 | import random, string 9 | 10 | 11 | class UuidGeneratorService(): 12 | 13 | def __init__(self): 14 | self._cancellable = Gio.Cancellable() 15 | 16 | def set_version(self, version:int): 17 | self._version = version 18 | 19 | def set_amount(self, amount:int): 20 | self._amount = amount 21 | 22 | def get_cancellable(self) -> Gio.Cancellable: 23 | return self._cancellable 24 | 25 | def async_finish(self, result:Gio.AsyncResult, caller:GObject.Object): 26 | if not Gio.Task.is_valid(result, caller): 27 | return -1 28 | self._version = None 29 | self._amount = None 30 | return result.propagate_value().value 31 | 32 | def generate_async(self, caller:GObject.Object, callback:callable): 33 | task = Gio.Task.new(caller, None, callback, self._cancellable) 34 | task.set_return_on_cancel(True) 35 | task.run_in_thread(self._generate_thread) 36 | 37 | def _generate_thread(self, task:Gio.Task, source_object:GObject.Object, task_data:object, cancelable:Gio.Cancellable): 38 | if task.return_error_if_cancelled(): 39 | return 40 | outcome = self._generate(self._version, self._amount) 41 | task.return_value(outcome) 42 | 43 | def _generate(self, version:int, amount:int) -> str: 44 | output = "" 45 | 46 | for _ in range(0, amount-1): 47 | match version: 48 | case 0: # Version-1 49 | output += str(uuid.uuid1()) + "\n" 50 | case 1: # Version-3 51 | output += str(uuid.uuid3(uuid.NAMESPACE_DNS, self._random_string())) + "\n" 52 | case 2: # Version-4 53 | output += str(uuid.uuid4()) + "\n" 54 | case 3: # Version-5 55 | output += str(uuid.uuid5(uuid.NAMESPACE_DNS, self._random_string())) + "\n" 56 | case 4: # Version-6 57 | output += str(uuid6()) + "\n" 58 | case 5: # Version-7 59 | output += str(uuid7()) + "\n" 60 | 61 | return output 62 | 63 | def _random_string(self) -> str: 64 | letters = string.ascii_lowercase 65 | return ''.join(random.choice(letters) for i in range(6)) 66 | -------------------------------------------------------------------------------- /src/services/xml_validator.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gio, GObject 6 | from lxml import etree 7 | 8 | 9 | class XmlValidatorService(): 10 | 11 | def __init__(self): 12 | self._cancellable = Gio.Cancellable() 13 | 14 | def set_xml(self, xml:str): 15 | self._xml = xml 16 | 17 | def set_xsd(self, xsd:str): 18 | self._xsd = xsd 19 | 20 | def get_cancellable(self) -> Gio.Cancellable: 21 | return self._cancellable 22 | 23 | def async_finish(self, result:Gio.AsyncResult, caller:GObject.Object): 24 | if not Gio.Task.is_valid(result, caller): 25 | return -1 26 | self._xml = None 27 | self._xsd = None 28 | return result.propagate_value().value 29 | 30 | def check_xml_async(self, caller:GObject.Object, callback:callable): 31 | task = Gio.Task.new(caller, None, callback, self._cancellable) 32 | task.set_return_on_cancel(True) 33 | task.run_in_thread(self._check_xml_thread) 34 | 35 | def _check_xml_thread(self, task:Gio.Task, source_object:GObject.Object, task_data:object, cancelable:Gio.Cancellable): 36 | if task.return_error_if_cancelled(): 37 | return 38 | outcome = self._check_xml(self._xml, self._xsd) 39 | task.return_value(outcome) 40 | 41 | def _check_xml(self, xml:str, xsd:str): 42 | 43 | parser = etree.XMLParser(no_network=False) 44 | 45 | try: 46 | schema_root = etree.fromstring(bytes(xsd, encoding="utf-8"), parser=parser) 47 | schema = etree.XMLSchema(etree=schema_root) 48 | try: 49 | schema.assertValid(etree.fromstring(bytes(xml, encoding='utf-8'), parser=parser)) 50 | return True, None, None 51 | except etree.DocumentInvalid as error: 52 | return False, error, None 53 | except etree.XMLSchemaParseError as error: 54 | return False, None, error 55 | -------------------------------------------------------------------------------- /src/ui/about_dialog.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | using Gtk 4.0; 6 | using Adw 1; 7 | 8 | Adw.AboutDialog about_dialog { 9 | application-name: "Dev Toolbox"; 10 | copyright: _("Copyright © 2022 - 2024 Alessandro Iepure\nSome icons are copyright of the GNOME Project"); 11 | comments: _("Dev tools at your fingertips"); 12 | website: "https://github.com/aleiepure/devtoolbox"; 13 | issue-url: "https://github.com/aleiepure/devtoolbox/issues"; 14 | application-icon: "me.iepure.devtoolbox"; 15 | license-type: gpl_3_0; 16 | developer-name: "Alessandro Iepure"; 17 | // TRANSLATORS: replace with your name (with optional email or website) 18 | translator-credits: _("translator-credits"); 19 | } 20 | -------------------------------------------------------------------------------- /src/ui/gtk/help-overlay.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | using Gtk 4.0; 6 | 7 | ShortcutsWindow help_overlay { 8 | modal: true; 9 | 10 | ShortcutsSection { 11 | section-name: "shortcuts"; 12 | max-height: 10; 13 | 14 | ShortcutsGroup { 15 | title: C_("shortcut window", "General"); 16 | 17 | ShortcutsShortcut { 18 | title: C_("shortcut window", "Search Tools"); 19 | accelerator: "f"; 20 | } 21 | 22 | ShortcutsShortcut { 23 | title: C_("shortcut window", "Show Shortcuts"); 24 | action-name: "win.show-help-overlay"; 25 | } 26 | 27 | ShortcutsShortcut { 28 | title: C_("shortcut window", "Quit"); 29 | action-name: "app.quit"; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ui/views/base64_encoder.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $Base64EncoderView: Adw.Bin { 8 | Adw.ToastOverlay _toast { 9 | child: ScrolledWindow { 10 | child: Adw.Clamp { 11 | vexpand: true; 12 | maximum-size: 1200; 13 | tightening-threshold: 600; 14 | 15 | child: Box { 16 | orientation: vertical; 17 | 18 | $UtilityTitle _title { 19 | title: _("Base64 Encoder & Decoder"); 20 | description: _("Encode and decode strings using the Base64 format"); 21 | tool-name: "base64-encoder"; 22 | } 23 | 24 | Separator { 25 | margin-top: 10; 26 | margin-bottom: 10; 27 | } 28 | 29 | Adw.PreferencesGroup { 30 | title: _("Tool options"); 31 | 32 | Adw.ActionRow { 33 | title: _("Direction"); 34 | subtitle: _("Select the desired encoding direction"); 35 | icon-name: "horizontal-arrows-symbolic"; 36 | 37 | [suffix] 38 | Adw.ToggleGroup _direction_selector { 39 | valign: center; 40 | Adw.Toggle { 41 | label: _("Encode"); 42 | } 43 | 44 | Adw.Toggle { 45 | label: _("Decode"); 46 | } 47 | } 48 | } 49 | 50 | Adw.SwitchRow _url_safe_switch { 51 | title: _("URL Safe"); 52 | subtitle: _("Use URL safe Base64 processing"); 53 | icon-name: "chain-link"; 54 | } 55 | } 56 | 57 | Separator { 58 | margin-top: 10; 59 | } 60 | 61 | Box { 62 | orientation: horizontal; 63 | homogeneous: true; 64 | spacing: 10; 65 | margin-bottom: 10; 66 | 67 | $TextArea _input_area { 68 | name: _("Input"); 69 | show-clear-btn: true; 70 | show-paste-btn: true; 71 | show-open-btn: true; 72 | use-default-text-extensions: true; 73 | text-show-line-numbers: false; 74 | text-syntax-highlighting: false; // Base64 input is plain text 75 | text-language-highlight: "plain"; 76 | text-wrap-mode: "char"; 77 | } 78 | 79 | $TextArea _output_area { 80 | name: _("Output"); 81 | show-copy-btn: true; 82 | show-clear-btn: false; 83 | show-paste-btn: false; 84 | show-open-btn: false; 85 | text-editable: false; 86 | allow-drag-and-drop: false; 87 | text-show-line-numbers: false; 88 | text-syntax-highlighting: false; // Base64 output is plain text 89 | text-language-highlight: "plain"; 90 | text-wrap-mode: "char"; 91 | } 92 | } 93 | }; 94 | }; 95 | }; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/ui/views/colorblindness_simulator.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $ColorblindnessSimulatorView: Adw.Bin { 8 | Adw.ToastOverlay _toast { 9 | child: ScrolledWindow { 10 | child: Adw.Clamp { 11 | vexpand: true; 12 | maximum-size: 1200; 13 | tightening-threshold: 600; 14 | 15 | child: Box { 16 | orientation: vertical; 17 | // Title 18 | $UtilityTitle _title { 19 | title: _("Color Blindness Simulator"); 20 | description: _("Simulate color blindness in images"); 21 | tool-name: "colorblind-sim"; 22 | } 23 | 24 | Separator { 25 | margin-top: 10; 26 | margin-bottom: 10; 27 | } 28 | 29 | // Options 30 | Adw.PreferencesGroup { 31 | title: _("Tool options"); 32 | 33 | Adw.ActionRow { 34 | title: _("Severity"); 35 | subtitle: _("Severity of the anomaly"); 36 | icon-name: "weight"; 37 | 38 | [suffix] 39 | Scale _severity_scale { 40 | hexpand: true; 41 | 42 | adjustment: Adjustment { 43 | lower: 0; 44 | upper: 1; 45 | step-increment: 0.01; 46 | value: 0.8; 47 | }; 48 | 49 | marks [ 50 | mark (0.8, bottom, "") 51 | ] 52 | } 53 | } 54 | } 55 | 56 | Separator { 57 | margin-top: 10; 58 | } 59 | 60 | // Content 61 | Box { 62 | orientation: vertical; 63 | homogeneous: true; 64 | margin-bottom: 10; 65 | 66 | Box { 67 | orientation: horizontal; 68 | homogeneous: true; 69 | spacing: 10; 70 | 71 | $ImageArea _original_imagearea { 72 | name: _("Original"); 73 | show-open-btn: true; 74 | show-clear-btn: true; 75 | allow-drag-and-drop: true; 76 | } 77 | 78 | $ImageArea _protanopia_imagearea { 79 | name: _("Protanopia"); 80 | default-save-name: "protanopia.png"; 81 | } 82 | } 83 | 84 | Box { 85 | orientation: horizontal; 86 | homogeneous: true; 87 | spacing: 10; 88 | 89 | $ImageArea _deuteranopia_imagearea { 90 | name: _("Deuteranopia"); 91 | default-save-name: "deuteranopia.png"; 92 | } 93 | 94 | $ImageArea _tritanopia_imagearea { 95 | name: _("Tritanopia"); 96 | default-save-name: "tritanopia.png"; 97 | } 98 | } 99 | } 100 | }; 101 | }; 102 | }; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/ui/views/formatter.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $FormatterView: Adw.Bin { 8 | Adw.ToastOverlay _toast { 9 | child: ScrolledWindow { 10 | child: Adw.Clamp { 11 | vexpand: true; 12 | maximum-size: 1200; 13 | tightening-threshold: 600; 14 | 15 | child: Box { 16 | orientation: vertical; 17 | 18 | $UtilityTitle _title {} 19 | 20 | Separator { 21 | margin-top: 10; 22 | margin-bottom: 10; 23 | } 24 | 25 | Adw.PreferencesGroup _tool_options { 26 | title: _("Tool options"); 27 | 28 | Adw.SpinRow _indents_spinner { 29 | title: _("Indentations"); 30 | subtitle: _("Number of spaces used for indentation"); 31 | icon-name: "format-indent-more-symbolic"; 32 | 33 | adjustment: Adjustment { 34 | lower: 2; 35 | upper: 8; 36 | step-increment: 1; 37 | value: 4; 38 | }; 39 | } 40 | } 41 | 42 | Separator { 43 | visible: bind _tool_options.visible; 44 | margin-top: 10; 45 | } 46 | 47 | $TextArea _textarea { 48 | show-action-btn: "true"; 49 | show-clear-btn: "true"; 50 | show-copy-btn: "true"; 51 | show-paste-btn: "true"; 52 | show-open-btn: "true"; 53 | // action-btn-name: C_("verb/action", "Format"); 54 | text-show-line-numbers: "true"; 55 | text-highlight-current-line: "true"; 56 | text-syntax-highlighting: "true"; 57 | area-height: "500"; 58 | use-custom-file-extensions: "true"; 59 | margin-bottom: "10"; 60 | text-wrap-mode: "none"; 61 | } 62 | }; 63 | }; 64 | }; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/ui/views/html_encoder.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $HtmlEncoderView: Adw.Bin { 8 | Adw.ToastOverlay _toast { 9 | child: ScrolledWindow { 10 | child: Adw.Clamp { 11 | vexpand: true; 12 | maximum-size: 1200; 13 | tightening-threshold: 600; 14 | 15 | child: Box { 16 | orientation: vertical; 17 | 18 | $UtilityTitle _title { 19 | title: _("HTML Encoder & Decoder"); 20 | description: _("Encode and decode special characters using the HTML format"); 21 | tool-name: "html-encoder"; 22 | } 23 | 24 | Separator { 25 | margin-top: 10; 26 | margin-bottom: 10; 27 | } 28 | 29 | Adw.PreferencesGroup { 30 | title: _("Tool options"); 31 | 32 | Adw.ActionRow { 33 | title: _("Direction"); 34 | subtitle: _("Select the desired encoding direction"); 35 | icon-name: "horizontal-arrows-symbolic"; 36 | 37 | [suffix] 38 | Adw.ToggleGroup _direction_selector { 39 | valign: center; 40 | notify::active => $_on_direction_changed(); 41 | 42 | Adw.Toggle { 43 | label: _("Encode"); 44 | } 45 | 46 | Adw.Toggle { 47 | label: _("Decode"); 48 | } 49 | } 50 | } 51 | } 52 | 53 | Separator { 54 | margin-top: 10; 55 | } 56 | 57 | Box { 58 | orientation: horizontal; 59 | homogeneous: true; 60 | spacing: 10; 61 | margin-bottom: 10; 62 | 63 | $TextArea _input_area { 64 | name: _("Input"); 65 | show-clear-btn: true; 66 | show-paste-btn: true; 67 | show-open-btn: true; 68 | use-default-text-extensions: true; 69 | text-show-line-numbers: true; 70 | text-syntax-highlighting: true; 71 | text-language-highlight: "html"; 72 | text-highlight-current-line: true; 73 | text-wrap-mode: "none"; 74 | } 75 | 76 | $TextArea _output_area { 77 | name: _("Output"); 78 | show-copy-btn: true; 79 | show-clear-btn: false; 80 | show-paste-btn: false; 81 | show-open-btn: false; 82 | text-editable: false; 83 | allow-drag-and-drop: false; 84 | text-show-line-numbers: true; 85 | text-syntax-highlighting: true; 86 | text-language-highlight: "html"; 87 | text-wrap-mode: "none"; 88 | } 89 | } 90 | }; 91 | }; 92 | }; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/ui/views/image_converter.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $ImageConverterView: Adw.Bin { 8 | Adw.ToastOverlay _toast { 9 | child: ScrolledWindow { 10 | child: Adw.Clamp { 11 | vexpand: true; 12 | maximum-size: 1200; 13 | tightening-threshold: 600; 14 | 15 | child: Box { 16 | orientation: vertical; 17 | // Title 18 | $UtilityTitle _title { 19 | title: _("Image Format Converter"); 20 | description: _("Convert images to different formats"); 21 | tool-name: "image-converter"; 22 | } 23 | 24 | Separator { 25 | margin-top: 10; 26 | margin-bottom: 10; 27 | } 28 | 29 | // Options 30 | Adw.PreferencesGroup _preference_group { 31 | title: _("Tool options"); 32 | 33 | Adw.ComboRow _format_combo { 34 | title: C_("noun", "Format"); 35 | subtitle: _("Select the desired output format"); 36 | icon-name: "list-large"; 37 | 38 | model: StringList { 39 | strings [ 40 | "BMP", 41 | "GIF", 42 | "ICNS", 43 | "JPEG", 44 | "PNG", 45 | "TIFF", 46 | "WEBP" 47 | ] 48 | }; 49 | } 50 | } 51 | 52 | Separator { 53 | margin-top: 10; 54 | } 55 | 56 | // Image area 57 | $ImageArea _imagearea { 58 | name: _("Image"); 59 | show-action-btn: true; 60 | action-btn-name: _("Convert"); 61 | show-open-btn: true; 62 | show-clear-btn: true; 63 | allow-drag-and-drop: true; 64 | default-save-name: _("converted-image"); 65 | margin-bottom: 10; 66 | } 67 | }; 68 | }; 69 | }; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/ui/views/json_validator.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $JsonValidatorView: Adw.Bin { 8 | Adw.ToastOverlay _toast { 9 | child: ScrolledWindow { 10 | child: Adw.Clamp { 11 | vexpand: true; 12 | maximum-size: 1200; 13 | tightening-threshold: 600; 14 | 15 | child: Box { 16 | orientation: vertical; 17 | 18 | $UtilityTitle _title { 19 | title: _("JSON Schema Validator"); 20 | description: _("Check a JSON file against a JSON schema"); 21 | tool-name: "json-validator"; 22 | } 23 | 24 | Separator { 25 | margin-top: 10; 26 | } 27 | 28 | Box { 29 | orientation: horizontal; 30 | homogeneous: true; 31 | spacing: 10; 32 | margin-bottom: 10; 33 | 34 | $TextArea _json_textarea { 35 | name: "JSON"; 36 | show-clear-btn: true; 37 | show-paste-btn: true; 38 | show-open-btn: true; 39 | show-copy-btn: true; 40 | use-custom-file-extensions: true; 41 | custom-file-extensions: "json"; 42 | text-show-line-numbers: true; 43 | text-syntax-highlighting: true; 44 | text-highlight-current-line: true; 45 | text-wrap-mode: "none"; 46 | } 47 | 48 | $TextArea _schema_textarea { 49 | name: "JSON Schema"; 50 | show-clear-btn: true; 51 | show-paste-btn: true; 52 | show-open-btn: true; 53 | show-copy-btn: true; 54 | use-custom-file-extensions: true; 55 | custom-file-extensions: "json"; 56 | text-show-line-numbers: true; 57 | text-syntax-highlighting: true; 58 | text-highlight-current-line: true; 59 | text-wrap-mode: "none"; 60 | } 61 | } 62 | 63 | Box _check_box { 64 | visible: false; 65 | orientation: horizontal; 66 | halign: center; 67 | margin-bottom: 15; 68 | margin-top: 5; 69 | 70 | Image _check_icon { 71 | pixel-size: 56; 72 | margin-end: 20; 73 | } 74 | 75 | Box { 76 | orientation: vertical; 77 | valign: center; 78 | 79 | Label _check_title_lbl { 80 | styles [ 81 | "heading" 82 | ] 83 | 84 | margin-bottom: 5; 85 | } 86 | 87 | Label _check_lbl { 88 | justify: center; 89 | width-request: 100; 90 | wrap: true; 91 | } 92 | } 93 | } 94 | }; 95 | }; 96 | }; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/ui/views/jwt_decoder.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $JwtDecoderView: Adw.Bin { 8 | Adw.ToastOverlay _toast { 9 | child: ScrolledWindow { 10 | child: Adw.Clamp { 11 | vexpand: true; 12 | maximum-size: 1200; 13 | tightening-threshold: 600; 14 | 15 | child: Box { 16 | orientation: vertical; 17 | 18 | $UtilityTitle _title { 19 | title: _("JWT tokens decoder"); 20 | description: _("Decode JWT tokens to header and payload"); 21 | tool-name: "jwt-decoder"; 22 | } 23 | 24 | Separator { 25 | margin-top: 10; 26 | } 27 | 28 | $TextArea _token_area { 29 | name: _("Token"); 30 | show-clear-btn: "true"; 31 | show-paste-btn: "true"; 32 | area-height: "100"; 33 | text-wrap-mode: "char"; 34 | } 35 | 36 | Separator { 37 | margin-top: 10; 38 | } 39 | 40 | Box { 41 | orientation: horizontal; 42 | homogeneous: true; 43 | spacing: 10; 44 | margin-bottom: 10; 45 | 46 | $TextArea _header_area { 47 | name: _("Header"); 48 | show-copy-btn: "true"; 49 | text-wrap-mode: "none"; 50 | } 51 | 52 | $TextArea _payload_area { 53 | name: _("Payload"); 54 | show-copy-btn: "true"; 55 | text-wrap-mode: "none"; 56 | } 57 | } 58 | }; 59 | }; 60 | }; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ui/views/lorem_generator.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $LoremGeneratorView: Adw.Bin { 8 | Adw.ToastOverlay _toast { 9 | child: ScrolledWindow { 10 | child: Adw.Clamp { 11 | vexpand: true; 12 | maximum-size: 1200; 13 | tightening-threshold: 600; 14 | 15 | child: Box { 16 | orientation: vertical; 17 | // Title 18 | $UtilityTitle _title { 19 | title: _("Lorem Ipsum Generator"); 20 | description: _("Generate lorem ipsum placeholder text"); 21 | tool-name: "lorem-generator"; 22 | } 23 | 24 | Separator { 25 | margin-top: 10; 26 | margin-bottom: 10; 27 | } 28 | 29 | // Options 30 | Adw.PreferencesGroup { 31 | title: _("Tool options"); 32 | 33 | Adw.SwitchRow _begin_with_switch { 34 | title: _("Begin with \"Lorem ipsum dolor sit amet\""); 35 | icon-name: "text-direction-ltr"; 36 | active: true; 37 | } 38 | 39 | Adw.SpinRow _quantity_spinner { 40 | title: _("Amount"); 41 | icon-name: "stacked-plates"; 42 | 43 | adjustment: Adjustment { 44 | lower: 1; 45 | upper: 999; 46 | step-increment: 1; 47 | value: 5; 48 | }; 49 | 50 | [suffix] 51 | DropDown _quantity_combo { 52 | model: StringList { 53 | strings [ 54 | _("Words"), 55 | _("Sentences"), 56 | _("Paragraphs") 57 | ] 58 | }; 59 | } 60 | } 61 | } 62 | 63 | Separator { 64 | margin-top: 10; 65 | } 66 | 67 | // Text area 68 | $TextArea _output_area { 69 | name: _("Generated text"); 70 | show-copy-btn: true; 71 | text-editable: false; 72 | allow-drag-and-drop: false; 73 | margin-bottom: 10; 74 | text-wrap-mode: word; 75 | } 76 | }; 77 | }; 78 | }; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/ui/views/markdown_preview.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $MarkdownPreviewView: Adw.Bin { 8 | Adw.ToastOverlay _toast { 9 | child: ScrolledWindow { 10 | child: Adw.Clamp { 11 | vexpand: true; 12 | maximum-size: 1200; 13 | tightening-threshold: 600; 14 | 15 | child: Box { 16 | orientation: vertical; 17 | 18 | $UtilityTitle _title { 19 | title: _("Markdown Previewer"); 20 | description: _("Preview markdown code as you type"); 21 | tool-name: "markdown-preview"; 22 | } 23 | 24 | Separator { 25 | margin-top: 10; 26 | } 27 | 28 | Box { 29 | orientation: horizontal; 30 | homogeneous: true; 31 | spacing: 10; 32 | margin-bottom: 10; 33 | 34 | $TextArea _textarea { 35 | name: _("Markdown"); 36 | show-clear-btn: true; 37 | show-paste-btn: true; 38 | show-open-btn: true; 39 | show-copy-btn: true; 40 | use-custom-file-extensions: true; 41 | custom-file-extensions: "md"; 42 | text-show-line-numbers: true; 43 | text-syntax-highlighting: true; 44 | text-highlight-current-line: true; 45 | text-wrap-mode: "word-char"; 46 | } 47 | 48 | $WebviewArea _webarea { 49 | name: _("Preview"); 50 | } 51 | } 52 | }; 53 | }; 54 | }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/ui/views/regex_tester.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $RegexTesterView: Adw.Bin { 8 | Adw.ToastOverlay _toast { 9 | child: ScrolledWindow { 10 | child: Adw.Clamp { 11 | vexpand: true; 12 | maximum-size: 1200; 13 | tightening-threshold: 600; 14 | 15 | child: Box { 16 | orientation: vertical; 17 | 18 | $UtilityTitle _title { 19 | title: _("Regular Expression Tester"); 20 | description: _("Find matching strings inside a text"); 21 | tool-name: "regex-tester"; 22 | } 23 | 24 | Separator { 25 | margin-top: 10; 26 | margin-bottom: 10; 27 | } 28 | 29 | Adw.PreferencesGroup { 30 | title: _("Tool options"); 31 | 32 | $EntryRow _regex_entry { 33 | styles [ 34 | "monospace", 35 | ] 36 | 37 | title: _("Regex to test"); 38 | show-copy-btn: "true"; 39 | show-paste-btn: "true"; 40 | show-clear-btn: "true"; 41 | } 42 | } 43 | 44 | Separator { 45 | margin-top: 10; 46 | } 47 | 48 | $TextArea _textarea { 49 | name: _("Text"); 50 | show-copy-btn: "true"; 51 | show-clear-btn: "true"; 52 | show-paste-btn: "true"; 53 | show-open-btn: "true"; 54 | use-default-text-extensions: "true"; 55 | margin-bottom: "10"; 56 | text-wrap-mode: "word-char"; 57 | } 58 | }; 59 | }; 60 | }; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ui/views/text_diff.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $TextDiffView: Adw.Bin { 8 | Adw.ToastOverlay _toast { 9 | child: ScrolledWindow { 10 | child: Adw.Clamp { 11 | vexpand: true; 12 | maximum-size: 1200; 13 | tightening-threshold: 600; 14 | 15 | child: Box { 16 | orientation: vertical; 17 | 18 | $UtilityTitle _title { 19 | title: _("Text Diff"); 20 | description: _("Analyze two texts and find differences"); 21 | tool-name: "text-diff"; 22 | } 23 | 24 | Separator { 25 | margin-top: 10; 26 | } 27 | 28 | Box { 29 | orientation: horizontal; 30 | spacing: 10; 31 | 32 | $TextArea _old_textarea { 33 | name: _("Old Text"); 34 | show-copy-btn: true; 35 | show-clear-btn: true; 36 | show-paste-btn: true; 37 | show-open-btn: true; 38 | text-show-line-numbers: true; 39 | use-default-text-extensions: true; 40 | text-highlight-current-line: true; 41 | text-wrap-mode: "word-char"; 42 | } 43 | 44 | $TextArea _new_textarea { 45 | name: _("New Text"); 46 | show-copy-btn: true; 47 | show-clear-btn: true; 48 | show-paste-btn: true; 49 | show-open-btn: true; 50 | text-show-line-numbers: true; 51 | use-default-text-extensions: true; 52 | text-highlight-current-line: true; 53 | text-wrap-mode: "word-char"; 54 | } 55 | } 56 | 57 | $TextArea _diff_textarea { 58 | name: _("Differences"); 59 | show-copy-btn: true; 60 | text-editable: false; 61 | margin-bottom: "10"; 62 | text-wrap-mode: "word-char"; 63 | } 64 | }; 65 | }; 66 | }; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/ui/views/timestamp.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $TimestampView: Adw.Bin { 8 | Adw.ToastOverlay _toast { 9 | child: ScrolledWindow { 10 | child: Adw.Clamp { 11 | vexpand: true; 12 | maximum-size: 1200; 13 | tightening-threshold: 600; 14 | 15 | child: Box { 16 | orientation: vertical; 17 | 18 | $UtilityTitle _title { 19 | title: _("Timestamp Converter"); 20 | description: _("Convert UNIX timestamps to and from plain dates"); 21 | tool-name: "timestamp"; 22 | } 23 | 24 | Separator { 25 | margin-top: 10; 26 | margin-bottom: 10; 27 | } 28 | 29 | Adw.PreferencesGroup _tool_options { 30 | title: _("Tool options"); 31 | } 32 | 33 | Separator { 34 | margin-top: 10; 35 | } 36 | 37 | $SpinArea _timestamp_spin_area { 38 | name: _("Timestamp"); 39 | show-copy-btn: "true"; 40 | show-paste-btn: "true"; 41 | show-action-btn: "true"; 42 | action-btn-name: _("Now"); 43 | action-btn-tooltip: _("Sets the current system timestamp"); 44 | } 45 | 46 | $DateArea _date_area { 47 | name: _("Date"); 48 | } 49 | 50 | Adw.PreferencesGroup { 51 | margin-top: 10; 52 | margin-bottom: 20; 53 | 54 | $EntryRow _iso_date { 55 | editable: "false"; 56 | show-copy-btn: "true"; 57 | title: _("ISO Date (ISO 8601/RFC 3339)"); 58 | } 59 | 60 | $EntryRow _rfc2822_date { 61 | editable: false; 62 | show-copy-btn: true; 63 | title: _("RFC 2822 Date"); 64 | } 65 | 66 | $EntryRow _short_date { 67 | editable: false; 68 | show-copy-btn: true; 69 | title: _("Short Date"); 70 | } 71 | 72 | $EntryRow _short_time { 73 | editable: false; 74 | show-copy-btn: true; 75 | title: _("Short Time"); 76 | } 77 | 78 | $EntryRow _long_date { 79 | editable: false; 80 | show-copy-btn: true; 81 | title: _("Long Date"); 82 | } 83 | 84 | $EntryRow _long_time { 85 | editable: false; 86 | show-copy-btn: true; 87 | title: _("Long Time"); 88 | } 89 | 90 | $EntryRow _full_long_date { 91 | editable: false; 92 | show-copy-btn: true; 93 | title: _("Full Long Date"); 94 | } 95 | } 96 | }; 97 | }; 98 | }; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/ui/views/uuid_generator.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $UuidGeneratorView: Adw.Bin { 8 | Adw.ToastOverlay _toast { 9 | child: ScrolledWindow { 10 | child: Adw.Clamp { 11 | vexpand: true; 12 | maximum-size: 1200; 13 | tightening-threshold: 600; 14 | 15 | child: Box { 16 | orientation: vertical; 17 | // Title 18 | $UtilityTitle _title { 19 | title: _("UUID Generator"); 20 | description: _("Generate Universally Unique IDs (UUID)"); 21 | tool-name: "uuid-generator"; 22 | } 23 | 24 | Separator { 25 | margin-top: 10; 26 | margin-bottom: 10; 27 | } 28 | 29 | // Options 30 | Adw.PreferencesGroup { 31 | title: _("Tool options"); 32 | 33 | Adw.ComboRow _version_dropdown { 34 | title: _("Version"); 35 | subtitle: _("Choose the UUID version to generate"); 36 | icon-name: "list-large"; 37 | 38 | model: StringList { 39 | strings [ 40 | _("Version 1 (MAC address)"), 41 | _("Version 3"), 42 | _("Version 4 (Random)"), 43 | _("Version 5"), 44 | _("Version 6 (Gregorian Time)"), 45 | _("Version 7 (UNIX Epoch)") 46 | ] 47 | }; 48 | 49 | selected: 2; 50 | } 51 | 52 | Adw.SpinRow _amount_spinner { 53 | title: _("Amount"); 54 | subtitle: _("Number of UUIDs to be generated (up to 50)"); 55 | icon-name: "stacked-plates"; 56 | 57 | adjustment: Adjustment { 58 | lower: 1; 59 | upper: 50; 60 | step-increment: 1; 61 | value: 15; 62 | }; 63 | } 64 | } 65 | 66 | Separator { 67 | margin-top: 10; 68 | } 69 | 70 | // Text area 71 | $TextArea _output_area { 72 | name: _("Generated UUIDs"); 73 | show-copy-btn: true; 74 | text-editable: false; 75 | allow-drag-and-drop: false; 76 | margin-bottom: 10; 77 | text-wrap-mode: char; 78 | } 79 | }; 80 | }; 81 | }; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/ui/views/xml_validator.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $XmlValidatorView: Adw.Bin { 8 | Adw.ToastOverlay _toast { 9 | child: ScrolledWindow { 10 | child: Adw.Clamp { 11 | vexpand: true; 12 | maximum-size: 1200; 13 | tightening-threshold: 600; 14 | 15 | child: Box { 16 | orientation: vertical; 17 | 18 | $UtilityTitle _title { 19 | title: _("XML Scheme Validator"); 20 | description: _("Check an XML file against an XSD schema"); 21 | tool-name: "xml-validator"; 22 | } 23 | 24 | Separator { 25 | margin-top: 10; 26 | } 27 | 28 | Box { 29 | orientation: horizontal; 30 | homogeneous: true; 31 | spacing: 10; 32 | margin-bottom: 10; 33 | 34 | $TextArea _xml_textarea { 35 | name: "XML"; 36 | show-clear-btn: true; 37 | show-paste-btn: true; 38 | show-open-btn: true; 39 | show-copy-btn: true; 40 | use-custom-file-extensions: true; 41 | custom-file-extensions: "xml"; 42 | text-show-line-numbers: true; 43 | text-syntax-highlighting: true; 44 | text-highlight-current-line: true; 45 | text-wrap-mode: "none"; 46 | } 47 | 48 | $TextArea _xsd_textarea { 49 | name: "XSD"; 50 | show-clear-btn: true; 51 | show-paste-btn: true; 52 | show-open-btn: true; 53 | show-copy-btn: true; 54 | use-custom-file-extensions: true; 55 | custom-file-extensions: "xsd"; 56 | text-show-line-numbers: true; 57 | text-syntax-highlighting: true; 58 | text-highlight-current-line: true; 59 | text-wrap-mode: "none"; 60 | } 61 | } 62 | 63 | Box _check_box { 64 | visible: false; 65 | orientation: horizontal; 66 | halign: center; 67 | margin-bottom: 15; 68 | margin-top: 5; 69 | 70 | Image _check_icon { 71 | pixel-size: 56; 72 | margin-end: 20; 73 | } 74 | 75 | Box { 76 | orientation: vertical; 77 | valign: center; 78 | 79 | Label _check_title_lbl { 80 | styles [ 81 | "heading", 82 | ] 83 | 84 | margin-bottom: 5; 85 | } 86 | 87 | Label _check_lbl { 88 | justify: center; 89 | width-request: 100; 90 | wrap: true; 91 | } 92 | } 93 | } 94 | }; 95 | }; 96 | }; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/ui/widgets/entry_row.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | using Gtk 4.0; 6 | using Adw 1; 7 | 8 | template $EntryRow : Adw.EntryRow { 9 | [suffix] 10 | Box { 11 | orientation: horizontal; 12 | 13 | styles [ 14 | "linked", 15 | ] 16 | 17 | Button _generate_btn { 18 | icon-name: "update"; 19 | valign: center; 20 | tooltip-text: _("Generate again"); 21 | 22 | styles [ 23 | "flat", 24 | ] 25 | } 26 | 27 | Button _copy_btn { 28 | icon-name: "edit-copy"; 29 | valign: center; 30 | tooltip-text: _("Copy to clipboard"); 31 | 32 | styles [ 33 | "flat", 34 | ] 35 | } 36 | 37 | Button _paste_btn { 38 | icon-name: "edit-paste"; 39 | valign: center; 40 | tooltip-text: _("Paste clipboard"); 41 | 42 | styles [ 43 | "flat", 44 | ] 45 | } 46 | 47 | [suffix] 48 | Button _clear_btn { 49 | icon-name: "edit-clear"; 50 | valign: center; 51 | tooltip-text: _("Clear input"); 52 | 53 | styles [ 54 | "flat", 55 | ] 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ui/widgets/file_view.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $FileView: Adw.Bin { 8 | styles [ 9 | "view", 10 | "rounded-border" 11 | ] 12 | 13 | Box { 14 | orientation: vertical; 15 | valign: center; 16 | vexpand: true; 17 | spacing: 10; 18 | margin-start: 20; 19 | margin-end: 20; 20 | 21 | Image { 22 | icon-name: "paper-filled"; 23 | pixel-size: 64; 24 | } 25 | 26 | Label _file_size_lbl {} 27 | 28 | Label _file_path_lbl { 29 | wrap: true; 30 | wrap-mode: char; 31 | justify: center; 32 | ellipsize: middle; 33 | lines: 5; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ui/widgets/spin_area.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $SpinArea: Adw.Bin { 8 | Box { 9 | margin-top: 10; 10 | orientation: vertical; 11 | spacing: 6; 12 | 13 | Box { 14 | orientation: horizontal; 15 | halign: fill; 16 | hexpand: true; 17 | 18 | Label _name_lbl { 19 | margin-bottom: 8; 20 | margin-top: 8; 21 | margin-start: 8; 22 | 23 | styles [ 24 | "heading" 25 | ] 26 | } 27 | 28 | Box { 29 | orientation: horizontal; 30 | halign: end; 31 | hexpand: true; 32 | 33 | Adw.Spinner _spinner { 34 | width-request: 16; 35 | } 36 | 37 | Separator _spinner_separator { 38 | styles [ 39 | "spacer" 40 | ] 41 | } 42 | 43 | Button _action_btn { 44 | styles [ 45 | "accent" 46 | ] 47 | } 48 | 49 | Separator _action_btn_separator { 50 | styles [ 51 | "spacer" 52 | ] 53 | } 54 | 55 | Box { 56 | margin-start: 6; 57 | spacing: 6; 58 | orientation: horizontal; 59 | 60 | Button _copy_btn { 61 | valign: center; 62 | 63 | styles [ 64 | "flat" 65 | ] 66 | 67 | icon-name: "edit-copy-symbolic"; 68 | tooltip-text: _("Copy to clipboard"); 69 | } 70 | 71 | Button _paste_btn { 72 | valign: center; 73 | 74 | styles [ 75 | "flat" 76 | ] 77 | 78 | icon-name: "edit-paste-symbolic"; 79 | tooltip-text: _("Paste clipboard"); 80 | } 81 | } 82 | } 83 | } 84 | 85 | Box { 86 | styles [ 87 | "card" 88 | ] 89 | 90 | orientation: vertical; 91 | 92 | SpinButton _spin_btn { 93 | styles [ 94 | "rounded-border", 95 | "view" 96 | ] 97 | 98 | numeric: true; 99 | update-policy: if_valid; 100 | 101 | adjustment: Adjustment { 102 | lower: 0; 103 | upper: 99999999999; 104 | step-increment: 1; 105 | }; 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/ui/widgets/theme_switcher.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | using Gtk 4.0; 6 | 7 | template $ThemeSwitcher : Box { 8 | styles [ 9 | "themeswitcher", 10 | ] 11 | 12 | hexpand: true; 13 | 14 | Box box { 15 | hexpand: true; 16 | orientation: horizontal; 17 | spacing: 12; 18 | 19 | CheckButton system { 20 | styles [ 21 | "theme-selector", 22 | "system", 23 | ] 24 | 25 | visible: bind template.show-system; 26 | hexpand: true; 27 | halign: center; 28 | focus-on-click: false; 29 | tooltip-text: _("Follow system style"); 30 | notify::active => $_on_color_scheme_changed(); 31 | } 32 | 33 | CheckButton light { 34 | styles [ 35 | "theme-selector", 36 | "light", 37 | ] 38 | 39 | hexpand: true; 40 | halign: center; 41 | group: system; 42 | focus-on-click: false; 43 | tooltip-text: _("Light style"); 44 | notify::active => $_on_color_scheme_changed(); 45 | } 46 | 47 | CheckButton dark { 48 | styles [ 49 | "theme-selector", 50 | "dark", 51 | ] 52 | 53 | hexpand: true; 54 | halign: center; 55 | group: system; 56 | focus-on-click: false; 57 | tooltip-text: _("Dark style"); 58 | notify::active => $_on_color_scheme_changed(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/ui/widgets/utility_title.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | using Gtk 4.0; 6 | using Adw 1; 7 | 8 | template $UtilityTitle : Adw.Bin { 9 | 10 | map => $_on_map(); 11 | 12 | Grid { 13 | Button _star_btn { 14 | visible: true; 15 | valign: center; 16 | margin-top: 10; 17 | 18 | styles [ 19 | "circular", 20 | ] 21 | 22 | layout { 23 | column: "0"; 24 | row: "0"; 25 | row-span: "2"; 26 | } 27 | 28 | clicked => $_on_star_btn_clicked(); 29 | } 30 | 31 | Label _title_lbl { 32 | halign: start; 33 | margin-top: 10; 34 | margin-start: 10; 35 | 36 | styles [ 37 | "title-1", 38 | ] 39 | 40 | layout { 41 | column: "1"; 42 | row: "0"; 43 | } 44 | } 45 | 46 | Label _description_lbl { 47 | halign: start; 48 | margin-start: 10; 49 | 50 | styles [ 51 | "caption-heading", 52 | ] 53 | 54 | layout { 55 | column: "1"; 56 | row: "1"; 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ui/widgets/webview_area.blp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 - 2023 Alessandro Iepure 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | using Gtk 4.0; 5 | using Adw 1; 6 | 7 | template $WebviewArea: Adw.Bin { 8 | Box _box { 9 | margin-top: 10; 10 | orientation: vertical; 11 | spacing: 6; 12 | 13 | Box { 14 | orientation: horizontal; 15 | halign: fill; 16 | hexpand: true; 17 | 18 | Label _name_lbl { 19 | margin-bottom: 8; 20 | margin-top: 8; 21 | margin-start: 8; 22 | 23 | styles [ 24 | "heading" 25 | ] 26 | } 27 | 28 | Box { 29 | orientation: horizontal; 30 | halign: end; 31 | hexpand: true; 32 | 33 | Adw.Spinner _spinner {} 34 | 35 | Separator _spinner_separator { 36 | styles [ 37 | "spacer" 38 | ] 39 | } 40 | 41 | Box { 42 | orientation: horizontal; 43 | 44 | Button _view_btn { 45 | styles [ 46 | "flat" 47 | ] 48 | 49 | valign: center; 50 | icon-name: "eye-open-symbolic"; 51 | tooltip-text: _("Open web page in another browser"); 52 | sensitive: false; 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/views/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | -------------------------------------------------------------------------------- /src/views/cron_converter.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gtk, Adw, GObject, Gio 6 | 7 | from ..services.cron_converter import CronConverterService 8 | from ..utils import Utils 9 | 10 | 11 | @Gtk.Template(resource_path="/me/iepure/devtoolbox/ui/views/cron_converter.ui") 12 | class CronConverterView(Adw.Bin): 13 | __gtype_name__ = "CronConverterView" 14 | 15 | # Template elements 16 | _toast = Gtk.Template.Child() 17 | _title = Gtk.Template.Child() 18 | _dates_spinner = Gtk.Template.Child() 19 | _format_text = Gtk.Template.Child() 20 | _expression = Gtk.Template.Child() 21 | _output_area = Gtk.Template.Child() 22 | 23 | # Service 24 | _service = CronConverterService() 25 | 26 | def __init__(self): 27 | super().__init__() 28 | 29 | self._generate_dates() 30 | 31 | # Signals 32 | self._dates_spinner.connect("notify::value", self._on_dates_value_changed) 33 | self._format_text.connect("changed", self._on_format_changed) 34 | self._expression.connect("changed", self._on_expression_changed) 35 | self._expression.connect("cleared", self._on_expression_cleared) 36 | 37 | def _on_dates_value_changed(self, pspec: GObject.ParamSpec, user_data:GObject.GPointer): 38 | self._generate_dates() 39 | 40 | def _on_format_changed(self, source_widget:GObject.Object): 41 | self._generate_dates() 42 | 43 | def _on_expression_changed(self, user_data:GObject.GPointer): 44 | self._generate_dates() 45 | 46 | def _on_expression_cleared(self, source_widget:GObject.Object): 47 | self._output_area.clear() 48 | 49 | def _generate_dates(self): 50 | self._expression.remove_css_class("border-red") 51 | 52 | expression = self._expression.get_text() 53 | format_str = self._format_text.get_text() 54 | number = int(self._dates_spinner.get_value()) 55 | 56 | # Setup task 57 | self._service.set_expression(expression) 58 | self._service.set_format_str(format_str) 59 | self._service.set_quantity(number) 60 | 61 | if Utils.is_cron_expression_valid(expression): 62 | self._output_area.set_spinner_spin(True) 63 | self._service.generate_dates_async(self, self._on_generate_done) 64 | 65 | else: 66 | self._output_area.set_spinner_spin(False) 67 | if len(expression) != 0: 68 | self._expression.add_css_class("border-red") 69 | 70 | def _on_generate_done(self, source_widget:GObject.Object, result:Gio.AsyncResult, user_data:GObject.GPointer): 71 | self._output_area.set_spinner_spin(False) 72 | outcome = self._service.generate_dates_async_finish(result, self) 73 | self._output_area.set_text(outcome) 74 | -------------------------------------------------------------------------------- /src/views/html_encoder.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gtk, Adw, Gio, GObject 6 | from gettext import gettext as _ 7 | 8 | from ..utils import Utils 9 | from ..services.html_encoder import HtmlEncoderService 10 | 11 | 12 | @Gtk.Template(resource_path="/me/iepure/devtoolbox/ui/views/html_encoder.ui") 13 | class HtmlEncoderView(Adw.Bin): 14 | __gtype_name__ = "HtmlEncoderView" 15 | 16 | # Template elements 17 | _toast = Gtk.Template.Child() 18 | _title = Gtk.Template.Child() 19 | _direction_selector = Gtk.Template.Child() 20 | _input_area = Gtk.Template.Child() 21 | _output_area = Gtk.Template.Child() 22 | 23 | _service = HtmlEncoderService() 24 | 25 | def __init__(self): 26 | super().__init__() 27 | 28 | # Language highlight 29 | self._input_area.set_text_language_highlight("html") 30 | self._output_area.set_text_language_highlight("html") 31 | 32 | # Signals 33 | # self._direction_selector.connect("toggled", self._on_input_changed) 34 | self._input_area.connect("text-changed", self._on_input_changed) 35 | self._input_area.connect("error", self._on_error) 36 | self._input_area.connect("view-cleared", self._on_view_cleared) 37 | self._output_area.connect("error", self._on_error) 38 | 39 | @Gtk.Template.Callback() 40 | def _on_direction_changed(self, 41 | pspec: GObject.GParamSpec, 42 | user_data: GObject.GPointer = None) -> None: 43 | self._convert() 44 | 45 | def _on_input_changed(self, source_widget: GObject.Object): 46 | self._convert() 47 | 48 | def _on_view_cleared(self, source_widget: GObject.Object): 49 | self._output_area.clear() 50 | 51 | def _on_error(self, source_widget: GObject.Object, error: str): 52 | self._toast.add_toast(Adw.Toast(title=_("Error: {error}").format( 53 | error=error), priority=Adw.ToastPriority.HIGH)) 54 | 55 | def _convert(self): 56 | 57 | # Stop previous tasks 58 | self._service.get_cancellable().cancel() 59 | self._output_area.set_spinner_spin(False) 60 | self._input_area.remove_css_class("border-red") 61 | 62 | # Setup task 63 | text = self._input_area.get_text() 64 | self._service.set_input(text) 65 | 66 | # Call task 67 | if self._direction_selector.get_active() == 0: # True: encode, False: decode 68 | self._output_area.set_spinner_spin(True) 69 | self._service.encode_async(self, self._on_async_done) 70 | else: 71 | self._output_area.set_spinner_spin(True) 72 | self._service.decode_async(self, self._on_async_done) 73 | 74 | def _on_async_done(self, source_widget: GObject.Object, result: Gio.AsyncResult, user_data: GObject.GPointer): 75 | self._output_area.set_spinner_spin(False) 76 | outcome = self._service.async_finish(result, self) 77 | self._output_area.set_text(outcome) 78 | -------------------------------------------------------------------------------- /src/views/jwt_decoder.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Adw, Gtk, GObject, Gio 6 | 7 | from ..utils import Utils 8 | from ..services.jwt_decoder import JwtDecoderService 9 | 10 | 11 | @Gtk.Template(resource_path="/me/iepure/devtoolbox/ui/views/jwt_decoder.ui") 12 | class JwtDecoderView(Adw.Bin): 13 | __gtype_name__ = "JwtDecoderView" 14 | 15 | _toast = Gtk.Template.Child() 16 | _title = Gtk.Template.Child() 17 | _token_area = Gtk.Template.Child() 18 | _header_area = Gtk.Template.Child() 19 | _payload_area = Gtk.Template.Child() 20 | 21 | _service = JwtDecoderService() 22 | 23 | def __init__(self): 24 | super().__init__() 25 | 26 | # Fix layout 27 | self._token_area.get_child().set_size_request(-1, 200) 28 | self._token_area.get_child().set_vexpand(False) 29 | self._token_area.get_child().set_vexpand_set(True) 30 | 31 | # Signals 32 | self._token_area.connect("view-cleared", self._on_view_cleared) 33 | self._token_area.connect("text-changed", self._on_token_changed) 34 | 35 | def _on_view_cleared(self, source_widget:GObject.Object): 36 | self._header_area.clear() 37 | self._payload_area.clear() 38 | 39 | def _on_token_changed(self, source_widget:GObject.Object): 40 | # Stop previous tasks 41 | self._service.get_cancellable().cancel() 42 | self._header_area.set_spinner_spin(False) 43 | self._payload_area.set_spinner_spin(False) 44 | self._token_area.remove_css_class("border-red") 45 | 46 | # Setup task 47 | token = self._token_area.get_text() 48 | self._service.set_token(token) 49 | if len(token) > 0 and Utils.is_jwt_token(token): 50 | self._header_area.set_spinner_spin(True) 51 | self._payload_area.set_spinner_spin(True) 52 | self._service.decode_header_async(self, self._on_header_decode_done) 53 | self._service.decode_payload_async(self, self._on_payload_decode_done) 54 | elif len(token) > 0: 55 | self._header_area.set_spinner_spin(False) 56 | self._payload_area.set_spinner_spin(False) 57 | self._token_area.add_css_class("border-red") 58 | 59 | def _on_header_decode_done(self, source_widget:GObject.Object, result:Gio.AsyncResult, user_data:GObject.GPointer): 60 | outcome = self._service.decode_finish(result, self) 61 | self._header_area.set_spinner_spin(False) 62 | self._header_area.set_text(outcome) 63 | 64 | def _on_payload_decode_done(self, source_widget:GObject.Object, result:Gio.AsyncResult, user_data:GObject.GPointer): 65 | outcome = self._service.decode_finish(result, self) 66 | self._payload_area.set_spinner_spin(False) 67 | self._payload_area.set_text(outcome) 68 | -------------------------------------------------------------------------------- /src/views/lorem_generator.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Adw, Gtk, GObject 6 | from gettext import gettext as _ 7 | 8 | from ..services.lorem_generator import LoremGeneratorService 9 | 10 | 11 | @Gtk.Template(resource_path="/me/iepure/devtoolbox/ui/views/lorem_generator.ui") 12 | class LoremGeneratorView(Adw.Bin): 13 | __gtype_name__ = "LoremGeneratorView" 14 | 15 | # Template elements 16 | _toast = Gtk.Template.Child() 17 | _title = Gtk.Template.Child() 18 | _begin_with_switch = Gtk.Template.Child() 19 | _quantity_combo = Gtk.Template.Child() 20 | _quantity_spinner = Gtk.Template.Child() 21 | _output_area = Gtk.Template.Child() 22 | 23 | _service = LoremGeneratorService() 24 | 25 | def __init__(self): 26 | super().__init__() 27 | 28 | self._generate_text() 29 | 30 | # Signals 31 | self._begin_with_switch.connect("notify::active", self._on_begin_with_switch_changed) 32 | self._quantity_combo.connect("notify::selected", self._on_quantity_combo_changed) 33 | self._quantity_spinner.connect("notify::value", self._on_quantity_spinner_changed) 34 | 35 | def _on_begin_with_switch_changed(self, pspec:GObject.ParamSpec, user_data:GObject.GPointer): 36 | self._generate_text() 37 | 38 | def _on_quantity_combo_changed(self, pspec:GObject.ParamSpec, user_data:GObject.GPointer): 39 | self._generate_text() 40 | 41 | def _on_quantity_spinner_changed(self, pspec: GObject.ParamSpec, user_data:GObject.GPointer): 42 | self._generate_text() 43 | 44 | def _generate_text(self): 45 | 46 | # Stop previous tasks 47 | self._service.get_cancellable().cancel() 48 | self._output_area.set_spinner_spin(False) 49 | 50 | # Setup task 51 | self._output_area.set_spinner_spin(True) 52 | self._service.set_beginning(self._begin_with_switch.get_active()) 53 | self._service.set_amount(self._quantity_combo.get_selected(), int(self._quantity_spinner.get_value())) 54 | 55 | # Call task 56 | self._service.generate_text_async(self, self._on_generate_done) 57 | 58 | def _on_generate_done(self, source_object, result, data): 59 | self._output_area.set_spinner_spin(False) 60 | outcome = self._service.async_finish(result, self) 61 | self._output_area.set_text(outcome) 62 | -------------------------------------------------------------------------------- /src/views/markdown_preview.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gtk, Adw, Gio, GObject 6 | 7 | from ..services.markdown_preview import MarkdownPreviewService 8 | 9 | 10 | @Gtk.Template(resource_path='/me/iepure/devtoolbox/ui/views/markdown_preview.ui') 11 | class MarkdownPreviewView(Adw.Bin): 12 | __gtype_name__ = "MarkdownPreviewView" 13 | 14 | # Template elements 15 | _toast = Gtk.Template.Child() 16 | _title = Gtk.Template.Child() 17 | _textarea = Gtk.Template.Child() 18 | _webarea = Gtk.Template.Child() 19 | 20 | _service = MarkdownPreviewService() 21 | 22 | def __init__(self): 23 | super().__init__() 24 | 25 | self._textarea.set_text_language_highlight("markdown") 26 | 27 | # Signals 28 | self._textarea.connect("text-changed", self._on_input_changed) 29 | self._textarea.connect("view-cleared", self._on_view_cleared) 30 | Adw.StyleManager.get_default().connect("notify::dark", self._on_style_changed) 31 | 32 | self._load_markdown() 33 | 34 | def _on_view_cleared(self, source_widget: GObject.Object): 35 | self._service.get_cancellable().cancel() 36 | 37 | def _on_input_changed(self, source_widget: GObject.Object): 38 | self._load_markdown() 39 | 40 | def _on_style_changed(self, pspec: GObject.ParamSpec, user_data: GObject.GPointer): 41 | self._load_markdown() 42 | 43 | def _load_markdown(self): 44 | 45 | # Stop previous tasks 46 | self._service.get_cancellable().cancel() 47 | 48 | # Setup task 49 | self._service.set_markdown(self._textarea.get_text()) 50 | 51 | # Call task 52 | self._service.build_html_from_markdown_async(self, self._on_build_done) 53 | 54 | def _on_build_done(self, source_widget: GObject.Object, result: Gio.AsyncResult, user_data: GObject.GPointer): 55 | html_path = self._service.async_finish(result, self) 56 | self._webarea.load_uri("file://" + html_path) 57 | -------------------------------------------------------------------------------- /src/views/meson.build: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name()) 6 | viewsdir = join_paths(pkgdatadir, 'devtoolbox/views') 7 | 8 | sources = [ 9 | '__init__.py', 10 | 'json_yaml.py', 11 | 'timestamp.py', 12 | 'base_converter.py', 13 | 'cron_converter.py', 14 | 'base64_encoder.py', 15 | 'html_encoder.py', 16 | 'url_encoder.py', 17 | 'gzip_compressor.py', 18 | 'jwt_decoder.py', 19 | 'formatter.py', 20 | 'hash_generator.py', 21 | 'lorem_generator.py', 22 | 'uuid_generator.py', 23 | 'text_inspector.py', 24 | 'regex_tester.py', 25 | 'text_diff.py', 26 | 'xml_validator.py', 27 | 'markdown_preview.py', 28 | 'contrast_checker.py', 29 | 'colorblindness_simulator.py', 30 | 'image_converter.py', 31 | 'certificate_parser.py', 32 | 'random_generator.py', 33 | 'certificate_request_generator.py', 34 | 'reverse_cron.py', 35 | 'chmod_calculator.py', 36 | 'qrcode_generator.py', 37 | 'json_validator.py', 38 | ] 39 | 40 | install_data(sources, install_dir: viewsdir) 41 | -------------------------------------------------------------------------------- /src/views/uuid_generator.py: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (C) 2022 - 2023 Alessandro Iepure 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | from gi.repository import Gtk, Adw, GObject, Gio 7 | 8 | from ..services.uuid_generator import UuidGeneratorService 9 | 10 | 11 | @Gtk.Template(resource_path="/me/iepure/devtoolbox/ui/views/uuid_generator.ui") 12 | class UuidGeneratorView(Adw.Bin): 13 | __gtype_name__ = "UuidGeneratorView" 14 | 15 | # Template elements 16 | _toast = Gtk.Template.Child() 17 | _title = Gtk.Template.Child() 18 | _version_dropdown = Gtk.Template.Child() 19 | _amount_spinner = Gtk.Template.Child() 20 | _output_area = Gtk.Template.Child() 21 | 22 | _service = UuidGeneratorService() 23 | 24 | def __init__(self): 25 | super().__init__() 26 | 27 | self._generate() 28 | 29 | # Signals 30 | self._version_dropdown.connect("notify::selected", self._on_version_changed) 31 | self._amount_spinner.connect("notify::value", self._on_amount_changed) 32 | 33 | def _on_version_changed(self, pspec:GObject.ParamSpec, user_data:GObject.GPointer): 34 | self._generate() 35 | 36 | def _on_amount_changed(self, pspec:GObject.ParamSpec, user_data:GObject.GPointer): 37 | self._generate() 38 | 39 | def _generate(self): 40 | 41 | # Stop previous tasks 42 | self._service.get_cancellable().cancel() 43 | self._output_area.set_spinner_spin(False) 44 | 45 | # Setup task 46 | self._output_area.set_spinner_spin(True) 47 | self._service.set_version(self._version_dropdown.get_selected()) 48 | self._service.set_amount(int(self._amount_spinner.get_value())) 49 | 50 | # Call task 51 | self._service.generate_async(self, self._on_generation_done) 52 | 53 | def _on_generation_done(self, source_widget:GObject.Object, result:Gio.AsyncResult, user_data:GObject.GPointer): 54 | outcome = self._service.async_finish(result, self) 55 | 56 | if len(outcome) > 0: 57 | self._output_area.set_text(outcome) 58 | 59 | self._output_area.set_spinner_spin(False) 60 | -------------------------------------------------------------------------------- /src/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | -------------------------------------------------------------------------------- /src/widgets/entry_row.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gtk, Adw, GObject, Gdk, Gio 6 | 7 | 8 | @Gtk.Template(resource_path="/me/iepure/devtoolbox/ui/widgets/entry_row.ui") 9 | class EntryRow(Adw.EntryRow): 10 | __gtype_name__ = "EntryRow" 11 | 12 | # Template elements 13 | _generate_btn = Gtk.Template.Child() 14 | _copy_btn = Gtk.Template.Child() 15 | _paste_btn = Gtk.Template.Child() 16 | _clear_btn = Gtk.Template.Child() 17 | 18 | # Properties 19 | show_clear_btn = GObject.Property(type=bool, default=False) 20 | show_copy_btn = GObject.Property(type=bool, default=False) 21 | show_paste_btn = GObject.Property(type=bool, default=False) 22 | show_generate_btn = GObject.Property(type=bool, default=False) 23 | 24 | # Custom signals 25 | __gsignals__ = { 26 | "cleared": (GObject.SIGNAL_RUN_LAST, None, ()), 27 | "generate-clicked": (GObject.SIGNAL_RUN_LAST, None, ()), 28 | } 29 | 30 | def __init__(self): 31 | super().__init__() 32 | 33 | # Property binding 34 | self.bind_property("show-copy-btn", self._copy_btn, "visible", GObject.BindingFlags.SYNC_CREATE) 35 | self.bind_property("show-paste-btn", self._paste_btn, "visible", GObject.BindingFlags.SYNC_CREATE) 36 | self.bind_property("show-clear-btn", self._clear_btn, "visible", GObject.BindingFlags.SYNC_CREATE) 37 | self.bind_property("show-generate-btn", self._generate_btn, "visible", GObject.BindingFlags.SYNC_CREATE) 38 | 39 | # Signals 40 | self._copy_btn.connect("clicked", self._on_copy_clicked) 41 | self._paste_btn.connect("clicked", self._on_paste_clicked) 42 | self._clear_btn.connect("clicked", self._on_clear_clicked) 43 | self._generate_btn.connect("clicked", self._on_generate_clicked) 44 | 45 | def _on_copy_clicked(self, user_data:GObject.GPointer): 46 | text = self.get_text() 47 | clipboard = Gdk.Display.get_clipboard(Gdk.Display.get_default()) 48 | clipboard.set(text) 49 | 50 | def _on_paste_clicked(self, user_data:GObject.GPointer): 51 | self.clipboard = Gdk.Display.get_clipboard(Gdk.Display.get_default()) 52 | self.clipboard.read_text_async(None, self._on_clipboard_read_done, None) 53 | 54 | def _on_clipboard_read_done(self, source_widget:GObject.Object, result:Gio.AsyncResult, user_data:GObject.GPointer): 55 | string = self.clipboard.read_text_finish(result) 56 | self.set_text(string) 57 | 58 | def _on_clear_clicked(self, user_data:GObject.GPointer): 59 | self._clear() 60 | 61 | def _on_generate_clicked(self, user_data:GObject.GPointer): 62 | self.emit("generate-clicked") 63 | 64 | def _clear(self): 65 | self.remove_css_class("border-red") 66 | self.set_text("") 67 | self.emit("cleared") 68 | 69 | def clear(self): 70 | self._clear() 71 | -------------------------------------------------------------------------------- /src/widgets/file_view.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gtk, Adw, GObject 6 | 7 | 8 | @Gtk.Template(resource_path='/me/iepure/devtoolbox/ui/widgets/file_view.ui') 9 | class FileView(Adw.Bin): 10 | __gtype_name__ = 'FileView' 11 | 12 | # Template elements 13 | _file_path_lbl = Gtk.Template.Child() 14 | _file_size_lbl = Gtk.Template.Child() 15 | 16 | # Properties 17 | file_path = GObject.Property(type=str, default="") 18 | file_size = GObject.Property(type=str, default="") 19 | 20 | def __init__(self): 21 | super().__init__() 22 | 23 | self.set_property("css-name", "fileview") 24 | 25 | # Property binding 26 | self.bind_property("file_path", self._file_path_lbl, "label", GObject.BindingFlags.SYNC_CREATE) 27 | self.bind_property("file_size", self._file_size_lbl, "label", GObject.BindingFlags.SYNC_CREATE) 28 | 29 | def get_file_path(self) -> str: 30 | return self.file_path 31 | 32 | def set_file_path(self, file_path:str): 33 | self.file_path = file_path 34 | 35 | def get_file_size(self) -> str: 36 | return self.file_size 37 | 38 | def set_file_size(self, file_size:str): 39 | self.file_size = file_size 40 | -------------------------------------------------------------------------------- /src/widgets/meson.build: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name()) 6 | widgetsdir = join_paths(pkgdatadir, 'devtoolbox/widgets') 7 | 8 | sources = [ 9 | '__init__.py', 10 | 'utility_title.py', 11 | 'text_area.py', 12 | 'file_view.py', 13 | 'text_file_area.py', 14 | 'sidebar_item.py', 15 | 'spin_area.py', 16 | 'date_area.py', 17 | 'entry_row.py', 18 | 'webview_area.py', 19 | 'image_area.py', 20 | 'theme_switcher.py', 21 | ] 22 | 23 | install_data(sources, install_dir: widgetsdir) 24 | -------------------------------------------------------------------------------- /src/widgets/sidebar_item.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 - 2023 Alessandro Iepure 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | from gi.repository import Gtk, GObject 6 | 7 | 8 | class SidebarItem(Gtk.ListBoxRow): 9 | __gtype_name__ = "SidebarItem" 10 | 11 | # Properties 12 | tool_name = GObject.Property(type=str, default="") 13 | title = GObject.Property(type=str, default="") 14 | icon_name = GObject.Property(type=str, default="") 15 | tool_tip = GObject.Property(type=str, default="") 16 | category = GObject.Property(type=str, default="") 17 | 18 | def __init__(self, tool_name:str, title:str, icon_name:str, tool_tip:str, category: str): 19 | super().__init__() 20 | 21 | self.tool_name = tool_name 22 | self.title = title 23 | self.icon_name = icon_name 24 | self.tool_tip = tool_tip 25 | self.category = category 26 | 27 | grid = Gtk.Grid() 28 | grid.set_hexpand(True) 29 | grid.set_margin_top(12) 30 | grid.set_margin_bottom(12) 31 | grid.set_margin_start(6) 32 | grid.set_margin_end(6) 33 | grid.set_column_spacing(12) 34 | 35 | icon = Gtk.Image() 36 | icon.set_from_icon_name(self.icon_name) 37 | grid.attach(icon, 0, 0, 1, 1) 38 | grid.attach(Gtk.Label(label=self.title, xalign=0.0), 1, 0, 1, 1) 39 | self.set_child(grid) 40 | self.set_tooltip_text(self.tool_tip) 41 | 42 | def get_tool_name(self) -> str: 43 | return self.tool_name 44 | 45 | def get_title(self) -> str: 46 | return self.title 47 | 48 | def get_icon_name(self) -> str: 49 | return self.icon_name 50 | 51 | def get_tool_tip(self) -> str: 52 | return self.tool_tip 53 | 54 | def get_category(self) -> str: 55 | return self.category 56 | -------------------------------------------------------------------------------- /src/widgets/theme_switcher.py: -------------------------------------------------------------------------------- 1 | # Code modified from https://gitlab.gnome.org/tijder/blueprintgtk 2 | # SPDX-License-Identifier: GPL-3.0-or-later 3 | # Original header below 4 | 5 | # Copyright 2020 Manuel Genovés 6 | # Copyright 2022 Mufeed Ali 7 | # Copyright 2022 Rafael Mardojai CM 8 | # SPDX-License-Identifier: GPL-3.0-or-later 9 | 10 | # Code modified from Apostrophe 11 | # https://github.com/dialect-app/dialect/blob/c0b7ca0580d4c7cfb32ff7ed0a3a08c06bbe40e0/dialect/theme_switcher.py 12 | 13 | from gi.repository import Adw, Gio, GObject, Gtk, Gdk 14 | 15 | 16 | @Gtk.Template(resource_path='/me/iepure/devtoolbox/ui/widgets/theme_switcher.ui') 17 | class ThemeSwitcher(Gtk.Box): 18 | __gtype_name__ = 'ThemeSwitcher' 19 | 20 | # GSettings 21 | _settings = Gio.Settings(schema_id="me.iepure.devtoolbox") 22 | 23 | show_system = GObject.property(type=bool, default=True) 24 | color_scheme = 'light' 25 | 26 | system = Gtk.Template.Child() 27 | light = Gtk.Template.Child() 28 | dark = Gtk.Template.Child() 29 | 30 | @GObject.Property(type=str) 31 | def selected_color_scheme(self): 32 | """Read-write integer property.""" 33 | 34 | return self.color_scheme 35 | 36 | @selected_color_scheme.setter 37 | def selected_color_scheme(self, color_scheme): 38 | self.color_scheme = color_scheme 39 | 40 | if color_scheme == 'auto': 41 | self.system.set_active(True) 42 | self.style_manager.set_color_scheme(Adw.ColorScheme.PREFER_LIGHT) 43 | if color_scheme == 'light': 44 | self.light.set_active(True) 45 | self.style_manager.set_color_scheme(Adw.ColorScheme.FORCE_LIGHT) 46 | if color_scheme == 'dark': 47 | self.dark.set_active(True) 48 | self.style_manager.set_color_scheme(Adw.ColorScheme.FORCE_DARK) 49 | 50 | def __init__(self, **kwargs): 51 | super().__init__(**kwargs) 52 | 53 | self.style_manager = Adw.StyleManager.get_default() 54 | 55 | self.style_manager.bind_property( 56 | 'system-supports-color-schemes', 57 | self, 'show_system', 58 | GObject.BindingFlags.SYNC_CREATE 59 | ) 60 | 61 | self.selected_color_scheme = self._settings.get_string("style-scheme") 62 | 63 | 64 | @Gtk.Template.Callback() 65 | def _on_color_scheme_changed(self, _widget, _paramspec): 66 | if self.system.get_active(): 67 | self.selected_color_scheme = 'auto' 68 | self._settings.set_string("style-scheme", "auto") 69 | if self.light.get_active(): 70 | self.selected_color_scheme = 'light' 71 | self._settings.set_string("style-scheme", "light") 72 | if self.dark.get_active(): 73 | self.selected_color_scheme = 'dark' 74 | self._settings.set_string("style-scheme", "dark") 75 | --------------------------------------------------------------------------------