Skip to content

Commit ca134cc

Browse files
authored
Merge pull request #362 from European-XFEL/feat/rowFiltering
Basic row filtering
2 parents 64c80c2 + 6f1c1e5 commit ca134cc

10 files changed

+1104
-195
lines changed

damnit/gui/main_window.py

+14-87
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@
3535
from .plot import (ImagePlotWindow, PlottingControls, ScatterPlotWindow,
3636
Xarray1DPlotWindow)
3737
from .process import ProcessingDialog
38+
from .standalone_comments import TimeComment
3839
from .table import DamnitTableModel, TableView, prettify_notation
3940
from .theme import Theme, ThemeManager, set_lexer_theme
4041
from .user_variables import AddUserVariableDialog
4142
from .web_viewer import PlotlyPlot, UrlSchemeHandler
42-
from .widgets import CollapsibleWidget
4343
from .zulip_messenger import ZulipMessenger
4444

4545
log = logging.getLogger(__name__)
@@ -595,18 +595,6 @@ def _updates_thread_launcher(self) -> None:
595595
self.update_agent.message.connect(self.handle_update)
596596
QtCore.QTimer.singleShot(0, self._updates_thread.start)
597597

598-
def _set_comment_date(self):
599-
self.comment_time.setText(
600-
time.strftime("%H:%M %d/%m/%Y", time.localtime(time.time()))
601-
)
602-
603-
def _comment_button_clicked(self):
604-
ts = datetime.strptime(self.comment_time.text(), "%H:%M %d/%m/%Y").timestamp()
605-
text = self.comment.text()
606-
comment_id = self.db.add_standalone_comment(ts, text)
607-
self.table.insert_comment_row(comment_id, text, ts)
608-
self.comment.clear()
609-
610598
def get_run_file(self, proposal, run, log=True):
611599
file_name = self.extracted_data_template.format(proposal, run)
612600

@@ -743,7 +731,6 @@ def show_run_logs(self, proposal, run):
743731
def _create_table_model(self, db, col_settings):
744732
table = DamnitTableModel(db, col_settings, self)
745733
table.value_changed.connect(self.save_value)
746-
table.time_comment_changed.connect(self.save_time_comment)
747734
table.run_visibility_changed.connect(lambda row, state: self.plot.update())
748735
table.rowsInserted.connect(self.on_rows_inserted)
749736
return table
@@ -756,82 +743,29 @@ def _create_view(self) -> None:
756743
vertical_layout.addWidget(toolbar)
757744

758745
# the table
759-
self.table_view = TableView()
746+
self.table_view = TableView(self)
760747
self.table_view.doubleClicked.connect(self._inspect_data_proxy_idx)
761748
self.table_view.settings_changed.connect(self.save_settings)
762749
self.table_view.zulip_action.triggered.connect(self.export_selection_to_zulip)
763750
self.table_view.process_action.triggered.connect(self.process_runs)
764751
self.table_view.log_view_requested.connect(self.show_run_logs)
765752

766-
# Add table view's toolbar widgets
767-
for widget in self.table_view.get_toolbar_widgets():
768-
toolbar.addWidget(widget)
769-
770-
vertical_layout.addWidget(self.table_view)
771-
772-
# add all other widgets on a collapsible layout
773-
collapsible = CollapsibleWidget()
774-
vertical_layout.addWidget(collapsible)
775-
776-
comment_horizontal_layout = QtWidgets.QHBoxLayout()
777-
self.comment = QtWidgets.QLineEdit(self)
778-
self.comment.setText("Time can be edited in the field on the right.")
779-
780-
self.comment_time = QtWidgets.QLineEdit(self)
781-
self.comment_time.setStyleSheet("width: 25px;")
782-
783-
comment_button = QtWidgets.QPushButton("Additional comment")
784-
comment_button.setEnabled(True)
785-
comment_button.clicked.connect(self._comment_button_clicked)
786-
787-
comment_horizontal_layout.addWidget(comment_button)
788-
comment_horizontal_layout.addWidget(self.comment, stretch=3)
789-
comment_horizontal_layout.addWidget(QtWidgets.QLabel("at"))
790-
comment_horizontal_layout.addWidget(self.comment_time, stretch=1)
791-
792-
collapsible.add_layout(comment_horizontal_layout)
793-
794-
comment_timer = QtCore.QTimer()
795-
self._set_comment_date()
796-
comment_timer.setInterval(30000)
797-
comment_timer.timeout.connect(self._set_comment_date)
798-
comment_timer.start()
799-
800-
# plotting control
753+
# Initialize plot controls
801754
self.plot = PlottingControls(self)
802-
plotting_group = QtWidgets.QGroupBox("Plotting controls")
803-
plot_vertical_layout = QtWidgets.QVBoxLayout()
804-
plot_horizontal_layout = QtWidgets.QHBoxLayout()
805-
plot_parameters_horizontal_layout = QtWidgets.QHBoxLayout()
806755

807-
plot_horizontal_layout.addWidget(self.plot._button_plot)
808-
self.plot._button_plot_runs.setMinimumWidth(200)
809-
plot_horizontal_layout.addStretch()
756+
self.plot_dialog_button = QtWidgets.QPushButton("Plot")
757+
self.plot_dialog_button.clicked.connect(self.plot.show_dialog)
758+
self.comment_button = QtWidgets.QPushButton("Time comment")
759+
self.comment_button.clicked.connect(lambda: TimeComment(self).show())
810760

811-
plot_horizontal_layout.addWidget(QtWidgets.QLabel("Y:"))
812-
plot_horizontal_layout.addWidget(self.plot._combo_box_y_axis)
813-
plot_horizontal_layout.addWidget(self.plot.vs_button)
814-
plot_horizontal_layout.addWidget(QtWidgets.QLabel("X:"))
815-
plot_horizontal_layout.addWidget(self.plot._combo_box_x_axis)
816-
817-
plot_vertical_layout.addLayout(plot_horizontal_layout)
818-
819-
plot_parameters_horizontal_layout.addWidget(self.plot._button_plot_runs)
820-
self.plot._button_plot.setMinimumWidth(200)
821-
plot_parameters_horizontal_layout.addStretch()
822-
823-
plot_parameters_horizontal_layout.addWidget(
824-
self.plot._toggle_probability_density
825-
)
826-
827-
plot_vertical_layout.addLayout(plot_parameters_horizontal_layout)
828-
829-
plotting_group.setLayout(plot_vertical_layout)
761+
toolbar.addWidget(self.plot_dialog_button)
762+
toolbar.addWidget(self.comment_button)
763+
for widget in self.table_view.get_toolbar_widgets():
764+
toolbar.addWidget(widget)
830765

831-
collapsible.add_widget(plotting_group)
766+
vertical_layout.addWidget(self.table_view)
767+
vertical_layout.setContentsMargins(0, 7, 0, 0)
832768

833-
vertical_layout.setSpacing(0)
834-
vertical_layout.setContentsMargins(0, 0, 0, 0)
835769
self._view_widget.setLayout(vertical_layout)
836770

837771
def configure_editor(self):
@@ -983,14 +917,6 @@ def save_value(self, prop, run, name, value):
983917
if self._connect_to_kafka:
984918
self.update_agent.run_values_updated(prop, run, name, value)
985919

986-
def save_time_comment(self, comment_id, value):
987-
if self.db is None:
988-
log.warning("No SQLite database in use, comment not saved")
989-
return
990-
991-
log.debug("Saving time-based comment ID %d", comment_id)
992-
self.db.change_standalone_comment(comment_id, value)
993-
994920
def check_zulip_messenger(self):
995921
if not isinstance(self.zulip_messenger, ZulipMessenger):
996922
self.zulip_messenger = ZulipMessenger(self)
@@ -1128,6 +1054,7 @@ def styleHint(self, hint, option=None, widget=None, returnData=None):
11281054
else:
11291055
return super().styleHint(hint, option, widget, returnData)
11301056

1057+
11311058
class TabBarStyle(QtWidgets.QProxyStyle):
11321059
"""
11331060
Subclass that enables bold tab text for tab 1 (the editor tab).

damnit/gui/plot.py

+43-15
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66
import numpy as np
77
import pandas as pd
88
import xarray as xr
9+
910
from matplotlib.backends.backend_qtagg import FigureCanvas
1011
from matplotlib.backends.backend_qtagg import \
1112
NavigationToolbar2QT as NavigationToolbar
1213
from matplotlib.colorbar import Colorbar
1314
from matplotlib.figure import Figure
1415
from mpl_pan_zoom import MouseButton, PanManager, zoom_factory
1516
from PyQt5 import QtCore, QtGui, QtWidgets
16-
from PyQt5.QtCore import Qt
17+
from PyQt5.QtCore import Qt, QObject
1718
from PyQt5.QtGui import QColor, QIcon, QPainter
1819
from PyQt5.QtWidgets import QMessageBox
1920

@@ -592,37 +593,64 @@ class PlottingControls:
592593
def __init__(self, main_window) -> None:
593594
self._main_window = main_window
594595

595-
self._button_plot = QtWidgets.QPushButton(main_window)
596+
self.dialog = QtWidgets.QDialog(main_window)
597+
self.dialog.setWindowTitle("Plot Controls")
598+
599+
plot_vertical_layout = QtWidgets.QVBoxLayout()
600+
plot_horizontal_layout = QtWidgets.QHBoxLayout()
601+
plot_parameters_horizontal_layout = QtWidgets.QHBoxLayout()
602+
603+
self._button_plot = QtWidgets.QPushButton(self.dialog)
596604
self._button_plot.setEnabled(True)
597605
self._button_plot.setText("Plot summary for all runs")
598606
self._button_plot.clicked.connect(self._plot_summaries_clicked)
599607

600-
self._button_plot_runs = QtWidgets.QPushButton(
601-
"Plot for selected runs", main_window
602-
)
608+
self._button_plot_runs = QtWidgets.QPushButton("Plot for selected runs", self.dialog)
603609
self._button_plot_runs.clicked.connect(self._plot_run_data_clicked)
604610

605-
self._combo_box_x_axis = SearchableComboBox(self._main_window)
606-
self._combo_box_y_axis = SearchableComboBox(self._main_window)
611+
plot_horizontal_layout.addWidget(self._button_plot)
612+
self._button_plot_runs.setMinimumWidth(200)
613+
plot_horizontal_layout.addStretch()
607614

608-
self._toggle_probability_density = QtWidgets.QPushButton(
609-
"Histogram", main_window
610-
)
611-
self._toggle_probability_density.setCheckable(True)
612-
self._toggle_probability_density.setChecked(False)
613-
self._toggle_probability_density.toggled.connect(
614-
self._combo_box_y_axis.setDisabled
615-
)
615+
self._combo_box_x_axis = SearchableComboBox(self.dialog)
616+
self._combo_box_y_axis = SearchableComboBox(self.dialog)
616617

617618
self.vs_button = QtWidgets.QToolButton()
618619
self.vs_button.setText("vs.")
619620
self.vs_button.setToolTip("Click to swap axes")
620621
self.vs_button.clicked.connect(self.swap_plot_axes)
621622

623+
plot_horizontal_layout.addWidget(QtWidgets.QLabel("Y:"))
624+
plot_horizontal_layout.addWidget(self._combo_box_y_axis)
625+
plot_horizontal_layout.addWidget(self.vs_button)
626+
plot_horizontal_layout.addWidget(QtWidgets.QLabel("X:"))
627+
plot_horizontal_layout.addWidget(self._combo_box_x_axis)
628+
622629
self._combo_box_x_axis.setCurrentText("Run")
623630

631+
plot_vertical_layout.addLayout(plot_horizontal_layout)
632+
633+
plot_parameters_horizontal_layout.addWidget(self._button_plot_runs)
634+
self._button_plot.setMinimumWidth(200)
635+
plot_parameters_horizontal_layout.addStretch()
636+
637+
self._toggle_probability_density = QtWidgets.QPushButton("Histogram", self.dialog)
638+
self._toggle_probability_density.setCheckable(True)
639+
self._toggle_probability_density.setChecked(False)
640+
self._toggle_probability_density.toggled.connect(self._combo_box_y_axis.setDisabled)
641+
642+
plot_parameters_horizontal_layout.addWidget(self._toggle_probability_density)
643+
644+
plot_vertical_layout.addLayout(plot_parameters_horizontal_layout)
645+
646+
self.dialog.setLayout(plot_vertical_layout)
647+
self.dialog.setVisible(False)
648+
624649
self._plot_windows = []
625650

651+
def show_dialog(self):
652+
self.dialog.setVisible(True)
653+
626654
def update_columns(self):
627655
keys = self.table.column_titles
628656

damnit/gui/standalone_comments.py

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import logging
2+
3+
from PyQt5.QtCore import (QAbstractTableModel, QDateTime, QModelIndex, Qt,
4+
QVariant)
5+
from PyQt5.QtWidgets import (QDateTimeEdit, QDialog, QHBoxLayout, QLineEdit,
6+
QPushButton, QTableView, QVBoxLayout, QHeaderView)
7+
8+
log = logging.getLogger(__name__)
9+
10+
11+
class CommentModel(QAbstractTableModel):
12+
def __init__(self, db, parent=None):
13+
super().__init__(parent)
14+
self.db = db
15+
self._data = []
16+
self._headers = ['#', 'Timestamp', 'Comment']
17+
self._sort_column = 1 # Default sort by timestamp
18+
self._sort_order = Qt.DescendingOrder
19+
self.load_comments()
20+
21+
def load_comments(self):
22+
"""Load comments from the database, sorted by timestamp in descending order"""
23+
self._data = self.db.conn.execute("""
24+
SELECT rowid, timestamp, comment FROM time_comments
25+
ORDER BY timestamp DESC
26+
""").fetchall()
27+
28+
self.layoutChanged.emit()
29+
30+
def data(self, index, role=Qt.DisplayRole):
31+
if role == Qt.DisplayRole:
32+
row = index.row()
33+
col = index.column()
34+
35+
if col == 1:
36+
return QDateTime.fromSecsSinceEpoch(
37+
int(self._data[row][col])
38+
).toString("yyyy-MM-dd HH:mm:ss")
39+
else:
40+
return self._data[row][col]
41+
return QVariant()
42+
43+
def headerData(self, section, orientation, role):
44+
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
45+
return self._headers[section]
46+
return QVariant()
47+
48+
def rowCount(self, parent=QModelIndex()):
49+
return len(self._data)
50+
51+
def columnCount(self, parent=QModelIndex()):
52+
return len(self._headers)
53+
54+
def addComment(self, timestamp, comment):
55+
"""Add a comment to the database"""
56+
if self.db is None:
57+
log.warning("No SQLite database in use, comment not saved")
58+
return
59+
60+
cid = self.db.add_standalone_comment(timestamp, comment)
61+
log.debug("Saving time-based id %d", cid)
62+
# Reload comments to reflect the latest state
63+
self.load_comments()
64+
65+
def sort(self, column, order):
66+
"""Sort table by given column number."""
67+
self._sort_column = column
68+
self._sort_order = order
69+
70+
self.layoutAboutToBeChanged.emit()
71+
self._data = sorted(self._data,
72+
key=lambda x: x[column],
73+
reverse=(order == Qt.DescendingOrder))
74+
self.layoutChanged.emit()
75+
76+
77+
class TimeComment(QDialog):
78+
79+
def __init__(self, parent=None):
80+
super().__init__(parent)
81+
82+
layout = QVBoxLayout()
83+
84+
# Table View
85+
self.tableView = QTableView()
86+
self.tableView.setSortingEnabled(True)
87+
self.model = CommentModel(self.parent().db, self)
88+
self.tableView.setModel(self.model)
89+
90+
# Configure column widths
91+
header = self.tableView.horizontalHeader()
92+
for ix in range(self.model.columnCount() - 1):
93+
header.setSectionResizeMode(ix, header.ResizeToContents)
94+
header.setStretchLastSection(True)
95+
# Set word wrap for the comment column
96+
self.tableView.setWordWrap(True)
97+
# Ensure rows resize properly
98+
self.tableView.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
99+
100+
# Dialog layout
101+
layout.addWidget(self.tableView)
102+
103+
inputLayout = QHBoxLayout()
104+
105+
self.timestampInput = QDateTimeEdit()
106+
self.timestampInput.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
107+
self.timestampInput.setDateTime(QDateTime.currentDateTime())
108+
109+
self.commentInput = QLineEdit()
110+
self.commentInput.setPlaceholderText('Comment:')
111+
112+
publishButton = QPushButton("Publish")
113+
publishButton.clicked.connect(self.publishComment)
114+
115+
inputLayout.addWidget(self.timestampInput)
116+
inputLayout.addWidget(self.commentInput)
117+
inputLayout.addWidget(publishButton)
118+
119+
layout.addLayout(inputLayout)
120+
121+
self.setLayout(layout)
122+
self.setWindowTitle('Standalone comments')
123+
self.resize(600, 400)
124+
125+
def publishComment(self):
126+
timestamp = self.timestampInput.dateTime().toSecsSinceEpoch()
127+
comment = self.commentInput.text()
128+
if comment:
129+
self.model.addComment(timestamp, comment)
130+
self.commentInput.clear()
131+
self.timestampInput.setDateTime(QDateTime.currentDateTime())
132+
self.tableView.resizeRowsToContents()

0 commit comments

Comments
 (0)