Source code for msl.qt.widgets.comments
"""
A :class:`QtWidgets.QDialog` to prompt the user to enter comments.
"""
import json
import os
from datetime import datetime
from .. import Button
from .. import Qt
from .. import QtWidgets
from .. import convert
from .. import prompt
from .. import utils
from ..constants import HOME_DIR
[docs]class Comments(QtWidgets.QDialog):
def __init__(self, json_path, title, even_row_color, odd_row_color):
"""A :class:`QtWidgets.QDialog` to prompt the user to enter comments.
Do not instantiate directly. Use :func:`msl.qt.prompt.comments` instead.
"""
super(Comments, self).__init__(
None, Qt.WindowType.WindowCloseButtonHint | Qt.WindowType.WindowMinMaxButtonsHint)
self.setWindowTitle(title)
self.path = json_path if json_path else os.path.join(HOME_DIR, 'msl-qt-comments.json')
self.comments = []
self.even_row_color = convert.to_qcolor(even_row_color)
self.odd_row_color = convert.to_qcolor(odd_row_color)
self._load_json()
self.comment_textedit = QtWidgets.QPlainTextEdit(self)
height = self.comment_textedit.fontMetrics().lineSpacing()
self.comment_textedit.setFixedHeight(5 * height) # display 5 lines
self.ok_button = Button(
text='OK',
left_click=self._prepend_and_close,
tooltip='Select the comment and exit',
parent=self,
)
self.ok_button.add_menu_item(
text='Clear history',
icon=QtWidgets.QStyle.StandardPixmap.SP_DialogResetButton,
triggered=self._clear_history,
tooltip='Delete all comments that are in the history'
)
#
# the filter field
#
self.filter_edit = QtWidgets.QLineEdit()
self.filter_edit.setToolTip('Search filter for the history')
self.filter_edit.returnPressed.connect(self._apply_filter) # noqa: returnPressed.connect
filter_button = Button(
icon=QtWidgets.QStyle.StandardPixmap.SP_FileDialogContentsView,
tooltip='Apply filter',
left_click=self._apply_filter
)
clear_button = Button(
icon=QtWidgets.QStyle.StandardPixmap.SP_LineEditClearButton,
tooltip='Clear filter',
left_click=self._clear_filter
)
filter_layout = QtWidgets.QHBoxLayout()
filter_layout.addWidget(filter_button)
filter_layout.addWidget(self.filter_edit)
filter_layout.addWidget(clear_button)
filter_layout.setSpacing(1)
#
# history table
#
self.table = QtWidgets.QTableWidget()
table_header = ['Timestamp', 'Comment']
self.table.setColumnCount(len(table_header))
self.table.setHorizontalHeaderLabels(table_header)
self.table.setSortingEnabled(True)
self.table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
self.table.horizontalHeader().setStretchLastSection(True)
self.table.horizontalHeader().sectionClicked.connect(self._update_table_row_colors_and_resize) # noqa: sectionClicked.connect
self.table.cellDoubleClicked.connect(self._table_double_click) # noqa: cellDoubleClicked.connect
self.table.keyPressEvent = self._table_key_press
for item in self.comments:
self._append_to_history_table(item['timestamp'], item['comment'])
self._update_table_row_colors_and_resize()
#
# main layout
#
layout = QtWidgets.QVBoxLayout()
layout.addWidget(QtWidgets.QLabel('Enter a new comment or select one from the history below'))
layout.addWidget(self.comment_textedit)
layout.addWidget(self.ok_button)
layout.addLayout(filter_layout)
layout.addWidget(self.table)
self.setLayout(layout)
geo = utils.screen_geometry(widget=self)
self.resize(int(geo.width()*0.4), int(geo.height()*0.6))
def _append_to_history_table(self, timestamp, comment):
index = self.table.rowCount()
self.table.insertRow(index)
self.table.setItem(index, 0, QtWidgets.QTableWidgetItem(timestamp))
self.table.setItem(index, 1, QtWidgets.QTableWidgetItem(comment))
def _apply_filter(self):
filter_text = self.filter_edit.text().lower()
if not filter_text and self.table.rowCount() == len(self.comments):
# all rows are already visible
return
self.table.setRowCount(0)
for item in self.comments:
if not filter_text or filter_text in item['timestamp'] or filter_text in item['comment'].lower():
self._append_to_history_table(item['timestamp'], item['comment'])
self._update_table_row_colors_and_resize()
def _clear_filter(self):
# clear the filter text, only if there is text written in the filter
if self.filter_edit.text():
self.filter_edit.setText('')
self._apply_filter()
def _clear_history(self):
if not self.comments or not prompt.yes_no('Clear the entire history?', default=False):
return
self.table.setRowCount(0)
self.comments = []
self._save_json()
def _load_json(self):
if not os.path.isfile(self.path):
# assume that this is a new file that will be created
return
with open(self.path, mode='rb') as fp:
try:
self.comments = json.load(fp)
except Exception as e:
prompt.warning(f'Error loading JSON file:\n{self.path}\n\n{e}')
def _prepend_and_close(self):
self.close()
if not self.text():
# no new comments were entered so there is nothing to save to the history file
return
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.comments.insert(0, {'timestamp': timestamp, 'comment': self.text()})
self._save_json()
def _save_json(self):
# ensure that the intermediate directories exist
root = os.path.dirname(self.path)
if root and not os.path.isdir(root):
os.makedirs(root)
with open(self.path, mode='wt') as fp:
json.dump(self.comments, fp, indent=2, ensure_ascii=False)
def _table_double_click(self, row, column): # noqa: parameter 'column' is not used
self.comment_textedit.setPlainText(self.table.item(row, 1).text())
def _table_key_press(self, event):
# CTRL+A pressed
if event.modifiers() == Qt.KeyboardModifier.ControlModifier and event.key() == Qt.Key.Key_A:
self.table.selectAll()
return
if event.key() != Qt.Key.Key_Delete:
return
# sort the selected rows in reverse order for the self.table.removeRow method below
selected = sorted([s.row() for s in self.table.selectionModel().selectedRows()], reverse=True)
if len(selected) == self.table.rowCount():
self._clear_history()
return
msg = 'this item' if len(selected) == 1 else f'these {len(selected)} items'
if not prompt.yes_no(f'Remove {msg} from the history?', default=False):
return
for index in selected:
self.table.removeRow(index)
del self.comments[index]
self._save_json()
def _update_table_row_colors_and_resize(self):
for row in range(self.table.rowCount()):
color = self.even_row_color if row % 2 else self.odd_row_color
try:
self.table.item(row, 0).setBackground(color)
self.table.item(row, 1).setBackground(color)
except AttributeError:
# non-reproducible bug
# sometimes the item in the row has a NoneTye
# possibly do to signaling issues?
pass
self.table.verticalHeader().resizeSections(QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
[docs] def text(self):
"""str: The text in the comment editor."""
return self.comment_textedit.toPlainText().strip()