Skip to content

Archive Search

ArchiveResultsTableModel(parent=None)

Bases: QAbstractTableModel

This table model holds the results of an archiver appliance PV search. This search is for names matching the input search words, and the results are a list of PV names that match that search.

Parameters:

Name Type Description Default
parent QObject

The parent item of this table

None
Source code in trace/widgets/archive_search.py
48
49
50
51
def __init__(self, parent: QObject = None) -> None:
    super().__init__(parent=parent)
    self.results_list = []
    self.column_names = ("PV",)

rowCount(index=QModelIndex())

Return the row count of the table

Source code in trace/widgets/archive_search.py
53
54
55
56
57
def rowCount(self, index: QModelIndex = QModelIndex()) -> int:
    """Return the row count of the table"""
    if index is not None and index.isValid():
        return 0
    return len(self.results_list)

columnCount(index=QModelIndex())

Return the column count of the table

Source code in trace/widgets/archive_search.py
59
60
61
62
63
def columnCount(self, index: QModelIndex = QModelIndex()) -> int:
    """Return the column count of the table"""
    if index is not None and index.isValid():
        return 0
    return len(self.column_names)

data(index, role)

Return the data for the associated role. Currently only supporting DisplayRole.

Source code in trace/widgets/archive_search.py
65
66
67
68
69
70
71
72
73
def data(self, index: QModelIndex, role: int) -> Any:
    """Return the data for the associated role. Currently only supporting DisplayRole."""
    if not index.isValid():
        return None

    if role != Qt.DisplayRole:
        return None

    return self.results_list[index.row()]

headerData(section, orientation, role=Qt.DisplayRole)

Return data associated with the header

Source code in trace/widgets/archive_search.py
75
76
77
78
79
80
def headerData(self, section, orientation, role=Qt.DisplayRole) -> Any:
    """Return data associated with the header"""
    if role != Qt.DisplayRole:
        return super().headerData(section, orientation, role)

    return str(self.column_names[section])

flags(index)

Return flags that determine how users can interact with the items in the table

Source code in trace/widgets/archive_search.py
82
83
84
85
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
    """Return flags that determine how users can interact with the items in the table"""
    if index.isValid():
        return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled

append(pv)

Appends a row to this table given the PV name as input

Source code in trace/widgets/archive_search.py
87
88
89
90
91
92
def append(self, pv: str) -> None:
    """Appends a row to this table given the PV name as input"""
    self.beginInsertRows(QModelIndex(), len(self.results_list), len(self.results_list))
    self.results_list.append(pv)
    self.endInsertRows()
    self.layoutChanged.emit()

replace_rows(pvs)

Overwrites any existing rows in the table with the input list of PV names

Source code in trace/widgets/archive_search.py
94
95
96
97
98
99
def replace_rows(self, pvs: list[str]) -> None:
    """Overwrites any existing rows in the table with the input list of PV names"""
    self.beginInsertRows(QModelIndex(), 0, len(pvs) - 1)
    self.results_list = pvs
    self.endInsertRows()
    self.layoutChanged.emit()

clear()

Clear out all data stored in this table

Source code in trace/widgets/archive_search.py
101
102
103
104
105
106
def clear(self) -> None:
    """Clear out all data stored in this table"""
    self.beginRemoveRows(QModelIndex(), 0, len(self.results_list))
    self.results_list = []
    self.endRemoveRows()
    self.layoutChanged.emit()

sort(col, order=Qt.AscendingOrder)

Sort the table by PV name

Source code in trace/widgets/archive_search.py
108
109
110
111
def sort(self, col: int, order=Qt.AscendingOrder) -> None:
    """Sort the table by PV name"""
    self.results_list.sort(reverse=order == Qt.DescendingOrder)
    self.layoutChanged.emit()

ArchiveSearchWidget(parent=None)

Bases: QWidget

Widget for searching and selecting PVs from the EPICS archiver appliance.

This widget provides a search interface for finding PVs by name patterns using the archiver appliance. Users can search for PVs and add them to the plot by selecting them from the results table.

Parameters:

Name Type Description Default
parent QObject

The parent item of this widget

None

Parameters:

Name Type Description Default
parent QObject

The parent object

None
Source code in trace/widgets/archive_search.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
def __init__(self, parent: QObject = None):
    """Initialize the archive search widget.

    Parameters
    ----------
    parent : QObject, optional
        The parent object
    """
    super().__init__(parent=parent)

    self.network_manager = QNetworkAccessManager()
    self.network_manager.finished.connect(self.populate_results_list)

    self.resize(400, 800)
    self.main_layout = QVBoxLayout()

    self.archive_url_layout = QHBoxLayout()
    self.archive_title_label = QLabel("Archive URL:")
    self.archive_url_layout.addWidget(self.archive_title_label)
    self.archive_url_textedit = QLineEdit(getenv("PYDM_ARCHIVER_URL"))
    self.archive_url_textedit.setFixedWidth(250)
    self.archive_url_textedit.setFixedHeight(25)
    self.archive_url_layout.addWidget(self.archive_url_textedit)
    self.main_layout.addLayout(self.archive_url_layout)

    self.search_layout = QHBoxLayout()
    self.search_label = QLabel("Pattern:")
    self.search_layout.addWidget(self.search_label)
    self.search_box = QLineEdit()
    self.search_layout.addWidget(self.search_box)
    self.search_button = QPushButton("Search")
    self.search_button.setDefault(True)
    self.search_button.clicked.connect(self.request_archiver_info)
    self.search_layout.addWidget(self.search_button)
    self.main_layout.addLayout(self.search_layout)

    self.loading_label = QLabel("Loading...")
    self.loading_label.hide()
    self.main_layout.addWidget(self.loading_label)

    self.results_table_model = ArchiveResultsTableModel()
    self.results_view = QTableView(self)
    self.results_view.setModel(self.results_table_model)
    # self.results_view.setProperty("showDropIndicator", False)
    # self.results_view.setDragDropOverwriteMode(False)
    # self.results_view.setDragEnabled(True)  # Removing drag & drop for now
    self.results_view.setSelectionMode(QAbstractItemView.ExtendedSelection)
    self.results_view.setSelectionBehavior(QAbstractItemView.SelectRows)
    # self.results_view.setDropIndicatorShown(True)
    self.results_view.setCornerButtonEnabled(False)
    self.results_view.setSortingEnabled(True)
    self.results_view.verticalHeader().setVisible(False)
    self.results_view.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
    # self.results_view.startDrag = self.startDragAction
    self.results_view.doubleClicked.connect(lambda: self.append_PVs_requested.emit(self.selectedPVs()))
    self.main_layout.addWidget(self.results_view)

    self.insert_button = QPushButton("Add PVs")
    self.insert_button.clicked.connect(lambda: self.append_PVs_requested.emit(self.selectedPVs()))
    self.main_layout.addWidget(self.insert_button)

    self.setLayout(self.main_layout)

selectedPVs()

Get the list of selected PVs from the results table.

Returns:

Type Description
list[str]

List of selected PV names

Source code in trace/widgets/archive_search.py
192
193
194
195
196
197
198
199
200
201
202
203
204
def selectedPVs(self) -> list[str]:
    """Get the list of selected PVs from the results table.

    Returns
    -------
    list[str]
        List of selected PV names
    """
    indices = self.results_view.selectedIndexes()
    pv_list = []
    for index in indices:
        pv_list.append(self.results_table_model.results_list[index.row()])
    return pv_list

startDragAction(supported_actions)

Handle drag action for PV names.

This method is called when a user initiates a drag action for one of the results in the table. It allows dragging PV names onto a plot to automatically start drawing data for that PV.

Parameters:

Name Type Description Default
supported_actions DropActions

The supported drop actions, unused

required
Source code in trace/widgets/archive_search.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
def startDragAction(self, supported_actions) -> None:
    """Handle drag action for PV names.

    This method is called when a user initiates a drag action for one of
    the results in the table. It allows dragging PV names onto a plot to
    automatically start drawing data for that PV.

    Parameters
    ----------
    supported_actions : Qt.DropActions
        The supported drop actions, unused
    """
    drag = QDrag(self)
    mime_data = QMimeData()
    mime_data.setText(self.selectedPVs())
    drag.setMimeData(mime_data)
    drag.exec_()

keyPressEvent(e)

Handle key press events for search submission.

Parameters:

Name Type Description Default
e QKeyEvent

The key press event

required
Source code in trace/widgets/archive_search.py
224
225
226
227
228
229
230
231
232
233
234
def keyPressEvent(self, e: QKeyEvent) -> None:
    """Handle key press events for search submission.

    Parameters
    ----------
    e : QKeyEvent
        The key press event
    """
    if e.key() == Qt.Key_Return or e.key() == Qt.Key_Enter:
        self.request_archiver_info()
    return super().keyPressEvent(e)

request_archiver_info()

Send a search request to the archiver appliance.

Converts the search text to a regex pattern and queries the archiver appliance for matching PV names.

Source code in trace/widgets/archive_search.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
def request_archiver_info(self) -> None:
    """Send a search request to the archiver appliance.

    Converts the search text to a regex pattern and queries the archiver
    appliance for matching PV names.
    """
    search_text = self.search_box.text()
    search_text = search_text.replace("?", ".")
    search_text = search_text.replace("*", ".*")
    search_text = search_text.replace("%", ".*")
    url_string = f"{self.archive_url_textedit.text()}/retrieval/bpl/searchForPVsRegex?regex=.*{search_text}.*"
    request = QNetworkRequest(QUrl(url_string))
    self.network_manager.get(request)
    self.loading_label.show()

populate_results_list(reply)

Handle the response from the archiver appliance search.

Parameters:

Name Type Description Default
reply QNetworkReply

The network reply containing search results

required
Source code in trace/widgets/archive_search.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
def populate_results_list(self, reply: QNetworkReply) -> None:
    """Handle the response from the archiver appliance search.

    Parameters
    ----------
    reply : QNetworkReply
        The network reply containing search results
    """
    self.loading_label.hide()
    if reply.error() == QNetworkReply.NoError:
        self.results_table_model.clear()
        bytes_str = reply.readAll()
        pv_list = str(bytes_str, "utf-8").split()
        self.results_table_model.replace_rows(pv_list)
    else:
        logger.error(f"Could not retrieve archiver results due to: {reply.error()}")
    reply.deleteLater()