Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add objects filter to the objects dock #1566

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bd138a3
Add the line edit
thabetx May 7, 2017
9b2c9a5
New filter
thabetx May 7, 2017
59d44b4
Initial working version
thabetx May 9, 2017
a821e3d
Fix object selection from map
thabetx May 9, 2017
eb8e1ae
Fix saving and restoring expanded groups
thabetx May 9, 2017
afaf337
Make the models private
thabetx May 9, 2017
6d42d49
Fix style
thabetx May 9, 2017
0553dbb
Update filtering logic
thabetx May 10, 2017
2ffaefd
Add keyboard shortcuts to the objects dock
thabetx May 10, 2017
eeb0dd7
Fix style
thabetx May 10, 2017
e72855d
Save filter string between maps
thabetx May 10, 2017
6ebcdf9
Refactoring
thabetx May 12, 2017
a46173b
Use returnPressed signal instead of handling return key
thabetx May 12, 2017
6b522ae
Expand groups having matching objects
thabetx May 12, 2017
4dc0fdb
Restore the prefilter expansion state when filter is canceled
thabetx May 13, 2017
e425e53
Fix style
thabetx May 14, 2017
12eaea7
Use Filter string from TileStampsDock
thabetx May 16, 2017
5f6f1df
Refactor the switching code
thabetx May 16, 2017
184bff3
Prevent collapsing while filtering
thabetx May 16, 2017
d302c97
Fix spaces
thabetx May 17, 2017
3c0a470
Use nullptr instead of 0 for initializing ObjectsFilterModel
thabetx May 17, 2017
9a51d46
Rename mProxyModel to mReversingProxyModel
thabetx May 17, 2017
e4f3b46
Reverse the order of the proxy models
thabetx May 17, 2017
5a59bd7
Rename proxyIndex to viewIndex
thabetx May 17, 2017
fd400ad
Create mapToViewModel function
thabetx May 17, 2017
1ad9ef2
Refactor getGroupIndex code
thabetx May 18, 2017
a440d16
Merge remote-tracking branch 'upstream/master' into new-filter
thabetx May 18, 2017
b62505e
Filter using objects instead of indices
thabetx May 18, 2017
04cbe51
Rename getGroupIndex to groupIndex
thabetx May 18, 2017
7bdad99
Fixed the logic in ReversingProxyModel::sourceRowsAboutToBeInserted
bjorn May 20, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
187 changes: 158 additions & 29 deletions src/tiled/objectsdock.cpp
Expand Up @@ -37,7 +37,9 @@
#include <QBoxLayout>
#include <QContextMenuEvent>
#include <QHeaderView>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QSettings>
#include <QToolBar>
Expand All @@ -52,8 +54,10 @@ using namespace Tiled::Internal;

ObjectsDock::ObjectsDock(QWidget *parent)
: QDockWidget(parent)
, mFilterEdit(new QLineEdit(this))
, mObjectsView(new ObjectsView)
, mMapDocument(nullptr)
, mFilterWasEmpty(true)
{
setObjectName(QLatin1String("ObjectsDock"));

Expand All @@ -64,10 +68,15 @@ ObjectsDock::ObjectsDock(QWidget *parent)

MapDocumentActionHandler *handler = MapDocumentActionHandler::instance();

mFilterEdit->setClearButtonEnabled(true);
connect(mFilterEdit, &QLineEdit::textChanged, this, &ObjectsDock::filterObjects);
connect(mFilterEdit, &QLineEdit::returnPressed, [&] { mObjectsView->setFocus(); });

QWidget *widget = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(widget);
layout->setMargin(0);
layout->setSpacing(0);
layout->addWidget(mFilterEdit);
layout->addWidget(mObjectsView);

mActionNewLayer = new QAction(this);
Expand Down Expand Up @@ -136,7 +145,12 @@ void ObjectsDock::moveObjectsDown()
void ObjectsDock::setMapDocument(MapDocument *mapDoc)
{
if (mMapDocument) {
saveExpandedGroups();
mFilterStrings[mMapDocument] = mFilterEdit->text();

// Save only if no filter was applied, otherwise the state is already saved
if (mFilterEdit->text().isEmpty())
saveExpandedGroups();

mMapDocument->disconnect(this);
}

Expand All @@ -145,7 +159,19 @@ void ObjectsDock::setMapDocument(MapDocument *mapDoc)
mObjectsView->setMapDocument(mapDoc);

if (mMapDocument) {
restoreExpandedGroups();
QString filterString = mFilterStrings.take(mMapDocument);

// A hack to prevent saving the state if no filter was applied before switching
mFilterWasEmpty = false;

mFilterEdit->setText(filterString);

// Restore the expansion state if no filter is applied
if (filterString.isEmpty()) {
restoreExpandedGroups();
mFilterWasEmpty = true;
}

connect(mMapDocument, SIGNAL(selectedObjectsChanged()),
this, SLOT(updateActions()));
}
Expand Down Expand Up @@ -174,6 +200,9 @@ void ObjectsDock::retranslateUi()
mActionMoveUp->setToolTip(tr("Move Objects Up"));
mActionMoveDown->setToolTip(tr("Move Objects Down"));

// Avoiding adding new strings during the string freeze period
mFilterEdit->setPlaceholderText(QCoreApplication::translate("Tiled::Internal::TileStampsDock", "Filter"));

updateActions();
}

Expand Down Expand Up @@ -225,43 +254,82 @@ void ObjectsDock::saveExpandedGroups()
{
mExpandedGroups[mMapDocument].clear();

const auto proxyModel = static_cast<QAbstractProxyModel*>(mObjectsView->model());
const auto &objectGroups = mMapDocument->map()->objectGroups();

for (ObjectGroup *og : objectGroups) {
const QModelIndex sourceIndex = mMapDocument->mapObjectModel()->index(og);
const QModelIndex index = proxyModel->mapFromSource(sourceIndex);
if (mObjectsView->isExpanded(index))
for (ObjectGroup *og : objectGroups)
if (mObjectsView->isGroupExpanded(og))
mExpandedGroups[mMapDocument].append(og);
}
}

void ObjectsDock::restoreExpandedGroups()
{
const auto objectGroups = mExpandedGroups.take(mMapDocument);
for (ObjectGroup *og : objectGroups) {
const QModelIndex sourceIndex = mMapDocument->mapObjectModel()->index(og);
const QModelIndex index = static_cast<QAbstractProxyModel*>(mObjectsView->model())->mapFromSource(sourceIndex);
mObjectsView->setExpanded(index, true);
}

for (ObjectGroup *og : objectGroups)
mObjectsView->setGroupExpanded(og, true);
}

void ObjectsDock::documentAboutToClose(Document *document)
{
if (MapDocument *mapDocument = qobject_cast<MapDocument*>(document))
if (MapDocument *mapDocument = qobject_cast<MapDocument*>(document)) {
mFilterStrings.remove(mapDocument);
mExpandedGroups.remove(mapDocument);
}
}

void ObjectsDock::filterObjects()
{
// Save the prefilter expansion state if this is the first applied filter
if (mFilterWasEmpty) {
mFilterWasEmpty = false;
saveExpandedGroups();
}

mObjectsView->objectsFilterModel()->setFilterFixedString(mFilterEdit->text());

if (mFilterEdit->text().isEmpty()) { // Restore the prefilter expansion state
mFilterWasEmpty = true;
const auto &objectGroups = mMapDocument->map()->objectGroups();

// Collapse all groups so expansion is restored to the original state
for (ObjectGroup *og : objectGroups)
mObjectsView->setGroupExpanded(og, false);

restoreExpandedGroups();
mObjectsView->setItemsExpandable(true);
} else { // Expand the filtered Groups
const auto &objectGroups = mMapDocument->map()->objectGroups();

for (ObjectGroup *og : objectGroups)
mObjectsView->setGroupExpanded(og, true);

mObjectsView->setItemsExpandable(false);
}
}

void ObjectsDock::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape)
mFilterEdit->clear();
else
QDockWidget::keyPressEvent(event);
}

///// ///// ///// ///// /////

ObjectsView::ObjectsView(QWidget *parent)
: QTreeView(parent)
, mMapDocument(nullptr)
, mProxyModel(new ReversingProxyModel(this))
, mObjectsFilterModel(new ObjectsFilterModel(mapObjectModel() ,this))
, mReversingProxyModel(new ReversingProxyModel(this))
, mSynching(false)
{
setUniformRowHeights(true);
setModel(mProxyModel);

mObjectsFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
mReversingProxyModel->setSourceModel(mObjectsFilterModel);
setModel(mReversingProxyModel);

setItemDelegate(new EyeVisibilityDelegate(this));

setSelectionBehavior(QAbstractItemView::SelectRows);
Expand Down Expand Up @@ -293,7 +361,7 @@ void ObjectsView::setMapDocument(MapDocument *mapDoc)
mMapDocument = mapDoc;

if (mMapDocument) {
mProxyModel->setSourceModel(mMapDocument->mapObjectModel());
mObjectsFilterModel->setSourceModel(mMapDocument->mapObjectModel());

const QSettings *settings = Preferences::instance()->settings();
const int firstSectionSize =
Expand All @@ -306,7 +374,7 @@ void ObjectsView::setMapDocument(MapDocument *mapDoc)
restoreVisibleSections();
synchronizeSelectedItems();
} else {
mProxyModel->setSourceModel(nullptr);
mObjectsFilterModel->setSourceModel(nullptr);
}
}

Expand All @@ -329,19 +397,44 @@ bool ObjectsView::event(QEvent *event)
return QTreeView::event(event);
}

void ObjectsView::onPressed(const QModelIndex &proxyIndex)
QModelIndex ObjectsView::mapFromViewModel(const QModelIndex &viewIndex) const
{
return mObjectsFilterModel->mapToSource(mReversingProxyModel->mapToSource(viewIndex));
}

QModelIndex ObjectsView::mapToViewModel(const QModelIndex &sourceIndex) const
{
return mReversingProxyModel->mapFromSource(mObjectsFilterModel->mapFromSource(sourceIndex));
}

QModelIndex ObjectsView::groupIndex(ObjectGroup *objectGroup) const
{
const QModelIndex index = mProxyModel->mapToSource(proxyIndex);
return mapToViewModel(mMapDocument->mapObjectModel()->index(objectGroup));
}

void ObjectsView::setGroupExpanded(ObjectGroup *objectGroup, bool expand)
{
setExpanded(groupIndex(objectGroup), expand);
}

bool ObjectsView::isGroupExpanded(ObjectGroup *objectGroup) const
{
return isExpanded(groupIndex(objectGroup));
}

void ObjectsView::onPressed(const QModelIndex &viewIndex)
{
const QModelIndex index = mapFromViewModel(viewIndex);

if (MapObject *mapObject = mapObjectModel()->toMapObject(index))
mMapDocument->setCurrentObject(mapObject);
else if (Layer *layer = mapObjectModel()->toLayer(index))
mMapDocument->setCurrentObject(layer);
}

void ObjectsView::onActivated(const QModelIndex &proxyIndex)
void ObjectsView::onActivated(const QModelIndex &viewIndex)
{
const QModelIndex index = mProxyModel->mapToSource(proxyIndex);
const QModelIndex index = mapFromViewModel(viewIndex);

if (MapObject *mapObject = mapObjectModel()->toMapObject(index)) {
mMapDocument->setCurrentObject(mapObject);
Expand Down Expand Up @@ -371,7 +464,7 @@ void ObjectsView::selectionChanged(const QItemSelection &selected,

QList<MapObject*> selectedObjects;
for (const QModelIndex &proxyIndex : selectedProxyRows) {
const QModelIndex index = mProxyModel->mapToSource(proxyIndex);
const QModelIndex index = mapFromViewModel(proxyIndex);

if (MapObject *o = mapObjectModel()->toMapObject(index))
selectedObjects.append(o);
Expand Down Expand Up @@ -399,7 +492,7 @@ void ObjectsView::selectedObjectsChanged()
const QList<MapObject *> &selectedObjects = mMapDocument->selectedObjects();
if (selectedObjects.count() == 1) {
MapObject *o = selectedObjects.first();
scrollTo(mProxyModel->mapFromSource(mapObjectModel()->index(o)));
scrollTo(mapToViewModel(mapObjectModel()->index(o)));
}
}

Expand All @@ -414,7 +507,7 @@ void ObjectsView::setColumnVisibility(bool visible)

QSettings *settings = Preferences::instance()->settings();
QVariantList visibleSections;
for (int i = 0; i < mProxyModel->columnCount(); i++) {
for (int i = 0; i < mReversingProxyModel->columnCount(); i++) {
if (!header()->isSectionHidden(i))
visibleSections.append(i);
}
Expand All @@ -425,7 +518,7 @@ void ObjectsView::showCustomMenu(const QPoint &point)
{
Q_UNUSED(point)
QMenu contextMenu(this);
QAbstractItemModel *model = mProxyModel->sourceModel();
QAbstractItemModel *model = mReversingProxyModel->sourceModel();
for (int i = 0; i < model->columnCount(); i++) {
if (i == MapObjectModel::Name)
continue;
Expand All @@ -443,8 +536,8 @@ void ObjectsView::restoreVisibleSections()
{
QSettings *settings = Preferences::instance()->settings();
QVariantList visibleSections = settings->value(QLatin1String(VISIBLE_SECTIONS_KEY),
QVariantList() << MapObjectModel::Name << MapObjectModel::Type).toList();
for (int i = 0; i < mProxyModel->columnCount(); i++) {
QVariantList() << MapObjectModel::Name << MapObjectModel::Type).toList();
for (int i = 0; i < mReversingProxyModel->columnCount(); i++) {
header()->setSectionHidden(i, !visibleSections.contains(i));
}
}
Expand All @@ -457,7 +550,7 @@ void ObjectsView::synchronizeSelectedItems()
QItemSelection itemSelection;

for (MapObject *o : mMapDocument->selectedObjects()) {
QModelIndex index = mProxyModel->mapFromSource(mapObjectModel()->index(o));
QModelIndex index = mapToViewModel(mapObjectModel()->index(o));
itemSelection.select(index, index);
}

Expand All @@ -468,3 +561,39 @@ void ObjectsView::synchronizeSelectedItems()
QItemSelectionModel::Clear);
mSynching = false;
}

ObjectsFilterModel::ObjectsFilterModel(MapObjectModel *mapObjectModel, QObject *parent)
: QSortFilterProxyModel(parent)
, mMapObjectModel(mapObjectModel)
{
}

bool ObjectsFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
// Show all if filter string is empty. Prevents hiding empty groups
if (filterRegExp().isEmpty())
return true;

QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);

if (ObjectGroup *objectGroup = mMapObjectModel->toObjectGroup(index))
return groupHasAnyMatchingObjects(objectGroup);
else if (MapObject *mapObject = mMapObjectModel->toMapObject(index))
return objectContainsFilterString(mapObject);
else return false;
}

bool ObjectsFilterModel::groupHasAnyMatchingObjects(const ObjectGroup *objectGroup) const
{
for (auto object:objectGroup->objects())
if (objectContainsFilterString(object))
return true;
return false;
}

bool ObjectsFilterModel::objectContainsFilterString(const MapObject *mapObject) const
{
return mapObject->name().contains(filterRegExp()) ||
mapObject->type().contains(filterRegExp()) ||
QString::number(mapObject->id()).contains(filterRegExp());
}