Attachment dialog

This commit is contained in:
Brad Davis 2016-01-23 17:14:30 -08:00
parent 881a94cd19
commit 664100b9b1
14 changed files with 774 additions and 1 deletions

View file

@ -0,0 +1,123 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import Qt.labs.settings 1.0
import "../../windows"
import "attachments"
Window {
id: root
title: "Edit Attachments"
width: 600
height: 600
resizable: true
// User must click OK or cancel to close the window
closable: false
readonly property var originalAttachments: MyAvatar.getAttachmentsVariant();
property var attachments: [];
property var settings: Settings {
category: "AttachmentsDialog"
property alias x: root.x
property alias y: root.y
property alias width: root.width
property alias height: root.height
}
Component.onCompleted: {
for (var i = 0; i < originalAttachments.length; ++i) {
var attachment = originalAttachments[i];
root.attachments.push(attachment);
listView.model.append({});
}
}
Rectangle {
anchors.fill: parent
radius: 4
Rectangle {
id: attachmentsBackground
anchors { left: parent.left; right: parent.right; top: parent.top; bottom: newAttachmentButton.top; margins: 8 }
color: "gray"
radius: 4
ScrollView{
id: scrollView
anchors.fill: parent
anchors.margins: 4
ListView {
id: listView
model: ListModel {}
delegate: Item {
implicitHeight: attachmentView.height + 8;
implicitWidth: attachmentView.width;
Attachment {
id: attachmentView
width: scrollView.width
attachment: root.attachments[index]
onDeleteAttachment: {
attachments.splice(index, 1);
listView.model.remove(index, 1);
}
onUpdateAttachment: MyAvatar.setAttachmentsVariant(attachments);
}
}
onCountChanged: MyAvatar.setAttachmentsVariant(attachments);
}
}
}
Button {
id: newAttachmentButton
anchors { left: parent.left; right: parent.right; bottom: buttonRow.top; margins: 8 }
text: "New Attachment"
onClicked: {
var template = {
modelUrl: "",
translation: { x: 0, y: 0, z: 0 },
rotation: { x: 0, y: 0, z: 0 },
scale: 1,
jointName: MyAvatar.jointNames[0],
soft: false
};
attachments.push(template);
listView.model.append({});
MyAvatar.setAttachmentsVariant(attachments);
}
}
Row {
id: buttonRow
spacing: 8
anchors { right: parent.right; bottom: parent.bottom; margins: 8 }
Button { action: cancelAction }
Button { action: okAction }
}
Action {
id: cancelAction
text: "Cancel"
onTriggered: {
MyAvatar.setAttachmentsVariant(originalAttachments);
root.destroy()
}
}
Action {
id: okAction
text: "OK"
onTriggered: {
for (var i = 0; i < attachments.length; ++i) {
console.log("Attachment " + i + ": " + attachments[i]);
}
MyAvatar.setAttachmentsVariant(attachments);
root.destroy()
}
}
}
}

View file

@ -0,0 +1,128 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.XmlListModel 2.0
import "../../windows"
import "../../js/Utils.js" as Utils
import "../models"
ModalWindow {
id: root
resizable: true
width: 640
height: 480
property var result;
signal selected(var modelUrl);
signal canceled();
Rectangle {
anchors.fill: parent
color: "white"
Item {
anchors { fill: parent; margins: 8 }
TextField {
id: filterEdit
anchors { left: parent.left; right: parent.right; top: parent.top }
placeholderText: "filter"
onTextChanged: tableView.model.filter = text
}
TableView {
id: tableView
anchors { left: parent.left; right: parent.right; top: filterEdit.bottom; topMargin: 8; bottom: buttonRow.top; bottomMargin: 8 }
model: S3Model{}
onCurrentRowChanged: {
if (currentRow == -1) {
root.result = null;
return;
}
result = model.baseUrl + "/" + model.get(tableView.currentRow).key;
}
itemDelegate: Component {
Item {
clip: true
Text {
x: 3
anchors.verticalCenter: parent.verticalCenter
color: tableView.activeFocus && styleData.row === tableView.currentRow ? "yellow" : styleData.textColor
elide: styleData.elideMode
text: getText()
function getText() {
switch(styleData.column) {
case 1:
return Utils.formatSize(styleData.value)
default:
return styleData.value;
}
}
}
}
}
TableViewColumn {
role: "name"
title: "Name"
width: 200
}
TableViewColumn {
role: "size"
title: "Size"
width: 100
}
TableViewColumn {
role: "modified"
title: "Last Modified"
width: 200
}
Rectangle {
anchors.fill: parent
visible: tableView.model.status !== XmlListModel.Ready
color: "#7fffffff"
BusyIndicator {
anchors.centerIn: parent
width: 48; height: 48
running: true
}
}
}
Row {
id: buttonRow
anchors { right: parent.right; bottom: parent.bottom }
Button { action: acceptAction }
Button { action: cancelAction }
}
Action {
id: acceptAction
text: qsTr("OK")
enabled: root.result ? true : false
shortcut: Qt.Key_Return
onTriggered: {
root.selected(root.result);
root.destroy();
}
}
Action {
id: cancelAction
text: qsTr("Cancel")
shortcut: Qt.Key_Escape
onTriggered: {
root.canceled();
root.destroy();
}
}
}
}
}

View file

@ -0,0 +1,128 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.XmlListModel 2.0
import "../../windows"
import "../../js/Utils.js" as Utils
import "../models"
ModalWindow {
id: root
resizable: true
width: 640
height: 480
property var result;
signal selected(var modelUrl);
signal canceled();
Rectangle {
anchors.fill: parent
color: "white"
Item {
anchors { fill: parent; margins: 8 }
TextField {
id: filterEdit
anchors { left: parent.left; right: parent.right; top: parent.top }
placeholderText: "filter"
onTextChanged: tableView.model.filter = text
}
TableView {
id: tableView
anchors { left: parent.left; right: parent.right; top: filterEdit.bottom; topMargin: 8; bottom: buttonRow.top; bottomMargin: 8 }
model: S3Model{}
onCurrentRowChanged: {
if (currentRow == -1) {
root.result = null;
return;
}
result = model.baseUrl + "/" + model.get(tableView.currentRow).key;
}
itemDelegate: Component {
Item {
clip: true
Text {
x: 3
anchors.verticalCenter: parent.verticalCenter
color: tableView.activeFocus && styleData.row === tableView.currentRow ? "yellow" : styleData.textColor
elide: styleData.elideMode
text: getText()
function getText() {
switch(styleData.column) {
case 1:
return Utils.formatSize(styleData.value)
default:
return styleData.value;
}
}
}
}
}
TableViewColumn {
role: "name"
title: "Name"
width: 200
}
TableViewColumn {
role: "size"
title: "Size"
width: 100
}
TableViewColumn {
role: "modified"
title: "Last Modified"
width: 200
}
Rectangle {
anchors.fill: parent
visible: tableView.model.status !== XmlListModel.Ready
color: "#7fffffff"
BusyIndicator {
anchors.centerIn: parent
width: 48; height: 48
running: true
}
}
}
Row {
id: buttonRow
anchors { right: parent.right; bottom: parent.bottom }
Button { action: acceptAction }
Button { action: cancelAction }
}
Action {
id: acceptAction
text: qsTr("OK")
enabled: root.result ? true : false
shortcut: Qt.Key_Return
onTriggered: {
root.selected(root.result);
root.destroy();
}
}
Action {
id: cancelAction
text: qsTr("Cancel")
shortcut: Qt.Key_Escape
onTriggered: {
root.canceled();
root.destroy();
}
}
}
}
}

View file

@ -0,0 +1,164 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import "../../../windows"
import "."
import ".."
Item {
height: column.height + 2 * 8
property var attachment;
signal deleteAttachment(var attachment);
signal updateAttachment();
property bool completed: false;
Rectangle { color: "white"; anchors.fill: parent; radius: 4 }
Component.onCompleted: {
completed = true;
}
Column {
y: 8
id: column
anchors { left: parent.left; right: parent.right; margins: 8 }
spacing: 8
Item {
height: modelChooserButton.height
anchors { left: parent.left; right: parent.right; }
Text { id: urlLabel; text: "Model URL:"; width: 80; anchors.verticalCenter: modelUrl.verticalCenter }
TextField {
id: modelUrl;
height: jointChooser.height;
anchors { left: urlLabel.right; leftMargin: 8; rightMargin: 8; right: modelChooserButton.left }
text: attachment ? attachment.modelUrl : ""
onTextChanged: {
if (completed && attachment && attachment.modelUrl !== text) {
attachment.modelUrl = text;
updateAttachment();
}
}
}
Button {
id: modelChooserButton;
text: "Choose";
anchors { right: parent.right; verticalCenter: modelUrl.verticalCenter }
Component {
id: modelBrowserBuiler;
ModelBrowserDialog {}
}
onClicked: {
var browser = modelBrowserBuiler.createObject(desktop);
browser.selected.connect(function(newModelUrl){
modelUrl.text = newModelUrl;
})
}
}
}
Item {
height: jointChooser.height
anchors { left: parent.left; right: parent.right; }
Text {
id: jointLabel;
width: 80;
text: "Joint:";
anchors.verticalCenter: jointChooser.verticalCenter;
}
ComboBox {
id: jointChooser;
anchors { left: jointLabel.right; leftMargin: 8; right: parent.right }
model: MyAvatar.jointNames
currentIndex: attachment ? model.indexOf(attachment.jointName) : -1
onCurrentIndexChanged: {
if (completed && attachment && currentIndex != -1 && currentText && currentText !== attachment.jointName) {
attachment.jointName = currentText;
updateAttachment();
}
}
}
}
Item {
height: translation.height
anchors { left: parent.left; right: parent.right; }
Text { id: translationLabel; width: 80; text: "Translation:"; anchors.verticalCenter: translation.verticalCenter; }
Translation {
id: translation;
anchors { left: translationLabel.right; leftMargin: 8; right: parent.right }
vector: attachment ? attachment.translation : {x: 0, y: 0, z: 0};
onValueChanged: {
if (completed && attachment) {
attachment.translation = vector;
updateAttachment();
}
}
}
}
Item {
height: rotation.height
anchors { left: parent.left; right: parent.right; }
Text { id: rotationLabel; width: 80; text: "Rotation:"; anchors.verticalCenter: rotation.verticalCenter; }
Rotation {
id: rotation;
anchors { left: rotationLabel.right; leftMargin: 8; right: parent.right }
vector: attachment ? attachment.rotation : {x: 0, y: 0, z: 0};
onValueChanged: {
if (completed && attachment) {
attachment.rotation = vector;
updateAttachment();
}
}
}
}
Item {
height: scaleSpinner.height
anchors { left: parent.left; right: parent.right; }
Text { id: scaleLabel; width: 80; text: "Scale:"; anchors.verticalCenter: scale.verticalCenter; }
SpinBox {
id: scaleSpinner;
anchors { left: scaleLabel.right; leftMargin: 8; right: parent.right }
decimals: 1;
minimumValue: 0.1
maximumValue: 10
stepSize: 0.1;
value: attachment ? attachment.scale : 1.0
onValueChanged: {
if (completed && attachment && attachment.scale !== value) {
attachment.scale = value;
updateAttachment();
}
}
}
}
Item {
height: soft.height
anchors { left: parent.left; right: parent.right; }
Text { id: softLabel; width: 80; text: "Is soft:"; anchors.verticalCenter: soft.verticalCenter; }
CheckBox {
id: soft;
anchors { left: softLabel.right; leftMargin: 8; right: parent.right }
checked: attachment ? attachment.soft : false
onCheckedChanged: {
if (completed && attachment && attachment.soft !== checked) {
attachment.soft = checked;
updateAttachment();
}
}
}
}
Button {
anchors { left: parent.left; right: parent.right; }
text: "Delete"
onClicked: deleteAttachment(root.attachment);
}
}
}

View file

@ -0,0 +1,9 @@
import "."
Vector3 {
decimals: 1;
stepSize: 1;
maximumValue: 180
minimumValue: -180
}

View file

@ -0,0 +1,9 @@
import "."
Vector3 {
decimals: 2;
stepSize: 0.01;
maximumValue: 10
minimumValue: -10
}

View file

@ -0,0 +1,71 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
Item {
id: root
implicitHeight: xspinner.height
readonly property real spacing: 8
property real spinboxWidth: (width / 3) - spacing
property var vector;
property real decimals: 0
property real stepSize: 1
property real maximumValue: 99
property real minimumValue: 0
signal valueChanged();
SpinBox {
id: xspinner
width: root.spinboxWidth
anchors { left: parent.left }
value: root.vector.x
decimals: root.decimals
stepSize: root.stepSize
maximumValue: root.maximumValue
minimumValue: root.minimumValue
onValueChanged: {
if (value !== vector.x) {
vector.x = value
root.valueChanged();
}
}
}
SpinBox {
id: yspinner
width: root.spinboxWidth
anchors { horizontalCenter: parent.horizontalCenter }
value: root.vector.y
decimals: root.decimals
stepSize: root.stepSize
maximumValue: root.maximumValue
minimumValue: root.minimumValue
onValueChanged: {
if (value !== vector.y) {
vector.y = value
root.valueChanged();
}
}
}
SpinBox {
id: zspinner
width: root.spinboxWidth
anchors { right: parent.right; }
value: root.vector.z
decimals: root.decimals
stepSize: root.stepSize
maximumValue: root.maximumValue
minimumValue: root.minimumValue
onValueChanged: {
if (value !== vector.z) {
vector.z = value
root.valueChanged();
}
}
}
}

View file

@ -0,0 +1,41 @@
import QtQuick 2.0
import QtQuick.XmlListModel 2.0
//http://s3.amazonaws.com/hifi-public?prefix=models/attachments
/*
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Contents>
<Key>models/attachments/guitar.fst</Key>
<LastModified>2015-11-10T00:28:22.000Z</LastModified>
<ETag>&quot;236c00c4802ba9c2605cabd5601d138e&quot;</ETag>
<Size>2992</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
</ListBucketResult>
*/
// FIXME how to deal with truncated results? Store the marker?
XmlListModel {
id: xmlModel
property string prefix: "models/attachments/"
property string extension: "fst"
property string filter;
readonly property string realPrefix: prefix.match('.*/$') ? prefix : (prefix + "/")
readonly property string nameRegex: realPrefix + (filter ? (".*" + filter) : "") + ".*\." + extension
readonly property string nameQuery: "Key/substring-before(substring-after(string(), '" + prefix + "'), '." + extension + "')"
readonly property string baseUrl: "http://s3.amazonaws.com/hifi-public"
// FIXME need to urlencode prefix?
source: baseUrl + "?prefix=" + realPrefix
query: "/ListBucketResult/Contents[matches(Key, '" + nameRegex + "')]"
namespaceDeclarations: "declare default element namespace 'http://s3.amazonaws.com/doc/2006-03-01/';"
XmlRole { name: "name"; query: nameQuery }
XmlRole { name: "size"; query: "Size/string()" }
XmlRole { name: "tag"; query: "ETag/string()" }
XmlRole { name: "modified"; query: "LastModified/string()" }
XmlRole { name: "key"; query: "Key/string()" }
}

View file

@ -14,3 +14,17 @@ function randomPosition(min, max) {
Math.random() * (max.x - min.x),
Math.random() * (max.y - min.y));
}
function formatSize(size) {
var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ];
var suffixIndex = 0
while ((size / 1024.0) > 1.1) {
size /= 1024.0;
++suffixIndex;
}
size = Math.round(size*1000)/1000;
size = size.toLocaleString()
return size + " " + suffixes[suffixIndex];
}

View file

@ -1186,6 +1186,7 @@ void Application::initializeUi() {
UpdateDialog::registerType();
qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->create(_offscreenContext->getContext());
offscreenUi->setProxyWindow(_window->windowHandle());
@ -1842,6 +1843,10 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_X:
if (isShifted && isMeta) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getRootContext()->engine()->clearComponentCache();
offscreenUi->load("hifi/dialogs/AttachmentsDialog.qml");
// OffscreenUi::information("Debugging", "Component cache cleared");
// placeholder for dialogs being converted to QML.
}
break;

View file

@ -25,6 +25,7 @@
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <QVariantGLM.h>
#include <Transform.h>
#include <NetworkAccessManager.h>
#include <NodeList.h>
@ -1673,3 +1674,65 @@ glm::vec3 AvatarData::getAbsoluteJointTranslationInObjectFrame(int index) const
assert(false);
return glm::vec3();
}
QVariant AttachmentData::toVariant() const {
QVariantMap result;
result["modelUrl"] = modelURL;
result["jointName"] = jointName;
result["translation"] = glmToQMap(translation);
result["rotation"] = glmToQMap(glm::degrees(safeEulerAngles(rotation)));
result["scale"] = scale;
result["soft"] = isSoft;
return result;
}
glm::vec3 variantToVec3(const QVariant& var) {
auto map = var.toMap();
glm::vec3 result;
result.x = map["x"].toFloat();
result.y = map["y"].toFloat();
result.z = map["z"].toFloat();
return result;
}
void AttachmentData::fromVariant(const QVariant& variant) {
auto map = variant.toMap();
if (map.contains("modelUrl")) {
auto urlString = map["modelUrl"].toString();
modelURL = urlString;
}
if (map.contains("jointName")) {
jointName = map["jointName"].toString();
}
if (map.contains("translation")) {
translation = variantToVec3(map["translation"]);
}
if (map.contains("rotation")) {
rotation = glm::quat(glm::radians(variantToVec3(map["rotation"])));
}
if (map.contains("scale")) {
scale = map["scale"].toFloat();
}
if (map.contains("soft")) {
isSoft = map["soft"].toBool();
}
}
QVariantList AvatarData::getAttachmentsVariant() const {
QVariantList result;
for (const auto& attachment : getAttachmentData()) {
result.append(attachment.toVariant());
}
return result;
}
void AvatarData::setAttachmentsVariant(const QVariantList& variant) {
QVector<AttachmentData> newAttachments;
newAttachments.reserve(variant.size());
for (const auto& attachmentVar : variant) {
AttachmentData attachment;
attachment.fromVariant(attachmentVar);
newAttachments.append(attachment);
}
setAttachmentData(newAttachments);
}

View file

@ -277,6 +277,9 @@ public:
Q_INVOKABLE void setBlendshape(QString name, float val) { _headData->setBlendshape(name, val); }
Q_INVOKABLE QVariantList getAttachmentsVariant() const;
Q_INVOKABLE void setAttachmentsVariant(const QVariantList& variant);
void setForceFaceTrackerConnected(bool connected) { _forceFaceTrackerConnected = connected; }
// key state
@ -448,6 +451,9 @@ public:
QJsonObject toJson() const;
void fromJson(const QJsonObject& json);
QVariant toVariant() const;
void fromVariant(const QVariant& variant);
};
QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment);

View file

@ -959,7 +959,7 @@ void LimitedNodeList::putLocalPortIntoSharedMemory(const QString key, QObject* p
bool LimitedNodeList::getLocalServerPortFromSharedMemory(const QString key, quint16& localPort) {
QSharedMemory sharedMem(key);
if (!sharedMem.attach(QSharedMemory::ReadOnly)) {
qWarning() << "Could not attach to shared memory at key" << key;
qCWarning(networking) << "Could not attach to shared memory at key" << key;
return false;
} else {
sharedMem.lock();

View file

@ -7,6 +7,7 @@ import "../../../interface/resources/qml"
import "../../../interface/resources/qml/windows"
import "../../../interface/resources/qml/dialogs"
import "../../../interface/resources/qml/hifi"
import "../../../interface/resources/qml/hifi/dialogs"
ApplicationWindow {
id: appWindow
@ -196,4 +197,15 @@ ApplicationWindow {
}
*/
}
Action {
text: "Open Browser"
shortcut: "Ctrl+Shift+X"
onTriggered: {
builder.createObject(desktop);
}
property var builder: Component {
ModelBrowserDialog{}
}
}
}