KXmlGui

kshortcutseditordelegate.cpp
1 /*
2  This file is part of the KDE libraries
3  SPDX-FileCopyrightText: 1998 Mark Donohoe <[email protected]>
4  SPDX-FileCopyrightText: 1997 Nicolas Hadacek <[email protected]>
5  SPDX-FileCopyrightText: 1998 Matthias Ettrich <[email protected]>
6  SPDX-FileCopyrightText: 2001 Ellis Whitehead <[email protected]>
7  SPDX-FileCopyrightText: 2006 Hamish Rodda <[email protected]>
8  SPDX-FileCopyrightText: 2007 Roberto Raggi <[email protected]>
9  SPDX-FileCopyrightText: 2007 Andreas Hartmetz <[email protected]>
10  SPDX-FileCopyrightText: 2008 Michael Jansen <[email protected]>
11 
12  SPDX-License-Identifier: LGPL-2.0-or-later
13 */
14 #include "kshortcutsdialog_p.h"
15 
16 #include <QAction>
17 #include <QApplication>
18 #include <QHeaderView>
19 #include <QKeyEvent>
20 #include <QLabel>
21 #include <QPainter>
22 #include <QTreeWidgetItemIterator>
23 
24 KShortcutsEditorDelegate::KShortcutsEditorDelegate(QTreeWidget *parent, bool allowLetterShortcuts)
25  : KExtendableItemDelegate(parent),
26  m_allowLetterShortcuts(allowLetterShortcuts),
27  m_editor(nullptr)
28 {
29  Q_ASSERT(qobject_cast<QAbstractItemView *>(parent));
30 
31  QPixmap pixmap(16, 16);
32  pixmap.fill(QColor(Qt::transparent));
33  QPainter p(&pixmap);
34  QStyleOption option;
35  option.rect = pixmap.rect();
36 
37  bool isRtl = QApplication::isRightToLeft();
39  p.end();
40  setExtendPixmap(pixmap);
41 
42  pixmap.fill(QColor(Qt::transparent));
43  p.begin(&pixmap);
45  p.end();
46  setContractPixmap(pixmap);
47 
48  parent->installEventFilter(this);
49 
50  // Listen to activation signals
51  // connect(parent, SIGNAL(activated(QModelIndex)), this, SLOT(itemActivated(QModelIndex)));
52  connect(parent, &QAbstractItemView::clicked, this, &KShortcutsEditorDelegate::itemActivated);
53 
54  // Listen to collapse signals
55  connect(parent, &QTreeView::collapsed, this, &KShortcutsEditorDelegate::itemCollapsed);
56 }
57 
58 void KShortcutsEditorDelegate::stealShortcut(
59  const QKeySequence &seq,
60  QAction *action)
61 {
62  QTreeWidget *view = static_cast<QTreeWidget *>(parent());
63 
64  // Iterate over all items
66 
67  for (; (*it); ++it) {
68  KShortcutsEditorItem *item = dynamic_cast<KShortcutsEditorItem *>(*it);
69  if (item && item->data(0, ObjectRole).value<QObject *>() == action) {
70 
71  // We found the action, snapshot the current state. Steal the
72  // shortcut. We will save the change later.
73  const QList<QKeySequence> cut = action->shortcuts();
74  const QKeySequence primary = cut.isEmpty() ? QKeySequence() : cut.at(0);
75  const QKeySequence alternate = cut.size() <= 1 ? QKeySequence() : cut.at(1);
76 
77  if (primary.matches(seq) != QKeySequence::NoMatch
78  || seq.matches(primary) != QKeySequence::NoMatch) {
79  item->setKeySequence(LocalPrimary, QKeySequence());
80  }
81 
82  if (alternate.matches(seq) != QKeySequence::NoMatch
83  || seq.matches(alternate) != QKeySequence::NoMatch) {
84  item->setKeySequence(LocalAlternate, QKeySequence());
85  }
86  break;
87  }
88  }
89 
90 }
91 
92 QSize KShortcutsEditorDelegate::sizeHint(const QStyleOptionViewItem &option,
93  const QModelIndex &index) const
94 {
95  QSize ret(KExtendableItemDelegate::sizeHint(option, index));
96  ret.rheight() += 4;
97  return ret;
98 }
99 
100 //slot
101 void KShortcutsEditorDelegate::itemActivated(const QModelIndex &_index)
102 {
103  //As per our constructor our parent *is* a QTreeWidget
104  QTreeWidget *view = static_cast<QTreeWidget *>(parent());
105  QModelIndex index(_index);
106 
107  KShortcutsEditorItem *item = KShortcutsEditorPrivate::itemFromIndex(view, index);
108  if (!item) {
109  //that probably was a non-leaf (type() !=ActionItem) item
110  return;
111  }
112 
113  int column = index.column();
114  if (column == Name) {
115  // If user click in the name column activate the (Global|Local)Primary
116  // column if possible.
117  if (!view->header()->isSectionHidden(LocalPrimary)) {
118  column = LocalPrimary;
119  } else if (!view->header()->isSectionHidden(GlobalPrimary)) {
120  column = GlobalPrimary;
121  } else {
122  // do nothing.
123  }
124  index = index.sibling(index.row(), column);
126  }
127 
128  // Check if the models wants us to edit the item at index
129  if (!index.data(ShowExtensionIndicatorRole).toBool()) {
130  return;
131  }
132 
133  if (!isExtended(index)) {
134  //we only want maximum ONE extender open at any time.
135  if (m_editingIndex.isValid()) {
136  KShortcutsEditorItem *oldItem = KShortcutsEditorPrivate::itemFromIndex(view,
137  m_editingIndex);
138  Q_ASSERT(oldItem); //here we really expect nothing but a real KShortcutsEditorItem
139 
140  oldItem->setNameBold(false);
141  contractItem(m_editingIndex);
142  }
143 
144  m_editingIndex = index;
145  QWidget *viewport = static_cast<QAbstractItemView *>(parent())->viewport();
146 
147  if (column >= LocalPrimary && column <= GlobalAlternate) {
148  ShortcutEditWidget *editor = new ShortcutEditWidget(viewport,
149  index.data(DefaultShortcutRole).value<QKeySequence>(),
150  index.data(ShortcutRole).value<QKeySequence>(),
151  m_allowLetterShortcuts);
152  if (column == GlobalPrimary) {
153  QObject *action = index.data(ObjectRole).value<QObject *>();
154  editor->setAction(action);
155  editor->setMultiKeyShortcutsAllowed(false);
156  QString componentName = action->property("componentName").toString();
157  if (componentName.isEmpty()) {
158  componentName = QCoreApplication::applicationName();
159  }
160  editor->setComponentName(componentName);
161  }
162 
163  m_editor = editor;
164  // For global shortcuts check against the kde standard shortcuts
165  if (column == GlobalPrimary || column == GlobalAlternate) {
166  editor->setCheckForConflictsAgainst(
170  }
171 
172  editor->setCheckActionCollections(m_checkActionCollections);
173 
174  connect(editor, &ShortcutEditWidget::keySequenceChanged,
175  this, &KShortcutsEditorDelegate::keySequenceChanged);
176  connect(editor, &ShortcutEditWidget::stealShortcut,
177  this, &KShortcutsEditorDelegate::stealShortcut);
178 
179  } else if (column == RockerGesture) {
180  m_editor = new QLabel(QStringLiteral("A lame placeholder"), viewport);
181 
182  } else if (column == ShapeGesture) {
183  m_editor = new QLabel(QStringLiteral("<i>A towel</i>"), viewport);
184 
185  } else {
186  return;
187  }
188 
189  m_editor->installEventFilter(this);
190  item->setNameBold(true);
191  extendItem(m_editor, index);
192 
193  } else {
194  //the item is extended, and clicking on it again closes it
195  item->setNameBold(false);
196  contractItem(index);
198  m_editingIndex = QModelIndex();
199  m_editor = nullptr;
200  }
201 }
202 
203 //slot
204 void KShortcutsEditorDelegate::itemCollapsed(const QModelIndex &index)
205 {
206  if (!m_editingIndex.isValid()) {
207  return;
208  }
209 
210  const QAbstractItemModel *model = index.model();
211  for (int row = 0; row < model->rowCount(index); ++row) {
212  for (int col = 0; col < index.model()->columnCount(index); ++col) {
213  QModelIndex colIndex = model->index(row, col, index);
214 
215  if (colIndex == m_editingIndex) {
216  itemActivated(m_editingIndex); //this will *close* the item's editor because it's already open
217  }
218  }
219  }
220 }
221 
222 //slot
223 void KShortcutsEditorDelegate::hiddenBySearchLine(QTreeWidgetItem *item, bool hidden)
224 {
225  if (!hidden || !item) {
226  return;
227  }
228  QTreeWidget *view = static_cast<QTreeWidget *>(parent());
229  QTreeWidgetItem *editingItem = KShortcutsEditorPrivate::itemFromIndex(view, m_editingIndex);
230  if (editingItem == item) {
231  itemActivated(m_editingIndex); //this will *close* the item's editor because it's already open
232  }
233 }
234 
235 bool KShortcutsEditorDelegate::eventFilter(QObject *o, QEvent *e)
236 {
237  if (o == m_editor) {
238  //Prevent clicks in the empty part of the editor widget from closing the editor
239  //because they would propagate to the itemview and be interpreted as a click in
240  //an item's rect. That in turn would lead to an itemActivated() call, closing
241  //the current editor.
242 
243  switch (e->type()) {
247  return true;
248  default:
249  return false;
250  }
251  } else if (o == parent()) {
252  // Make left/right cursor keys switch items instead of operate the scroll bar
253  // (subclassing QtreeView/Widget would be cleaner but much more of a hassle)
254  // Note that in our case we know that the selection mode is SingleSelection,
255  // so we don't have to ask QAbstractItemView::selectionCommand() et al.
256 
257  if (e->type() != QEvent::KeyPress) {
258  return false;
259  }
260  QKeyEvent *ke = static_cast<QKeyEvent *>(e);
261  QTreeWidget *view = static_cast<QTreeWidget *>(parent());
262  QItemSelectionModel *selection = view->selectionModel();
263  QModelIndex index = selection->currentIndex();
264 
265  switch (ke->key()) {
266  case Qt::Key_Space:
267  case Qt::Key_Select:
268  // we are not using the standard "open editor" mechanism of QAbstractItemView,
269  // so let's emulate that here.
270  itemActivated(index);
271  return true;
272  case Qt::Key_Left:
273  index = index.sibling(index.row(), index.column() - 1);
274  break;
275  case Qt::Key_Right:
276  index = index.sibling(index.row(), index.column() + 1);
277  break;
278  default:
279  return false;
280  }
281  // a cursor key was pressed
282  if (index.isValid()) {
284  //### using PositionAtCenter for now;
285  // EnsureVisible has no effect which seems to be a bug.
287  }
288  return true;
289  }
290  return false;
291 }
292 
293 //slot
294 void KShortcutsEditorDelegate::keySequenceChanged(const QKeySequence &seq)
295 {
296  QVariant ret = QVariant::fromValue(seq);
297  emit shortcutChanged(ret, m_editingIndex);
298 }
299 
300 void KShortcutsEditorDelegate::setCheckActionCollections(
301  const QList<KActionCollection *> &checkActionCollections)
302 {
303  m_checkActionCollections = checkActionCollections;
304 }
305 
306 #if 0
307 //slot
308 void KShortcutsEditorDelegate::shapeGestureChanged(const KShapeGesture &gest)
309 {
310  //this is somewhat verbose because the gesture types are not "built in" to QVariant
311  QVariant ret = QVariant::fromValue(gest);
312  emit shortcutChanged(ret, m_editingIndex);
313 }
314 #endif
315 
316 #if 0
317 //slot
318 void KShortcutsEditorDelegate::rockerGestureChanged(const KRockerGesture &gest)
319 {
320  QVariant ret = QVariant::fromValue(gest);
321  emit shortcutChanged(ret, m_editingIndex);
322 }
323 #endif
324 
bool isRightToLeft()
virtual int rowCount(const QModelIndex &parent) const const =0
MouseButtonPress
QEvent::Type type() const const
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const =0
Check against global shortcuts.
QModelIndex currentIndex() const const
QItemSelectionModel * selectionModel() const const
void collapsed(const QModelIndex &index)
QList< QKeySequence > shortcuts() const const
T value() const const
const QList< QKeySequence > & cut()
int size() const const
Check with local shortcuts.
bool isValid() const const
QKeySequence::SequenceMatch matches(const QKeySequence &seq) const const
QVariant property(const char *name) const const
void installEventFilter(QObject *filterObj)
bool isSectionHidden(int logicalIndex) const const
bool isEmpty() const const
virtual void select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command)
bool isEmpty() const const
int row() const const
PE_IndicatorArrowLeft
int key() const const
Check against standard shortcuts.
virtual void scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint) override
QVariant fromValue(const T &value)
const QAbstractItemModel * model() const const
QVariant data(int role) const const
QStyle * style()
QModelIndex sibling(int row, int column) const const
virtual int columnCount(const QModelIndex &parent) const const =0
int column() const const
bool toBool() const const
virtual void drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const const =0
void clicked(const QModelIndex &index)
virtual void setCurrentIndex(const QModelIndex &index, QItemSelectionModel::SelectionFlags command)
QHeaderView * header() const const
transparent
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
QString toString() const const
Key_Space
QString applicationName()
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Wed Aug 12 2020 22:50:46 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.