1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3  * Pan - A Newsreader for Gtk+
4  * Copyright (C) 2002-2006  Charles Kerr <charles@rebelbase.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #include <config.h>
21 #include <ostream>
22 #include <fstream>
23 #include <iostream>
24 extern "C" {
25   #include <glib/gi18n.h>
26   #include "gtk-compat.h"
27 }
28 #include <pan/general/log.h>
29 #include <pan/general/macros.h>
30 #include <pan/general/string-view.h>
31 #include "log-ui.h"
32 #include "pad.h"
33 
34 using namespace pan;
35 
36 namespace
37 {
38   enum { COL_HIDDEN, COL_SEVERITY, COL_DATE, COL_MESSAGE, N_COLS };
39 
40   struct MyLogListener: private Log::Listener
41   {
42     GtkTreeStore * myStore;
43 
MyLogListener__anonfe89997a0111::MyLogListener44     MyLogListener (GtkTreeStore * store): myStore(store) {
45       Log::get().add_listener (this);
46     }
47 
~MyLogListener__anonfe89997a0111::MyLogListener48     ~MyLogListener () {
49       Log::get().remove_listener (this);
50     }
51 
on_log_entry_added__anonfe89997a0111::MyLogListener52     virtual void on_log_entry_added (const Log::Entry& e) {
53       GtkTreeIter iter;
54       gtk_tree_store_prepend (myStore, &iter, NULL);
55       gtk_tree_store_set (myStore, &iter,
56                           COL_HIDDEN, "",
57                           COL_SEVERITY, (e.severity & Log::PAN_SEVERITY_ERROR),
58                           COL_DATE, (unsigned long)e.date,
59                           COL_MESSAGE, &e, -1);
60        if (!e.messages.empty())
61        {
62         GtkTreeIter child;
63 
64         foreach_const (Log::entries_p, e.messages, lit)
65         {
66           Log::Entry entry(**lit);
67           gtk_tree_store_prepend (myStore, &child, &iter );
68           gtk_tree_store_set (myStore, &child,
69                           COL_HIDDEN, "",
70                           COL_SEVERITY, (entry.severity & Log::PAN_SEVERITY_ERROR),
71                           COL_DATE, (unsigned long)entry.date,
72                           COL_MESSAGE, &*lit, -1);
73         }
74       }
75     }
76 
on_log_cleared__anonfe89997a0111::MyLogListener77     virtual void on_log_cleared () {
78       gtk_tree_store_clear (myStore);
79     }
80   };
81 
delete_my_log_listener(gpointer object)82   void delete_my_log_listener (gpointer object)
83   {
84     delete (MyLogListener*) object;
85   }
86 }
87 
88 namespace
89 {
90   void
log_response_cb(GtkDialog * dialog,int response,gpointer)91   log_response_cb (GtkDialog * dialog, int response, gpointer )
92   {
93     if (response == GTK_RESPONSE_NO)
94     {
95       Log::get().clear ();
96     }
97     else if (response == GTK_RESPONSE_CLOSE)
98     {
99       gtk_widget_destroy (GTK_WIDGET(dialog));
100     }
101     else if (response == GTK_RESPONSE_APPLY)
102     {
103       GtkWidget * d = gtk_file_chooser_dialog_new (
104         _("Save Event List"),
105         GTK_WINDOW(dialog),
106         GTK_FILE_CHOOSER_ACTION_SAVE,
107         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
108         GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
109         NULL);
110       if (GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(d)))
111       {
112         char * fname = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (d));
113         const Log::entries_t& entries (Log::get().get_entries());
114         std::ofstream out (fname, std::ios_base::out|std::ios_base::trunc);
115         foreach_const (Log::entries_t, entries, it) {
116           StringView date (ctime (&it->date));
117           --date.len; // trim off the \n
118           out << date << " - " << it->message << '\n';
119         }
120         out.close ();
121         g_free (fname);
122       }
123       gtk_widget_destroy (d);
124     }
125   }
126 }
127 
128 namespace
129 {
to_string(std::deque<Log::Entry> d)130   std::string to_string(std::deque<Log::Entry> d)
131   {
132     std::string tmp;
133     foreach_const(std::deque<Log::Entry>, d, it)
134       tmp += it->message + "\n";
135     return tmp;
136   }
137 }
138 
139 namespace
140 {
141   GtkTreeStore*
create_model()142   create_model ()
143   {
144     GtkTreeStore * store = gtk_tree_store_new (N_COLS,
145                                                G_TYPE_STRING,
146                                                G_TYPE_BOOLEAN, // true==error, false==info
147                                                G_TYPE_ULONG, // date
148                                                G_TYPE_POINTER); // message
149 
150     const Log::entries_t& entries (Log::get().get_entries());
151     foreach_const (Log::entries_t, entries, it) {
152       GtkTreeIter   top, child, tmp;
153       gtk_tree_store_prepend (store, &top, 0);
154       gtk_tree_store_set (store, &top,
155                           COL_HIDDEN, "",
156                           COL_SEVERITY, (it->severity & Log::PAN_SEVERITY_ERROR),
157                           COL_DATE, (unsigned long)it->date,
158                           COL_MESSAGE, &*it, -1);
159       if (!it->messages.empty())
160       {
161         foreach_const (Log::entries_p, it->messages, lit)
162         {
163           Log::Entry entry (**lit);
164           gtk_tree_store_prepend (store, &child, &top );
165           gtk_tree_store_set (store, &child,
166                           COL_HIDDEN, "",
167                           COL_SEVERITY, (entry.severity & Log::PAN_SEVERITY_ERROR),
168                           COL_DATE, (unsigned long)entry.date,
169                           COL_MESSAGE, &*lit, -1);
170         }
171       }
172     }
173     return store;
174   }
175 }
176 
177 namespace
178 {
179   void
render_severity(GtkTreeViewColumn *,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer dialog)180   render_severity (GtkTreeViewColumn * ,
181                    GtkCellRenderer   * renderer,
182                    GtkTreeModel      * model,
183                    GtkTreeIter       * iter,
184                    gpointer            dialog)
185   {
186     gboolean severe (false);
187     gtk_tree_model_get (model, iter, COL_SEVERITY, &severe, -1);
188     const char * key (severe ? "pixbuf-error" : "pixbuf-info");
189     g_object_set (renderer, "pixbuf", g_object_get_data(G_OBJECT(dialog),key), NULL);
190   }
191 
192   void
render_date(GtkTreeViewColumn *,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer)193   render_date (GtkTreeViewColumn * ,
194                GtkCellRenderer   * renderer,
195                GtkTreeModel      * model,
196                GtkTreeIter       * iter,
197                gpointer            )
198   {
199     unsigned long date_ul;
200     gtk_tree_model_get (model, iter, COL_DATE, &date_ul, -1);
201     time_t date_t (date_ul);
202     std::string s = ctime (&date_t);
203     s.resize (s.size()-1); // remove \n
204     g_object_set (renderer, "text", s.c_str(), NULL);
205   }
206 
207     void
render_message(GtkTreeViewColumn *,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer)208   render_message (GtkTreeViewColumn * ,
209                   GtkCellRenderer   * renderer,
210                   GtkTreeModel      * model,
211                   GtkTreeIter       * iter,
212                   gpointer            )
213   {
214     Log::Entry* log_entry(0);
215     gtk_tree_model_get (model, iter, COL_MESSAGE, &log_entry, -1);
216     bool bold (log_entry->is_child);
217     g_object_set (renderer,
218                   "text", log_entry ? log_entry->message.c_str() : "",
219                   "weight", bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
220                   NULL);
221   }
222 
223 
224 }
225 
226 gboolean
on_button_pressed(GtkWidget * treeview,GdkEventButton * event,gpointer userdata)227 pan :: on_button_pressed (GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
228 {
229   // single click with the right mouse button?
230   if (event->type == GDK_BUTTON_PRESS && event->button == 3)
231   {
232     GtkTreeSelection * selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
233     GtkTreePath * path;
234     if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW(treeview),
235                                        (gint)event->x, (gint)event->y,
236                                        &path, NULL, NULL, NULL))
237     {
238       if (!gtk_tree_selection_path_is_selected (selection, path))
239       {
240         gtk_tree_selection_unselect_all (selection);
241         gtk_tree_selection_select_path (selection, path);
242       }
243     }
244     const bool expanded (gtk_tree_view_row_expanded (GTK_TREE_VIEW(treeview), path));
245     if (expanded)
246       gtk_tree_view_collapse_row(GTK_TREE_VIEW(treeview),path);
247     else
248       gtk_tree_view_expand_row (GTK_TREE_VIEW(treeview),path,false);
249     gtk_tree_path_free (path);
250     return true;
251   }
252   return false;
253 }
254 
255 GtkWidget*
log_dialog_new(Prefs & prefs,GtkWindow * window)256 pan :: log_dialog_new (Prefs& prefs, GtkWindow* window)
257 {
258   GtkWidget * dialog = gtk_dialog_new_with_buttons (_("Pan: Events"),
259                                                     window,
260                                                     GTK_DIALOG_DESTROY_WITH_PARENT,
261                                                     GTK_STOCK_CLEAR, GTK_RESPONSE_NO,
262                                                     GTK_STOCK_SAVE, GTK_RESPONSE_APPLY,
263                                                     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
264                                                     NULL);
265   g_signal_connect (dialog, "response", G_CALLBACK(log_response_cb), NULL);
266 
267   GtkIconTheme * theme = gtk_icon_theme_get_default ();
268   GdkPixbuf * err_pixbuf = gtk_icon_theme_load_icon (theme, GTK_STOCK_DIALOG_ERROR, 20, (GtkIconLookupFlags)0, NULL);
269   g_object_set_data_full (G_OBJECT(dialog), "pixbuf-error", err_pixbuf, g_object_unref);
270   GdkPixbuf * info_pixbuf = gtk_icon_theme_load_icon (theme, GTK_STOCK_DIALOG_INFO, 20, (GtkIconLookupFlags)0, NULL);
271   g_object_set_data_full (G_OBJECT(dialog), "pixbuf-info", info_pixbuf, g_object_unref);
272 
273   GtkTreeStore * store = create_model ();
274   GtkWidget * view = gtk_tree_view_new_with_model (GTK_TREE_MODEL(store));
275 
276   gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(view),false);
277 
278   g_object_set_data_full (G_OBJECT(view), "listener", new MyLogListener(store), delete_my_log_listener);
279   GtkWidget * scroll = gtk_scrolled_window_new (0, 0);
280   gtk_container_set_border_width (GTK_CONTAINER(scroll), PAD_BIG);
281   gtk_container_add (GTK_CONTAINER(scroll), view);
282 
283   GtkCellRenderer * pixbuf_renderer = gtk_cell_renderer_pixbuf_new ();
284   GtkCellRenderer * text_renderer = gtk_cell_renderer_text_new ();
285 
286   /* placeholder for expander */
287   GtkTreeViewColumn * col = gtk_tree_view_column_new ();
288   gtk_tree_view_column_set_resizable (col, false);
289   gtk_tree_view_append_column (GTK_TREE_VIEW(view), col);
290   gtk_tree_view_column_set_visible(col,false);
291   gtk_tree_view_set_expander_column(GTK_TREE_VIEW(view), col);
292 
293   // severity
294   col = gtk_tree_view_column_new ();
295   gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_FIXED);
296   gtk_tree_view_column_set_fixed_width (col, 35);
297   gtk_tree_view_column_set_resizable (col, false);
298   gtk_tree_view_column_pack_start (col, pixbuf_renderer, false);
299   gtk_tree_view_column_set_cell_data_func (col, pixbuf_renderer, render_severity, dialog, 0);
300   gtk_tree_view_column_set_sort_column_id (col, COL_SEVERITY);
301   gtk_tree_view_append_column (GTK_TREE_VIEW(view), col);
302 
303   // date
304   col = gtk_tree_view_column_new ();
305   gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
306   gtk_tree_view_column_set_sort_column_id (col, COL_DATE);
307   gtk_tree_view_column_set_title (col, _("Date"));
308   gtk_tree_view_column_pack_start (col, text_renderer, false);
309   gtk_tree_view_column_set_cell_data_func (col, text_renderer, render_date, 0, 0);
310   gtk_tree_view_append_column (GTK_TREE_VIEW(view), col);
311 
312   // message
313   text_renderer = gtk_cell_renderer_text_new ();
314   col = gtk_tree_view_column_new ();
315   gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
316   gtk_tree_view_column_set_sort_column_id (col, COL_MESSAGE);
317   gtk_tree_view_column_set_title (col, _("Message"));
318   gtk_tree_view_column_pack_start (col, text_renderer, true);
319   gtk_tree_view_column_set_cell_data_func (col, text_renderer, render_message, 0, 0);
320   gtk_tree_view_append_column (GTK_TREE_VIEW(view), col);
321   gtk_tree_view_set_expander_column(GTK_TREE_VIEW(view), col);
322 
323   gtk_widget_show (view);
324   gtk_widget_show (scroll);
325   pan_box_pack_start_defaults (GTK_BOX(gtk_dialog_get_content_area( GTK_DIALOG(dialog))), scroll);
326 
327   gtk_window_set_role (GTK_WINDOW(dialog), "pan-events-dialog");
328   prefs.set_window ("events-window", GTK_WINDOW(dialog), 150, 150, 600, 300);
329   gtk_window_set_resizable (GTK_WINDOW(dialog), true);
330   if (window != 0)
331     gtk_window_set_transient_for (GTK_WINDOW(dialog), window);
332 
333   g_signal_connect (view, "button-press-event", G_CALLBACK(on_button_pressed), view);
334 
335   return dialog;
336 }
337