# Copyright (c) 2023 Adrian Niemann Dmitry Puzyrev
#
# This file is part of RodTracker.
# RodTracker is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RodTracker is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with RodTracker. If not, see <http://www.gnu.org/licenses/>.
"""**TBD**"""
import pandas as pd
from PyQt5 import QtCore, QtGui, QtWidgets
import RodTracker.backend.file_locations as fl
[docs]class ConfirmDeleteDialog(QtWidgets.QDialog):
"""Confirmation dialog for data that was marked for deletion automatically.
The user shall confirm/deny the deletion of rows that were automatically
marked for deletion.
Parameters
----------
to_delete : DataFrame
Rows of the main DataFrame that are automatically identified to be
deleted and shall be confirmed by the user.
parent : QWidget
Window/Widget that serves as this dialog's parent.
Attributes
----------
confirmed_delete : List[bool]
Entries correspond to a row from the :attr:`to_delete` ``DataFrame``.
``True`` -> user confirms deletion
``False`` -> user denies deletion
"""
def __init__(self, to_delete: pd.DataFrame, parent: QtWidgets.QWidget):
super().__init__(parent=parent)
self.to_delete = to_delete
self.confirmed_delete = len(to_delete) * [True]
# Create visual elements
self.description = QtWidgets.QLabel("")
self.table = QtWidgets.QTableWidget(len(to_delete), 3, parent=self)
self.controls = QtWidgets.QDialogButtonBox()
self.layout = QtWidgets.QVBoxLayout(self)
self.setup_ui()
[docs] def setup_ui(self):
"""Setup the UI elements."""
self.setWindowTitle("Confirm deletions")
description_text = """
<p>Please review the rods that were marked for complete deletion
from the output files. <br><br>
<b>Caution: The changes made after clicking OK cannot be
reverted.</b></p>
"""
self.description.setText(description_text)
self.table.setHorizontalHeaderLabels(["Number", "Frame", "Color"])
h_header = self.table.horizontalHeader()
h_header.setStyleSheet("font: bold;")
self.table.verticalHeader().hide()
self.controls.addButton(QtWidgets.QDialogButtonBox.Ok)
self.controls.addButton(QtWidgets.QDialogButtonBox.Cancel)
self.controls.accepted.connect(self.accept)
self.controls.rejected.connect(self.reject)
self.layout.addWidget(self.description)
self.layout.addWidget(self.table)
self.layout.addWidget(self.controls)
self.layout.addStretch()
self.table.horizontalHeader().setSectionResizeMode(
QtWidgets.QHeaderView.Stretch)
self.setLayout(self.layout)
next_row = 0
for row in self.to_delete.iterrows():
next_frame = QtWidgets.QTableWidgetItem(str(row[1].frame))
next_color = QtWidgets.QTableWidgetItem(str(row[1].color))
next_particle = QtWidgets.QTableWidgetItem(
str(row[1].particle))
next_frame.setTextAlignment(QtCore.Qt.AlignHCenter |
QtCore.Qt.AlignVCenter)
next_color.setTextAlignment(QtCore.Qt.AlignHCenter |
QtCore.Qt.AlignVCenter)
next_particle.setTextAlignment(QtCore.Qt.AlignHCenter |
QtCore.Qt.AlignVCenter)
self.table.setItem(next_row, 1, next_frame)
self.table.setItem(next_row, 2, next_color)
next_particle.setFlags(QtCore.Qt.ItemIsUserCheckable |
QtCore.Qt.ItemIsEnabled)
next_particle.setCheckState(QtCore.Qt.Checked)
self.table.setItem(next_row, 0, next_particle)
next_row += 1
self.table.itemClicked.connect(self.handle_item_clicked)
[docs] @QtCore.pyqtSlot(QtWidgets.QTableWidgetItem)
def handle_item_clicked(self, item: QtWidgets.QTableWidgetItem) -> None:
"""Handles the checking/unchecking of rows to mark for deletion.
Parameters
----------
item : QTableWidgetItem
Returns
-------
None
"""
if item.checkState() == QtCore.Qt.Checked:
self.confirmed_delete[item.row()] = True
else:
self.confirmed_delete[item.row()] = False
[docs]def show_warning(text: str):
"""Display a warning with custom text and Ok button."""
msg = QtWidgets.QMessageBox()
msg.setWindowIcon(QtGui.QIcon(fl.icon_path()))
msg.setIcon(QtWidgets.QMessageBox.Warning)
msg.setWindowTitle("Rod Tracker")
msg.setText(text)
msg.setStandardButtons(QtWidgets.QMessageBox.Ok)
msg.exec()
[docs]def show_about(parent: QtWidgets.QWidget):
about_txt = """
<style>
table { background-color: transparent; }
a { text-decoration:none; font-weight:bold; }
</style>
<table border="0" cellpadding="0" cellspacing="5" width="400"
align="left" style="margin-top:0px;">
<tr>
<td width="200", colspan="2"> <h3>Version:</h3> </td>
<td width="200"> <p> 0.6.0 </p> </td>
</tr>
<tr>
<td width="200", colspan="2"> <h3>Date:</h3> </td>
<td width="200"> <p> 22.02.2023 </p> </td>
</tr>
<tr>
<td width="200", colspan="2"> <h3><br>Developers:<br></h3> </td>
<td width="200">
<p> Adrian Niemann <br>
Dmitry Puzyrev <br>
Meera Subramanian <br>
Adithya Viswanathan
</p>
</td>
</tr>
<tr>
<td width="200", colspan="2"> <h3>License:</h3> </td>
<td width="200">
<p><a href="https://www.gnu.org/licenses/gpl-3.0.en.html">
GPLv3</a>
</p>
</td>
</tr>
<tr>
<td width="400", colspan="3"> <br><h3>3rd Party Software:</h3>
<p> This application either uses code and tools from the
following projects in part or in their entirety as deemed
permissible by each project's open-source license.</p>
</td>
</tr>
<tr>
<td width="50">
<p><a href="https://docutils.sourceforge.io/">
docutils</a>:</p>
</td>
<td width="150">0.16</td>
<td width="200"><p>BSD 2, GPL, PSF</p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://github.com/pycqa/flake8">flake8</a>:</p>
</td>
<td width="150">5.0.4</td>
<td width="200"><p>MIT</p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://github.com/python/importlib_resources">
importlib-resources</a>:</p>
</td>
<td width="150">1.4.0</td>
<td width="200"><p>Apache-2.0</p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://material.io/">Material Design</a>:</p>
</td>
<td width="150">2</td>
<td width="200"><p> Apache-2.0 </p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://matplotlib.org/">matplotlib</a>:</p>
</td>
<td width="150">3.6.2</td>
<td width="200"><p>matplotlib</p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://myst-parser.readthedocs.io/">
MyST-Parser</a>:</p>
</td>
<td width="150">0.18.1</td>
<td width="200"><p>MIT</p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://numpy.org/">NumPy</a>:</p>
</td>
<td width="150">1.21.0</td>
<td width="200"><p>BSD 3</p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://pandas.pydata.org/">Pandas</a>:</p>
</td>
<td width="150">1.2.5</td>
<td width="200"><p> BSD 3 </p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://github.com/ANP-Granular/ParticleTracking">
particledetection</a>:</p>
</td>
<td width="150">0.4.0</td>
<td width="200"><p> GPLv3 </p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://github.com/platformdirs/platformdirs">
platformdirs</a>:</p>
</td>
<td width="150">3.2.0</td>
<td width="200"><p> MIT </p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://www.pyinstaller.org/">PyInstaller</a>:</p>
</td>
<td width="150">5.7</td>
<td width="200"><p> GPLv2+ </p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://www.riverbankcomputing.com/software
/pyqt">PyQt3D</a>:</p>
</td>
<td width="150">5.15.5</td>
<td width="200"><p> GPLv3+ </p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://www.riverbankcomputing.com/software
/pyqt">PyQt5</a>:</p>
</td>
<td width="150">5.15.4</td>
<td width="200"><p> GPLv3+ </p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://docs.pytest.org/">pytest</a>:</p>
</td>
<td width="150">7.1.2</td>
<td width="200"><p>MIT</p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://github.com/pytest-dev/pytest-cov">
pytest-cov</a>:</p>
</td>
<td width="150">3.0.0</td>
<td width="200"><p>MIT</p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://github.com/pytest-dev/pytest-qt">
pytest-qt</a>:</p>
</td>
<td width="150">4.0.0</td>
<td width="200"><p>MIT</p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://www.qt.io">Qt5</a>:</p>
</td>
<td width="150">5.15.2</td>
<td width="200"><p> LGPLv3 </p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://www.sphinx-doc.org/">Sphinx</a>:</p>
</td>
<td width="150">5.3</td>
<td width="200"><p>BSD</p></td>
</tr>
<tr>
<td width="50">
<p><a href="https://sphinx-rtd-theme.readthedocs.io/">
sphinx_rtd_theme</a>:</p>
</td>
<td width="150">0.5.1</td>
<td width="200"><p>MIT</p></td>
</tr>
</table>
<br>
<br>
<p>
Copyright © 2023 Adrian Niemann, Dmitry Puzyrev
</p>"""
QtWidgets.QMessageBox.about(parent, "About RodTracker", about_txt)
[docs]class ConflictDialog(QtWidgets.QMessageBox):
"""Dialog for switching rod numbers in various modes."""
def __init__(self, last_id, new_id, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setWindowIcon(QtGui.QIcon(fl.icon_path()))
self.setIcon(QtWidgets.QMessageBox.Warning)
self.setWindowTitle("Rod Tracker")
self.setText(
f"A rod number switching attempt of"
f"#{last_id} <---> #{new_id} was detected. \nHow shall "
f"this conflict be resolved?")
self.btn_switch_all = self.addButton(
"Switch in:\nBoth views, following frames",
QtWidgets.QMessageBox.ActionRole)
self.btn_one_cam = self.addButton("Switch in:\nThis view, following "
"frames",
QtWidgets.QMessageBox.ActionRole)
self.btn_both_cams = self.addButton(
"Switch in:\nBoth views, this frame",
QtWidgets.QMessageBox.ActionRole)
self.btn_only_this = self.addButton(
"Switch in:\nThis view, this frame",
QtWidgets.QMessageBox.ActionRole)
self.btn_cancel = self.addButton(QtWidgets.QMessageBox.Abort)
self.setDefaultButton(self.btn_switch_all)
self.setEscapeButton(self.btn_cancel)