7 Commits

Author SHA1 Message Date
Aaron
8ba9af8971 Significant Edits to Functionality 2021-02-03 17:46:16 -05:00
Aaron
624ce61764 Merge branch 'QtTesting' into notes 2021-02-02 13:01:30 -05:00
Aaron
546297e763 Our first glimpse of a cross-platform note editor. It doesn't even have save functionality. How sad. 2021-02-02 12:58:53 -05:00
Aaron
3ce0f0ead9 Added an incredibly basic note system. Only works on Windows with lots of caveats right now. 2021-01-26 11:32:41 -05:00
Aaron
16f6a2e2fd Added some learning file for QT6 2021-01-26 10:29:19 -05:00
Timothy Davis
5930fd681d Added very basic checklist functionality
This is absolutely subject to change. This is just a trial run to get advice on the checklist implementation so far.
2021-01-25 22:51:59 -05:00
Aaron
26a38d7db8 Initial Branch Commit 2021-01-21 20:50:22 -05:00
4 changed files with 369 additions and 0 deletions

View File

@@ -0,0 +1 @@
PySide6~=6.0.0

140
src/QtLearning.py Normal file
View File

@@ -0,0 +1,140 @@
import sys
from pathlib import Path
from PySide6.QtCore import QStandardPaths
from PySide6.QtGui import QStandardItemModel, QStandardItem, QIcon
from PySide6.QtWidgets import QMainWindow, QApplication, QTreeView, QTextEdit, QSplitter, QSizePolicy, QMenuBar, QMenu
# TODO: Split this out into its own module for handling the FS operations in an abstract manner. We need to relocate
# all of this code up to the class.
NYTEWORKS_DIR = QStandardPaths.locate(QStandardPaths.AppDataLocation, 'NyteWoks', QStandardPaths.LocateDirectory)
if not NYTEWORKS_DIR:
# We don't already have a directory in the roaming area
NYTEWORKS_DIR = QStandardPaths.standardLocations(QStandardPaths.AppDataLocation)
if not NYTEWORKS_DIR:
# There are no suitable areas on the system to write our data to. Throw an error and let the user decide where
# to store the data if possible.
# TODO: Implement some sort of handling for this unfortunate situation...
print('Unable to find a suitable location for data on your system.', file=sys.stderr)
sys.exit(1)
else:
NYTEWORKS_DIR = Path(NYTEWORKS_DIR[0] + '/Nyteworks')
else:
NYTEWORKS_DIR = Path(NYTEWORKS_DIR)
# We have the parent directory, let's get the binder list
LIFEFLOW_DIR = NYTEWORKS_DIR / 'LifeFlow'
BINDERS_DIR = LIFEFLOW_DIR / 'Binders'
# Ensure that the binder directory exists
BINDERS_DIR.mkdir(parents=True, exist_ok=True)
# Some business logic that is currently necessary...
# TODO: Find a more elegant way to handle the Inbox folder
INBOX_DIR = BINDERS_DIR / 'Inbox'
Path(INBOX_DIR).mkdir(parents=True, exist_ok=True)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setWindowTitle('LifeFlow - Notes for the Real World')
self._create_ui()
def _create_ui(self):
self._setup_menu_bar()
self._create_note_tree_view()
self.text_area = QTextEdit()
self.setCentralWidget(self.note_tree_view)
self.note_tree_view.setSizePolicy(QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding))
self.text_area.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
self.note_tree_view.clicked.connect(self.selected_note_changed)
self.splitter = QSplitter()
self.splitter.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
self.splitter.addWidget(self.note_tree_view)
self.splitter.addWidget(self.text_area)
self.splitter.setSizes([self.note_tree_view.sizeHint().width(), 1000])
self.setCentralWidget(self.splitter)
def _setup_menu_bar(self):
menuBar = self.menuBar()
fileMenu = menuBar.addMenu('&File')
fileMenu.addAction('Open')
fileMenu.addAction('New')
fileMenu.addAction('Save')
fileMenu.addAction('Save As...')
editMenu = menuBar.addMenu('&Edit')
editMenu.addAction('Undo')
editMenu.addAction('Redo')
editMenu.addSeparator()
editMenu.addAction('Cut')
editMenu.addAction('Copy')
editMenu.addAction('Paste')
def _create_note_tree_view(self):
self.note_tree_view = QTreeView()
self.note_tree_view.setHeaderHidden(True)
self.note_tree_view.setModel(self._create_note_model())
def _create_note_model(self):
self.note_tree_model = QStandardItemModel()
root = self.note_tree_model.invisibleRootItem()
# TODO: Investigate what else pathlib might be able to do for us better here.
# TODO: Right now the note hierarchy is hard-coded. We need a more generic recursion code and need to consider
# how the database will be implemented so that other views fit in the hierarchy.
binders = [binder for binder in BINDERS_DIR.iterdir() if binder.is_dir()]
for binder in binders:
binder_name = binder.parts[-1]
binder_item = QStandardItem(binder_name)
binder_item.setData(False, 1)
notebooks = [notebook for notebook in binder.iterdir() if notebook.is_dir()]
for notebook in notebooks:
notebook_name = notebook.parts[-1]
notebook_item = QStandardItem(notebook_name)
notebook_item.setData(False, 1)
notes = [note for note in notebook.iterdir() if note.is_file()]
for note in notes:
note_name = note.parts[-1]
note_item = QStandardItem(note_name)
note_item.is_note = True
note_item.setData(True, 1)
notebook_item.appendRow(note_item)
binder_item.appendRow(notebook_item)
root.appendRow(binder_item)
return self.note_tree_model
def selected_note_changed(self, index):
item = index.model().itemFromIndex(index)
if item.data(1):
notebook = item.parent().text()
binder = item.parent().parent().text()
note = BINDERS_DIR / binder / notebook / item.text()
print('I should get:', note)
note_text = None
with open(note, 'r') as note_file:
note_text = note_file.readlines()
self.text_area.setText(''.join(note_text))
def main():
app = QApplication()
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

94
src/checklist_view.py Normal file
View File

@@ -0,0 +1,94 @@
"""
考えろ、バカ:
- Checklists will have a tree structure.
- The parent nodes are checklists.
- Leaf nodes are checklist items.
- If a parent task's status is changed to completed, all of its children will be changed as well.
- If all of a parent task's children tasks are completed, it will also be automatically completed.
- If one of a completed parent task's child tasks is un-completed, the parent task will be as well.
- Until it's discussed more, I'll treat the schedule variable as the number of days until which you want the list
to reset.
CURRENT SETBACKS:
- There might be unfortunate times whenever you accidentally complete a parent task, subsequently completing
all of the subtasks. In which case, it might be impossible to auto 'un-complete' the subtasks.
"""
class ChecklistItem:
def __init__(self, task=None):
self.task = task
self.parent = None
self.children = []
self.completed = False
def add_child(self, sub_task):
sub_task.set_parent(self)
self.children.append(sub_task)
def mark_task(self):
self.completed = True
def unmark_task(self):
self.completed = False
def set_parent(self, parent):
self.parent = parent
def get_task(self):
return self.task
def get_completion(self):
return self.completed
def get_parent(self):
return self.parent
class Checklist:
def __init__(self, schedule=0):
self.root = ChecklistItem()
self.schedule = schedule
def print_checklist(root):
for item in root.children:
temp = root
while temp.get_parent() is not None:
temp = temp.get_parent()
print(" ", end="")
if item.get_completion():
print("x " + item.get_task())
else:
print("o " + item.get_task())
print_checklist(item)
if __name__ == "__main__":
test_checklist = Checklist()
"""
As a proof of concept, I'll make a checklist similar to the following, x is complete, o is incomplete:
Backpack Checklist
o back pocket
x laptop
o げんき third edition Japanese textbook and workbook with bonus learning exercises
o front pocket
o pens
o headphones
"""
test_checklist.root.add_child(ChecklistItem("back pocket"))
test_checklist.root.children[0].add_child(ChecklistItem("laptop"))
test_checklist.root.children[0].children[0].mark_task()
test_checklist.root.children[0].add_child(ChecklistItem("げんき third edition Japanese textbook and workbook with "
"bonus learning exercises"))
test_checklist.root.add_child(ChecklistItem("front pocket"))
test_checklist.root.children[1].add_child(ChecklistItem("pens"))
test_checklist.root.children[1].add_child(ChecklistItem("headphones"))
print_checklist(test_checklist.root)

134
src/notes_view.py Normal file
View File

@@ -0,0 +1,134 @@
import os
import sys
import os.path
import appdirs
import shutil
import datetime
from os.path import join
from pathlib import Path
BASE_LOCATION = appdirs.user_data_dir("LifeFlow", "NyteWorks", "0.1.0")
NOTES_FOLDER = join(BASE_LOCATION, "Notes")
#TODO: In the future, the user will be able to change which folder is the default for QuickNotes
QUICK_NOTES_FOLDER = join(NOTES_FOLDER, "Inbox")
if not os.path.exists(BASE_LOCATION):
os.makedirs(BASE_LOCATION, exist_ok=True)
if not os.path.exists(NOTES_FOLDER):
os.makedirs(NOTES_FOLDER, exist_ok=True)
if not os.path.exists(QUICK_NOTES_FOLDER):
os.makedirs(QUICK_NOTES_FOLDER, exist_ok=True)
current_notebook = "Inbox"
all_notebooks = []
for notebook in os.listdir(NOTES_FOLDER):
all_notebooks.append(notebook)
def list_notes(args):
if len(args) == 0:
print("You have the following notebooks:")
notebooks = os.listdir(NOTES_FOLDER)
for notebook in notebooks:
print("*", notebook)
else:
notebook = args[0]
print("These are the notes in", notebook)
notes = os.listdir(join(NOTES_FOLDER, notebook))
for note in notes:
note = ".".join(note.split(".")[:-1])
print(note)
def select_notebook(args):
global all_notebooks
global current_notebook
if args[0] in all_notebooks:
current_notebook = args[0]
def create_notebook(args):
global all_notebooks
global current_notebook
if len(args) < 1:
print("You need a name for your new notebook!", file=sys.stderr)
return
if not os.path.exists(join(NOTES_FOLDER, args[0])):
os.makedirs(join(NOTES_FOLDER, args[0]), exist_ok=True)
all_notebooks.append(args[0])
current_notebook = args[0]
def delete(args):
global current_notebook
global all_notebooks
if len(args) < 1:
print("Unable to delete nothing.", file=sys.stderr)
return
if args[0] == 'notebook':
if len(args) < 2:
print("You need to select a notebook to delete.")
else:
if args[1] == 'Inbox':
print("You cannot delete the Inbox.")
return
elif args[1] == current_notebook:
current_notebook = "Inbox"
path = join(NOTES_FOLDER, args[1])
if not os.path.exists(path):
print("That notebook does not exist.")
return
print("CHECKING:", path)
shutil.rmtree(path)
print("Deleted notebook", args[1])
else:
path = join(NOTES_FOLDER, current_notebook, args[0] + ".txt")
print("CHECKING:", path)
if not os.path.exists(path):
print("That note does not exist.")
return
os.remove(path)
print("Deleted", args[0])
def add_note(arguments):
global current_notebook
if current_notebook is None:
current_notebook = "Inbox"
if len(arguments) == 0:
filename = '{date:%Y-%m-%d_%H:%M:%S}.txt'.format(date=datetime.datetime.now())
else:
#TODO: We need to sanitize this for legal filename
filename = " ".join(arguments) + ".txt"
filename = join(NOTES_FOLDER, current_notebook, filename)
if not os.path.exists(filename):
filepath = Path(filename)
filepath.touch()
os.startfile(join(NOTES_FOLDER, current_notebook, filename))
def run_tui():
global current_notebook
valid_commands = {
"list": list_notes,
"select": select_notebook,
"create": create_notebook,
"delete": delete,
"add": add_note,
"quit": exit,
}
print("Welcome to LifeFlow Notes")
print("-------------------------")
print()
command = None
while(command != "quit"):
if current_notebook is None:
current_notebook = "Inbox"
print("You are currently in notebook:", current_notebook)
command_parts = input("> ").strip().lower().split(" ")
arguments = command_parts[1:]
command = command_parts[0]
if command not in valid_commands:
print("Invalid command. Please input one of: ", file=sys.stderr)
for key in valid_commands.keys():
print("\t", key)
else:
valid_commands[command](arguments)
if __name__ == '__main__':
run_tui()