diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index ef82ce4884..fc10bd4f68 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -82,6 +82,12 @@ ModalWindow { // Clear selection when click on external frame. frameClicked.connect(function() { d.clearSelection(); }); + + if (selectDirectory) { + currentSelection.text = d.capitalizeDrive(helper.urlToPath(initialFolder)); + } + + fileTableView.forceActiveFocus(); } Item { @@ -703,7 +709,6 @@ ModalWindow { if (!helper.urlIsWritable(selection)) { desktop.messageBox({ icon: OriginalDialogs.StandardIcon.Warning, - buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No, text: "Unable to write to location " + selection }) return; diff --git a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml index 9a19889938..2cf50891c9 100644 --- a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml +++ b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml @@ -65,7 +65,10 @@ Preference { verticalCenter: dataTextField.verticalCenter } onClicked: { - var browser = fileBrowserBuilder.createObject(desktop, { selectDirectory: true, folder: fileDialogHelper.pathToUrl(preference.value) }); + var browser = fileBrowserBuilder.createObject(desktop, { + selectDirectory: true, + dir: fileDialogHelper.pathToUrl(preference.value) + }); browser.selectedFile.connect(function(fileUrl){ console.log(fileUrl); dataTextField.text = fileDialogHelper.urlToPath(fileUrl); diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 0443c65453..f0ae221566 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -14,6 +14,8 @@ #include <QMessageBox> #include <QScriptValue> +#include <SettingHandle.h> + #include "Application.h" #include "DomainHandler.h" #include "MainWindow.h" @@ -23,6 +25,10 @@ #include "WindowScriptingInterface.h" +static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); +static const QString LAST_BROWSE_LOCATION_SETTING = "LastBrowseLocation"; + + WindowScriptingInterface::WindowScriptingInterface() { const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler(); connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged); @@ -101,6 +107,14 @@ QString fixupPathForMac(const QString& directory) { return path; } +QString WindowScriptingInterface::getPreviousBrowseLocation() const { + return Setting::Handle<QString>(LAST_BROWSE_LOCATION_SETTING, DESKTOP_LOCATION).get(); +} + +void WindowScriptingInterface::setPreviousBrowseLocation(const QString& location) { + Setting::Handle<QVariant>(LAST_BROWSE_LOCATION_SETTING).set(location); +} + /// Display an open file dialog. If `directory` is an invalid file or directory the browser will start at the current /// working directory. /// \param const QString& title title of the window @@ -108,8 +122,17 @@ QString fixupPathForMac(const QString& directory) { /// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog` /// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue` QScriptValue WindowScriptingInterface::browse(const QString& title, const QString& directory, const QString& nameFilter) { - QString path = fixupPathForMac(directory); + QString path = directory; + if (path.isEmpty()) { + path = getPreviousBrowseLocation(); + } +#ifndef Q_OS_WIN + path = fixupPathForMac(directory); +#endif QString result = OffscreenUi::getOpenFileName(nullptr, title, path, nameFilter); + if (!result.isEmpty()) { + setPreviousBrowseLocation(QFileInfo(result).absolutePath()); + } return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); } @@ -120,8 +143,17 @@ QScriptValue WindowScriptingInterface::browse(const QString& title, const QStrin /// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog` /// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue` QScriptValue WindowScriptingInterface::save(const QString& title, const QString& directory, const QString& nameFilter) { - QString path = fixupPathForMac(directory); + QString path = directory; + if (path.isEmpty()) { + path = getPreviousBrowseLocation(); + } +#ifndef Q_OS_WIN + path = fixupPathForMac(directory); +#endif QString result = OffscreenUi::getSaveFileName(nullptr, title, path, nameFilter); + if (!result.isEmpty()) { + setPreviousBrowseLocation(QFileInfo(result).absolutePath()); + } return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); } diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index dfe02a5064..b92114c1bf 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -49,6 +49,10 @@ signals: private slots: WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height); + +private: + QString getPreviousBrowseLocation() const; + void setPreviousBrowseLocation(const QString& location); }; #endif // hifi_WindowScriptingInterface_h diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 7b1133c2aa..4ba4ad5676 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -17,6 +17,7 @@ #include "EntityItem.h" #include "EntityItemProperties.h" #include "EntityTypes.h" +#include "EntitiesLogging.h" #include "LightEntityItem.h" #include "ModelEntityItem.h" @@ -63,6 +64,9 @@ EntityTypes::EntityType EntityTypes::getEntityTypeFromName(const QString& name) if (matchedTypeName != _nameToTypeMap.end()) { return matchedTypeName.value(); } + if (name.size() > 0 && name[0].isLower()) { + qCDebug(entities) << "Entity types must start with an uppercase letter. Please change the type" << name; + } return Unknown; } diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 6880b7a329..170669dede 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -97,7 +97,6 @@ void DomainHandler::softReset() { clearSettings(); - _domainConnectionRefusals.clear(); _connectionDenialsSinceKeypairRegen = 0; // cancel the failure timeout for any pending requests for settings @@ -118,6 +117,9 @@ void DomainHandler::hardReset() { _hostname = QString(); _sockAddr.clear(); + _hasSignalledProtocolMismatch = false; + _domainConnectionRefusals.clear(); + _hasCheckedForAccessToken = false; // clear any pending path we may have wanted to ask the previous DS about @@ -405,9 +407,25 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec // and check and signal for an access token so that we can make sure they are logged in qCWarning(networking) << "The domain-server denied a connection request: " << reasonMessage; - if (!_domainConnectionRefusals.contains(reasonMessage)) { - _domainConnectionRefusals.append(reasonMessage); - emit domainConnectionRefused(reasonMessage, (int)reasonCode); + if (!_domainConnectionRefusals.contains(reasonCode)) { + + _domainConnectionRefusals.append(reasonCode); + + bool shouldSignal = true; + + // only signal once for a protocol mismatch, even between soft resets that will reset the _domainConnectionRefusals + if (reasonCode == ConnectionRefusedReason::ProtocolMismatch) { + if (_hasSignalledProtocolMismatch) { + shouldSignal = false; + } else { + _hasSignalledProtocolMismatch = true; + } + } + + if (shouldSignal) { + emit domainConnectionRefused(reasonMessage, (int)reasonCode); + } + } auto accountManager = DependencyManager::get<AccountManager>(); diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 1328174e87..3ab583d597 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -144,7 +144,8 @@ private: QString _pendingPath; QTimer _settingsTimer; - QStringList _domainConnectionRefusals; + QList<ConnectionRefusedReason> _domainConnectionRefusals; + bool _hasSignalledProtocolMismatch { false }; bool _hasCheckedForAccessToken { false }; int _connectionDenialsSinceKeypairRegen { 0 }; diff --git a/libraries/shared/src/SettingHandle.h b/libraries/shared/src/SettingHandle.h index f19fc5875b..8e07d28dad 100644 --- a/libraries/shared/src/SettingHandle.h +++ b/libraries/shared/src/SettingHandle.h @@ -77,15 +77,40 @@ namespace Setting { virtual ~Handle() { deinit(); } // Returns setting value, returns its default value if not found - T get() { return get(_defaultValue); } + T get() const { + return get(_defaultValue); + } + // Returns setting value, returns other if not found - T get(const T& other) { maybeInit(); return (_isSet) ? _value : other; } - T getDefault() const { return _defaultValue; } + T get(const T& other) const { + maybeInit(); + return (_isSet) ? _value : other; + } + + const T& getDefault() const { + return _defaultValue; + } - void set(const T& value) { maybeInit(); _value = value; _isSet = true; } - void reset() { set(_defaultValue); } - - void remove() { maybeInit(); _isSet = false; } + void reset() { + set(_defaultValue); + } + + void set(const T& value) { + maybeInit(); + if ((!_isSet && (value != _defaultValue)) || _value != value) { + _value = value; + _isSet = true; + save(); + } + } + + void remove() { + maybeInit(); + if (_isSet) { + _isSet = false; + save(); + } + } protected: virtual void setVariant(const QVariant& variant); diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index 1ebaa5cf82..95c6bc1efc 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -119,9 +119,9 @@ namespace Setting { } - void Interface::maybeInit() { + void Interface::maybeInit() const { if (!_isInitialized) { - init(); + const_cast<Interface*>(this)->init(); } } diff --git a/libraries/shared/src/SettingInterface.h b/libraries/shared/src/SettingInterface.h index 2b32e8a3b4..5e23d42223 100644 --- a/libraries/shared/src/SettingInterface.h +++ b/libraries/shared/src/SettingInterface.h @@ -39,19 +39,20 @@ namespace Setting { virtual ~Interface() = default; void init(); - void maybeInit(); + void maybeInit() const; void deinit(); void save(); void load(); - - bool _isInitialized = false; + bool _isSet = false; const QString _key; + + private: + mutable bool _isInitialized = false; friend class Manager; - - QWeakPointer<Manager> _manager; + mutable QWeakPointer<Manager> _manager; }; } diff --git a/libraries/shared/src/SettingManager.cpp b/libraries/shared/src/SettingManager.cpp index bacec2ee0c..abb8525b03 100644 --- a/libraries/shared/src/SettingManager.cpp +++ b/libraries/shared/src/SettingManager.cpp @@ -33,8 +33,8 @@ namespace Setting { void Manager::customDeleter() { } - void Manager::registerHandle(Setting::Interface* handle) { - QString key = handle->getKey(); + void Manager::registerHandle(Interface* handle) { + const QString& key = handle->getKey(); withWriteLock([&] { if (_handles.contains(key)) { qWarning() << "Setting::Manager::registerHandle(): Key registered more than once, overriding: " << key; @@ -58,7 +58,9 @@ namespace Setting { } else { loadedValue = value(key); } - handle->setVariant(loadedValue); + if (loadedValue.isValid()) { + handle->setVariant(loadedValue); + } }); } @@ -94,6 +96,7 @@ namespace Setting { } void Manager::saveAll() { + bool forceSync = false; withWriteLock([&] { for (auto key : _pendingChanges.keys()) { auto newValue = _pendingChanges[key]; @@ -101,15 +104,21 @@ namespace Setting { if (newValue == savedValue) { continue; } - if (newValue == UNSET_VALUE) { + if (newValue == UNSET_VALUE || !newValue.isValid()) { + forceSync = true; remove(key); } else { + forceSync = true; setValue(key, newValue); } } _pendingChanges.clear(); }); + if (forceSync) { + sync(); + } + // Restart timer if (_saveTimer) { _saveTimer->start(); diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 83b72c5c08..69c98d48c3 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -366,6 +366,10 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint } else if (button == vr::k_EButton_SteamVR_Touchpad) { _buttonPressedMap.insert(isLeftHand ? LS : RS); } + } else { + if (button == vr::k_EButton_Grip) { + _axisStateMap[isLeftHand ? LEFT_GRIP : RIGHT_GRIP] = 0.0f; + } } if (touched) { diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 0c4f4f3c5c..b39c843411 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1221,8 +1221,7 @@ function handeMenuEvent(menuItem) { if (!selectionManager.hasSelection()) { Window.alert("No entities have been selected."); } else { - var filename = "entities__" + Window.location.hostname + ".svo.json"; - filename = Window.save("Select Where to Save", filename, "*.json") + var filename = Window.save("Select Where to Save", "", "*.json") if (filename) { var success = Clipboard.exportEntities(filename, selectionManager.selections); if (!success) {