diff --git a/src/plugins/bazaar/bazaarplugin.cpp b/src/plugins/bazaar/bazaarplugin.cpp index 7e7d5dac576..9729f00d97c 100644 --- a/src/plugins/bazaar/bazaarplugin.cpp +++ b/src/plugins/bazaar/bazaarplugin.cpp @@ -601,7 +601,7 @@ void BazaarPlugin::showCommitWidget(const QList &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, diff --git a/src/plugins/bazaar/commiteditor.cpp b/src/plugins/bazaar/commiteditor.cpp index 885bb6d38ad..332f7539493 100644 --- a/src/plugins/bazaar/commiteditor.cpp +++ b/src/plugins/bazaar/commiteditor.cpp @@ -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() diff --git a/src/plugins/clearcase/clearcasesubmiteditor.cpp b/src/plugins/clearcase/clearcasesubmiteditor.cpp index 2b194ced05e..2f36dabcafb 100644 --- a/src/plugins/clearcase/clearcasesubmiteditor.cpp +++ b/src/plugins/clearcase/clearcasesubmiteditor.cpp @@ -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() diff --git a/src/plugins/coreplugin/editormanager/documentmodel.cpp b/src/plugins/coreplugin/editormanager/documentmodel.cpp index b30dab6d7c5..aa4b7b0401e 100644 --- a/src/plugins/coreplugin/editormanager/documentmodel.cpp +++ b/src/plugins/coreplugin/editormanager/documentmodel.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include 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; + DocumentModel::Entry *previousEntry = m_entries.at(previousIndex); + 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/**/; 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 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 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 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,8 +602,10 @@ void DocumentModelPrivate::itemChanged() } if (!found && !fixedPath.isEmpty()) m_entryByFixedPath[fixedPath] = entry; - QModelIndex mindex = index(idx + 1/**/, 0); - emit dataChanged(mindex, mindex); + if (!disambiguateDisplayNames(d->m_entries.at(idx))) { + QModelIndex mindex = index(idx + 1/**/, 0); + emit dataChanged(mindex, mindex); + } } QList DocumentModel::entries() diff --git a/src/plugins/coreplugin/editormanager/documentmodel.h b/src/plugins/coreplugin/editormanager/documentmodel.h index 8b5f286a66c..daf2996755c 100644 --- a/src/plugins/coreplugin/editormanager/documentmodel.h +++ b/src/plugins/coreplugin/editormanager/documentmodel.h @@ -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; diff --git a/src/plugins/coreplugin/editormanager/editormanager.cpp b/src/plugins/coreplugin/editormanager/editormanager.cpp index 83fc2583a8b..603373af2da 100644 --- a/src/plugins/coreplugin/editormanager/editormanager.cpp +++ b/src/plugins/coreplugin/editormanager/editormanager.cpp @@ -2386,7 +2386,7 @@ IEditor *EditorManager::openEditorWithContents(Id editorId, } if (!title.isEmpty()) - edt->document()->setDisplayName(title); + edt->document()->setPreferredDisplayName(title); EditorManagerPrivate::addEditor(edt); diff --git a/src/plugins/coreplugin/editormanager/openeditorswindow.cpp b/src/plugins/coreplugin/editormanager/openeditorswindow.cpp index 29962c596f2..b039cedaaa1 100644 --- a/src/plugins/coreplugin/editormanager/openeditorswindow.cpp +++ b/src/plugins/coreplugin/editormanager/openeditorswindow.cpp @@ -225,7 +225,8 @@ void OpenEditorsWindow::addHistoryItems(const QList &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()) diff --git a/src/plugins/coreplugin/idocument.cpp b/src/plugins/coreplugin/idocument.cpp index 075f13c2382..b5ce0eace62 100644 --- a/src/plugins/coreplugin/idocument.cpp +++ b/src/plugins/coreplugin/idocument.cpp @@ -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 diff --git a/src/plugins/coreplugin/idocument.h b/src/plugins/coreplugin/idocument.h index c5e4fa79f9d..86d7c3efc27 100644 --- a/src/plugins/coreplugin/idocument.h +++ b/src/plugins/coreplugin/idocument.h @@ -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; diff --git a/src/plugins/coreplugin/locator/opendocumentsfilter.cpp b/src/plugins/coreplugin/locator/opendocumentsfilter.cpp index d23ee536df2..68cd2597412 100644 --- a/src/plugins/coreplugin/locator/opendocumentsfilter.cpp +++ b/src/plugins/coreplugin/locator/opendocumentsfilter.cpp @@ -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)), - 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 OpenDocumentsFilter::matchesFor(QFutureInterface &future, const QString &entry_) diff --git a/src/plugins/cpaster/cpasterplugin.cpp b/src/plugins/cpaster/cpasterplugin.cpp index 571e8de60a6..338cb5dab92 100644 --- a/src/plugins/cpaster/cpasterplugin.cpp +++ b/src/plugins/cpaster/cpasterplugin.cpp @@ -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() diff --git a/src/plugins/debugger/disassembleragent.cpp b/src/plugins/debugger/disassembleragent.cpp index d1041622cb0..9269a8aafc1 100644 --- a/src/plugins/debugger/disassembleragent.cpp +++ b/src/plugins/debugger/disassembleragent.cpp @@ -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(); diff --git a/src/plugins/diffeditor/diffeditordocument.cpp b/src/plugins/diffeditor/diffeditordocument.cpp index 0049abe8ff3..262e2e9c09a 100644 --- a/src/plugins/diffeditor/diffeditordocument.cpp +++ b/src/plugins/diffeditor/diffeditordocument.cpp @@ -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; } diff --git a/src/plugins/diffeditor/diffeditormanager.cpp b/src/plugins/diffeditor/diffeditormanager.cpp index 44621939366..06ec139e99e 100644 --- a/src/plugins/diffeditor/diffeditormanager.cpp +++ b/src/plugins/diffeditor/diffeditormanager.cpp @@ -98,7 +98,7 @@ Core::IDocument *DiffEditorManager::findOrCreate(const QString &vcsId, const QSt document = qobject_cast(diffEditor->document()); QTC_ASSERT(diffEditor, return 0); - document->setDisplayName(displayName); + document->setPreferredDisplayName(displayName); m_instance->m_idToDocument.insert(vcsId, document); diff --git a/src/plugins/git/gitplugin.cpp b/src/plugins/git/gitplugin.cpp index 36e6b590ec6..4c9f1b9dbde 100644 --- a/src/plugins/git/gitplugin.cpp +++ b/src/plugins/git/gitplugin.cpp @@ -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))); diff --git a/src/plugins/mercurial/commiteditor.cpp b/src/plugins/mercurial/commiteditor.cpp index 73b9b7ab845..724ce2d100f 100644 --- a/src/plugins/mercurial/commiteditor.cpp +++ b/src/plugins/mercurial/commiteditor.cpp @@ -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() diff --git a/src/plugins/mercurial/mercurialplugin.cpp b/src/plugins/mercurial/mercurialplugin.cpp index 137a9de51cf..13ada03c798 100644 --- a/src/plugins/mercurial/mercurialplugin.cpp +++ b/src/plugins/mercurial/mercurialplugin.cpp @@ -581,7 +581,7 @@ void MercurialPlugin::showCommitWidget(const QList &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, diff --git a/src/plugins/perforce/perforcesubmiteditor.cpp b/src/plugins/perforce/perforcesubmiteditor.cpp index 2155a9b1663..3f595143241 100644 --- a/src/plugins/perforce/perforcesubmiteditor.cpp +++ b/src/plugins/perforce/perforcesubmiteditor.cpp @@ -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); } diff --git a/src/plugins/subversion/subversionsubmiteditor.cpp b/src/plugins/subversion/subversionsubmiteditor.cpp index d08e70824d2..9483d720bc6 100644 --- a/src/plugins/subversion/subversionsubmiteditor.cpp +++ b/src/plugins/subversion/subversionsubmiteditor.cpp @@ -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); } diff --git a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp index cc4b7eea417..dd9c2cc4439 100644 --- a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp +++ b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp @@ -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();