Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Qt connection handler for date/time widgets #37

Merged
merged 4 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion echo/qt/autoconnect.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
connect_text,
connect_button,
connect_combo_selection,
connect_list_selection)
connect_list_selection,
connect_datetime)

__all__ = ['autoconnect_callbacks_to_qt']

Expand All @@ -20,6 +21,7 @@
HANDLERS['button'] = connect_button
HANDLERS['combosel'] = connect_combo_selection
HANDLERS['listsel'] = connect_list_selection
HANDLERS['datetime'] = connect_datetime


def autoconnect_callbacks_to_qt(instance, widget, connect_kwargs={}):
Expand Down Expand Up @@ -64,6 +66,10 @@ def autoconnect_callbacks_to_qt(instance, widget, connect_kwargs={}):
* ``combotext``: the callback property is linked to a QComboBox based on
the label of the entries in the combo box.

* ``datetime``: the callback property is linked to a QDateTimeEdit.
Note that this connection will also work for the more specific QDateEdit
and QTimeEdit widgets, as they are subclasses of QDateTimeEdit.

Applications can also define additional mappings between type and
auto-linking. To do this, simply add a new entry to the ``HANDLERS`` object::

Expand Down
53 changes: 51 additions & 2 deletions echo/qt/connect.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# The functions in this module are used to connect callback properties to Qt
# widgets.

from datetime import datetime
import math

from qtpy import QtGui, QtWidgets
from qtpy.QtCore import Qt
from qtpy.QtCore import Qt, QDateTime

import numpy as np

Expand All @@ -13,7 +14,7 @@

__all__ = ['connect_checkable_button', 'connect_text', 'connect_combo_data',
'connect_combo_text', 'connect_float_text', 'connect_value',
'connect_combo_selection', 'connect_list_selection',
'connect_combo_selection', 'connect_list_selection', 'connect_datetime',
'BaseConnection']


Expand Down Expand Up @@ -580,3 +581,51 @@
def disconnect(self):
remove_callback(self._instance, self._prop, self.update_widget)
self._widget.itemSelectionChanged.disconnect(self.update_prop)


class connect_datetime(BaseConnection):
"""
Connect a CallbackProperty to a QDateTimeEdit.
Since QDateEdit and QTimeEdit are subclasses of QDateTimeEdit, this connection
will work for those more specific widgets as well.
"""

def __init__(self, instance, prop, widget):
super(connect_datetime, self).__init__(instance, prop, widget)
self.connect()

def update_prop(self):
qdatetime = self._widget.dateTime().toUTC()
value = np.datetime64(qdatetime.toPython())
setattr(self._instance, self._prop, value)

def update_widget(self, value):
if value is None:
value = np.datetime64('now')
dt = value.item()

# datetime64::item can return a date
# If this happens, we use midnight as our time
# (datetime is a subclass of date so we need to check this way)
if not isinstance(dt, datetime):
date = dt
time = datetime.min.time()

Check warning on line 612 in echo/qt/connect.py

View check run for this annotation

Codecov / codecov/patch

echo/qt/connect.py#L611-L612

Added lines #L611 - L612 were not covered by tests
else:
date = dt.date()
time = dt.time()

qdatetime = QDateTime(date, time, Qt.TimeSpec.UTC).toTimeSpec(self._widget.timeSpec())
self._widget.setDateTime(qdatetime)

def connect(self):
add_callback(self._instance, self._prop, self.update_widget)
self._widget.dateTimeChanged.connect(self.update_prop)
self._widget.dateChanged.connect(self.update_prop)
self._widget.timeChanged.connect(self.update_prop)
self.update_widget(getattr(self._instance, self._prop))

def disconnect(self):
remove_callback(self._instance, self._prop, self.update_widget)
self._widget.dateTimeChanged.disconnect(self.update_prop)
self._widget.dateChanged.disconnect(self.update_prop)
self._widget.timeChanged.disconnect(self.update_prop)

Check warning on line 631 in echo/qt/connect.py

View check run for this annotation

Codecov / codecov/patch

echo/qt/connect.py#L628-L631

Added lines #L628 - L631 were not covered by tests
19 changes: 19 additions & 0 deletions echo/qt/tests/test_autoconnect.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from datetime import datetime

from numpy import datetime64
from qtpy import QtWidgets, QtGui
from qtpy.QtCore import QDateTime, Qt

from echo.qt.autoconnect import autoconnect_callbacks_to_qt
from echo import CallbackProperty
Expand Down Expand Up @@ -47,13 +51,18 @@ def __init__(self, parent=None):
self.bool_log.setCheckable(True)
self.layout.addWidget(self.bool_log)

self.datetime_dob = QtWidgets.QDateTimeEdit(objectName="datetime_dob")
self.datetime_dob.setTimeSpec(Qt.TimeSpec.UTC)
self.layout.addWidget(self.datetime_dob)

class Person(object):
planet = CallbackProperty()
dataset = CallbackProperty()
name = CallbackProperty()
age = CallbackProperty()
height = CallbackProperty()
log = CallbackProperty()
dob = CallbackProperty()

widget = CustomWidget()

Expand Down Expand Up @@ -86,6 +95,11 @@ class Person(object):
widget.bool_log.setChecked(True)
assert person.log

dob = datetime(2000, 1, 1, 11, 52, 6)
qdob = QDateTime(dob.date(), dob.time(), Qt.TimeSpec.UTC)
widget.datetime_dob.setDateTime(qdob)
assert person.dob.item() == dob

# Check that modifying the callback properties updates the Qt widget

person.planet = 'mars'
Expand All @@ -106,6 +120,11 @@ class Person(object):
person.log = False
assert not widget.bool_log.isChecked()

dob = datetime(2010, 2, 3, 16, 22, 11)
person.dob = datetime64(dob)
qdatetime = widget.datetime_dob.dateTime().toUTC()
assert qdatetime.toPython() == person.dob.item()


def test_autoconnect_with_empty_qt_item():

Expand Down
29 changes: 28 additions & 1 deletion echo/qt/tests/test_connect.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from datetime import datetime

import pytest
from numpy import datetime64
from unittest.mock import MagicMock

from qtpy import QtWidgets
from qtpy.QtCore import QDateTime, Qt

from echo import CallbackProperty
from echo.qt.connect import (connect_checkable_button, connect_text,
from echo.qt.connect import (connect_checkable_button, connect_datetime, connect_text,
connect_combo_data, connect_combo_text,
connect_float_text, connect_value, connect_button,
UserDataWrapper)
Expand Down Expand Up @@ -211,3 +215,26 @@ class Example(object):
assert e.a.call_count == 0
button.clicked.emit()
assert e.a.call_count == 1


def test_connect_datetime():

class Example(object):
t = CallbackProperty()

e = Example()

widget = QtWidgets.QDateTimeEdit()
widget.setTimeSpec(Qt.TimeSpec.UTC)

conn = connect_datetime(e, 't', widget) # noqa

dt = datetime(2010, 5, 7, 13, 15, 3)
qdt = QDateTime(dt.date(), dt.time(), Qt.TimeSpec.UTC)
widget.setDateTime(qdt)
widget.dateTimeChanged.emit(qdt)
assert dt == e.t.item()

dt = datetime(2020, 3, 4, 7, 48, 16)
e.t = datetime64(dt)
assert widget.dateTime().toUTC().toPython() == dt
Loading