Merge pull request #4092 from thoys/20255

CR for Job #20255 - Visually show the grouping of scripts in the "Running Scripts" dialog
This commit is contained in:
Andrew Meadows 2015-01-15 16:58:24 -08:00
commit 5c9e9e79bf
7 changed files with 296 additions and 73 deletions

View file

@ -31,20 +31,30 @@ static const QString IS_TRUNCATED_NAME = "IsTruncated";
static const QString CONTAINER_NAME = "Contents";
static const QString KEY_NAME = "Key";
ScriptItem::ScriptItem(const QString& filename, const QString& fullPath) :
_filename(filename),
_fullPath(fullPath) {
TreeNodeBase::TreeNodeBase(TreeNodeFolder* parent, const QString& name, TreeNodeType type) :
_parent(parent),
_name(name),
_type(type) {
};
TreeNodeScript::TreeNodeScript(const QString& localPath, const QString& fullPath, ScriptOrigin origin) :
TreeNodeBase(NULL, localPath.split("/").last(), TREE_NODE_TYPE_SCRIPT),
_localPath(localPath),
_fullPath(fullPath),
_origin(origin) {
};
TreeNodeFolder::TreeNodeFolder(const QString& foldername, TreeNodeFolder* parent) :
TreeNodeBase(parent, foldername, TREE_NODE_TYPE_FOLDER) {
};
ScriptsModel::ScriptsModel(QObject* parent) :
QAbstractListModel(parent),
QAbstractItemModel(parent),
_loadingScripts(false),
_localDirectory(),
_fsWatcher(),
_localFiles(),
_remoteFiles()
_treeNodes()
{
_localDirectory.setFilter(QDir::Files | QDir::Readable);
_localDirectory.setNameFilters(QStringList("*.js"));
@ -57,31 +67,61 @@ ScriptsModel::ScriptsModel(QObject* parent) :
reloadRemoteFiles();
}
QVariant ScriptsModel::data(const QModelIndex& index, int role) const {
const QList<ScriptItem*>* files = NULL;
int row = 0;
bool isLocal = index.row() < _localFiles.size();
if (isLocal) {
files = &_localFiles;
row = index.row();
} else {
files = &_remoteFiles;
row = index.row() - _localFiles.size();
}
ScriptsModel::~ScriptsModel() {
for (int i = _treeNodes.size() - 1; i >= 0; i--) {
delete _treeNodes.at(i);
}
_treeNodes.clear();
}
if (role == Qt::DisplayRole) {
return QVariant((*files)[row]->getFilename() + (isLocal ? " (local)" : ""));
} else if (role == ScriptPath) {
return QVariant((*files)[row]->getFullPath());
TreeNodeBase* ScriptsModel::getTreeNodeFromIndex(const QModelIndex& index) const {
if (index.isValid()) {
return static_cast<TreeNodeBase*>(index.internalPointer());
}
return NULL;
}
QModelIndex ScriptsModel::index(int row, int column, const QModelIndex& parent) const {
if (row < 0 || column < 0) {
return QModelIndex();
}
return createIndex(row, column, getFolderNodes(static_cast<TreeNodeFolder*>(getTreeNodeFromIndex(parent))).at(row));
}
QModelIndex ScriptsModel::parent(const QModelIndex& child) const {
TreeNodeFolder* parent = (static_cast<TreeNodeBase*>(child.internalPointer()))->getParent();
if (!parent) {
return QModelIndex();
}
TreeNodeFolder* grandParent = parent->getParent();
int row = getFolderNodes(grandParent).indexOf(parent);
return createIndex(row, 0, parent);
}
QVariant ScriptsModel::data(const QModelIndex& index, int role) const {
TreeNodeBase* node = getTreeNodeFromIndex(index);
if (node->getType() == TREE_NODE_TYPE_SCRIPT) {
TreeNodeScript* script = static_cast<TreeNodeScript*>(node);
if (role == Qt::DisplayRole) {
return QVariant(script->getName() + (script->getOrigin() == SCRIPT_ORIGIN_LOCAL ? " (local)" : ""));
} else if (role == ScriptPath) {
return QVariant(script->getFullPath());
}
} else if (node->getType() == TREE_NODE_TYPE_FOLDER) {
TreeNodeFolder* folder = static_cast<TreeNodeFolder*>(node);
if (role == Qt::DisplayRole) {
return QVariant(folder->getName());
}
}
return QVariant();
}
int ScriptsModel::rowCount(const QModelIndex& parent) const {
if (parent.isValid()) {
return 0;
}
return _localFiles.length() + _remoteFiles.length();
return getFolderNodes(static_cast<TreeNodeFolder*>(getTreeNodeFromIndex(parent))).count();
}
int ScriptsModel::columnCount(const QModelIndex& parent) const {
return 1;
}
void ScriptsModel::updateScriptsLocation(const QString& newPath) {
@ -93,7 +133,7 @@ void ScriptsModel::updateScriptsLocation(const QString& newPath) {
if (!_localDirectory.absolutePath().isEmpty()) {
_fsWatcher.addPath(_localDirectory.absolutePath());
}
}
}
reloadLocalFiles();
}
@ -101,8 +141,14 @@ void ScriptsModel::updateScriptsLocation(const QString& newPath) {
void ScriptsModel::reloadRemoteFiles() {
if (!_loadingScripts) {
_loadingScripts = true;
while (!_remoteFiles.isEmpty()) {
delete _remoteFiles.takeFirst();
for (int i = _treeNodes.size() - 1; i >= 0; i--) {
TreeNodeBase* node = _treeNodes.at(i);
if (node->getType() == TREE_NODE_TYPE_SCRIPT &&
static_cast<TreeNodeScript*>(node)->getOrigin() == SCRIPT_ORIGIN_REMOTE)
{
delete node;
_treeNodes.removeAt(i);
}
}
requestRemoteFiles();
}
@ -121,7 +167,6 @@ void ScriptsModel::requestRemoteFiles(QString marker) {
QNetworkRequest request(url);
QNetworkReply* reply = networkAccessManager.get(request);
connect(reply, SIGNAL(finished()), SLOT(downloadFinished()));
}
void ScriptsModel::downloadFinished() {
@ -170,7 +215,7 @@ bool ScriptsModel::parseXML(QByteArray xmlFile) {
xml.readNext();
lastKey = xml.text().toString();
if (jsRegex.exactMatch(xml.text().toString())) {
_remoteFiles.append(new ScriptItem(lastKey.mid(MODELS_LOCATION.length()), S3_URL + "/" + lastKey));
_treeNodes.append(new TreeNodeScript(lastKey.mid(MODELS_LOCATION.length()), S3_URL + "/" + lastKey, SCRIPT_ORIGIN_REMOTE));
}
}
xml.readNext();
@ -178,7 +223,7 @@ bool ScriptsModel::parseXML(QByteArray xmlFile) {
}
xml.readNext();
}
rebuildTree();
endResetModel();
// Error handling
@ -198,8 +243,14 @@ bool ScriptsModel::parseXML(QByteArray xmlFile) {
void ScriptsModel::reloadLocalFiles() {
beginResetModel();
while (!_localFiles.isEmpty()) {
delete _localFiles.takeFirst();
for (int i = _treeNodes.size() - 1; i >= 0; i--) {
TreeNodeBase* node = _treeNodes.at(i);
if (node->getType() == TREE_NODE_TYPE_SCRIPT &&
static_cast<TreeNodeScript*>(node)->getOrigin() == SCRIPT_ORIGIN_LOCAL)
{
delete node;
_treeNodes.removeAt(i);
}
}
_localDirectory.refresh();
@ -207,8 +258,53 @@ void ScriptsModel::reloadLocalFiles() {
const QFileInfoList localFiles = _localDirectory.entryInfoList();
for (int i = 0; i < localFiles.size(); i++) {
QFileInfo file = localFiles[i];
_localFiles.append(new ScriptItem(file.fileName(), file.absoluteFilePath()));
_treeNodes.append(new TreeNodeScript(file.fileName(), file.absoluteFilePath(), SCRIPT_ORIGIN_LOCAL));
}
rebuildTree();
endResetModel();
}
void ScriptsModel::rebuildTree() {
for (int i = _treeNodes.size() - 1; i >= 0; i--) {
if (_treeNodes.at(i)->getType() == TREE_NODE_TYPE_FOLDER) {
delete _treeNodes.at(i);
_treeNodes.removeAt(i);
}
}
QHash<QString, TreeNodeFolder*> folders;
for (int i = 0; i < _treeNodes.size(); i++) {
TreeNodeBase* node = _treeNodes.at(i);
if (node->getType() == TREE_NODE_TYPE_SCRIPT) {
TreeNodeScript* script = static_cast<TreeNodeScript*>(node);
TreeNodeFolder* parent = NULL;
QString hash;
QStringList pathList = script->getLocalPath().split(tr("/"));
pathList.removeLast();
QStringList::const_iterator pathIterator;
for (pathIterator = pathList.constBegin(); pathIterator != pathList.constEnd(); ++pathIterator) {
hash.append(*pathIterator + "/");
if (!folders.contains(hash)) {
folders[hash] = new TreeNodeFolder(*pathIterator, parent);
}
parent = folders[hash];
}
script->setParent(parent);
}
}
QHash<QString, TreeNodeFolder*>::const_iterator folderIterator;
for (folderIterator = folders.constBegin(); folderIterator != folders.constEnd(); ++folderIterator) {
_treeNodes.append(*folderIterator);
}
folders.clear();
}
QList<TreeNodeBase*> ScriptsModel::getFolderNodes(TreeNodeFolder* parent) const {
QList<TreeNodeBase*> result;
for (int i = 0; i < _treeNodes.size(); i++) {
TreeNodeBase* node = _treeNodes.at(i);
if (node->getParent() == parent) {
result.append(node);
}
}
return result;
}

View file

@ -12,30 +12,69 @@
#ifndef hifi_ScriptsModel_h
#define hifi_ScriptsModel_h
#include <QAbstractListModel>
#include <QAbstractItemModel>
#include <QDir>
#include <QNetworkReply>
#include <QFileSystemWatcher>
class ScriptItem {
public:
ScriptItem(const QString& filename, const QString& fullPath);
class TreeNodeFolder;
const QString& getFilename() { return _filename; };
const QString& getFullPath() { return _fullPath; };
private:
QString _filename;
QString _fullPath;
enum ScriptOrigin {
SCRIPT_ORIGIN_LOCAL,
SCRIPT_ORIGIN_REMOTE
};
class ScriptsModel : public QAbstractListModel {
enum TreeNodeType {
TREE_NODE_TYPE_SCRIPT,
TREE_NODE_TYPE_FOLDER
};
class TreeNodeBase {
public:
TreeNodeFolder* getParent() const { return _parent; }
void setParent(TreeNodeFolder* parent) { _parent = parent; }
TreeNodeType getType() { return _type; }
const QString& getName() { return _name; };
private:
TreeNodeFolder* _parent;
TreeNodeType _type;
protected:
QString _name;
TreeNodeBase(TreeNodeFolder* parent, const QString& name, TreeNodeType type);
};
class TreeNodeScript : public TreeNodeBase {
public:
TreeNodeScript(const QString& localPath, const QString& fullPath, ScriptOrigin origin);
const QString& getLocalPath() { return _localPath; }
const QString& getFullPath() { return _fullPath; };
const ScriptOrigin getOrigin() { return _origin; };
private:
QString _localPath;
QString _fullPath;
ScriptOrigin _origin;
};
class TreeNodeFolder : public TreeNodeBase {
public:
TreeNodeFolder(const QString& foldername, TreeNodeFolder* parent);
};
class ScriptsModel : public QAbstractItemModel {
Q_OBJECT
public:
ScriptsModel(QObject* parent = NULL);
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
~ScriptsModel();
QModelIndex index(int row, int column, const QModelIndex& parent) const;
QModelIndex parent(const QModelIndex& child) const;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
int rowCount(const QModelIndex& parent = QModelIndex()) const;
int columnCount(const QModelIndex& parent = QModelIndex()) const;
TreeNodeBase* getTreeNodeFromIndex(const QModelIndex& index) const;
QList<TreeNodeBase*> getFolderNodes(TreeNodeFolder* parent) const;
enum Role {
ScriptPath = Qt::UserRole,
@ -50,13 +89,13 @@ protected slots:
protected:
void requestRemoteFiles(QString marker = QString());
bool parseXML(QByteArray xmlFile);
void rebuildTree();
private:
bool _loadingScripts;
QDir _localDirectory;
QFileSystemWatcher _fsWatcher;
QList<ScriptItem*> _localFiles;
QList<ScriptItem*> _remoteFiles;
QList<TreeNodeBase*> _treeNodes;
};
#endif // hifi_ScriptsModel_h

View file

@ -0,0 +1,44 @@
//
// ScriptsModelFilter.cpp
// interface/src
//
// Created by Thijs Wenker on 01/11/15.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ScriptsModelFilter.h"
ScriptsModelFilter::ScriptsModelFilter(QObject *parent) :
QSortFilterProxyModel(parent) {
}
bool ScriptsModelFilter::lessThan(const QModelIndex& left, const QModelIndex& right) const {
ScriptsModel* scriptsModel = static_cast<ScriptsModel*>(sourceModel());
TreeNodeBase* leftNode = scriptsModel->getTreeNodeFromIndex(left);
TreeNodeBase* rightNode = scriptsModel->getTreeNodeFromIndex(right);
if (leftNode->getType() != rightNode->getType()) {
return leftNode->getType() == TREE_NODE_TYPE_FOLDER;
}
return leftNode->getName() < rightNode->getName();
}
bool ScriptsModelFilter::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const {
if (!filterRegExp().isEmpty()) {
ScriptsModel* scriptsModel = static_cast<ScriptsModel*>(sourceModel());
TreeNodeBase* node = scriptsModel->getFolderNodes(
static_cast<TreeNodeFolder*>(scriptsModel->getTreeNodeFromIndex(sourceParent))).at(sourceRow);
QModelIndex sourceIndex = sourceModel()->index(sourceRow, this->filterKeyColumn(), sourceParent);
if (node->getType() == TREE_NODE_TYPE_FOLDER) {
int rows = scriptsModel->rowCount(sourceIndex);
for (int i = 0; i < rows; i++) {
if (filterAcceptsRow(i, sourceIndex)) {
return true;
}
}
}
}
return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
}

View file

@ -0,0 +1,27 @@
//
// ScriptsModelFilter.h
// interface/src
//
// Created by Thijs Wenker on 01/11/15.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ScriptsModelFilter_h
#define hifi_ScriptsModelFilter_h
#include "ScriptsModel.h"
#include <QSortFilterProxyModel>
class ScriptsModelFilter : public QSortFilterProxyModel {
Q_OBJECT
public:
ScriptsModelFilter(QObject *parent = NULL);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const;
bool lessThan(const QModelIndex& left, const QModelIndex& right) const;
};
#endif // hifi_ScriptsModelFilter_h

View file

@ -33,7 +33,7 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) :
Qt::WindowCloseButtonHint),
ui(new Ui::RunningScriptsWidget),
_signalMapper(this),
_proxyModel(this),
_scriptsModelFilter(this),
_scriptsModel(this) {
ui->setupUi(this);
@ -41,46 +41,49 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) :
ui->filterLineEdit->installEventFilter(this);
connect(&_proxyModel, &QSortFilterProxyModel::modelReset,
connect(&_scriptsModelFilter, &QSortFilterProxyModel::modelReset,
this, &RunningScriptsWidget::selectFirstInList);
QString shortcutText = Menu::getInstance()->getActionForOption(MenuOption::ReloadAllScripts)->shortcut().toString(QKeySequence::NativeText);
ui->tipLabel->setText("Tip: Use " + shortcutText + " to reload all scripts.");
_proxyModel.setSourceModel(&_scriptsModel);
_proxyModel.sort(0, Qt::AscendingOrder);
_proxyModel.setDynamicSortFilter(true);
ui->scriptListView->setModel(&_proxyModel);
_scriptsModelFilter.setSourceModel(&_scriptsModel);
_scriptsModelFilter.sort(0, Qt::AscendingOrder);
_scriptsModelFilter.setDynamicSortFilter(true);
ui->scriptTreeView->setModel(&_scriptsModelFilter);
connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &RunningScriptsWidget::updateFileFilter);
connect(ui->scriptListView, &QListView::doubleClicked, this, &RunningScriptsWidget::loadScriptFromList);
connect(ui->scriptTreeView, &QTreeView::doubleClicked, this, &RunningScriptsWidget::loadScriptFromList);
connect(ui->reloadAllButton, &QPushButton::clicked,
Application::getInstance(), &Application::reloadAllScripts);
connect(ui->stopAllButton, &QPushButton::clicked,
this, &RunningScriptsWidget::allScriptsStopped);
connect(ui->loadScriptButton, &QPushButton::clicked,
connect(ui->loadScriptFromDiskButton, &QPushButton::clicked,
Application::getInstance(), &Application::loadDialog);
connect(ui->loadScriptFromURLButton, &QPushButton::clicked,
Application::getInstance(), &Application::loadScriptURLDialog);
connect(&_signalMapper, SIGNAL(mapped(QString)), Application::getInstance(), SLOT(stopScript(const QString&)));
}
RunningScriptsWidget::~RunningScriptsWidget() {
delete ui;
_scriptsModel.deleteLater();
}
void RunningScriptsWidget::updateFileFilter(const QString& filter) {
QRegExp regex("^.*" + QRegExp::escape(filter) + ".*$", Qt::CaseInsensitive);
_proxyModel.setFilterRegExp(regex);
_scriptsModelFilter.setFilterRegExp(regex);
selectFirstInList();
}
void RunningScriptsWidget::loadScriptFromList(const QModelIndex& index) {
QVariant scriptFile = _proxyModel.data(index, ScriptsModel::ScriptPath);
QVariant scriptFile = _scriptsModelFilter.data(index, ScriptsModel::ScriptPath);
Application::getInstance()->loadScript(scriptFile.toString());
}
void RunningScriptsWidget::loadSelectedScript() {
QModelIndex selectedIndex = ui->scriptListView->currentIndex();
QModelIndex selectedIndex = ui->scriptTreeView->currentIndex();
if (selectedIndex.isValid()) {
loadScriptFromList(selectedIndex);
}
@ -165,8 +168,8 @@ void RunningScriptsWidget::showEvent(QShowEvent* event) {
}
void RunningScriptsWidget::selectFirstInList() {
if (_proxyModel.rowCount() > 0) {
ui->scriptListView->setCurrentIndex(_proxyModel.index(0, 0));
if (_scriptsModelFilter.rowCount() > 0) {
ui->scriptTreeView->setCurrentIndex(_scriptsModelFilter.index(0, 0));
}
}
@ -177,7 +180,7 @@ bool RunningScriptsWidget::eventFilter(QObject* sender, QEvent* event) {
}
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
QModelIndex selectedIndex = ui->scriptListView->currentIndex();
QModelIndex selectedIndex = ui->scriptTreeView->currentIndex();
if (selectedIndex.isValid()) {
loadScriptFromList(selectedIndex);
}

View file

@ -18,6 +18,7 @@
#include <QSortFilterProxyModel>
#include "ScriptsModel.h"
#include "ScriptsModelFilter.h"
#include "ScriptsTableWidget.h"
namespace Ui {
@ -54,7 +55,7 @@ private slots:
private:
Ui::RunningScriptsWidget* ui;
QSignalMapper _signalMapper;
QSortFilterProxyModel _proxyModel;
ScriptsModelFilter _scriptsModelFilter;
ScriptsModel _scriptsModel;
ScriptsTableWidget* _recentlyLoadedScriptsTable;
QStringList _recentlyLoadedScripts;

View file

@ -245,8 +245,8 @@ font: bold 16px;
<rect>
<x>0</x>
<y>0</y>
<width>328</width>
<height>18</height>
<width>334</width>
<height>20</height>
</rect>
</property>
<property name="sizePolicy">
@ -373,7 +373,7 @@ font: bold 16px;
font: bold 16px;</string>
</property>
<property name="text">
<string>Scripts</string>
<string>Load Scripts</string>
</property>
</widget>
</item>
@ -391,9 +391,16 @@ font: bold 16px;</string>
</spacer>
</item>
<item>
<widget class="QPushButton" name="loadScriptButton">
<widget class="QPushButton" name="loadScriptFromURLButton">
<property name="text">
<string>Load script</string>
<string>from URL</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="loadScriptFromDiskButton">
<property name="text">
<string>from Disk</string>
</property>
</widget>
</item>
@ -429,7 +436,7 @@ font: bold 16px;</string>
</widget>
</item>
<item>
<widget class="QListView" name="scriptListView">
<widget class="QTreeView" name="scriptTreeView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -442,6 +449,12 @@ font: bold 16px;</string>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="rootIsDecorated">
<bool>true</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>