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 "outlinefactory.h"
27 #include <coreplugin/coreconstants.h>
28 #include <coreplugin/icore.h>
29 #include <coreplugin/editormanager/editormanager.h>
30 #include <coreplugin/editormanager/ieditor.h>
31 
32 #include <utils/utilsicons.h>
33 #include <utils/qtcassert.h>
34 
35 #include <QToolButton>
36 #include <QLabel>
37 #include <QStackedWidget>
38 
39 #include <QDebug>
40 
41 namespace TextEditor {
42 
43 static QList<IOutlineWidgetFactory *> g_outlineWidgetFactories;
44 static QPointer<Internal::OutlineFactory> g_outlineFactory;
45 
IOutlineWidgetFactory()46 IOutlineWidgetFactory::IOutlineWidgetFactory()
47 {
48     g_outlineWidgetFactories.append(this);
49 }
50 
~IOutlineWidgetFactory()51 IOutlineWidgetFactory::~IOutlineWidgetFactory()
52 {
53     g_outlineWidgetFactories.removeOne(this);
54 }
55 
updateOutline()56 void IOutlineWidgetFactory::updateOutline()
57 {
58     if (QTC_GUARD(!g_outlineFactory.isNull()))
59         emit g_outlineFactory->updateOutline();
60 }
61 
62 namespace Internal {
63 
OutlineWidgetStack(OutlineFactory * factory)64 OutlineWidgetStack::OutlineWidgetStack(OutlineFactory *factory) :
65     QStackedWidget(),
66     m_factory(factory),
67     m_syncWithEditor(true),
68     m_sorted(false)
69 {
70     QLabel *label = new QLabel(tr("No outline available"), this);
71     label->setAlignment(Qt::AlignCenter);
72 
73     // set background to be white
74     label->setAutoFillBackground(true);
75     label->setBackgroundRole(QPalette::Base);
76 
77     addWidget(label);
78 
79     m_toggleSync = new QToolButton(this);
80     m_toggleSync->setIcon(Utils::Icons::LINK_TOOLBAR.icon());
81     m_toggleSync->setCheckable(true);
82     m_toggleSync->setChecked(true);
83     m_toggleSync->setToolTip(tr("Synchronize with Editor"));
84     connect(m_toggleSync, &QAbstractButton::clicked,
85             this, &OutlineWidgetStack::toggleCursorSynchronization);
86 
87     m_filterButton = new QToolButton(this);
88     // The ToolButton needs a parent because updateFilterMenu() sets
89     // it visible. That would open a top-level window if the button
90     // did not have a parent in that moment.
91 
92     m_filterButton->setIcon(Utils::Icons::FILTER.icon());
93     m_filterButton->setToolTip(tr("Filter tree"));
94     m_filterButton->setPopupMode(QToolButton::InstantPopup);
95     m_filterButton->setProperty("noArrow", true);
96     m_filterMenu = new QMenu(m_filterButton);
97     m_filterButton->setMenu(m_filterMenu);
98 
99     m_toggleSort = new QToolButton(this);
100     m_toggleSort->setIcon(Utils::Icons::SORT_ALPHABETICALLY_TOOLBAR.icon());
101     m_toggleSort->setCheckable(true);
102     m_toggleSort->setChecked(false);
103     m_toggleSort->setToolTip(tr("Sort Alphabetically"));
104     connect(m_toggleSort, &QAbstractButton::clicked, this, &OutlineWidgetStack::toggleSort);
105 
106     connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged,
107             this, &OutlineWidgetStack::updateEditor);
108     connect(factory, &OutlineFactory::updateOutline,
109             this, &OutlineWidgetStack::updateCurrentEditor);
110     updateCurrentEditor();
111 }
112 
toolButtons()113 QList<QToolButton *> OutlineWidgetStack::toolButtons()
114 {
115     return {m_filterButton, m_toggleSort, m_toggleSync};
116 }
117 
118 OutlineWidgetStack::~OutlineWidgetStack() = default;
119 
saveSettings(QSettings * settings,int position)120 void OutlineWidgetStack::saveSettings(QSettings *settings, int position)
121 {
122     const QString baseKey = QStringLiteral("Outline.%1.").arg(position);
123     settings->setValue(baseKey + QLatin1String("SyncWithEditor"), m_toggleSync->isChecked());
124     for (auto iter = m_widgetSettings.constBegin(); iter != m_widgetSettings.constEnd(); ++iter)
125         settings->setValue(baseKey + iter.key(), iter.value());
126 }
127 
restoreSettings(QSettings * settings,int position)128 void OutlineWidgetStack::restoreSettings(QSettings *settings, int position)
129 {
130     const QString baseKey = QStringLiteral("Outline.%1.").arg(position);
131 
132     bool syncWithEditor = true;
133     m_widgetSettings.clear();
134     foreach (const QString &longKey, settings->allKeys()) {
135         if (!longKey.startsWith(baseKey))
136             continue;
137 
138         const QString key = longKey.mid(baseKey.length());
139 
140         if (key == QLatin1String("SyncWithEditor")) {
141             syncWithEditor = settings->value(longKey).toBool();
142             continue;
143         }
144         m_widgetSettings.insert(key, settings->value(longKey));
145     }
146 
147     m_toggleSync->setChecked(syncWithEditor);
148     if (auto outlineWidget = qobject_cast<IOutlineWidget*>(currentWidget()))
149         outlineWidget->restoreSettings(m_widgetSettings);
150 }
151 
isCursorSynchronized() const152 bool OutlineWidgetStack::isCursorSynchronized() const
153 {
154     return m_syncWithEditor;
155 }
156 
toggleCursorSynchronization()157 void OutlineWidgetStack::toggleCursorSynchronization()
158 {
159     m_syncWithEditor = !m_syncWithEditor;
160     if (auto outlineWidget = qobject_cast<IOutlineWidget*>(currentWidget()))
161         outlineWidget->setCursorSynchronization(m_syncWithEditor);
162 }
163 
toggleSort()164 void OutlineWidgetStack::toggleSort()
165 {
166     m_sorted = !m_sorted;
167     if (auto outlineWidget = qobject_cast<IOutlineWidget*>(currentWidget()))
168         outlineWidget->setSorted(m_sorted);
169 }
170 
updateFilterMenu()171 void OutlineWidgetStack::updateFilterMenu()
172 {
173     m_filterMenu->clear();
174     if (auto outlineWidget = qobject_cast<IOutlineWidget*>(currentWidget())) {
175         foreach (QAction *filterAction, outlineWidget->filterMenuActions()) {
176             m_filterMenu->addAction(filterAction);
177         }
178     }
179     m_filterButton->setVisible(!m_filterMenu->actions().isEmpty());
180 }
181 
updateCurrentEditor()182 void OutlineWidgetStack::updateCurrentEditor()
183 {
184     updateEditor(Core::EditorManager::currentEditor());
185 }
186 
updateEditor(Core::IEditor * editor)187 void OutlineWidgetStack::updateEditor(Core::IEditor *editor)
188 {
189     IOutlineWidget *newWidget = nullptr;
190 
191     if (editor) {
192         for (IOutlineWidgetFactory *widgetFactory : qAsConst(g_outlineWidgetFactories)) {
193             if (widgetFactory->supportsEditor(editor)) {
194                 newWidget = widgetFactory->createWidget(editor);
195                 m_toggleSort->setVisible(widgetFactory->supportsSorting());
196                 break;
197             }
198         }
199     }
200 
201     if (newWidget != currentWidget()) {
202         // delete old widget
203         if (auto outlineWidget = qobject_cast<IOutlineWidget*>(currentWidget())) {
204             QVariantMap widgetSettings = outlineWidget->settings();
205             for (auto iter = widgetSettings.constBegin(); iter != widgetSettings.constEnd(); ++iter)
206                 m_widgetSettings.insert(iter.key(), iter.value());
207             removeWidget(outlineWidget);
208             delete outlineWidget;
209         }
210         if (newWidget) {
211             newWidget->restoreSettings(m_widgetSettings);
212             newWidget->setCursorSynchronization(m_syncWithEditor);
213             m_toggleSort->setChecked(newWidget->isSorted());
214             addWidget(newWidget);
215             setCurrentWidget(newWidget);
216             setFocusProxy(newWidget);
217         }
218 
219         updateFilterMenu();
220     }
221 }
222 
OutlineFactory()223 OutlineFactory::OutlineFactory()
224 {
225     QTC_CHECK(g_outlineFactory.isNull());
226     g_outlineFactory = this;
227     setDisplayName(tr("Outline"));
228     setId("Outline");
229     setPriority(600);
230 }
231 
createWidget()232 Core::NavigationView OutlineFactory::createWidget()
233 {
234     auto placeHolder = new OutlineWidgetStack(this);
235     return {placeHolder, placeHolder->toolButtons()};
236 }
237 
saveSettings(Utils::QtcSettings * settings,int position,QWidget * widget)238 void OutlineFactory::saveSettings(Utils::QtcSettings *settings, int position, QWidget *widget)
239 {
240     auto widgetStack = qobject_cast<OutlineWidgetStack *>(widget);
241     Q_ASSERT(widgetStack);
242     widgetStack->saveSettings(settings, position);
243 }
244 
restoreSettings(QSettings * settings,int position,QWidget * widget)245 void OutlineFactory::restoreSettings(QSettings *settings, int position, QWidget *widget)
246 {
247     auto widgetStack = qobject_cast<OutlineWidgetStack *>(widget);
248     Q_ASSERT(widgetStack);
249     widgetStack->restoreSettings(settings, position);
250 }
251 
252 } // namespace Internal
253 } // namespace TextEditor
254