Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
actioneditor.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant reason:default
4
7#include "iconloader_p.h"
17
18#include <QtDesigner/abstractformeditor.h>
19#include <QtDesigner/abstractpropertyeditor.h>
20#include <QtDesigner/propertysheet.h>
21#include <QtDesigner/qextensionmanager.h>
22#include <QtDesigner/abstractmetadatabase.h>
23#include <QtDesigner/abstractsettings.h>
24
25#include <QtWidgets/qmenu.h>
26#include <QtWidgets/qtoolbar.h>
27#include <QtWidgets/qsplitter.h>
28#include <QtWidgets/qapplication.h>
29#if QT_CONFIG(clipboard)
30#include <QtGui/qclipboard.h>
31#endif
32#include <QtWidgets/qitemdelegate.h>
33#include <QtWidgets/qboxlayout.h>
34#include <QtWidgets/qlineedit.h>
35#include <QtWidgets/qlabel.h>
36#include <QtWidgets/qpushbutton.h>
37#include <QtWidgets/qtoolbutton.h>
38
39#include <QtGui/qaction.h>
40#include <QtGui/qactiongroup.h>
41#include <QtGui/qevent.h>
42#include <QtGui/qpainter.h>
43
44#include <QtCore/qitemselectionmodel.h>
45#include <QtCore/qregularexpression.h>
46#include <QtCore/qdebug.h>
47#include <QtCore/qbuffer.h>
48
50
51using namespace Qt::StringLiterals;
52
53static constexpr auto actionEditorViewModeKey = "ActionEditorViewMode"_L1;
54
55static constexpr auto iconPropertyC = "icon"_L1;
56static constexpr auto shortcutPropertyC = "shortcut"_L1;
57static constexpr auto menuRolePropertyC = "menuRole"_L1;
58static constexpr auto toolTipPropertyC = "toolTip"_L1;
59static constexpr auto checkablePropertyC = "checkable"_L1;
60static constexpr auto objectNamePropertyC = "objectName"_L1;
61static constexpr auto textPropertyC = "text"_L1;
62
63namespace qdesigner_internal {
64//-------- ActionGroupDelegate
66{
67public:
68 ActionGroupDelegate(QObject *parent)
70
71 void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
72 {
73 if (option.state & QStyle::State_Selected)
74 painter->fillRect(option.rect, option.palette.highlight());
75
76 QItemDelegate::paint(painter, option, index);
77 }
78
79 void drawFocus(QPainter *, const QStyleOptionViewItem &, const QRect &) const override {}
80};
81
82//-------- ActionEditor
84
87 m_core(core),
88 m_actionGroups(nullptr),
90 m_actionNew(new QAction(tr("New..."), this)),
91 m_actionEdit(new QAction(tr("Edit..."), this)),
92 m_actionNavigateToSlot(new QAction(tr("Go to slot..."), this)),
93#if QT_CONFIG(clipboard)
94 m_actionCopy(new QAction(tr("Copy"), this)),
95 m_actionCut(new QAction(tr("Cut"), this)),
96 m_actionPaste(new QAction(tr("Paste"), this)),
97#endif
98 m_actionSelectAll(new QAction(tr("Select all"), this)),
99 m_actionDelete(new QAction(tr("Delete"), this)),
100 m_viewModeGroup(new QActionGroup(this)),
101 m_iconViewAction(nullptr),
102 m_listViewAction(nullptr),
103 m_filterWidget(nullptr)
104{
107 setWindowTitle(tr("Actions"));
108
109 QVBoxLayout *l = new QVBoxLayout(this);
111 l->setSpacing(0);
112
114 toolbar->setIconSize(QSize(22, 22));
117 // edit actions
119 "filenew.png"_L1);
121 m_actionNew->setEnabled(false);
124
126
127#if QT_CONFIG(clipboard)
128 m_actionCut->setEnabled(false);
131 "editcut.png"_L1);
133
134 m_actionCopy->setEnabled(false);
137 "editcopy.png"_L1);
140
143 "editpaste.png"_L1);
146#endif
147
148 m_actionEdit->setEnabled(false);
150
152
154 "editdelete.png"_L1);
159
160 // Toolbutton with menu containing action group for detailed/icon view. Steal the icons from the file dialog.
161 //
163 toolbar->addWidget(createConfigureMenuButton(tr("Configure Action Editor"), &configureMenu));
164
171
172 m_listViewAction = m_viewModeGroup->addAction(tr("Detailed View"));
177 // filter
180 filterLayout->setContentsMargins(0, 0, 0, 0);
189
190 // main layout
193
196
197#if 0 // ### implement me
207#endif
208
211
214 // make it possible for vs integration to reimplement edit action dialog
216
219
222
224
227}
228
229// Utility to create a configure button with menu for usage on toolbars
244
249
251{
252 return m_actionNew;
253}
254
256{
257 return m_actionDelete;
258}
259
264
266{
267 if (formWindow != nullptr && formWindow->mainContainer() == nullptr)
268 formWindow = nullptr;
269
270 // we do NOT rely on this function to update the action editor
272 return;
273
274 if (m_formWindow != nullptr) {
276 for (QAction *action : actionList)
278 }
279
281
283
284 m_actionEdit->setEnabled(false);
285#if QT_CONFIG(clipboard)
286 m_actionCopy->setEnabled(false);
287 m_actionCut->setEnabled(false);
288#endif
290
291 if (!formWindow || !formWindow->mainContainer()) {
292 m_actionNew->setEnabled(false);
294 return;
295 }
296
297 m_actionNew->setEnabled(true);
299
301 for (QAction *action : actionList)
302 if (!action->isSeparator() && core()->metaDataBase()->item(action) != nullptr) {
303 // Show unless it has a menu. However, listen for change on menu actions also as it might be removed
304 if (!action->menu())
307 }
308
310}
311
313{
314 const bool hasSelection = !selected.indexes().isEmpty();
315#if QT_CONFIG(clipboard)
318#endif
320}
321
323{
325 if (m_withinSelectAction || fw == nullptr)
326 return;
327
328 const bool hasCurrentAction = action != nullptr;
330
331 if (!action) {
333 return;
334 }
335
337
338 // Check if we have at least one associated QWidget:
341 [](QObject *obj) {
342 return qobject_cast<QWidget *>(obj) != nullptr;
343 });
344 if (it == associatedObjects.cend()) {
345 // Special case: action not in object tree. Deselect all and set in property editor
346 fw->clearSelection(false);
347 if (oi)
350 } else {
351 if (oi)
353 }
354}
355
357{
359 Q_ASSERT(action != nullptr);
360
362 const int row = model->findAction(action);
363 if (row == -1) {
364 if (action->menu() == nullptr) // action got its menu deleted, create item
366 } else if (action->menu() != nullptr) { // action got its menu created, remove item
368 } else {
369 // action text or icon changed, update item
370 model->update(row);
371 }
372}
373
375{
376 return m_core;
377}
378
380{
381 return m_filter;
382}
383
385{
386 m_filter = f;
388}
389
390// Set changed state of icon property, reset when icon is cleared
391static void refreshIconPropertyChanged(const QAction *action, QDesignerPropertySheetExtension *sheet)
392{
393 sheet->setChanged(sheet->indexOf(iconPropertyC), !action->icon().isNull());
394}
395
412
414{
416 action->setParent(nullptr);
417
419
420 const int row = m_actionView->model()->findAction(action);
421 if (row != -1)
423}
424
425// Set an initial property and mark it as changed in the sheet
426static void setInitialProperty(QDesignerPropertySheetExtension *sheet, const QString &name, const QVariant &value)
427{
428 const int index = sheet->indexOf(name);
429 Q_ASSERT(index != -1);
430 sheet->setProperty(index, value);
431 sheet->setChanged(index, true);
432}
433
435{
436 NewActionDialog dlg(this);
437 dlg.setWindowTitle(tr("New action"));
438
439 if (dlg.exec() == QDialog::Accepted) {
446
450
453
456
458
460
462 cmd->init(action);
464 }
465}
466
467// return a FormWindow command to apply an icon or a reset command in case it
468// is empty.
469
470static QDesignerFormWindowCommand *setIconPropertyCommand(const PropertySheetIconValue &newIcon, QAction *action, QDesignerFormWindowInterface *fw)
471{
472 const QString iconProperty = iconPropertyC;
473 if (newIcon.isEmpty()) {
474 ResetPropertyCommand *cmd = new ResetPropertyCommand(fw);
475 cmd->init(action, iconProperty);
476 return cmd;
477 }
478 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
479 cmd->init(action, iconProperty, QVariant::fromValue(newIcon));
480 return cmd;
481}
482
483// return a FormWindow command to apply a QKeySequence or a reset command
484// in case it is empty.
485
486static QDesignerFormWindowCommand *setKeySequencePropertyCommand(const PropertySheetKeySequenceValue &ks, QAction *action, QDesignerFormWindowInterface *fw)
487{
488 const QString shortcutProperty = shortcutPropertyC;
489 if (ks.value().isEmpty()) {
490 ResetPropertyCommand *cmd = new ResetPropertyCommand(fw);
491 cmd->init(action, shortcutProperty);
492 return cmd;
493 }
494 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
495 cmd->init(action, shortcutProperty, QVariant::fromValue(ks));
496 return cmd;
497}
498
499// return a FormWindow command to apply a POD value or reset command in case
500// it is equal to the default value.
501
502template <class T>
503QDesignerFormWindowCommand *setPropertyCommand(const QString &name, T value, T defaultValue,
504 QObject *o, QDesignerFormWindowInterface *fw)
505{
506 if (value == defaultValue) {
507 ResetPropertyCommand *cmd = new ResetPropertyCommand(fw);
508 cmd->init(o, name);
509 return cmd;
510 }
511 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
512 cmd->init(o, name, QVariant(value));
513 return cmd;
514}
515
516// Return the text value of a string property via PropertySheetStringValue
517static inline QString textPropertyValue(const QDesignerPropertySheetExtension *sheet, const QString &name)
518{
519 const int index = sheet->indexOf(name);
520 Q_ASSERT(index != -1);
521 const PropertySheetStringValue ps = qvariant_cast<PropertySheetStringValue>(sheet->property(index));
522 return ps.value();
523}
524
526{
527 if (!action)
528 return;
529
530 NewActionDialog dlg(this);
531 dlg.setWindowTitle(tr("Edit action"));
532
543
544 switch (column) {
546 dlg.focusName();
547 break;
549 dlg.focusText();
550 break;
553 break;
556 break;
559 break;
562 break;
563 }
564
565 if (!dlg.exec())
566 return;
567
568 // figure out changes and whether to start a macro
571 if (changeMask == 0u)
572 return;
573
578
581 if (severalChanges)
582 fw->beginCommand(u"Edit action"_s);
583
586
589
592
595
598
601
604
605 if (severalChanges)
606 fw->endCommand();
607}
608
610{
612 editAction(a);
613}
614
616{
618 QDesignerTaskMenu::navigateToSlot(m_core, a, u"triggered()"_s);
619}
620
622{
623 // We need a macro even in the case of single action because the commands might cause the
624 // scheduling of other commands (signal slots connections)
625 const QString description = actions.size() == 1
626 ? tr("Remove action '%1'").arg(actions.constFirst()->objectName())
627 : tr("Remove actions");
629 for (QAction *action : actions) {
631 cmd->init(action);
633 }
634 fw->endCommand();
635}
636
637#if QT_CONFIG(clipboard)
639{
641 if (!fw )
642 return;
643
646
647 if (clipboard.empty())
648 return;
649
652
657 delete formBuilder;
658}
659#endif
660
662{
664 if (!fw)
665 return;
666
668 if (selection.isEmpty())
669 return;
670
672}
673
674// UnderScore: "Open file" -> actionOpen_file
675static QString underscore(QString text)
676{
677 static const QRegularExpression nonAsciiPattern(u"[^a-zA-Z_0-9]"_s);
678 Q_ASSERT(nonAsciiPattern.isValid());
679 text.replace(nonAsciiPattern, "_"_L1);
680 static const QRegularExpression multipleSpacePattern(u"__*"_s);
681 Q_ASSERT(multipleSpacePattern.isValid());
682 text.replace(multipleSpacePattern, "_"_L1);
683 if (text.endsWith(u'_'))
684 text.chop(1);
685 return text;
686}
687
688// CamelCase: "Open file" -> actionOpenFile, ignoring non-ASCII letters.
689
691
692static inline CharacterCategory category(QChar c)
693{
694 if (c.isDigit())
695 return DigitOrAsciiLetter;
696 if (c.isLetter()) {
697 const ushort uc = c.unicode();
698 return (uc >= 'a' && uc <= 'z') || (uc >= 'A' && uc <= 'Z')
700 }
701 return OtherCharacter;
702}
703
704static QString camelCase(const QString &text)
705{
706 QString result;
707 result.reserve(text.size());
708 bool lastCharAccepted = false;
709 for (QChar c : text) {
710 const CharacterCategory cat = category(c);
711 if (cat != NonAsciiLetter) {
712 const bool acceptable = cat == DigitOrAsciiLetter;
713 if (acceptable)
714 result.append(lastCharAccepted ? c : c.toUpper()); // New word starts
715 lastCharAccepted = acceptable;
716 }
717 }
718 return result;
719}
720
722{
723 QString name = text;
724 if (name.isEmpty())
725 return QString();
727
728}
729
731{
733 if (!fw)
734 return;
735
741 if (newIcon.paths().isEmpty() || newIcon.paths() == oldIcon.paths())
742 return;
743
745}
746
748{
749 // Invalidate references to objects kept in model
750 if (sender() == formWindow())
751 setFormWindow(nullptr);
752}
753
755{
756 // For use by the menu editor; block the syncing of the object inspector
757 // in slotCurrentItemChanged() since the menu editor updates it itself.
760 m_withinSelectAction = false;
761}
762
764{
765 // For use by the menu editor; block the syncing of the object inspector
766 // in slotCurrentItemChanged() since the menu editor updates it itself.
769 m_withinSelectAction = false;
770}
771
773{
776}
777
779{
781 if (!fw )
782 return;
783
785 if (!oi)
786 return;
787
788 fw->clearSelection(); // Actually, there are no widgets selected due to focus in event handling. Just to be sure.
789 oi->selectObject(w);
790}
791
793{
797}
798
800{
803}
804
806{
807 switch (m_actionView->viewMode()) {
808 case ActionView::IconView:
810 break;
813 break;
814 }
815}
816
817#if QT_CONFIG(clipboard)
819{
821 if (!fw )
822 return;
823
825 if (selection.isEmpty())
826 return;
827
829}
830
831void ActionEditor::slotCut()
832{
834 if (!fw )
835 return;
836
838 if (selection.isEmpty())
839 return;
840
843}
844
846{
848 if (!fw)
849 return;
852}
853#endif
854
856{
857 QMenu menu(this);
863
864 // Associated Widgets
867 if (!associatedWidgets.isEmpty()) {
869 for (QWidget *w : associatedWidgets) {
871 this, [this, w] { this->slotSelectAssociatedWidget(w); });
872 }
873 }
874 }
875
877#if QT_CONFIG(clipboard)
881#endif
887
889
890 menu.exec(e->globalPos());
891 e->accept();
892}
893
894} // namespace qdesigner_internal
895
896QT_END_NAMESPACE
static constexpr auto toolTipPropertyC
static constexpr auto iconPropertyC
static constexpr auto textPropertyC
static constexpr auto shortcutPropertyC
static constexpr auto menuRolePropertyC
static constexpr auto actionEditorViewModeKey
static constexpr auto objectNamePropertyC
static constexpr auto checkablePropertyC
friend class QPainter
void drawFocus(QPainter *, const QStyleOptionViewItem &, const QRect &) const override
Renders the region within the rectangle specified by rect, indicating that it has the focus,...
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
This pure abstract function must be reimplemented if you want to provide custom rendering.
Combined button and popup list for selecting options.
Auxiliary methods to store/retrieve settings.
static QDesignerFormWindowCommand * setKeySequencePropertyCommand(const PropertySheetKeySequenceValue &ks, QAction *action, QDesignerFormWindowInterface *fw)
static CharacterCategory category(QChar c)
static QDesignerFormWindowCommand * setIconPropertyCommand(const PropertySheetIconValue &newIcon, QAction *action, QDesignerFormWindowInterface *fw)
static void setInitialProperty(QDesignerPropertySheetExtension *sheet, const QString &name, const QVariant &value)
static void refreshIconPropertyChanged(const QAction *action, QDesignerPropertySheetExtension *sheet)
static QString underscore(QString text)
static QString textPropertyValue(const QDesignerPropertySheetExtension *sheet, const QString &name)
static QString camelCase(const QString &text)
QDesignerFormWindowCommand * setPropertyCommand(const QString &name, T value, T defaultValue, QObject *o, QDesignerFormWindowInterface *fw)