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
This commit is contained in:
Jann Stute
2025-06-06 21:41:52 +02:00
committed by GitHub
parent 14d1c2b618
commit 3999d5d39b
5 changed files with 97 additions and 14 deletions

View File

@@ -79,7 +79,8 @@ class GlobalSettings(BaseModel):
with open(path, "w") as f:
toml.dump(self.model_dump(), f, encoder=TomlEnumEncoder())
def format_datetime(self, dt: datetime) -> str:
@property
def datetime_format(self) -> str:
date_format = self.date_format
is_24h = self.hour_format
hour_format = "%H:%M:%S" if is_24h else "%I:%M:%S %p"
@@ -94,5 +95,7 @@ class GlobalSettings(BaseModel):
hour_format = hour_format.replace("%H", f"%{zero_padding_symbol}H").replace(
"%I", f"%{zero_padding_symbol}I"
)
return f"{date_format}, {hour_format}"
return datetime.strftime(dt, f"{date_format}, {hour_format}")
def format_datetime(self, dt: datetime) -> str:
return datetime.strftime(dt, self.datetime_format)

View File

@@ -37,7 +37,6 @@ from PySide6.QtGui import (
QMouseEvent,
QPalette,
)
from PySide6.QtUiTools import QUiLoader
from PySide6.QtWidgets import (
QApplication,
QFileDialog,
@@ -298,8 +297,6 @@ class QtDriver(DriverMixin, QObject):
def start(self) -> None:
"""Launch the main Qt window."""
_ = QUiLoader()
if self.settings.theme == Theme.SYSTEM and platform.system() == "Windows":
sys.argv += ["-platform", "windows:darkmode=2"]
self.app = QApplication(sys.argv)

View File

@@ -0,0 +1,82 @@
import typing
from collections.abc import Callable
from datetime import datetime as dt
from typing import cast
from PySide6.QtCore import QDateTime
from PySide6.QtWidgets import QDateTimeEdit, QVBoxLayout
from tagstudio.qt.widgets.panel import PanelWidget
if typing.TYPE_CHECKING:
from tagstudio.qt.ts_qt import QtDriver
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
QDTF2DTF = {
"%d": "dd",
"%m": "MM",
"%y": "yy",
"%H": "HH",
"%M": "mm",
"%S": "ss",
"%Y": "yyyy",
"%I": "hh",
"%p": "AP",
"%x": "MM/dd/yy",
}
def qdtf2dtf(dtf: str) -> str:
out = dtf
for old, new in QDTF2DTF.items():
out = out.replace(old, new)
return out
class DatetimePicker(PanelWidget):
def __init__(self, driver: "QtDriver", datetime: dt | str):
super().__init__()
self.root_layout = QVBoxLayout(self)
self.root_layout.setContentsMargins(6, 0, 6, 0)
if isinstance(datetime, str):
datetime = DatetimePicker.string2dt(datetime)
self.datetime_edit = QDateTimeEdit()
self.datetime_edit.setCalendarPopup(True)
self.datetime_edit.setDateTime(DatetimePicker.dt2qdt(datetime))
# sketchy way to show seconds without showing the day of the week;
# while also still having localisation
self.datetime_edit.setDisplayFormat(qdtf2dtf(driver.settings.datetime_format))
self.initial_value = datetime
self.root_layout.addWidget(self.datetime_edit)
def get_content(self):
return DatetimePicker.dt2string(DatetimePicker.qdt2dt(self.datetime_edit.dateTime()))
def reset(self):
self.datetime_edit.setDateTime(DatetimePicker.dt2qdt(self.initial_value))
def add_callback(self, callback: Callable, event: str = "returnPressed"):
if event == "returnPressed":
pass
else:
raise ValueError(f"unknown event type: {event}")
@staticmethod
def qdt2dt(qdt: QDateTime) -> dt:
return cast(dt, qdt.toPython())
@staticmethod
def dt2qdt(datetime: dt) -> QDateTime:
return QDateTime.fromSecsSinceEpoch(int(datetime.timestamp()))
@staticmethod
def string2dt(datetime_str: str) -> dt:
return dt.strptime(datetime_str, DATETIME_FORMAT)
@staticmethod
def dt2string(datetime: dt) -> str:
return dt.strftime(datetime, DATETIME_FORMAT)

View File

@@ -33,6 +33,7 @@ from tagstudio.core.library.alchemy.fields import (
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.alchemy.models import Entry, Tag
from tagstudio.qt.translations import Translations
from tagstudio.qt.widgets.datetime_picker import DatetimePicker
from tagstudio.qt.widgets.fields import FieldContainer
from tagstudio.qt.widgets.panel import PanelModal
from tagstudio.qt.widgets.tag_box import TagBoxWidget
@@ -390,21 +391,23 @@ class FieldContainers(QWidget):
if not is_mixed:
container.set_title(field.type.name)
container.set_inline(False)
title = f"{field.type.name} (Date)"
try:
title = f"{field.type.name} (Date)"
assert field.value is not None
text = self.driver.settings.format_datetime(
dt.strptime(field.value or "", "%Y-%m-%d %H:%M:%S")
DatetimePicker.string2dt(field.value)
)
except ValueError:
title = f"{field.type.name} (Date) (Unknown Format)"
except (ValueError, AssertionError):
title += " (Unknown Format)"
text = str(field.value)
inner_widget = TextWidget(title, text)
container.set_inner_widget(inner_widget)
modal = PanelModal( # TODO Replace with proper date picker including timezone etc.
EditTextLine(field.value),
title=f"Edit {field.type.name} in 'YYYY-MM-DD HH:MM:SS' format",
modal = PanelModal(
DatetimePicker(self.driver, field.value or dt.now()),
title=f"Edit {field.type.name}",
window_title=f"Edit {field.type.name}",
save_callback=(
lambda content: (

View File

@@ -1,8 +1,6 @@
# Copyright (C) 2025 Travis Abendshien (CyanVoxel).
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from collections.abc import Callable
from PySide6.QtWidgets import QLineEdit, QVBoxLayout