1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
7 #include "nsColorPicker.h"
11 #include "mozilla/ArrayUtils.h"
12 #include "mozilla/AutoRestore.h"
13 #include "nsIWidget.h"
15 #include "WidgetUtils.h"
16 #include "nsPIDOMWindow.h"
18 using namespace mozilla::widget
;
21 // Manages NS_NATIVE_TMP_WINDOW child windows. NS_NATIVE_TMP_WINDOWs are
22 // temporary child windows of mParentWidget created to address RTL issues
23 // in picker dialogs. We are responsible for destroying these.
24 class AutoDestroyTmpWindow
{
26 explicit AutoDestroyTmpWindow(HWND aTmpWnd
) : mWnd(aTmpWnd
) {}
28 ~AutoDestroyTmpWindow() {
29 if (mWnd
) DestroyWindow(mWnd
);
32 inline HWND
get() const { return mWnd
; }
38 static DWORD
ColorStringToRGB(const nsAString
& aColor
) {
41 for (uint32_t i
= 1; i
< aColor
.Length(); ++i
) {
44 char16_t c
= aColor
[i
];
45 if (c
>= '0' && c
<= '9') {
47 } else if (c
>= 'a' && c
<= 'f') {
48 result
+= 10 + (c
- 'a');
50 result
+= 10 + (c
- 'A');
54 DWORD r
= result
& 0x00FF0000;
55 DWORD g
= result
& 0x0000FF00;
56 DWORD b
= result
& 0x000000FF;
66 static nsString
ToHexString(BYTE n
) {
71 result
.AppendInt(n
, 16);
75 static void BGRIntToRGBString(DWORD color
, nsAString
& aResult
) {
76 BYTE r
= GetRValue(color
);
77 BYTE g
= GetGValue(color
);
78 BYTE b
= GetBValue(color
);
81 aResult
.Append(ToHexString(r
));
82 aResult
.Append(ToHexString(g
));
83 aResult
.Append(ToHexString(b
));
87 static AsyncColorChooser
* gColorChooser
;
89 AsyncColorChooser::AsyncColorChooser(COLORREF aInitialColor
,
90 const nsTArray
<nsString
>& aDefaultColors
,
91 nsIWidget
* aParentWidget
,
92 nsIColorPickerShownCallback
* aCallback
)
93 : mozilla::Runnable("AsyncColorChooser"),
94 mInitialColor(aInitialColor
),
95 mDefaultColors(aDefaultColors
.Clone()),
96 mColor(aInitialColor
),
97 mParentWidget(aParentWidget
),
98 mCallback(aCallback
) {}
101 AsyncColorChooser::Run() {
102 MOZ_ASSERT(NS_IsMainThread(),
103 "Color pickers can only be opened from main thread currently");
105 // Allow only one color picker to be opened at a time, to workaround bug
107 if (!gColorChooser
) {
108 mozilla::AutoRestore
<AsyncColorChooser
*> restoreColorChooser(gColorChooser
);
109 gColorChooser
= this;
111 AutoDestroyTmpWindow
adtw(
112 (HWND
)(mParentWidget
.get()
113 ? mParentWidget
->GetNativeData(NS_NATIVE_TMP_WINDOW
)
116 COLORREF customColors
[16];
117 for (size_t i
= 0; i
< mozilla::ArrayLength(customColors
); i
++) {
118 if (i
< mDefaultColors
.Length()) {
119 customColors
[i
] = ColorStringToRGB(mDefaultColors
[i
]);
121 customColors
[i
] = 0x00FFFFFF;
126 options
.lStructSize
= sizeof(options
);
127 options
.hwndOwner
= adtw
.get();
128 options
.Flags
= CC_RGBINIT
| CC_FULLOPEN
| CC_ENABLEHOOK
;
129 options
.rgbResult
= mInitialColor
;
130 options
.lpCustColors
= customColors
;
131 options
.lpfnHook
= HookProc
;
133 mColor
= ChooseColor(&options
) ? options
.rgbResult
: mInitialColor
;
136 "Currently, it's not possible to open more than one color "
138 mColor
= mInitialColor
;
142 nsAutoString colorStr
;
143 BGRIntToRGBString(mColor
, colorStr
);
144 mCallback
->Done(colorStr
);
150 void AsyncColorChooser::Update(COLORREF aColor
) {
151 if (mColor
!= aColor
) {
154 nsAutoString colorStr
;
155 BGRIntToRGBString(mColor
, colorStr
);
156 mCallback
->Update(colorStr
);
160 /* static */ UINT_PTR CALLBACK
AsyncColorChooser::HookProc(HWND aDialog
,
164 if (!gColorChooser
) {
168 if (aMsg
== WM_INITDIALOG
) {
169 // "The default dialog box procedure processes the WM_INITDIALOG message
170 // before passing it to the hook procedure.
171 // For all other messages, the hook procedure receives the message first."
172 // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/nc-commdlg-lpcchookproc
173 // "The dialog box procedure should return TRUE to direct the system to
174 // set the keyboard focus to the control specified by wParam."
175 // https://docs.microsoft.com/en-us/windows/win32/dlgbox/wm-initdialog
179 if (aMsg
== WM_CTLCOLORSTATIC
) {
180 // The color picker does not expose a proper way to retrieve the current
181 // color, so we need to obtain it from the static control displaying the
182 // current color instead.
183 const int kCurrentColorBoxID
= 709;
184 if ((HWND
)aLParam
== GetDlgItem(aDialog
, kCurrentColorBoxID
)) {
185 gColorChooser
->Update(GetPixel((HDC
)aWParam
, 0, 0));
189 // Let the default dialog box procedure processes the message.
193 ///////////////////////////////////////////////////////////////////////////////
196 nsColorPicker::nsColorPicker() {}
198 nsColorPicker::~nsColorPicker() {}
200 NS_IMPL_ISUPPORTS(nsColorPicker
, nsIColorPicker
)
203 nsColorPicker::Init(mozIDOMWindowProxy
* parent
, const nsAString
& title
,
204 const nsAString
& aInitialColor
,
205 const nsTArray
<nsString
>& aDefaultColors
) {
207 "Null parent passed to colorpicker, no color picker for you!");
209 WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(parent
));
210 mInitialColor
= ColorStringToRGB(aInitialColor
);
211 mDefaultColors
.Assign(aDefaultColors
);
216 nsColorPicker::Open(nsIColorPickerShownCallback
* aCallback
) {
217 NS_ENSURE_ARG(aCallback
);
218 nsCOMPtr
<nsIRunnable
> event
= new AsyncColorChooser(
219 mInitialColor
, mDefaultColors
, mParentWidget
, aCallback
);
220 return NS_DispatchToMainThread(event
);