# # # patch "guitone/src/model/Inventory.cpp" # from [0148aa9cddac6881602f0b5d9cac11b15e2a70a1] # to [94936a69183eb90f7dfa76bc11133f50aa08ae5a] # # patch "guitone/src/model/Inventory.h" # from [c2e138ad64013bad901b7070063e3b18548161bf] # to [f51bf6895af94f8b722edc6965b17766958627ff] # # patch "guitone/src/model/InventoryItem.cpp" # from [e305b43611376bc02e6b2f26871694d07c1ce19a] # to [86a0a0404442274286cdc56fdbfeb1484939413a] # # patch "guitone/src/model/InventoryItem.h" # from [7ca548cdb224d240876d5cb8654dc2b6548f5c20] # to [9221a2ea935f5e15ba8f34f6d9f25af8ef5e02c8] # # patch "guitone/src/view/InventoryView.cpp" # from [75865a2ee33160e4529cda5d8a587899e031c431] # to [08e7a36304a63a97c91992d39b81ea4eb620aa32] # # patch "guitone/src/view/MainWindow.cpp" # from [3d713da4f0ef02fb7a34fd7839cfc67d7b4a1876] # to [4b23f850fac013002072e62f25e3ebbc84d32ebf] # # patch "guitone/src/view/MainWindow.h" # from [1d9646474da2a667ec9b56ce3f30a8fc933b6969] # to [7a5289b56c377cc5c7c805b465f2d68944f0e014] # # patch "notes/status-writeup.ods" # from [429860477aa173edf0da057c36acee1f18170e55] # to [2cc2e9847c7723fe1c776e1a11cb779da19a6f05] # ============================================================ --- guitone/src/model/Inventory.cpp 0148aa9cddac6881602f0b5d9cac11b15e2a70a1 +++ guitone/src/model/Inventory.cpp 94936a69183eb90f7dfa76bc11133f50aa08ae5a @@ -25,6 +25,8 @@ #include +const QString Inventory::MIME_TYPE = "application/guitone.inventoryitem.list"; + Inventory::Inventory(QObject *parent) : QAbstractItemModel(parent) { // create a dummy item since the view needs at least one item @@ -60,7 +62,8 @@ void Inventory::parseOutput() QStringList lines = AutomateCommand::data.split("\n", QString::SkipEmptyParts); QMap renameMap; QMap::iterator renameIter; - + itemMap.clear(); + InventoryItem *item; QList tempItems; int status(0); @@ -91,6 +94,7 @@ void Inventory::parseOutput() } tempItems.push_back(item); + itemMap.insert(path, item); } int id = 0; @@ -114,6 +118,7 @@ void Inventory::parseOutput() branch->setChildren(buildTreeRecursive(tempItems, NULL)); rootItem->appendChild(branch); + itemMap.insert("", branch); // reset the model to repaint the view completly // (all QModelIndexes are discarded through that, e.g. selections!) @@ -244,18 +249,30 @@ bool Inventory::setData(const QModelInde InventoryItem * item = static_cast(idx.internalPointer()); if (!item || role != Qt::EditRole) return false; - if (!item->rename(value.toString())) return false; + // trim the path and bail out if the string is left empty + QString newName = value.toString().trimmed(); + if (newName.size() == 0) return false; + // abort the rename if the new name is invalid + // this is a bit more restrictive than it actually should be + // on UNIX platforms, however Win32 bails out if it finds any of them + if (newName.indexOf(QRegExp("[\\/:*?<>|]")) > -1) + { + emit workspaceActionFailed( + tr("The new filename contains invalid characters.") + ); + return false; + } + + QString newPath = item->getBaseDirectory() + newName; + + if (!renameItem(item, newPath)) return false; + emit layoutChanged(); return true; } -bool Inventory::setItemData(const QModelIndex & index, const QMap & roles) -{ - return false; -} - Qt::ItemFlags Inventory::flags(const QModelIndex &index) const { if (!index.isValid()) return 0; @@ -276,6 +293,94 @@ Qt::ItemFlags Inventory::flags(const QMo return flags; } +QStringList Inventory::mimeTypes() const +{ + QStringList types; + types << MIME_TYPE; + return types; +} + +QMimeData * Inventory::mimeData(const QModelIndexList &indexes) const +{ + QMimeData * mimeData = new QMimeData(); + QByteArray encodedData; + + QDataStream stream(&encodedData, QIODevice::WriteOnly); + + foreach (QModelIndex index, indexes) + { + if (!index.isValid()) continue; + + InventoryItem * item = static_cast(index.internalPointer); + Q_ASSERT(item); + stream << item->getPath(); + } + + mimeData->setData(MIME_TYPE, encodedData); + return mimeData; +} + +bool Inventory::dropMimeData(const QMimeData * data, + Qt::DropAction action, int row, int column, const QModelIndex & parent) +{ + if (action == Qt::IgnoreAction) + return true; + + if (!data->hasFormat(MIME_TYPE)) + return false; + + // there are two ways a drop can come in: + // a) one or more other items are dropped on an existing directory from + // the folder tree or the file list into the folder tree or the file + // list + // b) one or more items from the folder tree are placed into the file list + // but without having an actual item hit, then the drop occurs to the + // parent of the invalid item, i.e. the currently listed directory + // In each case the parent index should be valid since if a drop occurs in + // the void (i.e. no valid index is available), the root index is used + // by Qt internally, and this root may be the root of the rooted list view + // I just need for b) + Q_ASSERT(parent.isValid()); + + // ensure that the drop target has the correct type + InventoryItem * dropTarget = + static_cast(parent.internalPointer()); + + if (!dropTarget->isDirectory() || dropTarget->isCdUp()) + return false; + + QByteArray encodedData = data->data(MIME_TYPE); + QDataStream stream(&encodedData, QIODevice::ReadOnly); + bool abort = false; + + while (!stream.atEnd()) + { + QString path; + stream >> path; + + if (!itemMap.contains(path)) + { + W(QString("Unknown path %1").arg(path)); + continue; + } + + InventoryItem * item = itemMap[path]; + newPath = dropTarget->getBaseDirectory() + item->getFilename(); + + // FIXME: shall we do a rollback if some of the renamed items could + // not be renamed for some reason? + if (!renameItem(item, newPath)) + { + abort = true; + break; + } + } + + emit layoutChanged(); + + return !abort; +} + // the root item needs to store the header for each field QVariant Inventory::headerData(int section, Qt::Orientation orientation, int role) const { @@ -317,18 +422,6 @@ int Inventory::rowCount(const QModelInde return parentItem->childCount(); } -bool Inventory::insertRows(int row, int count, const QModelIndex & parent) -{ - // FIXME: implement item adding - return false; -} - -bool Inventory::insertColumns(int column, int count, const QModelIndex & parent) -{ - // FIXME: implement item adding - return false; -} - bool Inventory::parseInventoryLine( const QString &inputString, int &status, @@ -442,3 +535,99 @@ bool Inventory::handleError(int errCode) return true; } +bool Inventory::renameItem(InventoryItem * item, const QString & newPath) +{ + // check if the item is not ignored, dropped or unknown + if (item->hasStatus(InventoryItem::Unknown) || + item->hasStatus(InventoryItem::Ignored) || + item->hasStatus(InventoryItem::Dropped)) + { + emit workspaceActionFailed( + tr("Cannot rename unknown/ignored/dropped file %1.") + .arg(item->getPath()) + ); + return false; + } + + // check if the path actually changed + if (item->getPath() == newPath) + { + W(QString("File path %1 is unchanged").arg(newPath)); + return false; + } + + // check if the source item has already been moved + if (item->hasNotStatus(InventoryItem::RenamedFrom) && + item->hasNotStatus(InventoryItem::RenamedTo)) + { + // the item has not been moved, move the item to the new path by + // creating a new item and reference both with each other + + // look for an existing item with the same path + if (itemMap.contains(newPath)) + { + InventoryItem * newItem = itemMap[newPath]; + + // if it is an old path (renamed_from), set the source item + // as renamed_to + if (newItem->hasStatus(InventoryItem::Dropped) || + newItem->hasStatus(InventoryItem::RenamedFrom)) + { + newItem->setRenamedFrom(item); + item->setRenamedTo(newItem); + newItem->setChildren(item->getChildren()); + item->deleteAllChildren(); + + // FIXME: we need some kind of merge() method here to merge + // the attributes of item into newItem + + return true; + } + + // if it is a new path, abort + emit workspaceActionFailed( + tr("Cannot rename %1: Destination path %2 exists.") + .arg(item->getPath()).arg(newPath) + ); + return false; + } + } + // the item has been moved, check if it is an old path (renamed_from) or + // a new path (renamed_to) + else + if (item->hasStatus(InventoryItem::RenamedFrom) && + item->hasNotStatus(InventoryItem::RenamedTo)) + { + // TODO: this is an old path, find the target item and move that one + return false; + } + else + if (item->hasStatus(InventoryItem::RenamedTo) && + item->hasNotStatus(InventoryItem::RenamedFrom)) + { + // this is a new path, move it to the new location + InventoryItem * newItem = new InventoryItem(item); + + // FIXME: I hope this invalidates all references on the item + // but I guess it will just segfault... + delete item; + + newItem->setPath(newPath); + QString baseDir = newItem->getBaseDirectory(); + Q_ASSERT(itemMap.contains(baseDir)); + InventoryItem * parent = itemMap[baseDir]; + parent->appendChild(newItem); + return true; + } + else + if (item->hasStatus(InventoryItem::RenamedTo) && + item->hasStatus(InventoryItem::RenamedFrom)) + { + // TODO: this item has both an old and a new path assigned, rename both + // on the move + return false; + } + + Q_ASSERT(false); +} + ============================================================ --- guitone/src/model/Inventory.h c2e138ad64013bad901b7070063e3b18548161bf +++ guitone/src/model/Inventory.h f51bf6895af94f8b722edc6965b17766958627ff @@ -41,6 +41,7 @@ class Inventory : public QAbstractItemMo static QString getOption(QObject *, const QString &); static QString getBranchName(QObject *); static QString getBranchNameShort(QObject *); + static const QString MIME_TYPE; // needed Qt Model methods QVariant data(const QModelIndex&, int) const; @@ -60,16 +61,20 @@ class Inventory : public QAbstractItemMo bool handleError(int); bool parseInventoryLine(const QString &, int &, int &, int &, QString &, bool &); QList buildTreeRecursive(QList &, InventoryItem*); - + bool renameItem(InventoryItem *, const QString &); + InventoryItem * rootItem; IconProvider * iconProvider; QRegExp * regex; MonotoneDelegate * mtnDelegate; QString branchName; + + QMap itemMap; signals: void modelCreated(); void invalidWorkspaceFormat(const QString &); + void workspaceActionFailed(const QString &); }; #endif ============================================================ --- guitone/src/model/InventoryItem.cpp e305b43611376bc02e6b2f26871694d07c1ce19a +++ guitone/src/model/InventoryItem.cpp 86a0a0404442274286cdc56fdbfeb1484939413a @@ -307,57 +307,3 @@ QString InventoryItem::getRenameInfo() c return strings.join(", "); } -bool InventoryItem::rename(const QString & n) -{ - QString newName(n.trimmed()); - if (newName.size() == 0) return false; - - // the file wasn't renamed - if (newName == getFilename()) return false; - - qDebug("InventoryItem::rename: renaming to %s", qPrintable(newName)); - - // TODO: do the actual rename here - - // check if this item is part of a uncommitted rename action already - if (hasStatus(InventoryItem::RenamedTo)) - { - setPath(getBaseDirectory() + newName); - qDebug("InventoryItem::rename: item already renamed, changing item's name"); - return true; - } - - if (hasStatus(InventoryItem::RenamedFrom)) - { - InventoryItem * renamedTo = getRenamedTo(); - renamedTo->setPath(getBaseDirectory() + newName); - qDebug("InventoryItem::rename: item already renamed, changing target item's name"); - return true; - } - - // apparently its not, lets create a new rename entry - InventoryItem * renamedItem = new InventoryItem(this); - parentItem->appendChild(renamedItem); - - renamedItem->setStatus(status | InventoryItem::RenamedTo); - setStatus(status | InventoryItem::RenamedFrom); - - renamedItem->setPath(getBaseDirectory() + newName); - - renamedItem->setRenamedFrom(this); - this->setRenamedTo(renamedItem); - - // remove any children from the renamed_from item, these entries - // are now part of the renamed_to item - // FIXME: this crashes, we probably need to do this directly in the model - /* - if (isDirectory()) - { - deleteAllChildren(); - } - */ - - qDebug("InventoryItem::rename: created new rename item"); - - return true; -} ============================================================ --- guitone/src/model/InventoryItem.h 7ca548cdb224d240876d5cb8654dc2b6548f5c20 +++ guitone/src/model/InventoryItem.h 9221a2ea935f5e15ba8f34f6d9f25af8ef5e02c8 @@ -32,8 +32,8 @@ public: InventoryItem(const InventoryItem *); ~InventoryItem(void); - inline void setRenamedFrom(InventoryItem * from) { renamed_from = from; } - inline void setRenamedTo(InventoryItem * to) { renamed_to = to; } + inline void setRenamedFrom(InventoryItem * from) { renamed_from = from; status |= RenamedTo; } + inline void setRenamedTo(InventoryItem * to) { renamed_to = to; status |= RenamedFrom; } inline InventoryItem * getRenamedFrom(void) const { return renamed_from; } inline InventoryItem * getRenamedTo(void) const { return renamed_to; } @@ -57,10 +57,10 @@ public: inline int childCount(void) const { return children.count(); }; inline int columnCount(void) const { return 3; } - void setChildren(QList); + void setChildren(QList); void deleteAllChildren(void); - QString getRelativePath(const QString&) const; + QString getRelativePath(const QString &) const; QString getFilename(void) const; QString getBaseDirectory(void) const; @@ -71,9 +71,8 @@ public: /* needed for Qt model class */ int row(void) const; - void appendChild(InventoryItem*); + void appendChild(InventoryItem *); QVariant data(int, int) const; - bool rename(const QString &); static const int RenamedFrom; static const int RenamedTo; ============================================================ --- guitone/src/view/InventoryView.cpp 75865a2ee33160e4529cda5d8a587899e031c431 +++ guitone/src/view/InventoryView.cpp 08e7a36304a63a97c91992d39b81ea4eb620aa32 @@ -188,7 +188,10 @@ void InventoryView::slotContextMenuReque if (item->hasChangedRecursive()) { menu.addAction(actCommit); - menu.addAction(actRevisionDiff); + if (item->isDirectory() + { + menu.addAction(actRevisionDiff); + } } if (item->hasNotStatus(InventoryItem::Ignored) && ============================================================ --- guitone/src/view/MainWindow.cpp 3d713da4f0ef02fb7a34fd7839cfc67d7b4a1876 +++ guitone/src/view/MainWindow.cpp 4b23f850fac013002072e62f25e3ebbc84d32ebf @@ -68,6 +68,11 @@ MainWindow::MainWindow() this, SLOT(invalidWorkspaceFormat(const QString &)) ); + connect( + invModel, SIGNAL(workspaceActionFailed(const QString &)), + this, SLOT(workspaceActionFailed(const QString &)) + ); + // ProxyModels proxyModelFolderTree = new InventoryProxyModel(this, true); proxyModelFileList = new InventoryProxyModel(this, false); @@ -632,3 +637,16 @@ void MainWindow::checkIfWindowClose() if (mode == None) QTimer::singleShot(0, this, SLOT(close())); } +void MainWindow::workspaceActionFailed(const QString & msg) +{ + QMessageBox::critical( + this, + tr("Workspace action failed"), + tr("The triggered workspace action failed:\n\n" + "%1\n\n" + "Note that parts of the workspace might have been affected" + "if this action involved several items.").arg(msg) + QMessageBox::Ok + ); +} + ============================================================ --- guitone/src/view/MainWindow.h 1d9646474da2a667ec9b56ce3f30a8fc933b6969 +++ guitone/src/view/MainWindow.h 7a5289b56c377cc5c7c805b465f2d68944f0e014 @@ -79,6 +79,7 @@ private slots: void updateWindowList(); void activateOtherWindow(); void invalidWorkspaceFormat(const QString &); + void workspaceActionFailed(const QString &); void checkIfWindowClose(); private: ============================================================ # notes/status-writeup.ods is binary