/* -*- mode: C++; tab-width: 4; c-basic-offset: 4; -*- */ /* AbiSource Application Framework * Copyright (C) 1998 AbiSource, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ // TODO add code to do an auto save anytime anything is changed. #include #include #include #include #include #include "ut_debugmsg.h" #include "ut_growbuf.h" #include "ut_string.h" #include "ut_string_class.h" #include "ut_go_file.h" #include "xap_Prefs.h" #include "xap_App.h" struct xmlToIdMapping { const char *m_name; int m_type; }; static UT_sint32 compareStrings(const void * ppS1, const void * ppS2) { const char * sz1 = static_cast(ppS1); const char * sz2 = static_cast(ppS2); return g_ascii_strcasecmp(sz1, sz2); } enum { TT_ABIPREFERENCES, TT_GEOMETRY, TT_FACE, TT_FONTS, TT_LOG, TT_PLUGIN, TT_RECENT, TT_SCHEME, TT_SELECT }; // keep these in alphabetical order static struct xmlToIdMapping s_Tokens[] = { { "AbiPreferences", TT_ABIPREFERENCES }, { "Face", TT_FACE }, { "Fonts", TT_FONTS }, { "Geometry", TT_GEOMETRY }, { "Log", TT_LOG}, { "Plugin", TT_PLUGIN }, { "Recent", TT_RECENT }, { "Scheme", TT_SCHEME }, { "Select", TT_SELECT } }; /*****************************************************************/ XAP_PrefsScheme::XAP_PrefsScheme( XAP_Prefs *pPrefs, const gchar * szSchemeName) : m_hash(41) { m_pPrefs = pPrefs; m_uTick = 0; m_bValidSortedKeys = false; if (szSchemeName && *szSchemeName) m_szName = g_strdup(szSchemeName); else m_szName = NULL; } XAP_PrefsScheme::~XAP_PrefsScheme(void) { FREEP(m_szName); // loop through and g_free the values UT_GenericVector * pVec = m_hash.enumerate (); UT_uint32 cnt = pVec->size(); for (UT_uint32 i = 0 ; i < cnt; i++) { char * val = pVec->getNthItem (i); FREEP(val); } DELETEP(pVec); } const gchar * XAP_PrefsScheme::getSchemeName(void) const { return m_szName; } bool XAP_PrefsScheme::setSchemeName(const gchar * szNewSchemeName) { FREEP(m_szName); return (NULL != (m_szName = g_strdup(szNewSchemeName))); } bool XAP_PrefsScheme::setValue(const gchar * szKey, const gchar * szValue) { ++m_uTick; gchar * pEntry = m_hash.pick(szKey); if (pEntry) { if (strcmp(szValue,pEntry) == 0) return true; // equal values, no changes required m_hash.set (szKey, g_strdup (szValue)); FREEP(pEntry); } else { // otherwise, need to add a new entry m_hash.insert(szKey, g_strdup(szValue)); m_bValidSortedKeys = false; } m_pPrefs->_markPrefChange( szKey ); return true; } bool XAP_PrefsScheme::setValueBool(const gchar * szKey, bool bValue) { return setValue(szKey, reinterpret_cast((bValue) ? "1" : "0")); } bool XAP_PrefsScheme::setValueInt(const gchar * szKey, const int nValue) { gchar szValue[32]; sprintf(szValue, "%d", nValue); return setValue(szKey, szValue); } bool XAP_PrefsScheme::getValue(const gchar * szKey, const gchar ** pszValue) const { gchar *pEntry = m_hash.pick(szKey); if (!pEntry) return false; if (pszValue) *pszValue = pEntry; return true; } bool XAP_PrefsScheme::getValue(const UT_String &stKey, UT_String &stValue) const { gchar *pEntry = m_hash.pick(stKey); if (!pEntry) return false; stValue = pEntry; return true; } bool XAP_PrefsScheme::getValueInt(const gchar * szKey, int& nValue) const { const gchar * szValue = NULL; if (!getValue(szKey,&szValue)) return false; // bogus keyword ?? if (!szValue || !*szValue) return false; // no value for known keyword ?? nValue = atoi(szValue); return true; } bool XAP_PrefsScheme::getValueBool(const gchar * szKey, bool * pbValue) const { *pbValue = false; // assume something const gchar * szValue = NULL; if (!getValue(szKey,&szValue)) return false; // bogus keyword ?? if (!szValue || !*szValue) return false; // no value for known keyword ?? switch (szValue[0]) { case '1': case 't': case 'T': case 'y': case 'Y': *pbValue = true; return true; default: *pbValue = false; return true; } } bool XAP_PrefsScheme::getNthValue(UT_uint32 k, const gchar ** pszKey, const gchar ** pszValue) { if (k >= static_cast(m_hash.size())) return false; // // Output prefs in alphabetic Order. // if (!m_bValidSortedKeys) { UT_GenericVector * vecD = m_hash.keys(); UT_GenericVector vecKeys(vecD->getItemCount(), 4, true); UT_sint32 i=0; m_sortedKeys.clear(); for(i=0; i< vecD->getItemCount(); i++) { m_sortedKeys.addItem(vecD->getNthItem(i)->c_str()); } m_sortedKeys.qsort(compareStrings); m_bValidSortedKeys = true; delete vecD; } const char * szKey = NULL; const char * szValue = NULL; szKey = m_sortedKeys.getNthItem(k); szValue = m_hash.pick(szKey); if(szValue && *szValue) { *pszKey = szKey; *pszValue = szValue; return true; } else { *pszKey = NULL; *pszValue = NULL; return false; } return false; } /*****************************************************************/ bool XAP_Prefs::getAutoSavePrefs(void) const { return m_bAutoSavePrefs; } void XAP_Prefs::setAutoSavePrefs(bool bAuto) { m_bAutoSavePrefs = bAuto; // TODO if turning autosave on, we should do a save now.... // TODO if was on and turning off, should we save it now ?? } /*****************************************************************/ bool XAP_Prefs::getUseEnvLocale(void) const { return m_bUseEnvLocale; } void XAP_Prefs::setUseEnvLocale(bool bUse) { m_bUseEnvLocale = bUse; } /*****************************************************************/ UT_sint32 XAP_Prefs::getMaxRecent(void) const { return m_iMaxRecent; } void XAP_Prefs::setMaxRecent(UT_sint32 k) { UT_ASSERT_HARMLESS(k<=XAP_PREF_LIMIT_MaxRecent); if (k > XAP_PREF_LIMIT_MaxRecent) k = XAP_PREF_LIMIT_MaxRecent; m_iMaxRecent = k; } UT_sint32 XAP_Prefs::getRecentCount(void) const { return m_vecRecent.getItemCount(); } const char * XAP_Prefs::getRecent(UT_sint32 k) const { // NB: k is one-based UT_return_val_if_fail(k <= m_iMaxRecent, NULL); const char * pRecent = NULL; if (k <= m_vecRecent.getItemCount()) { pRecent = reinterpret_cast(m_vecRecent.getNthItem(k - 1)); } return pRecent; } void XAP_Prefs::addRecent(const char * szRecent) { char * sz; bool bFound = false; UT_return_if_fail(szRecent); if (m_iMaxRecent == 0) return; // NOOP if(m_bIgnoreThisOne) { m_bIgnoreThisOne = false; return; } // was it already here? for (UT_sint32 i=0; i0); UT_return_if_fail(k<=getRecentCount()); char * sz = m_vecRecent.getNthItem(k-1); FREEP(sz); m_vecRecent.deleteNthItem(k-1); } void XAP_Prefs::_pruneRecent(void) { UT_sint32 i; UT_sint32 count = getRecentCount(); if (m_iMaxRecent == 0) { // nuke the whole thing for (i = count; i > 0 ; i--) { char * sz = m_vecRecent.getNthItem(i-1); FREEP(sz); } m_vecRecent.clear(); } else if (count > m_iMaxRecent) { // prune entries past m_iMaxRecent for (i = count; i > m_iMaxRecent; i--) removeRecent(i); } } /*****************************************************************/ bool XAP_Prefs::setGeometry(UT_sint32 posx, UT_sint32 posy, UT_uint32 width, UT_uint32 height, UT_uint32 flags) { m_parserState.m_bFoundGeometry = true; m_geom.m_width = width; m_geom.m_height = height; m_geom.m_posx = posx; m_geom.m_posy = posy; m_geom.m_flags = flags; //For now we turn on the autosave of prefs so that we can save this setting ... is this bad? setAutoSavePrefs(true); return true; } bool XAP_Prefs::getGeometry(UT_sint32 *posx, UT_sint32 *posy, UT_uint32 *width, UT_uint32 *height, UT_uint32 *flags) { if (m_parserState.m_bFoundGeometry == false) { return false; } if (width) { *width = m_geom.m_width; } if (height) { *height = m_geom.m_height; } if (posx) { *posx = m_geom.m_posx; } if (posy) { *posy = m_geom.m_posy; } if (flags) { *flags = m_geom.m_flags; } return true; } void XAP_Prefs::log(const char * where, const char * what, XAPPrefsLog_Level level) { UT_return_if_fail(where && what); char b[50]; time_t t = time(NULL); // we are inserting the log entries as comments, so we have to // ensure we have no "--" in there (it anoys the parser) UT_UTF8String sWhere(where); UT_UTF8String sWhat(what); UT_UTF8String sDashdash = "--"; UT_UTF8String sDash = "-"; while(strstr(sWhat.utf8_str(), "--")) { sWhat.escape(sDashdash, sDash); } while(strstr(sWhere.utf8_str(), "--")) { sWhere.escape(sDashdash, sDash); } strftime(b, 50, ""; m_vecLog.addItem(s); } /*****************************************************************/ XAP_Prefs::XAP_Prefs() : m_ahashChanges( 20 ) { m_bAutoSavePrefs = (atoi(XAP_PREF_DEFAULT_AutoSavePrefs) ? true : false); m_bUseEnvLocale = (atoi(XAP_PREF_DEFAULT_UseEnvLocale) ? true : false); m_currentScheme = NULL; m_builtinScheme = NULL; m_iMaxRecent = atoi(XAP_PREF_DEFAULT_MaxRecent); m_bInChangeBlock = false; m_bIgnoreThisOne = false; memset(&m_geom, 0, sizeof(m_geom)); // NOTE: since constructors cannot report g_try_malloc // NOTE: failures (and since it is virtual back // NOTE: to the application), our creator must call // NOTE: loadBuiltinPrefs(). // NOTE: we do not initialize the values in m_parserState // NOTE: since they are only used by the parser, it can // NOTE: initialize them. } XAP_Prefs::~XAP_Prefs(void) { UT_VECTOR_PURGEALL(XAP_PrefsScheme *, m_vecSchemes); UT_VECTOR_PURGEALL(XAP_PrefsScheme *, m_vecPluginSchemes); UT_VECTOR_FREEALL(char *, m_vecRecent); UT_VECTOR_PURGEALL(tPrefsListenersPair *, m_vecPrefsListeners); UT_VECTOR_PURGEALL(UT_UTF8String *, m_vecLog); } /*****************************************************************/ XAP_PrefsScheme * XAP_Prefs::_getNthScheme(UT_uint32 k, const UT_GenericVector &vecSchemes) const { UT_uint32 kLimit = vecSchemes.getItemCount(); if (k < kLimit) return vecSchemes.getNthItem(k); else return NULL; } XAP_PrefsScheme * XAP_Prefs::getNthScheme(UT_uint32 k) const { return _getNthScheme(k, m_vecSchemes); } XAP_PrefsScheme * XAP_Prefs::getNthPluginScheme(UT_uint32 k) const { return _getNthScheme(k, m_vecPluginSchemes); } XAP_PrefsScheme * XAP_Prefs::getScheme(const gchar * szSchemeName) const { UT_uint32 kLimit = m_vecSchemes.getItemCount(); UT_uint32 k; for (k=0; k(szSchemeName),static_cast(p->getSchemeName())) == 0) return p; } return NULL; } XAP_PrefsScheme * XAP_Prefs::getPluginScheme(const gchar * szSchemeName) const { UT_uint32 kLimit = m_vecPluginSchemes.getItemCount(); UT_uint32 k; for (k=0; k(szSchemeName),static_cast(p->getSchemeName())) == 0) return p; } return NULL; } bool XAP_Prefs::addScheme(XAP_PrefsScheme * pNewScheme) { const gchar * szBuiltinSchemeName = getBuiltinSchemeName(); const gchar * szThisSchemeName = pNewScheme->getSchemeName(); if (strcmp(static_cast(szThisSchemeName), static_cast(szBuiltinSchemeName)) == 0) { UT_ASSERT(m_builtinScheme == NULL); m_builtinScheme = pNewScheme; } return (m_vecSchemes.addItem(pNewScheme) == 0); } bool XAP_Prefs::addPluginScheme(XAP_PrefsScheme * pNewScheme) { return (m_vecPluginSchemes.addItem(pNewScheme) == 0); } XAP_PrefsScheme * XAP_Prefs::getCurrentScheme(bool bCreate) { if (bCreate) { // the builtin scheme is not updatable, // so we may need to create one that is if ( !strcmp(static_cast(m_currentScheme->getSchemeName()), "_builtin_") ) { const gchar new_name[] = "_custom_"; if (setCurrentScheme(new_name)) { // unused _custom_ scheme is lying around, so recycle it UT_ASSERT_HARMLESS(UT_TODO); // HYP: reset the current scheme's hash table contents? // ALT: replace the existing scheme with new empty one } else { // we need to create it XAP_PrefsScheme * pNewScheme = new XAP_PrefsScheme(this, new_name); UT_ASSERT(pNewScheme); addScheme(pNewScheme); setCurrentScheme(new_name); } } } return m_currentScheme; } bool XAP_Prefs::setCurrentScheme(const gchar * szSchemeName) { // set the current scheme. // TODO notify the application that the scheme has changed XAP_PrefsScheme * p = getScheme(szSchemeName); if (!p) return false; UT_DEBUGMSG(("Preferences::setCurrentScheme [%s].\n",szSchemeName)); m_currentScheme = p; return true; } /*****************************************************************/ static const gchar DEBUG_PREFIX[] = "DeBuG"; // case insensitive static const gchar NO_PREF_VALUE[] = ""; bool XAP_Prefs::getPrefsValue(const gchar * szKey, const gchar ** pszValue, bool bAllowBuiltin) const { // a convenient routine to get a name/value pair from the current scheme UT_return_val_if_fail(m_currentScheme,false); if (m_currentScheme->getValue(szKey,pszValue)) return true; if (bAllowBuiltin && m_builtinScheme->getValue(szKey,pszValue)) return true; // It is legal for there to be arbitrary preference tags that start with // "Debug", and Abi apps won't choke. The idea is that developers can use // these to selectively trigger development-time behaviors. if (g_ascii_strncasecmp(szKey, DEBUG_PREFIX, sizeof(DEBUG_PREFIX) - 1) == 0) { *pszValue = NO_PREF_VALUE; return true; } return false; } bool XAP_Prefs::getPrefsValue(const UT_String &stKey, UT_String &stValue, bool bAllowBuiltin) const { UT_return_val_if_fail(m_currentScheme,false); if (m_currentScheme->getValue(stKey, stValue)) return true; if (bAllowBuiltin && m_builtinScheme->getValue(stKey, stValue)) return true; // It is legal for there to be arbitrary preference tags that start with // "Debug", and Abi apps won't choke. The idea is that developers can use // these to selectively trigger development-time behaviors. if (g_ascii_strncasecmp(stKey.c_str(), DEBUG_PREFIX, sizeof(DEBUG_PREFIX) - 1) == 0) { stValue = NO_PREF_VALUE; return true; } return false; } bool XAP_Prefs::getPrefsValueBool(const gchar * szKey, bool * pbValue, bool bAllowBuiltin) const { // a convenient routine to get a name/value pair from the current scheme UT_return_val_if_fail(m_currentScheme,false); if (m_currentScheme->getValueBool(szKey,pbValue)) return true; if (bAllowBuiltin && m_builtinScheme->getValueBool(szKey,pbValue)) return true; // It is legal for there to be arbitrary preference tags that start with // "Debug", and Abi apps won't choke. The idea is that developers can use // these to selectively trigger development-time behaviors. if (g_ascii_strncasecmp(szKey, DEBUG_PREFIX, sizeof(DEBUG_PREFIX) - 1) == 0) { *pbValue = false; return true; } return false; } bool XAP_Prefs::getPrefsValueInt(const gchar * szKey, int& nValue, bool bAllowBuiltin) const { // a convenient routine to get a name/value pair from the current scheme UT_return_val_if_fail(m_currentScheme,false); if (m_currentScheme->getValueInt(szKey,nValue)) return true; if (bAllowBuiltin && m_builtinScheme->getValueInt(szKey,nValue)) return true; // It is legal for there to be arbitrary preference tags that start with // "Debug", and Abi apps won't choke. The idea is that developers can use // these to selectively trigger development-time behaviors. if (g_ascii_strncasecmp(szKey, DEBUG_PREFIX, sizeof(DEBUG_PREFIX) - 1) == 0) { nValue = -1; return true; } return false; } static int n_compare (const char *name, const xmlToIdMapping *id) { return strcmp (name, id->m_name); } /*****************************************************************/ void XAP_Prefs::startElement(const gchar *name, const gchar **atts) { if (m_bLoadSystemDefaultFile) /* redirection - used to happen earlier */ { _startElement_SystemDefaultFile (name, atts); return; } UT_DEBUGMSG(("Looking for %s \n",name)); XAP_PrefsScheme * pNewScheme = NULL; // must be freed if (!m_parserState.m_parserStatus) // eat if already had an error return; xmlToIdMapping * id = NULL; id = static_cast(bsearch (static_cast(name), static_cast(s_Tokens), sizeof(s_Tokens)/sizeof(xmlToIdMapping), sizeof (xmlToIdMapping), (int (*)(const void*, const void*)) n_compare)); if (!id) { UT_DEBUGMSG(("Didin't find it! Abort! \n")); UT_ASSERT_HARMLESS(0); return; } switch (id->m_type) { case TT_LOG: // ignore break; case TT_FONTS: { m_parserState.m_bFoundFonts = true; const gchar ** a = atts; while (a && *a) { if (!strcmp (a[0], "include")) { if (!strcmp (a[1], "1") || !strcmp (a[1], "true")) m_fonts.setIncludeFlag(true); else m_fonts.setIncludeFlag(false); } else { UT_DEBUGMSG(("Preferences: unknown attribute [%s] " "for \n", a[0])); } a += 2; } } break; case TT_FACE: { if (!m_parserState.m_bFoundFonts) { UT_ASSERT_HARMLESS (UT_SHOULD_NOT_HAPPEN); break; } const gchar ** a = atts; const gchar * pName = NULL; while (a && *a) { if (!strcmp (a[0], "name")) { pName = a[1]; } else { UT_DEBUGMSG(("Preferences: unknown attribute [%s] " "for \n", a[0])); } a += 2; } if (pName) m_fonts.addFont (pName); } break; case TT_ABIPREFERENCES: { m_parserState.m_bFoundAbiPreferences = true; // we expect something of the form: // ... const gchar ** a = atts; while (a && *a) { UT_ASSERT(a[1] && *a[1]); // require a value for each attribute keyword if (strcmp(static_cast(a[0]), "app") == 0) { // TODO the following test will fail if you are running // TODO both an AbiWord (release) build and an AbiWord // TODO Personal (development/personal) build. That is, // TODO you'll lose your MRU list if you alternate between // TODO the two types of executables. const char * szThisApp = XAP_App::getApp()->getApplicationName(); UT_DEBUGMSG(("Found preferences for application [%s] (this is [%s]).\n", a[1],szThisApp)); if (strcmp(static_cast(a[1]),szThisApp) != 0) { UT_DEBUGMSG(("Preferences file does not match this application.\n")); goto InvalidFileError; } } else if (strcmp(static_cast(a[0]), "ver") == 0) { // TODO test version number } a += 2; } break; } case TT_SELECT: { m_parserState.m_bFoundSelect = true; // we expect something of the form: //