Core: Use TreeModel for OpenEditorWindow

With 2000 open files this brings the population time from ~170ms
to ~20ms on my machine.

Change-Id: I1e6ba77ee4595276a7eaecafc9d7c84386b2155a
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
hjk
2023-10-05 14:26:26 +02:00
parent 577b26bf1c
commit fa0035cef3
2 changed files with 110 additions and 74 deletions

View File

@@ -10,9 +10,11 @@
#include "../idocument.h"
#include <utils/algorithm.h>
#include <utils/basetreeview.h>
#include <utils/fsengine/fileiconprovider.h>
#include <utils/hostosinfo.h>
#include <utils/qtcassert.h>
#include <utils/treemodel.h>
#include <utils/utilsicons.h>
#include <QFocusEvent>
@@ -24,59 +26,120 @@
Q_DECLARE_METATYPE(Core::Internal::EditorView*)
Q_DECLARE_METATYPE(Core::IDocument*)
using namespace Utils;
namespace Core::Internal {
class OpenEditorsTreeWidget : public QTreeWidget
class OpenEditorsItem : public TreeItem
{
public:
explicit OpenEditorsTreeWidget(QWidget *parent) : QTreeWidget(parent) {}
QVariant data(int column, int role) const override
{
if (column != 0)
return {};
if (!entry)
return {};
if (role == Qt::DecorationRole) {
return !entry->filePath().isEmpty() && entry->document->isFileReadOnly()
? DocumentModel::lockedIcon() : FileIconProvider::icon(entry->filePath());
}
if (role == Qt::DisplayRole) {
QString title = entry->displayName();
if (entry->document->isModified())
title += Tr::tr("*");
return title;
}
if (role == Qt::ToolTipRole)
return entry->filePath().toUserOutput();
return {};
}
DocumentModel::Entry *entry = nullptr;
EditorView *view = nullptr;
};
static void selectEditor(OpenEditorsItem *item)
{
if (!item)
return;
if (!EditorManagerPrivate::activateEditorForEntry(item->view, item->entry))
delete item;
}
class OpenEditorsView : public QTreeView
{
public:
OpenEditorsView()
{
m_model.setHeader({{}});
setModel(&m_model);
}
QSize sizeHint() const override
{
return QSize(sizeHintForColumn(0) + verticalScrollBar()->width() + frameWidth() * 2,
viewportSizeHint().height() + frameWidth() * 2);
}
};
enum class Role
{
Entry = Qt::UserRole,
View = Qt::UserRole + 1
OpenEditorsItem *currentItem() const
{
QModelIndexList indexes = selectedIndexes();
return indexes.size() == 1 ? m_model.itemForIndexAtLevel<1>(indexes.front()) : nullptr;
}
int currentRow() const
{
QModelIndexList indexes = selectedIndexes();
return indexes.size() == 1 ? indexes.front().row() : -1;
}
void mouseReleaseEvent(QMouseEvent *ev) override
{
QPoint pos = ev->pos();
QModelIndex idx = indexAt(pos);
if (OpenEditorsItem *item = m_model.itemForIndexAtLevel<1>(idx))
selectEditor(item);
setFocus();
}
TreeModel<TreeItem, OpenEditorsItem> m_model;
};
OpenEditorsWindow::OpenEditorsWindow(QWidget *parent) :
QFrame(parent, Qt::Popup),
m_editorList(new OpenEditorsTreeWidget(this))
QFrame(parent, Qt::Popup)
{
m_editorView = new OpenEditorsView;
setMinimumSize(300, 200);
m_editorList->setColumnCount(1);
m_editorList->header()->hide();
m_editorList->setIndentation(0);
m_editorList->setSelectionMode(QAbstractItemView::SingleSelection);
m_editorList->setTextElideMode(Qt::ElideMiddle);
m_editorView->header()->hide();
m_editorView->setIndentation(0);
m_editorView->setSelectionMode(QAbstractItemView::SingleSelection);
m_editorView->setTextElideMode(Qt::ElideMiddle);
if (Utils::HostOsInfo::isMacHost())
m_editorList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
m_editorList->installEventFilter(this);
m_editorList->setUniformRowHeights(true);
m_editorView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
m_editorView->installEventFilter(this);
m_editorView->setUniformRowHeights(true);
// We disable the frame on this list view and use a QFrame around it instead.
// This improves the look with QGTKStyle.
if (!Utils::HostOsInfo::isMacHost())
setFrameStyle(m_editorList->frameStyle());
m_editorList->setFrameStyle(QFrame::NoFrame);
setFrameStyle(m_editorView->frameStyle());
m_editorView->setFrameStyle(QFrame::NoFrame);
auto layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(m_editorList);
connect(m_editorList, &QTreeWidget::itemClicked,
this, &OpenEditorsWindow::editorClicked);
layout->addWidget(m_editorView);
}
void OpenEditorsWindow::selectAndHide()
{
setVisible(false);
selectEditor(m_editorList->currentItem());
selectEditor(m_editorView->currentItem());
}
void OpenEditorsWindow::setVisible(bool visible)
@@ -88,7 +151,7 @@ void OpenEditorsWindow::setVisible(bool visible)
bool OpenEditorsWindow::eventFilter(QObject *obj, QEvent *e)
{
if (obj == m_editorList) {
if (obj == m_editorView) {
if (e->type() == QEvent::KeyPress) {
auto ke = static_cast<QKeyEvent*>(e);
if (ke->key() == Qt::Key_Escape) {
@@ -97,7 +160,7 @@ bool OpenEditorsWindow::eventFilter(QObject *obj, QEvent *e)
}
if (ke->key() == Qt::Key_Return
|| ke->key() == Qt::Key_Enter) {
selectEditor(m_editorList->currentItem());
selectEditor(m_editorView->currentItem());
return true;
}
} else if (e->type() == QEvent::KeyRelease) {
@@ -115,18 +178,18 @@ bool OpenEditorsWindow::eventFilter(QObject *obj, QEvent *e)
void OpenEditorsWindow::focusInEvent(QFocusEvent *)
{
m_editorList->setFocus();
m_editorView->setFocus();
}
void OpenEditorsWindow::selectUpDown(bool up)
{
int itemCount = m_editorList->topLevelItemCount();
int itemCount = m_editorView->m_model.rootItem()->childCount();
if (itemCount < 2)
return;
int index = m_editorList->indexOfTopLevelItem(m_editorList->currentItem());
int index = m_editorView->currentRow();
if (index < 0)
return;
QTreeWidgetItem *editor = nullptr;
TreeItem *editor = nullptr;
int count = 0;
while (!editor && count < itemCount) {
if (up) {
@@ -138,11 +201,12 @@ void OpenEditorsWindow::selectUpDown(bool up)
if (index >= itemCount)
index = 0;
}
editor = m_editorList->topLevelItem(index);
editor = m_editorView->m_model.rootItem()->childAt(index);
count++;
}
if (editor) {
m_editorList->setCurrentItem(editor);
// m_editorView->setCurrentItem(editor);
m_editorView->setCurrentIndex(m_editorView->m_model.index(index, 0));
ensureCurrentVisible();
}
}
@@ -154,7 +218,7 @@ void OpenEditorsWindow::selectPreviousEditor()
QSize OpenEditorsWindow::sizeHint() const
{
return m_editorList->sizeHint() + QSize(frameWidth() * 2, frameWidth() * 2);
return m_editorView->sizeHint() + QSize(frameWidth() * 2, frameWidth() * 2);
}
void OpenEditorsWindow::selectNextEditor()
@@ -164,7 +228,7 @@ void OpenEditorsWindow::selectNextEditor()
void OpenEditorsWindow::setEditors(const QList<EditLocation> &globalHistory, EditorView *view)
{
m_editorList->clear();
m_editorView->m_model.clear();
QSet<const DocumentModel::Entry *> entriesDone;
addHistoryItems(view->editorHistory(), view, entriesDone);
@@ -176,26 +240,9 @@ void OpenEditorsWindow::setEditors(const QList<EditLocation> &globalHistory, Edi
addRemainingItems(view, entriesDone);
}
void OpenEditorsWindow::selectEditor(QTreeWidgetItem *item)
{
if (!item)
return;
auto entry = item->data(0, int(Role::Entry)).value<DocumentModel::Entry *>();
QTC_ASSERT(entry, return);
auto view = item->data(0, int(Role::View)).value<EditorView *>();
if (!EditorManagerPrivate::activateEditorForEntry(view, entry))
delete item;
}
void OpenEditorsWindow::editorClicked(QTreeWidgetItem *item)
{
selectEditor(item);
setFocus();
}
void OpenEditorsWindow::ensureCurrentVisible()
{
m_editorList->scrollTo(m_editorList->currentIndex(), QAbstractItemView::PositionAtCenter);
m_editorView->scrollTo(m_editorView->currentIndex(), QAbstractItemView::PositionAtCenter);
}
static DocumentModel::Entry *entryForEditLocation(const EditLocation &item)
@@ -228,23 +275,14 @@ void OpenEditorsWindow::addItem(DocumentModel::Entry *entry,
{
if (!Utils::insert(entriesDone, entry))
return;
QString title = entry->displayName();
QTC_ASSERT(!title.isEmpty(), return);
auto item = new QTreeWidgetItem();
if (entry->document->isModified())
title += Tr::tr("*");
item->setIcon(0, !entry->filePath().isEmpty() && entry->document->isFileReadOnly()
? DocumentModel::lockedIcon() : Utils::FileIconProvider::icon(entry->filePath()));
item->setText(0, title);
item->setToolTip(0, entry->filePath().toString());
item->setData(0, int(Role::Entry), QVariant::fromValue(entry));
item->setData(0, int(Role::View), QVariant::fromValue(view));
item->setTextAlignment(0, Qt::AlignLeft);
m_editorList->addTopLevelItem(item);
auto item = new OpenEditorsItem;
item->entry = entry;
item->view = view;
m_editorView->m_model.rootItem()->appendChild(item);
if (m_editorList->topLevelItemCount() == 1)
m_editorList->setCurrentItem(item);
if (m_editorView->m_model.rootItem()->childCount() == 1)
m_editorView->setCurrentIndex(m_editorView->m_model.index(0, 0));
}
} // Core::Internal

View File

@@ -9,12 +9,11 @@
#include <QFrame>
#include <QList>
QT_BEGIN_NAMESPACE
class QTreeWidgetItem;
QT_END_NAMESPACE
namespace Core::Internal {
class OpenEditorsItem;
class OpenEditorsView;
class OpenEditorsWindow : public QFrame
{
public:
@@ -34,8 +33,7 @@ public:
void selectAndHide();
private:
void editorClicked(QTreeWidgetItem *item);
static void selectEditor(QTreeWidgetItem *item);
void editorClicked(OpenEditorsItem *item);
void addHistoryItems(const QList<EditLocation> &history, EditorView *view,
QSet<const DocumentModel::Entry *> &entriesDone);
@@ -46,7 +44,7 @@ private:
void ensureCurrentVisible();
void selectUpDown(bool up);
class OpenEditorsTreeWidget *m_editorList;
OpenEditorsView *m_editorView;
};
} // Core::Internal