From 075bcba49f2fb88b542e2d5e688759a89093231c Mon Sep 17 00:00:00 2001 From: vladest Date: Tue, 9 Jan 2018 23:48:40 +0100 Subject: [PATCH 001/116] AddressBar trigger implemented. Fixed warnings on non existing context properties. Fixed warnings about non notifyable property --- interface/src/Menu.cpp | 2 +- interface/src/ui/AddressBarDialog.h | 2 +- interface/src/ui/DialogsManager.cpp | 22 ++++++++++++++++++++-- interface/src/ui/DialogsManager.h | 4 +++- interface/src/ui/overlays/Web3DOverlay.cpp | 7 +++++++ 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index d295e96867..f7f9e5480a 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -278,7 +278,7 @@ Menu::Menu() { // Navigate > Show Address Bar addActionToQMenuAndActionHash(navigateMenu, MenuOption::AddressBar, Qt::CTRL | Qt::Key_L, - dialogsManager.data(), SLOT(showAddressBar())); + dialogsManager.data(), SLOT(toggleAddressBar())); // Navigate > LocationBookmarks related menus -- Note: the LocationBookmarks class adds its own submenus here. auto locationBookmarks = DependencyManager::get(); diff --git a/interface/src/ui/AddressBarDialog.h b/interface/src/ui/AddressBarDialog.h index 66f208ca90..7806436774 100644 --- a/interface/src/ui/AddressBarDialog.h +++ b/interface/src/ui/AddressBarDialog.h @@ -22,7 +22,7 @@ class AddressBarDialog : public OffscreenQmlDialog { Q_PROPERTY(bool backEnabled READ backEnabled NOTIFY backEnabledChanged) Q_PROPERTY(bool forwardEnabled READ forwardEnabled NOTIFY forwardEnabledChanged) Q_PROPERTY(bool useFeed READ useFeed WRITE setUseFeed NOTIFY useFeedChanged) - Q_PROPERTY(QString metaverseServerUrl READ metaverseServerUrl) + Q_PROPERTY(QString metaverseServerUrl READ metaverseServerUrl CONSTANT) public: AddressBarDialog(QQuickItem* parent = nullptr); diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index ff2d4868df..310a4cc1cd 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -58,7 +58,7 @@ void DialogsManager::showAddressBar() { hmd->openTablet(); } qApp->setKeyboardFocusOverlay(hmd->getCurrentTabletScreenID()); - emit addressBarShown(true); + setAddressBarVisible(true); } void DialogsManager::hideAddressBar() { @@ -71,7 +71,7 @@ void DialogsManager::hideAddressBar() { hmd->closeTablet(); } qApp->setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID); - emit addressBarShown(false); + setAddressBarVisible(false); } void DialogsManager::showFeed() { @@ -157,6 +157,24 @@ void DialogsManager::hmdToolsClosed() { } } +void DialogsManager::toggleAddressBar() { + auto tabletScriptingInterface = DependencyManager::get(); + auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); + + const bool addressBarLoaded = tablet->isPathLoaded(TABLET_ADDRESS_DIALOG); + + if (_addressBarVisible || addressBarLoaded) { + hideAddressBar(); + } else { + showAddressBar(); + } +} + +void DialogsManager::setAddressBarVisible(bool addressBarVisible) { + _addressBarVisible = addressBarVisible; + emit addressBarShown(_addressBarVisible); +} + void DialogsManager::showTestingResults() { if (!_testingDialog) { _testingDialog = new TestingDialog(qApp->getWindow()); diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index 3a40b15a3b..f17ac39a7e 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -39,6 +39,7 @@ public: QPointer getOctreeStatsDialog() const { return _octreeStatsDialog; } QPointer getTestingDialog() const { return _testingDialog; } void emitAddressBarShown(bool visible) { emit addressBarShown(visible); } + void setAddressBarVisible(bool addressBarVisible); public slots: void showAddressBar(); @@ -52,6 +53,7 @@ public slots: void hmdTools(bool showTools); void showDomainConnectionDialog(); void showTestingResults(); + void toggleAddressBar(); // Application Update void showUpdateDialog(); @@ -78,7 +80,7 @@ private: QPointer _octreeStatsDialog; QPointer _testingDialog; QPointer _domainConnectionDialog; - bool _closeAddressBar { false }; + bool _addressBarVisible { false }; }; #endif // hifi_DialogsManager_h diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index a5da5e99b6..cdf09ffe19 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -72,6 +72,13 @@ Web3DOverlay::Web3DOverlay() { connect(this, &Web3DOverlay::requestWebSurface, this, &Web3DOverlay::buildWebSurface); connect(this, &Web3DOverlay::releaseWebSurface, this, &Web3DOverlay::destroyWebSurface); connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface); + + //need to be intialized before Tablet 1st open + _webSurface = DependencyManager::get()->acquire(_url); + _webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get().data()); + _webSurface->getSurfaceContext()->setContextProperty("Account", GlobalServicesScriptingInterface::getInstance()); + _webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get().data()); + } Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) : From 24f1fc35460313449e37b0794af30b71c34e7cc3 Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Wed, 10 Jan 2018 22:30:43 +0000 Subject: [PATCH 002/116] fix Ui keyboard navigation various fixes to allow some support for keyboard navigation in message boxes and dialogs --- .../resources/qml/controls-uit/Button.qml | 4 +- .../resources/qml/dialogs/FileDialog.qml | 2 +- .../resources/qml/dialogs/MessageDialog.qml | 42 ++++++++++++++----- .../resources/qml/dialogs/QueryDialog.qml | 38 +++++++++++++---- .../messageDialog/MessageDialogButton.qml | 17 ++++++-- .../scripting/WindowScriptingInterface.cpp | 6 +-- 6 files changed, 83 insertions(+), 26 deletions(-) diff --git a/interface/resources/qml/controls-uit/Button.qml b/interface/resources/qml/controls-uit/Button.qml index 02c6181952..dde07f45ea 100644 --- a/interface/resources/qml/controls-uit/Button.qml +++ b/interface/resources/qml/controls-uit/Button.qml @@ -57,7 +57,7 @@ Original.Button { hifi.buttons.disabledColorStart[control.colorScheme] } else if (control.pressed) { hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { + } else if (control.hovered || control.focus) { hifi.buttons.hoveredColor[control.color] } else { hifi.buttons.colorStart[control.color] @@ -71,7 +71,7 @@ Original.Button { hifi.buttons.disabledColorFinish[control.colorScheme] } else if (control.pressed) { hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { + } else if (control.hovered || control.focus) { hifi.buttons.hoveredColor[control.color] } else { hifi.buttons.colorFinish[control.color] diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index b9633104d5..581ac37a74 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -704,7 +704,7 @@ ModalWindow { KeyNavigation.up: selectionType KeyNavigation.left: openButton KeyNavigation.right: fileTableView.contentItem - Keys.onReturnPressed: { canceled(); root.enabled = false } + Keys.onReturnPressed: { cancelAction.trigger() } } } diff --git a/interface/resources/qml/dialogs/MessageDialog.qml b/interface/resources/qml/dialogs/MessageDialog.qml index 40c5a01e15..79d4b009db 100644 --- a/interface/resources/qml/dialogs/MessageDialog.qml +++ b/interface/resources/qml/dialogs/MessageDialog.qml @@ -37,6 +37,16 @@ ModalWindow { return OffscreenUi.waitForMessageBoxResult(root); } + Keys.onRightPressed: if(defaultButton === OriginalDialogs.StandardButton.Yes) { + yesButton.forceActiveFocus() + } else if(defaultButton === OriginalDialogs.StandardButton.Ok) { + okButton.forceActiveFocus() + } + Keys.onTabPressed: if(defaultButton === OriginalDialogs.StandardButton.Yes) { + yesButton.forceActiveFocus() + } else if(defaultButton === OriginalDialogs.StandardButton.Ok) { + okButton.forceActiveFocus() + } property alias detailedText: detailedText.text property alias text: mainTextContainer.text property alias informativeText: informativeTextContainer.text @@ -47,7 +57,6 @@ ModalWindow { onIconChanged: updateIcon(); property int defaultButton: OriginalDialogs.StandardButton.NoButton; property int clickedButton: OriginalDialogs.StandardButton.NoButton; - focus: defaultButton === OriginalDialogs.StandardButton.NoButton property int titleWidth: 0 onTitleWidthChanged: d.resize(); @@ -134,16 +143,35 @@ ModalWindow { MessageDialogButton { dialog: root; text: qsTr("Reset"); button: OriginalDialogs.StandardButton.Reset; } MessageDialogButton { dialog: root; text: qsTr("Discard"); button: OriginalDialogs.StandardButton.Discard; } MessageDialogButton { dialog: root; text: qsTr("No to All"); button: OriginalDialogs.StandardButton.NoToAll; } - MessageDialogButton { dialog: root; text: qsTr("No"); button: OriginalDialogs.StandardButton.No; } + MessageDialogButton { + id: noButton + dialog: root + text: qsTr("No") + button: OriginalDialogs.StandardButton.No + KeyNavigation.left: yesButton + KeyNavigation.backtab: yesButton + } MessageDialogButton { dialog: root; text: qsTr("Yes to All"); button: OriginalDialogs.StandardButton.YesToAll; } - MessageDialogButton { dialog: root; text: qsTr("Yes"); button: OriginalDialogs.StandardButton.Yes; } + MessageDialogButton { + id: yesButton + dialog: root + text: qsTr("Yes") + button: OriginalDialogs.StandardButton.Yes + KeyNavigation.right: noButton + KeyNavigation.tab: noButton + } MessageDialogButton { dialog: root; text: qsTr("Apply"); button: OriginalDialogs.StandardButton.Apply; } MessageDialogButton { dialog: root; text: qsTr("Ignore"); button: OriginalDialogs.StandardButton.Ignore; } MessageDialogButton { dialog: root; text: qsTr("Retry"); button: OriginalDialogs.StandardButton.Retry; } MessageDialogButton { dialog: root; text: qsTr("Save All"); button: OriginalDialogs.StandardButton.SaveAll; } MessageDialogButton { dialog: root; text: qsTr("Save"); button: OriginalDialogs.StandardButton.Save; } MessageDialogButton { dialog: root; text: qsTr("Open"); button: OriginalDialogs.StandardButton.Open; } - MessageDialogButton { dialog: root; text: qsTr("OK"); button: OriginalDialogs.StandardButton.Ok; } + MessageDialogButton { + id: okButton + dialog: root + text: qsTr("OK") + button: OriginalDialogs.StandardButton.Ok + } Button { id: moreButton @@ -230,12 +258,6 @@ ModalWindow { event.accepted = true root.click(OriginalDialogs.StandardButton.Cancel) break - - case Qt.Key_Enter: - case Qt.Key_Return: - event.accepted = true - root.click(root.defaultButton) - break } } } diff --git a/interface/resources/qml/dialogs/QueryDialog.qml b/interface/resources/qml/dialogs/QueryDialog.qml index 9a38c3f0d6..a5ec9828e2 100644 --- a/interface/resources/qml/dialogs/QueryDialog.qml +++ b/interface/resources/qml/dialogs/QueryDialog.qml @@ -95,19 +95,19 @@ ModalWindow { TextField { id: textResult label: root.label - focus: items ? false : true visible: items ? false : true anchors { left: parent.left; right: parent.right; bottom: parent.bottom } + KeyNavigation.down: acceptButton + KeyNavigation.tab: acceptButton } ComboBox { id: comboBox label: root.label - focus: true visible: items ? true : false anchors { left: parent.left @@ -115,6 +115,8 @@ ModalWindow { bottom: parent.bottom } model: items ? items : [] + KeyNavigation.down: acceptButton + KeyNavigation.tab: acceptButton } } @@ -135,7 +137,6 @@ ModalWindow { Flow { id: buttons - focus: true spacing: hifi.dimensions.contentSpacing.x onHeightChanged: d.resize(); onWidthChanged: d.resize(); layoutDirection: Qt.RightToLeft @@ -145,8 +146,21 @@ ModalWindow { margins: 0 bottomMargin: hifi.dimensions.contentSpacing.y } - Button { action: cancelAction } - Button { action: acceptAction } + Button { + id: cancelButton + action: cancelAction + KeyNavigation.left: acceptButton + KeyNavigation.up: items ? comboBox : textResult + KeyNavigation.backtab: acceptButton + } + Button { + id: acceptButton + action: acceptAction + KeyNavigation.right: cancelButton + KeyNavigation.up: items ? comboBox : textResult + KeyNavigation.tab: cancelButton + KeyNavigation.backtab: items ? comboBox : textResult + } } Action { @@ -184,7 +198,13 @@ ModalWindow { case Qt.Key_Return: case Qt.Key_Enter: - acceptAction.trigger() + if (acceptButton.focus) { + acceptAction.trigger() + } else if(cancelButton.focus) { + cancelAction.trigger() + } else if(comboBox.focus || comboBox.popup.focus) { + comboBox.showList() + } event.accepted = true; break; } @@ -194,6 +214,10 @@ ModalWindow { keyboardEnabled = HMD.active; updateIcon(); d.resize(); - textResult.forceActiveFocus(); + if (items) { + comboBox.forceActiveFocus() + } else { + textResult.forceActiveFocus() + } } } diff --git a/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml b/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml index b7ff9354eb..bdb42aba61 100644 --- a/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml +++ b/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml @@ -16,10 +16,21 @@ import "../../controls-uit" Button { property var dialog; - property int button: StandardButton.NoButton; + property int button: StandardButton.Ok; - color: dialog.defaultButton === button ? hifi.buttons.blue : hifi.buttons.white - focus: dialog.defaultButton === button + color: focus ? hifi.buttons.blue : hifi.buttons.white onClicked: dialog.click(button) visible: dialog.buttons & button + Keys.onPressed: { + if (!focus) { + return + } + switch (event.key) { + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true + dialog.click(button) + break + } + } } diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 4b355653b6..300297a074 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -103,14 +103,14 @@ void WindowScriptingInterface::raiseMainWindow() { /// \param const QString& message message to display /// \return QScriptValue::UndefinedValue void WindowScriptingInterface::alert(const QString& message) { - OffscreenUi::asyncWarning("", message); + OffscreenUi::asyncWarning("", message, QMessageBox::Ok, QMessageBox::Ok); } /// Display a confirmation box with the options 'Yes' and 'No' /// \param const QString& message message to display /// \return QScriptValue `true` if 'Yes' was clicked, `false` otherwise QScriptValue WindowScriptingInterface::confirm(const QString& message) { - return QScriptValue((QMessageBox::Yes == OffscreenUi::question("", message, QMessageBox::Yes | QMessageBox::No))); + return QScriptValue((QMessageBox::Yes == OffscreenUi::question("", message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes))); } /// Display a prompt with a text box @@ -353,7 +353,7 @@ QScriptValue WindowScriptingInterface::browseAssets(const QString& title, const return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); } -/// Display a select asset dialog that lets the user select an asset from the Asset Server. If `directory` is an invalid +/// Display a select asset dialog that lets the user select an asset from the Asset Server. If `directory` is an invalid /// directory the browser will start at the root directory. /// \param const QString& title title of the window /// \param const QString& directory directory to start the asset browser at From 46a209037040f688a450e29a5b8a18d2b16fe0dc Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 17 Jan 2018 16:08:12 -0800 Subject: [PATCH 003/116] fix black flashing on transparent objects --- libraries/render-utils/src/RenderDeferredTask.cpp | 6 ++++-- libraries/render/src/render/ShapePipeline.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index f860c0494e..2f4656627a 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -335,11 +335,13 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& // Setup lighting model for all items; batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); - // Setup haze iff curretn zone has haze + // Setup haze iff current zone has haze auto hazeStage = args->_scene->getStage(); if (hazeStage && hazeStage->_currentFrame._hazes.size() > 0) { model::HazePointer hazePointer = hazeStage->getHaze(hazeStage->_currentFrame._hazes.front()); - batch.setUniformBuffer(render::ShapePipeline::Slot::HAZE_MODEL, hazePointer->getHazeParametersBuffer()); + if (hazePointer) { + batch.setUniformBuffer(render::ShapePipeline::Slot::HAZE_MODEL, hazePointer->getHazeParametersBuffer()); + } } // From the lighting model define a global shapKey ORED with individiual keys diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 4254280fa1..4cf1b306ab 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -87,7 +87,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT)); slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK)); slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS)); - slotBindings.insert(gpu::Shader::Binding(std::string("hazeParametersBuffer"), Slot::BUFFER::HAZE_MODEL)); + slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), Slot::BUFFER::HAZE_MODEL)); gpu::Shader::makeProgram(*program, slotBindings); From cede8ed597d0b096eda2b9bfe502972a8f622f87 Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Thu, 18 Jan 2018 19:56:55 +0000 Subject: [PATCH 004/116] add focused color keyboard navigation uses focusedColor --- interface/resources/qml/controls-uit/Button.qml | 8 ++++++-- interface/resources/qml/dialogs/MessageDialog.qml | 8 ++++---- interface/resources/qml/dialogs/QueryDialog.qml | 4 ++-- interface/resources/qml/styles-uit/HifiConstants.qml | 1 + 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/controls-uit/Button.qml b/interface/resources/qml/controls-uit/Button.qml index dde07f45ea..e4360f8cf5 100644 --- a/interface/resources/qml/controls-uit/Button.qml +++ b/interface/resources/qml/controls-uit/Button.qml @@ -57,8 +57,10 @@ Original.Button { hifi.buttons.disabledColorStart[control.colorScheme] } else if (control.pressed) { hifi.buttons.pressedColor[control.color] - } else if (control.hovered || control.focus) { + } else if (control.hovered) { hifi.buttons.hoveredColor[control.color] + } else if (!control.hovered && control.focus) { + hifi.buttons.focusedColor[control.color] } else { hifi.buttons.colorStart[control.color] } @@ -71,8 +73,10 @@ Original.Button { hifi.buttons.disabledColorFinish[control.colorScheme] } else if (control.pressed) { hifi.buttons.pressedColor[control.color] - } else if (control.hovered || control.focus) { + } else if (control.hovered) { hifi.buttons.hoveredColor[control.color] + } else if (!control.hovered && control.focus) { + hifi.buttons.focusedColor[control.color] } else { hifi.buttons.colorFinish[control.color] } diff --git a/interface/resources/qml/dialogs/MessageDialog.qml b/interface/resources/qml/dialogs/MessageDialog.qml index 79d4b009db..351df6dc8a 100644 --- a/interface/resources/qml/dialogs/MessageDialog.qml +++ b/interface/resources/qml/dialogs/MessageDialog.qml @@ -37,14 +37,14 @@ ModalWindow { return OffscreenUi.waitForMessageBoxResult(root); } - Keys.onRightPressed: if(defaultButton === OriginalDialogs.StandardButton.Yes) { + Keys.onRightPressed: if (defaultButton === OriginalDialogs.StandardButton.Yes) { yesButton.forceActiveFocus() - } else if(defaultButton === OriginalDialogs.StandardButton.Ok) { + } else if (defaultButton === OriginalDialogs.StandardButton.Ok) { okButton.forceActiveFocus() } - Keys.onTabPressed: if(defaultButton === OriginalDialogs.StandardButton.Yes) { + Keys.onTabPressed: if (defaultButton === OriginalDialogs.StandardButton.Yes) { yesButton.forceActiveFocus() - } else if(defaultButton === OriginalDialogs.StandardButton.Ok) { + } else if (defaultButton === OriginalDialogs.StandardButton.Ok) { okButton.forceActiveFocus() } property alias detailedText: detailedText.text diff --git a/interface/resources/qml/dialogs/QueryDialog.qml b/interface/resources/qml/dialogs/QueryDialog.qml index a5ec9828e2..b5de5362f2 100644 --- a/interface/resources/qml/dialogs/QueryDialog.qml +++ b/interface/resources/qml/dialogs/QueryDialog.qml @@ -200,9 +200,9 @@ ModalWindow { case Qt.Key_Enter: if (acceptButton.focus) { acceptAction.trigger() - } else if(cancelButton.focus) { + } else if (cancelButton.focus) { cancelAction.trigger() - } else if(comboBox.focus || comboBox.popup.focus) { + } else if (comboBox.focus || comboBox.popup.focus) { comboBox.showList() } event.accepted = true; diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index cf800cb62e..5da587ea57 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -219,6 +219,7 @@ Item { readonly property var colorFinish: [ colors.lightGrayText, colors.blueAccent, "#94132e", colors.black, Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ] readonly property var hoveredColor: [ colorStart[white], colorStart[blue], colorStart[red], colorFinish[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] + readonly property var focusedColor: [ colors.lightGray50, colors.blueAccent, colors.redAccent, colors.darkGray, colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] readonly property var disabledColorStart: [ colorStart[white], colors.baseGrayHighlight] readonly property var disabledColorFinish: [ colorFinish[white], colors.baseGrayShadow] readonly property var disabledTextColor: [ colors.lightGrayText, colors.baseGrayShadow] From faf18fcaa89f58cf6b7b3826aa28c7919d4a19f2 Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Fri, 19 Jan 2018 01:49:24 +0000 Subject: [PATCH 005/116] improve file dialog navigation --- .../resources/qml/dialogs/FileDialog.qml | 54 +++++++++++++++---- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 581ac37a74..42d6bf4261 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -102,7 +102,17 @@ ModalWindow { } }); - fileTableView.forceActiveFocus(); + focusTimer.start(); + } + + Timer { + id: focusTimer + interval: 10 + running: false + repeat: false + onTriggered: { + fileTableView.contentItem.forceActiveFocus(); + } } Item { @@ -122,7 +132,9 @@ ModalWindow { drag.target: root onClicked: { d.clearSelection(); - frame.forceActiveFocus(); // Defocus text field so that the keyboard gets hidden. + // Defocus text field so that the keyboard gets hidden. + // Clicking also breaks keyboard navigation apart from backtabbing to cancel + frame.forceActiveFocus(); } } @@ -142,6 +154,12 @@ ModalWindow { size: 30 enabled: fileTableModel.parentFolder && fileTableModel.parentFolder !== "" onClicked: d.navigateUp(); + KeyNavigation.up: fileTableView.contentItem + KeyNavigation.down: fileTableView.contentItem + KeyNavigation.tab: fileTableView.contentItem + KeyNavigation.backtab: fileTableView.contentItem + KeyNavigation.left: fileTableView.contentItem + KeyNavigation.right: fileTableView.contentItem } GlyphButton { @@ -152,6 +170,12 @@ ModalWindow { width: height enabled: d.homeDestination ? true : false onClicked: d.navigateHome(); + KeyNavigation.up: fileTableView.contentItem + KeyNavigation.down: fileTableView.contentItem + KeyNavigation.tab: fileTableView.contentItem + KeyNavigation.backtab: fileTableView.contentItem + KeyNavigation.left: fileTableView.contentItem + KeyNavigation.right: fileTableView.contentItem } } @@ -220,9 +244,15 @@ ModalWindow { d.currentSelectionUrl = helper.pathToUrl(currentText); } fileTableModel.folder = folder; - fileTableView.forceActiveFocus(); } } + + KeyNavigation.up: fileTableView.contentItem + KeyNavigation.down: fileTableView.contentItem + KeyNavigation.tab: fileTableView.contentItem + KeyNavigation.backtab: fileTableView.contentItem + KeyNavigation.left: fileTableView.contentItem + KeyNavigation.right: fileTableView.contentItem } QtObject { @@ -638,6 +668,8 @@ ModalWindow { break; } } + + KeyNavigation.tab: root.saveDialog ? currentSelection : openButton } TextField { @@ -654,6 +686,10 @@ ModalWindow { activeFocusOnTab: !readOnly onActiveFocusChanged: if (activeFocus) { selectAll(); } onAccepted: okAction.trigger(); + KeyNavigation.up: fileTableView.contentItem + KeyNavigation.down: openButton + KeyNavigation.tab: openButton + KeyNavigation.backtab: fileTableView.contentItem } FileTypeSelection { @@ -664,8 +700,6 @@ ModalWindow { right: parent.right } visible: !selectDirectory && filtersCount > 1 - KeyNavigation.left: fileTableView - KeyNavigation.right: openButton } Keyboard { @@ -693,18 +727,18 @@ ModalWindow { color: hifi.buttons.blue action: okAction Keys.onReturnPressed: okAction.trigger() - KeyNavigation.up: selectionType - KeyNavigation.left: selectionType KeyNavigation.right: cancelButton + KeyNavigation.up: root.saveDialog ? currentSelection : fileTableView.contentItem + KeyNavigation.tab: cancelButton } Button { id: cancelButton action: cancelAction - KeyNavigation.up: selectionType - KeyNavigation.left: openButton - KeyNavigation.right: fileTableView.contentItem Keys.onReturnPressed: { cancelAction.trigger() } + KeyNavigation.left: openButton + KeyNavigation.up: root.saveDialog ? currentSelection : fileTableView.contentItem + KeyNavigation.backtab: openButton } } From e69fdba6ad5879dadc2340b276a40383c40427a6 Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Fri, 19 Jan 2018 12:18:29 +0000 Subject: [PATCH 006/116] add padding to sorting glyph the file dialog sorting glyphs are clipping on the right margin for me, adding 10 extra pixels fixes it --- interface/resources/qml/controls-uit/Table.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/controls-uit/Table.qml b/interface/resources/qml/controls-uit/Table.qml index 1443cd5d6e..a3e4113d08 100644 --- a/interface/resources/qml/controls-uit/Table.qml +++ b/interface/resources/qml/controls-uit/Table.qml @@ -93,7 +93,7 @@ TableView { size: hifi.fontSizes.tableHeadingIcon anchors { left: titleText.right - leftMargin: -hifi.fontSizes.tableHeadingIcon / 3 - (centerHeaderText ? 5 : 0) + leftMargin: -hifi.fontSizes.tableHeadingIcon / 3 - (centerHeaderText ? 15 : 10) right: parent.right rightMargin: hifi.dimensions.tablePadding verticalCenter: titleText.verticalCenter From 44740d5716cba750ff515910f1836833e4bf423a Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Fri, 19 Jan 2018 12:27:49 +0000 Subject: [PATCH 007/116] add sound on button focus changed Feels more consistent with hovering --- interface/resources/qml/controls-uit/Button.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/interface/resources/qml/controls-uit/Button.qml b/interface/resources/qml/controls-uit/Button.qml index e4360f8cf5..926e9c4fe5 100644 --- a/interface/resources/qml/controls-uit/Button.qml +++ b/interface/resources/qml/controls-uit/Button.qml @@ -32,6 +32,12 @@ Original.Button { Tablet.playSound(TabletEnums.ButtonHover); } } + + onFocusChanged: { + if (focus) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } onClicked: { Tablet.playSound(TabletEnums.ButtonClick); From f1147948cb50ef3a28f2c4ff56a44723cd358699 Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Fri, 19 Jan 2018 12:28:27 +0000 Subject: [PATCH 008/116] add focused color to GlyphButton --- interface/resources/qml/controls-uit/GlyphButton.qml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/interface/resources/qml/controls-uit/GlyphButton.qml b/interface/resources/qml/controls-uit/GlyphButton.qml index 9c23171ee1..024c131a09 100644 --- a/interface/resources/qml/controls-uit/GlyphButton.qml +++ b/interface/resources/qml/controls-uit/GlyphButton.qml @@ -31,6 +31,12 @@ Original.Button { } } + onFocusChanged: { + if (focus) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + onClicked: { Tablet.playSound(TabletEnums.ButtonClick); } @@ -50,6 +56,8 @@ Original.Button { hifi.buttons.pressedColor[control.color] } else if (control.hovered) { hifi.buttons.hoveredColor[control.color] + } else if (!control.hovered && control.focus) { + hifi.buttons.focusedColor[control.color] } else { hifi.buttons.colorStart[control.color] } @@ -64,6 +72,8 @@ Original.Button { hifi.buttons.pressedColor[control.color] } else if (control.hovered) { hifi.buttons.hoveredColor[control.color] + } else if (!control.hovered && control.focus) { + hifi.buttons.focusedColor[control.color] } else { hifi.buttons.colorFinish[control.color] } From 4c27538fa2ad39f82322d2038fc0e514f75174f8 Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Fri, 19 Jan 2018 12:30:00 +0000 Subject: [PATCH 009/116] change file dialog keyboard navigation Allowed navigating with keyboard to home and up buttons in file dialog. --- .../resources/qml/dialogs/FileDialog.qml | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 42d6bf4261..145c0b2a30 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -154,12 +154,11 @@ ModalWindow { size: 30 enabled: fileTableModel.parentFolder && fileTableModel.parentFolder !== "" onClicked: d.navigateUp(); - KeyNavigation.up: fileTableView.contentItem - KeyNavigation.down: fileTableView.contentItem - KeyNavigation.tab: fileTableView.contentItem - KeyNavigation.backtab: fileTableView.contentItem - KeyNavigation.left: fileTableView.contentItem - KeyNavigation.right: fileTableView.contentItem + Keys.onReturnPressed: { d.navigateUp(); } + KeyNavigation.tab: homeButton + KeyNavigation.backtab: upButton + KeyNavigation.left: upButton + KeyNavigation.right: homeButton } GlyphButton { @@ -170,12 +169,10 @@ ModalWindow { width: height enabled: d.homeDestination ? true : false onClicked: d.navigateHome(); - KeyNavigation.up: fileTableView.contentItem - KeyNavigation.down: fileTableView.contentItem + Keys.onReturnPressed: { d.navigateHome(); } KeyNavigation.tab: fileTableView.contentItem - KeyNavigation.backtab: fileTableView.contentItem - KeyNavigation.left: fileTableView.contentItem - KeyNavigation.right: fileTableView.contentItem + KeyNavigation.backtab: upButton + KeyNavigation.left: upButton } } @@ -505,7 +502,6 @@ ModalWindow { } headerVisible: !selectDirectory onDoubleClicked: navigateToRow(row); - focus: true Keys.onReturnPressed: navigateToCurrentRow(); Keys.onEnterPressed: navigateToCurrentRow(); @@ -582,7 +578,7 @@ ModalWindow { resizable: true } TableViewColumn { - id: fileMofifiedColumn + id: fileModifiedColumn role: "fileModified" title: "Date" width: 0.3 * fileTableView.width @@ -593,7 +589,7 @@ ModalWindow { TableViewColumn { role: "fileSize" title: "Size" - width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width + width: fileTableView.width - fileNameColumn.width - fileModifiedColumn.width movable: false resizable: true visible: !selectDirectory @@ -668,7 +664,7 @@ ModalWindow { break; } } - + KeyNavigation.tab: root.saveDialog ? currentSelection : openButton } From 0f7f58417bc2ffbaa1a31e918a1b5b928edb0fbf Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 19 Jan 2018 19:11:12 +0100 Subject: [PATCH 010/116] JS scripting console auto-complete --- interface/CMakeLists.txt | 10 + interface/src/ui/JSConsole.cpp | 279 ++++++++++++++---- interface/src/ui/JSConsole.h | 16 +- .../src/UsersScriptingInterface.h | 2 +- tools/jsdoc/config.json | 5 +- tools/jsdoc/package.json | 7 + tools/jsdoc/plugins/hifi.js | 8 + tools/jsdoc/plugins/hifiJSONExport.js | 14 + 8 files changed, 285 insertions(+), 56 deletions(-) create mode 100644 tools/jsdoc/package.json create mode 100644 tools/jsdoc/plugins/hifiJSONExport.js diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 21225756b4..fbc40f70c2 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -349,6 +349,16 @@ endif() add_bugsplat() +# generate the JSDoc JSON for the JSConsole auto-completer +add_custom_command(TARGET ${TARGET_NAME} #POST_BUILD + COMMAND npm install + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tools/jsdoc +) +add_custom_command(TARGET ${TARGET_NAME} + COMMAND node_modules/.bin/jsdoc . -c config.json + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tools/jsdoc +) + if (WIN32) set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"") diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp index c5f8b54ebd..7f68a205e6 100644 --- a/interface/src/ui/JSConsole.cpp +++ b/interface/src/ui/JSConsole.cpp @@ -12,10 +12,11 @@ #include "JSConsole.h" #include -#include #include #include #include +#include +#include #include #include @@ -61,12 +62,64 @@ void _writeLines(const QString& filename, const QList& lines) { QTextStream(&file) << json; } +void JSConsole::readAPI() { + QFile file(PathUtils::resourcesPath() + "auto-complete/export.json"); + file.open(QFile::ReadOnly); + auto json = QTextStream(&file).readAll().toUtf8(); + _apiDocs = QJsonDocument::fromJson(json).array(); +} + +QStandardItem* getAutoCompleteItem(QJsonValue propertyObject) { + auto propertyItem = new QStandardItem(propertyObject.toObject().value("name").toString()); + propertyItem->setData(propertyObject.toVariant()); + return propertyItem; +} + +QStandardItemModel* JSConsole::getAutoCompleteModel(const QString& memberOf) { + QString memberOfProperty = nullptr; + + auto model = new QStandardItemModel(this); + + if (memberOf != nullptr) { + foreach(auto doc, _apiDocs) { + auto object = doc.toObject(); + if (object.value("name").toString() == memberOf && object.value("scope").toString() == "global" && + object.value("kind").toString() == "namespace") { + + memberOfProperty = object.value("longname").toString(); + + auto properties = doc.toObject().value("properties").toArray(); + foreach(auto propertyObject, properties) { + model->appendRow(getAutoCompleteItem(propertyObject)); + } + } + } + if (memberOfProperty == nullptr) { + return nullptr; + } + } + + foreach(auto doc, _apiDocs) { + auto object = doc.toObject(); + auto scope = object.value("scope"); + if ((memberOfProperty == nullptr && scope.toString() == "global" && object.value("kind").toString() == "namespace") || (memberOfProperty != nullptr && object.value("memberof").toString() == memberOfProperty)) { + model->appendRow(getAutoCompleteItem(doc)); + } + } + model->sort(0); + return model; +} + JSConsole::JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine) : QWidget(parent), _ui(new Ui::Console), _currentCommandInHistory(NO_CURRENT_HISTORY_COMMAND), _savedHistoryFilename(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + HISTORY_FILENAME), - _commandHistory(_readLines(_savedHistoryFilename)) { + _commandHistory(_readLines(_savedHistoryFilename)), + _completer(new QCompleter(this)) { + + readAPI(); + _ui->setupUi(this); _ui->promptTextEdit->setLineWrapMode(QTextEdit::NoWrap); _ui->promptTextEdit->setWordWrapMode(QTextOption::NoWrap); @@ -78,38 +131,75 @@ JSConsole::JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine) : setStyleSheet(styleSheet.readAll()); } - connect(_ui->scrollArea->verticalScrollBar(), SIGNAL(rangeChanged(int, int)), this, SLOT(scrollToBottom())); - connect(_ui->promptTextEdit, SIGNAL(textChanged()), this, SLOT(resizeTextInput())); + connect(_ui->scrollArea->verticalScrollBar(), &QScrollBar::rangeChanged, this, &JSConsole::scrollToBottom); + connect(_ui->promptTextEdit, &QTextEdit::textChanged, this, &JSConsole::resizeTextInput); + + _completer->setWidget(_ui->promptTextEdit); + _completer->setModel(getAutoCompleteModel(nullptr)); + _completer->setModelSorting(QCompleter::CaseSensitivelySortedModel); + _completer->setMaxVisibleItems(12); + _completer->setFilterMode(Qt::MatchStartsWith); + _completer->setWrapAround(false); + _completer->setCompletionMode(QCompleter::PopupCompletion); + _completer->setCaseSensitivity(Qt::CaseSensitive); + + QListView *listView = new QListView(); + listView->setEditTriggers(QAbstractItemView::NoEditTriggers); + listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + listView->setSelectionBehavior(QAbstractItemView::SelectRows); + listView->setSelectionMode(QAbstractItemView::SingleSelection); + listView->setModelColumn(_completer->completionColumn()); + + _completer->setPopup(listView); + _completer->popup()->installEventFilter(this); + QObject::connect(_completer, static_cast(&QCompleter::activated), this, + &JSConsole::insertCompletion); + + QObject::connect(_completer, static_cast(&QCompleter::highlighted), this, + &JSConsole::highlightedCompletion); setScriptEngine(scriptEngine); resizeTextInput(); - connect(&_executeWatcher, SIGNAL(finished()), this, SLOT(commandFinished())); + connect(&_executeWatcher, &QFutureWatcher::finished, this, &JSConsole::commandFinished); +} + +void JSConsole::insertCompletion(const QModelIndex& completion) { + auto completionString = completion.data().toString(); + QTextCursor tc = _ui->promptTextEdit->textCursor(); + int extra = completionString.length() - _completer->completionPrefix().length(); + tc.movePosition(QTextCursor::Left); + tc.movePosition(QTextCursor::EndOfWord); + tc.insertText(completionString.right(extra)); + _ui->promptTextEdit->setTextCursor(tc); +} + +void JSConsole::highlightedCompletion(const QModelIndex& completion) { + qDebug() << "Highlighted " << completion.data().toString(); + auto jsdocObject = QJsonValue::fromVariant(completion.data(Qt::UserRole + 1)).toObject(); + + // qDebug() << "Highlighted data " << QJsonDocument(jsdocObject).toJson(QJsonDocument::Compact); } JSConsole::~JSConsole() { if (_scriptEngine) { - disconnect(_scriptEngine.data(), SIGNAL(printedMessage(const QString&)), this, SLOT(handlePrint(const QString&))); - disconnect(_scriptEngine.data(), SIGNAL(errorMessage(const QString&)), this, SLOT(handleError(const QString&))); + disconnect(_scriptEngine.data(), nullptr, this, nullptr); _scriptEngine.reset(); } delete _ui; } void JSConsole::setScriptEngine(const ScriptEnginePointer& scriptEngine) { - if (_scriptEngine == scriptEngine && scriptEngine != NULL) { + if (_scriptEngine == scriptEngine && scriptEngine != nullptr) { return; } - if (_scriptEngine != NULL) { - disconnect(_scriptEngine.data(), &ScriptEngine::printedMessage, this, &JSConsole::handlePrint); - disconnect(_scriptEngine.data(), &ScriptEngine::infoMessage, this, &JSConsole::handleInfo); - disconnect(_scriptEngine.data(), &ScriptEngine::warningMessage, this, &JSConsole::handleWarning); - disconnect(_scriptEngine.data(), &ScriptEngine::errorMessage, this, &JSConsole::handleError); + if (_scriptEngine != nullptr) { + disconnect(_scriptEngine.data(), nullptr, this, nullptr); _scriptEngine.reset(); } - // if scriptEngine is NULL then create one and keep track of it using _ownScriptEngine + // if scriptEngine is nullptr then create one and keep track of it using _ownScriptEngine if (scriptEngine.isNull()) { _scriptEngine = DependencyManager::get()->loadScript(_consoleFileName, false); } else { @@ -199,45 +289,132 @@ void JSConsole::showEvent(QShowEvent* event) { } bool JSConsole::eventFilter(QObject* sender, QEvent* event) { - if (sender == _ui->promptTextEdit) { - if (event->type() == QEvent::KeyPress) { - QKeyEvent* keyEvent = static_cast(event); - int key = keyEvent->key(); + if ((sender == _ui->promptTextEdit || sender == _completer->popup()) && event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(event); + int key = keyEvent->key(); - if ((key == Qt::Key_Return || key == Qt::Key_Enter)) { - if (keyEvent->modifiers() & Qt::ShiftModifier) { - // If the shift key is being used then treat it as a regular return/enter. If this isn't done, - // a new QTextBlock isn't created. - keyEvent->setModifiers(keyEvent->modifiers() & ~Qt::ShiftModifier); - } else { - QString command = _ui->promptTextEdit->toPlainText().replace("\r\n","\n").trimmed(); - - if (!command.isEmpty()) { - QTextCursor cursor = _ui->promptTextEdit->textCursor(); - _ui->promptTextEdit->clear(); - - executeCommand(command); - } - - return true; - } - } else if (key == Qt::Key_Down) { - // Go to the next command in history if the cursor is at the last line of the current command. - int blockNumber = _ui->promptTextEdit->textCursor().blockNumber(); - int blockCount = _ui->promptTextEdit->document()->blockCount(); - if (blockNumber == blockCount - 1) { - setToNextCommandInHistory(); - return true; - } - } else if (key == Qt::Key_Up) { - // Go to the previous command in history if the cursor is at the first line of the current command. - int blockNumber = _ui->promptTextEdit->textCursor().blockNumber(); - if (blockNumber == 0) { - setToPreviousCommandInHistory(); - return true; - } + if (_completer->popup()->isVisible()) { + // The following keys are forwarded by the completer to the widget + switch (key) { + case Qt::Key_Space: + case Qt::Key_Enter: + case Qt::Key_Return: + insertCompletion(_completer->currentIndex()); + _completer->popup()->hide(); + return true; + case Qt::Key_Escape: + case Qt::Key_Tab: + case Qt::Key_Backtab: + qDebug() << "test"; + keyEvent->ignore();//setAccepted(false); + return false; // let the completer do default behavior + default: + return false; } } + + if ((key == Qt::Key_Return || key == Qt::Key_Enter)) { + if (keyEvent->modifiers() & Qt::ShiftModifier) { + // If the shift key is being used then treat it as a regular return/enter. If this isn't done, + // a new QTextBlock isn't created. + keyEvent->setModifiers(keyEvent->modifiers() & ~Qt::ShiftModifier); + } else { + QString command = _ui->promptTextEdit->toPlainText().replace("\r\n", "\n").trimmed(); + + if (!command.isEmpty()) { + QTextCursor cursor = _ui->promptTextEdit->textCursor(); + _ui->promptTextEdit->clear(); + + executeCommand(command); + } + + return true; + } + } else if (key == Qt::Key_Down) { + // Go to the next command in history if the cursor is at the last line of the current command. + int blockNumber = _ui->promptTextEdit->textCursor().blockNumber(); + int blockCount = _ui->promptTextEdit->document()->blockCount(); + if (blockNumber == blockCount - 1) { + setToNextCommandInHistory(); + return true; + } + } else if (key == Qt::Key_Up) { + // Go to the previous command in history if the cursor is at the first line of the current command. + int blockNumber = _ui->promptTextEdit->textCursor().blockNumber(); + if (blockNumber == 0) { + setToPreviousCommandInHistory(); + return true; + } + } + } else if ((sender == _ui->promptTextEdit || sender == _completer->popup()) && event->type() == QEvent::KeyRelease) { + QKeyEvent* keyEvent = static_cast(event); + int key = keyEvent->key(); + + // completer shortcut (CTRL + SPACE) + bool isCompleterShortcut = ((keyEvent->modifiers() & Qt::ControlModifier) && key == Qt::Key_Space) || + key == Qt::Key_Period; + if (_completer->popup()->isVisible() || isCompleterShortcut) { + + const bool ctrlOrShift = keyEvent->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier); + if (ctrlOrShift && keyEvent->text().isEmpty()) { + return false; + } + + static QString eow("~!@#$%^&*()+{}|:\"<>?,/;'[]\\-="); // end of word + bool hasModifier = (keyEvent->modifiers() != Qt::NoModifier) && !ctrlOrShift; + + + if (!isCompleterShortcut && (!keyEvent->text().isEmpty() && eow.contains(keyEvent->text().right(1)))) { + qDebug() << "eow contains " << keyEvent->text().right(1) << " full text: " << keyEvent->text(); + _completer->popup()->hide(); + return false; + } + qDebug() << "auto completing"; + + auto textCursor = _ui->promptTextEdit->textCursor(); + + textCursor.select(QTextCursor::WordUnderCursor); + + QString completionPrefix = textCursor.selectedText(); + + auto leftOfCursor = _ui->promptTextEdit->toPlainText().left(textCursor.position()); + qDebug() << "leftOfCursor" << leftOfCursor; + + // RegEx [3] [4] + // (Module.subModule).(property/subModule) + + const int MODULE_INDEX = 3; + const int PROPERTY_INDEX = 4; + // TODO: disallow invalid characters on left of property + QRegExp regExp("((([A-Za-z0-9_\\.]+)\\.)|(?!\\.))([a-zA-Z0-9_]*)$"); + int pos = regExp.indexIn(leftOfCursor); + auto rexExpCapturedTexts = regExp.capturedTexts(); + auto memberOf = rexExpCapturedTexts[MODULE_INDEX]; + completionPrefix = rexExpCapturedTexts[PROPERTY_INDEX]; + bool switchedModule = false; + if (memberOf != _completerModule) { + _completerModule = memberOf; + auto autoCompleteModel = getAutoCompleteModel(memberOf); + if (autoCompleteModel == nullptr) { + _completer->popup()->hide(); + return false; + } + _completer->setModel(autoCompleteModel); + _completer->popup()->installEventFilter(this); + switchedModule = true; + } + + if (switchedModule || completionPrefix != _completer->completionPrefix()) { + _completer->setCompletionPrefix(completionPrefix); + qDebug() << "Set completion prefix to:" << completionPrefix; + _completer->popup()->setCurrentIndex(_completer->completionModel()->index(0, 0)); + } + auto cursorRect = _ui->promptTextEdit->cursorRect(); + cursorRect.setWidth(_completer->popup()->sizeHintForColumn(0) + + _completer->popup()->verticalScrollBar()->sizeHint().width()); + _completer->complete(cursorRect); + return false; + } } return false; } @@ -321,7 +498,7 @@ void JSConsole::appendMessage(const QString& gutter, const QString& message) { void JSConsole::clear() { QLayoutItem* item; - while ((item = _ui->logArea->layout()->takeAt(0)) != NULL) { + while ((item = _ui->logArea->layout()->takeAt(0)) != nullptr) { delete item->widget(); delete item; } diff --git a/interface/src/ui/JSConsole.h b/interface/src/ui/JSConsole.h index 4b6409a76f..eeb3601886 100644 --- a/interface/src/ui/JSConsole.h +++ b/interface/src/ui/JSConsole.h @@ -12,12 +12,11 @@ #ifndef hifi_JSConsole_h #define hifi_JSConsole_h -#include -#include #include #include -#include #include +#include +#include #include "ui_console.h" #include "ScriptEngine.h" @@ -54,12 +53,20 @@ protected slots: void handleError(const QString& message, const QString& scriptName); void commandFinished(); +private slots: + void insertCompletion(const QModelIndex& completion); + void highlightedCompletion(const QModelIndex& completion); + private: void appendMessage(const QString& gutter, const QString& message); void setToNextCommandInHistory(); void setToPreviousCommandInHistory(); void resetCurrentCommandHistory(); + void readAPI(); + + QStandardItemModel* getAutoCompleteModel(const QString& memberOf = nullptr); + QFutureWatcher _executeWatcher; Ui::Console* _ui; int _currentCommandInHistory; @@ -68,6 +75,9 @@ private: QString _rootCommand; ScriptEnginePointer _scriptEngine; static const QString _consoleFileName; + QJsonArray _apiDocs; + QCompleter* _completer; + QString _completerModule {""}; }; diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index 2d33bbca14..a4e741a797 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -150,7 +150,7 @@ signals: /**jsdoc * Notifies scripts that a user has disconnected from the domain - * @function Users.avatar.avatarDisconnected + * @function Users.avatarDisconnected * @param {nodeID} NodeID The session ID of the avatar that has disconnected */ void avatarDisconnected(const QUuid& nodeID); diff --git a/tools/jsdoc/config.json b/tools/jsdoc/config.json index 0fb833d015..a24e248661 100644 --- a/tools/jsdoc/config.json +++ b/tools/jsdoc/config.json @@ -4,5 +4,8 @@ "outputSourceFiles": false } }, - "plugins": ["plugins/hifi"] + "plugins": [ + "plugins/hifi", + "plugins/hifiJSONExport" + ] } diff --git a/tools/jsdoc/package.json b/tools/jsdoc/package.json new file mode 100644 index 0000000000..215ceec177 --- /dev/null +++ b/tools/jsdoc/package.json @@ -0,0 +1,7 @@ +{ + "name": "hifiJSDoc", + "dependencies": { + "jsdoc": "^3.5.5" + }, + "private": true +} diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index 3af5fbeee3..afa3285c37 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -47,5 +47,13 @@ exports.handlers = { } }); }); + + fs.writeFile("out/hifiJSDoc.js", e.source, function(err) { + if (err) { + return console.log(err); + } + + console.log("The Hifi JSDoc JS was saved!"); + }); } }; diff --git a/tools/jsdoc/plugins/hifiJSONExport.js b/tools/jsdoc/plugins/hifiJSONExport.js new file mode 100644 index 0000000000..7948fd2673 --- /dev/null +++ b/tools/jsdoc/plugins/hifiJSONExport.js @@ -0,0 +1,14 @@ +exports.handlers = { + processingComplete: function(e) { + var doclets = e.doclets.map(doclet => Object.assign({}, doclet)); + const fs = require('fs'); + doclets.map(doclet => {delete doclet.meta; delete doclet.comment}); + fs.writeFile("out/hifiJSDoc.json", JSON.stringify(doclets, null, 4), function(err) { + if (err) { + return console.log(err); + } + + console.log("The Hifi JSDoc JSON was saved!"); + }); + } +}; From a31fe7546ad2500a2544fc694697a92634c3c8bd Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 19 Jan 2018 19:45:25 +0100 Subject: [PATCH 011/116] - remove debug - fix API export JSON filename - fix warnings --- interface/src/ui/JSConsole.cpp | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp index 7f68a205e6..3100b47882 100644 --- a/interface/src/ui/JSConsole.cpp +++ b/interface/src/ui/JSConsole.cpp @@ -63,7 +63,7 @@ void _writeLines(const QString& filename, const QList& lines) { } void JSConsole::readAPI() { - QFile file(PathUtils::resourcesPath() + "auto-complete/export.json"); + QFile file(PathUtils::resourcesPath() + "auto-complete/hifiJSDoc.json"); file.open(QFile::ReadOnly); auto json = QTextStream(&file).readAll().toUtf8(); _apiDocs = QJsonDocument::fromJson(json).array(); @@ -302,18 +302,12 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { insertCompletion(_completer->currentIndex()); _completer->popup()->hide(); return true; - case Qt::Key_Escape: - case Qt::Key_Tab: - case Qt::Key_Backtab: - qDebug() << "test"; - keyEvent->ignore();//setAccepted(false); - return false; // let the completer do default behavior default: return false; } } - if ((key == Qt::Key_Return || key == Qt::Key_Enter)) { + if (key == Qt::Key_Return || key == Qt::Key_Enter) { if (keyEvent->modifiers() & Qt::ShiftModifier) { // If the shift key is being used then treat it as a regular return/enter. If this isn't done, // a new QTextBlock isn't created. @@ -361,15 +355,11 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { } static QString eow("~!@#$%^&*()+{}|:\"<>?,/;'[]\\-="); // end of word - bool hasModifier = (keyEvent->modifiers() != Qt::NoModifier) && !ctrlOrShift; - if (!isCompleterShortcut && (!keyEvent->text().isEmpty() && eow.contains(keyEvent->text().right(1)))) { - qDebug() << "eow contains " << keyEvent->text().right(1) << " full text: " << keyEvent->text(); _completer->popup()->hide(); return false; } - qDebug() << "auto completing"; auto textCursor = _ui->promptTextEdit->textCursor(); @@ -378,7 +368,6 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { QString completionPrefix = textCursor.selectedText(); auto leftOfCursor = _ui->promptTextEdit->toPlainText().left(textCursor.position()); - qDebug() << "leftOfCursor" << leftOfCursor; // RegEx [3] [4] // (Module.subModule).(property/subModule) @@ -387,7 +376,7 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { const int PROPERTY_INDEX = 4; // TODO: disallow invalid characters on left of property QRegExp regExp("((([A-Za-z0-9_\\.]+)\\.)|(?!\\.))([a-zA-Z0-9_]*)$"); - int pos = regExp.indexIn(leftOfCursor); + regExp.indexIn(leftOfCursor); auto rexExpCapturedTexts = regExp.capturedTexts(); auto memberOf = rexExpCapturedTexts[MODULE_INDEX]; completionPrefix = rexExpCapturedTexts[PROPERTY_INDEX]; @@ -399,14 +388,13 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { _completer->popup()->hide(); return false; } - _completer->setModel(autoCompleteModel); + _completer->setModel(autoCompleteModel); _completer->popup()->installEventFilter(this); switchedModule = true; } if (switchedModule || completionPrefix != _completer->completionPrefix()) { _completer->setCompletionPrefix(completionPrefix); - qDebug() << "Set completion prefix to:" << completionPrefix; _completer->popup()->setCurrentIndex(_completer->completionModel()->index(0, 0)); } auto cursorRect = _ui->promptTextEdit->cursorRect(); From 483074b11e2dfd80f144e74aa0287c623c7d401d Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 22 Jan 2018 17:59:01 +0100 Subject: [PATCH 012/116] jsdoc cmake fixes --- interface/CMakeLists.txt | 19 +++++++++---------- tools/CMakeLists.txt | 4 +++- tools/jsdoc/CMakeLists.txt | 14 ++++++++++++++ tools/jsdoc/plugins/hifi.js | 9 ++++++--- tools/jsdoc/plugins/hifiJSONExport.js | 4 +++- 5 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 tools/jsdoc/CMakeLists.txt diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index fbc40f70c2..07788e5865 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -279,6 +279,9 @@ endif(UNIX) # assume we are using a Qt build without bearer management add_definitions(-DQT_NO_BEARERMANAGEMENT) +# require JSDoc to be build before interface is deployed (Console Auto-complete) +add_dependencies(${TARGET_NAME} jsdoc) + if (APPLE) # link in required OS X frameworks and include the right GL headers find_library(OpenGL OpenGL) @@ -299,6 +302,9 @@ if (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/../Resources/scripts" + COMMAND "${CMAKE_COMMAND}" -E copy + "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" + "$/../Resources/auto-complete" ) # call the fixup_interface macro to add required bundling commands for installation @@ -313,6 +319,9 @@ else (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/scripts" + COMMAND "${CMAKE_COMMAND}" -E copy + "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" + "$/resources/auto-complete" ) # link target to external libraries @@ -349,16 +358,6 @@ endif() add_bugsplat() -# generate the JSDoc JSON for the JSConsole auto-completer -add_custom_command(TARGET ${TARGET_NAME} #POST_BUILD - COMMAND npm install - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tools/jsdoc -) -add_custom_command(TARGET ${TARGET_NAME} - COMMAND node_modules/.bin/jsdoc . -c config.json - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tools/jsdoc -) - if (WIN32) set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"") diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 16446c5071..ac920d930d 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -2,6 +2,9 @@ add_subdirectory(scribe) set_target_properties(scribe PROPERTIES FOLDER "Tools") +add_subdirectory(jsdoc) +set_target_properties(jsdoc PROPERTIES FOLDER "Tools") + if (BUILD_TOOLS) add_subdirectory(udt-test) set_target_properties(udt-test PROPERTIES FOLDER "Tools") @@ -27,4 +30,3 @@ if (BUILD_TOOLS) add_subdirectory(auto-tester) set_target_properties(auto-tester PROPERTIES FOLDER "Tools") endif() - diff --git a/tools/jsdoc/CMakeLists.txt b/tools/jsdoc/CMakeLists.txt new file mode 100644 index 0000000000..9a9883a6ad --- /dev/null +++ b/tools/jsdoc/CMakeLists.txt @@ -0,0 +1,14 @@ +set(TARGET_NAME jsdoc) + +add_custom_target(${TARGET_NAME}) + +SET(JSDOC_WORKING_DIR ${CMAKE_SOURCE_DIR}/tools/jsdoc) +file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/node_modules/.bin/jsdoc JSDOC_PATH) +file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/config.json JSDOC_CONFIG_PATH) +file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR} NATIVE_JSDOC_WORKING_DIR) + +add_custom_command(TARGET ${TARGET_NAME} + COMMAND (npm --no-progress install) & (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH}) + WORKING_DIRECTORY ${JSDOC_WORKING_DIR} + COMMENT "generate the JSDoc JSON for the JSConsole auto-completer" +) diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index afa3285c37..c434ecc0d6 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -10,6 +10,8 @@ function endsWith(path, exts) { exports.handlers = { beforeParse: function(e) { + const pathTools = require('path'); + var rootFolder = pathTools.dirname(e.filename); console.log("Scanning hifi source for jsdoc comments..."); // directories to scan for jsdoc comments @@ -34,9 +36,10 @@ exports.handlers = { const fs = require('fs'); dirList.forEach(function (dir) { - var files = fs.readdirSync(dir) + var joinedDir = pathTools.join(rootFolder, dir); + var files = fs.readdirSync(joinedDir) files.forEach(function (file) { - var path = dir + "/" + file; + var path = pathTools.join(joinedDir, file); if (fs.lstatSync(path).isFile() && endsWith(path, exts)) { var data = fs.readFileSync(path, "utf8"); var reg = /(\/\*\*jsdoc(.|[\r\n])*?\*\/)/gm; @@ -48,7 +51,7 @@ exports.handlers = { }); }); - fs.writeFile("out/hifiJSDoc.js", e.source, function(err) { + fs.writeFile(pathTools.join(rootFolder, "out/hifiJSDoc.js"), e.source, function(err) { if (err) { return console.log(err); } diff --git a/tools/jsdoc/plugins/hifiJSONExport.js b/tools/jsdoc/plugins/hifiJSONExport.js index 7948fd2673..5531d3ff25 100644 --- a/tools/jsdoc/plugins/hifiJSONExport.js +++ b/tools/jsdoc/plugins/hifiJSONExport.js @@ -1,9 +1,11 @@ exports.handlers = { processingComplete: function(e) { + const pathTools = require('path'); + var rootFolder = pathTools.join(__dirname, '..'); var doclets = e.doclets.map(doclet => Object.assign({}, doclet)); const fs = require('fs'); doclets.map(doclet => {delete doclet.meta; delete doclet.comment}); - fs.writeFile("out/hifiJSDoc.json", JSON.stringify(doclets, null, 4), function(err) { + fs.writeFile(pathTools.join(rootFolder, "out/hifiJSDoc.json"), JSON.stringify(doclets, null, 4), function(err) { if (err) { return console.log(err); } From 58216b947c1de891a6b948000f9030412e0e2f09 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 22 Jan 2018 18:54:24 +0100 Subject: [PATCH 013/116] removal of unnecessary JS export (for now) --- tools/jsdoc/plugins/hifi.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index c434ecc0d6..bb556814e8 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -50,13 +50,5 @@ exports.handlers = { } }); }); - - fs.writeFile(pathTools.join(rootFolder, "out/hifiJSDoc.js"), e.source, function(err) { - if (err) { - return console.log(err); - } - - console.log("The Hifi JSDoc JS was saved!"); - }); } }; From 4ff3c9e0468ca69daf67bc86016100ac238e9beb Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 22 Jan 2018 19:00:45 +0100 Subject: [PATCH 014/116] fix jsdoc output folder --- tools/jsdoc/CMakeLists.txt | 3 ++- tools/jsdoc/plugins/hifiJSONExport.js | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/jsdoc/CMakeLists.txt b/tools/jsdoc/CMakeLists.txt index 9a9883a6ad..265d8e9913 100644 --- a/tools/jsdoc/CMakeLists.txt +++ b/tools/jsdoc/CMakeLists.txt @@ -5,10 +5,11 @@ add_custom_target(${TARGET_NAME}) SET(JSDOC_WORKING_DIR ${CMAKE_SOURCE_DIR}/tools/jsdoc) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/node_modules/.bin/jsdoc JSDOC_PATH) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/config.json JSDOC_CONFIG_PATH) +file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/out OUTPUT_DIR) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR} NATIVE_JSDOC_WORKING_DIR) add_custom_command(TARGET ${TARGET_NAME} - COMMAND (npm --no-progress install) & (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH}) + COMMAND (npm --no-progress install) & (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR}) WORKING_DIRECTORY ${JSDOC_WORKING_DIR} COMMENT "generate the JSDoc JSON for the JSConsole auto-completer" ) diff --git a/tools/jsdoc/plugins/hifiJSONExport.js b/tools/jsdoc/plugins/hifiJSONExport.js index 5531d3ff25..cd14c9faad 100644 --- a/tools/jsdoc/plugins/hifiJSONExport.js +++ b/tools/jsdoc/plugins/hifiJSONExport.js @@ -1,11 +1,14 @@ exports.handlers = { processingComplete: function(e) { const pathTools = require('path'); - var rootFolder = pathTools.join(__dirname, '..'); + var outputFolder = pathTools.join(__dirname, '../out'); var doclets = e.doclets.map(doclet => Object.assign({}, doclet)); const fs = require('fs'); + if (!fs.existsSync(outputFolder)) { + fs.mkdirSync(outputFolder); + } doclets.map(doclet => {delete doclet.meta; delete doclet.comment}); - fs.writeFile(pathTools.join(rootFolder, "out/hifiJSDoc.json"), JSON.stringify(doclets, null, 4), function(err) { + fs.writeFile(pathTools.join(outputFolder, "hifiJSDoc.json"), JSON.stringify(doclets, null, 4), function(err) { if (err) { return console.log(err); } From f4e6606c8d407256686dd557a1bea82fc936ec5d Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 22 Jan 2018 19:01:13 +0100 Subject: [PATCH 015/116] fix MyAvatar JS API typo --- interface/src/avatar/MyAvatar.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 58b49b61ff..086c76dd1a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -81,7 +81,7 @@ class MyAvatar : public Avatar { * and MyAvatar.customListenOrientation properties. * @property customListenPosition {Vec3} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the position * of audio spatialization listener. - * @property customListenOreintation {Quat} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the orientation + * @property customListenOrientation {Quat} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the orientation * of the audio spatialization listener. * @property audioListenerModeHead {number} READ-ONLY. When passed to MyAvatar.audioListenerMode, it will set the audio listener * around the avatar's head. From 59263840769ff54cd2506a1220caffde3df573d9 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 22 Jan 2018 14:52:05 -0800 Subject: [PATCH 016/116] Take defaultPoseFlag section into account when computing byteArraySize. (cherry picked from commit c13c9f301cf263aca26795a7a333c985dd73a7b2) --- libraries/avatars/src/AvatarData.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 9924cc137e..2a3c0452f3 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -308,7 +308,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + (hasFaceTrackerInfo ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getNumSummedBlendshapeCoefficients()) : 0) + - (hasJointData ? AvatarDataPacket::maxJointDataSize(_jointData.size()) : 0); + (hasJointData ? AvatarDataPacket::maxJointDataSize(_jointData.size()) : 0) + + (hasJointDefaultPoseFlags ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0); QByteArray avatarDataByteArray((int)byteArraySize, 0); unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); From 5885569644b4290e29c58e9aaa16175b0f923d3e Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 23 Jan 2018 01:29:52 +0100 Subject: [PATCH 017/116] fix UNIX jsdoc command order --- tools/jsdoc/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/jsdoc/CMakeLists.txt b/tools/jsdoc/CMakeLists.txt index 265d8e9913..36c2af4265 100644 --- a/tools/jsdoc/CMakeLists.txt +++ b/tools/jsdoc/CMakeLists.txt @@ -9,7 +9,7 @@ file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/out OUTPUT_DIR) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR} NATIVE_JSDOC_WORKING_DIR) add_custom_command(TARGET ${TARGET_NAME} - COMMAND (npm --no-progress install) & (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR}) + COMMAND (npm --no-progress install) && (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR}) WORKING_DIRECTORY ${JSDOC_WORKING_DIR} COMMENT "generate the JSDoc JSON for the JSConsole auto-completer" ) From 67effd1ad190ebdd0417591ac0e50fefe2031908 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 23 Jan 2018 02:15:31 +0100 Subject: [PATCH 018/116] create folder --- interface/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 07788e5865..130848fc08 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -302,6 +302,8 @@ if (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/../Resources/scripts" + COMMAND "${CMAKE_COMMAND}" -E make_directory + "$/../Resources/auto-complete" COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" "$/../Resources/auto-complete" @@ -319,6 +321,8 @@ else (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/scripts" + COMMAND "${CMAKE_COMMAND}" -E make_directory + "$/resources/auto-complete" COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" "$/resources/auto-complete" From 3c12ae892545e568606eecd62ac5ee4cc52f4abc Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 22 Jan 2018 20:52:18 -0700 Subject: [PATCH 019/116] Fix Edit/Asset Browser in HMD mode --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ffe2f781db..5854501809 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6195,7 +6195,7 @@ void Application::showAssetServerWidget(QString filePath) { if (!hmd->getShouldShowTablet() && !isHMDMode()) { DependencyManager::get()->show(url, "AssetServer", startUpload); } else { - static const QUrl url("../dialogs/TabletAssetServer.qml"); + static const QUrl url("hifi/dialogs/TabletAssetServer.qml"); tablet->pushOntoStack(url); } } From fddbf13796376c989f7f4c980a690140b207c8c7 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 23 Jan 2018 11:12:22 -0700 Subject: [PATCH 020/116] Fix Asset Browser access from Create --- interface/resources/qml/hifi/tablet/Edit.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/tablet/Edit.qml b/interface/resources/qml/hifi/tablet/Edit.qml index e2e8c4362e..efd797e12d 100644 --- a/interface/resources/qml/hifi/tablet/Edit.qml +++ b/interface/resources/qml/hifi/tablet/Edit.qml @@ -12,7 +12,7 @@ StackView { HifiConstants { id: hifi } function pushSource(path) { - editRoot.push(Qt.resolvedUrl(path)); + editRoot.push(Qt.resolvedUrl("../../" + path)); editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); } From 72ed5f6d91852e0463e69f5876ceb9deb81adaca Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Tue, 23 Jan 2018 13:40:31 -0500 Subject: [PATCH 021/116] [Case 4315] Fixes OK button hard to use within HMD mode (details below). * Shape Color picker dynamically updates entity color. * This removes the OK button which was hard to interact with in HMD mode. The user no longer needs to click the OK button to submit changes to the color; rather the color of the entity updates as the user manipulates the color wheel and/or slider. Known Issue/TODO(s): * Color Picker's Color Preview area has a bug where the original/starting color preview is black as opposed to reflecting the entity's color upon clicking the color picker. * Color preview restore color functionality isn't working. Clicking the initial color preview doesn't restore the color. * In HMD Mode: While interacting with the picker the user is able to control scrolling navigation for the edit menu. For example, when moving up or down to change the hue the edit menu behind the picker will scroll up and down correlatively. * In HMD Mode: When the picker is dismissed (user clicks outside the picker), the RGB fields for the color still receive input. Tested in Desktop & HMD Modes. Changes Committed: modified: scripts/system/html/js/entityProperties.js --- scripts/system/html/js/entityProperties.js | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 72092b66ca..3b6e207ccb 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1040,9 +1040,9 @@ function loaded() { elZoneAmbientLightURL.value = properties.ambientLight.ambientURL; // Haze - elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); + elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); - elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); + elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); elZoneHazeRange.value = properties.haze.hazeRange.toFixed(0); elZoneHazeColor.style.backgroundColor = "rgb(" + @@ -1308,15 +1308,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', + submit: false, // We don't want to have a submission button onShow: function(colpick) { $('#property-color-control2').attr('active', 'true'); }, onHide: function(colpick) { $('#property-color-control2').attr('active', 'false'); }, - onSubmit: function(hsb, hex, rgb, el) { + onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); - $(el).colpickHide(); emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); } })); @@ -1332,15 +1332,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', + submit: false, // We don't want to have a submission button onShow: function(colpick) { $('#property-light-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-light-color').attr('active', 'false'); }, - onSubmit: function(hsb, hex, rgb, el) { + onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); - $(el).colpickHide(); emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); } })); @@ -1387,15 +1387,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', + submit: false, // We don't want to have a submission button onShow: function(colpick) { $('#property-text-text-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-text-text-color').attr('active', 'false'); }, - onSubmit: function(hsb, hex, rgb, el) { + onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); - $(el).colpickHide(); $(el).attr('active', 'false'); emitColorPropertyUpdate('textColor', rgb.r, rgb.g, rgb.b); } @@ -1411,15 +1411,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', + submit: false, // We don't want to have a submission button onShow: function(colpick) { $('#property-text-background-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-text-background-color').attr('active', 'false'); }, - onSubmit: function(hsb, hex, rgb, el) { + onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); - $(el).colpickHide(); emitColorPropertyUpdate('backgroundColor', rgb.r, rgb.g, rgb.b); } })); @@ -1436,15 +1436,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', + submit: false, // We don't want to have a submission button onShow: function(colpick) { $('#property-zone-key-light-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-key-light-color').attr('active', 'false'); }, - onSubmit: function(hsb, hex, rgb, el) { + onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); - $(el).colpickHide(); emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'keyLight'); } })); @@ -1505,15 +1505,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', + submit: false, // We don't want to have a submission button onShow: function(colpick) { $('#property-zone-haze-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-haze-color').attr('active', 'false'); }, - onSubmit: function(hsb, hex, rgb, el) { + onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); - $(el).colpickHide(); emitColorPropertyUpdate('hazeColor', rgb.r, rgb.g, rgb.b, 'haze'); } })); @@ -1530,15 +1530,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', + submit: false, // We don't want to have a submission button onShow: function(colpick) { $('#property-zone-haze-glare-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-haze-glare-color').attr('active', 'false'); }, - onSubmit: function(hsb, hex, rgb, el) { + onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); - $(el).colpickHide(); emitColorPropertyUpdate('hazeGlareColor', rgb.r, rgb.g, rgb.b, 'haze'); } })); @@ -1572,15 +1572,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', + submit: false, // We don't want to have a submission button onShow: function(colpick) { $('#property-zone-skybox-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-skybox-color').attr('active', 'false'); }, - onSubmit: function(hsb, hex, rgb, el) { + onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); - $(el).colpickHide(); emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'skybox'); } })); From 4da7a1e65ae28a4927ceb5a20938625fc5ab5a18 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Tue, 23 Jan 2018 13:48:47 -0500 Subject: [PATCH 022/116] [Case 4315] ESLint pass on entityProperties.js. Re-ran after changes and issue count was 0. Changes Committed: modified: scripts/system/html/js/entityProperties.js --- scripts/system/html/js/entityProperties.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 3b6e207ccb..7008d0df66 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -653,16 +653,16 @@ function loaded() { var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); // Skybox - var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); + var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); var elZoneSkyboxModeDisabled = document.getElementById("property-zone-skybox-mode-disabled"); - var elZoneSkyboxModeEnabled = document.getElementById("property-zone-skybox-mode-enabled"); + var elZoneSkyboxModeEnabled = document.getElementById("property-zone-skybox-mode-enabled"); // Ambient light var elCopySkyboxURLToAmbientURL = document.getElementById("copy-skybox-url-to-ambient-url"); - var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); + var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); - var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); + var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); var elZoneAmbientLightIntensity = document.getElementById("property-zone-key-ambient-intensity"); var elZoneAmbientLightURL = document.getElementById("property-zone-key-ambient-url"); @@ -1013,9 +1013,9 @@ function loaded() { } else if (properties.type === "Zone") { // Key light - elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); + elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); elZoneKeyLightModeDisabled.checked = (properties.keyLightMode === 'disabled'); - elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); + elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; @@ -1027,14 +1027,14 @@ function loaded() { elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2); // Skybox - elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); + elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); elZoneSkyboxModeDisabled.checked = (properties.skyboxMode === 'disabled'); - elZoneSkyboxModeEnabled.checked = (properties.skyboxMode === 'enabled'); + elZoneSkyboxModeEnabled.checked = (properties.skyboxMode === 'enabled'); // Ambient light - elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); + elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); elZoneAmbientLightModeDisabled.checked = (properties.ambientLightMode === 'disabled'); - elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); + elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); elZoneAmbientLightIntensity.value = properties.ambientLight.ambientIntensity.toFixed(2); elZoneAmbientLightURL.value = properties.ambientLight.ambientURL; From 180be18178139099b9e42662d8c1e9282b438238 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Tue, 23 Jan 2018 14:02:31 -0500 Subject: [PATCH 023/116] [Case 4315] Fixes color picker issue in HMD mode (details below). Previously when in HMD mode, using the color picker to select a color via the gradient or hue areas would result in the page scrolling making it difficult to select the color or hue desired. This resolves the issue by turning off touch actions for the elements of the color picker that should have it when using the color picker. Tested in HMD & Desktop mode. Changes Committed: modified: scripts/system/html/css/colpick.css --- scripts/system/html/css/colpick.css | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/system/html/css/colpick.css b/scripts/system/html/css/colpick.css index 564f60cb3b..98417a5e9a 100644 --- a/scripts/system/html/css/colpick.css +++ b/scripts/system/html/css/colpick.css @@ -26,6 +26,7 @@ colpick Color Picker / colpick.com /*Color selection box with gradients*/ .colpick_color { position: absolute; + touch-action: none; left: 7px; top: 7px; width: 156px; @@ -84,6 +85,7 @@ colpick Color Picker / colpick.com /*Vertical hue bar*/ .colpick_hue { position: absolute; + touch-action: none; top: 6px; left: 175px; width: 19px; @@ -94,6 +96,7 @@ colpick Color Picker / colpick.com /*Hue bar sliding indicator*/ .colpick_hue_arrs { position: absolute; + touch-action: none; left: -8px; width: 35px; height: 7px; @@ -101,6 +104,7 @@ colpick Color Picker / colpick.com } .colpick_hue_larr { position:absolute; + touch-action: none; width: 0; height: 0; border-top: 6px solid transparent; @@ -109,6 +113,7 @@ colpick Color Picker / colpick.com } .colpick_hue_rarr { position:absolute; + touch-action: none; right:0; width: 0; height: 0; @@ -119,6 +124,7 @@ colpick Color Picker / colpick.com /*New color box*/ .colpick_new_color { position: absolute; + touch-action: none; left: 207px; top: 6px; width: 60px; @@ -129,6 +135,7 @@ colpick Color Picker / colpick.com /*Current color box*/ .colpick_current_color { position: absolute; + touch-action: none; left: 277px; top: 6px; width: 60px; @@ -139,6 +146,7 @@ colpick Color Picker / colpick.com /*Input field containers*/ .colpick_field, .colpick_hex_field { position: absolute; + touch-action: none; height: 20px; width: 60px; overflow:hidden; @@ -198,6 +206,7 @@ colpick Color Picker / colpick.com /*Text inputs*/ .colpick_field input, .colpick_hex_field input { position: absolute; + touch-action: none; right: 11px; margin: 0; padding: 0; @@ -217,6 +226,7 @@ colpick Color Picker / colpick.com /*Field up/down arrows*/ .colpick_field_arrs { position: absolute; + touch-action: none; top: 0; right: 0; width: 9px; @@ -225,6 +235,7 @@ colpick Color Picker / colpick.com } .colpick_field_uarr { position: absolute; + touch-action: none; top: 5px; width: 0; height: 0; @@ -234,6 +245,7 @@ colpick Color Picker / colpick.com } .colpick_field_darr { position: absolute; + touch-action: none; bottom:5px; width: 0; height: 0; @@ -244,6 +256,7 @@ colpick Color Picker / colpick.com /*Submit/Select button*/ .colpick_submit { position: absolute; + touch-action: none; left: 207px; top: 149px; width: 130px; From 79dbde6a6ff07189c351c8c25639887e3bdc7f13 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 23 Jan 2018 23:01:59 +0100 Subject: [PATCH 024/116] optional auto-complete build (requires NPM installed) --- cmake/macros/FindNPM.cmake | 14 ++++++++++++++ interface/CMakeLists.txt | 35 +++++++++++++++++++++++++---------- tools/CMakeLists.txt | 7 +++++-- tools/jsdoc/CMakeLists.txt | 4 +++- 4 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 cmake/macros/FindNPM.cmake diff --git a/cmake/macros/FindNPM.cmake b/cmake/macros/FindNPM.cmake new file mode 100644 index 0000000000..c66114f878 --- /dev/null +++ b/cmake/macros/FindNPM.cmake @@ -0,0 +1,14 @@ +# +# FindNPM.cmake +# cmake/macros +# +# Created by Thijs Wenker on 01/23/18. +# Copyright 2018 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 +# + +macro(find_npm) + find_program(NPM_EXECUTABLE "npm") +endmacro() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 130848fc08..b03a4637d0 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -41,6 +41,8 @@ endif() file(GLOB_RECURSE INTERFACE_SRCS "src/*.cpp" "src/*.h") GroupSources("src") +find_npm() + # Add SpeechRecognizer if on Windows or OS X, otherwise remove if (WIN32) # Use .cpp and .h files as is. @@ -297,22 +299,38 @@ if (APPLE) set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources") + if (NPM_EXECUTABLE) + set(EXTRA_COPY_COMMANDS + COMMAND "${CMAKE_COMMAND}" -E make_directory + "$/../Resources/auto-complete" + COMMAND "${CMAKE_COMMAND}" -E copy + "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" + "$/../Resources/auto-complete" + ) + endif() + # copy script files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/../Resources/scripts" - COMMAND "${CMAKE_COMMAND}" -E make_directory - "$/../Resources/auto-complete" - COMMAND "${CMAKE_COMMAND}" -E copy - "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" - "$/../Resources/auto-complete" + + ${EXTRA_COPY_COMMANDS} ) # call the fixup_interface macro to add required bundling commands for installation fixup_interface() else (APPLE) + if (NPM_EXECUTABLE) + set(EXTRA_COPY_COMMANDS + COMMAND "${CMAKE_COMMAND}" -E make_directory + "$/resources/auto-complete" + COMMAND "${CMAKE_COMMAND}" -E copy + "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" + "$/resources/auto-complete" + ) + endif() # copy the resources files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory @@ -321,11 +339,8 @@ else (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/scripts" - COMMAND "${CMAKE_COMMAND}" -E make_directory - "$/resources/auto-complete" - COMMAND "${CMAKE_COMMAND}" -E copy - "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" - "$/resources/auto-complete" + + ${EXTRA_COPY_COMMANDS} ) # link target to external libraries diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index ac920d930d..53d7fc2836 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -2,8 +2,11 @@ add_subdirectory(scribe) set_target_properties(scribe PROPERTIES FOLDER "Tools") -add_subdirectory(jsdoc) -set_target_properties(jsdoc PROPERTIES FOLDER "Tools") +find_npm() +if (NPM_EXECUTABLE) + add_subdirectory(jsdoc) + set_target_properties(jsdoc PROPERTIES FOLDER "Tools") +endif() if (BUILD_TOOLS) add_subdirectory(udt-test) diff --git a/tools/jsdoc/CMakeLists.txt b/tools/jsdoc/CMakeLists.txt index 36c2af4265..b52d229130 100644 --- a/tools/jsdoc/CMakeLists.txt +++ b/tools/jsdoc/CMakeLists.txt @@ -2,6 +2,8 @@ set(TARGET_NAME jsdoc) add_custom_target(${TARGET_NAME}) +find_npm() + SET(JSDOC_WORKING_DIR ${CMAKE_SOURCE_DIR}/tools/jsdoc) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/node_modules/.bin/jsdoc JSDOC_PATH) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/config.json JSDOC_CONFIG_PATH) @@ -9,7 +11,7 @@ file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/out OUTPUT_DIR) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR} NATIVE_JSDOC_WORKING_DIR) add_custom_command(TARGET ${TARGET_NAME} - COMMAND (npm --no-progress install) && (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR}) + COMMAND (${NPM_EXECUTABLE} --no-progress install) && (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR}) WORKING_DIRECTORY ${JSDOC_WORKING_DIR} COMMENT "generate the JSDoc JSON for the JSConsole auto-completer" ) From 29dd24b05f1a060c1f2700a57e00ffe61c1d2988 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 23 Jan 2018 15:50:32 -0700 Subject: [PATCH 025/116] Fix GoTo app in HMD mode --- interface/resources/qml/hifi/Feed.qml | 3 ++- interface/resources/qml/hifi/tablet/TabletAddressDialog.qml | 1 + scripts/system/tablet-goto.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index a9629c0e4e..3f3a47a297 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -33,6 +33,7 @@ Column { property int labelSize: 20; property string metaverseServerUrl: ''; + property string protocol: ''; property string actions: 'snapshot'; // sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick. property string labelText: actions; @@ -102,7 +103,7 @@ Column { 'include_actions=' + actions, 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), 'require_online=true', - 'protocol=' + encodeURIComponent(Window.protocolSignature()), + 'protocol=' + protocol, 'page=' + pageNumber ]; var url = metaverseBase + 'user_stories?' + options.join('&'); diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index f3cda533e9..d4550d3843 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -54,6 +54,7 @@ StackView { console.debug('TabletAddressDialog::fromScript: refreshFeeds', 'feeds = ', feeds); feeds.forEach(function(feed) { + feed.protocol = encodeURIComponent(message.protocolSignature); Qt.callLater(feed.fillDestinations); }); diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 43dae9625a..9cd8420a88 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -100,7 +100,7 @@ button.editProperties({isActive: shouldActivateButton}); wireEventBridge(true); messagesWaiting(false); - tablet.sendToQml({ method: 'refreshFeeds' }) + tablet.sendToQml({ method: 'refreshFeeds', protocolSignature: Window.protocolSignature() }) } else { shouldActivateButton = false; From c46a1150006690ab8353bd888c0add5b55ec77cc Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 24 Jan 2018 01:45:53 +0100 Subject: [PATCH 026/116] attempt to fix ubuntu build --- tools/jsdoc/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/jsdoc/CMakeLists.txt b/tools/jsdoc/CMakeLists.txt index b52d229130..292523a813 100644 --- a/tools/jsdoc/CMakeLists.txt +++ b/tools/jsdoc/CMakeLists.txt @@ -4,14 +4,14 @@ add_custom_target(${TARGET_NAME}) find_npm() -SET(JSDOC_WORKING_DIR ${CMAKE_SOURCE_DIR}/tools/jsdoc) +set(JSDOC_WORKING_DIR ${CMAKE_SOURCE_DIR}/tools/jsdoc) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/node_modules/.bin/jsdoc JSDOC_PATH) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/config.json JSDOC_CONFIG_PATH) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/out OUTPUT_DIR) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR} NATIVE_JSDOC_WORKING_DIR) add_custom_command(TARGET ${TARGET_NAME} - COMMAND (${NPM_EXECUTABLE} --no-progress install) && (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR}) + COMMAND ${NPM_EXECUTABLE} --no-progress install && ${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR} WORKING_DIRECTORY ${JSDOC_WORKING_DIR} COMMENT "generate the JSDoc JSON for the JSConsole auto-completer" ) From 1529fea9837a75bb490d4bb58c5acde1bade004f Mon Sep 17 00:00:00 2001 From: John Conklin II Date: Fri, 19 Jan 2018 15:15:52 -0800 Subject: [PATCH 027/116] Revert "Display both lasers on tablet and Web surfaces" (cherry picked from commit 455090d2b94c6b6dd26b863bd8ec1b15936200fa) --- .../src/raypick/PointerScriptingInterface.cpp | 6 +- .../src/raypick/PointerScriptingInterface.h | 8 -- libraries/pointers/src/Pointer.cpp | 15 +--- libraries/pointers/src/Pointer.h | 3 +- libraries/pointers/src/PointerManager.cpp | 7 -- libraries/pointers/src/PointerManager.h | 1 - .../controllers/controllerDispatcher.js | 42 +--------- .../controllerModules/webSurfaceLaserInput.js | 84 ++++--------------- 8 files changed, 25 insertions(+), 141 deletions(-) diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index a334834979..ac5a467e76 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -175,8 +175,4 @@ QVariantMap PointerScriptingInterface::getPrevPickResult(unsigned int uid) const result = pickResult->toVariantMap(); } return result; -} - -void PointerScriptingInterface::setDoesHover(unsigned int uid, bool hover) const { - DependencyManager::get()->setDoesHover(uid, hover); -} +} \ No newline at end of file diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 451c132769..1cc7b56503 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -202,14 +202,6 @@ public: */ Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isOverlay, offsetMat); } - /**jsdoc - * Sets whether or not a pointer should generate hover events. - * @function Pointers.setDoesHover - * @param {boolean} uid - The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @param {boolean} hover - If true then the pointer generates hover events, otherwise it does not. - */ - Q_INVOKABLE void setDoesHover(unsigned int uid, bool hove) const; - /**jsdoc * Check if a Pointer is associated with the left hand. * @function Pointers.isLeftHand diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index 287d5a3c97..5307e17355 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -64,12 +64,6 @@ bool Pointer::isMouse() const { return DependencyManager::get()->isMouse(_pickUID); } -void Pointer::setDoesHover(bool doesHover) { - withWriteLock([&] { - _hover = doesHover; - }); -} - void Pointer::update(unsigned int pointerID) { // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts withReadLock([&] { @@ -101,8 +95,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } // Hover events - bool doHover = _hover && shouldHover(pickResult); - + bool doHover = shouldHover(pickResult); Pointer::PickedObject hoveredObject = getHoveredObject(pickResult); PointerEvent hoveredEvent = buildPointerEvent(hoveredObject, pickResult); hoveredEvent.setType(PointerEvent::Move); @@ -111,7 +104,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin hoveredEvent.setMoveOnHoverLeave(moveOnHoverLeave); // if shouldHover && !_prevDoHover, only send hoverBegin - if (_enabled && doHover && !_prevDoHover) { + if (_enabled && _hover && doHover && !_prevDoHover) { if (hoveredObject.type == ENTITY) { emit pointerManager->hoverBeginEntity(hoveredObject.objectID, hoveredEvent); } else if (hoveredObject.type == OVERLAY) { @@ -119,7 +112,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } else if (hoveredObject.type == HUD) { emit pointerManager->hoverBeginHUD(hoveredEvent); } - } else if (_enabled && doHover) { + } else if (_enabled && _hover && doHover) { if (hoveredObject.type == OVERLAY) { if (_prevHoveredObject.type == OVERLAY) { if (hoveredObject.objectID == _prevHoveredObject.objectID) { @@ -236,7 +229,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } // if we disable the pointer or disable hovering, send hoverEnd events after triggerEnd - if ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover)) { + if (_hover && ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover))) { if (_prevHoveredObject.type == ENTITY) { emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); } else if (_prevHoveredObject.type == OVERLAY) { diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index 9fd434fb15..3197c80cad 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -62,8 +62,6 @@ public: virtual void setLength(float length) {} virtual void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) {} - virtual void setDoesHover(bool hover); - void update(unsigned int pointerID); virtual void updateVisuals(const PickResultPointer& pickResult) = 0; void generatePointerEvents(unsigned int pointerID, const PickResultPointer& pickResult); @@ -103,6 +101,7 @@ private: std::unordered_map _triggeredObjects; PointerEvent::Button chooseButton(const std::string& button); + }; #endif // hifi_Pick_h diff --git a/libraries/pointers/src/PointerManager.cpp b/libraries/pointers/src/PointerManager.cpp index 13b38457b6..be890da392 100644 --- a/libraries/pointers/src/PointerManager.cpp +++ b/libraries/pointers/src/PointerManager.cpp @@ -122,13 +122,6 @@ void PointerManager::setLockEndUUID(unsigned int uid, const QUuid& objectID, boo } } -void PointerManager::setDoesHover(unsigned int uid, bool hover) const { - auto pointer = find(uid); - if (pointer) { - pointer->setDoesHover(hover); - } -} - bool PointerManager::isLeftHand(unsigned int uid) { auto pointer = find(uid); if (pointer) { diff --git a/libraries/pointers/src/PointerManager.h b/libraries/pointers/src/PointerManager.h index 2c9a37e129..b98558622f 100644 --- a/libraries/pointers/src/PointerManager.h +++ b/libraries/pointers/src/PointerManager.h @@ -37,7 +37,6 @@ public: void setLength(unsigned int uid, float length) const; void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const; - void setDoesHover(unsigned int uid, bool hover) const; void update(); diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index a8658933e7..16f1d086b7 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -44,12 +44,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.highVarianceCount = 0; this.veryhighVarianceCount = 0; this.tabletID = null; - this.TABLET_UI_UUIDS = []; this.blacklist = []; - this.leftPointerDoesHover = true; - this.leftPointerDoesHoverChanged = false; - this.rightPointerDoesHover = true; - this.rightPointerDoesHoverChanged = false; this.pointerManager = new PointerManager(); // a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are @@ -127,10 +122,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); return getControllerWorldLocation(Controller.Standard.RightHand, true); }; - this.isTabletID = function (uuid) { - return _this.TABLET_UI_UUIDS.indexOf(uuid) !== -1; - }; - this.updateTimings = function () { _this.intervalCount++; var thisInterval = Date.now(); @@ -157,35 +148,11 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.setIgnorePointerItems = function() { if (HMD.tabletID !== this.tabletID) { this.tabletID = HMD.tabletID; - this.TABLET_UI_UUIDS = [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, HMD.homeButtonHighlightID]; Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist); Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist); } }; - this.updateDoesHover = function(handLaser, doesHover) { - if (handLaser.doesHover !== undefined) { - if (handLaser.hand === LEFT_HAND && _this.leftPointerDoesHover !== doesHover) { - _this.leftPointerDoesHover = doesHover; - _this.leftPointerDoesHoverChanged = true; - } else if (handLaser.hand === RIGHT_HAND && _this.rightPointerDoesHover !== doesHover) { - _this.rightPointerDoesHover = doesHover; - _this.rightPointerDoesHoverChanged = true; - } - } - } - - this.updateHovering = function () { - if (_this.leftPointerDoesHoverChanged) { - Pointers.setDoesHover(_this.leftPointer, _this.leftPointerDoesHover); - _this.leftPointerDoesHoverChanged = false; - } - if (_this.rightPointerDoesHoverChanged) { - Pointers.setDoesHover(_this.rightPointer, _this.rightPointerDoesHover); - _this.rightPointerDoesHoverChanged = false; - } - }; - this.update = function () { try { _this.updateInternal(); @@ -357,8 +324,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); _this.runningPluginNames[orderedPluginName] = true; _this.markSlots(candidatePlugin, orderedPluginName); _this.pointerManager.makePointerVisible(candidatePlugin.parameters.handLaser); - _this.updateDoesHover(candidatePlugin.parameters.handLaser, - candidatePlugin.parameters.handLaser.doesHover); if (DEBUG) { print("controllerDispatcher running " + orderedPluginName); } @@ -389,15 +354,12 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.beginProfileRange("dispatch.run." + runningPluginName); } var runningness = plugin.run(controllerData, deltaTime); - if (runningness.active) { - _this.updateDoesHover(plugin.parameters.handLaser, plugin.parameters.handLaser.doesHover); - } else { + if (!runningness.active) { // plugin is finished running, for now. remove it from the list // of running plugins and mark its activity-slots as "not in use" delete _this.runningPluginNames[runningPluginName]; _this.markSlots(plugin, false); _this.pointerManager.makePointerInvisible(plugin.parameters.handLaser); - _this.updateDoesHover(plugin.parameters.handLaser, true); if (DEBUG) { print("controllerDispatcher stopping " + runningPluginName); } @@ -410,8 +372,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } } _this.pointerManager.updatePointersRenderState(controllerData.triggerClicks, controllerData.triggerValues); - _this.updateHovering(); - if (PROFILE) { Script.endProfileRange("dispatch.run"); } diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index cc8378af84..3d9d7979d5 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -87,89 +87,41 @@ Script.include("/~/system/libraries/controllers.js"); return MyAvatar.getDominantHand() === "right" ? 1 : 0; }; - this.letOtherHandRunFirst = function (controllerData, pointingAt) { - // If both hands are ready to run, let the other hand run first if it is the dominant hand so that it gets the - // highlight. - var isOtherTriggerPressed = controllerData.triggerValues[this.otherHand] > TRIGGER_OFF_VALUE; - var isLetOtherHandRunFirst = !this.getOtherModule().running - && this.getDominantHand() === this.otherHand - && (this.parameters.handLaser.allwaysOn || isOtherTriggerPressed); - if (isLetOtherHandRunFirst) { - var otherHandPointingAt = controllerData.rayPicks[this.otherHand].objectID; - if (this.isTabletID(otherHandPointingAt)) { - otherHandPointingAt = HMD.tabletID; - } - isLetOtherHandRunFirst = pointingAt === otherHandPointingAt; - } - return isLetOtherHandRunFirst; - }; - - this.hoverItem = null; - - this.isTabletID = function (uuid) { - return [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, HMD.homeButtonHighlightID].indexOf(uuid) !== -1; - }; + this.dominantHandOverride = false; this.isReady = function(controllerData) { - if (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)) { + var otherModuleRunning = this.getOtherModule().running; + otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. + var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE + && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; + if ((!otherModuleRunning || isTriggerPressed) + && (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData))) { this.updateAllwaysOn(); - - var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; + if (isTriggerPressed) { + this.dominantHandOverride = true; // Override dominant hand. + this.getOtherModule().dominantHandOverride = false; + } if (this.parameters.handLaser.allwaysOn || isTriggerPressed) { - var pointingAt = controllerData.rayPicks[this.hand].objectID; - if (this.isTabletID(pointingAt)) { - pointingAt = HMD.tabletID; - } - - if (!this.letOtherHandRunFirst(controllerData, pointingAt)) { - - if (pointingAt !== this.getOtherModule().hoverItem) { - this.parameters.handLaser.doesHover = true; - this.hoverItem = pointingAt; - } else { - this.parameters.handLaser.doesHover = false; - this.hoverItem = null; - } - - return makeRunningValues(true, [], []); - } + return makeRunningValues(true, [], []); } } - - this.parameters.handLaser.doesHover = false; - this.hoverItem = null; - return makeRunningValues(false, [], []); }; this.run = function(controllerData, deltaTime) { + var otherModuleRunning = this.getOtherModule().running; + otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. + otherModuleRunning = otherModuleRunning || this.getOtherModule().dominantHandOverride; // Override dominant hand. var grabModuleNeedsToRun = this.grabModuleWantsNearbyOverlay(controllerData); - var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; - if (!grabModuleNeedsToRun && (isTriggerPressed || this.parameters.handLaser.allwaysOn + if (!otherModuleRunning && !grabModuleNeedsToRun && (controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE + || this.parameters.handLaser.allwaysOn && (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)))) { this.running = true; - - var pointingAt = controllerData.rayPicks[this.hand].objectID; - if (this.isTabletID(pointingAt)) { - pointingAt = HMD.tabletID; - } - - if (pointingAt !== this.getOtherModule().hoverItem || isTriggerPressed) { - this.parameters.handLaser.doesHover = true; - this.hoverItem = pointingAt; - } else { - this.parameters.handLaser.doesHover = false; - this.hoverItem = null; - } - return makeRunningValues(true, [], []); } this.deleteContextOverlay(); this.running = false; - - this.parameters.handLaser.doesHover = false; - this.hoverItem = null; - + this.dominantHandOverride = false; return makeRunningValues(false, [], []); }; } From 6149630ccd52c4695de7a611f03e1731226a5e8f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 24 Jan 2018 16:36:08 -0800 Subject: [PATCH 028/116] don't manually highlight tablet buttons from swipeview, since the tablet button highligh themselves --- .../resources/qml/hifi/tablet/TabletButton.qml | 1 - .../resources/qml/hifi/tablet/TabletHome.qml | 15 --------------- 2 files changed, 16 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletButton.qml b/interface/resources/qml/hifi/tablet/TabletButton.qml index 4d443fb97c..5f49f9f10b 100644 --- a/interface/resources/qml/hifi/tablet/TabletButton.qml +++ b/interface/resources/qml/hifi/tablet/TabletButton.qml @@ -158,7 +158,6 @@ Item { gridView.currentIndex = buttonIndex tabletButton.isEntered = true; Tablet.playSound(TabletEnums.ButtonHover); - if (tabletButton.isActive) { tabletButton.state = "hover active state"; } else { diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml index 524bbf5f4d..88d8194c49 100644 --- a/interface/resources/qml/hifi/tablet/TabletHome.qml +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -144,22 +144,7 @@ Item { bottomMargin: 0 } - function setButtonState(buttonIndex, buttonstate) { - if (buttonIndex < 0 || gridView.contentItem === undefined - || gridView.contentItem.children.length - 1 < buttonIndex) { - return; - } - var itemat = gridView.contentItem.children[buttonIndex].children[0]; - if (itemat.isActive) { - itemat.state = "active state"; - } else { - itemat.state = buttonstate; - } - } - onCurrentIndexChanged: { - setButtonState(previousGridIndex, "base state"); - setButtonState(currentIndex, "hover state"); previousGridIndex = currentIndex } From c73c0487a09938c925493b406340311e4096c0a8 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 24 Jan 2018 16:41:19 -0800 Subject: [PATCH 029/116] min diff --- interface/resources/qml/hifi/tablet/TabletButton.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/resources/qml/hifi/tablet/TabletButton.qml b/interface/resources/qml/hifi/tablet/TabletButton.qml index 5f49f9f10b..4d443fb97c 100644 --- a/interface/resources/qml/hifi/tablet/TabletButton.qml +++ b/interface/resources/qml/hifi/tablet/TabletButton.qml @@ -158,6 +158,7 @@ Item { gridView.currentIndex = buttonIndex tabletButton.isEntered = true; Tablet.playSound(TabletEnums.ButtonHover); + if (tabletButton.isActive) { tabletButton.state = "hover active state"; } else { From 58d3d8ef3dc421bf39743a7f86cbf1bf3f9944a0 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 24 Jan 2018 18:58:08 -0800 Subject: [PATCH 030/116] Bug fix for incorrect positioned eyes on other peoples avatars There was a bug in writeBitVector(), where the last byte was not consistantly written into the destination buffer. A unit test was added to verify that writeBitVector() and readBitVector() are correct. (cherry picked from commit a252e90f969e85503f2a83380a6e101026732783) --- libraries/shared/src/BitVectorHelpers.h | 16 +++-- tests/shared/src/BitVectorHelperTests.cpp | 80 +++++++++++++++++++++++ tests/shared/src/BitVectorHelperTests.h | 23 +++++++ 3 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 tests/shared/src/BitVectorHelperTests.cpp create mode 100644 tests/shared/src/BitVectorHelperTests.h diff --git a/libraries/shared/src/BitVectorHelpers.h b/libraries/shared/src/BitVectorHelpers.h index 71826036eb..9f5af0c380 100644 --- a/libraries/shared/src/BitVectorHelpers.h +++ b/libraries/shared/src/BitVectorHelpers.h @@ -12,14 +12,14 @@ #ifndef hifi_BitVectorHelpers_h #define hifi_BitVectorHelpers_h -size_t calcBitVectorSize(int numBits) { +int calcBitVectorSize(int numBits) { return ((numBits - 1) >> 3) + 1; } // func should be of type bool func(int index) template -size_t writeBitVector(uint8_t* destinationBuffer, int numBits, const F& func) { - size_t totalBytes = ((numBits - 1) >> 3) + 1; +int writeBitVector(uint8_t* destinationBuffer, int numBits, const F& func) { + int totalBytes = calcBitVectorSize(numBits); uint8_t* cursor = destinationBuffer; uint8_t byte = 0; uint8_t bit = 0; @@ -34,13 +34,19 @@ size_t writeBitVector(uint8_t* destinationBuffer, int numBits, const F& func) { bit = 0; } } + // write the last byte, if necessary + if (bit != 0) { + *cursor++ = byte; + } + + assert((int)(cursor - destinationBuffer) == totalBytes); return totalBytes; } // func should be of type 'void func(int index, bool value)' template -size_t readBitVector(const uint8_t* sourceBuffer, int numBits, const F& func) { - size_t totalBytes = ((numBits - 1) >> 3) + 1; +int readBitVector(const uint8_t* sourceBuffer, int numBits, const F& func) { + int totalBytes = calcBitVectorSize(numBits); const uint8_t* cursor = sourceBuffer; uint8_t bit = 0; diff --git a/tests/shared/src/BitVectorHelperTests.cpp b/tests/shared/src/BitVectorHelperTests.cpp new file mode 100644 index 0000000000..070e90eec7 --- /dev/null +++ b/tests/shared/src/BitVectorHelperTests.cpp @@ -0,0 +1,80 @@ +// +// BitVectorHelperTests.cpp +// tests/shared/src +// +// Copyright 2018 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 "BitVectorHelperTests.h" + +#include + +#include "../QTestExtensions.h" +#include +#include +#include +#include + +QTEST_MAIN(BitVectorHelperTests) + +const int BITS_IN_BYTE = 8; + +void BitVectorHelperTests::sizeTest() { + std::vector sizes = {0, 6, 7, 8, 30, 31, 32, 33, 87, 88, 89, 90, 90, 91, 92, 93}; + for (auto& size : sizes) { + const int oldWay = (int)ceil((float)size / (float)BITS_IN_BYTE); + const int newWay = (int)calcBitVectorSize(size); + QCOMPARE(oldWay, newWay); + } +} + +static void readWriteHelper(const std::vector& src) { + + int numBits = (int)src.size(); + int numBytes = calcBitVectorSize(numBits); + uint8_t* bytes = new uint8_t[numBytes]; + memset(bytes, numBytes, sizeof(uint8_t)); + int numBytesWritten = writeBitVector(bytes, numBits, [&](int i) { + return src[i]; + }); + QCOMPARE(numBytesWritten, numBytes); + + std::vector dst; + int numBytesRead = readBitVector(bytes, numBits, [&](int i, bool value) { + dst.push_back(value); + }); + QCOMPARE(numBytesRead, numBytes); + + QCOMPARE(numBits, (int)src.size()); + QCOMPARE(numBits, (int)dst.size()); + for (int i = 0; i < numBits; i++) { + bool a = src[i]; + bool b = dst[i]; + QCOMPARE(a, b); + } +} + +void BitVectorHelperTests::readWriteTest() { + std::vector sizes = {0, 6, 7, 8, 30, 31, 32, 33, 87, 88, 89, 90, 90, 91, 92, 93}; + + for (auto& size : sizes) { + std::vector allTrue(size, true); + std::vector allFalse(size, false); + std::vector evenSet; + evenSet.reserve(size); + std::vector oddSet; + oddSet.reserve(size); + for (int i = 0; i < size; i++) { + bool isOdd = (i & 0x1) > 0; + evenSet.push_back(!isOdd); + oddSet.push_back(isOdd); + } + readWriteHelper(allTrue); + readWriteHelper(allFalse); + readWriteHelper(evenSet); + readWriteHelper(oddSet); + } +} diff --git a/tests/shared/src/BitVectorHelperTests.h b/tests/shared/src/BitVectorHelperTests.h new file mode 100644 index 0000000000..1f52ba1ac9 --- /dev/null +++ b/tests/shared/src/BitVectorHelperTests.h @@ -0,0 +1,23 @@ +// +// BitVectorHelperTests.h +// tests/shared/src +// +// Copyright 2018 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_BitVectorHelperTests_h +#define hifi_BitVectorHelperTests_h + +#include + +class BitVectorHelperTests : public QObject { + Q_OBJECT +private slots: + void sizeTest(); + void readWriteTest(); +}; + +#endif // hifi_BitVectorHelperTests_h From 7ee560899222142202291afee62979926a9c3dd5 Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Thu, 25 Jan 2018 12:02:26 -0800 Subject: [PATCH 031/116] initial first pass of exposing headless verification --- .../ui/overlays/ContextOverlayInterface.cpp | 192 ++++++++++-------- .../src/ui/overlays/ContextOverlayInterface.h | 30 +-- 2 files changed, 126 insertions(+), 96 deletions(-) diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index d690880f99..3886d5ad10 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -168,7 +168,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& _contextOverlay->setColorPulse(CONTEXT_OVERLAY_UNHOVERED_COLORPULSE); _contextOverlay->setIgnoreRayIntersection(false); _contextOverlay->setDrawInFront(true); - _contextOverlay->setURL(PathUtils::resourcesPath() + "images/inspect-icon.png"); + _contextOverlay->setURL(PathUtils::resourcesUrl() + "images/inspect-icon.png"); _contextOverlay->setIsFacingAvatar(true); _contextOverlayID = qApp->getOverlays().addOverlay(_contextOverlay); } @@ -189,6 +189,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& return false; } + bool ContextOverlayInterface::contextOverlayFilterPassed(const EntityItemID& entityItemID) { EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags); Setting::Handle _settingSwitch{ "commerce", true }; @@ -266,6 +267,112 @@ void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemI } } +bool ContextOverlayInterface::getLastInspectedEntityWasValid() { + if (!_lastInspectedEntity.isNull() && !_lastInspectedValidEntity.isNull()) { + return _lastInspectedEntity == _lastInspectedValidEntity; + } + return false; +} + +void ContextOverlayInterface::requestEntityOwnershipVerification(const QUuid& entityItemID) { + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags); + _entityMarketplaceID = entityProperties.getMarketplaceID(); + if (!entityItemID.isNull() && _entityMarketplaceID.length() > 0) { + setLastInspectedEntity(entityItemID); + requestOwnershipVerification(); + } +} + +void ContextOverlayInterface::requestOwnershipVerification() { + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags); + + auto nodeList = DependencyManager::get(); + + if (entityProperties.getClientOnly()) { + if (entityProperties.verifyStaticCertificateProperties()) { + SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer); + + if (entityServer) { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest; + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); + requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer"); + QJsonObject request; + request["certificate_id"] = entityProperties.getCertificateID(); + networkRequest.setUrl(requestURL); + + QNetworkReply* networkReply = NULL; + networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + + connect(networkReply, &QNetworkReply::finished, [=]() { + QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); + jsonObject = jsonObject["data"].toObject(); + + if (networkReply->error() == QNetworkReply::NoError) { + if (!jsonObject["invalid_reason"].toString().isEmpty()) { + qCDebug(entities) << "invalid_reason not empty"; + } + else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") { + qCDebug(entities) << "'transfer_status' is 'failed'"; + } + else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { + qCDebug(entities) << "'transfer_status' is 'pending'"; + } + else { + QString ownerKey = jsonObject["transfer_recipient_key"].toString(); + + QByteArray certID = entityProperties.getCertificateID().toUtf8(); + QByteArray text = DependencyManager::get()->getTree()->computeNonce(certID, ownerKey); + QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122(); + + int certIDByteArraySize = certID.length(); + int textByteArraySize = text.length(); + int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length(); + + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, + certIDByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(textByteArraySize); + challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize); + challengeOwnershipPacket->write(certID); + challengeOwnershipPacket->write(text); + challengeOwnershipPacket->write(nodeToChallengeByteArray); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer); + + // Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer"); + return; + } + else { + startChallengeOwnershipTimer(); + } + } + } + else { + qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << + "More info:" << networkReply->readAll(); + } + + networkReply->deleteLater(); + }); + } + else { + qCWarning(context_overlay) << "Couldn't get Entity Server!"; + } + } + else { + auto ledger = DependencyManager::get(); + _challengeOwnershipTimeoutTimer.stop(); + emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); + qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!"; + } + } +} + static const QString INSPECTION_CERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; void ContextOverlayInterface::openInspectionCertificate() { // lets open the tablet to the inspection certificate QML @@ -275,87 +382,7 @@ void ContextOverlayInterface::openInspectionCertificate() { _hmdScriptingInterface->openTablet(); setLastInspectedEntity(_currentEntityWithContextOverlay); - - EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags); - - auto nodeList = DependencyManager::get(); - - if (entityProperties.getClientOnly()) { - if (entityProperties.verifyStaticCertificateProperties()) { - SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer); - - if (entityServer) { - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest; - networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); - requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer"); - QJsonObject request; - request["certificate_id"] = entityProperties.getCertificateID(); - networkRequest.setUrl(requestURL); - - QNetworkReply* networkReply = NULL; - networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); - - connect(networkReply, &QNetworkReply::finished, [=]() { - QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); - jsonObject = jsonObject["data"].toObject(); - - if (networkReply->error() == QNetworkReply::NoError) { - if (!jsonObject["invalid_reason"].toString().isEmpty()) { - qCDebug(entities) << "invalid_reason not empty"; - } else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") { - qCDebug(entities) << "'transfer_status' is 'failed'"; - } else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { - qCDebug(entities) << "'transfer_status' is 'pending'"; - } else { - QString ownerKey = jsonObject["transfer_recipient_key"].toString(); - - QByteArray certID = entityProperties.getCertificateID().toUtf8(); - QByteArray text = DependencyManager::get()->getTree()->computeNonce(certID, ownerKey); - QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122(); - - int certIDByteArraySize = certID.length(); - int textByteArraySize = text.length(); - int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length(); - - auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, - certIDByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int), - true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); - challengeOwnershipPacket->writePrimitive(textByteArraySize); - challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize); - challengeOwnershipPacket->write(certID); - challengeOwnershipPacket->write(text); - challengeOwnershipPacket->write(nodeToChallengeByteArray); - nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer); - - // Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time - if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer"); - return; - } else { - startChallengeOwnershipTimer(); - } - } - } else { - qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << - "More info:" << networkReply->readAll(); - } - - networkReply->deleteLater(); - }); - } else { - qCWarning(context_overlay) << "Couldn't get Entity Server!"; - } - } else { - auto ledger = DependencyManager::get(); - _challengeOwnershipTimeoutTimer.stop(); - emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); - qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!"; - } - } + requestOwnershipVerification(); } } @@ -421,6 +448,7 @@ void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer if (verificationSuccess) { emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); + _lastInspectedValidEntity = _lastInspectedEntity; } else { emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED)); } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index 990a7fe599..389b377bd5 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -33,24 +33,25 @@ /**jsdoc * @namespace ContextOverlay */ -class ContextOverlayInterface : public QObject, public Dependency { +class ContextOverlayInterface : public QObject, public Dependency { Q_OBJECT - Q_PROPERTY(QUuid entityWithContextOverlay READ getCurrentEntityWithContextOverlay WRITE setCurrentEntityWithContextOverlay) - Q_PROPERTY(bool enabled READ getEnabled WRITE setEnabled) - Q_PROPERTY(bool isInMarketplaceInspectionMode READ getIsInMarketplaceInspectionMode WRITE setIsInMarketplaceInspectionMode) - QSharedPointer _entityScriptingInterface; + Q_PROPERTY(QUuid entityWithContextOverlay READ getCurrentEntityWithContextOverlay WRITE setCurrentEntityWithContextOverlay) + Q_PROPERTY(bool enabled READ getEnabled WRITE setEnabled) + Q_PROPERTY(bool isInMarketplaceInspectionMode READ getIsInMarketplaceInspectionMode WRITE setIsInMarketplaceInspectionMode) + + QSharedPointer _entityScriptingInterface; EntityPropertyFlags _entityPropertyFlags; QSharedPointer _hmdScriptingInterface; QSharedPointer _tabletScriptingInterface; QSharedPointer _selectionScriptingInterface; - OverlayID _contextOverlayID { UNKNOWN_OVERLAY_ID }; - std::shared_ptr _contextOverlay { nullptr }; + OverlayID _contextOverlayID{ UNKNOWN_OVERLAY_ID }; + std::shared_ptr _contextOverlay{ nullptr }; public: - ContextOverlayInterface(); - Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; } + Q_INVOKABLE bool getLastInspectedEntityWasValid(); + Q_INVOKABLE void requestEntityOwnershipVerification(const QUuid& entityID); void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; } void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; } void setEnabled(bool enabled); @@ -61,7 +62,7 @@ public: signals: void contextOverlayClicked(const QUuid& currentEntityWithContextOverlay); -public slots: + public slots: bool createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event); bool destroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event); bool destroyContextOverlay(const EntityItemID& entityItemID); @@ -72,7 +73,7 @@ public slots: void contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event); bool contextOverlayFilterPassed(const EntityItemID& entityItemID); -private slots: + private slots: void handleChallengeOwnershipReplyPacket(QSharedPointer packet, SharedNodePointer sendingNode); private: @@ -80,17 +81,18 @@ private: enum { MAX_SELECTION_COUNT = 16 }; - - bool _verboseLogging { true }; - bool _enabled { true }; + bool _verboseLogging{ true }; + bool _enabled{ true }; EntityItemID _currentEntityWithContextOverlay{}; EntityItemID _lastInspectedEntity{}; + EntityItemID _lastInspectedValidEntity{}; QString _entityMarketplaceID; bool _contextOverlayJustClicked { false }; bool _isInMarketplaceInspectionMode { false }; void openInspectionCertificate(); + void requestOwnershipVerification(); void openMarketplace(); void enableEntityHighlight(const EntityItemID& entityItemID); void disableEntityHighlight(const EntityItemID& entityItemID); From 179c21acf4b3c35752dfd648de52c47700fea982 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 25 Jan 2018 22:46:30 +0100 Subject: [PATCH 032/116] jsdoc info on selected auto-complete entry --- interface/src/ui/JSConsole.cpp | 145 ++++++++++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 11 deletions(-) diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp index 3100b47882..1ca1ac2842 100644 --- a/interface/src/ui/JSConsole.cpp +++ b/interface/src/ui/JSConsole.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -39,6 +40,21 @@ const QString RESULT_ERROR_STYLE = "color: #d13b22;"; const QString GUTTER_PREVIOUS_COMMAND = "<"; const QString GUTTER_ERROR = "X"; +const QString JSDOC_LINE_SEPARATOR = "\r"; + +const QString JSDOC_STYLE = + ""; + const QString JSConsole::_consoleFileName { "about:console" }; const QString JSON_KEY = "entries"; @@ -50,7 +66,7 @@ QList _readLines(const QString& filename) { // TODO: check root["version"] return root[JSON_KEY].toVariant().toStringList(); } - + void _writeLines(const QString& filename, const QList& lines) { QFile file(filename); file.open(QFile::WriteOnly); @@ -62,6 +78,10 @@ void _writeLines(const QString& filename, const QList& lines) { QTextStream(&file) << json; } +QString _jsdocTypeToString(QJsonValue jsdocType) { + return jsdocType.toObject().value("names").toVariant().toStringList().join("/"); +} + void JSConsole::readAPI() { QFile file(PathUtils::resourcesPath() + "auto-complete/hifiJSDoc.json"); file.open(QFile::ReadOnly); @@ -102,7 +122,10 @@ QStandardItemModel* JSConsole::getAutoCompleteModel(const QString& memberOf) { foreach(auto doc, _apiDocs) { auto object = doc.toObject(); auto scope = object.value("scope"); - if ((memberOfProperty == nullptr && scope.toString() == "global" && object.value("kind").toString() == "namespace") || (memberOfProperty != nullptr && object.value("memberof").toString() == memberOfProperty)) { + if ((memberOfProperty == nullptr && scope.toString() == "global" && object.value("kind").toString() == "namespace") || + (memberOfProperty != nullptr && object.value("memberof").toString() == memberOfProperty && + object.value("kind").toString() != "typedef")) { + model->appendRow(getAutoCompleteItem(doc)); } } @@ -166,20 +189,119 @@ JSConsole::JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine) : } void JSConsole::insertCompletion(const QModelIndex& completion) { + auto jsdocObject = QJsonValue::fromVariant(completion.data(Qt::UserRole + 1)).toObject(); + auto kind = jsdocObject.value("kind").toString(); auto completionString = completion.data().toString(); - QTextCursor tc = _ui->promptTextEdit->textCursor(); + if (kind == "function") { + auto params = jsdocObject.value("params").toArray(); + // automatically add the parenthesis/parentheses for the functions + completionString += params.isEmpty() ? "()" : "("; + } + QTextCursor textCursor = _ui->promptTextEdit->textCursor(); int extra = completionString.length() - _completer->completionPrefix().length(); - tc.movePosition(QTextCursor::Left); - tc.movePosition(QTextCursor::EndOfWord); - tc.insertText(completionString.right(extra)); - _ui->promptTextEdit->setTextCursor(tc); + textCursor.movePosition(QTextCursor::Left); + textCursor.movePosition(QTextCursor::EndOfWord); + textCursor.insertText(completionString.right(extra)); + _ui->promptTextEdit->setTextCursor(textCursor); } void JSConsole::highlightedCompletion(const QModelIndex& completion) { - qDebug() << "Highlighted " << completion.data().toString(); auto jsdocObject = QJsonValue::fromVariant(completion.data(Qt::UserRole + 1)).toObject(); - - // qDebug() << "Highlighted data " << QJsonDocument(jsdocObject).toJson(QJsonDocument::Compact); + QString memberOf = ""; + if (!_completerModule.isEmpty()) { + memberOf = _completerModule + "."; + } + auto name = memberOf + "" + jsdocObject.value("name").toString() + ""; + auto description = jsdocObject.value("description").toString(); + auto examples = jsdocObject.value("examples").toArray(); + auto kind = jsdocObject.value("kind").toString(); + QString returnTypeText = ""; + + QString paramsTable = ""; + if (kind == "function") { + auto params = jsdocObject.value("params").toArray(); + auto returns = jsdocObject.value("returns"); + if (!returns.isUndefined()) { + returnTypeText = _jsdocTypeToString(jsdocObject.value("returns").toArray().at(0).toObject().value("type")) + " "; + } + name += "("; + if (!params.isEmpty()) { + bool hasDefaultParam = false; + bool hasOptionalParam = false; + bool firstItem = true; + foreach(auto param, params) { + auto paramObject = param.toObject(); + if (!hasOptionalParam && paramObject.value("optional").toBool(false)) { + hasOptionalParam = true; + name += "["; + } + if (!firstItem) { + name += ", "; + } else { + firstItem = false; + } + name += paramObject.value("name").toString(); + if (!hasDefaultParam && !paramObject.value("defaultvalue").isUndefined()) { + hasDefaultParam = true; + } + } + if (hasOptionalParam) { + name += "]"; + } + + paramsTable += ""; + if (hasDefaultParam) { + paramsTable += ""; + } + paramsTable += ""; + foreach(auto param, params) { + auto paramObject = param.toObject(); + paramsTable += ""; + } + + paramsTable += "
NameTypeDefaultDescription
" + paramObject.value("name").toString() + "" + + _jsdocTypeToString(paramObject.value("type")) + ""; + if (hasDefaultParam) { + paramsTable += paramObject.value("defaultvalue").toVariant().toString() + ""; + } + paramsTable += paramObject.value("description").toString() + "
"; + } + name += ")"; + } else if (!jsdocObject.value("type").isUndefined()){ + returnTypeText = _jsdocTypeToString(jsdocObject.value("type")) + " "; + } + auto popupText = JSDOC_STYLE + "" + returnTypeText + name + ""; + auto descriptionText = "

" + description.replace(JSDOC_LINE_SEPARATOR, "
") + "

"; + + popupText += descriptionText; + popupText += paramsTable; + auto returns = jsdocObject.value("returns"); + if (!returns.isUndefined()) { + foreach(auto returnEntry, returns.toArray()) { + auto returnsObject = returnEntry.toObject(); + auto returnsDescription = returnsObject.value("description").toString().replace(JSDOC_LINE_SEPARATOR, "
"); + popupText += "

Returns

" + returnsDescription + "

Type
" +
+                _jsdocTypeToString(returnsObject.value("type")) + "
"; + } + } + + if (!examples.isEmpty()) { + popupText += "

Examples

"; + foreach(auto example, examples) { + auto exampleText = example.toString(); + auto exampleLines = exampleText.split(JSDOC_LINE_SEPARATOR); + foreach(auto exampleLine, exampleLines) { + if (exampleLine.contains("")) { + popupText += exampleLine.replace("caption>", "h5>"); + } else { + popupText += "
" + exampleLine + "\n
"; + } + } + } + } + + QToolTip::showText(QPoint(_completer->popup()->pos().x() + _completer->popup()->width(), _completer->popup()->pos().y()), + popupText, _completer->popup()); } JSConsole::~JSConsole() { @@ -299,7 +421,7 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { case Qt::Key_Space: case Qt::Key_Enter: case Qt::Key_Return: - insertCompletion(_completer->currentIndex()); + insertCompletion(_completer->popup()->currentIndex()); _completer->popup()->hide(); return true; default: @@ -401,6 +523,7 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { cursorRect.setWidth(_completer->popup()->sizeHintForColumn(0) + _completer->popup()->verticalScrollBar()->sizeHint().width()); _completer->complete(cursorRect); + highlightedCompletion(_completer->popup()->currentIndex()); return false; } } From ce50380698285dde03ffccf639e795be51aeed9d Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 25 Jan 2018 22:47:35 +0100 Subject: [PATCH 033/116] fix JSAPI typo --- libraries/entities/src/EntityScriptingInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 095a821482..9342f96e07 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -139,7 +139,7 @@ public slots: Q_INVOKABLE bool canRezTmpCertified(); /**jsdoc - * @function Entities.canWriteAsseets + * @function Entities.canWriteAssets * @return {bool} `true` if the DomainServer will allow this Node/Avatar to write to the asset server */ Q_INVOKABLE bool canWriteAssets(); From 2874a3d2cab9f46aa1dd9e4fbb1092f5029e7012 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 25 Jan 2018 23:39:32 +0100 Subject: [PATCH 034/116] fix android build (skip auto-complete when BUILD_TOOLS is FALSE) --- interface/CMakeLists.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 96d04c784d..d0d89919d5 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -276,8 +276,10 @@ endif() # assume we are using a Qt build without bearer management add_definitions(-DQT_NO_BEARERMANAGEMENT) -# require JSDoc to be build before interface is deployed (Console Auto-complete) -add_dependencies(${TARGET_NAME} jsdoc) +if (BUILD_TOOLS) + # require JSDoc to be build before interface is deployed (Console Auto-complete) + add_dependencies(${TARGET_NAME} jsdoc) +endif() if (APPLE) # link in required OS X frameworks and include the right GL headers @@ -294,7 +296,7 @@ if (APPLE) set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources") - if (NPM_EXECUTABLE) + if (BUILD_TOOLS AND NPM_EXECUTABLE) set(EXTRA_COPY_COMMANDS COMMAND "${CMAKE_COMMAND}" -E make_directory "$/../Resources/auto-complete" @@ -317,7 +319,7 @@ if (APPLE) fixup_interface() else() - if (NPM_EXECUTABLE) + if (BUILD_TOOLS AND NPM_EXECUTABLE) set(EXTRA_COPY_COMMANDS COMMAND "${CMAKE_COMMAND}" -E make_directory "$/resources/auto-complete" From 7e5684c439abc1399963ce1e9866d57af8d8a309 Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Thu, 25 Jan 2018 15:04:12 -0800 Subject: [PATCH 035/116] use signals --- .../ui/overlays/ContextOverlayInterface.cpp | 12 ++++------- .../src/ui/overlays/ContextOverlayInterface.h | 20 +++++++++---------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 3886d5ad10..069446494f 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -267,13 +267,6 @@ void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemI } } -bool ContextOverlayInterface::getLastInspectedEntityWasValid() { - if (!_lastInspectedEntity.isNull() && !_lastInspectedValidEntity.isNull()) { - return _lastInspectedEntity == _lastInspectedValidEntity; - } - return false; -} - void ContextOverlayInterface::requestEntityOwnershipVerification(const QUuid& entityItemID) { EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags); _entityMarketplaceID = entityProperties.getMarketplaceID(); @@ -368,6 +361,7 @@ void ContextOverlayInterface::requestOwnershipVerification() { auto ledger = DependencyManager::get(); _challengeOwnershipTimeoutTimer.stop(); emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); + emit ownershipVerificationFailed(_lastInspectedEntity); qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!"; } } @@ -424,6 +418,7 @@ void ContextOverlayInterface::startChallengeOwnershipTimer() { connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() { qCDebug(entities) << "Ownership challenge timed out for" << _lastInspectedEntity; emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT)); + emit ownershipVerificationFailed(_lastInspectedEntity); }); _challengeOwnershipTimeoutTimer.start(5000); @@ -448,8 +443,9 @@ void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer if (verificationSuccess) { emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); - _lastInspectedValidEntity = _lastInspectedEntity; + emit ownershipVerificationSuccess(_lastInspectedEntity); } else { emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED)); + emit ownershipVerificationFailed(_lastInspectedEntity); } } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index 389b377bd5..c96c5989a6 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -36,21 +36,20 @@ class ContextOverlayInterface : public QObject, public Dependency { Q_OBJECT - Q_PROPERTY(QUuid entityWithContextOverlay READ getCurrentEntityWithContextOverlay WRITE setCurrentEntityWithContextOverlay) - Q_PROPERTY(bool enabled READ getEnabled WRITE setEnabled) - Q_PROPERTY(bool isInMarketplaceInspectionMode READ getIsInMarketplaceInspectionMode WRITE setIsInMarketplaceInspectionMode) + Q_PROPERTY(QUuid entityWithContextOverlay READ getCurrentEntityWithContextOverlay WRITE setCurrentEntityWithContextOverlay) + Q_PROPERTY(bool enabled READ getEnabled WRITE setEnabled) + Q_PROPERTY(bool isInMarketplaceInspectionMode READ getIsInMarketplaceInspectionMode WRITE setIsInMarketplaceInspectionMode) - QSharedPointer _entityScriptingInterface; + QSharedPointer _entityScriptingInterface; EntityPropertyFlags _entityPropertyFlags; QSharedPointer _hmdScriptingInterface; QSharedPointer _tabletScriptingInterface; QSharedPointer _selectionScriptingInterface; - OverlayID _contextOverlayID{ UNKNOWN_OVERLAY_ID }; - std::shared_ptr _contextOverlay{ nullptr }; + OverlayID _contextOverlayID { UNKNOWN_OVERLAY_ID }; + std::shared_ptr _contextOverlay { nullptr }; public: ContextOverlayInterface(); Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; } - Q_INVOKABLE bool getLastInspectedEntityWasValid(); Q_INVOKABLE void requestEntityOwnershipVerification(const QUuid& entityID); void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; } void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; } @@ -61,8 +60,10 @@ public: signals: void contextOverlayClicked(const QUuid& currentEntityWithContextOverlay); + void ownershipVerificationSuccess(const QUuid& entityID); + void ownershipVerificationFailed(const QUuid& entityID); - public slots: +public slots: bool createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event); bool destroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event); bool destroyContextOverlay(const EntityItemID& entityItemID); @@ -73,7 +74,7 @@ signals: void contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event); bool contextOverlayFilterPassed(const EntityItemID& entityItemID); - private slots: +private slots: void handleChallengeOwnershipReplyPacket(QSharedPointer packet, SharedNodePointer sendingNode); private: @@ -85,7 +86,6 @@ private: bool _enabled{ true }; EntityItemID _currentEntityWithContextOverlay{}; EntityItemID _lastInspectedEntity{}; - EntityItemID _lastInspectedValidEntity{}; QString _entityMarketplaceID; bool _contextOverlayJustClicked { false }; From f3e20e72cb51f032ef024c9dbbbf339b2f72d761 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 25 Jan 2018 15:09:03 -0800 Subject: [PATCH 036/116] really fix multi-highlighing --- interface/resources/qml/hifi/tablet/TabletHome.qml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml index 88d8194c49..f56d4f73be 100644 --- a/interface/resources/qml/hifi/tablet/TabletHome.qml +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -148,6 +148,18 @@ Item { previousGridIndex = currentIndex } + onMovementStarted: { + if (currentIndex < 0 || gridView.currentItem === undefined || gridView.contentItem.children.length - 1 < currentIndex) { + return; + } + var button = gridView.contentItem.children[currentIndex].children[0]; + if (button.isActive) { + button.state = "active state"; + } else { + button.state = "base state"; + } + } + cellWidth: width/3 cellHeight: cellWidth flow: GridView.LeftToRight From 3a6f184278946aff8bdbb914918f95728cadbdc9 Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Thu, 25 Jan 2018 15:13:14 -0800 Subject: [PATCH 037/116] make clear function is for avatar entities only, add error handling in non-client entity cases --- interface/src/ui/overlays/ContextOverlayInterface.cpp | 7 ++++++- interface/src/ui/overlays/ContextOverlayInterface.h | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 069446494f..d86d441e3b 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -267,10 +267,15 @@ void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemI } } -void ContextOverlayInterface::requestEntityOwnershipVerification(const QUuid& entityItemID) { +void ContextOverlayInterface::proveAvatarEntityOwnershipVerification(const QUuid& entityItemID) { EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags); _entityMarketplaceID = entityProperties.getMarketplaceID(); if (!entityItemID.isNull() && _entityMarketplaceID.length() > 0) { + if (!entityProperties.getClientOnly()) { + qCDebug(entities) << "Failed to prove ownership of:" << entityItemID << "is not an avatar entity"; + emit ownershipVerificationFailed(entityItemID); + return; + } setLastInspectedEntity(entityItemID); requestOwnershipVerification(); } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index c96c5989a6..04416299d6 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -50,7 +50,7 @@ class ContextOverlayInterface : public QObject, public Dependency { public: ContextOverlayInterface(); Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; } - Q_INVOKABLE void requestEntityOwnershipVerification(const QUuid& entityID); + Q_INVOKABLE void proveAvatarEntityOwnershipVerification(const QUuid& entityID); void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; } void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; } void setEnabled(bool enabled); From faf8350369e26b627a1c6b574cbac2578061bef8 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 25 Jan 2018 15:57:48 -0800 Subject: [PATCH 038/116] Bug fix for avatar neck cauterization The basic dual-quaternion skinning algorithm does not handle non-rigid transformations like scale well. Because we only use scaling for head cauterization, we special case this by passing in a cauterization factor, as well as a cauterization position to the vertex shader. If a vertex is flagged as cauterized, we slam it to equal the cauterization position. Although, not as smooth as the previous method, it seems to work well enough on the avatar's I've tested. --- .../render-utils/src/CauterizedModel.cpp | 2 + libraries/render-utils/src/Model.h | 10 +++- libraries/render-utils/src/Skinning.slh | 50 +++++++++++++++---- 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index e3f26a43d8..0b8c636678 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -115,6 +115,7 @@ void CauterizedModel::updateClusterMatrices() { Transform clusterTransform; Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform); + state.clusterTransforms[j].setCauterizationParameters(0.0f, jointPose.trans()); #else auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]); @@ -151,6 +152,7 @@ void CauterizedModel::updateClusterMatrices() { Transform clusterTransform; Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform); + state.clusterTransforms[j].setCauterizationParameters(1.0f, cauterizePose.trans()); #else glm_mat4u_mul(cauterizeMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]); #endif diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 560aa82f0c..17ac251459 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -263,26 +263,34 @@ public: _scale.x = p.scale().x; _scale.y = p.scale().y; _scale.z = p.scale().z; + _scale.w = 0.0f; _dq = DualQuaternion(p.rot(), p.trans()); } TransformDualQuaternion(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) { _scale.x = scale.x; _scale.y = scale.y; _scale.z = scale.z; + _scale.w = 0.0f; _dq = DualQuaternion(rot, trans); } TransformDualQuaternion(const Transform& transform) { _scale = glm::vec4(transform.getScale(), 0.0f); + _scale.w = 0.0f; _dq = DualQuaternion(transform.getRotation(), transform.getTranslation()); } glm::vec3 getScale() const { return glm::vec3(_scale); } glm::quat getRotation() const { return _dq.getRotation(); } glm::vec3 getTranslation() const { return _dq.getTranslation(); } glm::mat4 getMatrix() const { return createMatFromScaleQuatAndPos(getScale(), getRotation(), getTranslation()); }; + + void setCauterizationParameters(float cauterizationAmount, const glm::vec3& cauterizedPosition) { + _scale.w = cauterizationAmount; + _cauterizedPosition = glm::vec4(cauterizedPosition, 1.0f); + } protected: glm::vec4 _scale { 1.0f, 1.0f, 1.0f, 0.0f }; DualQuaternion _dq; - glm::vec4 _padding; + glm::vec4 _cauterizedPosition { 0.0f, 0.0f, 0.0f, 1.0f }; }; #endif diff --git a/libraries/render-utils/src/Skinning.slh b/libraries/render-utils/src/Skinning.slh index a7edfb14a6..702813366f 100644 --- a/libraries/render-utils/src/Skinning.slh +++ b/libraries/render-utils/src/Skinning.slh @@ -58,17 +58,19 @@ mat4 dualQuatToMat4(vec4 real, vec4 dual) { void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, out vec4 skinnedPosition) { // linearly blend scale and dual quaternion components - vec3 sAccum = vec3(0.0, 0.0, 0.0); + vec4 sAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0); + vec4 cAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1]; for (int i = 0; i < INDICES_PER_VERTEX; i++) { mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])]; float clusterWeight = skinClusterWeight[i]; - vec3 scale = vec3(clusterMatrix[0]); + vec4 scale = clusterMatrix[0]; vec4 real = clusterMatrix[1]; vec4 dual = clusterMatrix[2]; + vec4 cauterizedPos = clusterMatrix[3]; // to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity. float dqClusterWeight = clusterWeight; @@ -79,6 +81,7 @@ void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPositio sAccum += scale * clusterWeight; rAccum += real * dqClusterWeight; dAccum += dual * dqClusterWeight; + cAccum += cauterizedPos * clusterWeight; } // normalize dual quaternion @@ -88,25 +91,34 @@ void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPositio // conversion from dual quaternion to 4x4 matrix. mat4 m = dualQuatToMat4(rAccum, dAccum); - skinnedPosition = m * (vec4(sAccum, 1) * inPosition); + + // an sAccum.w of > 0 indicates that this joint is cauterized. + if (sAccum.w > 0.1) { + skinnedPosition = cAccum; + } else { + sAccum.w = 1.0; + skinnedPosition = m * (sAccum * inPosition); + } } void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, vec3 inNormal, out vec4 skinnedPosition, out vec3 skinnedNormal) { // linearly blend scale and dual quaternion components - vec3 sAccum = vec3(0.0, 0.0, 0.0); + vec4 sAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0); + vec4 cAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1]; for (int i = 0; i < INDICES_PER_VERTEX; i++) { mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])]; float clusterWeight = skinClusterWeight[i]; - vec3 scale = vec3(clusterMatrix[0]); + vec4 scale = clusterMatrix[0]; vec4 real = clusterMatrix[1]; vec4 dual = clusterMatrix[2]; + vec4 cauterizedPos = clusterMatrix[3]; // to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity. float dqClusterWeight = clusterWeight; @@ -117,6 +129,7 @@ void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inP sAccum += scale * clusterWeight; rAccum += real * dqClusterWeight; dAccum += dual * dqClusterWeight; + cAccum += cauterizedPos * clusterWeight; } // normalize dual quaternion @@ -126,7 +139,15 @@ void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inP // conversion from dual quaternion to 4x4 matrix. mat4 m = dualQuatToMat4(rAccum, dAccum); - skinnedPosition = m * (vec4(sAccum, 1) * inPosition); + + // an sAccum.w of > 0 indicates that this joint is cauterized. + if (sAccum.w > 0.1) { + skinnedPosition = cAccum; + } else { + sAccum.w = 1.0; + skinnedPosition = m * (sAccum * inPosition); + } + skinnedNormal = vec3(m * vec4(inNormal, 0)); } @@ -134,18 +155,20 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v out vec4 skinnedPosition, out vec3 skinnedNormal, out vec3 skinnedTangent) { // linearly blend scale and dual quaternion components - vec3 sAccum = vec3(0.0, 0.0, 0.0); + vec4 sAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0); + vec4 cAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1]; for (int i = 0; i < INDICES_PER_VERTEX; i++) { mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])]; float clusterWeight = skinClusterWeight[i]; - vec3 scale = vec3(clusterMatrix[0]); + vec4 scale = clusterMatrix[0]; vec4 real = clusterMatrix[1]; vec4 dual = clusterMatrix[2]; + vec4 cauterizedPos = clusterMatrix[3]; // to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity. float dqClusterWeight = clusterWeight; @@ -156,6 +179,7 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v sAccum += scale * clusterWeight; rAccum += real * dqClusterWeight; dAccum += dual * dqClusterWeight; + cAccum += cauterizedPos * clusterWeight; } // normalize dual quaternion @@ -165,7 +189,15 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v // conversion from dual quaternion to 4x4 matrix. mat4 m = dualQuatToMat4(rAccum, dAccum); - skinnedPosition = m * (vec4(sAccum, 1) * inPosition); + + // an sAccum.w of > 0 indicates that this vertex is cauterized. + if (sAccum.w > 0.1) { + skinnedPosition = cAccum; + } else { + sAccum.w = 1.0; + skinnedPosition = m * (sAccum * inPosition); + } + skinnedNormal = vec3(m * vec4(inNormal, 0)); skinnedTangent = vec3(m * vec4(inTangent, 0)); } From 78d72a1703155554af3ff21f073a2b5dbe782cf6 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 25 Jan 2018 16:03:05 -0800 Subject: [PATCH 039/116] remove unnecessary invokeMethod from ResourceCache::getResource --- libraries/networking/src/ResourceCache.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index a3ac995bcf..7ba7cca96d 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -340,14 +340,6 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& return resource; } - if (QThread::currentThread() != thread()) { - qCDebug(networking) << "Fetching asynchronously:" << url; - QMetaObject::invokeMethod(this, "getResource", - Q_ARG(QUrl, url), Q_ARG(QUrl, fallback)); - // Cannot use extra parameter as it might be freed before the invocation - return QSharedPointer(); - } - if (!url.isValid() && !url.isEmpty() && fallback.isValid()) { return getResource(fallback, QUrl()); } @@ -358,6 +350,7 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& extra); resource->setSelf(resource); resource->setCache(this); + resource->moveToThread(qApp->thread()); connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); { QWriteLocker locker(&_resourcesLock); From 21c1e3249013b65716b3b3ebb6a50362bdb598e6 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 25 Jan 2018 15:57:48 -0800 Subject: [PATCH 040/116] Bug fix for avatar neck cauterization The basic dual-quaternion skinning algorithm does not handle non-rigid transformations like scale well. Because we only use scaling for head cauterization, we special case this by passing in a cauterization factor, as well as a cauterization position to the vertex shader. If a vertex is flagged as cauterized, we slam it to equal the cauterization position. Although, not as smooth as the previous method, it seems to work well enough on the avatar's I've tested. (cherry picked from commit faf8350369e26b627a1c6b574cbac2578061bef8) --- .../render-utils/src/CauterizedModel.cpp | 2 + libraries/render-utils/src/Model.h | 10 +++- libraries/render-utils/src/Skinning.slh | 50 +++++++++++++++---- 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index e3f26a43d8..0b8c636678 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -115,6 +115,7 @@ void CauterizedModel::updateClusterMatrices() { Transform clusterTransform; Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform); + state.clusterTransforms[j].setCauterizationParameters(0.0f, jointPose.trans()); #else auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]); @@ -151,6 +152,7 @@ void CauterizedModel::updateClusterMatrices() { Transform clusterTransform; Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform); + state.clusterTransforms[j].setCauterizationParameters(1.0f, cauterizePose.trans()); #else glm_mat4u_mul(cauterizeMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]); #endif diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 560aa82f0c..17ac251459 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -263,26 +263,34 @@ public: _scale.x = p.scale().x; _scale.y = p.scale().y; _scale.z = p.scale().z; + _scale.w = 0.0f; _dq = DualQuaternion(p.rot(), p.trans()); } TransformDualQuaternion(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) { _scale.x = scale.x; _scale.y = scale.y; _scale.z = scale.z; + _scale.w = 0.0f; _dq = DualQuaternion(rot, trans); } TransformDualQuaternion(const Transform& transform) { _scale = glm::vec4(transform.getScale(), 0.0f); + _scale.w = 0.0f; _dq = DualQuaternion(transform.getRotation(), transform.getTranslation()); } glm::vec3 getScale() const { return glm::vec3(_scale); } glm::quat getRotation() const { return _dq.getRotation(); } glm::vec3 getTranslation() const { return _dq.getTranslation(); } glm::mat4 getMatrix() const { return createMatFromScaleQuatAndPos(getScale(), getRotation(), getTranslation()); }; + + void setCauterizationParameters(float cauterizationAmount, const glm::vec3& cauterizedPosition) { + _scale.w = cauterizationAmount; + _cauterizedPosition = glm::vec4(cauterizedPosition, 1.0f); + } protected: glm::vec4 _scale { 1.0f, 1.0f, 1.0f, 0.0f }; DualQuaternion _dq; - glm::vec4 _padding; + glm::vec4 _cauterizedPosition { 0.0f, 0.0f, 0.0f, 1.0f }; }; #endif diff --git a/libraries/render-utils/src/Skinning.slh b/libraries/render-utils/src/Skinning.slh index 49d0df3d2c..095e210bca 100644 --- a/libraries/render-utils/src/Skinning.slh +++ b/libraries/render-utils/src/Skinning.slh @@ -58,17 +58,19 @@ mat4 dualQuatToMat4(vec4 real, vec4 dual) { void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, out vec4 skinnedPosition) { // linearly blend scale and dual quaternion components - vec3 sAccum = vec3(0.0, 0.0, 0.0); + vec4 sAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0); + vec4 cAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1]; for (int i = 0; i < INDICES_PER_VERTEX; i++) { mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])]; float clusterWeight = skinClusterWeight[i]; - vec3 scale = vec3(clusterMatrix[0]); + vec4 scale = clusterMatrix[0]; vec4 real = clusterMatrix[1]; vec4 dual = clusterMatrix[2]; + vec4 cauterizedPos = clusterMatrix[3]; // to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity. float dqClusterWeight = clusterWeight; @@ -79,6 +81,7 @@ void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPositio sAccum += scale * clusterWeight; rAccum += real * dqClusterWeight; dAccum += dual * dqClusterWeight; + cAccum += cauterizedPos * clusterWeight; } // normalize dual quaternion @@ -88,25 +91,34 @@ void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPositio // conversion from dual quaternion to 4x4 matrix. mat4 m = dualQuatToMat4(rAccum, dAccum); - skinnedPosition = m * (vec4(sAccum, 1) * inPosition); + + // an sAccum.w of > 0 indicates that this joint is cauterized. + if (sAccum.w > 0.1) { + skinnedPosition = cAccum; + } else { + sAccum.w = 1.0; + skinnedPosition = m * (sAccum * inPosition); + } } void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, vec3 inNormal, out vec4 skinnedPosition, out vec3 skinnedNormal) { // linearly blend scale and dual quaternion components - vec3 sAccum = vec3(0.0, 0.0, 0.0); + vec4 sAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0); + vec4 cAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1]; for (int i = 0; i < INDICES_PER_VERTEX; i++) { mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])]; float clusterWeight = skinClusterWeight[i]; - vec3 scale = vec3(clusterMatrix[0]); + vec4 scale = clusterMatrix[0]; vec4 real = clusterMatrix[1]; vec4 dual = clusterMatrix[2]; + vec4 cauterizedPos = clusterMatrix[3]; // to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity. float dqClusterWeight = clusterWeight; @@ -117,6 +129,7 @@ void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inP sAccum += scale * clusterWeight; rAccum += real * dqClusterWeight; dAccum += dual * dqClusterWeight; + cAccum += cauterizedPos * clusterWeight; } // normalize dual quaternion @@ -126,7 +139,15 @@ void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inP // conversion from dual quaternion to 4x4 matrix. mat4 m = dualQuatToMat4(rAccum, dAccum); - skinnedPosition = m * (vec4(sAccum, 1) * inPosition); + + // an sAccum.w of > 0 indicates that this joint is cauterized. + if (sAccum.w > 0.1) { + skinnedPosition = cAccum; + } else { + sAccum.w = 1.0; + skinnedPosition = m * (sAccum * inPosition); + } + skinnedNormal = vec3(m * vec4(inNormal, 0)); } @@ -134,18 +155,20 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v out vec4 skinnedPosition, out vec3 skinnedNormal, out vec3 skinnedTangent) { // linearly blend scale and dual quaternion components - vec3 sAccum = vec3(0.0, 0.0, 0.0); + vec4 sAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0); + vec4 cAccum = vec4(0.0, 0.0, 0.0, 0.0); vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1]; for (int i = 0; i < INDICES_PER_VERTEX; i++) { mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])]; float clusterWeight = skinClusterWeight[i]; - vec3 scale = vec3(clusterMatrix[0]); + vec4 scale = clusterMatrix[0]; vec4 real = clusterMatrix[1]; vec4 dual = clusterMatrix[2]; + vec4 cauterizedPos = clusterMatrix[3]; // to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity. float dqClusterWeight = clusterWeight; @@ -156,6 +179,7 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v sAccum += scale * clusterWeight; rAccum += real * dqClusterWeight; dAccum += dual * dqClusterWeight; + cAccum += cauterizedPos * clusterWeight; } // normalize dual quaternion @@ -165,7 +189,15 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v // conversion from dual quaternion to 4x4 matrix. mat4 m = dualQuatToMat4(rAccum, dAccum); - skinnedPosition = m * (vec4(sAccum, 1) * inPosition); + + // an sAccum.w of > 0 indicates that this vertex is cauterized. + if (sAccum.w > 0.1) { + skinnedPosition = cAccum; + } else { + sAccum.w = 1.0; + skinnedPosition = m * (sAccum * inPosition); + } + skinnedNormal = vec3(m * vec4(inNormal, 0)); skinnedTangent = vec3(m * vec4(inTangent, 0)); } From 1ad65b8da5bb3e862c2d985689d2bf6078c18044 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 23 Jan 2018 16:05:43 -0800 Subject: [PATCH 041/116] Bug fix for twisted knees on some avatars. The FBXReader inverse bind pose calculation can sometimes introduce floating point fuzz into the bottom row of the matrix. The Transform class checks this bottom row before doing decomposition into translation, rotation and scale. If it detects that this row is not exactly (0, 0, 0, 1) it aborts. And returns identity. To guarantee that it preforms the decomposition correctly slam the row to (0, 0, 0, 1), before conversion to a Transform instance. (cherry picked from commit 991ba7f1951db5043dd8fbf73ba6403b193f38eb) --- libraries/fbx/src/FBXReader.cpp | 14 ++++++++++++-- libraries/render-utils/src/Skinning.slh | 4 ++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 4bc5eb1b6d..171fc88443 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1733,8 +1733,18 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS qCDebug(modelformat) << "Joint not in model list: " << jointID; fbxCluster.jointIndex = 0; } + fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform; + + // slam bottom row to (0, 0, 0, 1), we KNOW this is not a perspective matrix and + // sometimes floating point fuzz can be introduced after the inverse. + fbxCluster.inverseBindMatrix[0][3] = 0.0f; + fbxCluster.inverseBindMatrix[1][3] = 0.0f; + fbxCluster.inverseBindMatrix[2][3] = 0.0f; + fbxCluster.inverseBindMatrix[3][3] = 1.0f; + fbxCluster.inverseBindTransform = Transform(fbxCluster.inverseBindMatrix); + extracted.mesh.clusters.append(fbxCluster); // override the bind rotation with the transform link @@ -1836,13 +1846,13 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } // now that we've accumulated the most relevant weights for each vertex - // normalize and compress to 8-bits + // normalize and compress to 16-bits extracted.mesh.clusterWeights.fill(0, numClusterIndices); int numVertices = extracted.mesh.vertices.size(); for (int i = 0; i < numVertices; ++i) { int j = i * WEIGHTS_PER_VERTEX; - // normalize weights into uint8_t + // normalize weights into uint16_t float totalWeight = weightAccumulators[j]; for (int k = j + 1; k < j + WEIGHTS_PER_VERTEX; ++k) { totalWeight += weightAccumulators[k]; diff --git a/libraries/render-utils/src/Skinning.slh b/libraries/render-utils/src/Skinning.slh index 49d0df3d2c..e85ee75c58 100644 --- a/libraries/render-utils/src/Skinning.slh +++ b/libraries/render-utils/src/Skinning.slh @@ -39,12 +39,12 @@ mat4 dualQuatToMat4(vec4 real, vec4 dual) { twoRealXZ - twoRealYW, 0.0); vec4 col1 = vec4(twoRealXY - twoRealZW, - 1 - twoRealXSq - twoRealZSq, + 1.0 - twoRealXSq - twoRealZSq, twoRealYZ + twoRealXW, 0.0); vec4 col2 = vec4(twoRealXZ + twoRealYW, twoRealYZ - twoRealXW, - 1 - twoRealXSq - twoRealYSq, + 1.0 - twoRealXSq - twoRealYSq, 0.0); vec4 col3 = vec4(2.0 * (-dual.w * real.x + dual.x * real.w - dual.y * real.z + dual.z * real.y), 2.0 * (-dual.w * real.y + dual.x * real.z + dual.y * real.w - dual.z * real.x), From ced5aad1d17efe7b9818f093330b350755cb3783 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 2 Nov 2017 11:14:35 -0700 Subject: [PATCH 042/116] backtrace for rc-63 --- cmake/externals/crashpad/CMakeLists.txt | 35 ++++++++ cmake/macros/AddCrashpad.cmake | 41 +++++++++ .../macros/PackageCrashpadForDeployment.cmake | 30 +++++++ cmake/modules/FindCrashpad.cmake | 41 +++++++++ interface/CMakeLists.txt | 2 + libraries/shared/CMakeLists.txt | 8 +- libraries/shared/src/shared/Crashpad.cpp | 87 +++++++++++++++++++ libraries/shared/src/shared/Crashpad.h | 17 ++++ 8 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 cmake/externals/crashpad/CMakeLists.txt create mode 100644 cmake/macros/AddCrashpad.cmake create mode 100644 cmake/macros/PackageCrashpadForDeployment.cmake create mode 100644 cmake/modules/FindCrashpad.cmake create mode 100644 libraries/shared/src/shared/Crashpad.cpp create mode 100644 libraries/shared/src/shared/Crashpad.h diff --git a/cmake/externals/crashpad/CMakeLists.txt b/cmake/externals/crashpad/CMakeLists.txt new file mode 100644 index 0000000000..c464dcbc1b --- /dev/null +++ b/cmake/externals/crashpad/CMakeLists.txt @@ -0,0 +1,35 @@ +include(ExternalProject) +set(EXTERNAL_NAME crashpad) + +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +if (WIN32) + ExternalProject_Add( + ${EXTERNAL_NAME} + URL https://backtrace.io/download/crashpad_062317.zip + URL_MD5 65817e564b3628492abfc1dbd2a1e98b + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 + ) + + ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) + + set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE PATH "List of Crashpad include directories") + + set(LIB_EXT "lib") + + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}crashpad_client.${LIB_EXT} CACHE FILEPATH "Path to Crashpad release library") + set(${EXTERNAL_NAME_UPPER}_BASE_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}base.${LIB_EXT} CACHE FILEPATH "Path to Crashpad base release library") + set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}crashpad_util.${LIB_EXT} CACHE FILEPATH "Path to Crashpad util release library") + + set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}crashpad_client.${LIB_EXT} CACHE FILEPATH "Path to Crashpad debug library") + set(${EXTERNAL_NAME_UPPER}_BASE_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}base.${LIB_EXT} CACHE FILEPATH "Path to Crashpad base debug library") + set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}crashpad_util.${LIB_EXT} CACHE FILEPATH "Path to Crashpad util debug library") + + set(CRASHPAD_HANDLER_EXE_PATH ${SOURCE_DIR}/out/Release_x64/crashpad_handler.exe CACHE FILEPATH "Path to the Crashpad handler executable") +endif () + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") diff --git a/cmake/macros/AddCrashpad.cmake b/cmake/macros/AddCrashpad.cmake new file mode 100644 index 0000000000..573e13c8a2 --- /dev/null +++ b/cmake/macros/AddCrashpad.cmake @@ -0,0 +1,41 @@ +# +# AddCrashpad.cmake +# cmake/macros +# +# Created by Clement Brisset on 01/19/18. +# Copyright 2018 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 +# + +macro(add_crashpad) + get_property(CRASHPAD_CHECKED GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE) + + set (USE_CRASHPAD TRUE) + if ("$ENV{CMAKE_BACKTRACE_URL}" STREQUAL "") + set (USE_CRASHPAD FALSE) + else() + set (CMAKE_BACKTRACE_URL $ENV{CMAKE_BACKTRACE_URL}) + endif() + + if ("$ENV{CMAKE_BACKTRACE_TOKEN}" STREQUAL "") + set (USE_CRASHPAD FALSE) + else() + set (CMAKE_BACKTRACE_TOKEN $ENV{CMAKE_BACKTRACE_TOKEN}) + endif() + + if (WIN32 AND USE_CRASHPAD AND NOT CRASHPAD_CHECKED) + set_property(GLOBAL PROPERTY HAS_CRASHPAD TRUE) + add_definitions(-DHAS_CRASHPAD) + add_definitions(-DCMAKE_BACKTRACE_URL=\"${CMAKE_BACKTRACE_URL}\") + add_definitions(-DCMAKE_BACKTRACE_TOKEN=\"${CMAKE_BACKTRACE_TOKEN}\") + + add_dependency_external_projects(crashpad) + find_package(crashpad REQUIRED) + target_include_directories(${TARGET_NAME} PRIVATE ${CRASHPAD_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${CRASHPAD_LIBRARY} ${CRASHPAD_BASE_LIBRARY} ${CRASHPAD_UTIL_LIBRARY}) + + set_property(GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE TRUE) + endif () +endmacro() diff --git a/cmake/macros/PackageCrashpadForDeployment.cmake b/cmake/macros/PackageCrashpadForDeployment.cmake new file mode 100644 index 0000000000..adee7a79e6 --- /dev/null +++ b/cmake/macros/PackageCrashpadForDeployment.cmake @@ -0,0 +1,30 @@ +# +# PackageCrashpadForDeployment.cmake +# cmake/macros +# +# Copyright 2018 High Fidelity, Inc. +# Created by Clement Brisset on 01/19/18 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +macro(PACKAGE_CRASHPAD_FOR_DEPLOYMENT) + get_property(HAS_CRASHPAD GLOBAL PROPERTY HAS_CRASHPAD) + + if (HAS_CRASHPAD) + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$/" + ) + install( + PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/crashpad_handler.exe" + DESTINATION ${CLIENT_COMPONENT} + COMPONENT ${INTERFACE_INSTALL_DIR} + ) + + message(STATUS "CRASHPAD_HANDLER_EXE_PATH: ${CRASHPAD_HANDLER_EXE_PATH}") + message(STATUS "Target: $") + endif () +endmacro() diff --git a/cmake/modules/FindCrashpad.cmake b/cmake/modules/FindCrashpad.cmake new file mode 100644 index 0000000000..5095a0b0c9 --- /dev/null +++ b/cmake/modules/FindCrashpad.cmake @@ -0,0 +1,41 @@ +# +# FindCrashpad.cmake +# +# Try to find Crashpad libraries and include path. +# Once done this will define +# +# CRASHPAD_FOUND +# DRACO_INCLUDE_DIRS +# CRASHPAD_LIBRARY +# CRASHPAD_BASE_LIBRARY +# CRASHPAD_UTIL_LIBRARY +# +# Created on 01/19/2018 by Clement Brisset +# Copyright 2018 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("${MACRO_DIR}/HifiLibrarySearchHints.cmake") +hifi_library_search_hints("crashpad") + +find_path(CRASHPAD_INCLUDE_DIRS base/macros.h PATH_SUFFIXES include HINTS ${CRASHPAD_SEARCH_DIRS}) + +find_library(CRASHPAD_LIBRARY_RELEASE crashpad PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS}) +find_library(CRASHPAD_BASE_LIBRARY_RELEASE base PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS}) +find_library(CRASHPAD_UTIL_LIBRARY_RELEASE util PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS}) + +find_library(CRASHPAD_LIBRARY_DEBUG crashpad PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS}) +find_library(CRASHPAD_BASE_LIBRARY_DEBUG base PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS}) +find_library(CRASHPAD_UTIL_LIBRARY_DEBUG util PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS}) + +find_file(CRASHPAD_HANDLER_EXE_PATH NAME "crashpad_handler.exe" PATH_SUFFIXES "Release_x64" HINTS ${CRASHPAD_SEARCH_DIRS}) + +include(SelectLibraryConfigurations) +select_library_configurations(CRASHPAD) +select_library_configurations(CRASHPAD_BASE) +select_library_configurations(CRASHPAD_UTIL) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(CRASHPAD DEFAULT_MSG CRASHPAD_INCLUDE_DIRS CRASHPAD_LIBRARY CRASHPAD_BASE_LIBRARY CRASHPAD_UTIL_LIBRARY) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 21225756b4..caa812e133 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -349,6 +349,8 @@ endif() add_bugsplat() +package_crashpad_for_deployment() + if (WIN32) set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"") diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index f9b835df5c..379c15c999 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -5,7 +5,13 @@ setup_hifi_library(Gui Network Script Widgets) if (WIN32) target_link_libraries(${TARGET_NAME} Wbemuuid.lib) + + add_crashpad() +endif() + +if (ANDROID) + target_link_libraries(${TARGET_NAME} android) endif() target_zlib() -target_nsight() \ No newline at end of file +target_nsight() diff --git a/libraries/shared/src/shared/Crashpad.cpp b/libraries/shared/src/shared/Crashpad.cpp new file mode 100644 index 0000000000..a655ddec4b --- /dev/null +++ b/libraries/shared/src/shared/Crashpad.cpp @@ -0,0 +1,87 @@ +// +// Crashpad.cpp +// shared/src/shared +// +// Created by Clement Brisset on 01/19/18. +// Copyright 2018 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 "Crashpad.h" + +#include + +#if HAS_CRASHPAD + +#include +#include + +#include + +#include +#include +#include + +using namespace crashpad; + +static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL }; +static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN }; + +extern QString qAppFileName(); + +bool startCrashHandler() { + if (BACKTRACE_URL.empty() || BACKTRACE_TOKEN.empty()) { + return false; + } + + CrashpadClient client; + std::map annotations; + std::vector arguments; + + annotations["token"] = BACKTRACE_TOKEN; + annotations["format"] = "minidump"; + annotations["service-name"] = BuildInfo::INTERFACE_NAME.toStdString(); + annotations["version"] = BuildInfo::VERSION.toStdString(); + + arguments.push_back("--no-rate-limit"); + + // Setup Crashpad DB directory + const auto crashpadDbName = "crashpad-db"; + const auto crashpadDbDir = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); + QDir(crashpadDbDir).mkpath(crashpadDbName); // Make sure the directory exists + const auto crashpadDbPath = crashpadDbDir.toStdString() + "/" + crashpadDbName; + + // Locate Crashpad handler + const std::string CRASHPAD_HANDLER_PATH = QFileInfo(qAppFileName()).absolutePath().toStdString() + "/crashpad_handler.exe"; + + // Setup different file paths + base::FilePath::StringType dbPath; + base::FilePath::StringType handlerPath; + dbPath.assign(crashpadDbPath.cbegin(), crashpadDbPath.cend()); + handlerPath.assign(CRASHPAD_HANDLER_PATH.cbegin(), CRASHPAD_HANDLER_PATH.cend()); + + base::FilePath db(dbPath); + base::FilePath handler(handlerPath); + + auto database = crashpad::CrashReportDatabase::Initialize(db); + if (database == nullptr || database->GetSettings() == nullptr) { + return false; + } + + // Enable automated uploads. + database->GetSettings()->SetUploadsEnabled(true); + + return client.StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true); +} + +#else + + +bool startCrashHandler() { + qDebug() << "No crash handler available."; + return false; +} + +#endif \ No newline at end of file diff --git a/libraries/shared/src/shared/Crashpad.h b/libraries/shared/src/shared/Crashpad.h new file mode 100644 index 0000000000..a40503a703 --- /dev/null +++ b/libraries/shared/src/shared/Crashpad.h @@ -0,0 +1,17 @@ +// +// Crashpad.h +// shared/src/shared +// +// Created by Clement Brisset on 01/19/18. +// Copyright 2018 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_Crashpad_h +#define hifi_Crashpad_h + +bool startCrashHandler(); + +#endif // hifi_Crashpad_h From 47f44eb21fc1f40cb7123c47f7acd46b51d9d3e7 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 22 Jan 2018 11:12:45 -0800 Subject: [PATCH 043/116] extirpate bugsplat --- cmake/macros/AddBugSplat.cmake | 34 ------- cmake/modules/FindBugSplat.cmake | 30 ------ interface/CMakeLists.txt | 2 - interface/src/CrashReporter.cpp | 166 ------------------------------- interface/src/CrashReporter.h | 32 ------ interface/src/main.cpp | 36 +------ 6 files changed, 2 insertions(+), 298 deletions(-) delete mode 100644 cmake/macros/AddBugSplat.cmake delete mode 100644 cmake/modules/FindBugSplat.cmake delete mode 100644 interface/src/CrashReporter.cpp delete mode 100644 interface/src/CrashReporter.h diff --git a/cmake/macros/AddBugSplat.cmake b/cmake/macros/AddBugSplat.cmake deleted file mode 100644 index 979dcfe817..0000000000 --- a/cmake/macros/AddBugSplat.cmake +++ /dev/null @@ -1,34 +0,0 @@ -# -# AddBugSplat.cmake -# cmake/macros -# -# Created by Ryan Huffman on 02/09/16. -# Copyright 2016 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 -# - -macro(add_bugsplat) - get_property(BUGSPLAT_CHECKED GLOBAL PROPERTY CHECKED_FOR_BUGSPLAT_ONCE) - - if (NOT BUGSPLAT_CHECKED) - find_package(BugSplat) - set_property(GLOBAL PROPERTY CHECKED_FOR_BUGSPLAT_ONCE TRUE) - endif () - - if (BUGSPLAT_FOUND) - add_definitions(-DHAS_BUGSPLAT) - - target_include_directories(${TARGET_NAME} PRIVATE ${BUGSPLAT_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${BUGSPLAT_LIBRARIES}) - add_paths_to_fixup_libs(${BUGSPLAT_DLL_PATH}) - - add_custom_command(TARGET ${TARGET_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${BUGSPLAT_RC_DLL_PATH} "$/") - add_custom_command(TARGET ${TARGET_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${BUGSPLAT_EXE_PATH} "$/") - endif () -endmacro() diff --git a/cmake/modules/FindBugSplat.cmake b/cmake/modules/FindBugSplat.cmake deleted file mode 100644 index 8bea1cb1e1..0000000000 --- a/cmake/modules/FindBugSplat.cmake +++ /dev/null @@ -1,30 +0,0 @@ -# -# FindBugSplat.cmake -# cmake/modules -# -# Created by Ryan Huffman on 02/09/16. -# Copyright 2016 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 -# - -if (WIN32) - include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") - hifi_library_search_hints("BugSplat") - - find_path(BUGSPLAT_INCLUDE_DIRS NAMES BugSplat.h PATH_SUFFIXES inc HINTS ${BUGSPLAT_SEARCH_DIRS}) - - find_library(BUGSPLAT_LIBRARY_RELEASE "BugSplat64.lib" PATH_SUFFIXES "lib64" HINTS ${BUGSPLAT_SEARCH_DIRS}) - find_path(BUGSPLAT_DLL_PATH NAMES "BugSplat64.dll" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS}) - find_file(BUGSPLAT_RC_DLL_PATH NAMES "BugSplatRc64.dll" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS}) - find_file(BUGSPLAT_EXE_PATH NAMES "BsSndRpt64.exe" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS}) - - include(SelectLibraryConfigurations) - select_library_configurations(BUGSPLAT) - - set(BUGSPLAT_LIBRARIES ${BUGSPLAT_LIBRARY_RELEASE}) -endif () - -set(BUGSPLAT_REQUIREMENTS BUGSPLAT_INCLUDE_DIRS BUGSPLAT_LIBRARIES BUGSPLAT_DLL_PATH BUGSPLAT_RC_DLL_PATH BUGSPLAT_EXE_PATH) -find_package_handle_standard_args(BugSplat DEFAULT_MSG ${BUGSPLAT_REQUIREMENTS}) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index caa812e133..6cb1c352c7 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -347,8 +347,6 @@ if (SCRIPTS_INSTALL_DIR) ) endif() -add_bugsplat() - package_crashpad_for_deployment() if (WIN32) diff --git a/interface/src/CrashReporter.cpp b/interface/src/CrashReporter.cpp deleted file mode 100644 index 596c34ca92..0000000000 --- a/interface/src/CrashReporter.cpp +++ /dev/null @@ -1,166 +0,0 @@ -// -// CrashReporter.cpp -// interface/src -// -// Created by Ryan Huffman on 11 April 2016. -// Copyright 2016 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 "Application.h" -#include "CrashReporter.h" - -#ifdef _WIN32 -#include -#include -#include - -#include -#include - - -#pragma comment(lib, "Dbghelp.lib") - -// SetUnhandledExceptionFilter can be overridden by the CRT at the point that an error occurs. More information -// can be found here: http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li -// A fairly common approach is to patch the SetUnhandledExceptionFilter so that it cannot be overridden and so -// that the applicaiton can handle it itself. -// The CAPIHook class referenced in the above article is not openly available, but a similar implementation -// can be found here: http://blog.kalmbach-software.de/2008/04/02/unhandled-exceptions-in-vc8-and-above-for-x86-and-x64/ -// The below has been adapted to work with different library and functions. -BOOL redirectLibraryFunctionToFunction(char* library, char* function, void* fn) -{ - HMODULE lib = LoadLibrary(library); - if (lib == NULL) return FALSE; - void *pOrgEntry = GetProcAddress(lib, function); - if (pOrgEntry == NULL) return FALSE; - - DWORD dwOldProtect = 0; - SIZE_T jmpSize = 5; -#ifdef _M_X64 - jmpSize = 13; -#endif - BOOL bProt = VirtualProtect(pOrgEntry, jmpSize, - PAGE_EXECUTE_READWRITE, &dwOldProtect); - BYTE newJump[20]; - void *pNewFunc = fn; -#ifdef _M_IX86 - DWORD dwOrgEntryAddr = (DWORD)pOrgEntry; - dwOrgEntryAddr += jmpSize; // add 5 for 5 op-codes for jmp rel32 - DWORD dwNewEntryAddr = (DWORD)pNewFunc; - DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr; - // JMP rel32: Jump near, relative, displacement relative to next instruction. - newJump[0] = 0xE9; // JMP rel32 - memcpy(&newJump[1], &dwRelativeAddr, sizeof(pNewFunc)); -#elif _M_X64 - // We must use R10 or R11, because these are "scratch" registers - // which need not to be preserved accross function calls - // For more info see: Register Usage for x64 64-Bit - // http://msdn.microsoft.com/en-us/library/ms794547.aspx - // Thanks to Matthew Smith!!! - newJump[0] = 0x49; // MOV R11, ... - newJump[1] = 0xBB; // ... - memcpy(&newJump[2], &pNewFunc, sizeof(pNewFunc)); - //pCur += sizeof (ULONG_PTR); - newJump[10] = 0x41; // JMP R11, ... - newJump[11] = 0xFF; // ... - newJump[12] = 0xE3; // ... -#endif - SIZE_T bytesWritten; - BOOL bRet = WriteProcessMemory(GetCurrentProcess(), - pOrgEntry, newJump, jmpSize, &bytesWritten); - - if (bProt != FALSE) - { - DWORD dwBuf; - VirtualProtect(pOrgEntry, jmpSize, dwOldProtect, &dwBuf); - } - return bRet; -} - -void printStackTrace(ULONG framesToSkip = 1) { - HANDLE process = GetCurrentProcess(); - SymInitialize(process, NULL, TRUE); - void* stack[100]; - uint16_t frames = CaptureStackBackTrace(framesToSkip, 100, stack, NULL); - SYMBOL_INFO* symbol = (SYMBOL_INFO *)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1); - symbol->MaxNameLen = 255; - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - - for (uint16_t i = 0; i < frames; ++i) { - SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol); - qWarning() << QString("%1: %2 - 0x%0X").arg(QString::number(frames - i - 1), QString(symbol->Name), QString::number(symbol->Address, 16)); - } - - free(symbol); - - // Try to force the log to sync to the filesystem - auto app = qApp; - if (app && app->getLogger()) { - app->getLogger()->sync(); - } -} - -void handleSignal(int signal) { - // Throw so BugSplat can handle - throw(signal); -} - -void __cdecl handlePureVirtualCall() { - qWarning() << "Pure virtual function call detected"; - printStackTrace(2); - // Throw so BugSplat can handle - throw("ERROR: Pure virtual call"); -} - -void handleInvalidParameter(const wchar_t * expression, const wchar_t * function, const wchar_t * file, - unsigned int line, uintptr_t pReserved ) { - // Throw so BugSplat can handle - throw("ERROR: Invalid parameter"); -} - -int handleNewError(size_t size) { - // Throw so BugSplat can handle - throw("ERROR: Errors calling new"); -} - -LPTOP_LEVEL_EXCEPTION_FILTER WINAPI noop_SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) { - return nullptr; -} - -_purecall_handler __cdecl noop_set_purecall_handler(_purecall_handler pNew) { - return nullptr; -} - -#ifdef HAS_BUGSPLAT - -static const DWORD BUG_SPLAT_FLAGS = MDSF_PREVENTHIJACKING | MDSF_USEGUARDMEMORY; - -CrashReporter::CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version) - : mpSender(qPrintable(bugSplatDatabase), qPrintable(bugSplatApplicationName), qPrintable(version), nullptr, BUG_SPLAT_FLAGS) -{ - signal(SIGSEGV, handleSignal); - signal(SIGABRT, handleSignal); - _set_purecall_handler(handlePureVirtualCall); - _set_invalid_parameter_handler(handleInvalidParameter); - _set_new_mode(1); - _set_new_handler(handleNewError); - - // Disable WER popup - //SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); - //_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); - - // QtWebEngineCore internally sets its own purecall handler, overriding our own error handling. This disables that. - if (!redirectLibraryFunctionToFunction("msvcr120.dll", "_set_purecall_handler", &noop_set_purecall_handler)) { - qWarning() << "Failed to patch _set_purecall_handler"; - } - // Patch SetUnhandledExceptionFilter to keep the CRT from overriding our own error handling. - if (!redirectLibraryFunctionToFunction("kernel32.dll", "SetUnhandledExceptionFilter", &noop_SetUnhandledExceptionFilter)) { - qWarning() << "Failed to patch setUnhandledExceptionFilter"; - } -} -#endif -#endif diff --git a/interface/src/CrashReporter.h b/interface/src/CrashReporter.h deleted file mode 100644 index 5f02066a74..0000000000 --- a/interface/src/CrashReporter.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// CrashReporter.h -// interface/src -// -// Created by Ryan Huffman on 11 April 2016. -// Copyright 2016 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 -// - -#pragma once - -#ifndef hifi_CrashReporter_h -#define hifi_CrashReporter_h - -#include - -#ifdef HAS_BUGSPLAT - -#include - -class CrashReporter { -public: - CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version); - - MiniDmpSender mpSender; -}; - -#endif - -#endif // hifi_CrashReporter_h \ No newline at end of file diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 5c07bebc23..ad43bf45c1 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -31,11 +31,6 @@ #include "UserActivityLogger.h" #include "MainWindow.h" -#ifdef HAS_BUGSPLAT -#include -#include -#endif - #ifdef Q_OS_WIN extern "C" { typedef int(__stdcall * CHECKMINSPECPROC) (); @@ -43,11 +38,6 @@ extern "C" { #endif int main(int argc, const char* argv[]) { -#if HAS_BUGSPLAT - static QString BUG_SPLAT_DATABASE = "interface_alpha"; - static QString BUG_SPLAT_APPLICATION_NAME = "Interface"; - CrashReporter crashReporter { BUG_SPLAT_DATABASE, BUG_SPLAT_APPLICATION_NAME, BuildInfo::VERSION }; -#endif #ifdef Q_OS_LINUX QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); @@ -252,7 +242,6 @@ int main(int argc, const char* argv[]) { } } #endif - // Setup local server QLocalServer server { &app }; @@ -261,29 +250,8 @@ int main(int argc, const char* argv[]) { server.removeServer(applicationName); server.listen(applicationName); - QObject::connect(&server, &QLocalServer::newConnection, &app, &Application::handleLocalServerConnection, Qt::DirectConnection); - -#ifdef HAS_BUGSPLAT - auto accountManager = DependencyManager::get(); - crashReporter.mpSender.setDefaultUserName(qPrintable(accountManager->getAccountInfo().getUsername())); - QObject::connect(accountManager.data(), &AccountManager::usernameChanged, &app, [&crashReporter](const QString& newUsername) { - crashReporter.mpSender.setDefaultUserName(qPrintable(newUsername)); - }); - - // BugSplat WILL NOT work with file paths that do not use OS native separators. - auto logger = app.getLogger(); - auto logPath = QDir::toNativeSeparators(logger->getFilename()); - crashReporter.mpSender.sendAdditionalFile(qPrintable(logPath)); - - QMetaObject::Connection connection; - connection = QObject::connect(logger, &FileLogger::rollingLogFile, &app, [&crashReporter, &connection](QString newFilename) { - // We only want to add the first rolled log file (the "beginning" of the log) to BugSplat to ensure we don't exceed the 2MB - // zipped limit, so we disconnect here. - QObject::disconnect(connection); - auto rolledLogPath = QDir::toNativeSeparators(newFilename); - crashReporter.mpSender.sendAdditionalFile(qPrintable(rolledLogPath)); - }); -#endif + QObject::connect(&server, &QLocalServer::newConnection, + &app, &Application::handleLocalServerConnection, Qt::DirectConnection); printSystemInformation(); From 6c044316367ee9a66723e8991f596c15c67c3997 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 22 Jan 2018 11:50:07 -0800 Subject: [PATCH 044/116] call startCrashhandler from interface --- interface/src/main.cpp | 3 +++ libraries/shared/src/shared/Crashpad.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index ad43bf45c1..069aeb6775 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "AddressManager.h" #include "Application.h" @@ -45,6 +46,8 @@ int main(int argc, const char* argv[]) { disableQtBearerPoll(); // Fixes wifi ping spikes + startCrashHandler(); + QElapsedTimer startupTime; startupTime.start(); diff --git a/libraries/shared/src/shared/Crashpad.cpp b/libraries/shared/src/shared/Crashpad.cpp index a655ddec4b..7bc3ce7584 100644 --- a/libraries/shared/src/shared/Crashpad.cpp +++ b/libraries/shared/src/shared/Crashpad.cpp @@ -84,4 +84,4 @@ bool startCrashHandler() { return false; } -#endif \ No newline at end of file +#endif From 093fff9c9e3ae775aa639be6c90bc316804daadc Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 2 Nov 2017 11:14:35 -0700 Subject: [PATCH 045/116] Add crashpad --- cmake/macros/PackageCrashpadForDeployment.cmake | 5 +---- interface/CMakeLists.txt | 2 ++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cmake/macros/PackageCrashpadForDeployment.cmake b/cmake/macros/PackageCrashpadForDeployment.cmake index adee7a79e6..1808ddc3d1 100644 --- a/cmake/macros/PackageCrashpadForDeployment.cmake +++ b/cmake/macros/PackageCrashpadForDeployment.cmake @@ -19,12 +19,9 @@ macro(PACKAGE_CRASHPAD_FOR_DEPLOYMENT) COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$/" ) install( - PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/crashpad_handler.exe" + PROGRAMS ${CRASHPAD_HANDLER_EXE_PATH} DESTINATION ${CLIENT_COMPONENT} COMPONENT ${INTERFACE_INSTALL_DIR} ) - - message(STATUS "CRASHPAD_HANDLER_EXE_PATH: ${CRASHPAD_HANDLER_EXE_PATH}") - message(STATUS "Target: $") endif () endmacro() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 6cb1c352c7..b64ad20239 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -349,6 +349,8 @@ endif() package_crashpad_for_deployment() +package_crashpad_for_deployment() + if (WIN32) set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"") From 88f66f03cc20ba85ec4e629b344ecefab4299bd1 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 23 Jan 2018 12:02:37 -0800 Subject: [PATCH 046/116] remove merge mistake --- interface/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index b64ad20239..6cb1c352c7 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -349,8 +349,6 @@ endif() package_crashpad_for_deployment() -package_crashpad_for_deployment() - if (WIN32) set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"") From 39f26e35f0733f396c98e07e0a1fdb32715e08fe Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 22 Jan 2018 17:17:48 -0800 Subject: [PATCH 047/116] backtrace for rc-63 --- interface/src/Application.cpp | 18 +++++++++- interface/src/main.cpp | 7 ++++ libraries/shared/src/SettingInterface.cpp | 41 ++++++++++++----------- libraries/shared/src/SharedUtil.cpp | 38 +++++++++++++++++++++ libraries/shared/src/SharedUtil.h | 26 ++++++++++++-- 5 files changed, 107 insertions(+), 23 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5854501809..62dd1c97cd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -600,7 +600,23 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { qApp->setProperty(hifi::properties::APP_LOCAL_DATA_PATH, cacheDir); } - Setting::init(); + // FIXME fix the OSX installer to install the resources.rcc binary instead of resource files and remove + // this conditional exclusion +#if !defined(Q_OS_OSX) + { +#if defined(Q_OS_ANDROID) + const QString resourcesBinaryFile = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/resources.rcc"; +#else + const QString resourcesBinaryFile = QCoreApplication::applicationDirPath() + "/resources.rcc"; +#endif + if (!QFile::exists(resourcesBinaryFile)) { + throw std::runtime_error("Unable to find primary resources"); + } + if (!QResource::registerResource(resourcesBinaryFile)) { + throw std::runtime_error("Unable to load primary resources"); + } + } +#endif // Tell the plugin manager about our statically linked plugins auto pluginManager = PluginManager::getInstance(); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 069aeb6775..6bede00558 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -57,6 +57,13 @@ int main(int argc, const char* argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + Setting::init(); + + // Instance UserActivityLogger now that the settings are loaded + auto& ual = UserActivityLogger::getInstance(); + + qDebug() << "UserActivityLogger is enabled:" << ual.isEnabled(); + QStringList arguments; for (int i = 0; i < argc; ++i) { arguments << argv[i]; diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index 01b9f3884f..9dc126a6ce 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -20,12 +20,13 @@ #include "SettingHelpers.h" #include "SettingManager.h" #include "SharedLogging.h" +#include "SharedUtil.h" namespace Setting { static QSharedPointer globalManager; // cleans up the settings private instance. Should only be run once at closing down. - void cleanupPrivateInstance() { + static void cleanupPrivateInstance() { // grab the thread before we nuke the instance QThread* settingsManagerThread = DependencyManager::get()->thread(); @@ -34,12 +35,30 @@ namespace Setting { // globalManager.reset(); - + // quit the settings manager thread and wait on it to make sure it's gone settingsManagerThread->quit(); settingsManagerThread->wait(); } - + + static void setupPrivateInstance() { + // Let's set up the settings Private instance on its own thread + QThread* thread = new QThread(); + Q_CHECK_PTR(thread); + thread->setObjectName("Settings Thread"); + + QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer())); + QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + QObject::connect(thread, SIGNAL(finished()), globalManager.data(), SLOT(deleteLater())); + globalManager->moveToThread(thread); + thread->start(); + qCDebug(shared) << "Settings thread started."; + + // Register cleanupPrivateInstance to run inside QCoreApplication's destructor. + qAddPostRoutine(cleanupPrivateInstance); + } + FIXED_Q_COREAPP_STARTUP_FUNCTION(setupPrivateInstance) + // Sets up the settings private instance. Should only be run once at startup. preInit() must be run beforehand, void init() { // Set settings format @@ -59,23 +78,7 @@ namespace Setting { qCDebug(shared) << (deleted ? "Deleted" : "Failed to delete") << "settings lock file" << settingsLockFilename; } - - // Let's set up the settings Private instance on its own thread - QThread* thread = new QThread(); - Q_CHECK_PTR(thread); - thread->setObjectName("Settings Thread"); - globalManager = DependencyManager::set(); - - QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer())); - QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - QObject::connect(thread, SIGNAL(finished()), globalManager.data(), SLOT(deleteLater())); - globalManager->moveToThread(thread); - thread->start(); - qCDebug(shared) << "Settings thread started."; - - // Register cleanupPrivateInstance to run inside QCoreApplication's destructor. - qAddPostRoutine(cleanupPrivateInstance); } void Interface::init() { diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 2d2ec7c28f..8e5c30711c 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -62,6 +63,43 @@ extern "C" FILE * __cdecl __iob_func(void) { #include "OctalCode.h" #include "SharedLogging.h" +static std::unordered_map stagedGlobalInstances; + + +std::mutex& globalInstancesMutex() { + static std::mutex mutex; + return mutex; +} + +static void commitGlobalInstances() { + std::unique_lock lock(globalInstancesMutex()); + for (const auto& it : stagedGlobalInstances) { + qApp->setProperty(it.first.c_str(), it.second); + } + stagedGlobalInstances.clear(); +} +FIXED_Q_COREAPP_STARTUP_FUNCTION(commitGlobalInstances) + +QVariant getGlobalInstance(const char* propertyName) { + if (qApp) { + return qApp->property(propertyName); + } else { + auto it = stagedGlobalInstances.find(propertyName); + if (it != stagedGlobalInstances.end()) { + return it->second; + } + } + return QVariant(); +} + +void setGlobalInstance(const char* propertyName, const QVariant& variant) { + if (qApp) { + qApp->setProperty(propertyName, variant); + } else { + stagedGlobalInstances[propertyName] = variant; + } +} + static qint64 usecTimestampNowAdjust = 0; // in usec void usecTimestampNowForceClockSkew(qint64 clockSkew) { ::usecTimestampNowAdjust = clockSkew; diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 6cf5a4755d..940dc095b8 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -25,6 +25,22 @@ #include #include +// Workaround for https://bugreports.qt.io/browse/QTBUG-54479 +// Wrap target function inside another function that holds +// a unique string identifier and uses it to ensure it only runs once +// by storing a state within the qApp +// We cannot used std::call_once with a static once_flag because +// this is used in shared libraries that are linked by several DLLs +// (ie. plugins), meaning the static will be useless in that case +#define FIXED_Q_COREAPP_STARTUP_FUNCTION(AFUNC) \ + static void AFUNC ## _fixed() { \ + const auto propertyName = std::string(Q_FUNC_INFO) + __FILE__; \ + if (!qApp->property(propertyName.c_str()).toBool()) { \ + AFUNC(); \ + qApp->setProperty(propertyName.c_str(), QVariant(true)); \ + } \ + } \ + Q_COREAPP_STARTUP_FUNCTION(AFUNC ## _fixed) // When writing out avatarEntities to a QByteArray, if the parentID is the ID of MyAvatar, use this ID instead. This allows // the value to be reset when the sessionID changes. @@ -52,6 +68,10 @@ bool destroyGlobalInstance() { return false; } +std::mutex& globalInstancesMutex(); +QVariant getGlobalInstance(const char* propertyName); +void setGlobalInstance(const char* propertyName, const QVariant& variant); + // Provides efficient access to a named global type. By storing the value // in the QApplication by name we can implement the singleton pattern and // have the single instance function across DLL boundaries. @@ -60,9 +80,9 @@ T* globalInstance(const char* propertyName, Args&&... args) { static T* resultInstance { nullptr }; static std::mutex mutex; if (!resultInstance) { - std::unique_lock lock(mutex); + std::unique_lock lock(globalInstancesMutex()); if (!resultInstance) { - auto variant = qApp->property(propertyName); + auto variant = getGlobalInstance(propertyName); if (variant.isNull()) { std::unique_ptr& instancePtr = globalInstancePointer(); if (!instancePtr.get()) { @@ -72,7 +92,7 @@ T* globalInstance(const char* propertyName, Args&&... args) { } void* voidInstance = &(*instancePtr); variant = QVariant::fromValue(voidInstance); - qApp->setProperty(propertyName, variant); + setGlobalInstance(propertyName, variant); } void* returnedVoidInstance = variant.value(); resultInstance = static_cast(returnedVoidInstance); From e32089d3dd67f3a582eac5cb33e6711c7c3f18ee Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 23 Jan 2018 15:28:05 -0800 Subject: [PATCH 048/116] only start crash hanlder if UserActivityLogger::isEnabled() is true --- interface/src/main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 6bede00558..b39bc96bf6 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -64,6 +64,11 @@ int main(int argc, const char* argv[]) { qDebug() << "UserActivityLogger is enabled:" << ual.isEnabled(); + if (ual) { + auto crashHandlerStarted = startCrashHandler(); + qDebug() << "Crash handler started:" << crashHandlerStarted; + } + QStringList arguments; for (int i = 0; i < argc; ++i) { arguments << argv[i]; From c02ab28c60d05552f74b8ddef177275d63c8aea4 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 23 Jan 2018 15:45:02 -0800 Subject: [PATCH 049/116] fix clang build --- libraries/shared/src/SettingInterface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index 9dc126a6ce..878a84da7c 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -26,7 +26,7 @@ namespace Setting { static QSharedPointer globalManager; // cleans up the settings private instance. Should only be run once at closing down. - static void cleanupPrivateInstance() { + void cleanupPrivateInstance() { // grab the thread before we nuke the instance QThread* settingsManagerThread = DependencyManager::get()->thread(); @@ -41,7 +41,7 @@ namespace Setting { settingsManagerThread->wait(); } - static void setupPrivateInstance() { + void setupPrivateInstance() { // Let's set up the settings Private instance on its own thread QThread* thread = new QThread(); Q_CHECK_PTR(thread); From 9eb10c09dab8ef295c4ffcf762b7dd373af8bb20 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 23 Jan 2018 16:32:16 -0800 Subject: [PATCH 050/116] oops --- interface/src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index b39bc96bf6..253f152bb3 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -64,7 +64,7 @@ int main(int argc, const char* argv[]) { qDebug() << "UserActivityLogger is enabled:" << ual.isEnabled(); - if (ual) { + if (ual.isEnabled()) { auto crashHandlerStarted = startCrashHandler(); qDebug() << "Crash handler started:" << crashHandlerStarted; } From df62ba88cb36535dde8eed795a0cb37a880b638e Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 24 Jan 2018 14:31:11 -0800 Subject: [PATCH 051/116] Fix rebase error --- interface/src/main.cpp | 1 - libraries/shared/src/shared/Crashpad.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 253f152bb3..2b89c13ae7 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -61,7 +61,6 @@ int main(int argc, const char* argv[]) { // Instance UserActivityLogger now that the settings are loaded auto& ual = UserActivityLogger::getInstance(); - qDebug() << "UserActivityLogger is enabled:" << ual.isEnabled(); if (ual.isEnabled()) { diff --git a/libraries/shared/src/shared/Crashpad.cpp b/libraries/shared/src/shared/Crashpad.cpp index 7bc3ce7584..cd5262a5b0 100644 --- a/libraries/shared/src/shared/Crashpad.cpp +++ b/libraries/shared/src/shared/Crashpad.cpp @@ -42,7 +42,6 @@ bool startCrashHandler() { annotations["token"] = BACKTRACE_TOKEN; annotations["format"] = "minidump"; - annotations["service-name"] = BuildInfo::INTERFACE_NAME.toStdString(); annotations["version"] = BuildInfo::VERSION.toStdString(); arguments.push_back("--no-rate-limit"); From c550620cb4e51161c228820859977020a1323d5a Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 24 Jan 2018 16:04:47 -0800 Subject: [PATCH 052/116] backtrace for rc-63 --- interface/src/Application.cpp | 4 ++++ interface/src/Application.h | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 62dd1c97cd..25c359573b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -865,6 +865,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _logger->setSessionID(accountManager->getSessionID()); +#if HAS_CRASHPAD + backtraceAnnotations->SetKeyValue("MetaverseSessionID", accountManager->getSessionID()); +#endif // HAS_CRASHPAD + if (steamClient) { qCDebug(interfaceapp) << "[VERSION] SteamVR buildID:" << steamClient->getSteamVRBuildID(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index ddb8ce11e5..a501372598 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -47,6 +47,7 @@ #include #include #include +#include #include @@ -707,5 +708,9 @@ private: std::atomic _pendingIdleEvent { true }; std::atomic _pendingRenderEvent { true }; + +#if HAS_CRASHPAD + crashpad::SimpleStringDictionary* crashpadAnnotations { nullptr }; +#endif // HAS_CRASHPAD }; #endif // hifi_Application_h From dbe9fd290ea9b8c2399e8712f4e41132790cd187 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 24 Jan 2018 20:08:09 -0800 Subject: [PATCH 053/116] comment out code that would set annotations after StartHandler is called --- interface/src/Application.cpp | 4 +--- interface/src/Application.h | 4 ---- libraries/shared/src/shared/Crashpad.cpp | 19 +++++++++++++++++-- libraries/shared/src/shared/Crashpad.h | 3 +++ 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 25c359573b..e8950c6f12 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -865,9 +865,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _logger->setSessionID(accountManager->getSessionID()); -#if HAS_CRASHPAD - backtraceAnnotations->SetKeyValue("MetaverseSessionID", accountManager->getSessionID()); -#endif // HAS_CRASHPAD + setCrashAnnotation("metaverse_session_id", accountManager->getSessionID().toString().toStdString()); if (steamClient) { qCDebug(interfaceapp) << "[VERSION] SteamVR buildID:" << steamClient->getSteamVRBuildID(); diff --git a/interface/src/Application.h b/interface/src/Application.h index a501372598..8a19e69014 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -708,9 +708,5 @@ private: std::atomic _pendingIdleEvent { true }; std::atomic _pendingRenderEvent { true }; - -#if HAS_CRASHPAD - crashpad::SimpleStringDictionary* crashpadAnnotations { nullptr }; -#endif // HAS_CRASHPAD }; #endif // hifi_Application_h diff --git a/libraries/shared/src/shared/Crashpad.cpp b/libraries/shared/src/shared/Crashpad.cpp index cd5262a5b0..f28cad2d18 100644 --- a/libraries/shared/src/shared/Crashpad.cpp +++ b/libraries/shared/src/shared/Crashpad.cpp @@ -23,6 +23,8 @@ #include #include #include +// #include +// #include using namespace crashpad; @@ -31,15 +33,17 @@ static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN }; extern QString qAppFileName(); +// crashpad::AnnotationList* crashpadAnnotations { nullptr }; + bool startCrashHandler() { if (BACKTRACE_URL.empty() || BACKTRACE_TOKEN.empty()) { return false; } CrashpadClient client; - std::map annotations; std::vector arguments; + std::map annotations; annotations["token"] = BACKTRACE_TOKEN; annotations["format"] = "minidump"; annotations["version"] = BuildInfo::VERSION.toStdString(); @@ -75,12 +79,23 @@ bool startCrashHandler() { return client.StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true); } -#else +void setCrashAnnotation(std::string name, std::string value) { + // if (!crashpadAnnotations) { + // crashpadAnnotations = new crashpad::AnnotationList(); // don't free this, let it leak + // crashpad::CrashpadInfo* crashpad_info = crashpad::GetCrashpadInfo(); + // crashpad_info->set_simple_annotations(crashpadAnnotations); + // } + // crashpadAnnotations->SetKeyValue(name, value); +} +#else bool startCrashHandler() { qDebug() << "No crash handler available."; return false; } +void setCrashAnnotation(std::string name, std::string value) { +} + #endif diff --git a/libraries/shared/src/shared/Crashpad.h b/libraries/shared/src/shared/Crashpad.h index a40503a703..c4c559f459 100644 --- a/libraries/shared/src/shared/Crashpad.h +++ b/libraries/shared/src/shared/Crashpad.h @@ -12,6 +12,9 @@ #ifndef hifi_Crashpad_h #define hifi_Crashpad_h +#include + bool startCrashHandler(); +void setCrashAnnotation(std::string name, std::string value); #endif // hifi_Crashpad_h From cb5ca26600b10cc59c00fb66cc24cb48a66e3e5c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 26 Jan 2018 07:22:07 -0800 Subject: [PATCH 054/116] quiet warnings --- interface/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 6cb1c352c7..a06825cd51 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -175,6 +175,10 @@ else () add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM}) endif () +if (WIN32) + set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/ignore:4099") +endif() + if (WIN32) # These are external plugins, but we need to do the 'add dependency' here so that their # binary directories get added to the fixup path From 2962c8979ccde39c2666251b463dd993c4e910d7 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 25 Jan 2018 15:15:59 -0800 Subject: [PATCH 055/116] print warnings if head position contains not-a-number --- interface/src/avatar/MyAvatar.cpp | 37 ++++++++++++++++++++++++------- libraries/animation/src/Rig.cpp | 25 ++++++++++++++------- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e93b897013..57d7c7533e 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -561,6 +561,12 @@ void MyAvatar::simulate(float deltaTime) { if (!_skeletonModel->getHeadPosition(headPosition)) { headPosition = getWorldPosition(); } + + if (isNaN(headPosition)) { + qCDebug(interfaceapp) << "MyAvatar::simulate headPosition is NaN"; + headPosition = glm::vec3(0.0f); + } + head->setPosition(headPosition); head->setScale(getModelScale()); head->simulate(deltaTime); @@ -2700,27 +2706,42 @@ void MyAvatar::setWalkSpeed(float value) { } glm::vec3 MyAvatar::getPositionForAudio() { + glm::vec3 result; switch (_audioListenerMode) { case AudioListenerMode::FROM_HEAD: - return getHead()->getPosition(); + result = getHead()->getPosition(); case AudioListenerMode::FROM_CAMERA: - return qApp->getCamera().getPosition(); + result = qApp->getCamera().getPosition(); case AudioListenerMode::CUSTOM: - return _customListenPosition; + result = _customListenPosition; } - return vec3(); + + if (isNaN(result)) { + qCDebug(interfaceapp) << "MyAvatar::getPositionForAudio produced NaN" << _audioListenerMode; + result = glm::vec3(0.0f); + } + + return result; } glm::quat MyAvatar::getOrientationForAudio() { + glm::quat result; + switch (_audioListenerMode) { case AudioListenerMode::FROM_HEAD: - return getHead()->getFinalOrientationInWorldFrame(); + result = getHead()->getFinalOrientationInWorldFrame(); case AudioListenerMode::FROM_CAMERA: - return qApp->getCamera().getOrientation(); + result = qApp->getCamera().getOrientation(); case AudioListenerMode::CUSTOM: - return _customListenOrientation; + result = _customListenOrientation; } - return quat(); + + if (isNaN(result)) { + qCDebug(interfaceapp) << "MyAvatar::getOrientationForAudio produced NaN" << _audioListenerMode; + result = glm::quat(); + } + + return result; } void MyAvatar::setAudioListenerMode(AudioListenerMode audioListenerMode) { diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index b1b41775a8..309bb59cff 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -445,22 +445,31 @@ void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, flo } bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { + bool success { false }; if (QThread::currentThread() == thread()) { if (isIndexValid(jointIndex)) { position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation; - return true; + success = true; } else { - return false; + success = false; + } + } else { + QReadLocker readLock(&_externalPoseSetLock); + if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) { + position = (rotation * _externalPoseSet._absolutePoses[jointIndex].trans()) + translation; + success = true; + } else { + success = false; } } - QReadLocker readLock(&_externalPoseSetLock); - if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) { - position = (rotation * _externalPoseSet._absolutePoses[jointIndex].trans()) + translation; - return true; - } else { - return false; + if (isNaN(position)) { + qCWarning(animation) << "Rig::getJointPositionInWorldFrame produces NaN"; + success = false; + position = glm::vec3(0.0f); } + + return success; } bool Rig::getJointPosition(int jointIndex, glm::vec3& position) const { From b8f3d8140deb46437fd667b4762ea6a29ff43799 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 26 Jan 2018 10:34:43 -0800 Subject: [PATCH 056/116] add breaks in case statements that used to have returns --- interface/src/avatar/MyAvatar.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 57d7c7533e..b870c61f8f 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2710,10 +2710,13 @@ glm::vec3 MyAvatar::getPositionForAudio() { switch (_audioListenerMode) { case AudioListenerMode::FROM_HEAD: result = getHead()->getPosition(); + break; case AudioListenerMode::FROM_CAMERA: result = qApp->getCamera().getPosition(); + break; case AudioListenerMode::CUSTOM: result = _customListenPosition; + break; } if (isNaN(result)) { @@ -2730,10 +2733,13 @@ glm::quat MyAvatar::getOrientationForAudio() { switch (_audioListenerMode) { case AudioListenerMode::FROM_HEAD: result = getHead()->getFinalOrientationInWorldFrame(); + break; case AudioListenerMode::FROM_CAMERA: result = qApp->getCamera().getOrientation(); + break; case AudioListenerMode::CUSTOM: result = _customListenOrientation; + break; } if (isNaN(result)) { From 19409cc6ce4d56345732a92eca2394989154bbe8 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 26 Jan 2018 10:45:18 -0800 Subject: [PATCH 057/116] fix merge/cherry-pick error --- interface/src/Application.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e8950c6f12..6aead4aa62 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include From b7303414f210c393b9e62a7f0ea43b32b5eb2ca2 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 26 Jan 2018 11:57:30 -0800 Subject: [PATCH 058/116] unmangle merges --- .../macros/PackageCrashpadForDeployment.cmake | 5 +++++ cmake/modules/FindCrashpad.cmake | 2 +- interface/src/Application.cpp | 18 ------------------ interface/src/main.cpp | 2 -- libraries/shared/CMakeLists.txt | 2 -- 5 files changed, 6 insertions(+), 23 deletions(-) diff --git a/cmake/macros/PackageCrashpadForDeployment.cmake b/cmake/macros/PackageCrashpadForDeployment.cmake index 1808ddc3d1..024d1624fb 100644 --- a/cmake/macros/PackageCrashpadForDeployment.cmake +++ b/cmake/macros/PackageCrashpadForDeployment.cmake @@ -13,6 +13,11 @@ macro(PACKAGE_CRASHPAD_FOR_DEPLOYMENT) get_property(HAS_CRASHPAD GLOBAL PROPERTY HAS_CRASHPAD) if (HAS_CRASHPAD) + + if (WIN32) + set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/ignore:4099") + endif() + add_custom_command( TARGET ${TARGET_NAME} POST_BUILD diff --git a/cmake/modules/FindCrashpad.cmake b/cmake/modules/FindCrashpad.cmake index 5095a0b0c9..283058336d 100644 --- a/cmake/modules/FindCrashpad.cmake +++ b/cmake/modules/FindCrashpad.cmake @@ -5,7 +5,7 @@ # Once done this will define # # CRASHPAD_FOUND -# DRACO_INCLUDE_DIRS +# CRASHPAD_INCLUDE_DIRS # CRASHPAD_LIBRARY # CRASHPAD_BASE_LIBRARY # CRASHPAD_UTIL_LIBRARY diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6aead4aa62..6c7093ff4d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -601,24 +601,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { qApp->setProperty(hifi::properties::APP_LOCAL_DATA_PATH, cacheDir); } - // FIXME fix the OSX installer to install the resources.rcc binary instead of resource files and remove - // this conditional exclusion -#if !defined(Q_OS_OSX) - { -#if defined(Q_OS_ANDROID) - const QString resourcesBinaryFile = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/resources.rcc"; -#else - const QString resourcesBinaryFile = QCoreApplication::applicationDirPath() + "/resources.rcc"; -#endif - if (!QFile::exists(resourcesBinaryFile)) { - throw std::runtime_error("Unable to find primary resources"); - } - if (!QResource::registerResource(resourcesBinaryFile)) { - throw std::runtime_error("Unable to load primary resources"); - } - } -#endif - // Tell the plugin manager about our statically linked plugins auto pluginManager = PluginManager::getInstance(); pluginManager->setInputPluginProvider([] { return getInputPlugins(); }); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 2b89c13ae7..94ac8b8008 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -46,8 +46,6 @@ int main(int argc, const char* argv[]) { disableQtBearerPoll(); // Fixes wifi ping spikes - startCrashHandler(); - QElapsedTimer startupTime; startupTime.start(); diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index 379c15c999..a8fe14e23e 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -5,8 +5,6 @@ setup_hifi_library(Gui Network Script Widgets) if (WIN32) target_link_libraries(${TARGET_NAME} Wbemuuid.lib) - - add_crashpad() endif() if (ANDROID) From 1237749628fa5c18ed8a209583cfc7bf6d822c81 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 26 Jan 2018 12:05:26 -0800 Subject: [PATCH 059/116] oops --- interface/CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index a06825cd51..6cb1c352c7 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -175,10 +175,6 @@ else () add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM}) endif () -if (WIN32) - set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/ignore:4099") -endif() - if (WIN32) # These are external plugins, but we need to do the 'add dependency' here so that their # binary directories get added to the fixup path From d73538f233ac4cd375032c383a0f099069731815 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 26 Jan 2018 12:08:19 -0800 Subject: [PATCH 060/116] oops --- libraries/shared/CMakeLists.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index a8fe14e23e..8456838fd3 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -5,10 +5,8 @@ setup_hifi_library(Gui Network Script Widgets) if (WIN32) target_link_libraries(${TARGET_NAME} Wbemuuid.lib) -endif() -if (ANDROID) - target_link_libraries(${TARGET_NAME} android) + add_crashpad() endif() target_zlib() From be42f0cfb1c4d66aba476ee040000104a1850b95 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 26 Jan 2018 13:24:44 -0800 Subject: [PATCH 061/116] finally fix the multihighlighting --- interface/resources/qml/hifi/tablet/TabletHome.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml index f56d4f73be..cfff3a3273 100644 --- a/interface/resources/qml/hifi/tablet/TabletHome.qml +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -127,7 +127,7 @@ Item { GridView { id: gridView - + flickableDirection: Flickable.AutoFlickIfNeeded keyNavigationEnabled: false highlightFollowsCurrentItem: false From 43eaa02ef069f560b8fb1d5477b2c132722026da Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 26 Jan 2018 14:01:33 -0800 Subject: [PATCH 062/116] updated Skinning.slh comment and constant. --- libraries/render-utils/src/Skinning.slh | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/libraries/render-utils/src/Skinning.slh b/libraries/render-utils/src/Skinning.slh index 702813366f..6048ba4ade 100644 --- a/libraries/render-utils/src/Skinning.slh +++ b/libraries/render-utils/src/Skinning.slh @@ -92,8 +92,11 @@ void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPositio // conversion from dual quaternion to 4x4 matrix. mat4 m = dualQuatToMat4(rAccum, dAccum); - // an sAccum.w of > 0 indicates that this joint is cauterized. - if (sAccum.w > 0.1) { + // sAccum.w indicates the amount of cauterization for this vertex. + // 0 indicates no cauterization and 1 indicates full cauterization. + // TODO: make this cauterization smoother or implement full dual-quaternion scale support. + const float CAUTERIZATION_THRESHOLD = 0.1; + if (sAccum.w > CAUTERIZATION_THRESHOLD) { skinnedPosition = cAccum; } else { sAccum.w = 1.0; @@ -140,8 +143,11 @@ void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inP // conversion from dual quaternion to 4x4 matrix. mat4 m = dualQuatToMat4(rAccum, dAccum); - // an sAccum.w of > 0 indicates that this joint is cauterized. - if (sAccum.w > 0.1) { + // sAccum.w indicates the amount of cauterization for this vertex. + // 0 indicates no cauterization and 1 indicates full cauterization. + // TODO: make this cauterization smoother or implement full dual-quaternion scale support. + const float CAUTERIZATION_THRESHOLD = 0.1; + if (sAccum.w > CAUTERIZATION_THRESHOLD) { skinnedPosition = cAccum; } else { sAccum.w = 1.0; @@ -190,8 +196,11 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v // conversion from dual quaternion to 4x4 matrix. mat4 m = dualQuatToMat4(rAccum, dAccum); - // an sAccum.w of > 0 indicates that this vertex is cauterized. - if (sAccum.w > 0.1) { + // sAccum.w indicates the amount of cauterization for this vertex. + // 0 indicates no cauterization and 1 indicates full cauterization. + // TODO: make this cauterization smoother or implement full dual-quaternion scale support. + const float CAUTERIZATION_THRESHOLD = 0.1; + if (sAccum.w > CAUTERIZATION_THRESHOLD) { skinnedPosition = cAccum; } else { sAccum.w = 1.0; From 3be857567b58b20cae316a1890daa6fcdf5f63bb Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 26 Jan 2018 14:01:33 -0800 Subject: [PATCH 063/116] updated Skinning.slh comment and constant. (cherry picked from commit 43eaa02ef069f560b8fb1d5477b2c132722026da) --- libraries/render-utils/src/Skinning.slh | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/libraries/render-utils/src/Skinning.slh b/libraries/render-utils/src/Skinning.slh index 095e210bca..cd839e2776 100644 --- a/libraries/render-utils/src/Skinning.slh +++ b/libraries/render-utils/src/Skinning.slh @@ -92,8 +92,11 @@ void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPositio // conversion from dual quaternion to 4x4 matrix. mat4 m = dualQuatToMat4(rAccum, dAccum); - // an sAccum.w of > 0 indicates that this joint is cauterized. - if (sAccum.w > 0.1) { + // sAccum.w indicates the amount of cauterization for this vertex. + // 0 indicates no cauterization and 1 indicates full cauterization. + // TODO: make this cauterization smoother or implement full dual-quaternion scale support. + const float CAUTERIZATION_THRESHOLD = 0.1; + if (sAccum.w > CAUTERIZATION_THRESHOLD) { skinnedPosition = cAccum; } else { sAccum.w = 1.0; @@ -140,8 +143,11 @@ void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inP // conversion from dual quaternion to 4x4 matrix. mat4 m = dualQuatToMat4(rAccum, dAccum); - // an sAccum.w of > 0 indicates that this joint is cauterized. - if (sAccum.w > 0.1) { + // sAccum.w indicates the amount of cauterization for this vertex. + // 0 indicates no cauterization and 1 indicates full cauterization. + // TODO: make this cauterization smoother or implement full dual-quaternion scale support. + const float CAUTERIZATION_THRESHOLD = 0.1; + if (sAccum.w > CAUTERIZATION_THRESHOLD) { skinnedPosition = cAccum; } else { sAccum.w = 1.0; @@ -190,8 +196,11 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v // conversion from dual quaternion to 4x4 matrix. mat4 m = dualQuatToMat4(rAccum, dAccum); - // an sAccum.w of > 0 indicates that this vertex is cauterized. - if (sAccum.w > 0.1) { + // sAccum.w indicates the amount of cauterization for this vertex. + // 0 indicates no cauterization and 1 indicates full cauterization. + // TODO: make this cauterization smoother or implement full dual-quaternion scale support. + const float CAUTERIZATION_THRESHOLD = 0.1; + if (sAccum.w > CAUTERIZATION_THRESHOLD) { skinnedPosition = cAccum; } else { sAccum.w = 1.0; From 314c51ab3bb48c4bcb5e9c8267f5f4dc7bbbc82a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 26 Jan 2018 15:53:08 -0800 Subject: [PATCH 064/116] Fix settings init in all exe --- domain-server/src/DomainServer.cpp | 1 - domain-server/src/main.cpp | 2 ++ tests/qt59/src/main.cpp | 2 -- tools/ac-client/src/ACClientApp.cpp | 1 - tools/ac-client/src/main.cpp | 2 ++ tools/atp-client/src/ATPClientApp.cpp | 1 - tools/atp-client/src/main.cpp | 2 ++ tools/oven/src/Oven.cpp | 7 ------- tools/oven/src/main.cpp | 8 ++++++++ 9 files changed, 14 insertions(+), 12 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 290f4a7f53..22273ae85f 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -157,7 +157,6 @@ DomainServer::DomainServer(int argc, char* argv[]) : DependencyManager::set(); LogUtils::init(); - Setting::init(); qDebug() << "Setting up domain-server"; qDebug() << "[VERSION] Build sequence:" << qPrintable(applicationVersion()); diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index 725a04ec46..dc3ee54fe7 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -29,6 +29,8 @@ int main(int argc, char* argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + Setting::init(); + #ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); #endif diff --git a/tests/qt59/src/main.cpp b/tests/qt59/src/main.cpp index c66a5e6f9a..7b95cabd6c 100644 --- a/tests/qt59/src/main.cpp +++ b/tests/qt59/src/main.cpp @@ -33,8 +33,6 @@ private: Qt59TestApp::Qt59TestApp(int argc, char* argv[]) : QCoreApplication(argc, argv) { - - Setting::init(); DependencyManager::registerInheritance(); DependencyManager::set([&] { return QString("Mozilla/5.0 (HighFidelityACClient)"); }); DependencyManager::set(); diff --git a/tools/ac-client/src/ACClientApp.cpp b/tools/ac-client/src/ACClientApp.cpp index 88884a4fee..9eadc1dec2 100644 --- a/tools/ac-client/src/ACClientApp.cpp +++ b/tools/ac-client/src/ACClientApp.cpp @@ -97,7 +97,6 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : _password = pieces[1]; } - Setting::init(); DependencyManager::registerInheritance(); DependencyManager::set([&]{ return QString("Mozilla/5.0 (HighFidelityACClient)"); }); diff --git a/tools/ac-client/src/main.cpp b/tools/ac-client/src/main.cpp index 12c5e6f5f8..c9affde3b5 100644 --- a/tools/ac-client/src/main.cpp +++ b/tools/ac-client/src/main.cpp @@ -25,6 +25,8 @@ int main(int argc, char * argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + Setting::init(); + ACClientApp app(argc, argv); return app.exec(); diff --git a/tools/atp-client/src/ATPClientApp.cpp b/tools/atp-client/src/ATPClientApp.cpp index 9fd1bf8d4f..0e7f223f28 100644 --- a/tools/atp-client/src/ATPClientApp.cpp +++ b/tools/atp-client/src/ATPClientApp.cpp @@ -135,7 +135,6 @@ ATPClientApp::ATPClientApp(int argc, char* argv[]) : _domainServerAddress = domainURL.toString(); } - Setting::init(); DependencyManager::registerInheritance(); DependencyManager::set(); diff --git a/tools/atp-client/src/main.cpp b/tools/atp-client/src/main.cpp index 88119604cf..830c049bc7 100644 --- a/tools/atp-client/src/main.cpp +++ b/tools/atp-client/src/main.cpp @@ -25,6 +25,8 @@ int main(int argc, char * argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + Setting::init(); + ATPClientApp app(argc, argv); return app.exec(); diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index 9de06a35bb..69d2ef84ce 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -14,7 +14,6 @@ #include #include -#include #include "ui/OvenMainWindow.h" #include "Oven.h" @@ -29,12 +28,6 @@ static const QString CLI_TYPE_PARAMETER = "t"; Oven::Oven(int argc, char* argv[]) : QApplication(argc, argv) { - QCoreApplication::setOrganizationName("High Fidelity"); - QCoreApplication::setApplicationName("Oven"); - - // init the settings interface so we can save and load settings - Setting::init(); - // parse the command line parameters QCommandLineParser parser; diff --git a/tools/oven/src/main.cpp b/tools/oven/src/main.cpp index 9c778245b5..788470b75e 100644 --- a/tools/oven/src/main.cpp +++ b/tools/oven/src/main.cpp @@ -10,7 +10,15 @@ #include "Oven.h" +#include + int main (int argc, char** argv) { + QCoreApplication::setOrganizationName("High Fidelity"); + QCoreApplication::setApplicationName("Oven"); + + // init the settings interface so we can save and load settings + Setting::init(); + Oven app(argc, argv); return app.exec(); } From 20635acf27d5501e431288576ac5cad98dac571c Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Fri, 26 Jan 2018 17:13:39 -0800 Subject: [PATCH 065/116] moving logic to wallet, crashes currently --- .../scripting/WalletScriptingInterface.cpp | 22 +++++++++++++++++++ .../src/scripting/WalletScriptingInterface.h | 5 +++++ .../ui/overlays/ContextOverlayInterface.cpp | 19 ++++------------ .../src/ui/overlays/ContextOverlayInterface.h | 3 +-- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/interface/src/scripting/WalletScriptingInterface.cpp b/interface/src/scripting/WalletScriptingInterface.cpp index 71a7076bdf..8d6dc7bbcd 100644 --- a/interface/src/scripting/WalletScriptingInterface.cpp +++ b/interface/src/scripting/WalletScriptingInterface.cpp @@ -16,6 +16,16 @@ CheckoutProxy::CheckoutProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(q } WalletScriptingInterface::WalletScriptingInterface() { + _contextOverlayInterface = DependencyManager::get(); + + _entityPropertyFlags += PROP_POSITION; + _entityPropertyFlags += PROP_ROTATION; + _entityPropertyFlags += PROP_MARKETPLACE_ID; + _entityPropertyFlags += PROP_DIMENSIONS; + _entityPropertyFlags += PROP_REGISTRATION_POINT; + _entityPropertyFlags += PROP_CERTIFICATE_ID; + _entityPropertyFlags += PROP_CLIENT_ONLY; + _entityPropertyFlags += PROP_OWNING_AVATAR_ID; } void WalletScriptingInterface::refreshWalletStatus() { @@ -26,4 +36,16 @@ void WalletScriptingInterface::refreshWalletStatus() { void WalletScriptingInterface::setWalletStatus(const uint& status) { _walletStatus = status; emit DependencyManager::get()->walletStatusResult(status); +} + +void WalletScriptingInterface::proveAvatarEntityOwnershipVerification(const QUuid& entityID) { + EntityItemProperties entityProperties = DependencyManager::get()->getEntityProperties(entityID, _entityPropertyFlags); + if (!entityID.isNull() && entityProperties.getMarketplaceID().length() > 0) { + if (!entityProperties.getClientOnly()) { + qCDebug(entities) << "Failed to prove ownership of:" << entityID << "is not an avatar entity"; + emit _contextOverlayInterface->ownershipVerificationFailed(entityID); + return; + } + _contextOverlayInterface->requestOwnershipVerification(entityID); + } } \ No newline at end of file diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index 5469e732c7..c725c34685 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -21,6 +21,7 @@ #include #include "Application.h" #include "commerce/Wallet.h" +#include "ui/overlays/ContextOverlayInterface.h" class CheckoutProxy : public QmlWrapper { Q_OBJECT @@ -33,12 +34,15 @@ class WalletScriptingInterface : public QObject, public Dependency { Q_OBJECT Q_PROPERTY(uint walletStatus READ getWalletStatus WRITE setWalletStatus NOTIFY walletStatusChanged) + QSharedPointer _contextOverlayInterface; + public: WalletScriptingInterface(); Q_INVOKABLE void refreshWalletStatus(); Q_INVOKABLE uint getWalletStatus() { return _walletStatus; } + Q_INVOKABLE void proveAvatarEntityOwnershipVerification(const QUuid& entityID); // setWalletStatus() should never be made Q_INVOKABLE. If it were, // scripts could cause the Wallet to incorrectly report its status. void setWalletStatus(const uint& status); @@ -49,6 +53,7 @@ signals: private: uint _walletStatus; + EntityPropertyFlags _entityPropertyFlags; }; #endif // hifi_WalletScriptingInterface_h diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index d86d441e3b..4a5aa3d0f4 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -267,21 +267,10 @@ void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemI } } -void ContextOverlayInterface::proveAvatarEntityOwnershipVerification(const QUuid& entityItemID) { - EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags); - _entityMarketplaceID = entityProperties.getMarketplaceID(); - if (!entityItemID.isNull() && _entityMarketplaceID.length() > 0) { - if (!entityProperties.getClientOnly()) { - qCDebug(entities) << "Failed to prove ownership of:" << entityItemID << "is not an avatar entity"; - emit ownershipVerificationFailed(entityItemID); - return; - } - setLastInspectedEntity(entityItemID); - requestOwnershipVerification(); - } -} +void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID) { + + setLastInspectedEntity(entityID); -void ContextOverlayInterface::requestOwnershipVerification() { EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags); auto nodeList = DependencyManager::get(); @@ -381,7 +370,7 @@ void ContextOverlayInterface::openInspectionCertificate() { _hmdScriptingInterface->openTablet(); setLastInspectedEntity(_currentEntityWithContextOverlay); - requestOwnershipVerification(); + requestOwnershipVerification(_lastInspectedEntity); } } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index 04416299d6..4063b28785 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -50,13 +50,13 @@ class ContextOverlayInterface : public QObject, public Dependency { public: ContextOverlayInterface(); Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; } - Q_INVOKABLE void proveAvatarEntityOwnershipVerification(const QUuid& entityID); void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; } void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; } void setEnabled(bool enabled); bool getEnabled() { return _enabled; } bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; } void setIsInMarketplaceInspectionMode(bool mode) { _isInMarketplaceInspectionMode = mode; } + void requestOwnershipVerification(const QUuid& entityID); signals: void contextOverlayClicked(const QUuid& currentEntityWithContextOverlay); @@ -92,7 +92,6 @@ private: bool _isInMarketplaceInspectionMode { false }; void openInspectionCertificate(); - void requestOwnershipVerification(); void openMarketplace(); void enableEntityHighlight(const EntityItemID& entityItemID); void disableEntityHighlight(const EntityItemID& entityItemID); From 3d9985fa6732e1a0a1f5431f866c56a34c65ab06 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Sat, 27 Jan 2018 08:48:19 -0800 Subject: [PATCH 066/116] FIx the scattering ambient lighting diffuse which was too dark (got divided by PI) comparedd to non scattering --- libraries/render-utils/src/LightAmbient.slh | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/render-utils/src/LightAmbient.slh b/libraries/render-utils/src/LightAmbient.slh index eb565d60e4..097b4f4335 100644 --- a/libraries/render-utils/src/LightAmbient.slh +++ b/libraries/render-utils/src/LightAmbient.slh @@ -99,7 +99,6 @@ void evalLightingAmbient(out vec3 diffuse, out vec3 specular, LightAmbient ambie // Diffuse from ambient diffuse = sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(ambient), lowNormalCurvature.xyz).xyz; - diffuse /= 3.1415926; specular = vec3(0.0); } <@endif@> From 221475d7d9097b8c097656e57f4f5d1b567c568b Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Sat, 27 Jan 2018 10:37:32 -0800 Subject: [PATCH 067/116] FIxing the dark ambient lighting on scattering surface --- libraries/render-utils/src/LightAmbient.slh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/render-utils/src/LightAmbient.slh b/libraries/render-utils/src/LightAmbient.slh index eb565d60e4..acd30d527d 100644 --- a/libraries/render-utils/src/LightAmbient.slh +++ b/libraries/render-utils/src/LightAmbient.slh @@ -99,8 +99,9 @@ void evalLightingAmbient(out vec3 diffuse, out vec3 specular, LightAmbient ambie // Diffuse from ambient diffuse = sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(ambient), lowNormalCurvature.xyz).xyz; - diffuse /= 3.1415926; - specular = vec3(0.0); + + // Scattering ambient specular is the same as non scattering for now + // TODO: we should use the same specular answer as for direct lighting } <@endif@> From 898c99e5b83a7b5315f626e152bcb82933daecb9 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 27 Jan 2018 19:50:52 +0100 Subject: [PATCH 068/116] append file jsdoc json file to resources.rcc resource file --- cmake/macros/GenerateQrc.cmake | 9 ++++++- interface/CMakeLists.txt | 49 +++++++++++++--------------------- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/cmake/macros/GenerateQrc.cmake b/cmake/macros/GenerateQrc.cmake index 9bf530b2a2..5e2be71c82 100644 --- a/cmake/macros/GenerateQrc.cmake +++ b/cmake/macros/GenerateQrc.cmake @@ -1,7 +1,7 @@ function(GENERATE_QRC) set(oneValueArgs OUTPUT PREFIX PATH) - set(multiValueArgs GLOBS) + set(multiValueArgs CUSTOM_PATHS GLOBS) cmake_parse_arguments(GENERATE_QRC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) if ("${GENERATE_QRC_PREFIX}" STREQUAL "") set(QRC_PREFIX_PATH /) @@ -20,6 +20,13 @@ function(GENERATE_QRC) endforeach() endforeach() + foreach(CUSTOM_PATH ${GENERATE_QRC_CUSTOM_PATHS}) + string(REPLACE "=" ";" CUSTOM_PATH ${CUSTOM_PATH}) + list(GET CUSTOM_PATH 0 IMPORT_PATH) + list(GET CUSTOM_PATH 1 LOCAL_PATH) + set(QRC_CONTENTS "${QRC_CONTENTS}${IMPORT_PATH}\n") + endforeach() + set(GENERATE_QRC_DEPENDS ${ALL_FILES} PARENT_SCOPE) configure_file("${HF_CMAKE_DIR}/templates/resources.qrc.in" ${GENERATE_QRC_OUTPUT}) endfunction() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index d0d89919d5..fc3bc4a0d1 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -11,9 +11,22 @@ function(JOIN VALUES GLUE OUTPUT) set (${OUTPUT} "${_TMP_STR}" PARENT_SCOPE) endfunction() +set(CUSTOM_INTERFACE_QRC_PATHS "") + +function(ADD_CUSTOM_QRC_PATH CUSTOM_PATHS_VAR IMPORT_PATH LOCAL_PATH) + list(APPEND ${CUSTOM_PATHS_VAR} "${IMPORT_PATH}=${LOCAL_PATH}") + set(${CUSTOM_PATHS_VAR} ${${CUSTOM_PATHS_VAR}} PARENT_SCOPE) +endfunction() + +find_npm() + +if (BUILD_TOOLS AND NPM_EXECUTABLE) + add_custom_qrc_path(CUSTOM_INTERFACE_QRC_PATHS "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" "auto-complete/hifiJSDoc.json") +endif () + set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc) set(RESOURCES_RCC ${CMAKE_CURRENT_SOURCE_DIR}/compiledResources/resources.rcc) -generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *) +generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources CUSTOM_PATHS ${CUSTOM_INTERFACE_QRC_PATHS} GLOBS *) add_custom_command( OUTPUT ${RESOURCES_RCC} @@ -53,8 +66,6 @@ file(GLOB_RECURSE INTERFACE_SRCS "src/*.cpp" "src/*.h") GroupSources("src") list(APPEND INTERFACE_SRCS ${RESOURCES_RCC}) -find_npm() - # Add SpeechRecognizer if on Windows or OS X, otherwise remove if (WIN32) # Use .cpp and .h files as is. @@ -165,6 +176,11 @@ else () add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM}) endif () +if (BUILD_TOOLS AND NPM_EXECUTABLE) + # require JSDoc to be build before interface is deployed (Console Auto-complete) + add_dependencies(resources jsdoc) +endif() + add_dependencies(${TARGET_NAME} resources) if (WIN32) @@ -276,11 +292,6 @@ endif() # assume we are using a Qt build without bearer management add_definitions(-DQT_NO_BEARERMANAGEMENT) -if (BUILD_TOOLS) - # require JSDoc to be build before interface is deployed (Console Auto-complete) - add_dependencies(${TARGET_NAME} jsdoc) -endif() - if (APPLE) # link in required OS X frameworks and include the right GL headers find_library(OpenGL OpenGL) @@ -296,38 +307,18 @@ if (APPLE) set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources") - if (BUILD_TOOLS AND NPM_EXECUTABLE) - set(EXTRA_COPY_COMMANDS - COMMAND "${CMAKE_COMMAND}" -E make_directory - "$/../Resources/auto-complete" - COMMAND "${CMAKE_COMMAND}" -E copy - "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" - "$/../Resources/auto-complete" - ) - endif() # copy script files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/../Resources/scripts" - - ${EXTRA_COPY_COMMANDS} ) # call the fixup_interface macro to add required bundling commands for installation fixup_interface() else() - if (BUILD_TOOLS AND NPM_EXECUTABLE) - set(EXTRA_COPY_COMMANDS - COMMAND "${CMAKE_COMMAND}" -E make_directory - "$/resources/auto-complete" - COMMAND "${CMAKE_COMMAND}" -E copy - "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" - "$/resources/auto-complete" - ) - endif() # copy the resources files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_if_different @@ -344,8 +335,6 @@ else() COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/scripts" - - ${EXTRA_COPY_COMMANDS} ) # link target to external libraries From 8fdbac521fc6b010248921dfb0d63bc2e6bee0a5 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 27 Jan 2018 19:58:15 +0100 Subject: [PATCH 069/116] move ADD_CUSTOM_QRC_PATH function to its own file --- cmake/macros/AddCustomQrcPath.cmake | 7 +++++++ interface/CMakeLists.txt | 5 ----- 2 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 cmake/macros/AddCustomQrcPath.cmake diff --git a/cmake/macros/AddCustomQrcPath.cmake b/cmake/macros/AddCustomQrcPath.cmake new file mode 100644 index 0000000000..6bd54baa4d --- /dev/null +++ b/cmake/macros/AddCustomQrcPath.cmake @@ -0,0 +1,7 @@ +# adds a custom path and local path to the inserted CUSTOM_PATHS_VAR list which +# can be given to the GENERATE_QRC command + +function(ADD_CUSTOM_QRC_PATH CUSTOM_PATHS_VAR IMPORT_PATH LOCAL_PATH) + list(APPEND ${CUSTOM_PATHS_VAR} "${IMPORT_PATH}=${LOCAL_PATH}") + set(${CUSTOM_PATHS_VAR} ${${CUSTOM_PATHS_VAR}} PARENT_SCOPE) +endfunction() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index fc3bc4a0d1..a49019e775 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -13,11 +13,6 @@ endfunction() set(CUSTOM_INTERFACE_QRC_PATHS "") -function(ADD_CUSTOM_QRC_PATH CUSTOM_PATHS_VAR IMPORT_PATH LOCAL_PATH) - list(APPEND ${CUSTOM_PATHS_VAR} "${IMPORT_PATH}=${LOCAL_PATH}") - set(${CUSTOM_PATHS_VAR} ${${CUSTOM_PATHS_VAR}} PARENT_SCOPE) -endfunction() - find_npm() if (BUILD_TOOLS AND NPM_EXECUTABLE) From 206927d72ac254f27cf7073ac122c0db34d9eca1 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sat, 27 Jan 2018 11:42:17 -0800 Subject: [PATCH 070/116] Fix haze parameters uniform buffer size --- libraries/graphics/src/graphics/Haze.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/graphics/src/graphics/Haze.h b/libraries/graphics/src/graphics/Haze.h index 608449a97e..eda9f1e407 100644 --- a/libraries/graphics/src/graphics/Haze.h +++ b/libraries/graphics/src/graphics/Haze.h @@ -117,13 +117,14 @@ namespace graphics { // Amount of background (skybox) to display, overriding the haze effect for the background float hazeBackgroundBlend{ INITIAL_HAZE_BACKGROUND_BLEND }; - // The haze attenuation exponents used by both fragment and directional light attenuation float hazeRangeFactor{ convertHazeRangeToHazeRangeFactor(INITIAL_HAZE_RANGE) }; float hazeHeightFactor{ convertHazeAltitudeToHazeAltitudeFactor(INITIAL_HAZE_HEIGHT) }; - float hazeKeyLightRangeFactor{ convertHazeRangeToHazeRangeFactor(INITIAL_KEY_LIGHT_RANGE) }; + float hazeKeyLightAltitudeFactor{ convertHazeAltitudeToHazeAltitudeFactor(INITIAL_KEY_LIGHT_ALTITUDE) }; + // Padding required to align the structure to sizeof(vec4) + vec3 __padding; Parameters() {} }; From d334f1c35ce1729ddd6d106b1a7347c88fd49040 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 29 Jan 2018 13:45:39 +1300 Subject: [PATCH 071/116] Don't append trailing FBX root nodes to the tail of the tree --- libraries/fbx/src/FBXReader.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 171fc88443..2f5277241c 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -323,12 +323,12 @@ public: }; void appendModelIDs(const QString& parentID, const QMultiMap& connectionChildMap, - QHash& models, QSet& remainingModels, QVector& modelIDs) { + QHash& models, QSet& remainingModels, QVector& modelIDs, bool isRootNode = false) { if (remainingModels.contains(parentID)) { modelIDs.append(parentID); remainingModels.remove(parentID); } - int parentIndex = modelIDs.size() - 1; + int parentIndex = isRootNode ? -1 : modelIDs.size() - 1; foreach (const QString& childID, connectionChildMap.values(parentID)) { if (remainingModels.contains(childID)) { FBXModel& model = models[childID]; @@ -1478,7 +1478,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } QString topID = getTopModelID(_connectionParentMap, models, first, url); - appendModelIDs(_connectionParentMap.value(topID), _connectionChildMap, models, remainingModels, modelIDs); + appendModelIDs(_connectionParentMap.value(topID), _connectionChildMap, models, remainingModels, modelIDs, true); } // figure the number of animation frames from the curves From 6acec39e92b5684f69010b5cb93154ae72b27196 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 29 Jan 2018 10:05:23 -0800 Subject: [PATCH 072/116] HMD low LOD threshold changed: 45 --> 42 --- interface/src/LODManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index ca6be9380b..9e9871b4ad 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -20,7 +20,7 @@ #include const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0f; -const float DEFAULT_HMD_LOD_DOWN_FPS = 45.0f; +const float DEFAULT_HMD_LOD_DOWN_FPS = 42.0f; const float DEFAULT_DESKTOP_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_DESKTOP_LOD_DOWN_FPS; // msec const float DEFAULT_HMD_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_HMD_LOD_DOWN_FPS; // msec const float MAX_LIKELY_DESKTOP_FPS = 59.0f; // this is essentially, V-synch - 1 fps From c2817fc09123ecbeac0d1e30de3c12da2d57ca30 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 29 Jan 2018 10:18:08 -0800 Subject: [PATCH 073/116] HMD low LOD threshold changed: 42 --> 34 --- interface/src/LODManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 9e9871b4ad..7b10579077 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -20,7 +20,7 @@ #include const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0f; -const float DEFAULT_HMD_LOD_DOWN_FPS = 42.0f; +const float DEFAULT_HMD_LOD_DOWN_FPS = 34.0f; const float DEFAULT_DESKTOP_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_DESKTOP_LOD_DOWN_FPS; // msec const float DEFAULT_HMD_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_HMD_LOD_DOWN_FPS; // msec const float MAX_LIKELY_DESKTOP_FPS = 59.0f; // this is essentially, V-synch - 1 fps From 0e3a01f1e8a1b1671076731f274d27b612bb65bd Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Mon, 29 Jan 2018 10:20:16 -0800 Subject: [PATCH 074/116] it's working! removed my null pointer and just get it from the dependency manager when required --- interface/src/scripting/WalletScriptingInterface.cpp | 5 ++--- interface/src/scripting/WalletScriptingInterface.h | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/interface/src/scripting/WalletScriptingInterface.cpp b/interface/src/scripting/WalletScriptingInterface.cpp index 8d6dc7bbcd..87c5c89ab3 100644 --- a/interface/src/scripting/WalletScriptingInterface.cpp +++ b/interface/src/scripting/WalletScriptingInterface.cpp @@ -16,7 +16,6 @@ CheckoutProxy::CheckoutProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(q } WalletScriptingInterface::WalletScriptingInterface() { - _contextOverlayInterface = DependencyManager::get(); _entityPropertyFlags += PROP_POSITION; _entityPropertyFlags += PROP_ROTATION; @@ -43,9 +42,9 @@ void WalletScriptingInterface::proveAvatarEntityOwnershipVerification(const QUui if (!entityID.isNull() && entityProperties.getMarketplaceID().length() > 0) { if (!entityProperties.getClientOnly()) { qCDebug(entities) << "Failed to prove ownership of:" << entityID << "is not an avatar entity"; - emit _contextOverlayInterface->ownershipVerificationFailed(entityID); + emit DependencyManager::get()->ownershipVerificationFailed(entityID); return; } - _contextOverlayInterface->requestOwnershipVerification(entityID); + DependencyManager::get()->requestOwnershipVerification(entityID); } } \ No newline at end of file diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index c725c34685..8e2dcd47a8 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -34,8 +34,6 @@ class WalletScriptingInterface : public QObject, public Dependency { Q_OBJECT Q_PROPERTY(uint walletStatus READ getWalletStatus WRITE setWalletStatus NOTIFY walletStatusChanged) - QSharedPointer _contextOverlayInterface; - public: WalletScriptingInterface(); From 68fd9eafa8c3fd7409d410750be1cae162ec89b1 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 29 Jan 2018 10:23:24 -0800 Subject: [PATCH 075/116] fix entity parenting crash --- libraries/entities/src/EntityTree.cpp | 15 +++++++++++++-- libraries/entities/src/EntityTree.h | 2 ++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 15fc3256bd..91694c86aa 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1678,14 +1678,23 @@ void EntityTree::entityChanged(EntityItemPointer entity) { } } +QVector EntityTree::getEntitiesParentFixup() const { + QReadLocker locker(&_needsParentFixupLock); + return _needsParentFixup; +} + +void EntityTree::setNeedsParentFixup(QVector entitiesFixup) { + QWriteLocker locker(&_needsParentFixupLock); + _needsParentFixup = entitiesFixup; +} void EntityTree::fixupNeedsParentFixups() { PROFILE_RANGE(simulation_physics, "FixupParents"); MovingEntitiesOperator moveOperator; - QWriteLocker locker(&_needsParentFixupLock); + QVector entitiesParentFixup = getEntitiesParentFixup(); - QMutableVectorIterator iter(_needsParentFixup); + QMutableVectorIterator iter(entitiesParentFixup); while (iter.hasNext()) { EntityItemWeakPointer entityWP = iter.next(); EntityItemPointer entity = entityWP.lock(); @@ -1748,6 +1757,8 @@ void EntityTree::fixupNeedsParentFixups() { PerformanceTimer perfTimer("recurseTreeWithOperator"); recurseTreeWithOperator(&moveOperator); } + + setNeedsParentFixup(entitiesParentFixup); } void EntityTree::deleteDescendantsOfAvatar(QUuid avatarID) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 11a747d624..fa44fd37b8 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -385,6 +385,8 @@ private: void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode); void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation); + QVector getEntitiesParentFixup() const; + void setNeedsParentFixup(QVector entitiesFixup); std::shared_ptr _myAvatar{ nullptr }; }; From 6a971e136296f962695f142a22570661465d7287 Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Mon, 29 Jan 2018 11:27:30 -0800 Subject: [PATCH 076/116] remove restriction to be avatar only --- interface/src/scripting/WalletScriptingInterface.cpp | 9 +++------ interface/src/scripting/WalletScriptingInterface.h | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/interface/src/scripting/WalletScriptingInterface.cpp b/interface/src/scripting/WalletScriptingInterface.cpp index 87c5c89ab3..3e125caeeb 100644 --- a/interface/src/scripting/WalletScriptingInterface.cpp +++ b/interface/src/scripting/WalletScriptingInterface.cpp @@ -37,14 +37,11 @@ void WalletScriptingInterface::setWalletStatus(const uint& status) { emit DependencyManager::get()->walletStatusResult(status); } -void WalletScriptingInterface::proveAvatarEntityOwnershipVerification(const QUuid& entityID) { +void WalletScriptingInterface::proveEntityOwnershipVerification(const QUuid& entityID) { EntityItemProperties entityProperties = DependencyManager::get()->getEntityProperties(entityID, _entityPropertyFlags); if (!entityID.isNull() && entityProperties.getMarketplaceID().length() > 0) { - if (!entityProperties.getClientOnly()) { - qCDebug(entities) << "Failed to prove ownership of:" << entityID << "is not an avatar entity"; - emit DependencyManager::get()->ownershipVerificationFailed(entityID); - return; - } DependencyManager::get()->requestOwnershipVerification(entityID); + } else { + qCDebug(entities) << "Failed to prove ownership of:" << entityID << "is null or not a marketplace item"; } } \ No newline at end of file diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index 8e2dcd47a8..960c38d6fb 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -40,7 +40,7 @@ public: Q_INVOKABLE void refreshWalletStatus(); Q_INVOKABLE uint getWalletStatus() { return _walletStatus; } - Q_INVOKABLE void proveAvatarEntityOwnershipVerification(const QUuid& entityID); + Q_INVOKABLE void proveEntityOwnershipVerification(const QUuid& entityID); // setWalletStatus() should never be made Q_INVOKABLE. If it were, // scripts could cause the Wallet to incorrectly report its status. void setWalletStatus(const uint& status); From 00555250a07bcc062f1ed5d77c70c65a7405e135 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 29 Jan 2018 12:07:07 -0800 Subject: [PATCH 077/116] 400 connections per page for Wallet and PAL --- scripts/system/commerce/wallet.js | 2 +- scripts/system/pal.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index ad864622ed..46672a3f66 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -78,7 +78,7 @@ }); } function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successful. (Logs otherwise) - url = METAVERSE_BASE + '/api/v1/users?' + url = METAVERSE_BASE + '/api/v1/users?per_page=400&' if (domain) { url += 'status=' + domain.slice(1, -1); // without curly braces } else { diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 1b93bdde32..5a656c4ba0 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -361,7 +361,7 @@ function getProfilePicture(username, callback) { // callback(url) if successfull }); } function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise) - url = METAVERSE_BASE + '/api/v1/users?' + url = METAVERSE_BASE + '/api/v1/users?per_page=400&' if (domain) { url += 'status=' + domain.slice(1, -1); // without curly braces } else { From 061f9ea35953cd6c234d6f7d92d0ff3d70f0ee76 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 29 Jan 2018 11:52:43 -0800 Subject: [PATCH 078/116] Don't run settings preroutine when not initialized --- libraries/shared/src/SettingInterface.cpp | 30 +++++++++++++---------- libraries/shared/src/SharedUtil.h | 16 ++++++------ 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index 878a84da7c..327668574e 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -33,7 +33,6 @@ namespace Setting { // tell the private instance to clean itself up on its thread DependencyManager::destroy(); - // globalManager.reset(); // quit the settings manager thread and wait on it to make sure it's gone @@ -42,20 +41,23 @@ namespace Setting { } void setupPrivateInstance() { - // Let's set up the settings Private instance on its own thread - QThread* thread = new QThread(); - Q_CHECK_PTR(thread); - thread->setObjectName("Settings Thread"); + // Ensure Setting::init has already ran and qApp exists + if (qApp && globalManager) { + // Let's set up the settings Private instance on its own thread + QThread* thread = new QThread(); + Q_CHECK_PTR(thread); + thread->setObjectName("Settings Thread"); - QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer())); - QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - QObject::connect(thread, SIGNAL(finished()), globalManager.data(), SLOT(deleteLater())); - globalManager->moveToThread(thread); - thread->start(); - qCDebug(shared) << "Settings thread started."; + QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer())); + QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + QObject::connect(thread, SIGNAL(finished()), globalManager.data(), SLOT(deleteLater())); + globalManager->moveToThread(thread); + thread->start(); + qCDebug(shared) << "Settings thread started."; - // Register cleanupPrivateInstance to run inside QCoreApplication's destructor. - qAddPostRoutine(cleanupPrivateInstance); + // Register cleanupPrivateInstance to run inside QCoreApplication's destructor. + qAddPostRoutine(cleanupPrivateInstance); + } } FIXED_Q_COREAPP_STARTUP_FUNCTION(setupPrivateInstance) @@ -79,6 +81,8 @@ namespace Setting { } globalManager = DependencyManager::set(); + + setupPrivateInstance(); } void Interface::init() { diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 940dc095b8..5a1e48d9c0 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -32,14 +32,14 @@ // We cannot used std::call_once with a static once_flag because // this is used in shared libraries that are linked by several DLLs // (ie. plugins), meaning the static will be useless in that case -#define FIXED_Q_COREAPP_STARTUP_FUNCTION(AFUNC) \ - static void AFUNC ## _fixed() { \ - const auto propertyName = std::string(Q_FUNC_INFO) + __FILE__; \ - if (!qApp->property(propertyName.c_str()).toBool()) { \ - AFUNC(); \ - qApp->setProperty(propertyName.c_str(), QVariant(true)); \ - } \ - } \ +#define FIXED_Q_COREAPP_STARTUP_FUNCTION(AFUNC) \ + static void AFUNC ## _fixed() { \ + const auto propertyName = std::string(Q_FUNC_INFO) + __FILE__; \ + if (!qApp->property(propertyName.c_str()).toBool()) { \ + AFUNC(); \ + qApp->setProperty(propertyName.c_str(), QVariant(true)); \ + } \ + } \ Q_COREAPP_STARTUP_FUNCTION(AFUNC ## _fixed) // When writing out avatarEntities to a QByteArray, if the parentID is the ID of MyAvatar, use this ID instead. This allows From f8f76d6757ae6c089ae1b562b0d5efd815e71f6f Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Mon, 29 Jan 2018 12:46:00 -0800 Subject: [PATCH 079/116] changing back to avatar only and making some changes to avoid multiple calls to dependency manager --- .../scripting/WalletScriptingInterface.cpp | 24 +++++++++---------- .../src/scripting/WalletScriptingInterface.h | 3 +-- .../ui/overlays/ContextOverlayInterface.cpp | 1 - .../src/ui/overlays/ContextOverlayInterface.h | 1 + 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/interface/src/scripting/WalletScriptingInterface.cpp b/interface/src/scripting/WalletScriptingInterface.cpp index 3e125caeeb..e2158b9fd7 100644 --- a/interface/src/scripting/WalletScriptingInterface.cpp +++ b/interface/src/scripting/WalletScriptingInterface.cpp @@ -17,14 +17,6 @@ CheckoutProxy::CheckoutProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(q WalletScriptingInterface::WalletScriptingInterface() { - _entityPropertyFlags += PROP_POSITION; - _entityPropertyFlags += PROP_ROTATION; - _entityPropertyFlags += PROP_MARKETPLACE_ID; - _entityPropertyFlags += PROP_DIMENSIONS; - _entityPropertyFlags += PROP_REGISTRATION_POINT; - _entityPropertyFlags += PROP_CERTIFICATE_ID; - _entityPropertyFlags += PROP_CLIENT_ONLY; - _entityPropertyFlags += PROP_OWNING_AVATAR_ID; } void WalletScriptingInterface::refreshWalletStatus() { @@ -37,11 +29,17 @@ void WalletScriptingInterface::setWalletStatus(const uint& status) { emit DependencyManager::get()->walletStatusResult(status); } -void WalletScriptingInterface::proveEntityOwnershipVerification(const QUuid& entityID) { - EntityItemProperties entityProperties = DependencyManager::get()->getEntityProperties(entityID, _entityPropertyFlags); - if (!entityID.isNull() && entityProperties.getMarketplaceID().length() > 0) { - DependencyManager::get()->requestOwnershipVerification(entityID); +void WalletScriptingInterface::proveAvatarEntityOwnershipVerification(const QUuid& entityID) { + QSharedPointer contextOverlayInterface = DependencyManager::get(); + EntityItemProperties entityProperties = DependencyManager::get()->getEntityProperties(entityID, + contextOverlayInterface->getEntityPropertyFlags()); + if (entityProperties.getClientOnly()) { + if (!entityID.isNull() && entityProperties.getCertificateID().length() > 0) { + contextOverlayInterface->requestOwnershipVerification(entityID); + } else { + qCDebug(entities) << "Failed to prove ownership of:" << entityID << "is null or not a certified item"; + } } else { - qCDebug(entities) << "Failed to prove ownership of:" << entityID << "is null or not a marketplace item"; + qCDebug(entities) << "Failed to prove ownership of:" << entityID << "is not an avatar entity"; } } \ No newline at end of file diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index 960c38d6fb..8f5c65e335 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -40,7 +40,7 @@ public: Q_INVOKABLE void refreshWalletStatus(); Q_INVOKABLE uint getWalletStatus() { return _walletStatus; } - Q_INVOKABLE void proveEntityOwnershipVerification(const QUuid& entityID); + Q_INVOKABLE void proveAvatarEntityOwnershipVerification(const QUuid& entityID); // setWalletStatus() should never be made Q_INVOKABLE. If it were, // scripts could cause the Wallet to incorrectly report its status. void setWalletStatus(const uint& status); @@ -51,7 +51,6 @@ signals: private: uint _walletStatus; - EntityPropertyFlags _entityPropertyFlags; }; #endif // hifi_WalletScriptingInterface_h diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 4a5aa3d0f4..a273304eaa 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -189,7 +189,6 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& return false; } - bool ContextOverlayInterface::contextOverlayFilterPassed(const EntityItemID& entityItemID) { EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags); Setting::Handle _settingSwitch{ "commerce", true }; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index 4063b28785..5bb0b522a3 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -57,6 +57,7 @@ public: bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; } void setIsInMarketplaceInspectionMode(bool mode) { _isInMarketplaceInspectionMode = mode; } void requestOwnershipVerification(const QUuid& entityID); + EntityPropertyFlags getEntityPropertyFlags() { return _entityPropertyFlags; } signals: void contextOverlayClicked(const QUuid& currentEntityWithContextOverlay); From 634bc83ed5e6ef325a206a9bbde9213a92af7156 Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Mon, 29 Jan 2018 12:54:52 -0800 Subject: [PATCH 080/116] fix elses --- .../ui/overlays/ContextOverlayInterface.cpp | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index a273304eaa..a7f8861882 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -299,14 +299,11 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID if (networkReply->error() == QNetworkReply::NoError) { if (!jsonObject["invalid_reason"].toString().isEmpty()) { qCDebug(entities) << "invalid_reason not empty"; - } - else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") { + } else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") { qCDebug(entities) << "'transfer_status' is 'failed'"; - } - else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { + } else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { qCDebug(entities) << "'transfer_status' is 'pending'"; - } - else { + } else { QString ownerKey = jsonObject["transfer_recipient_key"].toString(); QByteArray certID = entityProperties.getCertificateID().toUtf8(); @@ -332,25 +329,21 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID if (thread() != QThread::currentThread()) { QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer"); return; - } - else { + } else { startChallengeOwnershipTimer(); } } - } - else { + } else { qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "More info:" << networkReply->readAll(); } networkReply->deleteLater(); }); - } - else { + } else { qCWarning(context_overlay) << "Couldn't get Entity Server!"; } - } - else { + } else { auto ledger = DependencyManager::get(); _challengeOwnershipTimeoutTimer.stop(); emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); From 08511a2a2ef779874154519c2c80ed0dc912903f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 29 Jan 2018 15:27:55 -0800 Subject: [PATCH 081/116] fix new model path --- scripts/system/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 92ccdf6565..00989d95f9 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -469,7 +469,7 @@ var toolBar = (function () { // tablet version of new-model dialog var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - tablet.pushOntoStack("NewModelDialog.qml"); + tablet.pushOntoStack("hifi/tablet/NewModelDialog.qml"); }); addButton("newCubeButton", "cube-01.svg", function () { From ce8159340b31c3372018409b70c5f968e4557b28 Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Mon, 29 Jan 2018 15:51:09 -0800 Subject: [PATCH 082/116] fix vs autoformatting --- interface/src/ui/overlays/ContextOverlayInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index 5bb0b522a3..a260c9cc39 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -84,7 +84,7 @@ private: MAX_SELECTION_COUNT = 16 }; bool _verboseLogging{ true }; - bool _enabled{ true }; + bool _enabled { true }; EntityItemID _currentEntityWithContextOverlay{}; EntityItemID _lastInspectedEntity{}; QString _entityMarketplaceID; From 8b7c9895081d957aba5242488dde56465295cda5 Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Mon, 29 Jan 2018 16:34:44 -0800 Subject: [PATCH 083/116] move emit signals to wallet interface --- interface/src/scripting/WalletScriptingInterface.h | 2 ++ interface/src/ui/overlays/ContextOverlayInterface.cpp | 8 ++++---- interface/src/ui/overlays/ContextOverlayInterface.h | 3 +-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index 8f5c65e335..9e40aad087 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -48,6 +48,8 @@ public: signals: void walletStatusChanged(); void walletNotSetup(); + void ownershipVerificationSuccess(const QUuid& entityID); + void ownershipVerificationFailed(const QUuid& entityID); private: uint _walletStatus; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index a7f8861882..ed7b811fb0 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -347,7 +347,7 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID auto ledger = DependencyManager::get(); _challengeOwnershipTimeoutTimer.stop(); emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); - emit ownershipVerificationFailed(_lastInspectedEntity); + emit DependencyManager::get()->ownershipVerificationFailed(_lastInspectedEntity); qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!"; } } @@ -404,7 +404,7 @@ void ContextOverlayInterface::startChallengeOwnershipTimer() { connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() { qCDebug(entities) << "Ownership challenge timed out for" << _lastInspectedEntity; emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT)); - emit ownershipVerificationFailed(_lastInspectedEntity); + emit DependencyManager::get()->ownershipVerificationFailed(_lastInspectedEntity); }); _challengeOwnershipTimeoutTimer.start(5000); @@ -429,9 +429,9 @@ void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer if (verificationSuccess) { emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); - emit ownershipVerificationSuccess(_lastInspectedEntity); + emit DependencyManager::get()->ownershipVerificationSuccess(_lastInspectedEntity); } else { emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED)); - emit ownershipVerificationFailed(_lastInspectedEntity); + emit DependencyManager::get()->ownershipVerificationFailed(_lastInspectedEntity); } } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index a260c9cc39..6aad2a773b 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -26,6 +26,7 @@ #include "ui/overlays/Overlays.h" #include "scripting/HMDScriptingInterface.h" #include "scripting/SelectionScriptingInterface.h" +#include "scripting/WalletScriptingInterface.h" #include "EntityTree.h" #include "ContextOverlayLogging.h" @@ -61,8 +62,6 @@ public: signals: void contextOverlayClicked(const QUuid& currentEntityWithContextOverlay); - void ownershipVerificationSuccess(const QUuid& entityID); - void ownershipVerificationFailed(const QUuid& entityID); public slots: bool createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event); From 09b92b93aab5a54c0df2a667d36289fc2352393d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 30 Jan 2018 14:36:34 +1300 Subject: [PATCH 084/116] Handle geometric offsets in FBX mesh extents calculations --- libraries/fbx/src/FBXReader.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 2f5277241c..e23d46df18 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -272,6 +272,11 @@ glm::mat4 getGlobalTransform(const QMultiMap& _connectionParen const FBXModel& model = models.value(nodeID); globalTransform = glm::translate(model.translation) * model.preTransform * glm::mat4_cast(model.preRotation * model.rotation * model.postRotation) * model.postTransform * globalTransform; + if (model.hasGeometricOffset) { + glm::mat4 geometricOffset = createMatFromScaleQuatAndPos(model.geometricScaling, model.geometricRotation, model.geometricTranslation); + globalTransform = globalTransform * geometricOffset; + } + if (mixamoHack) { // there's something weird about the models from Mixamo Fuse; they don't skin right with the full transform return globalTransform; From cadd4e61ad5bb2c2e5e3025614f16fcf7feac00b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 30 Jan 2018 14:36:53 +1300 Subject: [PATCH 085/116] Fix indent --- libraries/fbx/src/FBXReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index e23d46df18..aa5c594981 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1538,7 +1538,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS joint.transform = geometry.offset * glm::translate(joint.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; joint.inverseDefaultRotation = glm::inverse(combinedRotation); - joint.distanceToParent = 0.0f; + joint.distanceToParent = 0.0f; } else { const FBXJoint& parentJoint = geometry.joints.at(joint.parentIndex); From 3aa12eb7df2a36d2d12786f4a2b7669407d479d5 Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Mon, 29 Jan 2018 17:51:26 -0800 Subject: [PATCH 086/116] pass in deletedIDs so can delete children depth > 1 --- scripts/system/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 92ccdf6565..f8c097795a 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1344,7 +1344,7 @@ function recursiveDelete(entities, childrenList, deletedIDs) { var entityID = entities[i]; var children = Entities.getChildrenIDs(entityID); var grandchildrenList = []; - recursiveDelete(children, grandchildrenList); + recursiveDelete(children, grandchildrenList, deletedIDs); var initialProperties = Entities.getEntityProperties(entityID); childrenList.push({ entityID: entityID, From cb6bebd824cfc3fa53212d4a9d67e6a6e2838778 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 29 Jan 2018 19:43:32 -0800 Subject: [PATCH 087/116] Fix Mac dev builds --- libraries/shared/src/PathUtils.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 41db7281ac..ac402a549f 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -37,10 +37,10 @@ QString TEMP_DIR_FORMAT { "%1-%2-%3" }; #if !defined(Q_OS_ANDROID) && defined(DEV_BUILD) -#if defined(Q_OS_OSX) -static bool USE_SOURCE_TREE_RESOURCES = true; -#else static bool USE_SOURCE_TREE_RESOURCES() { +#if defined(Q_OS_OSX) + return true; +#else static bool result = false; static std::once_flag once; std::call_once(once, [&] { @@ -48,8 +48,8 @@ static bool USE_SOURCE_TREE_RESOURCES() { result = QProcessEnvironment::systemEnvironment().contains(USE_SOURCE_TREE_RESOURCES_FLAG); }); return result; -} #endif +} #endif #ifdef DEV_BUILD From 280264d7edec00af0dd7309fca5ad6937287920a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 30 Jan 2018 20:46:27 +1300 Subject: [PATCH 088/116] Add location.domainID as a synonym for location.domainId And deprecate location.domainId. --- .../src/entities/EntityServer.cpp | 2 +- libraries/networking/src/AddressManager.cpp | 2 +- libraries/networking/src/AddressManager.h | 9 ++++++--- scripts/system/pal.js | 2 +- scripts/system/snapshot.js | 18 +++++++++--------- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index ecdf14ebec..f72832f902 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -453,7 +453,7 @@ void EntityServer::domainSettingsRequestFailed() { void EntityServer::startDynamicDomainVerification() { qCDebug(entities) << "Starting Dynamic Domain Verification..."; - QString thisDomainID = DependencyManager::get()->getDomainId().remove(QRegExp("\\{|\\}")); + QString thisDomainID = DependencyManager::get()->getDomainID().remove(QRegExp("\\{|\\}")); EntityTreePointer tree = std::static_pointer_cast(_tree); QHash localMap(tree->getEntityCertificateIDMap()); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index fa5507fcf7..4e1354d3aa 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -776,7 +776,7 @@ void AddressManager::copyPath() { QApplication::clipboard()->setText(currentPath()); } -QString AddressManager::getDomainId() const { +QString AddressManager::getDomainID() const { return DependencyManager::get()->getDomainHandler().getUUID().toString(); } diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 0173a1fad7..6c9f06d2dd 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -35,9 +35,11 @@ const QString GET_PLACE = "/api/v1/places/%1"; * The location API provides facilities related to your current location in the metaverse. * * @namespace location - * @property {Uuid} domainId - A UUID uniquely identifying the domain you're visiting. Is {@link Uuid|Uuid.NULL} if you're not + * @property {Uuid} domainID - A UUID uniquely identifying the domain you're visiting. Is {@link Uuid|Uuid.NULL} if you're not * connected to the domain. * Read-only. + * @property {Uuid} domainId - Synonym for domainId. Read-only. Deprecated: This property + * is deprecated and will soon be removed. * @property {string} hostname - The name of the domain for your current metaverse address (e.g., "AvatarIsland", * localhost, or an IP address). * Read-only. @@ -66,7 +68,8 @@ class AddressManager : public QObject, public Dependency { Q_PROPERTY(QString hostname READ getHost) Q_PROPERTY(QString pathname READ currentPath) Q_PROPERTY(QString placename READ getPlaceName) - Q_PROPERTY(QString domainId READ getDomainId) + Q_PROPERTY(QString domainID READ getDomainID) + Q_PROPERTY(QString domainId READ getDomainID) public: /**jsdoc @@ -164,7 +167,7 @@ public: const QUuid& getRootPlaceID() const { return _rootPlaceID; } const QString& getPlaceName() const { return _shareablePlaceName.isEmpty() ? _placeName : _shareablePlaceName; } - QString getDomainId() const; + QString getDomainID() const; const QString& getHost() const { return _host; } diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 1b93bdde32..b551276e91 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -493,7 +493,7 @@ function populateNearbyUserList(selectData, oldAudioData) { data.push(avatarPalDatum); print('PAL data:', JSON.stringify(avatarPalDatum)); }); - getConnectionData(false, location.domainId); // Even admins don't get relationship data in requestUsernameFromID (which is still needed for admin status, which comes from domain). + getConnectionData(false, location.domainID); // Even admins don't get relationship data in requestUsernameFromID (which is still needed for admin status, which comes from domain). conserveResources = Object.keys(avatarsOfInterest).length > 20; sendToQml({ method: 'nearbyUsers', params: data }); if (selectData) { diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index dad642075f..ae8ef52a15 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -337,7 +337,7 @@ function fillImageDataFromPrevious() { containsGif: previousAnimatedSnapPath !== "", processingGif: false, shouldUpload: false, - canBlast: location.domainId === Settings.getValue("previousSnapshotDomainID"), + canBlast: location.domainID === Settings.getValue("previousSnapshotDomainID"), isLoggedIn: isLoggedIn }; imageData = []; @@ -416,7 +416,7 @@ function snapshotUploaded(isError, reply) { } isUploadingPrintableStill = false; } -var href, domainId; +var href, domainID; function takeSnapshot() { tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", @@ -443,11 +443,11 @@ function takeSnapshot() { MyAvatar.setClearOverlayWhenMoving(false); // We will record snapshots based on the starting location. That could change, e.g., when recording a .gif. - // Even the domainId could change (e.g., if the user falls into a teleporter while recording). + // Even the domainID could change (e.g., if the user falls into a teleporter while recording). href = location.href; Settings.setValue("previousSnapshotHref", href); - domainId = location.domainId; - Settings.setValue("previousSnapshotDomainID", domainId); + domainID = location.domainID; + Settings.setValue("previousSnapshotDomainID", domainID); maybeDeleteSnapshotStories(); @@ -548,7 +548,7 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { } HMD.openTablet(); - isDomainOpen(domainId, function (canShare) { + isDomainOpen(domainID, function (canShare) { snapshotOptions = { containsGif: false, processingGif: false, @@ -594,7 +594,7 @@ function processingGifStarted(pathStillSnapshot) { } HMD.openTablet(); - isDomainOpen(domainId, function (canShare) { + isDomainOpen(domainID, function (canShare) { snapshotOptions = { containsGif: true, processingGif: true, @@ -622,13 +622,13 @@ function processingGifCompleted(pathAnimatedSnapshot) { Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot); - isDomainOpen(domainId, function (canShare) { + isDomainOpen(domainID, function (canShare) { snapshotOptions = { containsGif: true, processingGif: false, canShare: canShare, isLoggedIn: isLoggedIn, - canBlast: location.domainId === Settings.getValue("previousSnapshotDomainID"), + canBlast: location.domainID === Settings.getValue("previousSnapshotDomainID"), }; imageData = [{ localPath: pathAnimatedSnapshot, href: href }]; tablet.emitScriptEvent(JSON.stringify({ From 2a6ff80911f49d350d90fd574b7168756ef40d50 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 30 Jan 2018 09:13:47 -0800 Subject: [PATCH 089/116] fix indentation --- .../macros/PackageCrashpadForDeployment.cmake | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/cmake/macros/PackageCrashpadForDeployment.cmake b/cmake/macros/PackageCrashpadForDeployment.cmake index 024d1624fb..65509c31d4 100644 --- a/cmake/macros/PackageCrashpadForDeployment.cmake +++ b/cmake/macros/PackageCrashpadForDeployment.cmake @@ -10,23 +10,23 @@ # macro(PACKAGE_CRASHPAD_FOR_DEPLOYMENT) - get_property(HAS_CRASHPAD GLOBAL PROPERTY HAS_CRASHPAD) + get_property(HAS_CRASHPAD GLOBAL PROPERTY HAS_CRASHPAD) - if (HAS_CRASHPAD) + if (HAS_CRASHPAD) - if (WIN32) - set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/ignore:4099") - endif() + if (WIN32) + set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/ignore:4099") + endif() - add_custom_command( - TARGET ${TARGET_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$/" - ) - install( - PROGRAMS ${CRASHPAD_HANDLER_EXE_PATH} - DESTINATION ${CLIENT_COMPONENT} - COMPONENT ${INTERFACE_INSTALL_DIR} - ) - endif () + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$/" + ) + install( + PROGRAMS ${CRASHPAD_HANDLER_EXE_PATH} + DESTINATION ${CLIENT_COMPONENT} + COMPONENT ${INTERFACE_INSTALL_DIR} + ) + endif () endmacro() From bed2ea052df8dbe4f08b501dd0646b0eeb905d7a Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 30 Jan 2018 09:44:49 -0800 Subject: [PATCH 090/116] better solution --- libraries/entities/src/EntityTree.cpp | 18 +++--------------- libraries/entities/src/EntityTree.h | 2 -- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 8dad126d30..f485f4121e 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -65,7 +65,7 @@ public: EntityTree::EntityTree(bool shouldReaverage) : - Octree(shouldReaverage) + Octree(shouldReaverage), _needsParentFixupLock(QReadWriteLock::Recursive) { resetClientEditStats(); @@ -1679,23 +1679,13 @@ void EntityTree::entityChanged(EntityItemPointer entity) { } } -QVector EntityTree::getEntitiesParentFixup() const { - QReadLocker locker(&_needsParentFixupLock); - return _needsParentFixup; -} - -void EntityTree::setNeedsParentFixup(QVector entitiesFixup) { - QWriteLocker locker(&_needsParentFixupLock); - _needsParentFixup = entitiesFixup; -} - void EntityTree::fixupNeedsParentFixups() { PROFILE_RANGE(simulation_physics, "FixupParents"); MovingEntitiesOperator moveOperator; - QVector entitiesParentFixup = getEntitiesParentFixup(); + QWriteLocker locker(&_needsParentFixupLock); - QMutableVectorIterator iter(entitiesParentFixup); + QMutableVectorIterator iter(_needsParentFixup); while (iter.hasNext()) { EntityItemWeakPointer entityWP = iter.next(); EntityItemPointer entity = entityWP.lock(); @@ -1758,8 +1748,6 @@ void EntityTree::fixupNeedsParentFixups() { PerformanceTimer perfTimer("recurseTreeWithOperator"); recurseTreeWithOperator(&moveOperator); } - - setNeedsParentFixup(entitiesParentFixup); } void EntityTree::deleteDescendantsOfAvatar(QUuid avatarID) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index f4ff7ad118..8cb89d6493 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -385,8 +385,6 @@ private: void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode); void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation); - QVector getEntitiesParentFixup() const; - void setNeedsParentFixup(QVector entitiesFixup); std::shared_ptr _myAvatar{ nullptr }; }; From 4c0a1732874c205d3235f1ae28536c8b648f31d7 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 30 Jan 2018 10:23:19 -0800 Subject: [PATCH 091/116] trying another solution --- libraries/entities/src/EntityTree.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index f485f4121e..bf29f3bec9 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -65,7 +65,7 @@ public: EntityTree::EntityTree(bool shouldReaverage) : - Octree(shouldReaverage), _needsParentFixupLock(QReadWriteLock::Recursive) + Octree(shouldReaverage) { resetClientEditStats(); @@ -1682,10 +1682,14 @@ void EntityTree::entityChanged(EntityItemPointer entity) { void EntityTree::fixupNeedsParentFixups() { PROFILE_RANGE(simulation_physics, "FixupParents"); MovingEntitiesOperator moveOperator; + QVector entitiesToFixup; + { + QWriteLocker locker(&_needsParentFixupLock); + entitiesToFixup = _needsParentFixup; + _needsParentFixup.clear(); + } - QWriteLocker locker(&_needsParentFixupLock); - - QMutableVectorIterator iter(_needsParentFixup); + QMutableVectorIterator iter(entitiesToFixup); while (iter.hasNext()) { EntityItemWeakPointer entityWP = iter.next(); EntityItemPointer entity = entityWP.lock(); @@ -1748,6 +1752,12 @@ void EntityTree::fixupNeedsParentFixups() { PerformanceTimer perfTimer("recurseTreeWithOperator"); recurseTreeWithOperator(&moveOperator); } + + { + QWriteLocker locker(&_needsParentFixupLock); + // add back the entities that did not get fixup + _needsParentFixup.append(entitiesToFixup); + } } void EntityTree::deleteDescendantsOfAvatar(QUuid avatarID) { From 1e8a5485c1b407c9d69755d7d1cc91b8f2138b36 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 30 Jan 2018 10:03:19 -0800 Subject: [PATCH 092/116] 8x shader compilation optimization and watchdog disable addPlumberPipeline in RenderPipelines.cpp would recompile a single program 7 more times then necessary. This was causing startup times on Mac OS X to trigger the deadlock watchdog. Even with this fix, compiling shaders is still too slow, As a workaround, we disable the watchdog thread during rendering pipeline setup. --- interface/src/Application.cpp | 24 ++++++++++- libraries/render/src/render/ShapePipeline.cpp | 42 ++++++++++--------- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5854501809..326575c6cc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -378,6 +378,7 @@ public: setObjectName("Deadlock Watchdog"); // Give the heartbeat an initial value _heartbeat = usecTimestampNow(); + _paused = false; connect(qApp, &QCoreApplication::aboutToQuit, [this] { _quit = true; }); @@ -395,11 +396,20 @@ public: *crashTrigger = 0xDEAD10CC; } + static void pause() { + _paused = true; + } + + static void resume() { + _paused = false; + updateHeartbeat(); + } + void run() override { while (!_quit) { QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS); // Don't do heartbeat detection under nsight - if (nsightActive()) { + if (nsightActive() || _paused) { continue; } uint64_t lastHeartbeat = _heartbeat; // sample atomic _heartbeat, because we could context switch away and have it updated on us @@ -455,6 +465,7 @@ public: } } + static std::atomic _paused; static std::atomic _heartbeat; static std::atomic _maxElapsed; static std::atomic _maxElapsedAverage; @@ -463,6 +474,7 @@ public: bool _quit { false }; }; +std::atomic DeadlockWatchdogThread::_paused; std::atomic DeadlockWatchdogThread::_heartbeat; std::atomic DeadlockWatchdogThread::_maxElapsed; std::atomic DeadlockWatchdogThread::_maxElapsedAverage; @@ -2206,6 +2218,11 @@ void Application::initializeGL() { initDisplay(); qCDebug(interfaceapp, "Initialized Display."); +#ifdef Q_OS_OSX + // FIXME: on mac os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread. + DeadlockWatchdogThread::pause(); +#endif + // Set up the render engine render::CullFunctor cullFunctor = LODManager::shouldRender; static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; @@ -2213,6 +2230,11 @@ void Application::initializeGL() { _renderEngine->addJob("UpdateScene"); _renderEngine->addJob("SecondaryCameraJob", cullFunctor); _renderEngine->addJob("RenderMainView", cullFunctor, isDeferred); + +#ifdef Q_OS_OSX + DeadlockWatchdogThread::resume(); +#endif + _renderEngine->load(); _renderEngine->registerScene(_main3DScene); diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 4cf1b306ab..b825a349ad 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -70,26 +70,30 @@ void ShapePlumber::addPipeline(const Key& key, const gpu::ShaderPointer& program void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& program, const gpu::StatePointer& state, BatchSetter batchSetter, ItemSetter itemSetter) { - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), Slot::BUFFER::LIGHTING_MODEL)); - slotBindings.insert(gpu::Shader::Binding(std::string("skinClusterBuffer"), Slot::BUFFER::SKINNING)); - slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), Slot::BUFFER::MATERIAL)); - slotBindings.insert(gpu::Shader::Binding(std::string("texMapArrayBuffer"), Slot::BUFFER::TEXMAPARRAY)); - slotBindings.insert(gpu::Shader::Binding(std::string("albedoMap"), Slot::MAP::ALBEDO)); - slotBindings.insert(gpu::Shader::Binding(std::string("roughnessMap"), Slot::MAP::ROUGHNESS)); - slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), Slot::MAP::NORMAL)); - slotBindings.insert(gpu::Shader::Binding(std::string("metallicMap"), Slot::MAP::METALLIC)); - slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), Slot::MAP::EMISSIVE_LIGHTMAP)); - slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), Slot::MAP::OCCLUSION)); - slotBindings.insert(gpu::Shader::Binding(std::string("scatteringMap"), Slot::MAP::SCATTERING)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::BUFFER::LIGHT)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), Slot::BUFFER::LIGHT_AMBIENT_BUFFER)); - slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT)); - slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK)); - slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS)); - slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), Slot::BUFFER::HAZE_MODEL)); - gpu::Shader::makeProgram(*program, slotBindings); + // don't call makeProgram on shaders that are already made. + if (program->getUniformBuffers().empty()) { + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), Slot::BUFFER::LIGHTING_MODEL)); + slotBindings.insert(gpu::Shader::Binding(std::string("skinClusterBuffer"), Slot::BUFFER::SKINNING)); + slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), Slot::BUFFER::MATERIAL)); + slotBindings.insert(gpu::Shader::Binding(std::string("texMapArrayBuffer"), Slot::BUFFER::TEXMAPARRAY)); + slotBindings.insert(gpu::Shader::Binding(std::string("albedoMap"), Slot::MAP::ALBEDO)); + slotBindings.insert(gpu::Shader::Binding(std::string("roughnessMap"), Slot::MAP::ROUGHNESS)); + slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), Slot::MAP::NORMAL)); + slotBindings.insert(gpu::Shader::Binding(std::string("metallicMap"), Slot::MAP::METALLIC)); + slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), Slot::MAP::EMISSIVE_LIGHTMAP)); + slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), Slot::MAP::OCCLUSION)); + slotBindings.insert(gpu::Shader::Binding(std::string("scatteringMap"), Slot::MAP::SCATTERING)); + slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::BUFFER::LIGHT)); + slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), Slot::BUFFER::LIGHT_AMBIENT_BUFFER)); + slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT)); + slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK)); + slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS)); + slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), Slot::BUFFER::HAZE_MODEL)); + + gpu::Shader::makeProgram(*program, slotBindings); + } auto locations = std::make_shared(); From db08f1dda67d95c869ca62cbc3f0ff69456112d4 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 30 Jan 2018 11:39:48 -0800 Subject: [PATCH 093/116] Move backtrace to interface --- cmake/macros/AddCrashpad.cmake | 16 +++++++++- .../macros/PackageCrashpadForDeployment.cmake | 32 ------------------- interface/CMakeLists.txt | 3 +- interface/src/Application.cpp | 1 + interface/src/Application.h | 1 - .../src/shared => interface/src}/Crashpad.cpp | 0 .../src/shared => interface/src}/Crashpad.h | 0 interface/src/main.cpp | 2 +- libraries/shared/CMakeLists.txt | 2 -- 9 files changed, 18 insertions(+), 39 deletions(-) delete mode 100644 cmake/macros/PackageCrashpadForDeployment.cmake rename {libraries/shared/src/shared => interface/src}/Crashpad.cpp (100%) rename {libraries/shared/src/shared => interface/src}/Crashpad.h (100%) diff --git a/cmake/macros/AddCrashpad.cmake b/cmake/macros/AddCrashpad.cmake index 573e13c8a2..64c0216912 100644 --- a/cmake/macros/AddCrashpad.cmake +++ b/cmake/macros/AddCrashpad.cmake @@ -26,7 +26,6 @@ macro(add_crashpad) endif() if (WIN32 AND USE_CRASHPAD AND NOT CRASHPAD_CHECKED) - set_property(GLOBAL PROPERTY HAS_CRASHPAD TRUE) add_definitions(-DHAS_CRASHPAD) add_definitions(-DCMAKE_BACKTRACE_URL=\"${CMAKE_BACKTRACE_URL}\") add_definitions(-DCMAKE_BACKTRACE_TOKEN=\"${CMAKE_BACKTRACE_TOKEN}\") @@ -35,6 +34,21 @@ macro(add_crashpad) find_package(crashpad REQUIRED) target_include_directories(${TARGET_NAME} PRIVATE ${CRASHPAD_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${CRASHPAD_LIBRARY} ${CRASHPAD_BASE_LIBRARY} ${CRASHPAD_UTIL_LIBRARY}) + + if (WIN32) + set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/ignore:4099") + endif() + + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$/" + ) + install( + PROGRAMS ${CRASHPAD_HANDLER_EXE_PATH} + DESTINATION ${CLIENT_COMPONENT} + COMPONENT ${INTERFACE_INSTALL_DIR} + ) set_property(GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE TRUE) endif () diff --git a/cmake/macros/PackageCrashpadForDeployment.cmake b/cmake/macros/PackageCrashpadForDeployment.cmake deleted file mode 100644 index 65509c31d4..0000000000 --- a/cmake/macros/PackageCrashpadForDeployment.cmake +++ /dev/null @@ -1,32 +0,0 @@ -# -# PackageCrashpadForDeployment.cmake -# cmake/macros -# -# Copyright 2018 High Fidelity, Inc. -# Created by Clement Brisset on 01/19/18 -# -# Distributed under the Apache License, Version 2.0. -# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# - -macro(PACKAGE_CRASHPAD_FOR_DEPLOYMENT) - get_property(HAS_CRASHPAD GLOBAL PROPERTY HAS_CRASHPAD) - - if (HAS_CRASHPAD) - - if (WIN32) - set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/ignore:4099") - endif() - - add_custom_command( - TARGET ${TARGET_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$/" - ) - install( - PROGRAMS ${CRASHPAD_HANDLER_EXE_PATH} - DESTINATION ${CLIENT_COMPONENT} - COMPONENT ${INTERFACE_INSTALL_DIR} - ) - endif () -endmacro() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 6cb1c352c7..db1ea6df9a 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -216,6 +216,7 @@ target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries target_bullet() target_opengl() +add_crashpad() # perform standard include and linking for found externals foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) @@ -347,8 +348,6 @@ if (SCRIPTS_INSTALL_DIR) ) endif() -package_crashpad_for_deployment() - if (WIN32) set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"") diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6c7093ff4d..92988267c4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -145,6 +145,7 @@ #include "avatar/AvatarManager.h" #include "avatar/MyHead.h" #include "CrashHandler.h" +#include "Crashpad.h" #include "devices/DdeFaceTracker.h" #include "DiscoverabilityManager.h" #include "GLCanvas.h" diff --git a/interface/src/Application.h b/interface/src/Application.h index 8a19e69014..ddb8ce11e5 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -47,7 +47,6 @@ #include #include #include -#include #include diff --git a/libraries/shared/src/shared/Crashpad.cpp b/interface/src/Crashpad.cpp similarity index 100% rename from libraries/shared/src/shared/Crashpad.cpp rename to interface/src/Crashpad.cpp diff --git a/libraries/shared/src/shared/Crashpad.h b/interface/src/Crashpad.h similarity index 100% rename from libraries/shared/src/shared/Crashpad.h rename to interface/src/Crashpad.h diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 94ac8b8008..fdc4f091f0 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -24,10 +24,10 @@ #include #include #include -#include #include "AddressManager.h" #include "Application.h" +#include "Crashpad.h" #include "InterfaceLogging.h" #include "UserActivityLogger.h" #include "MainWindow.h" diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index 8456838fd3..da345d1970 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -5,8 +5,6 @@ setup_hifi_library(Gui Network Script Widgets) if (WIN32) target_link_libraries(${TARGET_NAME} Wbemuuid.lib) - - add_crashpad() endif() target_zlib() From edfffc575a07d4c8d8b3e54419d58535e18947c3 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 30 Jan 2018 11:48:01 -0800 Subject: [PATCH 094/116] Handle multiple targets for add_crashpad --- cmake/macros/AddCrashpad.cmake | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/cmake/macros/AddCrashpad.cmake b/cmake/macros/AddCrashpad.cmake index 64c0216912..8694c4968a 100644 --- a/cmake/macros/AddCrashpad.cmake +++ b/cmake/macros/AddCrashpad.cmake @@ -10,8 +10,6 @@ # macro(add_crashpad) - get_property(CRASHPAD_CHECKED GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE) - set (USE_CRASHPAD TRUE) if ("$ENV{CMAKE_BACKTRACE_URL}" STREQUAL "") set (USE_CRASHPAD FALSE) @@ -25,13 +23,20 @@ macro(add_crashpad) set (CMAKE_BACKTRACE_TOKEN $ENV{CMAKE_BACKTRACE_TOKEN}) endif() - if (WIN32 AND USE_CRASHPAD AND NOT CRASHPAD_CHECKED) + if (WIN32 AND USE_CRASHPAD) + get_property(CRASHPAD_CHECKED GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE) + if (NOT CRASHPAD_CHECKED) + + add_dependency_external_projects(crashpad) + find_package(crashpad REQUIRED) + + set_property(GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE TRUE) + endif() + add_definitions(-DHAS_CRASHPAD) add_definitions(-DCMAKE_BACKTRACE_URL=\"${CMAKE_BACKTRACE_URL}\") add_definitions(-DCMAKE_BACKTRACE_TOKEN=\"${CMAKE_BACKTRACE_TOKEN}\") - add_dependency_external_projects(crashpad) - find_package(crashpad REQUIRED) target_include_directories(${TARGET_NAME} PRIVATE ${CRASHPAD_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${CRASHPAD_LIBRARY} ${CRASHPAD_BASE_LIBRARY} ${CRASHPAD_UTIL_LIBRARY}) @@ -49,7 +54,5 @@ macro(add_crashpad) DESTINATION ${CLIENT_COMPONENT} COMPONENT ${INTERFACE_INSTALL_DIR} ) - - set_property(GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE TRUE) endif () endmacro() From 019a2d346c30525ea85df258b0e3341f1770d989 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 30 Jan 2018 12:00:24 -0800 Subject: [PATCH 095/116] Fix spaces --- cmake/macros/AddCrashpad.cmake | 18 +++++++++--------- interface/src/Crashpad.cpp | 2 +- interface/src/Crashpad.h | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cmake/macros/AddCrashpad.cmake b/cmake/macros/AddCrashpad.cmake index 8694c4968a..bf59418f37 100644 --- a/cmake/macros/AddCrashpad.cmake +++ b/cmake/macros/AddCrashpad.cmake @@ -12,25 +12,25 @@ macro(add_crashpad) set (USE_CRASHPAD TRUE) if ("$ENV{CMAKE_BACKTRACE_URL}" STREQUAL "") - set (USE_CRASHPAD FALSE) + set(USE_CRASHPAD FALSE) else() - set (CMAKE_BACKTRACE_URL $ENV{CMAKE_BACKTRACE_URL}) + set(CMAKE_BACKTRACE_URL $ENV{CMAKE_BACKTRACE_URL}) endif() if ("$ENV{CMAKE_BACKTRACE_TOKEN}" STREQUAL "") - set (USE_CRASHPAD FALSE) + set(USE_CRASHPAD FALSE) else() - set (CMAKE_BACKTRACE_TOKEN $ENV{CMAKE_BACKTRACE_TOKEN}) + set(CMAKE_BACKTRACE_TOKEN $ENV{CMAKE_BACKTRACE_TOKEN}) endif() if (WIN32 AND USE_CRASHPAD) get_property(CRASHPAD_CHECKED GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE) if (NOT CRASHPAD_CHECKED) - - add_dependency_external_projects(crashpad) - find_package(crashpad REQUIRED) - set_property(GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE TRUE) + add_dependency_external_projects(crashpad) + find_package(crashpad REQUIRED) + + set_property(GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE TRUE) endif() add_definitions(-DHAS_CRASHPAD) @@ -39,7 +39,7 @@ macro(add_crashpad) target_include_directories(${TARGET_NAME} PRIVATE ${CRASHPAD_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${CRASHPAD_LIBRARY} ${CRASHPAD_BASE_LIBRARY} ${CRASHPAD_UTIL_LIBRARY}) - + if (WIN32) set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/ignore:4099") endif() diff --git a/interface/src/Crashpad.cpp b/interface/src/Crashpad.cpp index f28cad2d18..8ed3fc23bd 100644 --- a/interface/src/Crashpad.cpp +++ b/interface/src/Crashpad.cpp @@ -1,6 +1,6 @@ // // Crashpad.cpp -// shared/src/shared +// interface/src // // Created by Clement Brisset on 01/19/18. // Copyright 2018 High Fidelity, Inc. diff --git a/interface/src/Crashpad.h b/interface/src/Crashpad.h index c4c559f459..a815ed701a 100644 --- a/interface/src/Crashpad.h +++ b/interface/src/Crashpad.h @@ -1,6 +1,6 @@ // // Crashpad.h -// shared/src/shared +// interface/src // // Created by Clement Brisset on 01/19/18. // Copyright 2018 High Fidelity, Inc. From d0a0120dfe129e095525349b94a6bb3038ff7828 Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Tue, 30 Jan 2018 13:34:38 -0800 Subject: [PATCH 096/116] removing old game sample from script-archive --- .../exterminatorGame/gameServer/.gitignore | 1 - .../exterminatorGame/gameServer/Procfile | 1 - .../exterminatorGame/gameServer/README.txt | 5 - .../games/exterminatorGame/gameServer/app.js | 76 - .../gameServer/client/app.jsx | 87 - .../exterminatorGame/gameServer/gulpfile.js | 15 - .../exterminatorGame/gameServer/package.json | 21 - .../gameServer/public/css/style.css | 36 - .../gameServer/public/index.html | 12 - .../gameServer/public/js/app.js | 30691 ---------------- .../pistolScriptSpawnerSpawner.js | 31 - 11 files changed, 30976 deletions(-) delete mode 100644 script-archive/example/games/exterminatorGame/gameServer/.gitignore delete mode 100644 script-archive/example/games/exterminatorGame/gameServer/Procfile delete mode 100644 script-archive/example/games/exterminatorGame/gameServer/README.txt delete mode 100644 script-archive/example/games/exterminatorGame/gameServer/app.js delete mode 100644 script-archive/example/games/exterminatorGame/gameServer/client/app.jsx delete mode 100644 script-archive/example/games/exterminatorGame/gameServer/gulpfile.js delete mode 100644 script-archive/example/games/exterminatorGame/gameServer/package.json delete mode 100644 script-archive/example/games/exterminatorGame/gameServer/public/css/style.css delete mode 100644 script-archive/example/games/exterminatorGame/gameServer/public/index.html delete mode 100644 script-archive/example/games/exterminatorGame/gameServer/public/js/app.js delete mode 100644 script-archive/example/games/exterminatorGame/pistolScriptSpawnerSpawner.js diff --git a/script-archive/example/games/exterminatorGame/gameServer/.gitignore b/script-archive/example/games/exterminatorGame/gameServer/.gitignore deleted file mode 100644 index 30bc162798..0000000000 --- a/script-archive/example/games/exterminatorGame/gameServer/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/node_modules \ No newline at end of file diff --git a/script-archive/example/games/exterminatorGame/gameServer/Procfile b/script-archive/example/games/exterminatorGame/gameServer/Procfile deleted file mode 100644 index 207d22f803..0000000000 --- a/script-archive/example/games/exterminatorGame/gameServer/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: node app.js \ No newline at end of file diff --git a/script-archive/example/games/exterminatorGame/gameServer/README.txt b/script-archive/example/games/exterminatorGame/gameServer/README.txt deleted file mode 100644 index 6180f96212..0000000000 --- a/script-archive/example/games/exterminatorGame/gameServer/README.txt +++ /dev/null @@ -1,5 +0,0 @@ -This gameserver sets up a server with websockets that listen for messages from interface regarding when users shoot rats, and updates a real-time game board with that information. This is just a first pass, and the plan is to abstract this to work with any kind of game content creators wish to make with High Fidelity. - -To enter the game: Run pistol.js and shoot at rats. -For every rat you kill, you get a point. -You're score will be displayed at https://desolate-bastion-1742.herokuapp.com/ \ No newline at end of file diff --git a/script-archive/example/games/exterminatorGame/gameServer/app.js b/script-archive/example/games/exterminatorGame/gameServer/app.js deleted file mode 100644 index 30af1c2fae..0000000000 --- a/script-archive/example/games/exterminatorGame/gameServer/app.js +++ /dev/null @@ -1,76 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var express = require('express'); -var http = require('http'); -var _ = require('underscore'); -var shortid = require('shortid'); - - -var app = express(); -var server = http.createServer(app); - -var WebSocketServer = require('websocket').server; -var wsServer = new WebSocketServer({ - httpServer: server -}); - -var users = []; -var connections = []; -wsServer.on('request', function(request) { - console.log("SOMEONE JOINED"); - var connection = request.accept(null, request.origin); - connections.push(connection); - connection.on('message', function(data) { - var userData = JSON.parse(data.utf8Data); - var user = _.find(users, function(user) { - return user.username === userData.username; - }); - if (user) { - // This user already exists, so just update score - users[users.indexOf(user)].score = userData.score; - } else { - users.push({ - id: shortid.generate(), - username: userData.username, - score: userData.score - }); - } - connections.forEach(function(aConnection) { - aConnection.sendUTF(JSON.stringify({ - users: users - })); - }) - }); -}); - -app.get('/users', function(req, res) { - res.send({ - users: users - }); -}); - - - -/* Configuration */ -app.set('views', __dirname + '/views'); -app.use(express.static(__dirname + '/public')); -app.set('port', (process.env.PORT || 5000)); - -if (process.env.NODE_ENV === 'development') { - app.use(express.errorHandler({ - dumpExceptions: true, - showStack: true - })); -} - - -/* Start server */ -server.listen(app.get('port'), function() { - console.log('Express server listening on port %d in %s mode', app.get('port'), app.get('env')); -}); - -module.exports = app; \ No newline at end of file diff --git a/script-archive/example/games/exterminatorGame/gameServer/client/app.jsx b/script-archive/example/games/exterminatorGame/gameServer/client/app.jsx deleted file mode 100644 index a8bd5fdb08..0000000000 --- a/script-archive/example/games/exterminatorGame/gameServer/client/app.jsx +++ /dev/null @@ -1,87 +0,0 @@ -'use strict'; - -var React = require('react'); -var _ = require('underscore') -var $ = require('jquery'); - -var UserList = React.createClass({ -render: function(){ - var sortedUsers = _.sortBy(this.props.data.users, function(users){ - //Show higher scorers at top of board - return 1 - users.score; - }); - var users = sortedUsers.map(function(user) { - - return ( - - ) - }); - return ( -
{users}
- ) - } -}); - -var GameBoard = React.createClass({ - loadDataFromServer: function(data) { - $.ajax({ - url: this.props.url, - dataType: 'json', - cache: false, - success: function(data) { - this.setState({data: data}); - }.bind(this), - error: function(xhr, status, err) { - console.error(this.props.url, status, err.toString()); - }.bind(this) - }); - }, - getInitialState: function() { - return {data: {users: []}}; - }, - componentDidMount: function() { - this.loadDataFromServer(); - //set up web socket - var path = window.location.hostname + ":" + window.location.port; - console.log("LOCATION ", path) - var socketClient = new WebSocket("wss://" + path); - var self = this; - socketClient.onopen = function() { - console.log("CONNECTED"); - socketClient.onmessage = function(data) { - console.log("ON MESSAGE"); - self.setState({data: JSON.parse(data.data)}); - }; - }; - - }, - render: function() { - - return ( -
-
Kill All The Rats!
-
-
PLAYER
-
SCORE
-
- -
- ); - } -}); - -var User = React.createClass({ - render: function() { - return ( -
-
{this.props.username}
-
{this.props.score}
-
- ); - } -}) - -React.render( - , - document.getElementById('app') -); \ No newline at end of file diff --git a/script-archive/example/games/exterminatorGame/gameServer/gulpfile.js b/script-archive/example/games/exterminatorGame/gameServer/gulpfile.js deleted file mode 100644 index 84543dc28c..0000000000 --- a/script-archive/example/games/exterminatorGame/gameServer/gulpfile.js +++ /dev/null @@ -1,15 +0,0 @@ -var gulp = require('gulp'); -var exec = require('child_process').exec; - -gulp.task('build', function() { - exec('npm run build', function(msg){ - console.log(msg); - }); -}); - -gulp.task('watch', function() { - gulp.watch('client/*.jsx', ['build']); -}); - - -gulp.task('default', ['build', 'watch']) \ No newline at end of file diff --git a/script-archive/example/games/exterminatorGame/gameServer/package.json b/script-archive/example/games/exterminatorGame/gameServer/package.json deleted file mode 100644 index 49befaad70..0000000000 --- a/script-archive/example/games/exterminatorGame/gameServer/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "KillAllTheRats", - "version": "0.6.9", - "scripts": { - "build": "browserify ./client/app.jsx -t babelify --outfile ./public/js/app.js", - "start": "node app.js" - }, - "dependencies": { - "express": "^4.13.1", - "gulp": "^3.9.0", - "jquery": "^2.1.4", - "react": "^0.13.3", - "shortid": "^2.2.4", - "underscore": "^1.8.3", - "websocket": "^1.0.22" - }, - "devDependencies": { - "babelify": "^6.1.3", - "browserify": "^10.2.6" - } -} diff --git a/script-archive/example/games/exterminatorGame/gameServer/public/css/style.css b/script-archive/example/games/exterminatorGame/gameServer/public/css/style.css deleted file mode 100644 index b001103ac2..0000000000 --- a/script-archive/example/games/exterminatorGame/gameServer/public/css/style.css +++ /dev/null @@ -1,36 +0,0 @@ -body { - font-family: Impact; - background-color: #009DC0 ; - font-size: 60px; -} - -.gameTitle { - color: #D61010; -} - -.entry{ - width:100%; - height:50px; - border:1px solid #A9D1E1; - color: white; - margin-right:10px; - padding: 10px; - float:left; - font-size: 40px; -} - -.boardHeader{ - width:100%; - height:50px; - border:5px solid #A9D1E1; - color: white; - margin-right:10px; - padding: 10px; - float:left; - font-size: 40px; -} -.username{ - font-weight: bold; - float: left; - margin-right: 50%; -} \ No newline at end of file diff --git a/script-archive/example/games/exterminatorGame/gameServer/public/index.html b/script-archive/example/games/exterminatorGame/gameServer/public/index.html deleted file mode 100644 index 125039a934..0000000000 --- a/script-archive/example/games/exterminatorGame/gameServer/public/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Kill The Rats! - - -
- - - diff --git a/script-archive/example/games/exterminatorGame/gameServer/public/js/app.js b/script-archive/example/games/exterminatorGame/gameServer/public/js/app.js deleted file mode 100644 index 38f8bf3727..0000000000 --- a/script-archive/example/games/exterminatorGame/gameServer/public/js/app.js +++ /dev/null @@ -1,30691 +0,0 @@ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1) { - for (var i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; - } - } - queue.push(new Item(fun, args)); - if (queue.length === 1 && !draining) { - setTimeout(drainQueue, 0); - } -}; - -// v8 likes predictible objects -function Item(fun, array) { - this.fun = fun; - this.array = array; -} -Item.prototype.run = function () { - this.fun.apply(null, this.array); -}; -process.title = 'browser'; -process.browser = true; -process.env = {}; -process.argv = []; -process.version = ''; // empty string to avoid regexp issues -process.versions = {}; - -function noop() {} - -process.on = noop; -process.addListener = noop; -process.once = noop; -process.off = noop; -process.removeListener = noop; -process.removeAllListeners = noop; -process.emit = noop; - -process.binding = function (name) { - throw new Error('process.binding is not supported'); -}; - -process.cwd = function () { return '/' }; -process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); -}; -process.umask = function() { return 0; }; - -},{}],3:[function(require,module,exports){ -/*! - * jQuery JavaScript Library v2.1.4 - * http://jquery.com/ - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * - * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2015-04-28T16:01Z - */ - -(function( global, factory ) { - - if ( typeof module === "object" && typeof module.exports === "object" ) { - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Support: Firefox 18+ -// Can't be in strict mode, several libs including ASP.NET trace -// the stack via arguments.caller.callee and Firefox dies if -// you try to trace through "use strict" call chains. (#13335) -// - -var arr = []; - -var slice = arr.slice; - -var concat = arr.concat; - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var support = {}; - - - -var - // Use the correct document accordingly with window argument (sandbox) - document = window.document, - - version = "2.1.4", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }, - - // Support: Android<4.1 - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([\da-z])/gi, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }; - -jQuery.fn = jQuery.prototype = { - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // Start with an empty selector - selector: "", - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num != null ? - - // Return just the one element from the set - ( num < 0 ? this[ num + this.length ] : this[ num ] ) : - - // Return all the elements in a clean array - slice.call( this ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - ret.context = this.context; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function( elem, i ) { - return callback.call( elem, i, elem ); - })); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(null); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[0] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction(target) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) { - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray(src) ? src : []; - - } else { - clone = src && jQuery.isPlainObject(src) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend({ - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isFunction: function( obj ) { - return jQuery.type(obj) === "function"; - }, - - isArray: Array.isArray, - - isWindow: function( obj ) { - return obj != null && obj === obj.window; - }, - - isNumeric: function( obj ) { - // parseFloat NaNs numeric-cast false positives (null|true|false|"") - // ...but misinterprets leading-number strings, particularly hex literals ("0x...") - // subtraction forces infinities to NaN - // adding 1 corrects loss of precision from parseFloat (#15100) - return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0; - }, - - isPlainObject: function( obj ) { - // Not plain objects: - // - Any object or value whose internal [[Class]] property is not "[object Object]" - // - DOM nodes - // - window - if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { - return false; - } - - if ( obj.constructor && - !hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) { - return false; - } - - // If the function hasn't returned already, we're confident that - // |obj| is a plain object, created by {} or constructed with new Object - return true; - }, - - isEmptyObject: function( obj ) { - var name; - for ( name in obj ) { - return false; - } - return true; - }, - - type: function( obj ) { - if ( obj == null ) { - return obj + ""; - } - // Support: Android<4.0, iOS<6 (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call(obj) ] || "object" : - typeof obj; - }, - - // Evaluates a script in a global context - globalEval: function( code ) { - var script, - indirect = eval; - - code = jQuery.trim( code ); - - if ( code ) { - // If the code includes a valid, prologue position - // strict mode pragma, execute code by injecting a - // script tag into the document. - if ( code.indexOf("use strict") === 1 ) { - script = document.createElement("script"); - script.text = code; - document.head.appendChild( script ).parentNode.removeChild( script ); - } else { - // Otherwise, avoid the DOM node creation, insertion - // and removal by using an indirect global eval - indirect( code ); - } - } - }, - - // Convert dashed to camelCase; used by the css and data modules - // Support: IE9-11+ - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - }, - - // args is for internal usage only - each: function( obj, callback, args ) { - var value, - i = 0, - length = obj.length, - isArray = isArraylike( obj ); - - if ( args ) { - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback.apply( obj[ i ], args ); - - if ( value === false ) { - break; - } - } - } else { - for ( i in obj ) { - value = callback.apply( obj[ i ], args ); - - if ( value === false ) { - break; - } - } - } - - // A special, fast, case for the most common use of each - } else { - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback.call( obj[ i ], i, obj[ i ] ); - - if ( value === false ) { - break; - } - } - } else { - for ( i in obj ) { - value = callback.call( obj[ i ], i, obj[ i ] ); - - if ( value === false ) { - break; - } - } - } - } - - return obj; - }, - - // Support: Android<4.1 - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArraylike( Object(arr) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var value, - i = 0, - length = elems.length, - isArray = isArraylike( elems ), - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var tmp, args, proxy; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - now: Date.now, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -}); - -// Populate the class2type map -jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -}); - -function isArraylike( obj ) { - - // Support: iOS 8.2 (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = "length" in obj && obj.length, - type = jQuery.type( obj ); - - if ( type === "function" || jQuery.isWindow( obj ) ) { - return false; - } - - if ( obj.nodeType === 1 && length ) { - return true; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.2.0-pre - * http://sizzlejs.com/ - * - * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2014-12-16 - */ -(function( window ) { - -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // General-purpose constants - MAX_NEGATIVE = 1 << 31, - - // Instance methods - hasOwn = ({}).hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf as it's faster than native - // http://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[i] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - // http://www.w3.org/TR/css3-syntax/#characters - characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", - - // Loosely modeled on CSS identifier characters - // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors - // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = characterEncoding.replace( "w", "w#" ), - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace + - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", - - pseudos = ":(" + characterEncoding + ")(?:\\((" + - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + characterEncoding + ")" ), - "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), - "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - rescape = /'|\\/g, - - // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : - high < 0 ? - // BMP codepoint - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }; - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - (arr = slice.call( preferredDoc.childNodes )), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - push_native.apply( target, slice.call(els) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var match, elem, m, nodeType, - // QSA vars - i, groups, old, nid, newContext, newSelector; - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - - context = context || document; - results = results || []; - nodeType = context.nodeType; - - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - if ( !seed && documentIsHTML ) { - - // Try to shortcut find operations when possible (e.g., not under DocumentFragment) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { - // Speed-up: Sizzle("#ID") - if ( (m = match[1]) ) { - if ( nodeType === 9 ) { - elem = context.getElementById( m ); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document (jQuery #6963) - if ( elem && elem.parentNode ) { - // Handle the case where IE, Opera, and Webkit return items - // by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - } else { - // Context is not a document - if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && - contains( context, elem ) && elem.id === m ) { - results.push( elem ); - return results; - } - } - - // Speed-up: Sizzle("TAG") - } else if ( match[2] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Speed-up: Sizzle(".CLASS") - } else if ( (m = match[3]) && support.getElementsByClassName ) { - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // QSA path - if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - nid = old = expando; - newContext = context; - newSelector = nodeType !== 1 && selector; - - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - groups = tokenize( selector ); - - if ( (old = context.getAttribute("id")) ) { - nid = old.replace( rescape, "\\$&" ); - } else { - context.setAttribute( "id", nid ); - } - nid = "[id='" + nid + "'] "; - - i = groups.length; - while ( i-- ) { - groups[i] = nid + toSelector( groups[i] ); - } - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; - newSelector = groups.join(","); - } - - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch(qsaError) { - } finally { - if ( !old ) { - context.removeAttribute("id"); - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {Function(string, Object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key + " " ] = value); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created div and expects a boolean result - */ -function assert( fn ) { - var div = document.createElement("div"); - - try { - return !!fn( div ); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if ( div.parentNode ) { - div.parentNode.removeChild( div ); - } - // release memory in IE - div = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split("|"), - i = attrs.length; - - while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - ( ~b.sourceIndex || MAX_NEGATIVE ) - - ( ~a.sourceIndex || MAX_NEGATIVE ); - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { - argument = +argument; - return markFunction(function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, parent, - doc = node ? node.ownerDocument || node : preferredDoc; - - // If no document and documentElement is available, return - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Set our document - document = doc; - docElem = doc.documentElement; - parent = doc.defaultView; - - // Support: IE>8 - // If iframe document is assigned to "document" variable and if iframe has been reloaded, - // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 - // IE6-8 do not support the defaultView property so parent will be undefined - if ( parent && parent !== parent.top ) { - // IE11 does not have attachEvent, so all must suffer - if ( parent.addEventListener ) { - parent.addEventListener( "unload", unloadHandler, false ); - } else if ( parent.attachEvent ) { - parent.attachEvent( "onunload", unloadHandler ); - } - } - - /* Support tests - ---------------------------------------------------------------------- */ - documentIsHTML = !isXML( doc ); - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert(function( div ) { - div.className = "i"; - return !div.getAttribute("className"); - }); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( div ) { - div.appendChild( doc.createComment("") ); - return !div.getElementsByTagName("*").length; - }); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( doc.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert(function( div ) { - docElem.appendChild( div ).id = expando; - return !doc.getElementsByName || !doc.getElementsByName( expando ).length; - }); - - // ID find and filter - if ( support.getById ) { - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var m = context.getElementById( id ); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [ m ] : []; - } - }; - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute("id") === attrId; - }; - }; - } else { - // Support: IE6/7 - // getElementById is not reliable as a find shortcut - delete Expr.find["ID"]; - - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; - } - - // Tag - Expr.find["TAG"] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( (elem = results[i++]) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { - if ( documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See http://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function( div ) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // http://bugs.jquery.com/ticket/12359 - docElem.appendChild( div ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( div.querySelectorAll("[msallowcapture^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !div.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+ - if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push("~="); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibing-combinator selector` fails - if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push(".#.+[+~]"); - } - }); - - assert(function( div ) { - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = doc.createElement("input"); - input.setAttribute( "type", "hidden" ); - div.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( div.querySelectorAll("[name=d]").length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":enabled").length ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Opera 10-11 does not throw on post-comma invalid pseudos - div.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { - - assert(function( div ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( div, "div" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( div, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - }); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully does not implement inclusive descendent - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { - - // Choose the first element that is related to our preferred document - if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { - return -1; - } - if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - return a === doc ? -1 : - b === doc ? 1 : - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( (cur = cur.parentNode) ) { - ap.unshift( cur ); - } - cur = b; - while ( (cur = cur.parentNode) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { - i++; - } - - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : - - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; - - return doc; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - - if ( support.matchesSelector && documentIsHTML && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch (e) {} - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? - val.value : - null; -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - // If no nodeType, this is expected to be an array - while ( (node = elem[i++]) ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1].slice( 0, 3 ) === "nth" ) { - // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); - - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[6] && match[2]; - - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[3] ) { - match[2] = match[4] || match[5] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { return true; } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); - }); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, what, argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, context, xml ) { - var cache, outerCache, node, diff, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( (node = node[ dir ]) ) { - if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - // Seek `elem` from a previously-cached index - outerCache = parent[ expando ] || (parent[ expando ] = {}); - cache = outerCache[ type ] || []; - nodeIndex = cache[0] === dirruns && cache[1]; - diff = cache[0] === dirruns && cache[2]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( (node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - outerCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - // Use previously-cached element index if available - } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { - diff = cache[1]; - - // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) - } else { - // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { - - if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { - // Cache the index of each encountered element - if ( useCache ) { - (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - // Potentially complex pseudos - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - // Don't keep the element (issue #299) - input[0] = null; - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - "contains": markFunction(function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; - }; - }), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( (elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); - return false; - }; - }), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - - // Boolean properties - "enabled": function( elem ) { - return elem.disabled === false; - }, - - "disabled": function( elem ) { - return elem.disabled === true; - }, - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo(function() { - return [ 0 ]; - }), - - "last": createPositionalPseudo(function( matchIndexes, length ) { - return [ length - 1 ]; - }), - - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - }), - - "even": createPositionalPseudo(function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "odd": createPositionalPseudo(function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }) - } -}; - -Expr.pseudos["nth"] = Expr.pseudos["eq"]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; - } - groups.push( (tokens = []) ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - matched = match.shift(); - tokens.push({ - value: matched, - // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { - matched = match.shift(); - tokens.push({ - value: matched, - type: type, - matches: match - }); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[i].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - checkNonElements = base && dir === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching - if ( xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - if ( (oldCache = outerCache[ dir ]) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return (newCache[ 2 ] = oldCache[ 2 ]); - } else { - // Reuse newcache so results back-propagate to previous elements - outerCache[ dir ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { - return true; - } - } - } - } - } - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); - } - } - postFinder( null, (matcherOut = []), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { - - seed[temp] = !(results[temp] = elem); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; - } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), - len = elems.length; - - if ( outermost ) { - outermostContext = context !== document && context; - } - - // Add elements passing elementMatchers directly to results - // Keep `i` a string if there are no elements so `matchedCount` will be "00" below - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // Apply set filters to unmatched elements - matchedCount += i; - if ( bySet && i !== matchedCount ) { - j = 0; - while ( (matcher = setMatchers[j++]) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( (selector = compiled.selector || selector) ); - - results = results || []; - - // Try to minimize operations if there is no seed and only one group - if ( match.length === 1 ) { - - // Take a shortcut and set the context if the root selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - support.getById && context.nodeType === 9 && documentIsHTML && - Expr.relative[ tokens[1].type ] ) { - - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( div1 ) { - // Should return 1, but returns 4 (following) - return div1.compareDocumentPosition( document.createElement("div") ) & 1; -}); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( div ) { - div.innerHTML = ""; - return div.firstChild.getAttribute("href") === "#" ; -}) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - }); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( div ) { - div.innerHTML = ""; - div.firstChild.setAttribute( "value", "" ); - return div.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - }); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( div ) { - return div.getAttribute("disabled") == null; -}) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - (val = elem.getAttributeNode( name )) && val.specified ? - val.value : - null; - } - }); -} - -return Sizzle; - -})( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; -jQuery.expr[":"] = jQuery.expr.pseudos; -jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; - - - -var rneedsContext = jQuery.expr.match.needsContext; - -var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); - - - -var risSimple = /^.[^:#\[\.,]*$/; - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - /* jshint -W018 */ - return !!qualifier.call( elem, i, elem ) !== not; - }); - - } - - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - }); - - } - - if ( typeof qualifier === "string" ) { - if ( risSimple.test( qualifier ) ) { - return jQuery.filter( qualifier, elements, not ); - } - - qualifier = jQuery.filter( qualifier, elements ); - } - - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) >= 0 ) !== not; - }); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - return elems.length === 1 && elem.nodeType === 1 ? - jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : - jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - })); -}; - -jQuery.fn.extend({ - find: function( selector ) { - var i, - len = this.length, - ret = [], - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter(function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - }) ); - } - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - // Needed because $( selector, context ) becomes $( context ).find( selector ) - ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); - ret.selector = this.selector ? this.selector + " " + selector : selector; - return ret; - }, - filter: function( selector ) { - return this.pushStack( winnow(this, selector || [], false) ); - }, - not: function( selector ) { - return this.pushStack( winnow(this, selector || [], true) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -}); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, - - init = jQuery.fn.init = function( selector, context ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) { - context = context instanceof jQuery ? context[0] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[1], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - // Properties of context are called as methods if possible - if ( jQuery.isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[2] ); - - // Support: Blackberry 4.6 - // gEBID returns nodes no longer in the document (#6963) - if ( elem && elem.parentNode ) { - // Inject the element directly into the jQuery object - this.length = 1; - this[0] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || rootjQuery ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this.context = this[0] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return typeof rootjQuery.ready !== "undefined" ? - rootjQuery.ready( selector ) : - // Execute immediately if ready is not present - selector( jQuery ); - } - - if ( selector.selector !== undefined ) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.extend({ - dir: function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; - }, - - sibling: function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; - } -}); - -jQuery.fn.extend({ - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter(function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[i] ) ) { - return true; - } - } - }); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? - jQuery( selectors, context || this.context ) : - 0; - - for ( ; i < l; i++ ) { - for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { - // Always skip document fragments - if ( cur.nodeType < 11 && (pos ? - pos.index(cur) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector(cur, selectors)) ) { - - matched.push( cur ); - break; - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.unique( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter(selector) - ); - } -}); - -function sibling( cur, dir ) { - while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each({ - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return jQuery.dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return jQuery.dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return jQuery.dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return jQuery.dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return jQuery.dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return jQuery.dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return jQuery.sibling( elem.firstChild ); - }, - contents: function( elem ) { - return elem.contentDocument || jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.unique( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -}); -var rnotwhite = (/\S+/g); - - - -// String to Object options format cache -var optionsCache = {}; - -// Convert String-formatted options into Object-formatted ones and store in cache -function createOptions( options ) { - var object = optionsCache[ options ] = {}; - jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { - object[ flag ] = true; - }); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - ( optionsCache[ options ] || createOptions( options ) ) : - jQuery.extend( {}, options ); - - var // Last fire value (for non-forgettable lists) - memory, - // Flag to know if list was already fired - fired, - // Flag to know if list is currently firing - firing, - // First callback to fire (used internally by add and fireWith) - firingStart, - // End of the loop when firing - firingLength, - // Index of currently firing callback (modified by remove if needed) - firingIndex, - // Actual callback list - list = [], - // Stack of fire calls for repeatable lists - stack = !options.once && [], - // Fire callbacks - fire = function( data ) { - memory = options.memory && data; - fired = true; - firingIndex = firingStart || 0; - firingStart = 0; - firingLength = list.length; - firing = true; - for ( ; list && firingIndex < firingLength; firingIndex++ ) { - if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { - memory = false; // To prevent further calls using add - break; - } - } - firing = false; - if ( list ) { - if ( stack ) { - if ( stack.length ) { - fire( stack.shift() ); - } - } else if ( memory ) { - list = []; - } else { - self.disable(); - } - } - }, - // Actual Callbacks object - self = { - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - // First, we save the current length - var start = list.length; - (function add( args ) { - jQuery.each( args, function( _, arg ) { - var type = jQuery.type( arg ); - if ( type === "function" ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && type !== "string" ) { - // Inspect recursively - add( arg ); - } - }); - })( arguments ); - // Do we need to add the callbacks to the - // current firing batch? - if ( firing ) { - firingLength = list.length; - // With memory, if we're not firing then - // we should call right away - } else if ( memory ) { - firingStart = start; - fire( memory ); - } - } - return this; - }, - // Remove a callback from the list - remove: function() { - if ( list ) { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - // Handle firing indexes - if ( firing ) { - if ( index <= firingLength ) { - firingLength--; - } - if ( index <= firingIndex ) { - firingIndex--; - } - } - } - }); - } - return this; - }, - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); - }, - // Remove all callbacks from the list - empty: function() { - list = []; - firingLength = 0; - return this; - }, - // Have the list do nothing anymore - disable: function() { - list = stack = memory = undefined; - return this; - }, - // Is it disabled? - disabled: function() { - return !list; - }, - // Lock the list in its current state - lock: function() { - stack = undefined; - if ( !memory ) { - self.disable(); - } - return this; - }, - // Is it locked? - locked: function() { - return !stack; - }, - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( list && ( !fired || stack ) ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - if ( firing ) { - stack.push( args ); - } else { - fire( args ); - } - } - return this; - }, - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -jQuery.extend({ - - Deferred: function( func ) { - var tuples = [ - // action, add listener, listener list, final state - [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], - [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], - [ "notify", "progress", jQuery.Callbacks("memory") ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - then: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - return jQuery.Deferred(function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { - var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; - // deferred[ done | fail | progress ] for forwarding actions to newDefer - deferred[ tuple[1] ](function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise() - .done( newDefer.resolve ) - .fail( newDefer.reject ) - .progress( newDefer.notify ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); - } - }); - }); - fns = null; - }).promise(); - }, - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Keep pipe for back-compat - promise.pipe = promise.then; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 3 ]; - - // promise[ done | fail | progress ] = list.add - promise[ tuple[1] ] = list.add; - - // Handle state - if ( stateString ) { - list.add(function() { - // state = [ resolved | rejected ] - state = stateString; - - // [ reject_list | resolve_list ].disable; progress_list.lock - }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); - } - - // deferred[ resolve | reject | notify ] - deferred[ tuple[0] ] = function() { - deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); - return this; - }; - deferred[ tuple[0] + "With" ] = list.fireWith; - }); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( subordinate /* , ..., subordinateN */ ) { - var i = 0, - resolveValues = slice.call( arguments ), - length = resolveValues.length, - - // the count of uncompleted subordinates - remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, - - // the master Deferred. If resolveValues consist of only a single Deferred, just use that. - deferred = remaining === 1 ? subordinate : jQuery.Deferred(), - - // Update function for both resolve and progress values - updateFunc = function( i, contexts, values ) { - return function( value ) { - contexts[ i ] = this; - values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( values === progressValues ) { - deferred.notifyWith( contexts, values ); - } else if ( !( --remaining ) ) { - deferred.resolveWith( contexts, values ); - } - }; - }, - - progressValues, progressContexts, resolveContexts; - - // Add listeners to Deferred subordinates; treat others as resolved - if ( length > 1 ) { - progressValues = new Array( length ); - progressContexts = new Array( length ); - resolveContexts = new Array( length ); - for ( ; i < length; i++ ) { - if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { - resolveValues[ i ].promise() - .done( updateFunc( i, resolveContexts, resolveValues ) ) - .fail( deferred.reject ) - .progress( updateFunc( i, progressContexts, progressValues ) ); - } else { - --remaining; - } - } - } - - // If we're not waiting on anything, resolve the master - if ( !remaining ) { - deferred.resolveWith( resolveContexts, resolveValues ); - } - - return deferred.promise(); - } -}); - - -// The deferred used on DOM ready -var readyList; - -jQuery.fn.ready = function( fn ) { - // Add the callback - jQuery.ready.promise().done( fn ); - - return this; -}; - -jQuery.extend({ - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Hold (or release) the ready event - holdReady: function( hold ) { - if ( hold ) { - jQuery.readyWait++; - } else { - jQuery.ready( true ); - } - }, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.triggerHandler ) { - jQuery( document ).triggerHandler( "ready" ); - jQuery( document ).off( "ready" ); - } - } -}); - -/** - * The ready event handler and self cleanup method - */ -function completed() { - document.removeEventListener( "DOMContentLoaded", completed, false ); - window.removeEventListener( "load", completed, false ); - jQuery.ready(); -} - -jQuery.ready.promise = function( obj ) { - if ( !readyList ) { - - readyList = jQuery.Deferred(); - - // Catch cases where $(document).ready() is called after the browser event has already occurred. - // We once tried to use readyState "interactive" here, but it caused issues like the one - // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 - if ( document.readyState === "complete" ) { - // Handle it asynchronously to allow scripts the opportunity to delay ready - setTimeout( jQuery.ready ); - - } else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed, false ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed, false ); - } - } - return readyList.promise( obj ); -}; - -// Kick off the DOM ready check even if the user does not -jQuery.ready.promise(); - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( jQuery.type( key ) === "object" ) { - chainable = true; - for ( i in key ) { - jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !jQuery.isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); - } - } - } - - return chainable ? - elems : - - // Gets - bulk ? - fn.call( elems ) : - len ? fn( elems[0], key ) : emptyGet; -}; - - -/** - * Determines whether an object can have data - */ -jQuery.acceptData = function( owner ) { - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - /* jshint -W018 */ - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - -function Data() { - // Support: Android<4, - // Old WebKit does not have Object.preventExtensions/freeze method, - // return new empty object instead with no [[set]] accessor - Object.defineProperty( this.cache = {}, 0, { - get: function() { - return {}; - } - }); - - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; -Data.accepts = jQuery.acceptData; - -Data.prototype = { - key: function( owner ) { - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return the key for a frozen object. - if ( !Data.accepts( owner ) ) { - return 0; - } - - var descriptor = {}, - // Check if the owner object already has a cache key - unlock = owner[ this.expando ]; - - // If not, create one - if ( !unlock ) { - unlock = Data.uid++; - - // Secure it in a non-enumerable, non-writable property - try { - descriptor[ this.expando ] = { value: unlock }; - Object.defineProperties( owner, descriptor ); - - // Support: Android<4 - // Fallback to a less secure definition - } catch ( e ) { - descriptor[ this.expando ] = unlock; - jQuery.extend( owner, descriptor ); - } - } - - // Ensure the cache object - if ( !this.cache[ unlock ] ) { - this.cache[ unlock ] = {}; - } - - return unlock; - }, - set: function( owner, data, value ) { - var prop, - // There may be an unlock assigned to this node, - // if there is no entry for this "owner", create one inline - // and set the unlock as though an owner entry had always existed - unlock = this.key( owner ), - cache = this.cache[ unlock ]; - - // Handle: [ owner, key, value ] args - if ( typeof data === "string" ) { - cache[ data ] = value; - - // Handle: [ owner, { properties } ] args - } else { - // Fresh assignments by object are shallow copied - if ( jQuery.isEmptyObject( cache ) ) { - jQuery.extend( this.cache[ unlock ], data ); - // Otherwise, copy the properties one-by-one to the cache object - } else { - for ( prop in data ) { - cache[ prop ] = data[ prop ]; - } - } - } - return cache; - }, - get: function( owner, key ) { - // Either a valid cache is found, or will be created. - // New caches will be created and the unlock returned, - // allowing direct access to the newly created - // empty data object. A valid owner object must be provided. - var cache = this.cache[ this.key( owner ) ]; - - return key === undefined ? - cache : cache[ key ]; - }, - access: function( owner, key, value ) { - var stored; - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ((key && typeof key === "string") && value === undefined) ) { - - stored = this.get( owner, key ); - - return stored !== undefined ? - stored : this.get( owner, jQuery.camelCase(key) ); - } - - // [*]When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, name, camel, - unlock = this.key( owner ), - cache = this.cache[ unlock ]; - - if ( key === undefined ) { - this.cache[ unlock ] = {}; - - } else { - // Support array or space separated string of keys - if ( jQuery.isArray( key ) ) { - // If "name" is an array of keys... - // When data is initially created, via ("key", "val") signature, - // keys will be converted to camelCase. - // Since there is no way to tell _how_ a key was added, remove - // both plain key and camelCase key. #12786 - // This will only penalize the array argument path. - name = key.concat( key.map( jQuery.camelCase ) ); - } else { - camel = jQuery.camelCase( key ); - // Try the string as a key before any manipulation - if ( key in cache ) { - name = [ key, camel ]; - } else { - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - name = camel; - name = name in cache ? - [ name ] : ( name.match( rnotwhite ) || [] ); - } - } - - i = name.length; - while ( i-- ) { - delete cache[ name[ i ] ]; - } - } - }, - hasData: function( owner ) { - return !jQuery.isEmptyObject( - this.cache[ owner[ this.expando ] ] || {} - ); - }, - discard: function( owner ) { - if ( owner[ this.expando ] ) { - delete this.cache[ owner[ this.expando ] ]; - } - } -}; -var data_priv = new Data(); - -var data_user = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /([A-Z])/g; - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - // Only convert to a number if it doesn't change the string - +data + "" === data ? +data : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; - } catch( e ) {} - - // Make sure we set the data so it isn't changed later - data_user.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend({ - hasData: function( elem ) { - return data_user.hasData( elem ) || data_priv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return data_user.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - data_user.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to data_priv methods, these can be deprecated. - _data: function( elem, name, data ) { - return data_priv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - data_priv.remove( elem, name ); - } -}); - -jQuery.fn.extend({ - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = data_user.get( elem ); - - if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE11+ - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = jQuery.camelCase( name.slice(5) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - data_priv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each(function() { - data_user.set( this, key ); - }); - } - - return access( this, function( value ) { - var data, - camelKey = jQuery.camelCase( key ); - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - // Attempt to get data from the cache - // with the key as-is - data = data_user.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to get data from the cache - // with the key camelized - data = data_user.get( elem, camelKey ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, camelKey, undefined ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each(function() { - // First, attempt to store a copy or reference of any - // data that might've been store with a camelCased key. - var data = data_user.get( this, camelKey ); - - // For HTML5 data-* attribute interop, we have to - // store property names with dashes in a camelCase form. - // This might not apply to all properties...* - data_user.set( this, camelKey, value ); - - // *... In the case of properties that might _actually_ - // have dashes, we need to also store a copy of that - // unchanged property. - if ( key.indexOf("-") !== -1 && data !== undefined ) { - data_user.set( this, key, value ); - } - }); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each(function() { - data_user.remove( this, key ); - }); - } -}); - - -jQuery.extend({ - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = data_priv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || jQuery.isArray( data ) ) { - queue = data_priv.access( elem, type, jQuery.makeArray(data) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return data_priv.get( elem, key ) || data_priv.access( elem, key, { - empty: jQuery.Callbacks("once memory").add(function() { - data_priv.remove( elem, [ type + "queue", key ] ); - }) - }); - } -}); - -jQuery.fn.extend({ - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[0], type ); - } - - return data === undefined ? - this : - this.each(function() { - var queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[0] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - }); - }, - dequeue: function( type ) { - return this.each(function() { - jQuery.dequeue( this, type ); - }); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = data_priv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -}); -var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var isHidden = function( elem, el ) { - // isHidden might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); - }; - -var rcheckableType = (/^(?:checkbox|radio)$/i); - - - -(function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Safari<=5.1 - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Safari<=5.1, Android<4.2 - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE<=11+ - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; -})(); -var strundefined = typeof undefined; - - - -support.focusinBubbles = "onfocusin" in window; - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/, - rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = data_priv.get( elem ); - - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !(events = elemData.events) ) { - events = elemData.events = {}; - } - if ( !(eventHandle = elemData.handle) ) { - eventHandle = elemData.handle = function( e ) { - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnotwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[t] ) || []; - type = origType = tmp[1]; - namespaces = ( tmp[2] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend({ - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join(".") - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !(handlers = events[ type ]) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle, false ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = data_priv.hasData( elem ) && data_priv.get( elem ); - - if ( !elemData || !(events = elemData.events) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnotwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[t] ) || []; - type = origType = tmp[1]; - namespaces = ( tmp[2] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - delete elemData.handle; - data_priv.remove( elem, "events" ); - } - }, - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; - - cur = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf(".") >= 0 ) { - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split("."); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf(":") < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join("."); - event.namespace_re = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === (elem.ownerDocument || document) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { - - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && jQuery.acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && - jQuery.acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - elem[ type ](); - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - dispatch: function( event ) { - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( event ); - - var i, j, ret, matched, handleObj, - handlerQueue = [], - args = slice.call( arguments ), - handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[0] = event; - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { - - // Triggered event must either 1) have no namespace, or 2) have namespace(s) - // a subset or equal to those in the bound event (both can have no namespace). - if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) - .apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( (event.result = ret) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, matches, sel, handleObj, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - // Black-hole SVG instance trees (#13180) - // Avoid non-left-click bubbling in Firefox (#3861) - if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.disabled !== true || event.type !== "click" ) { - matches = []; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matches[ sel ] === undefined ) { - matches[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) >= 0 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matches[ sel ] ) { - matches.push( handleObj ); - } - } - if ( matches.length ) { - handlerQueue.push({ elem: cur, handlers: matches }); - } - } - } - } - - // Add the remaining (directly-bound) handlers - if ( delegateCount < handlers.length ) { - handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); - } - - return handlerQueue; - }, - - // Includes some event props shared by KeyEvent and MouseEvent - props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), - - fixHooks: {}, - - keyHooks: { - props: "char charCode key keyCode".split(" "), - filter: function( event, original ) { - - // Add which for key events - if ( event.which == null ) { - event.which = original.charCode != null ? original.charCode : original.keyCode; - } - - return event; - } - }, - - mouseHooks: { - props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "), - filter: function( event, original ) { - var eventDoc, doc, body, - button = original.button; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && original.clientX != null ) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; - - event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); - event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && button !== undefined ) { - event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); - } - - return event; - } - }, - - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // Create a writable copy of the event object and normalize some properties - var i, prop, copy, - type = event.type, - originalEvent = event, - fixHook = this.fixHooks[ type ]; - - if ( !fixHook ) { - this.fixHooks[ type ] = fixHook = - rmouseEvent.test( type ) ? this.mouseHooks : - rkeyEvent.test( type ) ? this.keyHooks : - {}; - } - copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; - - event = new jQuery.Event( originalEvent ); - - i = copy.length; - while ( i-- ) { - prop = copy[ i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Support: Cordova 2.5 (WebKit) (#13255) - // All events should have a target; Cordova deviceready doesn't - if ( !event.target ) { - event.target = document; - } - - // Support: Safari 6.0+, Chrome<28 - // Target should not be a text node (#504, #13143) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; - }, - - special: { - load: { - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - focus: { - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - this.focus(); - return false; - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - click: { - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { - this.click(); - return false; - } - }, - - // For cross-browser consistency, don't fire native .click() on links - _default: function( event ) { - return jQuery.nodeName( event.target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - }, - - simulate: function( type, elem, event, bubble ) { - // Piggyback on a donor event to simulate a different one. - // Fake originalEvent to avoid donor's stopPropagation, but if the - // simulated event prevents default then we do the same on the donor. - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true, - originalEvent: {} - } - ); - if ( bubble ) { - jQuery.event.trigger( e, null, elem ); - } else { - jQuery.event.dispatch.call( elem, e ); - } - if ( e.isDefaultPrevented() ) { - event.preventDefault(); - } - } -}; - -jQuery.removeEvent = function( elem, type, handle ) { - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle, false ); - } -}; - -jQuery.Event = function( src, props ) { - // Allow instantiation without the 'new' keyword - if ( !(this instanceof jQuery.Event) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - // Support: Android<4.0 - src.returnValue === false ? - returnTrue : - returnFalse; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && e.preventDefault ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && e.stopPropagation ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && e.stopImmediatePropagation ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Create mouseenter/leave events using mouseover/out and event-time checks -// Support: Chrome 15+ -jQuery.each({ - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mousenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || (related !== target && !jQuery.contains( target, related )) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -}); - -// Support: Firefox, Chrome, Safari -// Create "bubbling" focus and blur events -if ( !support.focusinBubbles ) { - jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - var doc = this.ownerDocument || this, - attaches = data_priv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - data_priv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this, - attaches = data_priv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - data_priv.remove( doc, fix ); - - } else { - data_priv.access( doc, fix, attaches ); - } - } - }; - }); -} - -jQuery.fn.extend({ - - on: function( types, selector, data, fn, /*INTERNAL*/ one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - this.on( type, selector, data, types[ type ], one ); - } - return this; - } - - if ( data == null && fn == null ) { - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return this; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return this.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - }); - }, - one: function( types, selector, data, fn ) { - return this.on( types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each(function() { - jQuery.event.remove( this, types, fn, selector ); - }); - }, - - trigger: function( type, data ) { - return this.each(function() { - jQuery.event.trigger( type, data, this ); - }); - }, - triggerHandler: function( type, data ) { - var elem = this[0]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -}); - - -var - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, - rtagName = /<([\w:]+)/, - rhtml = /<|&#?\w+;/, - rnoInnerhtml = /<(?:script|style|link)/i, - // checked="checked" or checked - rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, - rscriptType = /^$|\/(?:java|ecma)script/i, - rscriptTypeMasked = /^true\/(.*)/, - rcleanScript = /^\s*\s*$/g, - - // We have to close these tags to support XHTML (#13200) - wrapMap = { - - // Support: IE9 - option: [ 1, "" ], - - thead: [ 1, "", "
" ], - col: [ 2, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], - - _default: [ 0, "", "" ] - }; - -// Support: IE9 -wrapMap.optgroup = wrapMap.option; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// Support: 1.x compatibility -// Manipulating tables requires a tbody -function manipulationTarget( elem, content ) { - return jQuery.nodeName( elem, "table" ) && - jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? - - elem.getElementsByTagName("tbody")[0] || - elem.appendChild( elem.ownerDocument.createElement("tbody") ) : - elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - var match = rscriptTypeMasked.exec( elem.type ); - - if ( match ) { - elem.type = match[ 1 ]; - } else { - elem.removeAttribute("type"); - } - - return elem; -} - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - data_priv.set( - elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" ) - ); - } -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( data_priv.hasData( src ) ) { - pdataOld = data_priv.access( src ); - pdataCur = data_priv.set( dest, pdataOld ); - events = pdataOld.events; - - if ( events ) { - delete pdataCur.handle; - pdataCur.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( data_user.hasData( src ) ) { - udataOld = data_user.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - data_user.set( dest, udataCur ); - } -} - -function getAll( context, tag ) { - var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) : - context.querySelectorAll ? context.querySelectorAll( tag || "*" ) : - []; - - return tag === undefined || tag && jQuery.nodeName( context, tag ) ? - jQuery.merge( [ context ], ret ) : - ret; -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -jQuery.extend({ - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = jQuery.contains( elem.ownerDocument, elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - buildFragment: function( elems, context, scripts, selection ) { - var elem, tmp, tag, wrap, contains, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( jQuery.type( elem ) === "object" ) { - // Support: QtWebKit, PhantomJS - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement("div") ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: QtWebKit, PhantomJS - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( (elem = nodes[ i++ ]) ) { - - // #4087 - If origin and destination elements are the same, and this is - // that element, do not do anything - if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { - continue; - } - - contains = jQuery.contains( elem.ownerDocument, elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( contains ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( (elem = tmp[ j++ ]) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; - }, - - cleanData: function( elems ) { - var data, elem, type, key, - special = jQuery.event.special, - i = 0; - - for ( ; (elem = elems[ i ]) !== undefined; i++ ) { - if ( jQuery.acceptData( elem ) ) { - key = elem[ data_priv.expando ]; - - if ( key && (data = data_priv.cache[ key ]) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - if ( data_priv.cache[ key ] ) { - // Discard any remaining `private` data - delete data_priv.cache[ key ]; - } - } - } - // Discard any remaining `user` data - delete data_user.cache[ elem[ data_user.expando ] ]; - } - } -}); - -jQuery.fn.extend({ - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each(function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - }); - }, null, value, arguments.length ); - }, - - append: function() { - return this.domManip( arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - }); - }, - - prepend: function() { - return this.domManip( arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - }); - }, - - before: function() { - return this.domManip( arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - }); - }, - - after: function() { - return this.domManip( arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - }); - }, - - remove: function( selector, keepData /* Internal Use Only */ ) { - var elem, - elems = selector ? jQuery.filter( selector, this ) : this, - i = 0; - - for ( ; (elem = elems[i]) != null; i++ ) { - if ( !keepData && elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem ) ); - } - - if ( elem.parentNode ) { - if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { - setGlobalEval( getAll( elem, "script" ) ); - } - elem.parentNode.removeChild( elem ); - } - } - - return this; - }, - - empty: function() { - var elem, - i = 0; - - for ( ; (elem = this[i]) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map(function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - }); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = value.replace( rxhtmlTag, "<$1>" ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var arg = arguments[ 0 ]; - - // Make the changes, replacing each context element with the new content - this.domManip( arguments, function( elem ) { - arg = this.parentNode; - - jQuery.cleanData( getAll( this ) ); - - if ( arg ) { - arg.replaceChild( elem, this ); - } - }); - - // Force removal if there was no new content (e.g., from empty arguments) - return arg && (arg.length || arg.nodeType) ? this : this.remove(); - }, - - detach: function( selector ) { - return this.remove( selector, true ); - }, - - domManip: function( args, callback ) { - - // Flatten any nested arrays - args = concat.apply( [], args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = this.length, - set = this, - iNoClone = l - 1, - value = args[ 0 ], - isFunction = jQuery.isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( isFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return this.each(function( index ) { - var self = set.eq( index ); - if ( isFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - self.domManip( args, callback ); - }); - } - - if ( l ) { - fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - if ( first ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - // Support: QtWebKit - // jQuery.merge because push.apply(_, arraylike) throws - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( this[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { - - if ( node.src ) { - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); - } - } else { - jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) ); - } - } - } - } - } - } - - return this; - } -}); - -jQuery.each({ - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: QtWebKit - // .get() because push.apply(_, arraylike) throws - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -}); - - -var iframe, - elemdisplay = {}; - -/** - * Retrieve the actual display of a element - * @param {String} name nodeName of the element - * @param {Object} doc Document object - */ -// Called only from within defaultDisplay -function actualDisplay( name, doc ) { - var style, - elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), - - // getDefaultComputedStyle might be reliably used only on attached element - display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ? - - // Use of this method is a temporary fix (more like optimization) until something better comes along, - // since it was removed from specification and supported only in FF - style.display : jQuery.css( elem[ 0 ], "display" ); - - // We don't have any data stored on the element, - // so use "detach" method as fast way to get rid of the element - elem.detach(); - - return display; -} - -/** - * Try to determine the default display value of an element - * @param {String} nodeName - */ -function defaultDisplay( nodeName ) { - var doc = document, - display = elemdisplay[ nodeName ]; - - if ( !display ) { - display = actualDisplay( nodeName, doc ); - - // If the simple way fails, read from inside an iframe - if ( display === "none" || !display ) { - - // Use the already-created iframe if possible - iframe = (iframe || jQuery( "