/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/Util.h" #include "nscore.h" #include "nsXPLookAndFeel.h" #include "nsLookAndFeel.h" #include "nsCRT.h" #include "nsFont.h" #include "mozilla/Preferences.h" #include "gfxPlatform.h" #include "qcms.h" #ifdef DEBUG #include "nsSize.h" #endif using namespace mozilla; nsLookAndFeelIntPref nsXPLookAndFeel::sIntPrefs[] = { { "ui.caretBlinkTime", eIntID_CaretBlinkTime, false, 0 }, { "ui.caretWidth", eIntID_CaretWidth, false, 0 }, { "ui.caretVisibleWithSelection", eIntID_ShowCaretDuringSelection, false, 0 }, { "ui.submenuDelay", eIntID_SubmenuDelay, false, 0 }, { "ui.dragThresholdX", eIntID_DragThresholdX, false, 0 }, { "ui.dragThresholdY", eIntID_DragThresholdY, false, 0 }, { "ui.useAccessibilityTheme", eIntID_UseAccessibilityTheme, false, 0 }, { "ui.menusCanOverlapOSBar", eIntID_MenusCanOverlapOSBar, false, 0 }, { "ui.useOverlayScrollbars", eIntID_UseOverlayScrollbars, false, 0 }, { "ui.scrollbarDisplayOnMouseMove", eIntID_ScrollbarDisplayOnMouseMove, false, 0 }, { "ui.scrollbarFadeBeginDelay", eIntID_ScrollbarFadeBeginDelay, false, 0 }, { "ui.scrollbarFadeDuration", eIntID_ScrollbarFadeDuration, false, 0 }, { "ui.showHideScrollbars", eIntID_ShowHideScrollbars, false, 0 }, { "ui.skipNavigatingDisabledMenuItem", eIntID_SkipNavigatingDisabledMenuItem, false, 0 }, { "ui.treeOpenDelay", eIntID_TreeOpenDelay, false, 0 }, { "ui.treeCloseDelay", eIntID_TreeCloseDelay, false, 0 }, { "ui.treeLazyScrollDelay", eIntID_TreeLazyScrollDelay, false, 0 }, { "ui.treeScrollDelay", eIntID_TreeScrollDelay, false, 0 }, { "ui.treeScrollLinesMax", eIntID_TreeScrollLinesMax, false, 0 }, { "accessibility.tabfocus", eIntID_TabFocusModel, false, 0 }, { "ui.alertNotificationOrigin", eIntID_AlertNotificationOrigin, false, 0 }, { "ui.scrollToClick", eIntID_ScrollToClick, false, 0 }, { "ui.IMERawInputUnderlineStyle", eIntID_IMERawInputUnderlineStyle, false, 0 }, { "ui.IMESelectedRawTextUnderlineStyle", eIntID_IMESelectedRawTextUnderlineStyle, false, 0 }, { "ui.IMEConvertedTextUnderlineStyle", eIntID_IMEConvertedTextUnderlineStyle, false, 0 }, { "ui.IMESelectedConvertedTextUnderlineStyle", eIntID_IMESelectedConvertedTextUnderline, false, 0 }, { "ui.SpellCheckerUnderlineStyle", eIntID_SpellCheckerUnderlineStyle, false, 0 }, { "ui.scrollbarButtonAutoRepeatBehavior", eIntID_ScrollbarButtonAutoRepeatBehavior, false, 0 }, { "ui.tooltipDelay", eIntID_TooltipDelay, false, 0 }, }; nsLookAndFeelFloatPref nsXPLookAndFeel::sFloatPrefs[] = { { "ui.IMEUnderlineRelativeSize", eFloatID_IMEUnderlineRelativeSize, false, 0 }, { "ui.SpellCheckerUnderlineRelativeSize", eFloatID_SpellCheckerUnderlineRelativeSize, false, 0 }, { "ui.caretAspectRatio", eFloatID_CaretAspectRatio, false, 0 }, }; // This array MUST be kept in the same order as the color list in LookAndFeel.h. /* XXX If you add any strings longer than * "ui.IMESelectedConvertedTextBackground" * to the following array then you MUST update the * sizes of the sColorPrefs array in nsXPLookAndFeel.h */ const char nsXPLookAndFeel::sColorPrefs[][38] = { "ui.windowBackground", "ui.windowForeground", "ui.widgetBackground", "ui.widgetForeground", "ui.widgetSelectBackground", "ui.widgetSelectForeground", "ui.widget3DHighlight", "ui.widget3DShadow", "ui.textBackground", "ui.textForeground", "ui.textSelectBackground", "ui.textSelectForeground", "ui.textSelectBackgroundDisabled", "ui.textSelectBackgroundAttention", "ui.textHighlightBackground", "ui.textHighlightForeground", "ui.IMERawInputBackground", "ui.IMERawInputForeground", "ui.IMERawInputUnderline", "ui.IMESelectedRawTextBackground", "ui.IMESelectedRawTextForeground", "ui.IMESelectedRawTextUnderline", "ui.IMEConvertedTextBackground", "ui.IMEConvertedTextForeground", "ui.IMEConvertedTextUnderline", "ui.IMESelectedConvertedTextBackground", "ui.IMESelectedConvertedTextForeground", "ui.IMESelectedConvertedTextUnderline", "ui.SpellCheckerUnderline", "ui.activeborder", "ui.activecaption", "ui.appworkspace", "ui.background", "ui.buttonface", "ui.buttonhighlight", "ui.buttonshadow", "ui.buttontext", "ui.captiontext", "ui.graytext", "ui.highlight", "ui.highlighttext", "ui.inactiveborder", "ui.inactivecaption", "ui.inactivecaptiontext", "ui.infobackground", "ui.infotext", "ui.menu", "ui.menutext", "ui.scrollbar", "ui.threeddarkshadow", "ui.threedface", "ui.threedhighlight", "ui.threedlightshadow", "ui.threedshadow", "ui.window", "ui.windowframe", "ui.windowtext", "ui.-moz-buttondefault", "ui.-moz-field", "ui.-moz-fieldtext", "ui.-moz-dialog", "ui.-moz-dialogtext", "ui.-moz-dragtargetzone", "ui.-moz-cellhighlight", "ui.-moz_cellhighlighttext", "ui.-moz-html-cellhighlight", "ui.-moz-html-cellhighlighttext", "ui.-moz-buttonhoverface", "ui.-moz_buttonhovertext", "ui.-moz_menuhover", "ui.-moz_menuhovertext", "ui.-moz_menubartext", "ui.-moz_menubarhovertext", "ui.-moz_eventreerow", "ui.-moz_oddtreerow", "ui.-moz_mac_chrome_active", "ui.-moz_mac_chrome_inactive", "ui.-moz-mac-focusring", "ui.-moz-mac-menuselect", "ui.-moz-mac-menushadow", "ui.-moz-mac-menutextdisable", "ui.-moz-mac-menutextselect", "ui.-moz_mac_disabledtoolbartext", "ui.-moz-mac-alternateprimaryhighlight", "ui.-moz-mac-secondaryhighlight", "ui.-moz-win-mediatext", "ui.-moz-win-communicationstext", "ui.-moz-nativehyperlinktext", "ui.-moz-comboboxtext", "ui.-moz-combobox" }; int32_t nsXPLookAndFeel::sCachedColors[LookAndFeel::eColorID_LAST_COLOR] = {0}; int32_t nsXPLookAndFeel::sCachedColorBits[COLOR_CACHE_SIZE] = {0}; bool nsXPLookAndFeel::sInitialized = false; bool nsXPLookAndFeel::sUseNativeColors = true; nsLookAndFeel* nsXPLookAndFeel::sInstance = nullptr; bool nsXPLookAndFeel::sShutdown = false; // static nsLookAndFeel* nsXPLookAndFeel::GetInstance() { if (sInstance) { return sInstance; } NS_ENSURE_TRUE(!sShutdown, nullptr); sInstance = new nsLookAndFeel(); return sInstance; } // static void nsXPLookAndFeel::Shutdown() { if (sShutdown) { return; } sShutdown = true; delete sInstance; sInstance = nullptr; } nsXPLookAndFeel::nsXPLookAndFeel() : LookAndFeel() { } // static void nsXPLookAndFeel::IntPrefChanged(nsLookAndFeelIntPref *data) { if (!data) { return; } int32_t intpref; nsresult rv = Preferences::GetInt(data->name, &intpref); if (NS_FAILED(rv)) { return; } data->intVar = intpref; data->isSet = true; #ifdef DEBUG_akkana printf("====== Changed int pref %s to %d\n", data->name, data->intVar); #endif } // static void nsXPLookAndFeel::FloatPrefChanged(nsLookAndFeelFloatPref *data) { if (!data) { return; } int32_t intpref; nsresult rv = Preferences::GetInt(data->name, &intpref); if (NS_FAILED(rv)) { return; } data->floatVar = (float)intpref / 100.0f; data->isSet = true; #ifdef DEBUG_akkana printf("====== Changed float pref %s to %f\n", data->name, data->floatVar); #endif } // static void nsXPLookAndFeel::ColorPrefChanged (unsigned int index, const char *prefName) { nsAutoString colorStr; nsresult rv = Preferences::GetString(prefName, &colorStr); if (NS_FAILED(rv)) { return; } if (!colorStr.IsEmpty()) { nscolor thecolor; if (colorStr[0] == PRUnichar('#')) { if (NS_HexToRGB(nsDependentString(colorStr, 1), &thecolor)) { int32_t id = NS_PTR_TO_INT32(index); CACHE_COLOR(id, thecolor); } } else if (NS_ColorNameToRGB(colorStr, &thecolor)) { int32_t id = NS_PTR_TO_INT32(index); CACHE_COLOR(id, thecolor); #ifdef DEBUG_akkana printf("====== Changed color pref %s to 0x%lx\n", prefName, thecolor); #endif } } else { // Reset to the default color, by clearing the cache // to force lookup when the color is next used int32_t id = NS_PTR_TO_INT32(index); CLEAR_COLOR_CACHE(id); } } void nsXPLookAndFeel::InitFromPref(nsLookAndFeelIntPref* aPref) { int32_t intpref; nsresult rv = Preferences::GetInt(aPref->name, &intpref); if (NS_SUCCEEDED(rv)) { aPref->isSet = true; aPref->intVar = intpref; } } void nsXPLookAndFeel::InitFromPref(nsLookAndFeelFloatPref* aPref) { int32_t intpref; nsresult rv = Preferences::GetInt(aPref->name, &intpref); if (NS_SUCCEEDED(rv)) { aPref->isSet = true; aPref->floatVar = (float)intpref / 100.0f; } } void nsXPLookAndFeel::InitColorFromPref(int32_t i) { nsAutoString colorStr; nsresult rv = Preferences::GetString(sColorPrefs[i], &colorStr); if (NS_FAILED(rv) || colorStr.IsEmpty()) { return; } nscolor thecolor; if (colorStr[0] == PRUnichar('#')) { nsAutoString hexString; colorStr.Right(hexString, colorStr.Length() - 1); if (NS_HexToRGB(hexString, &thecolor)) { CACHE_COLOR(i, thecolor); } } else if (NS_ColorNameToRGB(colorStr, &thecolor)) { CACHE_COLOR(i, thecolor); } } // static int nsXPLookAndFeel::OnPrefChanged(const char* aPref, void* aClosure) { // looping in the same order as in ::Init nsDependentCString prefName(aPref); unsigned int i; for (i = 0; i < ArrayLength(sIntPrefs); ++i) { if (prefName.Equals(sIntPrefs[i].name)) { IntPrefChanged(&sIntPrefs[i]); return 0; } } for (i = 0; i < ArrayLength(sFloatPrefs); ++i) { if (prefName.Equals(sFloatPrefs[i].name)) { FloatPrefChanged(&sFloatPrefs[i]); return 0; } } for (i = 0; i < ArrayLength(sColorPrefs); ++i) { if (prefName.Equals(sColorPrefs[i])) { ColorPrefChanged(i, sColorPrefs[i]); return 0; } } return 0; } // // Read values from the user's preferences. // This is done once at startup, but since the user's preferences // haven't actually been read yet at that time, we also have to // set a callback to inform us of changes to each pref. // void nsXPLookAndFeel::Init() { // Say we're already initialized, and take the chance that it might fail; // protects against some other process writing to our static variables. sInitialized = true; // XXX If we could reorganize the pref names, we should separate the branch // for each types. Then, we could reduce the unnecessary loop from // nsXPLookAndFeel::OnPrefChanged(). Preferences::RegisterCallback(OnPrefChanged, "ui."); Preferences::RegisterCallback(OnPrefChanged, "accessibility.tabfocus"); unsigned int i; for (i = 0; i < ArrayLength(sIntPrefs); ++i) { InitFromPref(&sIntPrefs[i]); } for (i = 0; i < ArrayLength(sFloatPrefs); ++i) { InitFromPref(&sFloatPrefs[i]); } for (i = 0; i < ArrayLength(sColorPrefs); ++i) { InitColorFromPref(i); } bool val; if (NS_SUCCEEDED(Preferences::GetBool("ui.use_native_colors", &val))) { sUseNativeColors = val; } } nsXPLookAndFeel::~nsXPLookAndFeel() { NS_ASSERTION(sInstance == this, "This destroying instance isn't the singleton instance"); sInstance = nullptr; } bool nsXPLookAndFeel::IsSpecialColor(ColorID aID, nscolor &aColor) { switch (aID) { case eColorID_TextSelectForeground: return (aColor == NS_DONT_CHANGE_COLOR); case eColorID_IMESelectedRawTextBackground: case eColorID_IMESelectedConvertedTextBackground: case eColorID_IMERawInputBackground: case eColorID_IMEConvertedTextBackground: case eColorID_IMESelectedRawTextForeground: case eColorID_IMESelectedConvertedTextForeground: case eColorID_IMERawInputForeground: case eColorID_IMEConvertedTextForeground: case eColorID_IMERawInputUnderline: case eColorID_IMEConvertedTextUnderline: case eColorID_IMESelectedRawTextUnderline: case eColorID_IMESelectedConvertedTextUnderline: case eColorID_SpellCheckerUnderline: return NS_IS_SELECTION_SPECIAL_COLOR(aColor); default: /* * In GetColor(), every color that is not a special color is color * corrected. Use false to make other colors color corrected. */ return false; } return false; } // // All these routines will return NS_OK if they have a value, // in which case the nsLookAndFeel should use that value; // otherwise we'll return NS_ERROR_NOT_AVAILABLE, in which case, the // platform-specific nsLookAndFeel should use its own values instead. // nsresult nsXPLookAndFeel::GetColorImpl(ColorID aID, nscolor &aResult) { if (!sInitialized) Init(); // define DEBUG_SYSTEM_COLOR_USE if you want to debug system color // use in a skin that uses them. When set, it will make all system // color pairs that are appropriate for foreground/background // pairing the same. This means if the skin is using system colors // correctly you will not be able to see *any* text. #undef DEBUG_SYSTEM_COLOR_USE #ifdef DEBUG_SYSTEM_COLOR_USE { nsresult rv = NS_OK; switch (aID) { // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors case eColorID_activecaption: // active window caption background case eColorID_captiontext: // text in active window caption aResult = NS_RGB(0xff, 0x00, 0x00); break; case eColorID_highlight: // background of selected item case eColorID_highlighttext: // text of selected item aResult = NS_RGB(0xff, 0xff, 0x00); break; case eColorID_inactivecaption: // inactive window caption case eColorID_inactivecaptiontext: // text in inactive window caption aResult = NS_RGB(0x66, 0x66, 0x00); break; case eColorID_infobackground: // tooltip background color case eColorID_infotext: // tooltip text color aResult = NS_RGB(0x00, 0xff, 0x00); break; case eColorID_menu: // menu background case eColorID_menutext: // menu text aResult = NS_RGB(0x00, 0xff, 0xff); break; case eColorID_threedface: case eColorID_buttonface: // 3-D face color case eColorID_buttontext: // text on push buttons aResult = NS_RGB(0x00, 0x66, 0x66); break; case eColorID_window: case eColorID_windowtext: aResult = NS_RGB(0x00, 0x00, 0xff); break; // from the CSS3 working draft (not yet finalized) // http://www.w3.org/tr/2000/wd-css3-userint-20000216.html#color case eColorID__moz_field: case eColorID__moz_fieldtext: aResult = NS_RGB(0xff, 0x00, 0xff); break; case eColorID__moz_dialog: case eColorID__moz_dialogtext: aResult = NS_RGB(0x66, 0x00, 0x66); break; default: rv = NS_ERROR_NOT_AVAILABLE; } if (NS_SUCCEEDED(rv)) return rv; } #endif // DEBUG_SYSTEM_COLOR_USE if (IS_COLOR_CACHED(aID)) { aResult = sCachedColors[aID]; return NS_OK; } // There are no system color settings for these, so set them manually if (aID == eColorID_TextSelectBackgroundDisabled) { // This is used to gray out the selection when it's not focused // Used with nsISelectionController::SELECTION_DISABLED aResult = NS_RGB(0xb0, 0xb0, 0xb0); return NS_OK; } if (aID == eColorID_TextSelectBackgroundAttention) { // This makes the selection stand out when typeaheadfind is on // Used with nsISelectionController::SELECTION_ATTENTION aResult = NS_RGB(0x38, 0xd8, 0x78); return NS_OK; } if (aID == eColorID_TextHighlightBackground) { // This makes the matched text stand out when findbar highlighting is on // Used with nsISelectionController::SELECTION_FIND aResult = NS_RGB(0xef, 0x0f, 0xff); return NS_OK; } if (aID == eColorID_TextHighlightForeground) { // The foreground color for the matched text in findbar highlighting // Used with nsISelectionController::SELECTION_FIND aResult = NS_RGB(0xff, 0xff, 0xff); return NS_OK; } if (sUseNativeColors && NS_SUCCEEDED(NativeGetColor(aID, aResult))) { if ((gfxPlatform::GetCMSMode() == eCMSMode_All) && !IsSpecialColor(aID, aResult)) { qcms_transform *transform = gfxPlatform::GetCMSInverseRGBTransform(); if (transform) { uint8_t color[3]; color[0] = NS_GET_R(aResult); color[1] = NS_GET_G(aResult); color[2] = NS_GET_B(aResult); qcms_transform_data(transform, color, color, 1); aResult = NS_RGB(color[0], color[1], color[2]); } } CACHE_COLOR(aID, aResult); return NS_OK; } return NS_ERROR_NOT_AVAILABLE; } nsresult nsXPLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult) { if (!sInitialized) Init(); // Set the default values for these prefs. but allow different platforms // to override them in their nsLookAndFeel if desired. switch (aID) { case eIntID_ScrollButtonLeftMouseButtonAction: aResult = 0; return NS_OK; case eIntID_ScrollButtonMiddleMouseButtonAction: aResult = 3; return NS_OK; case eIntID_ScrollButtonRightMouseButtonAction: aResult = 3; return NS_OK; default: /* * The metrics above are hardcoded platform defaults. All the other * metrics are stored in sIntPrefs and can be changed at runtime. */ break; } for (unsigned int i = 0; i < ArrayLength(sIntPrefs); ++i) { if (sIntPrefs[i].isSet && (sIntPrefs[i].id == aID)) { aResult = sIntPrefs[i].intVar; return NS_OK; } } return NS_ERROR_NOT_AVAILABLE; } nsresult nsXPLookAndFeel::GetFloatImpl(FloatID aID, float &aResult) { if (!sInitialized) Init(); for (unsigned int i = 0; i < ArrayLength(sFloatPrefs); ++i) { if (sFloatPrefs[i].isSet && sFloatPrefs[i].id == aID) { aResult = sFloatPrefs[i].floatVar; return NS_OK; } } return NS_ERROR_NOT_AVAILABLE; } void nsXPLookAndFeel::RefreshImpl() { // Wipe out our color cache. uint32_t i; for (i = 0; i < eColorID_LAST_COLOR; i++) sCachedColors[i] = 0; for (i = 0; i < COLOR_CACHE_SIZE; i++) sCachedColorBits[i] = 0; } namespace mozilla { // static nsresult LookAndFeel::GetColor(ColorID aID, nscolor* aResult) { return nsLookAndFeel::GetInstance()->GetColorImpl(aID, *aResult); } // static nsresult LookAndFeel::GetInt(IntID aID, int32_t* aResult) { return nsLookAndFeel::GetInstance()->GetIntImpl(aID, *aResult); } // static nsresult LookAndFeel::GetFloat(FloatID aID, float* aResult) { return nsLookAndFeel::GetInstance()->GetFloatImpl(aID, *aResult); } // static bool LookAndFeel::GetFont(FontID aID, nsString& aName, gfxFontStyle& aStyle, float aDevPixPerCSSPixel) { return nsLookAndFeel::GetInstance()->GetFontImpl(aID, aName, aStyle, aDevPixPerCSSPixel); } // static PRUnichar LookAndFeel::GetPasswordCharacter() { return nsLookAndFeel::GetInstance()->GetPasswordCharacterImpl(); } // static bool LookAndFeel::GetEchoPassword() { return nsLookAndFeel::GetInstance()->GetEchoPasswordImpl(); } // static uint32_t LookAndFeel::GetPasswordMaskDelay() { return nsLookAndFeel::GetInstance()->GetPasswordMaskDelayImpl(); } // static void LookAndFeel::Refresh() { nsLookAndFeel::GetInstance()->RefreshImpl(); } } // namespace mozilla