1 /********************************************************************\
2 * gnc-gtk-utils.c -- utility functions based on glib functions *
3 * Copyright (C) 2006 David Hampton <hampton@employees.org> *
4 * *
5 * This program is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU General Public License as *
7 * published by the Free Software Foundation; either version 2 of *
8 * the License, or (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License*
16 * along with this program; if not, contact: *
17 * *
18 * Free Software Foundation Voice: +1-617-542-5942 *
19 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
20 * Boston, MA 02110-1301, USA gnu@gnu.org *
21 * *
22 \********************************************************************/
23
24 #include <config.h>
25
26 #include "gnc-gtk-utils.h"
27
28 #define LAST_INDEX "last_index"
29 #define CHANGED_ID "changed_id"
30
31
32 /** Find an entry in the GtkComboBox by its text value, and set
33 * the widget to that value. This function also records the index of
34 * that text value for use when the user leaves the widget.
35 *
36 * @param cbwe A pointer to a GtkComboBox with entry widget.
37 *
38 * @param text The entry text to find in the model of the combo box
39 * entry. */
40 void
gnc_cbwe_set_by_string(GtkComboBox * cbwe,const gchar * text)41 gnc_cbwe_set_by_string(GtkComboBox *cbwe,
42 const gchar *text)
43 {
44 GtkTreeModel *model;
45 GtkTreeIter iter;
46 gchar *tree_string;
47 gint column, index, id;
48 gboolean match;
49
50 model = gtk_combo_box_get_model(GTK_COMBO_BOX(cbwe));
51 if (!gtk_tree_model_get_iter_first(model, &iter))
52 {
53 /* empty tree */
54 gtk_combo_box_set_active(GTK_COMBO_BOX(cbwe), -1);
55 return;
56 }
57
58 column = gtk_combo_box_get_entry_text_column(cbwe);
59 do
60 {
61 gtk_tree_model_get(model, &iter, column, &tree_string, -1);
62 match = g_utf8_collate(text, tree_string) == 0;
63 g_free(tree_string);
64 if (!match)
65 continue;
66
67 /* Found a matching string */
68 id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cbwe), CHANGED_ID));
69 g_signal_handler_block(cbwe, id);
70 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(cbwe), &iter);
71 g_signal_handler_unblock(cbwe, id);
72
73 index = gtk_combo_box_get_active(GTK_COMBO_BOX(cbwe));
74 g_object_set_data(G_OBJECT(cbwe), LAST_INDEX, GINT_TO_POINTER(index));
75 return;
76 }
77 while (gtk_tree_model_iter_next(model, &iter));
78 }
79
80
81 /** The GtkComboBox with entry widget has changed its value. If the widget
82 * now points to another valid entry string then record the index of
83 * that string for use when the user leaves the widget.
84 *
85 * @param widget Unused.
86 *
87 * @param cbwe A pointer to a GtkComboBox widget. */
88 static void
gnc_cbwe_changed_cb(GtkComboBox * widget,GtkComboBox * cbwe)89 gnc_cbwe_changed_cb (GtkComboBox *widget,
90 GtkComboBox *cbwe)
91 {
92 gint index;
93
94 index = gtk_combo_box_get_active(widget);
95 if (index == -1)
96 return;
97 g_object_set_data(G_OBJECT(cbwe), LAST_INDEX, GINT_TO_POINTER(index));
98 }
99
100
101 /** The completion attached to currency edit widget has selected a
102 * match. This function extracts the completed string from the
103 * completion code's temporary model, and uses that to set the index
104 * of that currency name for use when the user leaves the widget.
105 * This should always point to a valid currency name since the user
106 * made the selection from a list of currency names.
107 *
108 * @param completion Unused.
109 *
110 * @param comp_model A temporary model used by completion code that
111 * contains only the current matches.
112 *
113 * @param comp_iter The iter in the completion's temporary model
114 * that represents the user selected match.
115 *
116 * @param cbwe A pointer to a currency entry widget. */
117 static gboolean
gnc_cbwe_match_selected_cb(GtkEntryCompletion * completion,GtkTreeModel * comp_model,GtkTreeIter * comp_iter,GtkComboBox * cbwe)118 gnc_cbwe_match_selected_cb (GtkEntryCompletion *completion,
119 GtkTreeModel *comp_model,
120 GtkTreeIter *comp_iter,
121 GtkComboBox *cbwe)
122 {
123 gint column;
124 gchar *text;
125
126 column = gtk_combo_box_get_entry_text_column(cbwe);
127 gtk_tree_model_get(comp_model, comp_iter, column, &text, -1);
128 gnc_cbwe_set_by_string(cbwe, text);
129 g_free(text);
130 return FALSE;
131 }
132
133
134 /** The focus left the currency edit widget, so reset the widget to
135 * its last known good value. If the widget value contained a valid
136 * currency then this is a noop. Otherwise the widget will be reset
137 * to the last user selected currency. This latter state will occur
138 * if the user has typed characters directly into the widget but not
139 * selected a completion.
140 *
141 * @param entry Unused.
142 *
143 * @param event Unused.
144 *
145 * @param cbwe A pointer to a currency entry widget. */
146 static gboolean
gnc_cbwe_focus_out_cb(GtkEntry * entry,GdkEventFocus * event,GtkComboBox * cbwe)147 gnc_cbwe_focus_out_cb (GtkEntry *entry,
148 GdkEventFocus *event,
149 GtkComboBox *cbwe)
150 {
151 const gchar *text;
152 gint index;
153
154 /* Make a final attempt to match the current text. */
155 text = gtk_entry_get_text(entry);
156 gnc_cbwe_set_by_string(cbwe, text);
157
158 /* Get the last known index (which may have just been set). */
159 index = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cbwe), LAST_INDEX));
160 gtk_combo_box_set_active(GTK_COMBO_BOX(cbwe), index);
161 return FALSE;
162 }
163
164 void
gnc_cbwe_add_completion(GtkComboBox * cbwe)165 gnc_cbwe_add_completion (GtkComboBox *cbwe)
166 {
167 GtkEntry *entry;
168 GtkEntryCompletion *completion;
169 GtkTreeModel *model;
170
171 entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(cbwe)));
172 completion = gtk_entry_get_completion(entry);
173 if (completion)
174 return;
175
176 /* No completion yet? Set one up. */
177 completion = gtk_entry_completion_new();
178 model = gtk_combo_box_get_model(GTK_COMBO_BOX(cbwe));
179 gtk_entry_completion_set_model(completion, model);
180 gtk_entry_completion_set_text_column(completion, 0);
181 gtk_entry_set_completion(entry, completion);
182 g_object_unref(completion);
183 }
184
185 void
gnc_cbwe_require_list_item(GtkComboBox * cbwe)186 gnc_cbwe_require_list_item (GtkComboBox *cbwe)
187 {
188 GtkEntry *entry;
189 GtkEntryCompletion *completion;
190 GtkTreeModel *model;
191 GtkTreeIter iter;
192 gint index, id;
193
194 /* Ensure completion is set up. */
195 gnc_cbwe_add_completion(cbwe);
196
197 /* If an item in the combo box isn't already selected, then force
198 * select the first item. Take care, the combo box may not have been
199 * filled yet. */
200 entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(cbwe)));
201 completion = gtk_entry_get_completion(entry);
202 index = gtk_combo_box_get_active(GTK_COMBO_BOX(cbwe));
203 if (index == -1)
204 {
205 model = gtk_entry_completion_get_model(completion);
206 if (gtk_tree_model_get_iter_first(model, &iter))
207 {
208 gtk_combo_box_set_active(GTK_COMBO_BOX(cbwe), 0);
209 index = 0;
210 }
211 }
212 g_object_set_data(G_OBJECT(cbwe), LAST_INDEX, GINT_TO_POINTER(index));
213
214 /* Now the signals to make sure the user can't leave the
215 widget without a valid match. */
216 id = g_signal_connect(cbwe, "changed",
217 G_CALLBACK(gnc_cbwe_changed_cb), cbwe);
218 g_signal_connect(completion, "match_selected",
219 G_CALLBACK(gnc_cbwe_match_selected_cb), cbwe);
220 g_signal_connect(entry, "focus-out-event",
221 G_CALLBACK(gnc_cbwe_focus_out_cb), cbwe);
222
223 g_object_set_data(G_OBJECT(cbwe), CHANGED_ID, GINT_TO_POINTER(id));
224 }
225
226 /** Return whether the current gtk theme is a dark one. A theme is considered "dark" if
227 * it has a dark background color with a light foreground color (used for text and so on).
228 * We only test on the foregrond color assuming a sane theme chooses enough contrast between
229 * foreground and background colors.
230 *
231 * @param fg_color The foreground color to test.
232 *
233 * @returns TRUE if the theme is considered dark, FALSE otherwise.
234 */
235 gboolean
gnc_is_dark_theme(GdkRGBA * fg_color)236 gnc_is_dark_theme (GdkRGBA *fg_color)
237 {
238 gboolean is_dark = FALSE;
239
240 // Counting the perceptive luminance - human eye favors green color...
241 double lightness = (0.299 * fg_color->red + 0.587 * fg_color->green + 0.114 * fg_color->blue);
242
243 if (lightness > 0.5)
244 is_dark = TRUE;
245
246 return is_dark;
247 }
248
249 /** Wrapper to get the background color of a widget for a given state
250 *
251 * @param context Style context of widget.
252 *
253 * @param state The stateflag of the widget.
254 *
255 * @param color The returned background color of the widget.
256 */
257 void
gnc_style_context_get_background_color(GtkStyleContext * context,GtkStateFlags state,GdkRGBA * color)258 gnc_style_context_get_background_color (GtkStyleContext *context,
259 GtkStateFlags state,
260 GdkRGBA *color)
261 {
262 GdkRGBA *c;
263
264 g_return_if_fail (color != NULL);
265 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
266
267 gtk_style_context_get (context,
268 state,
269 GTK_STYLE_PROPERTY_BACKGROUND_COLOR, &c,
270 NULL);
271 *color = *c;
272 gdk_rgba_free (c);
273 }
274
275 /** Wrapper to get the border color of a widget for a given state
276 *
277 * @param context Style context of widget.
278 *
279 * @param state The stateflag of the widget.
280 *
281 * @param color The returned border color of the widget.
282 */
283 void
gnc_style_context_get_border_color(GtkStyleContext * context,GtkStateFlags state,GdkRGBA * color)284 gnc_style_context_get_border_color (GtkStyleContext *context,
285 GtkStateFlags state,
286 GdkRGBA *color)
287 {
288 GdkRGBA *c;
289
290 g_return_if_fail (color != NULL);
291 g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
292
293 gtk_style_context_get (context,
294 state,
295 GTK_STYLE_PROPERTY_BORDER_COLOR, &c,
296 NULL);
297 *color = *c;
298 gdk_rgba_free (c);
299 }
300