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

View file

@ -70,12 +70,12 @@ SilicaFlickable {
onNeedDummyItemChanged: if (needDummyItem) hasDummyItem = true
editMode: shelfView.editMode
onRelativePathChanged: longStartTimer.restart()
onPathChanged: globalSettings.currentFolder = path
}
BooksPathModel {
id: pathModel
path: shelfModel.relativePath
storage: shelfModel.storage
}
onEditModeChanged: {
@ -176,15 +176,10 @@ SilicaFlickable {
BooksShelfTitle {
width: grid.width
text: model.name
editable: editMode
editable: editMode && currentFolder
currentFolder: model.index === (pathModel.count-1)
onClicked: {
if (currentFolder) {
if (editMode) {
console.log("editing", model.name)
editName()
}
} else {
if (!currentFolder) {
console.log("switching to", model.path)
shelfView.stopEditing()
shelfModel.relativePath = model.path
@ -192,8 +187,9 @@ SilicaFlickable {
}
onRename: {
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;
QString path;
QStringList pathList;
QStringList parentList;
for (i=0; i<newSize; i++) {
parentList.append(path);
if (!path.isEmpty()) path += "/";
path += newNames.at(i);
pathList.append(path);
@ -74,7 +72,7 @@ void BooksPathModel::setPath(QString aPath)
if (oldSize < newSize) {
beginInsertRows(QModelIndex(), oldSize, newSize-1);
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);
}
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> roles;
@ -142,30 +126,3 @@ QVariant BooksPathModel::data(const QModelIndex& aIndex, int aRole) const
}
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
#include "BooksTypes.h"
#include "BooksStorage.h"
#include <QHash>
#include <QVariant>
@ -48,7 +47,6 @@ class BooksPathModel: public QAbstractListModel
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(QObject* storage READ storage WRITE setStorage NOTIFY storageChanged)
public:
explicit BooksPathModel(QObject* aParent = NULL);
@ -56,19 +54,15 @@ public:
int count() const { return iList.count(); }
QString path() const { return iPath; }
void setPath(QString aPath);
QObject* storage() { return &iStorage; }
void setStorage(QObject* aStorage);
// QAbstractListModel
virtual QHash<int,QByteArray> roleNames() const;
virtual int rowCount(const QModelIndex& aParent) const;
virtual QVariant data(const QModelIndex& aIndex, int aRole) const;
virtual bool setData(const QModelIndex& aIndex, const QVariant& aValue, int aRole);
Q_SIGNALS:
void countChanged();
void pathChanged();
void storageChanged();
private:
bool validIndex(int aIndex) const;
@ -76,14 +70,11 @@ private:
private:
class Data {
public:
QString iParentPath;
QString iPath;
QString iName;
Data(QString aParentPath, QString aPath, QString aName) :
iParentPath(aParentPath), iPath(aPath), iName(aName) {}
Data(QString aPath, QString aName) : iPath(aPath), iName(aName) {}
};
BooksStorage iStorage;
QList<Data> iList;
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 -
// only the objects allocated by QML do that.
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
int slashPos = aRelativePath.lastIndexOf('/');
iFileName = (slashPos >= 0) ?
aRelativePath.right(aRelativePath.length() - slashPos - 1) :
aRelativePath;
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()
{
BooksLoadingSignalBlocker block(this);
@ -598,6 +648,7 @@ void BooksShelf::updatePath()
if (iStorage.isValid()) {
iPath = iStorage.fullPath(iRelativePath);
}
updateFileName();
if (oldPath != iPath) {
const int oldDummyItemIndex = iDummyItemIndex;
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()
{
HASSERT(iLoadTask);
@ -680,7 +743,7 @@ void BooksShelf::saveState()
QVariantMap state;
state.insert(SHELF_STATE_ORDER, order);
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() ?
iStorage.configDir().path() + "/" + iRelativePath + ("/" SHELF_STATE_FILE) :
iStorage.configDir().path() + "/" + aRelativePath + ("/" SHELF_STATE_FILE) :
QString();
}
@ -745,8 +808,7 @@ void BooksShelf::setEditMode(bool aEditMode)
iEditMode = aEditMode;
HDEBUG(iEditMode);
if (iSaveTimer && iSaveTimer->saveRequested()) {
iSaveTimer->cancelSave();
saveState();
iSaveTimer->saveNow();
}
setHasDummyItem(false);
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 accessible READ accessible CONSTANT)
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 relativePath READ relativePath WRITE setRelativePath NOTIFY relativePathChanged)
Q_PROPERTY(bool editMode READ editMode WRITE setEditMode NOTIFY editModeChanged)
@ -90,6 +90,7 @@ public:
void setRelativePath(QString aPath);
BooksBook* bookAt(int aIndex) const;
QObject* storage() { return &iStorage; }
void setName(QString aName);
bool editMode() const { return iEditMode; }
void setEditMode(bool aEditMode);
@ -147,6 +148,7 @@ private Q_SLOTS:
private:
void init();
QString stateFileName() const;
QString stateFileName(QString aRelativePath) const;
int bookIndex(BooksBook* aBook) const;
int itemIndex(QString aFileName, int aStartIndex = 0) const;
bool validIndex(int aIndex) const;
@ -154,6 +156,7 @@ private:
void queueStateSave();
void loadBookList();
void updatePath();
void updateFileName();
void submitDeleteTask(int aIndex);
private:
@ -184,5 +187,7 @@ QML_DECLARE_TYPE(BooksShelf)
inline bool BooksShelf::validIndex(int aIndex) const
{ return aIndex >= 0 && aIndex < iList.count(); }
inline QString BooksShelf::stateFileName() const
{ return stateFileName(iRelativePath); }
#endif // BOOKS_SHELF_MODEL_H

View file

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

View file

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