Improve file browser
This commit is contained in:
parent
93c9ffe5ca
commit
83e1dd6d8a
|
@ -49,7 +49,7 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingLogEntry {
|
|||
QString message;
|
||||
};
|
||||
|
||||
enum class SyncthingItemType { Unknown, File, Directory };
|
||||
enum class SyncthingItemType { Unknown, File, Directory, Symlink };
|
||||
|
||||
struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingItem {
|
||||
QString name;
|
||||
|
@ -58,6 +58,7 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingItem {
|
|||
SyncthingItemType type = SyncthingItemType::Unknown;
|
||||
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)
|
||||
QString path; // not populated but might be set as needed
|
||||
std::size_t index = std::size_t();
|
||||
int level = 0; // the level of nesting, does *not* include levels of the prefix
|
||||
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;
|
||||
} else if (type == QLatin1String("FILE_INFO_TYPE_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);
|
||||
item->childrenPopulated = !levels || level < levels;
|
||||
|
|
|
@ -4,8 +4,12 @@
|
|||
#include <syncthingconnector/syncthingconnection.h>
|
||||
#include <syncthingconnector/utils.h>
|
||||
|
||||
#include <qtutilities/misc/desktoputils.h>
|
||||
|
||||
#include <c++utilities/conversion/stringconversion.h>
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QGuiApplication>
|
||||
#include <QStringBuilder>
|
||||
|
||||
using namespace std;
|
||||
|
@ -13,12 +17,30 @@ using namespace CppUtilities;
|
|||
|
||||
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)
|
||||
: SyncthingModel(connection, parent)
|
||||
, m_connection(connection)
|
||||
, m_dirId(dir.id)
|
||||
, m_root(std::make_unique<SyncthingItem>())
|
||||
{
|
||||
if (m_connection.isLocal()) {
|
||||
m_localPath = dir.pathWithoutTrailingSlash().toString();
|
||||
}
|
||||
m_root->name = dir.displayName();
|
||||
m_root->modificationTime = dir.lastFileTime;
|
||||
m_root->size = dir.globalStats.bytes;
|
||||
|
@ -33,6 +55,7 @@ SyncthingFileModel::SyncthingFileModel(SyncthingConnection &connection, const Sy
|
|||
}
|
||||
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());
|
||||
populatePath(QString(), items);
|
||||
m_root->children = std::move(items);
|
||||
m_root->childrenPopulated = true;
|
||||
endInsertRows();
|
||||
|
@ -50,6 +73,7 @@ QHash<int, QByteArray> SyncthingFileModel::roleNames() const
|
|||
{ NameRole, "name" },
|
||||
{ SizeRole, "size" },
|
||||
{ ModificationTimeRole, "modificationTime" },
|
||||
{ PathRole, "path" },
|
||||
{ Actions, "actions" },
|
||||
{ ActionNames, "actionNames" },
|
||||
{ ActionIcons, "actionIcons" },
|
||||
|
@ -175,9 +199,22 @@ QVariant SyncthingFileModel::data(const QModelIndex &index, int role) const
|
|||
case 0:
|
||||
return item->name;
|
||||
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:
|
||||
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;
|
||||
case Qt::DecorationRole: {
|
||||
|
@ -189,33 +226,64 @@ QVariant SyncthingFileModel::data(const QModelIndex &index, int role) const
|
|||
return icons.file;
|
||||
case SyncthingItemType::Directory:
|
||||
return icons.folder;
|
||||
case SyncthingItemType::Symlink:
|
||||
return icons.link;
|
||||
default:
|
||||
return icons.cogs;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Qt::ToolTipRole:
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
return item->path;
|
||||
case 2:
|
||||
return agoString(item->modificationTime);
|
||||
}
|
||||
break;
|
||||
case NameRole:
|
||||
return item->name;
|
||||
case SizeRole:
|
||||
return static_cast<qsizetype>(item->size);
|
||||
case ModificationTimeRole:
|
||||
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) {
|
||||
return QStringList({ QStringLiteral("refresh") });
|
||||
res << QStringLiteral("refresh");
|
||||
}
|
||||
break;
|
||||
case ActionNames:
|
||||
if (!m_localPath.isEmpty()) {
|
||||
res << QStringLiteral("open") << QStringLiteral("copy-path");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
case ActionNames: {
|
||||
auto res = QStringList();
|
||||
res.reserve(3);
|
||||
if (item->type == SyncthingItemType::Directory) {
|
||||
return QStringList({ tr("Refresh") });
|
||||
res << tr("Refresh");
|
||||
}
|
||||
break;
|
||||
case ActionIcons:
|
||||
if (!m_localPath.isEmpty()) {
|
||||
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) {
|
||||
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();
|
||||
}
|
||||
|
@ -281,6 +349,18 @@ void SyncthingFileModel::triggerAction(const QString &action, const QModelIndex
|
|||
if (action == QLatin1String("refresh")) {
|
||||
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()
|
||||
|
@ -313,7 +393,8 @@ void SyncthingFileModel::processFetchQueue()
|
|||
return;
|
||||
}
|
||||
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));
|
||||
refreshedItem->children.clear();
|
||||
endRemoveRows();
|
||||
|
@ -324,11 +405,16 @@ void SyncthingFileModel::processFetchQueue()
|
|||
for (auto &item : items) {
|
||||
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());
|
||||
refreshedItem->children = std::move(items);
|
||||
refreshedItem->childrenPopulated = true;
|
||||
endInsertRows();
|
||||
}
|
||||
if (refreshedItem->children.size() != previousChildCount) {
|
||||
const auto sizeIndex = refreshedIndex.siblingAtColumn(1);
|
||||
emit dataChanged(sizeIndex, sizeIndex, QList<int>{ Qt::DisplayRole });
|
||||
}
|
||||
processFetchQueue();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,14 +6,21 @@
|
|||
#include <syncthingconnector/syncthingconnection.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Data {
|
||||
|
||||
class LIB_SYNCTHING_MODEL_EXPORT SyncthingFileModel : public SyncthingModel {
|
||||
Q_OBJECT
|
||||
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);
|
||||
~SyncthingFileModel() override;
|
||||
|
@ -46,6 +53,7 @@ private:
|
|||
private:
|
||||
SyncthingConnection &m_connection;
|
||||
QString m_dirId;
|
||||
QString m_localPath;
|
||||
QStringList m_fetchQueue;
|
||||
QMetaObject::Connection m_pendingRequest;
|
||||
std::unique_ptr<SyncthingItem> m_root;
|
||||
|
|
|
@ -102,11 +102,11 @@ QDialog *browseRemoteFilesDialog(Data::SyncthingConnection &connection, const Da
|
|||
return;
|
||||
}
|
||||
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 actionIndex = qsizetype();
|
||||
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),
|
||||
&QAction::triggered, model, [model, action, index]() { model->triggerAction(action, index); });
|
||||
++actionIndex;
|
||||
|
|
Loading…
Reference in New Issue