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
213 // make it possible for vs integration to reimplement edit action dialog
215
218
221
223
226}
227
228// Utility to create a configure button with menu for usage on toolbars
243
248
250{
251 return m_actionNew;
252}
253
255{
256 return m_actionDelete;
257}
258
263
265{
266 if (formWindow != nullptr && formWindow->mainContainer() == nullptr)
267 formWindow = nullptr;
268
269 // we do NOT rely on this function to update the action editor
271 return;
272
273 if (m_formWindow != nullptr) {
275 for (QAction *action : actionList)
277 }
278
280
282
283 m_actionEdit->setEnabled(false);
284#if QT_CONFIG(clipboard)
285 m_actionCopy->setEnabled(false);
286 m_actionCut->setEnabled(false);
287#endif
289
290 if (!formWindow || !formWindow->mainContainer()) {
291 m_actionNew->setEnabled(false);
293 return;
294 }
295
296 m_actionNew->setEnabled(true);
298
300 for (QAction *action : actionList)
301 if (!action->isSeparator() && core()->metaDataBase()->item(action) != nullptr) {
302 // Show unless it has a menu. However, listen for change on menu actions also as it might be removed
303 if (!action->menu())
306 }
307
309}
310
312{
313 const bool hasSelection = !selected.indexes().isEmpty();
314#if QT_CONFIG(clipboard)
317#endif
319}
320
322{
324 if (m_withinSelectAction || fw == nullptr)
325 return;
326
327 const bool hasCurrentAction = action != nullptr;
329
330 if (!action) {
332 return;
333 }
334
336
337 // Check if we have at least one associated QWidget:
340 [](QObject *obj) {
341 return qobject_cast<QWidget *>(obj) != nullptr;
342 });
343 if (it == associatedObjects.cend()) {
344 // Special case: action not in object tree. Deselect all and set in property editor
345 fw->clearSelection(false);
346 if (oi)
349 } else {
350 if (oi)
352 }
353}
354
356{
358 Q_ASSERT(action != nullptr);
359
361 const int row = model->findAction(action);
362 if (row == -1) {
363 if (action->menu() == nullptr) // action got its menu deleted, create item
365 } else if (action->menu() != nullptr) { // action got its menu created, remove item
367 } else {
368 // action text or icon changed, update item
369 model->update(row);
370 }
371}
372
374{
375 return m_core;
376}
377
379{
380 return m_filter;
381}
382
384{
385 m_filter = f;
387}
388
389// Set changed state of icon property, reset when icon is cleared
390static void refreshIconPropertyChanged(const QAction *action, QDesignerPropertySheetExtension *sheet)
391{
392 sheet->setChanged(sheet->indexOf(iconPropertyC), !action->icon().isNull());
393}
394
411
413{
415 action->setParent(nullptr);
416
418
419 const int row = m_actionView->model()->findAction(action);
420 if (row != -1)
422}
423
424// Set an initial property and mark it as changed in the sheet
425static void setInitialProperty(QDesignerPropertySheetExtension *sheet, const QString &name, const QVariant &value)
426{
427 const int index = sheet->indexOf(name);
428 Q_ASSERT(index != -1);
429 sheet->setProperty(index, value);
430 sheet->setChanged(index, true);
431}
432
434{
435 NewActionDialog dlg(this);
436 dlg.setWindowTitle(tr("New action"));
437
438 if (dlg.exec() == QDialog::Accepted) {
445
449
452
455
457
459
461 cmd->init(action);
463 }
464}
465
466// return a FormWindow command to apply an icon or a reset command in case it
467// is empty.
468
469static QDesignerFormWindowCommand *setIconPropertyCommand(const PropertySheetIconValue &newIcon, QAction *action, QDesignerFormWindowInterface *fw)
470{
471 const QString iconProperty = iconPropertyC;
472 if (newIcon.isEmpty()) {
473 ResetPropertyCommand *cmd = new ResetPropertyCommand(fw);
474 cmd->init(action, iconProperty);
475 return cmd;
476 }
477 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
478 cmd->init(action, iconProperty, QVariant::fromValue(newIcon));
479 return cmd;
480}
481
482// return a FormWindow command to apply a QKeySequence or a reset command
483// in case it is empty.
484
485static QDesignerFormWindowCommand *setKeySequencePropertyCommand(const PropertySheetKeySequenceValue &ks, QAction *action, QDesignerFormWindowInterface *fw)
486{
487 const QString shortcutProperty = shortcutPropertyC;
488 if (ks.value().isEmpty()) {
489 ResetPropertyCommand *cmd = new ResetPropertyCommand(fw);
490 cmd->init(action, shortcutProperty);
491 return cmd;
492 }
493 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
494 cmd->init(action, shortcutProperty, QVariant::fromValue(ks));
495 return cmd;
496}
497
498// return a FormWindow command to apply a POD value or reset command in case
499// it is equal to the default value.
500
501template <class T>
502QDesignerFormWindowCommand *setPropertyCommand(const QString &name, T value, T defaultValue,
503 QObject *o, QDesignerFormWindowInterface *fw)
504{
505 if (value == defaultValue) {
506 ResetPropertyCommand *cmd = new ResetPropertyCommand(fw);
507 cmd->init(o, name);
508 return cmd;
509 }
510 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
511 cmd->init(o, name, QVariant(value));
512 return cmd;
513}
514
515// Return the text value of a string property via PropertySheetStringValue
516static inline QString textPropertyValue(const QDesignerPropertySheetExtension *sheet, const QString &name)
517{
518 const int index = sheet->indexOf(name);
519 Q_ASSERT(index != -1);
520 const PropertySheetStringValue ps = qvariant_cast<PropertySheetStringValue>(sheet->property(index));
521 return ps.value();
522}
523
525{
526 if (!action)
527 return;
528
529 NewActionDialog dlg(this);
530 dlg.setWindowTitle(tr("Edit action"));
531
542
543 switch (column) {
545 dlg.focusName();
546 break;
548 dlg.focusText();
549 break;
552 break;
555 break;
558 break;
561 break;
562 }
563
564 if (!dlg.exec())
565 return;
566
567 // figure out changes and whether to start a macro
570 if (changeMask == 0u)
571 return;
572
577
580 if (severalChanges)
581 fw->beginCommand(u"Edit action"_s);
582
585
588
591
594
597
600
603
604 if (severalChanges)
605 fw->endCommand();
606}
607
609{
611 editAction(a);
612}
613
615{
617 QDesignerTaskMenu::navigateToSlot(m_core, a, u"triggered()"_s);
618}
619
621{
622 // We need a macro even in the case of single action because the commands might cause the
623 // scheduling of other commands (signal slots connections)
624 const QString description = actions.size() == 1
625 ? tr("Remove action '%1'").arg(actions.constFirst()->objectName())
626 : tr("Remove actions");
628 for (QAction *action : actions) {
630 cmd->init(action);
632 }
633 fw->endCommand();
634}
635
636#if QT_CONFIG(clipboard)
638{
640 if (!fw )
641 return;
642
645
646 if (clipboard.empty())
647 return;
648
651
656 delete formBuilder;
657}
658#endif
659
661{
663 if (!fw)
664 return;
665
667 if (selection.isEmpty())
668 return;
669
671}
672
673// UnderScore: "Open file" -> actionOpen_file
674static QString underscore(QString text)
675{
676 static const QRegularExpression nonAsciiPattern(u"[^a-zA-Z_0-9]"_s);
677 Q_ASSERT(nonAsciiPattern.isValid());
678 text.replace(nonAsciiPattern, "_"_L1);
679 static const QRegularExpression multipleSpacePattern(u"__*"_s);
680 Q_ASSERT(multipleSpacePattern.isValid());
681 text.replace(multipleSpacePattern, "_"_L1);
682 if (text.endsWith(u'_'))
683 text.chop(1);
684 return text;
685}
686
687// CamelCase: "Open file" -> actionOpenFile, ignoring non-ASCII letters.
688
690
691static inline CharacterCategory category(QChar c)
692{
693 if (c.isDigit())
694 return DigitOrAsciiLetter;
695 if (c.isLetter()) {
696 const ushort uc = c.unicode();
697 return (uc >= 'a' && uc <= 'z') || (uc >= 'A' && uc <= 'Z')
699 }
700 return OtherCharacter;
701}
702
703static QString camelCase(const QString &text)
704{
705 QString result;
706 result.reserve(text.size());
707 bool lastCharAccepted = false;
708 for (QChar c : text) {
709 const CharacterCategory cat = category(c);
710 if (cat != NonAsciiLetter) {
711 const bool acceptable = cat == DigitOrAsciiLetter;
712 if (acceptable)
713 result.append(lastCharAccepted ? c : c.toUpper()); // New word starts
714 lastCharAccepted = acceptable;
715 }
716 }
717 return result;
718}
719
721{
722 QString name = text;
723 if (name.isEmpty())
724 return QString();
726
727}
728
730{
732 if (!fw)
733 return;
734
740 if (newIcon.paths().isEmpty() || newIcon.paths() == oldIcon.paths())
741 return;
742
744}
745
747{
748 // Invalidate references to objects kept in model
749 if (sender() == formWindow())
750 setFormWindow(nullptr);
751}
752
754{
755 // For use by the menu editor; block the syncing of the object inspector
756 // in slotCurrentItemChanged() since the menu editor updates it itself.
759 m_withinSelectAction = false;
760}
761
763{
764 // For use by the menu editor; block the syncing of the object inspector
765 // in slotCurrentItemChanged() since the menu editor updates it itself.
768 m_withinSelectAction = false;
769}
770
772{
775}
776
778{
780 if (!fw )
781 return;
782
784 if (!oi)
785 return;
786
787 fw->clearSelection(); // Actually, there are no widgets selected due to focus in event handling. Just to be sure.
788 oi->selectObject(w);
789}
790
792{
796}
797
799{
802}
803
805{
806 switch (m_actionView->viewMode()) {
807 case ActionView::IconView:
809 break;
812 break;
813 }
814}
815
816#if QT_CONFIG(clipboard)
818{
820 if (!fw )
821 return;
822
824 if (selection.isEmpty())
825 return;
826
828}
829
830void ActionEditor::slotCut()
831{
833 if (!fw )
834 return;
835
837 if (selection.isEmpty())
838 return;
839
842}
843
845{
847 if (!fw)
848 return;
851}
852#endif
853
855{
856 QMenu menu(this);
862
863 // Associated Widgets
866 if (!associatedWidgets.isEmpty()) {
868 for (QWidget *w : associatedWidgets) {
870 this, [this, w] { this->slotSelectAssociatedWidget(w); });
871 }
872 }
873 }
874
876#if QT_CONFIG(clipboard)
880#endif
886
888
889 menu.exec(e->globalPos());
890 e->accept();
891}
892
893} // namespace qdesigner_internal
894
895QT_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)