/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "variablechooser.h" #include "coreconstants.h" #include // IconButton #include // IconButton #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Utils; namespace Core { namespace Internal { enum { UnexpandedTextRole = Qt::UserRole, ExpandedTextRole }; class VariableTreeView : public QTreeView { public: VariableTreeView(QWidget *parent, VariableChooserPrivate *target) : QTreeView(parent), m_target(target) { setAttribute(Qt::WA_MacSmallSize); setAttribute(Qt::WA_MacShowFocusRect, false); setIndentation(indentation() * 7/10); header()->hide(); new HeaderViewStretcher(header(), 0); } void contextMenuEvent(QContextMenuEvent *ev); void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); private: VariableChooserPrivate *m_target; }; class VariableChooserPrivate : public QObject { public: VariableChooserPrivate(VariableChooser *parent); void createIconButton() { m_iconButton = new IconButton; m_iconButton->setPixmap(QPixmap(QLatin1String(":/core/images/replace.png"))); m_iconButton->setToolTip(VariableChooser::tr("Insert Variable")); m_iconButton->hide(); connect(m_iconButton.data(), static_cast(&QAbstractButton::clicked), this, &VariableChooserPrivate::updatePositionAndShow); } void updateDescription(const QModelIndex &index); void updateCurrentEditor(QWidget *old, QWidget *widget); void handleItemActivated(const QModelIndex &index); void insertText(const QString &variable); void updatePositionAndShow(bool); QWidget *currentWidget(); int buttonMargin() const; void updateButtonGeometry(); public: VariableChooser *q; TreeModel m_model; QPointer m_lineEdit; QPointer m_textEdit; QPointer m_plainTextEdit; QPointer m_iconButton; VariableTreeView *m_variableTree; QLabel *m_variableDescription; QString m_defaultDescription; QByteArray m_currentVariableName; // Prevent recursive insertion of currently expanded item }; class VariableGroupItem : public TreeItem { public: VariableGroupItem() : m_chooser(0), m_populated(false) { } QVariant data(int column, int role) const { if (role == Qt::DisplayRole || role == Qt::EditRole) { if (column == 0) if (MacroExpander *expander = m_provider()) return expander->displayName(); } return QVariant(); } bool canFetchMore() const { return !m_populated; } void fetchMore() { if (MacroExpander *expander = m_provider()) populateGroup(expander); m_populated = true; } void populateGroup(MacroExpander *expander); public: VariableChooserPrivate *m_chooser; // Not owned. bool m_populated; MacroExpanderProvider m_provider; }; class VariableItem : public TreeItem { public: VariableItem() {} QVariant data(int column, int role) const { if (role == Qt::DisplayRole || role == Qt::EditRole) { if (column == 0) return m_variable; } if (role == Qt::ToolTipRole) { const QByteArray var = m_variable.toUtf8(); QString description = m_expander->variableDescription(var); QString value; if (!m_expander->isPrefixVariable(var)) value = m_expander->value(var).toHtmlEscaped(); if (!value.isEmpty()) description += QLatin1String("

") + VariableChooser::tr("Current Value: %1").arg(value); return description; } if (role == UnexpandedTextRole) return QString(QLatin1String("%{") + m_variable + QLatin1Char('}')); if (role == ExpandedTextRole) return m_expander->expand(QLatin1String("%{") + m_variable + QLatin1Char('}')); return QVariant(); } public: MacroExpander *m_expander; QString m_variable; }; void VariableTreeView::contextMenuEvent(QContextMenuEvent *ev) { const QModelIndex index = indexAt(ev->pos()); QString unexpandedText = index.data(UnexpandedTextRole).toString(); QString expandedText = index.data(ExpandedTextRole).toString(); QMenu menu; QAction *insertUnexpandedAction = 0; QAction *insertExpandedAction = 0; if (unexpandedText.isEmpty()) { insertUnexpandedAction = menu.addAction(VariableChooser::tr("Insert Unexpanded Value")); insertUnexpandedAction->setEnabled(false); } else { insertUnexpandedAction = menu.addAction(VariableChooser::tr("Insert \"%1\"").arg(unexpandedText)); } if (expandedText.isEmpty()) { insertExpandedAction = menu.addAction(VariableChooser::tr("Insert Expanded Value")); insertExpandedAction->setEnabled(false); } else { insertExpandedAction = menu.addAction(VariableChooser::tr("Insert \"%1\"").arg(expandedText)); } QAction *act = menu.exec(ev->globalPos()); if (act == insertUnexpandedAction) m_target->insertText(unexpandedText); else if (act == insertExpandedAction) m_target->insertText(expandedText); } void VariableTreeView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { m_target->updateDescription(current); QTreeView::currentChanged(current, previous); } VariableChooserPrivate::VariableChooserPrivate(VariableChooser *parent) : q(parent), m_lineEdit(0), m_textEdit(0), m_plainTextEdit(0), m_iconButton(0), m_variableTree(0), m_variableDescription(0) { m_defaultDescription = VariableChooser::tr("Select a variable to insert."); m_variableTree = new VariableTreeView(q, this); m_variableDescription = new QLabel(q); m_variableTree->setModel(&m_model); m_variableDescription->setText(m_defaultDescription); m_variableDescription->setMinimumSize(QSize(0, 60)); m_variableDescription->setAlignment(Qt::AlignLeft|Qt::AlignTop); m_variableDescription->setWordWrap(true); m_variableDescription->setAttribute(Qt::WA_MacSmallSize); m_variableDescription->setTextInteractionFlags(Qt::TextBrowserInteraction); QVBoxLayout *verticalLayout = new QVBoxLayout(q); verticalLayout->setContentsMargins(3, 3, 3, 12); verticalLayout->addWidget(m_variableTree); verticalLayout->addWidget(m_variableDescription); connect(m_variableTree, &QTreeView::activated, this, &VariableChooserPrivate::handleItemActivated); connect(qobject_cast(qApp), &QApplication::focusChanged, this, &VariableChooserPrivate::updateCurrentEditor); updateCurrentEditor(0, qApp->focusWidget()); } void VariableGroupItem::populateGroup(MacroExpander *expander) { if (!expander) return; foreach (const QByteArray &variable, expander->visibleVariables()) { auto item = new VariableItem; item->m_variable = QString::fromUtf8(variable); item->m_expander = expander; if (variable == m_chooser->m_currentVariableName) item->setFlags(Qt::ItemIsSelectable); // not ItemIsEnabled appendChild(item); } foreach (const MacroExpanderProvider &subProvider, expander->subProviders()) { if (!subProvider) continue; if (expander->isAccumulating()) { populateGroup(subProvider()); } else { auto item = new VariableGroupItem; item->m_chooser = m_chooser; item->m_provider = subProvider; appendChild(item); } } } } // namespace Internal using namespace Internal; /*! * \class Core::VariableChooser * \brief The VariableChooser class is used to add a tool window for selecting \QC variables * to line edits, text edits or plain text edits. * * If you allow users to add \QC variables to strings that are specified in your UI, for example * when users can provide a string through a text control, you should add a variable chooser to it. * The variable chooser allows users to open a tool window that contains the list of * all available variables together with a description. Double-clicking a variable inserts the * corresponding string into the corresponding text control like a line edit. * * \image variablechooser.png "External Tools Preferences with Variable Chooser" * * The variable chooser monitors focus changes of all children of its parent widget. * When a text control gets focus, the variable chooser checks if it has variable support set, * either through the addVariableSupport() function. If the control supports variables, * a tool button which opens the variable chooser is shown in it while it has focus. * * Supported text controls are QLineEdit, QTextEdit and QPlainTextEdit. * * The variable chooser is deleted when its parent widget is deleted. * * Example: * \code * QWidget *myOptionsContainerWidget = new QWidget; * new Core::VariableChooser(myOptionsContainerWidget) * QLineEdit *myLineEditOption = new QLineEdit(myOptionsContainerWidget); * myOptionsContainerWidget->layout()->addWidget(myLineEditOption); * Core::VariableChooser::addVariableSupport(myLineEditOption); * \endcode */ /*! * \internal * \variable VariableChooser::kVariableSupportProperty * Property name that is checked for deciding if a widget supports \QC variables. * Can be manually set with * \c{textcontrol->setProperty(VariableChooser::kVariableSupportProperty, true)} * \sa addVariableSupport() */ const char kVariableSupportProperty[] = "QtCreator.VariableSupport"; const char kVariableNameProperty[] = "QtCreator.VariableName"; /*! * Creates a variable chooser that tracks all children of \a parent for variable support. * Ownership is also transferred to \a parent. * \sa addVariableSupport() */ VariableChooser::VariableChooser(QWidget *parent) : QWidget(parent), d(new VariableChooserPrivate(this)) { setWindowTitle(tr("Variables")); setWindowFlags(Qt::Tool); setFocusPolicy(Qt::StrongFocus); setFocusProxy(d->m_variableTree); addMacroExpanderProvider([]() { return globalMacroExpander(); }); } /*! * \internal */ VariableChooser::~VariableChooser() { delete d->m_iconButton; delete d; } void VariableChooser::addMacroExpanderProvider(const MacroExpanderProvider &provider) { auto item = new VariableGroupItem; item->m_chooser = d; item->m_provider = provider; d->m_model.rootItem()->prependChild(item); } /*! * Marks the control as supporting variables. * \sa kVariableSupportProperty */ void VariableChooser::addSupportedWidget(QWidget *textcontrol, const QByteArray &ownName) { QTC_ASSERT(textcontrol, return); textcontrol->setProperty(kVariableSupportProperty, QVariant::fromValue(this)); textcontrol->setProperty(kVariableNameProperty, ownName); } void VariableChooser::addSupportForChildWidgets(QWidget *parent, MacroExpander *expander) { auto chooser = new VariableChooser(parent); chooser->addMacroExpanderProvider([expander] { return expander; }); foreach (QWidget *child, parent->findChildren()) { if (qobject_cast(child) || qobject_cast(child) || qobject_cast(child)) chooser->addSupportedWidget(child); } } /*! * \internal */ void VariableChooserPrivate::updateDescription(const QModelIndex &index) { if (m_variableDescription) m_variableDescription->setText(m_model.data(index, Qt::ToolTipRole).toString()); } /*! * \internal */ int VariableChooserPrivate::buttonMargin() const { int margin = m_iconButton->pixmap().width() + 8; if (q->style()->inherits("OxygenStyle")) margin = qMax(24, margin); return margin; } void VariableChooserPrivate::updateButtonGeometry() { QWidget *current = currentWidget(); int margin = buttonMargin(); m_iconButton->setGeometry(current->rect().adjusted( current->width() - (margin + 4), 0, 0, -qMax(0, current->height() - (margin + 4)))); } void VariableChooserPrivate::updateCurrentEditor(QWidget *old, QWidget *widget) { Q_UNUSED(old); if (!widget) // we might loose focus, but then keep the previous state return; // prevent children of the chooser itself, and limit to children of chooser's parent bool handle = false; QWidget *parent = widget; while (parent) { if (parent == q) return; if (parent == q->parentWidget()) { handle = true; break; } parent = parent->parentWidget(); } if (!handle) return; QLineEdit *previousLineEdit = m_lineEdit; QWidget *previousWidget = currentWidget(); m_lineEdit = 0; m_textEdit = 0; m_plainTextEdit = 0; QWidget *chooser = widget->property(kVariableSupportProperty).value(); m_currentVariableName = widget->property(kVariableNameProperty).value(); bool supportsVariables = chooser == q; if (QLineEdit *lineEdit = qobject_cast(widget)) m_lineEdit = (supportsVariables ? lineEdit : 0); else if (QTextEdit *textEdit = qobject_cast(widget)) m_textEdit = (supportsVariables ? textEdit : 0); else if (QPlainTextEdit *plainTextEdit = qobject_cast(widget)) m_plainTextEdit = (supportsVariables ? plainTextEdit : 0); QWidget *current = currentWidget(); if (current != previousWidget) { if (previousWidget) previousWidget->removeEventFilter(q); if (previousLineEdit) previousLineEdit->setTextMargins(0, 0, 0, 0); if (m_iconButton) { m_iconButton->hide(); m_iconButton->setParent(0); } if (current) { current->installEventFilter(q); // escape key handling and geometry changes if (!m_iconButton) createIconButton(); int margin = buttonMargin(); if (m_lineEdit) m_lineEdit->setTextMargins(0, 0, margin, 0); m_iconButton->setParent(current); updateButtonGeometry(); m_iconButton->show(); } else { q->hide(); } } } /*! * \internal */ void VariableChooserPrivate::updatePositionAndShow(bool) { if (QWidget *w = q->parentWidget()) { QPoint parentCenter = w->mapToGlobal(w->geometry().center()); q->move(parentCenter.x() - q->width()/2, parentCenter.y() - q->height()/2); } q->show(); q->raise(); q->activateWindow(); m_variableTree->expandAll(); } /*! * \internal */ QWidget *VariableChooserPrivate::currentWidget() { if (m_lineEdit) return m_lineEdit; if (m_textEdit) return m_textEdit; return m_plainTextEdit; } /*! * \internal */ void VariableChooserPrivate::handleItemActivated(const QModelIndex &index) { QString text = m_model.data(index, UnexpandedTextRole).toString(); if (!text.isEmpty()) insertText(text); } /*! * \internal */ void VariableChooserPrivate::insertText(const QString &text) { if (m_lineEdit) { m_lineEdit->insert(text); m_lineEdit->activateWindow(); } else if (m_textEdit) { m_textEdit->insertPlainText(text); m_textEdit->activateWindow(); } else if (m_plainTextEdit) { m_plainTextEdit->insertPlainText(text); m_plainTextEdit->activateWindow(); } } /*! * \internal */ static bool handleEscapePressed(QKeyEvent *ke, QWidget *widget) { if (ke->key() == Qt::Key_Escape && !ke->modifiers()) { ke->accept(); QTimer::singleShot(0, widget, &QWidget::close); return true; } return false; } /*! * \internal */ bool VariableChooser::event(QEvent *ev) { if (ev->type() == QEvent::KeyPress || ev->type() == QEvent::ShortcutOverride) { QKeyEvent *ke = static_cast(ev); if (handleEscapePressed(ke, this)) return true; } return QWidget::event(ev); } /*! * \internal */ bool VariableChooser::eventFilter(QObject *obj, QEvent *event) { if (obj != d->currentWidget()) return false; if ((event->type() == QEvent::KeyPress || event->type() == QEvent::ShortcutOverride) && isVisible()) { QKeyEvent *ke = static_cast(event); return handleEscapePressed(ke, this); } else if (event->type() == QEvent::Resize) { d->updateButtonGeometry(); } else if (event->type() == QEvent::Hide) { close(); } return false; } } // namespace Internal