mirror of
https://github.com/lubosz/overte.git
synced 2025-04-23 12:14:00 +02:00
Merge pull request #6943 from jherico/attachments
Attachment dialog in overlay
This commit is contained in:
commit
dc6662d115
20 changed files with 931 additions and 340 deletions
148
interface/resources/qml/controls/ComboBox.qml
Normal file
148
interface/resources/qml/controls/ComboBox.qml
Normal file
|
@ -0,0 +1,148 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import "." as VrControls
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
property alias model: comboBox.model;
|
||||
readonly property alias currentText: comboBox.currentText;
|
||||
property alias currentIndex: comboBox.currentIndex;
|
||||
implicitHeight: comboBox.height;
|
||||
focus: true
|
||||
|
||||
readonly property ComboBox control: comboBox
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
gradient: Gradient {
|
||||
GradientStop {color: control.pressed ? "#bababa" : "#fefefe" ; position: 0}
|
||||
GradientStop {color: control.pressed ? "#ccc" : "#e3e3e3" ; position: 1}
|
||||
}
|
||||
anchors.fill: parent
|
||||
border.color: control.activeFocus ? "#47b" : "#999"
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: control.activeFocus ? "#47b" : "white"
|
||||
opacity: control.hovered || control.activeFocus ? 0.1 : 0
|
||||
Behavior on opacity {NumberAnimation{ duration: 100 }}
|
||||
}
|
||||
}
|
||||
|
||||
SystemPalette { id: palette }
|
||||
|
||||
ComboBox {
|
||||
id: comboBox
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
}
|
||||
|
||||
Text {
|
||||
id: textField
|
||||
anchors { left: parent.left; leftMargin: 2; right: dropIcon.left; verticalCenter: parent.verticalCenter }
|
||||
text: comboBox.currentText
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dropIcon
|
||||
anchors { right: parent.right; verticalCenter: parent.verticalCenter }
|
||||
width: 20
|
||||
height: textField.height
|
||||
VrControls.FontAwesome {
|
||||
anchors.centerIn: parent; size: 16;
|
||||
text: "\uf0d7"
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: toggleList();
|
||||
}
|
||||
|
||||
function toggleList() {
|
||||
if (popup.visible) {
|
||||
hideList();
|
||||
} else {
|
||||
showList();
|
||||
}
|
||||
}
|
||||
|
||||
function showList() {
|
||||
var r = desktop.mapFromItem(root, 0, 0, root.width, root.height);
|
||||
listView.currentIndex = root.currentIndex
|
||||
scrollView.x = r.x;
|
||||
scrollView.y = r.y + r.height;
|
||||
var bottom = scrollView.y + scrollView.height;
|
||||
if (bottom > desktop.height) {
|
||||
scrollView.y -= bottom - desktop.height + 8;
|
||||
}
|
||||
popup.visible = true;
|
||||
popup.forceActiveFocus();
|
||||
}
|
||||
|
||||
function hideList() {
|
||||
popup.visible = false;
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: popup
|
||||
parent: desktop
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
focus: true
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: hideList();
|
||||
}
|
||||
|
||||
function previousItem() { listView.currentIndex = (listView.currentIndex + listView.count - 1) % listView.count; }
|
||||
function nextItem() { listView.currentIndex = (listView.currentIndex + listView.count + 1) % listView.count; }
|
||||
function selectCurrentItem() { root.currentIndex = listView.currentIndex; hideList(); }
|
||||
|
||||
Keys.onUpPressed: previousItem();
|
||||
Keys.onDownPressed: nextItem();
|
||||
Keys.onSpacePressed: selectCurrentItem();
|
||||
Keys.onRightPressed: selectCurrentItem();
|
||||
Keys.onReturnPressed: selectCurrentItem();
|
||||
Keys.onEscapePressed: hideList();
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
height: 480
|
||||
width: root.width
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
height: textView.height * count
|
||||
model: root.model
|
||||
highlight: Rectangle{
|
||||
width: listView.currentItem ? listView.currentItem.width : 0
|
||||
height: listView.currentItem ? listView.currentItem.height : 0
|
||||
color: "red"
|
||||
}
|
||||
delegate: Rectangle {
|
||||
width: root.width
|
||||
height: popupText.implicitHeight * 1.4
|
||||
color: ListView.isCurrentItem ? palette.highlight : palette.base
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
id: popupText
|
||||
x: 3
|
||||
text: listView.model[index]
|
||||
}
|
||||
MouseArea {
|
||||
id: popupHover
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: true
|
||||
onEntered: listView.currentIndex = index;
|
||||
onClicked: popup.selectCurrentItem()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
124
interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml
Normal file
124
interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml
Normal file
|
@ -0,0 +1,124 @@
|
|||
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"
|
||||
objectName: "AttachmentsDialog"
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
128
interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml
Normal file
128
interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
128
interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml
Normal file
128
interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
165
interface/resources/qml/hifi/dialogs/attachments/Attachment.qml
Normal file
165
interface/resources/qml/hifi/dialogs/attachments/Attachment.qml
Normal file
|
@ -0,0 +1,165 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import "../../../windows"
|
||||
import "../../../controls" as VrControls
|
||||
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;
|
||||
}
|
||||
VrControls.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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import "."
|
||||
|
||||
Vector3 {
|
||||
decimals: 1;
|
||||
stepSize: 1;
|
||||
maximumValue: 180
|
||||
minimumValue: -180
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import "."
|
||||
|
||||
Vector3 {
|
||||
decimals: 2;
|
||||
stepSize: 0.01;
|
||||
maximumValue: 10
|
||||
minimumValue: -10
|
||||
}
|
||||
|
71
interface/resources/qml/hifi/dialogs/attachments/Vector3.qml
Normal file
71
interface/resources/qml/hifi/dialogs/attachments/Vector3.qml
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
41
interface/resources/qml/hifi/models/S3Model.qml
Normal file
41
interface/resources/qml/hifi/models/S3Model.qml
Normal 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>"236c00c4802ba9c2605cabd5601d138e"</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()" }
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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,9 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
|
||||
case Qt::Key_X:
|
||||
if (isShifted && isMeta) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->getRootContext()->engine()->clearComponentCache();
|
||||
OffscreenUi::information("Debugging", "Component cache cleared");
|
||||
// placeholder for dialogs being converted to QML.
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -70,8 +70,8 @@ Menu::Menu() {
|
|||
}
|
||||
|
||||
// File > Update -- FIXME: needs implementation
|
||||
auto updateAction = addActionToQMenuAndActionHash(fileMenu, "Update");
|
||||
updateAction->setDisabled(true);
|
||||
auto action = addActionToQMenuAndActionHash(fileMenu, "Update");
|
||||
action->setDisabled(true);
|
||||
|
||||
// File > Help
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::Help, 0, qApp, SLOT(showHelp()));
|
||||
|
@ -166,8 +166,11 @@ Menu::Menu() {
|
|||
QObject* avatar = avatarManager->getMyAvatar();
|
||||
|
||||
// Avatar > Attachments...
|
||||
addActionToQMenuAndActionHash(avatarMenu, MenuOption::Attachments, 0,
|
||||
dialogsManager.data(), SLOT(editAttachments()));
|
||||
action = addActionToQMenuAndActionHash(avatarMenu, MenuOption::Attachments);
|
||||
connect(action, &QAction::triggered, [] {
|
||||
DependencyManager::get<OffscreenUi>()->show(QString("hifi/dialogs/AttachmentsDialog.qml"), "AttachmentsDialog");
|
||||
});
|
||||
|
||||
|
||||
// Avatar > Size
|
||||
MenuWrapper* avatarSizeMenu = avatarMenu->addMenu("Size");
|
||||
|
@ -285,7 +288,7 @@ Menu::Menu() {
|
|||
addCheckableActionToQMenuAndActionHash(settingsMenu, "Developer Menus", 0, false, this, SLOT(toggleDeveloperMenus()));
|
||||
|
||||
// Settings > General...
|
||||
auto action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_Comma, nullptr, nullptr, QAction::PreferencesRole);
|
||||
action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_Comma, nullptr, nullptr, QAction::PreferencesRole);
|
||||
connect(action, &QAction::triggered, [] {
|
||||
DependencyManager::get<OffscreenUi>()->toggle(QString("hifi/dialogs/GeneralPreferencesDialog.qml"), "GeneralPreferencesDialog");
|
||||
});
|
||||
|
|
|
@ -1,239 +0,0 @@
|
|||
//
|
||||
// AttachmentsDialog.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Andrzej Kapolka on 5/4/14.
|
||||
// Copyright 2014 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 <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFormLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
#include <QCheckBox>
|
||||
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include <avatar/MyAvatar.h>
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#include "AttachmentsDialog.h"
|
||||
#include "ModelsBrowser.h"
|
||||
|
||||
AttachmentsDialog::AttachmentsDialog(QWidget* parent) :
|
||||
QDialog(parent) {
|
||||
|
||||
setWindowTitle("Edit Attachments");
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
setLayout(layout);
|
||||
|
||||
QScrollArea* area = new QScrollArea();
|
||||
layout->addWidget(area);
|
||||
area->setWidgetResizable(true);
|
||||
QWidget* container = new QWidget();
|
||||
container->setLayout(_attachments = new QVBoxLayout());
|
||||
container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
|
||||
area->setWidget(container);
|
||||
_attachments->addStretch(1);
|
||||
|
||||
foreach (const AttachmentData& data, DependencyManager::get<AvatarManager>()->getMyAvatar()->getAttachmentData()) {
|
||||
addAttachment(data);
|
||||
}
|
||||
|
||||
QPushButton* newAttachment = new QPushButton("New Attachment");
|
||||
connect(newAttachment, SIGNAL(clicked(bool)), SLOT(addAttachment()));
|
||||
layout->addWidget(newAttachment);
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||
layout->addWidget(buttons);
|
||||
connect(buttons, SIGNAL(accepted()), SLOT(deleteLater()));
|
||||
_ok = buttons->button(QDialogButtonBox::Ok);
|
||||
|
||||
setMinimumSize(600, 600);
|
||||
}
|
||||
|
||||
void AttachmentsDialog::setVisible(bool visible) {
|
||||
QDialog::setVisible(visible);
|
||||
|
||||
// un-default the OK button
|
||||
if (visible) {
|
||||
_ok->setDefault(false);
|
||||
}
|
||||
}
|
||||
|
||||
void AttachmentsDialog::updateAttachmentData() {
|
||||
QVector<AttachmentData> data;
|
||||
for (int i = 0; i < _attachments->count() - 1; i++) {
|
||||
data.append(static_cast<AttachmentPanel*>(_attachments->itemAt(i)->widget())->getAttachmentData());
|
||||
}
|
||||
DependencyManager::get<AvatarManager>()->getMyAvatar()->setAttachmentData(data);
|
||||
}
|
||||
|
||||
void AttachmentsDialog::addAttachment(const AttachmentData& data) {
|
||||
_attachments->insertWidget(_attachments->count() - 1, new AttachmentPanel(this, data));
|
||||
}
|
||||
|
||||
static QDoubleSpinBox* createTranslationBox(AttachmentPanel* panel, float value) {
|
||||
QDoubleSpinBox* box = new QDoubleSpinBox();
|
||||
box->setSingleStep(0.01);
|
||||
box->setMinimum(-FLT_MAX);
|
||||
box->setMaximum(FLT_MAX);
|
||||
box->setValue(value);
|
||||
panel->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
|
||||
return box;
|
||||
}
|
||||
|
||||
static QDoubleSpinBox* createRotationBox(AttachmentPanel* panel, float value) {
|
||||
QDoubleSpinBox* box = new QDoubleSpinBox();
|
||||
box->setMinimum(-180.0);
|
||||
box->setMaximum(180.0);
|
||||
box->setWrapping(true);
|
||||
box->setValue(value);
|
||||
panel->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
|
||||
return box;
|
||||
}
|
||||
|
||||
AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data) :
|
||||
_dialog(dialog),
|
||||
_applying(false) {
|
||||
setFrameStyle(QFrame::StyledPanel);
|
||||
|
||||
QFormLayout* layout = new QFormLayout();
|
||||
layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||||
setLayout(layout);
|
||||
|
||||
QHBoxLayout* urlBox = new QHBoxLayout();
|
||||
layout->addRow("Model URL:", urlBox);
|
||||
urlBox->addWidget(_modelURL = new QLineEdit(data.modelURL.toString()), 1);
|
||||
_modelURL->setText(data.modelURL.toString());
|
||||
connect(_modelURL, SIGNAL(editingFinished()), SLOT(modelURLChanged()));
|
||||
QPushButton* chooseURL = new QPushButton("Choose");
|
||||
urlBox->addWidget(chooseURL);
|
||||
connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseModelURL()));
|
||||
|
||||
layout->addRow("Joint:", _jointName = new QComboBox());
|
||||
QSharedPointer<NetworkGeometry> geometry = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSkeletonModel().getGeometry();
|
||||
if (geometry && geometry->isLoaded()) {
|
||||
foreach (const FBXJoint& joint, geometry->getFBXGeometry().joints) {
|
||||
_jointName->addItem(joint.name);
|
||||
}
|
||||
}
|
||||
_jointName->setCurrentText(data.jointName);
|
||||
connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(jointNameChanged()));
|
||||
|
||||
QHBoxLayout* translationBox = new QHBoxLayout();
|
||||
translationBox->addWidget(_translationX = createTranslationBox(this, data.translation.x));
|
||||
translationBox->addWidget(_translationY = createTranslationBox(this, data.translation.y));
|
||||
translationBox->addWidget(_translationZ = createTranslationBox(this, data.translation.z));
|
||||
layout->addRow("Translation:", translationBox);
|
||||
|
||||
QHBoxLayout* rotationBox = new QHBoxLayout();
|
||||
glm::vec3 eulers = glm::degrees(safeEulerAngles(data.rotation));
|
||||
rotationBox->addWidget(_rotationX = createRotationBox(this, eulers.x));
|
||||
rotationBox->addWidget(_rotationY = createRotationBox(this, eulers.y));
|
||||
rotationBox->addWidget(_rotationZ = createRotationBox(this, eulers.z));
|
||||
layout->addRow("Rotation:", rotationBox);
|
||||
|
||||
layout->addRow("Scale:", _scale = new QDoubleSpinBox());
|
||||
_scale->setSingleStep(0.01);
|
||||
_scale->setMaximum(FLT_MAX);
|
||||
_scale->setValue(data.scale);
|
||||
connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
|
||||
|
||||
layout->addRow("Is Soft:", _isSoft = new QCheckBox());
|
||||
_isSoft->setChecked(data.isSoft);
|
||||
connect(_isSoft, SIGNAL(stateChanged(int)), SLOT(updateAttachmentData()));
|
||||
|
||||
QPushButton* remove = new QPushButton("Delete");
|
||||
layout->addRow(remove);
|
||||
connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater()));
|
||||
dialog->connect(remove, SIGNAL(clicked(bool)), SLOT(updateAttachmentData()), Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
AttachmentData AttachmentPanel::getAttachmentData() const {
|
||||
AttachmentData data;
|
||||
data.modelURL = _modelURL->text();
|
||||
data.jointName = _jointName->currentText();
|
||||
data.translation = glm::vec3(_translationX->value(), _translationY->value(), _translationZ->value());
|
||||
data.rotation = glm::quat(glm::radians(glm::vec3(_rotationX->value(), _rotationY->value(), _rotationZ->value())));
|
||||
data.scale = _scale->value();
|
||||
data.isSoft = _isSoft->isChecked();
|
||||
return data;
|
||||
}
|
||||
|
||||
void AttachmentPanel::chooseModelURL() {
|
||||
ModelsBrowser modelBrowser(FSTReader::ATTACHMENT_MODEL, this);
|
||||
connect(&modelBrowser, SIGNAL(selected(QString)), SLOT(setModelURL(const QString&)));
|
||||
modelBrowser.browse();
|
||||
}
|
||||
|
||||
void AttachmentPanel::setModelURL(const QString& url) {
|
||||
_modelURL->setText(url);
|
||||
modelURLChanged();
|
||||
}
|
||||
|
||||
void AttachmentPanel::modelURLChanged() {
|
||||
// check for saved attachment data
|
||||
if (_modelURL->text().isEmpty()) {
|
||||
_dialog->updateAttachmentData();
|
||||
return;
|
||||
}
|
||||
AttachmentData attachment = DependencyManager::get<AvatarManager>()->getMyAvatar()->loadAttachmentData(_modelURL->text());
|
||||
if (attachment.isValid()) {
|
||||
_applying = true;
|
||||
_jointName->setCurrentText(attachment.jointName);
|
||||
applyAttachmentData(attachment);
|
||||
}
|
||||
_dialog->updateAttachmentData();
|
||||
}
|
||||
|
||||
void AttachmentPanel::jointNameChanged() {
|
||||
if (_applying) {
|
||||
return;
|
||||
}
|
||||
// check for saved attachment data specific to this joint
|
||||
if (_modelURL->text().isEmpty()) {
|
||||
_dialog->updateAttachmentData();
|
||||
return;
|
||||
}
|
||||
AttachmentData attachment = DependencyManager::get<AvatarManager>()->getMyAvatar()->loadAttachmentData(
|
||||
_modelURL->text(), _jointName->currentText());
|
||||
if (attachment.isValid()) {
|
||||
applyAttachmentData(attachment);
|
||||
}
|
||||
updateAttachmentData();
|
||||
}
|
||||
|
||||
void AttachmentPanel::updateAttachmentData() {
|
||||
if (_applying) {
|
||||
return;
|
||||
}
|
||||
// save the attachment data under the model URL (if any)
|
||||
if (!_modelURL->text().isEmpty()) {
|
||||
DependencyManager::get<AvatarManager>()->getMyAvatar()->saveAttachmentData(getAttachmentData());
|
||||
}
|
||||
_dialog->updateAttachmentData();
|
||||
}
|
||||
|
||||
void AttachmentPanel::applyAttachmentData(const AttachmentData& attachment) {
|
||||
_applying = true;
|
||||
_translationX->setValue(attachment.translation.x);
|
||||
_translationY->setValue(attachment.translation.y);
|
||||
_translationZ->setValue(attachment.translation.z);
|
||||
glm::vec3 eulers = glm::degrees(safeEulerAngles(attachment.rotation));
|
||||
_rotationX->setValue(eulers.x);
|
||||
_rotationY->setValue(eulers.y);
|
||||
_rotationZ->setValue(eulers.z);
|
||||
_scale->setValue(attachment.scale);
|
||||
_isSoft->setChecked(attachment.isSoft);
|
||||
_applying = false;
|
||||
_dialog->updateAttachmentData();
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
//
|
||||
// AttachmentsDialog.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Andrzej Kapolka on 5/4/14.
|
||||
// Copyright 2014 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_AttachmentsDialog_h
|
||||
#define hifi_AttachmentsDialog_h
|
||||
|
||||
#include <QDialog>
|
||||
#include <QFrame>
|
||||
|
||||
#include <AvatarData.h>
|
||||
|
||||
class QComboBox;
|
||||
class QDoubleSpinner;
|
||||
class QLineEdit;
|
||||
class QVBoxLayout;
|
||||
|
||||
/// Allows users to edit the avatar attachments.
|
||||
class AttachmentsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AttachmentsDialog(QWidget* parent = nullptr);
|
||||
|
||||
virtual void setVisible(bool visible);
|
||||
|
||||
public slots:
|
||||
|
||||
void updateAttachmentData();
|
||||
|
||||
private slots:
|
||||
|
||||
void addAttachment(const AttachmentData& data = AttachmentData());
|
||||
|
||||
private:
|
||||
|
||||
QVBoxLayout* _attachments;
|
||||
QPushButton* _ok;
|
||||
};
|
||||
|
||||
/// A panel controlling a single attachment.
|
||||
class AttachmentPanel : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data = AttachmentData());
|
||||
|
||||
AttachmentData getAttachmentData() const;
|
||||
|
||||
private slots:
|
||||
|
||||
void chooseModelURL();
|
||||
void setModelURL(const QString& url);
|
||||
void modelURLChanged();
|
||||
void jointNameChanged();
|
||||
void updateAttachmentData();
|
||||
|
||||
private:
|
||||
|
||||
void applyAttachmentData(const AttachmentData& attachment);
|
||||
|
||||
AttachmentsDialog* _dialog;
|
||||
QLineEdit* _modelURL;
|
||||
QComboBox* _jointName;
|
||||
QDoubleSpinBox* _translationX;
|
||||
QDoubleSpinBox* _translationY;
|
||||
QDoubleSpinBox* _translationZ;
|
||||
QDoubleSpinBox* _rotationX;
|
||||
QDoubleSpinBox* _rotationY;
|
||||
QDoubleSpinBox* _rotationZ;
|
||||
QDoubleSpinBox* _scale;
|
||||
QCheckBox* _isSoft;
|
||||
bool _applying;
|
||||
};
|
||||
|
||||
#endif // hifi_AttachmentsDialog_h
|
|
@ -19,7 +19,6 @@
|
|||
#include <PathUtils.h>
|
||||
|
||||
#include "AddressBarDialog.h"
|
||||
#include "AttachmentsDialog.h"
|
||||
#include "BandwidthDialog.h"
|
||||
#include "CachesSizeDialog.h"
|
||||
#include "DiskCacheEditor.h"
|
||||
|
@ -91,15 +90,6 @@ void DialogsManager::cachesSizeDialog() {
|
|||
_cachesSizeDialog->raise();
|
||||
}
|
||||
|
||||
void DialogsManager::editAttachments() {
|
||||
if (!_attachmentsDialog) {
|
||||
maybeCreateDialog(_attachmentsDialog);
|
||||
_attachmentsDialog->show();
|
||||
} else {
|
||||
_attachmentsDialog->close();
|
||||
}
|
||||
}
|
||||
|
||||
void DialogsManager::audioStatsDetails() {
|
||||
if (! _audioStatsDialog) {
|
||||
_audioStatsDialog = new AudioStatsDialog(qApp->getWindow());
|
||||
|
|
|
@ -48,7 +48,6 @@ public slots:
|
|||
void showLoginDialog();
|
||||
void octreeStatsDetails();
|
||||
void cachesSizeDialog();
|
||||
void editAttachments();
|
||||
void audioStatsDetails();
|
||||
void bandwidthDetails();
|
||||
void lodTools();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue