& must be encoded as &
[prss.git] / src / main.c
1 /*
2  * Copyright (C) 2012-2013 jeanfi@gmail.com
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301 USA
18  */
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <getopt.h>
23
24 #include <sys/stat.h>
25 #include <sys/types.h>
26
27 #include <json/json.h>
28 #include <webkit/webkit.h>
29 #include <glib/gi18n.h>
30 #include <gtk/gtk.h>
31
32 #include <config.h>
33 #include "http.h"
34 #include <plog.h>
35 #include <pstr.h>
36 #include "ttrss.h"
37 #include "ttrss_ws.h"
38 #include "webbrowser.h"
39
40 static const char *program_name;
41 static GSettings *settings;
42
43 static GtkPaned *w_panel;
44 static GtkLabel *w_headline_title;
45 static GtkLabel *w_headline_date;
46 static GtkWindow *window;
47 static int model_state;
48
49 enum {
50         COL_FEED_TITLE,
51         COL_FEED_ID
52 };
53
54 enum {
55         COL_HEADLINE_TITLE,
56         COL_HEADLINE_ID,
57         COL_HEADLINE_DATE,
58         COL_HEADLINE_EPOCH_TIME
59 };
60
61 static struct option long_options[] = {
62         {"version", no_argument, 0, 'v'},
63         {"help", no_argument, 0, 'h'},
64         {"debug", required_argument, 0, 'd'},
65         {0, 0, 0, 0}
66 };
67
68 static void print_version()
69 {
70         printf("prss %s\n", VERSION);
71         printf(_("Copyright (C) %s jeanfi@gmail.com\n"
72                  "License GPLv2: GNU GPL version 2 or later "
73                  "<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>\n"
74                  "This is free software: you are free to change and "
75                  " redistribute it.\n"
76                  "There is NO WARRANTY, to the extent permitted by law.\n"),
77                "2012-2013");
78 }
79
80 static void print_help()
81 {
82         printf(_("Usage: %s [OPTION]...\n"), program_name);
83
84         puts(_("Prss is a GTK+ news reader for tt-rss."));
85
86         puts("");
87         puts(_("Options:"));
88         puts(_("  -h, --help          display this help and exit\n"
89                "  -v, --version       display version information and exit"));
90         puts("");
91         puts(_("  -d, --debug=LEVEL   "
92                "set the debug level, integer between 0 and 3"));
93
94         puts("");
95
96         printf(_("Report bugs to: %s\n"), PACKAGE_BUGREPORT);
97         puts("");
98         printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
99 }
100
101 GtkTreeView *w_feedtree;
102 GtkTreeView *w_headlinetree;
103
104 static char *feed_get_formated_title(struct feed *f)
105 {
106         if (f->unread)
107                 return g_strdup_printf("<b>%s (%d)</b>",
108                                        f->title,
109                                        f->unread);
110         else
111                 return strdup(f->title);
112 }
113
114 void update()
115 {
116         struct feed **feeds, **cur;
117         GtkListStore *model, *headline_model;
118         GtkTreeIter iter;
119         char *title;
120
121         model = GTK_LIST_STORE(gtk_tree_view_get_model(w_feedtree));
122         headline_model
123                 = GTK_LIST_STORE(gtk_tree_view_get_model(w_headlinetree));
124
125         log_debug("update(): clear feed tree.");
126         model_state = 1;
127         gtk_list_store_clear(model);
128         gtk_list_store_clear(headline_model);
129         log_debug("update(): clear feed tree done.");
130         feeds = ttrss_get_feeds();
131         cur = feeds;
132         while (cur && *cur) {
133                 title = feed_get_formated_title(*cur);
134
135                 gtk_list_store_append(model, &iter);
136
137                 gtk_list_store_set(model,
138                                    &iter,
139                                    COL_FEED_TITLE, title,
140                                    COL_FEED_ID, (*cur)->id,
141                                    -1);
142                 g_free(title);
143                 cur++;
144         }
145
146         if (feeds)
147                 ttrs_download_headline_content(feeds);
148
149         model_state = 0;
150
151         log_debug("update() done");
152 }
153
154 void update_layout()
155 {
156         int layout;
157
158         layout = g_settings_get_int(settings, "layout");
159
160         switch (layout) {
161         case 1:
162                 gtk_orientable_set_orientation(GTK_ORIENTABLE(w_panel),
163                                                GTK_ORIENTATION_HORIZONTAL);
164                 break;
165         case 0:
166         default:
167                 gtk_orientable_set_orientation(GTK_ORIENTABLE(w_panel),
168                                                GTK_ORIENTATION_VERTICAL);
169         }
170 }
171
172 void refresh_clicked_cbk(GtkWidget *btn, gpointer data)
173 {
174         log_debug("refresh_clicked_cbk()");
175         update();
176         log_debug("refresh_clicked_cbk() done");
177 }
178
179 void preferences_action_activate_cbk(GtkAction *action, gpointer data)
180 {
181         GtkDialog *diag;
182         GtkBuilder *builder;
183         GtkEntry *login, *url, *pwd;
184         GtkComboBox *layout;
185
186         log_debug("settings_activate_cbk()");
187
188         builder = gtk_builder_new();
189
190         gtk_builder_add_from_file
191                 (builder,
192                  PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "prss.glade",
193                  NULL);
194
195         diag = GTK_DIALOG(gtk_builder_get_object(builder, "settings_dialog"));
196
197         url = GTK_ENTRY(gtk_builder_get_object(builder, "url_entry"));
198         gtk_entry_set_text(url, g_settings_get_string(settings, "url"));
199
200         login = GTK_ENTRY(gtk_builder_get_object(builder, "login_entry"));
201         gtk_entry_set_text(login, g_settings_get_string(settings, "user"));
202
203         pwd = GTK_ENTRY(gtk_builder_get_object(builder, "password_entry"));
204         gtk_entry_set_text(pwd, g_settings_get_string(settings, "password"));
205
206         layout = GTK_COMBO_BOX(gtk_builder_get_object(builder,
207                                                       "layout"));
208
209         gtk_combo_box_set_active(layout, g_settings_get_int(settings,
210                                                             "layout"));
211
212         if (gtk_dialog_run(diag)) {
213                 g_settings_set_int(settings,
214                                    "layout",
215                                    gtk_combo_box_get_active(layout));
216
217                 g_settings_set_string(settings,
218                                       "password",
219                                       gtk_entry_get_text(pwd));
220                 g_settings_set_string(settings,
221                                       "url",
222                                       gtk_entry_get_text(url));
223                 g_settings_set_string(settings,
224                                       "user",
225                                       gtk_entry_get_text(login));
226
227                 ttrss_set_config(g_settings_get_string(settings, "url"),
228                                  g_settings_get_string(settings, "user"),
229                                  g_settings_get_string(settings, "password"));
230                 ws_open_session();
231
232                 update_layout();
233                 update();
234         }
235
236         gtk_widget_destroy(GTK_WIDGET(diag));
237
238         g_object_unref(G_OBJECT(builder));
239 }
240
241 static struct feed *get_selected_feed()
242 {
243         GtkTreePath *path;
244         GtkTreeViewColumn *cols;
245         GtkTreeIter iter;
246         GtkTreeModel *model;
247         int id;
248
249         gtk_tree_view_get_cursor(w_feedtree, &path, &cols);
250
251         if (path) {
252                 model = gtk_tree_view_get_model(w_feedtree);
253                 gtk_tree_model_get_iter(model, &iter, path);
254                 gtk_tree_model_get(model, &iter, 1, &id, -1);
255
256                 gtk_tree_path_free(path);
257
258                 return ttrss_get_feed(id);
259         }
260
261         return NULL;
262 }
263
264 static struct headline *get_selected_headline(GtkTreeIter *iter)
265 {
266         GtkTreePath *path;
267         GtkTreeViewColumn *cols;
268         GtkTreeModel *model;
269         int id;
270
271         gtk_tree_view_get_cursor(w_headlinetree, &path, &cols);
272
273         if (path) {
274                 model = gtk_tree_view_get_model(w_headlinetree);
275                 gtk_tree_model_get_iter(model, iter, path);
276                 gtk_tree_model_get(model, iter, 1, &id, -1);
277
278                 gtk_tree_path_free(path);
279
280                 return ttrss_get_headline(id);
281         }
282
283         return NULL;
284 }
285
286 static char *headline_to_markup(struct headline *h)
287 {
288         return strrepg(h->title, "&", "&amp;");
289 }
290
291 static char *headline_get_formated_headline(struct headline *h)
292 {
293         char *tmp, *res;
294
295         tmp = headline_to_markup(h);
296
297         if (h->unread)
298                 res = g_strdup_printf("<b>%s</b>", tmp);
299         else
300                 res = strdup(tmp);
301
302         free(tmp);
303
304         return res;
305 }
306
307 static char *headline_get_date(struct headline *h)
308 {
309         struct tm *tmp;
310         char date[200];
311
312         tmp = localtime(&h->date);
313
314         if (h->unread)
315                 strftime(date, sizeof(date), "<b>%D</b>", tmp);
316         else
317                 strftime(date, sizeof(date), "%D", tmp);
318
319         return strdup(date);
320 }
321
322 int feed_cursor_changed_cbk(GtkTreeView *treeview, gpointer data)
323 {
324         GtkTreeIter iter;
325         GtkTreeModel *headline_model;
326         GtkListStore *headline_store;
327         struct feed *feed;
328         struct headline **headlines, *h;
329         char *title, *date;
330
331         if (model_state)
332                 return TRUE;
333
334         log_debug("feed_cursor_changed_cbk()");
335
336         feed = get_selected_feed();
337
338         if (feed) {
339                 headline_model = gtk_tree_view_get_model(w_headlinetree);
340                 headline_store = GTK_LIST_STORE(headline_model);
341                 model_state = 1;
342                 gtk_list_store_clear(headline_store);
343
344                 if (feed) {
345                         headlines = ttrss_feed_get_headlines(feed);
346                         while (headlines && *headlines) {
347                                 gtk_list_store_append(headline_store, &iter);
348
349                                 h = *headlines;
350
351                                 title = headline_get_formated_headline(h);
352                                 date = headline_get_date(h);
353
354                                 gtk_list_store_set(headline_store,
355                                                    &iter,
356                                                    COL_HEADLINE_TITLE,
357                                                    title,
358                                                    COL_HEADLINE_ID,
359                                                    h->id,
360                                                    COL_HEADLINE_DATE,
361                                                    date,
362                                                    COL_HEADLINE_EPOCH_TIME,
363                                                    h->date,
364                                                    -1);
365
366                                 g_free(title);
367                                 free(date);
368
369                                 headlines++;
370                         }
371                 }
372
373                 model_state = 0;
374         }
375
376         return 1;
377 }
378
379 int headline_cursor_changed_cbk(GtkTreeView *treeview, gpointer data)
380 {
381         GtkTreeIter iter;
382         GtkTreeModel *model;
383         struct headline *headline;
384         char *str, *date, *title;
385
386         log_debug("headline_cursor_changed_cbk()");
387
388         if (model_state)
389                 return TRUE;
390
391         headline = get_selected_headline(&iter);
392
393         if (headline) {
394                 title = headline_to_markup(headline);
395                 gtk_label_set_markup(w_headline_title, title);
396
397                 gtk_label_set_text(w_headline_date, ctime(&headline->date));
398
399                 str = ttrss_get_headline_content(headline);
400
401                 if (str) {
402                         web_load(str);
403                         free(str);
404                 } else {
405                         web_load("");
406                 }
407
408                 if (headline->unread) {
409                         ttrss_set_article_unread(headline->id, 0);
410                         headline->unread = 0;
411                 }
412
413                 model = gtk_tree_view_get_model(w_headlinetree);
414                 date = headline_get_date(headline);
415                 gtk_list_store_set(GTK_LIST_STORE(model),
416                                    &iter,
417                                    COL_HEADLINE_TITLE, title,
418                                    COL_HEADLINE_DATE, date,
419                                    COL_HEADLINE_EPOCH_TIME, headline->date,
420                                    -1);
421                 free(date);
422                 free(title);
423         } else {
424                 log_debug("headline not found");
425         }
426
427         return TRUE;
428 }
429
430 void headline_row_activated_cbk(GtkWidget *widget, gpointer data)
431 {
432         GdkScreen *screen;
433         GError *error;
434         GtkTreeSelection *selection;
435         GtkTreeModel *model;
436         GtkTreeIter iter;
437         struct headline *h;
438         const char *url;
439         int id;
440
441         printf("headline_row_activated_cbk()");
442
443         selection = gtk_tree_view_get_selection(w_headlinetree);
444         model = gtk_tree_view_get_model(w_headlinetree);
445
446         if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
447                 gtk_tree_model_get(model, &iter, 1, &id, -1);
448
449                 h = ttrss_get_headline(id);
450                 if (h) {
451                         url = h->url;
452
453                         if (gtk_widget_has_screen(widget))
454                                 screen = gtk_widget_get_screen(widget);
455                         else
456                                 screen = gdk_screen_get_default();
457
458                         error = NULL;
459                         gtk_show_uri(screen, url,
460                                      gtk_get_current_event_time(), &error);
461                 }
462         }
463 }
464
465 static void save_settings()
466 {
467         int w, h, x, y;
468
469         gtk_window_get_size(window, &w, &h);
470         gtk_window_get_position(window, &x, &y);
471
472         log_debug("save_settings(): x=%d, y=%d, w=%d, h=%d", x, y, w, h);
473
474         g_settings_set_int(settings, "window-x", x);
475         g_settings_set_int(settings, "window-y", y);
476         g_settings_set_int(settings, "window-width", w);
477         g_settings_set_int(settings, "window-height", h);
478
479         g_settings_sync();
480 }
481
482 gboolean
483 window_delete_event_cbk(GtkWidget *widget, GdkEvent *event, gpointer data)
484 {
485         log_debug("window_delete_event_cbk");
486
487         save_settings();
488         gtk_main_quit();
489
490         return TRUE;
491 }
492
493 void quit_activate_cbk(GtkWidget *menu_item, gpointer data)
494 {
495         log_debug("quit_activate_cbk");
496
497         save_settings();
498         gtk_main_quit();
499 }
500
501 void help_activate_cbk(GtkWidget *menu_item, gpointer data)
502 {
503         log_debug("help_activate_cbk");
504
505         gtk_show_about_dialog
506                 (NULL,
507                  "comments",
508                  _("PRSS is a GTK+ client application for Tiny Tiny RSS"),
509                  "copyright",
510                  _("Copyright(c) 2010-2013\njeanfi@gmail.com"),
511                  "logo-icon-name", "prss",
512                  "program-name", "PRSS",
513                  "title", _("About PRSS"),
514                  "version", VERSION,
515                  "website", PACKAGE_URL,
516                  "website-label", _("PRSS Homepage"),
517                  NULL);
518 }
519
520 static void log_init()
521 {
522         char *home, *path, *dir;
523
524         home = getenv("HOME");
525
526         if (!home)
527                 return ;
528
529         dir = malloc(strlen(home)+1+strlen(".prss")+1);
530         sprintf(dir, "%s/%s", home, ".prss");
531         mkdir(dir, 0777);
532
533         path = malloc(strlen(dir)+1+strlen("log")+1);
534         sprintf(path, "%s/%s", dir, "log");
535
536         log_open(path);
537
538         free(dir);
539         free(path);
540 }
541
542 int main(int argc, char **argv)
543 {
544         GtkWidget *panh;
545         GtkBuilder *builder;
546         int optc, cmdok, opti;
547
548         program_name = argv[0];
549
550         setlocale(LC_ALL, "");
551
552 #if ENABLE_NLS
553         bindtextdomain(PACKAGE, LOCALEDIR);
554         textdomain(PACKAGE);
555 #endif
556
557         cmdok = 1;
558         while ((optc = getopt_long(argc, argv, "vhd:", long_options,
559                                    &opti)) != -1) {
560                 switch (optc) {
561                 case 'd':
562                         log_level = atoi(optarg);
563                         log_info(_("Enables debug mode."));
564                         break;
565                 case 'h':
566                         print_help();
567                         exit(EXIT_SUCCESS);
568                 case 'v':
569                         print_version();
570                         exit(EXIT_SUCCESS);
571                 default:
572                         cmdok = 0;
573                         break;
574                 }
575         }
576
577         if (!cmdok || optind != argc) {
578                 fprintf(stderr, _("Try `%s --help' for more information.\n"),
579                         program_name);
580                 exit(EXIT_FAILURE);
581         }
582
583         log_init();
584
585         ws_init();
586
587         gtk_init(NULL, NULL);
588         settings = g_settings_new("prss");
589
590         ttrss_set_config(g_settings_get_string(settings, "url"),
591                          g_settings_get_string(settings, "user"),
592                          g_settings_get_string(settings, "password"));
593         ws_open_session();
594
595         builder = gtk_builder_new();
596         gtk_builder_add_from_file
597                 (builder,
598                  PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "prss.glade",
599                  NULL);
600         window = GTK_WINDOW(gtk_builder_get_object(builder, "window"));
601
602         gtk_window_set_default_size
603                 (window,
604                  g_settings_get_int(settings, "window-width"),
605                  g_settings_get_int(settings, "window-height"));
606         gtk_window_move(window,
607                         g_settings_get_int(settings, "window-x"),
608                         g_settings_get_int(settings, "window-y"));
609
610         w_feedtree = GTK_TREE_VIEW(gtk_builder_get_object(builder,
611                                                           "feed_tree"));
612         w_headlinetree = GTK_TREE_VIEW(gtk_builder_get_object(builder,
613                                                               "headline_tree"));
614
615         w_panel = GTK_PANED(gtk_builder_get_object(builder, "paned2"));
616         w_headline_title
617                 = GTK_LABEL(gtk_builder_get_object(builder, "headline_title"));
618         w_headline_date
619                 = GTK_LABEL(gtk_builder_get_object(builder, "headline_date"));
620
621         gtk_builder_connect_signals(builder, NULL);
622
623         panh = GTK_WIDGET(gtk_builder_get_object(builder, "webkit_window"));
624
625         webbrowser_init(GTK_STATUSBAR(gtk_builder_get_object
626                                       (builder, "statusbar")));
627
628         gtk_container_add(GTK_CONTAINER(panh), web_get_widget());
629
630         g_object_unref(G_OBJECT(builder));
631
632         gtk_widget_show_all(GTK_WIDGET(window));
633         update_layout();
634
635         gtk_main();
636
637         exit(EXIT_SUCCESS);
638 }