Prefix duplicate names in Open Documents to make them unique

If a document has a file name associated with it then the prefix is
composed from path components (subdirectories), starting from the one
where the file is located and going up the parents until the resulting
name becomes unique among other open documents.

If a document doesn't have an associated file name, then a sequential
number (starting from 1) is appended to the display name of the
document.

This feature is useful when working with big projects that have lots
of idendical file names across different subdirectories (e.g.
Makefile.in, main.cpp, etc.) that need to be edited at the same
time. It allows to easily recognize such a file when switching
between documents in the editor, w/o the need to place the
mouse pointer over the name entry to get its full path.

Started-by: Dmitriy Kuminov <coding@dmik.org>
Task-number: QTCREATORBUG-10185
Change-Id: I633ea6d9b9b4fce8b67335dbcce1bda29254efde
Reviewed-by: Eike Ziller <eike.ziller@theqtcompany.com>
This commit is contained in:
Orgad Shaneh
2015-02-25 14:36:03 +02:00
committed by Orgad Shaneh
parent 991cf84991
commit aee35662a2
20 changed files with 191 additions and 42 deletions

View File

@@ -601,7 +601,7 @@ void BazaarPlugin::showCommitWidget(const QList<VcsBaseClient::StatusItem> &stat
const QString msg = tr("Commit changes for \"%1\".").
arg(QDir::toNativeSeparators(m_submitRepository));
commitEditor->document()->setDisplayName(msg);
commitEditor->document()->setPreferredDisplayName(msg);
const BranchInfo branch = m_client->synchronousBranchQuery(m_submitRepository);
commitEditor->setFields(m_submitRepository, branch,

View File

@@ -42,7 +42,7 @@ CommitEditor::CommitEditor(const VcsBase::VcsBaseSubmitEditorParameters *paramet
: VcsBase::VcsBaseSubmitEditor(parameters, new BazaarCommitWidget),
m_fileModel(0)
{
document()->setDisplayName(tr("Commit Editor"));
document()->setPreferredDisplayName(tr("Commit Editor"));
}
BazaarCommitWidget *CommitEditor::commitWidget()

View File

@@ -41,7 +41,7 @@ using namespace ClearCase::Internal;
ClearCaseSubmitEditor::ClearCaseSubmitEditor(const VcsBase::VcsBaseSubmitEditorParameters *parameters) :
VcsBase::VcsBaseSubmitEditor(parameters, new ClearCaseSubmitEditorWidget)
{
document()->setDisplayName(tr("ClearCase Check In"));
document()->setPreferredDisplayName(tr("ClearCase Check In"));
}
ClearCaseSubmitEditorWidget *ClearCaseSubmitEditor::submitEditorWidget()

View File

@@ -40,6 +40,7 @@
#include <QDir>
#include <QIcon>
#include <QMimeData>
#include <QSet>
#include <QUrl>
namespace Core {
@@ -69,11 +70,40 @@ public:
int indexOfFilePath(const Utils::FileName &filePath) const;
int indexOfDocument(IDocument *document) const;
bool disambiguateDisplayNames(DocumentModel::Entry *entry);
private slots:
friend class DocumentModel;
void itemChanged();
private:
class DynamicEntry
{
public:
DocumentModel::Entry *entry;
int pathComponents;
DynamicEntry(DocumentModel::Entry *e) :
entry(e),
pathComponents(0)
{
}
DocumentModel::Entry *operator->() const { return entry; }
void disambiguate()
{
entry->document->setUniqueDisplayName(entry->fileName().fileName(++pathComponents));
}
void setNumberedName(int number)
{
entry->document->setUniqueDisplayName(QStringLiteral("%1 (%2)")
.arg(entry->document->displayName())
.arg(number));
}
};
const QIcon m_lockedIcon;
const QIcon m_unlockedIcon;
@@ -139,6 +169,11 @@ QString DocumentModel::Entry::displayName() const
return document ? document->displayName() : m_displayName;
}
QString DocumentModel::Entry::plainDisplayName() const
{
return document ? document->plainDisplayName() : m_displayName;
}
Id DocumentModel::Entry::id() const
{
return document ? document->id() : m_id;
@@ -199,28 +234,37 @@ void DocumentModelPrivate::addEntry(DocumentModel::Entry *entry)
// replace a non-loaded entry (aka 'restored') if possible
int previousIndex = indexOfFilePath(fileName);
if (previousIndex >= 0) {
if (entry->document && m_entries.at(previousIndex)->document == 0) {
DocumentModel::Entry *previousEntry = m_entries.at(previousIndex);
m_entries[previousIndex] = entry;
const bool replace = entry->document && !previousEntry->document;
if (replace) {
delete previousEntry;
m_entries[previousIndex] = entry;
if (!fixedPath.isEmpty())
m_entryByFixedPath[fixedPath] = entry;
connect(entry->document, SIGNAL(changed()), this, SLOT(itemChanged()));
} else {
delete entry;
entry = previousEntry;
}
previousEntry = 0;
disambiguateDisplayNames(entry);
if (replace)
connect(entry->document, SIGNAL(changed()), this, SLOT(itemChanged()));
return;
}
int index;
QString displayName = entry->displayName();
const QString displayName = entry->plainDisplayName();
for (index = 0; index < m_entries.count(); ++index) {
if (displayName.localeAwareCompare(m_entries.at(index)->displayName()) < 0)
int cmp = displayName.localeAwareCompare(m_entries.at(index)->plainDisplayName());
if (cmp < 0)
break;
if (cmp == 0 && fileName < d->m_entries.at(index)->fileName())
break;
}
int row = index + 1/*<no document>*/;
beginInsertRows(QModelIndex(), row, row);
m_entries.insert(index, entry);
disambiguateDisplayNames(entry);
if (!fixedPath.isEmpty())
m_entryByFixedPath[fixedPath] = entry;
if (entry->document)
@@ -228,6 +272,71 @@ void DocumentModelPrivate::addEntry(DocumentModel::Entry *entry)
endInsertRows();
}
bool DocumentModelPrivate::disambiguateDisplayNames(DocumentModel::Entry *entry)
{
const QString displayName = entry->plainDisplayName();
int minIdx = -1, maxIdx = -1;
QList<DynamicEntry> dups;
for (int i = 0, total = m_entries.count(); i < total; ++i) {
DocumentModel::Entry *e = m_entries.at(i);
if (!e->document)
continue;
if (e == entry || e->plainDisplayName() == displayName) {
e->document->setUniqueDisplayName(QString());
dups += DynamicEntry(e);
maxIdx = i;
if (minIdx < 0)
minIdx = i;
}
}
const int dupsCount = dups.count();
if (dupsCount == 0)
return false;
if (dupsCount > 1) {
int serial = 0;
int count = 0;
// increase uniqueness unless no dups are left
forever {
bool seenDups = false;
for (int i = 0; i < dupsCount - 1; ++i) {
DynamicEntry &e = dups[i];
const Utils::FileName myFileName = e->document->filePath();
if (e->document->isTemporary() || myFileName.isEmpty() || count > 10) {
// path-less entry, append number
e.setNumberedName(++serial);
continue;
}
for (int j = i + 1; j < dupsCount; ++j) {
DynamicEntry &e2 = dups[j];
if (e->displayName() == e2->displayName()) {
const Utils::FileName otherFileName = e2->document->filePath();
if (otherFileName.isEmpty())
continue;
seenDups = true;
e2.disambiguate();
if (j > maxIdx)
maxIdx = j;
}
}
if (seenDups) {
e.disambiguate();
++count;
break;
}
}
if (!seenDups)
break;
}
}
emit dataChanged(index(minIdx + 1, 0), index(maxIdx + 1, 0));
return true;
}
int DocumentModelPrivate::indexOfFilePath(const Utils::FileName &filePath) const
{
if (filePath.isEmpty())
@@ -285,6 +394,7 @@ void DocumentModelPrivate::removeDocument(int idx)
}
if (IDocument *document = entry->document)
disconnect(document, SIGNAL(changed()), this, SLOT(itemChanged()));
disambiguateDisplayNames(entry);
delete entry;
}
@@ -298,6 +408,14 @@ void DocumentModel::removeAllRestoredEntries()
d->endRemoveRows();
}
}
QSet<QString> displayNames;
foreach (DocumentModel::Entry *entry, d->m_entries) {
const QString displayName = entry->plainDisplayName();
if (displayNames.contains(displayName))
continue;
displayNames.insert(displayName);
d->disambiguateDisplayNames(entry);
}
}
QList<IEditor *> DocumentModel::editorsForDocument(IDocument *document)
@@ -406,10 +524,12 @@ QVariant DocumentModelPrivate::data(const QModelIndex &index, int role) const
}
const DocumentModel::Entry *e = m_entries.at(entryIndex);
switch (role) {
case Qt::DisplayRole:
return (e->document && e->document->isModified())
? e->displayName() + QLatin1Char('*')
: e->displayName();
case Qt::DisplayRole: {
QString name = e->displayName();
if (e->document && e->document->isModified())
name += QLatin1Char('*');
return name;
}
case Qt::DecorationRole:
{
bool showLock = false;
@@ -420,9 +540,7 @@ QVariant DocumentModelPrivate::data(const QModelIndex &index, int role) const
return showLock ? m_lockedIcon : QIcon();
}
case Qt::ToolTipRole:
return e->fileName().isEmpty()
? e->displayName()
: e->fileName().toUserOutput();
return e->fileName().isEmpty() ? e->displayName() : e->fileName().toUserOutput();
default:
return QVariant();
}
@@ -484,9 +602,11 @@ void DocumentModelPrivate::itemChanged()
}
if (!found && !fixedPath.isEmpty())
m_entryByFixedPath[fixedPath] = entry;
if (!disambiguateDisplayNames(d->m_entries.at(idx))) {
QModelIndex mindex = index(idx + 1/*<no document>*/, 0);
emit dataChanged(mindex, mindex);
}
}
QList<DocumentModel::Entry *> DocumentModel::entries()
{

View File

@@ -61,6 +61,8 @@ public:
IDocument *document;
Utils::FileName fileName() const;
QString displayName() const;
QString plainDisplayName() const;
QString uniqueDisplayName() const;
Id id() const;
Utils::FileName m_fileName;
QString m_displayName;

View File

@@ -2386,7 +2386,7 @@ IEditor *EditorManager::openEditorWithContents(Id editorId,
}
if (!title.isEmpty())
edt->document()->setDisplayName(title);
edt->document()->setPreferredDisplayName(title);
EditorManagerPrivate::addEditor(edt);

View File

@@ -225,7 +225,8 @@ void OpenEditorsWindow::addHistoryItems(const QList<EditLocation> &history, Edit
if (hi.document.isNull() || documentsDone.contains(hi.document))
continue;
documentsDone.insert(hi.document.data());
QString title = hi.document->displayName();
DocumentModel::Entry *entry = DocumentModel::entryForDocument(hi.document);
QString title = entry ? entry->displayName() : hi.document->displayName();
QTC_ASSERT(!title.isEmpty(), continue);
QTreeWidgetItem *item = new QTreeWidgetItem();
if (hi.document->isModified())

View File

@@ -83,7 +83,8 @@ public:
QString mimeType;
Utils::FileName filePath;
QString displayName;
QString preferredDisplayName;
QString uniqueDisplayName;
QString autoSaveName;
InfoBar *infoBar;
Id id;
@@ -258,13 +259,16 @@ void IDocument::setFilePath(const Utils::FileName &filePath)
/*!
Returns the string to display for this document, e.g. in the open document combo box
and pane.
The returned string has the following priority:
* Unique display name set by the document model
* Preferred display name set by the owner
* Base name of the document's file name
\sa setDisplayName()
*/
QString IDocument::displayName() const
{
if (!d->displayName.isEmpty())
return d->displayName;
return d->filePath.fileName();
return d->uniqueDisplayName.isEmpty() ? plainDisplayName() : d->uniqueDisplayName;
}
/*!
@@ -274,12 +278,30 @@ QString IDocument::displayName() const
\sa displayName()
\sa filePath()
*/
void IDocument::setDisplayName(const QString &name)
void IDocument::setPreferredDisplayName(const QString &name)
{
if (name == d->displayName)
if (name == d->preferredDisplayName)
return;
d->displayName = name;
d->preferredDisplayName = name;
emit changed();
}
/*!
\internal
Returns displayName without disambiguation.
*/
QString IDocument::plainDisplayName() const
{
return d->preferredDisplayName.isEmpty() ? d->filePath.fileName() : d->preferredDisplayName;
}
/*!
\internal
Sets unique display name for the document. Used by the document model.
*/
void IDocument::setUniqueDisplayName(const QString &name)
{
d->uniqueDisplayName = name;
}
} // namespace Core

View File

@@ -92,7 +92,9 @@ public:
Utils::FileName filePath() const;
virtual void setFilePath(const Utils::FileName &filePath);
QString displayName() const;
void setDisplayName(const QString &name);
void setPreferredDisplayName(const QString &name);
QString plainDisplayName() const;
void setUniqueDisplayName(const QString &name);
virtual bool isFileReadOnly() const;
bool isTemporary() const;

View File

@@ -50,10 +50,12 @@ OpenDocumentsFilter::OpenDocumentsFilter()
setPriority(High);
setIncludedByDefault(true);
connect(EditorManager::instance(), SIGNAL(editorOpened(Core::IEditor*)),
this, SLOT(refreshInternally()));
connect(EditorManager::instance(), SIGNAL(editorsClosed(QList<Core::IEditor*>)),
this, SLOT(refreshInternally()));
connect(DocumentModel::model(), &QAbstractItemModel::dataChanged,
this, &OpenDocumentsFilter::refreshInternally);
connect(DocumentModel::model(), &QAbstractItemModel::rowsInserted,
this, &OpenDocumentsFilter::refreshInternally);
connect(DocumentModel::model(), &QAbstractItemModel::rowsRemoved,
this, &OpenDocumentsFilter::refreshInternally);
}
QList<LocatorFilterEntry> OpenDocumentsFilter::matchesFor(QFutureInterface<LocatorFilterEntry> &future, const QString &entry_)

View File

@@ -394,7 +394,7 @@ void CodepasterPlugin::finishFetch(const QString &titleDescription,
// Open editor with title.
IEditor *editor = EditorManager::openEditor(fileName);
QTC_ASSERT(editor, return);
editor->document()->setDisplayName(titleDescription);
editor->document()->setPreferredDisplayName(titleDescription);
}
CodepasterPlugin *CodepasterPlugin::instance()

View File

@@ -307,7 +307,7 @@ void DisassemblerAgent::setContentsToDocument(const DisassemblerLines &contents)
d->document->setPlainText(contents.toString());
d->document->setDisplayName(_("Disassembler (%1)")
d->document->setPreferredDisplayName(_("Disassembler (%1)")
.arg(d->location.functionName()));
updateBreakpointMarkers();

View File

@@ -88,7 +88,7 @@ bool DiffEditorDocument::save(QString *errorString, const QString &fileName, boo
const QFileInfo fi(fileName);
setTemporary(false);
setFilePath(Utils::FileName::fromString(fi.absoluteFilePath()));
setDisplayName(QString());
setPreferredDisplayName(QString());
return true;
}

View File

@@ -98,7 +98,7 @@ Core::IDocument *DiffEditorManager::findOrCreate(const QString &vcsId, const QSt
document = qobject_cast<Internal::DiffEditorDocument *>(diffEditor->document());
QTC_ASSERT(diffEditor, return 0);
document->setDisplayName(displayName);
document->setPreferredDisplayName(displayName);
m_instance->m_idToDocument.insert(vcsId, document);

View File

@@ -1011,7 +1011,7 @@ IEditor *GitPlugin::openSubmitEditor(const QString &fileName, const CommitData &
title = tr("Git Commit");
}
IDocument *document = submitEditor->document();
document->setDisplayName(title);
document->setPreferredDisplayName(title);
VcsBasePlugin::setSource(document, m_submitRepository);
connect(submitEditor, SIGNAL(diff(QStringList,QStringList)), this, SLOT(submitEditorDiff(QStringList,QStringList)));
connect(submitEditor, SIGNAL(merge(QStringList)), this, SLOT(submitEditorMerge(QStringList)));

View File

@@ -45,7 +45,7 @@ CommitEditor::CommitEditor(const VcsBaseSubmitEditorParameters *parameters)
: VcsBaseSubmitEditor(parameters, new MercurialCommitWidget),
fileModel(0)
{
document()->setDisplayName(tr("Commit Editor"));
document()->setPreferredDisplayName(tr("Commit Editor"));
}
MercurialCommitWidget *CommitEditor::commitWidget()

View File

@@ -581,7 +581,7 @@ void MercurialPlugin::showCommitWidget(const QList<VcsBaseClient::StatusItem> &s
const QString msg = tr("Commit changes for \"%1\".").
arg(QDir::toNativeSeparators(m_submitRepository));
commitEditor->document()->setDisplayName(msg);
commitEditor->document()->setPreferredDisplayName(msg);
QString branch = versionControl()->vcsTopic(m_submitRepository);
commitEditor->setFields(m_submitRepository, branch,

View File

@@ -48,7 +48,7 @@ PerforceSubmitEditor::PerforceSubmitEditor(const VcsBase::VcsBaseSubmitEditorPar
VcsBaseSubmitEditor(parameters, new PerforceSubmitEditorWidget),
m_fileModel(new VcsBase::SubmitFileModel(this))
{
document()->setDisplayName(tr("Perforce Submit"));
document()->setPreferredDisplayName(tr("Perforce Submit"));
setFileModel(m_fileModel);
}

View File

@@ -39,7 +39,7 @@ using namespace Subversion::Internal;
SubversionSubmitEditor::SubversionSubmitEditor(const VcsBase::VcsBaseSubmitEditorParameters *parameters) :
VcsBase::VcsBaseSubmitEditor(parameters, new VcsBase::SubmitEditorWidget)
{
document()->setDisplayName(tr("Subversion Submit"));
document()->setPreferredDisplayName(tr("Subversion Submit"));
setDescriptionMandatory(false);
}

View File

@@ -185,7 +185,7 @@ VcsBaseSubmitEditor::VcsBaseSubmitEditor(const VcsBaseSubmitEditorParameters *pa
d(new VcsBaseSubmitEditorPrivate(parameters, editorWidget, this))
{
setWidget(d->m_widget);
document()->setDisplayName(QCoreApplication::translate("VCS", d->m_parameters->displayName));
document()->setPreferredDisplayName(QCoreApplication::translate("VCS", d->m_parameters->displayName));
// Message font according to settings
CompletingTextEdit *descriptionEdit = editorWidget->descriptionEdit();