Compare commits
No commits in common. "qt" and "gtk4" have entirely different histories.
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
.idea
|
|
||||||
|
8
emacs_client.py
Normal file
8
emacs_client.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import subprocess
|
||||||
|
|
||||||
|
def navigate_emacs_to_id(item_id):
|
||||||
|
item_id = item_id.replace('"', '\\"')
|
||||||
|
return subprocess.check_call([
|
||||||
|
'emacsclient', '-e', '(org-id-goto "{}")'.format(item_id)],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
)
|
373
main.py
Executable file → Normal file
373
main.py
Executable file → Normal file
@ -1,241 +1,184 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import os
|
||||||
import webbrowser
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
from PySide2.QtCore import QObject, QThread, Signal, Slot
|
import task_manager
|
||||||
|
import emacs_client
|
||||||
from PySide2.QtGui import QPalette, QColor
|
|
||||||
|
|
||||||
from PySide2.QtWidgets import (
|
|
||||||
QApplication,
|
|
||||||
QDialog,
|
|
||||||
QFrame,
|
|
||||||
QGroupBox,
|
|
||||||
QHBoxLayout,
|
|
||||||
QLabel,
|
|
||||||
QLineEdit,
|
|
||||||
QProgressBar,
|
|
||||||
QPushButton,
|
|
||||||
QScrollArea,
|
|
||||||
QScroller,
|
|
||||||
QTabBar,
|
|
||||||
QVBoxLayout,
|
|
||||||
)
|
|
||||||
|
|
||||||
import doc_manager
|
|
||||||
|
|
||||||
|
APP_TITLE = "Org-Convergence"
|
||||||
DOCS_PATH = os.environ["ORG_PATH"]
|
DOCS_PATH = os.environ["ORG_PATH"]
|
||||||
MAX_SEARCH_NODES = 100
|
STYLE_FILE_PATH = os.path.join(
|
||||||
|
os.path.dirname(os.path.abspath(__file__)),
|
||||||
|
"style.css")
|
||||||
|
|
||||||
|
MIN_TITLE_WIDTH_CHARS = 10
|
||||||
|
|
||||||
class LoadDoneSignal(QObject):
|
import gi
|
||||||
sig = Signal(doc_manager.DocumentManager)
|
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "4.0")
|
||||||
|
gi.require_version('Polkit', '1.0')
|
||||||
|
gi.require_version(namespace='Adw', version='1')
|
||||||
|
|
||||||
class DocumentLoader(QThread):
|
from gi.repository import Gtk, Polkit, GObject, Gio, Adw, Gdk
|
||||||
def __init__(self, manager):
|
|
||||||
QThread.__init__(self, None)
|
|
||||||
self.manager = manager
|
|
||||||
self.signal = LoadDoneSignal()
|
|
||||||
|
|
||||||
def run(self):
|
class MainWindow(Gtk.Window):
|
||||||
self.manager.load()
|
__gsignals__ = {
|
||||||
self.signal.sig.emit(self.manager)
|
"open-in-emacs": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object, )),
|
||||||
|
}
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
def __init__(self, *, title, application, task_manager, with_titlebar=True):
|
||||||
|
super().__init__(title=title, application=application)
|
||||||
|
self.application = application
|
||||||
|
self.task_manager = task_manager
|
||||||
|
self.loading = 0
|
||||||
|
|
||||||
class Dialog(QDialog):
|
if with_titlebar:
|
||||||
def __init__(self):
|
self.header_bar = Gtk.HeaderBar()
|
||||||
super(Dialog, self).__init__()
|
# self.header_bar.set_show_close_button(True)
|
||||||
|
# self.header_bar.props.title = APP_TITLE
|
||||||
|
self.set_titlebar(self.header_bar)
|
||||||
|
|
||||||
palette = self.palette()
|
self.progress_spinner = Gtk.Spinner()
|
||||||
palette.setColor(QPalette.Window, QColor(255, 255, 255))
|
self.progress_spinner.start()
|
||||||
self.setPalette(palette)
|
self.header_bar.pack_end(self.progress_spinner)
|
||||||
self.setAutoFillBackground(True)
|
|
||||||
|
|
||||||
self.setWindowTitle("OrgEditor")
|
|
||||||
scrSize = self.screen().size()
|
|
||||||
self.resize(scrSize.width() / 1.5, scrSize.height() / 1.5)
|
|
||||||
self.loader = None
|
|
||||||
self.manager = doc_manager.DocumentManager(DOCS_PATH)
|
|
||||||
|
|
||||||
layout = QVBoxLayout()
|
|
||||||
|
|
||||||
# Edit box
|
|
||||||
self.progressBar = QProgressBar()
|
|
||||||
self.progressBar.setRange(0, 0) # Make undetermined
|
|
||||||
layout.addWidget(self.progressBar)
|
|
||||||
|
|
||||||
self.edit = QLineEdit("", placeholderText="Search for notes")
|
|
||||||
self.edit.textEdited.connect(self.on_text_edited)
|
|
||||||
layout.addWidget(self.edit)
|
|
||||||
|
|
||||||
layout.setSpacing(0)
|
|
||||||
|
|
||||||
self.results = QScrollArea(widgetResizable=True)
|
|
||||||
layout.addWidget(self.results)
|
|
||||||
QScroller.grabGesture(
|
|
||||||
self.results.viewport(),
|
|
||||||
QScroller.LeftMouseButtonGesture,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Options
|
|
||||||
self.tabBar = QTabBar(shape=QTabBar.RoundedSouth)
|
|
||||||
|
|
||||||
self.tabBar.addTab("Agenda")
|
|
||||||
self.tabBar.addTab("Notes")
|
|
||||||
self.tabBar.addTab("Tasks")
|
|
||||||
self.tabBar.addTab("History")
|
|
||||||
|
|
||||||
self.tabBar.currentChanged.connect(self.update_tab)
|
|
||||||
|
|
||||||
layout.addWidget(self.tabBar)
|
|
||||||
|
|
||||||
self.setLayout(layout)
|
|
||||||
self.startLoad()
|
|
||||||
|
|
||||||
@Slot()
|
|
||||||
def on_text_edited(self):
|
|
||||||
if self.tabBar.currentIndex() != 1:
|
|
||||||
self.tabBar.setCurrentIndex(1)
|
|
||||||
else:
|
else:
|
||||||
self.loadNotes()
|
self.header_bar = None
|
||||||
|
self.progress_spinner = None
|
||||||
|
|
||||||
@Slot()
|
# self.main_box = Gtk.Box(name='main-box', vexpand=True, hexpand=True)
|
||||||
def update_tab(self):
|
self.scrollview = Gtk.ScrolledWindow(vexpand=True, hexpand=True)
|
||||||
tabIndex = self.tabBar.currentIndex()
|
|
||||||
if tabIndex == 0:
|
|
||||||
self.loadAgenda()
|
|
||||||
elif tabIndex == 1:
|
|
||||||
self.loadNotes()
|
|
||||||
elif tabIndex == 2:
|
|
||||||
self.loadTasks()
|
|
||||||
elif tabIndex == 3:
|
|
||||||
self.loadHistory()
|
|
||||||
|
|
||||||
def startLoad(self):
|
self.task_list = Gtk.ListBox(name='task-list')
|
||||||
self.edit.setDisabled(True)
|
self.scrollview.set_child(self.task_list)
|
||||||
self.edit.setVisible(False)
|
self.item_rows = []
|
||||||
self.tabBar.setDisabled(True)
|
|
||||||
self.progressBar.setVisible(True)
|
|
||||||
|
|
||||||
self.loader = DocumentLoader(self.manager)
|
# self.main_box.props.valign = Gtk.Align.CENTER
|
||||||
self.loader.signal.sig.connect(self.longoperationcomplete)
|
# self.main_box.props.halign = Gtk.Align.CENTER
|
||||||
self.loading_start_time = time.time()
|
# self.main_box.append(self.scrollview)
|
||||||
self.loader.start()
|
# self.set_child(self.main_box)
|
||||||
|
self.set_child(self.scrollview)
|
||||||
|
|
||||||
def endLoad(self):
|
self.loading += 1
|
||||||
self.edit.setDisabled(False)
|
self.task_manager.get_task_list(
|
||||||
self.edit.setVisible(True)
|
self.on_task_list_update,
|
||||||
self.tabBar.setDisabled(False)
|
self.on_task_list_ready,
|
||||||
self.progressBar.setVisible(False)
|
|
||||||
|
|
||||||
self.update_tab()
|
|
||||||
|
|
||||||
def longoperationcomplete(self, data):
|
|
||||||
logging.info(
|
|
||||||
"Loading complete in {:.3f}s".format(time.time() - self.loading_start_time)
|
|
||||||
)
|
)
|
||||||
self.endLoad()
|
|
||||||
|
|
||||||
def loadAgenda(self):
|
## Keyboard shortcuts
|
||||||
agenda = self.manager.get_agenda()
|
def open_in_emacs(self, *args):
|
||||||
old = self.results.layout()
|
row = self.task_list.get_selected_row()
|
||||||
|
if row is None:
|
||||||
if old:
|
return
|
||||||
print("Deleting old")
|
item = self.item_rows[row.get_index()]
|
||||||
old.deleteLater()
|
item_id = item.id
|
||||||
|
if item_id is None:
|
||||||
layout = QVBoxLayout()
|
logging.warning("No ID found for item: {}".format(item))
|
||||||
|
return
|
||||||
for item in agenda.with_hour:
|
emacs_client.navigate_emacs_to_id(item_id)
|
||||||
layout.addWidget(self.build_agenda_task_widget(item))
|
|
||||||
|
|
||||||
# if len(agenda.with_hour) > 0 and len(agenda.no_hour) > 0:
|
|
||||||
# layout.addWidget(QSplitter())
|
|
||||||
|
|
||||||
for item in agenda.no_hour:
|
|
||||||
layout.addWidget(self.build_agenda_task_widget(item))
|
|
||||||
|
|
||||||
layout.addStretch()
|
|
||||||
|
|
||||||
frame = QFrame(self.results)
|
|
||||||
frame.setLayout(layout)
|
|
||||||
self.results.setWidget(frame)
|
|
||||||
|
|
||||||
def build_agenda_task_widget(self, item):
|
|
||||||
box = QHBoxLayout()
|
|
||||||
frame = QFrame()
|
|
||||||
frame.setLayout(box)
|
|
||||||
|
|
||||||
state_button = QPushButton(text=f"{item.state or '-'}", maximumWidth=60)
|
|
||||||
if item.is_done:
|
|
||||||
state_button.setFlat(True)
|
|
||||||
box.addWidget(state_button)
|
|
||||||
|
|
||||||
box.addWidget(QLabel(text=f"{item.scheduled.time}", maximumWidth=200))
|
|
||||||
box.addWidget(QLabel(text=f"{item.title}"))
|
|
||||||
|
|
||||||
def on_clicked():
|
|
||||||
state_button.setText("DONE")
|
|
||||||
# state_button.setFlat(True)
|
|
||||||
# item.state = 'DONE'
|
|
||||||
|
|
||||||
if not item.is_done:
|
|
||||||
state_button.clicked.connect(on_clicked)
|
|
||||||
|
|
||||||
return frame
|
|
||||||
|
|
||||||
def build_note_task_widget(self, item):
|
|
||||||
box = QHBoxLayout()
|
|
||||||
frame = QGroupBox()
|
|
||||||
frame.setLayout(box)
|
|
||||||
|
|
||||||
titleButton = QPushButton(text=f"{item.title}")
|
|
||||||
box.addWidget(titleButton)
|
|
||||||
|
|
||||||
def on_clicked():
|
|
||||||
webbrowser.open("org-protocol://org-id?id=" + item.id)
|
|
||||||
|
|
||||||
titleButton.clicked.connect(on_clicked)
|
|
||||||
|
|
||||||
return frame
|
|
||||||
|
|
||||||
def loadNotes(self):
|
|
||||||
query = self.edit.text()
|
|
||||||
notes = self.manager.get_notes(query.split())
|
|
||||||
old = self.results.layout()
|
|
||||||
|
|
||||||
if old:
|
|
||||||
print("Deleting old")
|
|
||||||
old.deleteLater()
|
|
||||||
|
|
||||||
layout = QVBoxLayout()
|
|
||||||
|
|
||||||
for note in notes[:MAX_SEARCH_NODES]:
|
|
||||||
layout.addWidget(self.build_note_task_widget(note))
|
|
||||||
|
|
||||||
layout.addStretch()
|
|
||||||
|
|
||||||
frame = QFrame(self.results)
|
|
||||||
frame.setLayout(layout)
|
|
||||||
self.results.setWidget(frame)
|
|
||||||
|
|
||||||
def loadTasks(self):
|
|
||||||
logging.warning("loadTasks not yet implemented")
|
|
||||||
|
|
||||||
def loadHistory(self):
|
|
||||||
logging.warning("loadHistory not yet implemented")
|
|
||||||
|
|
||||||
|
|
||||||
# Create the Qt Application
|
## Rendering
|
||||||
if __name__ == "__main__":
|
def build_agenda_task_row(self, task):
|
||||||
|
row = Gtk.ListBoxRow()
|
||||||
|
hbox = Gtk.Box()
|
||||||
|
|
||||||
|
state_button = Gtk.Button.new_with_label(task.state or '')
|
||||||
|
state_button.props.css_classes = ('state-button',)
|
||||||
|
state_button.connect("clicked", self.on_status_button_clicked)
|
||||||
|
hbox.append(state_button)
|
||||||
|
|
||||||
|
clock_button = Gtk.Button.new_with_label('C')
|
||||||
|
clock_button.props.css_classes = ('clock-button',)
|
||||||
|
clock_button.connect("clicked", self.on_clock_button_clicked)
|
||||||
|
hbox.append(clock_button)
|
||||||
|
|
||||||
|
# task_name_label = Gtk.Entry(text=task.title, width_chars=max(MIN_TITLE_WIDTH_CHARS, len(task.title)))
|
||||||
|
task_name_label = Gtk.Label()
|
||||||
|
task_name_label.set_text(task.title)
|
||||||
|
task_name_label.props.css_classes = ('task-name',)
|
||||||
|
hbox.append(task_name_label)
|
||||||
|
|
||||||
|
row.set_child(hbox)
|
||||||
|
|
||||||
|
return row
|
||||||
|
|
||||||
|
def on_ready(self):
|
||||||
|
self.loading -= 1
|
||||||
|
if self.loading < 0:
|
||||||
|
self.loading = 0
|
||||||
|
elif self.loading == 0:
|
||||||
|
if self.progress_spinner is not None:
|
||||||
|
self.progress_spinner.stop()
|
||||||
|
|
||||||
|
## Callbacks
|
||||||
|
def on_task_list_update(self, new_rows):
|
||||||
|
for item in new_rows.with_hour:
|
||||||
|
self.task_list.append(self.build_agenda_task_row(item))
|
||||||
|
self.item_rows.append(item)
|
||||||
|
|
||||||
|
for item in new_rows.no_hour:
|
||||||
|
self.task_list.append(self.build_agenda_task_row(item))
|
||||||
|
self.item_rows.append(item)
|
||||||
|
|
||||||
|
def on_task_list_ready(self, success):
|
||||||
|
self.on_ready()
|
||||||
|
|
||||||
|
## Reactions
|
||||||
|
def on_status_button_clicked(self, button):
|
||||||
|
print('Status button clicked: {}'.format(button))
|
||||||
|
|
||||||
|
def on_clock_button_clicked(self, button):
|
||||||
|
print('Clock button clicked: {}'.format(button))
|
||||||
|
|
||||||
|
|
||||||
|
class Application(Gtk.Application):
|
||||||
|
""" Main Aplication class """
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(application_id='com.codigoparallevar.org-convergence',
|
||||||
|
flags=Gio.ApplicationFlags.FLAGS_NONE)
|
||||||
|
self.task_manager = task_manager.TaskManager(DOCS_PATH)
|
||||||
|
|
||||||
|
def do_activate(self):
|
||||||
|
win = self.props.active_window
|
||||||
|
if not win:
|
||||||
|
if os.path.exists(STYLE_FILE_PATH):
|
||||||
|
style_provider = Gtk.CssProvider()
|
||||||
|
Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||||
|
style_provider.load_from_path(STYLE_FILE_PATH)
|
||||||
|
|
||||||
|
win = MainWindow(
|
||||||
|
title=APP_TITLE,
|
||||||
|
application=self,
|
||||||
|
task_manager=self.task_manager,
|
||||||
|
)
|
||||||
|
|
||||||
|
# PinePhone screen is 720x1440 (portrait) but, has 2x pixel density
|
||||||
|
win.set_default_size(360, 720)
|
||||||
|
|
||||||
|
## Load shortcuts
|
||||||
|
# Open in emacs
|
||||||
|
action = Gio.SimpleAction.new("open-in-emacs", None)
|
||||||
|
action.connect("activate", win.open_in_emacs)
|
||||||
|
self.add_action(action)
|
||||||
|
|
||||||
|
self.set_accels_for_action('app.open-in-emacs', ["<Ctrl>e"])
|
||||||
|
|
||||||
|
|
||||||
|
win.present()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
""" Run the main application"""
|
||||||
|
# GObject.threads_init()
|
||||||
logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s")
|
logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s")
|
||||||
|
app = Application()
|
||||||
|
return app.run(sys.argv)
|
||||||
|
|
||||||
app = QApplication(sys.argv)
|
|
||||||
|
|
||||||
dialog = Dialog()
|
if __name__ == '__main__':
|
||||||
sys.exit(dialog.exec_())
|
main()
|
||||||
|
149
org-mode.svg
149
org-mode.svg
@ -1,149 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="144.98"
|
|
||||||
height="160"
|
|
||||||
viewBox="-7.65 -13.389 144.98 160"
|
|
||||||
version="1.1"
|
|
||||||
id="svg28"
|
|
||||||
sodipodi:docname="org-mode.svg"
|
|
||||||
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
|
|
||||||
<metadata
|
|
||||||
id="metadata34">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<defs
|
|
||||||
id="defs32" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="1364"
|
|
||||||
inkscape:window-height="743"
|
|
||||||
id="namedview30"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:zoom="1.066"
|
|
||||||
inkscape:cx="88.23"
|
|
||||||
inkscape:cy="32.52"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="23"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="layer2"
|
|
||||||
inkscape:document-rotation="0" />
|
|
||||||
<g
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
inkscape:label="Backdrop"
|
|
||||||
style="display:inline">
|
|
||||||
<circle
|
|
||||||
style="opacity:1;fill:#1a1a1a;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.0067;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:1;paint-order:fill markers stroke"
|
|
||||||
id="path878"
|
|
||||||
cx="66.35"
|
|
||||||
cy="72.64"
|
|
||||||
r="68.86" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer2"
|
|
||||||
inkscape:label="Base"
|
|
||||||
style="display:inline">
|
|
||||||
<path
|
|
||||||
fill="#77aa99"
|
|
||||||
stroke="#000000"
|
|
||||||
stroke-width="0.5"
|
|
||||||
d="m 6.448,104.3 c 0,0 10.022,36.1 46.552,24.6 25.19,-7.2 43.25,3 46.11,4.1 l 4.39,-2.5 2.9,-2.1 C 105.8,126.9 102.8,114.6 76.98,99.64 61.72,91.32 71.3,76.99 72.39,71.93 c 0,0 12.23,8.55 21.09,-4.52 8.02,2.06 13.32,-1.47 20.22,1.03 4.2,1.83 21.8,0.72 15.3,-9.11 4.1,-2.68 4.5,-1.81 6.6,-5.9 1.1,-4.95 -1.4,-6.01 -2.2,-7.36 -0.2,-3.15 -2.8,-4.37 -6,-2.13 -7.2,-1.29 -14.3,-0.68 -17.8,-0.98 -7.7,-1.07 -14.03,-5.13 -14.03,-5.13 0.93,-1.24 0.48,-3.91 -5.5,-4.1 -3.29,-1.08 -6.75,-3.13 -9.29,-3.16 -2.57,0 -2.91,-2.54 -2.91,-2.54 -1.61,-0.87 -3.93,-4.25 -3.91,-9.44 0,-3.27 -6.09,0.32 -6.09,6.79 0,6.46 -5.82,7.76 -5.82,7.76 0,0 -2.55,-2.28 -2.86,-5.96 -0.58,-3.79 -4.93,-9.56 -7.05,-2.43 -3.23,7.64 -3.49,9.42 -4.12,13.15 -1.31,7.71 -0.34,8.01 -0.34,8.01 L 14.85,69.34 Z"
|
|
||||||
id="path4"
|
|
||||||
transform="translate(0,0.001)"
|
|
||||||
sodipodi:nodetypes="ccccccccscccccccscsscccsccc" />
|
|
||||||
<path
|
|
||||||
fill="#314b49"
|
|
||||||
stroke="#314b49"
|
|
||||||
stroke-width="0.75"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="m 84.11,42.83 c 1.55,-0.56 0.9,-0.41 1.15,-0.58 -2.96,0.58 -9.63,0.62 -14.31,-1.13 0.39,0.23 2.56,0.96 2.84,1.13 0.22,0.71 0.1,1.43 2.93,2.71 2.56,0.79 5.85,0.46 6.84,-0.53 0.11,-1.69 0.12,-1.07 0.55,-1.6 z"
|
|
||||||
id="path6"
|
|
||||||
transform="translate(0,0.001)" />
|
|
||||||
<path
|
|
||||||
fill="#314b49"
|
|
||||||
stroke="#314b49"
|
|
||||||
stroke-width="0.5"
|
|
||||||
d="m 116.5,61.98 c -2.9,-2.09 -4.9,-0.27 -6.5,-0.41 1,-0.65 3.8,-2.07 8.3,-2.07 2.5,0 3.8,2.2 5.5,2.21 1.2,0 4.5,-1.71 5.2,-2.38 -1,0.84 0.4,1.76 -0.5,3.01 -0.3,0.36 -0.9,1.51 -2.7,2.05 -2,0.97 -5.4,1.76 -9.3,-2.41 z"
|
|
||||||
id="path8"
|
|
||||||
transform="translate(0,0.001)" />
|
|
||||||
<path
|
|
||||||
fill="#314b49"
|
|
||||||
d="m 54.93,24.03 c 0,0 -3.35,8 0.31,15.33 3.67,7.34 0.53,-6.83 4.69,-4.16 3.4,0.39 -2.38,-3.21 -2.03,-7.82 -0.18,-2.89 -1.77,-5.19 -2.97,-3.35 z m 64.37,26.39 c 0,1.12 -1.3,2.03 -3,2.03 3.6,-1.12 -0.2,-4.66 -3.1,-2.03 0,-1.12 1.4,-2.03 3.1,-2.03 1.7,0 3,0.91 3,2.03 z"
|
|
||||||
id="path10"
|
|
||||||
transform="translate(0,0.001)" />
|
|
||||||
<path
|
|
||||||
fill="#314b49"
|
|
||||||
d="m 114.2,47.83 c 3.7,-0.23 6.3,0.33 5.5,3.14 0.6,-1.12 1.3,-2.83 -1,-3.51 -1.8,-0.38 -3.3,-0.37 -4.5,0.37 z"
|
|
||||||
id="path12"
|
|
||||||
transform="translate(0,0.001)" />
|
|
||||||
<path
|
|
||||||
fill="#796958"
|
|
||||||
stroke="#000000"
|
|
||||||
stroke-width="0.5"
|
|
||||||
d="m 76.27,30.18 c 0,0 -0.25,7.47 6.72,2.6 3.61,0.1 2.01,-3.86 2.01,-3.86 2.44,-0.68 2.81,-3.85 1.85,-5.71 3.11,0.1 2.61,-4.72 2.18,-6.38 2.44,-0.93 2.77,-3.83 1.77,-6.13 2.93,-0.67 3.02,-4.116 2.77,-6.55 3.02,-0.168 2.6,-5.457 2.6,-6.549 2.6,-1.679 2.02,-3.946 2.42,-6.573 1.61,-3.248 -0.57,-4.178 -2.11,-0.71 -1.65,3.001 -3.77,4.311 -3.75,6.528 0.75,1.259 -5.63,3.106 -3.61,7.052 -1.43,1.763 -4.79,4.03 -3.59,6.732 -0.61,1.33 -4.89,4.43 -3.04,7.37 -4.03,2.69 -3.79,3.34 -2.94,5.79 -2.16,1.38 -4.41,4.14 -3.28,6.39 z"
|
|
||||||
id="path16"
|
|
||||||
transform="translate(0,0.001)" />
|
|
||||||
<path
|
|
||||||
fill="#ffffff"
|
|
||||||
d="m 94.09,-5.087 c 0,0 -0.73,1.324 -0.73,2.133 0,0.809 2.18,0.568 2.93,-0.227 -1.63,0.02 -2.97,0.289 -2.2,-1.906 z m -4.26,6.27 c 0,0 -0.81,1.068 -0.18,2.316 0.39,0.98 2.81,0.962 3.55,0.167 C 91.57,3.69 89.06,3.379 89.83,1.183 Z M 86.7,7.638 c 0,0 -1,1.346 -0.49,2.602 0,0.81 2.83,0.96 3.58,0.16 -1.63,0 -3.65,-0.488 -3.09,-2.762 z M 83.62,14.8 c 0,0 -1.4,1.54 -0.15,2.95 1.44,0.8 3.75,0 4.49,-0.75 -1.63,0 -4.9,0.1 -4.34,-2.2 z m -3,5.72 c 0,0 -1.58,1.42 0,3.31 1.43,0.81 4.57,0.2 5.31,-0.59 -1.63,0 -5.99,-1.35 -5.29,-2.72 z m -3.15,6.74 c 0,0 -1.4,1.54 -0.15,2.95 1.44,0.81 6.04,-0.19 6.78,-0.98 -1.63,0 -7.19,0.31 -6.63,-1.97 z"
|
|
||||||
id="path18"
|
|
||||||
transform="translate(0,0.001)" />
|
|
||||||
<path
|
|
||||||
opacity="0.26"
|
|
||||||
d="m 60.33,57.94 c 0,0 6.76,13.59 17.59,13.99 10.84,0.4 10.84,-2.73 10.84,-2.73 0,0 -15.53,3.04 -28.43,-11.26 z m 3.16,7.69 c 2.27,3.1 4.85,5.22 7.72,6.38 0,0 -7.37,11.11 -3.61,20.02 3.75,8.87 13.12,11.07 23.32,21.27 7.94,7.9 12.98,16.6 12.98,16.6 l -4.52,2.4 c 0,0 -9.14,-8.2 -20.73,-7.5 0,0 -2.68,-9.7 -10.58,-17.6 C 56.83,95.95 51.9,81.32 63.49,65.63 Z"
|
|
||||||
id="path22"
|
|
||||||
transform="translate(0,0.001)"
|
|
||||||
sodipodi:nodetypes="csccccsscccsc" />
|
|
||||||
<path
|
|
||||||
opacity="0.27"
|
|
||||||
fill="#ffffff"
|
|
||||||
d="m 71.28,19.39 c 0.99,-0.96 1.14,-0.65 0.85,0.28 -0.98,2.36 0.35,4.65 0.8,6.21 l -3.87,0.11 c 0.11,-2.27 0.25,-4.79 2.22,-6.6 z"
|
|
||||||
id="path26"
|
|
||||||
transform="translate(0,0.001)" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer4"
|
|
||||||
inkscape:label="Simp"
|
|
||||||
style="display:inline">
|
|
||||||
<path
|
|
||||||
fill="#a04d32"
|
|
||||||
stroke="#000000"
|
|
||||||
stroke-width="0.5"
|
|
||||||
d="m 52.84,54.65 c -1.64,-2.19 -2.62,-4.33 -3.21,-6.27 -1.4,-3.3 -3.43,-8.02 -0.61,-17.24 0,0 0.62,-0.38 0,0 -2.62,1.63 -3.57,6.69 -13.08,9.89 -3.21,1.07 -9.87,7.03 -15.43,7.48 C 10.94,49.28 6.393,61.65 -2.091,77.2 l 0.693,6.03 1.4808,7.02 C 7.319,109.3 3.994,101 9.172,110.4 c 2.138,5.4 2.988,4.2 2.988,4.2 -5.641,-23.37 5.43,5.7 5.78,-16.85 1.74,3.65 36.48,-40.52 34.9,-43.1 z"
|
|
||||||
id="path14-3"
|
|
||||||
style="display:inline"
|
|
||||||
sodipodi:nodetypes="cccsssccccccc" />
|
|
||||||
<path
|
|
||||||
fill="#a04d32"
|
|
||||||
stroke="#000000"
|
|
||||||
stroke-width="0.5"
|
|
||||||
d="m 76.29,30.29 c -0.4,-0.45 -0.5,-0.9 -0.4,-1.72 0,0 0.4,-4.11 -16.29,-0.16 -1.07,0.74 2.4,4.73 2.4,4.73 0,0 0.42,0.21 1.05,-0.42 5.7,4.24 17.09,1.64 13.24,-2.43 z"
|
|
||||||
id="path20-6"
|
|
||||||
style="display:inline"
|
|
||||||
sodipodi:nodetypes="cccccc" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 7.4 KiB |
13
style.css
Normal file
13
style.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#task-list {
|
||||||
|
}
|
||||||
|
|
||||||
|
#task-list .state-button,
|
||||||
|
#task-list .clock-button {
|
||||||
|
margin-right: 1ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
window {
|
||||||
|
}
|
||||||
|
|
||||||
|
#task-list .task-name {
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
@ -9,6 +10,8 @@ from org_rw import OrgDoc, OrgTime
|
|||||||
|
|
||||||
EXTENSIONS = (".org", ".org.txt")
|
EXTENSIONS = (".org", ".org.txt")
|
||||||
|
|
||||||
|
from gi.repository import GObject
|
||||||
|
|
||||||
|
|
||||||
def is_today(ot: OrgTime):
|
def is_today(ot: OrgTime):
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
@ -39,17 +42,22 @@ class Agenda:
|
|||||||
for item in self.no_hour:
|
for item in self.no_hour:
|
||||||
print(item.scheduled.time, item.state, item.title)
|
print(item.scheduled.time, item.state, item.title)
|
||||||
|
|
||||||
|
class TaskManager:
|
||||||
|
docs: List[OrgDoc]
|
||||||
|
threads: List[threading.Thread]
|
||||||
|
|
||||||
class DocumentManager:
|
def __init__(self, docs_path: os.PathLike):
|
||||||
docs: list[OrgDoc]
|
self.docs_path = docs_path
|
||||||
|
self.threads = []
|
||||||
def __init__(self, base_path: os.PathLike):
|
self.docs = None
|
||||||
self.base_path = base_path
|
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
top = os.path.abspath(self.base_path)
|
t0 = datetime.now()
|
||||||
|
top = os.path.abspath(self.docs_path)
|
||||||
|
|
||||||
docs = []
|
docs = []
|
||||||
|
if self.docs is None:
|
||||||
|
self.docs = docs
|
||||||
|
|
||||||
for root, dirs, files in os.walk(top):
|
for root, dirs, files in os.walk(top):
|
||||||
# Prune dirs
|
# Prune dirs
|
||||||
@ -68,8 +76,9 @@ class DocumentManager:
|
|||||||
path = os.path.join(root, name)
|
path = os.path.join(root, name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
doc = org_rw.load(open(path), extra_cautious=True)
|
doc = org_rw.load(open(path), extra_cautious=False)
|
||||||
docs.append(doc)
|
docs.append(doc)
|
||||||
|
yield doc
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
@ -77,10 +86,29 @@ class DocumentManager:
|
|||||||
print(f"== On {path}")
|
print(f"== On {path}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
logging.info("Loaded {} files".format(len(docs)))
|
t1 = datetime.now()
|
||||||
|
logging.info("Loaded {} files in {}s".format(len(docs), t1 - t0))
|
||||||
|
|
||||||
self.docs = docs
|
self.docs = docs
|
||||||
|
|
||||||
|
def get_task_list(self, progress_callback, complete_callback):
|
||||||
|
def aux():
|
||||||
|
if self.docs is None:
|
||||||
|
last_result = None
|
||||||
|
# No docs read yet, load them iteratively
|
||||||
|
for doc in self.load():
|
||||||
|
result = self.get_agenda_from_doc(doc)
|
||||||
|
GObject.idle_add(progress_callback, result)
|
||||||
|
else:
|
||||||
|
result = self.get_agenda()
|
||||||
|
print("Result", result)
|
||||||
|
GObject.idle_add(progress_callback, result)
|
||||||
|
GObject.idle_add(complete_callback, True)
|
||||||
|
|
||||||
|
thread = threading.Thread(target=aux)
|
||||||
|
thread.start()
|
||||||
|
self.threads.append(thread)
|
||||||
|
|
||||||
def get_agenda(self) -> Agenda:
|
def get_agenda(self) -> Agenda:
|
||||||
headline_count = 0
|
headline_count = 0
|
||||||
items_in_agenda = []
|
items_in_agenda = []
|
||||||
@ -123,24 +151,38 @@ class DocumentManager:
|
|||||||
no_hour=other_items,
|
no_hour=other_items,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_notes(self, query) -> List[org_rw.Headline]:
|
def get_agenda_from_doc(self, doc: OrgDoc) -> Agenda:
|
||||||
headline_count = 0
|
headline_count = 0
|
||||||
t0 = datetime.now()
|
items_in_agenda = []
|
||||||
notes = []
|
now = datetime.now()
|
||||||
query = [q.lower() for q in query]
|
|
||||||
|
|
||||||
for doc in self.docs:
|
|
||||||
for hl in doc.getAllHeadlines():
|
for hl in doc.getAllHeadlines():
|
||||||
headline_count += 1
|
headline_count += 1
|
||||||
|
|
||||||
data = "\n".join(hl.get_contents("raw")).lower()
|
if (
|
||||||
if all([q in data for q in query]):
|
hl.scheduled
|
||||||
notes.append(hl)
|
and isinstance(hl.scheduled, OrgTime)
|
||||||
|
and hl.scheduled.time.active
|
||||||
|
):
|
||||||
|
if is_today(hl.scheduled):
|
||||||
|
items_in_agenda.append(hl)
|
||||||
|
elif (hl.scheduled.time.to_datetime() < now) and hl.is_todo:
|
||||||
|
items_in_agenda.append(hl)
|
||||||
|
|
||||||
logging.info(
|
items_with_hour = [
|
||||||
"Filtered {} to {} items in {:.3f}s".format(
|
item
|
||||||
headline_count, len(notes), (datetime.now() - t0).total_seconds()
|
for item in items_in_agenda
|
||||||
)
|
if item.scheduled and is_today(item.scheduled) and item.scheduled.time.hour
|
||||||
|
]
|
||||||
|
other_items = [
|
||||||
|
item
|
||||||
|
for item in items_in_agenda
|
||||||
|
if not (
|
||||||
|
item.scheduled and is_today(item.scheduled) and item.scheduled.time.hour
|
||||||
)
|
)
|
||||||
|
]
|
||||||
|
|
||||||
return notes
|
return Agenda(
|
||||||
|
with_hour=sorted(items_with_hour, key=lambda x: x.scheduled.time),
|
||||||
|
no_hour=other_items,
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user