Initial import of ephy (rev# 7126) from svn
[ephy-soc.git] / lib / widgets / .svn / text-base / ephy-location-entry.c.svn-base
blob1669e8abef27d6dbab82c6e40ca4705192aab694
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Copyright © 2002  Ricardo Fernández Pascual
4  *  Copyright © 2003, 2004  Marco Pesenti Gritti
5  *  Copyright © 2003, 2004, 2005  Christian Persch
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2, or (at your option)
10  *  any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  *  $Id$
22  */
24 #include "config.h"
26 #include "ephy-icon-entry.h"
27 #include "ephy-tree-model-node.h"
28 #include "ephy-location-entry.h"
29 #include "ephy-marshal.h"
30 #include "ephy-signal-accumulator.h"
31 #include "ephy-dnd.h"
32 #include "egg-editable-toolbar.h"
33 #include "ephy-stock-icons.h"
34 #include "ephy-debug.h"
35 #include "ephy-gui.h"
37 #include <glib/gi18n.h>
38 #include <gdk/gdkkeysyms.h>
39 #include <gtk/gtktoolbar.h>
40 #include <gtk/gtkentry.h>
41 #include <gtk/gtkwindow.h>
42 #include <gtk/gtkcellrenderertext.h>
43 #include <gtk/gtkcellrendererpixbuf.h>
44 #include <gtk/gtkcelllayout.h>
45 #include <gtk/gtktreemodelsort.h>
46 #include <gtk/gtkstock.h>
47 #include <gtk/gtkimage.h>
48 #include <gtk/gtkeventbox.h>
49 #include <gtk/gtkbox.h>
50 #include <gtk/gtkhbox.h>
51 #include <gtk/gtkvbox.h>
52 #include <gtk/gtkimagemenuitem.h>
53 #include <gtk/gtkseparatormenuitem.h>
54 #include <gtk/gtkalignment.h>
55 #include <gtk/gtkclipboard.h>
56 #include <gtk/gtkversion.h>
58 #include <string.h>
60 #define EPHY_LOCATION_ENTRY_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_LOCATION_ENTRY, EphyLocationEntryPrivate))
62 struct _EphyLocationEntryPrivate
64         GtkTooltips *tips;
65         EphyIconEntry *icon_entry;
66         GtkWidget *icon_ebox;
67         GtkWidget *icon;
68         GtkWidget *lock_ebox;
69         GtkWidget *lock;
70         GdkPixbuf *favicon;
71         GdkColor secure_bg_colour;
72         GdkColor secure_fg_colour;
74         char *before_completion;
76         guint text_col;
77         guint action_col;
78         guint keywords_col;
79         guint relevance_col;
80         guint extra_col;
81         guint favicon_col;
82         guint url_col;
84         guint hash;
86         guint user_changed : 1;
87         guint original_address : 1;
88         guint secure : 1;
89         guint apply_colours : 1;
90         guint needs_reset : 1;
93 static const struct
95         const char *prefix;
96         int len;
98 web_prefixes [] =
100         { "http://www.", 11 },
101         { "http://ftp.", 11 },
102         { "http://", 7 },
103         { "https://www.", 12 },
104         { "https://", 8 },
105         { "ftp://", 6},
106         { "ftp://ftp.", 10},
107         { "www.", 4 },
108         { "ftp.", 4}
111 static const GtkTargetEntry url_drag_types [] =
113         { EPHY_DND_URL_TYPE,        0, 0 },
114         { EPHY_DND_URI_LIST_TYPE,   0, 1 },
115         { EPHY_DND_TEXT_TYPE,       0, 2 }
118 static const GdkColor fallback_bg_colour = { 0, 0xf7f7, 0xf7f7, 0xbebe }; /* yellow-ish */
119 static const GdkColor fallback_fg_colour = { 0, 0, 0, 0 }; /* black */
121 static void ephy_location_entry_class_init (EphyLocationEntryClass *klass);
122 static void ephy_location_entry_init (EphyLocationEntry *le);
123 static gboolean ephy_location_entry_reset_internal (EphyLocationEntry *, gboolean);
125 static GObjectClass *parent_class = NULL;
127 enum signalsEnum
129         USER_CHANGED,
130         LOCK_CLICKED,
131         GET_LOCATION,
132         GET_TITLE,
133         LAST_SIGNAL
135 static gint signals[LAST_SIGNAL] = { 0 };
137 #define MAX_LOC_HISTORY_ITEMS 10
138 #define EPHY_LOC_HISTORY_XML_ROOT "ephy_location_history"
139 #define EPHY_LOC_HISTORY_XML_VERSION "0.1"
141 GType
142 ephy_location_entry_get_type (void)
144         static GType type = 0;
146         if (G_UNLIKELY (type == 0))
147         {
148                 const GTypeInfo our_info =
149                 {
150                         sizeof (EphyLocationEntryClass),
151                         NULL,
152                         NULL,
153                         (GClassInitFunc) ephy_location_entry_class_init,
154                         NULL,
155                         NULL,
156                         sizeof (EphyLocationEntry),
157                         0,
158                         (GInstanceInitFunc) ephy_location_entry_init
159                 };
161                 type = g_type_register_static (GTK_TYPE_TOOL_ITEM,
162                                                "EphyLocationEntry",
163                                                &our_info, 0);
164         }
166         return type;
169 static gboolean
170 ephy_location_entry_set_tooltip (GtkToolItem *tool_item,
171                                  GtkTooltips *tooltips,
172                                  const char *tip_text,
173                                  const char *tip_private)
175         EphyLocationEntry *entry = EPHY_LOCATION_ENTRY (tool_item);
176         EphyLocationEntryPrivate *priv = entry->priv;
178         gtk_tooltips_set_tip (tooltips, priv->icon_entry->entry,
179                               tip_text, tip_private);
181         return TRUE;
184 static void
185 ephy_location_entry_style_set (GtkWidget *widget,
186                                GtkStyle *previous_style)
188         EphyLocationEntry *entry = EPHY_LOCATION_ENTRY (widget);
189         EphyLocationEntryPrivate *priv = entry->priv;
190         GtkSettings *settings;
191         GdkColor *bg_colour = NULL, *fg_colour = NULL;
192         char *theme;
193         gboolean is_a11y_theme;
195         if (GTK_WIDGET_CLASS (parent_class)->style_set)
196         {
197                 GTK_WIDGET_CLASS (parent_class)->style_set (widget, previous_style);
198         }
200         settings = gtk_settings_get_for_screen (gtk_widget_get_screen (widget));
201         g_object_get (settings, "gtk-theme-name", &theme, NULL);
202         is_a11y_theme = strstr (theme, "HighContrast") != NULL ||
203                         strstr (theme, "LowContrast") != NULL;
204         g_free (theme);
206         gtk_widget_style_get (widget,
207                               "secure-fg-color", &fg_colour,
208                               "secure-bg-color", &bg_colour,
209                               NULL);
211         /* We only use the fallback colours when we don't have an a11y theme */
212         priv->apply_colours = !is_a11y_theme || (fg_colour != NULL && bg_colour != NULL);
214         if (fg_colour != NULL)
215         {
216                 priv->secure_fg_colour = *fg_colour;
217                 gdk_color_free (fg_colour);
218         }
219         else
220         {
221                 priv->secure_fg_colour = fallback_fg_colour;
222         }
224         if (bg_colour != NULL)
225         {
226                 priv->secure_bg_colour = *bg_colour;
227                 gdk_color_free (bg_colour);
228         }
229         else
230         {
231                 priv->secure_bg_colour = fallback_bg_colour;
232         }
234         /* Apply the new style */
235         ephy_location_entry_set_secure (entry, priv->secure);
238 static void
239 ephy_location_entry_finalize (GObject *object)
241         EphyLocationEntry *entry = EPHY_LOCATION_ENTRY (object);
242         EphyLocationEntryPrivate *priv = entry->priv;
244         if (priv->favicon != NULL)
245         {
246                 g_object_unref (priv->favicon);
247         }
249         g_object_unref (priv->tips);
251         parent_class->finalize (object);
254 static void
255 ephy_location_entry_class_init (EphyLocationEntryClass *klass)
257         GObjectClass *object_class = G_OBJECT_CLASS (klass);
258         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
259         GtkToolItemClass *tool_item_class = GTK_TOOL_ITEM_CLASS (klass);
261         parent_class = g_type_class_peek_parent (klass);
263         object_class->finalize = ephy_location_entry_finalize;
265         widget_class->style_set = ephy_location_entry_style_set;
267         tool_item_class->set_tooltip = ephy_location_entry_set_tooltip;
269         signals[USER_CHANGED] = g_signal_new (
270                 "user_changed", G_OBJECT_CLASS_TYPE (klass),
271                 G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST,
272                 G_STRUCT_OFFSET (EphyLocationEntryClass, user_changed),
273                 NULL, NULL,
274                 g_cclosure_marshal_VOID__VOID,
275                 G_TYPE_NONE,
276                 0,
277                 G_TYPE_NONE);
279         signals[LOCK_CLICKED] = g_signal_new (
280                 "lock-clicked",
281                 EPHY_TYPE_LOCATION_ENTRY,
282                 G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST,
283                 G_STRUCT_OFFSET (EphyLocationEntryClass, lock_clicked),
284                 NULL, NULL,
285                 g_cclosure_marshal_VOID__VOID,
286                 G_TYPE_NONE,
287                 0);
289         signals[GET_LOCATION] = g_signal_new (
290                 "get-location", G_OBJECT_CLASS_TYPE (klass),
291                 G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST,
292                 G_STRUCT_OFFSET (EphyLocationEntryClass, get_location),
293                 ephy_signal_accumulator_string, NULL,
294                 ephy_marshal_STRING__VOID,
295                 G_TYPE_STRING,
296                 0,
297                 G_TYPE_NONE);
299         signals[GET_TITLE] = g_signal_new (
300                 "get-title", G_OBJECT_CLASS_TYPE (klass),
301                 G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST,
302                 G_STRUCT_OFFSET (EphyLocationEntryClass, get_title),
303                 ephy_signal_accumulator_string, NULL,
304                 ephy_marshal_STRING__VOID,
305                 G_TYPE_STRING,
306                 0,
307                 G_TYPE_NONE);
309         gtk_widget_class_install_style_property (widget_class,
310                                                  g_param_spec_boxed ("secure-bg-color",
311                                                                      "Secure background colour",
312                                                                      "Background colour to use for secure sites",
313                                                                      GDK_TYPE_COLOR,
314                                                                      G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
316         gtk_widget_class_install_style_property (widget_class,
317                                                  g_param_spec_boxed ("secure-fg-color",
318                                                                      "Secure foreground Colour",
319                                                                      "Foreground colour to use for secure sites",
320                                                                      GDK_TYPE_COLOR,
321                                                                      G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
323         g_type_class_add_private (object_class, sizeof (EphyLocationEntryPrivate));
326 static void
327 update_address_state (EphyLocationEntry *entry)
329         EphyLocationEntryPrivate *priv = entry->priv;
330         const char *text;
332         text = gtk_entry_get_text (GTK_ENTRY (priv->icon_entry->entry));
333         priv->original_address = text != NULL &&
334                                  g_str_hash (text) == priv->hash;
337 static void
338 update_favicon (EphyLocationEntry *entry)
340         EphyLocationEntryPrivate *priv = entry->priv;
341         GtkImage *image = GTK_IMAGE (priv->icon);
343         /* Only show the favicon if the entry's text is the
344          * address of the current page.
345          */
346         if (priv->favicon != NULL && priv->original_address)
347         {
348                 gtk_image_set_from_pixbuf (image, priv->favicon);
349         }
350         else
351         {
352                 /* Here we could consider using fallback favicon that matches
353                  * the page MIME type, though text/html should be good enough
354                  * most of the time. See #337140
355                  */
356                 gtk_image_set_from_icon_name (image,
357                                               "text-html",
358                                               GTK_ICON_SIZE_MENU);
359         }
362 static void
363 editable_changed_cb (GtkEditable *editable,
364                      EphyLocationEntry *entry)
366         EphyLocationEntryPrivate *priv = entry->priv;
368         update_address_state (entry);
370         if (priv->user_changed == FALSE) return;
372         update_favicon (entry);
374         g_signal_emit (entry, signals[USER_CHANGED], 0);
377 static gboolean
378 entry_button_press_cb (GtkWidget *entry, GdkEventButton *event, EphyLocationEntry *le)
380         if (event->button == 1 && event->type == GDK_2BUTTON_PRESS)
381         {
382                 ephy_location_entry_activate (le);
383                 return TRUE;
384         }
386         return FALSE;
389 static gboolean
390 entry_key_press_cb (GtkEntry *entry,
391                     GdkEventKey *event,
392                     EphyLocationEntry *lentry)
394         guint state = event->state & gtk_accelerator_get_default_mod_mask ();
396         if (event->keyval == GDK_Escape && state == 0)
397         {
398                 ephy_location_entry_reset_internal (lentry, TRUE);
399                 /* don't return TRUE since we want to cancel the autocompletion popup too */
400         }
402         return FALSE;
405 static gboolean
406 entry_key_press_after_cb (GtkEntry *entry,
407                           GdkEventKey *event,
408                           EphyLocationEntry *lentry)
410         EphyLocationEntryPrivate *priv = lentry->priv;
412         guint state = event->state & gtk_accelerator_get_default_mod_mask ();
414         if ((event->keyval == GDK_Return ||
415              event->keyval == GDK_KP_Enter ||
416              event->keyval == GDK_ISO_Enter) &&
417             (state == GDK_CONTROL_MASK ||
418              state == (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
419         {
420                 gtk_im_context_reset (entry->im_context);
422                 priv->needs_reset = TRUE;
423                 g_signal_emit_by_name (entry, "activate");
425                 return TRUE;
426         }
428         return FALSE;
431 static void
432 entry_activate_after_cb (GtkEntry *entry,
433                          EphyLocationEntry *lentry)
435         EphyLocationEntryPrivate *priv = lentry->priv;
437         if (priv->needs_reset)
438         {
439                 ephy_location_entry_reset_internal (lentry, TRUE);
440                 priv->needs_reset = FALSE;
441         }
444 static gboolean
445 keyword_match (const char *list,
446                const char *keyword)
448         const char *p;
449         gsize keyword_len;
451         p = list;
452         keyword_len = strlen (keyword);
454         while (*p)
455         {
456                 int i;
458                 for (i = 0; i < keyword_len; i++)
459                 {
460                         if (p[i] != keyword[i])
461                         {
462                                 goto next_token;
463                         }
464                 }
465           
466                 return TRUE;
467           
468                 next_token:
470                 while (*p && !g_ascii_ispunct(*p) && !g_ascii_isspace(*p)) p++;
471                 while (*p && (g_ascii_ispunct(*p) || g_ascii_isspace(*p))) p++;
472         }
474         return FALSE;
477 static gboolean
478 completion_func (GtkEntryCompletion *completion,
479                  const char *key,
480                  GtkTreeIter *iter,
481                  gpointer data)
483         int i, len_key, len_prefix;
484         char *item = NULL;
485         char *keywords = NULL;
486         gboolean ret = FALSE;
487         EphyLocationEntry *le = EPHY_LOCATION_ENTRY (data);
488         GtkTreeModel *model;
490         model = gtk_entry_completion_get_model (completion);
492         gtk_tree_model_get (model, iter,
493                             le->priv->text_col, &item,
494                             le->priv->keywords_col, &keywords,
495                             -1);
497         len_key = strlen (key);
498         if (!strncasecmp (key, item, len_key))
499         {
500                 ret = TRUE;
501         }
502         else if (keyword_match (keywords, key))
503         {
504                 ret = TRUE;
505         }
506         else
507         {
508                 for (i = 0; i < G_N_ELEMENTS (web_prefixes); i++)
509                 {
510                         len_prefix = web_prefixes[i].len;
511                         if (!strncmp (web_prefixes[i].prefix, item, len_prefix) &&
512                             !strncasecmp (key, item + len_prefix, len_key))
513                         {
514                                 ret = TRUE;
515                                 break;
516                         }
517                 }
518         }
520         g_free (item);
521         g_free (keywords);
523         return ret;
526 static gboolean
527 match_selected_cb (GtkEntryCompletion *completion,
528                    GtkTreeModel *model,
529                    GtkTreeIter *iter,
530                    EphyLocationEntry *entry)
532         EphyLocationEntryPrivate *priv = entry->priv;
533         char *item = NULL;
534         guint state;
536         gtk_tree_model_get (model, iter,
537                             priv->action_col, &item, -1);
538         if (item == NULL) return FALSE;
540         ephy_gui_get_current_event (NULL, &state, NULL);
542         priv->needs_reset = (state == GDK_CONTROL_MASK ||
543                              state == (GDK_CONTROL_MASK | GDK_SHIFT_MASK));
545         ephy_location_entry_set_location (entry, item, NULL);
546         gtk_im_context_reset (GTK_ENTRY (entry->priv->icon_entry->entry)->im_context);
547         g_signal_emit_by_name (priv->icon_entry->entry, "activate");
549         g_free (item);
551         return TRUE;
554 static void
555 action_activated_after_cb (GtkEntryCompletion *completion,
556                            gint index,
557                            EphyLocationEntry *lentry)
559         guint state, button;
561         ephy_gui_get_current_event (NULL, &state, &button);
562         if ((state == GDK_CONTROL_MASK ||
563              state == (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) ||
564             button == 2)
565         {
566                 ephy_location_entry_reset_internal (lentry, TRUE);
567         }
570 static gboolean
571 toolbar_is_editable (GtkWidget *widget)
573         GtkWidget *etoolbar;
575         etoolbar = gtk_widget_get_ancestor (widget, EGG_TYPE_EDITABLE_TOOLBAR);
577         if (etoolbar)
578         {
579                 return egg_editable_toolbar_get_edit_mode
580                         (EGG_EDITABLE_TOOLBAR (etoolbar));
581         }
583         return FALSE;
586 static gboolean
587 entry_drag_motion_cb (GtkWidget        *widget,
588                       GdkDragContext   *context,
589                       gint              x,
590                       gint              y,
591                       guint             time)
593         if (toolbar_is_editable (widget))
594         {
595                 g_signal_stop_emission_by_name (widget, "drag_motion");
596         }
597     
598         return FALSE;
601 static gboolean
602 entry_drag_drop_cb (GtkWidget          *widget,
603                     GdkDragContext     *context,
604                     gint                x,
605                     gint                y,
606                     guint               time)
608         if (toolbar_is_editable (widget))
609         {
610                 g_signal_stop_emission_by_name (widget, "drag_drop");
611         }
613         return FALSE;
616 static void
617 entry_clear_activate_cb (GtkMenuItem *item,
618                          EphyLocationEntry *entry)
620         EphyLocationEntryPrivate *priv = entry->priv;
622         priv->user_changed = FALSE;
623         gtk_entry_set_text (GTK_ENTRY (priv->icon_entry->entry), "");
624         priv->user_changed = TRUE;
627 static void
628 entry_populate_popup_cb (GtkEntry *entry,
629                          GtkMenu *menu,
630                          EphyLocationEntry *lentry)
632         EphyLocationEntryPrivate *priv = lentry->priv;
633         GtkWidget *image;
634         GtkWidget *menuitem;
635         GList *children, *item;
636         int pos = 0, sep = 0;
637         gboolean is_editable;
639         /* Clear and Copy mnemonics conflict, make custom menuitem */
640         image = gtk_image_new_from_stock (GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU);
641         gtk_widget_show (image);
643         /* Translators: the mnemonic shouldn't conflict with any of the
644          * standard items in the GtkEntry context menu (Cut, Copy, Paste, Delete,
645          * Select All, Input Methods and Insert Unicode control character.)
646          */
647         menuitem = gtk_image_menu_item_new_with_mnemonic (_("Cl_ear"));
648         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM(menuitem), image);
649         g_signal_connect (menuitem , "activate",
650                           G_CALLBACK (entry_clear_activate_cb), lentry);
651         is_editable = gtk_editable_get_editable (GTK_EDITABLE (priv->icon_entry->entry));
652         gtk_widget_set_sensitive (menuitem, is_editable);
653         gtk_widget_show (menuitem);
655         /* search for the 2nd separator (the one after Select All) in the context
656          * menu, and insert this menu item before it.
657          * It's a bit of a hack, but there seems to be no better way to do it :/
658          */
659         children = GTK_MENU_SHELL (menu)->children;
660         for (item = children; item != NULL && sep < 2; item = item->next, pos++)
661         {
662                 if (GTK_IS_SEPARATOR_MENU_ITEM (item->data)) sep++;
663         }
665         gtk_menu_shell_insert (GTK_MENU_SHELL (menu), menuitem, pos - 1);
668 static void
669 each_url_get_data_binder (EphyDragEachSelectedItemDataGet iteratee,
670                           gpointer iterator_context,
671                           gpointer return_data)
673         EphyLocationEntry *entry = EPHY_LOCATION_ENTRY (iterator_context);
674         char *title = NULL, *address = NULL;
676         g_signal_emit (entry, signals[GET_LOCATION], 0, &address);
677         g_signal_emit (entry, signals[GET_TITLE], 0, &title);
678         g_return_if_fail (address != NULL && title != NULL);
680         iteratee (address, title, return_data);
682         g_free (address);
683         g_free (title);
686 #define DRAG_ICON_LAYOUT_BORDER         2
687 #define DRAG_ICON_ICON_SPACING          DRAG_ICON_LAYOUT_BORDER * 2
688 #define DRAG_ICON_MAX_WIDTH_CHARS       32
690 static GdkPixmap *
691 favicon_create_drag_pixmap (EphyLocationEntry *entry,
692                             GtkWidget *widget)
694         EphyLocationEntryPrivate *priv = entry->priv;
695         char *title = NULL, *address = NULL;
696         GString *text;
697         GdkDrawable *drawable;
698         PangoContext *context;
699         PangoLayout  *layout;
700         PangoFontMetrics *metrics;
701         int pixmap_height, pixmap_width;
702         int layout_width, layout_height;
703         int icon_width = 0, icon_height = 0, offset_x = 0;
704         int char_width;
706         g_signal_emit (entry, signals[GET_LOCATION], 0, &address);
707         g_signal_emit (entry, signals[GET_TITLE], 0, &title);
708         if (address == NULL || title == NULL) return NULL;
710         /* Compute text */
711         title = g_strstrip (title);
713         text = g_string_sized_new (strlen (address) + strlen (title) + 2);
714         if (title[0] != '\0')
715         {
716                 g_string_append (text, title);
717                 g_string_append (text, "\n");
718         }
720         if (address[0] != '\0')
721         {
722                 g_string_append (text, address);
723         }
725         /* Now build the pixmap */
727         if (priv->favicon != NULL)
728         {
729                 icon_width = gdk_pixbuf_get_width (priv->favicon);
730                 icon_height = gdk_pixbuf_get_height (priv->favicon);
731         }
733         context = gtk_widget_get_pango_context (widget);
734         layout = pango_layout_new (context);
736         context = gtk_widget_get_pango_context (widget);
737         metrics = pango_context_get_metrics (context,
738                                              widget->style->font_desc,
739                                              pango_context_get_language (context));
741         char_width = pango_font_metrics_get_approximate_digit_width (metrics);
742         pango_font_metrics_unref (metrics);
744         pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
745         pango_layout_set_width (layout, char_width * DRAG_ICON_MAX_WIDTH_CHARS);
746         pango_layout_set_text (layout, text->str, text->len);
748         pango_layout_get_size (layout, &layout_width, &layout_height);
750         pixmap_width  = layout_width  / PANGO_SCALE + DRAG_ICON_LAYOUT_BORDER * 2;
752         if (priv->favicon != NULL)
753         {
754                 offset_x = icon_width + 2 * DRAG_ICON_ICON_SPACING;
755                 pixmap_width += offset_x;
756                 pixmap_height = MAX (layout_height / PANGO_SCALE, icon_height) + DRAG_ICON_LAYOUT_BORDER * 2;
757         }
758         else
759         {
760                 pixmap_height = layout_height / PANGO_SCALE + DRAG_ICON_LAYOUT_BORDER * 2;
761         }
763         drawable = gdk_pixmap_new (widget->window,
764                                    pixmap_width  + 2,
765                                    pixmap_height + 2,
766                                    -1);
768         gdk_draw_rectangle (drawable,
769                             widget->style->base_gc [GTK_WIDGET_STATE (widget)],
770                             TRUE,
771                             0, 0,
772                             pixmap_width + 1,
773                             pixmap_height + 1);
776         if (priv->favicon != NULL)
777         {
778                 gdk_draw_pixbuf (drawable,
779                                  widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
780                                  priv->favicon,
781                                  0, 0, 
782                                  1 + DRAG_ICON_LAYOUT_BORDER + DRAG_ICON_ICON_SPACING,
783                                  1 + DRAG_ICON_LAYOUT_BORDER + (pixmap_height - icon_height) / 2,
784                                  -1, -1,
785                                  GDK_RGB_DITHER_NONE, 0, 0);
786         }
788         gdk_draw_layout (drawable,
789                          widget->style->text_gc [GTK_WIDGET_STATE (widget)],
790                          1 + DRAG_ICON_LAYOUT_BORDER + offset_x,
791                          1 + DRAG_ICON_LAYOUT_BORDER,
792                          layout);
794         gdk_draw_rectangle (drawable,
795                             widget->style->black_gc,
796                             FALSE,
797                             0, 0,
798                             pixmap_width + 1,
799                             pixmap_height + 1);
801         g_object_unref (layout);
803         g_free (address);
804         g_free (title);
805         g_string_free (text,TRUE);
807         return drawable;
810 static void
811 favicon_drag_begin_cb (GtkWidget *widget,
812                        GdkDragContext *context,
813                        EphyLocationEntry *entry)
815         GdkPixmap *pixmap;
817         pixmap = favicon_create_drag_pixmap (entry, widget);
819         if (pixmap != NULL)
820         {
821                 gtk_drag_set_icon_pixmap (context,
822                                           gdk_drawable_get_colormap (pixmap),
823                                           pixmap, NULL, -2, -2);
824                 g_object_unref (pixmap);
825         }
828 static void
829 favicon_drag_data_get_cb (GtkWidget *widget,
830                           GdkDragContext *context,
831                           GtkSelectionData *selection_data,
832                           guint info,
833                           guint32 time,
834                           EphyLocationEntry *entry)
836         g_assert (widget != NULL);
837         g_return_if_fail (context != NULL);
839         ephy_dnd_drag_data_get (widget, context, selection_data,
840                 time, entry, each_url_get_data_binder);
843 static gboolean
844 icon_button_press_event_cb (GtkWidget *ebox,
845                             GdkEventButton *event,
846                             EphyLocationEntry *entry)
848         guint state = event->state & gtk_accelerator_get_default_mod_mask ();
850         if (event->type == GDK_BUTTON_PRESS && 
851             event->button == 1 && 
852             state == 0 /* left */)
853         {
854                 GtkWidget *toplevel;
855                 EphyLocationEntryPrivate *priv = entry->priv;
856                 
857                 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (entry));
858                 gtk_window_set_focus (GTK_WINDOW(toplevel),
859                                       priv->icon_entry->entry);
861                 gtk_editable_select_region (GTK_EDITABLE (priv->icon_entry->entry), 
862                                             0, -1);
864                 return TRUE;
865         }
867         return FALSE;
870 static gboolean
871 lock_button_press_event_cb (GtkWidget *ebox,
872                             GdkEventButton *event,
873                             EphyLocationEntry *entry)
875         if (event->type == GDK_BUTTON_PRESS && event->button == 1 /* left */)
876         {
877                 g_signal_emit (entry, signals[LOCK_CLICKED], 0);
879                 return TRUE;
880         }
882         return FALSE;
885 static void
886 ephy_location_entry_construct_contents (EphyLocationEntry *entry)
888         EphyLocationEntryPrivate *priv = entry->priv;
889         GtkWidget *alignment;
891         LOG ("EphyLocationEntry constructing contents %p", entry);
893         alignment = gtk_alignment_new (0.0, 0.5, 1.0, 0.0);
894         gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 1, 1);
895         gtk_container_add (GTK_CONTAINER (entry), alignment);
897         priv->icon_entry = EPHY_ICON_ENTRY (ephy_icon_entry_new ());
898         gtk_container_add (GTK_CONTAINER (alignment),
899                            GTK_WIDGET (priv->icon_entry));
901         priv->icon_ebox = gtk_event_box_new ();
902         gtk_container_set_border_width (GTK_CONTAINER (priv->icon_ebox), 2);
903         gtk_event_box_set_visible_window (GTK_EVENT_BOX (priv->icon_ebox), FALSE);
904         gtk_widget_add_events (priv->icon_ebox, GDK_BUTTON_PRESS_MASK |
905                                                 GDK_BUTTON_RELEASE_MASK |
906                                                 GDK_POINTER_MOTION_HINT_MASK);
907         gtk_drag_source_set (priv->icon_ebox, GDK_BUTTON1_MASK,
908                              url_drag_types, G_N_ELEMENTS (url_drag_types),
909                              GDK_ACTION_ASK | GDK_ACTION_COPY | GDK_ACTION_LINK);
910         g_signal_connect (priv->icon_ebox, "button-press-event",
911                           G_CALLBACK (icon_button_press_event_cb), entry);
913         gtk_tooltips_set_tip (priv->tips, priv->icon_ebox,
914                               _("Drag and drop this icon to create a link to this page"), NULL);
916         priv->icon = gtk_image_new ();
917         gtk_container_add (GTK_CONTAINER (priv->icon_ebox), priv->icon);
919         ephy_icon_entry_pack_widget (priv->icon_entry, priv->icon_ebox, TRUE);
921         priv->lock_ebox = gtk_event_box_new ();
922         gtk_container_set_border_width (GTK_CONTAINER (priv->lock_ebox), 2);
923         gtk_widget_add_events (priv->lock_ebox, GDK_BUTTON_PRESS_MASK);
924         gtk_event_box_set_visible_window (GTK_EVENT_BOX (priv->lock_ebox), FALSE);
926         priv->lock = gtk_image_new_from_stock (STOCK_LOCK_INSECURE,
927                                                GTK_ICON_SIZE_MENU);
928         gtk_container_add (GTK_CONTAINER (priv->lock_ebox), priv->lock);
930         ephy_icon_entry_pack_widget (priv->icon_entry, priv->lock_ebox, FALSE);
932         g_signal_connect (priv->icon_ebox, "drag-data-get",
933                           G_CALLBACK (favicon_drag_data_get_cb), entry);
934         g_signal_connect (priv->icon_ebox, "drag-begin",
935                           G_CALLBACK (favicon_drag_begin_cb), entry);
937         g_signal_connect (priv->lock_ebox, "button-press-event",
938                           G_CALLBACK (lock_button_press_event_cb), entry);
940         g_signal_connect (priv->icon_entry->entry, "populate_popup",
941                           G_CALLBACK (entry_populate_popup_cb), entry);
942         g_signal_connect (priv->icon_entry->entry, "key-press-event",
943                           G_CALLBACK (entry_key_press_cb), entry);
944         g_signal_connect_after (priv->icon_entry->entry, "key-press-event",
945                                 G_CALLBACK (entry_key_press_after_cb), entry);
946         g_signal_connect_after (priv->icon_entry->entry, "activate",
947                                 G_CALLBACK (entry_activate_after_cb), entry);
948         g_signal_connect (priv->icon_entry->entry, "button-press-event",
949                           G_CALLBACK (entry_button_press_cb), entry);
950         g_signal_connect (priv->icon_entry->entry, "changed",
951                           G_CALLBACK (editable_changed_cb), entry);
952         g_signal_connect (priv->icon_entry->entry, "drag-motion",
953                           G_CALLBACK (entry_drag_motion_cb), entry);
954         g_signal_connect (priv->icon_entry->entry, "drag-drop",
955                           G_CALLBACK (entry_drag_drop_cb), entry);
957         gtk_widget_show_all (alignment);
960 static void
961 ephy_location_entry_init (EphyLocationEntry *le)
963         EphyLocationEntryPrivate *p;
965         LOG ("EphyLocationEntry initialising %p", le);
967         p = EPHY_LOCATION_ENTRY_GET_PRIVATE (le);
968         le->priv = p;
970         p->user_changed = TRUE;
972         p->tips = gtk_tooltips_new ();
973         g_object_ref_sink (p->tips);
975         ephy_location_entry_construct_contents (le);
977         gtk_tool_item_set_expand (GTK_TOOL_ITEM (le), TRUE);
980 GtkWidget *
981 ephy_location_entry_new (void)
983         return GTK_WIDGET (g_object_new (EPHY_TYPE_LOCATION_ENTRY, NULL));
986 static gint
987 sort_func (GtkTreeModel *model,
988            GtkTreeIter *a,
989            GtkTreeIter *b,
990            gpointer data)
992         gint valuea, valueb;
993         EphyLocationEntry *le = EPHY_LOCATION_ENTRY (data);
995         gtk_tree_model_get (model, a,
996                             le->priv->relevance_col, &valuea, -1);
997         gtk_tree_model_get (model, b,
998                             le->priv->relevance_col, &valueb, -1);
1000         return valueb - valuea;
1003 #if GTK_CHECK_VERSION (2, 11, 0)
1004 static gboolean
1005 cursor_on_match_cb  (GtkEntryCompletion *completion,
1006                      GtkTreeModel *model,
1007                      GtkTreeIter *iter,
1008                      EphyLocationEntry *le)
1010         char *item = NULL;
1011         GtkWidget *entry;
1013         gtk_tree_model_get (model, iter,
1014                             le->priv->url_col,
1015                             &item, -1);
1016         
1017         entry = gtk_entry_completion_get_entry (completion);
1018         gtk_entry_set_text (GTK_ENTRY (entry), item);
1020         g_free (item);
1022         return TRUE;
1024 #endif /* GTK+ 2.11.0 */
1026 void
1027 ephy_location_entry_set_completion (EphyLocationEntry *le,
1028                                     GtkTreeModel *model,
1029                                     guint text_col,
1030                                     guint action_col,
1031                                     guint keywords_col,
1032                                     guint relevance_col,
1033                                     guint extra_col,
1034                                     guint favicon_col,
1035                                     guint url_col)
1037         EphyLocationEntryPrivate *priv = le->priv;
1038         GtkTreeModel *sort_model;
1039         GtkEntryCompletion *completion;
1040         GtkCellRenderer *cell, *extracell, *iconcell;
1042         le->priv->text_col = text_col;
1043         le->priv->action_col = action_col;
1044         le->priv->keywords_col = keywords_col;
1045         le->priv->relevance_col = relevance_col;
1046         le->priv->extra_col = extra_col;
1047         le->priv->favicon_col = favicon_col;
1048         le->priv->url_col = url_col;
1050         sort_model = gtk_tree_model_sort_new_with_model (model);
1051         g_object_unref (model);
1052         gtk_tree_sortable_set_default_sort_func
1053                 (GTK_TREE_SORTABLE (sort_model),
1054                  sort_func, le, NULL);
1056         completion = gtk_entry_completion_new ();
1057         gtk_entry_completion_set_model (completion, sort_model);
1058         g_object_unref (sort_model);
1059         gtk_entry_completion_set_match_func (completion, completion_func, le, NULL);
1060         g_signal_connect (completion, "match-selected",
1061                           G_CALLBACK (match_selected_cb), le);
1062         g_signal_connect_after (completion, "action-activated",
1063                                 G_CALLBACK (action_activated_after_cb), le);
1065         iconcell = gtk_cell_renderer_pixbuf_new ();
1067         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion),
1068                                     iconcell, FALSE);
1069         gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion),
1070                                        iconcell, "pixbuf", favicon_col);
1072         cell = gtk_cell_renderer_text_new ();
1073         g_object_set (cell, 
1074                         "ellipsize", PANGO_ELLIPSIZE_END,
1075                         "ellipsize-set", TRUE,
1076                         NULL);
1077         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion),
1078                                     cell, TRUE);
1079         gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion),
1080                                        cell, "text", text_col);
1082         extracell = gtk_cell_renderer_text_new ();
1083         g_object_set (extracell, 
1084                         "ellipsize", PANGO_ELLIPSIZE_END,
1085                         "ellipsize-set", TRUE,
1086                         "foreground", "Gray",
1087                         "foreground-set", TRUE,
1088                         "alignment", PANGO_ALIGN_RIGHT,
1089                         NULL);
1091         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion),
1092                                     extracell, TRUE);
1093         gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion),
1094                                        extracell, "text", extra_col);
1096 #if GTK_CHECK_VERSION (2, 11, 0)
1097         g_object_set (completion, "inline-selection", TRUE, NULL);
1098         g_signal_connect (completion, "cursor-on-match",
1099                           G_CALLBACK (cursor_on_match_cb), le);
1100 #endif
1102         gtk_entry_set_completion (GTK_ENTRY (priv->icon_entry->entry), completion);
1103         g_object_unref (completion);
1106 void
1107 ephy_location_entry_set_location (EphyLocationEntry *entry,
1108                                   const char *address,
1109                                   const char *typed_address)
1111         EphyLocationEntryPrivate *priv = entry->priv;
1112         GtkClipboard *clipboard;
1113         const char *text;
1114         char* selection = NULL;
1115         int start, end;
1117         g_return_if_fail (address != NULL);
1119         /* Setting a new text will clear the clipboard. This makes it impossible
1120          * to copy&paste from the location entry of one tab into another tab, see
1121          * bug #155824. So we save the selection iff the clipboard was owned by
1122          * the location entry.
1123          */
1124         if (GTK_WIDGET_REALIZED (GTK_WIDGET (priv->icon_entry)))
1125         {
1126                 GtkWidget *gtkentry = priv->icon_entry->entry;
1128                 clipboard = gtk_widget_get_clipboard (gtkentry,
1129                                                       GDK_SELECTION_PRIMARY);
1130                 g_return_if_fail (clipboard != NULL);
1132                 if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (gtkentry) &&
1133                     gtk_editable_get_selection_bounds (GTK_EDITABLE (gtkentry),
1134                                                        &start, &end))
1135                 {
1136                         selection = gtk_editable_get_chars (GTK_EDITABLE (gtkentry),
1137                                                             start, end);
1138                 }
1139         }
1141         if (typed_address != NULL)
1142         {
1143                 text = typed_address;
1144         }
1145         else if (address != NULL && strcmp (address, "about:blank") != 0)
1146         {
1147                 text = address;
1148         }
1149         else
1150         {
1151                 text = "";
1152         }
1154         /* First record the new hash, then update the entry text */
1155         priv->hash = g_str_hash (address);
1157         priv->user_changed = FALSE;
1158         gtk_entry_set_text (GTK_ENTRY (priv->icon_entry->entry), text);
1159         priv->user_changed = TRUE;
1161         /* We need to call update_address_state() here, as the 'changed' signal
1162          * may not get called if the user has typed in the exact correct url */
1163         update_address_state (entry);
1164         update_favicon (entry);
1166         /* Now restore the selection.
1167          * Note that it's not owned by the entry anymore!
1168          */
1169         if (selection != NULL)
1170         {
1171                 gtk_clipboard_set_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY),
1172                                         selection, strlen (selection));
1173                 g_free (selection);
1174         }
1177 const char *
1178 ephy_location_entry_get_location (EphyLocationEntry *entry)
1180         EphyLocationEntryPrivate *priv = entry->priv;
1182         return gtk_entry_get_text (GTK_ENTRY (priv->icon_entry->entry));
1185 static gboolean
1186 ephy_location_entry_reset_internal (EphyLocationEntry *entry,
1187                                     gboolean notify)
1189         EphyLocationEntryPrivate *priv = entry->priv;
1190         const char *text, *old_text;
1191         char *url = NULL;
1192         gboolean retval;
1194         g_signal_emit (entry, signals[GET_LOCATION], 0, &url);
1195         text = url != NULL ? url : "";
1196         old_text = gtk_entry_get_text (GTK_ENTRY (priv->icon_entry->entry));
1197         old_text = old_text != NULL ? old_text : "";
1199         retval = g_str_hash (text) != g_str_hash (old_text);
1201         ephy_location_entry_set_location (entry, text, NULL);
1202         g_free (url);
1204         if (notify)
1205         {
1206                 g_signal_emit (entry, signals[USER_CHANGED], 0);
1207         }
1209         return retval;
1212 gboolean
1213 ephy_location_entry_reset (EphyLocationEntry *entry)
1215         return ephy_location_entry_reset_internal (entry, FALSE);
1218 void
1219 ephy_location_entry_activate (EphyLocationEntry *entry)
1221         EphyLocationEntryPrivate *priv = entry->priv;
1222         GtkWidget *toplevel;
1224         toplevel = gtk_widget_get_toplevel (GTK_WIDGET (entry));
1226         gtk_editable_select_region (GTK_EDITABLE(priv->icon_entry->entry),
1227                                     0, -1);
1228         gtk_window_set_focus (GTK_WINDOW(toplevel),
1229                               priv->icon_entry->entry);
1232 GtkWidget *
1233 ephy_location_entry_get_entry (EphyLocationEntry *entry)
1235         EphyLocationEntryPrivate *priv = entry->priv;
1237         return priv->icon_entry->entry;
1240 void
1241 ephy_location_entry_set_favicon (EphyLocationEntry *entry,
1242                                  GdkPixbuf *pixbuf)
1244         EphyLocationEntryPrivate *priv = entry->priv;
1246         if (priv->favicon != NULL)
1247         {
1248                 g_object_unref (priv->favicon);
1249         }
1251         priv->favicon = pixbuf ? g_object_ref (pixbuf) : NULL;
1253         update_favicon (entry);
1256 void
1257 ephy_location_entry_set_secure (EphyLocationEntry *entry,
1258                                 gboolean secure)
1260         EphyLocationEntryPrivate *priv = entry->priv;
1261         GtkWidget *widget = GTK_WIDGET (entry);
1262         GtkWidget *gentry = ephy_icon_entry_get_entry (entry->priv->icon_entry);
1264         priv->secure = secure;
1266         /* We have to set the colour of the GtkEntry in the EphyIconEntry */
1267         if (priv->secure && priv->apply_colours)
1268         {
1269                 gtk_widget_modify_text (gentry, GTK_STATE_NORMAL, &priv->secure_fg_colour);
1270                 gtk_widget_modify_base (gentry, GTK_STATE_NORMAL, &priv->secure_bg_colour);
1271         }
1272         else
1273         {
1274                 gtk_widget_modify_text (gentry, GTK_STATE_NORMAL, NULL);
1275                 gtk_widget_modify_base (gentry, GTK_STATE_NORMAL, NULL);
1276         }
1278         gtk_widget_queue_draw (widget);
1281 void
1282 ephy_location_entry_set_show_lock (EphyLocationEntry *entry,
1283                                    gboolean show_lock)
1285         EphyLocationEntryPrivate *priv = entry->priv;
1287         g_object_set (priv->lock_ebox, "visible", show_lock, NULL);
1290 void
1291 ephy_location_entry_set_lock_stock (EphyLocationEntry *entry,
1292                                     const char *stock_id)
1295         EphyLocationEntryPrivate *priv = entry->priv;
1297         gtk_image_set_from_stock (GTK_IMAGE (priv->lock), stock_id,
1298                                   GTK_ICON_SIZE_MENU);
1301 void
1302 ephy_location_entry_set_lock_tooltip (EphyLocationEntry *entry,
1303                                       const char *tooltip)
1305         EphyLocationEntryPrivate *priv = entry->priv;
1307         gtk_tooltips_set_tip (priv->tips, priv->lock_ebox, tooltip, NULL);