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 "qmljsoutline.h"
27 #include "qmloutlinemodel.h"
28 #include "qmljseditor.h"
29 #include "qmljsoutlinetreeview.h"
30
31 #include <coreplugin/find/itemviewfind.h>
32 #include <coreplugin/icore.h>
33 #include <coreplugin/idocument.h>
34 #include <coreplugin/editormanager/editormanager.h>
35
36 #include <QSettings>
37 #include <QAction>
38 #include <QVBoxLayout>
39 #include <QTextBlock>
40
41 using namespace QmlJS;
42
43 enum {
44 debug = false
45 };
46
47 namespace QmlJSEditor {
48 namespace Internal {
49
QmlJSOutlineFilterModel(QObject * parent)50 QmlJSOutlineFilterModel::QmlJSOutlineFilterModel(QObject *parent) :
51 QSortFilterProxyModel(parent)
52 {
53 setDynamicSortFilter(true);
54 }
55
flags(const QModelIndex & index) const56 Qt::ItemFlags QmlJSOutlineFilterModel::flags(const QModelIndex &index) const
57 {
58 Qt::ItemFlags f = sourceModel()->flags(index);
59 if (m_sorted)
60 f.setFlag(Qt::ItemIsDropEnabled, false);
61 return f;
62 }
63
filterAcceptsRow(int sourceRow,const QModelIndex & sourceParent) const64 bool QmlJSOutlineFilterModel::filterAcceptsRow(int sourceRow,
65 const QModelIndex &sourceParent) const
66 {
67 if (m_filterBindings) {
68 QModelIndex sourceIndex = sourceModel()->index(sourceRow, 0, sourceParent);
69 QVariant itemType = sourceIndex.data(QmlOutlineModel::ItemTypeRole);
70 if (itemType == QmlOutlineModel::NonElementBindingType)
71 return false;
72 }
73 return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
74 }
75
lessThan(const QModelIndex & sourceLeft,const QModelIndex & sourceRight) const76 bool QmlJSOutlineFilterModel::lessThan(const QModelIndex &sourceLeft,
77 const QModelIndex &sourceRight) const
78 {
79 if (!m_sorted)
80 return sourceLeft.row() > sourceRight.row();
81
82 return sourceLeft.data().toString() > sourceRight.data().toString();
83 }
84
data(const QModelIndex & index,int role) const85 QVariant QmlJSOutlineFilterModel::data(const QModelIndex &index, int role) const
86 {
87 if (role == QmlOutlineModel::AnnotationRole) {
88 // Don't show element id etc behind element if the property is also visible
89 if (!filterBindings()
90 && index.data(QmlOutlineModel::ItemTypeRole) == QmlOutlineModel::ElementType) {
91 return QVariant();
92 }
93 }
94 return QSortFilterProxyModel::data(index, role);
95 }
96
supportedDragActions() const97 Qt::DropActions QmlJSOutlineFilterModel::supportedDragActions() const
98 {
99 return sourceModel()->supportedDragActions();
100 }
101
filterBindings() const102 bool QmlJSOutlineFilterModel::filterBindings() const
103 {
104 return m_filterBindings;
105 }
106
setFilterBindings(bool filterBindings)107 void QmlJSOutlineFilterModel::setFilterBindings(bool filterBindings)
108 {
109 m_filterBindings = filterBindings;
110 invalidateFilter();
111 }
112
setSorted(bool sorted)113 void QmlJSOutlineFilterModel::setSorted(bool sorted)
114 {
115 m_sorted = sorted;
116 invalidate();
117 }
118
QmlJSOutlineWidget(QWidget * parent)119 QmlJSOutlineWidget::QmlJSOutlineWidget(QWidget *parent)
120 : TextEditor::IOutlineWidget(parent)
121 , m_treeView(new QmlJSOutlineTreeView(this))
122 , m_filterModel(new QmlJSOutlineFilterModel(this))
123 {
124 m_filterModel->setFilterBindings(false);
125
126 m_treeView->setModel(m_filterModel);
127 m_treeView->setSortingEnabled(true);
128
129 setFocusProxy(m_treeView);
130
131 auto layout = new QVBoxLayout;
132
133 layout->setContentsMargins(0, 0, 0, 0);
134 layout->setSpacing(0);
135 layout->addWidget(Core::ItemViewFind::createSearchableWrapper(m_treeView));
136
137 m_showBindingsAction = new QAction(this);
138 m_showBindingsAction->setText(tr("Show All Bindings"));
139 m_showBindingsAction->setCheckable(true);
140 m_showBindingsAction->setChecked(true);
141 connect(m_showBindingsAction, &QAction::toggled, this, &QmlJSOutlineWidget::setShowBindings);
142
143 setLayout(layout);
144 }
145
setEditor(QmlJSEditorWidget * editor)146 void QmlJSOutlineWidget::setEditor(QmlJSEditorWidget *editor)
147 {
148 m_editor = editor;
149
150 m_filterModel->setSourceModel(m_editor->qmlJsEditorDocument()->outlineModel());
151 m_treeView->expandAll();
152 connect(m_editor->qmlJsEditorDocument()->outlineModel(), &QAbstractItemModel::modelAboutToBeReset, m_treeView, [this]() {
153 if (m_treeView->selectionModel())
154 m_treeView->selectionModel()->blockSignals(true);
155 });
156 connect(m_editor->qmlJsEditorDocument()->outlineModel(), &QAbstractItemModel::modelReset, m_treeView, [this]() {
157 if (m_treeView->selectionModel())
158 m_treeView->selectionModel()->blockSignals(false);
159 });
160
161 connect(m_treeView->selectionModel(), &QItemSelectionModel::selectionChanged,
162 this, &QmlJSOutlineWidget::updateSelectionInText);
163
164 connect(m_treeView, &QAbstractItemView::activated,
165 this, &QmlJSOutlineWidget::focusEditor);
166
167 connect(m_editor, &QmlJSEditorWidget::outlineModelIndexChanged,
168 this, &QmlJSOutlineWidget::updateSelectionInTree);
169 connect(m_editor->qmlJsEditorDocument()->outlineModel(), &QmlOutlineModel::updated, this, [this] () {
170 m_treeView->expandAll();
171 m_editor->updateOutlineIndexNow();
172 });
173 }
174
filterMenuActions() const175 QList<QAction*> QmlJSOutlineWidget::filterMenuActions() const
176 {
177 return {m_showBindingsAction};
178 }
179
setCursorSynchronization(bool syncWithCursor)180 void QmlJSOutlineWidget::setCursorSynchronization(bool syncWithCursor)
181 {
182 m_enableCursorSync = syncWithCursor;
183 m_editor->updateOutlineIndexNow();
184 }
185
setSorted(bool sorted)186 void QmlJSOutlineWidget::setSorted(bool sorted)
187 {
188 m_sorted = sorted;
189 m_filterModel->setSorted(m_sorted);
190 }
191
restoreSettings(const QVariantMap & map)192 void QmlJSOutlineWidget::restoreSettings(const QVariantMap &map)
193 {
194 bool showBindings = map.value(QString::fromLatin1("QmlJSOutline.ShowBindings"), true).toBool();
195 m_showBindingsAction->setChecked(showBindings);
196 setSorted(map.value(QString("QmlJSOutline.Sort"), false).toBool());
197 }
198
settings() const199 QVariantMap QmlJSOutlineWidget::settings() const
200 {
201 return {
202 {QString("QmlJSOutline.ShowBindings"), m_showBindingsAction->isChecked()},
203 {QString("QmlJSOutline.Sort"), m_sorted}
204 };
205 }
206
updateSelectionInTree(const QModelIndex & index)207 void QmlJSOutlineWidget::updateSelectionInTree(const QModelIndex &index)
208 {
209 if (!syncCursor())
210 return;
211
212 m_blockCursorSync = true;
213
214 QModelIndex baseIndex = index;
215 QModelIndex filterIndex = m_filterModel->mapFromSource(baseIndex);
216 while (baseIndex.isValid() && !filterIndex.isValid()) { // Search for ancestor index actually shown
217 baseIndex = baseIndex.parent();
218 filterIndex = m_filterModel->mapFromSource(baseIndex);
219 }
220
221 m_treeView->setCurrentIndex(filterIndex);
222 m_treeView->scrollTo(filterIndex);
223 m_blockCursorSync = false;
224 }
225
updateSelectionInText(const QItemSelection & selection)226 void QmlJSOutlineWidget::updateSelectionInText(const QItemSelection &selection)
227 {
228 if (!syncCursor())
229 return;
230
231 if (!selection.indexes().isEmpty()) {
232 QModelIndex index = selection.indexes().first();
233
234 updateTextCursor(index);
235 }
236 }
237
updateTextCursor(const QModelIndex & index)238 void QmlJSOutlineWidget::updateTextCursor(const QModelIndex &index)
239 {
240 const auto update = [this](const QModelIndex &index) {
241 if (!m_editor->isOutlineCursorChangesBlocked()) {
242 QModelIndex sourceIndex = m_filterModel->mapToSource(index);
243
244 SourceLocation location
245 = m_editor->qmlJsEditorDocument()->outlineModel()->sourceLocation(sourceIndex);
246
247 if (!location.isValid())
248 return;
249
250 const QTextBlock lastBlock = m_editor->document()->lastBlock();
251 const uint textLength = lastBlock.position() + lastBlock.length();
252 if (location.offset >= textLength)
253 return;
254
255 Core::EditorManager::cutForwardNavigationHistory();
256 Core::EditorManager::addCurrentPositionToNavigationHistory();
257
258 QTextCursor textCursor = m_editor->textCursor();
259
260 textCursor.setPosition(location.offset);
261 m_editor->setTextCursor(textCursor);
262 m_editor->centerCursor();
263 }
264 };
265 m_blockCursorSync = true;
266 update(index);
267 m_blockCursorSync = false;
268 }
269
focusEditor()270 void QmlJSOutlineWidget::focusEditor()
271 {
272 m_editor->setFocus();
273 }
274
setShowBindings(bool showBindings)275 void QmlJSOutlineWidget::setShowBindings(bool showBindings)
276 {
277 m_filterModel->setFilterBindings(!showBindings);
278 m_treeView->expandAll();
279 m_editor->updateOutlineIndexNow();
280 }
281
syncCursor()282 bool QmlJSOutlineWidget::syncCursor()
283 {
284 return m_enableCursorSync && !m_blockCursorSync;
285 }
286
supportsEditor(Core::IEditor * editor) const287 bool QmlJSOutlineWidgetFactory::supportsEditor(Core::IEditor *editor) const
288 {
289 if (qobject_cast<QmlJSEditor*>(editor))
290 return true;
291 return false;
292 }
293
createWidget(Core::IEditor * editor)294 TextEditor::IOutlineWidget *QmlJSOutlineWidgetFactory::createWidget(Core::IEditor *editor)
295 {
296 auto widget = new QmlJSOutlineWidget;
297
298 auto qmlJSEditable = qobject_cast<const QmlJSEditor*>(editor);
299 auto qmlJSEditor = qobject_cast<QmlJSEditorWidget*>(qmlJSEditable->widget());
300 Q_ASSERT(qmlJSEditor);
301
302 widget->setEditor(qmlJSEditor);
303
304 return widget;
305 }
306
307 } // namespace Internal
308 } // namespace QmlJSEditor
309