Better fix for the Qt book list phantom edits issue

The phantom edits are happening because of the mirroring with the pin
view. Apparently in some update Qt has started triggering edits on
currentChanged events. Sigh.

For the moment we disable editing of cells in the mirrored view when it
is hidden this worksaround the problem for most people, need a better
fix for when the view is being used.
This commit is contained in:
Kovid Goyal 2025-02-10 07:52:16 +05:30
parent b56849623f
commit f6167d7f0e
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 61 additions and 49 deletions

View File

@ -206,23 +206,13 @@ def get_val_for_textlike_columns(index_):
class StyledItemDelegate(QStyledItemDelegate):
'''
When closing an editor and opening another, Qt sometimes picks what appears
to be a random line and column for the second editor. This function checks
that the current index for a new editor is the same as the current view. If
it isn't then the editor shouldn't be opened.
Set the flag ignore_kb_mods_on_edit before opening an editor if you don't
want keyboard modifiers taken into account, for example when using Shift-Tab
as a backtab when editing cells. This prevents opening dialogs by mistake.
See giu2.library.views.closeEditor() for an example.
'''
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.table_widget = args[0]
# Set this to True here. It is up the the subclasses to set it to False if needed.
self.is_editable_with_tab = True
self.ignore_kb_mods_on_edit = False
is_editable_with_tab = True # sub-classes set to False is needed
ignore_kb_mods_on_edit = False
def createEditor(self, parent, option, index):
current_indices = [self.table_widget.currentIndex()]

View File

@ -449,7 +449,7 @@ class BooksView(QTableView): # {{{
for wv in self, self.pin_view:
wv.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
wv.setSortingEnabled(True)
self.selectionModel().currentRowChanged.connect(self._model.current_changed, type=Qt.ConnectionType.QueuedConnection)
self.selectionModel().currentRowChanged.connect(self.on_current_row_change)
self.selectionModel().selectionChanged.connect(self.selection_changed.emit)
self.preserve_state = partial(PreserveViewState, self)
self.marked_changed_listener = FunctionDispatcher(self.marked_changed)
@ -1637,48 +1637,65 @@ class BooksView(QTableView): # {{{
def close(self):
self._model.close()
def is_index_editable_with_tab(self, index) -> bool:
if not index.isValid():
return False
col = self.column_map[index.column()]
m = self.model()
if m.is_custom_column(col):
# Don't try to open editors implemented by dialogs such as
# markdown, composites and comments
return self.itemDelegateForIndex(index).is_editable_with_tab
return bool(m.flags(index) & Qt.ItemFlag.ItemIsEditable)
def closeEditor(self, editor, hint):
# As of Qt 6.7.2, for some reason, Qt opens the next editor after
# closing this editor and then immediately closes it again. So
# workaround the bug by opening the editor again after an event loop
# tick.
# We want to implement our own go to next/previous cell behavior
# so do it here.
orig = self.currentIndex()
move_by = None
do_move = False
delta = 1
if hint is QAbstractItemDelegate.EndEditHint.EditNextItem:
move_by = QAbstractItemView.CursorAction.MoveNext
do_move = True
elif hint is QAbstractItemDelegate.EndEditHint.EditPreviousItem:
move_by = QAbstractItemView.CursorAction.MovePrevious
if move_by is not None:
hint = QAbstractItemDelegate.EndEditHint.NoHint
super().closeEditor(editor, hint)
if move_by is not None and self.currentIndex() == orig and self.state() is not QAbstractItemView.State.EditingState:
# Skip over columns that aren't editable or are implemented by a dialog
m = self._model
while True:
index = self.moveCursor(move_by, Qt.KeyboardModifier.NoModifier)
if not index.isValid():
break
self.setCurrentIndex(index)
col = self.column_map[index.column()]
if m.is_custom_column(col):
do_move = True
delta = -1
super().closeEditor(editor, QAbstractItemDelegate.EndEditHint.NoHint)
self.selectionModel().setCurrentIndex(orig, QItemSelectionModel.SelectionFlag.NoUpdate)
if do_move:
QTimer.singleShot(0, lambda: self.edit_next_cell(orig, delta))
def on_current_row_change(self, current, previous):
self._model.current_changed(current, previous)
def edit(self, index, trigger=QAbstractItemView.EditTrigger.AllEditTriggers, event=None):
edited = super().edit(index, trigger, event)
return edited
def edit_next_cell(self, current, delta=1):
m = self.model()
idx = m.index(current.row(), current.column(), current.parent())
while True:
col = idx.column() + delta
if col < 0 or col >= len(self.column_map):
return
colname = self.column_map[col]
idx = m.index(current.row(), col, current.parent())
if m.is_custom_column(colname):
if self.itemDelegateForIndex(idx).is_editable_with_tab:
# Don't try to open editors implemented by dialogs such as
# markdown, composites and comments
if self.itemDelegateForIndex(index).is_editable_with_tab:
break
elif m.flags(index) & Qt.ItemFlag.ItemIsEditable:
# Standard editable column
break
if index.isValid():
def edit():
if index.isValid():
self.setCurrentIndex(index)
# Tell the delegate to ignore keyboard modifiers in case
# Shift-Tab is being used to move the cell.
d = self.itemDelegateForIndex(index)
if d is not None:
d.ignore_kb_mods_on_edit = True
self.edit(index)
QTimer.singleShot(0, edit)
elif m.flags(idx) & Qt.ItemFlag.ItemIsEditable:
break
if idx.isValid():
# Tell the delegate to ignore keyboard modifiers in case
# Shift-Tab is being used to move the cell.
d = self.itemDelegateForIndex(idx)
if d is not None:
d.ignore_kb_mods_on_edit = True
self.setCurrentIndex(idx)
self.edit(idx)
def set_editable(self, editable, supports_backloading):
self._model.set_editable(editable)

View File

@ -2,7 +2,7 @@
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from qt.core import QSplitter, QTableView
from qt.core import QAbstractItemView, QSplitter, QTableView
from calibre.gui2 import gprefs
from calibre.gui2.library import DEFAULT_SORT
@ -17,6 +17,11 @@ class PinTableView(QTableView):
self.splitter = None
self.disable_save_state = False
def edit(self, index, trigger=QAbstractItemView.EditTrigger.AllEditTriggers, event=None):
if not self.isVisible():
return False
return super().edit(index, trigger, event)
@property
def column_map(self):
return self.books_view.column_map