Updated Spanish translation
[anjuta-git-plugin.git] / plugins / message-view / message-view.c
blob663fc4e14b05f10f6783d6cc2d21e5bb9ea39fee
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 #define HAVE_TOOLTIP_API (GTK_MAJOR_VERSION > 2 || (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 12))
29 struct _MessageViewPrivate
31 //guint num_messages;
32 gchar *line_buffer;
34 GtkWidget *tree_view;
36 AnjutaPreferences* prefs;
37 GtkWidget *popup_menu;
39 gint adj_chgd_hdlr;
41 /* Properties */
42 gchar *label;
43 gchar *pixmap;
44 gboolean highlite;
46 #if !HAVE_TOOLTIP_API
47 GdkRectangle tooltip_rect;
48 GtkWidget *tooltip_window;
49 gulong tooltip_timeout;
50 PangoLayout *tooltip_layout;
51 #endif
53 /* gconf notification ids */
54 GList *gconf_notify_ids;
57 typedef struct
59 IAnjutaMessageViewType type;
60 gchar *summary;
61 gchar *details;
63 } Message;
65 enum
67 COLUMN_COLOR = 0,
68 COLUMN_SUMMARY,
69 COLUMN_MESSAGE,
70 COLUMN_PIXBUF,
71 N_COLUMNS
74 enum
76 MV_PROP_ID = 0,
77 MV_PROP_LABEL,
78 MV_PROP_PIXMAP,
79 MV_PROP_HIGHLITE
82 static gpointer parent_class;
84 static void prefs_init (MessageView *mview);
85 static void prefs_finalize (MessageView *mview);
87 /* Ask the user for an uri name */
88 static gchar *
89 ask_user_for_save_uri (GtkWindow* parent)
91 GtkWidget* dialog;
92 gchar* uri;
94 dialog = gtk_file_chooser_dialog_new (_("Save file as"), parent,
95 GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
96 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
98 if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
100 uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
102 else
104 uri = NULL;
107 gtk_widget_destroy(dialog);
109 return uri;
112 /* Message object creation, copy and freeing */
113 static Message*
114 message_new (IAnjutaMessageViewType type, const gchar *summary,
115 const gchar *details)
117 /* DEBUG_PRINT ("Creating message"); */
118 Message *message = g_new0 (Message, 1);
119 message->type = type;
120 if (summary)
121 message->summary = g_strdup (summary);
122 if (details)
123 message->details = g_strdup (details);
124 return message;
127 static Message*
128 message_copy (const Message *src)
130 return message_new (src->type, src->summary, src->details);
133 static void
134 message_free (Message *message)
136 /* DEBUG_PRINT ("Freeing message"); */
137 g_free (message->summary);
138 g_free (message->details);
139 g_free (message);
142 static gboolean
143 message_serialize (Message *message, AnjutaSerializer *serializer)
145 if (!anjuta_serializer_write_int (serializer, "type",
146 message->type))
147 return FALSE;
148 if (!anjuta_serializer_write_string (serializer, "summary",
149 message->summary))
150 return FALSE;
151 if (!anjuta_serializer_write_string (serializer, "details",
152 message->details))
153 return FALSE;
154 return TRUE;
157 static gboolean
158 message_deserialize (Message *message, AnjutaSerializer *serializer)
160 gint type;
161 if (!anjuta_serializer_read_int (serializer, "type",
162 &type))
163 return FALSE;
164 message->type = type;
165 if (!anjuta_serializer_read_string (serializer, "summary",
166 &message->summary, TRUE))
167 return FALSE;
168 if (!anjuta_serializer_read_string (serializer, "details",
169 &message->details, TRUE))
170 return FALSE;
171 return TRUE;
174 static GType
175 message_get_type ()
177 static GType type = 0;
178 if (!type)
180 type = g_boxed_type_register_static ("MessageViewMessage",
181 (GBoxedCopyFunc) message_copy,
182 (GBoxedFreeFunc) message_free);
184 return type;
187 /* Utility functions */
188 /* Adds the char c to the string str */
189 static void
190 add_char(gchar** str, gchar c)
192 gchar* buffer;
194 g_return_if_fail(str != NULL);
196 buffer = g_strdup_printf("%s%c", *str, c);
197 g_free(*str);
198 *str = buffer;
201 static gchar*
202 escape_string (const gchar *str)
204 GString *gstr;
205 const gchar *iter;
207 gstr = g_string_new ("");
208 iter = str;
209 while (*iter != '\0')
211 if (*iter == '>')
212 gstr = g_string_append (gstr, "&gt;");
213 else if (*iter == '<')
214 gstr = g_string_append (gstr, "&lt;");
215 else if (*iter == '&')
216 gstr = g_string_append (gstr, "&amp;");
217 else
218 gstr = g_string_append_c (gstr, *iter);
219 iter++;
221 return g_string_free (gstr, FALSE);
224 #if HAVE_TOOLTIP_API
225 static gboolean
226 message_view_query_tooltip (GtkWidget* widget, gint x, gint y, gboolean keyboard,
227 GtkTooltip* tooltip)
229 GtkTreePath *path;
230 GtkTreeIter iter;
231 GtkTreeModel *model;
232 MessageView* view = MESSAGE_VIEW(widget);
234 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->privat->tree_view));
236 if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW(view->privat->tree_view),
237 x, y, &path, NULL, NULL, NULL))
239 Message *message;
240 gchar *text, *title, *desc;
242 gtk_tree_model_get_iter (model, &iter, path);
243 gtk_tree_model_get (model, &iter, COLUMN_MESSAGE, &message, -1);
244 gtk_tree_path_free(path);
246 if (!message->details || !message->summary ||
247 strlen (message->details) <= 0 ||
248 strlen (message->summary) <= 0)
249 return FALSE;
251 title = escape_string (message->summary);
252 desc = escape_string (message->details);
253 text = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
255 g_free (title);
256 g_free (desc);
258 gtk_tooltip_set_markup (tooltip, text);
259 g_free (text);
260 return TRUE;
262 return FALSE;
264 #endif
266 #if !HAVE_TOOLTIP_API
267 /* Tooltip operations -- taken from gtodo */
269 static gchar *
270 tooltip_get_display_text (MessageView *view)
272 GtkTreePath *path;
273 GtkTreeIter iter;
274 GtkTreeModel *model;
276 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->privat->tree_view));
278 if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW(view->privat->tree_view),
279 view->privat->tooltip_rect.x, view->privat->tooltip_rect.y,
280 &path, NULL, NULL, NULL))
282 Message *message;
283 gchar *text, *title, *desc;
285 gtk_tree_model_get_iter (model, &iter, path);
286 gtk_tree_model_get (model, &iter, COLUMN_MESSAGE, &message, -1);
287 gtk_tree_path_free(path);
289 if (!message->details || !message->summary ||
290 strlen (message->details) <= 0 ||
291 strlen (message->summary) <= 0)
292 return NULL;
294 title = escape_string (message->summary);
295 desc = escape_string (message->details);
296 text = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
298 g_free (title);
299 g_free (desc);
301 return text;
303 return NULL;
306 static void
307 tooltip_paint (GtkWidget *widget, GdkEventExpose *event, MessageView *view)
309 GtkStyle *style;
310 gchar *tooltiptext;
312 tooltiptext = tooltip_get_display_text (view);
314 if (!tooltiptext)
315 tooltiptext = g_strdup (_("No message details"));
317 pango_layout_set_markup (view->privat->tooltip_layout,
318 tooltiptext,
319 strlen (tooltiptext));
320 pango_layout_set_wrap(view->privat->tooltip_layout, PANGO_WRAP_CHAR);
321 pango_layout_set_width(view->privat->tooltip_layout, 600000);
322 style = view->privat->tooltip_window->style;
324 gtk_paint_flat_box (style, view->privat->tooltip_window->window,
325 GTK_STATE_NORMAL, GTK_SHADOW_OUT,
326 NULL, view->privat->tooltip_window,
327 "tooltip", 0, 0, -1, -1);
329 gtk_paint_layout (style, view->privat->tooltip_window->window,
330 GTK_STATE_NORMAL, TRUE,
331 NULL, view->privat->tooltip_window,
332 "tooltip", 4, 4, view->privat->tooltip_layout);
334 g_object_unref(layout);
336 g_free(tooltiptext);
337 return;
340 static gboolean
341 tooltip_timeout (MessageView *view)
343 gint scr_w,scr_h, w, h, x, y;
344 gchar *tooltiptext;
346 tooltiptext = tooltip_get_display_text (view);
348 if (!tooltiptext)
349 tooltiptext = g_strdup (_("No message details"));
351 view->privat->tooltip_window = gtk_window_new (GTK_WINDOW_POPUP);
352 view->privat->tooltip_window->parent = view->privat->tree_view;
353 gtk_widget_set_app_paintable (view->privat->tooltip_window, TRUE);
354 gtk_window_set_resizable (GTK_WINDOW(view->privat->tooltip_window), FALSE);
355 gtk_widget_set_name (view->privat->tooltip_window, "gtk-tooltips");
356 g_signal_connect (G_OBJECT(view->privat->tooltip_window), "expose_event",
357 G_CALLBACK(tooltip_paint), view);
358 gtk_widget_ensure_style (view->privat->tooltip_window);
360 view->privat->tooltip_layout =
361 gtk_widget_create_pango_layout (view->privat->tooltip_window, NULL);
362 pango_layout_set_wrap (view->privat->tooltip_layout, PANGO_WRAP_CHAR);
363 pango_layout_set_width (view->privat->tooltip_layout, 600000);
364 pango_layout_set_markup (view->privat->tooltip_layout, tooltiptext,
365 strlen (tooltiptext));
366 scr_w = gdk_screen_width();
367 scr_h = gdk_screen_height();
368 pango_layout_get_size (view->privat->tooltip_layout, &w, &h);
369 w = PANGO_PIXELS(w) + 8;
370 h = PANGO_PIXELS(h) + 8;
372 gdk_window_get_pointer (NULL, &x, &y, NULL);
373 if (GTK_WIDGET_NO_WINDOW (view->privat->tree_view))
374 y += view->privat->tree_view->allocation.y;
376 x -= ((w >> 1) + 4);
378 if ((x + w) > scr_w)
379 x -= (x + w) - scr_w;
380 else if (x < 0)
381 x = 0;
383 if ((y + h + 4) > scr_h)
384 y = y - h;
385 else
386 y = y + 6;
388 g_object_unref(layout);
390 gtk_widget_set_size_request (view->privat->tooltip_window, w, h);
391 gtk_window_move (GTK_WINDOW (view->privat->tooltip_window), x, y);
392 gtk_widget_show (view->privat->tooltip_window);
393 g_free (tooltiptext);
395 return FALSE;
398 static gboolean
399 tooltip_motion_cb (GtkWidget *tv, GdkEventMotion *event, MessageView *view)
401 GtkTreePath *path;
403 if (view->privat->tooltip_rect.y == 0 &&
404 view->privat->tooltip_rect.height == 0 &&
405 view->privat->tooltip_timeout)
407 g_source_remove (view->privat->tooltip_timeout);
408 view->privat->tooltip_timeout = 0;
409 if (view->privat->tooltip_window) {
410 gtk_widget_destroy (view->privat->tooltip_window);
411 view->privat->tooltip_window = NULL;
413 return FALSE;
415 if (view->privat->tooltip_timeout) {
416 if (((int)event->y > view->privat->tooltip_rect.y) &&
417 (((int)event->y - view->privat->tooltip_rect.height)
418 < view->privat->tooltip_rect.y))
419 return FALSE;
421 if(event->y == 0)
423 g_source_remove (view->privat->tooltip_timeout);
424 view->privat->tooltip_timeout = 0;
425 return FALSE;
427 /* We've left the cell. Remove the timeout and create a new one below */
428 if (view->privat->tooltip_window) {
429 gtk_widget_destroy (view->privat->tooltip_window);
430 view->privat->tooltip_window = NULL;
432 g_source_remove (view->privat->tooltip_timeout);
433 view->privat->tooltip_timeout = 0;
436 if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW(view->privat->tree_view),
437 event->x, event->y, &path,
438 NULL, NULL, NULL))
440 GtkTreeSelection *selection;
442 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view->privat->tree_view));
443 if (gtk_tree_selection_path_is_selected (selection, path))
445 gtk_tree_view_get_cell_area (GTK_TREE_VIEW (view->privat->tree_view),
446 path, NULL, &view->privat->tooltip_rect);
448 if (view->privat->tooltip_rect.y != 0 &&
449 view->privat->tooltip_rect.height != 0)
451 gchar *tooltiptext;
453 tooltiptext = tooltip_get_display_text (view);
454 if (tooltiptext == NULL)
455 return FALSE;
456 g_free (tooltiptext);
458 view->privat->tooltip_timeout =
459 g_timeout_add (500, (GSourceFunc) tooltip_timeout, view);
462 gtk_tree_path_free (path);
464 return FALSE;
467 static void
468 tooltip_leave_cb (GtkWidget *w, GdkEventCrossing *e, MessageView *view)
470 if (view->privat->tooltip_timeout) {
471 g_source_remove (view->privat->tooltip_timeout);
472 view->privat->tooltip_timeout = 0;
474 if (view->privat->tooltip_window) {
475 gtk_widget_destroy (view->privat->tooltip_window);
476 g_object_unref (view->privat->tooltip_layout);
477 view->privat->tooltip_window = NULL;
480 #endif
483 /* MessageView signal callbacks */
484 /* Send a signal if a message was double-clicked or ENTER or SPACE was pressed */
485 static gboolean
486 on_message_event (GObject* object, GdkEvent* event, gpointer data)
488 g_return_val_if_fail(object != NULL, FALSE);
489 g_return_val_if_fail(event != NULL, FALSE);
490 g_return_val_if_fail(data != NULL, FALSE);
492 MessageView* view = MESSAGE_VIEW(data);
494 if (event == NULL)
495 return FALSE;
497 if (event->type == GDK_KEY_PRESS)
499 switch(((GdkEventKey *)event)->keyval)
501 case GDK_space:
502 case GDK_Return:
504 const gchar* message =
505 ianjuta_message_view_get_current_message(IANJUTA_MESSAGE_VIEW (view), NULL);
506 if (message)
508 g_signal_emit_by_name (G_OBJECT (view), "message_clicked",
509 message);
510 return TRUE;
512 break;
514 default:
515 return FALSE;
518 else if (event->type == GDK_2BUTTON_PRESS)
520 if (((GdkEventButton *) event)->button == 1)
522 const gchar* message =
523 ianjuta_message_view_get_current_message(IANJUTA_MESSAGE_VIEW (view), NULL);
524 if (message)
526 g_signal_emit_by_name (G_OBJECT (view), "message_clicked",
527 message);
528 return TRUE;
531 return FALSE;
533 else if (event->type == GDK_BUTTON_PRESS)
535 if (((GdkEventButton *) event)->button == 3)
537 gtk_menu_popup (GTK_MENU (view->privat->popup_menu), NULL, NULL, NULL, NULL,
538 ((GdkEventButton *) event)->button,
539 ((GdkEventButton *) event)->time);
540 return TRUE;
543 return FALSE;
546 static void
547 on_adjustment_changed (GtkAdjustment* adj, gpointer data)
549 gtk_adjustment_set_value (adj, adj->upper - adj->page_size);
552 static void
553 on_adjustment_value_changed (GtkAdjustment* adj, gpointer data)
555 MessageView *self = MESSAGE_VIEW (data);
556 if (adj->value > (adj->upper - adj->page_size) - 0.1)
558 if (!self->privat->adj_chgd_hdlr)
560 self->privat->adj_chgd_hdlr =
561 g_signal_connect (G_OBJECT (adj), "changed",
562 G_CALLBACK (on_adjustment_changed), NULL);
565 else
567 if (self->privat->adj_chgd_hdlr)
569 g_signal_handler_disconnect (G_OBJECT (adj), self->privat->adj_chgd_hdlr);
570 self->privat->adj_chgd_hdlr = 0;
575 static void
576 message_view_set_property (GObject * object,
577 guint property_id,
578 const GValue * value, GParamSpec * pspec)
580 MessageView *self = MESSAGE_VIEW (object);
581 g_return_if_fail(value != NULL);
582 g_return_if_fail(pspec != NULL);
584 switch (property_id)
586 case MV_PROP_LABEL:
588 g_free (self->privat->label);
589 self->privat->label = g_value_dup_string (value);
590 break;
592 case MV_PROP_PIXMAP:
594 g_free (self->privat->pixmap);
595 self->privat->pixmap = g_value_dup_string (value);
596 break;
598 case MV_PROP_HIGHLITE:
600 self->privat->highlite = g_value_get_boolean (value);
601 break;
603 default:
605 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
606 break;
611 static void
612 message_view_get_property (GObject * object,
613 guint property_id,
614 GValue * value, GParamSpec * pspec)
616 MessageView *self = MESSAGE_VIEW (object);
618 g_return_if_fail(value != NULL);
619 g_return_if_fail(pspec != NULL);
621 switch (property_id)
623 case MV_PROP_LABEL:
625 g_value_set_string (value, self->privat->label);
626 break;
628 case MV_PROP_PIXMAP:
630 g_value_set_string (value, self->privat->pixmap);
631 break;
633 case MV_PROP_HIGHLITE:
635 g_value_set_boolean (value, self->privat->highlite);
636 break;
638 default:
640 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
641 break;
646 static void
647 message_view_dispose (GObject *obj)
649 MessageView *mview = MESSAGE_VIEW (obj);
650 if (mview->privat->gconf_notify_ids)
652 prefs_finalize (mview);
653 mview->privat->gconf_notify_ids = NULL;
655 #if !HAVE_TOOLTIP_API
656 if (mview->privat->tooltip_timeout) {
657 g_source_remove (mview->privat->tooltip_timeout);
658 mview->privat->tooltip_timeout = 0;
661 if (mview->privat->tooltip_window) {
662 gtk_widget_destroy (mview->privat->tooltip_window);
663 g_object_unref (mview->privat->tooltip_layout);
664 mview->privat->tooltip_window = NULL;
666 #endif
667 if (mview->privat->tree_view)
669 mview->privat->tree_view = NULL;
671 GNOME_CALL_PARENT (G_OBJECT_CLASS, dispose, (G_OBJECT(obj)));
674 static void
675 message_view_finalize (GObject *obj)
677 MessageView *mview = MESSAGE_VIEW (obj);
678 g_free (mview->privat->line_buffer);
679 g_free (mview->privat->label);
680 g_free (mview->privat->pixmap);
681 g_free (mview->privat);
682 GNOME_CALL_PARENT (G_OBJECT_CLASS, finalize, (G_OBJECT(obj)));
685 static void
686 message_view_instance_init (MessageView * self)
688 GtkWidget *scrolled_win;
689 GtkCellRenderer *renderer;
690 GtkCellRenderer *renderer_pixbuf;
691 GtkTreeViewColumn *column;
692 GtkTreeViewColumn *column_pixbuf;
693 GtkTreeSelection *select;
694 GtkListStore *model;
695 GtkAdjustment* adj;
697 g_return_if_fail(self != NULL);
698 self->privat = g_new0 (MessageViewPrivate, 1);
700 /* Init private data */
701 self->privat->line_buffer = g_strdup("");
703 /* Create the tree widget */
704 model = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING,
705 G_TYPE_STRING, MESSAGE_TYPE, G_TYPE_STRING);
706 self->privat->tree_view =
707 gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
708 gtk_widget_show (self->privat->tree_view);
709 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW
710 (self->privat->tree_view), FALSE);
712 /* Create pixbuf column */
713 renderer_pixbuf = gtk_cell_renderer_pixbuf_new ();
714 column_pixbuf = gtk_tree_view_column_new ();
715 gtk_tree_view_column_set_title (column_pixbuf, _("Icon"));
716 gtk_tree_view_column_pack_start (column_pixbuf, renderer_pixbuf, TRUE);
717 gtk_tree_view_column_add_attribute
718 (column_pixbuf, renderer_pixbuf, "stock-id", COLUMN_PIXBUF);
719 gtk_tree_view_append_column (GTK_TREE_VIEW (self->privat->tree_view),
720 column_pixbuf);
721 /* Create columns to hold text and color of a line, this
722 * columns are invisible of course. */
723 renderer = gtk_cell_renderer_text_new ();
724 g_object_set (renderer, "yalign", 0.0, "wrap-mode", PANGO_WRAP_WORD,
725 "wrap-width", 1000, NULL);
726 column = gtk_tree_view_column_new ();
727 gtk_tree_view_column_pack_start (column, renderer, TRUE);
728 gtk_tree_view_column_set_title (column, _("Messages"));
729 gtk_tree_view_column_add_attribute
730 (column, renderer, "foreground", COLUMN_COLOR);
731 gtk_tree_view_column_add_attribute
732 (column, renderer, "markup", COLUMN_SUMMARY);
733 gtk_tree_view_append_column (GTK_TREE_VIEW (self->privat->tree_view),
734 column);
736 /* Adjust selection */
737 select = gtk_tree_view_get_selection
738 (GTK_TREE_VIEW (self->privat->tree_view));
739 gtk_tree_selection_set_mode (select, GTK_SELECTION_BROWSE);
741 /* Add tree view to a scrolled window */
742 scrolled_win = gtk_scrolled_window_new (NULL, NULL);
743 gtk_container_add (GTK_CONTAINER (scrolled_win),
744 self->privat->tree_view);
745 gtk_widget_show (scrolled_win);
746 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
747 GTK_POLICY_AUTOMATIC,
748 GTK_POLICY_AUTOMATIC);
749 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW
750 (scrolled_win));
751 self->privat->adj_chgd_hdlr = g_signal_connect(G_OBJECT(adj), "changed",
752 G_CALLBACK (on_adjustment_changed), self);
753 g_signal_connect(G_OBJECT(adj), "value_changed",
754 G_CALLBACK(on_adjustment_value_changed), self);
756 /* Add it to the dockitem */
757 gtk_container_add (GTK_CONTAINER (self), scrolled_win);
759 /* Connect signals */
760 g_signal_connect (G_OBJECT(self->privat->tree_view), "event",
761 G_CALLBACK (on_message_event), self);
762 #if !HAVE_TOOLTIP_API
763 g_signal_connect (G_OBJECT (self->privat->tree_view), "motion-notify-event",
764 G_CALLBACK (tooltip_motion_cb), self);
765 g_signal_connect (G_OBJECT (self->privat->tree_view), "leave-notify-event",
766 G_CALLBACK (tooltip_leave_cb), self);
767 #else
768 g_object_set (G_OBJECT(self), "has-tooltip", TRUE, NULL);
769 #endif
770 g_object_unref (model);
773 static void
774 message_view_class_init (MessageViewClass * klass)
776 GParamSpec *message_view_spec_label;
777 GParamSpec *message_view_spec_pixmap;
778 GParamSpec *message_view_spec_highlite;
779 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
780 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS (klass);
782 parent_class = g_type_class_peek_parent (klass);
783 gobject_class->set_property = message_view_set_property;
784 gobject_class->get_property = message_view_get_property;
785 gobject_class->finalize = message_view_finalize;
786 gobject_class->dispose = message_view_dispose;
788 #if HAVE_TOOLTIP_API
789 widget_class->query_tooltip = message_view_query_tooltip;
790 #endif
792 message_view_spec_label = g_param_spec_string ("label",
793 "Label of the view",
794 "Used to decorate the view,"
795 "translateable",
796 "no label",
797 G_PARAM_READWRITE);
798 g_object_class_install_property (gobject_class,
799 MV_PROP_LABEL,
800 message_view_spec_label);
802 message_view_spec_pixmap = g_param_spec_string ("pixmap",
803 "Pixmap of the view",
804 "Used to decorate the view tab,"
805 "translateable",
806 "no label",
807 G_PARAM_READWRITE);
808 g_object_class_install_property (gobject_class,
809 MV_PROP_PIXMAP,
810 message_view_spec_pixmap);
812 message_view_spec_highlite = g_param_spec_boolean ("highlite",
813 "Highlite build messages",
814 "If TRUE, specify colors",
815 FALSE,
816 G_PARAM_READWRITE);
817 g_object_class_install_property (gobject_class,
818 MV_PROP_HIGHLITE,
819 message_view_spec_highlite);
822 /* Returns a new message-view instance */
823 GtkWidget *
824 message_view_new (AnjutaPreferences* prefs, GtkWidget* popup_menu)
826 MessageView * mv = MESSAGE_VIEW (g_object_new (message_view_get_type (), NULL));
827 mv->privat->prefs = prefs;
828 mv->privat->popup_menu = popup_menu;
829 prefs_init (mv);
830 return GTK_WIDGET(mv);
833 gboolean
834 message_view_serialize (MessageView *view, AnjutaSerializer *serializer)
836 GtkTreeModel *model;
837 GtkTreeIter iter;
839 if (!anjuta_serializer_write_string (serializer, "label",
840 view->privat->label))
841 return FALSE;
842 if (!anjuta_serializer_write_string (serializer, "pixmap",
843 view->privat->pixmap))
844 return FALSE;
845 if (!anjuta_serializer_write_int (serializer, "highlite",
846 view->privat->highlite))
847 return FALSE;
849 /* Serialize individual messages */
850 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->privat->tree_view));
852 if (!anjuta_serializer_write_int (serializer, "messages",
853 gtk_tree_model_iter_n_children (model, NULL)))
854 return FALSE;
856 if (gtk_tree_model_get_iter_first (model, &iter))
860 Message *message;
861 gtk_tree_model_get (model, &iter, COLUMN_MESSAGE, &message, -1);
862 if (message)
864 if (!message_serialize (message, serializer))
865 return FALSE;
868 while (gtk_tree_model_iter_next (model, &iter));
870 return TRUE;
873 gboolean
874 message_view_deserialize (MessageView *view, AnjutaSerializer *serializer)
876 GtkTreeModel *model;
877 gint messages, i;
879 if (!anjuta_serializer_read_string (serializer, "label",
880 &view->privat->label, TRUE))
881 return FALSE;
882 if (!anjuta_serializer_read_string (serializer, "pixmap",
883 &view->privat->pixmap, TRUE))
884 return FALSE;
885 if (!anjuta_serializer_read_int (serializer, "highlite",
886 &view->privat->highlite))
887 return FALSE;
889 /* Create individual messages */
890 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->privat->tree_view));
891 gtk_list_store_clear (GTK_LIST_STORE (model));
893 if (!anjuta_serializer_read_int (serializer, "messages", &messages))
894 return FALSE;
896 for (i = 0; i < messages; i++)
898 Message *message;
900 message = message_new (0, NULL, NULL);
901 if (!message_deserialize (message, serializer))
903 message_free (message);
904 return FALSE;
906 ianjuta_message_view_append (IANJUTA_MESSAGE_VIEW (view), message->type,
907 message->summary, message->details, NULL);
908 message_free (message);
910 return TRUE;
913 void message_view_next(MessageView* view)
915 GtkTreeIter iter;
916 GtkTreeModel *model;
917 GtkTreeSelection *select;
919 model = gtk_tree_view_get_model (GTK_TREE_VIEW
920 (view->privat->tree_view));
921 select = gtk_tree_view_get_selection (GTK_TREE_VIEW
922 (view->privat->tree_view));
924 if (!gtk_tree_selection_get_selected (select, &model, &iter))
926 if (gtk_tree_model_get_iter_first (model, &iter))
927 gtk_tree_selection_select_iter (select, &iter);
929 while (gtk_tree_model_iter_next (model, &iter))
931 Message *message;
932 gtk_tree_model_get (model, &iter, COLUMN_MESSAGE,
933 &message, -1);
934 if (message->type != IANJUTA_MESSAGE_VIEW_TYPE_NORMAL
935 && message->type != IANJUTA_MESSAGE_VIEW_TYPE_INFO)
937 const gchar* message;
938 gtk_tree_selection_select_iter (select, &iter);
939 message =
940 ianjuta_message_view_get_current_message(IANJUTA_MESSAGE_VIEW (view), NULL);
941 if (message)
943 GtkTreePath *path;
944 path = gtk_tree_model_get_path (model, &iter);
945 gtk_tree_view_set_cursor (GTK_TREE_VIEW
946 (view->privat->tree_view),
947 path, NULL, FALSE);
948 gtk_tree_path_free (path);
949 g_signal_emit_by_name (G_OBJECT (view), "message_clicked",
950 message);
952 break;
957 void message_view_previous(MessageView* view)
959 GtkTreeIter iter;
960 GtkTreeModel *model;
961 GtkTreeSelection *select;
962 GtkTreePath *path;
964 model = gtk_tree_view_get_model (GTK_TREE_VIEW
965 (view->privat->tree_view));
966 select = gtk_tree_view_get_selection (GTK_TREE_VIEW
967 (view->privat->tree_view));
969 if (!gtk_tree_selection_get_selected (select, &model, &iter))
971 if (gtk_tree_model_get_iter_first (model, &iter))
972 gtk_tree_selection_select_iter (select, &iter);
975 /* gtk_tree_model_iter_previous does not exist, use path */
976 path = gtk_tree_model_get_path (model, &iter);
978 while (gtk_tree_path_prev(path))
980 Message *message;
981 gtk_tree_model_get_iter(model, &iter, path);
982 gtk_tree_model_get (model, &iter, COLUMN_MESSAGE,
983 &message, -1);
984 if (message->type != IANJUTA_MESSAGE_VIEW_TYPE_NORMAL
985 && message->type != IANJUTA_MESSAGE_VIEW_TYPE_INFO)
987 const gchar* message;
989 gtk_tree_selection_select_iter (select, &iter);
990 message =
991 ianjuta_message_view_get_current_message(IANJUTA_MESSAGE_VIEW (view), NULL);
992 if (message)
994 GtkTreePath *path;
995 path = gtk_tree_model_get_path (model, &iter);
996 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW
997 (view->privat->tree_view),
998 path, NULL, FALSE, 0, 0);
999 gtk_tree_path_free (path);
1000 g_signal_emit_by_name (G_OBJECT (view), "message_clicked",
1001 message);
1003 break;
1006 gtk_tree_path_free (path);
1009 static gboolean message_view_save_as(MessageView* view, gchar* uri)
1011 GnomeVFSHandle* handle;
1012 GtkTreeIter iter;
1013 GtkTreeModel *model;
1014 gboolean ok;
1016 if (uri == NULL) return FALSE;
1018 /* Create file */
1019 if (gnome_vfs_create (&handle, uri, GNOME_VFS_OPEN_WRITE, FALSE, 0664) != GNOME_VFS_OK)
1021 return FALSE;
1024 /* Save all lines of message view */
1025 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->privat->tree_view));
1027 ok = TRUE;
1028 gtk_tree_model_get_iter_first (model, &iter);
1029 while (gtk_tree_model_iter_next (model, &iter))
1031 Message *message;
1032 GnomeVFSFileSize written;
1034 gtk_tree_model_get (model, &iter, COLUMN_MESSAGE, &message, -1);
1035 if (message)
1037 if (message->details && (strlen (message->details) > 0))
1039 if (gnome_vfs_write (handle, message->details, strlen (message->details), &written) != GNOME_VFS_OK)
1041 ok = FALSE;
1044 else
1046 if (gnome_vfs_write (handle, message->summary, strlen (message->summary), &written) != GNOME_VFS_OK)
1048 ok = FALSE;
1051 if (gnome_vfs_write (handle, "\n", 1, &written) != GNOME_VFS_OK)
1053 ok = FALSE;
1057 gnome_vfs_close (handle);
1059 return ok;
1062 void message_view_save(MessageView* view)
1064 GtkWindow* parent;
1065 gchar* uri;
1067 parent = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view)));
1069 uri = ask_user_for_save_uri (parent);
1070 if (uri)
1072 if (message_view_save_as (view, uri) == FALSE)
1074 anjuta_util_dialog_error(parent, _("Error writing %s"), uri);
1076 g_free (uri);
1080 /* Preferences notifications */
1081 static void
1082 pref_change_color (MessageView *mview, IAnjutaMessageViewType type,
1083 const gchar *color_pref_key)
1085 gchar* color;
1086 GtkListStore *store;
1087 GtkTreeIter iter;
1088 gboolean success;
1090 color = anjuta_preferences_get (mview->privat->prefs, color_pref_key);
1091 store = GTK_LIST_STORE (gtk_tree_view_get_model
1092 (GTK_TREE_VIEW (mview->privat->tree_view)));
1093 success = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter);
1094 while (success)
1096 Message *message;
1097 gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COLUMN_MESSAGE,
1098 &message, -1);
1099 if (message && message->type == type)
1101 gtk_list_store_set (store, &iter, COLUMN_COLOR, color, -1);
1103 success = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);
1105 g_free(color);
1109 static void
1110 on_gconf_notify_color_warning (GConfClient *gclient, guint cnxn_id,
1111 GConfEntry *entry, gpointer user_data)
1113 pref_change_color (MESSAGE_VIEW (user_data),
1114 IANJUTA_MESSAGE_VIEW_TYPE_WARNING,
1115 "messages.color.warning");
1118 static void
1119 on_gconf_notify_color_error (GConfClient *gclient, guint cnxn_id,
1120 GConfEntry *entry, gpointer user_data)
1122 pref_change_color (MESSAGE_VIEW (user_data),
1123 IANJUTA_MESSAGE_VIEW_TYPE_ERROR,
1124 "messages.color.error");
1127 #define REGISTER_NOTIFY(key, func) \
1128 notify_id = anjuta_preferences_notify_add (mview->privat->prefs, \
1129 key, func, mview, NULL); \
1130 mview->privat->gconf_notify_ids = g_list_prepend (mview->privat->gconf_notify_ids, \
1131 GINT_TO_POINTER(notify_id));
1132 static void
1133 prefs_init (MessageView *mview)
1135 guint notify_id;
1136 REGISTER_NOTIFY ("messages.color.warning", on_gconf_notify_color_warning);
1137 REGISTER_NOTIFY ("messages.color.error", on_gconf_notify_color_error);
1140 static void
1141 prefs_finalize (MessageView *mview)
1143 GList *node;
1144 node = mview->privat->gconf_notify_ids;
1145 while (node)
1147 anjuta_preferences_notify_remove (mview->privat->prefs,
1148 GPOINTER_TO_INT (node->data));
1149 node = g_list_next (node);
1151 g_list_free (mview->privat->gconf_notify_ids);
1152 mview->privat->gconf_notify_ids = NULL;
1155 /* IAnjutaMessageView interface implementation */
1157 /* Appends the text in buffer. Flushes the buffer where a newline is found.
1158 * by emiiting buffer_flushed signal. The string is expected to be utf8.
1160 static void
1161 imessage_view_buffer_append (IAnjutaMessageView * message_view,
1162 const gchar * message, GError ** e)
1164 MessageView *view;
1165 gint cur_char;
1166 int len = strlen(message);
1168 g_return_if_fail (MESSAGE_IS_VIEW (message_view));
1169 g_return_if_fail (message != NULL);
1171 view = MESSAGE_VIEW (message_view);
1173 /* Check if message contains newlines */
1174 for (cur_char = 0; cur_char < len; cur_char++)
1176 /* Replace "\\\n" with " " */
1177 if (message[cur_char] == '\\' && cur_char < len - 1 &&
1178 message[cur_char+1] == '\n')
1180 add_char(&view->privat->line_buffer, ' ');
1181 cur_char++;
1182 continue;
1185 /* Is newline => print line */
1186 if (message[cur_char] != '\n')
1188 add_char(&view->privat->line_buffer, message[cur_char]);
1190 else
1192 g_signal_emit_by_name (G_OBJECT (view), "buffer_flushed",
1193 view->privat->line_buffer);
1194 g_free(view->privat->line_buffer);
1195 view->privat->line_buffer = g_strdup("");
1200 static void
1201 imessage_view_append (IAnjutaMessageView *message_view,
1202 IAnjutaMessageViewType type,
1203 const gchar *summary,
1204 const gchar *details,
1205 GError ** e)
1207 gchar* color;
1208 GtkListStore *store;
1209 GtkTreeIter iter;
1210 gboolean highlite;
1211 gchar *utf8_msg;
1212 gchar *escaped_str;
1213 gchar* stock_id = NULL;
1215 MessageView *view;
1216 Message *message;
1218 g_return_if_fail (MESSAGE_IS_VIEW (message_view));
1220 view = MESSAGE_VIEW (message_view);
1222 message = message_new (type, summary, details);
1224 g_object_get (G_OBJECT (view), "highlite", &highlite, NULL);
1225 color = NULL;
1226 if (highlite)
1228 switch (message->type)
1230 case IANJUTA_MESSAGE_VIEW_TYPE_INFO:
1231 stock_id = GTK_STOCK_INFO;
1232 break;
1233 case IANJUTA_MESSAGE_VIEW_TYPE_WARNING:
1234 color = anjuta_preferences_get (view->privat->prefs,
1235 "messages.color.warning");
1236 /* FIXME: There is no GTK_STOCK_WARNING which would fit better here */
1237 stock_id = GTK_STOCK_DIALOG_WARNING;
1238 break;
1239 case IANJUTA_MESSAGE_VIEW_TYPE_ERROR:
1240 color = anjuta_preferences_get (view->privat->prefs,
1241 "messages.color.error");
1242 stock_id = GTK_STOCK_STOP;
1243 break;
1244 default:
1245 color = NULL;
1249 /* Add the message to the tree */
1250 store = GTK_LIST_STORE (gtk_tree_view_get_model
1251 (GTK_TREE_VIEW (view->privat->tree_view)));
1252 gtk_list_store_append (store, &iter);
1255 * Must be normalized to compose representation to be
1256 * displayed correctly (Bug in gtk_list???)
1258 utf8_msg = g_utf8_normalize (message->summary, -1,
1259 G_NORMALIZE_DEFAULT_COMPOSE);
1260 if (message->details && strlen (message->details) > 0)
1262 gchar *summary;
1263 summary = escape_string (message->summary);
1264 escaped_str = g_strdup_printf ("<b>%s</b>", summary);
1265 g_free (summary);
1266 } else {
1267 escaped_str = escape_string (message->summary);
1269 gtk_list_store_set (store, &iter,
1270 COLUMN_SUMMARY, escaped_str,
1271 COLUMN_MESSAGE, message,
1272 -1);
1273 if (color)
1275 gtk_list_store_set (store, &iter,
1276 COLUMN_COLOR, color, -1);
1278 if (stock_id)
1280 gtk_list_store_set (store, &iter,
1281 COLUMN_PIXBUF, stock_id, -1);
1283 g_free(color);
1284 message_free (message);
1285 g_free (utf8_msg);
1286 g_free (escaped_str);
1289 /* Clear all messages from the message view */
1290 static void
1291 imessage_view_clear (IAnjutaMessageView *message_view, GError **e)
1293 GtkListStore *store;
1294 MessageView *view;
1296 g_return_if_fail (MESSAGE_IS_VIEW (message_view));
1297 view = MESSAGE_VIEW (message_view);
1299 store = GTK_LIST_STORE (gtk_tree_view_get_model
1300 (GTK_TREE_VIEW (view->privat->tree_view)));
1301 gtk_list_store_clear (store);
1304 /* Move the selection to the next line. */
1305 static void
1306 imessage_view_select_next (IAnjutaMessageView * message_view,
1307 GError ** e)
1309 MessageView* view = MESSAGE_VIEW(message_view);
1310 message_view_next(view);
1313 /* Move the selection to the previous line. */
1314 static void
1315 imessage_view_select_previous (IAnjutaMessageView * message_view,
1316 GError ** e)
1318 MessageView *view = MESSAGE_VIEW(message_view);
1319 message_view_previous(view);
1322 /* Return the currently selected messages or the first message if no
1323 * message is selected or NULL if no messages are availible. The
1324 * returned message must not be freed.
1326 static const gchar *
1327 imessage_view_get_current_message (IAnjutaMessageView * message_view,
1328 GError ** e)
1330 MessageView *view;
1331 GtkTreeIter iter;
1332 GtkTreeSelection *select;
1333 GtkTreeModel *model;
1334 const Message *message;
1336 g_return_val_if_fail (MESSAGE_IS_VIEW (message_view), NULL);
1338 view = MESSAGE_VIEW (message_view);
1339 select = gtk_tree_view_get_selection (GTK_TREE_VIEW
1340 (view->privat->tree_view));
1342 if (!gtk_tree_selection_get_selected (select, &model, &iter))
1344 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter))
1346 gtk_tree_model_get (GTK_TREE_MODEL (model),
1347 &iter, COLUMN_MESSAGE, &message, -1);
1348 if (message)
1350 if (message->details && strlen (message->details) > 0)
1351 return message->details;
1352 else
1353 return message->summary;
1357 else
1359 gtk_tree_model_get (GTK_TREE_MODEL (model),
1360 &iter, COLUMN_MESSAGE, &message, -1);
1361 if (message)
1363 if (message->details && strlen (message->details) > 0)
1364 return message->details;
1365 else
1366 return message->summary;
1369 return NULL;
1372 /* Returns a GList which contains all messages, the GList itself
1373 * must be freed, the messages are managed by the message view and
1374 * must not be freed. NULL is return if no messages are availible.
1376 static GList *
1377 imessage_view_get_all_messages (IAnjutaMessageView * message_view,
1378 GError ** e)
1380 MessageView *view;
1381 GtkListStore *store;
1382 GtkTreeIter iter;
1383 Message *message;
1384 GList *messages = NULL;
1386 g_return_val_if_fail (MESSAGE_IS_VIEW (message_view), NULL);
1388 view = MESSAGE_VIEW (message_view);
1389 store = GTK_LIST_STORE (gtk_tree_view_get_model
1390 (GTK_TREE_VIEW (view->privat->tree_view)));
1392 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
1396 gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
1397 COLUMN_MESSAGE, &message);
1398 messages = g_list_prepend (messages, message->details);
1400 while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
1402 return messages;
1405 static void
1406 imessage_view_iface_init (IAnjutaMessageViewIface *iface)
1408 iface->buffer_append = imessage_view_buffer_append;
1409 iface->append = imessage_view_append;
1410 iface->clear = imessage_view_clear;
1411 iface->select_next = imessage_view_select_next;
1412 iface->select_previous = imessage_view_select_previous;
1413 iface->get_current_message = imessage_view_get_current_message;
1414 iface->get_all_messages = imessage_view_get_all_messages;
1417 ANJUTA_TYPE_BEGIN(MessageView, message_view, GTK_TYPE_HBOX);
1418 ANJUTA_TYPE_ADD_INTERFACE(imessage_view, IANJUTA_TYPE_MESSAGE_VIEW);
1419 ANJUTA_TYPE_END;