aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/coreplugin
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/coreplugin')
-rw-r--r--src/plugins/coreplugin/CMakeLists.txt4
-rw-r--r--src/plugins/coreplugin/actionmanager/actionmanager.cpp230
-rw-r--r--src/plugins/coreplugin/actionmanager/command.cpp120
-rw-r--r--src/plugins/coreplugin/actionmanager/command.h6
-rw-r--r--src/plugins/coreplugin/actionmanager/command_p.h8
-rw-r--r--src/plugins/coreplugin/actionmanager/commandmappings.cpp12
-rw-r--r--src/plugins/coreplugin/actionmanager/commandmappings.h4
-rw-r--r--src/plugins/coreplugin/actionmanager/commandsfile.cpp24
-rw-r--r--src/plugins/coreplugin/actionmanager/commandsfile.h2
-rw-r--r--src/plugins/coreplugin/coreconstants.h14
-rw-r--r--src/plugins/coreplugin/coreplugin.cpp8
-rw-r--r--src/plugins/coreplugin/coreplugin.h2
-rw-r--r--src/plugins/coreplugin/coreplugin.pro6
-rw-r--r--src/plugins/coreplugin/coreplugin.qbs8
-rw-r--r--src/plugins/coreplugin/dialogs/codecselector.cpp161
-rw-r--r--src/plugins/coreplugin/dialogs/codecselector.h69
-rw-r--r--src/plugins/coreplugin/dialogs/ioptionspage.cpp7
-rw-r--r--src/plugins/coreplugin/dialogs/ioptionspage.h4
-rw-r--r--src/plugins/coreplugin/dialogs/settingsdialog.cpp27
-rw-r--r--src/plugins/coreplugin/dialogs/shortcutsettings.cpp393
-rw-r--r--src/plugins/coreplugin/dialogs/shortcutsettings.h30
-rw-r--r--src/plugins/coreplugin/documentmanager.cpp40
-rw-r--r--src/plugins/coreplugin/documentmanager.h2
-rw-r--r--src/plugins/coreplugin/find/searchresultwidget.cpp19
-rw-r--r--src/plugins/coreplugin/find/searchresultwidget.h4
-rw-r--r--src/plugins/coreplugin/find/searchresultwindow.cpp10
-rw-r--r--src/plugins/coreplugin/find/searchresultwindow.h2
-rw-r--r--src/plugins/coreplugin/generatedfile.h26
-rw-r--r--src/plugins/coreplugin/helpitem.cpp2
-rw-r--r--src/plugins/coreplugin/helpmanager.cpp8
-rw-r--r--src/plugins/coreplugin/helpmanager.h4
-rw-r--r--src/plugins/coreplugin/helpmanager_implementation.h4
-rw-r--r--src/plugins/coreplugin/icontext.cpp213
-rw-r--r--src/plugins/coreplugin/icore.cpp22
-rw-r--r--src/plugins/coreplugin/icore.h2
-rw-r--r--src/plugins/coreplugin/locator/javascriptfilter.cpp9
-rw-r--r--src/plugins/coreplugin/locator/javascriptfilter.h4
-rw-r--r--src/plugins/coreplugin/locator/locator.pri4
-rw-r--r--src/plugins/coreplugin/mainwindow.cpp39
-rw-r--r--src/plugins/coreplugin/mainwindow.h1
-rw-r--r--src/plugins/coreplugin/messageoutputwindow.cpp2
-rw-r--r--src/plugins/coreplugin/outputpanemanager.cpp6
-rw-r--r--src/plugins/coreplugin/outputwindow.cpp309
-rw-r--r--src/plugins/coreplugin/outputwindow.h20
-rw-r--r--src/plugins/coreplugin/plugindialog.cpp323
-rw-r--r--src/plugins/coreplugin/plugindialog.h2
-rw-r--r--src/plugins/coreplugin/systemsettings.cpp2
-rw-r--r--src/plugins/coreplugin/variablechooser.cpp6
-rw-r--r--src/plugins/coreplugin/welcomepagehelper.cpp4
49 files changed, 1680 insertions, 548 deletions
diff --git a/src/plugins/coreplugin/CMakeLists.txt b/src/plugins/coreplugin/CMakeLists.txt
index 06194cddf57..10b580ba257 100644
--- a/src/plugins/coreplugin/CMakeLists.txt
+++ b/src/plugins/coreplugin/CMakeLists.txt
@@ -18,6 +18,7 @@ add_qtc_plugin(Core
coreplugin.cpp coreplugin.h
designmode.cpp designmode.h
dialogs/addtovcsdialog.cpp dialogs/addtovcsdialog.h dialogs/addtovcsdialog.ui
+ dialogs/codecselector.cpp dialogs/codecselector.h
dialogs/externaltoolconfig.cpp dialogs/externaltoolconfig.h dialogs/externaltoolconfig.ui
dialogs/filepropertiesdialog.cpp dialogs/filepropertiesdialog.h dialogs/filepropertiesdialog.ui
dialogs/ioptionspage.cpp dialogs/ioptionspage.h
@@ -179,9 +180,8 @@ extend_qtc_plugin(Core
)
extend_qtc_plugin(Core
- CONDITION TARGET Qt5::Script
+ CONDITION Qt5_VERSION VERSION_GREATER_EQUAL 5.14.0
FEATURE_INFO "Script Locator filter"
- DEPENDS Qt5::Script
DEFINES WITH_JAVASCRIPTFILTER
SOURCES
locator/javascriptfilter.cpp locator/javascriptfilter.h
diff --git a/src/plugins/coreplugin/actionmanager/actionmanager.cpp b/src/plugins/coreplugin/actionmanager/actionmanager.cpp
index 449a0040bcb..3f0d05a302e 100644
--- a/src/plugins/coreplugin/actionmanager/actionmanager.cpp
+++ b/src/plugins/coreplugin/actionmanager/actionmanager.cpp
@@ -30,6 +30,7 @@
#include <coreplugin/icore.h>
#include <coreplugin/id.h>
+#include <utils/algorithm.h>
#include <utils/fadingindicator.h>
#include <utils/qtcassert.h>
@@ -46,6 +47,7 @@ namespace {
}
static const char kKeyboardSettingsKey[] = "KeyboardShortcuts";
+static const char kKeyboardSettingsKeyV2[] = "KeyboardShortcutsV2";
using namespace Core;
using namespace Core::Internal;
@@ -58,86 +60,57 @@ using namespace Core::Internal;
\brief The ActionManager class is responsible for registration of menus and
menu items and keyboard shortcuts.
- The ActionManager is the central bookkeeper of actions and their shortcuts and layout.
- It is a singleton containing mostly static functions. If you need access to the instance,
- e.g. for connecting to signals, call its ActionManager::instance() function.
-
- The main reasons for the need of this class is to provide a central place where the users
- can specify all their keyboard shortcuts, and to provide a solution for actions that should
- behave differently in different contexts (like the copy/replace/undo/redo actions).
-
- \section1 Contexts
-
- All actions that are registered with the same Id (but different context lists)
- are considered to be overloads of the same command, represented by an instance
- of the Core::Command class.
- Exactly only one of the registered actions with the same ID is active at any time.
- Which action this is, is defined by the context list that the actions were registered
- with:
-
- If the current focus widget was registered via \l{ICore::addContextObject()},
- all the contexts returned by its IContext object are active. In addition all
- contexts set via \l{ICore::addAdditionalContext()} are active as well. If one
- of the actions was registered for one of these active contexts, it is the one
- active action, and receives \c triggered and \c toggled signals. Also the
- appearance of the visible action for this ID might be adapted to this
- active action (depending on the settings of the corresponding \l{Command} object).
-
- The action that is visible to the user is the one returned by Command::action().
- If you provide yourself a user visible representation of your action you need
- to use Command::action() for this.
- When this action is invoked by the user,
- the signal is forwarded to the registered action that is valid for the current context.
-
- \section1 Registering Actions
-
- To register a globally active action "My Action"
- put the following in your plugin's IPlugin::initialize function:
+ The action manager is the central bookkeeper of actions and their shortcuts
+ and layout. It is a singleton containing mostly static functions. If you
+ need access to the instance, for example for connecting to signals, call
+ its ActionManager::instance() function.
+
+ The action manager makes it possible to provide a central place where the
+ users can specify all their keyboard shortcuts, and provides a solution for
+ actions that should behave differently in different contexts (like the
+ copy/replace/undo/redo actions).
+
+ See \l{The Action Manager and Commands} for an overview of the interaction
+ between Core::ActionManager, Core::Command, and Core::Context.
+
+ Register a globally active action "My Action" by putting the following in
+ your plugin's ExtensionSystem::IPlugin::initialize() function.
+
\code
QAction *myAction = new QAction(tr("My Action"), this);
- Command *cmd = ActionManager::registerAction(myAction,
- "myplugin.myaction",
- Context(C_GLOBAL));
+ Command *cmd = ActionManager::registerAction(myAction, "myplugin.myaction", Context(C_GLOBAL));
cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Alt+u")));
connect(myAction, &QAction::triggered, this, &MyPlugin::performMyAction);
\endcode
- So the \c connect is done to your own QAction instance. If you create e.g.
- a tool button that should represent the action you add the action
- from Command::action() to it:
+ The \c connect is done to your own QAction instance. If you create for
+ example a tool button that should represent the action, add the action from
+ Command::action() to it.
+
\code
QToolButton *myButton = new QToolButton(someParentWidget);
myButton->setDefaultAction(cmd->action());
\endcode
- Also use the ActionManager to add items to registered
- action containers like the applications menu bar or menus in that menu bar.
- To do this, you register your action via the
- registerAction functions, get the action container for a specific ID (like specified in
- the Core::Constants namespace) with a call of
- actionContainer(const Id&) and add your command to this container.
+ Also use the action manager to add items to registered action containers
+ like the application's menu bar or menus in that menu bar. Register your
+ action via the Core::ActionManager::registerAction() function, get the
+ action container for a specific ID (as specified for example in the
+ Core::Constants namespace) with Core::ActionManager::actionContainer(), and
+ add your command to this container.
+
+ Building on the example, adding "My Action" to the "Tools" menu would be
+ done with
- Following the example adding "My Action" to the "Tools" menu would be done by
\code
- ActionManager::actionContainer(M_TOOLS)->addAction(cmd);
+ ActionManager::actionContainer(Core::Constants::M_TOOLS)->addAction(cmd);
\endcode
- \section1 Important Guidelines:
- \list
- \li Always register your actions and shortcuts!
- \li Register your actions and shortcuts during your plugin's \l{ExtensionSystem::IPlugin::initialize()}
- or \l{ExtensionSystem::IPlugin::extensionsInitialized()} functions, otherwise the shortcuts won't appear
- in the keyboard settings dialog from the beginning.
- \li When registering an action with \c{cmd=registerAction(action, id, contexts)} be sure to connect
- your own action \c{connect(action, SIGNAL...)} but make \c{cmd->action()} visible to the user, i.e.
- \c{widget->addAction(cmd->action())}.
- \li Use this class to add actions to the applications menus
- \endlist
-
\sa Core::ICore
\sa Core::Command
\sa Core::ActionContainer
\sa Core::IContext
+ \sa {The Action Manager and Commands}
*/
/*!
@@ -176,7 +149,7 @@ ActionManager::~ActionManager()
}
/*!
- Returns the pointer to the instance, which is only used for connecting to signals.
+ Returns the pointer to the instance. Only use for connecting to signals.
*/
ActionManager *ActionManager::instance()
{
@@ -184,13 +157,13 @@ ActionManager *ActionManager::instance()
}
/*!
- Creates a new menu with the given \a id.
+ Creates a new menu action container or returns an existing container with
+ the specified \a id. The ActionManager owns the returned ActionContainer.
+ Add your menu to some other menu or a menu bar via the actionContainer()
+ and ActionContainer::addMenu() functions.
- Returns a new ActionContainer that you can use to get the QMenu instance
- or to add menu items to the menu. The ActionManager owns
- the returned ActionContainer.
- Add your menu to some other menu or a menu bar via the
- ActionManager::actionContainer and ActionContainer::addMenu functions.
+ \sa actionContainer()
+ \sa ActionContainer::addMenu()
*/
ActionContainer *ActionManager::createMenu(Id id)
{
@@ -207,11 +180,12 @@ ActionContainer *ActionManager::createMenu(Id id)
}
/*!
- Creates a new menu bar with the given \a id.
+ Creates a new menu bar action container or returns an existing container
+ with the specified \a id. The ActionManager owns the returned
+ ActionContainer.
- Returns a new ActionContainer that you can use to get the QMenuBar instance
- or to add menus to the menu bar. The ActionManager owns
- the returned ActionContainer.
+ \sa createMenu()
+ \sa ActionContainer::addMenu()
*/
ActionContainer *ActionManager::createMenuBar(Id id)
{
@@ -232,13 +206,17 @@ ActionContainer *ActionManager::createMenuBar(Id id)
}
/*!
- Creates a touch bar with the given \a id.
+ Creates a new (sub) touch bar action container or returns an existing
+ container with the specified \a id. The ActionManager owns the returned
+ ActionContainer.
- Returns a new ActionContainer that you can use to add items to a (sub) touch bar.
Note that it is only possible to create a single level of sub touch bars.
- The sub touch bar will be represented as a button with \a icon and \a text (one can be left
- empty), which opens the sub touch bar when touched.
- The ActionManager owns the returned ActionContainer.
+ The sub touch bar will be represented as a button with \a icon and \a text
+ (either of which can be left empty), which opens the sub touch bar when
+ touched.
+
+ \sa actionContainer()
+ \sa ActionContainer::addMenu()
*/
ActionContainer *ActionManager::createTouchBar(Id id, const QIcon &icon, const QString &text)
{
@@ -255,15 +233,13 @@ ActionContainer *ActionManager::createTouchBar(Id id, const QIcon &icon, const Q
/*!
Makes an \a action known to the system under the specified \a id.
- Returns a command object that represents the action in the application and is
- owned by the ActionManager. You can register several actions with the
- same \a id as long as the \a context is different. In this case
- a trigger of the actual action is forwarded to the registered QAction
- for the currently active context.
- If the optional \a context argument is not specified, the global context
- will be assumed.
- A \a scriptable action can be called from a script without the need for the user
- to interact with it.
+ Returns a Command instance that represents the action in the application
+ and is owned by the ActionManager. You can register several actions with
+ the same \a id as long as the \a context is different. In this case
+ triggering the action is forwarded to the registered QAction for the
+ currently active context. If the optional \a context argument is not
+ specified, the global context will be assumed. A \a scriptable action can
+ be called from a script without the need for the user to interact with it.
*/
Command *ActionManager::registerAction(QAction *action, Id id, const Context &context, bool scriptable)
{
@@ -277,10 +253,10 @@ Command *ActionManager::registerAction(QAction *action, Id id, const Context &co
}
/*!
- Returns the Command object that is known to the system
- under the given \a id.
+ Returns the Command instance that has been created with registerAction()
+ for the specified \a id.
- \sa ActionManager::registerAction()
+ \sa registerAction()
*/
Command *ActionManager::command(Id id)
{
@@ -295,8 +271,15 @@ Command *ActionManager::command(Id id)
}
/*!
- Returns the IActionContainter object that is know to the system
- under the given \a id.
+ Returns the ActionContainter instance that has been created with
+ createMenu(), createMenuBar(), createTouchBar() for the specified \a id.
+
+ Use the ID \c{Core::Constants::MENU_BAR} to retrieve the main menu bar.
+
+ Use the IDs \c{Core::Constants::M_FILE}, \c{Core::Constants::M_EDIT}, and
+ similar constants to retrieve the various default menus.
+
+ Use the ID \c{Core::Constants::TOUCH_BAR} to retrieve the main touch bar.
\sa ActionManager::createMenu()
\sa ActionManager::createMenuBar()
@@ -314,7 +297,7 @@ ActionContainer *ActionManager::actionContainer(Id id)
}
/*!
- Returns all commands that have been registered.
+ Returns all registered commands.
*/
QList<Command *> ActionManager::commands()
{
@@ -355,9 +338,7 @@ void ActionManager::unregisterAction(QAction *action, Id id)
}
/*!
- Handles the display of the used shortcuts in the presentation mode. The presentation mode is
- \a enabled when starting \QC with the command line argument \c{-presentationMode}. In the
- presentation mode, \QC displays any pressed shortcut in a grey box.
+ \internal
*/
void ActionManager::setPresentationModeEnabled(bool enabled)
{
@@ -380,7 +361,9 @@ void ActionManager::setPresentationModeEnabled(bool enabled)
/*!
Returns whether presentation mode is enabled.
- \sa setPresentationModeEnabled
+ The presentation mode is enabled when starting \QC with the command line
+ argument \c{-presentationMode}. In presentation mode, \QC displays any
+ pressed shortcut in an overlay box.
*/
bool ActionManager::isPresentationModeEnabled()
{
@@ -388,7 +371,8 @@ bool ActionManager::isPresentationModeEnabled()
}
/*!
- \internal
+ Decorates the specified \a text with a numbered accelerator key \a number,
+ in the style of the \uicontrol {Recent Files} menu.
*/
QString ActionManager::withNumberAccelerator(const QString &text, const int number)
{
@@ -397,11 +381,17 @@ QString ActionManager::withNumberAccelerator(const QString &text, const int numb
return QString("&%1 | %2").arg(number).arg(text);
}
+/*!
+ \internal
+*/
void ActionManager::saveSettings()
{
d->saveSettings();
}
+/*!
+ \internal
+*/
void ActionManager::setContext(const Context &context)
{
d->setContext(context);
@@ -495,22 +485,50 @@ Action *ActionManagerPrivate::overridableAction(Id id)
void ActionManagerPrivate::readUserSettings(Id id, Action *cmd)
{
+ // TODO Settings V2 were introduced in Qt Creator 4.13, remove old settings at some point
QSettings *settings = ICore::settings();
- settings->beginGroup(QLatin1String(kKeyboardSettingsKey));
- if (settings->contains(id.toString()))
- cmd->setKeySequence(QKeySequence(settings->value(id.toString()).toString()));
+ // transfer from old settings if not done before
+ const QString group = settings->childGroups().contains(kKeyboardSettingsKeyV2)
+ ? QString(kKeyboardSettingsKeyV2)
+ : QString(kKeyboardSettingsKey);
+ settings->beginGroup(group);
+ if (settings->contains(id.toString())) {
+ const QVariant v = settings->value(id.toString());
+ if (QMetaType::Type(v.type()) == QMetaType::QStringList) {
+ cmd->setKeySequences(Utils::transform<QList>(v.toStringList(), [](const QString &s) {
+ return QKeySequence::fromString(s);
+ }));
+ } else {
+ cmd->setKeySequences({QKeySequence::fromString(v.toString())});
+ }
+ }
settings->endGroup();
}
void ActionManagerPrivate::saveSettings(Action *cmd)
{
- const QString settingsKey = QLatin1String(kKeyboardSettingsKey) + QLatin1Char('/')
- + cmd->id().toString();
- QKeySequence key = cmd->keySequence();
- if (key != cmd->defaultKeySequence())
- ICore::settings()->setValue(settingsKey, key.toString());
- else
+ const QString id = cmd->id().toString();
+ const QString settingsKey = QLatin1String(kKeyboardSettingsKeyV2) + '/' + id;
+ const QString compatSettingsKey = QLatin1String(kKeyboardSettingsKey) + '/' + id;
+ const QList<QKeySequence> keys = cmd->keySequences();
+ const QList<QKeySequence> defaultKeys = cmd->defaultKeySequences();
+ if (keys != defaultKeys) {
+ if (keys.isEmpty()) {
+ ICore::settings()->setValue(settingsKey, QString());
+ ICore::settings()->setValue(compatSettingsKey, QString());
+ } else if (keys.size() == 1) {
+ ICore::settings()->setValue(settingsKey, keys.first().toString());
+ ICore::settings()->setValue(compatSettingsKey, keys.first().toString());
+ } else {
+ ICore::settings()->setValue(settingsKey,
+ Utils::transform<QStringList>(keys,
+ [](const QKeySequence &k) {
+ return k.toString();
+ }));
+ }
+ } else {
ICore::settings()->remove(settingsKey);
+ }
}
void ActionManagerPrivate::saveSettings()
diff --git a/src/plugins/coreplugin/actionmanager/command.cpp b/src/plugins/coreplugin/actionmanager/command.cpp
index cd801c1ec2f..49ecface97b 100644
--- a/src/plugins/coreplugin/actionmanager/command.cpp
+++ b/src/plugins/coreplugin/actionmanager/command.cpp
@@ -50,11 +50,13 @@
action and its properties. If multiple actions are registered with the same ID (but
different contexts) the returned Command is the shared one between these actions.
- A Command has two basic properties: a default shortcut and a default text. The default
- shortcut is a key sequence that the user can use to trigger the active action that
- the Command represents. The default text is e.g. used for representing the Command
- in the keyboard shortcut preference pane. If the default text is empty, the text
- of the visible action is used.
+ A Command has two basic properties: a list of default shortcuts and a
+ default text. The default shortcuts are key sequence that the user can use
+ to trigger the active action that the Command represents. The first
+ shortcut in that list is the main shortcut that is for example also shown
+ in tool tips and menus. The default text is used for representing the
+ Command in the keyboard shortcut preference pane. If the default text is
+ empty, the text of the visible action is used.
The user visible action is updated to represent the state of the active action (if any).
For performance reasons only the enabled and visible state are considered by default though.
@@ -64,6 +66,12 @@
If there is no active action, the default behavior of the visible action is to be disabled.
You can change that behavior to make the visible action hide instead via the Command's
\l{Command::CommandAttribute}{attributes}.
+
+ See \l{The Action Manager and Commands} for an overview of how
+ Core::Command and Core::ActionManager interact.
+
+ \sa Core::ActionManager
+ \sa {The Action Manager and Commands}
*/
/*!
@@ -79,37 +87,56 @@
When there is no active action, hide the user-visible action, instead of just
disabling it.
\value CA_NonConfigurable
- Flag to indicate that the keyboard shortcut of this Command should not be
- configurable by the user.
+ Flag to indicate that the keyboard shortcuts of this Command should not
+ be configurable by the user.
*/
/*!
\fn void Core::Command::setDefaultKeySequence(const QKeySequence &key)
- Sets the default keyboard shortcut that can be used to activate this command to \a key.
- This is used if the user didn't customize the shortcut, or resets the shortcut
- to the default one.
+
+ Sets the default keyboard shortcut that can be used to activate this
+ command to \a key. This is used if the user didn't customize the shortcut,
+ or resets the shortcut to the default.
+*/
+
+/*!
+ \fn void Core::Command::setDefaultKeySequences(const QList<QKeySequence> &keys)
+
+ Sets the default keyboard shortcuts that can be used to activate this
+ command to \a keys. This is used if the user didn't customize the
+ shortcuts, or resets the shortcuts to the default.
*/
/*!
- \fn void Core::Command::defaultKeySequence() const
- Returns the default keyboard shortcut that can be used to activate this command.
- \sa setDefaultKeySequence()
+ \fn QList<QKeySequence> Core::Command::defaultKeySequences() const
+
+ Returns the default keyboard shortcuts that can be used to activate this
+ command.
+ \sa setDefaultKeySequences()
*/
/*!
\fn void Core::Command::keySequenceChanged()
- Sent when the keyboard shortcut assigned to this Command changes, e.g.
- when the user sets it in the keyboard shortcut settings dialog.
+ Sent when the keyboard shortcuts assigned to this Command change, e.g.
+ when the user sets them in the keyboard shortcut settings dialog.
+*/
+
+/*!
+ \fn QList<QKeySequence> Core::Command::keySequences() const
+
+ Returns the current keyboard shortcuts assigned to this Command.
+ \sa defaultKeySequences()
*/
/*!
\fn QKeySequence Core::Command::keySequence() const
- Returns the current keyboard shortcut assigned to this Command.
- \sa defaultKeySequence()
+
+ Returns the current main keyboard shortcut assigned to this Command.
+ \sa defaultKeySequences()
*/
/*!
- \fn void Core::Command::setKeySequence(const QKeySequence &key)
+ \fn void Core::Command::setKeySequences(const QList<QKeySequence> &keys)
\internal
*/
@@ -134,21 +161,20 @@
/*!
\fn QString Core::Command::stringWithAppendedShortcut(const QString &string) const
- Returns the \a string with an appended representation of the keyboard shortcut
- that is currently assigned to this Command.
+
+ Returns the \a string with an appended representation of the main keyboard
+ shortcut that is currently assigned to this Command.
*/
/*!
\fn QAction *Core::Command::action() const
- Returns the user visible action for this Command.
- If the Command represents a shortcut, it returns null.
- Use this action to put it on e.g. tool buttons. The action
- automatically forwards trigger and toggle signals to the
- action that is currently active for this Command.
- It also shows the current keyboard shortcut in its
- tool tip (in addition to the tool tip of the active action)
- and gets disabled/hidden when there is
- no active action for the current context.
+
+ Returns the user visible action for this Command. Use this action to put it
+ on e.g. tool buttons. The action automatically forwards \c triggered() and
+ \c toggled() signals to the action that is currently active for this
+ Command. It also shows the current main keyboard shortcut in its tool tip
+ (in addition to the tool tip of the active action) and gets disabled/hidden
+ when there is no active action for the current context.
*/
/*!
@@ -182,8 +208,8 @@
/*!
\fn bool Core::Command::isActive() const
- Returns whether the Command has an active action or shortcut for the current
- context.
+
+ Returns whether the Command has an active action for the current context.
*/
/*!
@@ -230,7 +256,7 @@
/*! \fn virtual QAction *Core::Command::touchBarAction() const
- Adds an action to the touch bar.
+ \internal
*/
namespace Core {
@@ -253,13 +279,20 @@ Id Action::id() const
void Action::setDefaultKeySequence(const QKeySequence &key)
{
if (!m_isKeyInitialized)
- setKeySequence(key);
- m_defaultKey = key;
+ setKeySequences({key});
+ m_defaultKeys = {key};
+}
+
+void Action::setDefaultKeySequences(const QList<QKeySequence> &keys)
+{
+ if (!m_isKeyInitialized)
+ setKeySequences(keys);
+ m_defaultKeys = keys;
}
-QKeySequence Action::defaultKeySequence() const
+QList<QKeySequence> Action::defaultKeySequences() const
{
- return m_defaultKey;
+ return m_defaultKeys;
}
QAction *Action::action() const
@@ -277,13 +310,18 @@ Context Action::context() const
return m_context;
}
-void Action::setKeySequence(const QKeySequence &key)
+void Action::setKeySequences(const QList<QKeySequence> &keys)
{
m_isKeyInitialized = true;
- m_action->setShortcut(key);
+ m_action->setShortcuts(keys);
emit keySequenceChanged();
}
+QList<QKeySequence> Action::keySequences() const
+{
+ return m_action->shortcuts();
+}
+
QKeySequence Action::keySequence() const
{
return m_action->shortcut();
@@ -491,8 +529,8 @@ QAction *Action::touchBarAction() const
} // namespace Internal
/*!
- Appends the keyboard shortcut that is currently assigned to the action \a a
- to its tool tip.
+ Appends the main keyboard shortcut that is currently assigned to the action
+ \a a to its tool tip.
*/
void Command::augmentActionWithShortcutToolTip(QAction *a) const
{
@@ -508,7 +546,7 @@ void Command::augmentActionWithShortcutToolTip(QAction *a) const
/*!
Returns a tool button for \a action.
- Appends the keyboard shortcut \a cmd to the tool tip of the action.
+ Appends the main keyboard shortcut \a cmd to the tool tip of the action.
*/
QToolButton *Command::toolButtonWithAppendedShortcut(QAction *action, Command *cmd)
{
diff --git a/src/plugins/coreplugin/actionmanager/command.h b/src/plugins/coreplugin/actionmanager/command.h
index 355507de121..d17ef0d2661 100644
--- a/src/plugins/coreplugin/actionmanager/command.h
+++ b/src/plugins/coreplugin/actionmanager/command.h
@@ -58,7 +58,9 @@ public:
Q_DECLARE_FLAGS(CommandAttributes, CommandAttribute)
virtual void setDefaultKeySequence(const QKeySequence &key) = 0;
- virtual QKeySequence defaultKeySequence() const = 0;
+ virtual void setDefaultKeySequences(const QList<QKeySequence> &keys) = 0;
+ virtual QList<QKeySequence> defaultKeySequences() const = 0;
+ virtual QList<QKeySequence> keySequences() const = 0;
virtual QKeySequence keySequence() const = 0;
// explicitly set the description (used e.g. in shortcut settings)
// default is to use the action text for actions, or the whatsThis for shortcuts,
@@ -78,7 +80,7 @@ public:
virtual bool isActive() const = 0;
- virtual void setKeySequence(const QKeySequence &key) = 0;
+ virtual void setKeySequences(const QList<QKeySequence> &keys) = 0;
virtual QString stringWithAppendedShortcut(const QString &str) const = 0;
void augmentActionWithShortcutToolTip(QAction *action) const;
static QToolButton *toolButtonWithAppendedShortcut(QAction *action, Command *cmd);
diff --git a/src/plugins/coreplugin/actionmanager/command_p.h b/src/plugins/coreplugin/actionmanager/command_p.h
index 9811c88fb66..de722d41cf5 100644
--- a/src/plugins/coreplugin/actionmanager/command_p.h
+++ b/src/plugins/coreplugin/actionmanager/command_p.h
@@ -52,9 +52,11 @@ public:
Id id() const override;
void setDefaultKeySequence(const QKeySequence &key) override;
- QKeySequence defaultKeySequence() const override;
+ void setDefaultKeySequences(const QList<QKeySequence> &key) override;
+ QList<QKeySequence> defaultKeySequences() const override;
- void setKeySequence(const QKeySequence &key) override;
+ void setKeySequences(const QList<QKeySequence> &keys) override;
+ QList<QKeySequence> keySequences() const override;
QKeySequence keySequence() const override;
void setDescription(const QString &text) override;
@@ -92,7 +94,7 @@ private:
Context m_context;
CommandAttributes m_attributes;
Id m_id;
- QKeySequence m_defaultKey;
+ QList<QKeySequence> m_defaultKeys;
QString m_defaultText;
QString m_touchBarText;
QIcon m_touchBarIcon;
diff --git a/src/plugins/coreplugin/actionmanager/commandmappings.cpp b/src/plugins/coreplugin/actionmanager/commandmappings.cpp
index 0280217dab4..4cec7e69a4a 100644
--- a/src/plugins/coreplugin/actionmanager/commandmappings.cpp
+++ b/src/plugins/coreplugin/actionmanager/commandmappings.cpp
@@ -74,11 +74,16 @@ public:
defaultButton = new QPushButton(CommandMappings::tr("Reset All"), groupBox);
defaultButton->setToolTip(CommandMappings::tr("Reset all to default."));
+ resetButton = new QPushButton(CommandMappings::tr("Reset"), groupBox);
+ resetButton->setToolTip(CommandMappings::tr("Reset to default."));
+ resetButton->setVisible(false);
+
importButton = new QPushButton(CommandMappings::tr("Import..."), groupBox);
exportButton = new QPushButton(CommandMappings::tr("Export..."), groupBox);
auto hboxLayout1 = new QHBoxLayout();
hboxLayout1->addWidget(defaultButton);
+ hboxLayout1->addWidget(resetButton);
hboxLayout1->addStretch();
hboxLayout1->addWidget(importButton);
hboxLayout1->addWidget(exportButton);
@@ -100,6 +105,7 @@ public:
q, &CommandMappings::importAction);
q->connect(defaultButton, &QPushButton::clicked,
q, &CommandMappings::defaultAction);
+ q->connect(resetButton, &QPushButton::clicked, q, &CommandMappings::resetRequested);
commandList->sortByColumn(0, Qt::AscendingOrder);
@@ -117,6 +123,7 @@ public:
FancyLineEdit *filterEdit;
QTreeWidget *commandList;
QPushButton *defaultButton;
+ QPushButton *resetButton;
QPushButton *importButton;
QPushButton *exportButton;
};
@@ -145,6 +152,11 @@ void CommandMappings::setImportExportEnabled(bool enabled)
d->exportButton->setVisible(enabled);
}
+void CommandMappings::setResetVisible(bool visible)
+{
+ d->resetButton->setVisible(visible);
+}
+
QTreeWidget *CommandMappings::commandList() const
{
return d->commandList;
diff --git a/src/plugins/coreplugin/actionmanager/commandmappings.h b/src/plugins/coreplugin/actionmanager/commandmappings.h
index b6e9af206ac..2d8b8ac017c 100644
--- a/src/plugins/coreplugin/actionmanager/commandmappings.h
+++ b/src/plugins/coreplugin/actionmanager/commandmappings.h
@@ -50,6 +50,7 @@ public:
signals:
void currentCommandChanged(QTreeWidgetItem *current);
+ void resetRequested();
protected:
virtual void defaultAction() = 0;
@@ -61,8 +62,9 @@ protected:
void filterChanged(const QString &f);
- // access to m_page
void setImportExportEnabled(bool enabled);
+ void setResetVisible(bool visible);
+
QTreeWidget *commandList() const;
QString filterText() const;
void setFilterText(const QString &text);
diff --git a/src/plugins/coreplugin/actionmanager/commandsfile.cpp b/src/plugins/coreplugin/actionmanager/commandsfile.cpp
index faeed00e07e..8b03fee9e57 100644
--- a/src/plugins/coreplugin/actionmanager/commandsfile.cpp
+++ b/src/plugins/coreplugin/actionmanager/commandsfile.cpp
@@ -83,9 +83,9 @@ CommandsFile::CommandsFile(const QString &filename)
/*!
\internal
*/
-QMap<QString, QKeySequence> CommandsFile::importCommands() const
+QMap<QString, QList<QKeySequence>> CommandsFile::importCommands() const
{
- QMap<QString, QKeySequence> result;
+ QMap<QString, QList<QKeySequence>> result;
QFile file(m_filename);
if (!file.open(QIODevice::ReadOnly|QIODevice::Text))
@@ -101,19 +101,17 @@ QMap<QString, QKeySequence> CommandsFile::importCommands() const
case QXmlStreamReader::StartElement: {
const QStringRef name = r.name();
if (name == ctx.shortCutElement) {
- if (!currentId.isEmpty()) // shortcut element without key element == empty shortcut
- result.insert(currentId, QKeySequence());
currentId = r.attributes().value(ctx.idAttribute).toString();
+ if (!result.contains(currentId))
+ result.insert(currentId, {});
} else if (name == ctx.keyElement) {
- QTC_ASSERT(!currentId.isEmpty(), return result);
+ QTC_ASSERT(!currentId.isEmpty(), continue);
const QXmlStreamAttributes attributes = r.attributes();
if (attributes.hasAttribute(ctx.valueAttribute)) {
const QString keyString = attributes.value(ctx.valueAttribute).toString();
- result.insert(currentId, QKeySequence(keyString));
- } else {
- result.insert(currentId, QKeySequence());
+ QList<QKeySequence> keys = result.value(currentId);
+ result.insert(currentId, keys << QKeySequence(keyString));
}
- currentId.clear();
} // if key element
} // case QXmlStreamReader::StartElement
default:
@@ -144,14 +142,16 @@ bool CommandsFile::exportCommands(const QList<ShortcutItem *> &items)
w.writeStartElement(ctx.mappingElement);
foreach (const ShortcutItem *item, items) {
const Id id = item->m_cmd->id();
- if (item->m_key.isEmpty()) {
+ if (item->m_keys.isEmpty() || item->m_keys.first().isEmpty()) {
w.writeEmptyElement(ctx.shortCutElement);
w.writeAttribute(ctx.idAttribute, id.toString());
} else {
w.writeStartElement(ctx.shortCutElement);
w.writeAttribute(ctx.idAttribute, id.toString());
- w.writeEmptyElement(ctx.keyElement);
- w.writeAttribute(ctx.valueAttribute, item->m_key.toString());
+ for (const QKeySequence &k : item->m_keys) {
+ w.writeEmptyElement(ctx.keyElement);
+ w.writeAttribute(ctx.valueAttribute, k.toString());
+ }
w.writeEndElement(); // Shortcut
}
}
diff --git a/src/plugins/coreplugin/actionmanager/commandsfile.h b/src/plugins/coreplugin/actionmanager/commandsfile.h
index 891856383fa..cd4bceeeeb6 100644
--- a/src/plugins/coreplugin/actionmanager/commandsfile.h
+++ b/src/plugins/coreplugin/actionmanager/commandsfile.h
@@ -43,7 +43,7 @@ class CommandsFile : public QObject
public:
CommandsFile(const QString &filename);
- QMap<QString, QKeySequence> importCommands() const;
+ QMap<QString, QList<QKeySequence> > importCommands() const;
bool exportCommands(const QList<ShortcutItem *> &items);
private:
diff --git a/src/plugins/coreplugin/coreconstants.h b/src/plugins/coreplugin/coreconstants.h
index fd817379579..d34886632fe 100644
--- a/src/plugins/coreplugin/coreconstants.h
+++ b/src/plugins/coreplugin/coreconstants.h
@@ -49,12 +49,13 @@ const char M_FILE[] = "QtCreator.Menu.File";
const char M_FILE_RECENTFILES[] = "QtCreator.Menu.File.RecentFiles";
const char M_EDIT[] = "QtCreator.Menu.Edit";
const char M_EDIT_ADVANCED[] = "QtCreator.Menu.Edit.Advanced";
+const char M_VIEW[] = "QtCreator.Menu.View";
+const char M_VIEW_MODESTYLES[] = "QtCreator.Menu.View.ModeStyles";
+const char M_VIEW_VIEWS[] = "QtCreator.Menu.View.Views";
+const char M_VIEW_PANES[] = "QtCreator.Menu.View.Panes";
const char M_TOOLS[] = "QtCreator.Menu.Tools";
const char M_TOOLS_EXTERNAL[] = "QtCreator.Menu.Tools.External";
const char M_WINDOW[] = "QtCreator.Menu.Window";
-const char M_WINDOW_PANES[] = "QtCreator.Menu.Window.Panes";
-const char M_WINDOW_MODESTYLES[] = "QtCreator.Menu.Window.ModeStyles";
-const char M_WINDOW_VIEWS[] = "QtCreator.Menu.Window.Views";
const char M_HELP[] = "QtCreator.Menu.Help";
// Contexts
@@ -174,12 +175,15 @@ const char G_EDIT_BLOCKS[] = "QtCreator.Group.Edit.Blocks";
const char G_EDIT_FONT[] = "QtCreator.Group.Edit.Font";
const char G_EDIT_EDITOR[] = "QtCreator.Group.Edit.Editor";
+// View menu groups
+const char G_VIEW_VIEWS[] = "QtCreator.Group.View.Views";
+const char G_VIEW_PANES[] = "QtCreator.Group.View.Panes";
+
+// Tools menu groups
const char G_TOOLS_OPTIONS[] = "QtCreator.Group.Tools.Options";
// Window menu groups
const char G_WINDOW_SIZE[] = "QtCreator.Group.Window.Size";
-const char G_WINDOW_PANES[] = "QtCreator.Group.Window.Panes";
-const char G_WINDOW_VIEWS[] = "QtCreator.Group.Window.Views";
const char G_WINDOW_SPLIT[] = "QtCreator.Group.Window.Split";
const char G_WINDOW_NAVIGATE[] = "QtCreator.Group.Window.Navigate";
const char G_WINDOW_LIST[] = "QtCreator.Group.Window.List";
diff --git a/src/plugins/coreplugin/coreplugin.cpp b/src/plugins/coreplugin/coreplugin.cpp
index 1b3a80d7189..ba2b91cc6ed 100644
--- a/src/plugins/coreplugin/coreplugin.cpp
+++ b/src/plugins/coreplugin/coreplugin.cpp
@@ -274,23 +274,23 @@ void CorePlugin::addToPathChooserContextMenu(Utils::PathChooser *pathChooser, QM
QList<QAction*> actions = menu->actions();
QAction *firstAction = actions.isEmpty() ? nullptr : actions.first();
- if (QDir().exists(pathChooser->path())) {
+ if (QDir().exists(pathChooser->filePath().toString())) {
auto *showInGraphicalShell = new QAction(Core::FileUtils::msgGraphicalShellAction(), menu);
connect(showInGraphicalShell, &QAction::triggered, pathChooser, [pathChooser]() {
- Core::FileUtils::showInGraphicalShell(pathChooser, pathChooser->path());
+ Core::FileUtils::showInGraphicalShell(pathChooser, pathChooser->filePath().toString());
});
menu->insertAction(firstAction, showInGraphicalShell);
auto *showInTerminal = new QAction(Core::FileUtils::msgTerminalHereAction(), menu);
connect(showInTerminal, &QAction::triggered, pathChooser, [pathChooser]() {
- Core::FileUtils::openTerminal(pathChooser->path());
+ Core::FileUtils::openTerminal(pathChooser->filePath().toString());
});
menu->insertAction(firstAction, showInTerminal);
} else {
auto *mkPathAct = new QAction(tr("Create Folder"), menu);
connect(mkPathAct, &QAction::triggered, pathChooser, [pathChooser]() {
- QDir().mkpath(pathChooser->path());
+ QDir().mkpath(pathChooser->filePath().toString());
pathChooser->triggerChanged();
});
menu->insertAction(firstAction, mkPathAct);
diff --git a/src/plugins/coreplugin/coreplugin.h b/src/plugins/coreplugin/coreplugin.h
index fee1458eabd..04c0b1d1178 100644
--- a/src/plugins/coreplugin/coreplugin.h
+++ b/src/plugins/coreplugin/coreplugin.h
@@ -76,6 +76,8 @@ private slots:
// Locator:
void test_basefilefilter();
void test_basefilefilter_data();
+
+ void testOutputFormatter();
#endif
private:
diff --git a/src/plugins/coreplugin/coreplugin.pro b/src/plugins/coreplugin/coreplugin.pro
index 0a11dafc56b..495d02028a8 100644
--- a/src/plugins/coreplugin/coreplugin.pro
+++ b/src/plugins/coreplugin/coreplugin.pro
@@ -112,7 +112,8 @@ SOURCES += corejsextensions.cpp \
coreicons.cpp \
diffservice.cpp \
menubarfilter.cpp \
- welcomepagehelper.cpp
+ welcomepagehelper.cpp \
+ dialogs/codecselector.cpp
HEADERS += corejsextensions.h \
mainwindow.h \
@@ -228,7 +229,8 @@ HEADERS += corejsextensions.h \
diffservice.h \
menubarfilter.h \
editormanager/ieditorfactory_p.h \
- welcomepagehelper.h
+ welcomepagehelper.h \
+ dialogs/codecselector.h
FORMS += dialogs/newdialog.ui \
dialogs/saveitemsdialog.ui \
diff --git a/src/plugins/coreplugin/coreplugin.qbs b/src/plugins/coreplugin/coreplugin.qbs
index 8afe2f0aa8f..63dcd4ab6f3 100644
--- a/src/plugins/coreplugin/coreplugin.qbs
+++ b/src/plugins/coreplugin/coreplugin.qbs
@@ -1,5 +1,6 @@
import qbs 1.0
import qbs.FileInfo
+import qbs.Utilities
Project {
name: "Core"
@@ -17,15 +18,13 @@ Project {
condition: qbs.targetOS.contains("windows")
}
- Depends { name: "Qt.script"; required: false }
-
Depends { name: "Utils" }
Depends { name: "Aggregation" }
Depends { name: "app_version_header" }
Properties {
- condition: Qt.script.present
+ condition: Utilities.versionCompare(Qt.qml.version, "5.14.0") >= 0
cpp.defines: base.concat("WITH_JAVASCRIPTFILTER")
}
@@ -209,6 +208,7 @@ Project {
prefix: "dialogs/"
files: [
"addtovcsdialog.cpp", "addtovcsdialog.h", "addtovcsdialog.ui",
+ "codecselector.cpp", "codecselector.h",
"externaltoolconfig.cpp", "externaltoolconfig.h", "externaltoolconfig.ui",
"filepropertiesdialog.cpp", "filepropertiesdialog.h", "filepropertiesdialog.ui",
"ioptionspage.cpp", "ioptionspage.h",
@@ -379,7 +379,7 @@ Project {
Group {
name: "Locator Javascript Filter"
- condition: Qt.script.present
+ condition: Utilities.versionCompare(Qt.qml.version, "5.14.0") >= 0
prefix: "locator/"
files: [
"javascriptfilter.cpp",
diff --git a/src/plugins/coreplugin/dialogs/codecselector.cpp b/src/plugins/coreplugin/dialogs/codecselector.cpp
new file mode 100644
index 00000000000..a84b0fa1122
--- /dev/null
+++ b/src/plugins/coreplugin/dialogs/codecselector.cpp
@@ -0,0 +1,161 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "codecselector.h"
+#include <coreplugin/textdocument.h>
+
+#include <utils/algorithm.h>
+#include <utils/fileutils.h>
+#include <utils/itemviews.h>
+
+#include <QDebug>
+#include <QTextCodec>
+#include <QPushButton>
+#include <QScrollBar>
+#include <QVBoxLayout>
+
+namespace Core {
+
+/* custom class to make sure the width is wide enough for the
+ * contents. Should be easier with Qt. */
+class CodecListWidget : public Utils::ListWidget
+{
+public:
+ CodecListWidget(QWidget *parent) : Utils::ListWidget(parent){}
+ QSize sizeHint() const override {
+ return QListWidget::sizeHint().expandedTo(
+ QSize(sizeHintForColumn(0) + verticalScrollBar()->sizeHint().width() + 4, 0));
+ }
+};
+
+CodecSelector::CodecSelector(QWidget *parent, Core::BaseTextDocument *doc)
+ : QDialog(parent)
+{
+ m_hasDecodingError = doc->hasDecodingError();
+ m_isModified = doc->isModified();
+
+ QByteArray buf;
+ if (m_hasDecodingError)
+ buf = doc->decodingErrorSample();
+
+ setWindowTitle(tr("Text Encoding"));
+ m_label = new QLabel(this);
+ QString decodingErrorHint;
+ if (m_hasDecodingError)
+ decodingErrorHint = QLatin1Char('\n') + tr("The following encodings are likely to fit:");
+ m_label->setText(tr("Select encoding for \"%1\".%2")
+ .arg(doc->filePath().fileName())
+ .arg(decodingErrorHint));
+
+ m_listWidget = new CodecListWidget(this);
+ m_listWidget->setActivationMode(Utils::DoubleClickActivation);
+
+ QStringList encodings;
+
+ QList<int> mibs = QTextCodec::availableMibs();
+ Utils::sort(mibs);
+ QList<int> sortedMibs;
+ foreach (int mib, mibs)
+ if (mib >= 0)
+ sortedMibs += mib;
+ foreach (int mib, mibs)
+ if (mib < 0)
+ sortedMibs += mib;
+
+ int currentIndex = -1;
+ foreach (int mib, sortedMibs) {
+ QTextCodec *c = QTextCodec::codecForMib(mib);
+ if (!buf.isEmpty()) {
+
+ // slow, should use a feature from QTextCodec or QTextDecoder (but those are broken currently)
+ QByteArray verifyBuf = c->fromUnicode(c->toUnicode(buf));
+ // the minSize trick lets us ignore unicode headers
+ int minSize = qMin(verifyBuf.size(), buf.size());
+ if (minSize < buf.size() - 4
+ || memcmp(verifyBuf.constData() + verifyBuf.size() - minSize,
+ buf.constData() + buf.size() - minSize, minSize))
+ continue;
+ }
+ QString names = QString::fromLatin1(c->name());
+ foreach (const QByteArray &alias, c->aliases())
+ names += QLatin1String(" / ") + QString::fromLatin1(alias);
+ if (doc->codec() == c)
+ currentIndex = encodings.count();
+ encodings << names;
+ }
+ m_listWidget->addItems(encodings);
+ if (currentIndex >= 0)
+ m_listWidget->setCurrentRow(currentIndex);
+
+ connect(m_listWidget, &QListWidget::itemSelectionChanged, this, &CodecSelector::updateButtons);
+
+ m_dialogButtonBox = new QDialogButtonBox(this);
+ m_reloadButton = m_dialogButtonBox->addButton(tr("Reload with Encoding"), QDialogButtonBox::DestructiveRole);
+ m_saveButton = m_dialogButtonBox->addButton(tr("Save with Encoding"), QDialogButtonBox::DestructiveRole);
+ m_dialogButtonBox->addButton(QDialogButtonBox::Cancel);
+ connect(m_dialogButtonBox, &QDialogButtonBox::clicked, this, &CodecSelector::buttonClicked);
+ connect(m_listWidget, &QAbstractItemView::activated, m_reloadButton, &QAbstractButton::click);
+
+ auto vbox = new QVBoxLayout(this);
+ vbox->addWidget(m_label);
+ vbox->addWidget(m_listWidget);
+ vbox->addWidget(m_dialogButtonBox);
+
+ updateButtons();
+}
+
+CodecSelector::~CodecSelector() = default;
+
+void CodecSelector::updateButtons()
+{
+ bool hasCodec = (selectedCodec() != nullptr);
+ m_reloadButton->setEnabled(!m_isModified && hasCodec);
+ m_saveButton->setEnabled(!m_hasDecodingError && hasCodec);
+}
+
+QTextCodec *CodecSelector::selectedCodec() const
+{
+ if (QListWidgetItem *item = m_listWidget->currentItem()) {
+ if (!item->isSelected())
+ return nullptr;
+ QString codecName = item->text();
+ if (codecName.contains(QLatin1String(" / ")))
+ codecName = codecName.left(codecName.indexOf(QLatin1String(" / ")));
+ return QTextCodec::codecForName(codecName.toLatin1());
+ }
+ return nullptr;
+}
+
+void CodecSelector::buttonClicked(QAbstractButton *button)
+{
+ Result result = Cancel;
+ if (button == m_reloadButton)
+ result = Reload;
+ if (button == m_saveButton)
+ result = Save;
+ done(result);
+}
+
+} // namespace Core
diff --git a/src/plugins/coreplugin/dialogs/codecselector.h b/src/plugins/coreplugin/dialogs/codecselector.h
new file mode 100644
index 00000000000..f2495bcc051
--- /dev/null
+++ b/src/plugins/coreplugin/dialogs/codecselector.h
@@ -0,0 +1,69 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "../core_global.h"
+
+#include <QDialog>
+#include <QLabel>
+#include <QDialogButtonBox>
+#include <QListWidget>
+
+namespace Utils { class ListWidget; }
+namespace Core { class BaseTextDocument; }
+
+namespace Core {
+
+class CORE_EXPORT CodecSelector : public QDialog
+{
+ Q_OBJECT
+
+public:
+
+ CodecSelector(QWidget *parent, Core::BaseTextDocument *doc);
+ ~CodecSelector() override;
+
+ QTextCodec *selectedCodec() const;
+
+ // Enumeration returned from QDialog::exec()
+ enum Result {
+ Cancel, Reload, Save
+ };
+
+private:
+ void updateButtons();
+ void buttonClicked(QAbstractButton *button);
+
+ bool m_hasDecodingError;
+ bool m_isModified;
+ QLabel *m_label;
+ Utils::ListWidget *m_listWidget;
+ QDialogButtonBox *m_dialogButtonBox;
+ QAbstractButton *m_reloadButton;
+ QAbstractButton *m_saveButton;
+};
+
+} // namespace Core
diff --git a/src/plugins/coreplugin/dialogs/ioptionspage.cpp b/src/plugins/coreplugin/dialogs/ioptionspage.cpp
index be700875ad4..80ce45d6521 100644
--- a/src/plugins/coreplugin/dialogs/ioptionspage.cpp
+++ b/src/plugins/coreplugin/dialogs/ioptionspage.cpp
@@ -36,6 +36,7 @@
#include <QIcon>
#include <QLabel>
#include <QPushButton>
+#include <QRegularExpression>
using namespace Utils;
@@ -227,11 +228,11 @@ const QList<Core::IOptionsPage *> Core::IOptionsPage::allOptionsPages()
}
/*!
- Is used by the \uicontrol Options dialog search filter to match \a searchKeyWord to this options
+ Is used by the \uicontrol Options dialog search filter to match \a regexp to this options
page. This defaults to take the widget and then looks for all child labels, check boxes, push
buttons, and group boxes. Should return \c true when a match is found.
*/
-bool Core::IOptionsPage::matches(const QString &searchKeyWord) const
+bool Core::IOptionsPage::matches(const QRegularExpression &regexp) const
{
if (!m_keywordsInitialized) {
auto that = const_cast<IOptionsPage *>(this);
@@ -251,7 +252,7 @@ bool Core::IOptionsPage::matches(const QString &searchKeyWord) const
m_keywordsInitialized = true;
}
foreach (const QString &keyword, m_keywords)
- if (keyword.contains(searchKeyWord, Qt::CaseInsensitive))
+ if (keyword.contains(regexp))
return true;
return false;
}
diff --git a/src/plugins/coreplugin/dialogs/ioptionspage.h b/src/plugins/coreplugin/dialogs/ioptionspage.h
index e3ceab0fd0c..bef965628dd 100644
--- a/src/plugins/coreplugin/dialogs/ioptionspage.h
+++ b/src/plugins/coreplugin/dialogs/ioptionspage.h
@@ -64,7 +64,7 @@ public:
using WidgetCreator = std::function<IOptionsPageWidget *()>;
void setWidgetCreator(const WidgetCreator &widgetCreator);
- virtual bool matches(const QString &searchKeyWord) const;
+ virtual bool matches(const QRegularExpression &regexp) const;
virtual QWidget *widget();
virtual void apply();
virtual void finish();
@@ -112,7 +112,7 @@ public:
QIcon categoryIcon() const;
virtual QList<IOptionsPage *> pages() const = 0;
- virtual bool matches(const QString & /* searchKeyWord*/) const = 0;
+ virtual bool matches(const QRegularExpression &regexp) const = 0;
protected:
void setCategory(Id category) { m_category = category; }
diff --git a/src/plugins/coreplugin/dialogs/settingsdialog.cpp b/src/plugins/coreplugin/dialogs/settingsdialog.cpp
index 459af08a4ae..192d0843ede 100644
--- a/src/plugins/coreplugin/dialogs/settingsdialog.cpp
+++ b/src/plugins/coreplugin/dialogs/settingsdialog.cpp
@@ -262,19 +262,18 @@ bool CategoryFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sou
if (QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent))
return true;
- const QString pattern = filterRegExp().pattern();
+ const QRegularExpression regex = filterRegularExpression();
const CategoryModel *cm = static_cast<CategoryModel*>(sourceModel());
const Category *category = cm->categories().at(sourceRow);
for (const IOptionsPage *page : category->pages) {
- if (page->displayCategory().contains(pattern, Qt::CaseInsensitive)
- || page->displayName().contains(pattern, Qt::CaseInsensitive)
- || page->matches(pattern))
+ if (page->displayCategory().contains(regex) || page->displayName().contains(regex)
+ || page->matches(regex))
return true;
}
if (!category->providerPagesCreated) {
for (const IOptionsPageProvider *provider : category->providers) {
- if (provider->matches(pattern))
+ if (provider->matches(regex))
return true;
}
}
@@ -469,8 +468,14 @@ SettingsDialog::SettingsDialog(QWidget *parent) :
// The order of the slot connection matters here, the filter slot
// opens the matching page after the model has filtered.
- connect(m_filterLineEdit, &Utils::FancyLineEdit::filterChanged,
- &m_proxyModel, &QSortFilterProxyModel::setFilterFixedString);
+ connect(m_filterLineEdit,
+ &Utils::FancyLineEdit::filterChanged,
+ &m_proxyModel,
+ [this](const QString &filter) {
+ m_proxyModel.setFilterRegularExpression(
+ QRegularExpression(QRegularExpression::escape(filter),
+ QRegularExpression::CaseInsensitiveOption));
+ });
connect(m_filterLineEdit, &Utils::FancyLineEdit::filterChanged,
this, &SettingsDialog::filter);
m_categoryList->setFocus();
@@ -631,12 +636,12 @@ void SettingsDialog::disconnectTabWidgets()
void SettingsDialog::updateEnabledTabs(Category *category, const QString &searchText)
{
int firstEnabledTab = -1;
+ const QRegularExpression regex(QRegularExpression::escape(searchText),
+ QRegularExpression::CaseInsensitiveOption);
for (int i = 0; i < category->pages.size(); ++i) {
const IOptionsPage *page = category->pages.at(i);
- const bool enabled = searchText.isEmpty()
- || page->category().toString().contains(searchText, Qt::CaseInsensitive)
- || page->displayName().contains(searchText, Qt::CaseInsensitive)
- || page->matches(searchText);
+ const bool enabled = searchText.isEmpty() || page->category().toString().contains(regex)
+ || page->displayName().contains(regex) || page->matches(regex);
category->tabWidget->setTabEnabled(i, enabled);
if (enabled && firstEnabledTab < 0)
firstEnabledTab = i;
diff --git a/src/plugins/coreplugin/dialogs/shortcutsettings.cpp b/src/plugins/coreplugin/dialogs/shortcutsettings.cpp
index 8f2cd60b50e..a2b26cc7015 100644
--- a/src/plugins/coreplugin/dialogs/shortcutsettings.cpp
+++ b/src/plugins/coreplugin/dialogs/shortcutsettings.cpp
@@ -33,6 +33,7 @@
#include <coreplugin/actionmanager/command_p.h>
#include <coreplugin/actionmanager/commandsfile.h>
+#include <utils/algorithm.h>
#include <utils/fancylineedit.h>
#include <utils/hostosinfo.h>
#include <utils/qtcassert.h>
@@ -50,6 +51,8 @@
Q_DECLARE_METATYPE(Core::Internal::ShortcutItem*)
+const char kSeparator[] = " | ";
+
static int translateModifiers(Qt::KeyboardModifiers state,
const QString &text)
{
@@ -70,6 +73,11 @@ static int translateModifiers(Qt::KeyboardModifiers state,
return result;
}
+static QList<QKeySequence> cleanKeys(const QList<QKeySequence> &ks)
+{
+ return Utils::filtered(ks, [](const QKeySequence &k) { return !k.isEmpty(); });
+}
+
static QString keySequenceToEditString(const QKeySequence &sequence)
{
QString text = sequence.toString(QKeySequence::PortableText);
@@ -82,9 +90,23 @@ static QString keySequenceToEditString(const QKeySequence &sequence)
return text;
}
+static QString keySequencesToEditString(const QList<QKeySequence> &sequence)
+{
+ return Utils::transform(cleanKeys(sequence), keySequenceToEditString).join(kSeparator);
+}
+
+static QString keySequencesToNativeString(const QList<QKeySequence> &sequence)
+{
+ return Utils::transform(cleanKeys(sequence),
+ [](const QKeySequence &k) {
+ return k.toString(QKeySequence::NativeText);
+ })
+ .join(kSeparator);
+}
+
static QKeySequence keySequenceFromEditString(const QString &editString)
{
- QString text = editString;
+ QString text = editString.trimmed();
if (Utils::HostOsInfo::isMacHost()) {
// adapt the modifier names
text.replace(QLatin1String("Opt"), QLatin1String("Alt"), Qt::CaseInsensitive);
@@ -238,85 +260,43 @@ private:
void initialize();
void handleCurrentCommandChanged(QTreeWidgetItem *current);
void resetToDefault();
- bool validateShortcutEdit() const;
- bool markCollisions(ShortcutItem *);
- void setKeySequence(const QKeySequence &key);
+ bool updateAndCheckForConflicts(const QKeySequence &key, int index) const;
+ bool markCollisions(ShortcutItem *, int index);
+ void markAllCollisions();
void showConflicts();
void clear();
+ void setupShortcutBox(ShortcutItem *scitem);
+
QList<ShortcutItem *> m_scitems;
QGroupBox *m_shortcutBox;
- Utils::FancyLineEdit *m_shortcutEdit;
- QLabel *m_warningLabel;
+ QGridLayout *m_shortcutLayout;
+ std::vector<std::unique_ptr<ShortcutInput>> m_shortcutInputs;
+ QPointer<QPushButton> m_addButton = nullptr;
};
ShortcutSettingsWidget::ShortcutSettingsWidget()
{
setPageTitle(tr("Keyboard Shortcuts"));
setTargetHeader(tr("Shortcut"));
+ setResetVisible(true);
connect(ActionManager::instance(), &ActionManager::commandListChanged,
this, &ShortcutSettingsWidget::initialize);
connect(this, &ShortcutSettingsWidget::currentCommandChanged,
this, &ShortcutSettingsWidget::handleCurrentCommandChanged);
+ connect(this,
+ &ShortcutSettingsWidget::resetRequested,
+ this,
+ &ShortcutSettingsWidget::resetToDefault);
m_shortcutBox = new QGroupBox(tr("Shortcut"), this);
m_shortcutBox->setEnabled(false);
- auto vboxLayout = new QVBoxLayout(m_shortcutBox);
- m_shortcutBox->setLayout(vboxLayout);
- auto hboxLayout = new QHBoxLayout;
- vboxLayout->addLayout(hboxLayout);
- m_shortcutEdit = new Utils::FancyLineEdit(m_shortcutBox);
- m_shortcutEdit->setFiltering(true);
- m_shortcutEdit->setPlaceholderText(tr("Enter key sequence as text"));
- auto shortcutLabel = new QLabel(tr("Key sequence:"));
- shortcutLabel->setToolTip(Utils::HostOsInfo::isMacHost()
- ? QLatin1String("<html><body>")
- + tr("Use \"Cmd\", \"Opt\", \"Ctrl\", and \"Shift\" for modifier keys. "
- "Use \"Escape\", \"Backspace\", \"Delete\", \"Insert\", \"Home\", and so on, for special keys. "
- "Combine individual keys with \"+\", "
- "and combine multiple shortcuts to a shortcut sequence with \",\". "
- "For example, if the user must hold the Ctrl and Shift modifier keys "
- "while pressing Escape, and then release and press A, "
- "enter \"Ctrl+Shift+Escape,A\".")
- + QLatin1String("</body></html>")
- : QLatin1String("<html><body>")
- + tr("Use \"Ctrl\", \"Alt\", \"Meta\", and \"Shift\" for modifier keys. "
- "Use \"Escape\", \"Backspace\", \"Delete\", \"Insert\", \"Home\", and so on, for special keys. "
- "Combine individual keys with \"+\", "
- "and combine multiple shortcuts to a shortcut sequence with \",\". "
- "For example, if the user must hold the Ctrl and Shift modifier keys "
- "while pressing Escape, and then release and press A, "
- "enter \"Ctrl+Shift+Escape,A\".")
- + QLatin1String("</body></html>"));
- auto shortcutButton = new ShortcutButton(m_shortcutBox);
- connect(shortcutButton, &ShortcutButton::keySequenceChanged,
- this, &ShortcutSettingsWidget::setKeySequence);
- auto resetButton = new QPushButton(tr("Reset"), m_shortcutBox);
- resetButton->setToolTip(tr("Reset to default."));
- connect(resetButton, &QPushButton::clicked,
- this, &ShortcutSettingsWidget::resetToDefault);
- hboxLayout->addWidget(shortcutLabel);
- hboxLayout->addWidget(m_shortcutEdit);
- hboxLayout->addWidget(shortcutButton);
- hboxLayout->addWidget(resetButton);
-
- m_warningLabel = new QLabel(m_shortcutBox);
- m_warningLabel->setTextFormat(Qt::RichText);
- QPalette palette = m_warningLabel->palette();
- palette.setColor(QPalette::Active, QPalette::WindowText,
- Utils::creatorTheme()->color(Utils::Theme::TextColorError));
- m_warningLabel->setPalette(palette);
- connect(m_warningLabel, &QLabel::linkActivated, this, &ShortcutSettingsWidget::showConflicts);
- vboxLayout->addWidget(m_warningLabel);
-
+ m_shortcutLayout = new QGridLayout(m_shortcutBox);
+ m_shortcutBox->setLayout(m_shortcutLayout);
layout()->addWidget(m_shortcutBox);
initialize();
-
- m_shortcutEdit->setValidationFunction([this](Utils::FancyLineEdit *, QString *) {
- return validateShortcutEdit();
- });
}
ShortcutSettingsWidget::~ShortcutSettingsWidget()
@@ -341,7 +321,7 @@ QWidget *ShortcutSettings::widget()
void ShortcutSettingsWidget::apply()
{
foreach (ShortcutItem *item, m_scitems)
- item->m_cmd->setKeySequence(item->m_key);
+ item->m_cmd->setKeySequences(item->m_keys);
}
void ShortcutSettings::apply()
@@ -366,60 +346,112 @@ void ShortcutSettingsWidget::handleCurrentCommandChanged(QTreeWidgetItem *curren
{
ShortcutItem *scitem = shortcutItem(current);
if (!scitem) {
- m_shortcutEdit->clear();
- m_warningLabel->clear();
+ m_shortcutInputs.clear();
+ delete m_addButton;
m_shortcutBox->setEnabled(false);
} else {
- setKeySequence(scitem->m_key);
- markCollisions(scitem);
+ // clean up before showing UI
+ scitem->m_keys = cleanKeys(scitem->m_keys);
+ setupShortcutBox(scitem);
m_shortcutBox->setEnabled(true);
}
}
-bool ShortcutSettingsWidget::validateShortcutEdit() const
+void ShortcutSettingsWidget::setupShortcutBox(ShortcutItem *scitem)
+{
+ const auto updateAddButton = [this] {
+ m_addButton->setEnabled(
+ !Utils::anyOf(m_shortcutInputs, [](const std::unique_ptr<ShortcutInput> &i) {
+ return i->keySequence().isEmpty();
+ }));
+ };
+ const auto addShortcutInput = [this, updateAddButton](int index, const QKeySequence &key) {
+ auto input = std::make_unique<ShortcutInput>();
+ input->addToLayout(m_shortcutLayout, index * 2);
+ input->setConflictChecker(
+ [this, index](const QKeySequence &k) { return updateAndCheckForConflicts(k, index); });
+ connect(input.get(),
+ &ShortcutInput::showConflictsRequested,
+ this,
+ &ShortcutSettingsWidget::showConflicts);
+ connect(input.get(), &ShortcutInput::changed, this, updateAddButton);
+ input->setKeySequence(key);
+ m_shortcutInputs.push_back(std::move(input));
+ };
+ const auto addButtonToLayout = [this, updateAddButton] {
+ m_shortcutLayout->addWidget(m_addButton,
+ m_shortcutInputs.size() * 2 - 1,
+ m_shortcutLayout->columnCount() - 1);
+ updateAddButton();
+ };
+ m_shortcutInputs.clear();
+ delete m_addButton;
+ m_addButton = new QPushButton(tr("Add"), this);
+ for (int i = 0; i < qMax(1, scitem->m_keys.size()); ++i)
+ addShortcutInput(i, scitem->m_keys.value(i));
+ connect(m_addButton, &QPushButton::clicked, this, [this, addShortcutInput, addButtonToLayout] {
+ addShortcutInput(m_shortcutInputs.size(), {});
+ addButtonToLayout();
+ });
+ addButtonToLayout();
+ updateAddButton();
+}
+
+static bool checkValidity(const QKeySequence &key, QString *warningMessage)
+{
+ if (key.isEmpty())
+ return true;
+ QTC_ASSERT(warningMessage, return true);
+ if (!keySequenceIsValid(key)) {
+ *warningMessage = ShortcutSettingsWidget::tr("Invalid key sequence.");
+ return false;
+ }
+ if (isTextKeySequence(key))
+ *warningMessage = ShortcutSettingsWidget::tr("Key sequence will not work in editor.");
+ return true;
+}
+
+bool ShortcutSettingsWidget::updateAndCheckForConflicts(const QKeySequence &key, int index) const
{
- m_warningLabel->clear();
QTreeWidgetItem *current = commandList()->currentItem();
ShortcutItem *item = shortcutItem(current);
if (!item)
- return true;
- bool valid = false;
-
- const QString text = m_shortcutEdit->text().trimmed();
- const QKeySequence currentKey = keySequenceFromEditString(text);
-
- if (keySequenceIsValid(currentKey) || text.isEmpty()) {
- item->m_key = currentKey;
- auto that = const_cast<ShortcutSettingsWidget *>(this);
- if (item->m_cmd->defaultKeySequence() != item->m_key)
- that->setModified(current, true);
- else
- that->setModified(current, false);
- current->setText(2, item->m_key.toString(QKeySequence::NativeText));
- valid = !that->markCollisions(item);
- if (!valid) {
- m_warningLabel->setText(
- tr("Key sequence has potential conflicts. <a href=\"#conflicts\">Show.</a>"));
- } else if (isTextKeySequence(currentKey)) {
- m_warningLabel->setText(tr("Key sequence will not work in editor."));
- }
- } else {
- m_warningLabel->setText(m_warningLabel->text() + tr("Invalid key sequence."));
- }
- return valid;
+ return false;
+
+ while (index >= item->m_keys.size())
+ item->m_keys.append(QKeySequence());
+ item->m_keys[index] = key;
+ auto that = const_cast<ShortcutSettingsWidget *>(this);
+ if (cleanKeys(item->m_keys) != item->m_cmd->defaultKeySequences())
+ that->setModified(current, true);
+ else
+ that->setModified(current, false);
+ current->setText(2, keySequencesToNativeString(item->m_keys));
+ return that->markCollisions(item, index);
}
bool ShortcutSettingsWidget::filterColumn(const QString &filterString, QTreeWidgetItem *item,
int column) const
{
- QString text;
- ShortcutItem *scitem = shortcutItem(item);
+ const ShortcutItem *scitem = shortcutItem(item);
if (column == item->columnCount() - 1) { // shortcut
// filter on the shortcut edit text
if (!scitem)
return true;
- text = keySequenceToEditString(scitem->m_key);
- } else if (column == 0 && scitem) { // command id
+ const QStringList filters = Utils::transform(filterString.split(kSeparator),
+ [](const QString &s) { return s.trimmed(); });
+ for (const QKeySequence &k : scitem->m_keys) {
+ const QString &keyString = keySequenceToEditString(k);
+ const bool found = Utils::anyOf(filters, [keyString](const QString &f) {
+ return keyString.contains(f, Qt::CaseInsensitive);
+ });
+ if (found)
+ return false;
+ }
+ return true;
+ }
+ QString text;
+ if (column == 0 && scitem) { // command id
text = scitem->m_cmd->id().toString();
} else {
text = item->text(column);
@@ -427,17 +459,12 @@ bool ShortcutSettingsWidget::filterColumn(const QString &filterString, QTreeWidg
return !text.contains(filterString, Qt::CaseInsensitive);
}
-void ShortcutSettingsWidget::setKeySequence(const QKeySequence &key)
-{
- m_shortcutEdit->setText(keySequenceToEditString(key));
-}
-
void ShortcutSettingsWidget::showConflicts()
{
QTreeWidgetItem *current = commandList()->currentItem();
ShortcutItem *scitem = shortcutItem(current);
if (scitem)
- setFilterText(keySequenceToEditString(scitem->m_key));
+ setFilterText(keySequencesToEditString(scitem->m_keys));
}
void ShortcutSettingsWidget::resetToDefault()
@@ -445,9 +472,9 @@ void ShortcutSettingsWidget::resetToDefault()
QTreeWidgetItem *current = commandList()->currentItem();
ShortcutItem *scitem = shortcutItem(current);
if (scitem) {
- setKeySequence(scitem->m_cmd->defaultKeySequence());
- foreach (ShortcutItem *item, m_scitems)
- markCollisions(item);
+ scitem->m_keys = scitem->m_cmd->defaultKeySequences();
+ setupShortcutBox(scitem);
+ markAllCollisions();
}
}
@@ -459,40 +486,35 @@ void ShortcutSettingsWidget::importAction()
if (!fileName.isEmpty()) {
CommandsFile cf(fileName);
- QMap<QString, QKeySequence> mapping = cf.importCommands();
-
- foreach (ShortcutItem *item, m_scitems) {
+ QMap<QString, QList<QKeySequence>> mapping = cf.importCommands();
+ for (ShortcutItem *item : qAsConst(m_scitems)) {
QString sid = item->m_cmd->id().toString();
if (mapping.contains(sid)) {
- item->m_key = mapping.value(sid);
- item->m_item->setText(2, item->m_key.toString(QKeySequence::NativeText));
+ item->m_keys = mapping.value(sid);
+ item->m_item->setText(2, keySequencesToNativeString(item->m_keys));
if (item->m_item == commandList()->currentItem())
emit currentCommandChanged(item->m_item);
- if (item->m_cmd->defaultKeySequence() != item->m_key)
+ if (item->m_keys != item->m_cmd->defaultKeySequences())
setModified(item->m_item, true);
else
setModified(item->m_item, false);
}
}
-
- foreach (ShortcutItem *item, m_scitems)
- markCollisions(item);
+ markAllCollisions();
}
}
void ShortcutSettingsWidget::defaultAction()
{
foreach (ShortcutItem *item, m_scitems) {
- item->m_key = item->m_cmd->defaultKeySequence();
- item->m_item->setText(2, item->m_key.toString(QKeySequence::NativeText));
+ item->m_keys = item->m_cmd->defaultKeySequences();
+ item->m_item->setText(2, keySequencesToNativeString(item->m_keys));
setModified(item->m_item, false);
if (item->m_item == commandList()->currentItem())
emit currentCommandChanged(item->m_item);
}
-
- foreach (ShortcutItem *item, m_scitems)
- markCollisions(item);
+ markAllCollisions();
}
void ShortcutSettingsWidget::exportAction()
@@ -549,54 +571,155 @@ void ShortcutSettingsWidget::initialize()
}
sections[section]->addChild(item);
- s->m_key = c->keySequence();
+ s->m_keys = c->keySequences();
item->setText(0, subId);
item->setText(1, c->description());
- item->setText(2, s->m_key.toString(QKeySequence::NativeText));
- if (s->m_cmd->defaultKeySequence() != s->m_key)
+ item->setText(2, keySequencesToNativeString(s->m_keys));
+ if (s->m_keys != s->m_cmd->defaultKeySequences())
setModified(item, true);
item->setData(0, Qt::UserRole, QVariant::fromValue(s));
-
- markCollisions(s);
}
+ markAllCollisions();
filterChanged(filterText());
}
-bool ShortcutSettingsWidget::markCollisions(ShortcutItem *item)
+bool ShortcutSettingsWidget::markCollisions(ShortcutItem *item, int index)
{
bool hasCollision = false;
- if (!item->m_key.isEmpty()) {
+ const QKeySequence key = item->m_keys.value(index);
+ if (!key.isEmpty()) {
Id globalId(Constants::C_GLOBAL);
const Context itemContext = item->m_cmd->context();
const bool itemHasGlobalContext = itemContext.contains(globalId);
- foreach (ShortcutItem *currentItem, m_scitems) {
- if (currentItem->m_key.isEmpty() || item == currentItem
- || item->m_key != currentItem->m_key)
+ for (ShortcutItem *currentItem : qAsConst(m_scitems)) {
+ if (item == currentItem)
+ continue;
+ if (!Utils::anyOf(currentItem->m_keys, Utils::equalTo(key)))
continue;
+ // check if contexts might conflict
const Context currentContext = currentItem->m_cmd->context();
bool currentIsConflicting = (itemHasGlobalContext && !currentContext.isEmpty());
if (!currentIsConflicting) {
- foreach (const Id &id, currentContext) {
- if ((id == globalId && !itemContext.isEmpty())
- || itemContext.contains(id)) {
+ for (const Id &id : currentContext) {
+ if ((id == globalId && !itemContext.isEmpty()) || itemContext.contains(id)) {
currentIsConflicting = true;
break;
}
}
}
if (currentIsConflicting) {
- currentItem->m_item->setForeground(
- 2, Utils::creatorTheme()->color(Utils::Theme::TextColorError));
+ currentItem->m_item->setForeground(2,
+ Utils::creatorTheme()->color(
+ Utils::Theme::TextColorError));
hasCollision = true;
}
}
}
- item->m_item->setForeground(2, hasCollision
- ? Utils::creatorTheme()->color(Utils::Theme::TextColorError)
- : commandList()->palette().windowText());
+ item->m_item->setForeground(2,
+ hasCollision
+ ? Utils::creatorTheme()->color(Utils::Theme::TextColorError)
+ : commandList()->palette().windowText());
return hasCollision;
}
+void ShortcutSettingsWidget::markAllCollisions()
+{
+ for (ShortcutItem *item : qAsConst(m_scitems))
+ for (int i = 0; i < item->m_keys.size(); ++i)
+ markCollisions(item, i);
+}
+
+ShortcutInput::ShortcutInput()
+{
+ m_shortcutLabel = new QLabel(tr("Key sequence:"));
+ m_shortcutLabel->setToolTip(
+ Utils::HostOsInfo::isMacHost()
+ ? QLatin1String("<html><body>")
+ + tr("Use \"Cmd\", \"Opt\", \"Ctrl\", and \"Shift\" for modifier keys. "
+ "Use \"Escape\", \"Backspace\", \"Delete\", \"Insert\", \"Home\", and so "
+ "on, for special keys. "
+ "Combine individual keys with \"+\", "
+ "and combine multiple shortcuts to a shortcut sequence with \",\". "
+ "For example, if the user must hold the Ctrl and Shift modifier keys "
+ "while pressing Escape, and then release and press A, "
+ "enter \"Ctrl+Shift+Escape,A\".")
+ + QLatin1String("</body></html>")
+ : QLatin1String("<html><body>")
+ + tr("Use \"Ctrl\", \"Alt\", \"Meta\", and \"Shift\" for modifier keys. "
+ "Use \"Escape\", \"Backspace\", \"Delete\", \"Insert\", \"Home\", and so "
+ "on, for special keys. "
+ "Combine individual keys with \"+\", "
+ "and combine multiple shortcuts to a shortcut sequence with \",\". "
+ "For example, if the user must hold the Ctrl and Shift modifier keys "
+ "while pressing Escape, and then release and press A, "
+ "enter \"Ctrl+Shift+Escape,A\".")
+ + QLatin1String("</body></html>"));
+
+ m_shortcutEdit = new Utils::FancyLineEdit;
+ m_shortcutEdit->setFiltering(true);
+ m_shortcutEdit->setPlaceholderText(tr("Enter key sequence as text"));
+ connect(m_shortcutEdit, &Utils::FancyLineEdit::textChanged, this, &ShortcutInput::changed);
+
+ m_shortcutButton = new ShortcutButton;
+ connect(m_shortcutButton,
+ &ShortcutButton::keySequenceChanged,
+ this,
+ [this](const QKeySequence &k) { setKeySequence(k); });
+
+ m_warningLabel = new QLabel;
+ m_warningLabel->setTextFormat(Qt::RichText);
+ QPalette palette = m_warningLabel->palette();
+ palette.setColor(QPalette::Active,
+ QPalette::WindowText,
+ Utils::creatorTheme()->color(Utils::Theme::TextColorError));
+ m_warningLabel->setPalette(palette);
+ connect(m_warningLabel, &QLabel::linkActivated, this, &ShortcutInput::showConflictsRequested);
+
+ m_shortcutEdit->setValidationFunction([this](Utils::FancyLineEdit *, QString *) {
+ QString warningMessage;
+ const QKeySequence key = keySequenceFromEditString(m_shortcutEdit->text());
+ const bool isValid = checkValidity(key, &warningMessage);
+ m_warningLabel->setText(warningMessage);
+ if (isValid && m_conflictChecker && m_conflictChecker(key)) {
+ m_warningLabel->setText(ShortcutSettingsWidget::tr(
+ "Key sequence has potential conflicts. <a href=\"#conflicts\">Show.</a>"));
+ }
+ return isValid;
+ });
+}
+
+ShortcutInput::~ShortcutInput()
+{
+ delete m_shortcutLabel;
+ delete m_shortcutEdit;
+ delete m_shortcutButton;
+ delete m_warningLabel;
+}
+
+void ShortcutInput::addToLayout(QGridLayout *layout, int row)
+{
+ layout->addWidget(m_shortcutLabel, row, 0);
+ layout->addWidget(m_shortcutEdit, row, 1);
+ layout->addWidget(m_shortcutButton, row, 2);
+
+ layout->addWidget(m_warningLabel, row + 1, 0, 1, 2);
+}
+
+void ShortcutInput::setKeySequence(const QKeySequence &key)
+{
+ m_shortcutEdit->setText(keySequenceToEditString(key));
+}
+
+QKeySequence ShortcutInput::keySequence() const
+{
+ return keySequenceFromEditString(m_shortcutEdit->text());
+}
+
+void ShortcutInput::setConflictChecker(const ShortcutInput::ConflictChecker &fun)
+{
+ m_conflictChecker = fun;
+}
+
} // namespace Internal
} // namespace Core
diff --git a/src/plugins/coreplugin/dialogs/shortcutsettings.h b/src/plugins/coreplugin/dialogs/shortcutsettings.h
index a49d45a6159..cee7f55c0b0 100644
--- a/src/plugins/coreplugin/dialogs/shortcutsettings.h
+++ b/src/plugins/coreplugin/dialogs/shortcutsettings.h
@@ -28,6 +28,7 @@
#include <coreplugin/actionmanager/commandmappings.h>
#include <coreplugin/dialogs/ioptionspage.h>
+#include <QGridLayout>
#include <QKeySequence>
#include <QPointer>
#include <QPushButton>
@@ -51,7 +52,7 @@ class ShortcutSettingsWidget;
struct ShortcutItem
{
Command *m_cmd;
- QKeySequence m_key;
+ QList<QKeySequence> m_keys;
QTreeWidgetItem *m_item;
};
@@ -80,6 +81,33 @@ private:
int m_keyNum = 0;
};
+class ShortcutInput : public QObject
+{
+ Q_OBJECT
+public:
+ ShortcutInput();
+ ~ShortcutInput();
+
+ void addToLayout(QGridLayout *layout, int row);
+
+ void setKeySequence(const QKeySequence &key);
+ QKeySequence keySequence() const;
+
+ using ConflictChecker = std::function<bool(QKeySequence)>;
+ void setConflictChecker(const ConflictChecker &fun);
+
+signals:
+ void changed();
+ void showConflictsRequested();
+
+private:
+ ConflictChecker m_conflictChecker;
+ QPointer<QLabel> m_shortcutLabel;
+ QPointer<Utils::FancyLineEdit> m_shortcutEdit;
+ QPointer<ShortcutButton> m_shortcutButton;
+ QPointer<QLabel> m_warningLabel;
+};
+
class ShortcutSettings final : public IOptionsPage
{
public:
diff --git a/src/plugins/coreplugin/documentmanager.cpp b/src/plugins/coreplugin/documentmanager.cpp
index f7a8340d9ef..f769b2484bc 100644
--- a/src/plugins/coreplugin/documentmanager.cpp
+++ b/src/plugins/coreplugin/documentmanager.cpp
@@ -30,6 +30,9 @@
#include "idocumentfactory.h"
#include "coreconstants.h"
+#include <coreplugin/actionmanager/actioncontainer.h>
+#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/actionmanager/command.h>
#include <coreplugin/diffservice.h>
#include <coreplugin/dialogs/filepropertiesdialog.h>
#include <coreplugin/dialogs/readonlyfilesdialog.h>
@@ -165,6 +168,8 @@ public:
void checkOnNextFocusChange();
void onApplicationFocusChange();
+ void registerSaveAllAction();
+
QMap<QString, FileState> m_states; // filePathKey -> FileState
QSet<QString> m_changedFiles; // watched file paths collected from file watcher notifications
QList<IDocument *> m_documentsWithoutWatch;
@@ -188,6 +193,8 @@ public:
// signal
// That makes the code easier
IDocument *m_blockedIDocument = nullptr;
+
+ QAction *m_saveAllAction;
};
static DocumentManager *m_instance;
@@ -231,7 +238,20 @@ void DocumentManagerPrivate::onApplicationFocusChange()
m_instance->checkForReload();
}
-DocumentManagerPrivate::DocumentManagerPrivate()
+void DocumentManagerPrivate::registerSaveAllAction()
+{
+ ActionContainer *mfile = ActionManager::actionContainer(Constants::M_FILE);
+ Command *cmd = ActionManager::registerAction(m_saveAllAction, Constants::SAVEALL);
+ cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? QString() : tr("Ctrl+Shift+S")));
+ mfile->addAction(cmd, Constants::G_FILE_SAVE);
+ m_saveAllAction->setEnabled(false);
+ connect(m_saveAllAction, &QAction::triggered, []() {
+ DocumentManager::saveAllModifiedDocumentsSilently();
+ });
+}
+
+DocumentManagerPrivate::DocumentManagerPrivate() :
+ m_saveAllAction(new QAction(tr("Save A&ll"), this))
{
// we do not want to do too much directly in the focus change event, so queue the connection
connect(qApp,
@@ -255,7 +275,7 @@ DocumentManager::DocumentManager(QObject *parent)
m_instance = this;
connect(Utils::GlobalFileChangeBlocker::instance(), &Utils::GlobalFileChangeBlocker::stateChanged,
- this, [this](bool blocked) {
+ this, [](bool blocked) {
d->m_postponeAutoReload = blocked;
if (!blocked)
QTimer::singleShot(500, m_instance, &DocumentManager::checkForReload);
@@ -348,6 +368,7 @@ void DocumentManager::addDocuments(const QList<IDocument *> &documents, bool add
m_instance, &DocumentManager::documentDestroyed);
connect(document, &IDocument::filePathChanged,
m_instance, &DocumentManager::filePathChanged);
+ connect(document, &IDocument::changed, m_instance, &DocumentManager::updateSaveAll);
d->m_documentsWithoutWatch.append(document);
}
}
@@ -360,6 +381,7 @@ void DocumentManager::addDocuments(const QList<IDocument *> &documents, bool add
connect(document, &QObject::destroyed, m_instance, &DocumentManager::documentDestroyed);
connect(document, &IDocument::filePathChanged,
m_instance, &DocumentManager::filePathChanged);
+ connect(document, &IDocument::changed, m_instance, &DocumentManager::updateSaveAll);
addFileInfo(document);
}
}
@@ -473,6 +495,11 @@ void DocumentManager::filePathChanged(const FilePath &oldName, const FilePath &n
emit m_instance->documentRenamed(doc, oldName.toString(), newName.toString());
}
+void DocumentManager::updateSaveAll()
+{
+ d->m_saveAllAction->setEnabled(!modifiedDocuments().empty());
+}
+
/*!
Adds \a document to the collection. If \a addWatcher is \c true
(the default), the document's file is added to a file system watcher
@@ -509,6 +536,7 @@ bool DocumentManager::removeDocument(IDocument *document)
disconnect(document, &IDocument::changed, m_instance, &DocumentManager::checkForNewFileName);
}
disconnect(document, &QObject::destroyed, m_instance, &DocumentManager::documentDestroyed);
+ disconnect(document, &IDocument::changed, m_instance, &DocumentManager::updateSaveAll);
return addWatcher;
}
@@ -640,7 +668,7 @@ static bool saveModifiedFilesHelper(const QList<IDocument *> &documents,
QList<IDocument *> modifiedDocuments;
foreach (IDocument *document, documents) {
- if (document && document->isModified()) {
+ if (document && document->isModified() && !document->isTemporary()) {
QString name = document->filePath().toString();
if (name.isEmpty())
name = document->fallbackSaveAsFileName();
@@ -739,6 +767,7 @@ bool DocumentManager::saveDocument(IDocument *document, const QString &fileName,
addDocument(document, addWatcher);
unexpectFileChange(effName);
+ m_instance->updateSaveAll();
return ret;
}
@@ -1501,6 +1530,11 @@ void DocumentManager::notifyFilesChangedInternally(const QStringList &files)
emit m_instance->filesChangedInternally(files);
}
+void DocumentManager::registerSaveAllAction()
+{
+ d->registerSaveAllAction();
+}
+
// -------------- FileChangeBlocker
/*!
diff --git a/src/plugins/coreplugin/documentmanager.h b/src/plugins/coreplugin/documentmanager.h
index 66ca54cd2e1..9e11d7cf424 100644
--- a/src/plugins/coreplugin/documentmanager.h
+++ b/src/plugins/coreplugin/documentmanager.h
@@ -162,6 +162,8 @@ private:
void checkForReload();
void changedFile(const QString &file);
void filePathChanged(const Utils::FilePath &oldName, const Utils::FilePath &newName);
+ void updateSaveAll();
+ static void registerSaveAllAction();
friend class Core::Internal::MainWindow;
friend class Core::Internal::DocumentManagerPrivate;
diff --git a/src/plugins/coreplugin/find/searchresultwidget.cpp b/src/plugins/coreplugin/find/searchresultwidget.cpp
index 23b894ef9ae..bfa941edae1 100644
--- a/src/plugins/coreplugin/find/searchresultwidget.cpp
+++ b/src/plugins/coreplugin/find/searchresultwidget.cpp
@@ -176,8 +176,8 @@ SearchResultWidget::SearchResultWidget(QWidget *parent) :
m_preserveCaseCheck = new QCheckBox(m_topReplaceWidget);
m_preserveCaseCheck->setText(tr("Preser&ve case"));
m_preserveCaseCheck->setEnabled(false);
- m_renameFilesCheckBox = new QCheckBox(m_topReplaceWidget);
- m_renameFilesCheckBox->setVisible(false);
+ m_additionalReplaceWidget = new QWidget(m_topReplaceWidget);
+ m_additionalReplaceWidget->setVisible(false);
m_replaceButton = new QToolButton(m_topReplaceWidget);
m_replaceButton->setToolTip(tr("Replace all occurrences."));
m_replaceButton->setText(tr("&Replace"));
@@ -198,7 +198,7 @@ SearchResultWidget::SearchResultWidget(QWidget *parent) :
topReplaceLayout->addWidget(m_replaceLabel);
topReplaceLayout->addWidget(m_replaceTextEdit);
topReplaceLayout->addWidget(m_preserveCaseCheck);
- topReplaceLayout->addWidget(m_renameFilesCheckBox);
+ topReplaceLayout->addWidget(m_additionalReplaceWidget);
topReplaceLayout->addWidget(m_replaceButton);
topReplaceLayout->addStretch(2);
setShowReplaceUI(m_replaceSupported);
@@ -208,6 +208,8 @@ SearchResultWidget::SearchResultWidget(QWidget *parent) :
this, &SearchResultWidget::handleJumpToSearchResult);
connect(m_replaceTextEdit, &QLineEdit::returnPressed,
this, &SearchResultWidget::handleReplaceButton);
+ connect(m_replaceTextEdit, &QLineEdit::textChanged,
+ this, &SearchResultWidget::replaceTextChanged);
connect(m_replaceButton, &QAbstractButton::clicked,
this, &SearchResultWidget::handleReplaceButton);
}
@@ -229,7 +231,16 @@ void SearchResultWidget::setInfo(const QString &label, const QString &toolTip, c
QWidget *SearchResultWidget::additionalReplaceWidget() const
{
- return m_renameFilesCheckBox;
+ return m_additionalReplaceWidget;
+}
+
+void SearchResultWidget::setAdditionalReplaceWidget(QWidget *widget)
+{
+ if (QLayoutItem *item = m_topReplaceWidget->layout()->replaceWidget(m_additionalReplaceWidget,
+ widget))
+ delete item;
+ delete m_additionalReplaceWidget;
+ m_additionalReplaceWidget = widget;
}
void SearchResultWidget::addResult(const QString &fileName,
diff --git a/src/plugins/coreplugin/find/searchresultwidget.h b/src/plugins/coreplugin/find/searchresultwidget.h
index 22193a231e6..ee91ded1322 100644
--- a/src/plugins/coreplugin/find/searchresultwidget.h
+++ b/src/plugins/coreplugin/find/searchresultwidget.h
@@ -54,6 +54,7 @@ public:
void setInfo(const QString &label, const QString &toolTip, const QString &term);
QWidget *additionalReplaceWidget() const;
+ void setAdditionalReplaceWidget(QWidget *widget);
void addResult(const QString &fileName,
const QString &lineText,
@@ -97,6 +98,7 @@ public slots:
signals:
void activated(const Core::SearchResultItem &item);
void replaceButtonClicked(const QString &replaceText, const QList<Core::SearchResultItem> &checkedItems, bool preserveCase);
+ void replaceTextChanged(const QString &replaceText);
void searchAgainRequested();
void cancelled();
void paused(bool paused);
@@ -131,7 +133,7 @@ private:
QToolButton *m_replaceButton = nullptr;
QToolButton *m_searchAgainButton = nullptr;
QCheckBox *m_preserveCaseCheck = nullptr;
- QCheckBox *m_renameFilesCheckBox = nullptr;
+ QWidget *m_additionalReplaceWidget = nullptr;
QWidget *m_descriptionContainer = nullptr;
QLabel *m_label = nullptr;
QLabel *m_searchTerm = nullptr;
diff --git a/src/plugins/coreplugin/find/searchresultwindow.cpp b/src/plugins/coreplugin/find/searchresultwindow.cpp
index 8f973dbe4c4..2b3723c4f62 100644
--- a/src/plugins/coreplugin/find/searchresultwindow.cpp
+++ b/src/plugins/coreplugin/find/searchresultwindow.cpp
@@ -708,6 +708,8 @@ SearchResult::SearchResult(SearchResultWidget *widget)
connect(widget, &SearchResultWidget::activated, this, &SearchResult::activated);
connect(widget, &SearchResultWidget::replaceButtonClicked,
this, &SearchResult::replaceButtonClicked);
+ connect(widget, &SearchResultWidget::replaceTextChanged,
+ this, &SearchResult::replaceTextChanged);
connect(widget, &SearchResultWidget::cancelled, this, &SearchResult::cancelled);
connect(widget, &SearchResultWidget::paused, this, &SearchResult::paused);
connect(widget, &SearchResultWidget::visibilityChanged,
@@ -770,6 +772,14 @@ QWidget *SearchResult::additionalReplaceWidget() const
}
/*!
+ Sets a UI for a global search and replace action.
+*/
+void SearchResult::setAdditionalReplaceWidget(QWidget *widget)
+{
+ m_widget->setAdditionalReplaceWidget(widget);
+}
+
+/*!
Adds a single result line to the \uicontrol {Search Results} output pane.
\a fileName, \a lineNumber, and \a lineText are shown on the result line.
diff --git a/src/plugins/coreplugin/find/searchresultwindow.h b/src/plugins/coreplugin/find/searchresultwindow.h
index 90f20b47b3f..6a450cb596b 100644
--- a/src/plugins/coreplugin/find/searchresultwindow.h
+++ b/src/plugins/coreplugin/find/searchresultwindow.h
@@ -60,6 +60,7 @@ public:
int count() const;
void setSearchAgainSupported(bool supported);
QWidget *additionalReplaceWidget() const;
+ void setAdditionalReplaceWidget(QWidget *widget);
public slots:
void addResult(const QString &fileName,
@@ -82,6 +83,7 @@ public slots:
signals:
void activated(const Core::SearchResultItem &item);
void replaceButtonClicked(const QString &replaceText, const QList<Core::SearchResultItem> &checkedItems, bool preserveCase);
+ void replaceTextChanged(const QString &replaceText);
void cancelled();
void paused(bool paused);
void visibilityChanged(bool visible);
diff --git a/src/plugins/coreplugin/generatedfile.h b/src/plugins/coreplugin/generatedfile.h
index 807f2f5166c..7dd987d56fe 100644
--- a/src/plugins/coreplugin/generatedfile.h
+++ b/src/plugins/coreplugin/generatedfile.h
@@ -36,18 +36,20 @@ class GeneratedFilePrivate;
class CORE_EXPORT GeneratedFile
{
public:
- enum Attribute { // Open this file in editor
- OpenEditorAttribute = 0x01,
- // Open project
- OpenProjectAttribute = 0x02,
- /* File is generated by external scripts, do not write out,
- * see BaseFileWizard::writeFiles() */
- CustomGeneratorAttribute = 0x4,
- /* File exists and the user indicated that he wants to keep it */
- KeepExistingFileAttribute = 0x8,
- /* Force overwriting of a file without asking the user to keep it */
- ForceOverwrite = 0x10
- };
+ enum Attribute {
+ // Open this file in editor
+ OpenEditorAttribute = 0x01,
+ // Open project
+ OpenProjectAttribute = 0x02,
+ // File is generated by external scripts, do not write out, see BaseFileWizard::writeFiles()
+ CustomGeneratorAttribute = 0x4,
+ // File exists and the user indicated that he wants to keep it
+ KeepExistingFileAttribute = 0x8,
+ // Force overwriting of a file without asking the user to keep it
+ ForceOverwrite = 0x10,
+ // Mark the document temporary after opening the file
+ TemporaryFile = 0x20
+ };
Q_DECLARE_FLAGS(Attributes, Attribute)
GeneratedFile();
diff --git a/src/plugins/coreplugin/helpitem.cpp b/src/plugins/coreplugin/helpitem.cpp
index 544c9be9c94..2a972cb98eb 100644
--- a/src/plugins/coreplugin/helpitem.cpp
+++ b/src/plugins/coreplugin/helpitem.cpp
@@ -211,7 +211,7 @@ const HelpItem::Links &HelpItem::links() const
m_helpLinks.emplace(Links{{m_keyword, m_helpUrl}});
} else {
m_helpLinks.emplace(); // set a value even if there are no help IDs
- QMap<QString, QUrl> helpLinks;
+ QMultiMap<QString, QUrl> helpLinks;
for (const QString &id : m_helpIds) {
helpLinks = Core::HelpManager::linksForIdentifier(id);
if (!helpLinks.isEmpty()) {
diff --git a/src/plugins/coreplugin/helpmanager.cpp b/src/plugins/coreplugin/helpmanager.cpp
index 3e8e5b40037..4fbafd202bc 100644
--- a/src/plugins/coreplugin/helpmanager.cpp
+++ b/src/plugins/coreplugin/helpmanager.cpp
@@ -86,14 +86,14 @@ void unregisterDocumentation(const QStringList &fileNames)
m_instance->unregisterDocumentation(fileNames);
}
-QMap<QString, QUrl> linksForIdentifier(const QString &id)
+QMultiMap<QString, QUrl> linksForIdentifier(const QString &id)
{
- return checkInstance() ? m_instance->linksForIdentifier(id) : QMap<QString, QUrl>();
+ return checkInstance() ? m_instance->linksForIdentifier(id) : QMultiMap<QString, QUrl>();
}
-QMap<QString, QUrl> linksForKeyword(const QString &keyword)
+QMultiMap<QString, QUrl> linksForKeyword(const QString &keyword)
{
- return checkInstance() ? m_instance->linksForKeyword(keyword) : QMap<QString, QUrl>();
+ return checkInstance() ? m_instance->linksForKeyword(keyword) : QMultiMap<QString, QUrl>();
}
QByteArray fileData(const QUrl &url)
diff --git a/src/plugins/coreplugin/helpmanager.h b/src/plugins/coreplugin/helpmanager.h
index 7b7e4f052e7..ac948b633d9 100644
--- a/src/plugins/coreplugin/helpmanager.h
+++ b/src/plugins/coreplugin/helpmanager.h
@@ -63,8 +63,8 @@ CORE_EXPORT QString documentationPath();
CORE_EXPORT void registerDocumentation(const QStringList &fileNames);
CORE_EXPORT void unregisterDocumentation(const QStringList &fileNames);
-CORE_EXPORT QMap<QString, QUrl> linksForIdentifier(const QString &id);
-CORE_EXPORT QMap<QString, QUrl> linksForKeyword(const QString &id);
+CORE_EXPORT QMultiMap<QString, QUrl> linksForIdentifier(const QString &id);
+CORE_EXPORT QMultiMap<QString, QUrl> linksForKeyword(const QString &id);
CORE_EXPORT QByteArray fileData(const QUrl &url);
CORE_EXPORT void showHelpUrl(const QUrl &url, HelpViewerLocation location = HelpModeAlways);
diff --git a/src/plugins/coreplugin/helpmanager_implementation.h b/src/plugins/coreplugin/helpmanager_implementation.h
index 9af4a499380..c317a95e361 100644
--- a/src/plugins/coreplugin/helpmanager_implementation.h
+++ b/src/plugins/coreplugin/helpmanager_implementation.h
@@ -40,8 +40,8 @@ protected:
public:
virtual void registerDocumentation(const QStringList &fileNames) = 0;
virtual void unregisterDocumentation(const QStringList &fileNames) = 0;
- virtual QMap<QString, QUrl> linksForIdentifier(const QString &id) = 0;
- virtual QMap<QString, QUrl> linksForKeyword(const QString &keyword) = 0;
+ virtual QMultiMap<QString, QUrl> linksForIdentifier(const QString &id) = 0;
+ virtual QMultiMap<QString, QUrl> linksForKeyword(const QString &keyword) = 0;
virtual QByteArray fileData(const QUrl &url) = 0;
virtual void showHelpUrl(const QUrl &url, HelpViewerLocation location = HelpModeAlways) = 0;
};
diff --git a/src/plugins/coreplugin/icontext.cpp b/src/plugins/coreplugin/icontext.cpp
index a0838e707ff..30dbf87059f 100644
--- a/src/plugins/coreplugin/icontext.cpp
+++ b/src/plugins/coreplugin/icontext.cpp
@@ -46,11 +46,218 @@ QDebug operator<<(QDebug debug, const Core::Context &context)
}
/*!
+ \class Core::Context
+ \inmodule QtCreator
+ \ingroup mainclasses
+ \brief The Context class implements a list of context IDs.
+
+ Contexts are used for registering actions with Core::ActionManager, and
+ when creating UI elements that provide a context for actions.
+
+ See \l{The Action Manager and Commands} for an overview of how contexts are
+ used.
+
+ \sa Core::IContext
+ \sa Core::ActionManager
+ \sa {The Action Manager and Commands}
+*/
+
+/*!
+ \typedef Core::Context::const_iterator
+
+ \brief The Context::const_iterator provides an STL-style const interator for
+ Context.
+*/
+
+/*!
+ \fn Core::Context::Context()
+
+ Creates a context list that represents the global context.
+*/
+
+/*!
+ \fn Core::Context::Context(Core::Id c1)
+
+ Creates a context list with a single ID \a c1.
+*/
+
+/*!
+ \fn Core::Context::Context(Core::Id c1, Core::Id c2)
+
+ Creates a context list with IDs \a c1 and \a c2.
+*/
+
+/*!
+ \fn Core::Context::Context(Core::Id c1, Core::Id c2, Core::Id c3)
+
+ Creates a context list with IDs \a c1, \a c2 and \a c3.
+*/
+
+/*!
+ \fn bool Core::Context::contains(Core::Id c) const
+
+ Returns whether this context list contains the ID \a c.
+*/
+
+/*!
+ \fn int Core::Context::size() const
+
+ Returns the number of IDs in the context list.
+*/
+
+/*!
+ \fn bool Core::Context::isEmpty() const
+
+ Returns whether this context list is empty and therefore default
+ constructed.
+*/
+
+/*!
+ \fn Core::Id Core::Context::at(int i) const
+
+ Returns the ID at index \a i in the context list.
+*/
+
+/*!
+ \fn Core::Context::const_iterator Core::Context::begin() const
+
+ Returns an STL-style iterator pointing to the first ID in the context list.
+*/
+
+/*!
+ \fn Core::Context::const_iterator Core::Context::end() const
+
+ Returns an STL-style iterator pointing to the imaginary item after the last
+ ID in the context list.
+*/
+
+/*!
+ \fn int Core::Context::indexOf(Core::Id c) const
+
+ Returns the index position of the ID \a c in the context list. Returns -1
+ if no item matched.
+*/
+
+/*!
+ \fn void Core::Context::removeAt(int i)
+
+ Removes the ID at index \a i from the context list.
+*/
+
+/*!
+ \fn void Core::Context::prepend(Core::Id c)
+
+ Adds the ID \a c as the first item to the context list.
+*/
+
+/*!
+ \fn void Core::Context::add(const Core::Context &c)
+
+ Adds the context list \a c at the end of this context list.
+*/
+
+/*!
+ \fn void Core::Context::add(Core::Id c)
+
+ Adds the ID \a c at the end of the context list.
+*/
+
+/*!
+ \fn bool Core::Context::operator==(const Core::Context &c) const
+ \internal
+*/
+
+/*!
\class Core::IContext
\inmodule QtCreator
\ingroup mainclasses
- \brief The IContext class holds the context for performing an action.
- The behavior of some actions depends on the context in which they are
- applied.
+ \brief The IContext class associates a widget with a context list and
+ context help.
+
+ An instance of IContext must be registered with
+ Core::ICore::addContextObject() to have an effect. For many subclasses of
+ IContext, like Core::IEditor and Core::IMode, this is done automatically.
+ But instances of IContext can be created manually to associate a context
+ and context help for an arbitrary widget, too. Mind that IContext instances
+ must be unregistered with Core::ICore::removeContextObject() as well.
+
+ Whenever the widget is part of the application wide focus widget's parent
+ chain, the associated context list is made active. This makes actions active
+ that were registered for any of the included context IDs. If the user
+ requests context help, the top-most IContext instance in the focus widget's
+ parent hierarchy is asked to provide it.
+
+ See \l{The Action Manager and Commands} for an overview of how contexts are
+ used for managing actions.
+
+ \sa Core::ICore
+ \sa Core::Context
+ \sa Core::ActionManager
+ \sa {The Action Manager and Commands}
+*/
+
+/*!
+ \fn Core::IContext::IContext(QObject *parent)
+
+ Creates an IContext with an optional \a parent.
+*/
+
+/*!
+ \fn Core::Context Core::IContext::context() const
+
+ Returns the context list associated with this IContext.
+
+ \sa setContext()
+*/
+
+/*!
+ \fn QWidget *Core::IContext::widget() const
+
+ Returns the widget associated with this IContext.
+
+ \sa setWidget()
+*/
+
+/*!
+ \typedef Core::IContext::HelpCallback
+
+ The HelpCallback class defines the callback function that is used to report
+ the help item to show when the user requests context help.
+*/
+
+/*!
+ \fn void Core::IContext::contextHelp(const Core::IContext::HelpCallback &callback) const
+
+ Called when the user requests context help and this IContext is the top-most
+ in the application focus widget's parent hierarchy. Implementations must
+ call the passed \a callback with the resulting help item.
+ The default implementation returns an help item with the help ID that was
+ set with setContextHelp().
+
+ \sa setContextHelp()
+*/
+
+/*!
+ \fn void Core::IContext::setContext(const Core::Context &context)
+
+ Sets the context list associated with this IContext to \a context.
+
+ \sa context()
+*/
+
+/*!
+ \fn void Core::IContext::setWidget(QWidget *widget)
+
+ Sets the widget associated with this IContext to \a widget.
+
+ \sa widget()
+*/
+
+/*!
+ \fn void Core::IContext::setContextHelp(const Core::HelpItem &id)
+
+ Sets the context help item associated with this IContext to \a id.
+
+ \sa contextHelp()
*/
diff --git a/src/plugins/coreplugin/icore.cpp b/src/plugins/coreplugin/icore.cpp
index 2ab0936300a..b5986bd67c6 100644
--- a/src/plugins/coreplugin/icore.cpp
+++ b/src/plugins/coreplugin/icore.cpp
@@ -356,8 +356,26 @@ QString ICore::cacheResourcePath()
QString ICore::installerResourcePath()
{
- return QFileInfo(settings(QSettings::SystemScope)->fileName()).path() + '/'
- + Constants::IDE_ID;
+ return QFileInfo(settings(QSettings::SystemScope)->fileName()).path() + '/' + Constants::IDE_ID;
+}
+
+QString ICore::pluginPath()
+{
+ return QDir::cleanPath(QCoreApplication::applicationDirPath() + '/' + RELATIVE_PLUGIN_PATH);
+}
+
+QString ICore::userPluginPath()
+{
+ QString pluginPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
+ if (Utils::HostOsInfo::isAnyUnixHost() && !Utils::HostOsInfo::isMacHost())
+ pluginPath += "/data";
+ pluginPath += '/' + QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR) + '/';
+ pluginPath += QLatin1String(Utils::HostOsInfo::isMacHost() ? Core::Constants::IDE_DISPLAY_NAME
+ : Core::Constants::IDE_ID);
+ pluginPath += "/plugins/";
+ pluginPath += QString::number(IDE_VERSION_MAJOR) + '.' + QString::number(IDE_VERSION_MINOR)
+ + '.' + QString::number(IDE_VERSION_RELEASE);
+ return pluginPath;
}
/*!
diff --git a/src/plugins/coreplugin/icore.h b/src/plugins/coreplugin/icore.h
index 487a9715792..8610023e6a6 100644
--- a/src/plugins/coreplugin/icore.h
+++ b/src/plugins/coreplugin/icore.h
@@ -97,6 +97,8 @@ public:
static QString userResourcePath();
static QString cacheResourcePath();
static QString installerResourcePath();
+ static QString pluginPath();
+ static QString userPluginPath();
static QString libexecPath();
static QString clangExecutable(const QString &clangBinDirectory);
static QString clangTidyExecutable(const QString &clangBinDirectory);
diff --git a/src/plugins/coreplugin/locator/javascriptfilter.cpp b/src/plugins/coreplugin/locator/javascriptfilter.cpp
index ab6727b8a29..b1f7f7719fa 100644
--- a/src/plugins/coreplugin/locator/javascriptfilter.cpp
+++ b/src/plugins/coreplugin/locator/javascriptfilter.cpp
@@ -27,7 +27,7 @@
#include <QClipboard>
#include <QGuiApplication>
-#include <QScriptEngine>
+#include <QJSEngine>
namespace Core {
namespace Internal {
@@ -48,8 +48,8 @@ JavaScriptFilter::JavaScriptFilter()
m_abortTimer.setInterval(1000);
connect(&m_abortTimer, &QTimer::timeout, this, [this] {
m_aborted = true;
- if (m_engine && m_engine->isEvaluating())
- m_engine->abortEvaluation();
+ if (m_engine)
+ m_engine->setInterrupted(true);
});
}
@@ -63,6 +63,7 @@ void JavaScriptFilter::prepareSearch(const QString &entry)
if (!m_engine)
setupEngine();
+ m_engine->setInterrupted(false);
m_aborted = false;
m_abortTimer.start();
}
@@ -118,7 +119,7 @@ void JavaScriptFilter::refresh(QFutureInterface<void> &future)
void JavaScriptFilter::setupEngine()
{
- m_engine.reset(new QScriptEngine);
+ m_engine.reset(new QJSEngine);
m_engine->evaluate(
"function abs(x) { return Math.abs(x); }\n"
"function acos(x) { return Math.acos(x); }\n"
diff --git a/src/plugins/coreplugin/locator/javascriptfilter.h b/src/plugins/coreplugin/locator/javascriptfilter.h
index a11b9c0f21d..28ef4476040 100644
--- a/src/plugins/coreplugin/locator/javascriptfilter.h
+++ b/src/plugins/coreplugin/locator/javascriptfilter.h
@@ -32,7 +32,7 @@
#include <memory>
QT_BEGIN_NAMESPACE
-class QScriptEngine;
+class QJSEngine;
QT_END_NAMESPACE
namespace Core {
@@ -55,7 +55,7 @@ public:
private:
void setupEngine();
- mutable std::unique_ptr<QScriptEngine> m_engine;
+ mutable std::unique_ptr<QJSEngine> m_engine;
QTimer m_abortTimer;
bool m_aborted = false;
};
diff --git a/src/plugins/coreplugin/locator/locator.pri b/src/plugins/coreplugin/locator/locator.pri
index 2223836c19f..ae5cf6e440a 100644
--- a/src/plugins/coreplugin/locator/locator.pri
+++ b/src/plugins/coreplugin/locator/locator.pri
@@ -38,9 +38,7 @@ SOURCES += \
FORMS += \
$$PWD/urllocatorfilter.ui
-qtHaveModule(script) {
- QT *= script
-
+minQtVersion(5, 14, 0) {
DEFINES += WITH_JAVASCRIPTFILTER
HEADERS += \
diff --git a/src/plugins/coreplugin/mainwindow.cpp b/src/plugins/coreplugin/mainwindow.cpp
index ea2e8db1479..2a9d4edeee9 100644
--- a/src/plugins/coreplugin/mainwindow.cpp
+++ b/src/plugins/coreplugin/mainwindow.cpp
@@ -424,6 +424,12 @@ void MainWindow::registerDefaultContainers()
medit->appendGroup(Constants::G_EDIT_FIND);
medit->appendGroup(Constants::G_EDIT_OTHER);
+ ActionContainer *mview = ActionManager::createMenu(Constants::M_VIEW);
+ menubar->addMenu(mview, Constants::G_VIEW);
+ mview->menu()->setTitle(tr("&View"));
+ mview->appendGroup(Constants::G_VIEW_VIEWS);
+ mview->appendGroup(Constants::G_VIEW_PANES);
+
// Tools Menu
ActionContainer *ac = ActionManager::createMenu(Constants::M_TOOLS);
menubar->addMenu(ac, Constants::G_TOOLS);
@@ -434,8 +440,6 @@ void MainWindow::registerDefaultContainers()
menubar->addMenu(mwindow, Constants::G_WINDOW);
mwindow->menu()->setTitle(tr("&Window"));
mwindow->appendGroup(Constants::G_WINDOW_SIZE);
- mwindow->appendGroup(Constants::G_WINDOW_VIEWS);
- mwindow->appendGroup(Constants::G_WINDOW_PANES);
mwindow->appendGroup(Constants::G_WINDOW_SPLIT);
mwindow->appendGroup(Constants::G_WINDOW_NAVIGATE);
mwindow->appendGroup(Constants::G_WINDOW_LIST);
@@ -465,6 +469,7 @@ void MainWindow::registerDefaultActions()
{
ActionContainer *mfile = ActionManager::actionContainer(Constants::M_FILE);
ActionContainer *medit = ActionManager::actionContainer(Constants::M_EDIT);
+ ActionContainer *mview = ActionManager::actionContainer(Constants::M_VIEW);
ActionContainer *mtools = ActionManager::actionContainer(Constants::M_TOOLS);
ActionContainer *mwindow = ActionManager::actionContainer(Constants::M_WINDOW);
ActionContainer *mhelp = ActionManager::actionContainer(Constants::M_HELP);
@@ -544,11 +549,7 @@ void MainWindow::registerDefaultActions()
mfile->addAction(cmd, Constants::G_FILE_SAVE);
// SaveAll Action
- m_saveAllAction = new QAction(tr("Save A&ll"), this);
- cmd = ActionManager::registerAction(m_saveAllAction, Constants::SAVEALL);
- cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? QString() : tr("Ctrl+Shift+S")));
- mfile->addAction(cmd, Constants::G_FILE_SAVE);
- connect(m_saveAllAction, &QAction::triggered, this, &MainWindow::saveAll);
+ DocumentManager::registerSaveAllAction();
// Print Action
icon = QIcon::fromTheme(QLatin1String("document-print"));
@@ -640,7 +641,10 @@ void MainWindow::registerDefaultActions()
: Utils::Icons::ZOOMOUT_TOOLBAR.icon();
tmpaction = new QAction(icon, tr("Zoom Out"), this);
cmd = ActionManager::registerAction(tmpaction, Constants::ZOOM_OUT);
- cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+-")));
+ if (useMacShortcuts)
+ cmd->setDefaultKeySequences({QKeySequence(tr("Ctrl+-")), QKeySequence(tr("Ctrl+Shift+-"))});
+ else
+ cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+-")));
tmpaction->setEnabled(false);
// Zoom Reset Action
@@ -714,7 +718,7 @@ void MainWindow::registerDefaultActions()
ProxyAction *toggleLeftSideBarProxyAction =
ProxyAction::proxyActionWithIcon(cmd->action(), Utils::Icons::TOGGLE_LEFT_SIDEBAR_TOOLBAR.icon());
m_toggleLeftSideBarButton->setDefaultAction(toggleLeftSideBarProxyAction);
- mwindow->addAction(cmd, Constants::G_WINDOW_VIEWS);
+ mview->addAction(cmd, Constants::G_VIEW_VIEWS);
m_toggleLeftSideBarAction->setEnabled(false);
// Show Right Sidebar Action
@@ -730,14 +734,14 @@ void MainWindow::registerDefaultActions()
ProxyAction *toggleRightSideBarProxyAction =
ProxyAction::proxyActionWithIcon(cmd->action(), Utils::Icons::TOGGLE_RIGHT_SIDEBAR_TOOLBAR.icon());
m_toggleRightSideBarButton->setDefaultAction(toggleRightSideBarProxyAction);
- mwindow->addAction(cmd, Constants::G_WINDOW_VIEWS);
+ mview->addAction(cmd, Constants::G_VIEW_VIEWS);
m_toggleRightSideBarButton->setEnabled(false);
registerModeSelectorStyleActions();
// Window->Views
- ActionContainer *mviews = ActionManager::createMenu(Constants::M_WINDOW_VIEWS);
- mwindow->addMenu(mviews, Constants::G_WINDOW_VIEWS);
+ ActionContainer *mviews = ActionManager::createMenu(Constants::M_VIEW_VIEWS);
+ mview->addMenu(mviews, Constants::G_VIEW_VIEWS);
mviews->menu()->setTitle(tr("&Views"));
// "Help" separators
@@ -781,7 +785,7 @@ void MainWindow::registerDefaultActions()
void MainWindow::registerModeSelectorStyleActions()
{
- ActionContainer *mwindow = ActionManager::actionContainer(Constants::M_WINDOW);
+ ActionContainer *mview = ActionManager::actionContainer(Constants::M_VIEW);
// Cycle Mode Selector Styles
m_cycleModeSelectorStyleAction = new QAction(tr("Cycle Mode Selector Styles"), this);
@@ -792,8 +796,8 @@ void MainWindow::registerModeSelectorStyleActions()
});
// Mode Selector Styles
- ActionContainer *mmodeLayouts = ActionManager::createMenu(Constants::M_WINDOW_MODESTYLES);
- mwindow->addMenu(mmodeLayouts, Constants::G_WINDOW_VIEWS);
+ ActionContainer *mmodeLayouts = ActionManager::createMenu(Constants::M_VIEW_MODESTYLES);
+ mview->addMenu(mmodeLayouts, Constants::G_VIEW_VIEWS);
QMenu *styleMenu = mmodeLayouts->menu();
styleMenu->setTitle(tr("Mode Selector Style"));
auto *stylesGroup = new QActionGroup(styleMenu);
@@ -889,11 +893,6 @@ void MainWindow::setFocusToEditor()
EditorManagerPrivate::doEscapeKeyFocusMoveMagic();
}
-void MainWindow::saveAll()
-{
- DocumentManager::saveAllModifiedDocumentsSilently();
-}
-
void MainWindow::exit()
{
// this function is most likely called from a user action
diff --git a/src/plugins/coreplugin/mainwindow.h b/src/plugins/coreplugin/mainwindow.h
index 0d7b533086a..554e7b62068 100644
--- a/src/plugins/coreplugin/mainwindow.h
+++ b/src/plugins/coreplugin/mainwindow.h
@@ -121,7 +121,6 @@ private:
void openFile();
void aboutToShowRecentFiles();
void setFocusToEditor();
- void saveAll();
void aboutQtCreator();
void aboutPlugins();
void updateFocusWidget(QWidget *old, QWidget *now);
diff --git a/src/plugins/coreplugin/messageoutputwindow.cpp b/src/plugins/coreplugin/messageoutputwindow.cpp
index 1ea565705bf..384b34c113a 100644
--- a/src/plugins/coreplugin/messageoutputwindow.cpp
+++ b/src/plugins/coreplugin/messageoutputwindow.cpp
@@ -111,7 +111,7 @@ void MessageOutputWindow::visibilityChanged(bool /*b*/)
void MessageOutputWindow::append(const QString &text)
{
- m_widget->appendText(text);
+ m_widget->appendMessage(text, Utils::DebugFormat);
}
int MessageOutputWindow::priorityInStatusBar() const
diff --git a/src/plugins/coreplugin/outputpanemanager.cpp b/src/plugins/coreplugin/outputpanemanager.cpp
index 6b17180d428..c0d96ef961c 100644
--- a/src/plugins/coreplugin/outputpanemanager.cpp
+++ b/src/plugins/coreplugin/outputpanemanager.cpp
@@ -391,11 +391,11 @@ OutputPaneManager::OutputPaneManager(QWidget *parent) :
StatusBarManager::addStatusBarWidget(m_buttonsWidget, StatusBarManager::Second);
- ActionContainer *mwindow = ActionManager::actionContainer(Constants::M_WINDOW);
+ ActionContainer *mview = ActionManager::actionContainer(Constants::M_VIEW);
// Window->Output Panes
- ActionContainer *mpanes = ActionManager::createMenu(Constants::M_WINDOW_PANES);
- mwindow->addMenu(mpanes, Constants::G_WINDOW_PANES);
+ ActionContainer *mpanes = ActionManager::createMenu(Constants::M_VIEW_PANES);
+ mview->addMenu(mpanes, Constants::G_VIEW_PANES);
mpanes->menu()->setTitle(tr("Output &Panes"));
mpanes->appendGroup("Coreplugin.OutputPane.ActionsGroup");
mpanes->appendGroup("Coreplugin.OutputPane.PanesGroup");
diff --git a/src/plugins/coreplugin/outputwindow.cpp b/src/plugins/coreplugin/outputwindow.cpp
index 149c7189fdb..344d117e4fa 100644
--- a/src/plugins/coreplugin/outputwindow.cpp
+++ b/src/plugins/coreplugin/outputwindow.cpp
@@ -26,19 +26,31 @@
#include "outputwindow.h"
#include "actionmanager/actionmanager.h"
+#include "editormanager/editormanager.h"
#include "coreconstants.h"
+#include "coreplugin.h"
#include "icore.h"
#include <utils/outputformatter.h>
-#include <utils/synchronousprocess.h>
+#include <utils/qtcassert.h>
#include <QAction>
#include <QCursor>
+#include <QElapsedTimer>
#include <QMimeData>
#include <QPointer>
#include <QRegularExpression>
#include <QScrollBar>
#include <QTextBlock>
+#include <QTimer>
+
+#ifdef WITH_TESTS
+#include <QtTest>
+#endif
+
+#include <numeric>
+
+const int chunkSize = 10000;
using namespace Utils;
@@ -61,11 +73,12 @@ public:
}
IContext *outputWindowContext = nullptr;
- QPointer<Utils::OutputFormatter> formatter;
QString settingsKey;
+ OutputFormatter formatter;
+ QList<QPair<QString, OutputFormat>> queuedOutput;
+ QTimer queueTimer;
- bool enforceNewline = false;
- bool prependCarriageReturn = false;
+ bool flushRequested = false;
bool scrollToBottom = true;
bool linksActive = true;
bool zoomEnabled = false;
@@ -78,6 +91,8 @@ public:
int lastFilteredBlockNumber = -1;
QPalette originalPalette;
OutputWindow::FilterModeFlags filterMode = OutputWindow::FilterModeFlag::Default;
+ QTimer scrollTimer;
+ QElapsedTimer lastMessage;
};
} // namespace Internal
@@ -93,6 +108,11 @@ OutputWindow::OutputWindow(Context context, const QString &settingsKey, QWidget
setFrameShape(QFrame::NoFrame);
setMouseTracking(true);
setUndoRedoEnabled(false);
+ d->formatter.setPlainTextEdit(this);
+
+ d->queueTimer.setSingleShot(true);
+ d->queueTimer.setInterval(10);
+ connect(&d->queueTimer, &QTimer::timeout, this, &OutputWindow::handleNextOutputChunk);
d->settingsKey = settingsKey;
@@ -135,16 +155,21 @@ OutputWindow::OutputWindow(Context context, const QString &settingsKey, QWidget
Core::ICore::settings()->setValue(d->settingsKey, fontZoom());
});
+ connect(outputFormatter(), &OutputFormatter::openInEditorRequested, this,
+ [](const Utils::FilePath &fp, int line, int column) {
+ EditorManager::openEditorAt(fp.toString(), line, column);
+ });
+
undoAction->setEnabled(false);
redoAction->setEnabled(false);
cutAction->setEnabled(false);
copyAction->setEnabled(false);
- m_scrollTimer.setInterval(10);
- m_scrollTimer.setSingleShot(true);
- connect(&m_scrollTimer, &QTimer::timeout,
+ d->scrollTimer.setInterval(10);
+ d->scrollTimer.setSingleShot(true);
+ connect(&d->scrollTimer, &QTimer::timeout,
this, &OutputWindow::scrollToBottom);
- m_lastMessage.start();
+ d->lastMessage.start();
d->originalFontSize = font().pointSizeF();
@@ -169,8 +194,7 @@ void OutputWindow::mouseReleaseEvent(QMouseEvent *e)
{
if (d->linksActive && d->mouseButtonPressed == Qt::LeftButton) {
const QString href = anchorAt(e->pos());
- if (d->formatter)
- d->formatter->handleLink(href);
+ d->formatter.handleLink(href);
}
// Mouse was released, activate links again
@@ -214,16 +238,15 @@ void OutputWindow::keyPressEvent(QKeyEvent *ev)
verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMaximum);
}
-OutputFormatter *OutputWindow::formatter() const
+void OutputWindow::setLineParsers(const QList<OutputLineParser *> &parsers)
{
- return d->formatter;
+ reset();
+ d->formatter.setLineParsers(parsers);
}
-void OutputWindow::setFormatter(OutputFormatter *formatter)
+OutputFormatter *OutputWindow::outputFormatter() const
{
- d->formatter = formatter;
- if (d->formatter)
- d->formatter->setPlainTextEdit(this);
+ return &d->formatter;
}
void OutputWindow::showEvent(QShowEvent *e)
@@ -364,47 +387,28 @@ void OutputWindow::filterNewContent()
scrollToBottom();
}
-QString OutputWindow::doNewlineEnforcement(const QString &out)
+void OutputWindow::handleNextOutputChunk()
{
- d->scrollToBottom = true;
- QString s = out;
- if (d->enforceNewline) {
- s.prepend('\n');
- d->enforceNewline = false;
+ QTC_ASSERT(!d->queuedOutput.isEmpty(), return);
+ auto &chunk = d->queuedOutput.first();
+ if (chunk.first.size() <= chunkSize) {
+ handleOutputChunk(chunk.first, chunk.second);
+ d->queuedOutput.removeFirst();
+ } else {
+ handleOutputChunk(chunk.first.left(chunkSize), chunk.second);
+ chunk.first.remove(0, chunkSize);
}
-
- if (s.endsWith('\n')) {
- d->enforceNewline = true; // make appendOutputInline put in a newline next time
- s.chop(1);
+ if (!d->queuedOutput.isEmpty())
+ d->queueTimer.start();
+ else if (d->flushRequested) {
+ d->formatter.flush();
+ d->flushRequested = false;
}
-
- return s;
-}
-
-void OutputWindow::setMaxCharCount(int count)
-{
- d->maxCharCount = count;
- setMaximumBlockCount(count / 100);
-}
-
-int OutputWindow::maxCharCount() const
-{
- return d->maxCharCount;
}
-void OutputWindow::appendMessage(const QString &output, OutputFormat format)
+void OutputWindow::handleOutputChunk(const QString &output, OutputFormat format)
{
QString out = output;
- if (d->prependCarriageReturn) {
- d->prependCarriageReturn = false;
- out.prepend('\r');
- }
- out = SynchronousProcess::normalizeNewlines(out);
- if (out.endsWith('\r')) {
- d->prependCarriageReturn = true;
- out.chop(1);
- }
-
if (out.size() > d->maxCharCount) {
// Current line alone exceeds limit, we need to cut it.
out.truncate(d->maxCharCount);
@@ -426,86 +430,42 @@ void OutputWindow::appendMessage(const QString &output, OutputFormat format)
}
}
- const bool atBottom = isScrollbarAtBottom() || m_scrollTimer.isActive();
-
- if (format == ErrorMessageFormat || format == NormalMessageFormat) {
- if (d->formatter)
- d->formatter->appendMessage(doNewlineEnforcement(out), format);
- } else {
-
- bool sameLine = format == StdOutFormatSameLine
- || format == StdErrFormatSameLine;
-
- if (sameLine) {
- d->scrollToBottom = true;
-
- bool enforceNewline = d->enforceNewline;
- d->enforceNewline = false;
-
- if (enforceNewline) {
- out.prepend('\n');
- } else {
- const int newline = out.indexOf('\n');
- moveCursor(QTextCursor::End);
- if (newline != -1) {
- if (d->formatter)
- d->formatter->appendMessage(out.left(newline), format);// doesn't enforce new paragraph like appendPlainText
- out = out.mid(newline);
- }
- }
-
- if (out.isEmpty()) {
- d->enforceNewline = true;
- } else {
- if (out.endsWith('\n')) {
- d->enforceNewline = true;
- out.chop(1);
- }
- if (d->formatter)
- d->formatter->appendMessage(out, format);
- }
- } else {
- if (d->formatter)
- d->formatter->appendMessage(doNewlineEnforcement(out), format);
- }
- }
+ const bool atBottom = isScrollbarAtBottom() || d->scrollTimer.isActive();
+ d->scrollToBottom = true;
+ d->formatter.appendMessage(out, format);
if (atBottom) {
- if (m_lastMessage.elapsed() < 5) {
- m_scrollTimer.start();
+ if (d->lastMessage.elapsed() < 5) {
+ d->scrollTimer.start();
} else {
- m_scrollTimer.stop();
+ d->scrollTimer.stop();
scrollToBottom();
}
}
- m_lastMessage.start();
+ d->lastMessage.start();
enableUndoRedo();
}
-// TODO rename
-void OutputWindow::appendText(const QString &textIn, const QTextCharFormat &format)
+void OutputWindow::setMaxCharCount(int count)
{
- const QString text = SynchronousProcess::normalizeNewlines(textIn);
- if (d->maxCharCount > 0 && document()->characterCount() >= d->maxCharCount)
- return;
- const bool atBottom = isScrollbarAtBottom();
- if (!d->cursor.atEnd())
- d->cursor.movePosition(QTextCursor::End);
- d->cursor.beginEditBlock();
- d->cursor.insertText(doNewlineEnforcement(text), format);
-
- if (d->maxCharCount > 0 && document()->characterCount() >= d->maxCharCount) {
- QTextCharFormat tmp;
- tmp.setFontWeight(QFont::Bold);
- d->cursor.insertText(doNewlineEnforcement(tr("Additional output omitted. You can increase "
- "the limit in the \"Build & Run\" settings.")
- + '\n'), tmp);
- }
+ d->maxCharCount = count;
+ setMaximumBlockCount(count / 100);
+}
- d->cursor.endEditBlock();
- if (atBottom)
- scrollToBottom();
+int OutputWindow::maxCharCount() const
+{
+ return d->maxCharCount;
+}
+
+void OutputWindow::appendMessage(const QString &output, OutputFormat format)
+{
+ if (d->queuedOutput.isEmpty() || d->queuedOutput.last().second != format)
+ d->queuedOutput << qMakePair(output, format);
+ else
+ d->queuedOutput.last().first.append(output);
+ if (!d->queueTimer.isActive())
+ d->queueTimer.start();
}
bool OutputWindow::isScrollbarAtBottom() const
@@ -542,11 +502,35 @@ QMimeData *OutputWindow::createMimeDataFromSelection() const
void OutputWindow::clear()
{
- d->enforceNewline = false;
- d->prependCarriageReturn = false;
- QPlainTextEdit::clear();
- if (d->formatter)
- d->formatter->clear();
+ d->formatter.clear();
+}
+
+void OutputWindow::flush()
+{
+ const int totalQueuedSize = std::accumulate(d->queuedOutput.cbegin(), d->queuedOutput.cend(), 0,
+ [](int val, const QPair<QString, OutputFormat> &c) { return val + c.first.size(); });
+ if (totalQueuedSize > 5 * chunkSize) {
+ d->flushRequested = true;
+ return;
+ }
+ d->queueTimer.stop();
+ for (const auto &chunk : d->queuedOutput)
+ handleOutputChunk(chunk.first, chunk.second);
+ d->queuedOutput.clear();
+ d->formatter.flush();
+}
+
+void OutputWindow::reset()
+{
+ flush();
+ d->queueTimer.stop();
+ d->formatter.reset();
+ if (!d->queuedOutput.isEmpty()) {
+ d->queuedOutput.clear();
+ d->formatter.appendMessage(tr("[Discarding excessive amount of pending output.]\n"),
+ ErrorMessageFormat);
+ }
+ d->flushRequested = false;
}
void OutputWindow::scrollToBottom()
@@ -595,4 +579,83 @@ void OutputWindow::setWordWrapEnabled(bool wrap)
setWordWrapMode(QTextOption::NoWrap);
}
+#ifdef WITH_TESTS
+
+// Handles all lines starting with "A" and the following ones up to and including the next
+// one starting with "A".
+class TestFormatterA : public OutputLineParser
+{
+private:
+ Result handleLine(const QString &text, OutputFormat) override
+ {
+ static const QString replacement = "handled by A\n";
+ if (m_handling) {
+ if (text.startsWith("A")) {
+ m_handling = false;
+ return {Status::Done, {}, replacement};
+ }
+ return {Status::InProgress, {}, replacement};
+ }
+ if (text.startsWith("A")) {
+ m_handling = true;
+ return {Status::InProgress, {}, replacement};
+ }
+ return Status::NotHandled;
+ }
+
+ bool m_handling = false;
+};
+
+// Handles all lines starting with "B". No continuation logic.
+class TestFormatterB : public OutputLineParser
+{
+private:
+ Result handleLine(const QString &text, OutputFormat) override
+ {
+ if (text.startsWith("B"))
+ return {Status::Done, {}, QString("handled by B\n")};
+ return Status::NotHandled;
+ }
+};
+
+void Internal::CorePlugin::testOutputFormatter()
+{
+ const QString input =
+ "B to be handled by B\r\n"
+ "not to be handled\n"
+ "A to be handled by A\n"
+ "continuation for A\r\n"
+ "B looks like B, but still continuation for A\r\n"
+ "A end of A\n"
+ "A next A\n"
+ "A end of next A\n"
+ " A trick\r\n"
+ "line with \r embedded carriage return\n"
+ "B to be handled by B\n";
+ const QString output =
+ "handled by B\n"
+ "not to be handled\n"
+ "handled by A\n"
+ "handled by A\n"
+ "handled by A\n"
+ "handled by A\n"
+ "handled by A\n"
+ "handled by A\n"
+ " A trick\n"
+ " embedded carriage return\n"
+ "handled by B\n";
+
+ // Stress-test the implementation by providing the input in chunks, splitting at all possible
+ // offsets.
+ for (int i = 0; i < input.length(); ++i) {
+ OutputFormatter formatter;
+ QPlainTextEdit textEdit;
+ formatter.setPlainTextEdit(&textEdit);
+ formatter.setLineParsers({new TestFormatterB, new TestFormatterA});
+ formatter.appendMessage(input.left(i), StdOutFormat);
+ formatter.appendMessage(input.mid(i), StdOutFormat);
+ QCOMPARE(textEdit.toPlainText(), output);
+ }
+}
+#endif // WITH_TESTS
} // namespace Core
diff --git a/src/plugins/coreplugin/outputwindow.h b/src/plugins/coreplugin/outputwindow.h
index 509ae6dfab0..91a69d20421 100644
--- a/src/plugins/coreplugin/outputwindow.h
+++ b/src/plugins/coreplugin/outputwindow.h
@@ -30,11 +30,12 @@
#include <utils/outputformat.h>
-#include <QElapsedTimer>
#include <QPlainTextEdit>
-#include <QTimer>
-namespace Utils { class OutputFormatter; }
+namespace Utils {
+class OutputFormatter;
+class OutputLineParser;
+}
namespace Core {
@@ -56,15 +57,15 @@ public:
OutputWindow(Context context, const QString &settingsKey, QWidget *parent = nullptr);
~OutputWindow() override;
- Utils::OutputFormatter *formatter() const;
- void setFormatter(Utils::OutputFormatter *formatter);
+ void setLineParsers(const QList<Utils::OutputLineParser *> &parsers);
+ Utils::OutputFormatter *outputFormatter() const;
void appendMessage(const QString &out, Utils::OutputFormat format);
- /// appends a \p text using \p format without using formater
- void appendText(const QString &text, const QTextCharFormat &format = QTextCharFormat());
void grayOutOldContent();
void clear();
+ void flush();
+ void reset();
void scrollToBottom();
@@ -103,11 +104,10 @@ private:
void wheelEvent(QWheelEvent *e) override;
using QPlainTextEdit::setFont; // call setBaseFont instead, which respects the zoom factor
- QTimer m_scrollTimer;
- QElapsedTimer m_lastMessage;
void enableUndoRedo();
- QString doNewlineEnforcement(const QString &out);
void filterNewContent();
+ void handleNextOutputChunk();
+ void handleOutputChunk(const QString &output, Utils::OutputFormat format);
Internal::OutputWindowPrivate *d = nullptr;
};
diff --git a/src/plugins/coreplugin/plugindialog.cpp b/src/plugins/coreplugin/plugindialog.cpp
index f3dceb73a8e..7bca53412ca 100644
--- a/src/plugins/coreplugin/plugindialog.cpp
+++ b/src/plugins/coreplugin/plugindialog.cpp
@@ -29,28 +29,239 @@
#include "dialogs/restartdialog.h"
-#include <extensionsystem/pluginmanager.h>
-#include <extensionsystem/pluginview.h>
+#include <app/app_version.h>
+
#include <extensionsystem/plugindetailsview.h>
#include <extensionsystem/pluginerrorview.h>
+#include <extensionsystem/pluginmanager.h>
#include <extensionsystem/pluginspec.h>
+#include <extensionsystem/pluginview.h>
+#include <utils/algorithm.h>
+#include <utils/checkablemessagebox.h>
+#include <utils/environment.h>
#include <utils/fancylineedit.h>
+#include <utils/infolabel.h>
+#include <utils/mimetypes/mimedatabase.h>
+#include <utils/pathchooser.h>
+#include <utils/qtcassert.h>
+#include <utils/synchronousprocess.h>
+#include <utils/wizard.h>
+#include <utils/wizardpage.h>
-#include <QVBoxLayout>
-#include <QHBoxLayout>
+#include <QButtonGroup>
#include <QCheckBox>
+#include <QDebug>
#include <QDialog>
#include <QDialogButtonBox>
-#include <QPushButton>
+#include <QDir>
+#include <QFileInfo>
+#include <QHBoxLayout>
#include <QLabel>
-#include <QDebug>
+#include <QPushButton>
+#include <QRadioButton>
+#include <QVBoxLayout>
+
+using namespace Utils;
namespace Core {
namespace Internal {
static bool s_isRestartRequired = false;
+const char kPath[] = "Path";
+const char kApplicationInstall[] = "ApplicationInstall";
+
+static bool hasLibSuffix(const QString &path)
+{
+ return (HostOsInfo().isWindowsHost() && path.endsWith(".dll", Qt::CaseInsensitive))
+ || (HostOsInfo().isLinuxHost() && QFileInfo(path).completeSuffix().startsWith(".so"))
+ || (HostOsInfo().isMacHost() && path.endsWith(".dylib"));
+}
+
+static bool isZipFile(const QString &path)
+{
+ const QList<MimeType> mimeType = mimeTypesForFileName(path);
+ return anyOf(mimeType, [](const MimeType &mt) { return mt.inherits("application/zip"); });
+}
+
+struct Tool
+{
+ FilePath executable;
+ QStringList arguments;
+};
+
+static Utils::optional<Tool> unzipTool(const FilePath &src, const FilePath &dest)
+{
+ const FilePath unzip = Utils::Environment::systemEnvironment().searchInPath(
+ Utils::HostOsInfo::withExecutableSuffix("unzip"));
+ if (!unzip.isEmpty())
+ return Tool{unzip, {"-o", src.toString(), "-d", dest.toString()}};
+
+ const FilePath sevenzip = Utils::Environment::systemEnvironment().searchInPath(
+ Utils::HostOsInfo::withExecutableSuffix("7z"));
+ if (!sevenzip.isEmpty())
+ return Tool{sevenzip, {"x", QString("-o") + dest.toString(), "-y", src.toString()}};
+
+ const FilePath cmake = Utils::Environment::systemEnvironment().searchInPath(
+ Utils::HostOsInfo::withExecutableSuffix("cmake"));
+ if (!cmake.isEmpty())
+ return Tool{cmake, {"-E", "tar", "xvf", src.toString()}};
+
+ return {};
+}
+
+class SourcePage : public WizardPage
+{
+public:
+ SourcePage(QWidget *parent)
+ : WizardPage(parent)
+ {
+ setTitle(PluginDialog::tr("Source"));
+ auto vlayout = new QVBoxLayout;
+ setLayout(vlayout);
+
+ auto label = new QLabel(
+ "<p>"
+ + PluginDialog::tr(
+ "Choose source location. This can be a plugin library file or a zip file.")
+ + "</p>");
+ label->setWordWrap(true);
+ vlayout->addWidget(label);
+
+ auto path = new PathChooser;
+ path->setExpectedKind(PathChooser::Any);
+ vlayout->addWidget(path);
+ registerFieldWithName(kPath, path, "path", SIGNAL(pathChanged(QString)));
+ connect(path, &PathChooser::pathChanged, this, &SourcePage::updateWarnings);
+
+ m_info = new InfoLabel;
+ m_info->setType(InfoLabel::Error);
+ m_info->setVisible(false);
+ vlayout->addWidget(m_info);
+ }
+
+ void updateWarnings()
+ {
+ m_info->setVisible(!isComplete());
+ emit completeChanged();
+ }
+
+ bool isComplete() const
+ {
+ const QString path = field(kPath).toString();
+ if (!QFile::exists(path)) {
+ m_info->setText(PluginDialog::tr("File does not exist."));
+ return false;
+ }
+ if (hasLibSuffix(path))
+ return true;
+
+ if (!isZipFile(path)) {
+ m_info->setText(PluginDialog::tr("File format not supported."));
+ return false;
+ }
+ if (!unzipTool({}, {})) {
+ m_info->setText(
+ PluginDialog::tr("Could not find unzip, 7z, or cmake executable in PATH."));
+ return false;
+ }
+ return true;
+ }
+
+ InfoLabel *m_info = nullptr;
+};
+
+class InstallLocationPage : public WizardPage
+{
+public:
+ InstallLocationPage(QWidget *parent)
+ : WizardPage(parent)
+ {
+ setTitle(PluginDialog::tr("Install Location"));
+ auto vlayout = new QVBoxLayout;
+ setLayout(vlayout);
+
+ auto label = new QLabel("<p>" + PluginDialog::tr("Choose install location.") + "</p>");
+ label->setWordWrap(true);
+ vlayout->addWidget(label);
+ vlayout->addSpacing(10);
+
+ auto localInstall = new QRadioButton(PluginDialog::tr("User plugins"));
+ localInstall->setChecked(true);
+ auto localLabel = new QLabel(
+ PluginDialog::tr("The plugin will be available to all compatible %1 "
+ "installations, but only for the current user.")
+ .arg(Constants::IDE_DISPLAY_NAME));
+ localLabel->setWordWrap(true);
+ localLabel->setAttribute(Qt::WA_MacSmallSize, true);
+
+ vlayout->addWidget(localInstall);
+ vlayout->addWidget(localLabel);
+ vlayout->addSpacing(10);
+
+ auto appInstall = new QRadioButton(
+ PluginDialog::tr("%1 installation").arg(Constants::IDE_DISPLAY_NAME));
+ auto appLabel = new QLabel(
+ PluginDialog::tr("The plugin will be available only to this %1 "
+ "installation, but for all users that can access it.")
+ .arg(Constants::IDE_DISPLAY_NAME));
+ appLabel->setWordWrap(true);
+ appLabel->setAttribute(Qt::WA_MacSmallSize, true);
+ vlayout->addWidget(appInstall);
+ vlayout->addWidget(appLabel);
+
+ auto group = new QButtonGroup(this);
+ group->addButton(localInstall);
+ group->addButton(appInstall);
+
+ registerFieldWithName(kApplicationInstall, this);
+ setField(kApplicationInstall, false);
+ connect(appInstall, &QRadioButton::toggled, this, [this](bool toggled) {
+ setField(kApplicationInstall, toggled);
+ });
+ }
+};
+
+static FilePath pluginInstallPath(QWizard *wizard)
+{
+ return FilePath::fromString(wizard->field(kApplicationInstall).toBool()
+ ? ICore::pluginPath()
+ : ICore::userPluginPath());
+}
+
+static FilePath pluginFilePath(QWizard *wizard)
+{
+ return FilePath::fromVariant(wizard->field(kPath));
+}
+
+class SummaryPage : public WizardPage
+{
+public:
+ SummaryPage(QWidget *parent)
+ : WizardPage(parent)
+ {
+ setTitle(PluginDialog::tr("Summary"));
+
+ auto vlayout = new QVBoxLayout;
+ setLayout(vlayout);
+
+ m_summaryLabel = new QLabel(this);
+ m_summaryLabel->setWordWrap(true);
+ vlayout->addWidget(m_summaryLabel);
+ }
+
+ void initializePage()
+ {
+ m_summaryLabel->setText(PluginDialog::tr("\"%1\" will be installed into \"%2\".")
+ .arg(pluginFilePath(wizard()).toUserOutput(),
+ pluginInstallPath(wizard()).toUserOutput()));
+ }
+
+private:
+ QLabel *m_summaryLabel;
+};
+
PluginDialog::PluginDialog(QWidget *parent)
: QDialog(parent),
m_view(new ExtensionSystem::PluginView(this))
@@ -78,6 +289,7 @@ PluginDialog::PluginDialog(QWidget *parent)
m_detailsButton = new QPushButton(tr("Details"), this);
m_errorDetailsButton = new QPushButton(tr("Error Details"), this);
m_closeButton = new QPushButton(tr("Close"), this);
+ m_installButton = new QPushButton(tr("Install Plugin..."), this);
m_detailsButton->setEnabled(false);
m_errorDetailsButton->setEnabled(false);
m_closeButton->setEnabled(true);
@@ -90,6 +302,7 @@ PluginDialog::PluginDialog(QWidget *parent)
auto hl = new QHBoxLayout;
hl->addWidget(m_detailsButton);
hl->addWidget(m_errorDetailsButton);
+ hl->addWidget(m_installButton);
hl->addSpacing(10);
hl->addWidget(m_restartRequired);
hl->addStretch(5);
@@ -110,8 +323,8 @@ PluginDialog::PluginDialog(QWidget *parent)
[this] { openDetails(m_view->currentPlugin()); });
connect(m_errorDetailsButton, &QAbstractButton::clicked,
this, &PluginDialog::openErrorDetails);
- connect(m_closeButton, &QAbstractButton::clicked,
- this, &PluginDialog::closeDialog);
+ connect(m_installButton, &QAbstractButton::clicked, this, &PluginDialog::showInstallWizard);
+ connect(m_closeButton, &QAbstractButton::clicked, this, &PluginDialog::closeDialog);
updateButtons();
}
@@ -126,6 +339,100 @@ void PluginDialog::closeDialog()
accept();
}
+static bool copyPluginFile(const FilePath &src, const FilePath &dest)
+{
+ const FilePath destFile = dest.pathAppended(src.fileName());
+ if (QFile::exists(destFile.toString())) {
+ QMessageBox box(QMessageBox::Question,
+ PluginDialog::tr("Overwrite File"),
+ PluginDialog::tr("The file \"%1\" exists. Overwrite?")
+ .arg(destFile.toUserOutput()),
+ QMessageBox::Cancel,
+ ICore::dialogParent());
+ QPushButton *acceptButton = box.addButton(PluginDialog::tr("Overwrite"), QMessageBox::AcceptRole);
+ box.setDefaultButton(acceptButton);
+ box.exec();
+ if (box.clickedButton() != acceptButton)
+ return false;
+ QFile::remove(destFile.toString());
+ }
+ QDir(dest.toString()).mkpath(".");
+ if (!QFile::copy(src.toString(), destFile.toString())) {
+ QMessageBox::warning(ICore::dialogParent(),
+ PluginDialog::tr("Failed to Write File"),
+ PluginDialog::tr("Failed to write file \"%1\".")
+ .arg(destFile.toUserOutput()));
+ return false;
+ }
+ return true;
+}
+
+static bool unzip(const FilePath &src, const FilePath &dest)
+{
+ const Utils::optional<Tool> tool = unzipTool(src, dest);
+ QTC_ASSERT(tool, return false);
+ const QString workingDirectory = dest.toFileInfo().absoluteFilePath();
+ QDir(workingDirectory).mkpath(".");
+ CheckableMessageBox box(ICore::dialogParent());
+ box.setIcon(QMessageBox::Information);
+ box.setWindowTitle(PluginDialog::tr("Unzipping File"));
+ box.setText(PluginDialog::tr("Unzipping \"%1\" to \"%2\".")
+ .arg(src.toUserOutput(), dest.toUserOutput()));
+ box.setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ box.button(QDialogButtonBox::Ok)->setEnabled(false);
+ box.setCheckBoxVisible(false);
+ box.setDetailedText(
+ PluginDialog::tr("Running %1\nin \"%2\".\n\n", "Running <cmd> in <workingdirectory>")
+ .arg(CommandLine(tool->executable, tool->arguments).toUserOutput(), workingDirectory));
+ QProcess process;
+ process.setProcessChannelMode(QProcess::MergedChannels);
+ QObject::connect(&process, &QProcess::readyReadStandardOutput, &box, [&box, &process]() {
+ box.setDetailedText(box.detailedText() + QString::fromUtf8(process.readAllStandardOutput()));
+ });
+ QObject::connect(&process,
+ QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
+ [&box](int, QProcess::ExitStatus) {
+ box.button(QDialogButtonBox::Ok)->setEnabled(true);
+ box.button(QDialogButtonBox::Cancel)->setEnabled(false);
+ });
+ QObject::connect(&box, &QMessageBox::rejected, &process, [&process] {
+ SynchronousProcess::stopProcess(process);
+ });
+ process.setProgram(tool->executable.toString());
+ process.setArguments(tool->arguments);
+ process.setWorkingDirectory(workingDirectory);
+ process.start(QProcess::ReadOnly);
+ box.exec();
+ return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
+}
+
+void PluginDialog::showInstallWizard()
+{
+ Wizard wizard(ICore::dialogParent());
+ wizard.setWindowTitle(tr("Install Plugin"));
+
+ auto filePage = new SourcePage(&wizard);
+ wizard.addPage(filePage);
+
+ auto installLocationPage = new InstallLocationPage(&wizard);
+ wizard.addPage(installLocationPage);
+
+ auto summaryPage = new SummaryPage(&wizard);
+ wizard.addPage(summaryPage);
+
+ if (wizard.exec()) {
+ const FilePath path = pluginFilePath(&wizard);
+ const FilePath installPath = pluginInstallPath(&wizard);
+ if (hasLibSuffix(path.toString())) {
+ if (copyPluginFile(path, installPath))
+ updateRestartRequired();
+ } else if (isZipFile(path.toString())) {
+ if (unzip(path, installPath))
+ updateRestartRequired();
+ }
+ }
+}
+
void PluginDialog::updateRestartRequired()
{
// just display the notice all the time after once changing something
diff --git a/src/plugins/coreplugin/plugindialog.h b/src/plugins/coreplugin/plugindialog.h
index 44e84637ddc..7c237d8a1f1 100644
--- a/src/plugins/coreplugin/plugindialog.h
+++ b/src/plugins/coreplugin/plugindialog.h
@@ -53,11 +53,13 @@ private:
void openDetails(ExtensionSystem::PluginSpec *spec);
void openErrorDetails();
void closeDialog();
+ void showInstallWizard();
ExtensionSystem::PluginView *m_view;
QPushButton *m_detailsButton;
QPushButton *m_errorDetailsButton;
+ QPushButton *m_installButton;
QPushButton *m_closeButton;
QLabel *m_restartRequired;
};
diff --git a/src/plugins/coreplugin/systemsettings.cpp b/src/plugins/coreplugin/systemsettings.cpp
index a0627359d83..59b2d518fc9 100644
--- a/src/plugins/coreplugin/systemsettings.cpp
+++ b/src/plugins/coreplugin/systemsettings.cpp
@@ -182,7 +182,7 @@ void SystemSettingsWidget::apply()
m_ui.externalFileBrowserEdit->text());
}
}
- PatchTool::setPatchCommand(m_ui.patchChooser->path());
+ PatchTool::setPatchCommand(m_ui.patchChooser->filePath().toString());
EditorManagerPrivate::setAutoSaveEnabled(m_ui.autoSaveCheckBox->isChecked());
EditorManagerPrivate::setAutoSaveInterval(m_ui.autoSaveInterval->value());
EditorManagerPrivate::setAutoSuspendEnabled(m_ui.autoSuspendCheckBox->isChecked());
diff --git a/src/plugins/coreplugin/variablechooser.cpp b/src/plugins/coreplugin/variablechooser.cpp
index 57668180295..f196dabb373 100644
--- a/src/plugins/coreplugin/variablechooser.cpp
+++ b/src/plugins/coreplugin/variablechooser.cpp
@@ -90,8 +90,8 @@ public:
if (!index.isValid())
return false;
- const QRegExp regexp = filterRegExp();
- if (regexp.isEmpty() || sourceModel()->rowCount(index) > 0)
+ const QRegularExpression regexp = filterRegularExpression();
+ if (regexp.pattern().isEmpty() || sourceModel()->rowCount(index) > 0)
return true;
const QString displayText = index.data(Qt::DisplayRole).toString();
@@ -548,7 +548,7 @@ void VariableChooserPrivate::updatePositionAndShow(bool)
void VariableChooserPrivate::updateFilter(const QString &filterText)
{
- m_sortModel->setFilterWildcard(filterText);
+ m_sortModel->setFilterRegularExpression(QRegularExpression::wildcardToRegularExpression(filterText));
m_variableTree->expandAll();
}
diff --git a/src/plugins/coreplugin/welcomepagehelper.cpp b/src/plugins/coreplugin/welcomepagehelper.cpp
index a8eeb042b8e..8e2b3c61a9f 100644
--- a/src/plugins/coreplugin/welcomepagehelper.cpp
+++ b/src/plugins/coreplugin/welcomepagehelper.cpp
@@ -603,6 +603,10 @@ bool ListItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
if (!item)
return false;
auto mev = static_cast<QMouseEvent *>(event);
+
+ if (mev->button() != Qt::LeftButton) // do not react on right click
+ return false;
+
if (index.isValid()) {
const QPoint pos = mev->pos();
if (pos.y() > option.rect.y() + GridProxyModel::TagsSeparatorY) {