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
6#include "iconloader_p.h"
16
17#include <QtDesigner/abstractformeditor.h>
18#include <QtDesigner/abstractpropertyeditor.h>
19#include <QtDesigner/propertysheet.h>
20#include <QtDesigner/qextensionmanager.h>
21#include <QtDesigner/abstractmetadatabase.h>
22#include <QtDesigner/abstractsettings.h>
23
24#include <QtWidgets/qmenu.h>
25#include <QtWidgets/qtoolbar.h>
26#include <QtWidgets/qsplitter.h>
27#include <QtWidgets/qapplication.h>
28#if QT_CONFIG(clipboard)
29#include <QtGui/qclipboard.h>
30#endif
31#include <QtWidgets/qitemdelegate.h>
32#include <QtWidgets/qboxlayout.h>
33#include <QtWidgets/qlineedit.h>
34#include <QtWidgets/qlabel.h>
35#include <QtWidgets/qpushbutton.h>
36#include <QtWidgets/qtoolbutton.h>
37
38#include <QtGui/qaction.h>
39#include <QtGui/qactiongroup.h>
40#include <QtGui/qevent.h>
41#include <QtGui/qpainter.h>
42
43#include <QtCore/qitemselectionmodel.h>
44#include <QtCore/qregularexpression.h>
45#include <QtCore/qdebug.h>
46#include <QtCore/qbuffer.h>
47
49
50using namespace Qt::StringLiterals;
51
52static constexpr auto actionEditorViewModeKey = "ActionEditorViewMode"_L1;
53
54static constexpr auto iconPropertyC = "icon"_L1;
55static constexpr auto shortcutPropertyC = "shortcut"_L1;
56static constexpr auto menuRolePropertyC = "menuRole"_L1;
57static constexpr auto toolTipPropertyC = "toolTip"_L1;
58static constexpr auto checkablePropertyC = "checkable"_L1;
59static constexpr auto objectNamePropertyC = "objectName"_L1;
60static constexpr auto textPropertyC = "text"_L1;
61
62namespace qdesigner_internal {
63//-------- ActionGroupDelegate
65{
66public:
67 ActionGroupDelegate(QObject *parent)
69
70 void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
71 {
72 if (option.state & QStyle::State_Selected)
73 painter->fillRect(option.rect, option.palette.highlight());
74
75 QItemDelegate::paint(painter, option, index);
76 }
77
78 void drawFocus(QPainter *, const QStyleOptionViewItem &, const QRect &) const override {}
79};
80
81//-------- ActionEditor
83
86 m_core(core),
87 m_actionGroups(nullptr),
89 m_actionNew(new QAction(tr("New..."), this)),
90 m_actionEdit(new QAction(tr("Edit..."), this)),
91 m_actionNavigateToSlot(new QAction(tr("Go to slot..."), this)),
92#if QT_CONFIG(clipboard)
93 m_actionCopy(new QAction(tr("Copy"), this)),
94 m_actionCut(new QAction(tr("Cut"), this)),
95 m_actionPaste(new QAction(tr("Paste"), this)),
96#endif
97 m_actionSelectAll(new QAction(tr("Select all"), this)),
98 m_actionDelete(new QAction(tr("Delete"), this)),
100 m_iconViewAction(nullptr),
101 m_listViewAction(nullptr),
102 m_filterWidget(nullptr)
103{
106 setWindowTitle(tr("Actions"));
107
108 QVBoxLayout *l = new QVBoxLayout(this);
110 l->setSpacing(0);
111
113 toolbar->setIconSize(QSize(22, 22));
116 // edit actions
118 "filenew.png"_L1);
120 m_actionNew->setEnabled(false);
123
125
126#if QT_CONFIG(clipboard)
127 m_actionCut->setEnabled(false);
130 "editcut.png"_L1);
132
133 m_actionCopy->setEnabled(false);
136 "editcopy.png"_L1);
139
142 "editpaste.png"_L1);
145#endif
146
147 m_actionEdit->setEnabled(false);
149
151
153 "editdelete.png"_L1);
158
159 // Toolbutton with menu containing action group for detailed/icon view. Steal the icons from the file dialog.
160 //
162 toolbar->addWidget(createConfigureMenuButton(tr("Configure Action Editor"), &configureMenu));
163
170
171 m_listViewAction = m_viewModeGroup->addAction(tr("Detailed View"));
176 // filter
179 filterLayout->setContentsMargins(0, 0, 0, 0);
188
189 // main layout
192
195
196#if 0 // ### implement me
206#endif
207
210
212 // make it possible for vs integration to reimplement edit action dialog
214
217
220
222
225}
226
227// Utility to create a configure button with menu for usage on toolbars
242
247
249{
250 return m_actionNew;
251}
252
254{
255 return m_actionDelete;
256}
257
262
264{
265 if (formWindow != nullptr && formWindow->mainContainer() == nullptr)
266 formWindow = nullptr;
267
268 // we do NOT rely on this function to update the action editor
270 return;
271
272 if (m_formWindow != nullptr) {
274 for (QAction *action : actionList)
276 }
277
279
281
282 m_actionEdit->setEnabled(false);
283#if QT_CONFIG(clipboard)
284 m_actionCopy->setEnabled(false);
285 m_actionCut->setEnabled(false);
286#endif
288
289 if (!formWindow || !formWindow->mainContainer()) {
290 m_actionNew->setEnabled(false);
292 return;
293 }
294
295 m_actionNew->setEnabled(true);
297
299 for (QAction *action : actionList)
300 if (!action->isSeparator() && core()->metaDataBase()->item(action) != nullptr) {
301 // Show unless it has a menu. However, listen for change on menu actions also as it might be removed
302 if (!action->menu())
305 }
306
308}
309
311{
312 const bool hasSelection = !selected.indexes().isEmpty();
313#if QT_CONFIG(clipboard)
316#endif
318}
319
321{
323 if (m_withinSelectAction || fw == nullptr)
324 return;
325
326 const bool hasCurrentAction = action != nullptr;
328
329 if (!action) {
331 return;
332 }
333
335
336 // Check if we have at least one associated QWidget:
339 [](QObject *obj) {
340 return qobject_cast<QWidget *>(obj) != nullptr;
341 });
342 if (it == associatedObjects.cend()) {
343 // Special case: action not in object tree. Deselect all and set in property editor
344 fw->clearSelection(false);
345 if (oi)
348 } else {
349 if (oi)
351 }
352}
353
355{
357 Q_ASSERT(action != nullptr);
358
360 const int row = model->findAction(action);
361 if (row == -1) {
362 if (action->menu() == nullptr) // action got its menu deleted, create item
364 } else if (action->menu() != nullptr) { // action got its menu created, remove item
366 } else {
367 // action text or icon changed, update item
368 model->update(row);
369 }
370}
371
373{
374 return m_core;
375}
376
378{
379 return m_filter;
380}
381
383{
384 m_filter = f;
386}
387
388// Set changed state of icon property, reset when icon is cleared
389static void refreshIconPropertyChanged(const QAction *action, QDesignerPropertySheetExtension *sheet)
390{
391 sheet->setChanged(sheet->indexOf(iconPropertyC), !action->icon().isNull());
392}
393
410
412{
414 action->setParent(nullptr);
415
417
418 const int row = m_actionView->model()->findAction(action);
419 if (row != -1)
421}
422
423// Set an initial property and mark it as changed in the sheet
424static void setInitialProperty(QDesignerPropertySheetExtension *sheet, const QString &name, const QVariant &value)
425{
426 const int index = sheet->indexOf(name);
427 Q_ASSERT(index != -1);
428 sheet->setProperty(index, value);
429 sheet->setChanged(index, true);
430}
431
433{
434 NewActionDialog dlg(this);
435 dlg.setWindowTitle(tr("New action"));
436
437 if (dlg.exec() == QDialog::Accepted) {
444
448
451
454
456
458
460 cmd->init(action);
462 }
463}
464
465// return a FormWindow command to apply an icon or a reset command in case it
466// is empty.
467
468static QDesignerFormWindowCommand *setIconPropertyCommand(const PropertySheetIconValue &newIcon, QAction *action, QDesignerFormWindowInterface *fw)
469{
470 const QString iconProperty = iconPropertyC;
471 if (newIcon.isEmpty()) {
472 ResetPropertyCommand *cmd = new ResetPropertyCommand(fw);
473 cmd->init(action, iconProperty);
474 return cmd;
475 }
476 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
477 cmd->init(action, iconProperty, QVariant::fromValue(newIcon));
478 return cmd;
479}
480
481// return a FormWindow command to apply a QKeySequence or a reset command
482// in case it is empty.
483
484static QDesignerFormWindowCommand *setKeySequencePropertyCommand(const PropertySheetKeySequenceValue &ks, QAction *action, QDesignerFormWindowInterface *fw)
485{
486 const QString shortcutProperty = shortcutPropertyC;
487 if (ks.value().isEmpty()) {
488 ResetPropertyCommand *cmd = new ResetPropertyCommand(fw);
489 cmd->init(action, shortcutProperty);
490 return cmd;
491 }
492 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
493 cmd->init(action, shortcutProperty, QVariant::fromValue(ks));
494 return cmd;
495}
496
497// return a FormWindow command to apply a POD value or reset command in case
498// it is equal to the default value.
499
500template <class T>
501QDesignerFormWindowCommand *setPropertyCommand(const QString &name, T value, T defaultValue,
502 QObject *o, QDesignerFormWindowInterface *fw)
503{
504 if (value == defaultValue) {
505 ResetPropertyCommand *cmd = new ResetPropertyCommand(fw);
506 cmd->init(o, name);
507 return cmd;
508 }
509 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
510 cmd->init(o, name, QVariant(value));
511 return cmd;
512}
513
514// Return the text value of a string property via PropertySheetStringValue
515static inline QString textPropertyValue(const QDesignerPropertySheetExtension *sheet, const QString &name)
516{
517 const int index = sheet->indexOf(name);
518 Q_ASSERT(index != -1);
519 const PropertySheetStringValue ps = qvariant_cast<PropertySheetStringValue>(sheet->property(index));
520 return ps.value();
521}
522
524{
525 if (!action)
526 return;
527
528 NewActionDialog dlg(this);
529 dlg.setWindowTitle(tr("Edit action"));
530
541
542 switch (column) {
544 dlg.focusName();
545 break;
547 dlg.focusText();
548 break;
551 break;
554 break;
557 break;
560 break;
561 }
562
563 if (!dlg.exec())
564 return;
565
566 // figure out changes and whether to start a macro
569 if (changeMask == 0u)
570 return;
571
576
579 if (severalChanges)
580 fw->beginCommand(u"Edit action"_s);
581
584
587
590
593
596
599
602
603 if (severalChanges)
604 fw->endCommand();
605}
606
608{
610 editAction(a);
611}
612
614{
616 QDesignerTaskMenu::navigateToSlot(m_core, a, u"triggered()"_s);
617}
618
620{
621 // We need a macro even in the case of single action because the commands might cause the
622 // scheduling of other commands (signal slots connections)
623 const QString description = actions.size() == 1
624 ? tr("Remove action '%1'").arg(actions.constFirst()->objectName())
625 : tr("Remove actions");
627 for (QAction *action : actions) {
629 cmd->init(action);
631 }
632 fw->endCommand();
633}
634
635#if QT_CONFIG(clipboard)
637{
639 if (!fw )
640 return;
641
644
645 if (clipboard.empty())
646 return;
647
650
655 delete formBuilder;
656}
657#endif
658
660{
662 if (!fw)
663 return;
664
666 if (selection.isEmpty())
667 return;
668
670}
671
672// UnderScore: "Open file" -> actionOpen_file
673static QString underscore(QString text)
674{
675 static const QRegularExpression nonAsciiPattern(u"[^a-zA-Z_0-9]"_s);
676 Q_ASSERT(nonAsciiPattern.isValid());
677 text.replace(nonAsciiPattern, "_"_L1);
678 static const QRegularExpression multipleSpacePattern(u"__*"_s);
679 Q_ASSERT(multipleSpacePattern.isValid());
680 text.replace(multipleSpacePattern, "_"_L1);
681 if (text.endsWith(u'_'))
682 text.chop(1);
683 return text;
684}
685
686// CamelCase: "Open file" -> actionOpenFile, ignoring non-ASCII letters.
687
689
690static inline CharacterCategory category(QChar c)
691{
692 if (c.isDigit())
693 return DigitOrAsciiLetter;
694 if (c.isLetter()) {
695 const ushort uc = c.unicode();
696 return (uc >= 'a' && uc <= 'z') || (uc >= 'A' && uc <= 'Z')
698 }
699 return OtherCharacter;
700}
701
702static QString camelCase(const QString &text)
703{
704 QString result;
705 result.reserve(text.size());
706 bool lastCharAccepted = false;
707 for (QChar c : text) {
708 const CharacterCategory cat = category(c);
709 if (cat != NonAsciiLetter) {
710 const bool acceptable = cat == DigitOrAsciiLetter;
711 if (acceptable)
712 result.append(lastCharAccepted ? c : c.toUpper()); // New word starts
713 lastCharAccepted = acceptable;
714 }
715 }
716 return result;
717}
718
720{
721 QString name = text;
722 if (name.isEmpty())
723 return QString();
725
726}
727
729{
731 if (!fw)
732 return;
733
739 if (newIcon.paths().isEmpty() || newIcon.paths() == oldIcon.paths())
740 return;
741
743}
744
746{
747 // Invalidate references to objects kept in model
748 if (sender() == formWindow())
749 setFormWindow(nullptr);
750}
751
753{
754 // For use by the menu editor; block the syncing of the object inspector
755 // in slotCurrentItemChanged() since the menu editor updates it itself.
758 m_withinSelectAction = false;
759}
760
762{
763 // For use by the menu editor; block the syncing of the object inspector
764 // in slotCurrentItemChanged() since the menu editor updates it itself.
767 m_withinSelectAction = false;
768}
769
771{
774}
775
777{
779 if (!fw )
780 return;
781
783 if (!oi)
784 return;
785
786 fw->clearSelection(); // Actually, there are no widgets selected due to focus in event handling. Just to be sure.
787 oi->selectObject(w);
788}
789
791{
795}
796
798{
801}
802
804{
805 switch (m_actionView->viewMode()) {
806 case ActionView::IconView:
808 break;
811 break;
812 }
813}
814
815#if QT_CONFIG(clipboard)
817{
819 if (!fw )
820 return;
821
823 if (selection.isEmpty())
824 return;
825
827}
828
829void ActionEditor::slotCut()
830{
832 if (!fw )
833 return;
834
836 if (selection.isEmpty())
837 return;
838
841}
842
844{
846 if (!fw)
847 return;
850}
851#endif
852
854{
855 QMenu menu(this);
861
862 // Associated Widgets
865 if (!associatedWidgets.isEmpty()) {
867 for (QWidget *w : associatedWidgets) {
869 this, [this, w] { this->slotSelectAssociatedWidget(w); });
870 }
871 }
872 }
873
875#if QT_CONFIG(clipboard)
879#endif
885
887
888 menu.exec(e->globalPos());
889 e->accept();
890}
891
892} // namespace qdesigner_internal
893
894QT_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)