Commit aee35662 authored by Orgad Shaneh's avatar Orgad Shaneh Committed by Orgad Shaneh

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: default avatarDmitriy Kuminov <coding@dmik.org>
Task-number: QTCREATORBUG-10185
Change-Id: I633ea6d9b9b4fce8b67335dbcce1bda29254efde
Reviewed-by: default avatarEike Ziller <eike.ziller@theqtcompany.com>
parent 991cf849
......@@ -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,
......
......@@ -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()
......
......@@ -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()
......
......@@ -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;
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/*<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,8 +602,10 @@ void DocumentModelPrivate::itemChanged()
}
if (!found && !fixedPath.isEmpty())
m_entryByFixedPath[fixedPath] = entry;
QModelIndex mindex = index(idx + 1/*<no document>*/, 0);
emit dataChanged(mindex, mindex);
if (!disambiguateDisplayNames(d->m_entries.at(idx))) {
QModelIndex mindex = index(idx + 1/*<no document>*/, 0);
emit dataChanged(mindex, mindex);
}
}
QList<DocumentModel::Entry *> DocumentModel::entries()
......
......@@ -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;
......
......@@ -2386,7 +2386,7 @@ IEditor *EditorManager::openEditorWithContents(Id editorId,
}
if (!title.isEmpty())
edt->document()->setDisplayName(title);
edt->document()->setPreferredDisplayName(title);
EditorManagerPrivate::addEditor(edt);
......
......@@ -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())
......
......@@ -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
......@@ -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;
......
......@@ -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_)
......
......@@ -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()
......
......@@ -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();
......
......@@ -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;
}
......
......@@ -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);
......
......@@ -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)));
......
......@@ -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()
......
......@@ -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,
......
......@@ -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);
}
......
......@@ -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);
}
......
......@@ -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();
......
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