Allow time log entries to be edited directly in their table cells
This commit is contained in:
parent
0bc5a37605
commit
a7d2c5500e
3 changed files with 114 additions and 20 deletions
|
|
@ -1,3 +1,7 @@
|
||||||
|
# 0.4.1
|
||||||
|
|
||||||
|
* Allow time log entries to be edited directly in their table cells
|
||||||
|
|
||||||
# 0.4
|
# 0.4
|
||||||
|
|
||||||
* Remove screenshot tool
|
* Remove screenshot tool
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,9 @@ class TimeLogDialog(QDialog):
|
||||||
self._db = db
|
self._db = db
|
||||||
self._date_iso = date_iso
|
self._date_iso = date_iso
|
||||||
self._current_entry_id: Optional[int] = None
|
self._current_entry_id: Optional[int] = None
|
||||||
|
# Guard flag used when repopulating the table so we don’t treat
|
||||||
|
# programmatic item changes as user edits.
|
||||||
|
self._reloading_entries: bool = False
|
||||||
|
|
||||||
self.setWindowTitle(strings._("time_log_for").format(date=date_iso))
|
self.setWindowTitle(strings._("time_log_for").format(date=date_iso))
|
||||||
self.resize(900, 600)
|
self.resize(900, 600)
|
||||||
|
|
@ -264,6 +267,8 @@ class TimeLogDialog(QDialog):
|
||||||
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||||
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
|
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||||
self.table.itemSelectionChanged.connect(self._on_row_selected)
|
self.table.itemSelectionChanged.connect(self._on_row_selected)
|
||||||
|
# When a cell is edited inline, commit the change back to the DB.
|
||||||
|
self.table.itemChanged.connect(self._on_table_item_changed)
|
||||||
root.addWidget(self.table, 1)
|
root.addWidget(self.table, 1)
|
||||||
|
|
||||||
# --- Close button
|
# --- Close button
|
||||||
|
|
@ -293,6 +298,14 @@ class TimeLogDialog(QDialog):
|
||||||
self.activity_edit.setCompleter(completer)
|
self.activity_edit.setCompleter(completer)
|
||||||
|
|
||||||
def _reload_entries(self) -> None:
|
def _reload_entries(self) -> None:
|
||||||
|
"""Reload the table from the database.
|
||||||
|
|
||||||
|
While we are repopulating the QTableWidget we temporarily disable the
|
||||||
|
itemChanged handler so that programmatic changes do not get written
|
||||||
|
back to the database.
|
||||||
|
"""
|
||||||
|
self._reloading_entries = True
|
||||||
|
try:
|
||||||
rows = self._db.time_log_for_date(self._date_iso)
|
rows = self._db.time_log_for_date(self._date_iso)
|
||||||
self.table.setRowCount(len(rows))
|
self.table.setRowCount(len(rows))
|
||||||
for row_idx, r in enumerate(rows):
|
for row_idx, r in enumerate(rows):
|
||||||
|
|
@ -315,6 +328,8 @@ class TimeLogDialog(QDialog):
|
||||||
self.table.setItem(row_idx, 1, item_act)
|
self.table.setItem(row_idx, 1, item_act)
|
||||||
self.table.setItem(row_idx, 2, item_note)
|
self.table.setItem(row_idx, 2, item_note)
|
||||||
self.table.setItem(row_idx, 3, item_hours)
|
self.table.setItem(row_idx, 3, item_hours)
|
||||||
|
finally:
|
||||||
|
self._reloading_entries = False
|
||||||
|
|
||||||
self._current_entry_id = None
|
self._current_entry_id = None
|
||||||
self.delete_btn.setEnabled(False)
|
self.delete_btn.setEnabled(False)
|
||||||
|
|
@ -405,6 +420,81 @@ class TimeLogDialog(QDialog):
|
||||||
self.note.setText(note)
|
self.note.setText(note)
|
||||||
self.hours_spin.setValue(hours)
|
self.hours_spin.setValue(hours)
|
||||||
|
|
||||||
|
def _on_table_item_changed(self, item: QTableWidgetItem) -> None:
|
||||||
|
"""Commit inline edits in the table back to the database.
|
||||||
|
|
||||||
|
Editing a cell should behave like selecting that row and pressing
|
||||||
|
the Add/Update button, so we reuse the same validation and DB logic.
|
||||||
|
"""
|
||||||
|
if self._reloading_entries:
|
||||||
|
# Ignore changes that come from _reload_entries().
|
||||||
|
return
|
||||||
|
|
||||||
|
if item is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
row = item.row()
|
||||||
|
|
||||||
|
proj_item = self.table.item(row, 0)
|
||||||
|
act_item = self.table.item(row, 1)
|
||||||
|
note_item = self.table.item(row, 2)
|
||||||
|
hours_item = self.table.item(row, 3)
|
||||||
|
|
||||||
|
if proj_item is None or act_item is None or hours_item is None:
|
||||||
|
# Incomplete row – nothing to do.
|
||||||
|
return
|
||||||
|
|
||||||
|
# Recover the entry id from the hidden UserRole on the project cell
|
||||||
|
entry_id = proj_item.data(Qt.ItemDataRole.UserRole)
|
||||||
|
self._current_entry_id = int(entry_id) if entry_id is not None else None
|
||||||
|
|
||||||
|
# Push values into the editors (similar to _on_row_selected).
|
||||||
|
proj_name = proj_item.text()
|
||||||
|
act_name = act_item.text()
|
||||||
|
note_text = note_item.text() if note_item is not None else ""
|
||||||
|
hours_text = hours_item.text()
|
||||||
|
|
||||||
|
# Set project combo by name, creating a project on the fly if needed.
|
||||||
|
idx = self.project_combo.findText(proj_name, Qt.MatchFixedString)
|
||||||
|
if idx < 0 and proj_name:
|
||||||
|
# Allow creating a new project directly from the table.
|
||||||
|
proj_id = self._db.add_project(proj_name)
|
||||||
|
self._reload_projects()
|
||||||
|
idx = self.project_combo.findData(proj_id)
|
||||||
|
if idx >= 0:
|
||||||
|
self.project_combo.setCurrentIndex(idx)
|
||||||
|
else:
|
||||||
|
self.project_combo.setCurrentIndex(-1)
|
||||||
|
|
||||||
|
self.activity_edit.setText(act_name)
|
||||||
|
self.note.setText(note_text)
|
||||||
|
|
||||||
|
# Parse hours; if invalid, show the same style of warning as elsewhere.
|
||||||
|
try:
|
||||||
|
hours = float(hours_text)
|
||||||
|
except ValueError:
|
||||||
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
strings._("invalid_time_title"),
|
||||||
|
strings._("invalid_time_message"),
|
||||||
|
)
|
||||||
|
# Reset table back to the last known-good state.
|
||||||
|
self._reload_entries()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.hours_spin.setValue(hours)
|
||||||
|
|
||||||
|
# Mirror button state to reflect whether we're updating or adding.
|
||||||
|
if self._current_entry_id is None:
|
||||||
|
self.delete_btn.setEnabled(False)
|
||||||
|
self.add_update_btn.setText(strings._("add_time_entry"))
|
||||||
|
else:
|
||||||
|
self.delete_btn.setEnabled(True)
|
||||||
|
self.add_update_btn.setText(strings._("update_time_entry"))
|
||||||
|
|
||||||
|
# Finally, reuse the existing validation + DB logic.
|
||||||
|
self._on_add_or_update()
|
||||||
|
|
||||||
def _on_delete_entry(self) -> None:
|
def _on_delete_entry(self) -> None:
|
||||||
if self._current_entry_id is None:
|
if self._current_entry_id is None:
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "bouquin"
|
name = "bouquin"
|
||||||
version = "0.4"
|
version = "0.4.1"
|
||||||
description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher."
|
description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher."
|
||||||
authors = ["Miguel Jacq <mig@mig5.net>"]
|
authors = ["Miguel Jacq <mig@mig5.net>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue