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