Commit d6e38c0c authored by Tomaz  Canabrava's avatar Tomaz Canabrava

Fix Duplicates on Quick Open File

Summary:
The logic on the quick open was overcomplex, and it led to
bugs while filling the document list.
The new logic fills a vector with all the files,
organizes them by Url, remove the files based on the
duplicated url's (so name matching doesn't matter) and only
then starts to populate the view.

I belive this is also faster than the old code as I minimized
the amount of memory allocations and deallocations.

I'm unsure about the use of QFileInfo in the end of the code as
we can have too many files there and creating a temporary
QFileInfo just to get the name is a huge overhead, we can do a split('/').last()
for the file name. I'll do some measurements with that.

Reviewers: cullmann, brauch, neundorf

Reviewed By: cullmann

Subscribers: gregormi, dhaumann, kwrite-devel

Tags: #kate

Differential Revision: https://phabricator.kde.org/D15804
parent e2ebc770
......@@ -27,7 +27,7 @@ set (KATE_LIBRARY_SRCS
katesavemodifieddialog.cpp
katemwmodonhddialog.cpp
katecolorschemechooser.cpp
katequickopenmodel.cpp
katetabbutton.cpp
katetabbar.cpp
......
......@@ -18,6 +18,7 @@
*/
#include "katequickopen.h"
#include "katequickopenmodel.h"
#include "katemainwindow.h"
#include "kateviewmanager.h"
......@@ -26,6 +27,8 @@
#include <ktexteditor/document.h>
#include <ktexteditor/view.h>
#include <tuple>
#include <KPluginFactory>
#include <KPluginLoader>
#include <KAboutData>
......@@ -46,10 +49,6 @@
Q_DECLARE_METATYPE(QPointer<KTextEditor::Document>)
static const int DocumentRole = Qt::UserRole + 1;
static const int UrlRole = Qt::UserRole + 2;
static const int SortFilterRole = Qt::UserRole + 3;
KateQuickOpen::KateQuickOpen(QWidget *parent, KateMainWindow *mainWindow)
: QWidget(parent)
, m_mainWindow(mainWindow)
......@@ -69,11 +68,11 @@ KateQuickOpen::KateQuickOpen(QWidget *parent, KateMainWindow *mainWindow)
layout->addWidget(m_listView, 1);
m_listView->setTextElideMode(Qt::ElideLeft);
m_base_model = new QStandardItemModel(0, 2, this);
m_base_model = new KateQuickOpenModel(m_mainWindow, this);
m_model = new QSortFilterProxyModel(this);
m_model->setFilterRole(SortFilterRole);
m_model->setSortRole(SortFilterRole);
m_model->setFilterRole(Qt::DisplayRole);
m_model->setSortRole(Qt::DisplayRole);
m_model->setFilterCaseSensitivity(Qt::CaseInsensitive);
m_model->setSortCaseSensitivity(Qt::CaseInsensitive);
......@@ -144,161 +143,15 @@ void KateQuickOpen::reselectFirst()
void KateQuickOpen::update()
{
/**
* new base mode creation
* remove from proxy model before populating to avoid wasting time
* with repeatedly sorting it
*/
m_base_model->clear();
m_model->setSourceModel(nullptr);
/**
* remember local file names to avoid dupes with project files
*/
QSet<QString> alreadySeenFiles;
QSet<KTextEditor::Document *> alreadySeenDocs;
/**
* get views in lru order
*/
const QList<KTextEditor::View *> sortedViews(m_mainWindow->viewManager()->sortedViews());
/**
* now insert them in order
*/
QModelIndex idxToSelect;
int linecount = 0;
foreach (KTextEditor::View *view, sortedViews) {
KTextEditor::Document *doc = view->document();
if (alreadySeenDocs.contains(doc)) {
continue;
}
alreadySeenDocs.insert(doc);
//QStandardItem *item=new QStandardItem(i18n("%1: %2",doc->documentName(),doc->url().toString()));
QStandardItem *itemName = new QStandardItem(doc->documentName());
itemName->setData(qVariantFromValue(QPointer<KTextEditor::Document> (doc)), DocumentRole);
itemName->setData(QString::fromLatin1("%1: %2").arg(doc->documentName()).arg(doc->url().toString()), SortFilterRole);
itemName->setEditable(false);
QFont font = itemName->font();
font.setBold(true);
itemName->setFont(font);
QStandardItem *itemUrl = new QStandardItem(doc->url().toString());
itemUrl->setEditable(false);
m_base_model->setItem(linecount, 0, itemName);
m_base_model->setItem(linecount, 1, itemUrl);
linecount++;
if (!doc->url().isEmpty() && doc->url().isLocalFile()) {
alreadySeenFiles.insert(doc->url().toLocalFile());
}
// select second document, that is the last used (beside the active one)
if (linecount == 2) {
idxToSelect = itemName->index();
}
}
/**
* get all open documents
*/
QList<KTextEditor::Document *> docs = KateApp::self()->documentManager()->documentList();
foreach(KTextEditor::Document * doc, docs) {
/**
* skip docs already open
*/
if (alreadySeenDocs.contains(doc)) {
continue;
}
//QStandardItem *item=new QStandardItem(i18n("%1: %2",doc->documentName(),doc->url().toString()));
QStandardItem *itemName = new QStandardItem(doc->documentName());
itemName->setData(qVariantFromValue(QPointer<KTextEditor::Document> (doc)), DocumentRole);
itemName->setData(QString::fromLatin1("%1: %2").arg(doc->documentName()).arg(doc->url().toString()), SortFilterRole);
itemName->setEditable(false);
QFont font = itemName->font();
font.setBold(true);
itemName->setFont(font);
QStandardItem *itemUrl = new QStandardItem(doc->url().toString());
itemUrl->setEditable(false);
m_base_model->setItem(linecount, 0, itemName);
m_base_model->setItem(linecount, 1, itemUrl);
linecount++;
if (!doc->url().isEmpty() && doc->url().isLocalFile()) {
alreadySeenFiles.insert(doc->url().toLocalFile());
}
}
/**
* insert all project files, if any project around
*/
if (QObject *projectView = m_mainWindow->pluginView(QStringLiteral("kateprojectplugin"))) {
QStringList projectFiles = projectView->property("projectFiles").toStringList();
foreach(const QString & file, projectFiles) {
/**
* skip files already open
*/
if (alreadySeenFiles.contains(file)) {
continue;
}
QFileInfo fi(file);
QStandardItem *itemName = new QStandardItem(fi.fileName());
itemName->setData(qVariantFromValue(QUrl::fromLocalFile(file)), UrlRole);
itemName->setData(QString::fromLatin1("%1: %2").arg(fi.fileName()).arg(file), SortFilterRole);
itemName->setEditable(false);
QStandardItem *itemUrl = new QStandardItem(file);
itemUrl->setEditable(false);
m_base_model->setItem(linecount, 0, itemName);
m_base_model->setItem(linecount, 1, itemUrl);
linecount++;
}
}
if (idxToSelect.isValid()) {
m_listView->setCurrentIndex(m_model->mapFromSource(idxToSelect));
} else {
reselectFirst();
}
m_model->setSourceModel(m_base_model);
/**
* adjust view
*/
m_base_model->refresh();
m_listView->resizeColumnToContents(0);
}
void KateQuickOpen::slotReturnPressed()
{
/**
* open document for first element, if possible
* prefer to use the document pointer
*/
// our data is in column 0 (clicking on column 1 results in no data, therefore, create new index)
const QModelIndex index = m_listView->model()->index(m_listView->currentIndex().row(), 0);
KTextEditor::Document *doc = index.data(DocumentRole).value<QPointer<KTextEditor::Document> >();
if (doc) {
m_mainWindow->wrapper()->activateView(doc);
} else {
QUrl url = index.data(UrlRole).value<QUrl>();
if (!url.isEmpty()) {
const auto index = m_listView->model()->index(m_listView->currentIndex().row(), KateQuickOpenModel::Columns::FilePath);
auto url = QUrl(index.data(Qt::DisplayRole).toString());
m_mainWindow->wrapper()->openUrl(url);
}
}
/**
* in any case, switch back to view manager
*/
m_mainWindow->slotWindowActivated();
m_inputLine->clear();
}
......@@ -29,6 +29,7 @@ class QModelIndex;
class QStandardItemModel;
class QSortFilterProxyModel;
class QTreeView;
class KateQuickOpenModel;
class KateQuickOpen : public QWidget
{
......@@ -61,7 +62,7 @@ private:
/**
* our model we search in
*/
QStandardItemModel *m_base_model;
KateQuickOpenModel *m_base_model;
/**
* filtered model we search in
......
/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
---
Copyright (C) 2018 Tomaz Canabrava <tcanabrava@kde.org>
*/
#include "katequickopenmodel.h"
#include "katemainwindow.h"
#include "kateviewmanager.h"
#include "kateapp.h"
#include <ktexteditor/document.h>
#include <ktexteditor/view.h>
KateQuickOpenModel::KateQuickOpenModel(KateMainWindow *mainWindow, QObject *parent) :
QAbstractTableModel (parent), m_mainWindow(mainWindow)
{
}
int KateQuickOpenModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid()) {
return 0;
}
return m_modelEntries.size();
}
int KateQuickOpenModel::columnCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
return 2;
}
QVariant KateQuickOpenModel::data(const QModelIndex& idx, int role) const
{
if(! idx.isValid()) {
return {};
}
if (role != Qt::DisplayRole && role != Qt::FontRole) {
return {};
}
auto entry = m_modelEntries.at(idx.row());
if (role == Qt::DisplayRole) {
switch(idx.column()) {
case Columns::FileName: return entry.fileName;
case Columns::FilePath: return entry.filePath;
}
} else if (role == Qt::FontRole) {
if (entry.bold) {
QFont font;
font.setBold(true);
return font;
}
}
return {};
}
void KateQuickOpenModel::refresh()
{
QObject *projectView = m_mainWindow->pluginView(QStringLiteral("kateprojectplugin"));
const QList<KTextEditor::View *> sortedViews = m_mainWindow->viewManager()->sortedViews();
const QList<KTextEditor::Document *> openDocs = KateApp::self()->documentManager()->documentList();
const QStringList projectDocs = projectView ? projectView->property("projectFiles").toStringList() : QStringList();
QVector<ModelEntry> allDocuments;
allDocuments.resize(sortedViews.size() + openDocs.size() + projectDocs.size());
for (auto *view : qAsConst(sortedViews)) {
auto doc = view->document();
allDocuments.push_back({ doc->documentName(), doc->url().toString(QUrl::NormalizePathSegments), false });
}
QStringList openedUrls;
openedUrls.reserve(openDocs.size());
for (auto *doc : qAsConst(openDocs)) {
const auto normalizedUrl = doc->url().toString(QUrl::NormalizePathSegments);
allDocuments.push_back({ doc->documentName(), normalizedUrl, false });
openedUrls.push_back(normalizedUrl);
}
for (const auto& file : qAsConst(projectDocs)) {
QFileInfo fi(file);
allDocuments.push_back({ fi.fileName(), QUrl::fromLocalFile(file).toString(QUrl::NormalizePathSegments), false });
}
/** Sort the arrays via Url. */
std::sort(std::begin(allDocuments), std::end(allDocuments),
[](const ModelEntry& a, const ModelEntry& b) {
return a.filePath < b.filePath;
});
/** remove Duplicates. */
allDocuments.erase(
std::unique(allDocuments.begin(), allDocuments.end(),
[](const ModelEntry& a, const ModelEntry& b) {
return a.filePath == b.filePath;
}),
std::end(allDocuments));
for(auto& doc : allDocuments) {
if (Q_UNLIKELY(openedUrls.indexOf(doc.filePath) != -1)) {
doc.bold = true;
}
}
/** sort the arrays via boldness (open or not */
std::sort(std::begin(allDocuments), std::end(allDocuments),
[](const ModelEntry& a, const ModelEntry& b) {
return a.bold > b.bold;
});
beginResetModel();
m_modelEntries = allDocuments;
endResetModel();
}
/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
---
Copyright (C) 2018 Tomaz Canabrava <tcanabrava@kde.org>
*/
#ifndef KATEQUICKOPENMODEL_H
#define KATEQUICKOPENMODEL_H
#include <QAbstractTableModel>
#include <QVector>
#include <tuple>
#include <QVariant>
#include "katemainwindow.h"
struct ModelEntry {
QString fileName;
QString filePath;
bool bold;
};
class KateQuickOpenModel : public QAbstractTableModel {
Q_OBJECT
public:
enum Columns : int { FileName, FilePath, Bold };
explicit KateQuickOpenModel(KateMainWindow *mainWindow, QObject *parent=nullptr);
int rowCount(const QModelIndex& parent) const override;
int columnCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex& idx, int role) const override;
void refresh();
private:
QVector<ModelEntry> m_modelEntries;
/* TODO: don't rely in a pointer to the main window.
* this is bad enginering, but current code is too tigth
* on this and it's hard to untangle without breaking existing
* code.
*/
KateMainWindow *m_mainWindow;
};
#endif
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment