From 905de8b03c384dcb426728d8f92fc479e2ba084c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 11:46:15 -0700 Subject: [PATCH 01/11] Adjust marketplace window size --- examples/edit.js | 8 ++++++-- examples/libraries/entityList.js | 2 +- examples/libraries/gridTool.js | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/edit.js b/examples/edit.js index c236336266..7630ca1b14 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -110,6 +110,10 @@ var importingSVOOverlay = Overlays.addOverlay("text", { visible: false, }); +var MARKETPLACE_URL = "https://metaverse.highfidelity.io/marketplace"; +var marketplaceWindow = new WebWindow('Marketplace', MARKETPLACE_URL, 900, 700, false); +marketplaceWindow.setVisible(false); + var toolBar = (function () { var that = {}, toolBar, @@ -311,7 +315,7 @@ var toolBar = (function () { return true; } if (browseModelsButton === toolBar.clicked(clickedOverlay)) { - browseModelsButtonDown = true; + marketplaceWindow.setVisible(true); return true; } @@ -989,7 +993,7 @@ PropertiesTool = function(opts) { var that = {}; var url = Script.resolvePath('html/entityProperties.html'); - var webView = new WebWindow('Entity Properties', url, 200, 280); + var webView = new WebWindow('Entity Properties', url, 200, 280, true); var visible = false; diff --git a/examples/libraries/entityList.js b/examples/libraries/entityList.js index 942edf18b6..d0b8ddac7f 100644 --- a/examples/libraries/entityList.js +++ b/examples/libraries/entityList.js @@ -2,7 +2,7 @@ EntityListTool = function(opts) { var that = {}; var url = Script.resolvePath('html/entityList.html'); - var webView = new WebWindow('Entities', url, 200, 280); + var webView = new WebWindow('Entities', url, 200, 280, true); var visible = false; diff --git a/examples/libraries/gridTool.js b/examples/libraries/gridTool.js index 6e16186abc..ed4e999be8 100644 --- a/examples/libraries/gridTool.js +++ b/examples/libraries/gridTool.js @@ -229,7 +229,7 @@ GridTool = function(opts) { var listeners = []; var url = Script.resolvePath('html/gridControls.html'); - var webView = new WebWindow('Grid', url, 200, 280); + var webView = new WebWindow('Grid', url, 200, 280, true); horizontalGrid.addListener(function(data) { webView.eventBridge.emitScriptEvent(JSON.stringify(data)); From 93cdb3a293afc164a55726d58760c5d9669543bb Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 11:51:40 -0700 Subject: [PATCH 02/11] Add option for non-tool window WebWindows --- interface/src/scripting/WebWindowClass.cpp | 42 +++++++++++++------ interface/src/scripting/WebWindowClass.h | 5 ++- .../scripting/WindowScriptingInterface.cpp | 4 +- .../src/scripting/WindowScriptingInterface.h | 2 +- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp index 2e0f88c776..36f0139a6d 100644 --- a/interface/src/scripting/WebWindowClass.cpp +++ b/interface/src/scripting/WebWindowClass.cpp @@ -18,6 +18,7 @@ #include #include "Application.h" +#include "MainWindow.h" #include "WindowScriptingInterface.h" #include "WebWindowClass.h" @@ -33,26 +34,40 @@ void ScriptEventBridge::emitScriptEvent(const QString& data) { } -WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height) +WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow) : QObject(NULL), - _eventBridge(new ScriptEventBridge(this)) { + _eventBridge(new ScriptEventBridge(this)), + _isToolWindow(isToolWindow) { - ToolWindow* toolWindow = Application::getInstance()->getToolWindow(); + if (_isToolWindow) { + ToolWindow* toolWindow = Application::getInstance()->getToolWindow(); - _dockWidget = new QDockWidget(title, toolWindow); - _dockWidget->setFeatures(QDockWidget::DockWidgetMovable); + auto dockWidget = new QDockWidget(title, toolWindow); + dockWidget->setFeatures(QDockWidget::DockWidgetMovable); - _webView = new QWebView(_dockWidget); + _webView = new QWebView(dockWidget); + addEventBridgeToWindowObject(); + + dockWidget->setWidget(_webView); + + toolWindow->addDockWidget(Qt::RightDockWidgetArea, dockWidget); + + _windowWidget = dockWidget; + } else { + _windowWidget = new QWidget(Application::getInstance()->getWindow(), Qt::Window); + _windowWidget->setMinimumSize(width, height); + + _webView = new QWebView(_windowWidget); + addEventBridgeToWindowObject(); + } + + _webView->setPage(new InterfaceWebPage()); _webView->setUrl(url); - addEventBridgeToWindowObject(); - _dockWidget->setWidget(_webView); - - toolWindow->addDockWidget(Qt::RightDockWidgetArea, _dockWidget); + connect(this, &WebWindowClass::destroyed, _windowWidget, &QWidget::deleteLater); connect(_webView->page()->mainFrame(), &QWebFrame::javaScriptWindowObjectCleared, this, &WebWindowClass::addEventBridgeToWindowObject); - connect(this, &WebWindowClass::destroyed, _dockWidget, &QWidget::deleteLater); } WebWindowClass::~WebWindowClass() { @@ -67,7 +82,7 @@ void WebWindowClass::setVisible(bool visible) { QMetaObject::invokeMethod( Application::getInstance()->getToolWindow(), "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible)); } - QMetaObject::invokeMethod(_dockWidget, "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible)); + QMetaObject::invokeMethod(_windowWidget, "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible)); } QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { @@ -78,7 +93,8 @@ QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* Q_ARG(const QString&, file), Q_ARG(QString, context->argument(1).toString()), Q_ARG(int, context->argument(2).toInteger()), - Q_ARG(int, context->argument(3).toInteger())); + Q_ARG(int, context->argument(3).toInteger()), + Q_ARG(bool, context->argument(4).toBool())); connect(engine, &QScriptEngine::destroyed, retVal, &WebWindowClass::deleteLater); diff --git a/interface/src/scripting/WebWindowClass.h b/interface/src/scripting/WebWindowClass.h index 0fa88804f2..c923fdd748 100644 --- a/interface/src/scripting/WebWindowClass.h +++ b/interface/src/scripting/WebWindowClass.h @@ -35,7 +35,7 @@ class WebWindowClass : public QObject { Q_OBJECT Q_PROPERTY(QObject* eventBridge READ getEventBridge) public: - WebWindowClass(const QString& title, const QString& url, int width, int height); + WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow = false); ~WebWindowClass(); static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); @@ -46,9 +46,10 @@ public slots: void addEventBridgeToWindowObject(); private: - QDockWidget* _dockWidget; + QWidget* _windowWidget; QWebView* _webView; ScriptEventBridge* _eventBridge; + bool _isToolWindow; }; #endif diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 52de31df3c..4a6afe4dbe 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -35,8 +35,8 @@ WindowScriptingInterface::WindowScriptingInterface() : connect(Application::getInstance(), &Application::svoImportRequested, this, &WindowScriptingInterface::svoImportRequested); } -WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height) { - return new WebWindowClass(title, url, width, height); +WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height, bool isToolWindow) { + return new WebWindowClass(title, url, width, height, isToolWindow); } QScriptValue WindowScriptingInterface::hasFocus() { diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 34942366eb..e3af898267 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -83,7 +83,7 @@ private slots: void nonBlockingFormAccepted() { _nonBlockingFormActive = false; _formResult = QDialog::Accepted; emit nonBlockingFormClosed(); } void nonBlockingFormRejected() { _nonBlockingFormActive = false; _formResult = QDialog::Rejected; emit nonBlockingFormClosed(); } - WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height); + WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height, bool isToolWindow); private: QString jsRegExp2QtRegExp(QString string); From 7ef1964a526b27ed41d136c5427a4b9160c83b2b Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 11:52:02 -0700 Subject: [PATCH 03/11] Add user agent for WebWindow --- interface/src/scripting/WebWindowClass.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp index 36f0139a6d..6f44497117 100644 --- a/interface/src/scripting/WebWindowClass.cpp +++ b/interface/src/scripting/WebWindowClass.cpp @@ -34,6 +34,16 @@ void ScriptEventBridge::emitScriptEvent(const QString& data) { } +class InterfaceWebPage : public QWebPage { +public: + InterfaceWebPage(QWidget* parent = nullptr) : QWebPage(parent) { } +protected: + virtual QString userAgentForUrl(const QUrl & url) const { + return "HighFidelityInterface/1.0"; + } +}; + + WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow) : QObject(NULL), _eventBridge(new ScriptEventBridge(this)), From dc3453a878ee719c0a00008e5417342abd27ad1c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 14:10:46 -0700 Subject: [PATCH 04/11] Add Window.raiseMainWindow() --- examples/edit.js | 2 ++ interface/src/scripting/WindowScriptingInterface.cpp | 9 +++++++-- interface/src/scripting/WindowScriptingInterface.h | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/edit.js b/examples/edit.js index 9ce8561ab0..0402b8d42b 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -865,6 +865,8 @@ function importSVO(importURL) { if (isActive) { selectionManager.setSelections(pastedEntityIDs); } + + Window.raiseMainWindow(); } else { Window.alert("There was an error importing the entity file."); } diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 4a6afe4dbe..7f4b5ddf45 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -44,8 +44,13 @@ QScriptValue WindowScriptingInterface::hasFocus() { } void WindowScriptingInterface::setFocus() { - Application::getInstance()->getWindow()->activateWindow(); - Application::getInstance()->getWindow()->setFocus(); + auto window = Application::getInstance()->getWindow(); + window->activateWindow(); + window->setFocus(); +} + +void WindowScriptingInterface::raiseMainWindow() { + Application::getInstance()->getWindow()->raise(); } void WindowScriptingInterface::setCursorVisible(bool visible) { diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index e3af898267..6a812f14e3 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -43,6 +43,7 @@ public slots: void setCursorVisible(bool visible); QScriptValue hasFocus(); void setFocus(); + void raiseMainWindow(); QScriptValue alert(const QString& message = ""); QScriptValue confirm(const QString& message = ""); QScriptValue form(const QString& title, QScriptValue array); From 53b5c7e9718ac1206bdd50b0974e3ab6707af614 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 14:11:01 -0700 Subject: [PATCH 05/11] Update WebWindow to raise when being set to visible --- interface/src/scripting/WebWindowClass.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp index 6f44497117..292018a42c 100644 --- a/interface/src/scripting/WebWindowClass.cpp +++ b/interface/src/scripting/WebWindowClass.cpp @@ -89,8 +89,12 @@ void WebWindowClass::addEventBridgeToWindowObject() { void WebWindowClass::setVisible(bool visible) { if (visible) { - QMetaObject::invokeMethod( - Application::getInstance()->getToolWindow(), "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible)); + if (_isToolWindow) { + QMetaObject::invokeMethod( + Application::getInstance()->getToolWindow(), "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible)); + } else { + QMetaObject::invokeMethod(_windowWidget, "raise", Qt::BlockingQueuedConnection); + } } QMetaObject::invokeMethod(_windowWidget, "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible)); } From 1193d86d7b9ce233a5ee9b9e287e48ff5531950a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 16:39:25 -0700 Subject: [PATCH 06/11] Update webwindow to use datawebview --- interface/src/scripting/WebWindowClass.cpp | 16 +++------------- interface/src/ui/DataWebPage.cpp | 6 +++++- interface/src/ui/DataWebPage.h | 5 ++++- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp index 292018a42c..00859b0c02 100644 --- a/interface/src/scripting/WebWindowClass.cpp +++ b/interface/src/scripting/WebWindowClass.cpp @@ -18,9 +18,10 @@ #include #include "Application.h" +#include "ui/DataWebPage.h" #include "MainWindow.h" -#include "WindowScriptingInterface.h" #include "WebWindowClass.h" +#include "WindowScriptingInterface.h" ScriptEventBridge::ScriptEventBridge(QObject* parent) : QObject(parent) { } @@ -33,17 +34,6 @@ void ScriptEventBridge::emitScriptEvent(const QString& data) { emit scriptEventReceived(data); } - -class InterfaceWebPage : public QWebPage { -public: - InterfaceWebPage(QWidget* parent = nullptr) : QWebPage(parent) { } -protected: - virtual QString userAgentForUrl(const QUrl & url) const { - return "HighFidelityInterface/1.0"; - } -}; - - WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow) : QObject(NULL), _eventBridge(new ScriptEventBridge(this)), @@ -71,7 +61,7 @@ WebWindowClass::WebWindowClass(const QString& title, const QString& url, int wid addEventBridgeToWindowObject(); } - _webView->setPage(new InterfaceWebPage()); + _webView->setPage(new DataWebPage()); _webView->setUrl(url); diff --git a/interface/src/ui/DataWebPage.cpp b/interface/src/ui/DataWebPage.cpp index 299ef86c51..b7aa6351a8 100644 --- a/interface/src/ui/DataWebPage.cpp +++ b/interface/src/ui/DataWebPage.cpp @@ -40,4 +40,8 @@ bool DataWebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkReques Qt::AutoConnection, Q_ARG(const QString&, request.url().toString())); return false; } -} \ No newline at end of file +} + +QString DataWebPage::userAgentForUrl(const QUrl & url) const { + return INTERFACE_USER_AGENT; +} diff --git a/interface/src/ui/DataWebPage.h b/interface/src/ui/DataWebPage.h index 6d89077a33..03c6781d05 100644 --- a/interface/src/ui/DataWebPage.h +++ b/interface/src/ui/DataWebPage.h @@ -14,12 +14,15 @@ #include +const QString INTERFACE_USER_AGENT = "HighFidelityInterface/1.0"; + class DataWebPage : public QWebPage { public: DataWebPage(QObject* parent = 0); protected: void javaScriptConsoleMessage(const QString & message, int lineNumber, const QString & sourceID); bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type); + virtual QString userAgentForUrl(const QUrl & url) const; }; -#endif // hifi_DataWebPage_h \ No newline at end of file +#endif // hifi_DataWebPage_h From 9d5c0cf4e3d19c368050f224092fcb60040fcd8a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 17:25:24 -0700 Subject: [PATCH 07/11] Add layout to WebWindowClass to that it maximizes the webview --- interface/src/scripting/WebWindowClass.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp index 00859b0c02..ae0e327cb1 100644 --- a/interface/src/scripting/WebWindowClass.cpp +++ b/interface/src/scripting/WebWindowClass.cpp @@ -57,7 +57,14 @@ WebWindowClass::WebWindowClass(const QString& title, const QString& url, int wid _windowWidget = new QWidget(Application::getInstance()->getWindow(), Qt::Window); _windowWidget->setMinimumSize(width, height); + auto layout = new QVBoxLayout(_windowWidget); + layout->setContentsMargins(0, 0, 0, 0); + _windowWidget->setLayout(layout); + _webView = new QWebView(_windowWidget); + + layout->addWidget(_webView); + addEventBridgeToWindowObject(); } From f44ab9da8f46f98665c24a16cf3ded511b51d574 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 17:29:01 -0700 Subject: [PATCH 08/11] Update OAuthNetworkAccessManager to only send auth to metaverse --- libraries/networking/src/OAuthNetworkAccessManager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/OAuthNetworkAccessManager.cpp b/libraries/networking/src/OAuthNetworkAccessManager.cpp index 1a52dc4ce7..89a73d5984 100644 --- a/libraries/networking/src/OAuthNetworkAccessManager.cpp +++ b/libraries/networking/src/OAuthNetworkAccessManager.cpp @@ -14,6 +14,7 @@ #include #include "AccountManager.h" +#include "LimitedNodeList.h" #include "SharedUtil.h" #include "OAuthNetworkAccessManager.h" @@ -32,7 +33,7 @@ QNetworkReply* OAuthNetworkAccessManager::createRequest(QNetworkAccessManager::O QIODevice* outgoingData) { AccountManager& accountManager = AccountManager::getInstance(); - if (accountManager.hasValidAccessToken()) { + if (accountManager.hasValidAccessToken() && req.url().host() == DEFAULT_NODE_AUTH_URL.host()) { QNetworkRequest authenticatedRequest(req); authenticatedRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); authenticatedRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, From aca509d5cce707ec75416b9b502f5ab33a59ccc6 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 17:35:06 -0700 Subject: [PATCH 09/11] Add Application::improtSVOFromURL --- interface/src/Application.cpp | 4 ++++ interface/src/Application.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 25077e0ac1..36835405f7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -877,6 +877,10 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod } } +void Application::importSVOFromURL(QUrl url) { + emit svoImportRequested(url.url()); +} + bool Application::event(QEvent* event) { // handle custom URL diff --git a/interface/src/Application.h b/interface/src/Application.h index 248aaa0f6a..b013692393 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -216,6 +216,8 @@ public: float getFieldOfView() { return _fieldOfView.get(); } void setFieldOfView(float fov) { _fieldOfView.set(fov); } + void importSVOFromURL(QUrl url); + NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; } void lockOctreeSceneStats() { _octreeSceneStatsLock.lockForRead(); } void unlockOctreeSceneStats() { _octreeSceneStatsLock.unlock(); } From 47e63719ee5129a198572f90d8d8bc33c5bc62c3 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 17:35:33 -0700 Subject: [PATCH 10/11] Handle svo request in DataWebPage as import requests --- interface/src/ui/DataWebPage.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/DataWebPage.cpp b/interface/src/ui/DataWebPage.cpp index b7aa6351a8..7172d87d36 100644 --- a/interface/src/ui/DataWebPage.cpp +++ b/interface/src/ui/DataWebPage.cpp @@ -31,8 +31,12 @@ void DataWebPage::javaScriptConsoleMessage(const QString& message, int lineNumbe } bool DataWebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type) { - + if (!request.url().toString().startsWith(HIFI_URL_SCHEME)) { + if (request.url().path().toLower().endsWith(SVO_EXTENSION)) { + Application::getInstance()->importSVOFromURL(request.url()); + return false; + } return true; } else { // this is a hifi URL - have the AddressManager handle it From 235b5d77ae72000cc25360a05a05c8a74001f4f1 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 17:38:36 -0700 Subject: [PATCH 11/11] Add missing include to DataWebPage --- interface/src/ui/DataWebPage.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/DataWebPage.cpp b/interface/src/ui/DataWebPage.cpp index 7172d87d36..de97ab94a6 100644 --- a/interface/src/ui/DataWebPage.cpp +++ b/interface/src/ui/DataWebPage.cpp @@ -11,6 +11,7 @@ #include +#include "Application.h" #include #include @@ -21,7 +22,7 @@ DataWebPage::DataWebPage(QObject* parent) : { // use an OAuthNetworkAccessManager instead of regular QNetworkAccessManager so our requests are authed setNetworkAccessManager(OAuthNetworkAccessManager::getInstance()); - + // give the page an empty stylesheet settings()->setUserStyleSheetUrl(QUrl()); }