Skip to content

Commit 3999d5d

Browse files
feat(ui): improved datetime field modal using QDateTimeEdit (#946)
* feat: custom modal making use of QDateTimeEdit * fix: add back license notice * refactor: remove unnecessary line * feat: use date and hour format from settings for date time picker
1 parent 14d1c2b commit 3999d5d

File tree

5 files changed

+97
-14
lines changed

5 files changed

+97
-14
lines changed

src/tagstudio/core/global_settings.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ def save(self, path: Path | None = None) -> None:
7979
with open(path, "w") as f:
8080
toml.dump(self.model_dump(), f, encoder=TomlEnumEncoder())
8181

82-
def format_datetime(self, dt: datetime) -> str:
82+
@property
83+
def datetime_format(self) -> str:
8384
date_format = self.date_format
8485
is_24h = self.hour_format
8586
hour_format = "%H:%M:%S" if is_24h else "%I:%M:%S %p"
@@ -94,5 +95,7 @@ def format_datetime(self, dt: datetime) -> str:
9495
hour_format = hour_format.replace("%H", f"%{zero_padding_symbol}H").replace(
9596
"%I", f"%{zero_padding_symbol}I"
9697
)
98+
return f"{date_format}, {hour_format}"
9799

98-
return datetime.strftime(dt, f"{date_format}, {hour_format}")
100+
def format_datetime(self, dt: datetime) -> str:
101+
return datetime.strftime(dt, self.datetime_format)

src/tagstudio/qt/ts_qt.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
QMouseEvent,
3838
QPalette,
3939
)
40-
from PySide6.QtUiTools import QUiLoader
4140
from PySide6.QtWidgets import (
4241
QApplication,
4342
QFileDialog,
@@ -298,8 +297,6 @@ def setup_signals(self):
298297

299298
def start(self) -> None:
300299
"""Launch the main Qt window."""
301-
_ = QUiLoader()
302-
303300
if self.settings.theme == Theme.SYSTEM and platform.system() == "Windows":
304301
sys.argv += ["-platform", "windows:darkmode=2"]
305302
self.app = QApplication(sys.argv)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import typing
2+
from collections.abc import Callable
3+
from datetime import datetime as dt
4+
from typing import cast
5+
6+
from PySide6.QtCore import QDateTime
7+
from PySide6.QtWidgets import QDateTimeEdit, QVBoxLayout
8+
9+
from tagstudio.qt.widgets.panel import PanelWidget
10+
11+
if typing.TYPE_CHECKING:
12+
from tagstudio.qt.ts_qt import QtDriver
13+
14+
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
15+
16+
17+
QDTF2DTF = {
18+
"%d": "dd",
19+
"%m": "MM",
20+
"%y": "yy",
21+
"%H": "HH",
22+
"%M": "mm",
23+
"%S": "ss",
24+
"%Y": "yyyy",
25+
"%I": "hh",
26+
"%p": "AP",
27+
"%x": "MM/dd/yy",
28+
}
29+
30+
31+
def qdtf2dtf(dtf: str) -> str:
32+
out = dtf
33+
for old, new in QDTF2DTF.items():
34+
out = out.replace(old, new)
35+
return out
36+
37+
38+
class DatetimePicker(PanelWidget):
39+
def __init__(self, driver: "QtDriver", datetime: dt | str):
40+
super().__init__()
41+
self.root_layout = QVBoxLayout(self)
42+
self.root_layout.setContentsMargins(6, 0, 6, 0)
43+
44+
if isinstance(datetime, str):
45+
datetime = DatetimePicker.string2dt(datetime)
46+
self.datetime_edit = QDateTimeEdit()
47+
self.datetime_edit.setCalendarPopup(True)
48+
self.datetime_edit.setDateTime(DatetimePicker.dt2qdt(datetime))
49+
# sketchy way to show seconds without showing the day of the week;
50+
# while also still having localisation
51+
self.datetime_edit.setDisplayFormat(qdtf2dtf(driver.settings.datetime_format))
52+
53+
self.initial_value = datetime
54+
self.root_layout.addWidget(self.datetime_edit)
55+
56+
def get_content(self):
57+
return DatetimePicker.dt2string(DatetimePicker.qdt2dt(self.datetime_edit.dateTime()))
58+
59+
def reset(self):
60+
self.datetime_edit.setDateTime(DatetimePicker.dt2qdt(self.initial_value))
61+
62+
def add_callback(self, callback: Callable, event: str = "returnPressed"):
63+
if event == "returnPressed":
64+
pass
65+
else:
66+
raise ValueError(f"unknown event type: {event}")
67+
68+
@staticmethod
69+
def qdt2dt(qdt: QDateTime) -> dt:
70+
return cast(dt, qdt.toPython())
71+
72+
@staticmethod
73+
def dt2qdt(datetime: dt) -> QDateTime:
74+
return QDateTime.fromSecsSinceEpoch(int(datetime.timestamp()))
75+
76+
@staticmethod
77+
def string2dt(datetime_str: str) -> dt:
78+
return dt.strptime(datetime_str, DATETIME_FORMAT)
79+
80+
@staticmethod
81+
def dt2string(datetime: dt) -> str:
82+
return dt.strftime(datetime, DATETIME_FORMAT)

src/tagstudio/qt/widgets/preview/field_containers.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from tagstudio.core.library.alchemy.library import Library
3434
from tagstudio.core.library.alchemy.models import Entry, Tag
3535
from tagstudio.qt.translations import Translations
36+
from tagstudio.qt.widgets.datetime_picker import DatetimePicker
3637
from tagstudio.qt.widgets.fields import FieldContainer
3738
from tagstudio.qt.widgets.panel import PanelModal
3839
from tagstudio.qt.widgets.tag_box import TagBoxWidget
@@ -390,21 +391,23 @@ def write_container(self, index: int, field: BaseField, is_mixed: bool = False):
390391
if not is_mixed:
391392
container.set_title(field.type.name)
392393
container.set_inline(False)
394+
395+
title = f"{field.type.name} (Date)"
393396
try:
394-
title = f"{field.type.name} (Date)"
397+
assert field.value is not None
395398
text = self.driver.settings.format_datetime(
396-
dt.strptime(field.value or "", "%Y-%m-%d %H:%M:%S")
399+
DatetimePicker.string2dt(field.value)
397400
)
398-
except ValueError:
399-
title = f"{field.type.name} (Date) (Unknown Format)"
401+
except (ValueError, AssertionError):
402+
title += " (Unknown Format)"
400403
text = str(field.value)
401404

402405
inner_widget = TextWidget(title, text)
403406
container.set_inner_widget(inner_widget)
404407

405-
modal = PanelModal( # TODO Replace with proper date picker including timezone etc.
406-
EditTextLine(field.value),
407-
title=f"Edit {field.type.name} in 'YYYY-MM-DD HH:MM:SS' format",
408+
modal = PanelModal(
409+
DatetimePicker(self.driver, field.value or dt.now()),
410+
title=f"Edit {field.type.name}",
408411
window_title=f"Edit {field.type.name}",
409412
save_callback=(
410413
lambda content: (

src/tagstudio/qt/widgets/text_line_edit.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
# Copyright (C) 2025 Travis Abendshien (CyanVoxel).
21
# Licensed under the GPL-3.0 License.
32
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
43

5-
64
from collections.abc import Callable
75

86
from PySide6.QtWidgets import QLineEdit, QVBoxLayout

0 commit comments

Comments
 (0)