1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "modemanager.h"
27
28 #include "fancytabwidget.h"
29 #include "fancyactionbar.h"
30 #include "icore.h"
31 #include "mainwindow.h"
32
33 #include <coreplugin/actionmanager/actionmanager.h>
34 #include <coreplugin/actionmanager/command.h>
35 #include <coreplugin/coreconstants.h>
36 #include <coreplugin/imode.h>
37
38 #include <extensionsystem/pluginmanager.h>
39
40 #include <utils/algorithm.h>
41 #include <utils/qtcassert.h>
42
43 #include <QAction>
44 #include <QDebug>
45 #include <QMap>
46 #include <QMouseEvent>
47 #include <QVector>
48
49 using namespace Utils;
50
51 namespace Core {
52
53 /*!
54 \class Core::ModeManager
55 \inheaderfile coreplugin/modemanager.h
56 \ingroup mainclasses
57 \inmodule QtCreator
58
59 \brief The ModeManager class manages the activation of modes and the
60 actions in the mode selector's tool bar.
61
62 Modes are implemented with the IMode class. Use the ModeManager to
63 force activation of a mode, or to be notified when the active mode changed.
64
65 The ModeManager also manages the actions that are visible in the mode
66 selector's toolbar. Adding actions to the tool bar should be done very
67 sparingly.
68 */
69
70 /*!
71 \enum ModeManager::Style
72 \internal
73 */
74
75 /*!
76 \fn void ModeManager::currentModeAboutToChange(Utils::Id mode)
77
78 Emitted before the current mode changes to \a mode.
79 */
80
81 /*!
82 \fn void ModeManager::currentModeChanged(Utils::Id mode, Utils::Id oldMode)
83
84 Emitted after the current mode changed from \a oldMode to \a mode.
85 */
86
87 struct ModeManagerPrivate
88 {
89 void showMenu(int index, QMouseEvent *event);
90 void appendMode(IMode *mode);
91 void enabledStateChanged(IMode *mode);
92 void activateModeHelper(Id id);
93 void extensionsInitializedHelper();
94
95 Internal::MainWindow *m_mainWindow;
96 Internal::FancyTabWidget *m_modeStack;
97 Internal::FancyActionBar *m_actionBar;
98 QMap<QAction*, int> m_actions;
99 QVector<IMode*> m_modes;
100 QVector<Command*> m_modeCommands;
101 Context m_addedContexts;
102 int m_oldCurrent;
103 ModeManager::Style m_modeStyle = ModeManager::Style::IconsAndText;
104
105 bool m_startingUp = true;
106 Id m_pendingFirstActiveMode; // Valid before extentionsInitialized.
107 };
108
109 static ModeManagerPrivate *d;
110 static ModeManager *m_instance = nullptr;
111
indexOf(Id id)112 static int indexOf(Id id)
113 {
114 for (int i = 0; i < d->m_modes.count(); ++i) {
115 if (d->m_modes.at(i)->id() == id)
116 return i;
117 }
118 qDebug() << "Warning, no such mode:" << id.toString();
119 return -1;
120 }
121
showMenu(int index,QMouseEvent * event)122 void ModeManagerPrivate::showMenu(int index, QMouseEvent *event)
123 {
124 QTC_ASSERT(m_modes.at(index)->menu(), return);
125 m_modes.at(index)->menu()->popup(event->globalPos());
126 }
127
ModeManager(Internal::MainWindow * mainWindow,Internal::FancyTabWidget * modeStack)128 ModeManager::ModeManager(Internal::MainWindow *mainWindow,
129 Internal::FancyTabWidget *modeStack)
130 {
131 m_instance = this;
132 d = new ModeManagerPrivate();
133 d->m_mainWindow = mainWindow;
134 d->m_modeStack = modeStack;
135 d->m_oldCurrent = -1;
136 d->m_actionBar = new Internal::FancyActionBar(modeStack);
137 d->m_modeStack->addCornerWidget(d->m_actionBar);
138 setModeStyle(d->m_modeStyle);
139
140 connect(d->m_modeStack, &Internal::FancyTabWidget::currentAboutToShow,
141 this, &ModeManager::currentTabAboutToChange);
142 connect(d->m_modeStack, &Internal::FancyTabWidget::currentChanged,
143 this, &ModeManager::currentTabChanged);
144 connect(d->m_modeStack, &Internal::FancyTabWidget::menuTriggered,
145 this, [](int index, QMouseEvent *e) { d->showMenu(index, e); });
146 }
147
~ModeManager()148 ModeManager::~ModeManager()
149 {
150 delete d;
151 d = nullptr;
152 m_instance = nullptr;
153 }
154
155 /*!
156 Returns the id of the current mode.
157
158 \sa activateMode()
159 \sa currentMode()
160 */
currentModeId()161 Id ModeManager::currentModeId()
162 {
163 int currentIndex = d->m_modeStack->currentIndex();
164 if (currentIndex < 0)
165 return Id();
166 return d->m_modes.at(currentIndex)->id();
167 }
168
findMode(Id id)169 static IMode *findMode(Id id)
170 {
171 const int index = indexOf(id);
172 if (index >= 0)
173 return d->m_modes.at(index);
174 return nullptr;
175 }
176
177 /*!
178 Makes the mode with ID \a id the current mode.
179
180 \sa currentMode()
181 \sa currentModeId()
182 \sa currentModeAboutToChange()
183 \sa currentModeChanged()
184 */
activateMode(Id id)185 void ModeManager::activateMode(Id id)
186 {
187 d->activateModeHelper(id);
188 }
189
activateModeHelper(Id id)190 void ModeManagerPrivate::activateModeHelper(Id id)
191 {
192 if (m_startingUp) {
193 m_pendingFirstActiveMode = id;
194 } else {
195 const int currentIndex = m_modeStack->currentIndex();
196 const int newIndex = indexOf(id);
197 if (newIndex != currentIndex && newIndex >= 0)
198 m_modeStack->setCurrentIndex(newIndex);
199 }
200 }
201
extensionsInitialized()202 void ModeManager::extensionsInitialized()
203 {
204 d->extensionsInitializedHelper();
205 }
206
extensionsInitializedHelper()207 void ModeManagerPrivate::extensionsInitializedHelper()
208 {
209 m_startingUp = false;
210
211 Utils::sort(m_modes, &IMode::priority);
212 std::reverse(m_modes.begin(), m_modes.end());
213
214 for (IMode *mode : qAsConst(m_modes))
215 appendMode(mode);
216
217 if (m_pendingFirstActiveMode.isValid())
218 activateModeHelper(m_pendingFirstActiveMode);
219 }
220
addMode(IMode * mode)221 void ModeManager::addMode(IMode *mode)
222 {
223 QTC_ASSERT(d->m_startingUp, return);
224 d->m_modes.append(mode);
225 }
226
appendMode(IMode * mode)227 void ModeManagerPrivate::appendMode(IMode *mode)
228 {
229 const int index = m_modeCommands.count();
230
231 m_mainWindow->addContextObject(mode);
232
233 m_modeStack->insertTab(index, mode->widget(), mode->icon(), mode->displayName(),
234 mode->menu() != nullptr);
235 m_modeStack->setTabEnabled(index, mode->isEnabled());
236
237 // Register mode shortcut
238 const Id actionId = mode->id().withPrefix("QtCreator.Mode.");
239 QAction *action = new QAction(ModeManager::tr("Switch to <b>%1</b> mode").arg(mode->displayName()), m_instance);
240 Command *cmd = ActionManager::registerAction(action, actionId);
241 cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? QString("Meta+%1").arg(index + 1)
242 : QString("Ctrl+%1").arg(index + 1)));
243 m_modeCommands.append(cmd);
244
245 m_modeStack->setTabToolTip(index, cmd->action()->toolTip());
246 QObject::connect(cmd, &Command::keySequenceChanged, m_instance, [cmd, index, this] {
247 m_modeStack->setTabToolTip(index, cmd->action()->toolTip());
248 });
249
250 Id id = mode->id();
251 QObject::connect(action, &QAction::triggered, [this, id] {
252 ModeManager::activateMode(id);
253 ICore::raiseWindow(m_modeStack);
254 });
255
256 QObject::connect(mode, &IMode::enabledStateChanged, [this, mode] { enabledStateChanged(mode); });
257 }
258
removeMode(IMode * mode)259 void ModeManager::removeMode(IMode *mode)
260 {
261 const int index = d->m_modes.indexOf(mode);
262 if (index >= d->m_modes.size() - 1 && d->m_modes.size() > 1)
263 d->m_modeStack->setCurrentIndex(d->m_modes.size() - 2);
264 d->m_modes.remove(index);
265 if (d->m_startingUp)
266 return;
267
268 d->m_modeCommands.remove(index);
269 d->m_modeStack->removeTab(index);
270
271 d->m_mainWindow->removeContextObject(mode);
272 }
273
enabledStateChanged(IMode * mode)274 void ModeManagerPrivate::enabledStateChanged(IMode *mode)
275 {
276 int index = d->m_modes.indexOf(mode);
277 QTC_ASSERT(index >= 0, return);
278 d->m_modeStack->setTabEnabled(index, mode->isEnabled());
279
280 // Make sure we leave any disabled mode to prevent possible crashes:
281 if (mode->id() == ModeManager::currentModeId() && !mode->isEnabled()) {
282 // This assumes that there is always at least one enabled mode.
283 for (int i = 0; i < d->m_modes.count(); ++i) {
284 if (d->m_modes.at(i) != mode &&
285 d->m_modes.at(i)->isEnabled()) {
286 ModeManager::activateMode(d->m_modes.at(i)->id());
287 break;
288 }
289 }
290 }
291 }
292
293 /*!
294 Adds the \a action to the mode selector's tool bar.
295 Actions are sorted by \a priority in descending order.
296 Use this functionality very sparingly.
297 */
addAction(QAction * action,int priority)298 void ModeManager::addAction(QAction *action, int priority)
299 {
300 d->m_actions.insert(action, priority);
301
302 // Count the number of commands with a higher priority
303 int index = 0;
304 foreach (int p, d->m_actions) {
305 if (p > priority)
306 ++index;
307 }
308
309 d->m_actionBar->insertAction(index, action);
310 }
311
312 /*!
313 \internal
314 */
addProjectSelector(QAction * action)315 void ModeManager::addProjectSelector(QAction *action)
316 {
317 d->m_actionBar->addProjectSelector(action);
318 d->m_actions.insert(0, INT_MAX);
319 }
320
currentTabAboutToChange(int index)321 void ModeManager::currentTabAboutToChange(int index)
322 {
323 if (index >= 0) {
324 IMode *mode = d->m_modes.at(index);
325 if (mode)
326 emit currentModeAboutToChange(mode->id());
327 }
328 }
329
currentTabChanged(int index)330 void ModeManager::currentTabChanged(int index)
331 {
332 // Tab index changes to -1 when there is no tab left.
333 if (index < 0)
334 return;
335
336 IMode *mode = d->m_modes.at(index);
337 if (!mode)
338 return;
339
340 // FIXME: This hardcoded context update is required for the Debug and Edit modes, since
341 // they use the editor widget, which is already a context widget so the main window won't
342 // go further up the parent tree to find the mode context.
343 ICore::updateAdditionalContexts(d->m_addedContexts, mode->context());
344 d->m_addedContexts = mode->context();
345
346 IMode *oldMode = nullptr;
347 if (d->m_oldCurrent >= 0)
348 oldMode = d->m_modes.at(d->m_oldCurrent);
349 d->m_oldCurrent = index;
350 emit currentModeChanged(mode->id(), oldMode ? oldMode->id() : Id());
351 }
352
353 /*!
354 \internal
355 */
setFocusToCurrentMode()356 void ModeManager::setFocusToCurrentMode()
357 {
358 IMode *mode = findMode(currentModeId());
359 QTC_ASSERT(mode, return);
360 QWidget *widget = mode->widget();
361 if (widget) {
362 QWidget *focusWidget = widget->focusWidget();
363 if (!focusWidget)
364 focusWidget = widget;
365 focusWidget->setFocus();
366 }
367 }
368
369 /*!
370 \internal
371 */
setModeStyle(ModeManager::Style style)372 void ModeManager::setModeStyle(ModeManager::Style style)
373 {
374 const bool visible = style != Style::Hidden;
375 const bool iconsOnly = style == Style::IconsOnly;
376
377 d->m_modeStyle = style;
378 d->m_actionBar->setIconsOnly(iconsOnly);
379 d->m_modeStack->setIconsOnly(iconsOnly);
380 d->m_modeStack->setSelectionWidgetVisible(visible);
381 }
382
383 /*!
384 \internal
385 */
cycleModeStyle()386 void ModeManager::cycleModeStyle()
387 {
388 auto nextStyle = Style((int(modeStyle()) + 1) % 3);
389 setModeStyle(nextStyle);
390 }
391
392 /*!
393 \internal
394 */
modeStyle()395 ModeManager::Style ModeManager::modeStyle()
396 {
397 return d->m_modeStyle;
398 }
399
400 /*!
401 Returns the pointer to the instance. Only use for connecting to signals.
402 */
instance()403 ModeManager *ModeManager::instance()
404 {
405 return m_instance;
406 }
407
408 /*!
409 Returns a pointer to the current mode.
410
411 \sa activateMode()
412 \sa currentModeId()
413 */
currentMode()414 IMode *ModeManager::currentMode()
415 {
416 const int currentIndex = d->m_modeStack->currentIndex();
417 return currentIndex < 0 ? nullptr : d->m_modes.at(currentIndex);
418 }
419
420 } // namespace Core
421