# # # add_file "res/images/close.svg" # content [68c1fd79b2d42f89823747a02e7f9c06fa5e5d69] # # add_file "res/images/search.svg" # content [b0f0d68b564390106ec0d64a0ccfa84c00d79fba] # # add_file "src/view/widgets/SearchInput.cpp" # content [e46ad9c696ada33e34da30344acca3ac7cdf66ff] # # add_file "src/view/widgets/SearchInput.h" # content [8d3b0bd2ebee053cc3b5efe4f8e0803eeb8c78ee] # # patch "src/model/Inventory.cpp" # from [dbd52e14bb66a0aa0e2737f70af4da384f4a0ef3] # to [758c0469a1427919e7af3814b8437a79e6f8a35c] # # patch "src/model/Inventory.h" # from [268e93aa6b0b830a5bcc947353018fba8508ded3] # to [705eda6338ef80e953adaaa0b490e06c67a62138] # # patch "src/model/InventoryModel.cpp" # from [d957fdce854d8b0b59565817d5dc14d5bb390be9] # to [376f3442efd15efb785342f562bbf37b35561d06] # # patch "src/model/InventoryModel.h" # from [c4bdceaabf117ed45fe64da4bd3d56ca6d9e31a1] # to [1915c6ef78d7902a9fe55d206e8c6ea6de564ce5] # # patch "src/model/InventoryProxyModel.cpp" # from [208e2c793a5b154ee6c3088c23dda52dc9922bce] # to [41d21933f657f8770ad2bdb9326713a5fb87e304] # # patch "src/model/InventoryProxyModel.h" # from [feb55b88ad3148ca79c6b03117b77c3d251bed57] # to [31ef55bd31c2a7d42b78e2075d55ec0790d878d5] # # patch "src/view/mainwindows/WorkspaceWindow.cpp" # from [ea508344b2cd8067b9ff3b02340462104ac05409] # to [f9c8af974e5585beff33b6f55b69e2fd9bd1f10e] # # patch "src/view/mainwindows/WorkspaceWindow.h" # from [99c36a0a442928d9c0653e3fe12fc0d4fef92de7] # to [f20d3515c093e43af587667d01ff49a8a3154c2f] # ============================================================ --- res/images/close.svg 68c1fd79b2d42f89823747a02e7f9c06fa5e5d69 +++ res/images/close.svg 68c1fd79b2d42f89823747a02e7f9c06fa5e5d69 @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + ============================================================ --- res/images/search.svg b0f0d68b564390106ec0d64a0ccfa84c00d79fba +++ res/images/search.svg b0f0d68b564390106ec0d64a0ccfa84c00d79fba @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + ============================================================ --- src/view/widgets/SearchInput.cpp e46ad9c696ada33e34da30344acca3ac7cdf66ff +++ src/view/widgets/SearchInput.cpp e46ad9c696ada33e34da30344acca3ac7cdf66ff @@ -0,0 +1,86 @@ +/*************************************************************************** + * Copyright (C) 2009 by Thomas Keller * + * address@hidden * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + ***************************************************************************/ + +#include "SearchInput.h" +#include "vocab.h" +#include +#include + +SearchInput::SearchInput(QWidget * parent) + : QLineEdit(parent) +{ + setMaximumHeight(20); + +#ifdef Q_WS_MAC + setAttribute(Qt::WA_MacShowFocusRect, false); +#endif + + QFont font; + font.setPointSize(12); + setFont(font); + + setStyleSheet( + "QLineEdit { " + " border: 1px solid palette(dark); " + " border-radius: 10px; " + " padding: 0 20px 0 16px; " + " selection-background-color: palette(dark); " + " background: url(:/images/search.svg) no-repeat left center; " + "} " + ); + + QBoxLayout * layout = new QBoxLayout(QBoxLayout::RightToLeft, this); + clearButton = new QPushButton(this); + clearButton->setStyleSheet( + "QPushButton { " + " border: 0; " + " background: transparent no-repeat center middle url(:/images/close.svg);" + "} " + ); + layout->addWidget(clearButton); + layout->addStretch(); + layout->setContentsMargins(0, 4, 0, 4); + + connect( + clearButton, SIGNAL(clicked()), + this, SLOT(clear()) + ); + + connect( + clearButton, SIGNAL(clicked()), + clearButton, SLOT(hide()) + ); + + connect( + this, SIGNAL(textChanged(const QString &)), + this, SLOT(slotTextChanged(const QString &)) + ); + + clearButton->setVisible(text().size() > 0); +} + +SearchInput::~SearchInput() +{ + delete clearButton; +} + +void SearchInput::slotTextChanged(const QString & text) +{ + clearButton->setVisible(text.size() > 0); +} + ============================================================ --- src/view/widgets/SearchInput.h 8d3b0bd2ebee053cc3b5efe4f8e0803eeb8c78ee +++ src/view/widgets/SearchInput.h 8d3b0bd2ebee053cc3b5efe4f8e0803eeb8c78ee @@ -0,0 +1,42 @@ +/*************************************************************************** + * Copyright (C) 2009 by Thomas Keller * + * address@hidden * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + ***************************************************************************/ + +#ifndef SEARCH_INPUT_H +#define SEARCH_INPUT_H + +#include +#include +#include +#include + +class SearchInput : public QLineEdit +{ + Q_OBJECT +public: + SearchInput(QWidget *); + ~SearchInput(); + +private: + QPushButton * clearButton; + +private slots: + void slotTextChanged(const QString &); +}; + +#endif + ============================================================ --- src/model/Inventory.cpp dbd52e14bb66a0aa0e2737f70af4da384f4a0ef3 +++ src/model/Inventory.cpp 758c0469a1427919e7af3814b8437a79e6f8a35c @@ -47,6 +47,16 @@ void Inventory::read(const QString & pat { I(!workspacePath.isEmpty()); + if (itemMap.contains(path)) + { + const InventoryItem * item = itemMap.value(path); + if (item->hasStatus(InventoryItem::Unknown)) + { + W(QString("skipping read on unknown path '%1'").arg(path)); + return; + } + } + QStringList cmd = QStringList() << "inventory"; if (!path.isEmpty()) { @@ -112,8 +122,10 @@ void Inventory::processTaskResult(const // not tell us if the node exists on the filesystem - in this case we // want to _keep_ the node because otherwise people won't be able to // add unknown paths later on... - if (output.indexOf("unknown path") != -1 && - itemMap.contains(queriedPath)) + if (task->outOfBandMessagesMatch( + MonotoneTask::Warning, + QRegExp("/restriction includes unknown path .+/") + ) && itemMap.contains(queriedPath)) { if (QFile::exists(workspacePath + "/" + itemMap[queriedPath]->getPath())) { @@ -134,10 +146,8 @@ void Inventory::processTaskResult(const // everything else _should_ be a problem with the workspace format. // note that we do not explicitely check for this, we throw it just - // back to the user... - emit invalidWorkspaceFormat( - MonotoneUtil::stripMtnPrefix(output) - ); + // back to the user and hopefully log something useful + emit invalidWorkspaceFormat(); return; } ============================================================ --- src/model/Inventory.h 268e93aa6b0b830a5bcc947353018fba8508ded3 +++ src/model/Inventory.h 705eda6338ef80e953adaaa0b490e06c67a62138 @@ -60,7 +60,7 @@ signals: void dataChanged(ModelItem *); void inventoryRead(const QString &); - void invalidWorkspaceFormat(const QString &); + void invalidWorkspaceFormat(); }; #endif ============================================================ --- src/model/InventoryModel.cpp d957fdce854d8b0b59565817d5dc14d5bb390be9 +++ src/model/InventoryModel.cpp 376f3442efd15efb785342f562bbf37b35561d06 @@ -29,8 +29,8 @@ InventoryModel::InventoryModel(QObject * ); connect( - inventory, SIGNAL(invalidWorkspaceFormat(const QString &)), - this, SIGNAL(invalidWorkspaceFormat(const QString &)) + inventory, SIGNAL(invalidWorkspaceFormat()), + this, SIGNAL(invalidWorkspaceFormat()) ); connect( @@ -183,6 +183,11 @@ QModelIndex InventoryModel::index(int ro QModelIndex InventoryModel::index(int row, int column, const QModelIndex & parent) const { + if (column < 0) + { + return QModelIndex(); + } + ModelItem * parentItem; if (!parent.isValid()) @@ -291,6 +296,11 @@ int InventoryModel::rowCount(const QMode int InventoryModel::rowCount(const QModelIndex & parent) const { + if (parent.column() > 0) + { + return 0; + } + ModelItem * parentItem = inventory->rootItem; if (parent.isValid()) ============================================================ --- src/model/InventoryModel.h c4bdceaabf117ed45fe64da4bd3d56ca6d9e31a1 +++ src/model/InventoryModel.h 1915c6ef78d7902a9fe55d206e8c6ea6de564ce5 @@ -74,7 +74,7 @@ signals: signals: //! forward - void invalidWorkspaceFormat(const QString &); + void invalidWorkspaceFormat(); void pathsInserted(const QStringList &); void pathsRemoved(const QStringList &); }; ============================================================ --- src/model/InventoryProxyModel.cpp 208e2c793a5b154ee6c3088c23dda52dc9922bce +++ src/model/InventoryProxyModel.cpp 41d21933f657f8770ad2bdb9326713a5fb87e304 @@ -24,21 +24,23 @@ InventoryProxyModel::InventoryProxyModel : QSortFilterProxyModel(parent), folderTree(isFolderTree), sortColumn(0), hideIgnored(false), viewOption(All), sortOrder(Qt::AscendingOrder) -{} +{ + filter.setCaseSensitivity(Qt::CaseInsensitive); +} InventoryProxyModel::~InventoryProxyModel() {} bool InventoryProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const { - QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); - if (!index.isValid()) return false; - ModelItem * item = static_cast(index.internalPointer()); + QModelIndex sourceIndex = sourceModel()->index(sourceRow, 0, sourceParent); + ModelItem * item = static_cast(sourceIndex.internalPointer()); + // always return the root item if (item->isRoot()) return true; - PseudoItem * psitem = dynamic_cast(item); + PseudoItem * psitem = qobject_cast(item); if (psitem) { // only display this item if this is not the folder tree view @@ -50,7 +52,7 @@ bool InventoryProxyModel::filterAcceptsR I(false); } - InventoryItem * invitem = dynamic_cast(item); + InventoryItem * invitem = qobject_cast(item); I(invitem); // hide ignored files (not recursively) @@ -88,23 +90,28 @@ bool InventoryProxyModel::filterAcceptsR // check if we should only display folders acceptRow &= !folderTree || invitem->isDirectory(); + + acceptRow &= filter.isEmpty() || + const_cast(this)->itemMatchesFilter(invitem); + return acceptRow; } bool InventoryProxyModel::lessThan(const QModelIndex & left, const QModelIndex & right) const { - ModelItem * itemLeft = static_cast(left.internalPointer()); + ModelItem * itemLeft = static_cast(left.internalPointer()); ModelItem * itemRight = static_cast(right.internalPointer()); - InventoryItem * invitemLeft = dynamic_cast(itemLeft); - InventoryItem * invitemRight = dynamic_cast(itemRight); + InventoryItem * invitemLeft = qobject_cast(itemLeft); + InventoryItem * invitemRight = qobject_cast(itemRight); - // We shall sort by the name of the item + // place pseudo items always right on top, in no particular order + if (!invitemLeft || !invitemRight) + return false; + + // we shall sort by the name of the item if (sortColumn == 0) { - // place pseudo items always right on top - if (!invitemLeft || !invitemRight) return false; - if (invitemLeft->isDirectory() && invitemRight->isDirectory()) { return invitemLeft->getFilename() < invitemRight->getFilename(); @@ -123,16 +130,15 @@ bool InventoryProxyModel::lessThan(const // last case: !invitemLeft->isDirectory() && invitemRight->isDirectory() return false; } + // we shall sort only by status - else if (sortColumn == 1) + if (sortColumn == 1) { return invitemLeft->getStatusString() < invitemRight->getStatusString(); } + // anything else - else - { - return false; - } + return false; } void InventoryProxyModel::sort(int column, Qt::SortOrder order) @@ -146,7 +152,7 @@ void InventoryProxyModel::setHideIgnored { if (hide == hideIgnored) return; hideIgnored = hide; - filterChanged(); + invalidateFilter(); } void InventoryProxyModel::setViewOption(int opt) @@ -154,6 +160,56 @@ void InventoryProxyModel::setViewOption( ViewOption newOpt = static_cast(opt); if (viewOption == newOpt) return; viewOption = newOpt; - filterChanged(); + invalidateFilter(); } +void InventoryProxyModel::filterItemsByName(const QString & pattern) +{ + if (pattern.isEmpty()) + { + filter.setPattern(QString()); + filterCache.clear(); + invalidateFilter(); + return; + } + + QString escapedPattern = QRegExp::escape(pattern); + if (filter.pattern() == escapedPattern) + return; + + filter.setPattern(escapedPattern); + filterCache.clear(); + invalidateFilter(); +} + +bool InventoryProxyModel::itemMatchesFilter(const InventoryItem * item) +{ + QString path = item->getPath(); + if (filterCache.contains(path)) + { + return filterCache[path]; + } + + if (filter.indexIn(item->getLabel()) >= 0) + { + filterCache.insert(path, true); + return true; + } + + if (folderTree) + { + foreach (const ModelItem * child, item->getChildren()) + { + const InventoryItem * invitem = + qobject_cast(child); + + if (invitem && itemMatchesFilter(invitem)) + { + return true; + } + } + } + + return false; +} + ============================================================ --- src/model/InventoryProxyModel.h feb55b88ad3148ca79c6b03117b77c3d251bed57 +++ src/model/InventoryProxyModel.h 31ef55bd31c2a7d42b78e2075d55ec0790d878d5 @@ -19,6 +19,7 @@ #ifndef INVENTORYPROXYMODEL_H #define INVENTORYPROXYMODEL_H +#include "InventoryItem.h" #include class InventoryProxyModel : public QSortFilterProxyModel @@ -35,16 +36,23 @@ public: bool lessThan(const QModelIndex &, const QModelIndex &) const; void sort(int, Qt::SortOrder order = Qt::AscendingOrder); + static QModelIndex inventoryIndex(const QModelIndex &); + public slots: void setHideIgnoredFiles(bool); void setViewOption(int); + void filterItemsByName(const QString &); private: + bool itemMatchesFilter(const InventoryItem *); + bool folderTree; int sortColumn; bool hideIgnored; ViewOption viewOption; Qt::SortOrder sortOrder; + QRegExp filter; + QMap filterCache; }; #endif ============================================================ --- src/view/mainwindows/WorkspaceWindow.cpp ea508344b2cd8067b9ff3b02340462104ac05409 +++ src/view/mainwindows/WorkspaceWindow.cpp f9c8af974e5585beff33b6f55b69e2fd9bd1f10e @@ -28,11 +28,12 @@ WorkspaceWindow::WorkspaceWindow() : Dat WorkspaceWindow::WorkspaceWindow() : DatabaseWindow(), mainSplitter(0), listSplitter(0), treeView(0), listView(0), attrView(0), statusBar(0), - iconHelp(0), invModel(0), attrModel(0), proxyModelFolderTree(0), - proxyModelFileList(0), invWatcher(0) + toolBar(0), searchInput(0), iconHelp(0), invModel(0), attrModel(0), + proxyModelFolderTree(0), proxyModelFileList(0), invWatcher(0) { setObjectName("WorkspaceWindow"); setDockOptions(QMainWindow::ForceTabbedDocks); + setUnifiedTitleAndToolBarOnMac(true); } WorkspaceWindow::~WorkspaceWindow() @@ -48,6 +49,8 @@ WorkspaceWindow::~WorkspaceWindow() if (listSplitter) delete listSplitter; if (mainSplitter) delete mainSplitter; if (statusBar) delete statusBar; + if (searchInput) delete searchInput; + if (toolBar) delete toolBar; if (iconHelp) { removeDockWidget(iconHelp); @@ -97,6 +100,18 @@ void WorkspaceWindow::setup() statusBar = new QStatusBar(this); setStatusBar(statusBar); + toolBar = new QToolBar(this); + + QWidget * spacer = new QWidget(toolBar); + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + toolBar->addWidget(spacer); + + searchInput = new SearchInput(toolBar); + searchInput->setMaximumWidth(150); + toolBar->addWidget(searchInput); + + addToolBar(toolBar); + mainSplitter = new Splitter(this, "MainSplitter"); mainSplitter->setOrientation(Qt::Horizontal); @@ -198,6 +213,16 @@ void WorkspaceWindow::setup() proxyModelFolderTree = new InventoryProxyModel(this, true); proxyModelFileList = new InventoryProxyModel(this, false); + connect( + searchInput, SIGNAL(textChanged(const QString &)), + proxyModelFolderTree, SLOT(filterItemsByName(const QString &)) + ); + + connect( + searchInput, SIGNAL(textChanged(const QString &)), + proxyModelFileList, SLOT(filterItemsByName(const QString &)) + ); + proxyModelFolderTree->setSourceModel(invModel); proxyModelFileList->setSourceModel(invModel); @@ -217,8 +242,8 @@ void WorkspaceWindow::setup() ); connect( - invModel, SIGNAL(invalidWorkspaceFormat(const QString &)), - this, SLOT(invalidWorkspaceFormat(const QString &)) + invModel, SIGNAL(invalidWorkspaceFormat()), + this, SLOT(invalidWorkspaceFormat()) ); connect( @@ -356,12 +381,12 @@ void WorkspaceWindow::maybeReadNodeInfo( attrModel->readAttributes(invitem->getPath()); } -void WorkspaceWindow::invalidWorkspaceFormat(const QString & error) +void WorkspaceWindow::invalidWorkspaceFormat() { QMessageBox::critical( this, tr("Error"), - tr("Unable to read workspace format:\n%1").arg(error), + tr("Unable to read the workspace. Please check the log for details."), QMessageBox::Ok, 0, 0 ); close(); ============================================================ --- src/view/mainwindows/WorkspaceWindow.h 99c36a0a442928d9c0653e3fe12fc0d4fef92de7 +++ src/view/mainwindows/WorkspaceWindow.h f20d3515c093e43af587667d01ff49a8a3154c2f @@ -31,8 +31,10 @@ #include "Splitter.h" #include "IconHelp.h" #include "NodeInfo.h" +#include "SearchInput.h" #include +#include class WorkspaceWindow: public DatabaseWindow { @@ -55,18 +57,20 @@ protected: InventoryView * listView; AttributesView * attrView; QStatusBar * statusBar; + QToolBar * toolBar; + SearchInput * searchInput; IconHelp * iconHelp; NodeInfo * nodeInfo; - InventoryModel * invModel; - GetAttributes * attrModel; - InventoryProxyModel * proxyModelFolderTree; - InventoryProxyModel * proxyModelFileList; - InventoryWatcher * invWatcher; + InventoryModel * invModel; + GetAttributes * attrModel; + InventoryProxyModel * proxyModelFolderTree; + InventoryProxyModel * proxyModelFileList; + InventoryWatcher * invWatcher; private slots: void maybeReadNodeInfo(const QModelIndexList &); - void invalidWorkspaceFormat(const QString &); + void invalidWorkspaceFormat(); void openFile(const QString &); };