1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  *
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "nsColorPicker.h"
8 
9 #include <shlwapi.h>
10 
11 #include "mozilla/AutoRestore.h"
12 #include "nsIWidget.h"
13 #include "nsString.h"
14 #include "WidgetUtils.h"
15 #include "nsPIDOMWindow.h"
16 
17 using namespace mozilla::widget;
18 
19 namespace {
20 // Manages NS_NATIVE_TMP_WINDOW child windows. NS_NATIVE_TMP_WINDOWs are
21 // temporary child windows of mParentWidget created to address RTL issues
22 // in picker dialogs. We are responsible for destroying these.
23 class AutoDestroyTmpWindow {
24  public:
AutoDestroyTmpWindow(HWND aTmpWnd)25   explicit AutoDestroyTmpWindow(HWND aTmpWnd) : mWnd(aTmpWnd) {}
26 
~AutoDestroyTmpWindow()27   ~AutoDestroyTmpWindow() {
28     if (mWnd) DestroyWindow(mWnd);
29   }
30 
get() const31   inline HWND get() const { return mWnd; }
32 
33  private:
34   HWND mWnd;
35 };
36 
ColorStringToRGB(const nsAString & aColor)37 static DWORD ColorStringToRGB(const nsAString& aColor) {
38   DWORD result = 0;
39 
40   for (uint32_t i = 1; i < aColor.Length(); ++i) {
41     result *= 16;
42 
43     char16_t c = aColor[i];
44     if (c >= '0' && c <= '9') {
45       result += c - '0';
46     } else if (c >= 'a' && c <= 'f') {
47       result += 10 + (c - 'a');
48     } else {
49       result += 10 + (c - 'A');
50     }
51   }
52 
53   DWORD r = result & 0x00FF0000;
54   DWORD g = result & 0x0000FF00;
55   DWORD b = result & 0x000000FF;
56 
57   r = r >> 16;
58   b = b << 16;
59 
60   result = r | g | b;
61 
62   return result;
63 }
64 
ToHexString(BYTE n)65 static nsString ToHexString(BYTE n) {
66   nsString result;
67   if (n <= 0x0F) {
68     result.Append('0');
69   }
70   result.AppendInt(n, 16);
71   return result;
72 }
73 
BGRIntToRGBString(DWORD color,nsAString & aResult)74 static void BGRIntToRGBString(DWORD color, nsAString& aResult) {
75   BYTE r = GetRValue(color);
76   BYTE g = GetGValue(color);
77   BYTE b = GetBValue(color);
78 
79   aResult.Assign('#');
80   aResult.Append(ToHexString(r));
81   aResult.Append(ToHexString(g));
82   aResult.Append(ToHexString(b));
83 }
84 }  // namespace
85 
86 static AsyncColorChooser* gColorChooser;
87 
AsyncColorChooser(COLORREF aInitialColor,nsIWidget * aParentWidget,nsIColorPickerShownCallback * aCallback)88 AsyncColorChooser::AsyncColorChooser(COLORREF aInitialColor,
89                                      nsIWidget* aParentWidget,
90                                      nsIColorPickerShownCallback* aCallback)
91     : mozilla::Runnable("AsyncColorChooser"),
92       mInitialColor(aInitialColor),
93       mColor(aInitialColor),
94       mParentWidget(aParentWidget),
95       mCallback(aCallback) {}
96 
97 NS_IMETHODIMP
Run()98 AsyncColorChooser::Run() {
99   static COLORREF sCustomColors[16] = {0};
100 
101   MOZ_ASSERT(NS_IsMainThread(),
102              "Color pickers can only be opened from main thread currently");
103 
104   // Allow only one color picker to be opened at a time, to workaround bug
105   // 944737
106   if (!gColorChooser) {
107     mozilla::AutoRestore<AsyncColorChooser*> restoreColorChooser(gColorChooser);
108     gColorChooser = this;
109 
110     AutoDestroyTmpWindow adtw((HWND)(
111         mParentWidget.get() ? mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW)
112                             : nullptr));
113 
114     CHOOSECOLOR options;
115     options.lStructSize = sizeof(options);
116     options.hwndOwner = adtw.get();
117     options.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ENABLEHOOK;
118     options.rgbResult = mInitialColor;
119     options.lpCustColors = sCustomColors;
120     options.lpfnHook = HookProc;
121 
122     mColor = ChooseColor(&options) ? options.rgbResult : mInitialColor;
123   } else {
124     NS_WARNING(
125         "Currently, it's not possible to open more than one color "
126         "picker at a time");
127     mColor = mInitialColor;
128   }
129 
130   if (mCallback) {
131     nsAutoString colorStr;
132     BGRIntToRGBString(mColor, colorStr);
133     mCallback->Done(colorStr);
134   }
135 
136   return NS_OK;
137 }
138 
Update(COLORREF aColor)139 void AsyncColorChooser::Update(COLORREF aColor) {
140   if (mColor != aColor) {
141     mColor = aColor;
142 
143     nsAutoString colorStr;
144     BGRIntToRGBString(mColor, colorStr);
145     mCallback->Update(colorStr);
146   }
147 }
148 
HookProc(HWND aDialog,UINT aMsg,WPARAM aWParam,LPARAM aLParam)149 /* static */ UINT_PTR CALLBACK AsyncColorChooser::HookProc(HWND aDialog,
150                                                            UINT aMsg,
151                                                            WPARAM aWParam,
152                                                            LPARAM aLParam) {
153   if (!gColorChooser) {
154     return 0;
155   }
156 
157   if (aMsg == WM_CTLCOLORSTATIC) {
158     // The color picker does not expose a proper way to retrieve the current
159     // color, so we need to obtain it from the static control displaying the
160     // current color instead.
161     const int kCurrentColorBoxID = 709;
162     if ((HWND)aLParam == GetDlgItem(aDialog, kCurrentColorBoxID)) {
163       gColorChooser->Update(GetPixel((HDC)aWParam, 0, 0));
164     }
165   }
166 
167   return 0;
168 }
169 
170 ///////////////////////////////////////////////////////////////////////////////
171 // nsIColorPicker
172 
nsColorPicker()173 nsColorPicker::nsColorPicker() {}
174 
~nsColorPicker()175 nsColorPicker::~nsColorPicker() {}
176 
NS_IMPL_ISUPPORTS(nsColorPicker,nsIColorPicker)177 NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
178 
179 NS_IMETHODIMP
180 nsColorPicker::Init(mozIDOMWindowProxy* parent, const nsAString& title,
181                     const nsAString& aInitialColor) {
182   NS_PRECONDITION(
183       parent, "Null parent passed to colorpicker, no color picker for you!");
184   mParentWidget =
185       WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(parent));
186   mInitialColor = ColorStringToRGB(aInitialColor);
187   return NS_OK;
188 }
189 
190 NS_IMETHODIMP
Open(nsIColorPickerShownCallback * aCallback)191 nsColorPicker::Open(nsIColorPickerShownCallback* aCallback) {
192   NS_ENSURE_ARG(aCallback);
193   nsCOMPtr<nsIRunnable> event =
194       new AsyncColorChooser(mInitialColor, mParentWidget, aCallback);
195   return NS_DispatchToMainThread(event);
196 }
197