History

Attachment dialog

Brad Davis committed 10 years ago

Commit 664100b1 parent 881a94c14 files · +774 −1Browse files

Unified · Split

14 files
  1. interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml +123 −0
  2. interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml +128 −0
  3. interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml +128 −0
  4. interface/resources/qml/hifi/dialogs/attachments/Attachment.qml +164 −0
  5. interface/resources/qml/hifi/dialogs/attachments/Rotation.qml +9 −0
  6. interface/resources/qml/hifi/dialogs/attachments/Translation.qml +9 −0
  7. interface/resources/qml/hifi/dialogs/attachments/Vector3.qml +71 −0
  8. interface/resources/qml/hifi/models/S3Model.qml +41 −0
  9. interface/resources/qml/js/Utils.js +14 −0
  10. interface/src/Application.cpp +5 −0
  11. libraries/avatars/src/AvatarData.cpp +63 −0
  12. libraries/avatars/src/AvatarData.h +6 −0
  13. libraries/networking/src/LimitedNodeList.cpp +1 −1
  14. tests/ui/qml/main.qml +12 −0
interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml+123−0
@@ -0,0 +1,123 @@
1import QtQuick 2.5
2import QtQuick.Controls 1.4
3import Qt.labs.settings 1.0
4
5import "../../windows"
6import "attachments"
7
8Window {
9 id: root
10 title: "Edit Attachments"
11 width: 600
12 height: 600
13 resizable: true
14 // User must click OK or cancel to close the window
15 closable: false
16
17 readonly property var originalAttachments: MyAvatar.getAttachmentsVariant();
18 property var attachments: [];
19
20 property var settings: Settings {
21 category: "AttachmentsDialog"
22 property alias x: root.x
23 property alias y: root.y
24 property alias width: root.width
25 property alias height: root.height
26 }
27
28 Component.onCompleted: {
29 for (var i = 0; i < originalAttachments.length; ++i) {
30 var attachment = originalAttachments[i];
31 root.attachments.push(attachment);
32 listView.model.append({});
33 }
34 }
35
36 Rectangle {
37 anchors.fill: parent
38 radius: 4
39
40 Rectangle {
41 id: attachmentsBackground
42 anchors { left: parent.left; right: parent.right; top: parent.top; bottom: newAttachmentButton.top; margins: 8 }
43 color: "gray"
44 radius: 4
45
46 ScrollView{
47 id: scrollView
48 anchors.fill: parent
49 anchors.margins: 4
50 ListView {
51 id: listView
52 model: ListModel {}
53 delegate: Item {
54 implicitHeight: attachmentView.height + 8;
55 implicitWidth: attachmentView.width;
56 Attachment {
57 id: attachmentView
58 width: scrollView.width
59 attachment: root.attachments[index]
60 onDeleteAttachment: {
61 attachments.splice(index, 1);
62 listView.model.remove(index, 1);
63 }
64 onUpdateAttachment: MyAvatar.setAttachmentsVariant(attachments);
65 }
66 }
67 onCountChanged: MyAvatar.setAttachmentsVariant(attachments);
68 }
69 }
70 }
71
72 Button {
73 id: newAttachmentButton
74 anchors { left: parent.left; right: parent.right; bottom: buttonRow.top; margins: 8 }
75 text: "New Attachment"
76
77 onClicked: {
78 var template = {
79 modelUrl: "",
80 translation: { x: 0, y: 0, z: 0 },
81 rotation: { x: 0, y: 0, z: 0 },
82 scale: 1,
83 jointName: MyAvatar.jointNames[0],
84 soft: false
85 };
86 attachments.push(template);
87 listView.model.append({});
88 MyAvatar.setAttachmentsVariant(attachments);
89 }
90 }
91
92 Row {
93 id: buttonRow
94 spacing: 8
95 anchors { right: parent.right; bottom: parent.bottom; margins: 8 }
96 Button { action: cancelAction }
97 Button { action: okAction }
98 }
99
100 Action {
101 id: cancelAction
102 text: "Cancel"
103 onTriggered: {
104 MyAvatar.setAttachmentsVariant(originalAttachments);
105 root.destroy()
106 }
107 }
108
109 Action {
110 id: okAction
111 text: "OK"
112 onTriggered: {
113 for (var i = 0; i < attachments.length; ++i) {
114 console.log("Attachment " + i + ": " + attachments[i]);
115 }
116
117 MyAvatar.setAttachmentsVariant(attachments);
118 root.destroy()
119 }
120 }
121 }
122}
123
interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml+128−0
@@ -0,0 +1,128 @@
1import QtQuick 2.5
2import QtQuick.Controls 1.4
3import QtQuick.XmlListModel 2.0
4
5import "../../windows"
6import "../../js/Utils.js" as Utils
7import "../models"
8
9ModalWindow {
10 id: root
11 resizable: true
12 width: 640
13 height: 480
14
15 property var result;
16
17 signal selected(var modelUrl);
18 signal canceled();
19
20 Rectangle {
21 anchors.fill: parent
22 color: "white"
23
24 Item {
25 anchors { fill: parent; margins: 8 }
26
27 TextField {
28 id: filterEdit
29 anchors { left: parent.left; right: parent.right; top: parent.top }
30 placeholderText: "filter"
31 onTextChanged: tableView.model.filter = text
32 }
33
34 TableView {
35 id: tableView
36 anchors { left: parent.left; right: parent.right; top: filterEdit.bottom; topMargin: 8; bottom: buttonRow.top; bottomMargin: 8 }
37 model: S3Model{}
38 onCurrentRowChanged: {
39 if (currentRow == -1) {
40 root.result = null;
41 return;
42 }
43 result = model.baseUrl + "/" + model.get(tableView.currentRow).key;
44 }
45 itemDelegate: Component {
46 Item {
47 clip: true
48 Text {
49 x: 3
50 anchors.verticalCenter: parent.verticalCenter
51 color: tableView.activeFocus && styleData.row === tableView.currentRow ? "yellow" : styleData.textColor
52 elide: styleData.elideMode
53 text: getText()
54
55 function getText() {
56 switch(styleData.column) {
57 case 1:
58 return Utils.formatSize(styleData.value)
59 default:
60 return styleData.value;
61 }
62 }
63
64 }
65 }
66 }
67 TableViewColumn {
68 role: "name"
69 title: "Name"
70 width: 200
71 }
72 TableViewColumn {
73 role: "size"
74 title: "Size"
75 width: 100
76 }
77 TableViewColumn {
78 role: "modified"
79 title: "Last Modified"
80 width: 200
81 }
82 Rectangle {
83 anchors.fill: parent
84 visible: tableView.model.status !== XmlListModel.Ready
85 color: "#7fffffff"
86 BusyIndicator {
87 anchors.centerIn: parent
88 width: 48; height: 48
89 running: true
90 }
91 }
92 }
93
94 Row {
95 id: buttonRow
96 anchors { right: parent.right; bottom: parent.bottom }
97 Button { action: acceptAction }
98 Button { action: cancelAction }
99 }
100
101 Action {
102 id: acceptAction
103 text: qsTr("OK")
104 enabled: root.result ? true : false
105 shortcut: Qt.Key_Return
106 onTriggered: {
107 root.selected(root.result);
108 root.destroy();
109 }
110 }
111
112 Action {
113 id: cancelAction
114 text: qsTr("Cancel")
115 shortcut: Qt.Key_Escape
116 onTriggered: {
117 root.canceled();
118 root.destroy();
119 }
120 }
121 }
122
123 }
124}
125
126
127
128
interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml+128−0
@@ -0,0 +1,128 @@
1import QtQuick 2.5
2import QtQuick.Controls 1.4
3import QtQuick.XmlListModel 2.0
4
5import "../../windows"
6import "../../js/Utils.js" as Utils
7import "../models"
8
9ModalWindow {
10 id: root
11 resizable: true
12 width: 640
13 height: 480
14
15 property var result;
16
17 signal selected(var modelUrl);
18 signal canceled();
19
20 Rectangle {
21 anchors.fill: parent
22 color: "white"
23
24 Item {
25 anchors { fill: parent; margins: 8 }
26
27 TextField {
28 id: filterEdit
29 anchors { left: parent.left; right: parent.right; top: parent.top }
30 placeholderText: "filter"
31 onTextChanged: tableView.model.filter = text
32 }
33
34 TableView {
35 id: tableView
36 anchors { left: parent.left; right: parent.right; top: filterEdit.bottom; topMargin: 8; bottom: buttonRow.top; bottomMargin: 8 }
37 model: S3Model{}
38 onCurrentRowChanged: {
39 if (currentRow == -1) {
40 root.result = null;
41 return;
42 }
43 result = model.baseUrl + "/" + model.get(tableView.currentRow).key;
44 }
45 itemDelegate: Component {
46 Item {
47 clip: true
48 Text {
49 x: 3
50 anchors.verticalCenter: parent.verticalCenter
51 color: tableView.activeFocus && styleData.row === tableView.currentRow ? "yellow" : styleData.textColor
52 elide: styleData.elideMode
53 text: getText()
54
55 function getText() {
56 switch(styleData.column) {
57 case 1:
58 return Utils.formatSize(styleData.value)
59 default:
60 return styleData.value;
61 }
62 }
63
64 }
65 }
66 }
67 TableViewColumn {
68 role: "name"
69 title: "Name"
70 width: 200
71 }
72 TableViewColumn {
73 role: "size"
74 title: "Size"
75 width: 100
76 }
77 TableViewColumn {
78 role: "modified"
79 title: "Last Modified"
80 width: 200
81 }
82 Rectangle {
83 anchors.fill: parent
84 visible: tableView.model.status !== XmlListModel.Ready
85 color: "#7fffffff"
86 BusyIndicator {
87 anchors.centerIn: parent
88 width: 48; height: 48
89 running: true
90 }
91 }
92 }
93
94 Row {
95 id: buttonRow
96 anchors { right: parent.right; bottom: parent.bottom }
97 Button { action: acceptAction }
98 Button { action: cancelAction }
99 }
100
101 Action {
102 id: acceptAction
103 text: qsTr("OK")
104 enabled: root.result ? true : false
105 shortcut: Qt.Key_Return
106 onTriggered: {
107 root.selected(root.result);
108 root.destroy();
109 }
110 }
111
112 Action {
113 id: cancelAction
114 text: qsTr("Cancel")
115 shortcut: Qt.Key_Escape
116 onTriggered: {
117 root.canceled();
118 root.destroy();
119 }
120 }
121 }
122
123 }
124}
125
126
127
128
interface/resources/qml/hifi/dialogs/attachments/Attachment.qml+164−0
@@ -0,0 +1,164 @@
1import QtQuick 2.5
2import QtQuick.Controls 1.4
3
4import "../../../windows"
5import "."
6import ".."
7
8Item {
9 height: column.height + 2 * 8
10
11 property var attachment;
12
13 signal deleteAttachment(var attachment);
14 signal updateAttachment();
15 property bool completed: false;
16
17 Rectangle { color: "white"; anchors.fill: parent; radius: 4 }
18
19 Component.onCompleted: {
20 completed = true;
21 }
22
23 Column {
24 y: 8
25 id: column
26 anchors { left: parent.left; right: parent.right; margins: 8 }
27 spacing: 8
28
29 Item {
30 height: modelChooserButton.height
31 anchors { left: parent.left; right: parent.right; }
32 Text { id: urlLabel; text: "Model URL:"; width: 80; anchors.verticalCenter: modelUrl.verticalCenter }
33 TextField {
34 id: modelUrl;
35 height: jointChooser.height;
36 anchors { left: urlLabel.right; leftMargin: 8; rightMargin: 8; right: modelChooserButton.left }
37 text: attachment ? attachment.modelUrl : ""
38 onTextChanged: {
39 if (completed && attachment && attachment.modelUrl !== text) {
40 attachment.modelUrl = text;
41 updateAttachment();
42 }
43 }
44 }
45 Button {
46 id: modelChooserButton;
47 text: "Choose";
48 anchors { right: parent.right; verticalCenter: modelUrl.verticalCenter }
49 Component {
50 id: modelBrowserBuiler;
51 ModelBrowserDialog {}
52 }
53
54 onClicked: {
55 var browser = modelBrowserBuiler.createObject(desktop);
56 browser.selected.connect(function(newModelUrl){
57 modelUrl.text = newModelUrl;
58 })
59 }
60 }
61 }
62
63 Item {
64 height: jointChooser.height
65 anchors { left: parent.left; right: parent.right; }
66 Text {
67 id: jointLabel;
68 width: 80;
69 text: "Joint:";
70 anchors.verticalCenter: jointChooser.verticalCenter;
71 }
72 ComboBox {
73 id: jointChooser;
74 anchors { left: jointLabel.right; leftMargin: 8; right: parent.right }
75 model: MyAvatar.jointNames
76 currentIndex: attachment ? model.indexOf(attachment.jointName) : -1
77 onCurrentIndexChanged: {
78 if (completed && attachment && currentIndex != -1 && currentText && currentText !== attachment.jointName) {
79 attachment.jointName = currentText;
80 updateAttachment();
81 }
82 }
83 }
84 }
85
86 Item {
87 height: translation.height
88 anchors { left: parent.left; right: parent.right; }
89 Text { id: translationLabel; width: 80; text: "Translation:"; anchors.verticalCenter: translation.verticalCenter; }
90 Translation {
91 id: translation;
92 anchors { left: translationLabel.right; leftMargin: 8; right: parent.right }
93 vector: attachment ? attachment.translation : {x: 0, y: 0, z: 0};
94 onValueChanged: {
95 if (completed && attachment) {
96 attachment.translation = vector;
97 updateAttachment();
98 }
99 }
100 }
101 }
102
103 Item {
104 height: rotation.height
105 anchors { left: parent.left; right: parent.right; }
106 Text { id: rotationLabel; width: 80; text: "Rotation:"; anchors.verticalCenter: rotation.verticalCenter; }
107 Rotation {
108 id: rotation;
109 anchors { left: rotationLabel.right; leftMargin: 8; right: parent.right }
110 vector: attachment ? attachment.rotation : {x: 0, y: 0, z: 0};
111 onValueChanged: {
112 if (completed && attachment) {
113 attachment.rotation = vector;
114 updateAttachment();
115 }
116 }
117 }
118 }
119
120 Item {
121 height: scaleSpinner.height
122 anchors { left: parent.left; right: parent.right; }
123 Text { id: scaleLabel; width: 80; text: "Scale:"; anchors.verticalCenter: scale.verticalCenter; }
124 SpinBox {
125 id: scaleSpinner;
126 anchors { left: scaleLabel.right; leftMargin: 8; right: parent.right }
127 decimals: 1;
128 minimumValue: 0.1
129 maximumValue: 10
130 stepSize: 0.1;
131 value: attachment ? attachment.scale : 1.0
132 onValueChanged: {
133 if (completed && attachment && attachment.scale !== value) {
134 attachment.scale = value;
135 updateAttachment();
136 }
137 }
138 }
139 }
140
141 Item {
142 height: soft.height
143 anchors { left: parent.left; right: parent.right; }
144 Text { id: softLabel; width: 80; text: "Is soft:"; anchors.verticalCenter: soft.verticalCenter; }
145 CheckBox {
146 id: soft;
147 anchors { left: softLabel.right; leftMargin: 8; right: parent.right }
148 checked: attachment ? attachment.soft : false
149 onCheckedChanged: {
150 if (completed && attachment && attachment.soft !== checked) {
151 attachment.soft = checked;
152 updateAttachment();
153 }
154 }
155 }
156 }
157
158 Button {
159 anchors { left: parent.left; right: parent.right; }
160 text: "Delete"
161 onClicked: deleteAttachment(root.attachment);
162 }
163 }
164}
interface/resources/qml/hifi/dialogs/attachments/Rotation.qml+9−0
@@ -0,0 +1,9 @@
1import "."
2
3Vector3 {
4 decimals: 1;
5 stepSize: 1;
6 maximumValue: 180
7 minimumValue: -180
8}
9
interface/resources/qml/hifi/dialogs/attachments/Translation.qml+9−0
@@ -0,0 +1,9 @@
1import "."
2
3Vector3 {
4 decimals: 2;
5 stepSize: 0.01;
6 maximumValue: 10
7 minimumValue: -10
8}
9
interface/resources/qml/hifi/dialogs/attachments/Vector3.qml+71−0
@@ -0,0 +1,71 @@
1import QtQuick 2.5
2import QtQuick.Controls 1.4
3
4Item {
5 id: root
6 implicitHeight: xspinner.height
7 readonly property real spacing: 8
8 property real spinboxWidth: (width / 3) - spacing
9 property var vector;
10 property real decimals: 0
11 property real stepSize: 1
12 property real maximumValue: 99
13 property real minimumValue: 0
14
15 signal valueChanged();
16
17 SpinBox {
18 id: xspinner
19 width: root.spinboxWidth
20 anchors { left: parent.left }
21 value: root.vector.x
22
23 decimals: root.decimals
24 stepSize: root.stepSize
25 maximumValue: root.maximumValue
26 minimumValue: root.minimumValue
27 onValueChanged: {
28 if (value !== vector.x) {
29 vector.x = value
30 root.valueChanged();
31 }
32 }
33 }
34
35 SpinBox {
36 id: yspinner
37 width: root.spinboxWidth
38 anchors { horizontalCenter: parent.horizontalCenter }
39 value: root.vector.y
40
41 decimals: root.decimals
42 stepSize: root.stepSize
43 maximumValue: root.maximumValue
44 minimumValue: root.minimumValue
45 onValueChanged: {
46 if (value !== vector.y) {
47 vector.y = value
48 root.valueChanged();
49 }
50 }
51 }
52
53 SpinBox {
54 id: zspinner
55 width: root.spinboxWidth
56 anchors { right: parent.right; }
57 value: root.vector.z
58
59 decimals: root.decimals
60 stepSize: root.stepSize
61 maximumValue: root.maximumValue
62 minimumValue: root.minimumValue
63 onValueChanged: {
64 if (value !== vector.z) {
65 vector.z = value
66 root.valueChanged();
67 }
68 }
69 }
70}
71
interface/resources/qml/hifi/models/S3Model.qml+41−0
@@ -0,0 +1,41 @@
1import QtQuick 2.0
2import QtQuick.XmlListModel 2.0
3
4//http://s3.amazonaws.com/hifi-public?prefix=models/attachments
5/*
6<?xml version="1.0" encoding="UTF-8"?>
7<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
8 <Contents>
9 <Key>models/attachments/guitar.fst</Key>
10 <LastModified>2015-11-10T00:28:22.000Z</LastModified>
11 <ETag>&quot;236c00c4802ba9c2605cabd5601d138e&quot;</ETag>
12 <Size>2992</Size>
13 <StorageClass>STANDARD</StorageClass>
14 </Contents>
15</ListBucketResult>
16*/
17
18// FIXME how to deal with truncated results? Store the marker?
19XmlListModel {
20 id: xmlModel
21 property string prefix: "models/attachments/"
22 property string extension: "fst"
23 property string filter;
24
25 readonly property string realPrefix: prefix.match('.*/$') ? prefix : (prefix + "/")
26 readonly property string nameRegex: realPrefix + (filter ? (".*" + filter) : "") + ".*\." + extension
27 readonly property string nameQuery: "Key/substring-before(substring-after(string(), '" + prefix + "'), '." + extension + "')"
28 readonly property string baseUrl: "http://s3.amazonaws.com/hifi-public"
29
30 // FIXME need to urlencode prefix?
31 source: baseUrl + "?prefix=" + realPrefix
32 query: "/ListBucketResult/Contents[matches(Key, '" + nameRegex + "')]"
33 namespaceDeclarations: "declare default element namespace 'http://s3.amazonaws.com/doc/2006-03-01/';"
34
35 XmlRole { name: "name"; query: nameQuery }
36 XmlRole { name: "size"; query: "Size/string()" }
37 XmlRole { name: "tag"; query: "ETag/string()" }
38 XmlRole { name: "modified"; query: "LastModified/string()" }
39 XmlRole { name: "key"; query: "Key/string()" }
40}
41
interface/resources/qml/js/Utils.js+14−0
@@ -14,3 +14,17 @@ function randomPosition(min, max) { · 13 unmodified lines
14 Math.random() * (max.x - min.x),14 Math.random() * (max.x - min.x),
15 Math.random() * (max.y - min.y));15 Math.random() * (max.y - min.y));
16}16}
17
18function formatSize(size) {
19 var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ];
20 var suffixIndex = 0
21 while ((size / 1024.0) > 1.1) {
22 size /= 1024.0;
23 ++suffixIndex;
24 }
25
26 size = Math.round(size*1000)/1000;
27 size = size.toLocaleString()
28
29 return size + " " + suffixes[suffixIndex];
30}
interface/src/Application.cpp+5−0
@@ -1186,6 +1186,7 @@ void Application::initializeUi() { · 1185 unmodified lines
1186 UpdateDialog::registerType();1186 UpdateDialog::registerType();
1187 qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");1187 qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");
1188 1188
1189
1189 auto offscreenUi = DependencyManager::get<OffscreenUi>();1190 auto offscreenUi = DependencyManager::get<OffscreenUi>();
1190 offscreenUi->create(_offscreenContext->getContext());1191 offscreenUi->create(_offscreenContext->getContext());
1191 offscreenUi->setProxyWindow(_window->windowHandle());1192 offscreenUi->setProxyWindow(_window->windowHandle());
@@ -1842,6 +1843,10 @@ void Application::keyPressEvent(QKeyEvent* event) { · 650 unmodified lines
1842 1843
1843 case Qt::Key_X:1844 case Qt::Key_X:
1844 if (isShifted && isMeta) {1845 if (isShifted && isMeta) {
1846 auto offscreenUi = DependencyManager::get<OffscreenUi>();
1847 offscreenUi->getRootContext()->engine()->clearComponentCache();
1848 offscreenUi->load("hifi/dialogs/AttachmentsDialog.qml");
1849 // OffscreenUi::information("Debugging", "Component cache cleared");
1845 // placeholder for dialogs being converted to QML.1850 // placeholder for dialogs being converted to QML.
1846 }1851 }
1847 break;1852 break;
libraries/avatars/src/AvatarData.cpp+63−0
@@ -25,6 +25,7 @@ · 24 unmodified lines
25#include <QtNetwork/QNetworkReply>25#include <QtNetwork/QNetworkReply>
26#include <QtNetwork/QNetworkRequest>26#include <QtNetwork/QNetworkRequest>
27 27
28#include <QVariantGLM.h>
28#include <Transform.h>29#include <Transform.h>
29#include <NetworkAccessManager.h>30#include <NetworkAccessManager.h>
30#include <NodeList.h>31#include <NodeList.h>
@@ -1673,3 +1674,65 @@ glm::vec3 AvatarData::getAbsoluteJointTranslationInObjectFrame(int index) const · 1642 unmodified lines
1673 assert(false);1674 assert(false);
1674 return glm::vec3();1675 return glm::vec3();
1675}1676}
1677
1678QVariant AttachmentData::toVariant() const {
1679 QVariantMap result;
1680 result["modelUrl"] = modelURL;
1681 result["jointName"] = jointName;
1682 result["translation"] = glmToQMap(translation);
1683 result["rotation"] = glmToQMap(glm::degrees(safeEulerAngles(rotation)));
1684 result["scale"] = scale;
1685 result["soft"] = isSoft;
1686 return result;
1687}
1688
1689glm::vec3 variantToVec3(const QVariant& var) {
1690 auto map = var.toMap();
1691 glm::vec3 result;
1692 result.x = map["x"].toFloat();
1693 result.y = map["y"].toFloat();
1694 result.z = map["z"].toFloat();
1695 return result;
1696}
1697
1698void AttachmentData::fromVariant(const QVariant& variant) {
1699 auto map = variant.toMap();
1700 if (map.contains("modelUrl")) {
1701 auto urlString = map["modelUrl"].toString();
1702 modelURL = urlString;
1703 }
1704 if (map.contains("jointName")) {
1705 jointName = map["jointName"].toString();
1706 }
1707 if (map.contains("translation")) {
1708 translation = variantToVec3(map["translation"]);
1709 }
1710 if (map.contains("rotation")) {
1711 rotation = glm::quat(glm::radians(variantToVec3(map["rotation"])));
1712 }
1713 if (map.contains("scale")) {
1714 scale = map["scale"].toFloat();
1715 }
1716 if (map.contains("soft")) {
1717 isSoft = map["soft"].toBool();
1718 }
1719}
1720
1721QVariantList AvatarData::getAttachmentsVariant() const {
1722 QVariantList result;
1723 for (const auto& attachment : getAttachmentData()) {
1724 result.append(attachment.toVariant());
1725 }
1726 return result;
1727}
1728
1729void AvatarData::setAttachmentsVariant(const QVariantList& variant) {
1730 QVector<AttachmentData> newAttachments;
1731 newAttachments.reserve(variant.size());
1732 for (const auto& attachmentVar : variant) {
1733 AttachmentData attachment;
1734 attachment.fromVariant(attachmentVar);
1735 newAttachments.append(attachment);
1736 }
1737 setAttachmentData(newAttachments);
1738}
libraries/avatars/src/AvatarData.h+6−0
@@ -277,6 +277,9 @@ public: · 276 unmodified lines
277 277
278 Q_INVOKABLE void setBlendshape(QString name, float val) { _headData->setBlendshape(name, val); }278 Q_INVOKABLE void setBlendshape(QString name, float val) { _headData->setBlendshape(name, val); }
279 279
280 Q_INVOKABLE QVariantList getAttachmentsVariant() const;
281 Q_INVOKABLE void setAttachmentsVariant(const QVariantList& variant);
282
280 void setForceFaceTrackerConnected(bool connected) { _forceFaceTrackerConnected = connected; }283 void setForceFaceTrackerConnected(bool connected) { _forceFaceTrackerConnected = connected; }
281 284
282 // key state285 // key state
@@ -448,6 +451,9 @@ public: · 165 unmodified lines
448 451
449 QJsonObject toJson() const;452 QJsonObject toJson() const;
450 void fromJson(const QJsonObject& json);453 void fromJson(const QJsonObject& json);
454
455 QVariant toVariant() const;
456 void fromVariant(const QVariant& variant);
451};457};
452 458
453QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment);459QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment);
libraries/networking/src/LimitedNodeList.cpp+1−1
@@ -959,7 +959,7 @@ void LimitedNodeList::putLocalPortIntoSharedMemory(const QString key, QObject* p · 958 unmodified lines
959bool LimitedNodeList::getLocalServerPortFromSharedMemory(const QString key, quint16& localPort) {959bool LimitedNodeList::getLocalServerPortFromSharedMemory(const QString key, quint16& localPort) {
960 QSharedMemory sharedMem(key);960 QSharedMemory sharedMem(key);
961 if (!sharedMem.attach(QSharedMemory::ReadOnly)) {961 if (!sharedMem.attach(QSharedMemory::ReadOnly)) {
962 qWarning() << "Could not attach to shared memory at key" << key;962 qCWarning(networking) << "Could not attach to shared memory at key" << key;
963 return false;963 return false;
964 } else {964 } else {
965 sharedMem.lock();965 sharedMem.lock();
tests/ui/qml/main.qml+12−0
@@ -7,6 +7,7 @@ import "../../../interface/resources/qml" · 6 unmodified lines
7import "../../../interface/resources/qml/windows"7import "../../../interface/resources/qml/windows"
8import "../../../interface/resources/qml/dialogs"8import "../../../interface/resources/qml/dialogs"
9import "../../../interface/resources/qml/hifi"9import "../../../interface/resources/qml/hifi"
10import "../../../interface/resources/qml/hifi/dialogs"
10 11
11ApplicationWindow {12ApplicationWindow {
12 id: appWindow13 id: appWindow
@@ -196,4 +197,15 @@ ApplicationWindow { · 183 unmodified lines
196 }197 }
197 */198 */
198 }199 }
200
201 Action {
202 text: "Open Browser"
203 shortcut: "Ctrl+Shift+X"
204 onTriggered: {
205 builder.createObject(desktop);
206 }
207 property var builder: Component {
208 ModelBrowserDialog{}
209 }
210 }
199}211}