Compare commits
No commits in common. "qt" and "gtk4" have entirely different histories.
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1 @@
|
||||
__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
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import webbrowser
|
||||
import os
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from PySide2.QtCore import QObject, QThread, Signal, Slot
|
||||
|
||||
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
|
||||
import task_manager
|
||||
import emacs_client
|
||||
|
||||
APP_TITLE = "Org-Convergence"
|
||||
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):
|
||||
sig = Signal(doc_manager.DocumentManager)
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "4.0")
|
||||
gi.require_version('Polkit', '1.0')
|
||||
gi.require_version(namespace='Adw', version='1')
|
||||
|
||||
class DocumentLoader(QThread):
|
||||
def __init__(self, manager):
|
||||
QThread.__init__(self, None)
|
||||
self.manager = manager
|
||||
self.signal = LoadDoneSignal()
|
||||
from gi.repository import Gtk, Polkit, GObject, Gio, Adw, Gdk
|
||||
|
||||
def run(self):
|
||||
self.manager.load()
|
||||
self.signal.sig.emit(self.manager)
|
||||
class MainWindow(Gtk.Window):
|
||||
__gsignals__ = {
|
||||
"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):
|
||||
def __init__(self):
|
||||
super(Dialog, self).__init__()
|
||||
if with_titlebar:
|
||||
self.header_bar = Gtk.HeaderBar()
|
||||
# self.header_bar.set_show_close_button(True)
|
||||
# self.header_bar.props.title = APP_TITLE
|
||||
self.set_titlebar(self.header_bar)
|
||||
|
||||
palette = self.palette()
|
||||
palette.setColor(QPalette.Window, QColor(255, 255, 255))
|
||||
self.setPalette(palette)
|
||||
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)
|
||||
self.progress_spinner = Gtk.Spinner()
|
||||
self.progress_spinner.start()
|
||||
self.header_bar.pack_end(self.progress_spinner)
|
||||
else:
|
||||
self.loadNotes()
|
||||
self.header_bar = None
|
||||
self.progress_spinner = None
|
||||
|
||||
@Slot()
|
||||
def update_tab(self):
|
||||
tabIndex = self.tabBar.currentIndex()
|
||||
if tabIndex == 0:
|
||||
self.loadAgenda()
|
||||
elif tabIndex == 1:
|
||||
self.loadNotes()
|
||||
elif tabIndex == 2:
|
||||
self.loadTasks()
|
||||
elif tabIndex == 3:
|
||||
self.loadHistory()
|
||||
# self.main_box = Gtk.Box(name='main-box', vexpand=True, hexpand=True)
|
||||
self.scrollview = Gtk.ScrolledWindow(vexpand=True, hexpand=True)
|
||||
|
||||
def startLoad(self):
|
||||
self.edit.setDisabled(True)
|
||||
self.edit.setVisible(False)
|
||||
self.tabBar.setDisabled(True)
|
||||
self.progressBar.setVisible(True)
|
||||
self.task_list = Gtk.ListBox(name='task-list')
|
||||
self.scrollview.set_child(self.task_list)
|
||||
self.item_rows = []
|
||||
|
||||
self.loader = DocumentLoader(self.manager)
|
||||
self.loader.signal.sig.connect(self.longoperationcomplete)
|
||||
self.loading_start_time = time.time()
|
||||
self.loader.start()
|
||||
# self.main_box.props.valign = Gtk.Align.CENTER
|
||||
# self.main_box.props.halign = Gtk.Align.CENTER
|
||||
# self.main_box.append(self.scrollview)
|
||||
# self.set_child(self.main_box)
|
||||
self.set_child(self.scrollview)
|
||||
|
||||
def endLoad(self):
|
||||
self.edit.setDisabled(False)
|
||||
self.edit.setVisible(True)
|
||||
self.tabBar.setDisabled(False)
|
||||
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.loading += 1
|
||||
self.task_manager.get_task_list(
|
||||
self.on_task_list_update,
|
||||
self.on_task_list_ready,
|
||||
)
|
||||
self.endLoad()
|
||||
|
||||
def loadAgenda(self):
|
||||
agenda = self.manager.get_agenda()
|
||||
old = self.results.layout()
|
||||
|
||||
if old:
|
||||
print("Deleting old")
|
||||
old.deleteLater()
|
||||
|
||||
layout = QVBoxLayout()
|
||||
|
||||
for item in agenda.with_hour:
|
||||
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")
|
||||
## Keyboard shortcuts
|
||||
def open_in_emacs(self, *args):
|
||||
row = self.task_list.get_selected_row()
|
||||
if row is None:
|
||||
return
|
||||
item = self.item_rows[row.get_index()]
|
||||
item_id = item.id
|
||||
if item_id is None:
|
||||
logging.warning("No ID found for item: {}".format(item))
|
||||
return
|
||||
emacs_client.navigate_emacs_to_id(item_id)
|
||||
|
||||
|
||||
# Create the Qt Application
|
||||
if __name__ == "__main__":
|
||||
## Rendering
|
||||
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")
|
||||
app = Application()
|
||||
return app.run(sys.argv)
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
dialog = Dialog()
|
||||
sys.exit(dialog.exec_())
|
||||
if __name__ == '__main__':
|
||||
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 os
|
||||
import sys
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
@ -9,6 +10,8 @@ from org_rw import OrgDoc, OrgTime
|
||||
|
||||
EXTENSIONS = (".org", ".org.txt")
|
||||
|
||||
from gi.repository import GObject
|
||||
|
||||
|
||||
def is_today(ot: OrgTime):
|
||||
now = datetime.now()
|
||||
@ -39,17 +42,22 @@ class Agenda:
|
||||
for item in self.no_hour:
|
||||
print(item.scheduled.time, item.state, item.title)
|
||||
|
||||
class TaskManager:
|
||||
docs: List[OrgDoc]
|
||||
threads: List[threading.Thread]
|
||||
|
||||
class DocumentManager:
|
||||
docs: list[OrgDoc]
|
||||
|
||||
def __init__(self, base_path: os.PathLike):
|
||||
self.base_path = base_path
|
||||
def __init__(self, docs_path: os.PathLike):
|
||||
self.docs_path = docs_path
|
||||
self.threads = []
|
||||
self.docs = None
|
||||
|
||||
def load(self):
|
||||
top = os.path.abspath(self.base_path)
|
||||
t0 = datetime.now()
|
||||
top = os.path.abspath(self.docs_path)
|
||||
|
||||
docs = []
|
||||
if self.docs is None:
|
||||
self.docs = docs
|
||||
|
||||
for root, dirs, files in os.walk(top):
|
||||
# Prune dirs
|
||||
@ -68,8 +76,9 @@ class DocumentManager:
|
||||
path = os.path.join(root, name)
|
||||
|
||||
try:
|
||||
doc = org_rw.load(open(path), extra_cautious=True)
|
||||
doc = org_rw.load(open(path), extra_cautious=False)
|
||||
docs.append(doc)
|
||||
yield doc
|
||||
except Exception as err:
|
||||
import traceback
|
||||
|
||||
@ -77,10 +86,29 @@ class DocumentManager:
|
||||
print(f"== On {path}")
|
||||
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
|
||||
|
||||
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:
|
||||
headline_count = 0
|
||||
items_in_agenda = []
|
||||
@ -123,24 +151,38 @@ class DocumentManager:
|
||||
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
|
||||
t0 = datetime.now()
|
||||
notes = []
|
||||
query = [q.lower() for q in query]
|
||||
items_in_agenda = []
|
||||
now = datetime.now()
|
||||
|
||||
for doc in self.docs:
|
||||
for hl in doc.getAllHeadlines():
|
||||
headline_count += 1
|
||||
|
||||
data = "\n".join(hl.get_contents("raw")).lower()
|
||||
if all([q in data for q in query]):
|
||||
notes.append(hl)
|
||||
if (
|
||||
hl.scheduled
|
||||
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(
|
||||
"Filtered {} to {} items in {:.3f}s".format(
|
||||
headline_count, len(notes), (datetime.now() - t0).total_seconds()
|
||||
)
|
||||
items_with_hour = [
|
||||
item
|
||||
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