Compare commits

...

No commits in common. "ef4d268225ed9b8de115e3d283d4b6367db37e2c" and "cbd99f25acd070b6c397f9c7d8a98eadb86341e1" have entirely different histories.

5 changed files with 386 additions and 181 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
__pycache__ __pycache__
.idea

View File

@ -1,7 +1,6 @@
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
@ -10,8 +9,6 @@ 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()
@ -42,22 +39,17 @@ 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]
def __init__(self, docs_path: os.PathLike): class DocumentManager:
self.docs_path = docs_path docs: list[OrgDoc]
self.threads = []
self.docs = None def __init__(self, base_path: os.PathLike):
self.base_path = base_path
def load(self): def load(self):
t0 = datetime.now() top = os.path.abspath(self.base_path)
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
@ -76,9 +68,8 @@ class TaskManager:
path = os.path.join(root, name) path = os.path.join(root, name)
try: try:
doc = org_rw.load(open(path), extra_cautious=False) doc = org_rw.load(open(path), extra_cautious=True)
docs.append(doc) docs.append(doc)
yield doc
except Exception as err: except Exception as err:
import traceback import traceback
@ -86,32 +77,10 @@ class TaskManager:
print(f"== On {path}") print(f"== On {path}")
sys.exit(1) sys.exit(1)
t1 = datetime.now() logging.info("Loaded {} files".format(len(docs)))
logging.info("Loaded {} files in {}s".format(len(docs), t1 - t0))
self.docs = docs self.docs = docs
def get_task_list(self, callback):
def aux():
if self.docs is None:
last_result = None
for doc in self.load():
result = self.get_agenda()
if ((last_result is None)
or (len(result.with_hour) != len(last_result.with_hour))
or (len(result.no_hour) != len(last_result.no_hour))):
print("Loaded:", doc._path)
GObject.idle_add(callback, result)
print("Load completed")
else:
result = self.get_agenda()
print("Result", result)
GObject.idle_add(callback, result)
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 = []
@ -153,3 +122,25 @@ class TaskManager:
with_hour=sorted(items_with_hour, key=lambda x: x.scheduled.time), with_hour=sorted(items_with_hour, key=lambda x: x.scheduled.time),
no_hour=other_items, no_hour=other_items,
) )
def get_notes(self, query) -> List[org_rw.Headline]:
headline_count = 0
t0 = datetime.now()
notes = []
query = [q.lower() for q in query]
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)
logging.info(
"Filtered {} to {} items in {:.3f}s".format(
headline_count, len(notes), (datetime.now() - t0).total_seconds()
)
)
return notes

328
main.py Normal file → Executable file
View File

@ -1,157 +1,241 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys
import os
import logging import logging
import threading import os
import sys
import time
import webbrowser
import task_manager 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
APP_TITLE = "Org-mob"
DOCS_PATH = os.environ["ORG_PATH"] DOCS_PATH = os.environ["ORG_PATH"]
STYLE_FILE_PATH = os.path.join( MAX_SEARCH_NODES = 100
os.path.dirname(os.path.abspath(__file__)),
"style.css")
MIN_TITLE_WIDTH_CHARS = 10
import gi class LoadDoneSignal(QObject):
sig = Signal(doc_manager.DocumentManager)
gi.require_version("Gtk", "4.0")
gi.require_version('Polkit', '1.0')
gi.require_version(namespace='Adw', version='1')
from gi.repository import Gtk, Polkit, GObject, Gio, Adw, Gdk class DocumentLoader(QThread):
def __init__(self, manager):
QThread.__init__(self, None)
self.manager = manager
self.signal = LoadDoneSignal()
class MainWindow(Gtk.Window): def run(self):
self.manager.load()
self.signal.sig.emit(self.manager)
## 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
if with_titlebar: class Dialog(QDialog):
self.header_bar = Gtk.HeaderBar() def __init__(self):
# self.header_bar.set_show_close_button(True) super(Dialog, self).__init__()
# self.header_bar.props.title = APP_TITLE
self.set_titlebar(self.header_bar)
self.progress_spinner = Gtk.Spinner() palette = self.palette()
self.progress_spinner.start() palette.setColor(QPalette.Window, QColor(255, 255, 255))
self.header_bar.pack_end(self.progress_spinner) 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)
else: else:
self.header_bar = None self.loadNotes()
self.progress_spinner = None
# self.main_box = Gtk.Box(name='main-box', vexpand=True, hexpand=True) @Slot()
self.scrollview = Gtk.ScrolledWindow(vexpand=True, hexpand=True) 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.task_list = Gtk.ListBox(name='task-list') def startLoad(self):
self.scrollview.set_child(self.task_list) self.edit.setDisabled(True)
self.edit.setVisible(False)
self.tabBar.setDisabled(True)
self.progressBar.setVisible(True)
# self.main_box.props.valign = Gtk.Align.CENTER self.loader = DocumentLoader(self.manager)
# self.main_box.props.halign = Gtk.Align.CENTER self.loader.signal.sig.connect(self.longoperationcomplete)
# self.main_box.append(self.scrollview) self.loading_start_time = time.time()
# self.set_child(self.main_box) self.loader.start()
self.set_child(self.scrollview)
self.loading += 1 def endLoad(self):
self.task_manager.get_task_list(self.on_task_list_ready) self.edit.setDisabled(False)
self.edit.setVisible(True)
self.tabBar.setDisabled(False)
self.progressBar.setVisible(False)
## Rendering self.update_tab()
def build_agenda_task_row(self, task):
row = Gtk.ListBoxRow()
hbox = Gtk.Box()
state_button = Gtk.Button.new_with_label(task.state or '') def longoperationcomplete(self, data):
state_button.props.css_classes = ('state-button',) logging.info(
state_button.connect("clicked", self.on_status_button_clicked) "Loading complete in {:.3f}s".format(time.time() - self.loading_start_time)
hbox.append(state_button) )
self.endLoad()
clock_button = Gtk.Button.new_with_label('C') def loadAgenda(self):
clock_button.props.css_classes = ('clock-button',) agenda = self.manager.get_agenda()
clock_button.connect("clicked", self.on_clock_button_clicked) old = self.results.layout()
hbox.append(clock_button)
# task_name_label = Gtk.Entry(text=task.title, width_chars=max(MIN_TITLE_WIDTH_CHARS, len(task.title))) if old:
task_name_label = Gtk.Label() print("Deleting old")
task_name_label.set_text(task.title) old.deleteLater()
task_name_label.props.css_classes = ('task-name',)
hbox.append(task_name_label)
row.set_child(hbox) layout = QVBoxLayout()
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_ready(self, agenda):
# TODO: Avoid reconstructing the whole list every time
i = 0
child = self.task_list.get_first_child()
while child is not None:
was = child
child = child.get_next_sibling()
i += 1
self.task_list.remove(was)
for item in agenda.with_hour: for item in agenda.with_hour:
self.task_list.append(self.build_agenda_task_row(item)) 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: for item in agenda.no_hour:
self.task_list.append(self.build_agenda_task_row(item)) layout.addWidget(self.build_agenda_task_widget(item))
self.on_ready()
## Reactions layout.addStretch()
def on_status_button_clicked(self, button):
print('Status button clicked: {}'.format(button))
def on_clock_button_clicked(self, button): frame = QFrame(self.results)
print('Clock button clicked: {}'.format(button)) 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")
class Application(Gtk.Application): # Create the Qt Application
""" Main Aplication class """ if __name__ == "__main__":
def __init__(self):
super().__init__(application_id='com.codigoparallevar.gtk4-organizer',
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,
)
win.set_default_size(720, 1024) # PinePhone screen is 720x1440
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)
if __name__ == '__main__': dialog = Dialog()
main() sys.exit(dialog.exec_())

149
org-mode.svg Normal file
View File

@ -0,0 +1,149 @@
<?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>

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -1,20 +0,0 @@
#task-list {
border: 1px solid #cdc7c2;
padding: 1ex;
margin: 1ex;
}
#task-list .state-button,
#task-list .clock-button {
margin-right: 1ex;
}
window {
background-color: #d6d5d4;
}
#task-list .task-name {
border: none;
border-bottom: 1px solid #ccc;
border-radius: 0;
}