1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include <gtk/gtk.h>
7 
8 #include "nsColor.h"
9 #include "nsColorPicker.h"
10 #include "nsGtkUtils.h"
11 #include "nsIWidget.h"
12 #include "WidgetUtils.h"
13 #include "nsPIDOMWindow.h"
14 
NS_IMPL_ISUPPORTS(nsColorPicker,nsIColorPicker)15 NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
16 
17 #if defined(ACTIVATE_GTK3_COLOR_PICKER)
18 int nsColorPicker::convertGdkRgbaComponent(gdouble color_component) {
19   // GdkRGBA value is in range [0.0..1.0]. We need something in range [0..255]
20   return color_component * 255 + 0.5;
21 }
22 
convertToGdkRgbaComponent(int color_component)23 gdouble nsColorPicker::convertToGdkRgbaComponent(int color_component) {
24   return color_component / 255.0;
25 }
26 
convertToRgbaColor(nscolor color)27 GdkRGBA nsColorPicker::convertToRgbaColor(nscolor color) {
28   GdkRGBA result = {convertToGdkRgbaComponent(NS_GET_R(color)),
29                     convertToGdkRgbaComponent(NS_GET_G(color)),
30                     convertToGdkRgbaComponent(NS_GET_B(color)),
31                     convertToGdkRgbaComponent(NS_GET_A(color))};
32 
33   return result;
34 }
35 #else
36 int nsColorPicker::convertGdkColorComponent(guint16 color_component) {
37   // GdkColor value is in range [0..65535]. We need something in range [0..255]
38   return (color_component * 255 + 127) / 65535;
39 }
40 
41 guint16 nsColorPicker::convertToGdkColorComponent(int color_component) {
42   return color_component * 65535 / 255;
43 }
44 
45 GdkColor nsColorPicker::convertToGdkColor(nscolor color) {
46   GdkColor result = {0 /* obsolete, unused 'pixel' value */,
47                      convertToGdkColorComponent(NS_GET_R(color)),
48                      convertToGdkColorComponent(NS_GET_G(color)),
49                      convertToGdkColorComponent(NS_GET_B(color))};
50 
51   return result;
52 }
53 
54 GtkColorSelection* nsColorPicker::WidgetGetColorSelection(GtkWidget* widget) {
55   return GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(
56       GTK_COLOR_SELECTION_DIALOG(widget)));
57 }
58 #endif
59 
Init(mozIDOMWindowProxy * aParent,const nsAString & title,const nsAString & initialColor)60 NS_IMETHODIMP nsColorPicker::Init(mozIDOMWindowProxy* aParent,
61                                   const nsAString& title,
62                                   const nsAString& initialColor) {
63   auto* parent = nsPIDOMWindowOuter::From(aParent);
64   mParentWidget = mozilla::widget::WidgetUtils::DOMWindowToWidget(parent);
65   mTitle = title;
66   mInitialColor = initialColor;
67 
68   return NS_OK;
69 }
70 
Open(nsIColorPickerShownCallback * aColorPickerShownCallback)71 NS_IMETHODIMP nsColorPicker::Open(
72     nsIColorPickerShownCallback* aColorPickerShownCallback) {
73   // Input color string should be 7 length (i.e. a string representing a valid
74   // simple color)
75   if (mInitialColor.Length() != 7) {
76     return NS_ERROR_FAILURE;
77   }
78 
79   const nsAString& withoutHash = StringTail(mInitialColor, 6);
80   nscolor color;
81   if (!NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) {
82     return NS_ERROR_FAILURE;
83   }
84 
85   if (mCallback) {
86     // It means Open has already been called: this is not allowed
87     NS_WARNING("mCallback is already set. Open called twice?");
88     return NS_ERROR_FAILURE;
89   }
90   mCallback = aColorPickerShownCallback;
91 
92   NS_ConvertUTF16toUTF8 title(mTitle);
93   GtkWindow* parent_window =
94       GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET));
95 
96 #if defined(ACTIVATE_GTK3_COLOR_PICKER)
97   GtkWidget* color_chooser = gtk_color_chooser_dialog_new(title, parent_window);
98 
99   if (parent_window) {
100     GtkWindow* window = GTK_WINDOW(color_chooser);
101     gtk_window_set_destroy_with_parent(window, TRUE);
102     if (gtk_window_get_modal(parent_window)) {
103       gtk_window_set_modal(window, TRUE);
104     }
105   }
106 
107   gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(color_chooser), FALSE);
108   GdkRGBA color_rgba = convertToRgbaColor(color);
109   gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(color_chooser), &color_rgba);
110 
111   g_signal_connect(GTK_COLOR_CHOOSER(color_chooser), "color-activated",
112                    G_CALLBACK(OnColorChanged), this);
113 #else
114   GtkWidget* color_chooser = gtk_color_selection_dialog_new(title.get());
115 
116   if (parent_window) {
117     GtkWindow* window = GTK_WINDOW(color_chooser);
118     gtk_window_set_transient_for(window, parent_window);
119     gtk_window_set_destroy_with_parent(window, TRUE);
120     if (gtk_window_get_modal(parent_window)) {
121       gtk_window_set_modal(window, TRUE);
122     }
123   }
124 
125   GdkColor color_gdk = convertToGdkColor(color);
126   gtk_color_selection_set_current_color(WidgetGetColorSelection(color_chooser),
127                                         &color_gdk);
128 
129   g_signal_connect(WidgetGetColorSelection(color_chooser), "color-changed",
130                    G_CALLBACK(OnColorChanged), this);
131 #endif
132 
133   NS_ADDREF_THIS();
134 
135   g_signal_connect(color_chooser, "response", G_CALLBACK(OnResponse), this);
136   g_signal_connect(color_chooser, "destroy", G_CALLBACK(OnDestroy), this);
137   gtk_widget_show(color_chooser);
138 
139   return NS_OK;
140 }
141 
142 #if defined(ACTIVATE_GTK3_COLOR_PICKER)
143 /* static */
OnColorChanged(GtkColorChooser * color_chooser,GdkRGBA * color,gpointer user_data)144 void nsColorPicker::OnColorChanged(GtkColorChooser* color_chooser,
145                                    GdkRGBA* color, gpointer user_data) {
146   static_cast<nsColorPicker*>(user_data)->Update(color);
147 }
148 
Update(GdkRGBA * color)149 void nsColorPicker::Update(GdkRGBA* color) {
150   SetColor(color);
151   if (mCallback) {
152     mCallback->Update(mColor);
153   }
154 }
155 
SetColor(const GdkRGBA * color)156 void nsColorPicker::SetColor(const GdkRGBA* color) {
157   mColor.Assign('#');
158   mColor += ToHexString(convertGdkRgbaComponent(color->red));
159   mColor += ToHexString(convertGdkRgbaComponent(color->green));
160   mColor += ToHexString(convertGdkRgbaComponent(color->blue));
161 }
162 #else
163 /* static */
OnColorChanged(GtkColorSelection * colorselection,gpointer user_data)164 void nsColorPicker::OnColorChanged(GtkColorSelection* colorselection,
165                                    gpointer user_data) {
166   static_cast<nsColorPicker*>(user_data)->Update(colorselection);
167 }
168 
Update(GtkColorSelection * colorselection)169 void nsColorPicker::Update(GtkColorSelection* colorselection) {
170   ReadValueFromColorSelection(colorselection);
171   if (mCallback) {
172     mCallback->Update(mColor);
173   }
174 }
175 
ReadValueFromColorSelection(GtkColorSelection * colorselection)176 void nsColorPicker::ReadValueFromColorSelection(
177     GtkColorSelection* colorselection) {
178   GdkColor rgba;
179   gtk_color_selection_get_current_color(colorselection, &rgba);
180 
181   mColor.Assign('#');
182   mColor += ToHexString(convertGdkColorComponent(rgba.red));
183   mColor += ToHexString(convertGdkColorComponent(rgba.green));
184   mColor += ToHexString(convertGdkColorComponent(rgba.blue));
185 }
186 #endif
187 
188 /* static */
OnResponse(GtkWidget * color_chooser,gint response_id,gpointer user_data)189 void nsColorPicker::OnResponse(GtkWidget* color_chooser, gint response_id,
190                                gpointer user_data) {
191   static_cast<nsColorPicker*>(user_data)->Done(color_chooser, response_id);
192 }
193 
194 /* static */
OnDestroy(GtkWidget * color_chooser,gpointer user_data)195 void nsColorPicker::OnDestroy(GtkWidget* color_chooser, gpointer user_data) {
196   static_cast<nsColorPicker*>(user_data)->Done(color_chooser,
197                                                GTK_RESPONSE_CANCEL);
198 }
199 
Done(GtkWidget * color_chooser,gint response)200 void nsColorPicker::Done(GtkWidget* color_chooser, gint response) {
201   switch (response) {
202     case GTK_RESPONSE_OK:
203     case GTK_RESPONSE_ACCEPT:
204 #if defined(ACTIVATE_GTK3_COLOR_PICKER)
205       GdkRGBA color;
206       gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(color_chooser), &color);
207       SetColor(&color);
208 #else
209       ReadValueFromColorSelection(WidgetGetColorSelection(color_chooser));
210 #endif
211       break;
212     case GTK_RESPONSE_CANCEL:
213     case GTK_RESPONSE_CLOSE:
214     case GTK_RESPONSE_DELETE_EVENT:
215       mColor = mInitialColor;
216       break;
217     default:
218       NS_WARNING("Unexpected response");
219       break;
220   }
221 
222   // A "response" signal won't be sent again but "destroy" will be.
223   g_signal_handlers_disconnect_by_func(color_chooser, FuncToGpointer(OnDestroy),
224                                        this);
225 
226   gtk_widget_destroy(color_chooser);
227   if (mCallback) {
228     mCallback->Done(mColor);
229     mCallback = nullptr;
230   }
231 
232   NS_RELEASE_THIS();
233 }
234 
ToHexString(int n)235 nsString nsColorPicker::ToHexString(int n) {
236   nsString result;
237   if (n <= 0x0F) {
238     result.Append('0');
239   }
240   result.AppendInt(n, 16);
241   return result;
242 }
243