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