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 <glib.h>
21 #include <glib/gprintf.h>
22
23 #include "applet-struct.h"
24 #include "applet-draw.h"
25 #include "applet-rss.h"
26
27 const gchar *cExtendedAscii[256-32] = {" ", "!", "\"", "#", "$", "%", "&", "’", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\"", "]", "^", "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", " ", "€", "�", "‚", "ƒ", "„", "…", "†", "‡", "ˆ", "‰", "Š", "<", "Œ", "�", "Ž", "�", "�", "‘", "’", "“", "”", "•", "–", "—", "˜", "™", "š", "›", "œ", "�", "ž", "Ÿ", " ", "¡", "¢", "£", "¤", "¥", "¦", "§", "¨", "©", "ª", "«", "¬", " ", "®", "¯", "°", "±", "²", "³", "´", "µ", "¶", "·", "¸", "¹", "º", "»", "¼", "½", "¾", "¿", "À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç", "È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï", "Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö", "×", "Ø", "Ù", "Ú", "Û", "Ü", "Ý", "Þ", "ß", "à", "á", "â", "ã", "ä", "å", "æ", "ç", "è", "é", "ê", "ë", "ì", "í", "î", "ï", "ð", "ñ", "ò", "ó", "ô", "õ", "ö", "÷", "ø", "ù", "ú", "û", "ü", "ý", "þ", "ÿ"};
28
29 /* Insere des retours chariots dans une chaine de caracteres de facon a la faire tenir dans un rectangle donne.
30 */
cd_rssreader_cut_line(gchar * cLine,PangoLayout * pLayout,int iMaxWidth)31 void cd_rssreader_cut_line (gchar *cLine, PangoLayout *pLayout, int iMaxWidth)
32 {
33 cd_debug ("%s (%s)", __func__, cLine);
34 // on convertit les caracteres internet.
35 gchar *str=cLine, *amp;
36 do
37 {
38 amp = strchr (str, '&');
39 if (!amp)
40 break;
41 if (amp[1] == '#' && g_ascii_isdigit (amp[2]) && g_ascii_isdigit (amp[3]) && g_ascii_isdigit (amp[4]) && amp[5] == ';') // '
42 {
43 int i = atoi (amp+2) - 32;
44 cd_debug ("%d", i);
45
46 if (i >= 0 && i < 256 - 32)
47 {
48 cd_debug ("%d -> %s", i, cExtendedAscii[i]);
49 strcpy (amp, cExtendedAscii[i]);
50 strcpy (amp+strlen (cExtendedAscii[i]), amp+6);
51 }
52 }
53 str = amp + 1;
54 } while (1);
55
56 // on insere des retours chariot pour tenir dans la largeur donnee.
57 PangoRectangle ink, log;
58 gchar *sp, *last_sp=NULL;
59 double w;
60 int iNbLines = 0;
61
62 str = cLine;
63 while (*str == ' ') // on saute les espaces en debut de ligne.
64 str ++;
65
66 sp = str;
67 do
68 {
69 sp = strchr (sp+1, ' '); // on trouve l'espace suivant.
70 if (!sp) // plus d'espace, on quitte.
71 break ;
72
73 *sp = '\0'; // on coupe a cet espace.
74 pango_layout_set_text (pLayout, str, -1); // on regarde la taille de str a sp.
75 pango_layout_get_pixel_extents (pLayout, &ink, &log);
76 //g_print ("%s => w:%d/%d, x:%d/%d\n", str, log.width, ink.width, log.x, ink.x);
77 w = log.width + log.x;
78
79 if (w > iMaxWidth) // on deborde.
80 {
81 if (last_sp != NULL) // on coupe au dernier espace connu.
82 {
83 *sp = ' '; // on remet l'espace.
84 *last_sp = '\n'; // on coupe.
85 iNbLines ++;
86 str = last_sp + 1; // on place le debut de ligne apres la coupure.
87 }
88 else // aucun espace, c'est un mot entier.
89 {
90 *sp = '\n'; // on coupe apres le mot.
91 iNbLines ++;
92 str = sp + 1; // on place le debut de ligne apres la coupure.
93 }
94
95 while (*str == ' ') // on saute les espaces en debut de ligne.
96 str ++;
97 sp = str;
98 last_sp = NULL;
99 }
100 else // ca rentre.
101 {
102 *sp = ' '; // on remet l'espace.
103 last_sp = sp; // on memorise la derniere cesure qui fait tenir la ligne en largeur.
104 sp ++; // on se place apres.
105 while (*sp == ' ') // on saute tous les espaces.
106 sp ++;
107 }
108 } while (sp);
109
110 // dernier mot.
111 pango_layout_set_text (pLayout, str, -1); // on regarde la taille de str a sp.
112 pango_layout_get_pixel_extents (pLayout, &ink, &log);
113 w = log.width + log.x;
114 if (w > iMaxWidth) // on deborde.
115 {
116 if (last_sp != NULL) // on coupe au dernier espace connu.
117 *last_sp = '\n';
118 }
119 }
120
121
_free_item(CDRssItem * pItem)122 static void _free_item (CDRssItem *pItem)
123 {
124 if (pItem == NULL)
125 return;
126
127 g_free (pItem->cTitle);
128 g_free (pItem->cDescription);
129 g_free (pItem->cLink);
130 g_free (pItem->cDate);
131 g_free (pItem);
132 }
cd_rssreader_free_item_list(GldiModuleInstance * myApplet)133 void cd_rssreader_free_item_list (GldiModuleInstance *myApplet)
134 {
135 if (myData.pItemList == NULL)
136 return;
137 CDRssItem *pItem;
138 GList *it;
139 for (it = myData.pItemList; it != NULL; it = it->next)
140 {
141 pItem = it->data;
142 _free_item (pItem);
143 }
144 g_list_free (myData.pItemList);
145 myData.pItemList = NULL;
146 myData.iFirstDisplayedItem = 0;
147
148 gldi_object_unref (GLDI_OBJECT(myData.pDialog)); // un peu bourrin mais bon ... on pourrait le reafficher s'il etait present ...
149 myData.pDialog = NULL;
150 }
151
152
_get_feeds(CDSharedMemory * pSharedMemory)153 static void _get_feeds (CDSharedMemory *pSharedMemory)
154 {
155 if (pSharedMemory->cUrl == NULL)
156 return ;
157
158 gchar *cUrlWithLoginPwd = NULL;
159 if (pSharedMemory->cUrlLogin && pSharedMemory->cUrlPassword
160 && *pSharedMemory->cUrlLogin != '\0' && *pSharedMemory->cUrlPassword != '\0')
161 {
162 // An URL is composed of that: "protocol://login:password@server/path"
163 // so look for the "://" string and insert "login:password@" at that place
164 gchar *location = g_strstr_len(pSharedMemory->cUrl, 10, "://");
165 if( location )
166 {
167 gsize length_first_part = location - pSharedMemory->cUrl + 3;
168 if( length_first_part > 0 )
169 {
170 gchar *first_part = g_strndup(pSharedMemory->cUrl, length_first_part);
171 cUrlWithLoginPwd = g_strdup_printf("%s%s:%s@%s", first_part, pSharedMemory->cUrlLogin, pSharedMemory->cUrlPassword, location+3);
172 g_free(first_part);
173 }
174 }
175 }
176
177 pSharedMemory->cTaskBridge = cairo_dock_get_url_data (cUrlWithLoginPwd ? cUrlWithLoginPwd : pSharedMemory->cUrl, NULL);
178 g_free (cUrlWithLoginPwd);
179 }
180
_parse_rss_item(xmlNodePtr node,CDRssItem * pItem,GList * pItemList)181 static GList * _parse_rss_item (xmlNodePtr node, CDRssItem *pItem, GList *pItemList)
182 {
183 xmlChar *content;
184 xmlNodePtr item;
185 for (item = node->children; item != NULL; item = item->next)
186 {
187 cd_debug (" + item: %s", item->name);
188 if (xmlStrcmp (item->name, BAD_CAST "item") == 0) // c'est un nouvel item.
189 {
190 CDRssItem *pNewItem = g_new0 (CDRssItem, 1);
191 pItemList = g_list_prepend (pItemList, pNewItem);
192
193 pItemList = _parse_rss_item (item, pNewItem, pItemList);
194 }
195 else if (xmlStrcmp (item->name, BAD_CAST "title") == 0) // c'est le titre.
196 {
197 if (pItem->cTitle == NULL) // cas du titre du flux force a une valeur par l'utilisateur.
198 {
199 content = xmlNodeGetContent (item);
200 if (content != NULL)
201 {
202 // remove leading and trailing carriage returns.
203 gchar *str = (gchar *) content;
204 while (*str == '\n') str ++;
205 int n = strlen(str);
206 while (str[n-1] == '\n') str[--n] = '\0';
207
208 pItem->cTitle = g_strdup (str);
209
210 xmlFree (content);
211 }
212 }
213 //g_print (" + titre : '%s'\n", pItem->cTitle);
214 }
215 else if (xmlStrcmp (item->name, BAD_CAST "description") == 0) // c'est la description.
216 {
217 content = xmlNodeGetContent (item);
218 pItem->cDescription = g_strdup ((gchar *)content);
219 xmlFree (content);
220
221 // on elimine les balises integrees a la description.
222 gchar *str = pItem->cDescription, *balise, *balise2;
223 do
224 {
225 balise2 = NULL;
226 balise = strchr (str, '<'); // debut de balise ("<")
227 if (balise)
228 {
229 balise2 = strchr (balise+1, '>'); // fin de balise (">")
230 if (balise2)
231 {
232 strcpy (balise, balise2+1);
233 str = balise;
234 }
235 }
236 }
237 while (balise2);
238 str = pItem->cDescription;
239 do
240 {
241 balise = g_strstr_len (str, -1, " ");
242 if (balise)
243 {
244 memset (balise, ' ', 6);
245 str = balise+6;
246 }
247 }
248 while (balise);
249 cd_debug (" + description : '%s'", pItem->cDescription);
250 }
251 else if (xmlStrcmp (item->name, BAD_CAST "link") == 0) // c'est le lien.
252 {
253 content = xmlNodeGetContent (item);
254 pItem->cLink = g_strdup ((gchar *)content);
255 xmlFree (content);
256 cd_debug (" + link : '%s'", pItem->cLink);
257 }
258 else if (xmlStrcmp (item->name, BAD_CAST "pubDate") == 0 || xmlStrcmp (item->name, BAD_CAST "date") == 0) // c'est la date (pubDate pour RSS, data pour RDF).
259 {
260 content = xmlNodeGetContent (item);
261 pItem->cDate = g_strdup ((gchar *)content);
262 xmlFree (content);
263 }
264 // pour recuperer l'image, on dirait qu'on a plusieurs cas, entre autre :
265 // <enclosure url="http://medias.lemonde.fr/mmpub/edt/ill/2009/11/01/h_1_ill_1261356_5d02_258607.jpg" length="2514" type="image/jpeg" /> ----> bien verifier que c'est une image.
266 // ou
267 // <media:thumbnail url="http://www.france24.com/fr/files_fr/EN-interview-Gursel-m.jpg" />
268 }
269 return pItemList;
270 }
271
_parse_atom_item(xmlNodePtr node,CDRssItem * pItem,GList * pItemList,const gchar * cBaseUrl)272 static GList * _parse_atom_item (xmlNodePtr node, CDRssItem *pItem, GList *pItemList, const gchar *cBaseUrl)
273 {
274 xmlChar *content;
275 xmlNodePtr item, author;
276 for (item = node->children; item != NULL; item = item->next)
277 {
278 if (xmlStrcmp (item->name, BAD_CAST "entry") == 0) // c'est un nouvel item.
279 {
280 CDRssItem *pNewItem = g_new0 (CDRssItem, 1);
281 pItemList = g_list_prepend (pItemList, pNewItem);
282
283 pItemList = _parse_atom_item (item, pNewItem, pItemList, cBaseUrl);
284 }
285 else if (xmlStrcmp (item->name, BAD_CAST "title") == 0) // c'est le titre.
286 {
287 if (pItem->cTitle == NULL) // cas du titre du flux force a une valeur par l'utilisateur.
288 {
289 content = xmlNodeGetContent (item);
290 if (content != NULL)
291 {
292 // remove leading and trailing carriage returns.
293 gchar *str = (gchar *)content;
294 while (*str == '\n') str ++;
295 int n = strlen(str);
296 while (str[n-1] == '\n') str[--n] = '\0';
297
298 pItem->cTitle = g_strdup (str);
299
300 xmlFree (content);
301 }
302 }
303 cd_debug ("+ title : '%s'", pItem->cTitle);
304 }
305 else if (xmlStrcmp (item->name, BAD_CAST "content") == 0) // c'est la description.
306 {
307 xmlAttrPtr attr = xmlHasProp (item, BAD_CAST "type");
308 if (attr && attr->children)
309 {
310 cd_debug (" description type : %s", attr->children->content);
311 if (strncmp ((gchar *)attr->children->content, "text", 4) != 0)
312 {
313 continue;
314 }
315 }
316 content = xmlNodeGetContent (item);
317 pItem->cDescription = g_strdup ((gchar *)content);
318 xmlFree (content);
319
320 // on elimine les balises integrees a la description.
321 gchar *str = pItem->cDescription, *balise, *balise2;
322 do
323 {
324 balise2 = NULL;
325 balise = strchr (str, '<'); // debut de balise ("<")
326 if (balise)
327 {
328 balise2 = strchr (balise+1, '>'); // fin de balise (">")
329 if (balise2)
330 {
331 strcpy (balise, balise2+1);
332 str = balise;
333 }
334 }
335 }
336 while (balise2);
337 cd_debug ("+ description : '%s'", pItem->cDescription);
338 }
339 else if (xmlStrcmp (item->name, BAD_CAST "link") == 0) // c'est le lien.
340 {
341 xmlAttrPtr attr = xmlHasProp (item, BAD_CAST "type"); // type="text/html" rel="alternate"
342 if (attr && attr->children)
343 {
344 cd_debug (" link type : %s", attr->children->content);
345 if (strncmp ((gchar *)attr->children->content, "text", 4) != 0)
346 {
347 continue;
348 }
349 }
350 attr = xmlHasProp (item, BAD_CAST "href");
351 if (attr && attr->children)
352 {
353 content = xmlNodeGetContent (attr->children);
354 if (strncmp ((gchar *)content, "http://", 7) == 0)
355 {
356 pItem->cLink = g_strdup ((gchar *)content);
357 }
358 else if (cBaseUrl != NULL)
359 {
360 pItem->cLink = g_strdup_printf ("%s%s", cBaseUrl, (gchar *)content);
361 }
362 xmlFree (content);
363 cd_debug ("+ link : '%s'", pItem->cLink);
364 }
365 }
366 else if (xmlStrcmp (item->name, BAD_CAST "updated") == 0) // c'est la date.
367 {
368 content = xmlNodeGetContent (item);
369 pItem->cDate = g_strdup ((gchar *)content);
370 xmlFree (content);
371 cd_debug ("+ date : '%s'", pItem->cDate);
372 }
373 else if (xmlStrcmp (item->name, BAD_CAST "author") == 0) // c'est l'auteur.
374 {
375 for (author = item->children; author != NULL; author = author->next)
376 {
377 if (xmlStrcmp (author->name, BAD_CAST "name") == 0) // c'est le nom de l'auteur.
378 {
379 content = xmlNodeGetContent (author);
380 pItem->cAuthor = g_strdup ((gchar *)content);
381 xmlFree (content);
382 cd_debug ("+ author : '%s'", pItem->cAuthor);
383 }
384 }
385 }
386 // et pour l'image je ne sais pas.
387 }
388 return pItemList;
389 }
390
_insert_error_message(GldiModuleInstance * myApplet,const gchar * cErrorMessage)391 static void _insert_error_message (GldiModuleInstance *myApplet, const gchar *cErrorMessage)
392 {
393 cd_debug ("%s (%s, %d)", __func__, cErrorMessage, myData.bError);
394 CDRssItem *pItem;
395 if (myData.pItemList != NULL) // on garde la liste courante, mais on insere un message.
396 {
397 if (! myData.bError) // pas encore de message d'erreur, on en insere un.
398 {
399 pItem = g_new0 (CDRssItem, 1);
400 pItem->cTitle = g_strdup (D_("Warning: couldn't retrieve data last time we tried."));
401 myData.pItemList = g_list_insert (myData.pItemList, pItem, 1);
402 }
403 }
404 else // aucune liste : c'est la 1ere recuperation => on met le titre si possible, suivi du message d'erreur.
405 {
406 pItem = g_new0 (CDRssItem, 1);
407 myData.pItemList = g_list_prepend (myData.pItemList, pItem);
408 if (myConfig.cUserTitle != NULL && myConfig.cUrl != NULL) // si le titre est connu on l'utilise (si aucun URL n'est defini ce n'est pas pertinent par contre).
409 {
410 pItem->cTitle = g_strdup (myConfig.cUserTitle);
411 pItem = g_new0 (CDRssItem, 1);
412 myData.pItemList = g_list_append (myData.pItemList, pItem);
413 }
414 pItem->cTitle = g_strdup (cErrorMessage);
415 }
416
417 myData.bError = TRUE;
418 }
419
_update_from_feeds(CDSharedMemory * pSharedMemory)420 static gboolean _update_from_feeds (CDSharedMemory *pSharedMemory)
421 {
422 GldiModuleInstance *myApplet = pSharedMemory->pApplet;
423 CD_APPLET_ENTER;
424 if (! myData.bInit) // pas encore initialise, on vire le message d'attente.
425 {
426 cd_rssreader_free_item_list (myApplet);
427 myData.pItemList = NULL;
428 myData.bInit = TRUE;
429 }
430
431 // On parse le flux XML.
432 if (pSharedMemory->cTaskBridge == NULL || *pSharedMemory->cTaskBridge == '\0')
433 {
434 cd_warning ("RSSresader : no data");
435 const gchar *cErrorMessage = (myConfig.cUrl == NULL ?
436 D_("No URL is defined.") :
437 D_("No data (no connection?)"));
438 _insert_error_message (myApplet, cErrorMessage);
439 if (myDesklet)
440 {
441 cd_applet_update_my_icon (myApplet);
442 }
443 myData.bUpdateIsManual = FALSE;
444
445 if (myData.pTask->iPeriod > 20)
446 {
447 cd_message ("no data, will re-try in 20s");
448 gldi_task_change_frequency (myData.pTask, 20); // on re-essaiera dans 20s.
449 }
450
451 CD_APPLET_LEAVE (TRUE);
452 }
453
454 if (myData.pTask->iPeriod != myConfig.iRefreshTime)
455 {
456 cd_message ("revert to normal frequency");
457 gldi_task_change_frequency (myData.pTask, myConfig.iRefreshTime);
458 }
459
460 //g_print (" --> RSS: '%s'\n", myData.cTaskBridge);
461 xmlDocPtr doc = xmlParseMemory (pSharedMemory->cTaskBridge, strlen (pSharedMemory->cTaskBridge));
462 g_free (pSharedMemory->cTaskBridge);
463 pSharedMemory->cTaskBridge = NULL;
464
465 if (doc == NULL)
466 {
467 cd_warning ("RSSresader : got invalid XML data");
468 const gchar *cErrorMessage = D_("Invalid data (invalid RSS/Atom feed?)");
469 _insert_error_message (myApplet, cErrorMessage);
470 if (myDesklet)
471 {
472 cd_applet_update_my_icon (myApplet);
473 }
474 g_free (myData.PrevFirstTitle);
475 myData.PrevFirstTitle = NULL;
476 myData.bUpdateIsManual = FALSE;
477 CD_APPLET_LEAVE (TRUE);
478 }
479
480 xmlNodePtr rss = xmlDocGetRootElement (doc);
481 if (rss == NULL || (xmlStrcmp (rss->name, BAD_CAST "rss") != 0 && xmlStrcmp (rss->name, BAD_CAST "feed") != 0 && xmlStrcmp (rss->name, BAD_CAST "RDF") != 0))
482 {
483 cd_warning ("RSSresader : got invalid XML data");
484 ///xmlCleanupParser ();
485 xmlFreeDoc (doc);
486
487 const gchar *cErrorMessage = D_("Invalid data (invalid RSS/Atom feed?)");
488 _insert_error_message (myApplet, cErrorMessage);
489 if (myDesklet)
490 {
491 cd_applet_update_my_icon (myApplet);
492 }
493 g_free (myData.PrevFirstTitle);
494 myData.PrevFirstTitle = NULL;
495 myData.bUpdateIsManual = FALSE;
496 CD_APPLET_LEAVE (TRUE);
497 }
498
499 // on extrait chaque item.
500 GList *pNewItemList = NULL;
501 CDRssItem *pItem = g_new0 (CDRssItem, 1); // on commence au debut de la liste (c'est le titre).
502 pNewItemList = g_list_prepend (pNewItemList, pItem);
503 if (myConfig.cUserTitle != NULL)
504 pItem->cTitle = g_strdup (myConfig.cUserTitle); // ne sera pas ecrase par les donnees du flux.
505
506 if (xmlStrcmp (rss->name, BAD_CAST "rss") == 0) // RSS
507 {
508 xmlAttrPtr attr = xmlHasProp (rss, BAD_CAST "version");
509 if (attr && attr->children)
510 {
511 cd_debug ("RSS version : %s", attr->children->content);
512 }
513
514 xmlNodePtr channel;
515 for (channel = rss->children; channel != NULL; channel = channel->next)
516 {
517 if (xmlStrcmp (channel->name, BAD_CAST "channel") == 0)
518 {
519 pNewItemList = _parse_rss_item (channel, pItem, pNewItemList); // on parse le channel comme un item, ce qui fait que le titre du flux est considere comme un simple item.
520 break; // un seul channel.
521 }
522 }
523 }
524 else if (xmlStrcmp (rss->name, BAD_CAST "RDF") == 0) // RDF
525 {
526 pNewItemList = _parse_rss_item (rss, pItem, pNewItemList); // on parse le premier groupe "channel" comme un item, ce qui fait que le titre du flux est considere comme un simple item.
527 }
528 else // Atom
529 {
530 xmlNodePtr feed = rss;
531 gchar *cBaseUrl = NULL; // on recupere la base de l'URL, pour le cas ou les link seraient exprimes relativement a elle.
532 gchar *str = g_strstr_len(myConfig.cUrl, 10, "://");
533 if (str)
534 {
535 str = strchr (str + 3, '/');
536 if (str)
537 cBaseUrl = g_strndup (myConfig.cUrl, (gpointer)str-(gpointer)myConfig.cUrl);
538 }
539 pNewItemList = _parse_atom_item (feed, pItem, pNewItemList, cBaseUrl); // on parse le feed comme un item, ce qui fait que le titre du flux est considere comme un simple item.
540 g_free (cBaseUrl);
541 }
542 pNewItemList = g_list_reverse (pNewItemList);
543
544 xmlFreeDoc (doc); // ne pas utiliser xmlCleanupParser dans un thread !
545
546 // si aucune donnee, on l'affiche et on quitte.
547 if (pNewItemList == NULL)
548 {
549 cd_debug ("RSS: aucune donnee");
550
551 const gchar *cErrorMessage = D_("No data");
552 _insert_error_message (myApplet, cErrorMessage);
553 if (myDesklet)
554 {
555 cd_applet_update_my_icon (myApplet);
556 }
557 g_free (myData.PrevFirstTitle);
558 myData.PrevFirstTitle = NULL;
559 myData.bUpdateIsManual = FALSE;
560 CD_APPLET_LEAVE (TRUE);
561 }
562
563 // si on est arrive a ce point, c'est qu'il n'y a pas eu d'erreur.
564 // on vide l'ancienne liste d'items.
565 cd_rssreader_free_item_list (myApplet);
566 myData.pItemList = pNewItemList;
567
568 // on met a jour le titre.
569 if (myIcon->cName == NULL) // il faut mettre a jour le titre
570 {
571 if (myDock && myConfig.cUserTitle == NULL) // en mode desklet inutile, le titre sera redessine avec le reste.
572 {
573 pItem = myData.pItemList->data;
574 if (pItem != NULL && pItem->cTitle != NULL)
575 CD_APPLET_SET_NAME_FOR_MY_ICON (pItem->cTitle);
576 }
577 }
578
579 // si aucun changement, on le signale, et si aucune erreur precedente, on quitte.
580 pItem = (myData.pItemList && myData.pItemList->next ? myData.pItemList->next->data : NULL);
581 gchar *cFirstTitle = (pItem ? pItem->cTitle : NULL);
582 if (! cairo_dock_strings_differ (myData.PrevFirstTitle, cFirstTitle))
583 {
584 cd_debug ("RSS: aucune modif");
585
586 if (myData.bUpdateIsManual) // L'update a ete manuel -> On affiche donc un dialogue meme s'il n'y a pas eu de changement
587 {
588 gldi_dialogs_remove_on_icon (myIcon);
589 gldi_dialog_show_temporary_with_icon (D_("No modification"),
590 myIcon,
591 myContainer,
592 2000, // Suffisant vu que la MaJ est manuelle
593 myDock ? "same icon" : MY_APPLET_SHARE_DATA_DIR"/"MY_APPLET_ICON_FILE);
594
595 myData.bUpdateIsManual = FALSE;
596 }
597
598 if (! myData.bError)
599 CD_APPLET_LEAVE (TRUE);
600 }
601
602 // on dessine le texte.
603 if (myDesklet)
604 {
605 cd_applet_update_my_icon (myApplet);
606 }
607
608 // on avertit l'utilisateur.
609 if (myData.PrevFirstTitle != NULL && myConfig.iNotificationType != 0)
610 {
611 if (myConfig.iNotificationType != 1)
612 {
613 gldi_dialogs_remove_on_icon (myIcon);
614 gldi_dialog_show_temporary_with_icon (D_("This RSS feed has been modified..."),
615 myIcon,
616 myContainer,
617 1000*myConfig.iNotificationDuration,
618 myDock ? "same icon" : MY_APPLET_SHARE_DATA_DIR"/"MY_APPLET_ICON_FILE);
619 }
620 if (myConfig.iNotificationType != 2)
621 {
622 CD_APPLET_DEMANDS_ATTENTION (myConfig.cNotificationAnimation, 3); /// myConfig.iNotificationDuration ?...
623 }
624 }
625
626 g_free (myData.PrevFirstTitle);
627 myData.PrevFirstTitle = g_strdup (cFirstTitle);
628 myData.bUpdateIsManual = FALSE;
629 myData.bError = FALSE;
630 CD_APPLET_LEAVE (TRUE);
631 }
632
633
_free_shared_memory(CDSharedMemory * pSharedMemory)634 static void _free_shared_memory (CDSharedMemory *pSharedMemory)
635 {
636 g_free (pSharedMemory->cUrl);
637 g_free (pSharedMemory->cUrlLogin);
638 g_free (pSharedMemory->cUrlPassword);
639 g_free (pSharedMemory->cTaskBridge);
640 g_free (pSharedMemory);
641 }
cd_rssreader_launch_task(GldiModuleInstance * myApplet)642 void cd_rssreader_launch_task (GldiModuleInstance *myApplet)
643 {
644 if (myData.pTask != NULL)
645 {
646 gldi_task_discard (myData.pTask);
647 myData.pTask = NULL;
648 }
649
650 CDSharedMemory *pSharedMemory = g_new0 (CDSharedMemory, 1);
651 pSharedMemory->cUrl = g_strdup (myConfig.cUrl);
652 pSharedMemory->cUrlLogin = g_strdup (myConfig.cUrlLogin);
653 pSharedMemory->cUrlPassword = g_strdup (myConfig.cUrlPassword);
654 pSharedMemory->pApplet = myApplet;
655
656 myData.pTask = gldi_task_new_full (myConfig.iRefreshTime,
657 (GldiGetDataAsyncFunc) _get_feeds,
658 (GldiUpdateSyncFunc) _update_from_feeds,
659 (GFreeFunc) _free_shared_memory,
660 pSharedMemory);
661 gldi_task_launch (myData.pTask);
662 }
663
664
665
_on_dialog_destroyed(GldiModuleInstance * myApplet)666 static void _on_dialog_destroyed (GldiModuleInstance *myApplet)
667 {
668 CD_APPLET_ENTER;
669 myData.pDialog = NULL;
670 CD_APPLET_LEAVE();
671 }
cd_rssreader_show_dialog(GldiModuleInstance * myApplet)672 void cd_rssreader_show_dialog (GldiModuleInstance *myApplet)
673 {
674 if (myData.pDialog != NULL) // on detruit le dialogue actuel.
675 {
676 gldi_object_unref (GLDI_OBJECT(myData.pDialog));
677 myData.pDialog = NULL;
678 return;
679 }
680 gldi_dialogs_remove_on_icon (myIcon); // on enleve tout autre dialogue (message d'erreur).
681
682 if (myData.pItemList != NULL && myData.pItemList->next != NULL && (myData.pItemList->next->next != NULL || ! myData.bError)) // on construit le dialogue contenant toutes les infos.
683 {
684 // On construit le widget GTK qui contient les lignes avec les liens.
685 GtkWidget *pVBox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); // le widget qu'on va inserer dans le dialogue.
686 GtkWidget *pScrolledWindow = gtk_scrolled_window_new (NULL, NULL);
687 g_object_set (pScrolledWindow, "height-request", 250, NULL);
688 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (pScrolledWindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
689 #if GTK_CHECK_VERSION (3, 8, 0)
690 gtk_container_add (GTK_CONTAINER (pScrolledWindow), pVBox);
691 #else
692 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (pScrolledWindow), pVBox);
693 #endif
694
695 PangoLayout *pLayout = pango_cairo_create_layout (myDrawContext);
696 PangoFontDescription *fd = pango_font_description_from_string ("");
697 pango_layout_set_font_description (pLayout, fd);
698
699 int w = MIN (600, g_desktopGeometry.Xscreen.width / g_desktopGeometry.iNbScreens / 2); // we don't know on which screen is place the container...
700 gchar *cLine;
701 GtkWidget *pLinkButton, *pAlign;
702 CDRssItem *pItem;
703 GList *it;
704 for (it = myData.pItemList->next; it != NULL; it = it->next)
705 {
706 pItem = it->data;
707 if (pItem->cTitle == NULL)
708 continue;
709
710 cLine = g_strdup (pItem->cTitle);
711 cd_rssreader_cut_line (cLine, pLayout, w);
712
713 if (pItem->cLink != NULL)
714 pLinkButton = gtk_link_button_new_with_label (pItem->cLink, cLine);
715 else
716 pLinkButton = gtk_label_new (cLine);
717 g_free (cLine);
718
719 pAlign = gtk_alignment_new (0., 0.5, 0., 0.);
720 gtk_container_add (GTK_CONTAINER (pAlign), pLinkButton);
721 gtk_box_pack_start (GTK_BOX (pVBox), pAlign, FALSE, FALSE, 0);
722
723 if (pItem->cDescription != NULL)
724 {
725 cLine = g_strdup (pItem->cDescription);
726 cd_rssreader_cut_line (cLine, pLayout, w);
727 pLinkButton = gtk_label_new (cLine);
728 gtk_label_set_selectable (GTK_LABEL (pLinkButton), TRUE);
729 g_free (cLine);
730
731 pAlign = gtk_alignment_new (0., 0.5, 0., 0.);
732 gtk_alignment_set_padding (GTK_ALIGNMENT (pAlign), 0, 0, 20, 0);
733 gtk_container_add (GTK_CONTAINER (pAlign), pLinkButton);
734 gtk_box_pack_start (GTK_BOX (pVBox), pAlign, FALSE, FALSE, 0);
735 }
736
737 if (pItem->cAuthor != NULL)
738 {
739 gchar *by = g_strdup_printf (" [by %s]", pItem->cAuthor);
740 pLinkButton = gtk_label_new (by);
741 g_free (by);
742
743 pAlign = gtk_alignment_new (0., 0.5, 0., 0.);
744 gtk_alignment_set_padding (GTK_ALIGNMENT (pAlign), 0, 0, 40, 0);
745 gtk_container_add (GTK_CONTAINER (pAlign), pLinkButton);
746 gtk_box_pack_start (GTK_BOX (pVBox), pAlign, FALSE, FALSE, 0);
747 }
748
749 if (pItem->cDate != NULL)
750 {
751 pLinkButton = gtk_label_new (pItem->cDate);
752
753 pAlign = gtk_alignment_new (1., 0.5, 0., 0.);
754 gtk_alignment_set_padding (GTK_ALIGNMENT (pAlign), 0, 0, 40, 0);
755 gtk_container_add (GTK_CONTAINER (pAlign), pLinkButton);
756 gtk_box_pack_start (GTK_BOX (pVBox), pAlign, FALSE, FALSE, 0);
757 }
758 }
759 pango_font_description_free (fd);
760
761 pItem = myData.pItemList->data; // le nom du flux en titre du dialogue.
762
763 // on affiche le dialogue.
764 myData.pDialog = gldi_dialog_show (pItem->cTitle,
765 myIcon, myContainer,
766 0,
767 myDock ? "same icon" : MY_APPLET_SHARE_DATA_DIR"/"MY_APPLET_ICON_FILE,
768 pScrolledWindow,
769 NULL,
770 myApplet,
771 (GFreeFunc)_on_dialog_destroyed);
772 /**g_signal_connect (G_OBJECT (myData.pDialog->container.pWidget),
773 "button-press-event",
774 G_CALLBACK (on_button_press_dialog),
775 myApplet);*/
776 }
777 else // on affiche un message clair a l'utilisateur.
778 {
779 if (myConfig.cUrl == NULL)
780 gldi_dialog_show_temporary_with_icon (D_("No URL is defined\nYou can define one by copying the URL in the clipboard,\n and selecting \"Paste the URL\" in the menu."),
781 myIcon,
782 myContainer,
783 1000*myConfig.iNotificationDuration,
784 myDock ? "same icon" : MY_APPLET_SHARE_DATA_DIR"/"MY_APPLET_ICON_FILE);
785 else
786 gldi_dialog_show_temporary_with_icon (D_("No data\nDid you set a valid RSS feed?\nIs your connection alive?"),
787 myIcon,
788 myContainer,
789 1000*myConfig.iNotificationDuration,
790 myDock ? "same icon" : MY_APPLET_SHARE_DATA_DIR"/"MY_APPLET_ICON_FILE);
791 }
792 }
793