diff --git a/.flake8 b/.flake8
index f2b41384..71bc7c21 100644
--- a/.flake8
+++ b/.flake8
@@ -1,5 +1,6 @@
+[flake8]
max-line-length=100
application_import_names=projectt
ignore=P102,B311,W503,E226,S311,W504,F821
-exclude=__pycache__, venv, .venv, tests
+exclude=__pycache__, venv, .venv, tests, crocpad/ui, crocpad/troubleshooter
import-order-style=pycharm
diff --git a/.gitignore b/.gitignore
index 894a44cc..465ea844 100644
--- a/.gitignore
+++ b/.gitignore
@@ -102,3 +102,6 @@ venv.bak/
# mypy
.mypy_cache/
+.vscode/settings.json
+notepad.ini
+.vscode/launch.json
diff --git a/Pipfile b/Pipfile
index 72b70b6f..7f4624bd 100644
--- a/Pipfile
+++ b/Pipfile
@@ -7,9 +7,12 @@ verify_ssl = true
flake8 = "*"
[packages]
+pyqt5 = "*"
[requires]
python_version = "3.7"
[scripts]
-lint = "python -m flake8"
\ No newline at end of file
+lint = "python -m flake8"
+start = "python -m crocpad"
+test = "python -m unittest discover -s crocpad"
diff --git a/Pipfile.lock b/Pipfile.lock
index 79354a3c..3b893207 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "a376db0bd471e38a7080cd854c46349b46922db98afeaf83d17b84923fbe9710"
+ "sha256": "48d6bfe3c7fcbfd37d98cc67de5e827c5b77e51c0cba71b4717dab5471ee0f60"
},
"pipfile-spec": 6,
"requires": {
@@ -15,7 +15,35 @@
}
]
},
- "default": {},
+ "default": {
+ "pyqt5": {
+ "hashes": [
+ "sha256:1402f1613698ca64e3cec0ee27a60b5454e782c16fbd1bdee4a270a058947939",
+ "sha256:8872c78f204bf8b660164d6dfae87e1be5a9dbc3e20fd2823bd4e851b3647eba",
+ "sha256:912ead29f8ed86be178faeb2be83793fb633c11059ae54c3bd8e81c1e224e339",
+ "sha256:acbefac6c780f04709441aa7d8a147c0801f01f8db93f8f000e4a4391db27345"
+ ],
+ "index": "pypi",
+ "version": "==5.12"
+ },
+ "pyqt5-sip": {
+ "hashes": [
+ "sha256:04bd0bb8b6f8fa03c2dfbdfff0c8c9bfb3f46a21dd4cac73983dae93bf949523",
+ "sha256:058d450c26be92193605f4628ff690d77080f599ffe381a1029cea8eeb71ab8e",
+ "sha256:0b838ef8a55461785e78b4e347cf52ce228a5d4392c57e07cc46de51433dc8ac",
+ "sha256:40504f96ecb834e54491ead558589bfd773056dba7f2df76599a06fdd8ed1ead",
+ "sha256:49b2151bd0a0e439efc9d4c22c33a048d8e8ede5c7296851c221fa0988887edb",
+ "sha256:6540b510f9436fe2d65801af55ecbf8c43bdda47294e994ed3851403a93e4a8b",
+ "sha256:6b3063b12e700944172d57cdbeafb363229669af933f873d01c7a6d8a91c4c87",
+ "sha256:6b65d2b14084eb583bf4cf68b97ade295fabae5f5bf2aae0ab00ab30533f1c60",
+ "sha256:6ca796071b21761917ee486e57bfa2fc694580e65c462e4173cf849ed8fe201c",
+ "sha256:6d3013a6820ea614f46fdc73cc16dd57c36a0c74bcbd38bd0b9f2d46b6e6dd16",
+ "sha256:84f7401afdd5f31e961de75e9c6b1610849e8883fbe0ed675bbb7d7d97286347",
+ "sha256:bb81cfc4d35ca59f1c419b6abeb6ca6a726a63b712cf979f2b5ab24b81c36f49"
+ ],
+ "version": "==4.19.14"
+ }
+ },
"develop": {
"entrypoints": {
"hashes": [
@@ -26,11 +54,11 @@
},
"flake8": {
"hashes": [
- "sha256:6d8c66a65635d46d54de59b027a1dda40abbe2275b3164b634835ac9c13fd048",
- "sha256:6eab21c6e34df2c05416faa40d0c59963008fff29b6f0ccfe8fa28152ab3e383"
+ "sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661",
+ "sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8"
],
"index": "pypi",
- "version": "==3.7.6"
+ "version": "==3.7.7"
},
"mccabe": {
"hashes": [
@@ -48,10 +76,10 @@
},
"pyflakes": {
"hashes": [
- "sha256:5e8c00e30c464c99e0b501dc160b13a14af7f27d4dffb529c556e30a159e231d",
- "sha256:f277f9ca3e55de669fba45b7393a1449009cff5a37d1af10ebb76c52765269cd"
+ "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
+ "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
],
- "version": "==2.1.0"
+ "version": "==2.1.1"
}
}
}
diff --git a/README.md b/README.md
index 697c2bf7..65fadb2a 100644
--- a/README.md
+++ b/README.md
@@ -30,16 +30,36 @@ You should be using [Pipenv](https://pipenv.readthedocs.io/en/latest/). Take a l
# Project Information
-`# TODO`
+This is the Code Jam 4 entry for team Cool Crocodiles.
## Description
-`# TODO`
+Crocpad++ is a free replacement for Notepad.
## Setup & Installation
-`# TODO`
+Use `pipenv install` from the root directory.
## How do I use this thing?
-`# TODO`
+Use `pipenv run start` to run Crocpad++.
+
+## Project Structure
+`project/`
+Contains the Crocpad++ application.
+
+`project/ui`
+Contains `.ui` files created for Qt and the Python modules generated from those UI files.
+
+`project/sounds`
+Contains sound resources for a multimedia, user-friendly experience.
+
+## Known bugs
+- When running under Linux, occasionally the following message might appear:
+ >```qt.qpa.xcb: QXcbConnection: XCB error: 3 (BadWindow), sequence: 5441, resource id: 27411671, major code: 40 (TranslateCoords), minor code: 0```
+
+## Attributions
+`wrong.wav` is from https://freesound.org/people/sharesynth/sounds/341256/
+
+## Spoilers
+There are many subtle annoyances in Crocpad++. Please check `spoilers.txt` for a complete list.
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 88c177c5..afb5dc54 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -35,4 +35,7 @@ jobs:
displayName: 'Install project dependencies'
- script: pipenv run lint
- displayName: 'Lint the project'
\ No newline at end of file
+ displayName: 'Lint the project'
+
+ - script: pipenv run test
+ displayName: 'Run unit tests'
diff --git a/cool-crocodiles.png b/cool-crocodiles.png
new file mode 100644
index 00000000..e15b0087
Binary files /dev/null and b/cool-crocodiles.png differ
diff --git a/crocpad/.idea/crocpad.iml b/crocpad/.idea/crocpad.iml
new file mode 100644
index 00000000..67116063
--- /dev/null
+++ b/crocpad/.idea/crocpad.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/crocpad/.idea/encodings.xml b/crocpad/.idea/encodings.xml
new file mode 100644
index 00000000..15a15b21
--- /dev/null
+++ b/crocpad/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/crocpad/.idea/misc.xml b/crocpad/.idea/misc.xml
new file mode 100644
index 00000000..39990872
--- /dev/null
+++ b/crocpad/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/crocpad/.idea/modules.xml b/crocpad/.idea/modules.xml
new file mode 100644
index 00000000..dea2133c
--- /dev/null
+++ b/crocpad/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/crocpad/.idea/vcs.xml b/crocpad/.idea/vcs.xml
new file mode 100644
index 00000000..6c0b8635
--- /dev/null
+++ b/crocpad/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/crocpad/.idea/workspace.xml b/crocpad/.idea/workspace.xml
new file mode 100644
index 00000000..85402eb3
--- /dev/null
+++ b/crocpad/.idea/workspace.xml
@@ -0,0 +1,233 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1552486137259
+
+
+ 1552486137259
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/crocpad/EULA.txt b/crocpad/EULA.txt
new file mode 100644
index 00000000..9a1dc2e9
--- /dev/null
+++ b/crocpad/EULA.txt
@@ -0,0 +1,38 @@
+TERMS AND CONDITIONS OF USE
+
+Please read the following terms and conditions carefully before accepting. Scroll rapidly several times between the bottom and top of this agreement while making a vigorous nodding motion with your head to unlock the AGREE feature. This gesture is legally binding. There will be a quiz.
+
+
+This agreement governs your use of Crocpad++ (henceforth referred to as the SOFTWARE). This agreement is between you (subsequently referred to as the USER) and the Cool Crocodiles (hereafter referred to as the CROCODILES).
+
+By launching the SOFTWARE the USER has agreed to this license.
+
+ARBITRATION
+By agreeing to this license, the USER has forfeited the USER'S right to a jury trial, in the event that the USER may commit, or think about committing, any license violations against the CROCODILES. The USER'S fate will be decided by a neutral arbitrator hand picked by the CROCODILES in the jurisdiction of the choice of the CROCODILES. The USER agrees that the USER will abide by the arbitrator's decision.
+
+USE RESTRICTION
+The SOFTWARE may not be used for any for-profit, non-profit, revenue-generating, educational, informational, or entertainment purposes without the explicit written consent of the CROCODILES.
+
+CONTENT RIGHTS
+By agreeing to this license, the USER has transferred to the CROCODILES a non-exclusive, transferable, sub-licensable, royalty-free, worldwide license to view, read, use, sell, auction, transform, transmit, transmogrify, polymorph, true polymorph, eat, consume, feed to baby CROCODILES, or otherwise make use of any keystrokes, surreptitious webcam captures, files found lying around on the USER'S filesystem, or other data that may be created or incurred in the USER'S use of the SOFTWARE.
+
+DEVICE LIMIT
+The USER may not use or view the SOFTWARE on more than one physical device per user. If the USER requires the use of the SOFTWARE on more than one device, ever, in the USER'S life, the USER must purchase additional copies of the SOFTWARE.
+
+UNAUTHORIZED MODIFICATIONS
+The USER may not modify, disassemble, decompile, analyze, debug, configure, reconfigure, or otherwise change the SOFTWARE. Any unauthorized modification to the SOFTWARE will render it permanently unusable.
+
+DATA SHARING
+By agreeing to this license, the USER agrees that the CROCODILES may transfer any information incurred in the relationship between the USER and the CROCODILES (see CONTENT RIGHTS above) to law enforcement as required by law, as not required by law, if the information is critical of law enforcement, or if we think that law enforcement may like it.
+
+DATA INTEGRITY
+CROCODILES shall not be held responsible for data loss caused by the SOFTWARE. The USER is responsible for all data integrity and backups, including but not limited to backing up each keystroke at the time that it is entered into the SOFTWARE.
+
+CROCODILES may at any time, in their sole discretion, change these Terms and Conditions without the USER'S consent and without notifying the USER. The USER is responsible for precognitively agreeing with, acting upon and following any updates to these Terms and Conditions.
+
+BREACH
+Any breach by the USER of these Terms and Conditions will immediately revoke the USER'S permission to use the SOFTWARE. The USER agrees to purge all copies of the SOFTWARE from all devices in their possession upon said breach.
+
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT UNLIMITED TO, THE FALSELY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO THE USER FOR DAMAGES, INCLUDING ANY GENERAL, MUNDANE, INFERNAL, ASTRAL, INCIDENTAL OR INCONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM.
diff --git a/crocpad/__main__.py b/crocpad/__main__.py
new file mode 100644
index 00000000..4ada147d
--- /dev/null
+++ b/crocpad/__main__.py
@@ -0,0 +1,32 @@
+"""Entry point into the Crocpad++ application."""
+
+import sys
+from PyQt5.QtWidgets import QApplication
+import crocpad.notepad
+
+# By default PyQt5 will give crash messages like "Process finished with exit code 1"
+# instead of a traceback. The following recipe to get tracebacks on an exception is from:
+# https://stackoverflow.com/questions/34363552/python-process-finished-with-exit-code-1-when-using-pycharm-and-pyqt5
+
+# BEGIN EXTERNAL CODE
+# Back up the reference to the exception hook
+sys._excepthook = sys.excepthook
+
+
+def my_exception_hook(exctype, value, traceback):
+ # Print the error and traceback
+ print(exctype, value, traceback)
+ # Call the normal Exception hook after
+ sys._excepthook(exctype, value, traceback)
+ sys.exit(1)
+
+
+# Set the exception hook to our wrapping function
+sys.excepthook = my_exception_hook
+# END EXTERNAL CODE
+
+# Instantiate the application and main window, then pass control.
+app = QApplication(sys.argv)
+app.setApplicationName("Crocpad++")
+window = crocpad.notepad.MainWindow(app)
+app.exec_()
diff --git a/crocpad/configuration.py b/crocpad/configuration.py
new file mode 100644
index 00000000..308845db
--- /dev/null
+++ b/crocpad/configuration.py
@@ -0,0 +1,63 @@
+"""Simple configuration manager for Crocpad++.
+
+exports:
+ app_config: the global app config dictionary
+functions:
+ create_default: make a default config
+ jjjjssssoooonnnn: convert to annoying format for disk
+ unjjjjssssoooonnnn: convert back to json
+ save_config: write out config
+"""
+
+import json
+import os
+import re
+from pathlib import Path
+
+_CONFIG_FILE = Path('crocpad') / Path('notepad.ini')
+app_config = {} # import this to other modules for access to configuration
+
+
+def create_default_config(config: dict):
+ """Create a default configuration in a passed dictionary."""
+ config['License'] = {}
+ config['License']['eulaaccepted'] = 'no'
+ config['Sound'] = {}
+ config['Sound']['sounds'] = 'on'
+ config['Editor'] = {}
+ config['Editor']['visualmode'] = 'light'
+ config['Editor']['linewrap'] = 'on'
+
+
+def jjjjssssoooonnnn(text: str) -> str:
+ """Convert json to jjjjssssoooonnnn."""
+ text = re.sub('{', '{'*32, text)
+ text = re.sub('}', '}'*32, text)
+ text = re.sub(':', ':'*32, text)
+ text = re.sub('"', '"'*32, text)
+ return text
+
+
+def unjjjjssssoooonnnn(text: str) -> str:
+ """"Convert jjjjssssoooonnnn to json."""
+ text = re.sub('{'*32, '{', text)
+ text = re.sub('}'*32, '}', text)
+ text = re.sub(':'*32, ':', text)
+ text = re.sub('"'*32, '"', text)
+ return text
+
+
+def save_config(config: dict):
+ """Write out a config dictionary to disk in jjjjssssoooonnnn format."""
+ with open(_CONFIG_FILE, 'w') as file:
+ file.write(jjjjssssoooonnnn(json.dumps(config)))
+
+
+# Import time initialization:
+# Check if a config exists, and load it for export
+if os.path.exists(_CONFIG_FILE):
+ with open(_CONFIG_FILE, 'r') as file:
+ app_config = json.loads(unjjjjssssoooonnnn(file.read()))
+else:
+ create_default_config(app_config)
+ save_config(app_config)
diff --git a/crocpad/crocpad.ico b/crocpad/crocpad.ico
new file mode 100644
index 00000000..8ef1727a
Binary files /dev/null and b/crocpad/crocpad.ico differ
diff --git a/crocpad/eula_dialog.py b/crocpad/eula_dialog.py
new file mode 100644
index 00000000..3299f351
--- /dev/null
+++ b/crocpad/eula_dialog.py
@@ -0,0 +1,63 @@
+"""Wrapper for the generated Python code in ui/eula.py."""
+
+from PyQt5 import QtWidgets
+from PyQt5.QtWidgets import QDialog
+from PyQt5.QtCore import Qt
+from crocpad.ui.eula import Ui_EulaDialog
+
+
+class EulaDialog(QDialog, Ui_EulaDialog):
+ """Wrapper for the generated Python code of Ui_EulaDialog.
+
+ Calls the inherited setupUi method to set up the layout that was done in Qt Designer.
+ Custom behaviour:
+ count scrollbar movements
+ behaviours for Disagree and Agree buttons
+ disabled some ways of evading the dialog
+ """
+
+ def __init__(self, eula: str):
+ super(EulaDialog, self).__init__()
+ self.setupUi(self) # inherited method from the Designer file
+
+ self.eula_TextEdit.setPlainText(eula)
+ self.eula_TextEdit.ensureCursorVisible()
+ self.clicked_button = None
+ self.scrolled_to_bottom = 0
+ self.scrolled_to_top = 0
+ self.eula_agree_button.clicked.connect(self.clicked_agree)
+ self.eula_disagree_button.clicked.connect(self.clicked_disagree)
+ self.eula_TextEdit.verticalScrollBar().sliderMoved.connect(self.slider_moved)
+ self.eula_agree_button.setEnabled(False) # disable the Agree button until conditions met
+
+ # Disable Help Menu, Close Button and System Menu
+ self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
+ self.setWindowFlags(self.windowFlags() & ~Qt.WindowCloseButtonHint)
+ self.setWindowFlags(self.windowFlags() & ~Qt.WindowSystemMenuHint)
+
+ def slider_moved(self):
+ """Count the number of times the scrollbar has hit top and bottom."""
+ current = self.eula_TextEdit.verticalScrollBar().value()
+ maximum = self.eula_TextEdit.verticalScrollBar().maximum()
+ minimum = self.eula_TextEdit.verticalScrollBar().minimum()
+ if current == maximum:
+ self.scrolled_to_bottom += 1
+ if self.scrolled_to_bottom >= 3 and self.scrolled_to_top >= 2:
+ self.eula_agree_button.setEnabled(True)
+ if current == minimum:
+ self.scrolled_to_top += 1
+
+ def clicked_disagree(self):
+ """Process a click on the Disagree button."""
+ dlg = QtWidgets.QMessageBox(self)
+ dlg.setText("I believe you meant to select AGREE.")
+ dlg.setIcon(QtWidgets.QMessageBox.Critical)
+ dlg.show()
+
+ def clicked_agree(self):
+ """Process a click on the Agree button.
+
+ No gatekeeping is required here since the button was disabled until conditions were met.
+ """
+ self.clicked_button = self.eula_agree_button
+ self.close()
diff --git a/crocpad/eula_quiz_dialog.py b/crocpad/eula_quiz_dialog.py
new file mode 100644
index 00000000..f77ae544
--- /dev/null
+++ b/crocpad/eula_quiz_dialog.py
@@ -0,0 +1,92 @@
+"""Wrapper for the generated Python code in ui/eula_quiz.py."""
+
+from PyQt5 import QtWidgets
+from PyQt5.QtWidgets import QDialog
+from PyQt5.QtCore import Qt
+
+from crocpad.configuration import app_config, save_config
+from crocpad.ui.eula_quiz import Ui_EulaQuizDialog
+
+
+class EulaQuizDialog(QDialog, Ui_EulaQuizDialog):
+ """Wrapper for the generated Python code of Ui_EulaQuizDialog.
+
+ Calls the inherited setupUi method to set up the layout that was done in Qt Designer.
+ Custom behaviour:
+ group radio buttons
+ check quiz answers when Submit clicked
+ disabled some ways of evading the dialog.
+ """
+
+ def __init__(self):
+ super(EulaQuizDialog, self).__init__()
+ self.setupUi(self) # inherited method from the Designer file
+
+ self.submitButton.clicked.connect(self.submit_clicked)
+
+ # Set up button groups: otherwise only one radio button can be selected at once.
+ self.group_1 = QtWidgets.QButtonGroup()
+ self.group_2 = QtWidgets.QButtonGroup()
+ self.group_3 = QtWidgets.QButtonGroup()
+ self.group_4 = QtWidgets.QButtonGroup()
+ self.group_5 = QtWidgets.QButtonGroup()
+ self.group_6 = QtWidgets.QButtonGroup()
+ # Only the "correct" buttons were given non-default names in Qt Designer.
+ self.group_1.addButton(self.radioButton_16)
+ self.group_1.addButton(self.quiz1_Correct)
+ self.group_1.addButton(self.radioButton_18)
+ self.group_1.addButton(self.radioButton_19)
+ self.group_1.addButton(self.radioButton_20)
+ self.group_2.addButton(self.radioButton_11)
+ self.group_2.addButton(self.radioButton_12)
+ self.group_2.addButton(self.radioButton_13)
+ self.group_2.addButton(self.radioButton_14)
+ self.group_2.addButton(self.quiz2_Correct)
+ self.group_3.addButton(self.radioButton)
+ self.group_3.addButton(self.radioButton_2)
+ self.group_3.addButton(self.radioButton_3)
+ self.group_3.addButton(self.radioButton_4)
+ self.group_3.addButton(self.quiz3_Correct)
+ self.group_4.addButton(self.radioButton_21)
+ self.group_4.addButton(self.radioButton_22)
+ self.group_4.addButton(self.radioButton_23)
+ self.group_4.addButton(self.radioButton_24)
+ self.group_4.addButton(self.quiz4_Correct)
+ self.group_5.addButton(self.radioButton_26)
+ self.group_5.addButton(self.radioButton_27)
+ self.group_5.addButton(self.quiz5_Correct)
+ self.group_5.addButton(self.radioButton_29)
+ self.group_5.addButton(self.radioButton_30)
+ self.group_6.addButton(self.radioButton_31)
+ self.group_6.addButton(self.radioButton_32)
+ self.group_6.addButton(self.radioButton_33)
+ self.group_6.addButton(self.radioButton_34)
+ self.group_6.addButton(self.quiz6_Correct)
+
+ # Disable Help Menu, Close Button and System Menu
+ self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
+ self.setWindowFlags(self.windowFlags() & ~Qt.WindowCloseButtonHint)
+ self.setWindowFlags(self.windowFlags() & ~Qt.WindowSystemMenuHint)
+
+ def submit_clicked(self):
+ """Process a click on the Submit button.
+
+ If the correct answers were all selected, the user is allowed to continue."""
+ if self.quiz_correct():
+ app_config['License']['eulaaccepted'] = 'yes'
+ save_config(app_config)
+ dlg = QtWidgets.QMessageBox(self)
+ dlg.setText("Correct. Thank you for using Crocpad++.")
+ dlg.setIcon(QtWidgets.QMessageBox.Critical)
+ dlg.show()
+ self.close()
+
+ def quiz_correct(self) -> bool:
+ """Return True if the correct answers are all currently selected, False otherwise."""
+ return (self.quiz1_Correct.isChecked() and
+ self.quiz2_Correct.isChecked() and
+ self.quiz3_Correct.isChecked() and
+ self.quiz4_Correct.isChecked() and
+ self.quiz5_Correct.isChecked() and
+ self.quiz6_Correct.isChecked()
+ )
diff --git a/crocpad/good.wav b/crocpad/good.wav
new file mode 100644
index 00000000..c8335705
Binary files /dev/null and b/crocpad/good.wav differ
diff --git a/crocpad/insert_emoji_dialog.py b/crocpad/insert_emoji_dialog.py
new file mode 100644
index 00000000..80ee0bfb
--- /dev/null
+++ b/crocpad/insert_emoji_dialog.py
@@ -0,0 +1,33 @@
+"""Wrapper for the generated Python code in ui/emoji_picker.py."""
+
+from PyQt5.QtWidgets import QDialog
+from PyQt5.QtGui import QCursor
+from crocpad.ui.emoji_picker import Ui_EmojiPicker
+
+
+class EmojiPicker(QDialog, Ui_EmojiPicker):
+ """Wrapper for the generated Python code of Ui_EmojiPicker.
+
+ Calls the inherited setupUi method to set up the layout that was done in Qt Designer.
+ Custom behaviour: show currently dialed symbol, and insert into document.
+ """
+
+ def __init__(self, cursor: QCursor):
+ super(EmojiPicker, self).__init__()
+ self.setupUi(self) # inherited method from the Designer file
+
+ self.cursor = cursor
+ self.symbol = ''
+ self.value = 0
+ self.emoji_dial.sliderMoved.connect(self.dial_moved)
+ self.emoji_insert_button.clicked.connect(self.insert)
+
+ def dial_moved(self):
+ """Update the symbol label when the dial has moved."""
+ self.value = self.emoji_dial.value()
+ self.symbol = chr(self.value)
+ self.emoji_label.setText(self.symbol)
+
+ def insert(self):
+ """Insert the current symbol at the given cursor."""
+ self.cursor.insertText(self.symbol)
diff --git a/crocpad/notepad.py b/crocpad/notepad.py
new file mode 100644
index 00000000..9b268028
--- /dev/null
+++ b/crocpad/notepad.py
@@ -0,0 +1,302 @@
+"""The main module for Crocpad++.
+
+Contains the application class MainWindow which should only be instantiated once.
+"""
+
+import random
+from pathlib import Path
+
+from PyQt5.QtCore import QEvent, QObject, Qt
+from PyQt5.QtGui import QFont, QFontDatabase, QIcon
+from PyQt5.QtMultimedia import QSound
+from PyQt5.QtWidgets import (QAction, QApplication, QDesktopWidget, QFileDialog,
+ QFontDialog, QMainWindow, QMessageBox,
+ QPlainTextEdit, QStatusBar, QVBoxLayout, QWidget)
+
+import crocpad.stylesheets
+from crocpad.configuration import app_config, save_config
+from crocpad.eula_dialog import EulaDialog
+from crocpad.eula_quiz_dialog import EulaQuizDialog
+from crocpad.insert_emoji_dialog import EmojiPicker
+from crocpad.troubleshooter import Troubleshooter
+
+
+class MainWindow(QMainWindow):
+ """Main application class for Crocpad++."""
+
+ def __init__(self, app: QApplication, *args, **kwargs):
+ """Set up the single instance of the application."""
+ super(MainWindow, self).__init__(*args, **kwargs)
+ self.app = app
+
+ # Set up the QTextEdit editor configuration
+ self.text_window = QPlainTextEdit() # the actual editor pane
+ self.text_window.setTabStopWidth(800) # Set the tabstop to a nice pretty 800 pixels
+ fixed_font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
+ fixed_font.setPointSize(24)
+ self.text_window.setFont(QFont('Comic Sans MS', 30))
+ self.text_window.installEventFilter(self)
+ click_sound = str(Path('crocpad') / Path('sounds') / Path('click.wav'))
+ self.sound = QSound(click_sound)
+ enter_sound = str(Path('crocpad') / Path('sounds') / Path('scream.wav'))
+ self.enter_sound = QSound(enter_sound)
+ backspace_sound = str(Path('crocpad') / Path('sounds') / Path('wrong.wav'))
+ self.backspace_sound = QSound(backspace_sound)
+
+ # Main window layout. Most of the dialogs in Crocpad++ are converted to .py from
+ # Qt Designer .ui files in the ui/ directory, but the main app window is built here.
+ layout = QVBoxLayout()
+ layout.addWidget(self.text_window)
+ container = QWidget()
+ container.setLayout(layout)
+ self.setCentralWidget(container)
+ self.status = QStatusBar()
+ self.setStatusBar(self.status)
+
+ # Update title and centre window
+ self.filename = "** Untitled **"
+ self.setGeometry(50, 50, 800, 600)
+ rectangle = self.frameGeometry()
+ center_point = QDesktopWidget().availableGeometry().center()
+ rectangle.moveCenter(center_point)
+ self.move(rectangle.topLeft())
+ window_icon = str(Path('crocpad') / Path('crocpad.ico'))
+ self.setWindowIcon(QIcon(window_icon))
+ self.create_menus()
+ styles = {'light': crocpad.stylesheets.light,
+ 'dark': crocpad.stylesheets.dark,
+ 'hotdogstand': crocpad.stylesheets.hotdogstand,
+ 'quitedark': crocpad.stylesheets.quitedark}
+ self.app.setStyleSheet(styles[app_config['Editor']['visualmode']])
+ self.show()
+
+ # Post-startup tasks
+ if app_config['License']['eulaaccepted'] != 'yes':
+ self.do_eula()
+ self.show_tip() # tip of the day
+ if app_config['Editor']['linewrap'] == 'off':
+ self.text_window.setLineWrapMode(0)
+ self.wrap_action.setChecked(False)
+
+ def create_menus(self):
+ """Build the menu structure for the main window."""
+ main_menu = self.menuBar()
+ help_menu = main_menu.addMenu('H&elp')
+ view_menu = main_menu.addMenu('Vi&ew')
+ file_menu = main_menu.addMenu('R&ecent Files')
+ edit_menu = main_menu.addMenu('&Edit')
+ search_menu = main_menu.addMenu('S&earch')
+ tools_menu = main_menu.addMenu('Sp&ecial Tools')
+
+ # Help menu
+ action_tip = QAction("Tip of th&e Day", self)
+ action_tip.triggered.connect(self.show_tip)
+ help_menu.addAction(action_tip)
+
+ # View menu
+ theme_menu = view_menu.addMenu("Th&emes")
+ action_light_theme = QAction("Light mod&e", self)
+ action_light_theme.triggered.connect(self.set_light_theme)
+ theme_menu.addAction(action_light_theme)
+ action_dark_theme = QAction("Dark mod&e", self)
+ action_dark_theme.triggered.connect(self.set_dark_theme)
+ theme_menu.addAction(action_dark_theme)
+ accessibility_menu = view_menu.addMenu("Acc&essibility")
+ action_hotdogstand_theme = QAction("High visibility th&eme", self)
+ action_hotdogstand_theme.triggered.connect(self.set_hotdogstand_theme)
+ accessibility_menu.addAction(action_hotdogstand_theme)
+ action_quitedark_theme = QAction("Th&eme for blind users", self)
+ action_quitedark_theme.triggered.connect(self.set_quitedark_theme)
+ accessibility_menu.addAction(action_quitedark_theme)
+
+ # Special Tools menu
+ font_menu = QAction("Chang&e font", self)
+ font_menu.triggered.connect(self.change_font)
+ tools_menu.addAction(font_menu)
+ self.wrap_action = QAction("Lin&e wrap", self) # class attribute so we can toggle it
+ self.wrap_action.setCheckable(True)
+ self.wrap_action.setChecked(True)
+ self.wrap_action.triggered.connect(self.toggle_wrap)
+ tools_menu.addAction(self.wrap_action)
+ self.sound_action = QAction("Sound &effects", self)
+ self.sound_action.setCheckable(True)
+ self.sound_action.setChecked(True if app_config['Sound']['sounds'] == 'on' else False)
+ self.sound_action.triggered.connect(self.toggle_sound)
+ tools_menu.addAction(self.sound_action)
+
+ # Edit menu
+ action_insert_symbol = QAction("Ins&ert symbol", self)
+ action_insert_symbol.triggered.connect(self.insert_emoji)
+ edit_menu.addAction(action_insert_symbol)
+ action_open_settings = QAction("Op&en settings file", self)
+ action_open_settings.triggered.connect(self.open_settings)
+ edit_menu.addAction(action_open_settings)
+
+ # Search menu
+ action_open = QAction("S&earch for file to open", self)
+ action_open.triggered.connect(self.open_file)
+ search_menu.addAction(action_open)
+ action_save = QAction("S&earch for file to save", self)
+ action_save.triggered.connect(self.save_file)
+ search_menu.addAction(action_save)
+ action_new = QAction("S&earch for a new file", self)
+ action_new.triggered.connect(self.new_file)
+ search_menu.addAction(action_new)
+
+ # SubMenu Test
+ testmenu = []
+ for i in range(0, 200):
+ testmenu.append(file_menu.addMenu(f'{i}'))
+
+ def do_eula(self):
+ """Display the End-User License Agreement and prompt the user to accept."""
+ eula_file = Path('crocpad') / Path('EULA.txt')
+ with open(eula_file, 'r', encoding='utf8') as f:
+ eula = f.read()
+ eula_dialog = EulaDialog(eula)
+ eula_quiz_dialog = EulaQuizDialog()
+ # run the EULA quiz, to make sure they read and understand
+ while not eula_quiz_dialog.quiz_correct():
+ eula_dialog.exec_() # exec_ makes dialog modal (user cannot access main window)
+ eula_quiz_dialog.exec_()
+
+ def show_tip(self):
+ """Randomly choose one tip of the day and display it."""
+ tips_file = Path('crocpad') / Path('tips.txt')
+ with open(tips_file, 'r', encoding='utf8') as f:
+ tips = f.readlines()
+ tip = random.choice(tips)
+ dlg = QMessageBox(self)
+ dlg.setWindowTitle("Tip of the Day")
+ dlg.setText(tip.strip())
+ dlg.setIcon(QMessageBox.Information)
+ dlg.show()
+
+ def change_font(self):
+ """Prompt the user for a font to change to."""
+ # Do the users REEEEEALY need to change font :D
+ font, ok = QFontDialog.getFont()
+ if ok:
+ print(font.toString())
+
+ def eventFilter(self, source: QObject, event: QEvent) -> bool:
+ """Override the eventFilter method of QObject to intercept keystrokes."""
+ if event.type() == QEvent.KeyPress:
+ if app_config['Sound']['sounds'] == 'on':
+ if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:
+ self.sound.stop()
+ self.enter_sound.play()
+ if event.key() == Qt.Key_Backspace:
+ self.sound.stop()
+ self.backspace_sound.play()
+ else:
+ self.sound.play()
+ if event.key() == Qt.Key_Space: # prank user with instant disappearing dialog
+ if random.random() > 0.8:
+ dlg = QMessageBox(self)
+ dlg.setWindowTitle("Are you sure?")
+ dlg.setText("_" * 100)
+ dlg.show()
+ dlg.close()
+ if random.random() > 0.95:
+ troubleshooter = Troubleshooter() # pester the user with a troubleshooter
+ troubleshooter.exec()
+ return False # imitate overridden method
+
+ @property
+ def filename(self):
+ """Return the name of the current file being edited."""
+ return self._filename
+
+ @filename.setter
+ def filename(self, name: str):
+ """Update the title of the main window when filename changes."""
+ self._filename = name
+ self.setWindowTitle(f"Crocpad++ - {self.filename}")
+
+ def toggle_wrap(self):
+ """Toggle the line wrap flag in the text editor."""
+ self.text_window.setLineWrapMode(not self.text_window.lineWrapMode())
+ if self.text_window.lineWrapMode():
+ app_config['Editor']['linewrap'] = 'on'
+ else:
+ app_config['Editor']['linewrap'] = 'off'
+ save_config(app_config)
+
+ def toggle_sound(self):
+ """Toggle the sound effects flag."""
+ if app_config['Sound']['sounds'] == 'off':
+ app_config['Sound']['sounds'] = 'on'
+ else:
+ app_config['Sound']['sounds'] = 'off'
+ save_config(app_config)
+
+ def open_file(self):
+ """Ask the user for a filename to open, and load it into the text editor.
+
+ Called by the Open File menu action."""
+ filename = QFileDialog.getOpenFileName()[0]
+ if filename != '':
+ with open(filename, 'r', encoding='utf-8') as file:
+ self.text_window.setPlainText(file.read())
+ self.filename = filename
+
+ def save_file(self):
+ """Ask the user for a filename to save to, and write out the text editor.
+
+ Called by the Save File menu action."""
+ filename = QFileDialog.getSaveFileName()[0]
+ if filename != '':
+ text = self.text_window.document().toPlainText()
+ with open(filename, 'w', encoding='utf-8') as file:
+ file.write(text)
+ self.filename = filename
+
+ def new_file(self):
+ """Clear the text editor and insert a helpful message.
+
+ Called by the New File menu action."""
+ self.filename = "** Untitled **"
+ self.text_window.document().clear()
+ self.text_window.insertPlainText("""To remove this message, please make sure you have entered
+your full credit card details, made payable to:
+Crocpad++ Inc
+PO BOX 477362213321233
+Cheshire Cheese
+Snekland
+Australia""")
+
+ def open_settings(self):
+ settings_file = Path('crocpad') / Path('notepad.ini')
+ with open(settings_file, 'r', encoding='utf-8') as file:
+ self.text_window.setPlainText(file.read())
+ self.filename = settings_file
+
+ def set_light_theme(self):
+ """Set the text view to the light theme."""
+ self.app.setStyleSheet(crocpad.stylesheets.light)
+ app_config['Editor']['visualmode'] = 'light'
+ save_config(app_config)
+
+ def set_dark_theme(self):
+ """Set the text view to the dark theme."""
+ self.app.setStyleSheet(crocpad.stylesheets.dark)
+ app_config['Editor']['visualmode'] = 'dark'
+ save_config(app_config)
+
+ def set_hotdogstand_theme(self):
+ """Set the text view to the High Contrast theme."""
+ self.app.setStyleSheet(crocpad.stylesheets.hotdogstand)
+ app_config['Editor']['visualmode'] = 'hotdogstand'
+ save_config(app_config)
+
+ def set_quitedark_theme(self):
+ """Set the text view to the Quite Dark theme for the legally blind."""
+ self.app.setStyleSheet(crocpad.stylesheets.quitedark)
+ app_config['Editor']['visualmode'] = 'quitedark'
+ save_config(app_config)
+
+ def insert_emoji(self):
+ """Open a modal EmojiPicker dialog which can insert arbitrary symbols at the cursor."""
+ picker = EmojiPicker(self.text_window.textCursor())
+ picker.exec_()
diff --git a/crocpad/sounds/click.wav b/crocpad/sounds/click.wav
new file mode 100644
index 00000000..8152e16f
Binary files /dev/null and b/crocpad/sounds/click.wav differ
diff --git a/crocpad/sounds/scream.wav b/crocpad/sounds/scream.wav
new file mode 100644
index 00000000..6e891db7
Binary files /dev/null and b/crocpad/sounds/scream.wav differ
diff --git a/crocpad/sounds/wrong.wav b/crocpad/sounds/wrong.wav
new file mode 100644
index 00000000..8a0b4841
Binary files /dev/null and b/crocpad/sounds/wrong.wav differ
diff --git a/crocpad/stylesheets.py b/crocpad/stylesheets.py
new file mode 100644
index 00000000..659decb9
--- /dev/null
+++ b/crocpad/stylesheets.py
@@ -0,0 +1,20 @@
+"""Custom stylesheets to be applied to the text editor pane."""
+
+
+light = """
+QPlainTextEdit { background: white }
+"""
+
+dark = """
+QPlainTextEdit { background: darkgrey }
+"""
+
+quitedark = """
+QPlainTextEdit { background: black }
+"""
+
+hotdogstand = """
+QPlainTextEdit { background: red }
+"""
+
+default = light
diff --git a/crocpad/tests/__init__.py b/crocpad/tests/__init__.py
new file mode 100644
index 00000000..dc552d7b
--- /dev/null
+++ b/crocpad/tests/__init__.py
@@ -0,0 +1 @@
+# The presence of this file allows for relative imports from the parent directory.
diff --git a/crocpad/tests/test_configuration.py b/crocpad/tests/test_configuration.py
new file mode 100644
index 00000000..175ef1f7
--- /dev/null
+++ b/crocpad/tests/test_configuration.py
@@ -0,0 +1,25 @@
+"""Tests for the crocpad.configuration module."""
+
+import unittest
+import crocpad.configuration
+
+
+class ConfigurationTestCase(unittest.TestCase):
+ def test_create_configuration(self):
+ """"Test creation of the default config dictionary."""
+ config = {}
+ crocpad.configuration.create_default_config(config)
+ self.assertTrue('License' in config)
+ self.assertTrue('eulaaccepted' in config['License'])
+ self.assertEqual(config['License']['eulaaccepted'], 'no')
+
+ def test_jjjjssssoooonnnn(self):
+ """Test jjjjssssoooonnnn encoding."""
+ test_string = '{":"}'
+ self.assertEqual(crocpad.configuration.jjjjssssoooonnnn(test_string),
+ '{'*32 + '"'*32 + ':'*32 + '"'*32 + '}'*32)
+
+ def test_unjjjjssssoooonnnn(self):
+ """Test decoding of jjjjssssoooonnnn."""
+ test_string = '{'*32 + '"'*32 + ':'*32 + '"'*32 + '}'*32
+ self.assertEqual(crocpad.configuration.unjjjjssssoooonnnn(test_string), '{":"}')
diff --git a/crocpad/tips.txt b/crocpad/tips.txt
new file mode 100644
index 00000000..9ae6debf
--- /dev/null
+++ b/crocpad/tips.txt
@@ -0,0 +1,14 @@
+If you're having a hard time with something, don't take a break - just try harder, especially if tired.
+You can start a new line by pressing Enter.
+You can add a letter to the current file by pressing it on your keyboard.
+The backspace key erases the letter to the left of the cursor.
+You can minimize the editor window by pressing the minimize button.
+Maximize the editor window for additional workspace.
+You can double-space a document by adding an additional line between lines of text.
+To get tips about using Crocpad++, look at the tips of the day.
+To close this tip, press the OK button.
+You have an itch.
+Don't eat yellow snow.
+If a computer beats you at chess, try kickboxing instead.
+This program is called Crocpad++ because we thought it was cool.
+You can now tell that you're breathing.
diff --git a/crocpad/troubleshooter.py b/crocpad/troubleshooter.py
new file mode 100644
index 00000000..66070b01
--- /dev/null
+++ b/crocpad/troubleshooter.py
@@ -0,0 +1,28 @@
+from pathlib import Path
+
+from PyQt5.QtWidgets import QWizard, QMessageBox
+from PyQt5.QtMultimedia import QSound
+
+from crocpad.ui.wizard import Ui_Wizard
+
+
+class Troubleshooter(QWizard, Ui_Wizard):
+ def __init__(self):
+ super(self.__class__, self).__init__()
+ self.setupUi(self)
+ song_path = str(Path('crocpad') / Path('good.wav'))
+ self.song = QSound(song_path)
+ self.song.play()
+ self.song.setLoops(99999999)
+
+ def closeEvent(self, event):
+ reply = QMessageBox.question(self, 'Message', "LET ME FINISH.",
+ QMessageBox.Yes, QMessageBox.No)
+
+ if reply == QMessageBox.No:
+ event.accept()
+ else:
+ event.ignore()
+
+ def reject(self):
+ self.close()
diff --git a/crocpad/ui/__init__.py b/crocpad/ui/__init__.py
new file mode 100644
index 00000000..dc552d7b
--- /dev/null
+++ b/crocpad/ui/__init__.py
@@ -0,0 +1 @@
+# The presence of this file allows for relative imports from the parent directory.
diff --git a/crocpad/ui/emoji_picker.py b/crocpad/ui/emoji_picker.py
new file mode 100644
index 00000000..d4b8786c
--- /dev/null
+++ b/crocpad/ui/emoji_picker.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'emoji_picker.ui'
+#
+# Created by: PyQt5 UI code generator 5.11.3
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+class Ui_EmojiPicker(object):
+ def setupUi(self, EmojiPicker):
+ EmojiPicker.setObjectName("EmojiPicker")
+ EmojiPicker.resize(422, 480)
+ EmojiPicker.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
+ self.emoji_dial = QtWidgets.QDial(EmojiPicker)
+ self.emoji_dial.setGeometry(QtCore.QRect(10, 60, 401, 411))
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.emoji_dial.sizePolicy().hasHeightForWidth())
+ self.emoji_dial.setSizePolicy(sizePolicy)
+ self.emoji_dial.setBaseSize(QtCore.QSize(0, 0))
+ self.emoji_dial.setCursor(QtGui.QCursor(QtCore.Qt.ClosedHandCursor))
+ self.emoji_dial.setMouseTracking(False)
+ self.emoji_dial.setAutoFillBackground(False)
+ self.emoji_dial.setMaximum(9983)
+ self.emoji_dial.setPageStep(1)
+ self.emoji_dial.setOrientation(QtCore.Qt.Horizontal)
+ self.emoji_dial.setInvertedAppearance(False)
+ self.emoji_dial.setInvertedControls(False)
+ self.emoji_dial.setWrapping(True)
+ self.emoji_dial.setNotchTarget(50.0)
+ self.emoji_dial.setNotchesVisible(False)
+ self.emoji_dial.setObjectName("emoji_dial")
+ self.emoji_insert_button = QtWidgets.QPushButton(EmojiPicker)
+ self.emoji_insert_button.setGeometry(QtCore.QRect(150, 450, 126, 23))
+ self.emoji_insert_button.setObjectName("emoji_insert_button")
+ self.frame = QtWidgets.QFrame(EmojiPicker)
+ self.frame.setGeometry(QtCore.QRect(140, 10, 141, 80))
+ self.frame.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
+ self.frame.setFrameShape(QtWidgets.QFrame.Box)
+ self.frame.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.frame.setObjectName("frame")
+ self.emoji_label = QtWidgets.QLabel(self.frame)
+ self.emoji_label.setGeometry(QtCore.QRect(30, 10, 81, 61))
+ font = QtGui.QFont()
+ font.setFamily("Segoe UI Symbol")
+ font.setPointSize(36)
+ self.emoji_label.setFont(font)
+ self.emoji_label.setText("")
+ self.emoji_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.emoji_label.setObjectName("emoji_label")
+
+ self.retranslateUi(EmojiPicker)
+ QtCore.QMetaObject.connectSlotsByName(EmojiPicker)
+
+ def retranslateUi(self, EmojiPicker):
+ _translate = QtCore.QCoreApplication.translate
+ EmojiPicker.setWindowTitle(_translate("EmojiPicker", "Insert symbol"))
+ self.emoji_insert_button.setText(_translate("EmojiPicker", "Insert"))
+
diff --git a/crocpad/ui/emoji_picker.ui b/crocpad/ui/emoji_picker.ui
new file mode 100644
index 00000000..980a34c1
--- /dev/null
+++ b/crocpad/ui/emoji_picker.ui
@@ -0,0 +1,131 @@
+
+
+ EmojiPicker
+
+
+
+ 0
+ 0
+ 422
+ 480
+
+
+
+ ArrowCursor
+
+
+ Insert symbol
+
+
+
+
+ 10
+ 60
+ 401
+ 411
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ ClosedHandCursor
+
+
+ false
+
+
+ false
+
+
+ 9983
+
+
+ 1
+
+
+ Qt::Horizontal
+
+
+ false
+
+
+ false
+
+
+ true
+
+
+ 50.000000000000000
+
+
+ false
+
+
+
+
+
+ 150
+ 450
+ 126
+ 23
+
+
+
+ Insert
+
+
+
+
+
+ 140
+ 10
+ 141
+ 80
+
+
+
+ ArrowCursor
+
+
+ QFrame::Box
+
+
+ QFrame::Sunken
+
+
+
+
+ 30
+ 10
+ 81
+ 61
+
+
+
+
+ Segoe UI Symbol
+ 36
+
+
+
+
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+
diff --git a/crocpad/ui/eula.py b/crocpad/ui/eula.py
new file mode 100644
index 00000000..5ca410c9
--- /dev/null
+++ b/crocpad/ui/eula.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'eula.ui'
+#
+# Created by: PyQt5 UI code generator 5.11.3
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+class Ui_EulaDialog(object):
+ def setupUi(self, EulaDialog):
+ EulaDialog.setObjectName("EulaDialog")
+ EulaDialog.resize(218, 701)
+ self.eula_TextEdit = QtWidgets.QPlainTextEdit(EulaDialog)
+ self.eula_TextEdit.setGeometry(QtCore.QRect(10, 10, 201, 651))
+ self.eula_TextEdit.setPlainText("")
+ self.eula_TextEdit.setTextInteractionFlags(QtCore.Qt.TextSelectableByKeyboard|QtCore.Qt.TextSelectableByMouse)
+ self.eula_TextEdit.setObjectName("eula_TextEdit")
+ self.eula_disagree_button = QtWidgets.QPushButton(EulaDialog)
+ self.eula_disagree_button.setGeometry(QtCore.QRect(10, 670, 61, 23))
+ self.eula_disagree_button.setObjectName("eula_disagree_button")
+ self.eula_agree_button = QtWidgets.QPushButton(EulaDialog)
+ self.eula_agree_button.setGeometry(QtCore.QRect(80, 670, 131, 23))
+ self.eula_agree_button.setObjectName("eula_agree_button")
+
+ self.retranslateUi(EulaDialog)
+ QtCore.QMetaObject.connectSlotsByName(EulaDialog)
+
+ def retranslateUi(self, EulaDialog):
+ _translate = QtCore.QCoreApplication.translate
+ EulaDialog.setWindowTitle(_translate("EulaDialog", "License"))
+ self.eula_disagree_button.setText(_translate("EulaDialog", "Disagree"))
+ self.eula_agree_button.setText(_translate("EulaDialog", "Reconfirm Agreement"))
+
diff --git a/crocpad/ui/eula.ui b/crocpad/ui/eula.ui
new file mode 100644
index 00000000..936890fa
--- /dev/null
+++ b/crocpad/ui/eula.ui
@@ -0,0 +1,61 @@
+
+
+ EulaDialog
+
+
+
+ 0
+ 0
+ 218
+ 701
+
+
+
+ License
+
+
+
+
+ 10
+ 10
+ 201
+ 651
+
+
+
+
+
+
+ Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse
+
+
+
+
+
+ 10
+ 670
+ 61
+ 23
+
+
+
+ Disagree
+
+
+
+
+
+ 80
+ 670
+ 131
+ 23
+
+
+
+ Reconfirm Agreement
+
+
+
+
+
+
diff --git a/crocpad/ui/eula_quiz.py b/crocpad/ui/eula_quiz.py
new file mode 100644
index 00000000..195f148f
--- /dev/null
+++ b/crocpad/ui/eula_quiz.py
@@ -0,0 +1,247 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'eula_quiz.ui'
+#
+# Created by: PyQt5 UI code generator 5.11.3
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+class Ui_EulaQuizDialog(object):
+ def setupUi(self, EulaQuizDialog):
+ EulaQuizDialog.setObjectName("EulaQuizDialog")
+ EulaQuizDialog.resize(711, 532)
+ self.verticalLayoutWidget = QtWidgets.QWidget(EulaQuizDialog)
+ self.verticalLayoutWidget.setGeometry(QtCore.QRect(10, 10, 691, 511))
+ self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
+ self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.label_8 = QtWidgets.QLabel(self.verticalLayoutWidget)
+ self.label_8.setObjectName("label_8")
+ self.verticalLayout.addWidget(self.label_8)
+ self.line = QtWidgets.QFrame(self.verticalLayoutWidget)
+ self.line.setFrameShape(QtWidgets.QFrame.HLine)
+ self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.line.setObjectName("line")
+ self.verticalLayout.addWidget(self.line)
+ self.gridLayout = QtWidgets.QGridLayout()
+ self.gridLayout.setObjectName("gridLayout")
+ self.verticalLayout_13 = QtWidgets.QVBoxLayout()
+ self.verticalLayout_13.setObjectName("verticalLayout_13")
+ self.label_6 = QtWidgets.QLabel(self.verticalLayoutWidget)
+ self.label_6.setMinimumSize(QtCore.QSize(0, 0))
+ self.label_6.setObjectName("label_6")
+ self.verticalLayout_13.addWidget(self.label_6)
+ self.quiz5_Layout = QtWidgets.QVBoxLayout()
+ self.quiz5_Layout.setObjectName("quiz5_Layout")
+ self.radioButton_26 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_26.setObjectName("radioButton_26")
+ self.quiz5_Layout.addWidget(self.radioButton_26)
+ self.radioButton_27 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_27.setObjectName("radioButton_27")
+ self.quiz5_Layout.addWidget(self.radioButton_27)
+ self.quiz5_Correct = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.quiz5_Correct.setObjectName("quiz5_Correct")
+ self.quiz5_Layout.addWidget(self.quiz5_Correct)
+ self.radioButton_29 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_29.setObjectName("radioButton_29")
+ self.quiz5_Layout.addWidget(self.radioButton_29)
+ self.radioButton_30 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_30.setObjectName("radioButton_30")
+ self.quiz5_Layout.addWidget(self.radioButton_30)
+ spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.quiz5_Layout.addItem(spacerItem)
+ self.verticalLayout_13.addLayout(self.quiz5_Layout)
+ self.gridLayout.addLayout(self.verticalLayout_13, 3, 0, 1, 1)
+ self.verticalLayout_15 = QtWidgets.QVBoxLayout()
+ self.verticalLayout_15.setObjectName("verticalLayout_15")
+ self.label_7 = QtWidgets.QLabel(self.verticalLayoutWidget)
+ self.label_7.setMinimumSize(QtCore.QSize(0, 0))
+ self.label_7.setObjectName("label_7")
+ self.verticalLayout_15.addWidget(self.label_7)
+ self.quiz6_Layout = QtWidgets.QVBoxLayout()
+ self.quiz6_Layout.setObjectName("quiz6_Layout")
+ self.radioButton_31 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_31.setObjectName("radioButton_31")
+ self.quiz6_Layout.addWidget(self.radioButton_31)
+ self.radioButton_32 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_32.setObjectName("radioButton_32")
+ self.quiz6_Layout.addWidget(self.radioButton_32)
+ self.radioButton_33 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_33.setObjectName("radioButton_33")
+ self.quiz6_Layout.addWidget(self.radioButton_33)
+ self.radioButton_34 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_34.setObjectName("radioButton_34")
+ self.quiz6_Layout.addWidget(self.radioButton_34)
+ self.quiz6_Correct = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.quiz6_Correct.setObjectName("quiz6_Correct")
+ self.quiz6_Layout.addWidget(self.quiz6_Correct)
+ spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.quiz6_Layout.addItem(spacerItem1)
+ self.verticalLayout_15.addLayout(self.quiz6_Layout)
+ self.gridLayout.addLayout(self.verticalLayout_15, 3, 1, 1, 1)
+ self.verticalLayout_5 = QtWidgets.QVBoxLayout()
+ self.verticalLayout_5.setObjectName("verticalLayout_5")
+ self.label_4 = QtWidgets.QLabel(self.verticalLayoutWidget)
+ self.label_4.setMinimumSize(QtCore.QSize(0, 0))
+ self.label_4.setObjectName("label_4")
+ self.verticalLayout_5.addWidget(self.label_4)
+ self.quiz1_Layout = QtWidgets.QVBoxLayout()
+ self.quiz1_Layout.setObjectName("quiz1_Layout")
+ self.radioButton_16 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_16.setObjectName("radioButton_16")
+ self.quiz1_Layout.addWidget(self.radioButton_16)
+ self.quiz1_Correct = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.quiz1_Correct.setObjectName("quiz1_Correct")
+ self.quiz1_Layout.addWidget(self.quiz1_Correct)
+ self.radioButton_18 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_18.setObjectName("radioButton_18")
+ self.quiz1_Layout.addWidget(self.radioButton_18)
+ self.radioButton_19 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_19.setObjectName("radioButton_19")
+ self.quiz1_Layout.addWidget(self.radioButton_19)
+ self.radioButton_20 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_20.setObjectName("radioButton_20")
+ self.quiz1_Layout.addWidget(self.radioButton_20)
+ spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.quiz1_Layout.addItem(spacerItem2)
+ self.verticalLayout_5.addLayout(self.quiz1_Layout)
+ self.gridLayout.addLayout(self.verticalLayout_5, 1, 0, 1, 1)
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout()
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.label = QtWidgets.QLabel(self.verticalLayoutWidget)
+ self.label.setMinimumSize(QtCore.QSize(0, 0))
+ self.label.setObjectName("label")
+ self.verticalLayout_2.addWidget(self.label)
+ self.quiz3_Layout = QtWidgets.QVBoxLayout()
+ self.quiz3_Layout.setObjectName("quiz3_Layout")
+ self.radioButton = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton.setObjectName("radioButton")
+ self.quiz3_Layout.addWidget(self.radioButton)
+ self.radioButton_2 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_2.setObjectName("radioButton_2")
+ self.quiz3_Layout.addWidget(self.radioButton_2)
+ self.radioButton_3 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_3.setObjectName("radioButton_3")
+ self.quiz3_Layout.addWidget(self.radioButton_3)
+ self.radioButton_4 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_4.setObjectName("radioButton_4")
+ self.quiz3_Layout.addWidget(self.radioButton_4)
+ self.quiz3_Correct = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.quiz3_Correct.setObjectName("quiz3_Correct")
+ self.quiz3_Layout.addWidget(self.quiz3_Correct)
+ spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.quiz3_Layout.addItem(spacerItem3)
+ self.verticalLayout_2.addLayout(self.quiz3_Layout)
+ self.gridLayout.addLayout(self.verticalLayout_2, 2, 0, 1, 1)
+ self.verticalLayout_6 = QtWidgets.QVBoxLayout()
+ self.verticalLayout_6.setObjectName("verticalLayout_6")
+ self.label_3 = QtWidgets.QLabel(self.verticalLayoutWidget)
+ self.label_3.setMinimumSize(QtCore.QSize(0, 0))
+ self.label_3.setObjectName("label_3")
+ self.verticalLayout_6.addWidget(self.label_3)
+ self.quiz2_Layout = QtWidgets.QVBoxLayout()
+ self.quiz2_Layout.setObjectName("quiz2_Layout")
+ self.radioButton_11 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_11.setObjectName("radioButton_11")
+ self.quiz2_Layout.addWidget(self.radioButton_11)
+ self.radioButton_12 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_12.setObjectName("radioButton_12")
+ self.quiz2_Layout.addWidget(self.radioButton_12)
+ self.radioButton_13 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_13.setObjectName("radioButton_13")
+ self.quiz2_Layout.addWidget(self.radioButton_13)
+ self.radioButton_14 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_14.setObjectName("radioButton_14")
+ self.quiz2_Layout.addWidget(self.radioButton_14)
+ self.quiz2_Correct = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.quiz2_Correct.setObjectName("quiz2_Correct")
+ self.quiz2_Layout.addWidget(self.quiz2_Correct)
+ spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.quiz2_Layout.addItem(spacerItem4)
+ self.verticalLayout_6.addLayout(self.quiz2_Layout)
+ self.gridLayout.addLayout(self.verticalLayout_6, 1, 1, 1, 1)
+ self.verticalLayout_11 = QtWidgets.QVBoxLayout()
+ self.verticalLayout_11.setObjectName("verticalLayout_11")
+ self.label_5 = QtWidgets.QLabel(self.verticalLayoutWidget)
+ self.label_5.setMinimumSize(QtCore.QSize(0, 0))
+ self.label_5.setObjectName("label_5")
+ self.verticalLayout_11.addWidget(self.label_5)
+ self.quiz4_Layout = QtWidgets.QVBoxLayout()
+ self.quiz4_Layout.setObjectName("quiz4_Layout")
+ self.radioButton_21 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_21.setObjectName("radioButton_21")
+ self.quiz4_Layout.addWidget(self.radioButton_21)
+ self.radioButton_22 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_22.setObjectName("radioButton_22")
+ self.quiz4_Layout.addWidget(self.radioButton_22)
+ self.radioButton_23 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_23.setObjectName("radioButton_23")
+ self.quiz4_Layout.addWidget(self.radioButton_23)
+ self.radioButton_24 = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.radioButton_24.setObjectName("radioButton_24")
+ self.quiz4_Layout.addWidget(self.radioButton_24)
+ self.quiz4_Correct = QtWidgets.QRadioButton(self.verticalLayoutWidget)
+ self.quiz4_Correct.setObjectName("quiz4_Correct")
+ self.quiz4_Layout.addWidget(self.quiz4_Correct)
+ spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.quiz4_Layout.addItem(spacerItem5)
+ self.verticalLayout_11.addLayout(self.quiz4_Layout)
+ self.gridLayout.addLayout(self.verticalLayout_11, 2, 1, 1, 1)
+ self.verticalLayout.addLayout(self.gridLayout)
+ self.horizontalLayout = QtWidgets.QHBoxLayout()
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ spacerItem6 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout.addItem(spacerItem6)
+ self.submitButton = QtWidgets.QPushButton(self.verticalLayoutWidget)
+ self.submitButton.setObjectName("submitButton")
+ self.horizontalLayout.addWidget(self.submitButton)
+ self.verticalLayout.addLayout(self.horizontalLayout)
+
+ self.retranslateUi(EulaQuizDialog)
+ QtCore.QMetaObject.connectSlotsByName(EulaQuizDialog)
+
+ def retranslateUi(self, EulaQuizDialog):
+ _translate = QtCore.QCoreApplication.translate
+ EulaQuizDialog.setWindowTitle(_translate("EulaQuizDialog", "Quiz"))
+ self.label_8.setText(_translate("EulaQuizDialog", "To indicate your understanding of the Terms and Conditions of the SOFTWARE, you must pass the following mandatory quiz."))
+ self.label_6.setText(_translate("EulaQuizDialog", "How are disputes between you and us to be resolved?"))
+ self.radioButton_26.setText(_translate("EulaQuizDialog", "Jury trial"))
+ self.radioButton_27.setText(_translate("EulaQuizDialog", "Trial by judge"))
+ self.quiz5_Correct.setText(_translate("EulaQuizDialog", "Arbitration, with an arbitrator chosen by us"))
+ self.radioButton_29.setText(_translate("EulaQuizDialog", "Violence"))
+ self.radioButton_30.setText(_translate("EulaQuizDialog", "None of the above"))
+ self.label_7.setText(_translate("EulaQuizDialog", "What must the SOFTWARE do for you?"))
+ self.radioButton_31.setText(_translate("EulaQuizDialog", "Be fit for any particular purpose"))
+ self.radioButton_32.setText(_translate("EulaQuizDialog", "Be responsible for the integrity of your data"))
+ self.radioButton_33.setText(_translate("EulaQuizDialog", "Convey liability from use or inability to use"))
+ self.radioButton_34.setText(_translate("EulaQuizDialog", "Not send your files over the internet"))
+ self.quiz6_Correct.setText(_translate("EulaQuizDialog", "None of the above"))
+ self.label_4.setText(_translate("EulaQuizDialog", "When did I agree to the Terms and Conditions?"))
+ self.radioButton_16.setText(_translate("EulaQuizDialog", "When I clicked Agree"))
+ self.quiz1_Correct.setText(_translate("EulaQuizDialog", "When I launched the SOFTWARE"))
+ self.radioButton_18.setText(_translate("EulaQuizDialog", "When I downloaded the SOFTWARE"))
+ self.radioButton_19.setText(_translate("EulaQuizDialog", "I did not agree"))
+ self.radioButton_20.setText(_translate("EulaQuizDialog", "None of the above"))
+ self.label.setText(_translate("EulaQuizDialog", "What may the SOFTWARE not be used for?"))
+ self.radioButton.setText(_translate("EulaQuizDialog", "Profit"))
+ self.radioButton_2.setText(_translate("EulaQuizDialog", "Non-profit"))
+ self.radioButton_3.setText(_translate("EulaQuizDialog", "Information"))
+ self.radioButton_4.setText(_translate("EulaQuizDialog", "Entertainment"))
+ self.quiz3_Correct.setText(_translate("EulaQuizDialog", "All of the above"))
+ self.label_3.setText(_translate("EulaQuizDialog", "When may we share your data with law enforcement?"))
+ self.radioButton_11.setText(_translate("EulaQuizDialog", "When legally required"))
+ self.radioButton_12.setText(_translate("EulaQuizDialog", "When not legally required"))
+ self.radioButton_13.setText(_translate("EulaQuizDialog", "If the data is critical of law enforcement"))
+ self.radioButton_14.setText(_translate("EulaQuizDialog", "If they might like it"))
+ self.quiz2_Correct.setText(_translate("EulaQuizDialog", "All of the above"))
+ self.label_5.setText(_translate("EulaQuizDialog", "How may we use your data?"))
+ self.radioButton_21.setText(_translate("EulaQuizDialog", "Read it"))
+ self.radioButton_22.setText(_translate("EulaQuizDialog", "Sell it"))
+ self.radioButton_23.setText(_translate("EulaQuizDialog", "Auction it"))
+ self.radioButton_24.setText(_translate("EulaQuizDialog", "Polymorph it"))
+ self.quiz4_Correct.setText(_translate("EulaQuizDialog", "All of the above"))
+ self.submitButton.setText(_translate("EulaQuizDialog", "Submit"))
+
diff --git a/crocpad/ui/eula_quiz.ui b/crocpad/ui/eula_quiz.ui
new file mode 100644
index 00000000..0f9b5233
--- /dev/null
+++ b/crocpad/ui/eula_quiz.ui
@@ -0,0 +1,487 @@
+
+
+ EulaQuizDialog
+
+
+
+ 0
+ 0
+ 711
+ 532
+
+
+
+ Quiz
+
+
+
+
+ 10
+ 10
+ 691
+ 511
+
+
+
+ -
+
+
+ To indicate your understanding of the Terms and Conditions of the SOFTWARE, you must pass the following mandatory quiz.
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ How are disputes between you and us to be resolved?
+
+
+
+ -
+
+
-
+
+
+ Jury trial
+
+
+
+ -
+
+
+ Trial by judge
+
+
+
+ -
+
+
+ Arbitration, with an arbitrator chosen by us
+
+
+
+ -
+
+
+ Violence
+
+
+
+ -
+
+
+ None of the above
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ What must the SOFTWARE do for you?
+
+
+
+ -
+
+
-
+
+
+ Be fit for any particular purpose
+
+
+
+ -
+
+
+ Be responsible for the integrity of your data
+
+
+
+ -
+
+
+ Convey liability from use or inability to use
+
+
+
+ -
+
+
+ Not send your files over the internet
+
+
+
+ -
+
+
+ None of the above
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ When did I agree to the Terms and Conditions?
+
+
+
+ -
+
+
-
+
+
+ When I clicked Agree
+
+
+
+ -
+
+
+ When I launched the SOFTWARE
+
+
+
+ -
+
+
+ When I downloaded the SOFTWARE
+
+
+
+ -
+
+
+ I did not agree
+
+
+
+ -
+
+
+ None of the above
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ What may the SOFTWARE not be used for?
+
+
+
+ -
+
+
-
+
+
+ Profit
+
+
+
+ -
+
+
+ Non-profit
+
+
+
+ -
+
+
+ Information
+
+
+
+ -
+
+
+ Entertainment
+
+
+
+ -
+
+
+ All of the above
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ When may we share your data with law enforcement?
+
+
+
+ -
+
+
-
+
+
+ When legally required
+
+
+
+ -
+
+
+ When not legally required
+
+
+
+ -
+
+
+ If the data is critical of law enforcement
+
+
+
+ -
+
+
+ If they might like it
+
+
+
+ -
+
+
+ All of the above
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ How may we use your data?
+
+
+
+ -
+
+
-
+
+
+ Read it
+
+
+
+ -
+
+
+ Sell it
+
+
+
+ -
+
+
+ Auction it
+
+
+
+ -
+
+
+ Polymorph it
+
+
+
+ -
+
+
+ All of the above
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Submit
+
+
+
+
+
+
+
+
+
+
+
diff --git a/crocpad/ui/wizard.py b/crocpad/ui/wizard.py
new file mode 100644
index 00000000..20af0db7
--- /dev/null
+++ b/crocpad/ui/wizard.py
@@ -0,0 +1,94 @@
+from PyQt5 import QtCore, QtGui, QtWidgets
+from pathlib import Path
+
+class LoadingPage(object):
+ def setupUi(self, WizardPage):
+ WizardPage.setObjectName("WizardPage")
+ WizardPage.resize(400, 300)
+ WizardPage.setCommitPage(True)
+ self.verticalLayoutWidget = QtWidgets.QWidget(WizardPage)
+ self.verticalLayoutWidget.setGeometry(QtCore.QRect(10, 10, 381, 281))
+ self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
+ self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout.setObjectName("verticalLayout")
+ spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout.addItem(spacerItem)
+ self.progressBar = QtWidgets.QProgressBar(self.verticalLayoutWidget)
+ self.progressBar.setProperty("value", 0)
+ self.progressBar.setObjectName("progressBar")
+ self.verticalLayout.addWidget(self.progressBar)
+ self.label = QtWidgets.QLabel(self.verticalLayoutWidget)
+ font = QtGui.QFont()
+ font.setFamily("Comic Sans MS")
+ self.label.setFont(font)
+ self.label.setCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
+ self.label.setObjectName("label")
+ self.verticalLayout.addWidget(self.label)
+ spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout.addItem(spacerItem1)
+ self.retranslateUi(WizardPage)
+ QtCore.QMetaObject.connectSlotsByName(WizardPage)
+
+ def retranslateUi(self, WizardPage):
+ _translate = QtCore.QCoreApplication.translate
+ WizardPage.setWindowTitle(_translate("WizardPage", "WizardPage"))
+ self.label.setText(_translate("WizardPage", "loading troubleshooter..."))
+
+class Ui_Wizard(object):
+ def setupUi(self, Wizard):
+ Wizard.setObjectName("Wizard")
+ Wizard.resize(402, 300)
+ icon = QtGui.QIcon()
+ window_icon = str(Path('crocpad') / Path('crocpad.ico'))
+ icon.addPixmap(QtGui.QPixmap(window_icon), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ Wizard.setWindowIcon(icon)
+ Wizard.setWizardStyle(QtWidgets.QWizard.ModernStyle)
+ Wizard.setOptions(QtWidgets.QWizard.NoBackButtonOnStartPage)
+ Wizard.setPixmap(QtWidgets.QWizard.LogoPixmap,
+ QtGui.QPixmap(window_icon))
+
+ self.help_button = Wizard.HelpButton
+ self.stretch = Wizard.Stretch
+ self.cancel_button = Wizard.CancelButton
+ self.next_button = Wizard.NextButton
+ buttons = [self.help_button, self.stretch, self.cancel_button, self.next_button]
+
+ self.setButtonLayout(buttons)
+
+ Wizard.button(self.help_button).clicked.connect(self.help)
+
+ self.wizardPage1 = QtWidgets.QWizardPage()
+ self.wizardPage1.setObjectName("wizardPage1")
+ self.verticalLayoutWidget = QtWidgets.QWidget(self.wizardPage1)
+ self.verticalLayoutWidget.setGeometry(QtCore.QRect(10, 10, 391, 251))
+ self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
+ self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout.setObjectName("verticalLayout")
+ spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout.addItem(spacerItem)
+ self.label = QtWidgets.QLabel(self.verticalLayoutWidget)
+ self.label.setObjectName("label")
+ self.verticalLayout.addWidget(self.label)
+ spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout.addItem(spacerItem1)
+ Wizard.addPage(self.wizardPage1)
+
+ self.loadingPage = QtWidgets.QWizardPage()
+ self.progressBar = LoadingPage().setupUi(self.loadingPage)
+ self.loadingPage.setObjectName("loadingPage")
+ Wizard.addPage(self.loadingPage)
+
+ font = QtGui.QFont()
+ font.setFamily("Comic Sans MS")
+ self.label.setFont(font)
+ self.retranslateUi(Wizard)
+ QtCore.QMetaObject.connectSlotsByName(Wizard)
+
+ def retranslateUi(self, Wizard):
+ Wizard.setWindowTitle(QtWidgets.QApplication.translate("Wizard", "crocpad++ troubleshooter", None, -1))
+ self.label.setText(QtWidgets.QApplication.translate("Wizard", "hello welcome to the crocpad++ troubleshooter\nlet me fix ur problems", None, -1))
+
+ def help(self):
+ QtWidgets.QMessageBox.question(self, 'Help', "This is a troubleshooter.")
diff --git a/project/__main__.py b/project/__main__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/spoilers.txt b/spoilers.txt
new file mode 100644
index 00000000..21924daf
--- /dev/null
+++ b/spoilers.txt
@@ -0,0 +1,9 @@
+"Multimedia" keystrokes (with sound effects) are space, enter and backspace.
+
+All the menu shortcuts are the same where possible (Alt+E).
+
+Sometimes when you press the space bar, a message box with an OK button pops up, then disappears.
+
+The "Insert symbol" feature has an unlabeled dial with almost 10,000 positions.
+
+The color theme for blind users is black on black.
\ No newline at end of file