Updated Spanish translation
[anjuta-git-plugin.git] / plugins / message-view / message-view.c
blobf40a8ca6cb75ce980683f7e45965e35340650106
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /* (c) Johannes Schmid 2003
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Library General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 #include <libgnomevfs/gnome-vfs.h>
20 #include <libanjuta/anjuta-utils.h>
21 #include <libanjuta/anjuta-debug.h>
22 #include <libanjuta/interfaces/ianjuta-message-view.h>
24 #include "message-view.h"
25 #define MESSAGE_TYPE message_get_type()
27 struct _MessageViewPrivate
29 //guint num_messages;
30 gchar *line_buffer;
32 GtkWidget *tree_view;
34 AnjutaPreferences* prefs;
35 GtkWidget *popup_menu;
37 gint adj_chgd_hdlr;
39 /* Properties */
40 gchar *label;
41 gchar *pixmap;
42 gboolean highlite;
44 GdkRectangle tooltip_rect;
45 GtkWidget *tooltip_window;
46 gulong tooltip_timeout;
47 PangoLayout *tooltip_layout;
49 /* gconf notification ids */
50 GList *gconf_notify_ids;
53 typedef struct
55 IAnjutaMessageViewType type;
56 gchar *summary;
57 gchar *details;
59 } Message;
61 enum
63 COLUMN_COLOR = 0,
64 COLUMN_SUMMARY,
65 COLUMN_MESSAGE,
66 COLUMN_PIXBUF,
67 N_COLUMNS
70 enum
72 MV_PROP_ID = 0,
73 MV_PROP_LABEL,
74 MV_PROP_PIXMAP,
75 MV_PROP_HIGHLITE
78 static gpointer parent_class;
80 static void prefs_init (MessageView *mview);
81 static void prefs_finalize (MessageView *mview);
83 /* Ask the user for an uri name */
84 static gchar *
85 ask_user_for_save_uri (GtkWindow* parent)
87 GtkWidget* dialog;
88 gchar* uri;
90 dialog = gtk_file_chooser_dialog_new (_("Save file as"), parent,
91 GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
92 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
94 if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
96 uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
98 else
100 uri = NULL;
103 gtk_widget_destroy(dialog);
105 return uri;
108 /* Message object creation, copy and freeing */
109 static Message*
110 message_new (IAnjutaMessageViewType type, const gchar *summary,
111 const gchar *details)
113 /* DEBUG_PRINT ("Creating message"); */
114 Message *message = g_new0 (Message, 1);
115 message->type = type;
116 if (summary)
117 message->summary = g_strdup (summary);
118 if (details)
119 message->details = g_strdup (details);
120 return message;
123 static Message*
124 message_copy (const Message *src)
126 return message_new (src->type, src->summary, src->details);
129 static void
130 message_free (Message *message)
132 /* DEBUG_PRINT ("Freeing message"); */
133 g_free (message->summary);
134 g_free (message->details);
135 g_free (message);
138 static gboolean
139 message_serialize (Message *message, AnjutaSerializer *serializer)
141 if (!anjuta_serializer_write_int (serializer, "type",
142 message->type))
143 return FALSE;
144 if (!anjuta_serializer_write_string (serializer, "summary",
145 message->summary))
146 return FALSE;
147 if (!anjuta_serializer_write_string (serializer, "details",
148 message->details))
149 return FALSE;
150 return TRUE;
153 static gboolean
154 message_deserialize (Message *message, AnjutaSerializer *serializer)
156 gint type;
157 if (!anjuta_serializer_read_int (serializer, "type",
158 &type))
159 return FALSE;
160 message->type = type;
161 if (!anjuta_serializer_read_string (serializer, "summary",
162 &message->summary, TRUE))
163 return FALSE;
164 if (!anjuta_serializer_read_string (serializer, "details",
165 &message->details, TRUE))
166 return FALSE;
167 return TRUE;
170 static GType
171 message_get_type ()
173 static GType type = 0;
174 if (!type)
176 type = g_boxed_type_register_static ("MessageViewMessage",
177 (GBoxedCopyFunc) message_copy,
178 (GBoxedFreeFunc) message_free);
180 return type;
183 /* Utility functions */
184 /* Adds the char c to the string str */
185 static void
186 add_char(gchar** str, gchar c)
188 gchar* buffer;
190 g_return_if_fail(str != NULL);
192 buffer = g_strdup_printf("%s%c", *str, c);
193 g_free(*str);
194 *str = buffer;
197 static gchar*
198 escape_string (const gchar *str)
200 GString *gstr;
201 const gchar *iter;
203 gstr = g_string_new ("");
204 iter = str;
205 while (*iter != '\0')
207 if (*iter == '>')
208 gstr = g_string_append (gstr, "&gt;");
209 else if (*iter == '<')
210 gstr = g_string_append (gstr, "&lt;");
211 else if (*iter == '&')
212 gstr = g_string_append (gstr, "&amp;");
213 else
214 gstr = g_string_append_c (gstr, *iter);
215 iter++;
217 return g_string_free (gstr, FALSE);
220 /* Tooltip operations -- taken from gtodo */
222 static gchar *
223 tooltip_get_display_text (MessageView *view)
225 GtkTreePath *path;
226 GtkTreeIter iter;
227 GtkTreeModel *model;
229 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->privat->tree_view));
231 if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW(view->privat->tree_view),
232 view->privat->tooltip_rect.x, view->privat->tooltip_rect.y,
233 &path, NULL, NULL, NULL))
235 Message *message;
236 gchar *text, *title, *desc;
238 gtk_tree_model_get_iter (model, &iter, path);
239 gtk_tree_model_get (model, &iter, COLUMN_MESSAGE, &message, -1);
240 gtk_tree_path_free(path);
242 if (!message->details || !message->summary ||
243 strlen (message->details) <= 0 ||
244 strlen (message->summary) <= 0)
245 return NULL;
247 title = escape_string (message->summary);
248 desc = escape_string (message->details);
249 text = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
251 g_free (title);
252 g_free (desc);
254 return text;
256 return NULL;
259 static void
260 tooltip_paint (GtkWidget *widget, GdkEventExpose *event, MessageView *view)
262 GtkStyle *style;
263 gchar *tooltiptext;
265 tooltiptext = tooltip_get_display_text (view);
267 if (!tooltiptext)
268 tooltiptext = g_strdup (_("No message details"));
270 pango_layout_set_markup (view->privat->tooltip_layout,
271 tooltiptext,
272 strlen (tooltiptext));
273 pango_layout_set_wrap(view->privat->tooltip_layout, PANGO_WRAP_CHAR);
274 pango_layout_set_width(view->privat->tooltip_layout, 600000);
275 style = view->privat->tooltip_window->style;
277 gtk_paint_flat_box (style, view->privat->tooltip_window->window,
278 GTK_STATE_NORMAL, GTK_SHADOW_OUT,
279 NULL, view->privat->tooltip_window,
280 "tooltip", 0, 0, -1, -1);
282 gtk_paint_layout (style, view->privat->tooltip_window->window,
283 GTK_STATE_NORMAL, TRUE,
284 NULL, view->privat->tooltip_window,
285 "tooltip", 4, 4, view->privat->tooltip_layout);
287 g_object_unref(layout);
289 g_free(tooltiptext);
290 return;
293 static gboolean
294 tooltip_timeout (MessageView *view)
296 gint scr_w,scr_h, w, h, x, y;
297 gchar *tooltiptext;
299 tooltiptext = tooltip_get_display_text (view);
301 if (!tooltiptext)
302 tooltiptext = g_strdup (_("No message details"));
304 view->privat->tooltip_window = gtk_window_new (GTK_WINDOW_POPUP);
305 view->privat->tooltip_window->parent = view->privat->tree_view;
306 gtk_widget_set_app_paintable (view->privat->tooltip_window, TRUE);
307 gtk_window_set_resizable (GTK_WINDOW(view->privat->tooltip_window), FALSE);
308 gtk_widget_set_name (view->privat->tooltip_window, "gtk-tooltips");
309 g_signal_connect (G_OBJECT(view->privat->tooltip_window), "expose_event",
310 G_CALLBACK(tooltip_paint), view);
311 gtk_widget_ensure_style (view->privat->tooltip_window);
313 view->privat->tooltip_layout =
314 gtk_widget_create_pango_layout (view->privat->tooltip_window, NULL);
315 pango_layout_set_wrap (view->privat->tooltip_layout, PANGO_WRAP_CHAR);
316 pango_layout_set_width (view->privat->tooltip_layout, 600000);
317 pango_layout_set_markup (view->privat->tooltip_layout, tooltiptext,
318 strlen (tooltiptext));
319 scr_w = gdk_screen_width();
320 scr_h = gdk_screen_height();
321 pango_layout_get_size (view->privat->tooltip_layout, &w, &h);
322 w = PANGO_PIXELS(w) + 8;
323 h = PANGO_PIXELS(h) + 8;
325 gdk_window_get_pointer (NULL, &x, &y, NULL);
326 if (GTK_WIDGET_NO_WINDOW (view->privat->tree_view))
327 y += view->privat->tree_view->allocation.y;
329 x -= ((w >> 1) + 4);
331 if ((x + w) > scr_w)
332 x -= (x + w) - scr_w;
333 else if (x < 0)
334 x = 0;
336 if ((y + h + 4) > scr_h)
337 y = y - h;
338 else
339 y = y + 6;
341 g_object_unref(layout);
343 gtk_widget_set_size_request (view->privat->tooltip_window, w, h);
344 gtk_window_move (GTK_WINDOW (view->privat->tooltip_window), x, y);
345 gtk_widget_show (view->privat->tooltip_window);
346 g_free (tooltiptext);
348 return FALSE;
351 static gboolean
352 tooltip_motion_cb (GtkWidget *tv, GdkEventMotion *event, MessageView *view)
354 GtkTreePath *path;
356 if (view->privat->tooltip_rect.y == 0 &&
357 view->privat->tooltip_rect.height == 0 &&
358 view->privat->tooltip_timeout)
360 g_source_remove (view->privat->tooltip_timeout);
361 view->privat->tooltip_timeout = 0;
362 if (view->privat->tooltip_window) {
363 gtk_widget_destroy (view->privat->tooltip_window);
364 view->privat->tooltip_window = NULL;
366 return FALSE;
368 if (view->privat->tooltip_timeout) {
369 if (((int)event->y > view->privat->tooltip_rect.y) &&
370 (((int)event->y - view->privat->tooltip_rect.height)
371 < view->privat->tooltip_rect.y))
372 return FALSE;
374 if(event->y == 0)
376 g_source_remove (view->privat->tooltip_timeout);
377 view->privat->tooltip_timeout = 0;
378 return FALSE;
380 /* We've left the cell. Remove the timeout and create a new one below */
381 if (view->privat->tooltip_window) {
382 gtk_widget_destroy (view->privat->tooltip_window);
383 view->privat->tooltip_window = NULL;
385 g_source_remove (view->privat->tooltip_timeout);
386 view->privat->tooltip_timeout = 0;
389 if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW(view->privat->tree_view),
390 event->x, event->y, &path,
391 NULL, NULL, NULL))
393 GtkTreeSelection *selection;
395 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view->privat->tree_view));
396 if (gtk_tree_selection_path_is_selected (selection, path))
398 gtk_tree_view_get_cell_area (GTK_TREE_VIEW (view->privat->tree_view),
399 path, NULL, &view->privat->tooltip_rect);
401 if (view->privat->tooltip_rect.y != 0 &&
402 view->privat->tooltip_rect.height != 0)
404 gchar *tooltiptext;
406 tooltiptext = tooltip_get_display_text (view);
407 if (tooltiptext == NULL)
408 return FALSE;
409 g_free (tooltiptext);
411 view->privat->tooltip_timeout =
412 g_timeout_add (500, (GSourceFunc) tooltip_timeout, view);
415 gtk_tree_path_free (path);
417 return FALSE;
420 static void
421 tooltip_leave_cb (GtkWidget *w, GdkEventCrossing *e, MessageView *view)
423 if (view->privat->tooltip_timeout) {
424 g_source_remove (view->privat->tooltip_timeout);
425 view->privat->tooltip_timeout = 0;
427 if (view->privat->tooltip_window) {
428 gtk_widget_destroy (view->privat->tooltip_window);
429 g_object_unref (view->privat->tooltip_layout);
430 view->privat->tooltip_window = NULL;
434 /* MessageView signal callbacks */
435 /* Send a signal if a message was double-clicked or ENTER or SPACE was pressed */
436 static gboolean
437 on_message_event (GObject* object, GdkEvent* event, gpointer data)
439 g_return_val_if_fail(object != NULL, FALSE);
440 g_return_val_if_fail(event != NULL, FALSE);
441 g_return_val_if_fail(data != NULL, FALSE);
443 MessageView* view = MESSAGE_VIEW(data);
445 if (event == NULL)
446 return FALSE;
448 if (event->type == GDK_KEY_PRESS)
450 switch(((GdkEventKey *)event)->keyval)
452 case GDK_space:
453 case GDK_Return:
455 const gchar* message =
456 ianjuta_message_view_get_current_message(IANJUTA_MESSAGE_VIEW (view), NULL);
457 if (message)
459 g_signal_emit_by_name (G_OBJECT (view), "message_clicked",
460 message);
461 return TRUE;
463 break;
465 default:
466 return FALSE;
469 else if (event->type == GDK_2BUTTON_PRESS)
471 if (((GdkEventButton *) event)->button == 1)
473 const gchar* message =
474 ianjuta_message_view_get_current_message(IANJUTA_MESSAGE_VIEW (view), NULL);
475 if (message)
477 g_signal_emit_by_name (G_OBJECT (view), "message_clicked",
478 message);
479 return TRUE;
482 return FALSE;
484 else if (event->type == GDK_BUTTON_PRESS)
486 if (((GdkEventButton *) event)->button == 3)
488 gtk_menu_popup (GTK_MENU (view->privat->popup_menu), NULL, NULL, NULL, NULL,
489 ((GdkEventButton *) event)->button,
490 ((GdkEventButton *) event)->time);
491 return TRUE;
494 return FALSE;
497 static void
498 on_adjustment_changed (GtkAdjustment* adj, gpointer data)
500 gtk_adjustment_set_value (adj, adj->upper - adj->page_size);
503 static void
504 on_adjustment_value_changed (GtkAdjustment* adj, gpointer data)
506 MessageView *self = MESSAGE_VIEW (data);
507 if (adj->value > (adj->upper - adj->page_size) - 0.1)
509 if (!self->privat->adj_chgd_hdlr)
511 self->privat->adj_chgd_hdlr =
512 g_signal_connect (G_OBJECT (adj), "changed",
513 G_CALLBACK (on_adjustment_changed), NULL);
516 else
518 if (self->privat->adj_chgd_hdlr)
520 g_signal_handler_disconnect (G_OBJECT (adj), self->privat->adj_chgd_hdlr);
521 self->privat->adj_chgd_hdlr = 0;
526 static void
527 message_view_set_property (GObject * object,
528 guint property_id,
529 const GValue * value, GParamSpec * pspec)
531 MessageView *self = MESSAGE_VIEW (object);
532 g_return_if_fail(value != NULL);
533 g_return_if_fail(pspec != NULL);
535 switch (property_id)
537 case MV_PROP_LABEL:
539 g_free (self->privat->label);
540 self->privat->label = g_value_dup_string (value);
541 break;
543 case MV_PROP_PIXMAP:
545 g_free (self->privat->pixmap);
546 self->privat->pixmap = g_value_dup_string (value);
547 break;
549 case MV_PROP_HIGHLITE:
551 self->privat->highlite = g_value_get_boolean (value);
552 break;
554 default:
556 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
557 break;
562 static void
563 message_view_get_property (GObject * object,
564 guint property_id,
565 GValue * value, GParamSpec * pspec)
567 MessageView *self = MESSAGE_VIEW (object);
569 g_return_if_fail(value != NULL);
570 g_return_if_fail(pspec != NULL);
572 switch (property_id)
574 case MV_PROP_LABEL:
576 g_value_set_string (value, self->privat->label);
577 break;
579 case MV_PROP_PIXMAP:
581 g_value_set_string (value, self->privat->pixmap);
582 break;
584 case MV_PROP_HIGHLITE:
586 g_value_set_boolean (value, self->privat->highlite);
587 break;
589 default:
591 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
592 break;
597 static void
598 message_view_dispose (GObject *obj)
600 MessageView *mview = MESSAGE_VIEW (obj);
601 if (mview->privat->gconf_notify_ids)
603 prefs_finalize (mview);
604 mview->privat->gconf_notify_ids = NULL;
606 if (mview->privat->tooltip_timeout) {
607 g_source_remove (mview->privat->tooltip_timeout);
608 mview->privat->tooltip_timeout = 0;
610 if (mview->privat->tooltip_window) {
611 gtk_widget_destroy (mview->privat->tooltip_window);
612 g_object_unref (mview->privat->tooltip_layout);
613 mview->privat->tooltip_window = NULL;
615 if (mview->privat->tree_view)
617 mview->privat->tree_view = NULL;
619 GNOME_CALL_PARENT (G_OBJECT_CLASS, dispose, (G_OBJECT(obj)));
622 static void
623 message_view_finalize (GObject *obj)
625 MessageView *mview = MESSAGE_VIEW (obj);
626 g_free (mview->privat->line_buffer);
627 g_free (mview->privat->label);
628 g_free (mview->privat->pixmap);
629 g_free (mview->privat);
630 GNOME_CALL_PARENT (G_OBJECT_CLASS, finalize, (G_OBJECT(obj)));
633 static void
634 message_view_instance_init (MessageView * self)
636 GtkWidget *scrolled_win;
637 GtkCellRenderer *renderer;
638 GtkCellRenderer *renderer_pixbuf;
639 GtkTreeViewColumn *column;
640 GtkTreeViewColumn *column_pixbuf;
641 GtkTreeSelection *select;
642 GtkListStore *model;
643 GtkAdjustment* adj;
645 g_return_if_fail(self != NULL);
646 self->privat = g_new0 (MessageViewPrivate, 1);
648 /* Init private data */
649 self->privat->line_buffer = g_strdup("");
651 /* Create the tree widget */
652 model = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING,
653 G_TYPE_STRING, MESSAGE_TYPE, G_TYPE_STRING);
654 self->privat->tree_view =
655 gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
656 gtk_widget_show (self->privat->tree_view);
657 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW
658 (self->privat->tree_view), FALSE);
660 /* Create pixbuf column */
661 renderer_pixbuf = gtk_cell_renderer_pixbuf_new ();
662 column_pixbuf = gtk_tree_view_column_new ();
663 gtk_tree_view_column_set_title (column_pixbuf, _("Icon"));
664 gtk_tree_view_column_pack_start (column_pixbuf, renderer_pixbuf, TRUE);
665 gtk_tree_view_column_add_attribute
666 (column_pixbuf, renderer_pixbuf, "stock-id", COLUMN_PIXBUF);
667 gtk_tree_view_append_column (GTK_TREE_VIEW (self->privat->tree_view),
668 column_pixbuf);
669 /* Create columns to hold text and color of a line, this
670 * columns are invisible of course. */
671 renderer = gtk_cell_renderer_text_new ();
672 g_object_set (renderer, "yalign", 0.0, "wrap-mode", PANGO_WRAP_WORD,
673 "wrap-width", 1000, NULL);
674 column = gtk_tree_view_column_new ();
675 gtk_tree_view_column_pack_start (column, renderer, TRUE);
676 gtk_tree_view_column_set_title (column, _("Messages"));
677 gtk_tree_view_column_add_attribute
678 (column, renderer, "foreground", COLUMN_COLOR);
679 gtk_tree_view_column_add_attribute
680 (column, renderer, "markup", COLUMN_SUMMARY);
681 gtk_tree_view_append_column (GTK_TREE_VIEW (self->privat->tree_view),
682 column);
684 /* Adjust selection */
685 select = gtk_tree_view_get_selection
686 (GTK_TREE_VIEW (self->privat->tree_view));
687 gtk_tree_selection_set_mode (select, GTK_SELECTION_BROWSE);
689 /* Add tree view to a scrolled window */
690 scrolled_win = gtk_scrolled_window_new (NULL, NULL);
691 gtk_container_add (GTK_CONTAINER (scrolled_win),
692 self->privat->tree_view);
693 gtk_widget_show (scrolled_win);
694 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
695 GTK_POLICY_AUTOMATIC,
696 GTK_POLICY_AUTOMATIC);
697 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW
698 (scrolled_win));
699 self->privat->adj_chgd_hdlr = g_signal_connect(G_OBJECT(adj), "changed",
700 G_CALLBACK (on_adjustment_changed), self);
701 g_signal_connect(G_OBJECT(adj), "value_changed",
702 G_CALLBACK(on_adjustment_value_changed), self);
704 /* Add it to the dockitem */
705 gtk_container_add (GTK_CONTAINER (self), scrolled_win);
707 /* Connect signals */
708 g_signal_connect (G_OBJECT(self->privat->tree_view), "event",
709 G_CALLBACK (on_message_event), self);
710 g_signal_connect (G_OBJECT (self->privat->tree_view), "motion-notify-event",
711 G_CALLBACK (tooltip_motion_cb), self);
712 g_signal_connect (G_OBJECT (self->privat->tree_view), "leave-notify-event",
713 G_CALLBACK (tooltip_leave_cb), self);
714 g_object_unref (model);
717 static void
718 message_view_class_init (MessageViewClass * klass)
720 GParamSpec *message_view_spec_label;
721 GParamSpec *message_view_spec_pixmap;
722 GParamSpec *message_view_spec_highlite;
723 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
725 parent_class = g_type_class_peek_parent (klass);
726 gobject_class->set_property = message_view_set_property;
727 gobject_class->get_property = message_view_get_property;
728 gobject_class->finalize = message_view_finalize;
729 gobject_class->dispose = message_view_dispose;
731 message_view_spec_label = g_param_spec_string ("label",
732 "Label of the view",
733 "Used to decorate the view,"
734 "translateable",
735 "no label",
736 G_PARAM_READWRITE);
737 g_object_class_install_property (gobject_class,
738 MV_PROP_LABEL,
739 message_view_spec_label);
741 message_view_spec_pixmap = g_param_spec_string ("pixmap",
742 "Pixmap of the view",
743 "Used to decorate the view tab,"
744 "translateable",
745 "no label",
746 G_PARAM_READWRITE);
747 g_object_class_install_property (gobject_class,
748 MV_PROP_PIXMAP,
749 message_view_spec_pixmap);
751 message_view_spec_highlite = g_param_spec_boolean ("highlite",
752 "Highlite build messages",
753 "If TRUE, specify colors",
754 FALSE,
755 G_PARAM_READWRITE);
756 g_object_class_install_property (gobject_class,
757 MV_PROP_HIGHLITE,
758 message_view_spec_highlite);
761 /* Returns a new message-view instance */
762 GtkWidget *
763 message_view_new (AnjutaPreferences* prefs, GtkWidget* popup_menu)
765 MessageView * mv = MESSAGE_VIEW (g_object_new (message_view_get_type (), NULL));
766 mv->privat->prefs = prefs;
767 mv->privat->popup_menu = popup_menu;
768 prefs_init (mv);
769 return GTK_WIDGET(mv);
772 gboolean
773 message_view_serialize (MessageView *view, AnjutaSerializer *serializer)
775 GtkTreeModel *model;
776 GtkTreeIter iter;
778 if (!anjuta_serializer_write_string (serializer, "label",
779 view->privat->label))
780 return FALSE;
781 if (!anjuta_serializer_write_string (serializer, "pixmap",
782 view->privat->pixmap))
783 return FALSE;
784 if (!anjuta_serializer_write_int (serializer, "highlite",
785 view->privat->highlite))
786 return FALSE;
788 /* Serialize individual messages */
789 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->privat->tree_view));
791 if (!anjuta_serializer_write_int (serializer, "messages",
792 gtk_tree_model_iter_n_children (model, NULL)))
793 return FALSE;
795 if (gtk_tree_model_get_iter_first (model, &iter))
799 Message *message;
800 gtk_tree_model_get (model, &iter, COLUMN_MESSAGE, &message, -1);
801 if (message)
803 if (!message_serialize (message, serializer))
804 return FALSE;
807 while (gtk_tree_model_iter_next (model, &iter));
809 return TRUE;
812 gboolean
813 message_view_deserialize (MessageView *view, AnjutaSerializer *serializer)
815 GtkTreeModel *model;
816 gint messages, i;
818 if (!anjuta_serializer_read_string (serializer, "label",
819 &view->privat->label, TRUE))
820 return FALSE;
821 if (!anjuta_serializer_read_string (serializer, "pixmap",
822 &view->privat->pixmap, TRUE))
823 return FALSE;
824 if (!anjuta_serializer_read_int (serializer, "highlite",
825 &view->privat->highlite))
826 return FALSE;
828 /* Create individual messages */
829 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->privat->tree_view));
830 gtk_list_store_clear (GTK_LIST_STORE (model));
832 if (!anjuta_serializer_read_int (serializer, "messages", &messages))
833 return FALSE;
835 for (i = 0; i < messages; i++)
837 Message *message;
839 message = message_new (0, NULL, NULL);
840 if (!message_deserialize (message, serializer))
842 message_free (message);
843 return FALSE;
845 ianjuta_message_view_append (IANJUTA_MESSAGE_VIEW (view), message->type,
846 message->summary, message->details, NULL);
847 message_free (message);
849 return TRUE;
852 void message_view_next(MessageView* view)
854 GtkTreeIter iter;
855 GtkTreeModel *model;
856 GtkTreeSelection *select;
858 model = gtk_tree_view_get_model (GTK_TREE_VIEW
859 (view->privat->tree_view));
860 select = gtk_tree_view_get_selection (GTK_TREE_VIEW
861 (view->privat->tree_view));
863 if (!gtk_tree_selection_get_selected (select, &model, &iter))
865 if (gtk_tree_model_get_iter_first (model, &iter))
866 gtk_tree_selection_select_iter (select, &iter);
868 while (gtk_tree_model_iter_next (model, &iter))
870 Message *message;
871 gtk_tree_model_get (model, &iter, COLUMN_MESSAGE,
872 &message, -1);
873 if (message->type != IANJUTA_MESSAGE_VIEW_TYPE_NORMAL
874 && message->type != IANJUTA_MESSAGE_VIEW_TYPE_INFO)
876 const gchar* message;
877 gtk_tree_selection_select_iter (select, &iter);
878 message =
879 ianjuta_message_view_get_current_message(IANJUTA_MESSAGE_VIEW (view), NULL);
880 if (message)
882 GtkTreePath *path;
883 path = gtk_tree_model_get_path (model, &iter);
884 gtk_tree_view_set_cursor (GTK_TREE_VIEW
885 (view->privat->tree_view),
886 path, NULL, FALSE);
887 gtk_tree_path_free (path);
888 g_signal_emit_by_name (G_OBJECT (view), "message_clicked",
889 message);
891 break;
896 void message_view_previous(MessageView* view)
898 GtkTreeIter iter;
899 GtkTreeModel *model;
900 GtkTreeSelection *select;
901 GtkTreePath *path;
903 model = gtk_tree_view_get_model (GTK_TREE_VIEW
904 (view->privat->tree_view));
905 select = gtk_tree_view_get_selection (GTK_TREE_VIEW
906 (view->privat->tree_view));
908 if (!gtk_tree_selection_get_selected (select, &model, &iter))
910 if (gtk_tree_model_get_iter_first (model, &iter))
911 gtk_tree_selection_select_iter (select, &iter);
914 /* gtk_tree_model_iter_previous does not exist, use path */
915 path = gtk_tree_model_get_path (model, &iter);
917 while (gtk_tree_path_prev(path))
919 Message *message;
920 gtk_tree_model_get_iter(model, &iter, path);
921 gtk_tree_model_get (model, &iter, COLUMN_MESSAGE,
922 &message, -1);
923 if (message->type != IANJUTA_MESSAGE_VIEW_TYPE_NORMAL
924 && message->type != IANJUTA_MESSAGE_VIEW_TYPE_INFO)
926 const gchar* message;
928 gtk_tree_selection_select_iter (select, &iter);
929 message =
930 ianjuta_message_view_get_current_message(IANJUTA_MESSAGE_VIEW (view), NULL);
931 if (message)
933 GtkTreePath *path;
934 path = gtk_tree_model_get_path (model, &iter);
935 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW
936 (view->privat->tree_view),
937 path, NULL, FALSE, 0, 0);
938 gtk_tree_path_free (path);
939 g_signal_emit_by_name (G_OBJECT (view), "message_clicked",
940 message);
942 break;
945 gtk_tree_path_free (path);
948 static gboolean message_view_save_as(MessageView* view, gchar* uri)
950 GnomeVFSHandle* handle;
951 GtkTreeIter iter;
952 GtkTreeModel *model;
953 gboolean ok;
955 if (uri == NULL) return FALSE;
957 /* Create file */
958 if (gnome_vfs_create (&handle, uri, GNOME_VFS_OPEN_WRITE, FALSE, 0664) != GNOME_VFS_OK)
960 return FALSE;
963 /* Save all lines of message view */
964 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->privat->tree_view));
966 ok = TRUE;
967 gtk_tree_model_get_iter_first (model, &iter);
968 while (gtk_tree_model_iter_next (model, &iter))
970 Message *message;
971 GnomeVFSFileSize written;
973 gtk_tree_model_get (model, &iter, COLUMN_MESSAGE, &message, -1);
974 if (message)
976 if (message->details && (strlen (message->details) > 0))
978 if (gnome_vfs_write (handle, message->details, strlen (message->details), &written) != GNOME_VFS_OK)
980 ok = FALSE;
983 else
985 if (gnome_vfs_write (handle, message->summary, strlen (message->summary), &written) != GNOME_VFS_OK)
987 ok = FALSE;
990 if (gnome_vfs_write (handle, "\n", 1, &written) != GNOME_VFS_OK)
992 ok = FALSE;
996 gnome_vfs_close (handle);
998 return ok;
1001 void message_view_save(MessageView* view)
1003 GtkWindow* parent;
1004 gchar* uri;
1006 parent = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view)));
1008 uri = ask_user_for_save_uri (parent);
1009 if (uri)
1011 if (message_view_save_as (view, uri) == FALSE)
1013 anjuta_util_dialog_error(parent, _("Error writing %s"), uri);
1015 g_free (uri);
1019 /* Preferences notifications */
1020 static void
1021 pref_change_color (MessageView *mview, IAnjutaMessageViewType type,
1022 const gchar *color_pref_key)
1024 gchar* color;
1025 GtkListStore *store;
1026 GtkTreeIter iter;
1027 gboolean success;
1029 color = anjuta_preferences_get (mview->privat->prefs, color_pref_key);
1030 store = GTK_LIST_STORE (gtk_tree_view_get_model
1031 (GTK_TREE_VIEW (mview->privat->tree_view)));
1032 success = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter);
1033 while (success)
1035 Message *message;
1036 gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COLUMN_MESSAGE,
1037 &message, -1);
1038 if (message && message->type == type)
1040 gtk_list_store_set (store, &iter, COLUMN_COLOR, color, -1);
1042 success = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);
1044 g_free(color);
1048 static void
1049 on_gconf_notify_color_warning (GConfClient *gclient, guint cnxn_id,
1050 GConfEntry *entry, gpointer user_data)
1052 pref_change_color (MESSAGE_VIEW (user_data),
1053 IANJUTA_MESSAGE_VIEW_TYPE_WARNING,
1054 "messages.color.warning");
1057 static void
1058 on_gconf_notify_color_error (GConfClient *gclient, guint cnxn_id,
1059 GConfEntry *entry, gpointer user_data)
1061 pref_change_color (MESSAGE_VIEW (user_data),
1062 IANJUTA_MESSAGE_VIEW_TYPE_ERROR,
1063 "messages.color.error");
1066 #define REGISTER_NOTIFY(key, func) \
1067 notify_id = anjuta_preferences_notify_add (mview->privat->prefs, \
1068 key, func, mview, NULL); \
1069 mview->privat->gconf_notify_ids = g_list_prepend (mview->privat->gconf_notify_ids, \
1070 GINT_TO_POINTER(notify_id));
1071 static void
1072 prefs_init (MessageView *mview)
1074 guint notify_id;
1075 REGISTER_NOTIFY ("messages.color.warning", on_gconf_notify_color_warning);
1076 REGISTER_NOTIFY ("messages.color.error", on_gconf_notify_color_error);
1079 static void
1080 prefs_finalize (MessageView *mview)
1082 GList *node;
1083 node = mview->privat->gconf_notify_ids;
1084 while (node)
1086 anjuta_preferences_notify_remove (mview->privat->prefs,
1087 GPOINTER_TO_INT (node->data));
1088 node = g_list_next (node);
1090 g_list_free (mview->privat->gconf_notify_ids);
1091 mview->privat->gconf_notify_ids = NULL;
1094 /* IAnjutaMessageView interface implementation */
1096 /* Appends the text in buffer. Flushes the buffer where a newline is found.
1097 * by emiiting buffer_flushed signal. The string is expected to be utf8.
1099 static void
1100 imessage_view_buffer_append (IAnjutaMessageView * message_view,
1101 const gchar * message, GError ** e)
1103 MessageView *view;
1104 gint cur_char;
1105 int len = strlen(message);
1107 g_return_if_fail (MESSAGE_IS_VIEW (message_view));
1108 g_return_if_fail (message != NULL);
1110 view = MESSAGE_VIEW (message_view);
1112 /* Check if message contains newlines */
1113 for (cur_char = 0; cur_char < len; cur_char++)
1115 /* Replace "\\\n" with " " */
1116 if (message[cur_char] == '\\' && cur_char < len - 1 &&
1117 message[cur_char+1] == '\n')
1119 add_char(&view->privat->line_buffer, ' ');
1120 cur_char++;
1121 continue;
1124 /* Is newline => print line */
1125 if (message[cur_char] != '\n')
1127 add_char(&view->privat->line_buffer, message[cur_char]);
1129 else
1131 g_signal_emit_by_name (G_OBJECT (view), "buffer_flushed",
1132 view->privat->line_buffer);
1133 g_free(view->privat->line_buffer);
1134 view->privat->line_buffer = g_strdup("");
1139 static void
1140 imessage_view_append (IAnjutaMessageView *message_view,
1141 IAnjutaMessageViewType type,
1142 const gchar *summary,
1143 const gchar *details,
1144 GError ** e)
1146 gchar* color;
1147 GtkListStore *store;
1148 GtkTreeIter iter;
1149 gboolean highlite;
1150 gchar *utf8_msg;
1151 gchar *escaped_str;
1152 gchar* stock_id = NULL;
1154 MessageView *view;
1155 Message *message;
1157 g_return_if_fail (MESSAGE_IS_VIEW (message_view));
1159 view = MESSAGE_VIEW (message_view);
1161 message = message_new (type, summary, details);
1163 g_object_get (G_OBJECT (view), "highlite", &highlite, NULL);
1164 color = NULL;
1165 if (highlite)
1167 switch (message->type)
1169 case IANJUTA_MESSAGE_VIEW_TYPE_INFO:
1170 stock_id = GTK_STOCK_INFO;
1171 break;
1172 case IANJUTA_MESSAGE_VIEW_TYPE_WARNING:
1173 color = anjuta_preferences_get (view->privat->prefs,
1174 "messages.color.warning");
1175 /* FIXME: There is no GTK_STOCK_WARNING which would fit better here */
1176 stock_id = GTK_STOCK_DIALOG_WARNING;
1177 break;
1178 case IANJUTA_MESSAGE_VIEW_TYPE_ERROR:
1179 color = anjuta_preferences_get (view->privat->prefs,
1180 "messages.color.error");
1181 stock_id = GTK_STOCK_STOP;
1182 break;
1183 default:
1184 color = NULL;
1188 /* Add the message to the tree */
1189 store = GTK_LIST_STORE (gtk_tree_view_get_model
1190 (GTK_TREE_VIEW (view->privat->tree_view)));
1191 gtk_list_store_append (store, &iter);
1194 * Must be normalized to compose representation to be
1195 * displayed correctly (Bug in gtk_list???)
1197 utf8_msg = g_utf8_normalize (message->summary, -1,
1198 G_NORMALIZE_DEFAULT_COMPOSE);
1199 if (message->details && strlen (message->details) > 0)
1201 gchar *summary;
1202 summary = escape_string (message->summary);
1203 escaped_str = g_strdup_printf ("<b>%s</b>", summary);
1204 g_free (summary);
1205 } else {
1206 escaped_str = escape_string (message->summary);
1208 gtk_list_store_set (store, &iter,
1209 COLUMN_SUMMARY, escaped_str,
1210 COLUMN_MESSAGE, message,
1211 -1);
1212 if (color)
1214 gtk_list_store_set (store, &iter,
1215 COLUMN_COLOR, color, -1);
1217 if (stock_id)
1219 gtk_list_store_set (store, &iter,
1220 COLUMN_PIXBUF, stock_id, -1);
1222 g_free(color);
1223 message_free (message);
1224 g_free (utf8_msg);
1225 g_free (escaped_str);
1228 /* Clear all messages from the message view */
1229 static void
1230 imessage_view_clear (IAnjutaMessageView *message_view, GError **e)
1232 GtkListStore *store;
1233 MessageView *view;
1235 g_return_if_fail (MESSAGE_IS_VIEW (message_view));
1236 view = MESSAGE_VIEW (message_view);
1238 store = GTK_LIST_STORE (gtk_tree_view_get_model
1239 (GTK_TREE_VIEW (view->privat->tree_view)));
1240 gtk_list_store_clear (store);
1243 /* Move the selection to the next line. */
1244 static void
1245 imessage_view_select_next (IAnjutaMessageView * message_view,
1246 GError ** e)
1248 MessageView* view = MESSAGE_VIEW(message_view);
1249 message_view_next(view);
1252 /* Move the selection to the previous line. */
1253 static void
1254 imessage_view_select_previous (IAnjutaMessageView * message_view,
1255 GError ** e)
1257 MessageView *view = MESSAGE_VIEW(message_view);
1258 message_view_previous(view);
1261 /* Return the currently selected messages or the first message if no
1262 * message is selected or NULL if no messages are availible. The
1263 * returned message must not be freed.
1265 static const gchar *
1266 imessage_view_get_current_message (IAnjutaMessageView * message_view,
1267 GError ** e)
1269 MessageView *view;
1270 GtkTreeIter iter;
1271 GtkTreeSelection *select;
1272 GtkTreeModel *model;
1273 const Message *message;
1275 g_return_val_if_fail (MESSAGE_IS_VIEW (message_view), NULL);
1277 view = MESSAGE_VIEW (message_view);
1278 select = gtk_tree_view_get_selection (GTK_TREE_VIEW
1279 (view->privat->tree_view));
1281 if (!gtk_tree_selection_get_selected (select, &model, &iter))
1283 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter))
1285 gtk_tree_model_get (GTK_TREE_MODEL (model),
1286 &iter, COLUMN_MESSAGE, &message, -1);
1287 if (message)
1289 if (message->details && strlen (message->details) > 0)
1290 return message->details;
1291 else
1292 return message->summary;
1296 else
1298 gtk_tree_model_get (GTK_TREE_MODEL (model),
1299 &iter, COLUMN_MESSAGE, &message, -1);
1300 if (message)
1302 if (message->details && strlen (message->details) > 0)
1303 return message->details;
1304 else
1305 return message->summary;
1308 return NULL;
1311 /* Returns a GList which contains all messages, the GList itself
1312 * must be freed, the messages are managed by the message view and
1313 * must not be freed. NULL is return if no messages are availible.
1315 static GList *
1316 imessage_view_get_all_messages (IAnjutaMessageView * message_view,
1317 GError ** e)
1319 MessageView *view;
1320 GtkListStore *store;
1321 GtkTreeIter iter;
1322 Message *message;
1323 GList *messages = NULL;
1325 g_return_val_if_fail (MESSAGE_IS_VIEW (message_view), NULL);
1327 view = MESSAGE_VIEW (message_view);
1328 store = GTK_LIST_STORE (gtk_tree_view_get_model
1329 (GTK_TREE_VIEW (view->privat->tree_view)));
1331 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
1335 gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
1336 COLUMN_MESSAGE, &message);
1337 messages = g_list_prepend (messages, message->details);
1339 while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
1341 return messages;
1344 static void
1345 imessage_view_iface_init (IAnjutaMessageViewIface *iface)
1347 iface->buffer_append = imessage_view_buffer_append;
1348 iface->append = imessage_view_append;
1349 iface->clear = imessage_view_clear;
1350 iface->select_next = imessage_view_select_next;
1351 iface->select_previous = imessage_view_select_previous;
1352 iface->get_current_message = imessage_view_get_current_message;
1353 iface->get_all_messages = imessage_view_get_all_messages;
1356 ANJUTA_TYPE_BEGIN(MessageView, message_view, GTK_TYPE_HBOX);
1357 ANJUTA_TYPE_ADD_INTERFACE(imessage_view, IANJUTA_TYPE_MESSAGE_VIEW);
1358 ANJUTA_TYPE_END;