1 /**
2 * This file is a part of the Cairo-Dock project
3 *
4 * Copyright : (C) see the 'copyright' file.
5 * E-mail : see the 'copyright' file.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 3
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <stdlib.h>
21 #include <string.h>
22
23 #include "applet-struct.h"
24 #include "applet-tips-dialog.h"
25
26 static void _on_tips_category_changed (GtkComboBox *pWidget, gpointer *data);
27
28 typedef struct {
29 GKeyFile *pKeyFile;
30 gchar **pGroupList;
31 gint iNbGroups;
32 gchar **pKeyList; // keys of the current group
33 gsize iNbKeys;
34 gint iNumTipGroup; // current group being displayed.
35 gint iNumTipKey; // current key being displayed.
36 GtkWidget *pCategoryCombo;
37 } CDTipsData;
38
_cairo_dock_get_next_tip(CDTipsData * pTips)39 static void _cairo_dock_get_next_tip (CDTipsData *pTips)
40 {
41 pTips->iNumTipKey ++; // skip the current expander to go to the current label, which will be skipped in the first iteration.
42 const gchar *cGroupName = pTips->pGroupList[pTips->iNumTipGroup];
43 gboolean bOk;
44 do
45 {
46 pTips->iNumTipKey ++;
47 if (pTips->iNumTipKey >= (gint) pTips->iNbKeys) // no more key, go to next group.
48 {
49 pTips->iNumTipGroup ++;
50 if (pTips->iNumTipGroup >= pTips->iNbGroups) // no more group, restart from first group.
51 pTips->iNumTipGroup = 0;
52 pTips->iNumTipKey = 0;
53
54 // since the group has changed, get the keys again.
55 g_strfreev (pTips->pKeyList);
56 cGroupName = pTips->pGroupList[pTips->iNumTipGroup];
57 pTips->pKeyList = g_key_file_get_keys (pTips->pKeyFile, cGroupName, &pTips->iNbKeys, NULL);
58
59 // and update the category in the comb
60 g_signal_handlers_block_matched (pTips->pCategoryCombo,
61 G_SIGNAL_MATCH_FUNC,
62 0,
63 0,
64 0,
65 _on_tips_category_changed,
66 NULL);
67 gtk_combo_box_set_active (GTK_COMBO_BOX (pTips->pCategoryCombo), pTips->iNumTipGroup);
68 g_signal_handlers_unblock_matched (pTips->pCategoryCombo,
69 G_SIGNAL_MATCH_FUNC,
70 0,
71 0,
72 0,
73 _on_tips_category_changed,
74 NULL);
75 }
76
77 // check if the key is an expander widget.
78 const gchar *cKeyName = pTips->pKeyList[pTips->iNumTipKey];
79 gchar *cKeyComment = g_key_file_get_comment (pTips->pKeyFile, cGroupName, cKeyName, NULL);
80 bOk = (cKeyComment && *cKeyComment == CAIRO_DOCK_WIDGET_EXPANDER); // whether it's an expander.
81 g_free (cKeyComment);
82 } while (!bOk);
83 }
84
_cairo_dock_get_previous_tip(CDTipsData * pTips)85 static void _cairo_dock_get_previous_tip (CDTipsData *pTips)
86 {
87 pTips->iNumTipKey --;
88
89 const gchar *cGroupName = pTips->pGroupList[pTips->iNumTipGroup];
90 gboolean bOk;
91 do
92 {
93 pTips->iNumTipKey --;
94 if (pTips->iNumTipKey < 0) // no more key, go to previous group.
95 {
96 pTips->iNumTipGroup --;
97 if (pTips->iNumTipGroup < 0) // no more group, restart from the last group.
98 pTips->iNumTipGroup = pTips->iNbGroups - 1;
99
100 // since the group has changed, get the keys again.
101 g_strfreev (pTips->pKeyList);
102 cGroupName = pTips->pGroupList[pTips->iNumTipGroup];
103 pTips->pKeyList = g_key_file_get_keys (pTips->pKeyFile, cGroupName, &pTips->iNbKeys, NULL);
104
105 pTips->iNumTipKey = pTips->iNbKeys - 2;
106
107 // and update the category in the comb
108 g_signal_handlers_block_matched (pTips->pCategoryCombo,
109 G_SIGNAL_MATCH_FUNC,
110 0,
111 0,
112 0,
113 _on_tips_category_changed,
114 NULL);
115 gtk_combo_box_set_active (GTK_COMBO_BOX (pTips->pCategoryCombo), pTips->iNumTipGroup);
116 g_signal_handlers_unblock_matched (pTips->pCategoryCombo,
117 G_SIGNAL_MATCH_FUNC,
118 0,
119 0,
120 0,
121 _on_tips_category_changed,
122 NULL);
123 }
124
125 // check if the key is an expander widget.
126 const gchar *cKeyName = pTips->pKeyList[pTips->iNumTipKey];
127 gchar *cKeyComment = g_key_file_get_comment (pTips->pKeyFile, cGroupName, cKeyName, NULL);
128 bOk = (cKeyComment && *cKeyComment == CAIRO_DOCK_WIDGET_EXPANDER); // whether it's an expander.
129 } while (!bOk);
130 }
131
_build_tip_text(CDTipsData * pTips)132 static gchar *_build_tip_text (CDTipsData *pTips)
133 {
134 const gchar *cGroupName = pTips->pGroupList[pTips->iNumTipGroup];
135 const gchar *cKeyName1 = pTips->pKeyList[pTips->iNumTipKey];
136 const gchar *cKeyName2 = pTips->pKeyList[pTips->iNumTipKey+1];
137
138 char iElementType;
139 guint iNbElements = 0;
140 gboolean bAligned;
141 const gchar *cHint1 = NULL; // points on the comment.
142 gchar **pAuthorizedValuesList1 = NULL;
143 gchar *cKeyComment1 = g_key_file_get_comment (pTips->pKeyFile, cGroupName, cKeyName1, NULL);
144 cairo_dock_parse_key_comment (cKeyComment1, &iElementType, &iNbElements, &pAuthorizedValuesList1, &bAligned, &cHint1); // points on the comment.
145
146 const gchar *cHint2 = NULL;
147 gchar **pAuthorizedValuesList2 = NULL;
148 gchar *cKeyComment2 = g_key_file_get_comment (pTips->pKeyFile, cGroupName, cKeyName2, NULL);
149 const gchar *cText2 = cairo_dock_parse_key_comment (cKeyComment2, &iElementType, &iNbElements, &pAuthorizedValuesList2, &bAligned, &cHint2);
150
151 gchar *cText = g_strdup_printf ("<b>%s</b>\n\n<i>%s</i>\n\n%s",
152 _("Tips and Tricks"),
153 (pAuthorizedValuesList1 ? gettext (pAuthorizedValuesList1[0]) : ""),
154 gettext (cText2));
155
156 g_strfreev (pAuthorizedValuesList1);
157 g_strfreev (pAuthorizedValuesList2);
158 g_free (cKeyComment1);
159 g_free (cKeyComment2);
160 return cText;
161 }
_update_tip_text(CDTipsData * pTips,CairoDialog * pDialog)162 static void _update_tip_text (CDTipsData *pTips, CairoDialog *pDialog)
163 {
164 gchar *cText = _build_tip_text (pTips);
165
166 gldi_dialog_set_message (pDialog, cText);
167
168 g_free (cText);
169 }
_tips_dialog_action(int iClickedButton,G_GNUC_UNUSED GtkWidget * pInteractiveWidget,CDTipsData * pTips,CairoDialog * pDialog)170 static void _tips_dialog_action (int iClickedButton, G_GNUC_UNUSED GtkWidget *pInteractiveWidget, CDTipsData *pTips, CairoDialog *pDialog)
171 {
172 cd_debug ("%s (%d)", __func__, iClickedButton);
173 if (iClickedButton == 2 || iClickedButton == -1) // click on "next", or Enter.
174 {
175 // show the next tip
176 _cairo_dock_get_next_tip (pTips);
177
178 _update_tip_text (pTips, pDialog);
179
180 gldi_object_ref (GLDI_OBJECT(pDialog)); // keep the dialog alive.
181 }
182 else if (iClickedButton == 1) // click on "previous"
183 {
184 // show the previous tip
185 _cairo_dock_get_previous_tip (pTips);
186
187 _update_tip_text (pTips, pDialog);
188
189 gldi_object_ref (GLDI_OBJECT(pDialog)); // keep the dialog alive.
190 }
191 else // click on "close" or Escape
192 {
193 myData.iLastTipGroup = pTips->iNumTipGroup;
194 myData.iLastTipKey = pTips->iNumTipKey;
195 gchar *cConfFilePath = g_strdup_printf ("%s/.help", g_cCairoDockDataDir);
196 cairo_dock_update_conf_file (cConfFilePath,
197 G_TYPE_INT, "Last Tip", "group", pTips->iNumTipGroup,
198 G_TYPE_INT, "Last Tip", "key", pTips->iNumTipKey,
199 G_TYPE_INVALID);
200 g_free (cConfFilePath);
201 }
202 cd_debug ("tips : %d/%d", pTips->iNumTipGroup, pTips->iNumTipKey);
203 }
_on_free_tips_dialog(CDTipsData * pTips)204 static void _on_free_tips_dialog (CDTipsData *pTips)
205 {
206 g_key_file_free (pTips->pKeyFile);
207 g_strfreev (pTips->pGroupList);
208 g_strfreev (pTips->pKeyList);
209 g_free (pTips);
210 }
211
_on_tips_category_changed(GtkComboBox * pWidget,gpointer * data)212 static void _on_tips_category_changed (GtkComboBox *pWidget, gpointer *data)
213 {
214 CDTipsData *pTips = data[0];
215 CairoDialog *pDialog = data[1];
216
217 int iNumItem = gtk_combo_box_get_active (pWidget);
218 g_return_if_fail (iNumItem < pTips->iNbGroups);
219
220 pTips->iNumTipGroup = iNumItem;
221
222 // since the group has changed, get the keys again.
223 g_strfreev (pTips->pKeyList);
224 const gchar *cGroupName = pTips->pGroupList[pTips->iNumTipGroup];
225 pTips->pKeyList = g_key_file_get_keys (pTips->pKeyFile, cGroupName, &pTips->iNbKeys, NULL);
226 pTips->iNumTipKey = 0;
227
228 _update_tip_text (pTips, pDialog);
229 }
230
cairo_dock_show_tips(void)231 void cairo_dock_show_tips (void)
232 {
233 if (myData.iSidGetParams != 0)
234 return;
235
236 // open the tips file
237 const gchar *cConfFilePath = CD_APPLET_MY_CONF_FILE;
238 GKeyFile *pKeyFile = cairo_dock_open_key_file (cConfFilePath);
239 g_return_if_fail (pKeyFile != NULL);
240
241 gsize iNbGroups = 0;
242 gchar **pGroupList = g_key_file_get_groups (pKeyFile, &iNbGroups);
243 iNbGroups -= 4; // skip the last 4 groups (Troubleshooting and Contribute + Icon and Desklet).
244 g_return_if_fail (pGroupList != NULL && iNbGroups > 0);
245
246 // get the last displayed tip.
247 guint iNumTipGroup, iNumTipKey;
248 if (myData.iLastTipGroup < 0 || myData.iLastTipKey < 0) // first time we display a tip.
249 {
250 iNumTipGroup = iNumTipKey = 0;
251 }
252 else
253 {
254 iNumTipGroup = myData.iLastTipGroup;
255 iNumTipKey = myData.iLastTipKey;
256 if (iNumTipGroup >= iNbGroups) // be sure to stay inside the limits.
257 {
258 iNumTipGroup = iNbGroups - 1;
259 iNumTipKey = 0;
260 }
261 }
262 const gchar *cGroupName = pGroupList[iNumTipGroup];
263
264 gsize iNbKeys = 0;
265 gchar **pKeyList = g_key_file_get_keys (pKeyFile, cGroupName, &iNbKeys, NULL);
266 g_return_if_fail (pKeyList != NULL && iNbKeys > 0);
267 if (iNumTipKey >= iNbKeys) // be sure to stay inside the limits.
268 iNumTipKey = 0;
269
270 CDTipsData *pTips = g_new0 (CDTipsData, 1);
271 pTips->pKeyFile = pKeyFile;
272 pTips->pGroupList = pGroupList;
273 pTips->iNbGroups = iNbGroups;
274 pTips->pKeyList = pKeyList;
275 pTips->iNbKeys = iNbKeys;
276 pTips->iNumTipGroup = iNumTipGroup;
277 pTips->iNumTipKey = iNumTipKey;
278
279 // update to the next tip.
280 if (myData.iLastTipGroup >= 0 && myData.iLastTipKey >= 0) // a previous tip exist => take the next one;
281 _cairo_dock_get_next_tip (pTips);
282
283 // build a list of the available groups.
284 GtkWidget *pInteractiveWidget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
285 GtkWidget *pComboBox = gtk_combo_box_text_new ();
286 guint i;
287 for (i = 0; i < iNbGroups; i ++)
288 {
289 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (pComboBox), gettext (pGroupList[i]));
290 }
291 gtk_combo_box_set_active (GTK_COMBO_BOX (pComboBox), pTips->iNumTipGroup);
292 pTips->pCategoryCombo = pComboBox;
293 static gpointer data_combo[2];
294 data_combo[0] = pTips; // the 2nd data is the dialog, we'll set it after we make it.
295 g_signal_connect (G_OBJECT (pComboBox), "changed", G_CALLBACK(_on_tips_category_changed), data_combo);
296 GtkWidget *pJumpBox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
297 GtkWidget *label = gtk_label_new (_("Category"));
298 gldi_dialog_set_widget_text_color (label);
299 gtk_box_pack_end (GTK_BOX (pJumpBox), pComboBox, FALSE, FALSE, 0);
300 gtk_box_pack_end (GTK_BOX (pJumpBox), label, FALSE, FALSE, 0);
301 gtk_box_pack_start (GTK_BOX (pInteractiveWidget), pJumpBox, FALSE, FALSE, 0);
302
303 // build the dialog.
304 gchar *cText = _build_tip_text (pTips);
305 CairoDialogAttr attr;
306 memset (&attr, 0, sizeof (CairoDialogAttr));
307 attr.cText = cText;
308 attr.cImageFilePath = NULL;
309 attr.pInteractiveWidget = pInteractiveWidget;
310 attr.pActionFunc = (CairoDockActionOnAnswerFunc)_tips_dialog_action;
311 attr.pUserData = pTips;
312 attr.pFreeDataFunc = (GFreeFunc)_on_free_tips_dialog;
313 /// GTK_STOCK is now deprecated, here is a temporary fix to avoid compilation errors
314 #if GTK_CHECK_VERSION(3, 9, 8)
315 const gchar *cButtons[] = {"cancel", "gtk-go-forward-rtl", "gtk-go-forward-ltr", NULL};
316 #else
317 const gchar *cButtons[] = {"cancel", GTK_STOCK_GO_FORWARD"-rtl", GTK_STOCK_GO_FORWARD"-ltr", NULL};
318 #endif
319 attr.cButtonsImage = cButtons;
320 attr.bUseMarkup = TRUE;
321 attr.pIcon = myIcon;
322 attr.pContainer = myContainer;
323 CairoDialog *pTipsDialog = gldi_dialog_new (&attr);
324
325 data_combo[1] = pTipsDialog;
326
327 g_free (cText);
328 }
329