forked from qt-creator/qt-creator
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:
committed by
Orgad Shaneh
parent
991cf84991
commit
aee35662a2
@@ -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;
|
||||
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;
|
||||
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();
|
||||
|
Reference in New Issue
Block a user