From 63340e329f52d7ed866f318cf87c93630117f85f Mon Sep 17 00:00:00 2001 From: Aleksandra Date: Wed, 17 Jun 2020 11:52:48 +0200 Subject: [PATCH] OWSelectRows: Connect time in calendar; Add tests --- Orange/widgets/data/owselectrows.py | 79 +++++++++++-------- .../widgets/data/tests/test_owselectrows.py | 55 +++++++++++-- 2 files changed, 95 insertions(+), 39 deletions(-) diff --git a/Orange/widgets/data/owselectrows.py b/Orange/widgets/data/owselectrows.py index eac7f03f7ec..6adc29babdd 100644 --- a/Orange/widgets/data/owselectrows.py +++ b/Orange/widgets/data/owselectrows.py @@ -145,13 +145,14 @@ def _plural(s): class CalendarWidgetWithTime(QCalendarWidget): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - timeedit = QDateTimeEdit(displayFormat="hh:mm:ss") + self.timeedit = QDateTimeEdit(displayFormat="hh:mm:ss") + self.timeedit.setTime(self.parent().min_datetime.time()) self._time_layout = sublay = QHBoxLayout() sublay.setContentsMargins(6, 6, 6, 6) sublay.addStretch(1) sublay.addWidget(QLabel("Time: ")) - sublay.addWidget(timeedit) + sublay.addWidget(self.timeedit) sublay.addStretch(1) self.layout().addLayout(sublay) @@ -529,30 +530,42 @@ def add_numeric(contents): box.controls.append(add_textual(lc[1])) elif vtype == 4: # time: datetime_format = (var.have_date, var.have_time) - widget = DateTimeWidget(self, var_idx, datetime_format) - widget.set_datetime(lc[0]) - box.controls = [widget] - box.layout().addWidget(widget) - self._box = [] + column = self.data[:, var_idx] + w = DateTimeWidget(self, column, datetime_format) + w.set_datetime(lc[0]) + box.controls = [w] + box.layout().addWidget(w) + w.dateTimeChanged.connect(self._datetime_changed) + self._box = box if oper > 5: gui.widgetLabel(box, " and ") - widget_ = DateTimeWidget(self, var_idx, datetime_format) - widget_.set_datetime(lc[1]) - box.layout().addWidget(widget_) - box.controls.append(widget_) + w_ = DateTimeWidget(self, column, datetime_format) + w_.set_datetime(lc[1]) + box.layout().addWidget(w_) + box.controls.append(w_) + self._invalidate_datetime() + w_.dateTimeChanged.connect(self._datetime_changed) self._box = box - self._invalidate_dates() else: box.controls = [] if not adding_all: self.conditions_changed() - def _invalidate_dates(self): - if getattr(self, "_box", None): - widget, widget_ = self._box.controls[0], self._box.controls[1] - if widget.dateTime() > widget_.dateTime(): - widget_.setDateTime(widget.dateTime()) - widget_.dateTimeChanged.connect(self.conditions_changed) + def _invalidate_datetime(self): + w = self._box.controls[0] + if len(self._box.controls) > 1: + w_ = self._box.controls[1] + if w.dateTime() > w_.dateTime(): + w_.setDateTime(w.dateTime()) + if w.format == (1, 1): + w.calendarWidget.timeedit.setTime(w.time()) + w_.calendarWidget.timeedit.setTime(w_.time()) + elif w.format == (1, 1): + w.calendarWidget.timeedit.setTime(w.time()) + + def _datetime_changed(self): + self.conditions_changed() + self._invalidate_datetime() @Inputs.data def set_data(self, data): @@ -869,54 +882,56 @@ def resizeEvent(self, QResizeEvent): class DateTimeWidget(QDateTimeEdit): - def __init__(self, parent, col_idx, datetime_format): + def __init__(self, parent, column, datetime_format): QDateTimeEdit.__init__(self, parent) - self.parent = parent self.format = datetime_format self.have_date, self.have_time = datetime_format[0], datetime_format[1] - self.column = parent.data[:, col_idx] - self.set_format() + self.set_format(column) self.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) - self.dateTimeChanged.connect(parent._invalidate_dates) - def set_format(self): + def set_format(self, column): str_format = Qt.ISODate if self.have_date and self.have_time: self.setDisplayFormat("yyyy-MM-dd hh:mm:ss") - self.setCalendarPopup(True) - self._calendarWidget = CalendarWidgetWithTime(self) - self.setCalendarWidget(self._calendarWidget) c_format = "%Y-%m-%d %H:%M:%S" - min_datetime, max_datetime = self.find_range(self.column, c_format) + min_datetime, max_datetime = self.find_range(column, c_format) self.min_datetime = QDateTime.fromString(min_datetime, str_format) self.max_datetime = QDateTime.fromString(max_datetime, str_format) + self.setCalendarPopup(True) + self.calendarWidget = CalendarWidgetWithTime(self) + self.calendarWidget.timeedit.timeChanged.connect( + self.set_datetime) + self.setCalendarWidget(self.calendarWidget) self.setDateTimeRange(self.min_datetime, self.max_datetime) elif self.have_date and not self.have_time: self.setDisplayFormat("yyyy-MM-dd") self.setCalendarPopup(True) - min_datetime, max_datetime = self.find_range(self.column, "%Y-%m-%d") + min_datetime, max_datetime = self.find_range(column, "%Y-%m-%d") self.min_datetime = QDate.fromString(min_datetime, str_format) self.max_datetime = QDate.fromString(max_datetime, str_format) self.setDateRange(self.min_datetime, self.max_datetime) elif not self.have_date and self.have_time: self.setDisplayFormat("hh:mm:ss") - min_datetime, max_datetime = self.find_range(self.column, "%H:%M:%S") + min_datetime, max_datetime = self.find_range(column, "%H:%M:%S") self.min_datetime = QTime.fromString(min_datetime, str_format) self.max_datetime = QTime.fromString(max_datetime, str_format) self.setTimeRange(self.min_datetime, self.max_datetime) def set_datetime(self, datetime): if self.have_date and self.have_time: - self.setDateTime(datetime if datetime else self.min_datetime) + if isinstance(datetime, QTime): + self.setDateTime( + QDateTime(self.date(), self.calendarWidget.timeedit.time())) + else: + self.setDateTime(datetime if datetime else self.min_datetime) elif self.have_date and not self.have_time: self.setDate(datetime if datetime else self.min_datetime) elif not self.have_date and self.have_time: self.setTime(datetime if datetime else self.min_datetime) - self.dateTimeChanged.connect(self.parent.conditions_changed) def find_range(self, column, convert_format): def convert_timestamp(timestamp): diff --git a/Orange/widgets/data/tests/test_owselectrows.py b/Orange/widgets/data/tests/test_owselectrows.py index a69e3ba9295..062b7e836d9 100644 --- a/Orange/widgets/data/tests/test_owselectrows.py +++ b/Orange/widgets/data/tests/test_owselectrows.py @@ -2,20 +2,19 @@ # pylint: disable=missing-docstring,unsubscriptable-object import time from unittest.mock import Mock, patch +import numpy as np -from AnyQt.QtCore import QLocale, Qt +from AnyQt.QtCore import QLocale, Qt, QDate from AnyQt.QtTest import QTest from AnyQt.QtWidgets import QLineEdit, QComboBox -import numpy as np - from Orange.data import ( Table, Variable, ContinuousVariable, StringVariable, DiscreteVariable, Domain) from Orange.preprocess import discretize from Orange.widgets.data import owselectrows from Orange.widgets.data.owselectrows import ( - OWSelectRows, FilterDiscreteType, SelectRowsContextHandler) + OWSelectRows, FilterDiscreteType, SelectRowsContextHandler, DateTimeWidget) from Orange.widgets.tests.base import WidgetTest, datasets from Orange.data.filter import FilterContinuous, FilterString @@ -430,6 +429,42 @@ def test_keep_operator(self): self.assertEqual( self.widget.cond_list.cellWidget(0, 1).currentText(), "is") + def test_calendar_dates(self): + data = Table(test_filename("datasets/cyber-security-breaches.tab")) + self.send_signal(self.widget.Inputs.data, data) + simulate.combobox_activate_item( + self.widget.cond_list.cellWidget(0, 0), "Date_Posted_or_Updated", + delay=0) + value_combo = self.widget.cond_list.cellWidget(0, 2).children()[1] + self.assertIsInstance(value_combo, DateTimeWidget) + + # first displayed date is min date + self.assertEqual(value_combo.date(), QDate(2014, 1, 23)) + self.assertEqual(len(self.get_output("Matching Data")), 691) + self.widget.remove_all_button.click() + self.enterFilter("Date_Posted_or_Updated", "is below", + QDate(2014, 4, 17)) + self.assertEqual(len(self.get_output("Matching Data")), 840) + self.enterFilter("Date_Posted_or_Updated", "is greater than", + QDate(2014, 6, 30)) + self.assertIsNone(self.get_output("Matching Data")) + self.widget.remove_all_button.click() + # date is in range min-max date + self.enterFilter("Date_Posted_or_Updated", "equals", QDate(2013, 1, 1)) + self.assertEqual(self.widget.conditions[0][2][0], QDate(2014, 1, 23)) + self.enterFilter("Date_Posted_or_Updated", "equals", QDate(2015, 1, 1)) + self.assertEqual(self.widget.conditions[1][2][0], QDate(2014, 6, 30)) + self.widget.remove_all_button.click() + # no date crossings + self.enterFilter("Date_Posted_or_Updated", "is between", + QDate(2014, 4, 17), QDate(2014, 1, 23)) + self.assertEqual(self.widget.conditions[0][2], + (QDate(2014, 4, 17), QDate(2014, 4, 17))) + self.widget.remove_all_button.click() + self.enterFilter("Date_Posted_or_Updated", "is between", + QDate(2014, 4, 17), QDate(2014, 4, 30)) + self.assertEqual(len(self.get_output("Matching Data")), 58) + @patch.object(owselectrows.QMessageBox, "question", return_value=owselectrows.QMessageBox.Ok) def test_add_all(self, msgbox): @@ -551,9 +586,13 @@ def __get_value_widgets(self, row): if isinstance(value_inputs, QComboBox): value_inputs = [value_inputs] else: - value_inputs = [ - w for w in value_inputs.children() - if isinstance(w, QLineEdit)] + value_input = [] + for widget in value_inputs.children(): + if isinstance(widget, QLineEdit): + value_input.append(widget) + elif isinstance(widget, DateTimeWidget): + value_input.append(widget) + return value_input return value_inputs @staticmethod @@ -564,5 +603,7 @@ def __set_value(widget, value): QTest.keyClick(widget, Qt.Key_Enter) elif isinstance(widget, QComboBox): simulate.combobox_activate_item(widget, value) + elif isinstance(widget, DateTimeWidget): + widget.setDate(value) else: raise ValueError("Unsupported widget {}".format(widget))