Compare commits
3 Commits
36e644a736
...
922cd87fe0
Author | SHA1 | Date | |
---|---|---|---|
922cd87fe0 | |||
730d0e324c | |||
d806cbe6f3 |
2
LICENSE
2
LICENSE
@ -1,4 +1,4 @@
|
|||||||
MIT License Copyright (c) 2020 Faerbit
|
MIT License Copyright (c) 2020 - 2021 Faerbit
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
16
README.md
16
README.md
@ -2,7 +2,21 @@
|
|||||||
|
|
||||||
Simple time tracking app written with Python and Qt5
|
Simple time tracking app written with Python and Qt5
|
||||||
|
|
||||||
Some JIRA integration is available.
|
Some Jira integration is built in.
|
||||||
|
|
||||||
|
## Installation, Qt versions and You
|
||||||
|
Fime supports using both Qt 5 and Qt 6 via their respective PySide packages.
|
||||||
|
|
||||||
|
On Linux I recommend installing the PySide package provided by the distribution.
|
||||||
|
Install `PySide2` for Qt 5 or `PySide6` for Qt 6.
|
||||||
|
|
||||||
|
Run `pip install --user fime` in that case.
|
||||||
|
|
||||||
|
On other OSes or if you decide not to follow my recommendation run either
|
||||||
|
* `pip install --user fime[qt5]` or
|
||||||
|
* `pip install --user fime[qt6]`
|
||||||
|
|
||||||
|
Note that it both versions of PySide are installed Qt 6 will be preferred.
|
||||||
|
|
||||||
## Config file
|
## Config file
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ author_email = faerbit@posteo.net
|
|||||||
description = Simple time tracking app written with Python and Qt5
|
description = Simple time tracking app written with Python and Qt5
|
||||||
long_description = file: README.md
|
long_description = file: README.md
|
||||||
long_description_content_type = text/markdown
|
long_description_content_type = text/markdown
|
||||||
|
license = MIT License
|
||||||
url = https://git.faerb.it/faerbit/fime
|
url = https://git.faerb.it/faerbit/fime
|
||||||
classifiers =
|
classifiers =
|
||||||
Programming Language :: Python :: 3
|
Programming Language :: Python :: 3
|
||||||
@ -12,6 +13,7 @@ classifiers =
|
|||||||
Operating System :: OS Independent
|
Operating System :: OS Independent
|
||||||
|
|
||||||
[options]
|
[options]
|
||||||
|
zip_safe = True
|
||||||
package_dir =
|
package_dir =
|
||||||
= src
|
= src
|
||||||
packages = find:
|
packages = find:
|
||||||
@ -19,11 +21,14 @@ python_requires = >=3.8
|
|||||||
install_requires =
|
install_requires =
|
||||||
requests
|
requests
|
||||||
requests-futures
|
requests-futures
|
||||||
PySide2
|
|
||||||
|
|
||||||
[options.packages.find]
|
[options.packages.find]
|
||||||
where = src
|
where = src
|
||||||
|
|
||||||
|
[options.extras_require]
|
||||||
|
qt5 = PySide2
|
||||||
|
qt6 = PySide6
|
||||||
|
|
||||||
[options.entry_points]
|
[options.entry_points]
|
||||||
gui_scripts =
|
gui_scripts =
|
||||||
fime = fime.main:main
|
fime = fime.main:main
|
||||||
|
@ -22,6 +22,7 @@ class Config:
|
|||||||
config_dir_path = Path(QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.ConfigLocation))
|
config_dir_path = Path(QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.ConfigLocation))
|
||||||
config_path = config_dir_path / "fime" / "fime.conf"
|
config_path = config_dir_path / "fime" / "fime.conf"
|
||||||
if config_path.exists():
|
if config_path.exists():
|
||||||
|
print(f'Reading config file "{config_path}"')
|
||||||
with open(config_path) as f:
|
with open(config_path) as f:
|
||||||
config_text = f.read()
|
config_text = f.read()
|
||||||
config_text = "[DEFAULT]\n" + config_text
|
config_text = "[DEFAULT]\n" + config_text
|
||||||
@ -39,3 +40,7 @@ class Config:
|
|||||||
def jira_token(self):
|
def jira_token(self):
|
||||||
return dequotify(self._configparser["DEFAULT"]["jira_token"])
|
return dequotify(self._configparser["DEFAULT"]["jira_token"])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tray_theme(self):
|
||||||
|
val = dequotify(self._configparser.get("DEFAULT", "tray_theme", fallback="dark"))
|
||||||
|
return val if val in ["light", "dark"] else "dark"
|
||||||
|
@ -176,6 +176,46 @@ ty:1;stroke:none\
|
|||||||
s=\x22ColorScheme-T\
|
s=\x22ColorScheme-T\
|
||||||
ext\x22/>\x0a </g>\x0a</\
|
ext\x22/>\x0a </g>\x0a</\
|
||||||
svg>\x0a\
|
svg>\x0a\
|
||||||
|
\x00\x00\x02`\
|
||||||
|
<\
|
||||||
|
svg xmlns=\x22http:\
|
||||||
|
//www.w3.org/200\
|
||||||
|
0/svg\x22 viewBox=\x22\
|
||||||
|
0 0 32 32\x22>\x0a <d\
|
||||||
|
efs\x0a id=\x22def\
|
||||||
|
s3051\x22>\x0a <sty\
|
||||||
|
le\x0a type=\x22\
|
||||||
|
text/css\x22\x0a \
|
||||||
|
id=\x22current-col\
|
||||||
|
or-scheme\x22>\x0a \
|
||||||
|
.ColorScheme-T\
|
||||||
|
ext {\x0a co\
|
||||||
|
lor:#dcd9d6;\x0a \
|
||||||
|
}\x0a </sty\
|
||||||
|
le>\x0a </defs>\x0a \
|
||||||
|
<path\x0a style\
|
||||||
|
=\x22fill:currentCo\
|
||||||
|
lor;fill-opacity\
|
||||||
|
:1;stroke:none\x22 \
|
||||||
|
\x0a d=\x22M 16 4 \
|
||||||
|
A 12 12 0 0 0 4 \
|
||||||
|
16 A 12 12 0 0 0\
|
||||||
|
16 28 A 12 12 0\
|
||||||
|
0 0 28 16 A 12 \
|
||||||
|
12 0 0 0 16 4 z \
|
||||||
|
M 16 5 A 11 11 0\
|
||||||
|
0 1 27 16 A 11 \
|
||||||
|
11 0 0 1 16 27 A\
|
||||||
|
11 11 0 0 1 5 1\
|
||||||
|
6 A 11 11 0 0 1 \
|
||||||
|
16 5 z M 15 7 L \
|
||||||
|
15 17 L 24 17 L \
|
||||||
|
24 16 L 16 16 L \
|
||||||
|
16 7 L 15 7 z \x22\x0a\
|
||||||
|
id=\x22path78\x22\
|
||||||
|
\x0a class=\x22Co\
|
||||||
|
lorScheme-Text\x22\x0a\
|
||||||
|
/>\x0a</svg>\x0a\
|
||||||
\x00\x00\x01L\
|
\x00\x00\x01L\
|
||||||
<\
|
<\
|
||||||
svg viewBox=\x220 0\
|
svg viewBox=\x220 0\
|
||||||
@ -282,6 +322,11 @@ qt_resource_name = b"\
|
|||||||
\x09\xc6\x14\xa7\
|
\x09\xc6\x14\xa7\
|
||||||
\x00l\
|
\x00l\
|
||||||
\x00i\x00s\x00t\x00-\x00a\x00d\x00d\x00.\x00s\x00v\x00g\
|
\x00i\x00s\x00t\x00-\x00a\x00d\x00d\x00.\x00s\x00v\x00g\
|
||||||
|
\x00\x19\
|
||||||
|
\x0b9\x06g\
|
||||||
|
\x00a\
|
||||||
|
\x00p\x00p\x00o\x00i\x00n\x00t\x00m\x00e\x00n\x00t\x00-\x00n\x00e\x00w\x00-\x00l\
|
||||||
|
\x00i\x00g\x00h\x00t\x00.\x00s\x00v\x00g\
|
||||||
\x00\x0b\
|
\x00\x0b\
|
||||||
\x0c+\x12G\
|
\x0c+\x12G\
|
||||||
\x00g\
|
\x00g\
|
||||||
@ -299,9 +344,9 @@ qt_resource_name = b"\
|
|||||||
qt_resource_struct = b"\
|
qt_resource_struct = b"\
|
||||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
|
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
|
||||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x08\x00\x00\x00\x02\
|
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x02\
|
||||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||||
\x00\x00\x01\x04\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x85\
|
\x00\x00\x01<\x00\x00\x00\x00\x00\x01\x00\x00\x0e\xe9\
|
||||||
\x00\x00\x01}.b\xfd-\
|
\x00\x00\x01}.b\xfd-\
|
||||||
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||||
\x00\x00\x01}.a\xe4+\
|
\x00\x00\x01}.a\xe4+\
|
||||||
@ -309,11 +354,13 @@ qt_resource_struct = b"\
|
|||||||
\x00\x00\x01}.\x1a\xb9\xf9\
|
\x00\x00\x01}.\x1a\xb9\xf9\
|
||||||
\x00\x00\x00\x88\x00\x00\x00\x00\x00\x01\x00\x00\x06\x95\
|
\x00\x00\x00\x88\x00\x00\x00\x00\x00\x01\x00\x00\x06\x95\
|
||||||
\x00\x00\x01}.b $\
|
\x00\x00\x01}.b $\
|
||||||
\x00\x00\x00\xe4\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x1e\
|
\x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x00\x0d\x82\
|
||||||
\x00\x00\x01}.a\xce_\
|
\x00\x00\x01}.a\xce_\
|
||||||
\x00\x00\x00\xaa\x00\x00\x00\x00\x00\x01\x00\x00\x07\xe4\
|
\x00\x00\x00\xaa\x00\x00\x00\x00\x00\x01\x00\x00\x07\xe4\
|
||||||
\x00\x00\x01}.b\xe1.\
|
\x00\x00\x01}.b\xe1.\
|
||||||
\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x01\x00\x00\x09\xce\
|
\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x01\x00\x00\x09\xce\
|
||||||
|
\x00\x00\x01}/\x83\xea~\
|
||||||
|
\x00\x00\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x0c2\
|
||||||
\x00\x00\x01}.b\x05\xfc\
|
\x00\x00\x01}.b\x05\xfc\
|
||||||
\x00\x00\x00d\x00\x00\x00\x00\x00\x01\x00\x00\x05E\
|
\x00\x00\x00d\x00\x00\x00\x00\x00\x01\x00\x00\x05E\
|
||||||
\x00\x00\x01}.b1)\
|
\x00\x00\x01}.b1)\
|
||||||
|
18
src/fime/icons/appointment-new-light.svg
Normal file
18
src/fime/icons/appointment-new-light.svg
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<defs
|
||||||
|
id="defs3051">
|
||||||
|
<style
|
||||||
|
type="text/css"
|
||||||
|
id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#dcd9d6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||||
|
d="M 16 4 A 12 12 0 0 0 4 16 A 12 12 0 0 0 16 28 A 12 12 0 0 0 28 16 A 12 12 0 0 0 16 4 z M 16 5 A 11 11 0 0 1 27 16 A 11 11 0 0 1 16 27 A 11 11 0 0 1 5 16 A 11 11 0 0 1 16 5 z M 15 7 L 15 17 L 24 17 L 24 16 L 16 16 L 16 7 L 15 7 z "
|
||||||
|
id="path78"
|
||||||
|
class="ColorScheme-Text"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 608 B |
@ -2,6 +2,7 @@
|
|||||||
<RCC version="1.0">
|
<RCC version="1.0">
|
||||||
<qresource prefix="icons">
|
<qresource prefix="icons">
|
||||||
<file>appointment-new.svg</file>
|
<file>appointment-new.svg</file>
|
||||||
|
<file>appointment-new-light.svg</file>
|
||||||
<file>arrow-left.svg</file>
|
<file>arrow-left.svg</file>
|
||||||
<file>arrow-right.svg</file>
|
<file>arrow-right.svg</file>
|
||||||
<file>dialog-cancel.svg</file>
|
<file>dialog-cancel.svg</file>
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
rcc resources.qrc -g python -o __init__.py
|
rcc resources.qrc -g python -o __init__.py
|
||||||
|
sed -i 's/^.*import QtCore.*$/try:\n from PySide6 import QtCore\nexcept ImportError:\n from PySide2 import QtCore/g' __init__.py
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from fime.config import Config
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PySide6 import QtGui, QtWidgets
|
from PySide6 import QtGui, QtWidgets
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -8,12 +10,12 @@ from fime.util import get_icon
|
|||||||
|
|
||||||
|
|
||||||
class ImportTask(QtWidgets.QDialog):
|
class ImportTask(QtWidgets.QDialog):
|
||||||
def __init__(self, parent, *args, **kwargs):
|
def __init__(self, config: Config, parent, *args, **kwargs):
|
||||||
super().__init__(parent, *args, **kwargs)
|
super().__init__(parent, *args, **kwargs)
|
||||||
self.setWindowTitle("New Tasks")
|
self.setWindowTitle("New Tasks")
|
||||||
|
|
||||||
self.line_edit = QtWidgets.QLineEdit()
|
self.line_edit = QtWidgets.QLineEdit()
|
||||||
completer = TaskCompleter()
|
completer = TaskCompleter(config)
|
||||||
self.line_edit.setCompleter(completer)
|
self.line_edit.setCompleter(completer)
|
||||||
self.line_edit.textChanged.connect(completer.update_picker)
|
self.line_edit.textChanged.connect(completer.update_picker)
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ import signal
|
|||||||
import sys
|
import sys
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
|
from fime.config import Config
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PySide6 import QtCore, QtWidgets
|
from PySide6 import QtCore, QtWidgets
|
||||||
PYSIDE_6 = True
|
PYSIDE_6 = True
|
||||||
@ -31,11 +33,15 @@ class App:
|
|||||||
self.log = Log(data)
|
self.log = Log(data)
|
||||||
self._active_task = self.log.last_log() or "Nothing"
|
self._active_task = self.log.last_log() or "Nothing"
|
||||||
|
|
||||||
|
self.config = Config()
|
||||||
|
if self.config.tray_theme == "light":
|
||||||
|
icon = get_icon("appointment-new-light")
|
||||||
|
else:
|
||||||
icon = get_icon("appointment-new")
|
icon = get_icon("appointment-new")
|
||||||
|
|
||||||
self.menu = QtWidgets.QMenu(None)
|
self.menu = QtWidgets.QMenu(None)
|
||||||
|
|
||||||
self.import_task = ImportTask(None)
|
self.import_task = ImportTask(self.config, None)
|
||||||
self.import_task.accepted.connect(self.new_task_imported)
|
self.import_task.accepted.connect(self.new_task_imported)
|
||||||
|
|
||||||
self.taskEdit = TaskEdit(None)
|
self.taskEdit = TaskEdit(None)
|
||||||
|
@ -15,12 +15,12 @@ from fime.config import Config
|
|||||||
|
|
||||||
|
|
||||||
class TaskCompleter(QtWidgets.QCompleter):
|
class TaskCompleter(QtWidgets.QCompleter):
|
||||||
def __init__(self, parent=None, *args, **kwargs):
|
def __init__(self, config: Config, parent=None, *args, **kwargs):
|
||||||
super().__init__([], parent, *args, **kwargs)
|
super().__init__([], parent, *args, **kwargs)
|
||||||
self.setFilterMode(QtCore.Qt.MatchFlag.MatchContains)
|
self.setFilterMode(QtCore.Qt.MatchFlag.MatchContains)
|
||||||
self.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
self.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||||
self.session = FuturesSession()
|
self.session = FuturesSession()
|
||||||
self.config = Config()
|
self.config = config
|
||||||
self.picker_url = os.path.join(self.config.jira_url, "rest/api/2/issue/picker")
|
self.picker_url = os.path.join(self.config.jira_url, "rest/api/2/issue/picker")
|
||||||
self.search_url = os.path.join(self.config.jira_url, "rest/api/2/search")
|
self.search_url = os.path.join(self.config.jira_url, "rest/api/2/search")
|
||||||
self.text = ""
|
self.text = ""
|
||||||
|
@ -13,5 +13,6 @@ def get_screen_height(qobject):
|
|||||||
|
|
||||||
|
|
||||||
def get_icon(icon_name):
|
def get_icon(icon_name):
|
||||||
|
theme_name = icon_name.replace("-light", "") # respect system theme
|
||||||
fallback = QtGui.QIcon(f":/icons/{icon_name}").pixmap(256, 256)
|
fallback = QtGui.QIcon(f":/icons/{icon_name}").pixmap(256, 256)
|
||||||
return QtGui.QIcon.fromTheme(icon_name, fallback)
|
return QtGui.QIcon.fromTheme(theme_name, fallback)
|
||||||
|
Loading…
Reference in New Issue
Block a user