diff options
Diffstat (limited to 'src/libs/utils')
80 files changed, 2193 insertions, 1218 deletions
diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 3dff6c4f6f..ca5e88b66c 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -94,6 +94,7 @@ add_qtc_library(Utils link.cpp link.h listmodel.h listutils.h + lua.cpp lua.h macroexpander.cpp macroexpander.h mathutils.cpp mathutils.h mimeconstants.h @@ -198,6 +199,7 @@ add_qtc_library(Utils utilstr.h utilsicons.cpp utilsicons.h utiltypes.h + utility.h variablechooser.cpp variablechooser.h winutils.cpp winutils.h wizard.cpp wizard.h @@ -270,7 +272,6 @@ extend_qtc_library(Utils fsengine/fsengine_impl.h fsengine/diriterator.h fsengine/fileiteratordevicesappender.h - fsengine/rootinjectfsengine.h fsengine/fixedlistfsengine.h fsengine/fsenginehandler.cpp fsengine/fsenginehandler.h diff --git a/src/libs/utils/algorithm.h b/src/libs/utils/algorithm.h index d1c3f37bff..849316c9a9 100644 --- a/src/libs/utils/algorithm.h +++ b/src/libs/utils/algorithm.h @@ -987,6 +987,14 @@ C filtered(const C &container, R (S::*predicate)() const) return out; } +template<typename C, typename R, typename S> +Q_REQUIRED_RESULT C filtered(const C &container, R S::*predicate) +{ + C out; + std::copy_if(std::begin(container), std::end(container), inserter(out), std::mem_fn(predicate)); + return out; +} + ////////////////// // filteredCast ///////////////// diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 6ef84c119e..e6b8267c4e 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -10,6 +10,7 @@ #include "guard.h" #include "iconbutton.h" #include "layoutbuilder.h" +#include "macroexpander.h" #include "passworddialog.h" #include "pathchooser.h" #include "pathlisteditor.h" @@ -27,6 +28,7 @@ #include <QDebug> #include <QGroupBox> #include <QLabel> +#include <QLayout> #include <QLineEdit> #include <QListWidget> #include <QPaintEvent> @@ -38,6 +40,7 @@ #include <QSpinBox> #include <QStandardItemModel> #include <QTextEdit> +#include <QTreeWidget> #include <QUndoStack> using namespace Layouting; @@ -93,6 +96,7 @@ public: BaseAspect::DataCloner m_dataCloner; QList<BaseAspect::DataExtractor> m_dataExtractors; + MacroExpander *m_expander = globalMacroExpander(); QUndoStack *m_undoStack = nullptr; }; @@ -103,16 +107,32 @@ public: \brief The \c BaseAspect class provides a common base for classes implementing aspects. - An aspect is a hunk of data like a property or collection of related + An \e aspect is a hunk of data like a property or collection of related properties of some object, together with a description of its behavior for common operations like visualizing or persisting. - Simple aspects are for example a boolean property represented by a QCheckBox + Simple aspects are, for example, a boolean property represented by a QCheckBox in the user interface, or a string property represented by a PathChooser, - selecting directories in the filesystem. + for selecting directories in the filesystem. - While aspects implementations usually have the ability to visualize and to persist + While aspects implementations usually can visualize and persist their data, or use an ID, neither of these is mandatory. + + The derived classes can implement addToLayout() to create a UI. + + Implement \c guiToBuffer() and \c bufferToGui() to synchronize the UI with + the internal data. +*/ + +/*! + \enum Utils::BaseAspect::Announcement + + Whether to emit a signal when a value changes. + + \value DoEmit + Emit a signal. + \value BeQuiet + Don't emit a signal. */ /*! @@ -159,7 +179,9 @@ QVariant BaseAspect::variantValue() const /*! Sets \a value. - Prefer the typed setValue() of derived classes. + If \a howToAnnounce is set to \c DoEmit, emits the \c valueChanged signal. + + Prefer the typed \c setValue() of the derived classes. */ void BaseAspect::setVariantValue(const QVariant &value, Announcement howToAnnounce) { @@ -185,7 +207,20 @@ QVariant BaseAspect::defaultVariantValue() const } /*! - \fn TypedAspect::setDefaultValue(const ValueType &value) + \class Utils::TypedAspect + \inheaderfile utils/aspects.h + \inmodule QtCreator + + \brief The \c TypedAspect class is a helper class for implementing a simple + aspect. + + A typed aspect contains a single piece of data that is of the type + \c ValueType. +*/ + + +/*! + \fn template <typename ValueType> void Utils::TypedAspect<ValueType>::setDefaultValue(const ValueType &value) Sets a default \a value and the current value for this aspect. @@ -248,14 +283,14 @@ QLabel *BaseAspect::createLabel() return label; } -void BaseAspect::addLabeledItem(LayoutItem &parent, QWidget *widget) +void BaseAspect::addLabeledItem(Layout &parent, QWidget *widget) { if (QLabel *l = createLabel()) { l->setBuddy(widget); parent.addItem(l); - parent.addItem(Span(std::max(d->m_spanX - 1, 1), LayoutItem(widget))); + parent.addItem(Span(std::max(d->m_spanX - 1, 1), widget)); } else { - parent.addItem(LayoutItem(widget)); + parent.addItem(widget); } } @@ -501,21 +536,20 @@ AspectContainer *BaseAspect::container() const Adds the visual representation of this aspect to the layout with the specified \a parent using a layout builder. */ -void BaseAspect::addToLayout(LayoutItem &) +void BaseAspect::addToLayout(Layout &) { } -void createItem(Layouting::LayoutItem *item, const BaseAspect &aspect) +void addToLayout(Layouting::Layout *iface, BaseAspect &aspect) { - const_cast<BaseAspect &>(aspect).addToLayout(*item); + aspect.addToLayout(*iface); } -void createItem(Layouting::LayoutItem *item, const BaseAspect *aspect) +void addToLayout(Layouting::Layout *item, BaseAspect *aspect) { - const_cast<BaseAspect *>(aspect)->addToLayout(*item); + aspect->addToLayout(*item); } - /*! Updates this aspect's value from user-initiated changes in the widget. @@ -692,6 +726,27 @@ QVariant BaseAspect::fromSettingsValue(const QVariant &val) const return d->m_fromSettings ? d->m_fromSettings(val) : val; } +void BaseAspect::setMacroExpander(MacroExpander *expander) +{ + d->m_expander = expander; +} + +MacroExpander *BaseAspect::macroExpander() const +{ + return d->m_expander; +} + +void BaseAspect::addMacroExpansion(QWidget *w) +{ + if (!d->m_expander) + return; + const auto chooser = new VariableChooser(w); + chooser->addSupportedWidget(w); + chooser->addMacroExpanderProvider([this] { return d->m_expander; }); + if (auto pathChooser = qobject_cast<PathChooser *>(w)) + pathChooser->setMacroExpander(d->m_expander); +} + namespace Internal { class BoolAspectPrivate @@ -827,15 +882,15 @@ public: aspect->bufferToGui(); } - void addToLayoutFirst(LayoutItem &parent) + void addToLayoutFirst(Layout &parent) { if (m_checked && m_checkBoxPlacement == CheckBoxPlacement::Top) { m_checked->addToLayout(parent); - parent.addItem(br); + parent.flush(); } } - void addToLayoutLast(LayoutItem &parent) + void addToLayoutLast(Layout &parent) { if (m_checked && m_checkBoxPlacement == CheckBoxPlacement::Right) m_checked->addToLayout(parent); @@ -855,7 +910,6 @@ public: Qt::TextElideMode m_elideMode = Qt::ElideNone; QString m_placeHolderText; Key m_historyCompleterKey; - MacroExpanderProvider m_expanderProvider; StringAspect::ValueAcceptor m_valueAcceptor; std::optional<FancyLineEdit::ValidationFunction> m_validator; @@ -900,6 +954,10 @@ public: class StringListAspectPrivate { public: + UndoableValue<QStringList> undoable; + bool allowAdding{true}; + bool allowRemoving{true}; + bool allowEditing{true}; }; class FilePathListAspectPrivate @@ -939,9 +997,6 @@ public: Based on QTextEdit, used for user-editable strings that often do not fit on a line. - \value PathChooserDisplay - Based on Utils::PathChooser. - \value PasswordLineEditDisplay Based on QLineEdit, used for password strings @@ -1086,16 +1141,6 @@ void StringAspect::setAcceptRichText(bool acceptRichText) emit acceptRichTextChanged(acceptRichText); } -void StringAspect::setMacroExpanderProvider(const MacroExpanderProvider &expanderProvider) -{ - d->m_expanderProvider = expanderProvider; -} - -void StringAspect::setUseGlobalMacroExpander() -{ - d->m_expanderProvider = &globalMacroExpander; -} - void StringAspect::setUseResetButton() { d->m_useResetButton = true; @@ -1112,18 +1157,10 @@ void StringAspect::setAutoApplyOnEditingFinished(bool applyOnEditingFinished) d->m_autoApplyOnEditingFinished = applyOnEditingFinished; } -void StringAspect::addToLayout(LayoutItem &parent) +void StringAspect::addToLayout(Layout &parent) { d->m_checkerImpl.addToLayoutFirst(parent); - const auto useMacroExpander = [this](QWidget *w) { - if (!d->m_expanderProvider) - return; - const auto chooser = new VariableChooser(w); - chooser->addSupportedWidget(w); - chooser->addMacroExpanderProvider(d->m_expanderProvider); - }; - const QString displayedString = d->m_displayFilter ? d->m_displayFilter(volatileValue()) : volatileValue(); @@ -1131,6 +1168,7 @@ void StringAspect::addToLayout(LayoutItem &parent) case PasswordLineEditDisplay: case LineEditDisplay: { auto lineEditDisplay = createSubWidget<FancyLineEdit>(); + addMacroExpansion(lineEditDisplay); lineEditDisplay->setPlaceholderText(d->m_placeHolderText); if (!d->m_historyCompleterKey.isEmpty()) lineEditDisplay->setHistoryCompleter(d->m_historyCompleterKey); @@ -1164,7 +1202,6 @@ void StringAspect::addToLayout(LayoutItem &parent) } addLabeledItem(parent, lineEditDisplay); - useMacroExpander(lineEditDisplay); if (d->m_useResetButton) { auto resetButton = createSubWidget<QPushButton>(Tr::tr("Reset")); resetButton->setEnabled(lineEditDisplay->text() != defaultValue()); @@ -1222,6 +1259,7 @@ void StringAspect::addToLayout(LayoutItem &parent) } case TextEditDisplay: { auto textEditDisplay = createSubWidget<QTextEdit>(); + addMacroExpansion(textEditDisplay); textEditDisplay->setPlaceholderText(d->m_placeHolderText); textEditDisplay->setUndoRedoEnabled(false); textEditDisplay->setAcceptRichText(d->m_acceptRichText); @@ -1240,7 +1278,6 @@ void StringAspect::addToLayout(LayoutItem &parent) } addLabeledItem(parent, textEditDisplay); - useMacroExpander(textEditDisplay); bufferToGui(); connect(this, &StringAspect::acceptRichTextChanged, @@ -1290,8 +1327,10 @@ void StringAspect::addToLayout(LayoutItem &parent) QString StringAspect::expandedValue() const { - if (!m_internal.isEmpty() && d->m_expanderProvider) - return d->m_expanderProvider()->expand(m_internal); + if (!m_internal.isEmpty()) { + if (auto expander = macroExpander()) + return expander->expand(m_internal); + } return m_internal; } @@ -1373,10 +1412,10 @@ public: PathChooser::Kind m_expectedKind = PathChooser::File; Environment m_environment; QPointer<PathChooser> m_pathChooserDisplay; - MacroExpanderProvider m_expanderProvider; FilePath m_baseFileName; StringAspect::ValueAcceptor m_valueAcceptor; std::optional<FancyLineEdit::ValidationFunction> m_validator; + std::optional<FilePath> m_effectiveBinary; std::function<void()> m_openTerminal; CheckableAspectImplementation m_checkerImpl; @@ -1397,6 +1436,8 @@ FilePathAspect::FilePathAspect(AspectContainer *container) addDataExtractor(this, &FilePathAspect::value, &Data::value); addDataExtractor(this, &FilePathAspect::operator(), &Data::filePath); + + connect(this, &BaseAspect::changed, this, [this] { d->m_effectiveBinary.reset(); }); } FilePathAspect::~FilePathAspect() = default; @@ -1416,7 +1457,35 @@ FilePath FilePathAspect::operator()() const FilePath FilePathAspect::expandedValue() const { - return FilePath::fromUserInput(TypedAspect::value()); + const auto value = TypedAspect::value(); + if (!value.isEmpty()) { + if (auto expander = macroExpander()) + return FilePath::fromUserInput(expander->expand(value)); + } + return FilePath::fromUserInput(value); +} + +/*! + Returns the full path of the set command. Only makes a difference if + expected kind is \c Command or \c ExistingCommand and the current + file path is an executable provided without its path. + Performs a lookup in PATH if necessary. + */ +FilePath FilePathAspect::effectiveBinary() const +{ + if (d->m_effectiveBinary) + return *d->m_effectiveBinary; + + const FilePath current = expandedValue(); + const PathChooser::Kind kind = d->m_expectedKind; + if (kind != PathChooser::ExistingCommand && kind != PathChooser::Command) + return current; + + if (current.needsDevice()) + return current; + + d->m_effectiveBinary.emplace(current.searchInPath()); + return *d->m_effectiveBinary; } QString FilePathAspect::value() const @@ -1425,7 +1494,9 @@ QString FilePathAspect::value() const } /*! - Sets the value of this file path aspect to \a value. + Sets the value of this file path aspect to \a filePath. + + If \a howToAnnounce is set to \c DoEmit, emits the \c valueChanged signal. \note This does not use any check that the value is actually a file path. @@ -1518,21 +1589,14 @@ PathChooser *FilePathAspect::pathChooser() const return d->m_pathChooserDisplay.data(); } -void FilePathAspect::addToLayout(Layouting::LayoutItem &parent) +void FilePathAspect::addToLayout(Layouting::Layout &parent) { d->m_checkerImpl.addToLayoutFirst(parent); - const auto useMacroExpander = [this](QWidget *w) { - if (!d->m_expanderProvider) - return; - const auto chooser = new VariableChooser(w); - chooser->addSupportedWidget(w); - chooser->addMacroExpanderProvider(d->m_expanderProvider); - }; - const QString displayedString = d->m_displayFilter ? d->m_displayFilter(value()) : value(); d->m_pathChooserDisplay = createSubWidget<PathChooser>(); + addMacroExpansion(d->m_pathChooserDisplay->lineEdit()); d->m_pathChooserDisplay->setExpectedKind(d->m_expectedKind); if (!d->m_historyCompleterKey.isEmpty()) d->m_pathChooserDisplay->setHistoryCompleter(d->m_historyCompleterKey); @@ -1557,7 +1621,6 @@ void FilePathAspect::addToLayout(Layouting::LayoutItem &parent) d->m_pathChooserDisplay->lineEdit()->setPlaceholderText(d->m_placeHolderText); d->m_checkerImpl.updateWidgetFromCheckStatus(this, d->m_pathChooserDisplay.data()); addLabeledItem(parent, d->m_pathChooserDisplay); - useMacroExpander(d->m_pathChooserDisplay->lineEdit()); connect(d->m_pathChooserDisplay, &PathChooser::validChanged, this, &FilePathAspect::validChanged); bufferToGui(); if (isAutoApply() && d->m_autoApplyOnEditingFinished) { @@ -1655,9 +1718,12 @@ void FilePathAspect::setAutoApplyOnEditingFinished(bool applyOnEditingFinished) */ void FilePathAspect::setExpectedKind(const PathChooser::Kind expectedKind) { - d->m_expectedKind = expectedKind; - if (d->m_pathChooserDisplay) - d->m_pathChooserDisplay->setExpectedKind(expectedKind); + if (d->m_expectedKind != expectedKind) { + d->m_expectedKind = expectedKind; + d->m_effectiveBinary.reset(); + if (d->m_pathChooserDisplay) + d->m_pathChooserDisplay->setExpectedKind(expectedKind); + } } void FilePathAspect::setEnvironment(const Environment &env) @@ -1698,11 +1764,6 @@ void FilePathAspect::setHistoryCompleter(const Key &historyCompleterKey) d->m_pathChooserDisplay->setHistoryCompleter(historyCompleterKey); } -void FilePathAspect::setMacroExpanderProvider(const MacroExpanderProvider &expanderProvider) -{ - d->m_expanderProvider = expanderProvider; -} - void FilePathAspect::validateInput() { if (d->m_pathChooserDisplay) @@ -1736,7 +1797,7 @@ ColorAspect::ColorAspect(AspectContainer *container) ColorAspect::~ColorAspect() = default; -void ColorAspect::addToLayout(Layouting::LayoutItem &parent) +void ColorAspect::addToLayout(Layouting::Layout &parent) { QTC_CHECK(!d->m_colorButton); d->m_colorButton = createSubWidget<QtColorButton>(); @@ -1913,7 +1974,7 @@ BoolAspect::BoolAspect(AspectContainer *container) */ BoolAspect::~BoolAspect() = default; -void BoolAspect::addToLayoutHelper(Layouting::LayoutItem &parent, QAbstractButton *button) +void BoolAspect::addToLayoutHelper(Layouting::Layout &parent, QAbstractButton *button) { switch (d->m_labelPlacement) { case LabelPlacement::Compact: @@ -1922,7 +1983,7 @@ void BoolAspect::addToLayoutHelper(Layouting::LayoutItem &parent, QAbstractButto break; case LabelPlacement::AtCheckBox: button->setText(labelText()); - parent.addItem(empty()); + parent.addItem(empty); parent.addItem(button); break; case LabelPlacement::InExtraLabel: @@ -1940,20 +2001,18 @@ void BoolAspect::addToLayoutHelper(Layouting::LayoutItem &parent, QAbstractButto }); } -LayoutItem BoolAspect::adoptButton(QAbstractButton *button) +std::function<void(Layouting::Layout *)> BoolAspect::adoptButton(QAbstractButton *button) { - LayoutItem parent; - - addToLayoutHelper(parent, button); - - bufferToGui(); - return parent; + return [this, button](Layouting::Layout *layout) { + addToLayoutHelper(*layout, button); + bufferToGui(); + }; } /*! \reimp */ -void BoolAspect::addToLayout(Layouting::LayoutItem &parent) +void BoolAspect::addToLayout(Layouting::Layout &parent) { QCheckBox *checkBox = createSubWidget<QCheckBox>(); addToLayoutHelper(parent, checkBox); @@ -2059,7 +2118,7 @@ SelectionAspect::~SelectionAspect() = default; /*! \reimp */ -void SelectionAspect::addToLayout(Layouting::LayoutItem &parent) +void SelectionAspect::addToLayout(Layouting::Layout &parent) { QTC_CHECK(d->m_buttonGroup == nullptr); QTC_CHECK(!d->m_comboBox); @@ -2233,7 +2292,7 @@ MultiSelectionAspect::~MultiSelectionAspect() = default; /*! \reimp */ -void MultiSelectionAspect::addToLayout(LayoutItem &builder) +void MultiSelectionAspect::addToLayout(Layout &builder) { QTC_CHECK(d->m_listView == nullptr); if (d->m_allValues.isEmpty()) @@ -2342,7 +2401,7 @@ IntegerAspect::~IntegerAspect() = default; /*! \reimp */ -void IntegerAspect::addToLayout(Layouting::LayoutItem &parent) +void IntegerAspect::addToLayout(Layouting::Layout &parent) { QTC_CHECK(!d->m_spinBox); d->m_spinBox = createSubWidget<QSpinBox>(); @@ -2444,7 +2503,7 @@ DoubleAspect::~DoubleAspect() = default; /*! \reimp */ -void DoubleAspect::addToLayout(LayoutItem &builder) +void DoubleAspect::addToLayout(Layout &builder) { QTC_CHECK(!d->m_spinBox); d->m_spinBox = createSubWidget<QDoubleSpinBox>(); @@ -2598,13 +2657,119 @@ StringListAspect::StringListAspect(AspectContainer *container) */ StringListAspect::~StringListAspect() = default; -/*! - \reimp -*/ -void StringListAspect::addToLayout(LayoutItem &parent) +bool StringListAspect::guiToBuffer() { - Q_UNUSED(parent) - // TODO - when needed. + const QStringList newValue = d->undoable.get(); + if (newValue != m_buffer) { + m_buffer = newValue; + return true; + } + return false; +} + +void StringListAspect::bufferToGui() +{ + d->undoable.setWithoutUndo(m_buffer); +} + +void StringListAspect::addToLayout(Layout &parent) +{ + d->undoable.setSilently(value()); + + auto editor = new QTreeWidget(); + editor->setHeaderHidden(true); + editor->setRootIsDecorated(false); + editor->setEditTriggers( + d->allowEditing ? QAbstractItemView::AllEditTriggers : QAbstractItemView::NoEditTriggers); + + QPushButton *add = d->allowAdding ? new QPushButton(Tr::tr("Add")) : nullptr; + QPushButton *remove = d->allowRemoving ? new QPushButton(Tr::tr("Remove")) : nullptr; + + auto itemsToStringList = [editor] { + QStringList items; + const QTreeWidgetItem *rootItem = editor->invisibleRootItem(); + for (int i = 0, count = rootItem->childCount(); i < count; ++i) { + auto expr = rootItem->child(i)->data(0, Qt::DisplayRole).toString(); + items.append(expr); + } + return items; + }; + + auto populate = [editor, this] { + editor->clear(); + for (const QString &entry : d->undoable.get()) { + auto item = new QTreeWidgetItem(editor, {entry}); + item->setData(0, Qt::ToolTipRole, entry); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); + } + }; + + if (add) { + connect(add, &QPushButton::clicked, this, [this, populate, editor] { + d->undoable.setSilently(d->undoable.get() << ""); + populate(); + const QTreeWidgetItem *root = editor->invisibleRootItem(); + QTreeWidgetItem *lastChild = root->child(root->childCount() - 1); + const QModelIndex index = editor->indexFromItem(lastChild, 0); + editor->edit(index); + }); + } + + if (remove) { + connect(remove, &QPushButton::clicked, this, [this, editor, itemsToStringList] { + const QList<QTreeWidgetItem *> selected = editor->selectedItems(); + QTC_ASSERT(selected.size() == 1, return); + editor->invisibleRootItem()->removeChild(selected.first()); + delete selected.first(); + d->undoable.set(undoStack(), itemsToStringList()); + }); + } + + connect( + &d->undoable.m_signal, &UndoSignaller::changed, editor, [this, populate, itemsToStringList] { + if (itemsToStringList() != d->undoable.get()) + populate(); + + handleGuiChanged(); + }); + + connect( + editor->model(), + &QAbstractItemModel::dataChanged, + this, + [this, + itemsToStringList](const QModelIndex &tl, const QModelIndex &br, const QList<int> &roles) { + if (!roles.contains(Qt::DisplayRole)) + return; + if (tl != br) + return; + d->undoable.set(undoStack(), itemsToStringList()); + }); + + populate(); + + parent.addItem( + // clang-format off + Column { + createLabel(), + Row { + editor, + If { d->allowAdding || d->allowRemoving, { + Column { + If { d->allowAdding, {add}, {}}, + If { d->allowRemoving, {remove}, {}}, + st, + } + }, {}}, + } + } // clang-format on + ); + + registerSubWidget(editor); + if (d->allowAdding) + registerSubWidget(add); + if (d->allowRemoving) + registerSubWidget(remove); } void StringListAspect::appendValue(const QString &s, bool allowDuplicates) @@ -2640,6 +2805,32 @@ void StringListAspect::removeValues(const QStringList &values) setValue(val); } +void StringListAspect::setUiAllowAdding(bool allowAdding) +{ + d->allowAdding = allowAdding; +} +void StringListAspect::setUiAllowRemoving(bool allowRemoving) +{ + d->allowRemoving = allowRemoving; +} +void StringListAspect::setUiAllowEditing(bool allowEditing) +{ + d->allowEditing = allowEditing; +} + +bool StringListAspect::uiAllowAdding() const +{ + return d->allowAdding; +} +bool StringListAspect::uiAllowRemoving() const +{ + return d->allowRemoving; +} +bool StringListAspect::uiAllowEditing() const +{ + return d->allowEditing; +} + /*! \class Utils::FilePathListAspect \inmodule QtCreator @@ -2677,7 +2868,7 @@ void FilePathListAspect::bufferToGui() d->undoable.setWithoutUndo(m_buffer); } -void FilePathListAspect::addToLayout(LayoutItem &parent) +void FilePathListAspect::addToLayout(Layout &parent) { d->undoable.setSilently(value()); @@ -2771,7 +2962,7 @@ IntegersAspect::~IntegersAspect() = default; /*! \reimp */ -void IntegersAspect::addToLayout(Layouting::LayoutItem &parent) +void IntegersAspect::addToLayout(Layouting::Layout &parent) { Q_UNUSED(parent) // TODO - when needed. @@ -2790,8 +2981,8 @@ void IntegersAspect::addToLayout(Layouting::LayoutItem &parent) */ /*! - Constructs a text display showing the \a message with an icon representing - type \a type. + Constructs a text display with the parent \a container. The display shows + \a message and an icon representing the type \a type. */ TextDisplay::TextDisplay(AspectContainer *container, const QString &message, InfoLabel::InfoType type) : BaseAspect(container), d(new Internal::TextDisplayPrivate) @@ -2808,7 +2999,7 @@ TextDisplay::~TextDisplay() = default; /*! \reimp */ -void TextDisplay::addToLayout(LayoutItem &parent) +void TextDisplay::addToLayout(Layout &parent) { if (!d->m_label) { d->m_label = createSubWidget<InfoLabel>(d->m_message, d->m_type); @@ -2860,7 +3051,7 @@ public: QList<BaseAspect *> m_items; // Both owned and non-owned. QList<BaseAspect *> m_ownedItems; // Owned only. QStringList m_settingsGroup; - std::function<Layouting::LayoutItem ()> m_layouter; + std::function<Layouting::Layout()> m_layouter; }; AspectContainer::AspectContainer() @@ -2875,6 +3066,11 @@ AspectContainer::~AspectContainer() qDeleteAll(d->m_ownedItems); } +void AspectContainer::addToLayout(Layouting::Layout &parent) +{ + parent.addItem(layouter()()); +} + /*! \internal */ @@ -2884,6 +3080,8 @@ void AspectContainer::registerAspect(BaseAspect *aspect, bool takeOwnership) d->m_items.append(aspect); if (takeOwnership) d->m_ownedItems.append(aspect); + + connect(aspect, &BaseAspect::changed, this, [this]() { emit changed(); }); } void AspectContainer::registerAspects(const AspectContainer &aspects) @@ -2912,12 +3110,12 @@ AspectContainer::const_iterator AspectContainer::end() const return d->m_items.cend(); } -void AspectContainer::setLayouter(const std::function<Layouting::LayoutItem ()> &layouter) +void AspectContainer::setLayouter(const std::function<Layouting::Layout ()> &layouter) { d->m_layouter = layouter; } -std::function<LayoutItem ()> AspectContainer::layouter() const +std::function<Layout ()> AspectContainer::layouter() const { return d->m_layouter; } @@ -3033,6 +3231,14 @@ void AspectContainer::setUndoStack(QUndoStack *undoStack) aspect->setUndoStack(undoStack); } +void AspectContainer::setMacroExpander(MacroExpander *expander) +{ + BaseAspect::setMacroExpander(expander); + + for (BaseAspect *aspect : std::as_const(d->m_items)) + aspect->setMacroExpander(expander); +} + bool AspectContainer::equals(const AspectContainer &other) const { // FIXME: Expensive, but should not really be needed in a fully aspectified world. @@ -3414,7 +3620,7 @@ private: int m_index; }; -void AspectList::addToLayout(Layouting::LayoutItem &parent) +void AspectList::addToLayout(Layouting::Layout &parent) { using namespace Layouting; @@ -3433,7 +3639,7 @@ void AspectList::addToLayout(Layouting::LayoutItem &parent) addItem(d->createItem()); }); - Column column{noMargin()}; + Column column{noMargin}; forEachItem<BaseAspect>([&column, this](const std::shared_ptr<BaseAspect> &item, int idx) { auto removeBtn = new IconButton; @@ -3524,7 +3730,7 @@ bool StringSelectionAspect::guiToBuffer() return oldBuffer != m_buffer; } -void StringSelectionAspect::addToLayout(Layouting::LayoutItem &parent) +void StringSelectionAspect::addToLayout(Layouting::Layout &parent) { QTC_ASSERT(m_fillCallback, return); @@ -3597,4 +3803,4 @@ void StringSelectionAspect::addToLayout(Layouting::LayoutItem &parent) return addLabeledItem(parent, comboBox); } -} // namespace Utils +} // Utils diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index a5c22bb858..60a198c1f7 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -7,7 +7,6 @@ #include "guiutils.h" #include "id.h" #include "infolabel.h" -#include "macroexpander.h" #include "pathchooser.h" #include "qtcsettings.h" #include "store.h" @@ -29,15 +28,14 @@ class QStandardItemModel; class QItemSelectionModel; QT_END_NAMESPACE -namespace Layouting { -class LayoutItem; -} +namespace Layouting { class Layout; } namespace Utils { class AspectContainer; class BoolAspect; class CheckableDecider; +class MacroExpander; namespace Internal { class AspectContainerPrivate; @@ -64,6 +62,7 @@ class QTCREATOR_UTILS_EXPORT BaseAspect : public QObject public: BaseAspect(AspectContainer *container = nullptr); + BaseAspect(const BaseAspect &) = delete; ~BaseAspect() override; Id id() const; @@ -125,9 +124,7 @@ public: virtual void toMap(Store &map) const; virtual void toActiveMap(Store &map) const { toMap(map); } virtual void volatileToMap(Store &map) const; - - virtual void addToLayout(Layouting::LayoutItem &parent); - + virtual void addToLayout(Layouting::Layout &parent); virtual void readSettings(); virtual void writeSettings() const; @@ -205,6 +202,9 @@ public: // This is expensive. Do not use without good reason void writeToSettingsImmediatly() const; + void setMacroExpander(MacroExpander *expander); + MacroExpander *macroExpander() const; + signals: void changed(); void volatileValueChanged(); @@ -222,8 +222,10 @@ protected: virtual void handleGuiChanged(); + void addMacroExpansion(QWidget *w); + QLabel *createLabel(); - void addLabeledItem(Layouting::LayoutItem &parent, QWidget *widget); + void addLabeledItem(Layouting::Layout &parent, QWidget *widget); void setDataCreatorHelper(const DataCreator &creator) const; void setDataClonerHelper(const DataCloner &cloner) const; @@ -276,8 +278,8 @@ private: friend class Internal::CheckableAspectImplementation; }; -QTCREATOR_UTILS_EXPORT void createItem(Layouting::LayoutItem *item, const BaseAspect &aspect); -QTCREATOR_UTILS_EXPORT void createItem(Layouting::LayoutItem *item, const BaseAspect *aspect); +QTCREATOR_UTILS_EXPORT void addToLayout(Layouting::Layout *layout, BaseAspect *aspect); +QTCREATOR_UTILS_EXPORT void addToLayout(Layouting::Layout *layout, BaseAspect &aspect); template<typename ValueType> class @@ -439,7 +441,7 @@ public: BoolAspect(AspectContainer *container = nullptr); ~BoolAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; std::function<void(QObject *)> groupChecker(); Utils::CheckableDecider askAgainCheckableDecider(); @@ -452,10 +454,10 @@ public: LabelPlacement labelPlacement = LabelPlacement::InExtraLabel); void setLabelPlacement(LabelPlacement labelPlacement); - Layouting::LayoutItem adoptButton(QAbstractButton *button); + std::function<void(Layouting::Layout *)> adoptButton(QAbstractButton *button); private: - void addToLayoutHelper(Layouting::LayoutItem &parent, QAbstractButton *button); + void addToLayoutHelper(Layouting::Layout &parent, QAbstractButton *button); void bufferToGui() override; bool guiToBuffer() override; @@ -504,7 +506,7 @@ public: ColorAspect(AspectContainer *container = nullptr); ~ColorAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; private: void bufferToGui() override; @@ -521,7 +523,7 @@ public: SelectionAspect(AspectContainer *container = nullptr); ~SelectionAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; void finish() override; QString stringValue() const; @@ -569,7 +571,7 @@ public: MultiSelectionAspect(AspectContainer *container = nullptr); ~MultiSelectionAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; enum class DisplayStyle { ListView }; void setDisplayStyle(DisplayStyle style); @@ -596,7 +598,7 @@ public: StringAspect(AspectContainer *container = nullptr); ~StringAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; QString operator()() const { return expandedValue(); } QString expandedValue() const; @@ -610,8 +612,6 @@ public: void setPlaceHolderText(const QString &placeHolderText); void setHistoryCompleter(const Key &historyCompleterKey); void setAcceptRichText(bool acceptRichText); - void setMacroExpanderProvider(const MacroExpanderProvider &expanderProvider); - void setUseGlobalMacroExpander(); void setUseResetButton(); void setValidationFunction(const FancyLineEdit::ValidationFunction &validator); void setAutoApplyOnEditingFinished(bool applyOnEditingFinished); @@ -666,6 +666,7 @@ public: }; FilePath operator()() const; + FilePath effectiveBinary() const; FilePath expandedValue() const; QString value() const; void setValue(const FilePath &filePath, Announcement howToAnnounce = DoEmit); @@ -687,7 +688,6 @@ public: void setValidationFunction(const FancyLineEdit::ValidationFunction &validator); void setDisplayFilter(const std::function<QString (const QString &)> &displayFilter); void setHistoryCompleter(const Key &historyCompleterKey); - void setMacroExpanderProvider(const MacroExpanderProvider &expanderProvider); void setShowToolTipOnLabel(bool show); void setAutoApplyOnEditingFinished(bool applyOnEditingFinished); @@ -703,7 +703,7 @@ public: PathChooser *pathChooser() const; // Avoid to use. - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; void fromMap(const Utils::Store &map) override; void toMap(Utils::Store &map) const override; @@ -730,7 +730,7 @@ public: IntegerAspect(AspectContainer *container = nullptr); ~IntegerAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; void setRange(qint64 min, qint64 max); void setLabel(const QString &label); // FIXME: Use setLabelText @@ -759,7 +759,7 @@ public: DoubleAspect(AspectContainer *container = nullptr); ~DoubleAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; void setRange(double min, double max); void setPrefix(const QString &prefix); @@ -831,13 +831,24 @@ public: StringListAspect(AspectContainer *container = nullptr); ~StringListAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + bool guiToBuffer() override; + void bufferToGui() override; + + void addToLayout(Layouting::Layout &parent) override; void appendValue(const QString &value, bool allowDuplicates = true); void removeValue(const QString &value); void appendValues(const QStringList &values, bool allowDuplicates = true); void removeValues(const QStringList &values); + void setUiAllowAdding(bool allowAdding); + void setUiAllowRemoving(bool allowRemoving); + void setUiAllowEditing(bool allowEditing); + + bool uiAllowAdding() const; + bool uiAllowRemoving() const; + bool uiAllowEditing() const; + private: std::unique_ptr<Internal::StringListAspectPrivate> d; }; @@ -855,7 +866,7 @@ public: bool guiToBuffer() override; void bufferToGui() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; void setPlaceHolderText(const QString &placeHolderText); void appendValue(const FilePath &path, bool allowDuplicates = true); @@ -875,7 +886,7 @@ public: IntegersAspect(AspectContainer *container = nullptr); ~IntegersAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; }; class QTCREATOR_UTILS_EXPORT TextDisplay : public BaseAspect @@ -888,7 +899,7 @@ public: InfoLabel::InfoType type = InfoLabel::None); ~TextDisplay() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; void setIconType(InfoLabel::InfoType t); void setText(const QString &message); @@ -939,6 +950,8 @@ public: AspectContainer(const AspectContainer &) = delete; AspectContainer &operator=(const AspectContainer &) = delete; + void addToLayout(Layouting::Layout &parent) override; + void registerAspect(BaseAspect *aspect, bool takeOwnership = false); void registerAspects(const AspectContainer &aspects); @@ -964,6 +977,8 @@ public: bool isDirty() override; void setUndoStack(QUndoStack *undoStack) override; + void setMacroExpander(MacroExpander *expander); + template <typename T> T *aspect() const { for (BaseAspect *aspect : aspects()) @@ -989,8 +1004,8 @@ public: const_iterator begin() const; const_iterator end() const; - void setLayouter(const std::function<Layouting::LayoutItem()> &layouter); - std::function<Layouting::LayoutItem()> layouter() const; + void setLayouter(const std::function<Layouting::Layout()> &layouter); + std::function<Layouting::Layout()> layouter() const; signals: void applied(); @@ -1131,7 +1146,7 @@ public: QVariant volatileVariantValue() const override { return {}; } - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; private: std::unique_ptr<Internal::AspectListPrivate> d; @@ -1143,7 +1158,7 @@ class QTCREATOR_UTILS_EXPORT StringSelectionAspect : public Utils::TypedAspect<Q public: StringSelectionAspect(Utils::AspectContainer *container = nullptr); - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; using ResultCallback = std::function<void(QList<QStandardItem *> items)>; using FillCallback = std::function<void(ResultCallback)>; diff --git a/src/libs/utils/async.h b/src/libs/utils/async.h index af6fb3fd85..8c7155bc13 100644 --- a/src/libs/utils/async.h +++ b/src/libs/utils/async.h @@ -7,6 +7,7 @@ #include "futuresynchronizer.h" #include "qtcassert.h" +#include "threadutils.h" #include <solutions/tasking/tasktree.h> @@ -130,7 +131,9 @@ template <typename ResultType> class Async : public AsyncBase { public: - Async() { + Async() + : m_synchronizer(isMainThread() ? futureSynchronizer() : nullptr) + { connect(&m_watcher, &QFutureWatcherBase::finished, this, &AsyncBase::done); connect(&m_watcher, &QFutureWatcherBase::resultReadyAt, this, &AsyncBase::resultReadyAt); } diff --git a/src/libs/utils/changeset.cpp b/src/libs/utils/changeset.cpp index c0c54b1e48..881097c6b7 100644 --- a/src/libs/utils/changeset.cpp +++ b/src/libs/utils/changeset.cpp @@ -100,6 +100,41 @@ void ChangeSet::clear() m_error = false; } +ChangeSet ChangeSet::makeReplace(const Range &range, const QString &replacement) +{ + ChangeSet c; + c.replace(range, replacement); + return c; +} + +ChangeSet ChangeSet::makeReplace(int start, int end, const QString &replacement) +{ + ChangeSet c; + c.replace(start, end, replacement); + return c; +} + +ChangeSet ChangeSet::makeRemove(const Range &range) +{ + ChangeSet c; + c.remove(range); + return c; +} + +ChangeSet ChangeSet::makeFlip(int start1, int end1, int start2, int end2) +{ + ChangeSet c; + c.flip(start1, end1, start2, end2); + return c; +} + +ChangeSet ChangeSet::makeInsert(int pos, const QString &text) +{ + ChangeSet c; + c.insert(pos, text); + return c; +} + bool ChangeSet::replace_helper(int pos, int length, const QString &replacement) { if (hasOverlap(pos, length)) diff --git a/src/libs/utils/changeset.h b/src/libs/utils/changeset.h index 6370b20d3c..aee9c99dab 100644 --- a/src/libs/utils/changeset.h +++ b/src/libs/utils/changeset.h @@ -76,6 +76,12 @@ public: void clear(); + static ChangeSet makeReplace(const Range &range, const QString &replacement); + static ChangeSet makeReplace(int start, int end, const QString &replacement); + static ChangeSet makeRemove(const Range &range); + static ChangeSet makeFlip(int start1, int end1, int start2, int end2); + static ChangeSet makeInsert(int pos, const QString &text); + bool replace(const Range &range, const QString &replacement); bool remove(const Range &range); bool move(const Range &range, int to); diff --git a/src/libs/utils/checkablemessagebox.cpp b/src/libs/utils/checkablemessagebox.cpp index 19d3669ada..1d721e04c8 100644 --- a/src/libs/utils/checkablemessagebox.cpp +++ b/src/libs/utils/checkablemessagebox.cpp @@ -3,6 +3,7 @@ #include "checkablemessagebox.h" +#include "guiutils.h" #include "hostosinfo.h" #include "qtcassert.h" #include "qtcsettings.h" @@ -95,7 +96,7 @@ static QMessageBox::StandardButton exec( return acceptButton; } - QMessageBox msgBox(parent); + QMessageBox msgBox(dialogParent(parent)); prepare(icon, title, text, decider, buttons, defaultButton, buttonTextOverrides, msg, msgBox); msgBox.exec(); @@ -131,7 +132,7 @@ static void show(QWidget *parent, return; } - QMessageBox *msgBox = new QMessageBox(parent); + QMessageBox *msgBox = new QMessageBox(dialogParent(parent)); prepare(icon, title, text, decider, buttons, defaultButton, buttonTextOverrides, msg, *msgBox); std::optional<QPointer<QObject>> guardPtr; diff --git a/src/libs/utils/clangutils.cpp b/src/libs/utils/clangutils.cpp index 4c8c7d801d..86e8e6204e 100644 --- a/src/libs/utils/clangutils.cpp +++ b/src/libs/utils/clangutils.cpp @@ -65,7 +65,7 @@ bool checkClangdVersion(const FilePath &clangd, QString *error) QVersionNumber minimumClangdVersion() { - return QVersionNumber(14); + return QVersionNumber(17); } } // namespace Utils diff --git a/src/libs/utils/codegeneration.cpp b/src/libs/utils/codegeneration.cpp index 8842023a23..ad3b8bad18 100644 --- a/src/libs/utils/codegeneration.cpp +++ b/src/libs/utils/codegeneration.cpp @@ -29,23 +29,6 @@ QTCREATOR_UTILS_EXPORT QString fileNameToCppIdentifier(const QString &s) return rc; } -QTCREATOR_UTILS_EXPORT QString headerGuard(const QString &file) -{ - return headerGuard(file, QStringList()); -} - -QTCREATOR_UTILS_EXPORT QString headerGuard(const QString &file, const QStringList &namespaceList) -{ - const QChar underscore = QLatin1Char('_'); - QString rc; - for (int i = 0; i < namespaceList.count(); i++) - rc += namespaceList.at(i).toUpper() + underscore; - - const QFileInfo fi(file); - rc += fileNameToCppIdentifier(fi.fileName()).toUpper(); - return rc; -} - QTCREATOR_UTILS_EXPORT void writeIncludeFileDirective(const QString &file, bool globalInclude, QTextStream &str) diff --git a/src/libs/utils/codegeneration.h b/src/libs/utils/codegeneration.h index 9ea0bb28c7..e1c297eb92 100644 --- a/src/libs/utils/codegeneration.h +++ b/src/libs/utils/codegeneration.h @@ -17,9 +17,6 @@ namespace Utils { // or replacing them by an underscore). QTCREATOR_UTILS_EXPORT QString fileNameToCppIdentifier(const QString &s); -QTCREATOR_UTILS_EXPORT QString headerGuard(const QString &file); -QTCREATOR_UTILS_EXPORT QString headerGuard(const QString &file, const QStringList &namespaceList); - QTCREATOR_UTILS_EXPORT void writeIncludeFileDirective(const QString &file, bool globalInclude, diff --git a/src/libs/utils/commandline.cpp b/src/libs/utils/commandline.cpp index 8e6bc60277..b4fa60481e 100644 --- a/src/libs/utils/commandline.cpp +++ b/src/libs/utils/commandline.cpp @@ -1425,6 +1425,19 @@ CommandLine::CommandLine(const FilePath &exe, const QStringList &args) addArgs(args); } +CommandLine::CommandLine(const FilePath &exe, std::initializer_list<ArgRef> args) + : m_executable(exe) +{ + for (const ArgRef &arg : args) { + if (const auto ptr = std::get_if<const char *>(&arg.m_arg)) + addArg(QString::fromUtf8(*ptr)); + else if (const auto ptr = std::get_if<std::reference_wrapper<const QString>>(&arg.m_arg)) + addArg(*ptr); + else if (const auto ptr = std::get_if<std::reference_wrapper<const QStringList>>(&arg.m_arg)) + addArgs(*ptr); + } +} + CommandLine::CommandLine(const FilePath &exe, const QStringList &args, OsType osType) : m_executable(exe) { diff --git a/src/libs/utils/commandline.h b/src/libs/utils/commandline.h index 52ff8c5496..3718f80bca 100644 --- a/src/libs/utils/commandline.h +++ b/src/libs/utils/commandline.h @@ -11,6 +11,8 @@ #include <QPair> #include <QStringList> +#include <variant> + namespace Utils { class AbstractMacroExpander; @@ -120,8 +122,22 @@ public: CommandLine(); ~CommandLine(); + struct ArgRef + { + ArgRef(const char *arg) : m_arg(arg) {} + ArgRef(const QString &arg) : m_arg(arg) {} + ArgRef(const QStringList &args) : m_arg(args) {} + + private: + friend class CommandLine; + const std::variant<const char *, + std::reference_wrapper<const QString>, + std::reference_wrapper<const QStringList>> m_arg; + }; + explicit CommandLine(const FilePath &executable); CommandLine(const FilePath &exe, const QStringList &args); + CommandLine(const FilePath &exe, std::initializer_list<ArgRef> args); CommandLine(const FilePath &exe, const QStringList &args, OsType osType); CommandLine(const FilePath &exe, const QString &unparsedArgs, RawType); diff --git a/src/libs/utils/crumblepath.cpp b/src/libs/utils/crumblepath.cpp index 779b1c9e55..d434350460 100644 --- a/src/libs/utils/crumblepath.cpp +++ b/src/libs/utils/crumblepath.cpp @@ -111,9 +111,9 @@ void CrumblePathButton::paintEvent(QPaintEvent*) p.drawPixmap(width() - overlapSize, segmentRect.top(), middleSegmentPixmap); if (option.state & QStyle::State_Enabled) - option.palette.setColor(QPalette::ButtonText, creatorTheme()->color(Theme::PanelTextColorLight)); + option.palette.setColor(QPalette::ButtonText, creatorColor(Theme::PanelTextColorLight)); else - option.palette.setColor(QPalette::Disabled, QPalette::ButtonText, creatorTheme()->color(Theme::IconsDisabledColor)); + option.palette.setColor(QPalette::Disabled, QPalette::ButtonText, creatorColor(Theme::IconsDisabledColor)); QStylePainter sp(this); if (option.state & QStyle::State_Sunken) diff --git a/src/libs/utils/detailsbutton.cpp b/src/libs/utils/detailsbutton.cpp index 88c0ca273e..c6e1b6da3f 100644 --- a/src/libs/utils/detailsbutton.cpp +++ b/src/libs/utils/detailsbutton.cpp @@ -87,8 +87,8 @@ QColor DetailsButton::outlineColor() { return HostOsInfo::isMacHost() ? QGuiApplication::palette().color(QPalette::Mid) - : StyleHelper::mergedColors(creatorTheme()->color(Theme::TextColorNormal), - creatorTheme()->color(Theme::BackgroundColorNormal), 15); + : StyleHelper::mergedColors(creatorColor(Theme::TextColorNormal), + creatorColor(Theme::BackgroundColorNormal), 15); } void DetailsButton::paintEvent(QPaintEvent *e) diff --git a/src/libs/utils/detailswidget.cpp b/src/libs/utils/detailswidget.cpp index ec7a3d22eb..d278a609ff 100644 --- a/src/libs/utils/detailswidget.cpp +++ b/src/libs/utils/detailswidget.cpp @@ -224,7 +224,7 @@ void DetailsWidget::paintEvent(QPaintEvent *paintEvent) QPainter p(this); if (creatorTheme()->flag(Theme::FlatProjectsMode) || HostOsInfo::isMacHost()) { const QColor bgColor = creatorTheme()->flag(Theme::FlatProjectsMode) ? - creatorTheme()->color(Theme::DetailsWidgetBackgroundColor) + creatorColor(Theme::DetailsWidgetBackgroundColor) : palette().color(QPalette::Window); p.fillRect(rect(), bgColor); } diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index 50d1f276ef..303ffa6bf5 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -1059,6 +1059,10 @@ expected_str<QByteArray> UnixDeviceFileAccess::fileContents(const FilePath &file if (disconnected()) return make_unexpected_disconnected(); + expected_str<FilePath> localSource = filePath.localSource(); + if (localSource && *localSource != filePath) + return localSource->fileContents(limit, offset); + QStringList args = {"if=" + filePath.path()}; if (limit > 0 || offset > 0) { const qint64 gcd = std::gcd(limit, offset); @@ -1088,6 +1092,10 @@ expected_str<qint64> UnixDeviceFileAccess::writeFileContents(const FilePath &fil if (disconnected()) return make_unexpected_disconnected(); + expected_str<FilePath> localSource = filePath.localSource(); + if (localSource && *localSource != filePath) + return localSource->writeFileContents(data); + QStringList args = {"of=" + filePath.path()}; RunResult result = runInShell({"dd", args, OsType::OsTypeLinux}, data); diff --git a/src/libs/utils/dropsupport.cpp b/src/libs/utils/dropsupport.cpp index 7416edf9c3..4ed1245643 100644 --- a/src/libs/utils/dropsupport.cpp +++ b/src/libs/utils/dropsupport.cpp @@ -108,7 +108,7 @@ bool DropSupport::eventFilter(QObject *obj, QEvent *event) de->acceptProposedAction(); bool needToScheduleEmit = m_files.isEmpty(); m_files.append(tempFiles); - m_dropPos = de->pos(); + m_dropPos = de->position().toPoint(); if (needToScheduleEmit) { // otherwise we already have a timer pending // Delay the actual drop, to avoid conflict between // actions that happen when opening files, and actions that the item views do @@ -122,7 +122,7 @@ bool DropSupport::eventFilter(QObject *obj, QEvent *event) accepted = true; bool needToScheduleEmit = m_values.isEmpty(); m_values.append(fileDropMimeData->values()); - m_dropPos = de->pos(); + m_dropPos = de->position().toPoint(); if (needToScheduleEmit) QTimer::singleShot(100, this, &DropSupport::emitValuesDropped); } diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index be43f2a206..190cde2d2d 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -433,60 +433,82 @@ const NameValueDictionary &Environment::resolved() const m_dict = Environment::systemEnvironment().toDictionary(); m_fullDict = true; break; - case SetFixedDictionary: - m_dict = std::get<SetFixedDictionary>(item); - m_fullDict = true; + case SetFixedDictionary: { + const auto dict = std::get_if<SetFixedDictionary>(&item); + if (QTC_GUARD(dict)) { + m_dict = *dict; + m_fullDict = true; + } break; + } case SetValue: { - auto [key, value, enabled] = std::get<SetValue>(item); - m_dict.set(key, value, enabled); + const auto setvalue = std::get_if<SetValue>(&item); + if (QTC_GUARD(setvalue)) { + auto [key, value, enabled] = *setvalue; + m_dict.set(key, value, enabled); + } break; } case SetFallbackValue: { - auto [key, value] = std::get<SetFallbackValue>(item); - if (m_fullDict) { - if (m_dict.value(key).isEmpty()) + const auto fallbackvalue = std::get_if<SetFallbackValue>(&item); + if (QTC_GUARD(fallbackvalue)) { + auto [key, value] = *fallbackvalue; + if (m_fullDict) { + if (m_dict.value(key).isEmpty()) + m_dict.set(key, value, true); + } else { + QTC_ASSERT(false, qDebug() << "operating on partial dictionary"); m_dict.set(key, value, true); - } else { - QTC_ASSERT(false, qDebug() << "operating on partial dictionary"); - m_dict.set(key, value, true); + } } break; } - case UnsetValue: - m_dict.unset(std::get<UnsetValue>(item)); + case UnsetValue: { + const auto unsetvalue = std::get_if<UnsetValue>(&item); + if (QTC_GUARD(unsetvalue)) + m_dict.unset(*unsetvalue); break; + } case PrependOrSet: { - auto [key, value, sep] = std::get<PrependOrSet>(item); - QTC_ASSERT(!key.contains('='), return m_dict); - const auto it = m_dict.findKey(key); - if (it == m_dict.m_values.end()) { - m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true}); - } else { - // Prepend unless it is already there - const QString toPrepend = value + pathListSeparator(sep); - if (!it.value().first.startsWith(toPrepend)) - it.value().first.prepend(toPrepend); + const auto prependorset = std::get_if<PrependOrSet>(&item); + if (QTC_GUARD(prependorset)) { + auto [key, value, sep] = *prependorset; + QTC_ASSERT(!key.contains('='), return m_dict); + const auto it = m_dict.findKey(key); + if (it == m_dict.m_values.end()) { + m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true}); + } else { + // Prepend unless it is already there + const QString toPrepend = value + pathListSeparator(sep); + if (!it.value().first.startsWith(toPrepend)) + it.value().first.prepend(toPrepend); + } } break; } case AppendOrSet: { - auto [key, value, sep] = std::get<AppendOrSet>(item); - QTC_ASSERT(!key.contains('='), return m_dict); - const auto it = m_dict.findKey(key); - if (it == m_dict.m_values.end()) { - m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true}); - } else { - // Prepend unless it is already there - const QString toAppend = pathListSeparator(sep) + value; - if (!it.value().first.endsWith(toAppend)) - it.value().first.append(toAppend); + const auto appendorset = std::get_if<AppendOrSet>(&item); + if (QTC_GUARD(appendorset)) { + auto [key, value, sep] = *appendorset; + QTC_ASSERT(!key.contains('='), return m_dict); + const auto it = m_dict.findKey(key); + if (it == m_dict.m_values.end()) { + m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true}); + } else { + // Prepend unless it is already there + const QString toAppend = pathListSeparator(sep) + value; + if (!it.value().first.endsWith(toAppend)) + it.value().first.append(toAppend); + } } break; } case Modify: { - EnvironmentItems items = std::get<Modify>(item); - m_dict.modify(items); + const auto modify = std::get_if<Modify>(&item); + if (QTC_GUARD(modify)) { + EnvironmentItems items = *modify; + m_dict.modify(items); + } break; } case SetupEnglishOutput: diff --git a/src/libs/utils/environmentmodel.cpp b/src/libs/utils/environmentmodel.cpp index 6ac9b35640..e81ee520d8 100644 --- a/src/libs/utils/environmentmodel.cpp +++ b/src/libs/utils/environmentmodel.cpp @@ -397,7 +397,8 @@ EnvironmentItems EnvironmentModel::userChanges() const void EnvironmentModel::setUserChanges(const EnvironmentItems &items) { EnvironmentItems filtered = Utils::filtered(items, [](const EnvironmentItem &i) { - return i.name != "export " && !i.name.contains('='); + return i.operation == EnvironmentItem::Comment + || (i.name != "export " && !i.name.contains('=')); }); // We assume nobody is reordering the items here. if (filtered == d->m_items) diff --git a/src/libs/utils/execmenu.cpp b/src/libs/utils/execmenu.cpp index 13d7915295..4e463bdd6f 100644 --- a/src/libs/utils/execmenu.cpp +++ b/src/libs/utils/execmenu.cpp @@ -44,7 +44,8 @@ QAction *execMenuAtWidget(QMenu *menu, QWidget *widget) } /*! - Adds tool tips to the menu that show the actions tool tip when hovering over an entry. + Adds tool tips to the \a menu that show the action's tool tip when hovering + over an entry. */ void addToolTipsToMenu(QMenu *menu) { diff --git a/src/libs/utils/expected.h b/src/libs/utils/expected.h index 33231c1246..0e018eb8ab 100644 --- a/src/libs/utils/expected.h +++ b/src/libs/utils/expected.h @@ -30,3 +30,12 @@ using expected_str = tl::expected<T, QString>; } \ do { \ } while (0) + +#define QTC_CHECK_EXPECTED(expected) \ + if (Q_LIKELY(expected)) { \ + } else { \ + ::Utils::writeAssertLocation( \ + QString("%1:%2: %3").arg(__FILE__).arg(__LINE__).arg(expected.error()).toUtf8().data()); \ + } \ + do { \ + } while (0) diff --git a/src/libs/utils/externalterminalprocessimpl.cpp b/src/libs/utils/externalterminalprocessimpl.cpp index 41b7259b8f..693905bddc 100644 --- a/src/libs/utils/externalterminalprocessimpl.cpp +++ b/src/libs/utils/externalterminalprocessimpl.cpp @@ -181,7 +181,7 @@ expected_str<qint64> ProcessStubCreator::startStubProcess(const ProcessSetupData process->setProcessMode(ProcessMode::Writer); } else { QString extraArgsFromOptions = terminal.executeArgs; - CommandLine cmdLine = {terminal.command, {}}; + CommandLine cmdLine{terminal.command}; if (!extraArgsFromOptions.isEmpty()) cmdLine.addArgs(extraArgsFromOptions, CommandLine::Raw); cmdLine.addCommandLineAsArgs(setupData.m_commandLine, CommandLine::Raw); @@ -190,8 +190,6 @@ expected_str<qint64> ProcessStubCreator::startStubProcess(const ProcessSetupData process->setEnvironment( setupData.m_environment.appliedToEnvironment(Environment::systemEnvironment())); - process->setEnvironment(setupData.m_environment); - process->start(); process->waitForStarted(); if (process->error() != QProcess::UnknownError) { diff --git a/src/libs/utils/fancylineedit.cpp b/src/libs/utils/fancylineedit.cpp index 44a9423608..69d7209c3a 100644 --- a/src/libs/utils/fancylineedit.cpp +++ b/src/libs/utils/fancylineedit.cpp @@ -5,6 +5,7 @@ #include "camelcasecursor.h" #include "execmenu.h" +#include "futuresynchronizer.h" #include "historycompleter.h" #include "hostosinfo.h" #include "icon.h" @@ -98,6 +99,7 @@ class FancyLineEditPrivate : public QObject { public: explicit FancyLineEditPrivate(FancyLineEdit *parent); + ~FancyLineEditPrivate(); bool eventFilter(QObject *obj, QEvent *event) override; @@ -134,8 +136,8 @@ FancyLineEditPrivate::FancyLineEditPrivate(FancyLineEdit *parent) : QObject(parent) , m_lineEdit(parent) , m_completionShortcut(completionShortcut()->key(), parent) - , m_okTextColor(creatorTheme()->color(Theme::TextColorNormal)) - , m_errorTextColor(creatorTheme()->color(Theme::TextColorError)) + , m_okTextColor(creatorColor(Theme::TextColorNormal)) + , m_errorTextColor(creatorColor(Theme::TextColorError)) , m_placeholderTextColor(QApplication::palette().color(QPalette::PlaceholderText)) , m_spinner(new SpinnerSolution::Spinner(SpinnerSolution::SpinnerSize::Small, m_lineEdit)) { @@ -163,6 +165,12 @@ FancyLineEditPrivate::FancyLineEditPrivate(FancyLineEdit *parent) } } +FancyLineEditPrivate::~FancyLineEditPrivate() +{ + if (m_validatorWatcher) + m_validatorWatcher->cancel(); +} + bool FancyLineEditPrivate::eventFilter(QObject *obj, QEvent *event) { int buttonIndex = -1; @@ -446,12 +454,27 @@ void FancyLineEdit::setFiltering(bool on) } } +/*! + Set a synchronous or asynchronous validation function \a fn. + Asynchronous validation functions can continue to run after destruction of the + FancyLineEdit instance. During shutdown asynchronous validation functions can continue + to run until before the plugin instances are deleted (at that point the plugin manager + waits for them to finish before continuing). + + \sa defaultValidationFunction() + */ void FancyLineEdit::setValidationFunction(const FancyLineEdit::ValidationFunction &fn) { d->m_validationFunction = fn; validate(); } +/*! + Returns the default validation function, which synchonously executes the line edit's + validator. + + \sa setValidationFunction() +*/ FancyLineEdit::ValidationFunction FancyLineEdit::defaultValidationFunction() { return &FancyLineEdit::validateWithValidator; @@ -581,6 +604,7 @@ void FancyLineEdit::validate() AsyncValidationFuture future = validationFunction(text()); d->m_validatorWatcher->setFuture(future); + Utils::futureSynchronizer()->addFuture(future); return; } diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 1b9b6e943c..c4d77a29ef 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -19,9 +19,11 @@ #include <QFileInfo> #include <QRegularExpression> #include <QStringView> +#include <QTemporaryFile> #include <QUrl> + +#include <QtConcurrent> #include <QtGlobal> -#include <QTemporaryFile> #ifdef Q_OS_WIN #ifdef QTCREATOR_PCH_H @@ -291,6 +293,26 @@ QString FilePath::toString() const return scheme() + "://" + encodedHost() + pathView(); } +bool FilePath::equals(const FilePath &first, const FilePath &second, Qt::CaseSensitivity cs) +{ + if (first.m_hash != 0 && second.m_hash != 0 && first.m_hash != second.m_hash) + return false; + + return first.pathView().compare(second.pathView(), cs) == 0 + && first.host() == second.host() + && first.scheme() == second.scheme(); +} + +/*! + * Returns true if the two file paths compare equal case-sensitively. + * This is relevant on semi-case sensitive systems like Windows with NTFS. + * @see QTCREATORBUG-30846 + */ +bool FilePath::equalsCaseSensitive(const FilePath &other) const +{ + return equals(*this, other, Qt::CaseSensitive); +} + /*! Returns a QString for passing on to QString based APIs. @@ -1228,6 +1250,9 @@ FilePathInfo FilePath::filePathInfo() const */ bool FilePath::exists() const { + if (isEmpty()) + return false; + const expected_str<DeviceFileAccess *> access = getFileAccess(*this); if (!access) return false; @@ -1240,6 +1265,9 @@ bool FilePath::exists() const */ bool FilePath::isExecutableFile() const { + if (isEmpty()) + return false; + const expected_str<DeviceFileAccess *> access = getFileAccess(*this); if (!access) return false; @@ -1252,6 +1280,9 @@ bool FilePath::isExecutableFile() const */ bool FilePath::isWritableDir() const { + if (isEmpty()) + return false; + const expected_str<DeviceFileAccess *> access = getFileAccess(*this); if (!access) return false; @@ -1264,6 +1295,9 @@ bool FilePath::isWritableDir() const */ bool FilePath::isWritableFile() const { + if (isEmpty()) + return false; + const expected_str<DeviceFileAccess *> access = getFileAccess(*this); if (!access) return false; @@ -1271,9 +1305,11 @@ bool FilePath::isWritableFile() const return (*access)->isWritableFile(*this); } - bool FilePath::isReadableFile() const { + if (isEmpty()) + return false; + const expected_str<DeviceFileAccess *> access = getFileAccess(*this); if (!access) return false; @@ -1283,6 +1319,9 @@ bool FilePath::isReadableFile() const bool FilePath::isReadableDir() const { + if (isEmpty()) + return false; + const expected_str<DeviceFileAccess *> access = getFileAccess(*this); if (!access) return false; @@ -1292,6 +1331,9 @@ bool FilePath::isReadableDir() const bool FilePath::isFile() const { + if (isEmpty()) + return false; + const expected_str<DeviceFileAccess *> access = getFileAccess(*this); if (!access) return false; @@ -1301,6 +1343,9 @@ bool FilePath::isFile() const bool FilePath::isDir() const { + if (isEmpty()) + return false; + const expected_str<DeviceFileAccess *> access = getFileAccess(*this); if (!access) return false; @@ -1310,6 +1355,9 @@ bool FilePath::isDir() const bool FilePath::isSymLink() const { + if (isEmpty()) + return false; + const expected_str<DeviceFileAccess *> access = getFileAccess(*this); if (!access) return false; @@ -1365,7 +1413,7 @@ FilePath FilePath::fromUtf8(const char *filename, int filenameSize) FilePath FilePath::fromSettings(const QVariant &variant) { - if (variant.type() == QVariant::Url) { + if (variant.typeId() == QMetaType::QUrl) { const QUrl url = variant.toUrl(); return FilePath::fromParts(url.scheme(), url.host(), url.path()); } @@ -1497,21 +1545,28 @@ FilePath FilePath::relativePathFrom(const FilePath &anchor) const FilePath absPath; QString filename; - if (isFile()) { + + const QList<FilePathInfo> infos + = QtConcurrent::blockingMapped(QList<FilePath>{*this, anchor}, [](const FilePath &path) { + return path.filePathInfo(); + }); + + if (infos.first().fileFlags.testFlag(FilePathInfo::FileFlag::FileType)) { absPath = absolutePath(); filename = fileName(); - } else if (isDir()) { + } else if (infos.first().fileFlags.testFlag(FilePathInfo::FileFlag::DirectoryType)) { absPath = absoluteFilePath(); } else { return {}; } FilePath absoluteAnchorPath; - if (anchor.isFile()) + if (infos.last().fileFlags.testFlag(FilePathInfo::FileFlag::FileType)) absoluteAnchorPath = anchor.absolutePath(); - else if (anchor.isDir()) + else if (infos.last().fileFlags.testFlag(FilePathInfo::FileFlag::DirectoryType)) absoluteAnchorPath = anchor.absoluteFilePath(); else return {}; + QString relativeFilePath = calcRelativePath(absPath.pathView(), absoluteAnchorPath.pathView()); if (!filename.isEmpty()) { if (relativeFilePath == ".") @@ -2319,12 +2374,7 @@ DeviceFileHooks &DeviceFileHooks::instance() QTCREATOR_UTILS_EXPORT bool operator==(const FilePath &first, const FilePath &second) { - if (first.m_hash != 0 && second.m_hash != 0 && first.m_hash != second.m_hash) - return false; - - return first.pathView().compare(second.pathView(), first.caseSensitivity()) == 0 - && first.host() == second.host() - && first.scheme() == second.scheme(); + return FilePath::equals(first, second, first.caseSensitivity()); } QTCREATOR_UTILS_EXPORT bool operator!=(const FilePath &first, const FilePath &second) diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index cb975248ec..e742bf1a0d 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -268,6 +268,8 @@ public: // FIXME: Avoid. See toSettings, toVariant, toUserOutput, toFSPathString, path, nativePath. QString toString() const; + bool equalsCaseSensitive(const FilePath &other) const; + private: // These are needed. QTCREATOR_UTILS_EXPORT friend bool operator==(const FilePath &first, const FilePath &second); @@ -282,6 +284,8 @@ private: QTCREATOR_UTILS_EXPORT friend QDebug operator<<(QDebug dbg, const FilePath &c); + static bool equals(const FilePath &first, const FilePath &second, Qt::CaseSensitivity cs); + // Implementation details. May change. friend class ::tst_fileutils; void setPath(QStringView path); diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index a2998e2f07..d82353e246 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -5,7 +5,6 @@ #include "savefile.h" #include "algorithm.h" -#include "devicefileaccess.h" #include "environment.h" #include "qtcassert.h" #include "utilstr.h" @@ -24,6 +23,8 @@ #include <qplatformdefs.h> #ifdef QT_GUI_LIB +#include "guiutils.h" + #include <QMessageBox> #include <QGuiApplication> #endif @@ -197,7 +198,8 @@ FileSaver::FileSaver(const FilePath &filePath, QIODevice::OpenMode mode) m_file.reset(tf); } else { const bool readOnlyOrAppend = mode & (QIODevice::ReadOnly | QIODevice::Append); - m_isSafe = !readOnlyOrAppend && !filePath.hasHardLinks(); + m_isSafe = !readOnlyOrAppend && !filePath.hasHardLinks() + && !qtcEnvironmentVariableIsSet("QTC_DISABLE_ATOMICSAVE"); if (m_isSafe) m_file.reset(new SaveFile(filePath)); else @@ -413,18 +415,6 @@ void withNtfsPermissions(const std::function<void()> &task) #ifdef QT_WIDGETS_LIB -static std::function<QWidget *()> s_dialogParentGetter; - -void FileUtils::setDialogParentGetter(const std::function<QWidget *()> &getter) -{ - s_dialogParentGetter = getter; -} - -static QWidget *dialogParent(QWidget *parent) -{ - return parent ? parent : s_dialogParentGetter ? s_dialogParentGetter() : nullptr; -} - static QUrl filePathToQUrl(const FilePath &filePath) { return QUrl::fromLocalFile(filePath.toFSPathString()); diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index b20f7568db..bdf7a7e3ba 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -89,8 +89,6 @@ public: static FilePaths usefulExtraSearchPaths(); #ifdef QT_WIDGETS_LIB - static void setDialogParentGetter(const std::function<QWidget *()> &getter); - static bool hasNativeFileDialog(); static FilePath getOpenFilePath(QWidget *parent, @@ -236,7 +234,5 @@ private: QTCREATOR_UTILS_EXPORT QTextStream &operator<<(QTextStream &s, const FilePath &fn); -bool isRelativePathHelper(const QString &path, OsType osType); - } // namespace Utils diff --git a/src/libs/utils/fsengine/fsengine_impl.h b/src/libs/utils/fsengine/fsengine_impl.h index 9d11b917ac..1a49f77cd2 100644 --- a/src/libs/utils/fsengine/fsengine_impl.h +++ b/src/libs/utils/fsengine/fsengine_impl.h @@ -48,7 +48,8 @@ public: uint ownerId(FileOwner) const override; QString owner(FileOwner) const override; -#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + // The FileTime change in QAbstractFileEngine, in qtbase, is in since Qt 6.7.1 +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 1) using FileTime = QFile::FileTime; #endif bool setFileTime(const QDateTime &newDate, FileTime time) override; diff --git a/src/libs/utils/fsengine/fsenginehandler.cpp b/src/libs/utils/fsengine/fsenginehandler.cpp index 29fe759a86..d6751d8b6a 100644 --- a/src/libs/utils/fsengine/fsenginehandler.cpp +++ b/src/libs/utils/fsengine/fsenginehandler.cpp @@ -3,17 +3,42 @@ #include "fsenginehandler.h" +#include "fileiteratordevicesappender.h" #include "fixedlistfsengine.h" -#include "fsengine_impl.h" -#include "rootinjectfsengine.h" - #include "fsengine.h" +#include "fsengine_impl.h" #include "../algorithm.h" #include "../hostosinfo.h" +#include <QtCore/private/qfsfileengine_p.h> + namespace Utils::Internal { +class RootInjectFSEngine final : public QFSFileEngine +{ +public: + using QFSFileEngine::QFSFileEngine; + +public: +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + IteratorUniquePtr beginEntryList(const QString &path, + QDir::Filters filters, + const QStringList &filterNames) override + { + return std::make_unique<FileIteratorWrapper>( + QFSFileEngine::beginEntryList(path, filters, filterNames)); + } +#else + Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override + { + std::unique_ptr<QAbstractFileEngineIterator> baseIterator( + QFSFileEngine::beginEntryList(filters, filterNames)); + return new FileIteratorWrapper(std::move(baseIterator)); + } +#endif +}; + static FilePath removeDoubleSlash(const QString &fileName) { // Reduce every two or more slashes to a single slash. diff --git a/src/libs/utils/fsengine/rootinjectfsengine.h b/src/libs/utils/fsengine/rootinjectfsengine.h deleted file mode 100644 index 9eb6a8a832..0000000000 --- a/src/libs/utils/fsengine/rootinjectfsengine.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "fileiteratordevicesappender.h" - -#include <QtCore/private/qfsfileengine_p.h> - -namespace Utils { -namespace Internal { - -class RootInjectFSEngine : public QFSFileEngine -{ -public: - using QFSFileEngine::QFSFileEngine; - -public: -#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) - IteratorUniquePtr beginEntryList(const QString &path, - QDir::Filters filters, - const QStringList &filterNames) override - { - return std::make_unique<FileIteratorWrapper>( - QFSFileEngine::beginEntryList(path, filters, filterNames)); - } -#else - Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override - { - std::unique_ptr<QAbstractFileEngineIterator> baseIterator( - QFSFileEngine::beginEntryList(filters, filterNames)); - return new FileIteratorWrapper(std::move(baseIterator)); - } -#endif -}; - -} // namespace Internal -} // namespace Utils diff --git a/src/libs/utils/futuresynchronizer.cpp b/src/libs/utils/futuresynchronizer.cpp index da3347f35e..912de677b9 100644 --- a/src/libs/utils/futuresynchronizer.cpp +++ b/src/libs/utils/futuresynchronizer.cpp @@ -3,6 +3,9 @@ #include "futuresynchronizer.h" +#include "qtcassert.h" +#include "threadutils.h" + /*! \class Utils::FutureSynchronizer \inmodule QtCreator @@ -62,4 +65,18 @@ void FutureSynchronizer::flushFinishedFutures() m_futures = newFutures; } +Q_GLOBAL_STATIC(FutureSynchronizer, s_futureSynchronizer); + +/*! + Returns a global FutureSynchronizer. + The application should cancel and wait for the tasks in this synchronizer before actually + unloading any libraries. This is for example done by the plugin manager in Qt Creator. + May only be accessed by the main thread. +*/ +FutureSynchronizer *futureSynchronizer() +{ + QTC_ASSERT(isMainThread(), return nullptr); + return s_futureSynchronizer; +} + } // namespace Utils diff --git a/src/libs/utils/futuresynchronizer.h b/src/libs/utils/futuresynchronizer.h index 29b1f5e456..83391dbc72 100644 --- a/src/libs/utils/futuresynchronizer.h +++ b/src/libs/utils/futuresynchronizer.h @@ -42,4 +42,6 @@ private: bool m_cancelOnWait = true; }; +QTCREATOR_UTILS_EXPORT FutureSynchronizer *futureSynchronizer(); + } // namespace Utils diff --git a/src/libs/utils/guiutils.cpp b/src/libs/utils/guiutils.cpp index 17cc905300..d7d9cb5f84 100644 --- a/src/libs/utils/guiutils.cpp +++ b/src/libs/utils/guiutils.cpp @@ -46,4 +46,16 @@ void QTCREATOR_UTILS_EXPORT setWheelScrollingWithoutFocusBlocked(QWidget *widget widget->setFocusPolicy(Qt::StrongFocus); } +static QWidget *(*s_dialogParentGetter)() = nullptr; + +void setDialogParentGetter(QWidget *(*getter)()) +{ + s_dialogParentGetter = getter; +} + +QWidget *dialogParent(QWidget *parent) +{ + return parent ? parent : s_dialogParentGetter ? s_dialogParentGetter() : nullptr; +} + } // namespace Utils diff --git a/src/libs/utils/guiutils.h b/src/libs/utils/guiutils.h index 8316377718..fea37f98f3 100644 --- a/src/libs/utils/guiutils.h +++ b/src/libs/utils/guiutils.h @@ -9,6 +9,9 @@ class QWidget; namespace Utils { -void QTCREATOR_UTILS_EXPORT setWheelScrollingWithoutFocusBlocked(QWidget *widget); +QTCREATOR_UTILS_EXPORT void setWheelScrollingWithoutFocusBlocked(QWidget *widget); + +QTCREATOR_UTILS_EXPORT QWidget *dialogParent(QWidget *parent); +QTCREATOR_UTILS_EXPORT void setDialogParentGetter(QWidget *(*getter)()); } // namespace Utils diff --git a/src/libs/utils/highlightingitemdelegate.cpp b/src/libs/utils/highlightingitemdelegate.cpp index 7fe0507311..9ad7abb01a 100644 --- a/src/libs/utils/highlightingitemdelegate.cpp +++ b/src/libs/utils/highlightingitemdelegate.cpp @@ -91,7 +91,7 @@ int HighlightingItemDelegate::drawLineNumber(QPainter *painter, const QStyleOpti return 0; const bool isSelected = option.state & QStyle::State_Selected; const QString lineText = QString::number(lineNumber); - const int minimumLineNumberDigits = qMax(kMinimumLineNumberDigits, lineText.count()); + const int minimumLineNumberDigits = qMax(kMinimumLineNumberDigits, lineText.size()); const int fontWidth = painter->fontMetrics().horizontalAdvance(QString(minimumLineNumberDigits, '0')); const int lineNumberAreaWidth = lineNumberAreaHorizontalPadding + fontWidth diff --git a/src/libs/utils/historycompleter.cpp b/src/libs/utils/historycompleter.cpp index 8eff3fdad8..8dc9ec3887 100644 --- a/src/libs/utils/historycompleter.cpp +++ b/src/libs/utils/historycompleter.cpp @@ -54,11 +54,11 @@ public: optCopy.state |= QStyle::State_HasFocus; QItemDelegate::paint(painter,option,index); // add remove button - QWindow *window = view->window()->windowHandle(); - const QPixmap iconPixmap = icon.pixmap(window, option.rect.size()); + const qreal devicePixelRatio = painter->device()->devicePixelRatio(); + const QPixmap iconPixmap = icon.pixmap(option.rect.size(), devicePixelRatio); QRect pixmapRect = QStyle::alignedRect(option.direction, Qt::AlignRight | Qt::AlignVCenter, - iconPixmap.size() / window->devicePixelRatio(), + iconPixmap.size() / devicePixelRatio, option.rect); if (!clearIconSize.isValid()) clearIconSize = pixmapRect.size(); diff --git a/src/libs/utils/icon.cpp b/src/libs/utils/icon.cpp index 5bd862c8f1..df880ac67e 100644 --- a/src/libs/utils/icon.cpp +++ b/src/libs/utils/icon.cpp @@ -42,7 +42,7 @@ static MasksAndColors masksAndColors(const QList<IconMaskAndColor> &icon, int dp MasksAndColors result; for (const IconMaskAndColor &i: icon) { const QString &fileName = i.first.toString(); - const QColor color = creatorTheme()->color(i.second); + const QColor color = creatorColor(i.second); const QString dprFileName = StyleHelper::availableImageResolutions(i.first.toString()) .contains(dpr) ? StyleHelper::imageFileWithResolution(fileName, dpr) @@ -165,7 +165,7 @@ QIcon Icon::icon() const const QPixmap combinedMask = Utils::combinedMask(masks, m_style); m_lastIcon.addPixmap(masksToIcon(masks, combinedMask, m_style)); - const QColor disabledColor = creatorTheme()->color(Theme::IconsDisabledColor); + const QColor disabledColor = creatorColor(Theme::IconsDisabledColor); m_lastIcon.addPixmap(maskToColorAndAlpha(combinedMask, disabledColor), QIcon::Disabled); } return m_lastIcon; @@ -182,7 +182,7 @@ QPixmap Icon::pixmap(QIcon::Mode iconMode) const masksAndColors(m_iconSourceList, qRound(qApp->devicePixelRatio())); const QPixmap combinedMask = Utils::combinedMask(masks, m_style); return iconMode == QIcon::Disabled - ? maskToColorAndAlpha(combinedMask, creatorTheme()->color(Theme::IconsDisabledColor)) + ? maskToColorAndAlpha(combinedMask, creatorColor(Theme::IconsDisabledColor)) : masksToIcon(masks, combinedMask, m_style); } } @@ -221,11 +221,11 @@ QIcon Icon::modeIcon(const Icon &classic, const Icon &flat, const Icon &flatActi QIcon Icon::combinedIcon(const QList<QIcon> &icons) { QIcon result; - QWindow *window = QApplication::allWidgets().constFirst()->windowHandle(); + const qreal devicePixelRatio = QApplication::allWidgets().constFirst()->devicePixelRatio(); for (const QIcon &icon: icons) for (const QIcon::Mode mode: {QIcon::Disabled, QIcon::Normal}) for (const QSize &size: icon.availableSizes(mode)) - result.addPixmap(icon.pixmap(window, size, mode), mode); + result.addPixmap(icon.pixmap(size, devicePixelRatio, mode), mode); return result; } diff --git a/src/libs/utils/iconbutton.cpp b/src/libs/utils/iconbutton.cpp index 746cf0ecb1..3be368c43b 100644 --- a/src/libs/utils/iconbutton.cpp +++ b/src/libs/utils/iconbutton.cpp @@ -26,7 +26,7 @@ void IconButton::paintEvent(QPaintEvent *e) QRect r(QPoint(), size()); if (m_containsMouse && isEnabled()) { - QColor c = creatorTheme()->color(Theme::TextColorDisabled); + QColor c = creatorColor(Theme::TextColorDisabled); c.setAlphaF(c.alphaF() * .5); StyleHelper::drawPanelBgRect(&p, r, c); } diff --git a/src/libs/utils/infobar.cpp b/src/libs/utils/infobar.cpp index bd6429a762..dc5fb4878d 100644 --- a/src/libs/utils/infobar.cpp +++ b/src/libs/utils/infobar.cpp @@ -47,10 +47,10 @@ void InfoBarWidget::paintEvent(QPaintEvent *event) { QWidget::paintEvent(event); QPainter p(this); - p.fillRect(rect(), creatorTheme()->color(Theme::InfoBarBackground)); + p.fillRect(rect(), creatorColor(Theme::InfoBarBackground)); const QRectF adjustedRect = QRectF(rect()).adjusted(0.5, 0.5, -0.5, -0.5); const bool topEdge = m_edge == Qt::TopEdge; - p.setPen(creatorTheme()->color(Theme::FancyToolBarSeparatorColor)); + p.setPen(creatorColor(Theme::FancyToolBarSeparatorColor)); p.drawLine(QLineF(topEdge ? adjustedRect.bottomLeft() : adjustedRect.topLeft(), topEdge ? adjustedRect.bottomRight() : adjustedRect.topRight())); } diff --git a/src/libs/utils/infolabel.cpp b/src/libs/utils/infolabel.cpp index dbc762d9fe..e05c093ca5 100644 --- a/src/libs/utils/infolabel.cpp +++ b/src/libs/utils/infolabel.cpp @@ -114,14 +114,13 @@ void InfoLabel::paintEvent(QPaintEvent *event) if (m_filled && isEnabled()) { p.save(); p.setOpacity(0.175); - p.fillRect(rect(), creatorTheme()->color(fillColorForType(m_type))); + p.fillRect(rect(), creatorColor(fillColorForType(m_type))); p.restore(); } const QIcon &icon = iconForType(m_type); - QWindow *window = this->window()->windowHandle(); const QIcon::Mode mode = !this->isEnabled() ? QIcon::Disabled : QIcon::Normal; const QPixmap iconPx = - icon.pixmap(window, QSize(iconSize, iconSize) * devicePixelRatio(), mode); + icon.pixmap(QSize(iconSize, iconSize) * devicePixelRatio(), devicePixelRatio(), mode); p.drawPixmap(iconRect, iconPx); ElidingLabel::paintEvent(event); } diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 31c67e4d0f..6f33e4f5ca 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -3,7 +3,6 @@ #include "layoutbuilder.h" -#include <QApplication> #include <QDebug> #include <QFormLayout> #include <QGridLayout> @@ -29,10 +28,23 @@ namespace Layouting { #define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_STRING(#cond); action; } do {} while (0) #define QTC_CHECK(cond) if (cond) {} else { QTC_STRING(#cond); } do {} while (0) -class FlowLayout final : public QLayout +template <typename X> +typename X::Implementation *access(const X *x) { - Q_OBJECT + return static_cast<typename X::Implementation *>(x->ptr); +} + +template <typename X> +void apply(X *x, std::initializer_list<typename X::I> ps) +{ + for (auto && p : ps) + p.apply(x); +} + +// FlowLayout +class FlowLayout : public QLayout +{ public: explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1) : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) @@ -181,29 +193,56 @@ private: \namespace Layouting \inmodule QtCreator - \brief The Layouting namespace contains classes for use with layout builders. + \brief The Layouting namespace contains classes and functions to conveniently + create layouts in code. + + Classes in the namespace help to create create QLayout or QWidget derived class, + instances should be used locally within a function and never stored. + + \sa Layouting::Widget, Layouting::Layout */ /*! - \class Layouting::LayoutItem + \class Layouting::Layout \inmodule QtCreator - \brief The LayoutItem class represents widgets, layouts, and aggregate - items for use in conjunction with layout builders. + The Layout class is a base class for more specific builder + classes to create QLayout derived objects. + */ - Layout items are typically implicitly constructed when adding items to a - \c LayoutBuilder instance using \c LayoutBuilder::addItem() or - \c LayoutBuilder::addItems() and never stored in user code. +/*! + \class Layouting::Widget + \inmodule QtCreator + + The Widget class is a base class for more specific builder + classes to create QWidget derived objects. */ /*! - Constructs a layout item instance representing an empty cell. - */ + \class Layouting::LayoutItem + \inmodule QtCreator + + The LayoutItem class is used for intermediate results + while creating layouts with a concept of rows and spans, such + as Form and Grid. +*/ + LayoutItem::LayoutItem() = default; LayoutItem::~LayoutItem() = default; +LayoutItem::LayoutItem(QLayout *l) + : layout(l), empty(!l) +{} + +LayoutItem::LayoutItem(QWidget *w) + : widget(w), empty(!w) +{} + +LayoutItem::LayoutItem(const QString &t) + : text(t), empty(t.isEmpty()) +{} /*! \fn template <class T> LayoutItem(const T &t) @@ -217,44 +256,15 @@ LayoutItem::~LayoutItem() = default; \li \c {QWidget *} \li \c {QLayout *} \endlist - */ - -struct ResultItem -{ - ResultItem() = default; - explicit ResultItem(QLayout *l) : layout(l), empty(!l) {} - explicit ResultItem(QWidget *w) : widget(w), empty(!w) {} +*/ - QString text; - QLayout *layout = nullptr; - QWidget *widget = nullptr; - int space = -1; - int stretch = -1; - int span = 1; - bool empty = false; -}; +// Object -struct Slice +Object::Object(std::initializer_list<I> ps) { - Slice() = default; - Slice(QLayout *l) : layout(l) {} - Slice(QWidget *w, bool isLayouting=false) : widget(w), isLayouting(isLayouting) {} - - QLayout *layout = nullptr; - QWidget *widget = nullptr; - - void flush(); - - // Grid-specific - int currentGridColumn = 0; - int currentGridRow = 0; - bool isFormAlignment = false; - bool isLayouting = false; - Qt::Alignment align = {}; // Can be changed to - - // Grid or Form - QList<ResultItem> pendingItems; -}; + ptr = new Implementation; + apply(this, ps); +} static QWidget *widgetForItem(QLayoutItem *item) { @@ -262,12 +272,11 @@ static QWidget *widgetForItem(QLayoutItem *item) return w; if (item->spacerItem()) return nullptr; - QLayout *l = item->layout(); - if (!l) - return nullptr; - for (int i = 0, n = l->count(); i < n; ++i) { - if (QWidget *w = widgetForItem(l->itemAt(i))) - return w; + if (QLayout *l = item->layout()) { + for (int i = 0, n = l->count(); i < n; ++i) { + if (QWidget *w = widgetForItem(l->itemAt(i))) + return w; + } } return nullptr; } @@ -279,7 +288,7 @@ static QLabel *createLabel(const QString &text) return label; } -static void addItemToBoxLayout(QBoxLayout *layout, const ResultItem &item) +static void addItemToBoxLayout(QBoxLayout *layout, const LayoutItem &item) { if (QWidget *w = item.widget) { layout->addWidget(w); @@ -287,8 +296,6 @@ static void addItemToBoxLayout(QBoxLayout *layout, const ResultItem &item) layout->addLayout(l); } else if (item.stretch != -1) { layout->addStretch(item.stretch); - } else if (item.space != -1) { - layout->addSpacing(item.space); } else if (!item.text.isEmpty()) { layout->addWidget(createLabel(item.text)); } else if (item.empty) { @@ -298,7 +305,7 @@ static void addItemToBoxLayout(QBoxLayout *layout, const ResultItem &item) } } -static void addItemToFlowLayout(FlowLayout *layout, const ResultItem &item) +static void addItemToFlowLayout(FlowLayout *layout, const LayoutItem &item) { if (QWidget *w = item.widget) { layout->addWidget(w); @@ -306,8 +313,6 @@ static void addItemToFlowLayout(FlowLayout *layout, const ResultItem &item) layout->addItem(l); // } else if (item.stretch != -1) { // layout->addStretch(item.stretch); -// } else if (item.space != -1) { -// layout->addSpacing(item.space); } else if (item.empty) { // Nothing to do, but no reason to warn, either } else if (!item.text.isEmpty()) { @@ -317,759 +322,682 @@ static void addItemToFlowLayout(FlowLayout *layout, const ResultItem &item) } } -void Slice::flush() -{ - if (pendingItems.empty()) - return; - - if (auto formLayout = qobject_cast<QFormLayout *>(layout)) { - - // If there are more than two items, we cram the last ones in one hbox. - if (pendingItems.size() > 2) { - auto hbox = new QHBoxLayout; - hbox->setContentsMargins(0, 0, 0, 0); - for (int i = 1; i < pendingItems.size(); ++i) - addItemToBoxLayout(hbox, pendingItems.at(i)); - while (pendingItems.size() > 1) - pendingItems.pop_back(); - pendingItems.append(ResultItem(hbox)); - } - - if (pendingItems.size() == 1) { // One one item given, so this spans both columns. - const ResultItem &f0 = pendingItems.at(0); - if (auto layout = f0.layout) - formLayout->addRow(layout); - else if (auto widget = f0.widget) - formLayout->addRow(widget); - } else if (pendingItems.size() == 2) { // Normal case, both columns used. - ResultItem &f1 = pendingItems[1]; - const ResultItem &f0 = pendingItems.at(0); - if (!f1.widget && !f1.layout && !f1.text.isEmpty()) - f1.widget = createLabel(f1.text); - - if (f0.widget) { - if (f1.layout) - formLayout->addRow(f0.widget, f1.layout); - else if (f1.widget) - formLayout->addRow(f0.widget, f1.widget); - } else { - if (f1.layout) - formLayout->addRow(createLabel(f0.text), f1.layout); - else if (f1.widget) - formLayout->addRow(createLabel(f0.text), f1.widget); - } - } else { - QTC_CHECK(false); - } - - // Set up label as buddy if possible. - const int lastRow = formLayout->rowCount() - 1; - QLayoutItem *l = formLayout->itemAt(lastRow, QFormLayout::LabelRole); - QLayoutItem *f = formLayout->itemAt(lastRow, QFormLayout::FieldRole); - if (l && f) { - if (QLabel *label = qobject_cast<QLabel *>(l->widget())) { - if (QWidget *widget = widgetForItem(f)) - label->setBuddy(widget); - } - } - - } else if (auto gridLayout = qobject_cast<QGridLayout *>(layout)) { - - for (const ResultItem &item : std::as_const(pendingItems)) { - Qt::Alignment a = currentGridColumn == 0 ? align : Qt::Alignment(); - if (item.widget) - gridLayout->addWidget(item.widget, currentGridRow, currentGridColumn, 1, item.span, a); - else if (item.layout) - gridLayout->addLayout(item.layout, currentGridRow, currentGridColumn, 1, item.span, a); - else if (!item.text.isEmpty()) - gridLayout->addWidget(createLabel(item.text), currentGridRow, currentGridColumn, 1, 1, a); - currentGridColumn += item.span; - } - ++currentGridRow; - currentGridColumn = 0; +/*! + \class Layouting::Space + \inmodule QtCreator - } else if (auto boxLayout = qobject_cast<QBoxLayout *>(layout)) { + \brief The Space class represents some empty space in a layout. + */ - for (const ResultItem &item : std::as_const(pendingItems)) - addItemToBoxLayout(boxLayout, item); +/*! + \class Layouting::Stretch + \inmodule QtCreator - } else if (auto flowLayout = qobject_cast<FlowLayout *>(layout)) { + \brief The Stretch class represents some stretch in a layout. + */ - for (const ResultItem &item : std::as_const(pendingItems)) - addItemToFlowLayout(flowLayout, item); - } else { - QTC_CHECK(false); - } +// Layout - pendingItems.clear(); +void Layout::span(int cols, int rows) +{ + QTC_ASSERT(!pendingItems.empty(), return); + pendingItems.back().spanCols = cols; + pendingItems.back().spanRows = rows; } -// LayoutBuilder - -class LayoutBuilder +void Layout::setNoMargins() { - Q_DISABLE_COPY_MOVE(LayoutBuilder) - -public: - LayoutBuilder(); - ~LayoutBuilder(); - - void addItem(const LayoutItem &item); - void addItems(const LayoutItems &items); - - QList<Slice> stack; -}; + setContentsMargins(0, 0, 0, 0); +} -static void addItemHelper(LayoutBuilder &builder, const LayoutItem &item) +void Layout::setNormalMargins() { - if (item.onAdd) - item.onAdd(builder); - - if (item.setter) { - if (QWidget *widget = builder.stack.last().widget) - item.setter(widget); - else if (QLayout *layout = builder.stack.last().layout) - item.setter(layout); - else - QTC_CHECK(false); - } - - for (const LayoutItem &subItem : item.subItems) - addItemHelper(builder, subItem); - - if (item.onExit) - item.onExit(builder); + setContentsMargins(9, 9, 9, 9); } -void doAddText(LayoutBuilder &builder, const QString &text) +void Layout::setContentsMargins(int left, int top, int right, int bottom) { - ResultItem fi; - fi.text = text; - builder.stack.last().pendingItems.append(fi); + access(this)->setContentsMargins(left, top, right, bottom); } -void doAddSpace(LayoutBuilder &builder, const Space &space) +/*! + Attaches the constructed layout to the provided QWidget \a w. + + This operation can only be performed once per LayoutBuilder instance. + */ +void Layout::attachTo(QWidget *widget) { - ResultItem fi; - fi.space = space.space; - builder.stack.last().pendingItems.append(fi); + flush(); + widget->setLayout(access(this)); } -void doAddStretch(LayoutBuilder &builder, const Stretch &stretch) +/*! + Adds the layout item \a item as sub items. + */ +void Layout::addItem(I item) { - ResultItem fi; - fi.stretch = stretch.stretch; - builder.stack.last().pendingItems.append(fi); + item.apply(this); } -void doAddLayout(LayoutBuilder &builder, QLayout *layout) +void Layout::addLayoutItem(const LayoutItem &item) { - builder.stack.last().pendingItems.append(ResultItem(layout)); + if (QBoxLayout *lt = asBox()) + addItemToBoxLayout(lt, item); + else if (FlowLayout *lt = asFlow()) + addItemToFlowLayout(lt, item); + else + pendingItems.push_back(item); } -void doAddWidget(LayoutBuilder &builder, QWidget *widget) +/*! + Adds the layout items \a items as sub items. + */ +void Layout::addItems(std::initializer_list<I> items) { - builder.stack.last().pendingItems.append(ResultItem(widget)); + for (const I &item : items) + item.apply(this); } - /*! - \class Layouting::Space - \inmodule QtCreator + Starts a new row containing \a items. The row can be further extended by + other items using \c addItem() or \c addItems(). - \brief The Space class represents some empty space in a layout. + \sa addItem(), addItems() */ -/*! - \class Layouting::Stretch - \inmodule QtCreator +void Layout::addRow(std::initializer_list<I> items) +{ + for (const I &item : items) + item.apply(this); + flush(); +} - \brief The Stretch class represents some stretch in a layout. - */ +void Layout::setSpacing(int spacing) +{ + access(this)->setSpacing(spacing); +} -/*! - \class Layouting::LayoutBuilder - \internal - \inmodule QtCreator +void Layout::setColumnStretch(int column, int stretch) +{ + if (auto grid = qobject_cast<QGridLayout *>(access(this))) { + grid->setColumnStretch(column, stretch); + } else { + QTC_CHECK(false); + } +} - \brief The LayoutBuilder class provides a convenient way to fill \c QFormLayout - and \c QGridLayouts with contents. +void addToWidget(Widget *widget, const Layout &layout) +{ + layout.flush_(); + access(widget)->setLayout(access(&layout)); +} - Filling a layout with items happens item-by-item, row-by-row. +void addToLayout(Layout *layout, const Widget &inner) +{ + layout->addLayoutItem(access(&inner)); +} - A LayoutBuilder instance is typically used locally within a function and never stored. +void addToLayout(Layout *layout, QWidget *inner) +{ + layout->addLayoutItem(inner); +} - \sa addItem(), addItems() -*/ +void addToLayout(Layout *layout, QLayout *inner) +{ + layout->addLayoutItem(inner); +} +void addToLayout(Layout *layout, const Layout &inner) +{ + inner.flush_(); + layout->addLayoutItem(access(&inner)); +} -LayoutBuilder::LayoutBuilder() = default; +void addToLayout(Layout *layout, const LayoutModifier &inner) +{ + inner(layout); +} -/*! - \internal - Destructs a layout builder. - */ -LayoutBuilder::~LayoutBuilder() = default; +void addToLayout(Layout *layout, const QString &inner) +{ + layout->addLayoutItem(inner); +} -void LayoutBuilder::addItem(const LayoutItem &item) +void empty(Layout *layout) { - addItemHelper(*this, item); + LayoutItem item; + item.empty = true; + layout->addLayoutItem(item); } -void LayoutBuilder::addItems(const LayoutItems &items) +void hr(Layout *layout) { - for (const LayoutItem &item : items) - addItemHelper(*this, item); + layout->addLayoutItem(createHr()); } -/*! - Starts a new row containing \a items. The row can be further extended by - other items using \c addItem() or \c addItems(). +void br(Layout *layout) +{ + layout->flush(); +} - \sa addItem(), addItems() - */ -void LayoutItem::addRow(const LayoutItems &items) +void st(Layout *layout) { - addItem(br); - addItems(items); + LayoutItem item; + item.stretch = 1; + layout->addLayoutItem(item); } -/*! - Adds the layout item \a item as sub items. - */ -void LayoutItem::addItem(const LayoutItem &item) +void noMargin(Layout *layout) { - subItems.append(item); + layout->setNoMargins(); } -/*! - Adds the layout items \a items as sub items. - */ -void LayoutItem::addItems(const LayoutItems &items) +void normalMargin(Layout *layout) { - subItems.append(items); + layout->setNormalMargins(); } -/*! - Attaches the constructed layout to the provided QWidget \a w. +QFormLayout *Layout::asForm() +{ + return qobject_cast<QFormLayout *>(access(this)); +} - This operation can only be performed once per LayoutBuilder instance. - */ +QGridLayout *Layout::asGrid() +{ + return qobject_cast<QGridLayout *>(access(this)); +} -void LayoutItem::attachTo(QWidget *w) const +QBoxLayout *Layout::asBox() { - LayoutBuilder builder; + return qobject_cast<QBoxLayout *>(access(this)); +} - builder.stack.append(w); - addItemHelper(builder, *this); +FlowLayout *Layout::asFlow() +{ + return dynamic_cast<FlowLayout *>(access(this)); } -QWidget *LayoutItem::emerge() +void Layout::flush() { - LayoutBuilder builder; + if (pendingItems.empty()) + return; - builder.stack.append(Slice()); - addItemHelper(builder, *this); + if (QGridLayout *lt = asGrid()) { + for (const LayoutItem &item : std::as_const(pendingItems)) { + Qt::Alignment a; + if (currentGridColumn == 0 && useFormAlignment) { + // if (auto widget = builder.stack.at(builder.stack.size() - 2).widget) { + // a = widget->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment); + } + if (item.widget) + lt->addWidget(item.widget, currentGridRow, currentGridColumn, item.spanRows, item.spanCols, a); + else if (item.layout) + lt->addLayout(item.layout, currentGridRow, currentGridColumn, item.spanRows, item.spanCols, a); + else if (!item.text.isEmpty()) + lt->addWidget(createLabel(item.text), currentGridRow, currentGridColumn, item.spanRows, item.spanCols, a); + currentGridColumn += item.spanCols; + // Intentionally not used, use 'br'/'empty' for vertical progress. + // currentGridRow += item.spanRows; + } + ++currentGridRow; + currentGridColumn = 0; + pendingItems.clear(); + return; + } - if (builder.stack.empty()) - return nullptr; + if (QFormLayout *fl = asForm()) { + if (pendingItems.size() > 2) { + auto hbox = new QHBoxLayout; + hbox->setContentsMargins(0, 0, 0, 0); + for (size_t i = 1; i < pendingItems.size(); ++i) + addItemToBoxLayout(hbox, pendingItems.at(i)); + while (pendingItems.size() > 1) + pendingItems.pop_back(); + pendingItems.push_back(hbox); + } - QTC_ASSERT(builder.stack.last().pendingItems.size() == 1, return nullptr); - ResultItem ri = builder.stack.last().pendingItems.takeFirst(); + if (pendingItems.size() == 1) { // Only one item given, so this spans both columns. + const LayoutItem &f0 = pendingItems.at(0); + if (auto layout = f0.layout) + fl->addRow(layout); + else if (auto widget = f0.widget) + fl->addRow(widget); + } else if (pendingItems.size() == 2) { // Normal case, both columns used. + LayoutItem &f1 = pendingItems[1]; + const LayoutItem &f0 = pendingItems.at(0); + if (!f1.widget && !f1.layout && !f1.text.isEmpty()) + f1.widget = createLabel(f1.text); - QTC_ASSERT(ri.layout || ri.widget, return nullptr); + // QFormLayout accepts only widgets or text in the first column. + // FIXME: Should we be more generous? + if (f0.widget) { + if (f1.layout) + fl->addRow(f0.widget, f1.layout); + else if (f1.widget) + fl->addRow(f0.widget, f1.widget); + } else { + if (f1.layout) + fl->addRow(createLabel(f0.text), f1.layout); + else if (f1.widget) + fl->addRow(createLabel(f0.text), f1.widget); + } + } else { + QTC_CHECK(false); + } - if (ri.layout) { - auto w = new QWidget; - w->setLayout(ri.layout); - return w; + // Set up label as buddy if possible. + const int lastRow = fl->rowCount() - 1; + QLayoutItem *l = fl->itemAt(lastRow, QFormLayout::LabelRole); + QLayoutItem *f = fl->itemAt(lastRow, QFormLayout::FieldRole); + if (l && f) { + if (QLabel *label = qobject_cast<QLabel *>(l->widget())) { + if (QWidget *widget = widgetForItem(f)) + label->setBuddy(widget); + } + } + + pendingItems.clear(); + return; } - return ri.widget; + QTC_CHECK(false); // The other layouts shouldn't use flush() } -static void layoutExit(LayoutBuilder &builder) +void Layout::flush_() const { - builder.stack.last().flush(); - QLayout *layout = builder.stack.last().layout; - builder.stack.pop_back(); - - if (builder.stack.last().isLayouting) { - builder.stack.last().pendingItems.append(ResultItem(layout)); - } else if (QWidget *widget = builder.stack.last().widget) { - widget->setLayout(layout); - } else - builder.stack.last().pendingItems.append(ResultItem(layout)); + const_cast<Layout *>(this)->flush(); } -template<class T> -static void layoutingWidgetExit(LayoutBuilder &builder) +void withFormAlignment(Layout *layout) { - const Slice slice = builder.stack.last(); - T *w = qobject_cast<T *>(slice.widget); - for (const ResultItem &ri : slice.pendingItems) { - if (ri.widget) { - w->addWidget(ri.widget); - } else if (ri.layout) { - auto child = new QWidget; - child->setLayout(ri.layout); - w->addWidget(child); - } - } - builder.stack.pop_back(); - builder.stack.last().pendingItems.append(ResultItem(w)); + layout->useFormAlignment = true; } -static void widgetExit(LayoutBuilder &builder) +// Flow + +Flow::Flow(std::initializer_list<I> ps) { - QWidget *widget = builder.stack.last().widget; - builder.stack.pop_back(); - builder.stack.last().pendingItems.append(ResultItem(widget)); + ptr = new FlowLayout; + apply(this, ps); + flush(); } -Column::Column(std::initializer_list<LayoutItem> items) +// Row & Column + +Row::Row(std::initializer_list<I> ps) { - subItems = items; - onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QVBoxLayout); }; - onExit = layoutExit; + ptr = new QHBoxLayout; + apply(this, ps); + flush(); } -Row::Row(std::initializer_list<LayoutItem> items) +Column::Column(std::initializer_list<I> ps) { - subItems = items; - onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QHBoxLayout); }; - onExit = layoutExit; + ptr = new QVBoxLayout; + apply(this, ps); + flush(); } -Flow::Flow(std::initializer_list<LayoutItem> items) +// Grid + +Grid::Grid() { - subItems = items; - onAdd = [](LayoutBuilder &builder) { builder.stack.append(new FlowLayout); }; - onExit = layoutExit; + ptr = new QGridLayout; } -Grid::Grid(std::initializer_list<LayoutItem> items) +Grid::Grid(std::initializer_list<I> ps) { - subItems = items; - onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QGridLayout); }; - onExit = layoutExit; + ptr = new QGridLayout; + apply(this, ps); + flush(); } -static QFormLayout *newFormLayout() +// Form + +Form::Form() { - auto formLayout = new QFormLayout; - formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); - return formLayout; + ptr = new QFormLayout; } -Form::Form(std::initializer_list<LayoutItem> items) +Form::Form(std::initializer_list<I> ps) { - subItems = items; - onAdd = [](LayoutBuilder &builder) { builder.stack.append(newFormLayout()); }; - onExit = layoutExit; + auto lt = new QFormLayout; + ptr = lt; + lt->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + apply(this, ps); + flush(); } -LayoutItem br() +void Layout::setFieldGrowthPolicy(int policy) { - LayoutItem item; - item.onAdd = [](LayoutBuilder &builder) { - builder.stack.last().flush(); - }; - return item; + if (auto lt = asForm()) + lt->setFieldGrowthPolicy(QFormLayout::FieldGrowthPolicy(policy)); } -LayoutItem empty() +QWidget *Layout::emerge() const { - LayoutItem item; - item.onAdd = [](LayoutBuilder &builder) { - ResultItem ri; - ri.empty = true; - builder.stack.last().pendingItems.append(ri); - }; - return item; + const_cast<Layout *>(this)->flush(); + QWidget *widget = new QWidget; + widget->setLayout(access(this)); + return widget; } -LayoutItem hr() +void Layout::show() const { - LayoutItem item; - item.onAdd = [](LayoutBuilder &builder) { doAddWidget(builder, createHr()); }; - return item; + return emerge()->show(); } -LayoutItem st() +// "Widgets" + +Widget::Widget(std::initializer_list<I> ps) { - LayoutItem item; - item.onAdd = [](LayoutBuilder &builder) { doAddStretch(builder, Stretch(1)); }; - return item; + ptr = new Implementation; + apply(this, ps); } -LayoutItem noMargin() +void Widget::setSize(int w, int h) { - return customMargin({}); + access(this)->resize(w, h); } -LayoutItem normalMargin() +void Widget::setLayout(const Layout &layout) { - return customMargin({9, 9, 9, 9}); + access(this)->setLayout(access(&layout)); } -LayoutItem customMargin(const QMargins &margin) +void Widget::setWindowTitle(const QString &title) { - LayoutItem item; - item.onAdd = [margin](LayoutBuilder &builder) { - if (auto layout = builder.stack.last().layout) - layout->setContentsMargins(margin); - else if (auto widget = builder.stack.last().widget) - widget->setContentsMargins(margin); - }; - return item; + access(this)->setWindowTitle(title); } -LayoutItem withFormAlignment() +void Widget::setToolTip(const QString &title) { - LayoutItem item; - item.onAdd = [](LayoutBuilder &builder) { - if (builder.stack.size() >= 2) { - if (auto widget = builder.stack.at(builder.stack.size() - 2).widget) { - const Qt::Alignment align(widget->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment)); - builder.stack.last().align = align; - } - } - }; - return item; + access(this)->setToolTip(title); } -// "Widgets" +void Widget::show() +{ + access(this)->show(); +} -template <class T> -void setupWidget(LayoutItem *item) +void Widget::setNoMargins(int) { - item->onAdd = [](LayoutBuilder &builder) { builder.stack.append(new T); }; - item->onExit = widgetExit; -}; + setContentsMargins(0, 0, 0, 0); +} -Widget::Widget(std::initializer_list<LayoutItem> items) +void Widget::setNormalMargins(int) { - this->subItems = items; - setupWidget<QWidget>(this); + setContentsMargins(9, 9, 9, 9); } -Group::Group(std::initializer_list<LayoutItem> items) +void Widget::setContentsMargins(int left, int top, int right, int bottom) { - this->subItems = items; - setupWidget<QGroupBox>(this); + access(this)->setContentsMargins(left, top, right, bottom); } -Stack::Stack(std::initializer_list<LayoutItem> items) +QWidget *Widget::emerge() const { - // We use a QStackedWidget instead of a QStackedLayout here because the latter will call - // "setVisible()" when a child is added, which can lead to the widget being spawned as a - // top-level widget. This can lead to the focus shifting away from the main application. - subItems = items; - onAdd = [](LayoutBuilder &builder) { - builder.stack.append(Slice(new QStackedWidget, true)); - }; - onExit = layoutingWidgetExit<QStackedWidget>; + return access(this); } -PushButton::PushButton(std::initializer_list<LayoutItem> items) +// Label + +Label::Label(std::initializer_list<I> ps) { - this->subItems = items; - setupWidget<QPushButton>(this); + ptr = new Implementation; + apply(this, ps); } -SpinBox::SpinBox(std::initializer_list<LayoutItem> items) +Label::Label(const QString &text) { - this->subItems = items; - setupWidget<QSpinBox>(this); + ptr = new Implementation; + setText(text); } -TextEdit::TextEdit(std::initializer_list<LayoutItem> items) +void Label::setText(const QString &text) { - this->subItems = items; - setupWidget<QTextEdit>(this); + access(this)->setText(text); } -Splitter::Splitter(std::initializer_list<LayoutItem> items) +void Label::setTextFormat(Qt::TextFormat format) { - subItems = items; - onAdd = [](LayoutBuilder &builder) { - auto splitter = new QSplitter; - splitter->setOrientation(Qt::Vertical); - builder.stack.append(Slice(splitter, true)); - }; - onExit = layoutingWidgetExit<QSplitter>; + access(this)->setTextFormat(format); } -ToolBar::ToolBar(std::initializer_list<LayoutItem> items) +void Label::setWordWrap(bool on) { - subItems = items; - onAdd = [](LayoutBuilder &builder) { - auto toolbar = new QToolBar; - toolbar->setOrientation(Qt::Horizontal); - builder.stack.append(Slice(toolbar, true)); - }; - onExit = layoutingWidgetExit<QToolBar>; + access(this)->setWordWrap(on); } -TabWidget::TabWidget(std::initializer_list<LayoutItem> items) +void Label::setTextInteractionFlags(Qt::TextInteractionFlags flags) { - this->subItems = items; - setupWidget<QTabWidget>(this); + access(this)->setTextInteractionFlags(flags); } -// Special Tab +void Label::setOpenExternalLinks(bool on) +{ + access(this)->setOpenExternalLinks(on); +} -Tab::Tab(const QString &tabName, const LayoutItem &item) +void Label::onLinkHovered(const std::function<void (const QString &)> &func, QObject *guard) { - onAdd = [item](LayoutBuilder &builder) { - auto tab = new QWidget; - builder.stack.append(tab); - item.attachTo(tab); - }; - onExit = [tabName](LayoutBuilder &builder) { - QWidget *inner = builder.stack.last().widget; - builder.stack.pop_back(); - auto tabWidget = qobject_cast<QTabWidget *>(builder.stack.last().widget); - QTC_ASSERT(tabWidget, return); - tabWidget->addTab(inner, tabName); - }; + QObject::connect(access(this), &QLabel::linkHovered, guard, func); } -// Special If +// Group -If::If(bool condition, const LayoutItems &items, const LayoutItems &other) +Group::Group(std::initializer_list<I> ps) { - subItems.append(condition ? items : other); + ptr = new Implementation; + apply(this, ps); } -// Special Application - -Application::Application(std::initializer_list<LayoutItem> items) +void Group::setTitle(const QString &title) { - subItems = items; - setupWidget<QWidget>(this); - onExit = {}; // Hack: Don't dropp the last slice, we need the resulting widget. + access(this)->setTitle(title); + access(this)->setObjectName(title); } -int Application::exec(int &argc, char *argv[]) +void Group::setGroupChecker(const std::function<void (QObject *)> &checker) { - QApplication app(argc, argv); - LayoutBuilder builder; - addItemHelper(builder, *this); - if (QWidget *widget = builder.stack.last().widget) - widget->show(); - return app.exec(); + checker(access(this)); } -// "Properties" +// SpinBox -LayoutItem title(const QString &title) +SpinBox::SpinBox(std::initializer_list<I> ps) { - return [title](QObject *target) { - if (auto groupBox = qobject_cast<QGroupBox *>(target)) { - groupBox->setTitle(title); - groupBox->setObjectName(title); - } else if (auto widget = qobject_cast<QWidget *>(target)) { - widget->setWindowTitle(title); - } else { - QTC_CHECK(false); - } - }; + ptr = new Implementation; + apply(this, ps); } -LayoutItem windowTitle(const QString &windowTitle) +void SpinBox::setValue(int val) { - return [windowTitle](QObject *target) { - if (auto widget = qobject_cast<QWidget *>(target)) { - widget->setWindowTitle(windowTitle); - } else { - QTC_CHECK(false); - } - }; + access(this)->setValue(val); } -LayoutItem text(const QString &text) +void SpinBox::onTextChanged(const std::function<void (QString)> &func) { - return [text](QObject *target) { - if (auto button = qobject_cast<QAbstractButton *>(target)) { - button->setText(text); - } else if (auto textEdit = qobject_cast<QTextEdit *>(target)) { - textEdit->setText(text); - } else { - QTC_CHECK(false); - } - }; + QObject::connect(access(this), &QSpinBox::textChanged, func); } -LayoutItem tooltip(const QString &toolTip) +// TextEdit + +TextEdit::TextEdit(std::initializer_list<I> ps) { - return [toolTip](QObject *target) { - if (auto widget = qobject_cast<QWidget *>(target)) { - widget->setToolTip(toolTip); - } else { - QTC_CHECK(false); - } - }; + ptr = new Implementation; + apply(this, ps); } -LayoutItem spacing(int spacing) +void TextEdit::setText(const QString &text) { - return [spacing](QObject *target) { - if (auto layout = qobject_cast<QLayout *>(target)) { - layout->setSpacing(spacing); - } else { - QTC_CHECK(false); - } - }; + access(this)->setText(text); } -LayoutItem resize(int w, int h) +// PushButton + +PushButton::PushButton(std::initializer_list<I> ps) { - return [w, h](QObject *target) { - if (auto widget = qobject_cast<QWidget *>(target)) { - widget->resize(w, h); - } else { - QTC_CHECK(false); - } - }; + ptr = new Implementation; + apply(this, ps); } -LayoutItem columnStretch(int column, int stretch) +void PushButton::setText(const QString &text) { - return [column, stretch](QObject *target) { - if (auto grid = qobject_cast<QGridLayout *>(target)) { - grid->setColumnStretch(column, stretch); - } else { - QTC_CHECK(false); - } - }; + access(this)->setText(text); } -LayoutItem fieldGrowthPolicy(QFormLayout::FieldGrowthPolicy policy) +void PushButton::onClicked(const std::function<void ()> &func, QObject *guard) { - return [policy](QObject *target) { - if (auto form = qobject_cast<QFormLayout *>(target)) { - form->setFieldGrowthPolicy(policy); - } else { - QTC_CHECK(false); - } - }; + QObject::connect(access(this), &QAbstractButton::clicked, guard, func); } +// Stack -// Id based setters - -LayoutItem id(ID &out) +// We use a QStackedWidget instead of a QStackedLayout here because the latter will call +// "setVisible()" when a child is added, which can lead to the widget being spawned as a +// top-level widget. This can lead to the focus shifting away from the main application. +Stack::Stack(std::initializer_list<I> ps) { - return [&out](QObject *target) { out.ob = target; }; + ptr = new Implementation; + apply(this, ps); } -void setText(ID id, const QString &text) +void addToStack(Stack *stack, const Widget &inner) { - if (auto textEdit = qobject_cast<QTextEdit *>(id.ob)) - textEdit->setText(text); + access(stack)->addWidget(inner.emerge()); } -// Signals +void addToStack(Stack *stack, const Layout &inner) +{ + inner.flush_(); + access(stack)->addWidget(inner.emerge()); +} -LayoutItem onClicked(const std::function<void ()> &func, QObject *guard) +void addToStack(Stack *stack, QWidget *inner) { - return [func, guard](QObject *target) { - if (auto button = qobject_cast<QAbstractButton *>(target)) { - QObject::connect(button, &QAbstractButton::clicked, guard ? guard : target, func); - } else { - QTC_CHECK(false); - } - }; + access(stack)->addWidget(inner); } -LayoutItem onTextChanged(const std::function<void (const QString &)> &func, QObject *guard) +// Splitter + +Splitter::Splitter(std::initializer_list<I> ps) { - return [func, guard](QObject *target) { - if (auto button = qobject_cast<QSpinBox *>(target)) { - QObject::connect(button, &QSpinBox::textChanged, guard ? guard : target, func); - } else { - QTC_CHECK(false); - } - }; + ptr = new Implementation; + access(this)->setOrientation(Qt::Vertical); + apply(this, ps); } -LayoutItem onValueChanged(const std::function<void (int)> &func, QObject *guard) +void addToSplitter(Splitter *splitter, QWidget *inner) { - return [func, guard](QObject *target) { - if (auto button = qobject_cast<QSpinBox *>(target)) { - QObject::connect(button, &QSpinBox::valueChanged, guard ? guard : target, func); - } else { - QTC_CHECK(false); - } - }; + access(splitter)->addWidget(inner); } -// Convenience +void addToSplitter(Splitter *splitter, const Widget &inner) +{ + access(splitter)->addWidget(inner.emerge()); +} -QWidget *createHr(QWidget *parent) +void addToSplitter(Splitter *splitter, const Layout &inner) { - auto frame = new QFrame(parent); - frame->setFrameShape(QFrame::HLine); - frame->setFrameShadow(QFrame::Sunken); - return frame; + inner.flush_(); + access(splitter)->addWidget(inner.emerge()); } -// Singletons. +// ToolBar -LayoutItem::LayoutItem(const LayoutItem &t) +ToolBar::ToolBar(std::initializer_list<I> ps) { - operator=(t); + ptr = new Implementation; + apply(this, ps); + access(this)->setOrientation(Qt::Horizontal); } -void createItem(LayoutItem *item, LayoutItem(*t)()) +// TabWidget + +TabWidget::TabWidget(std::initializer_list<I> ps) { - *item = t(); + ptr = new Implementation; + apply(this, ps); } -void createItem(LayoutItem *item, const std::function<void(QObject *target)> &t) +Tab::Tab(const QString &tabName, const Layout &inner) + : tabName(tabName), inner(inner) +{} + +void addToTabWidget(TabWidget *tabWidget, const Tab &tab) { - item->setter = t; + access(tabWidget)->addTab(tab.inner.emerge(), tab.tabName); } -void createItem(LayoutItem *item, QWidget *t) -{ - if (auto l = qobject_cast<QLabel *>(t)) - l->setTextInteractionFlags(l->textInteractionFlags() | Qt::TextSelectableByMouse); +// Special If + +If::If(bool condition, + const std::initializer_list<Layout::I> ifcase, + const std::initializer_list<Layout::I> thencase) + : used(condition ? ifcase : thencase) +{} - item->onAdd = [t](LayoutBuilder &builder) { doAddWidget(builder, t); }; +void addToLayout(Layout *layout, const If &inner) +{ + for (const Layout::I &item : inner.used) + item.apply(layout); } -void createItem(LayoutItem *item, QLayout *t) +// Specials + +QWidget *createHr(QWidget *parent) { - item->onAdd = [t](LayoutBuilder &builder) { doAddLayout(builder, t); }; + auto frame = new QFrame(parent); + frame->setFrameShape(QFrame::HLine); + frame->setFrameShadow(QFrame::Sunken); + return frame; } -void createItem(LayoutItem *item, const QString &t) +Span::Span(int cols, const Layout::I &item) + : item(item), spanCols(cols) +{} + +Span::Span(int cols, int rows, const Layout::I &item) + : item(item), spanCols(cols), spanRows(rows) +{} + +void addToLayout(Layout *layout, const Span &inner) { - item->onAdd = [t](LayoutBuilder &builder) { doAddText(builder, t); }; + layout->addItem(inner.item); + if (layout->pendingItems.empty()) { + QTC_CHECK(inner.spanCols == 1 && inner.spanRows == 1); + return; + } + layout->pendingItems.back().spanCols = inner.spanCols; + layout->pendingItems.back().spanRows = inner.spanRows; } -void createItem(LayoutItem *item, const Space &t) +LayoutModifier spacing(int space) { - item->onAdd = [t](LayoutBuilder &builder) { doAddSpace(builder, t); }; + return [space](Layout *layout) { layout->setSpacing(space); }; } -void createItem(LayoutItem *item, const Stretch &t) +void addToLayout(Layout *layout, const Space &inner) { - item->onAdd = [t](LayoutBuilder &builder) { doAddStretch(builder, t); }; + if (auto lt = layout->asBox()) + lt->addSpacing(inner.space); } -void createItem(LayoutItem *item, const Span &t) +void addToLayout(Layout *layout, const Stretch &inner) { - item->onAdd = [t](LayoutBuilder &builder) { - addItemHelper(builder, t.item); - builder.stack.last().pendingItems.last().span = t.span; - }; + if (auto lt = layout->asBox()) + lt->addStretch(inner.stretch); } -} // Layouting +// void createItem(LayoutItem *item, QWidget *t) +// { +// if (auto l = qobject_cast<QLabel *>(t)) +// l->setTextInteractionFlags(l->textInteractionFlags() | Qt::TextSelectableByMouse); + +// item->onAdd = [t](LayoutBuilder &builder) { doAddWidget(builder, t); }; +// } -#include "layoutbuilder.moc" + +} // Layouting diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index c9fac7d838..a411ea4763 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -3,12 +3,11 @@ #pragma once -#include <QFormLayout> -#include <QList> #include <QString> -#include <QtGlobal> -#include <optional> +#include <functional> +#include <initializer_list> +#include <vector> #if defined(UTILS_LIBRARY) # define QTCREATOR_UTILS_EXPORT Q_DECL_EXPORT @@ -19,251 +18,570 @@ #endif QT_BEGIN_NAMESPACE +class QBoxLayout; +class QFormLayout; +class QGridLayout; +class QGroupBox; +class QHBoxLayout; +class QLabel; class QLayout; -class QMargins; class QObject; +class QPushButton; +class QSpinBox; +class QSplitter; +class QStackedWidget; +class QTabWidget; +class QTextEdit; +class QToolBar; +class QVBoxLayout; class QWidget; -template <class T> T qobject_cast(QObject *object); QT_END_NAMESPACE namespace Layouting { -// LayoutItem +class NestId {}; -class LayoutBuilder; -class LayoutItem; -using LayoutItems = QList<LayoutItem>; - -class QTCREATOR_UTILS_EXPORT LayoutItem +template <typename Id, typename Arg> +class IdAndArg { public: - using Setter = std::function<void(QObject *target)>; + IdAndArg(Id, const Arg &arg) : arg(arg) {} + const Arg arg; // FIXME: Could be const &, but this would currently break bindTo(). +}; - LayoutItem(); - ~LayoutItem(); +template<typename T1, typename T2> +struct Arg2 +{ + Arg2(const T1 &a1, const T2 &a2) + : p1(a1) + , p2(a2) + {} + const T1 p1; + const T2 p2; +}; + +template<typename T1, typename T2, typename T3> +struct Arg3 +{ + Arg3(const T1 &a1, const T2 &a2, const T3 &a3) + : p1(a1) + , p2(a2) + , p3(a3) + {} + const T1 p1; + const T2 p2; + const T3 p3; +}; + +template<typename T1, typename T2, typename T3, typename T4> +struct Arg4 +{ + Arg4(const T1 &a1, const T2 &a2, const T3 &a3, const T4 &a4) + : p1(a1) + , p2(a2) + , p3(a3) + , p4(a4) + {} + const T1 p1; + const T2 p2; + const T3 p3; + const T4 p4; +}; + +// The main dispatcher - LayoutItem(const LayoutItem &t); - LayoutItem &operator=(const LayoutItem &t) = default; +void doit(auto x, auto id, auto p); - template <class T> LayoutItem(const T &t) +template <typename X> class BuilderItem +{ +public: + // Nested child object + template <typename Inner> + BuilderItem(Inner && p) { - if constexpr (std::is_base_of_v<LayoutItem, T>) - LayoutItem::operator=(t); - else - createItem(this, t); + apply = [&p](X *x) { doit(x, NestId{}, std::forward<Inner>(p)); }; } - void attachTo(QWidget *w) const; - QWidget *emerge(); - - void addItem(const LayoutItem &item); - void addItems(const LayoutItems &items); - void addRow(const LayoutItems &items); + // Property setter + template <typename Id, typename Arg> + BuilderItem(IdAndArg<Id, Arg> && idarg) + { + apply = [&idarg](X *x) { doit(x, Id{}, idarg.arg); }; + } - std::function<void(LayoutBuilder &)> onAdd; - std::function<void(LayoutBuilder &)> onExit; - std::function<void(QObject *target)> setter; - LayoutItems subItems; + std::function<void(X *)> apply; }; -// Special items -class QTCREATOR_UTILS_EXPORT Space +////////////////////////////////////////////// + +// +// Basic +// + +class QTCREATOR_UTILS_EXPORT Thing { public: - explicit Space(int space) : space(space) {} - const int space; + void *ptr; // The product. }; -class QTCREATOR_UTILS_EXPORT Stretch +class QTCREATOR_UTILS_EXPORT Object : public Thing { public: - explicit Stretch(int stretch = 1) : stretch(stretch) {} - const int stretch; + using Implementation = QObject; + using I = BuilderItem<Object>; + + Object() = default; + Object(std::initializer_list<I> ps); }; -class QTCREATOR_UTILS_EXPORT Span +// +// Layouts +// + +class FlowLayout; +class Layout; +using LayoutModifier = std::function<void(Layout *)>; + +class QTCREATOR_UTILS_EXPORT LayoutItem { public: - Span(int span, const LayoutItem &item) : span(span), item(item) {} - const int span; - LayoutItem item; + ~LayoutItem(); + LayoutItem(); + LayoutItem(QLayout *l); + LayoutItem(QWidget *w); + LayoutItem(const QString &t); + + QString text; + QLayout *layout = nullptr; + QWidget *widget = nullptr; + int stretch = -1; + int spanCols = 1; + int spanRows = 1; + bool empty = false; }; -class QTCREATOR_UTILS_EXPORT Column : public LayoutItem +class QTCREATOR_UTILS_EXPORT Layout : public Object { public: - Column(std::initializer_list<LayoutItem> items); + using Implementation = QLayout; + using I = BuilderItem<Layout>; + + Layout() = default; + Layout(Implementation *w) { ptr = w; } + + void span(int cols, int rows); + + void setNoMargins(); + void setNormalMargins(); + void setContentsMargins(int left, int top, int right, int bottom); + void setColumnStretch(int cols, int rows); + void setSpacing(int space); + void setFieldGrowthPolicy(int policy); + + void attachTo(QWidget *); + + void addItem(I item); + void addItems(std::initializer_list<I> items); + void addRow(std::initializer_list<I> items); + void addLayoutItem(const LayoutItem &item); + + void flush(); + void flush_() const; + + QWidget *emerge() const; + void show() const; + + QFormLayout *asForm(); + QGridLayout *asGrid(); + QBoxLayout *asBox(); + FlowLayout *asFlow(); + + // Grid-only + int currentGridColumn = 0; + int currentGridRow = 0; + //Qt::Alignment align = {}; + bool useFormAlignment = false; + + std::vector<LayoutItem> pendingItems; }; -class QTCREATOR_UTILS_EXPORT Row : public LayoutItem +class QTCREATOR_UTILS_EXPORT Column : public Layout { public: - Row(std::initializer_list<LayoutItem> items); + using Implementation = QVBoxLayout; + using I = BuilderItem<Column>; + + Column(std::initializer_list<I> ps); }; -class QTCREATOR_UTILS_EXPORT Flow : public LayoutItem +class QTCREATOR_UTILS_EXPORT Row : public Layout { public: - Flow(std::initializer_list<LayoutItem> items); + using Implementation = QHBoxLayout; + using I = BuilderItem<Row>; + + Row(std::initializer_list<I> ps); }; -class QTCREATOR_UTILS_EXPORT Grid : public LayoutItem +class QTCREATOR_UTILS_EXPORT Form : public Layout { public: - Grid() : Grid({}) {} - Grid(std::initializer_list<LayoutItem> items); + using Implementation = QFormLayout; + using I = BuilderItem<Form>; + + Form(); + Form(std::initializer_list<I> ps); }; -class QTCREATOR_UTILS_EXPORT Form : public LayoutItem +class QTCREATOR_UTILS_EXPORT Grid : public Layout { public: - Form() : Form({}) {} - Form(std::initializer_list<LayoutItem> items); + using Implementation = QGridLayout; + using I = BuilderItem<Grid>; + + Grid(); + Grid(std::initializer_list<I> ps); }; -class QTCREATOR_UTILS_EXPORT Widget : public LayoutItem +class QTCREATOR_UTILS_EXPORT Flow : public Layout { public: - Widget(std::initializer_list<LayoutItem> items); + Flow(std::initializer_list<I> ps); }; -class QTCREATOR_UTILS_EXPORT Stack : public LayoutItem +class QTCREATOR_UTILS_EXPORT Stretch { public: - Stack() : Stack({}) {} - Stack(std::initializer_list<LayoutItem> items); + explicit Stretch(int stretch) : stretch(stretch) {} + + int stretch; }; -class QTCREATOR_UTILS_EXPORT Tab : public LayoutItem +class QTCREATOR_UTILS_EXPORT Space { public: - Tab(const QString &tabName, const LayoutItem &item); + explicit Space(int space) : space(space) {} + + int space; }; -class QTCREATOR_UTILS_EXPORT If : public LayoutItem +class QTCREATOR_UTILS_EXPORT Span { public: - If(bool condition, const LayoutItems &item, const LayoutItems &other = {}); + Span(int cols, const Layout::I &item); + Span(int cols, int rows, const Layout::I &item); + + Layout::I item; + int spanCols = 1; + int spanRows = 1; }; -class QTCREATOR_UTILS_EXPORT Group : public LayoutItem +// +// Widgets +// + +class QTCREATOR_UTILS_EXPORT Widget : public Object { public: - Group(std::initializer_list<LayoutItem> items); + using Implementation = QWidget; + using I = BuilderItem<Widget>; + + Widget() = default; + Widget(std::initializer_list<I> ps); + Widget(Implementation *w) { ptr = w; } + + QWidget *emerge() const; + void show(); + + void setLayout(const Layout &layout); + void setSize(int, int); + void setWindowTitle(const QString &); + void setToolTip(const QString &); + void setNoMargins(int = 0); + void setNormalMargins(int = 0); + void setContentsMargins(int left, int top, int right, int bottom); }; -class QTCREATOR_UTILS_EXPORT TextEdit : public LayoutItem +class QTCREATOR_UTILS_EXPORT Label : public Widget { public: - TextEdit(std::initializer_list<LayoutItem> items); + using Implementation = QLabel; + using I = BuilderItem<Label>; + + Label(std::initializer_list<I> ps); + Label(const QString &text); + + void setText(const QString &); + void setTextFormat(Qt::TextFormat); + void setWordWrap(bool); + void setTextInteractionFlags(Qt::TextInteractionFlags); + void setOpenExternalLinks(bool); + void onLinkHovered(const std::function<void(const QString &)> &, QObject *guard); }; -class QTCREATOR_UTILS_EXPORT PushButton : public LayoutItem +class QTCREATOR_UTILS_EXPORT Group : public Widget { public: - PushButton(std::initializer_list<LayoutItem> items); + using Implementation = QGroupBox; + using I = BuilderItem<Group>; + + Group(std::initializer_list<I> ps); + + void setTitle(const QString &); + void setGroupChecker(const std::function<void(QObject *)> &); }; -class QTCREATOR_UTILS_EXPORT SpinBox : public LayoutItem +class QTCREATOR_UTILS_EXPORT SpinBox : public Widget { public: - SpinBox(std::initializer_list<LayoutItem> items); + using Implementation = QSpinBox; + using I = BuilderItem<SpinBox>; + + SpinBox(std::initializer_list<I> ps); + + void setValue(int); + void onTextChanged(const std::function<void(QString)> &); }; -class QTCREATOR_UTILS_EXPORT Splitter : public LayoutItem +class QTCREATOR_UTILS_EXPORT PushButton : public Widget { public: - Splitter(std::initializer_list<LayoutItem> items); + using Implementation = QPushButton; + using I = BuilderItem<PushButton>; + + PushButton(std::initializer_list<I> ps); + + void setText(const QString &); + void onClicked(const std::function<void()> &, QObject *guard); }; -class QTCREATOR_UTILS_EXPORT ToolBar : public LayoutItem +class QTCREATOR_UTILS_EXPORT TextEdit : public Widget { public: - ToolBar(std::initializer_list<LayoutItem> items); + using Implementation = QTextEdit; + using I = BuilderItem<TextEdit>; + using Id = Implementation *; + + TextEdit(std::initializer_list<I> ps); + + void setText(const QString &); }; -class QTCREATOR_UTILS_EXPORT TabWidget : public LayoutItem +class QTCREATOR_UTILS_EXPORT Splitter : public Widget { public: - TabWidget(std::initializer_list<LayoutItem> items); + using Implementation = QSplitter; + using I = BuilderItem<Splitter>; + + Splitter(std::initializer_list<I> items); }; -class QTCREATOR_UTILS_EXPORT Application : public LayoutItem +class QTCREATOR_UTILS_EXPORT Stack : public Widget { public: - Application(std::initializer_list<LayoutItem> items); + using Implementation = QStackedWidget; + using I = BuilderItem<Stack>; - int exec(int &argc, char *argv[]); + Stack() : Stack({}) {} + Stack(std::initializer_list<I> items); }; +class QTCREATOR_UTILS_EXPORT Tab : public Widget +{ +public: + using Implementation = QWidget; -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const std::function<void(QObject *target)> &t); -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, QWidget *t); -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, QLayout *t); -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, LayoutItem(*t)()); -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const QString &t); -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const Span &t); -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const Space &t); -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const Stretch &t); - + Tab(const QString &tabName, const Layout &inner); -// "Singletons" + const QString tabName; + const Layout inner; +}; -QTCREATOR_UTILS_EXPORT LayoutItem br(); -QTCREATOR_UTILS_EXPORT LayoutItem st(); -QTCREATOR_UTILS_EXPORT LayoutItem empty(); -QTCREATOR_UTILS_EXPORT LayoutItem hr(); -QTCREATOR_UTILS_EXPORT LayoutItem noMargin(); -QTCREATOR_UTILS_EXPORT LayoutItem normalMargin(); -QTCREATOR_UTILS_EXPORT LayoutItem customMargin(const QMargins &margin); -QTCREATOR_UTILS_EXPORT LayoutItem withFormAlignment(); +class QTCREATOR_UTILS_EXPORT TabWidget : public Widget +{ +public: + using Implementation = QTabWidget; + using I = BuilderItem<TabWidget>; -// "Setters" + TabWidget(std::initializer_list<I> items); +}; -QTCREATOR_UTILS_EXPORT LayoutItem title(const QString &title); -QTCREATOR_UTILS_EXPORT LayoutItem text(const QString &text); -QTCREATOR_UTILS_EXPORT LayoutItem tooltip(const QString &toolTip); -QTCREATOR_UTILS_EXPORT LayoutItem resize(int, int); -QTCREATOR_UTILS_EXPORT LayoutItem columnStretch(int column, int stretch); -QTCREATOR_UTILS_EXPORT LayoutItem spacing(int); -QTCREATOR_UTILS_EXPORT LayoutItem windowTitle(const QString &windowTitle); -QTCREATOR_UTILS_EXPORT LayoutItem fieldGrowthPolicy(QFormLayout::FieldGrowthPolicy policy); +class QTCREATOR_UTILS_EXPORT ToolBar : public Widget +{ +public: + using Implementation = QToolBar; + using I = Layouting::BuilderItem<ToolBar>; + ToolBar(std::initializer_list<I> items); +}; -// "Getters" +// Special -class ID +class QTCREATOR_UTILS_EXPORT If { public: - QObject *ob = nullptr; + If(bool condition, + const std::initializer_list<Layout::I> ifcase, + const std::initializer_list<Layout::I> thencase = {}); + + const std::initializer_list<Layout::I> used; }; -QTCREATOR_UTILS_EXPORT LayoutItem id(ID &out); +// +// Dispatchers +// -QTCREATOR_UTILS_EXPORT void setText(ID id, const QString &text); +// We need one 'Id' (and a corresponding function wrapping arguments into a +// tuple marked by this id) per 'name' of "backend" setter member function, +// i.e. one 'text' is sufficient for QLabel::setText, QLineEdit::setText. +// The name of the Id does not have to match the backend names as it +// is mapped per-backend-type in the respective setter implementation +// but we assume that it generally makes sense to stay close to the +// wrapped API name-wise. +// These are free functions overloaded on the type of builder object +// and setter id. The function implementations are independent, but +// the base expectation is that they will forwards to the backend +// type's setter. -// "Signals" +// Special dispatchers -QTCREATOR_UTILS_EXPORT LayoutItem onClicked(const std::function<void()> &, - QObject *guard = nullptr); -QTCREATOR_UTILS_EXPORT LayoutItem onTextChanged(const std::function<void(const QString &)> &, - QObject *guard = nullptr); -QTCREATOR_UTILS_EXPORT LayoutItem onValueChanged(const std::function<void(int)> &, - QObject *guard = nullptr); -QTCREATOR_UTILS_EXPORT LayoutItem onTextChanged(ID &id, QVariant(*sig)(QObject *)); +class BindToId {}; -// Convenience +template <typename T> +auto bindTo(T **p) +{ + return IdAndArg{BindToId{}, p}; +} -QTCREATOR_UTILS_EXPORT QWidget *createHr(QWidget *parent = nullptr); +template <typename Interface> +void doit(Interface *x, BindToId, auto p) +{ + *p = static_cast<typename Interface::Implementation *>(x->ptr); +} + +class IdId {}; +auto id(auto p) { return IdAndArg{IdId{}, p}; } + +template <typename Interface> +void doit(Interface *x, IdId, auto p) +{ + **p = static_cast<typename Interface::Implementation *>(x->ptr); +} + +// Setter dispatchers + +#define QTCREATOR_SETTER(name, setter) \ + class name##_TAG {}; \ + inline auto name(auto p) { return IdAndArg{name##_TAG{}, p}; } \ + inline void doit(auto x, name##_TAG, auto p) { x->setter(p); } + +#define QTCREATOR_SETTER2(name, setter) \ + class name##_TAG {}; \ + inline auto name(auto p1, auto p2) { return IdAndArg{name##_TAG{}, Arg2{p1, p2}}; } \ + inline void doit(auto x, name##_TAG, auto p) { x->setter(p.p1, p.p2); } + +#define QTCREATOR_SETTER3(name, setter) \ + class name##_TAG {}; \ + inline auto name(auto p1, auto p2, auto p3) { return IdAndArg{name##_TAG{}, Arg3{p1, p2, p3}}; } \ + inline void doit(auto x, name##_TAG, auto p) { x->setter(p.p1, p.p2, p.p3); } + +#define QTCREATOR_SETTER4(name, setter) \ + class name##_TAG {}; \ + inline auto name(auto p1, auto p2, auto p3, auto p4) { return IdAndArg{name##_TAG{}, Arg4{p1, p2, p3, p4}}; } \ + inline void doit(auto x, name##_TAG, auto p) { x->setter(p.p1, p.p2, p.p3, p.p4); } + +QTCREATOR_SETTER(fieldGrowthPolicy, setFieldGrowthPolicy); +QTCREATOR_SETTER(groupChecker, setGroupChecker); +QTCREATOR_SETTER(openExternalLinks, setOpenExternalLinks); +QTCREATOR_SETTER2(size, setSize) +QTCREATOR_SETTER(text, setText) +QTCREATOR_SETTER(textFormat, setTextFormat); +QTCREATOR_SETTER(textInteractionFlags, setTextInteractionFlags); +QTCREATOR_SETTER(title, setTitle) +QTCREATOR_SETTER(toolTip, setToolTip); +QTCREATOR_SETTER(windowTitle, setWindowTitle); +QTCREATOR_SETTER(wordWrap, setWordWrap); +QTCREATOR_SETTER2(columnStretch, setColumnStretch); +QTCREATOR_SETTER2(onClicked, onClicked); +QTCREATOR_SETTER2(onLinkHovered, onLinkHovered); +QTCREATOR_SETTER2(onTextChanged, onTextChanged); +QTCREATOR_SETTER4(customMargins, setContentsMargins); + +// Nesting dispatchers + +QTCREATOR_UTILS_EXPORT void addToLayout(Layout *layout, const Layout &inner); +QTCREATOR_UTILS_EXPORT void addToLayout(Layout *layout, const Widget &inner); +QTCREATOR_UTILS_EXPORT void addToLayout(Layout *layout, QWidget *inner); +QTCREATOR_UTILS_EXPORT void addToLayout(Layout *layout, QLayout *inner); +QTCREATOR_UTILS_EXPORT void addToLayout(Layout *layout, const LayoutModifier &inner); +QTCREATOR_UTILS_EXPORT void addToLayout(Layout *layout, const QString &inner); +QTCREATOR_UTILS_EXPORT void addToLayout(Layout *layout, const Space &inner); +QTCREATOR_UTILS_EXPORT void addToLayout(Layout *layout, const Stretch &inner); +QTCREATOR_UTILS_EXPORT void addToLayout(Layout *layout, const If &inner); +QTCREATOR_UTILS_EXPORT void addToLayout(Layout *layout, const Span &inner); +// ... can be added to anywhere later to support "user types" + +QTCREATOR_UTILS_EXPORT void addToWidget(Widget *widget, const Layout &layout); + +QTCREATOR_UTILS_EXPORT void addToTabWidget(TabWidget *tabWidget, const Tab &inner); + +QTCREATOR_UTILS_EXPORT void addToSplitter(Splitter *splitter, QWidget *inner); +QTCREATOR_UTILS_EXPORT void addToSplitter(Splitter *splitter, const Widget &inner); +QTCREATOR_UTILS_EXPORT void addToSplitter(Splitter *splitter, const Layout &inner); + +QTCREATOR_UTILS_EXPORT void addToStack(Stack *stack, QWidget *inner); +QTCREATOR_UTILS_EXPORT void addToStack(Stack *stack, const Widget &inner); +QTCREATOR_UTILS_EXPORT void addToStack(Stack *stack, const Layout &inner); + +template <class Inner> +void doit_nested(Layout *outer, Inner && inner) +{ + addToLayout(outer, std::forward<Inner>(inner)); +} + +void doit_nested(Widget *outer, auto inner) +{ + addToWidget(outer, inner); +} + +void doit_nested(TabWidget *outer, auto inner) +{ + addToTabWidget(outer, inner); +} + +void doit_nested(Stack *outer, auto inner) +{ + addToStack(outer, inner); +} + +void doit_nested(Splitter *outer, auto inner) +{ + addToSplitter(outer, inner); +} -template <class T> -LayoutItem bindTo(T **out) +template <class Inner> +void doit(auto outer, NestId, Inner && inner) { - return [out](QObject *target) { *out = qobject_cast<T *>(target); }; + doit_nested(outer, std::forward<Inner>(inner)); } +// Special layout items + +QTCREATOR_UTILS_EXPORT void empty(Layout *); +QTCREATOR_UTILS_EXPORT void br(Layout *); +QTCREATOR_UTILS_EXPORT void st(Layout *); +QTCREATOR_UTILS_EXPORT void noMargin(Layout *); +QTCREATOR_UTILS_EXPORT void normalMargin(Layout *); +QTCREATOR_UTILS_EXPORT void withFormAlignment(Layout *); +QTCREATOR_UTILS_EXPORT void hr(Layout *); + +QTCREATOR_UTILS_EXPORT LayoutModifier spacing(int space); + +// Convenience + +QTCREATOR_UTILS_EXPORT QWidget *createHr(QWidget *parent = nullptr); } // Layouting diff --git a/src/libs/utils/lua.cpp b/src/libs/utils/lua.cpp new file mode 100644 index 0000000000..4f12c626f1 --- /dev/null +++ b/src/libs/utils/lua.cpp @@ -0,0 +1,30 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "lua.h" + +#include "utilstr.h" + +namespace Utils { + +static LuaInterface *s_luaInterface = nullptr; + +void setLuaInterface(LuaInterface *luaInterface) +{ + s_luaInterface = luaInterface; +} + +LuaInterface *luaInterface() +{ + return s_luaInterface; +} + +expected_str<std::unique_ptr<LuaState>> runScript(const QString &script, const QString &name) +{ + if (!s_luaInterface) + return make_unexpected(Tr::tr("No Lua interface set")); + + return s_luaInterface->runScript(script, name); +} + +} // namespace Utils diff --git a/src/libs/utils/lua.h b/src/libs/utils/lua.h new file mode 100644 index 0000000000..7a3b8f7833 --- /dev/null +++ b/src/libs/utils/lua.h @@ -0,0 +1,34 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include <QObject> + +#include "expected.h" +#include "utils_global.h" + +namespace Utils { + +class LuaState +{ +public: + virtual ~LuaState() = default; +}; + +class QTCREATOR_UTILS_EXPORT LuaInterface +{ +public: + virtual ~LuaInterface() = default; + virtual expected_str<std::unique_ptr<LuaState>> runScript( + const QString &script, const QString &name) + = 0; +}; + +QTCREATOR_UTILS_EXPORT void setLuaInterface(LuaInterface *luaInterface); +QTCREATOR_UTILS_EXPORT LuaInterface *luaInterface(); + +QTCREATOR_UTILS_EXPORT expected_str<std::unique_ptr<LuaState>> runScript( + const QString &script, const QString &name); + +} // namespace Utils diff --git a/src/libs/utils/macroexpander.cpp b/src/libs/utils/macroexpander.cpp index dd57cd6b5a..fc13897474 100644 --- a/src/libs/utils/macroexpander.cpp +++ b/src/libs/utils/macroexpander.cpp @@ -284,7 +284,7 @@ QByteArray MacroExpander::expand(const QByteArray &stringWithVariables) const QVariant MacroExpander::expandVariant(const QVariant &v) const { - const auto type = QMetaType::Type(v.type()); + const int type = v.typeId(); if (type == QMetaType::QString) { return expand(v.toString()); } else if (type == QMetaType::QStringList) { diff --git a/src/libs/utils/macroexpander.h b/src/libs/utils/macroexpander.h index adde4db1d3..fe98d46872 100644 --- a/src/libs/utils/macroexpander.h +++ b/src/libs/utils/macroexpander.h @@ -5,7 +5,6 @@ #include "utils_global.h" -#include <QCoreApplication> #include <QList> #include <functional> diff --git a/src/libs/utils/namevalueitem.cpp b/src/libs/utils/namevalueitem.cpp index 3f4cdbd4b2..cb34f8a550 100644 --- a/src/libs/utils/namevalueitem.cpp +++ b/src/libs/utils/namevalueitem.cpp @@ -19,6 +19,10 @@ EnvironmentItems EnvironmentItem::fromStringList(const QStringList &list) { EnvironmentItems result; for (const QString &string : list) { + if (string.startsWith("##")) { + result.append({string.mid(2), {}, EnvironmentItem::Comment}); + continue; + } int pos = string.indexOf("+="); if (pos != -1) { result.append({string.left(pos), string.mid(pos + 2), EnvironmentItem::Append}); @@ -59,6 +63,8 @@ QStringList EnvironmentItem::toStringList(const EnvironmentItems &list) return QString('#' + item.name + '=' + item.value); case EnvironmentItem::SetEnabled: return QString(item.name + '=' + item.value); + case EnvironmentItem::Comment: + return QString("##" + item.name); } return QString(); }); @@ -170,6 +176,8 @@ void EnvironmentItem::apply(NameValueDictionary *dictionary, Operation op) const apply(dictionary, SetEnabled); } } break; + case Comment: // ignore comments when applying to environment + break; } } @@ -195,6 +203,9 @@ QDebug operator<<(QDebug debug, const EnvironmentItem &i) case EnvironmentItem::Append: debug << "append to \"" << i.name << "\":\"" << i.value << '"'; break; + case EnvironmentItem::Comment: + debug << "comment:" << i.name; + break; } debug << ')'; return debug; diff --git a/src/libs/utils/namevalueitem.h b/src/libs/utils/namevalueitem.h index 6ad5ed0379..c518b17e66 100644 --- a/src/libs/utils/namevalueitem.h +++ b/src/libs/utils/namevalueitem.h @@ -16,7 +16,7 @@ namespace Utils { class QTCREATOR_UTILS_EXPORT EnvironmentItem { public: - enum Operation : char { SetEnabled, Unset, Prepend, Append, SetDisabled }; + enum Operation : char { SetEnabled, Unset, Prepend, Append, SetDisabled, Comment }; EnvironmentItem() = default; EnvironmentItem(const QString &key, const QString &value, Operation operation = SetEnabled) : name(key) diff --git a/src/libs/utils/namevaluesdialog.cpp b/src/libs/utils/namevaluesdialog.cpp index 35587f8791..5a45c54a75 100644 --- a/src/libs/utils/namevaluesdialog.cpp +++ b/src/libs/utils/namevaluesdialog.cpp @@ -45,7 +45,11 @@ signals: void lostFocus(); private: - void focusOutEvent(QFocusEvent *) override { emit lostFocus(); } + void focusOutEvent(QFocusEvent *e) override + { + QPlainTextEdit::focusOutEvent(e); + emit lostFocus(); + } }; } // namespace Internal @@ -56,11 +60,12 @@ NameValueItemsWidget::NameValueItemsWidget(QWidget *parent) const QString helpText = Tr::tr( "Enter one environment variable per line.\n" "To set or change a variable, use VARIABLE=VALUE.\n" + "To disable a variable, prefix this line with \"#\".\n" "To append to a variable, use VARIABLE+=VALUE.\n" "To prepend to a variable, use VARIABLE=+VALUE.\n" "Existing variables can be referenced in a VALUE with ${OTHER}.\n" "To clear a variable, put its name on a line with nothing else on it.\n" - "To disable a variable, prefix the line with \"#\"."); + "Lines starting with \"##\" will be treated as comments."); m_editor = new Internal::TextEditHelper(this); auto layout = new QVBoxLayout(this); @@ -137,7 +142,7 @@ bool NameValueItemsWidget::editVariable(const QString &name, Selection selection skipWhiteSpace(); if (offset < line.length()) { QChar nextChar = line.at(offset); - if (nextChar.isLetterOrNumber()) + if (nextChar.isLetterOrNumber() || nextChar == '_') continue; if (nextChar == '=') { if (++offset < line.length() && line.at(offset) == '+') diff --git a/src/libs/utils/outputformatter.cpp b/src/libs/utils/outputformatter.cpp index f8b3d8cec7..d36662f4d3 100644 --- a/src/libs/utils/outputformatter.cpp +++ b/src/libs/utils/outputformatter.cpp @@ -58,7 +58,7 @@ Link OutputLineParser::parseLinkTarget(const QString &target) return {}; return Link(FilePath::fromString(parts.first()), parts.length() > 1 ? parts.at(1).toInt() : 0, - parts.length() > 2 ? parts.at(2).toInt() : 0); + parts.length() > 2 ? parts.at(2).toInt() - 1 : 0); } // The redirection mechanism is needed for broken build tools (e.g. xcodebuild) that get invoked @@ -141,26 +141,39 @@ FilePath OutputLineParser::absoluteFilePath(const FilePath &filePath) const return filePath; } -void OutputLineParser::addLinkSpecForAbsoluteFilePath(OutputLineParser::LinkSpecs &linkSpecs, - const FilePath &filePath, int lineNo, int pos, int len) +void OutputLineParser::addLinkSpecForAbsoluteFilePath( + OutputLineParser::LinkSpecs &linkSpecs, + const FilePath &filePath, + int lineNo, + int column, + int pos, + int len) { if (filePath.toFileInfo().isAbsolute()) - linkSpecs.append({pos, len, createLinkTarget(filePath, lineNo)}); + linkSpecs.append({pos, len, createLinkTarget(filePath, lineNo, column)}); } -void OutputLineParser::addLinkSpecForAbsoluteFilePath(OutputLineParser::LinkSpecs &linkSpecs, - const FilePath &filePath, int lineNo, const QRegularExpressionMatch &match, - int capIndex) +void OutputLineParser::addLinkSpecForAbsoluteFilePath( + OutputLineParser::LinkSpecs &linkSpecs, + const FilePath &filePath, + int lineNo, + int column, + const QRegularExpressionMatch &match, + int capIndex) { - addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, match.capturedStart(capIndex), - match.capturedLength(capIndex)); + addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, column, + match.capturedStart(capIndex), match.capturedLength(capIndex)); } -void OutputLineParser::addLinkSpecForAbsoluteFilePath(OutputLineParser::LinkSpecs &linkSpecs, - const FilePath &filePath, int lineNo, const QRegularExpressionMatch &match, - const QString &capName) +void OutputLineParser::addLinkSpecForAbsoluteFilePath( + OutputLineParser::LinkSpecs &linkSpecs, + const FilePath &filePath, + int lineNo, + int column, + const QRegularExpressionMatch &match, + const QString &capName) { - addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, match.capturedStart(capName), - match.capturedLength(capName)); + addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, column, + match.capturedStart(capName), match.capturedLength(capName)); } bool Utils::OutputLineParser::fileExists(const FilePath &fp) const @@ -342,6 +355,7 @@ OutputLineParser::Result OutputFormatter::handleMessage(const QString &text, Out = d->nextParser->handleLine(text, outputTypeForParser(d->nextParser, format)); switch (res.status) { case OutputLineParser::Status::Done: + d->nextParser->flush(); d->nextParser = nullptr; return res; case OutputLineParser::Status::InProgress: @@ -359,6 +373,7 @@ OutputLineParser::Result OutputFormatter::handleMessage(const QString &text, Out = parser->handleLine(text, outputTypeForParser(parser, format)); switch (res.status) { case OutputLineParser::Status::Done: + parser->flush(); involvedParsers << parser; return res; case OutputLineParser::Status::InProgress: @@ -402,23 +417,32 @@ const QList<FormattedText> OutputFormatter::linkifiedText( } for (int nextLocalTextPos = 0; nextLocalTextPos < t.text.size(); ) { - - // There are no more links in this part, so copy the rest of the text as-is. - if (nextLinkSpecIndex >= linkSpecs.size()) { + const auto copyRestOfSegmentAsIs = [&] { linkified << FormattedText(t.text.mid(nextLocalTextPos), t.format); totalTextLengthSoFar += t.text.length() - nextLocalTextPos; + }; + + // We are out of links. + if (nextLinkSpecIndex >= linkSpecs.size()) { + copyRestOfSegmentAsIs(); break; } const OutputLineParser::LinkSpec &linkSpec = linkSpecs.at(nextLinkSpecIndex); const int localLinkStartPos = linkSpec.startPos - totalPreviousTextLength; + + // There are more links, but not in this segment. + if (localLinkStartPos >= t.text.size()) { + copyRestOfSegmentAsIs(); + break; + } + ++nextLinkSpecIndex; // We ignore links that would cross format boundaries. if (localLinkStartPos < nextLocalTextPos || localLinkStartPos + linkSpec.length > t.text.length()) { - linkified << FormattedText(t.text.mid(nextLocalTextPos), t.format); - totalTextLengthSoFar += t.text.length() - nextLocalTextPos; + copyRestOfSegmentAsIs(); break; } @@ -456,7 +480,7 @@ void OutputFormatter::append(const QString &text, const QTextCharFormat &format) QTextCharFormat OutputFormatter::linkFormat(const QTextCharFormat &inputFormat, const QString &href) { QTextCharFormat result = inputFormat; - result.setForeground(creatorTheme()->color(Theme::TextColorLink)); + result.setForeground(creatorColor(Theme::TextColorLink)); result.setUnderlineStyle(QTextCharFormat::SingleUnderline); result.setAnchor(true); result.setAnchorHref(href); @@ -491,14 +515,13 @@ void OutputFormatter::initFormats() if (!plainTextEdit()) return; - Theme *theme = creatorTheme(); - d->formats[NormalMessageFormat].setForeground(theme->color(Theme::OutputPanes_NormalMessageTextColor)); - d->formats[ErrorMessageFormat].setForeground(theme->color(Theme::OutputPanes_ErrorMessageTextColor)); - d->formats[LogMessageFormat].setForeground(theme->color(Theme::OutputPanes_WarningMessageTextColor)); - d->formats[StdOutFormat].setForeground(theme->color(Theme::OutputPanes_StdOutTextColor)); - d->formats[StdErrFormat].setForeground(theme->color(Theme::OutputPanes_StdErrTextColor)); - d->formats[DebugFormat].setForeground(theme->color(Theme::OutputPanes_DebugTextColor)); - d->formats[GeneralMessageFormat].setForeground(theme->color(Theme::OutputPanes_DebugTextColor)); + d->formats[NormalMessageFormat].setForeground(creatorColor(Theme::OutputPanes_NormalMessageTextColor)); + d->formats[ErrorMessageFormat].setForeground(creatorColor(Theme::OutputPanes_ErrorMessageTextColor)); + d->formats[LogMessageFormat].setForeground(creatorColor(Theme::OutputPanes_WarningMessageTextColor)); + d->formats[StdOutFormat].setForeground(creatorColor(Theme::OutputPanes_StdOutTextColor)); + d->formats[StdErrFormat].setForeground(creatorColor(Theme::OutputPanes_StdErrTextColor)); + d->formats[DebugFormat].setForeground(creatorColor(Theme::OutputPanes_DebugTextColor)); + d->formats[GeneralMessageFormat].setForeground(creatorColor(Theme::OutputPanes_DebugTextColor)); setBoldFontEnabled(d->boldFontEnabled); } diff --git a/src/libs/utils/outputformatter.h b/src/libs/utils/outputformatter.h index d26dc64327..da4dfe8a3a 100644 --- a/src/libs/utils/outputformatter.h +++ b/src/libs/utils/outputformatter.h @@ -93,12 +93,13 @@ protected: Utils::FilePath absoluteFilePath(const Utils::FilePath &filePath) const; static QString createLinkTarget(const FilePath &filePath, int line, int column); static void addLinkSpecForAbsoluteFilePath(LinkSpecs &linkSpecs, const FilePath &filePath, - int lineNo, int pos, int len); + int lineNo, int column, int pos, int len); static void addLinkSpecForAbsoluteFilePath(LinkSpecs &linkSpecs, const FilePath &filePath, - int lineNo, const QRegularExpressionMatch &match, - int capIndex); + int lineNo, int column, + const QRegularExpressionMatch &match, int capIndex); static void addLinkSpecForAbsoluteFilePath(LinkSpecs &linkSpecs, const FilePath &filePath, - int lineNo, const QRegularExpressionMatch &match, + int lineNo, int column, + const QRegularExpressionMatch &match, const QString &capName); bool fileExists(const Utils::FilePath &fp) const; diff --git a/src/libs/utils/overlaywidget.cpp b/src/libs/utils/overlaywidget.cpp index 720c5a90e1..950dc4b5dc 100644 --- a/src/libs/utils/overlaywidget.cpp +++ b/src/libs/utils/overlaywidget.cpp @@ -8,16 +8,34 @@ #include <QEvent> #include <QPainter> +namespace Utils::Internal { +class OverlayWidgetPrivate +{ +public: + OverlayWidget::PaintFunction m_paint; + OverlayWidget::ResizeFunction m_resize; +}; +} // namespace Utils::Internal + Utils::OverlayWidget::OverlayWidget(QWidget *parent) + : d(new Internal::OverlayWidgetPrivate) { setAttribute(Qt::WA_TransparentForMouseEvents); if (parent) attachToWidget(parent); + d->m_resize = [](QWidget *w, const QSize &size) { w->setGeometry(QRect(QPoint(0, 0), size)); }; } +Utils::OverlayWidget::~OverlayWidget() = default; + void Utils::OverlayWidget::setPaintFunction(const Utils::OverlayWidget::PaintFunction &paint) { - m_paint = paint; + d->m_paint = paint; +} + +void Utils::OverlayWidget::setResizeFunction(const ResizeFunction &resize) +{ + d->m_resize = resize; } bool Utils::OverlayWidget::eventFilter(QObject *obj, QEvent *ev) @@ -29,9 +47,9 @@ bool Utils::OverlayWidget::eventFilter(QObject *obj, QEvent *ev) void Utils::OverlayWidget::paintEvent(QPaintEvent *ev) { - if (m_paint) { + if (d->m_paint) { QPainter p(this); - m_paint(this, p, ev); + d->m_paint(this, p, ev); } } @@ -50,5 +68,6 @@ void Utils::OverlayWidget::attachToWidget(QWidget *parent) void Utils::OverlayWidget::resizeToParent() { QTC_ASSERT(parentWidget(), return ); - setGeometry(QRect(QPoint(0, 0), parentWidget()->size())); + if (d->m_resize) + d->m_resize(this, parentWidget()->size()); } diff --git a/src/libs/utils/overlaywidget.h b/src/libs/utils/overlaywidget.h index 8bc5b7d1eb..c5686ad171 100644 --- a/src/libs/utils/overlaywidget.h +++ b/src/libs/utils/overlaywidget.h @@ -8,18 +8,26 @@ #include <QWidget> #include <functional> +#include <memory> namespace Utils { +namespace Internal { +class OverlayWidgetPrivate; +} + class QTCREATOR_UTILS_EXPORT OverlayWidget : public QWidget { public: using PaintFunction = std::function<void(QWidget *, QPainter &, QPaintEvent *)>; + using ResizeFunction = std::function<void(QWidget *, QSize)>; explicit OverlayWidget(QWidget *parent = nullptr); + ~OverlayWidget(); void attachToWidget(QWidget *parent); void setPaintFunction(const PaintFunction &paint); + void setResizeFunction(const ResizeFunction &resize); protected: bool eventFilter(QObject *obj, QEvent *ev) override; @@ -28,7 +36,7 @@ protected: private: void resizeToParent(); - PaintFunction m_paint; + std::unique_ptr<Internal::OverlayWidgetPrivate> d; }; } // namespace Utils diff --git a/src/libs/utils/passworddialog.cpp b/src/libs/utils/passworddialog.cpp index 7958f7d6bd..94de5f7f0f 100644 --- a/src/libs/utils/passworddialog.cpp +++ b/src/libs/utils/passworddialog.cpp @@ -34,10 +34,9 @@ void ShowPasswordButton::paintEvent(QPaintEvent *e) QRect r(QPoint(), size()); if (m_containsMouse && isEnabled()) - StyleHelper::drawPanelBgRect(&p, r, creatorTheme()->color(Theme::FancyToolButtonHoverColor)); + StyleHelper::drawPanelBgRect(&p, r, creatorColor(Theme::FancyToolButtonHoverColor)); - QWindow *window = this->window()->windowHandle(); - QSize s = icon.actualSize(window, QSize(32, 16)); + QSize s = icon.actualSize(QSize(32, 16)); QPixmap px = icon.pixmap(s); QRect iRect(QPoint(), s); @@ -61,8 +60,7 @@ void ShowPasswordButton::leaveEvent(QEvent *e) QSize ShowPasswordButton::sizeHint() const { - QWindow *window = this->window()->windowHandle(); - QSize s = Utils::Icons::EYE_OPEN_TOOLBAR.icon().actualSize(window, QSize(32, 16)) + QSize(8, 8); + QSize s = Utils::Icons::EYE_OPEN_TOOLBAR.icon().actualSize(QSize(32, 16)) + QSize(8, 8); if (StyleHelper::toolbarStyle() == StyleHelper::ToolbarStyleRelaxed) s += QSize(5, 5); diff --git a/src/libs/utils/persistentsettings.cpp b/src/libs/utils/persistentsettings.cpp index 54a829004b..3702ecf960 100644 --- a/src/libs/utils/persistentsettings.cpp +++ b/src/libs/utils/persistentsettings.cpp @@ -106,13 +106,13 @@ const QString keyAttribute("key"); struct ParseValueStackEntry { - explicit ParseValueStackEntry(QVariant::Type t = QVariant::Invalid, const QString &k = {}) : type(t), key(k) {} + explicit ParseValueStackEntry(QMetaType::Type t = QMetaType::UnknownType, const QString &k = {}) : typeId(t), key(k) {} explicit ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k); QVariant value() const; void addChild(const QString &key, const QVariant &v); - QVariant::Type type; + QMetaType::Type typeId; QString key; QVariant simpleValue; QVariantList listValue; @@ -120,19 +120,19 @@ struct ParseValueStackEntry }; ParseValueStackEntry::ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k) - : type(aSimpleValue.type()), key(k), simpleValue(aSimpleValue) + : typeId(QMetaType::Type(aSimpleValue.typeId())), key(k), simpleValue(aSimpleValue) { QTC_ASSERT(simpleValue.isValid(), return); } QVariant ParseValueStackEntry::value() const { - switch (type) { - case QVariant::Invalid: + switch (typeId) { + case QMetaType::UnknownType: return QVariant(); - case QVariant::Map: + case QMetaType::QVariantMap: return QVariant(mapValue); - case QVariant::List: + case QMetaType::QVariantList: return QVariant(listValue); default: break; @@ -142,16 +142,16 @@ QVariant ParseValueStackEntry::value() const void ParseValueStackEntry::addChild(const QString &key, const QVariant &v) { - switch (type) { - case QVariant::Map: + switch (typeId) { + case QMetaType::QVariantMap: mapValue.insert(key, v); break; - case QVariant::List: + case QMetaType::QVariantList: listValue.push_back(v); break; default: qWarning() << "ParseValueStackEntry::Internal error adding " << key << v << " to " - << QVariant::typeToName(type) << value(); + << QMetaType(typeId).name() << value(); break; } } @@ -226,14 +226,14 @@ bool ParseContext::handleStartElement(QXmlStreamReader &r) const QXmlStreamAttributes attributes = r.attributes(); const QString key = attributes.hasAttribute(keyAttribute) ? attributes.value(keyAttribute).toString() : QString(); - m_valueStack.push_back(ParseValueStackEntry(QVariant::List, key)); + m_valueStack.push_back(ParseValueStackEntry(QMetaType::QVariantList, key)); return false; } if (name == valueMapElement) { const QXmlStreamAttributes attributes = r.attributes(); const QString key = attributes.hasAttribute(keyAttribute) ? attributes.value(keyAttribute).toString() : QString(); - m_valueStack.push_back(ParseValueStackEntry(QVariant::Map, key)); + m_valueStack.push_back(ParseValueStackEntry(QMetaType::QVariantMap, key)); return false; } return false; @@ -369,8 +369,8 @@ static void writeVariantValue(QXmlStreamWriter &w, const QVariant &variant, cons w.writeAttribute(typeAttribute, QLatin1String(variant.typeName())); if (!key.isEmpty()) w.writeAttribute(keyAttribute, xmlAttrFromKey(key)); - switch (variant.type()) { - case QVariant::Rect: + switch (variant.typeId()) { + case QMetaType::QRect: w.writeCharacters(rectangleToString(variant.toRect())); break; default: diff --git a/src/libs/utils/projectintropage.cpp b/src/libs/utils/projectintropage.cpp index 1f32cce6bc..2c8b43b66a 100644 --- a/src/libs/utils/projectintropage.cpp +++ b/src/libs/utils/projectintropage.cpp @@ -110,7 +110,8 @@ ProjectIntroPage::ProjectIntroPage(QWidget *parent) : using namespace Layouting; Form { - Tr::tr("Name:"), d->m_nameLineEdit, br, + Tr::tr("Name:"), d->m_nameLineEdit, + br, d->m_projectLabel, d->m_projectComboBox, br, Column { Space(12) }, br, Tr::tr("Create in:"), d->m_pathChooser, br, @@ -135,7 +136,7 @@ ProjectIntroPage::ProjectIntroPage(QWidget *parent) : connect(d->m_nameLineEdit, &FancyLineEdit::validReturnPressed, this, &ProjectIntroPage::slotActivated); connect(d->m_projectComboBox, &QComboBox::currentIndexChanged, - this, &ProjectIntroPage::slotChanged); + this, &ProjectIntroPage::onCurrentProjectIndexChanged); setProperty(SHORT_TITLE_PROPERTY, Tr::tr("Location")); registerFieldWithName(QLatin1String("Path"), d->m_pathChooser, "path", SIGNAL(textChanged(QString))); @@ -192,15 +193,10 @@ bool ProjectIntroPage::isComplete() const bool ProjectIntroPage::validate() { - if (d->m_forceSubProject) { - int index = d->m_projectComboBox->currentIndex(); - if (index == 0) - return false; - d->m_pathChooser->setFilePath(d->m_projectDirectories.at(index)); - } // Validate and display status if (!d->m_pathChooser->isValid()) { - displayStatusMessage(InfoLabel::Error, d->m_pathChooser->errorMessage()); + if (const QString msg = d->m_pathChooser->errorMessage(); !msg.isEmpty()) + displayStatusMessage(InfoLabel::Error, msg); return false; } @@ -259,6 +255,25 @@ void ProjectIntroPage::slotActivated() emit activated(); } +void ProjectIntroPage::onCurrentProjectIndexChanged(int index) +{ + if (d->m_forceSubProject) { + const int available = d->m_projectDirectories.size(); + if (available == 0) + return; + QTC_ASSERT(index < available, return); + if (index < 0) + return; + + const FilePath current = d->m_projectDirectories.at(index); + const FilePath visible = d->m_pathChooser->filePath(); + if (visible != current && !visible.isChildOf(current)) + d->m_pathChooser->setFilePath(current); + + fieldsUpdated(); + } +} + bool ProjectIntroPage::forceSubProject() const { return d->m_forceSubProject; diff --git a/src/libs/utils/projectintropage.h b/src/libs/utils/projectintropage.h index 48b2f6b036..562b5b06e4 100644 --- a/src/libs/utils/projectintropage.h +++ b/src/libs/utils/projectintropage.h @@ -58,6 +58,7 @@ public slots: private: void slotChanged(); void slotActivated(); + void onCurrentProjectIndexChanged(int index); bool validate(); void displayStatusMessage(InfoLabel::InfoType t, const QString &); diff --git a/src/libs/utils/qtcolorbutton.cpp b/src/libs/utils/qtcolorbutton.cpp index 6a8537bd5f..5be9c9c069 100644 --- a/src/libs/utils/qtcolorbutton.cpp +++ b/src/libs/utils/qtcolorbutton.cpp @@ -171,7 +171,7 @@ void QtColorButton::paintEvent(QPaintEvent *event) constexpr int size = 11; const qreal horPadding = (width() - size) / 2.0; const qreal verPadding = (height() - size) / 2.0; - const QPen pen(creatorTheme()->color(overlayColor), 2); + const QPen pen(creatorColor(overlayColor), 2); p.save(); p.setOpacity(overlayOpacity); @@ -202,7 +202,7 @@ void QtColorButton::paintEvent(QPaintEvent *event) p.setPen(pen); p.setCompositionMode(QPainter::CompositionMode_Difference); } else { - p.setPen(creatorTheme()->color(overlayColor)); + p.setPen(creatorColor(overlayColor)); p.setOpacity(overlayOpacity); } p.drawRect(rect().adjusted(0, 0, -1, -1)); diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index b399d86b36..dca106a7d5 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -769,6 +769,7 @@ private: std::unique_ptr<ProcessInterfaceHandler> m_processHandler; mutable QMutex m_mutex; QList<ProcessInterfaceSignal *> m_signals; + Guard m_guard; }; class ProcessPrivate : public QObject @@ -961,6 +962,10 @@ GeneralProcessBlockingImpl::GeneralProcessBlockingImpl(ProcessPrivate *parent) bool GeneralProcessBlockingImpl::waitForSignal(ProcessSignalType newSignal, QDeadlineTimer timeout) { + QTC_ASSERT(!m_guard.isLocked(), qWarning("Process::waitForSignal() called recursively. " + "The call is being ignored."); return false); + GuardLocker locker(m_guard); + m_processHandler->setParent(nullptr); QThread thread; @@ -1355,7 +1360,7 @@ QString Process::toStandaloneCommandLine() const parts.append("/usr/bin/env"); if (!d->m_setup.m_workingDirectory.isEmpty()) { parts.append("-C"); - d->m_setup.m_workingDirectory.path(); + parts.append(d->m_setup.m_workingDirectory.path()); } parts.append("-i"); if (d->m_setup.m_environment.hasChanges()) { diff --git a/src/libs/utils/reloadpromptutils.cpp b/src/libs/utils/reloadpromptutils.cpp index 5257138245..8a07592d3f 100644 --- a/src/libs/utils/reloadpromptutils.cpp +++ b/src/libs/utils/reloadpromptutils.cpp @@ -89,12 +89,10 @@ QTCREATOR_UTILS_EXPORT FileDeletedPromptAnswer "Do you want to save it under a different name, or close " "the editor?").arg(QDir::toNativeSeparators(fileName)); QMessageBox box(QMessageBox::Question, title, msg, QMessageBox::NoButton, parent); - QPushButton *close = - box.addButton(Tr::tr("&Close"), QMessageBox::RejectRole); + QPushButton *saveas = box.addButton(Tr::tr("Save &as..."), QMessageBox::ActionRole); + QPushButton *close = box.addButton(Tr::tr("&Close"), QMessageBox::RejectRole); QPushButton *closeAll = box.addButton(Tr::tr("C&lose All"), QMessageBox::RejectRole); - QPushButton *saveas = - box.addButton(Tr::tr("Save &as..."), QMessageBox::ActionRole); QPushButton *save = box.addButton(Tr::tr("&Save"), QMessageBox::AcceptRole); box.setDefaultButton(saveas); diff --git a/src/libs/utils/smallstring.h b/src/libs/utils/smallstring.h index c522a6cae9..e8c3d74b92 100644 --- a/src/libs/utils/smallstring.h +++ b/src/libs/utils/smallstring.h @@ -93,7 +93,7 @@ public: static_cast<std::size_t>(std::distance(begin, end))} {} - template<typename Type, typename = std::enable_if_t<std::is_pointer<Type>::value>> + template<typename Type, typename std::enable_if_t<std::is_pointer<Type>::value, bool> = true> BasicSmallString(Type characterPointer) noexcept : BasicSmallString(characterPointer, std::char_traits<char>::length(characterPointer)) { @@ -118,7 +118,7 @@ public: template<typename BeginIterator, typename EndIterator, - typename = std::enable_if_t<std::is_same<BeginIterator, EndIterator>::value>> + typename std::enable_if_t<std::is_same<BeginIterator, EndIterator>::value, bool> = true> BasicSmallString(BeginIterator begin, EndIterator end) noexcept : BasicSmallString(&(*begin), size_type(end - begin)) {} @@ -354,6 +354,14 @@ public: return false; } + bool startsWith(QStringView subStringToSearch) const noexcept + { + if (size() >= Utils::usize(subStringToSearch)) + return subStringToSearch == QLatin1StringView{data(), subStringToSearch.size()}; + + return false; + } + bool startsWith(char characterToSearch) const noexcept { return data()[0] == characterToSearch; @@ -423,13 +431,55 @@ public: size_type oldSize = size(); size_type newSize = oldSize + string.size(); - reserve(optimalCapacity(newSize)); + if (fitsNotInCapacity(newSize)) + reserve(optimalCapacity(newSize)); + std::char_traits<char>::copy(std::next(data(), static_cast<std::ptrdiff_t>(oldSize)), string.data(), string.size()); setSize(newSize); } + void append(char character) noexcept + { + size_type oldSize = size(); + size_type newSize = oldSize + 1; + + if (fitsNotInCapacity(newSize)) + reserve(optimalCapacity(newSize)); + + auto current = std::next(data(), static_cast<std::ptrdiff_t>(oldSize)); + *current = character; + setSize(newSize); + } + + template<typename Type, typename std::enable_if_t<std::is_arithmetic_v<Type>, bool> = true> + void append(Type number) + { +#if defined(__cpp_lib_to_chars) && (__cpp_lib_to_chars >= 201611L) + // 2 bytes for the sign and because digits10 returns the floor + char buffer[std::numeric_limits<Type>::digits10 + 2]; + auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); + auto endOfConversionString = result.ptr; + + append({buffer, endOfConversionString}); +#else + if constexpr (std::is_floating_point_v<Type>) { + QLocale locale{QLocale::Language::C}; + append(locale.toString(number)); + return; + } else { + // 2 bytes for the sign and because digits10 returns the floor + char buffer[std::numeric_limits<Type>::digits10 + 2]; + auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); + auto endOfConversionString = result.ptr; + + append({buffer, endOfConversionString}); + } + +#endif + } + void append(QStringView string) noexcept { QStringEncoder encoder{QStringEncoder::Utf8}; @@ -469,6 +519,13 @@ public: return *this; } + BasicSmallString &operator+=(char character) noexcept + { + append(character); + + return *this; + } + BasicSmallString &operator+=(QStringView string) noexcept { append(string); @@ -476,6 +533,14 @@ public: return *this; } + template<typename Type, typename std::enable_if_t<std::is_arithmetic_v<Type>, bool> = true> + BasicSmallString &operator+=(Type number) noexcept + { + append(number); + + return *this; + } + BasicSmallString &operator+=(std::initializer_list<SmallStringView> list) noexcept { appendInitializerList(list, size()); @@ -580,37 +645,12 @@ public: return joinedString; } - static - BasicSmallString number(int number) + template<typename Type, typename std::enable_if_t<std::is_arithmetic_v<Type>, bool> = true> + static BasicSmallString number(Type number) { - // 2 bytes for the sign and because digits10 returns the floor - char buffer[std::numeric_limits<int>::digits10 + 2]; - auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); - auto endOfConversionString = result.ptr; - return BasicSmallString(buffer, endOfConversionString); - } - - static BasicSmallString number(long long int number) noexcept - { - // 2 bytes for the sign and because digits10 returns the floor - char buffer[std::numeric_limits<long long int>::digits10 + 2]; - auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); - auto endOfConversionString = result.ptr; - return BasicSmallString(buffer, endOfConversionString); - } - - static BasicSmallString number(double number) noexcept - { -#if defined(__cpp_lib_to_chars) && (__cpp_lib_to_chars >= 201611L) - // 2 bytes for the sign and because digits10 returns the floor - char buffer[std::numeric_limits<double>::digits10 + 2]; - auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); - auto endOfConversionString = result.ptr; - return BasicSmallString(buffer, endOfConversionString); -#else - QLocale locale{QLocale::Language::C}; - return BasicSmallString{locale.toString(number)}; -#endif + BasicSmallString string; + string.append(number); + return string; } char &operator[](std::size_t index) noexcept { return *(data() + index); } @@ -655,7 +695,6 @@ public: friend BasicSmallString operator+(const BasicSmallString &first, const char (&second)[ArraySize]) noexcept { - return operator+(first, SmallStringView(second)); } @@ -687,8 +726,10 @@ unittest_public: bool fitsNotInCapacity(size_type capacity) const noexcept { - return (isShortString() && capacity > shortStringCapacity()) - || (!isShortString() && capacity > m_data.reference.capacity); + if (isShortString()) + return capacity > shortStringCapacity(); + + return capacity > m_data.reference.capacity; } static size_type optimalHeapCapacity(const size_type size) noexcept diff --git a/src/libs/utils/store.cpp b/src/libs/utils/store.cpp index fa8d4232c0..8c0e2a6046 100644 --- a/src/libs/utils/store.cpp +++ b/src/libs/utils/store.cpp @@ -50,10 +50,10 @@ static QVariantList mapListFromStoreList(const QVariantList &storeList); QVariant storeEntryFromMapEntry(const QVariant &mapEntry) { - if (mapEntry.type() == QVariant::Map) + if (mapEntry.typeId() == QMetaType::QVariantMap) return QVariant::fromValue(storeFromMap(mapEntry.toMap())); - if (mapEntry.type() == QVariant::List) + if (mapEntry.typeId() == QMetaType::QVariantList) return QVariant::fromValue(storeListFromMapList(mapEntry.toList())); return mapEntry; @@ -64,7 +64,7 @@ QVariant mapEntryFromStoreEntry(const QVariant &storeEntry) if (storeEntry.metaType() == QMetaType::fromType<Store>()) return QVariant::fromValue(mapFromStore(storeEntry.value<Store>())); - if (storeEntry.type() == QVariant::List) + if (storeEntry.typeId() == QMetaType::QVariantList) return QVariant::fromValue(mapListFromStoreList(storeEntry.toList())); return storeEntry; diff --git a/src/libs/utils/stringutils.cpp b/src/libs/utils/stringutils.cpp index d8de512b32..d21c7cb9bc 100644 --- a/src/libs/utils/stringutils.cpp +++ b/src/libs/utils/stringutils.cpp @@ -637,7 +637,7 @@ void MarkdownHighlighter::highlightBlock(const QString &text) image.fill(QColor(0, 0, 0, 0).rgba()); image.setPixel(0, height - 1, - Utils::creatorTheme()->color(Theme::TextColorDisabled).rgba()); + Utils::creatorColor(Theme::TextColorDisabled).rgba()); h2Brush = QBrush(image); } @@ -672,4 +672,10 @@ void MarkdownHighlighter::highlightBlock(const QString &text) } } +QString ansiColoredText(const QString &text, const QColor &color) +{ + static const QString formatString("\033[38;2;%1;%2;%3m%4\033[0m"); + return formatString.arg(color.red()).arg(color.green()).arg(color.blue()).arg(text); +} + } // namespace Utils diff --git a/src/libs/utils/stringutils.h b/src/libs/utils/stringutils.h index 3e729e672c..cab6d774f3 100644 --- a/src/libs/utils/stringutils.h +++ b/src/libs/utils/stringutils.h @@ -138,4 +138,6 @@ private: QBrush m_codeBgBrush; }; +QTCREATOR_UTILS_EXPORT QString ansiColoredText(const QString &text, const QColor &color); + } // namespace Utils diff --git a/src/libs/utils/stylehelper.cpp b/src/libs/utils/stylehelper.cpp index 69b5e39556..080bb8a382 100644 --- a/src/libs/utils/stylehelper.cpp +++ b/src/libs/utils/stylehelper.cpp @@ -107,7 +107,7 @@ QColor StyleHelper::notTooBrightHighlightColor() QPalette StyleHelper::sidebarFontPalette(const QPalette &original) { QPalette palette = original; - const QColor textColor = creatorTheme()->color(Theme::ProgressBarTitleColor); + const QColor textColor = creatorColor(Theme::ProgressBarTitleColor); palette.setColor(QPalette::WindowText, textColor); palette.setColor(QPalette::Text, textColor); return palette; @@ -137,7 +137,7 @@ QColor StyleHelper::requestedBaseColor() QColor StyleHelper::toolbarBaseColor(bool lightColored) { if (creatorTheme()->flag(Theme::QDSTheme)) - return creatorTheme()->color(Utils::Theme::DStoolbarBackground); + return creatorColor(Utils::Theme::DStoolbarBackground); else return StyleHelper::baseColor(lightColored); } @@ -194,7 +194,7 @@ void StyleHelper::setBaseColor(const QColor &newcolor) { s_requestedBaseColor = newcolor; - const QColor themeBaseColor = creatorTheme()->color(Theme::PanelStatusBarBackgroundColor); + const QColor themeBaseColor = creatorColor(Theme::PanelStatusBarBackgroundColor); const QColor defaultBaseColor = QColor(DEFAULT_BASE_COLOR); QColor color; @@ -366,11 +366,11 @@ void StyleHelper::drawArrow(QStyle::PrimitiveElement element, QPainter *painter, }; if (!enabled) { - drawCommonStyleArrow(image.rect(), creatorTheme()->color(Theme::IconsDisabledColor)); + drawCommonStyleArrow(image.rect(), creatorColor(Theme::IconsDisabledColor)); } else { if (creatorTheme()->flag(Theme::ToolBarIconShadow)) drawCommonStyleArrow(image.rect().translated(0, devicePixelRatio), toolBarDropShadowColor()); - drawCommonStyleArrow(image.rect(), creatorTheme()->color(Theme::IconsBaseColor)); + drawCommonStyleArrow(image.rect(), creatorColor(Theme::IconsBaseColor)); } painter.end(); pixmap = QPixmap::fromImage(image); @@ -463,9 +463,9 @@ void StyleHelper::drawMinimalArrow(QStyle::PrimitiveElement element, QPainter *p if (enabled) { if (creatorTheme()->flag(Theme::ToolBarIconShadow)) drawArrow(image.rect().translated(0, devicePixelRatio), toolBarDropShadowColor()); - drawArrow(image.rect(), creatorTheme()->color(Theme::IconsBaseColor)); + drawArrow(image.rect(), creatorColor(Theme::IconsBaseColor)); } else { - drawArrow(image.rect(), creatorTheme()->color(Theme::IconsDisabledColor)); + drawArrow(image.rect(), creatorColor(Theme::IconsDisabledColor)); } painter.end(); pixmap = QPixmap::fromImage(image); @@ -551,8 +551,7 @@ void StyleHelper::drawIconWithShadow(const QIcon &icon, const QRect &rect, // return a high-dpi pixmap, which will in that case have a devicePixelRatio // different than 1. The shadow drawing caluculations are done in device // pixels. - QWindow *window = dynamic_cast<QWidget*>(p->device())->window()->windowHandle(); - QPixmap px = icon.pixmap(window, rect.size(), iconMode); + QPixmap px = icon.pixmap(rect.size(), devicePixelRatio, iconMode); int radius = int(dipRadius * devicePixelRatio); QPoint offset = dipOffset * devicePixelRatio; cache = QPixmap(px.size() + QSize(radius * 2, radius * 2)); @@ -563,7 +562,7 @@ void StyleHelper::drawIconWithShadow(const QIcon &icon, const QRect &rect, const bool hasDisabledState = icon.availableSizes().count() == icon.availableSizes(QIcon::Disabled).count(); if (!hasDisabledState) - px = disabledSideBarIcon(icon.pixmap(window, rect.size())); + px = disabledSideBarIcon(icon.pixmap(rect.size(), devicePixelRatio)); } else if (creatorTheme()->flag(Theme::ToolBarIconShadow)) { // Draw shadow QImage tmp(px.size() + QSize(radius * 2, radius * 2 + 1), QImage::Format_ARGB32_Premultiplied); @@ -716,12 +715,7 @@ Qt::HighDpiScaleFactorRoundingPolicy StyleHelper::defaultHighDpiScaleFactorRound QIcon StyleHelper::getIconFromIconFont(const QString &fontName, const QList<IconFontHelper> ¶meters) { - QFontDatabase a; - - QTC_ASSERT(a.hasFamily(fontName), {}); - - if (!a.hasFamily(fontName)) - return {}; + QTC_ASSERT(QFontDatabase::hasFamily(fontName), {}); QIcon icon; @@ -751,38 +745,31 @@ QIcon StyleHelper::getIconFromIconFont(const QString &fontName, const QList<Icon QIcon StyleHelper::getIconFromIconFont(const QString &fontName, const QString &iconSymbol, int fontSize, int iconSize, QColor color) { - QFontDatabase a; - - QTC_ASSERT(a.hasFamily(fontName), {}); - - if (a.hasFamily(fontName)) { - - QIcon icon; - QSize size(iconSize, iconSize); + QTC_ASSERT(QFontDatabase::hasFamily(fontName), {}); - const int maxDpr = qRound(qApp->devicePixelRatio()); - for (int dpr = 1; dpr <= maxDpr; dpr++) { - QPixmap pixmap(size * dpr); - pixmap.setDevicePixelRatio(dpr); - pixmap.fill(Qt::transparent); + QIcon icon; + QSize size(iconSize, iconSize); - QFont font(fontName); - font.setPixelSize(fontSize); + const int maxDpr = qRound(qApp->devicePixelRatio()); + for (int dpr = 1; dpr <= maxDpr; dpr++) { + QPixmap pixmap(size * dpr); + pixmap.setDevicePixelRatio(dpr); + pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - painter.save(); - painter.setPen(color); - painter.setFont(font); - painter.drawText(QRectF(QPoint(0, 0), size), Qt::AlignCenter, iconSymbol); - painter.restore(); + QFont font(fontName); + font.setPixelSize(fontSize); - icon.addPixmap(pixmap); - } + QPainter painter(&pixmap); + painter.save(); + painter.setPen(color); + painter.setFont(font); + painter.drawText(QRectF(QPoint(0, 0), size), Qt::AlignCenter, iconSymbol); + painter.restore(); - return icon; + icon.addPixmap(pixmap); } - return {}; + return icon; } QIcon StyleHelper::getIconFromIconFont(const QString &fontName, const QString &iconSymbol, int fontSize, int iconSize) @@ -794,55 +781,47 @@ QIcon StyleHelper::getIconFromIconFont(const QString &fontName, const QString &i QIcon StyleHelper::getCursorFromIconFont(const QString &fontName, const QString &cursorFill, const QString &cursorOutline, int fontSize, int iconSize) { - QFontDatabase a; - - QTC_ASSERT(a.hasFamily(fontName), {}); + QTC_ASSERT(QFontDatabase::hasFamily(fontName), {}); const QColor outlineColor = Qt::black; const QColor fillColor = Qt::white; - if (a.hasFamily(fontName)) { - - QIcon icon; - QSize size(iconSize, iconSize); - - const int maxDpr = qRound(qApp->devicePixelRatio()); - for (int dpr = 1; dpr <= maxDpr; dpr++) { - QPixmap pixmap(size * dpr); - pixmap.setDevicePixelRatio(dpr); - pixmap.fill(Qt::transparent); + QIcon icon; + QSize size(iconSize, iconSize); - QFont font(fontName); - font.setPixelSize(fontSize); + const int maxDpr = qRound(qApp->devicePixelRatio()); + for (int dpr = 1; dpr <= maxDpr; dpr++) { + QPixmap pixmap(size * dpr); + pixmap.setDevicePixelRatio(dpr); + pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - painter.save(); - painter.setRenderHint(QPainter::Antialiasing, true); - painter.setRenderHint(QPainter::TextAntialiasing, true); - painter.setRenderHint(QPainter::LosslessImageRendering, true); - painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + QFont font(fontName); + font.setPixelSize(fontSize); - painter.setFont(font); - painter.setPen(outlineColor); - painter.drawText(QRectF(QPointF(0.0, 0.0), size), - Qt::AlignCenter, cursorOutline); + QPainter painter(&pixmap); + painter.save(); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setRenderHint(QPainter::TextAntialiasing, true); + painter.setRenderHint(QPainter::LosslessImageRendering, true); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); - painter.setPen(fillColor); - painter.drawText(QRectF(QPointF(0.0, 0.0), size), - Qt::AlignCenter, cursorFill); + painter.setFont(font); + painter.setPen(outlineColor); + painter.drawText(QRectF(QPointF(0.0, 0.0), size), + Qt::AlignCenter, cursorOutline); - painter.restore(); + painter.setPen(fillColor); + painter.drawText(QRectF(QPointF(0.0, 0.0), size), + Qt::AlignCenter, cursorFill); - icon.addPixmap(pixmap); - } + painter.restore(); - return icon; + icon.addPixmap(pixmap); } - return {}; + return icon; } - QString StyleHelper::dpiSpecificImageFile(const QString &fileName) { // See QIcon::addFile() @@ -970,6 +949,8 @@ static const UiFontMetrics& uiFontMetrics(StyleHelper::UiElement element) {StyleHelper::UiElementBody2, {12, 20, QFont::Light}}, {StyleHelper::UiElementButtonMedium, {12, 16, QFont::Bold}}, {StyleHelper::UiElementButtonSmall, {10, 12, QFont::Bold}}, + {StyleHelper::UiElementLabelMedium, {12, 16, QFont::DemiBold}}, + {StyleHelper::UiElementLabelSmall, {10, 12, QFont::DemiBold}}, {StyleHelper::UiElementCaptionStrong, {10, 12, QFont::DemiBold}}, {StyleHelper::UiElementCaption, {10, 12, QFont::Normal}}, {StyleHelper::UiElementIconStandard, {12, 16, QFont::Medium}}, @@ -1010,7 +991,13 @@ QFont StyleHelper::uiFont(UiElement element) const qreal qrealPointSize = metrics.pixelSize * pixelsToPointSizeFactor; font.setPointSizeF(qrealPointSize); - font.setWeight(metrics.weight); + // Intermediate font weights can produce blurry rendering and are harder to read. + // For "non-retina" screens, apply the weight only for some fonts. + static const bool isHighDpi = qApp->devicePixelRatio() >= 2; + const bool setWeight = isHighDpi || element == UiElementCaptionStrong + || element <= UiElementH4; + if (setWeight) + font.setWeight(metrics.weight); return font; } diff --git a/src/libs/utils/stylehelper.h b/src/libs/utils/stylehelper.h index 7d434925e6..ecc2eea2b9 100644 --- a/src/libs/utils/stylehelper.h +++ b/src/libs/utils/stylehelper.h @@ -79,6 +79,8 @@ enum ToolbarStyle { }; constexpr ToolbarStyle defaultToolbarStyle = ToolbarStyleCompact; +// Keep in sync with: +// SyleHelper::uiFontMetrics, ICore::uiConfigInformation, tst_manual_widgets_uifonts::main enum UiElement { UiElementH1, UiElementH2, @@ -91,6 +93,8 @@ enum UiElement { UiElementBody2, UiElementButtonMedium, UiElementButtonSmall, + UiElementLabelMedium, + UiElementLabelSmall, UiElementCaptionStrong, UiElementCaption, UiElementIconStandard, diff --git a/src/libs/utils/terminalinterface.cpp b/src/libs/utils/terminalinterface.cpp index 0d5a3be020..6f7bb11f81 100644 --- a/src/libs/utils/terminalinterface.cpp +++ b/src/libs/utils/terminalinterface.cpp @@ -334,22 +334,12 @@ void TerminalInterface::start() Environment finalEnv = m_setup.m_environment; - if (HostOsInfo::isWindowsHost()) { - if (!finalEnv.hasKey("PATH")) { - const QString path = qtcEnvironmentVariable("PATH"); - if (!path.isEmpty()) - finalEnv.set("PATH", path); - } - if (!finalEnv.hasKey("SystemRoot")) { - const QString systemRoot = qtcEnvironmentVariable("SystemRoot"); - if (!systemRoot.isEmpty()) - finalEnv.set("SystemRoot", systemRoot); - } - } else if (HostOsInfo::isMacHost()) { + if (HostOsInfo::isMacHost()) finalEnv.set("TERM", "xterm-256color"); - } if (finalEnv.hasChanges()) { + finalEnv = finalEnv.appliedToEnvironment(Environment::systemEnvironment()); + d->envListFile = std::make_unique<QTemporaryFile>(this); if (!d->envListFile->open()) { cleanupAfterStartFailure(msgCannotCreateTempFile(d->envListFile->errorString())); @@ -402,7 +392,7 @@ void TerminalInterface::start() m_setup.m_commandLine.executable().fileName()); if (m_setup.m_runAsRoot && !HostOsInfo::isWindowsHost()) { - CommandLine rootCommand("sudo", {}); + CommandLine rootCommand("sudo"); rootCommand.addCommandLineAsArgs(cmd); stubSetupData.m_commandLine = rootCommand; } else { diff --git a/src/libs/utils/theme/theme.cpp b/src/libs/utils/theme/theme.cpp index 9b24078f86..ef20ddddb7 100644 --- a/src/libs/utils/theme/theme.cpp +++ b/src/libs/utils/theme/theme.cpp @@ -18,6 +18,7 @@ namespace Utils { static Theme *m_creatorTheme = nullptr; +static std::optional<QPalette> m_initialPalette; ThemePrivate::ThemePrivate() { @@ -37,9 +38,31 @@ Theme *proxyTheme() return new Theme(m_creatorTheme); } +// Convenience +QColor creatorColor(Theme::Color role) +{ + return m_creatorTheme->color(role); +} + +static bool paletteIsDark(const QPalette &pal) +{ + return pal.color(QPalette::Window).lightnessF() < pal.color(QPalette::WindowText).lightnessF(); +} + +static bool isOverridingPalette(const Theme *theme) +{ + if (theme->flag(Theme::DerivePaletteFromTheme)) + return true; + if (theme->flag(Theme::DerivePaletteFromThemeIfNeeded) + && paletteIsDark(Theme::initialPalette()) != theme->flag(Theme::DarkUserInterface)) { + return true; + } + return false; +} + void setThemeApplicationPalette() { - if (m_creatorTheme && m_creatorTheme->flag(Theme::ApplyThemePaletteGlobally)) + if (m_creatorTheme && isOverridingPalette(m_creatorTheme)) QApplication::setPalette(m_creatorTheme->palette()); } @@ -185,7 +208,7 @@ void Theme::setDisplayName(const QString &name) void Theme::readSettingsInternal(QSettings &settings) { - const QStringList includes = settings.value("Includes").toString().split(",", Qt::SkipEmptyParts); + const QStringList includes = settings.value("Includes").toStringList(); for (const QString &include : includes) { FilePath path = FilePath::fromString(d->fileName); @@ -311,7 +334,13 @@ bool Theme::systemUsesDarkMode() if (HostOsInfo::isMacHost()) return macOSSystemIsDark(); - return false; + // Avoid enforcing the initial palette. + // The initial palette must be set after setting the macOS appearance in setInitialPalette, + // but systemUsesDarkMode is used to determine the default theme, which is in turn required + // for the setInitialPalette call + if (m_initialPalette) + return paletteIsDark(*m_initialPalette); + return paletteIsDark(QApplication::palette()); } // If you copy QPalette, default values stay at default, even if that default is different @@ -347,14 +376,17 @@ void Theme::setHelpMenu(QMenu *menu) QPalette Theme::initialPalette() { - static QPalette palette = copyPalette(QApplication::palette()); - return palette; + if (!m_initialPalette) { + m_initialPalette = copyPalette(QApplication::palette()); + QApplication::setPalette(*m_initialPalette); + } + return *m_initialPalette; } QPalette Theme::palette() const { QPalette pal = initialPalette(); - if (!flag(DerivePaletteFromTheme)) + if (!isOverridingPalette(this)) return pal; const static struct { diff --git a/src/libs/utils/theme/theme.h b/src/libs/utils/theme/theme.h index 6b3bf97d45..8d7e922e79 100644 --- a/src/libs/utils/theme/theme.h +++ b/src/libs/utils/theme/theme.h @@ -247,6 +247,10 @@ public: Token_Notification_Success, Token_Notification_Neutral, Token_Notification_Danger, + Token_Gradient01_Start, + Token_Gradient01_End, + Token_Gradient02_Start, + Token_Gradient02_End, /* Timeline Library */ Timeline_TextColor, @@ -527,7 +531,7 @@ public: DrawToolBarBorders, ComboBoxDrawTextShadow, DerivePaletteFromTheme, - ApplyThemePaletteGlobally, + DerivePaletteFromThemeIfNeeded, FlatToolBars, FlatSideBarIcons, FlatProjectsMode, @@ -577,5 +581,6 @@ private: QTCREATOR_UTILS_EXPORT Theme *creatorTheme(); QTCREATOR_UTILS_EXPORT Theme *proxyTheme(); +QTCREATOR_UTILS_EXPORT QColor creatorColor(Theme::Color role); } // namespace Utils diff --git a/src/libs/utils/threadutils.cpp b/src/libs/utils/threadutils.cpp index 344779dc93..cfb18059a6 100644 --- a/src/libs/utils/threadutils.cpp +++ b/src/libs/utils/threadutils.cpp @@ -10,7 +10,12 @@ namespace Utils { bool isMainThread() { +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + // TODO: Remove threadutils.h and replace all usages with: + return QThread::isMainThread(); +#else return QThread::currentThread() == qApp->thread(); +#endif } } // namespace Utils diff --git a/src/libs/utils/tooltip/tooltip.cpp b/src/libs/utils/tooltip/tooltip.cpp index 93f4c1d2ff..da11540c08 100644 --- a/src/libs/utils/tooltip/tooltip.cpp +++ b/src/libs/utils/tooltip/tooltip.cpp @@ -288,9 +288,11 @@ void ToolTip::hideTipWithDelay() void ToolTip::hideTipImmediately() { if (m_tip) { - m_tip->close(); - m_tip->deleteLater(); - m_tip = nullptr; + TipLabel *tip = m_tip.data(); + m_tip.clear(); + + tip->close(); + tip->deleteLater(); } m_showTimer.stop(); m_hideDelayTimer.stop(); diff --git a/src/libs/utils/treemodel.cpp b/src/libs/utils/treemodel.cpp index 4649bb43c8..edddb2d484 100644 --- a/src/libs/utils/treemodel.cpp +++ b/src/libs/utils/treemodel.cpp @@ -459,13 +459,13 @@ void ModelTest::data() // General Purpose roles that should return a QString QVariant variant = model->data(model->index(0, 0), Qt::ToolTipRole); if (variant.isValid()) - Q_ASSERT(variant.canConvert(QVariant::String)); + Q_ASSERT(variant.canConvert(QMetaType::QString)); variant = model->data(model->index(0, 0), Qt::StatusTipRole); if (variant.isValid()) - Q_ASSERT(variant.canConvert(QVariant::String)); + Q_ASSERT(variant.canConvert(QMetaType::QString)); variant = model->data(model->index(0, 0), Qt::WhatsThisRole); if (variant.isValid()) - Q_ASSERT(variant.canConvert(QVariant::String)); + Q_ASSERT(variant.canConvert(QMetaType::QString)); // General Purpose roles that should return a QSize variant = model->data(model->index(0, 0), Qt::SizeHintRole); diff --git a/src/libs/utils/unarchiver.cpp b/src/libs/utils/unarchiver.cpp index 8436565425..d9092e4ca9 100644 --- a/src/libs/utils/unarchiver.cpp +++ b/src/libs/utils/unarchiver.cpp @@ -43,6 +43,7 @@ static const QList<Tool> &sTools() { static QList<Tool> tools; if (tools.isEmpty()) { + // clang-format off if (HostOsInfo::isWindowsHost()) { tools << Tool{{"powershell", "-command Expand-Archive -Force '%{src}' '%{dest}'", CommandLine::Raw}, {"application/zip"}, @@ -74,6 +75,10 @@ static const QList<Tool> &sTools() tools << Tool{{"cmake", {"-E", "tar", "xvjf", "%{src}"}}, {"application/x-bzip-compressed-tar"}, additionalCMakeDirs}; + // Keep this at the end so its only used as last resort. Otherwise it might be used for + // .tar.gz files. + tools << Tool{{"gzip", {"-d", "%{src}", "-c"}}, {"application/gzip"}, {}}; + // clang-format on } return tools; } @@ -147,6 +152,45 @@ void Unarchiver::start() m_sourceAndCommand->m_sourceFile, m_destDir); m_destDir.ensureWritableDir(); + if (command.executable().fileName() == "gzip") { + std::shared_ptr<QFile> outputFile = std::make_shared<QFile>( + (m_destDir / m_gzipFileDestName).toFSPathString()); + + if (!outputFile->open(QIODevice::WriteOnly)) { + emit outputReceived(Tr::tr("Failed to open output file.")); + emit done(DoneResult::Error); + return; + } + + m_process.reset(new Process); + QObject::connect(m_process.get(), &Process::readyReadStandardOutput, this, [this, outputFile] { + const QByteArray data = m_process->readAllRawStandardOutput(); + if (outputFile->write(data) != data.size()) { + emit outputReceived(Tr::tr("Failed to write output file.")); + emit done(DoneResult::Error); + } + }); + QObject::connect(m_process.get(), &Process::readyReadStandardError, this, [this] { + emit outputReceived(m_process->readAllStandardError()); + }); + QObject::connect(m_process.get(), &Process::done, this, [outputFile, this] { + outputFile->close(); + const bool success = m_process->result() == ProcessResult::FinishedWithSuccess; + if (!success) { + outputFile->remove(); + emit outputReceived(Tr::tr("Command failed.")); + } + emit done(toDoneResult(success)); + }); + emit outputReceived( + Tr::tr("Running %1\nin \"%2\".\n\n", "Running <cmd> in <workingdirectory>") + .arg(command.toUserOutput(), m_destDir.toUserOutput())); + m_process->setCommand(command); + m_process->setWorkingDirectory(m_destDir); + m_process->start(); + return; + } + m_process.reset(new Process); m_process->setProcessChannelMode(QProcess::MergedChannels); QObject::connect(m_process.get(), &Process::readyReadStandardOutput, this, [this] { diff --git a/src/libs/utils/unarchiver.h b/src/libs/utils/unarchiver.h index 61818318bb..23fcad07a8 100644 --- a/src/libs/utils/unarchiver.h +++ b/src/libs/utils/unarchiver.h @@ -32,7 +32,10 @@ public: void setSourceAndCommand(const SourceAndCommand &data) { m_sourceAndCommand = data; } void setDestDir(const FilePath &destDir) { m_destDir = destDir; } - + void setGZipFileDestName(const QString &gzipFileDestName) + { + m_gzipFileDestName = gzipFileDestName; + } void start(); signals: @@ -43,6 +46,7 @@ private: std::optional<SourceAndCommand> m_sourceAndCommand; FilePath m_destDir; std::unique_ptr<Process> m_process; + QString m_gzipFileDestName; }; class QTCREATOR_UTILS_EXPORT UnarchiverTaskAdapter : public Tasking::TaskAdapter<Unarchiver> diff --git a/src/libs/utils/utility.h b/src/libs/utils/utility.h new file mode 100644 index 0000000000..27de88d339 --- /dev/null +++ b/src/libs/utils/utility.h @@ -0,0 +1,14 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +namespace Utils { + +template<typename Enumeration> +[[nodiscard]] constexpr std::underlying_type_t<Enumeration> to_underlying(Enumeration enumeration) noexcept +{ + return static_cast<std::underlying_type_t<Enumeration>>(enumeration); +} + +} // namespace Utils diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 211d184f73..afc0f202aa 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -185,6 +185,8 @@ QtcLibrary { "link.h", "listmodel.h", "listutils.h", + "lua.cpp", + "lua.h", "macroexpander.cpp", "macroexpander.h", "mathutils.cpp", @@ -374,8 +376,7 @@ QtcLibrary { "fsenginehandler.cpp", "fsenginehandler.h", "fsengine_impl.cpp", - "fsengine_impl.h", - "rootinjectfsengine.h", + "fsengine_impl.h" ] } diff --git a/src/libs/utils/wizard.cpp b/src/libs/utils/wizard.cpp index 10d801c521..29bfb787a6 100644 --- a/src/libs/utils/wizard.cpp +++ b/src/libs/utils/wizard.cpp @@ -45,7 +45,9 @@ public: m_indicatorPixmap(indicatorPixmap) { m_indicatorLabel = new QLabel(this); - m_indicatorLabel->setFixedSize(m_indicatorPixmap.size()); + const QSizeF indicatorSize = m_indicatorPixmap.deviceIndependentSize(); + m_indicatorLabel->setFixedSize( + {qCeil(indicatorSize.width()), qCeil(indicatorSize.height())}); m_titleLabel = new QLabel(title, this); auto l = new QHBoxLayout(this); l->setContentsMargins(0, 0, 0, 0); @@ -276,6 +278,7 @@ public: bool m_automaticProgressCreation = true; WizardProgress *m_wizardProgress = nullptr; QSet<QString> m_fieldNames; + bool m_skipForSubproject = false; }; Wizard::Wizard(QWidget *parent, Qt::WindowFlags flags) : @@ -365,8 +368,8 @@ QHash<QString, QVariant> Wizard::variables() const QString typeOf(const QVariant &v) { QString result; - switch (v.type()) { - case QVariant::Map: + switch (v.typeId()) { + case QMetaType::QVariantMap: result = QLatin1String("Object"); break; default: @@ -525,6 +528,32 @@ void Wizard::_q_pageRemoved(int pageId) d->m_wizardProgress->removeItem(item); } +void Wizard::setSkipForSubprojects(bool skip) +{ + Q_D(Wizard); + d->m_skipForSubproject = skip; +} + +int Wizard::nextId() const +{ + Q_D(const Wizard); + if (!d->m_skipForSubproject) + return QWizard::nextId(); + + const QList<int> allIds = pageIds(); + int index = allIds.indexOf(currentId()); + QTC_ASSERT(index > -1, return QWizard::nextId()); + + while (++index < allIds.size()) { + if (auto wp = qobject_cast<WizardPage *>(page(index))) { + if (!wp->skipForSubprojects()) + return index; + } + } + QTC_CHECK(false); // should not happen + return QWizard::nextId(); +} + class WizardProgressPrivate { WizardProgress *q_ptr; diff --git a/src/libs/utils/wizard.h b/src/libs/utils/wizard.h index 35abff2f6d..ac3ad795bf 100644 --- a/src/libs/utils/wizard.h +++ b/src/libs/utils/wizard.h @@ -50,6 +50,10 @@ public: void showVariables(); + // allows to skip pages + void setSkipForSubprojects(bool skip); + int nextId() const override; + protected: virtual QString stringify(const QVariant &v) const; virtual QString evaluate(const QVariant &v) const; diff --git a/src/libs/utils/wizardpage.h b/src/libs/utils/wizardpage.h index 76d3039a67..1ab23f92b5 100644 --- a/src/libs/utils/wizardpage.h +++ b/src/libs/utils/wizardpage.h @@ -74,6 +74,9 @@ public: void registerFieldWithName(const QString &name, QWidget *widget, const char *property = nullptr, const char *changedSignal = nullptr); + void setSkipForSubprojects(bool skip) { m_skipForSubproject = skip; } + bool skipForSubprojects() const { return m_skipForSubproject; } + virtual bool handleReject(); virtual bool handleAccept(); @@ -85,6 +88,7 @@ private: void registerFieldName(const QString &name); QSet<QString> m_toRegister; + bool m_skipForSubproject = false; }; } // namespace Utils |