Improve file browser
This commit is contained in:
parent
93c9ffe5ca
commit
83e1dd6d8a
|
@ -49,7 +49,7 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingLogEntry {
|
||||||
QString message;
|
QString message;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class SyncthingItemType { Unknown, File, Directory };
|
enum class SyncthingItemType { Unknown, File, Directory, Symlink };
|
||||||
|
|
||||||
struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingItem {
|
struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingItem {
|
||||||
QString name;
|
QString name;
|
||||||
|
@ -58,6 +58,7 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingItem {
|
||||||
SyncthingItemType type = SyncthingItemType::Unknown;
|
SyncthingItemType type = SyncthingItemType::Unknown;
|
||||||
std::vector<std::unique_ptr<SyncthingItem>> children;
|
std::vector<std::unique_ptr<SyncthingItem>> children;
|
||||||
SyncthingItem *parent = nullptr; // not populated but might be set as needed (take care in case the pointer gets invalidated)
|
SyncthingItem *parent = nullptr; // not populated but might be set as needed (take care in case the pointer gets invalidated)
|
||||||
|
QString path; // not populated but might be set as needed
|
||||||
std::size_t index = std::size_t();
|
std::size_t index = std::size_t();
|
||||||
int level = 0; // the level of nesting, does *not* include levels of the prefix
|
int level = 0; // the level of nesting, does *not* include levels of the prefix
|
||||||
bool childrenPopulated = false; // populated depending on requested level
|
bool childrenPopulated = false; // populated depending on requested level
|
||||||
|
|
|
@ -1635,6 +1635,8 @@ static void readSyncthingItems(const QJsonArray &array, std::vector<std::unique_
|
||||||
item->type = SyncthingItemType::File;
|
item->type = SyncthingItemType::File;
|
||||||
} else if (type == QLatin1String("FILE_INFO_TYPE_DIRECTORY")) {
|
} else if (type == QLatin1String("FILE_INFO_TYPE_DIRECTORY")) {
|
||||||
item->type = SyncthingItemType::Directory;
|
item->type = SyncthingItemType::Directory;
|
||||||
|
} else if (type == QLatin1String("FILE_INFO_TYPE_SYMLINK")) {
|
||||||
|
item->type = SyncthingItemType::Symlink;
|
||||||
}
|
}
|
||||||
readSyncthingItems(children.toArray(), item->children, level + 1, levels);
|
readSyncthingItems(children.toArray(), item->children, level + 1, levels);
|
||||||
item->childrenPopulated = !levels || level < levels;
|
item->childrenPopulated = !levels || level < levels;
|
||||||
|
|
|
@ -4,8 +4,12 @@
|
||||||
#include <syncthingconnector/syncthingconnection.h>
|
#include <syncthingconnector/syncthingconnection.h>
|
||||||
#include <syncthingconnector/utils.h>
|
#include <syncthingconnector/utils.h>
|
||||||
|
|
||||||
|
#include <qtutilities/misc/desktoputils.h>
|
||||||
|
|
||||||
#include <c++utilities/conversion/stringconversion.h>
|
#include <c++utilities/conversion/stringconversion.h>
|
||||||
|
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QGuiApplication>
|
||||||
#include <QStringBuilder>
|
#include <QStringBuilder>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
@ -13,12 +17,30 @@ using namespace CppUtilities;
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
||||||
|
/// \cond
|
||||||
|
static void populatePath(const QString &root, std::vector<std::unique_ptr<SyncthingItem>> &items)
|
||||||
|
{
|
||||||
|
if (root.isEmpty()) {
|
||||||
|
for (auto &item : items) {
|
||||||
|
populatePath(item->path = item->name, item->children);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (auto &item : items) {
|
||||||
|
populatePath(item->path = root % QChar('/') % item->name, item->children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// \endcond
|
||||||
|
|
||||||
SyncthingFileModel::SyncthingFileModel(SyncthingConnection &connection, const SyncthingDir &dir, QObject *parent)
|
SyncthingFileModel::SyncthingFileModel(SyncthingConnection &connection, const SyncthingDir &dir, QObject *parent)
|
||||||
: SyncthingModel(connection, parent)
|
: SyncthingModel(connection, parent)
|
||||||
, m_connection(connection)
|
, m_connection(connection)
|
||||||
, m_dirId(dir.id)
|
, m_dirId(dir.id)
|
||||||
, m_root(std::make_unique<SyncthingItem>())
|
, m_root(std::make_unique<SyncthingItem>())
|
||||||
{
|
{
|
||||||
|
if (m_connection.isLocal()) {
|
||||||
|
m_localPath = dir.pathWithoutTrailingSlash().toString();
|
||||||
|
}
|
||||||
m_root->name = dir.displayName();
|
m_root->name = dir.displayName();
|
||||||
m_root->modificationTime = dir.lastFileTime;
|
m_root->modificationTime = dir.lastFileTime;
|
||||||
m_root->size = dir.globalStats.bytes;
|
m_root->size = dir.globalStats.bytes;
|
||||||
|
@ -33,6 +55,7 @@ SyncthingFileModel::SyncthingFileModel(SyncthingConnection &connection, const Sy
|
||||||
}
|
}
|
||||||
const auto last = items.size() - 1;
|
const auto last = items.size() - 1;
|
||||||
beginInsertRows(index(0, 0), 0, last < std::numeric_limits<int>::max() ? static_cast<int>(last) : std::numeric_limits<int>::max());
|
beginInsertRows(index(0, 0), 0, last < std::numeric_limits<int>::max() ? static_cast<int>(last) : std::numeric_limits<int>::max());
|
||||||
|
populatePath(QString(), items);
|
||||||
m_root->children = std::move(items);
|
m_root->children = std::move(items);
|
||||||
m_root->childrenPopulated = true;
|
m_root->childrenPopulated = true;
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
|
@ -50,6 +73,7 @@ QHash<int, QByteArray> SyncthingFileModel::roleNames() const
|
||||||
{ NameRole, "name" },
|
{ NameRole, "name" },
|
||||||
{ SizeRole, "size" },
|
{ SizeRole, "size" },
|
||||||
{ ModificationTimeRole, "modificationTime" },
|
{ ModificationTimeRole, "modificationTime" },
|
||||||
|
{ PathRole, "path" },
|
||||||
{ Actions, "actions" },
|
{ Actions, "actions" },
|
||||||
{ ActionNames, "actionNames" },
|
{ ActionNames, "actionNames" },
|
||||||
{ ActionIcons, "actionIcons" },
|
{ ActionIcons, "actionIcons" },
|
||||||
|
@ -175,9 +199,22 @@ QVariant SyncthingFileModel::data(const QModelIndex &index, int role) const
|
||||||
case 0:
|
case 0:
|
||||||
return item->name;
|
return item->name;
|
||||||
case 1:
|
case 1:
|
||||||
return QString::fromStdString(CppUtilities::dataSizeToString(item->size));
|
switch (item->type) {
|
||||||
|
case SyncthingItemType::File:
|
||||||
|
return QString::fromStdString(CppUtilities::dataSizeToString(item->size));
|
||||||
|
case SyncthingItemType::Directory:
|
||||||
|
return item->childrenPopulated ? tr("%1 elements").arg(item->children.size()) : QString();
|
||||||
|
default:
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
case 2:
|
case 2:
|
||||||
return QString::fromStdString(item->modificationTime.toString());
|
switch (item->type) {
|
||||||
|
case SyncthingItemType::File:
|
||||||
|
case SyncthingItemType::Directory:
|
||||||
|
return QString::fromStdString(item->modificationTime.toString());
|
||||||
|
default:
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::DecorationRole: {
|
case Qt::DecorationRole: {
|
||||||
|
@ -189,33 +226,64 @@ QVariant SyncthingFileModel::data(const QModelIndex &index, int role) const
|
||||||
return icons.file;
|
return icons.file;
|
||||||
case SyncthingItemType::Directory:
|
case SyncthingItemType::Directory:
|
||||||
return icons.folder;
|
return icons.folder;
|
||||||
|
case SyncthingItemType::Symlink:
|
||||||
|
return icons.link;
|
||||||
default:
|
default:
|
||||||
return icons.cogs;
|
return icons.cogs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Qt::ToolTipRole:
|
||||||
|
switch (index.column()) {
|
||||||
|
case 0:
|
||||||
|
return item->path;
|
||||||
|
case 2:
|
||||||
|
return agoString(item->modificationTime);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case NameRole:
|
case NameRole:
|
||||||
return item->name;
|
return item->name;
|
||||||
case SizeRole:
|
case SizeRole:
|
||||||
return static_cast<qsizetype>(item->size);
|
return static_cast<qsizetype>(item->size);
|
||||||
case ModificationTimeRole:
|
case ModificationTimeRole:
|
||||||
return QString::fromStdString(item->modificationTime.toString());
|
return QString::fromStdString(item->modificationTime.toString());
|
||||||
case Actions:
|
case PathRole:
|
||||||
|
return item->path;
|
||||||
|
case Actions: {
|
||||||
|
auto res = QStringList();
|
||||||
|
res.reserve(3);
|
||||||
if (item->type == SyncthingItemType::Directory) {
|
if (item->type == SyncthingItemType::Directory) {
|
||||||
return QStringList({ QStringLiteral("refresh") });
|
res << QStringLiteral("refresh");
|
||||||
}
|
}
|
||||||
break;
|
if (!m_localPath.isEmpty()) {
|
||||||
case ActionNames:
|
res << QStringLiteral("open") << QStringLiteral("copy-path");
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
case ActionNames: {
|
||||||
|
auto res = QStringList();
|
||||||
|
res.reserve(3);
|
||||||
if (item->type == SyncthingItemType::Directory) {
|
if (item->type == SyncthingItemType::Directory) {
|
||||||
return QStringList({ tr("Refresh") });
|
res << tr("Refresh");
|
||||||
}
|
}
|
||||||
break;
|
if (!m_localPath.isEmpty()) {
|
||||||
case ActionIcons:
|
res << (item->type == SyncthingItemType::Directory ? tr("Browse locally") : tr("Open local version")) << tr("Copy local path");
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
case ActionIcons: {
|
||||||
|
auto res = QVariantList();
|
||||||
|
res.reserve(3);
|
||||||
if (item->type == SyncthingItemType::Directory) {
|
if (item->type == SyncthingItemType::Directory) {
|
||||||
return QStringList({ QStringLiteral("view-refresh") });
|
res << QIcon::fromTheme(QStringLiteral("view-refresh"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/view-refresh.svg")));
|
||||||
}
|
}
|
||||||
break;
|
if (!m_localPath.isEmpty()) {
|
||||||
|
res << QIcon::fromTheme(QStringLiteral("folder"), QIcon(QStringLiteral(":/icons/hicolor/scalable/places/folder-open.svg")));
|
||||||
|
res << QIcon::fromTheme(QStringLiteral("edit-copy"), QIcon(QStringLiteral(":/icons/hicolor/scalable/places/edit-copy.svg")));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
@ -281,6 +349,18 @@ void SyncthingFileModel::triggerAction(const QString &action, const QModelIndex
|
||||||
if (action == QLatin1String("refresh")) {
|
if (action == QLatin1String("refresh")) {
|
||||||
fetchMore(index);
|
fetchMore(index);
|
||||||
}
|
}
|
||||||
|
if (m_localPath.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto relPath = index.data(PathRole).toString();
|
||||||
|
const auto path = relPath.isEmpty() ? m_localPath : QString(m_localPath % QChar('/') % relPath);
|
||||||
|
if (action == QLatin1String("open")) {
|
||||||
|
QtUtilities::openLocalFileOrDir(path);
|
||||||
|
} else if (action == QLatin1String("copy-path")) {
|
||||||
|
if (auto *const clipboard = QGuiApplication::clipboard()) {
|
||||||
|
clipboard->setText(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SyncthingFileModel::handleConfigInvalidated()
|
void SyncthingFileModel::handleConfigInvalidated()
|
||||||
|
@ -313,7 +393,8 @@ void SyncthingFileModel::processFetchQueue()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto *const refreshedItem = reinterpret_cast<SyncthingItem *>(refreshedIndex.internalPointer());
|
auto *const refreshedItem = reinterpret_cast<SyncthingItem *>(refreshedIndex.internalPointer());
|
||||||
if (!refreshedItem->children.empty()) {
|
const auto previousChildCount = refreshedItem->children.size();
|
||||||
|
if (previousChildCount) {
|
||||||
beginRemoveRows(refreshedIndex, 0, static_cast<int>(refreshedItem->children.size() - 1));
|
beginRemoveRows(refreshedIndex, 0, static_cast<int>(refreshedItem->children.size() - 1));
|
||||||
refreshedItem->children.clear();
|
refreshedItem->children.clear();
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
|
@ -324,11 +405,16 @@ void SyncthingFileModel::processFetchQueue()
|
||||||
for (auto &item : items) {
|
for (auto &item : items) {
|
||||||
item->parent = refreshedItem;
|
item->parent = refreshedItem;
|
||||||
}
|
}
|
||||||
|
populatePath(refreshedItem->path, items);
|
||||||
beginInsertRows(refreshedIndex, 0, last < std::numeric_limits<int>::max() ? static_cast<int>(last) : std::numeric_limits<int>::max());
|
beginInsertRows(refreshedIndex, 0, last < std::numeric_limits<int>::max() ? static_cast<int>(last) : std::numeric_limits<int>::max());
|
||||||
refreshedItem->children = std::move(items);
|
refreshedItem->children = std::move(items);
|
||||||
refreshedItem->childrenPopulated = true;
|
refreshedItem->childrenPopulated = true;
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
}
|
}
|
||||||
|
if (refreshedItem->children.size() != previousChildCount) {
|
||||||
|
const auto sizeIndex = refreshedIndex.siblingAtColumn(1);
|
||||||
|
emit dataChanged(sizeIndex, sizeIndex, QList<int>{ Qt::DisplayRole });
|
||||||
|
}
|
||||||
processFetchQueue();
|
processFetchQueue();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,21 @@
|
||||||
#include <syncthingconnector/syncthingconnection.h>
|
#include <syncthingconnector/syncthingconnection.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
||||||
class LIB_SYNCTHING_MODEL_EXPORT SyncthingFileModel : public SyncthingModel {
|
class LIB_SYNCTHING_MODEL_EXPORT SyncthingFileModel : public SyncthingModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum SyncthingFileModelRole { NameRole = SyncthingModelUserRole + 1, SizeRole, ModificationTimeRole, Actions, ActionNames, ActionIcons };
|
enum SyncthingFileModelRole {
|
||||||
|
NameRole = SyncthingModelUserRole + 1,
|
||||||
|
SizeRole,
|
||||||
|
ModificationTimeRole,
|
||||||
|
PathRole,
|
||||||
|
Actions,
|
||||||
|
ActionNames,
|
||||||
|
ActionIcons
|
||||||
|
};
|
||||||
|
|
||||||
explicit SyncthingFileModel(SyncthingConnection &connection, const SyncthingDir &dir, QObject *parent = nullptr);
|
explicit SyncthingFileModel(SyncthingConnection &connection, const SyncthingDir &dir, QObject *parent = nullptr);
|
||||||
~SyncthingFileModel() override;
|
~SyncthingFileModel() override;
|
||||||
|
@ -46,6 +53,7 @@ private:
|
||||||
private:
|
private:
|
||||||
SyncthingConnection &m_connection;
|
SyncthingConnection &m_connection;
|
||||||
QString m_dirId;
|
QString m_dirId;
|
||||||
|
QString m_localPath;
|
||||||
QStringList m_fetchQueue;
|
QStringList m_fetchQueue;
|
||||||
QMetaObject::Connection m_pendingRequest;
|
QMetaObject::Connection m_pendingRequest;
|
||||||
std::unique_ptr<SyncthingItem> m_root;
|
std::unique_ptr<SyncthingItem> m_root;
|
||||||
|
|
|
@ -102,11 +102,11 @@ QDialog *browseRemoteFilesDialog(Data::SyncthingConnection &connection, const Da
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto actionNames = model->data(index, SyncthingFileModel::ActionNames).toStringList();
|
const auto actionNames = model->data(index, SyncthingFileModel::ActionNames).toStringList();
|
||||||
const auto actionIcons = model->data(index, SyncthingFileModel::ActionIcons).toStringList();
|
const auto actionIcons = model->data(index, SyncthingFileModel::ActionIcons).toList();
|
||||||
auto menu = QMenu(view);
|
auto menu = QMenu(view);
|
||||||
auto actionIndex = qsizetype();
|
auto actionIndex = qsizetype();
|
||||||
for (const auto &action : actions) {
|
for (const auto &action : actions) {
|
||||||
QObject::connect(menu.addAction(actionIndex < actionIcons.size() ? QIcon::fromTheme(actionIcons.at(actionIndex)) : QIcon(),
|
QObject::connect(menu.addAction(actionIndex < actionIcons.size() ? actionIcons.at(actionIndex).value<QIcon>() : QIcon(),
|
||||||
actionIndex < actionNames.size() ? actionNames.at(actionIndex) : action),
|
actionIndex < actionNames.size() ? actionNames.at(actionIndex) : action),
|
||||||
&QAction::triggered, model, [model, action, index]() { model->triggerAction(action, index); });
|
&QAction::triggered, model, [model, action, index]() { model->triggerAction(action, index); });
|
||||||
++actionIndex;
|
++actionIndex;
|
||||||
|
|
Loading…
Reference in New Issue