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