File indexing completed on 2025-04-20 03:43:56
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 1998 Mark Donohoe <donohoe@kde.org> 0004 SPDX-FileCopyrightText: 1997 Nicolas Hadacek <hadacek@kde.org> 0005 SPDX-FileCopyrightText: 1998 Matthias Ettrich <ettrich@kde.org> 0006 SPDX-FileCopyrightText: 2001 Ellis Whitehead <ellis@kde.org> 0007 SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org> 0008 SPDX-FileCopyrightText: 2007 Roberto Raggi <roberto@kdevelop.org> 0009 SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com> 0010 SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz> 0011 0012 SPDX-License-Identifier: LGPL-2.0-or-later 0013 */ 0014 #include "kshortcutsdialog_p.h" 0015 0016 #include <QAction> 0017 #include <QApplication> 0018 #include <QHeaderView> 0019 #include <QKeyEvent> 0020 #include <QLabel> 0021 #include <QPainter> 0022 #include <QTreeWidgetItemIterator> 0023 0024 KShortcutsEditorDelegate::KShortcutsEditorDelegate(QTreeWidget *parent, bool allowLetterShortcuts) 0025 : KExtendableItemDelegate(parent) 0026 , m_allowLetterShortcuts(allowLetterShortcuts) 0027 { 0028 Q_ASSERT(qobject_cast<QAbstractItemView *>(parent)); 0029 0030 const QSize indicatorSize(16, 16); 0031 const qreal dpr = parent->devicePixelRatioF(); 0032 QPixmap pixmap(indicatorSize * dpr); 0033 0034 pixmap.fill(QColor(Qt::transparent)); 0035 pixmap.setDevicePixelRatio(dpr); 0036 QPainter p(&pixmap); 0037 QStyleOption option; 0038 option.rect = QRect(QPoint(0, 0), indicatorSize); 0039 0040 bool isRtl = QApplication::isRightToLeft(); 0041 QApplication::style()->drawPrimitive(isRtl ? QStyle::PE_IndicatorArrowLeft : QStyle::PE_IndicatorArrowRight, &option, &p); 0042 p.end(); 0043 setExtendPixmap(pixmap); 0044 0045 pixmap.fill(QColor(Qt::transparent)); 0046 pixmap.setDevicePixelRatio(dpr); 0047 p.begin(&pixmap); 0048 QApplication::style()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &option, &p); 0049 p.end(); 0050 setContractPixmap(pixmap); 0051 0052 parent->installEventFilter(this); 0053 0054 // Listen to activation signals 0055 // connect(parent, SIGNAL(activated(QModelIndex)), this, SLOT(itemActivated(QModelIndex))); 0056 connect(parent, &QAbstractItemView::clicked, this, &KShortcutsEditorDelegate::itemActivated); 0057 0058 // Listen to collapse signals 0059 connect(parent, &QTreeView::collapsed, this, &KShortcutsEditorDelegate::itemCollapsed); 0060 } 0061 0062 void KShortcutsEditorDelegate::stealShortcut(const QKeySequence &seq, QAction *action) 0063 { 0064 QTreeWidget *view = static_cast<QTreeWidget *>(parent()); 0065 0066 // Iterate over all items 0067 QTreeWidgetItemIterator it(view, QTreeWidgetItemIterator::NoChildren); 0068 0069 for (; (*it); ++it) { 0070 KShortcutsEditorItem *item = dynamic_cast<KShortcutsEditorItem *>(*it); 0071 if (item && item->data(0, ObjectRole).value<QObject *>() == action) { 0072 // We found the action, snapshot the current state. Steal the 0073 // shortcut. We will save the change later. 0074 const QList<QKeySequence> cut = action->shortcuts(); 0075 const QKeySequence primary = cut.isEmpty() ? QKeySequence() : cut.at(0); 0076 const QKeySequence alternate = cut.size() <= 1 ? QKeySequence() : cut.at(1); 0077 0078 if (primary.matches(seq) != QKeySequence::NoMatch // 0079 || seq.matches(primary) != QKeySequence::NoMatch) { 0080 item->setKeySequence(LocalPrimary, QKeySequence()); 0081 } 0082 0083 if (alternate.matches(seq) != QKeySequence::NoMatch // 0084 || seq.matches(alternate) != QKeySequence::NoMatch) { 0085 item->setKeySequence(LocalAlternate, QKeySequence()); 0086 } 0087 break; 0088 } 0089 } 0090 } 0091 0092 QSize KShortcutsEditorDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const 0093 { 0094 QSize ret(KExtendableItemDelegate::sizeHint(option, index)); 0095 ret.rheight() += 4; 0096 return ret; 0097 } 0098 0099 // slot 0100 void KShortcutsEditorDelegate::itemActivated(const QModelIndex &_index) 0101 { 0102 // As per our constructor our parent *is* a QTreeWidget 0103 QTreeWidget *view = static_cast<QTreeWidget *>(parent()); 0104 QModelIndex index(_index); 0105 0106 KShortcutsEditorItem *item = KShortcutsEditorPrivate::itemFromIndex(view, index); 0107 if (!item) { 0108 // that probably was a non-leaf (type() !=ActionItem) item 0109 return; 0110 } 0111 0112 int column = index.column(); 0113 if (column == Name) { 0114 // If user click in the name column activate the (Global|Local)Primary 0115 // column if possible. 0116 if (!view->header()->isSectionHidden(LocalPrimary)) { 0117 column = LocalPrimary; 0118 } else if (!view->header()->isSectionHidden(GlobalPrimary)) { 0119 column = GlobalPrimary; 0120 } else { 0121 // do nothing. 0122 } 0123 index = index.sibling(index.row(), column); 0124 view->selectionModel()->select(index, QItemSelectionModel::SelectCurrent); 0125 } 0126 0127 // Check if the models wants us to edit the item at index 0128 if (!index.data(ShowExtensionIndicatorRole).toBool()) { 0129 return; 0130 } 0131 0132 if (!isExtended(index)) { 0133 // we only want maximum ONE extender open at any time. 0134 if (m_editingIndex.isValid()) { 0135 KShortcutsEditorItem *oldItem = KShortcutsEditorPrivate::itemFromIndex(view, m_editingIndex); 0136 Q_ASSERT(oldItem); // here we really expect nothing but a real KShortcutsEditorItem 0137 0138 oldItem->setNameBold(false); 0139 contractItem(m_editingIndex); 0140 } 0141 0142 m_editingIndex = index; 0143 QWidget *viewport = static_cast<QAbstractItemView *>(parent())->viewport(); 0144 0145 if (column >= LocalPrimary && column <= GlobalAlternate) { 0146 ShortcutEditWidget *editor = new ShortcutEditWidget(viewport, 0147 index.data(DefaultShortcutRole).value<QKeySequence>(), 0148 index.data(ShortcutRole).value<QKeySequence>(), 0149 m_allowLetterShortcuts); 0150 if (column == GlobalPrimary) { 0151 QObject *action = index.data(ObjectRole).value<QObject *>(); 0152 editor->setAction(action); 0153 editor->setMultiKeyShortcutsAllowed(false); 0154 QString componentName = action->property("componentName").toString(); 0155 if (componentName.isEmpty()) { 0156 componentName = QCoreApplication::applicationName(); 0157 } 0158 editor->setComponentName(componentName); 0159 } 0160 0161 m_editor = editor; 0162 // For global shortcuts check against the kde standard shortcuts 0163 if (column == GlobalPrimary || column == GlobalAlternate) { 0164 editor->setCheckForConflictsAgainst(KKeySequenceWidget::LocalShortcuts | KKeySequenceWidget::GlobalShortcuts 0165 | KKeySequenceWidget::StandardShortcuts); 0166 } 0167 0168 editor->setCheckActionCollections(m_checkActionCollections); 0169 0170 connect(editor, &ShortcutEditWidget::keySequenceChanged, this, &KShortcutsEditorDelegate::keySequenceChanged); 0171 connect(editor, &ShortcutEditWidget::stealShortcut, this, &KShortcutsEditorDelegate::stealShortcut); 0172 0173 } else if (column == RockerGesture) { 0174 m_editor = new QLabel(QStringLiteral("A lame placeholder"), viewport); 0175 0176 } else if (column == ShapeGesture) { 0177 m_editor = new QLabel(QStringLiteral("<i>A towel</i>"), viewport); 0178 0179 } else { 0180 return; 0181 } 0182 0183 m_editor->installEventFilter(this); 0184 item->setNameBold(true); 0185 extendItem(m_editor, index); 0186 0187 } else { 0188 // the item is extended, and clicking on it again closes it 0189 item->setNameBold(false); 0190 contractItem(index); 0191 view->selectionModel()->select(index, QItemSelectionModel::Clear); 0192 m_editingIndex = QModelIndex(); 0193 m_editor = nullptr; 0194 } 0195 } 0196 0197 // slot 0198 void KShortcutsEditorDelegate::itemCollapsed(const QModelIndex &index) 0199 { 0200 if (!m_editingIndex.isValid()) { 0201 return; 0202 } 0203 0204 const QAbstractItemModel *model = index.model(); 0205 for (int row = 0; row < model->rowCount(index); ++row) { 0206 for (int col = 0; col < index.model()->columnCount(index); ++col) { 0207 QModelIndex colIndex = model->index(row, col, index); 0208 0209 if (colIndex == m_editingIndex) { 0210 itemActivated(m_editingIndex); // this will *close* the item's editor because it's already open 0211 } 0212 } 0213 } 0214 } 0215 0216 // slot 0217 void KShortcutsEditorDelegate::hiddenBySearchLine(QTreeWidgetItem *item, bool hidden) 0218 { 0219 if (!hidden || !item) { 0220 return; 0221 } 0222 QTreeWidget *view = static_cast<QTreeWidget *>(parent()); 0223 QTreeWidgetItem *editingItem = KShortcutsEditorPrivate::itemFromIndex(view, m_editingIndex); 0224 if (editingItem == item) { 0225 itemActivated(m_editingIndex); // this will *close* the item's editor because it's already open 0226 } 0227 } 0228 0229 bool KShortcutsEditorDelegate::eventFilter(QObject *o, QEvent *e) 0230 { 0231 if (o == m_editor) { 0232 // Prevent clicks in the empty part of the editor widget from closing the editor 0233 // because they would propagate to the itemview and be interpreted as a click in 0234 // an item's rect. That in turn would lead to an itemActivated() call, closing 0235 // the current editor. 0236 0237 switch (e->type()) { 0238 case QEvent::MouseButtonPress: 0239 case QEvent::MouseButtonRelease: 0240 case QEvent::MouseButtonDblClick: 0241 return true; 0242 default: 0243 return false; 0244 } 0245 } else if (o == parent()) { 0246 // Make left/right cursor keys switch items instead of operate the scroll bar 0247 // (subclassing QtreeView/Widget would be cleaner but much more of a hassle) 0248 // Note that in our case we know that the selection mode is SingleSelection, 0249 // so we don't have to ask QAbstractItemView::selectionCommand() et al. 0250 0251 if (e->type() != QEvent::KeyPress) { 0252 return false; 0253 } 0254 QKeyEvent *ke = static_cast<QKeyEvent *>(e); 0255 QTreeWidget *view = static_cast<QTreeWidget *>(parent()); 0256 QItemSelectionModel *selection = view->selectionModel(); 0257 QModelIndex index = selection->currentIndex(); 0258 0259 switch (ke->key()) { 0260 case Qt::Key_Space: 0261 case Qt::Key_Select: 0262 // we are not using the standard "open editor" mechanism of QAbstractItemView, 0263 // so let's emulate that here. 0264 itemActivated(index); 0265 return true; 0266 case Qt::Key_Left: 0267 index = index.sibling(index.row(), index.column() - 1); 0268 break; 0269 case Qt::Key_Right: 0270 index = index.sibling(index.row(), index.column() + 1); 0271 break; 0272 default: 0273 return false; 0274 } 0275 // a cursor key was pressed 0276 if (index.isValid()) { 0277 selection->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); 0278 //### using PositionAtCenter for now; 0279 // EnsureVisible has no effect which seems to be a bug. 0280 view->scrollTo(index, QAbstractItemView::PositionAtCenter); 0281 } 0282 return true; 0283 } 0284 return false; 0285 } 0286 0287 // slot 0288 void KShortcutsEditorDelegate::keySequenceChanged(const QKeySequence &seq) 0289 { 0290 QVariant ret = QVariant::fromValue(seq); 0291 Q_EMIT shortcutChanged(ret, m_editingIndex); 0292 } 0293 0294 void KShortcutsEditorDelegate::setCheckActionCollections(const QList<KActionCollection *> &checkActionCollections) 0295 { 0296 m_checkActionCollections = checkActionCollections; 0297 } 0298 0299 #if 0 0300 //slot 0301 void KShortcutsEditorDelegate::shapeGestureChanged(const KShapeGesture &gest) 0302 { 0303 //this is somewhat verbose because the gesture types are not "built in" to QVariant 0304 QVariant ret = QVariant::fromValue(gest); 0305 Q_EMIT shortcutChanged(ret, m_editingIndex); 0306 } 0307 #endif 0308 0309 #if 0 0310 //slot 0311 void KShortcutsEditorDelegate::rockerGestureChanged(const KRockerGesture &gest) 0312 { 0313 QVariant ret = QVariant::fromValue(gest); 0314 Q_EMIT shortcutChanged(ret, m_editingIndex); 0315 } 0316 #endif