diff --git a/docs/JUCE changelist.txt b/docs/JUCE changelist.txt index 4eda61451d..ff143a59a4 100644 --- a/docs/JUCE changelist.txt +++ b/docs/JUCE changelist.txt @@ -20,6 +20,8 @@ Changelist for version 1.44 - added MultiDocumentPanel::createNewDocumentWindow() method to allow creation of custom document windows in a MultiDocumentPanel - added a Thread::getCurrentThread() method - added an option to MessageManagerLock that can check for thread termination, to avoid deadlocks. +- new method: PropertySet::setFallbackPropertySet() +- some simplifications to ApplicationProperties, because of problems it was causing when there were read-only common property files. ============================================================================== Changelist for version 1.43 diff --git a/src/juce_appframework/application/juce_ApplicationProperties.cpp b/src/juce_appframework/application/juce_ApplicationProperties.cpp index 61cc43761c..ec77b30c5c 100644 --- a/src/juce_appframework/application/juce_ApplicationProperties.cpp +++ b/src/juce_appframework/application/juce_ApplicationProperties.cpp @@ -47,7 +47,8 @@ ApplicationProperties::ApplicationProperties() throw() : userProps (0), commonProps (0), msBeforeSaving (3000), - options (PropertiesFile::storeAsBinary) + options (PropertiesFile::storeAsBinary), + commonSettingsAreReadOnly (0) { } @@ -75,9 +76,8 @@ bool ApplicationProperties::testWriteAccess (const bool testUserSettings, const bool testCommonSettings, const bool showWarningDialogOnFailure) { - const bool userOk = (! testUserSettings) || getUserSettings()->save(); - const bool commonOk = (! testCommonSettings) - || (userProps != getCommonSettings() && getCommonSettings()->save()); + const bool userOk = (! testUserSettings) || getUserSettings()->save(); + const bool commonOk = (! testCommonSettings) || getCommonSettings (false)->save(); if (! (userOk && commonOk)) { @@ -85,26 +85,16 @@ bool ApplicationProperties::testWriteAccess (const bool testUserSettings, { String filenames; - if (! userOk) - filenames << "\n" << getUserSettings()->getFile().getFullPathName(); + if (userProps != 0 && ! userOk) + filenames << '\n' << userProps->getFile().getFullPathName(); - if (! commonOk) - { - PropertiesFile* const realCommonProps - = PropertiesFile::createDefaultAppPropertiesFile (appName, fileSuffix, folderName, - true, msBeforeSaving, options); - - if (realCommonProps != 0) - { - filenames << "\n" << realCommonProps->getFile().getFullPathName(); - delete realCommonProps; - } - } + if (commonProps != 0 && ! commonOk) + filenames << '\n' << commonProps->getFile().getFullPathName(); AlertWindow::showMessageBox (AlertWindow::WarningIcon, appName + TRANS(" - Unable to save settings"), TRANS("An error occurred when trying to save the application's settings file...\n\nIn order to save and restore its settings, ") - + appName + TRANS(" needs to be able to write to the following files:\n") + + appName + TRANS(" needs to be able to write to the following files:\n") + filenames + TRANS("\n\nMake sure that these files aren't read-only, and that the disk isn't full.")); } @@ -116,45 +106,46 @@ bool ApplicationProperties::testWriteAccess (const bool testUserSettings, } //============================================================================== -PropertiesFile* ApplicationProperties::getUserSettings() throw() +void ApplicationProperties::openFiles() throw() { - if (userProps == 0) - { - // You need to call setStorageParameters() before trying to get hold of the - // properties! - jassert (appName.isNotEmpty()); + // You need to call setStorageParameters() before trying to get hold of the + // properties! + jassert (appName.isNotEmpty()); - if (appName.isNotEmpty()) + if (appName.isNotEmpty()) + { + if (userProps == 0) userProps = PropertiesFile::createDefaultAppPropertiesFile (appName, fileSuffix, folderName, false, msBeforeSaving, options); - if (userProps == 0) - { - // create an emergency properties object to avoid returning a null pointer.. - userProps = new PropertiesFile (File::nonexistent, msBeforeSaving, options); - } + if (commonProps == 0) + commonProps = PropertiesFile::createDefaultAppPropertiesFile (appName, fileSuffix, folderName, + true, msBeforeSaving, options); + + userProps->setFallbackPropertySet (commonProps); } +} + +PropertiesFile* ApplicationProperties::getUserSettings() throw() +{ + if (userProps == 0) + openFiles(); return userProps; } -PropertiesFile* ApplicationProperties::getCommonSettings() throw() +PropertiesFile* ApplicationProperties::getCommonSettings (const bool returnUserPropsIfReadOnly) throw() { if (commonProps == 0) - { - // You need to call setStorageParameters() before trying to get hold of the - // properties! - jassert (appName.isNotEmpty()); + openFiles(); - if (appName.isNotEmpty()) - commonProps = PropertiesFile::createDefaultAppPropertiesFile (appName, fileSuffix, folderName, - true, msBeforeSaving, options); - - if (commonProps == 0 || ! commonProps->save()) - { - delete commonProps; - commonProps = getUserSettings(); - } + if (returnUserPropsIfReadOnly) + { + if (commonSettingsAreReadOnly == 0) + commonSettingsAreReadOnly = commonProps->save() ? -1 : 1; + + if (commonSettingsAreReadOnly > 0) + return userProps; } return commonProps; @@ -163,17 +154,13 @@ PropertiesFile* ApplicationProperties::getCommonSettings() throw() bool ApplicationProperties::saveIfNeeded() { return (userProps == 0 || userProps->saveIfNeeded()) - && (commonProps == 0 || commonProps == userProps || commonProps->saveIfNeeded()); + && (commonProps == 0 || commonProps->saveIfNeeded()); } void ApplicationProperties::closeFiles() { - delete userProps; - - if (commonProps != userProps) - delete commonProps; - - userProps = commonProps = 0; + deleteAndZero (userProps); + deleteAndZero (commonProps); } diff --git a/src/juce_appframework/application/juce_ApplicationProperties.h b/src/juce_appframework/application/juce_ApplicationProperties.h index 362bf818f7..5e21ab33fa 100644 --- a/src/juce_appframework/application/juce_ApplicationProperties.h +++ b/src/juce_appframework/application/juce_ApplicationProperties.h @@ -106,7 +106,12 @@ public: //============================================================================== /** Returns the user settings file. - The first time this is called, it will create and load the file. + The first time this is called, it will create and load the properties file. + + Note that when you search the user PropertiesFile for a value that it doesn't contain, + the common settings are used as a second-chance place to look. This is done via the + PropertySet::setFallbackPropertySet() method - by default the common settings are set + to the fallback for the user settings. @see getCommonSettings */ @@ -114,14 +119,19 @@ public: /** Returns the common settings file. - The first time this is called, it will create and load the file. - - If the common settings file doesn't have write access but the user one does, - then this may return the same PropertiesFile object as getUserSettings(). + The first time this is called, it will create and load the properties file. + @param returnUserPropsIfReadOnly if this is true, and the common properties file is + read-only (e.g. because the user doesn't have permission to write + to shared files), then this will return the user settings instead, + (like getUserSettings() would do). This is handy if you'd like to + write a value to the common settings, but if that's no possible, + then you'd rather write to the user settings than none at all. + If returnUserPropsIfReadOnly is false, this method will always return + the common settings, even if any changes to them can't be saved. @see getUserSettings */ - PropertiesFile* getCommonSettings() throw(); + PropertiesFile* getCommonSettings (const bool returnUserPropsIfReadOnly) throw(); //============================================================================== /** Saves both files if they need to be saved. @@ -138,6 +148,7 @@ public: */ void closeFiles(); + //============================================================================== juce_UseDebuggingNewOperator @@ -148,9 +159,12 @@ private: String appName, fileSuffix, folderName; int msBeforeSaving, options; + int commonSettingsAreReadOnly; ApplicationProperties (const ApplicationProperties&); const ApplicationProperties& operator= (const ApplicationProperties&); + + void openFiles() throw(); }; diff --git a/src/juce_appframework/application/juce_PropertiesFile.cpp b/src/juce_appframework/application/juce_PropertiesFile.cpp index 827621effb..c1db2d0d7b 100644 --- a/src/juce_appframework/application/juce_PropertiesFile.cpp +++ b/src/juce_appframework/application/juce_PropertiesFile.cpp @@ -144,13 +144,23 @@ bool PropertiesFile::saveIfNeeded() return (! needsWriting) || save(); } +bool PropertiesFile::needsToBeSaved() const throw() +{ + const ScopedLock sl (getLock()); + + return needsWriting; +} + + bool PropertiesFile::save() { const ScopedLock sl (getLock()); stopTimer(); - if (file == File::nonexistent || file.isDirectory()) + if (file == File::nonexistent + || file.isDirectory() + || ! file.getParentDirectory().createDirectory()) return false; if ((options & storeAsXML) != 0) @@ -292,7 +302,9 @@ PropertiesFile* PropertiesFile::createDefaultAppPropertiesFile (const String& ap folderName, commonToAllUsers)); - if (file == File::nonexistent || ! file.getParentDirectory().createDirectory()) + jassert (file != File::nonexistent); + + if (file == File::nonexistent) return 0; return new PropertiesFile (file, millisecondsBeforeSaving, propertiesFileOptions); diff --git a/src/juce_appframework/application/juce_PropertiesFile.h b/src/juce_appframework/application/juce_PropertiesFile.h index 9c7835352e..b12da6f1df 100644 --- a/src/juce_appframework/application/juce_PropertiesFile.h +++ b/src/juce_appframework/application/juce_PropertiesFile.h @@ -114,6 +114,11 @@ public: */ bool save(); + /** Returns true if the properties have been altered since the last time they were + saved. + */ + bool needsToBeSaved() const throw(); + //============================================================================== /** Returns the file that's being used. */ const File getFile() const throw(); diff --git a/src/juce_appframework/gui/components/buttons/juce_Button.h b/src/juce_appframework/gui/components/buttons/juce_Button.h index 876140cf39..ed710c33fb 100644 --- a/src/juce_appframework/gui/components/buttons/juce_Button.h +++ b/src/juce_appframework/gui/components/buttons/juce_Button.h @@ -225,12 +225,16 @@ public: If generateTooltip is true, then the button's tooltip will be automatically generated based on the name of this command and its current shortcut key. - @see addShortcut + @see addShortcut, getCommandID */ void setCommandToTrigger (ApplicationCommandManager* commandManagerToUse, const int commandID, const bool generateTooltip); + /** Returns the command ID that was set by setCommandToTrigger(). + */ + int getCommandID() const throw() { return commandID; } + //============================================================================== /** Assigns a shortcut key to trigger the button. diff --git a/src/juce_appframework/gui/components/windows/juce_AlertWindow.cpp b/src/juce_appframework/gui/components/windows/juce_AlertWindow.cpp index 43bdf6448f..297765343e 100644 --- a/src/juce_appframework/gui/components/windows/juce_AlertWindow.cpp +++ b/src/juce_appframework/gui/components/windows/juce_AlertWindow.cpp @@ -45,24 +45,6 @@ static const int titleH = 24; static const int iconWidth = 80; -//============================================================================== -class AlertWindowTextButton : public TextButton -{ -public: - AlertWindowTextButton (const String& text) - : TextButton (text, String::empty) - { - setWantsKeyboardFocus (true); - setMouseClickGrabsKeyboardFocus (false); - } - - int returnValue; - -private: - AlertWindowTextButton (const AlertWindowTextButton&); - const AlertWindowTextButton& operator= (const AlertWindowTextButton&); -}; - //============================================================================== class AlertWindowTextEditor : public TextEditor { @@ -89,13 +71,13 @@ public: void returnPressed() { // pass these up the component hierarchy to be trigger the buttons - Component::keyPressed (KeyPress (KeyPress::returnKey, 0, T('\n'))); + getParentComponent()->keyPressed (KeyPress (KeyPress::returnKey, 0, T('\n'))); } void escapePressed() { // pass these up the component hierarchy to be trigger the buttons - Component::keyPressed (KeyPress (KeyPress::escapeKey, 0, 0)); + getParentComponent()->keyPressed (KeyPress (KeyPress::escapeKey, 0, 0)); } private: @@ -134,8 +116,6 @@ AlertWindow::AlertWindow (const String& title, lookAndFeelChanged(); constrainer.setMinimumOnscreenAmounts (0x10000, 0x10000, 0x10000, 0x10000); - - setWantsKeyboardFocus (getNumChildComponents() == 0); } AlertWindow::~AlertWindow() @@ -172,12 +152,12 @@ void AlertWindow::buttonClicked (Button* button) { for (int i = 0; i < buttons.size(); i++) { - AlertWindowTextButton* const c = (AlertWindowTextButton*) buttons[i]; + TextButton* const c = (TextButton*) buttons[i]; if (button->getName() == c->getName()) { if (c->getParentComponent() != 0) - c->getParentComponent()->exitModalState (c->returnValue); + c->getParentComponent()->exitModalState (c->getCommandID()); break; } @@ -190,9 +170,11 @@ void AlertWindow::addButton (const String& name, const KeyPress& shortcutKey1, const KeyPress& shortcutKey2) { - AlertWindowTextButton* const b = new AlertWindowTextButton (name); + TextButton* const b = new TextButton (name, String::empty); - b->returnValue = returnValue; + b->setWantsKeyboardFocus (true); + b->setMouseClickGrabsKeyboardFocus (false); + b->setCommandToTrigger (0, returnValue, false); b->addShortcut (shortcutKey1); b->addShortcut (shortcutKey2); b->addButtonListener (this); @@ -286,6 +268,7 @@ public: setCaretVisible (false); setScrollbarsShown (true); lookAndFeelChanged(); + setWantsKeyboardFocus (false); setFont (font); setText (message, false); @@ -441,14 +424,14 @@ void AlertWindow::updateLayout (const bool onlyIncreaseSize) int buttonW = 40; int i; for (i = 0; i < buttons.size(); ++i) - buttonW += 16 + ((const AlertWindowTextButton*) buttons[i])->getWidth(); + buttonW += 16 + ((const TextButton*) buttons[i])->getWidth(); w = jmax (buttonW, w); h += (textBoxes.size() + comboBoxes.size() + progressBars.size()) * 50; if (buttons.size() > 0) - h += 20 + ((AlertWindowTextButton*) buttons[0])->getHeight(); + h += 20 + ((TextButton*) buttons[0])->getHeight(); for (i = customComps.size(); --i >= 0;) { @@ -499,14 +482,14 @@ void AlertWindow::updateLayout (const bool onlyIncreaseSize) int totalWidth = -spacer; for (i = buttons.size(); --i >= 0;) - totalWidth += ((AlertWindowTextButton*) buttons[i])->getWidth() + spacer; + totalWidth += ((TextButton*) buttons[i])->getWidth() + spacer; int x = (w - totalWidth) / 2; int y = (int) (getHeight() * 0.95f); for (i = 0; i < buttons.size(); ++i) { - AlertWindowTextButton* const c = (AlertWindowTextButton*) buttons[i]; + TextButton* const c = (TextButton*) buttons[i]; int ny = proportionOfHeight (0.95f) - c->getHeight(); c->setTopLeftPosition (x, ny); if (ny < y) @@ -544,6 +527,8 @@ void AlertWindow::updateLayout (const bool onlyIncreaseSize) y += h + 10; } } + + setWantsKeyboardFocus (getNumChildComponents() == 0); } bool AlertWindow::containsAnyExtraComponents() const @@ -569,7 +554,7 @@ bool AlertWindow::keyPressed (const KeyPress& key) { for (int i = buttons.size(); --i >= 0;) { - AlertWindowTextButton* const b = (AlertWindowTextButton*) buttons[i]; + TextButton* const b = (TextButton*) buttons[i]; if (b->isRegisteredForShortcut (key)) { @@ -585,7 +570,7 @@ bool AlertWindow::keyPressed (const KeyPress& key) } else if (key.isKeyCode (KeyPress::returnKey) && buttons.size() == 1) { - ((AlertWindowTextButton*) buttons.getFirst())->triggerClick(); + ((TextButton*) buttons.getFirst())->triggerClick(); return true; } diff --git a/src/juce_core/containers/juce_PropertySet.cpp b/src/juce_core/containers/juce_PropertySet.cpp index 6bac254d7c..f3914f0d9c 100644 --- a/src/juce_core/containers/juce_PropertySet.cpp +++ b/src/juce_core/containers/juce_PropertySet.cpp @@ -42,6 +42,7 @@ BEGIN_JUCE_NAMESPACE //============================================================================== PropertySet::PropertySet (const bool ignoreCaseOfKeyNames) throw() : properties (ignoreCaseOfKeyNames), + fallbackProperties (0), ignoreCaseOfKeys (ignoreCaseOfKeyNames) { } @@ -51,11 +52,17 @@ PropertySet::~PropertySet() } const String PropertySet::getValue (const String& keyName, - const String& defaultReturnValue) const throw() + const String& defaultValue) const throw() { const ScopedLock sl (lock); - return properties.getValue (keyName, defaultReturnValue); + const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); + + if (index >= 0) + return properties.getAllValues() [index]; + + return fallbackProperties != 0 ? fallbackProperties->getValue (keyName, defaultValue) + : defaultValue; } int PropertySet::getIntValue (const String& keyName, @@ -67,7 +74,8 @@ int PropertySet::getIntValue (const String& keyName, if (index >= 0) return properties.getAllValues() [index].getIntValue(); - return defaultValue; + return fallbackProperties != 0 ? fallbackProperties->getIntValue (keyName, defaultValue) + : defaultValue; } double PropertySet::getDoubleValue (const String& keyName, @@ -79,7 +87,8 @@ double PropertySet::getDoubleValue (const String& keyName, if (index >= 0) return properties.getAllValues()[index].getDoubleValue(); - return defaultValue; + return fallbackProperties != 0 ? fallbackProperties->getDoubleValue (keyName, defaultValue) + : defaultValue; } bool PropertySet::getBoolValue (const String& keyName, @@ -91,7 +100,8 @@ bool PropertySet::getBoolValue (const String& keyName, if (index >= 0) return properties.getAllValues() [index].getIntValue() != 0; - return defaultValue; + return fallbackProperties != 0 ? fallbackProperties->getBoolValue (keyName, defaultValue) + : defaultValue; } XmlElement* PropertySet::getXmlValue (const String& keyName) const @@ -162,6 +172,12 @@ bool PropertySet::containsKey (const String& keyName) const throw() return properties.getAllKeys().contains (keyName, ignoreCaseOfKeys); } +void PropertySet::setFallbackPropertySet (PropertySet* fallbackProperties_) throw() +{ + const ScopedLock sl (lock); + fallbackProperties = fallbackProperties_; +} + void PropertySet::propertyChanged() { } diff --git a/src/juce_core/containers/juce_PropertySet.h b/src/juce_core/containers/juce_PropertySet.h index a3ef4da45d..d83c75d556 100644 --- a/src/juce_core/containers/juce_PropertySet.h +++ b/src/juce_core/containers/juce_PropertySet.h @@ -63,6 +63,10 @@ public: //============================================================================== /** Returns one of the properties as a string. + If the value isn't found in this set, then this will look for it in a fallback + property set (if you've specified one with the setFallbackPropertySet() method), + and if it can't find one there, it'll return the default value passed-in. + @param keyName the name of the property to retrieve @param defaultReturnValue a value to return if the named property doesn't actually exist */ @@ -71,6 +75,10 @@ public: /** Returns one of the properties as an integer. + If the value isn't found in this set, then this will look for it in a fallback + property set (if you've specified one with the setFallbackPropertySet() method), + and if it can't find one there, it'll return the default value passed-in. + @param keyName the name of the property to retrieve @param defaultReturnValue a value to return if the named property doesn't actually exist */ @@ -79,6 +87,10 @@ public: /** Returns one of the properties as an double. + If the value isn't found in this set, then this will look for it in a fallback + property set (if you've specified one with the setFallbackPropertySet() method), + and if it can't find one there, it'll return the default value passed-in. + @param keyName the name of the property to retrieve @param defaultReturnValue a value to return if the named property doesn't actually exist */ @@ -90,6 +102,10 @@ public: The result will be true if the string found for this key name can be parsed as a non-zero integer. + If the value isn't found in this set, then this will look for it in a fallback + property set (if you've specified one with the setFallbackPropertySet() method), + and if it can't find one there, it'll return the default value passed-in. + @param keyName the name of the property to retrieve @param defaultReturnValue a value to return if the named property doesn't actually exist */ @@ -101,6 +117,10 @@ public: The result will a new XMLElement object that the caller must delete. If may return 0 if the key isn't found, or if the entry contains an string that isn't valid XML. + If the value isn't found in this set, then this will look for it in a fallback + property set (if you've specified one with the setFallbackPropertySet() method), + and if it can't find one there, it'll return the default value passed-in. + @param keyName the name of the property to retrieve */ XmlElement* getXmlValue (const String& keyName) const; @@ -167,6 +187,25 @@ public: /** Returns the lock used when reading or writing to this set */ const CriticalSection& getLock() const throw() { return lock; } + //============================================================================== + /** Sets up a second PopertySet that will be used to look up any values that aren't + set in this one. + + If you set this up to be a pointer to a second property set, then whenever one + of the getValue() methods fails to find an entry in this set, it will look up that + value in the fallback set, and if it finds it, it will return that. + + Make sure that you don't delete the fallback set while it's still being used by + another set! To remove the fallback set, just call this method with a null pointer. + + @see getFallbackPropertySet + */ + void setFallbackPropertySet (PropertySet* fallbackProperties) throw(); + + /** Returns the fallback property set. + @see setFallbackPropertySet + */ + PropertySet* getFallbackPropertySet() const throw() { return fallbackProperties; } //============================================================================== juce_UseDebuggingNewOperator @@ -182,8 +221,9 @@ protected: private: //============================================================================== StringPairArray properties; - bool ignoreCaseOfKeys; + PropertySet* fallbackProperties; CriticalSection lock; + bool ignoreCaseOfKeys; PropertySet (const PropertySet&); const PropertySet& operator= (const PropertySet&);