[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:
parent
9f1261a822
commit
34fb489107
8 changed files with 111 additions and 89 deletions
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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('/');
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue