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