[app] Reworked support for folder renaming

The actual renaming is easier to do in BooksShelf rather than BooksPathModel
and it generally makes more sense.

Editing now also starts after long-pressing the title of the current folder.
This commit is contained in:
Slava Monich 2015-12-01 00:00:56 +02:00
parent 9f1261a822
commit 34fb489107
8 changed files with 111 additions and 89 deletions

View file

@ -38,24 +38,16 @@ MouseArea {
property alias text: label.text property alias text: label.text
property bool currentFolder property bool currentFolder
property bool editable property bool editable
property bool _editing
property bool _highlighted: pressed property bool _highlighted: pressed
property color _highlightedColor: Theme.rgba(Theme.highlightBackgroundColor, Theme.highlightBackgroundOpacity) property color _highlightedColor: Theme.rgba(Theme.highlightBackgroundColor, Theme.highlightBackgroundOpacity)
property bool _showPress: !currentFolder && (_highlighted || pressTimer.running) property bool _showPress: !currentFolder && (_highlighted || pressTimer.running)
signal rename(var to) signal rename(var to)
signal startEdit()
function editName() {
if (editable && !_editing) {
editor.text = text
_editing = true
}
}
onEditableChanged: { onEditableChanged: {
if (!editable && _editing) { // Sync edit field and the label
_editing = false if (editable) editor.text = text
}
} }
Column { Column {
@ -94,13 +86,14 @@ MouseArea {
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
} }
onTextChanged: { onTextChanged: {
if (!_editing) { if (!editable) {
editor.text = text editor.text = text
} }
} }
color: (currentFolder || pressed) ? Theme.highlightColor : Theme.primaryColor color: (currentFolder || pressed) ? Theme.highlightColor : Theme.primaryColor
opacity: _editing ? 0 : 1
visible: opacity > 0 visible: opacity > 0
opacity: editable ? 0 : 1
Behavior on opacity { FadeAnimation {} }
Behavior on color { ColorAnimation { duration: 100 } } Behavior on color { ColorAnimation { duration: 100 } }
} }
@ -115,17 +108,17 @@ MouseArea {
textLeftMargin: 0 textLeftMargin: 0
textRightMargin: 0 textRightMargin: 0
textTopMargin: 0 textTopMargin: 0
opacity: _editing ? 1 : 0
visible: opacity > 0 visible: opacity > 0
opacity: editable ? 1 : 0
Behavior on opacity { FadeAnimation {} }
//% "Enter folder name" //% "Enter folder name"
placeholderText: qsTrId("shelf-title-placeholder") placeholderText: qsTrId("shelf-title-placeholder")
EnterKey.enabled: text.length > 0 && text !== "." && text !== ".." && text.indexOf("/") < 0 EnterKey.enabled: text.length > 0 && text !== "." && text !== ".." && text.indexOf("/") < 0
EnterKey.onClicked: { EnterKey.onClicked: {
if (_editing) { if (editable) {
if (text) { if (text) {
root.rename(text) root.rename(text)
} }
_editing = false
parent.focus = true parent.focus = true
} }
} }
@ -141,6 +134,15 @@ MouseArea {
onPressed: pressTimer.start() onPressed: pressTimer.start()
onCanceled: pressTimer.stop() onCanceled: pressTimer.stop()
onPressAndHold: {
if (currentFolder && !editable) {
root.startEdit()
if (editable) {
editor.forceActiveFocus()
}
}
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: _showPress ? _highlightedColor : "transparent" color: _showPress ? _highlightedColor : "transparent"

View file

@ -70,12 +70,12 @@ SilicaFlickable {
onNeedDummyItemChanged: if (needDummyItem) hasDummyItem = true onNeedDummyItemChanged: if (needDummyItem) hasDummyItem = true
editMode: shelfView.editMode editMode: shelfView.editMode
onRelativePathChanged: longStartTimer.restart() onRelativePathChanged: longStartTimer.restart()
onPathChanged: globalSettings.currentFolder = path
} }
BooksPathModel { BooksPathModel {
id: pathModel id: pathModel
path: shelfModel.relativePath path: shelfModel.relativePath
storage: shelfModel.storage
} }
onEditModeChanged: { onEditModeChanged: {
@ -176,15 +176,10 @@ SilicaFlickable {
BooksShelfTitle { BooksShelfTitle {
width: grid.width width: grid.width
text: model.name text: model.name
editable: editMode editable: editMode && currentFolder
currentFolder: model.index === (pathModel.count-1) currentFolder: model.index === (pathModel.count-1)
onClicked: { onClicked: {
if (currentFolder) { if (!currentFolder) {
if (editMode) {
console.log("editing", model.name)
editName()
}
} else {
console.log("switching to", model.path) console.log("switching to", model.path)
shelfView.stopEditing() shelfView.stopEditing()
shelfModel.relativePath = model.path shelfModel.relativePath = model.path
@ -192,8 +187,9 @@ SilicaFlickable {
} }
onRename: { onRename: {
console.log(to) console.log(to)
model.name = to shelfModel.name = to
} }
onStartEdit: shelfView.startEditing()
} }
} }
} }

View file

@ -63,9 +63,7 @@ void BooksPathModel::setPath(QString aPath)
int i; int i;
QString path; QString path;
QStringList pathList; QStringList pathList;
QStringList parentList;
for (i=0; i<newSize; i++) { for (i=0; i<newSize; i++) {
parentList.append(path);
if (!path.isEmpty()) path += "/"; if (!path.isEmpty()) path += "/";
path += newNames.at(i); path += newNames.at(i);
pathList.append(path); pathList.append(path);
@ -74,7 +72,7 @@ void BooksPathModel::setPath(QString aPath)
if (oldSize < newSize) { if (oldSize < newSize) {
beginInsertRows(QModelIndex(), oldSize, newSize-1); beginInsertRows(QModelIndex(), oldSize, newSize-1);
for (int i=oldSize; i<newSize; i++) { for (int i=oldSize; i<newSize; i++) {
Data data(parentList.at(i), pathList.at(i), newNames.at(i)); Data data(pathList.at(i), newNames.at(i));
iList.append(data); iList.append(data);
} }
endInsertRows(); endInsertRows();
@ -104,20 +102,6 @@ void BooksPathModel::setPath(QString aPath)
} }
} }
void BooksPathModel::setStorage(QObject* aStorage)
{
BooksStorage storage;
if (aStorage) {
BooksStorage* newStorage = qobject_cast<BooksStorage*>(aStorage);
if (newStorage) storage.set(*newStorage);
}
if (!iStorage.equal(storage)) {
HDEBUG(storage.booksDir());
iStorage.set(storage);
Q_EMIT storageChanged();
}
}
QHash<int,QByteArray> BooksPathModel::roleNames() const QHash<int,QByteArray> BooksPathModel::roleNames() const
{ {
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
@ -142,30 +126,3 @@ QVariant BooksPathModel::data(const QModelIndex& aIndex, int aRole) const
} }
return QVariant(); return QVariant();
} }
bool BooksPathModel::setData(const QModelIndex& aIndex, const QVariant& aValue, int aRole)
{
const int i = aIndex.row();
if (validIndex(i) && aRole == BooksPathModelName && iStorage.isPresent()) {
QString newName(aValue.toString());
if (iList.at(i).iName != newName) {
const Data& data = iList.at(i);
QString oldPath = iStorage.fullPath(data.iPath);
QString newPath = iStorage.fullPath(data.iParentPath);
newPath += "/";
newPath += newName;
HDEBUG("renaming" << qPrintable(oldPath) << "->" << qPrintable(newPath));
if (rename(qPrintable(oldPath), qPrintable(newPath)) == 0) {
iList[i].iName = newName;
iList[i].iPath = newPath;
QModelIndex index = createIndex(i, 0);
Q_EMIT dataChanged(index, index);
return true;
} else {
HDEBUG(strerror(errno));
}
}
}
return false;
}

View file

@ -35,7 +35,6 @@
#define BOOKS_PATH_MODEL_H #define BOOKS_PATH_MODEL_H
#include "BooksTypes.h" #include "BooksTypes.h"
#include "BooksStorage.h"
#include <QHash> #include <QHash>
#include <QVariant> #include <QVariant>
@ -48,7 +47,6 @@ class BooksPathModel: public QAbstractListModel
Q_OBJECT Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged) Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(QObject* storage READ storage WRITE setStorage NOTIFY storageChanged)
public: public:
explicit BooksPathModel(QObject* aParent = NULL); explicit BooksPathModel(QObject* aParent = NULL);
@ -56,19 +54,15 @@ public:
int count() const { return iList.count(); } int count() const { return iList.count(); }
QString path() const { return iPath; } QString path() const { return iPath; }
void setPath(QString aPath); void setPath(QString aPath);
QObject* storage() { return &iStorage; }
void setStorage(QObject* aStorage);
// QAbstractListModel // QAbstractListModel
virtual QHash<int,QByteArray> roleNames() const; virtual QHash<int,QByteArray> roleNames() const;
virtual int rowCount(const QModelIndex& aParent) const; virtual int rowCount(const QModelIndex& aParent) const;
virtual QVariant data(const QModelIndex& aIndex, int aRole) const; virtual QVariant data(const QModelIndex& aIndex, int aRole) const;
virtual bool setData(const QModelIndex& aIndex, const QVariant& aValue, int aRole);
Q_SIGNALS: Q_SIGNALS:
void countChanged(); void countChanged();
void pathChanged(); void pathChanged();
void storageChanged();
private: private:
bool validIndex(int aIndex) const; bool validIndex(int aIndex) const;
@ -76,14 +70,11 @@ private:
private: private:
class Data { class Data {
public: public:
QString iParentPath;
QString iPath; QString iPath;
QString iName; QString iName;
Data(QString aParentPath, QString aPath, QString aName) : Data(QString aPath, QString aName) : iPath(aPath), iName(aName) {}
iParentPath(aParentPath), iPath(aPath), iName(aName) {}
}; };
BooksStorage iStorage;
QList<Data> iList; QList<Data> iList;
QString iPath; QString iPath;
}; };

View file

@ -547,10 +547,6 @@ BooksShelf::BooksShelf(BooksStorage aStorage, QString aRelativePath) :
// They also don't need to read the content of the directory - // They also don't need to read the content of the directory -
// only the objects allocated by QML do that. // only the objects allocated by QML do that.
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
int slashPos = aRelativePath.lastIndexOf('/');
iFileName = (slashPos >= 0) ?
aRelativePath.right(aRelativePath.length() - slashPos - 1) :
aRelativePath;
updatePath(); updatePath();
} }
@ -590,6 +586,60 @@ void BooksShelf::setDevice(QString aDevice)
} }
} }
void BooksShelf::setName(QString aName)
{
if (iStorage.isValid() &&
BooksUtil::isValidFileName(aName) &&
iFileName != aName) {
QString parentDir;
const int lastSlash = iRelativePath.lastIndexOf('/');
if (lastSlash > 0) {
parentDir = iRelativePath.left(lastSlash);
}
QString newRelativePath;
if (!parentDir.isEmpty()) {
newRelativePath = parentDir;
newRelativePath += '/';
}
newRelativePath += aName;
QString oldPath = iStorage.fullPath(iRelativePath);
QString newPath = iStorage.fullPath(newRelativePath);
HDEBUG("renaming" << qPrintable(oldPath) << "->" << qPrintable(newPath));
if (rename(qPrintable(oldPath), qPrintable(newPath)) == 0) {
const QString oldFileName(iFileName);
iRelativePath = newRelativePath;
iPath = iStorage.fullPath(iRelativePath);
updateFileName();
Q_EMIT pathChanged();
Q_EMIT relativePathChanged();
// Since this directiry has been renamed, we need to update the
// order of objects in the parent directory.
QVariantMap state;
const QString stateFile = stateFileName(parentDir);
if (HarbourJson::load(stateFile, state)) {
QVariantList order = state.value(SHELF_STATE_ORDER).toList();
int i, n = order.count();
for (i=0; i<n; i++) {
if (order.at(i).toString() == oldFileName) {
order[i] = iFileName;
state.insert(SHELF_STATE_ORDER, order);
if (HarbourJson::save(stateFile, state)) {
HDEBUG("wrote" << qPrintable(stateFile));
}
break;
}
}
}
} else {
HDEBUG(strerror(errno));
}
}
}
void BooksShelf::updatePath() void BooksShelf::updatePath()
{ {
BooksLoadingSignalBlocker block(this); BooksLoadingSignalBlocker block(this);
@ -598,6 +648,7 @@ void BooksShelf::updatePath()
if (iStorage.isValid()) { if (iStorage.isValid()) {
iPath = iStorage.fullPath(iRelativePath); iPath = iStorage.fullPath(iRelativePath);
} }
updateFileName();
if (oldPath != iPath) { if (oldPath != iPath) {
const int oldDummyItemIndex = iDummyItemIndex; const int oldDummyItemIndex = iDummyItemIndex;
Counts counts(this); Counts counts(this);
@ -628,6 +679,18 @@ void BooksShelf::updatePath()
} }
} }
void BooksShelf::updateFileName()
{
const int slashPos = iRelativePath.lastIndexOf('/');
const QString fileName = (slashPos >= 0) ?
iRelativePath.right(iRelativePath.length() - slashPos - 1) :
iRelativePath;
if (iFileName != fileName) {
iFileName = fileName;
Q_EMIT nameChanged();
}
}
void BooksShelf::onLoadTaskDone() void BooksShelf::onLoadTaskDone()
{ {
HASSERT(iLoadTask); HASSERT(iLoadTask);
@ -680,7 +743,7 @@ void BooksShelf::saveState()
QVariantMap state; QVariantMap state;
state.insert(SHELF_STATE_ORDER, order); state.insert(SHELF_STATE_ORDER, order);
if (HarbourJson::save(stateFileName(), state)) { if (HarbourJson::save(stateFileName(), state)) {
HDEBUG("wrote" << stateFileName()); HDEBUG("wrote" << qPrintable(stateFileName()));
} }
} }
@ -691,10 +754,10 @@ void BooksShelf::queueStateSave()
} }
} }
QString BooksShelf::stateFileName() const QString BooksShelf::stateFileName(QString aRelativePath) const
{ {
return iStorage.isValid() ? return iStorage.isValid() ?
iStorage.configDir().path() + "/" + iRelativePath + ("/" SHELF_STATE_FILE) : iStorage.configDir().path() + "/" + aRelativePath + ("/" SHELF_STATE_FILE) :
QString(); QString();
} }
@ -745,8 +808,7 @@ void BooksShelf::setEditMode(bool aEditMode)
iEditMode = aEditMode; iEditMode = aEditMode;
HDEBUG(iEditMode); HDEBUG(iEditMode);
if (iSaveTimer && iSaveTimer->saveRequested()) { if (iSaveTimer && iSaveTimer->saveRequested()) {
iSaveTimer->cancelSave(); iSaveTimer->saveNow();
saveState();
} }
setHasDummyItem(false); setHasDummyItem(false);
Q_EMIT editModeChanged(); Q_EMIT editModeChanged();

View file

@ -56,7 +56,7 @@ class BooksShelf: public QAbstractListModel, public BooksItem, public BooksLoadi
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
Q_PROPERTY(bool accessible READ accessible CONSTANT) Q_PROPERTY(bool accessible READ accessible CONSTANT)
Q_PROPERTY(QString path READ path NOTIFY pathChanged) Q_PROPERTY(QString path READ path NOTIFY pathChanged)
Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString device READ device WRITE setDevice NOTIFY deviceChanged) Q_PROPERTY(QString device READ device WRITE setDevice NOTIFY deviceChanged)
Q_PROPERTY(QString relativePath READ relativePath WRITE setRelativePath NOTIFY relativePathChanged) Q_PROPERTY(QString relativePath READ relativePath WRITE setRelativePath NOTIFY relativePathChanged)
Q_PROPERTY(bool editMode READ editMode WRITE setEditMode NOTIFY editModeChanged) Q_PROPERTY(bool editMode READ editMode WRITE setEditMode NOTIFY editModeChanged)
@ -90,6 +90,7 @@ public:
void setRelativePath(QString aPath); void setRelativePath(QString aPath);
BooksBook* bookAt(int aIndex) const; BooksBook* bookAt(int aIndex) const;
QObject* storage() { return &iStorage; } QObject* storage() { return &iStorage; }
void setName(QString aName);
bool editMode() const { return iEditMode; } bool editMode() const { return iEditMode; }
void setEditMode(bool aEditMode); void setEditMode(bool aEditMode);
@ -147,6 +148,7 @@ private Q_SLOTS:
private: private:
void init(); void init();
QString stateFileName() const; QString stateFileName() const;
QString stateFileName(QString aRelativePath) const;
int bookIndex(BooksBook* aBook) const; int bookIndex(BooksBook* aBook) const;
int itemIndex(QString aFileName, int aStartIndex = 0) const; int itemIndex(QString aFileName, int aStartIndex = 0) const;
bool validIndex(int aIndex) const; bool validIndex(int aIndex) const;
@ -154,6 +156,7 @@ private:
void queueStateSave(); void queueStateSave();
void loadBookList(); void loadBookList();
void updatePath(); void updatePath();
void updateFileName();
void submitDeleteTask(int aIndex); void submitDeleteTask(int aIndex);
private: private:
@ -184,5 +187,7 @@ QML_DECLARE_TYPE(BooksShelf)
inline bool BooksShelf::validIndex(int aIndex) const inline bool BooksShelf::validIndex(int aIndex) const
{ return aIndex >= 0 && aIndex < iList.count(); } { return aIndex >= 0 && aIndex < iList.count(); }
inline QString BooksShelf::stateFileName() const
{ return stateFileName(iRelativePath); }
#endif // BOOKS_SHELF_MODEL_H #endif // BOOKS_SHELF_MODEL_H

View file

@ -77,3 +77,11 @@ shared_ptr<Book> BooksUtil::bookFromFile(std::string aPath)
} }
return book; return book;
} }
bool BooksUtil::isValidFileName(QString aName)
{
return !aName.isEmpty() &&
aName != QStringLiteral(".") &&
aName != QStringLiteral("..") &&
!aName.contains('/');
}

View file

@ -43,6 +43,7 @@
namespace BooksUtil { namespace BooksUtil {
shared_ptr<Book> bookFromFile(std::string aPath); shared_ptr<Book> bookFromFile(std::string aPath);
shared_ptr<Book> bookFromFile(QString aPath); shared_ptr<Book> bookFromFile(QString aPath);
bool isValidFileName(QString aName);
} }
inline shared_ptr<Book> BooksUtil::bookFromFile(QString aPath) inline shared_ptr<Book> BooksUtil::bookFromFile(QString aPath)