Add remote file browsing dialog

This commit is contained in:
baldurk
2017-02-09 19:28:21 +00:00
parent 4ec3b4dce3
commit 9c03eb69bc
11 changed files with 1280 additions and 47 deletions
+3 -7
View File
@@ -378,14 +378,12 @@ QMessageBox::StandardButton RDDialog::messageBox(QMessageBox::Icon icon, QWidget
}
QString RDDialog::getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir,
QFileDialog::Options options, QAbstractProxyModel *proxy)
QFileDialog::Options options)
{
QFileDialog fd(parent, caption, dir, QString());
fd.setAcceptMode(QFileDialog::AcceptOpen);
fd.setFileMode(QFileDialog::DirectoryOnly);
fd.setOptions(options);
if(proxy)
fd.setProxyModel(proxy);
show(&fd);
if(fd.result() == QFileDialog::Accepted)
@@ -422,7 +420,7 @@ QString RDDialog::getOpenFileName(QWidget *parent, const QString &caption, const
}
QString RDDialog::getExecutableFileName(QWidget *parent, const QString &caption, const QString &dir,
QFileDialog::Options options, QAbstractProxyModel *proxy)
QFileDialog::Options options)
{
QString filter;
@@ -435,13 +433,11 @@ QString RDDialog::getExecutableFileName(QWidget *parent, const QString &caption,
fd.setOptions(options);
fd.setAcceptMode(QFileDialog::AcceptOpen);
fd.setFileMode(QFileDialog::ExistingFile);
if(!proxy)
{
QFileFilterModel *fileProxy = new QFileFilterModel(parent);
fileProxy->setRequirePermissions(QDir::Executable);
proxy = fileProxy;
fd.setProxyModel(fileProxy);
}
fd.setProxyModel(proxy);
show(&fd);
if(fd.result() == QFileDialog::Accepted)
+2 -4
View File
@@ -628,8 +628,7 @@ struct RDDialog
static QString getExistingDirectory(QWidget *parent = NULL, const QString &caption = QString(),
const QString &dir = QString(),
QFileDialog::Options options = QFileDialog::ShowDirsOnly,
QAbstractProxyModel *proxy = NULL);
QFileDialog::Options options = QFileDialog::ShowDirsOnly);
static QString getOpenFileName(QWidget *parent = NULL, const QString &caption = QString(),
const QString &dir = QString(), const QString &filter = QString(),
@@ -638,8 +637,7 @@ struct RDDialog
static QString getExecutableFileName(QWidget *parent = NULL, const QString &caption = QString(),
const QString &dir = QString(),
QFileDialog::Options options = QFileDialog::Options(),
QAbstractProxyModel *proxy = NULL);
QFileDialog::Options options = QFileDialog::Options());
static QString getSaveFileName(QWidget *parent = NULL, const QString &caption = QString(),
const QString &dir = QString(), const QString &filter = QString(),
+24 -12
View File
@@ -100,16 +100,21 @@ QStringList RenderManager::GetRemoteSupport()
return ret;
}
void RenderManager::GetHomeFolder(DirectoryBrowseMethod cb)
void RenderManager::GetHomeFolder(bool synchronous, DirectoryBrowseMethod cb)
{
if(!m_Remote)
return;
if(IsRunning() && m_Thread->isCurrentThread())
{
AsyncInvoke([cb, this](IReplayRenderer *r) {
auto lambda = [cb, this](IReplayRenderer *r) {
cb(m_Remote->GetHomeFolder().c_str(), rdctype::array<DirectoryFile>());
});
};
if(synchronous)
BlockInvoke(lambda);
else
AsyncInvoke(lambda);
return;
}
@@ -123,31 +128,35 @@ void RenderManager::GetHomeFolder(DirectoryBrowseMethod cb)
cb(home.c_str(), rdctype::array<DirectoryFile>());
}
bool RenderManager::ListFolder(QString path, DirectoryBrowseMethod cb)
bool RenderManager::ListFolder(QString path, bool synchronous, DirectoryBrowseMethod cb)
{
if(!m_Remote)
return false;
QByteArray pathUTF8 = path.toUtf8();
if(IsRunning() && m_Thread->isCurrentThread())
{
AsyncInvoke([cb, path, this](IReplayRenderer *r) {
const char *pathstr = path.toUtf8().data();
cb(pathstr, m_Remote->ListFolder(pathstr));
});
auto lambda = [cb, pathUTF8, this](IReplayRenderer *r) {
cb(pathUTF8.data(), m_Remote->ListFolder(pathUTF8.data()));
};
if(synchronous)
BlockInvoke(lambda);
else
AsyncInvoke(lambda);
return true;
}
rdctype::array<DirectoryFile> contents;
const char *pathstr = path.toUtf8().data();
// prevent pings while fetching remote FS data
{
QMutexLocker autolock(&m_RemoteLock);
contents = m_Remote->ListFolder(pathstr);
contents = m_Remote->ListFolder(pathUTF8.data());
}
cb(pathstr, contents);
cb(pathUTF8.data(), contents);
return true;
}
@@ -281,6 +290,9 @@ ReplayCreateStatus RenderManager::ConnectToRemoteServer(RemoteHost *host)
void RenderManager::DisconnectFromRemoteServer()
{
if(m_RemoteHost)
m_RemoteHost->Connected = false;
if(m_Remote)
{
QMutexLocker autolock(&m_RemoteLock);
+2 -2
View File
@@ -147,8 +147,8 @@ public:
CaptureOptions opts);
QStringList GetRemoteSupport();
void GetHomeFolder(DirectoryBrowseMethod cb);
bool ListFolder(QString path, DirectoryBrowseMethod cb);
void GetHomeFolder(bool synchronous, DirectoryBrowseMethod cb);
bool ListFolder(QString path, bool synchronous, DirectoryBrowseMethod cb);
QString CopyCaptureToRemote(const QString &localpath, QWidget *window);
void CopyCaptureFromRemote(const QString &remotepath, const QString &localpath, QWidget *window);
+42 -19
View File
@@ -27,6 +27,7 @@
#include <QStandardItemModel>
#include "Code/QRDUtils.h"
#include "Code/qprocessinfo.h"
#include "Windows/Dialogs/VirtualFileDialog.h"
#include "FlowLayout.h"
#include "LiveCapture.h"
#include "ToolWindowManager.h"
@@ -237,15 +238,29 @@ void CaptureDialog::on_processFilter_textChanged(const QString &filter)
model->setFilterFixedString(filter);
}
void CaptureDialog::on_exePath_textChanged(const QString &exe)
void CaptureDialog::on_exePath_textChanged(const QString &text)
{
QString exe = text;
// This is likely due to someone pasting a full path copied using copy path. Removing the quotes
// is safe in any case
if(exe.startsWith(QChar('"')) && exe.endsWith(QChar('"')) && exe.count() > 2)
{
exe = exe.mid(1, exe.count() - 2);
ui->exePath->setText(exe);
return;
}
QFileInfo f(exe);
QDir dir = f.dir();
bool valid = dir.makeAbsolute();
if(valid && f.isAbsolute())
{
QString path = QDir::toNativeSeparators(dir.absolutePath());
QString path = dir.absolutePath();
if(!m_Ctx->Renderer()->remote())
path = QDir::toNativeSeparators(path);
// match the path separators from the path
if(exe.count(QChar('/')) > exe.count(QChar('\\')))
@@ -291,17 +306,18 @@ void CaptureDialog::on_exePathBrowse_clicked()
file = m_Ctx->Config.LastCaptureExe;
}
QAbstractProxyModel *proxy = NULL;
QFileDialog::Options options;
QString filename;
if(m_Ctx->Renderer()->remote())
{
// proxy = new RemoteFileProxy(m_Ctx->Renderer());
options = QFileDialog::DontUseNativeDialog;
VirtualFileDialog vfd(m_Ctx, this);
RDDialog::show(&vfd);
filename = vfd.chosenPath();
}
else
{
filename = RDDialog::getExecutableFileName(this, tr("Choose executable"), initDir);
}
QString filename =
RDDialog::getExecutableFileName(this, tr("Choose executable"), initDir, options, proxy);
if(filename != "")
setExecutableFilename(filename);
@@ -324,17 +340,19 @@ void CaptureDialog::on_workDirBrowse_clicked()
initDir = m_Ctx->Config.LastCapturePath;
}
QAbstractProxyModel *proxy = NULL;
QFileDialog::Options options = QFileDialog::ShowDirsOnly;
QString dir;
if(m_Ctx->Renderer()->remote())
{
// proxy = new RemoteFileProxy(m_Ctx->Renderer());
options |= QFileDialog::DontUseNativeDialog;
VirtualFileDialog vfd(m_Ctx, this);
vfd.setDirBrowse();
RDDialog::show(&vfd);
dir = vfd.chosenPath();
}
else
{
dir = RDDialog::getExistingDirectory(this, "Choose working directory", initDir);
}
QString dir =
RDDialog::getExistingDirectory(this, "Choose working directory", initDir, options, proxy);
if(dir != "")
ui->workDirPath->setText(dir);
@@ -487,11 +505,16 @@ void CaptureDialog::fillProcessList()
void CaptureDialog::setExecutableFilename(QString filename)
{
filename = QDir::toNativeSeparators(QFileInfo(filename).absoluteFilePath());
if(!m_Ctx->Renderer()->remote())
filename = QDir::toNativeSeparators(QFileInfo(filename).absoluteFilePath());
ui->exePath->setText(filename);
m_Ctx->Config.LastCapturePath = QFileInfo(filename).absolutePath();
m_Ctx->Config.LastCaptureExe = QFileInfo(filename).completeBaseName();
if(!m_Ctx->Renderer()->remote())
{
m_Ctx->Config.LastCapturePath = QFileInfo(filename).absolutePath();
m_Ctx->Config.LastCaptureExe = QFileInfo(filename).completeBaseName();
}
}
void CaptureDialog::loadSettings(QString filename)
@@ -0,0 +1,826 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2017 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "VirtualFileDialog.h"
#include <QDateTime>
#include <QKeyEvent>
#include <QPainter>
#include <QPushButton>
#include <QRegExp>
#include <QSortFilterProxyModel>
#include "Code/CaptureContext.h"
#include "Code/RenderManager.h"
#include "ui_VirtualFileDialog.h"
class RemoteFileModel : public QAbstractItemModel
{
public:
enum Roles
{
FileIsDirRole = Qt::UserRole,
FileIsHiddenRole,
FileIsExecutableRole,
FileIsRootRole,
FileIsAccessDeniedRole,
FilePathRole,
FileNameRole,
};
RemoteFileModel(RenderManager *r, QObject *parent = NULL)
: Renderer(*r), QAbstractItemModel(parent)
{
makeIconStates(fileIcon, QString::fromUtf8(":/Resources/page_white_database.png"));
makeIconStates(exeIcon, QString::fromUtf8(":/Resources/page_white_code.png"));
makeIconStates(dirIcon, QString::fromUtf8(":/Resources/folder_page.png"));
Renderer.GetHomeFolder(true, [this](const char *path, const rdctype::array<DirectoryFile> &files) {
QString homeDir = QString::fromUtf8(path);
if(QChar(path[0]).isLetter() && path[1] == ':')
{
NTPaths = true;
// NT paths
Renderer.ListFolder(
"/", true, [this, homeDir](const char *path, const rdctype::array<DirectoryFile> &files) {
for(int i = 0; i < files.count; i++)
{
FSNode *node = new FSNode();
node->parent = NULL;
node->parentIndex = i;
node->file = files[i];
roots.push_back(node);
home = indexForPath(homeDir);
}
});
}
else
{
NTPaths = false;
FSNode *node = new FSNode();
node->parent = NULL;
node->parentIndex = 0;
node->file.filename = "/";
node->file.flags = eFileProp_Directory;
roots.push_back(node);
home = indexForPath(homeDir);
}
});
for(FSNode *node : roots)
populate(node);
}
~RemoteFileModel()
{
for(FSNode *n : roots)
delete n;
}
QModelIndex homeFolder() const { return home; }
QModelIndex indexForPath(const QString &path) const
{
QModelIndex ret = index(0, 0);
QString normPath = path;
// locate the drive
if(NTPaths)
{
// normalise to unix directory separators
normPath.replace(QChar('\\'), QChar('/'));
for(int i = 0; i < roots.count(); i++)
{
if(normPath[0] == roots[i]->file.filename.front())
{
ret = index(i, 0);
normPath.remove(0, 2);
break;
}
}
}
// normPath is now of the form /subdir1/subdir2/subdir3/...
// with ret pointing to the root directory (trivial on unix)
while(!normPath.isEmpty())
{
if(normPath[0] != '/')
{
qCritical() << "Malformed/unexpected path" << path;
return QModelIndex();
}
// ignore multiple /s adjacent
int start = 1;
while(start < normPath.count() && normPath[start] == '/')
start++;
// if we've hit trailing slashes just stop
if(start >= normPath.count())
break;
int nextDirEnd = normPath.indexOf(QChar('/'), start);
if(nextDirEnd == -1)
nextDirEnd = normPath.count();
QString nextDir = normPath.mid(start, nextDirEnd - start);
normPath.remove(0, nextDirEnd);
FSNode *node = getNode(ret);
populate(node);
for(int i = 0; i < node->children.count(); i++)
{
if(ToQStr(node->children[i]->file.filename)
.compare(nextDir, NTPaths ? Qt::CaseInsensitive : Qt::CaseSensitive) == 0)
{
ret = index(i, 0, ret);
break;
}
}
// if we didn't move to a child, stop searching
if(node == getNode(ret))
{
qCritical() << "Couldn't find child" << nextDir << "at" << ToQStr(node->file.filename)
<< "from" << path;
return QModelIndex();
}
}
return ret;
}
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
{
if(row < 0 || column < 0 || row >= rowCount(parent) || column >= columnCount(parent))
return QModelIndex();
FSNode *node = getNode(parent);
if(node == NULL)
return createIndex(row, column, roots[row]);
return createIndex(row, column, node->children[row]);
}
QModelIndex parent(const QModelIndex &index) const override
{
if(!index.isValid())
return QModelIndex();
FSNode *node = getNode(index);
// root nodes have no index
if(node->parent == NULL)
return QModelIndex();
node = node->parent;
return createIndex(node->parentIndex, 0, node);
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
if(!parent.isValid())
return roots.count();
return getNode(parent)->children.count();
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override
{
// Name | Date Modified | Type | Size
return 4;
}
Qt::ItemFlags flags(const QModelIndex &index) const override
{
Qt::ItemFlags ret = QAbstractItemModel::flags(index);
// disable drag/drop
ret &= ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled);
// disable editing, we don't support remote renaming
ret &= ~Qt::ItemIsEditable;
if(!index.isValid())
return ret;
// if it's not a dir, there are no children
if(getNode(index)->file.flags & eFileProp_Directory)
ret &= ~Qt::ItemNeverHasChildren;
// if we can't populate it, set it as disabled
if(getNode(index)->file.flags & eFileProp_ErrorAccessDenied)
ret &= ~Qt::ItemIsEnabled;
return ret;
}
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
switch(section)
{
case 0: return tr("Name");
case 1: return tr("Size");
case 2: return tr("Type");
case 3: return tr("Date Modified");
default: break;
}
}
return QVariant();
}
bool canFetchMore(const QModelIndex &parent) const override
{
FSNode *node = getNode(parent);
if(!node)
return true;
if(!node->populated)
return true;
for(FSNode *c : node->children)
if(!c->populated)
return true;
return false;
}
void fetchMore(const QModelIndex &parent) override
{
FSNode *node = getNode(parent);
if(!node)
return;
populate(node);
for(FSNode *c : node->children)
populate(c);
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
if(index.isValid())
{
FSNode *node = getNode(index);
switch(role)
{
case Qt::DisplayRole:
{
switch(index.column())
{
case 0: { return ToQStr(node->file.filename);
}
case 1:
{
if(node->file.flags & eFileProp_Directory)
return QVariant();
return qulonglong(node->file.size);
}
case 2:
{
if(node->file.flags & eFileProp_Directory)
return tr("Directory");
else if(node->file.flags & eFileProp_Executable)
return tr("Executable file");
else
return tr("File");
}
case 3:
{
if(node->file.lastmod == 0)
return QVariant();
return QDateTime::fromTime_t(node->file.lastmod);
}
default: break;
}
break;
}
case Qt::DecorationRole:
if(index.column() == 0)
{
int hideIndex = (node->file.flags & eFileProp_Hidden) ? 1 : 0;
if(node->file.flags & eFileProp_Directory)
return dirIcon[hideIndex];
else if(node->file.flags & eFileProp_Executable)
return exeIcon[hideIndex];
else
return fileIcon[hideIndex];
}
case Qt::TextAlignmentRole:
if(index.column() == 1)
return Qt::AlignRight;
break;
case FileIsDirRole: return bool(node->file.flags & eFileProp_Directory);
case FileIsHiddenRole: return bool(node->file.flags & eFileProp_Hidden);
case FileIsExecutableRole: return bool(node->file.flags & eFileProp_Executable);
case FileIsRootRole: return roots.contains(node);
case FileIsAccessDeniedRole: return bool(node->file.flags & eFileProp_ErrorAccessDenied);
case FilePathRole: return makePath(node);
case FileNameRole: return ToQStr(node->file.filename);
default: break;
}
}
return QVariant();
}
private:
RenderManager &Renderer;
QIcon dirIcon[2];
QIcon exeIcon[2];
QIcon fileIcon[2];
void makeIconStates(QIcon *icon, const QString &iconSource)
{
QPixmap normalPixmap(iconSource);
QPixmap disabledPixmap(normalPixmap.size());
disabledPixmap.fill(Qt::transparent);
QPainter p(&disabledPixmap);
p.setBackgroundMode(Qt::TransparentMode);
p.setBackground(QBrush(Qt::transparent));
p.eraseRect(normalPixmap.rect());
p.setOpacity(0.5);
p.drawPixmap(0, 0, normalPixmap);
p.end();
icon[0].addPixmap(normalPixmap);
icon[1].addPixmap(disabledPixmap);
}
struct FSNode
{
FSNode() { memset(&file, 0, sizeof(file)); }
~FSNode()
{
for(FSNode *n : children)
delete n;
}
FSNode *parent = NULL;
int parentIndex = 0;
bool populated = false;
DirectoryFile file;
QList<FSNode *> children;
};
bool NTPaths = false;
QList<FSNode *> roots;
QModelIndex home;
FSNode *getNode(const QModelIndex &idx) const
{
FSNode *node = (FSNode *)idx.internalPointer();
return node;
}
QString makePath(FSNode *node) const
{
QChar sep = NTPaths ? '\\' : '/';
QString ret = ToQStr(node->file.filename);
FSNode *parent = node->parent;
// iterate through subdirs but stop before a root
while(parent && parent->parent)
{
ret = ToQStr(parent->file.filename) + sep + ret;
parent = parent->parent;
}
if(parent)
{
// parent is now a root
ret = ToQStr(parent->file.filename) + ret;
}
ret.replace('/', sep);
return ret;
}
void populate(FSNode *node) const
{
if(!node || node->populated)
return;
node->populated = true;
// nothing to do for non-directories
if(!(node->file.flags & eFileProp_Directory))
return;
Renderer.ListFolder(
makePath(node), true,
[this, node](const char *path, const rdctype::array<DirectoryFile> &files) {
if(files.count == 1 && files[0].flags & eFileProp_ErrorAccessDenied)
{
node->file.flags |= eFileProp_ErrorAccessDenied;
return;
}
QVector<DirectoryFile> sortedFiles;
sortedFiles.reserve(files.count);
for(const DirectoryFile &f : files)
sortedFiles.push_back(f);
qSort(sortedFiles.begin(), sortedFiles.end(),
[](const DirectoryFile &a, const DirectoryFile &b) {
// sort greater than so that files with the flag are sorted before those without
if((a.flags & eFileProp_Directory) != (b.flags & eFileProp_Directory))
return (a.flags & eFileProp_Directory) > (b.flags & eFileProp_Directory);
return strcmp(a.filename.c_str(), b.filename.c_str()) < 0;
});
for(int i = 0; i < sortedFiles.count(); i++)
{
FSNode *child = new FSNode();
child->parent = node;
child->parentIndex = i;
child->file = sortedFiles[i];
node->children.push_back(child);
}
});
}
};
class RemoteFileProxy : public QSortFilterProxyModel
{
public:
RemoteFileProxy(QObject *parent = NULL) : QSortFilterProxyModel(parent) {}
int columnCount(const QModelIndex &parent = QModelIndex()) const override
{
return qMin(maxColCount, sourceModel()->columnCount(parent));
}
void refresh() { QSortFilterProxyModel::filterChanged(); }
int maxColCount = INT_MAX;
bool showFiles = true;
bool showDirs = true;
bool showHidden = true;
bool showNonExecutables = true;
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
{
bool isDir = sourceModel()
->data(sourceModel()->index(source_row, 0, source_parent),
RemoteFileModel::FileIsDirRole)
.toBool();
if(!showDirs && isDir)
return false;
if(!showFiles && !isDir)
return false;
bool isHidden = sourceModel()
->data(sourceModel()->index(source_row, 0, source_parent),
RemoteFileModel::FileIsHiddenRole)
.toBool();
if(!showHidden && isHidden)
return false;
// if we're showing dirs, never apply further filters like filename matching
if(isDir)
return true;
bool isExe = sourceModel()
->data(sourceModel()->index(source_row, 0, source_parent),
RemoteFileModel::FileIsExecutableRole)
.toBool();
if(!showNonExecutables && !isExe)
return false;
return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
}
virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override
{
// always sort dirs first
bool isLeftDir = sourceModel()->data(source_left, RemoteFileModel::FileIsDirRole).toBool();
bool isRightDir = sourceModel()->data(source_right, RemoteFileModel::FileIsDirRole).toBool();
if(isLeftDir && !isRightDir)
return true;
if(!isLeftDir && isRightDir)
return false;
return QSortFilterProxyModel::lessThan(source_left, source_right);
}
};
VirtualFileDialog::VirtualFileDialog(CaptureContext *ctx, QWidget *parent)
: QDialog(parent), ui(new Ui::VirtualFileDialog)
{
ui->setupUi(this);
m_Model = new RemoteFileModel(ctx->Renderer(), this);
m_DirProxy = new RemoteFileProxy(this);
m_DirProxy->setSourceModel(m_Model);
m_DirProxy->showFiles = false;
m_DirProxy->showHidden = ui->showHidden->isChecked();
m_DirProxy->maxColCount = 1;
m_FileProxy = new RemoteFileProxy(this);
m_FileProxy->setSourceModel(m_Model);
m_FileProxy->showHidden = ui->showHidden->isChecked();
ui->dirList->setModel(m_DirProxy);
ui->fileList->setModel(m_FileProxy);
ui->fileList->sortByColumn(0, Qt::AscendingOrder);
ui->fileList->header()->setSectionResizeMode(0, QHeaderView::Stretch);
ui->fileList->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
ui->fileList->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
ui->fileList->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
ui->filter->addItems({tr("Executables"), tr("All Files")});
ui->back->setEnabled(false);
ui->forward->setEnabled(false);
ui->upFolder->setEnabled(false);
ui->buttonBox->button(QDialogButtonBox::Ok)->setDefault(false);
// switch to home folder and expand it
changeCurrentDir(m_Model->homeFolder());
ui->dirList->expand(m_DirProxy->mapFromSource(currentDir()));
}
VirtualFileDialog::~VirtualFileDialog()
{
delete ui;
}
void VirtualFileDialog::setDirBrowse()
{
m_FileProxy->showFiles = false;
ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Select Folder"));
ui->filter->hide();
}
void VirtualFileDialog::keyPressEvent(QKeyEvent *e)
{
// swallow return/enter events
if(e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter)
return;
}
void VirtualFileDialog::accept()
{
// do nothing, don't accept except via our explicit calls
}
void VirtualFileDialog::on_location_keyPress(QKeyEvent *e)
{
// only process when enter is pressed
if(e->key() != Qt::Key_Return && e->key() != Qt::Key_Enter)
return;
// parse folder
QModelIndex idx = m_Model->indexForPath(ui->location->text());
if(idx.isValid())
changeCurrentDir(idx);
else
fileNotFound(ui->location->text());
}
QModelIndex VirtualFileDialog::currentDir()
{
return m_FileProxy->mapToSource(ui->fileList->rootIndex());
}
void VirtualFileDialog::changeCurrentDir(const QModelIndex &index, bool recordHistory)
{
// shouldn't happen, but sanity check
if(!index.isValid())
return;
// ignore changes to current dir
if(currentDir() == index)
return;
if(recordHistory)
{
// erase the history we backed up over
while(m_History.count() > m_HistoryIndex + 1)
m_History.pop_back();
// add new history
m_History.push_back(index);
m_HistoryIndex = m_History.count() - 1;
}
ui->back->setEnabled(m_HistoryIndex > 0);
ui->forward->setEnabled(m_HistoryIndex < m_History.count() - 1);
QModelIndex fileIndex = m_FileProxy->mapFromSource(index);
QModelIndex dirIndex = m_DirProxy->mapFromSource(index);
// set file list to this dir
ui->fileList->setRootIndex(fileIndex);
// update location text
ui->location->setText(m_FileProxy->data(fileIndex, RemoteFileModel::FilePathRole).toString());
// enable up button if we're not at a root
bool isRoot = m_FileProxy->data(fileIndex, RemoteFileModel::FileIsRootRole).toBool();
ui->upFolder->setEnabled(!isRoot);
// expand the directory list so this directory is visible
QModelIndex parent = m_DirProxy->parent(dirIndex);
while(parent.isValid())
{
ui->dirList->expand(parent);
parent = m_DirProxy->parent(parent);
}
// select this directory
ui->dirList->selectionModel()->setCurrentIndex(dirIndex, QItemSelectionModel::ClearAndSelect);
// if it was access denied, show an error now
if(m_FileProxy->data(fileIndex, RemoteFileModel::FileIsAccessDeniedRole).toBool())
{
accessDenied(ui->location->text());
}
}
void VirtualFileDialog::on_dirList_clicked(const QModelIndex &index)
{
changeCurrentDir(m_DirProxy->mapToSource(index));
}
void VirtualFileDialog::on_fileList_doubleClicked(const QModelIndex &index)
{
bool isDir = m_FileProxy->data(index, RemoteFileModel::FileIsDirRole).toBool();
if(isDir)
{
changeCurrentDir(m_FileProxy->mapToSource(index));
}
else
{
m_ChosenPath = m_FileProxy->data(index, RemoteFileModel::FilePathRole).toString();
QDialog::accept();
}
}
void VirtualFileDialog::on_fileList_clicked(const QModelIndex &index)
{
ui->filename->setText(m_FileProxy->data(index, RemoteFileModel::FileNameRole).toString());
}
void VirtualFileDialog::on_showHidden_toggled(bool checked)
{
m_DirProxy->showHidden = ui->showHidden->isChecked();
m_FileProxy->showHidden = ui->showHidden->isChecked();
m_DirProxy->refresh();
m_FileProxy->refresh();
}
void VirtualFileDialog::on_filename_keyPress(QKeyEvent *e)
{
// only process when enter is pressed
if(e->key() != Qt::Key_Return && e->key() != Qt::Key_Enter)
return;
QModelIndex curDir = ui->fileList->rootIndex();
QString text = ui->filename->text();
QRegExp re(text);
re.setPatternSyntax(QRegExp::Wildcard);
int fileCount = m_FileProxy->rowCount(curDir);
int matches = 0;
QString match;
for(int f = 0; f < fileCount; f++)
{
QModelIndex file = m_FileProxy->index(f, 0, curDir);
bool isDir = m_FileProxy->data(file, RemoteFileModel::FileIsDirRole).toBool();
if(isDir)
continue;
QString filename = m_FileProxy->data(file, RemoteFileModel::FileNameRole).toString();
if(re.exactMatch(filename))
{
matches++;
match = m_FileProxy->data(file, RemoteFileModel::FilePathRole).toString();
}
}
if(matches == 1)
{
m_ChosenPath = match;
QDialog::accept();
}
if(matches == 0 && !text.trimmed().isEmpty())
fileNotFound(text);
m_FileProxy->setFilterRegExp(re);
m_FileProxy->refresh();
}
void VirtualFileDialog::on_filter_currentIndexChanged(int index)
{
m_FileProxy->showNonExecutables = (index == 1);
m_FileProxy->refresh();
}
void VirtualFileDialog::on_buttonBox_accepted()
{
if(!m_FileProxy->showFiles)
{
// if browsing for a directory, accept current dir as path
m_ChosenPath = m_Model->data(currentDir(), RemoteFileModel::FilePathRole).toString();
QDialog::accept();
return;
}
// simulate enter being pressed
QKeyEvent fakeEvent(QEvent::KeyPress, Qt::Key_Return, 0);
on_filename_keyPress(&fakeEvent);
}
void VirtualFileDialog::on_back_clicked()
{
m_HistoryIndex = qMax(0, m_HistoryIndex - 1);
changeCurrentDir(m_History[m_HistoryIndex], false);
}
void VirtualFileDialog::on_forward_clicked()
{
m_HistoryIndex = qMin(m_HistoryIndex + 1, m_History.count() - 1);
changeCurrentDir(m_History[m_HistoryIndex], false);
}
void VirtualFileDialog::on_upFolder_clicked()
{
QModelIndex curDir = currentDir();
changeCurrentDir(m_Model->parent(curDir));
}
void VirtualFileDialog::fileNotFound(const QString &path)
{
RDDialog::critical(this, tr("File not found"),
tr("%1\nFile not found.\nCheck the file name and try again.").arg(path));
}
void VirtualFileDialog::accessDenied(const QString &path)
{
RDDialog::critical(this, tr("Access is denied"),
tr("%1 is not accessible\n\nAccess is denied.").arg(path));
}
@@ -0,0 +1,83 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2017 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#pragma once
#include <QDialog>
namespace Ui
{
class VirtualFileDialog;
}
class CaptureContext;
class RemoteFileModel;
class RemoteFileProxy;
class VirtualFileDialog : public QDialog
{
Q_OBJECT
public:
explicit VirtualFileDialog(CaptureContext *ctx, QWidget *parent = 0);
~VirtualFileDialog();
QString chosenPath() { return m_ChosenPath; }
void setDirBrowse();
private slots:
// automatic slots
void on_dirList_clicked(const QModelIndex &index);
void on_fileList_doubleClicked(const QModelIndex &index);
void on_fileList_clicked(const QModelIndex &index);
void on_showHidden_toggled(bool checked);
void on_filename_keyPress(QKeyEvent *e);
void on_filter_currentIndexChanged(int index);
void on_location_keyPress(QKeyEvent *e);
void on_buttonBox_accepted();
void on_back_clicked();
void on_forward_clicked();
void on_upFolder_clicked();
private:
Ui::VirtualFileDialog *ui;
QString m_ChosenPath;
RemoteFileModel *m_Model;
RemoteFileProxy *m_DirProxy;
RemoteFileProxy *m_FileProxy;
QList<QModelIndex> m_History;
int m_HistoryIndex = 0;
void keyPressEvent(QKeyEvent *e) override;
void accept() override;
QModelIndex currentDir();
void changeCurrentDir(const QModelIndex &index, bool recordHistory = true);
void fileNotFound(const QString &path);
void accessDenied(const QString &path);
};
@@ -0,0 +1,272 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>VirtualFileDialog</class>
<widget class="QDialog" name="VirtualFileDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>594</width>
<height>454</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QToolButton" name="back">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../resources.qrc">
<normaloff>:/Resources/back.png</normaloff>:/Resources/back.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="forward">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../resources.qrc">
<normaloff>:/Resources/forward.png</normaloff>:/Resources/forward.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="upFolder">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../resources.qrc">
<normaloff>:/Resources/upfolder.png</normaloff>:/Resources/upfolder.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="locationLabel">
<property name="text">
<string>Location:</string>
</property>
</widget>
</item>
<item>
<widget class="RDLineEdit" name="location"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTreeView" name="dirList">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="headerHidden">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QTreeView" name="fileList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="indentation">
<number>0</number>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<attribute name="headerShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="fileLabel">
<property name="text">
<string>Filename:</string>
</property>
</widget>
</item>
<item>
<widget class="RDLineEdit" name="filename">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>3</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="filter">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="showHidden">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Show hidden files</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>RDLineEdit</class>
<extends>QLineEdit</extends>
<header>Widgets/Extended/RDLineEdit.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../resources.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>VirtualFileDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+6 -3
View File
@@ -138,7 +138,8 @@ SOURCES += Code/qrenderdoc.cpp \
Windows/Dialogs/SettingsDialog.cpp \
Windows/Dialogs/OrderedListEditor.cpp \
Widgets/Extended/RDTableWidget.cpp \
Windows/Dialogs/SuggestRemoteDialog.cpp
Windows/Dialogs/SuggestRemoteDialog.cpp \
Windows/Dialogs/VirtualFileDialog.cpp
HEADERS += Code/CaptureContext.h \
Code/qprocessinfo.h \
@@ -181,7 +182,8 @@ HEADERS += Code/CaptureContext.h \
Windows/Dialogs/SettingsDialog.h \
Windows/Dialogs/OrderedListEditor.h \
Widgets/Extended/RDTableWidget.h \
Windows/Dialogs/SuggestRemoteDialog.h
Windows/Dialogs/SuggestRemoteDialog.h \
Windows/Dialogs/VirtualFileDialog.h
FORMS += Windows/Dialogs/AboutDialog.ui \
Windows/MainWindow.ui \
@@ -206,7 +208,8 @@ FORMS += Windows/Dialogs/AboutDialog.ui \
Windows/StatisticsViewer.ui \
Windows/Dialogs/SettingsDialog.ui \
Windows/Dialogs/OrderedListEditor.ui \
Windows/Dialogs/SuggestRemoteDialog.ui
Windows/Dialogs/SuggestRemoteDialog.ui \
Windows/Dialogs/VirtualFileDialog.ui
RESOURCES += resources.qrc
+5
View File
@@ -533,6 +533,7 @@
<ClCompile Include="generated\moc_ToolWindowManager.cpp" />
<ClCompile Include="generated\moc_ToolWindowManagerArea.cpp" />
<ClCompile Include="generated\moc_ToolWindowManagerWrapper.cpp" />
<ClCompile Include="generated\moc_VirtualFileDialog.cpp" />
<ClCompile Include="generated\moc_VulkanPipelineStateViewer.cpp" />
<ClCompile Include="generated\qrc_resources.cpp" />
<ClCompile Include="Widgets\BufferFormatSpecifier.cpp" />
@@ -560,6 +561,7 @@
<ClCompile Include="Windows\Dialogs\SettingsDialog.cpp" />
<ClCompile Include="Windows\Dialogs\SuggestRemoteDialog.cpp" />
<ClCompile Include="Windows\Dialogs\TextureSaveDialog.cpp" />
<ClCompile Include="Windows\Dialogs\VirtualFileDialog.cpp" />
<ClCompile Include="Windows\EventBrowser.cpp" />
<ClCompile Include="3rdparty\flowlayout\FlowLayout.cpp">
<WarningLevel>Level3</WarningLevel>
@@ -679,6 +681,7 @@
<ClInclude Include="generated\ui_TextureSaveDialog.h" />
<ClInclude Include="generated\ui_TextureViewer.h" />
<ClInclude Include="generated\ui_ThumbnailStrip.h" />
<ClInclude Include="generated\ui_VirtualFileDialog.h" />
<ClInclude Include="generated\ui_VulkanPipelineStateViewer.h" />
<ClInclude Include="Resources\resource.h" />
<ClInclude Include="Widgets\BufferFormatSpecifier.h" />
@@ -706,6 +709,7 @@
<ClInclude Include="Windows\Dialogs\SettingsDialog.h" />
<ClInclude Include="Windows\Dialogs\SuggestRemoteDialog.h" />
<ClInclude Include="Windows\Dialogs\TextureSaveDialog.h" />
<ClInclude Include="Windows\Dialogs\VirtualFileDialog.h" />
<ClInclude Include="Windows\EventBrowser.h" />
<ClInclude Include="3rdparty\flowlayout\FlowLayout.h" />
<ClInclude Include="Widgets\Extended\RDLineEdit.h" />
@@ -738,6 +742,7 @@
<None Include="Windows\Dialogs\SettingsDialog.ui" />
<None Include="Windows\Dialogs\SuggestRemoteDialog.ui" />
<None Include="Windows\Dialogs\TextureSaveDialog.ui" />
<None Include="Windows\Dialogs\VirtualFileDialog.ui" />
<None Include="Windows\EventBrowser.ui" />
<None Include="Windows\MainWindow.ui" />
<None Include="Windows\PipelineState\D3D11PipelineStateViewer.ui" />
@@ -505,6 +505,12 @@
<ClCompile Include="generated\moc_SuggestRemoteDialog.cpp">
<Filter>Generated Files</Filter>
</ClCompile>
<ClCompile Include="Windows\Dialogs\VirtualFileDialog.cpp">
<Filter>Windows\Dialogs</Filter>
</ClCompile>
<ClCompile Include="generated\moc_VirtualFileDialog.cpp">
<Filter>Generated Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="3rdparty\flowlayout\FlowLayout.h">
@@ -900,6 +906,12 @@
<ClInclude Include="generated\ui_SuggestRemoteDialog.h">
<Filter>Generated Files</Filter>
</ClInclude>
<ClInclude Include="Windows\Dialogs\VirtualFileDialog.h">
<Filter>Windows\Dialogs</Filter>
</ClInclude>
<ClInclude Include="generated\ui_VirtualFileDialog.h">
<Filter>Generated Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="resources.qrc">
@@ -1147,6 +1159,9 @@
<None Include="Windows\Dialogs\SuggestRemoteDialog.ui">
<Filter>Windows\Dialogs</Filter>
</None>
<None Include="Windows\Dialogs\VirtualFileDialog.ui">
<Filter>Windows\Dialogs</Filter>
</None>
</ItemGroup>
<ItemGroup>
<ClInclude Include="generated\ui_AboutDialog.h">