/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
* Copyright © 2003, 2004 Marco Pesenti Gritti
* Copyright © 2003, 2004 Christian Persch
*
* This file is part of Epiphany.
*
* Epiphany is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Epiphany is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Epiphany. If not, see .
*/
#include "config.h"
#include "ephy-location-controller.h"
#include "ephy-debug.h"
#include "ephy-dnd.h"
#include "ephy-embed-container.h"
#include "ephy-embed-utils.h"
#include "ephy-link.h"
#include "ephy-location-entry.h"
#include "ephy-shell.h"
#include "ephy-suggestion-model.h"
#include "ephy-title-widget.h"
#include "ephy-uri-helpers.h"
#include "ephy-widgets-type-builtins.h"
#include
#include
#include
#include
/**
* SECTION:ephy-location-controller
* @short_description: An #EphyLink implementation
*
* #EphyLocationController handles navigation together with an #EphyTitleWidget
*/
struct _EphyLocationController {
GObject parent_instance;
EphyWindow *window;
EphyTitleWidget *title_widget;
GtkGesture *longpress_gesture;
char *address;
gboolean editable;
gboolean sync_address_is_blocked;
EphySearchEngineManager *search_engine_manager;
};
static void ephy_location_controller_finalize (GObject *object);
static void user_changed_cb (GtkWidget *widget,
EphyLocationController *controller);
static void sync_address (EphyLocationController *controller,
GParamSpec *pspec,
GtkWidget *widget);
enum {
PROP_0,
PROP_ADDRESS,
PROP_EDITABLE,
PROP_WINDOW,
PROP_TITLE_WIDGET,
LAST_PROP
};
static GParamSpec *obj_properties[LAST_PROP];
G_DEFINE_TYPE_WITH_CODE (EphyLocationController, ephy_location_controller, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (EPHY_TYPE_LINK,
NULL))
static void
entry_drag_data_received_cb (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
GtkSelectionData *selection_data,
guint info,
guint time,
EphyLocationController *controller)
{
GtkEntry *entry;
GdkAtom url_type;
GdkAtom text_type;
const guchar *sel_data;
sel_data = gtk_selection_data_get_data (selection_data);
url_type = gdk_atom_intern (EPHY_DND_URL_TYPE, FALSE);
text_type = gdk_atom_intern (EPHY_DND_TEXT_TYPE, FALSE);
if (gtk_selection_data_get_length (selection_data) <= 0 || sel_data == NULL)
return;
entry = GTK_ENTRY (widget);
if (gtk_selection_data_get_target (selection_data) == url_type) {
char **uris;
uris = g_uri_list_extract_uris ((char *)sel_data);
if (uris != NULL && uris[0] != NULL && *uris[0] != '\0') {
gtk_entry_set_text (entry, (char *)uris[0]);
ephy_link_open (EPHY_LINK (controller),
uris[0],
NULL,
ephy_link_flags_from_current_event ());
}
g_strfreev (uris);
} else if (gtk_selection_data_get_target (selection_data) == text_type) {
char *address;
gtk_entry_set_text (entry, (const gchar *)sel_data);
address = ephy_embed_utils_normalize_or_autosearch_address ((const gchar *)sel_data);
ephy_link_open (EPHY_LINK (controller),
address,
NULL,
ephy_link_flags_from_current_event ());
g_free (address);
}
}
static void
entry_activate_cb (GtkEntry *entry,
EphyLocationController *controller)
{
const char *content;
char *address;
char *effective_address;
if (controller->sync_address_is_blocked) {
controller->sync_address_is_blocked = FALSE;
g_signal_handlers_unblock_by_func (controller, G_CALLBACK (sync_address), entry);
}
content = gtk_entry_get_text (entry);
if (content == NULL || content[0] == '\0')
return;
if (g_str_has_prefix (content, "ephy-tab://")) {
GtkWidget *notebook = ephy_window_get_notebook (controller->window);
GtkWidget *tab;
EphyWebView *webview;
gint current = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
g_auto (GStrv) split = g_strsplit (content + strlen ("ephy-tab://"), "@", -1);
g_assert (g_strv_length (split) == 2);
tab = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), current);
webview = ephy_embed_get_web_view (EPHY_EMBED (tab));
if (atoi (split[1]) != 0) {
GApplication *application;
EphyEmbedShell *shell;
EphyWindow *window;
GList *windows;
shell = ephy_embed_shell_get_default ();
application = G_APPLICATION (shell);
windows = gtk_application_get_windows (GTK_APPLICATION (application));
window = g_list_nth_data (windows, atoi (split[1]));
notebook = ephy_window_get_notebook (window);
gtk_window_present (GTK_WINDOW (window));
}
gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), atoi (split[0]));
if (ephy_web_view_is_overview (webview))
g_signal_emit_by_name (GTK_NOTEBOOK (notebook), "tab-close-request", tab);
return;
}
address = g_strdup (content);
effective_address = ephy_embed_utils_normalize_or_autosearch_address (g_strstrip (address));
g_free (address);
#if 0
if (!ephy_embed_utils_address_has_web_scheme (effective_address)) {
/* After normalization there are still some cases that are
* impossible to tell apart. One example is : and :. To fix this, let's do a HEAD request to the
* effective URI prefxed with http://; if we get OK Status the URI
* exists, and we'll go ahead, otherwise we'll try to launch a
* proper handler through gtk_show_uri. We only do this in
* ephy_web_view_load_url, since this case is only relevant for URIs
* typed in the location entry, which uses this method to do the
* load. */
/* TODO: however, this is not really possible, because normalize_or_autosearch_address
* prepends http:// for anything that doesn't look like a URL.
*/
}
#endif
ephy_link_open (EPHY_LINK (controller), effective_address, NULL,
ephy_link_flags_from_current_event () | EPHY_LINK_TYPED);
g_free (effective_address);
}
static void
user_changed_cb (GtkWidget *widget,
EphyLocationController *controller)
{
const char *address;
DzlSuggestionEntry *entry = DZL_SUGGESTION_ENTRY (ephy_location_entry_get_entry (EPHY_LOCATION_ENTRY (widget)));
GListModel *model;
address = dzl_suggestion_entry_get_typed_text (entry);
LOG ("user_changed_cb, address %s", address);
model = dzl_suggestion_entry_get_model (entry);
ephy_suggestion_model_query_async (EPHY_SUGGESTION_MODEL (model), address, TRUE, NULL, NULL, NULL);
}
static void
sync_address (EphyLocationController *controller,
GParamSpec *pspec,
GtkWidget *widget)
{
LOG ("sync_address %s", controller->address);
g_signal_handlers_block_by_func (widget, G_CALLBACK (user_changed_cb), controller);
ephy_title_widget_set_address (controller->title_widget, controller->address);
g_signal_handlers_unblock_by_func (widget, G_CALLBACK (user_changed_cb), controller);
}
static char *
get_location_cb (EphyLocationEntry *entry,
EphyLocationController *controller)
{
EphyEmbed *embed;
const char *address;
embed = ephy_embed_container_get_active_child (EPHY_EMBED_CONTAINER (controller->window));
address = ephy_web_view_get_address (ephy_embed_get_web_view (embed));
return ephy_embed_utils_is_no_show_address (address) ? NULL : ephy_uri_decode (address);
}
static char *
get_title_cb (EphyLocationEntry *entry,
EphyLocationController *controller)
{
EphyEmbed *embed;
embed = ephy_embed_container_get_active_child
(EPHY_EMBED_CONTAINER (controller->window));
return g_strdup (ephy_embed_get_title (embed));
}
static gboolean
focus_in_event_cb (GtkWidget *entry,
GdkEventFocus *event,
EphyLocationController *controller)
{
const char *address;
/* Never block sync if the location entry is empty, else homepage URL
* will be missing in homepage mode. */
address = ephy_title_widget_get_address (controller->title_widget);
if (!controller->sync_address_is_blocked && address && *address) {
controller->sync_address_is_blocked = TRUE;
g_signal_handlers_block_by_func (controller, G_CALLBACK (sync_address), entry);
}
return FALSE;
}
static gboolean
focus_out_event_cb (GtkWidget *entry,
GdkEventFocus *event,
EphyLocationController *controller)
{
if (controller->sync_address_is_blocked) {
controller->sync_address_is_blocked = FALSE;
g_signal_handlers_unblock_by_func (controller, G_CALLBACK (sync_address), entry);
}
return FALSE;
}
static void
switch_page_cb (GtkNotebook *notebook,
GtkWidget *page,
guint page_num,
EphyLocationController *controller)
{
if (controller->sync_address_is_blocked == TRUE) {
controller->sync_address_is_blocked = FALSE;
g_signal_handlers_unblock_by_func (controller, G_CALLBACK (sync_address), controller->title_widget);
}
}
static void
longpress_gesture_cb (GtkGestureLongPress *gesture,
gdouble x,
gdouble y,
gpointer user_data)
{
GtkWidget *entry = user_data;
gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1);
}
static void
reader_mode_button_press_event_cb (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
EphyLocationController *controller = EPHY_LOCATION_CONTROLLER (user_data);
EphyWindow *window = controller->window;
EphyEmbed *embed = ephy_embed_container_get_active_child (EPHY_EMBED_CONTAINER (window));
EphyWebView *view = ephy_embed_get_web_view (embed);
EphyLocationEntry *lentry;
g_assert (EPHY_IS_LOCATION_ENTRY (controller->title_widget));
lentry = EPHY_LOCATION_ENTRY (controller->title_widget);
ephy_location_entry_set_reader_mode_state (lentry, !ephy_location_entry_get_reader_mode_state (lentry));
ephy_web_view_toggle_reader_mode (view, ephy_location_entry_get_reader_mode_state (lentry));
}
static void
ephy_location_controller_constructed (GObject *object)
{
EphyLocationController *controller = EPHY_LOCATION_CONTROLLER (object);
EphyHistoryService *history_service;
EphyBookmarksManager *bookmarks_manager;
EphySuggestionModel *model;
GtkWidget *notebook, *widget, *reader_mode, *entry;
G_OBJECT_CLASS (ephy_location_controller_parent_class)->constructed (object);
notebook = ephy_window_get_notebook (controller->window);
widget = GTK_WIDGET (controller->title_widget);
g_signal_connect (notebook, "switch-page",
G_CALLBACK (switch_page_cb), controller);
sync_address (controller, NULL, widget);
g_signal_connect_object (controller, "notify::address",
G_CALLBACK (sync_address), widget, 0);
if (!EPHY_IS_LOCATION_ENTRY (controller->title_widget))
return;
entry = ephy_location_entry_get_entry (EPHY_LOCATION_ENTRY (controller->title_widget));
g_signal_connect (controller->title_widget, "user-changed", G_CALLBACK (user_changed_cb), controller);
controller->longpress_gesture = gtk_gesture_long_press_new (entry);
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (controller->longpress_gesture), TRUE);
g_signal_connect (controller->longpress_gesture, "pressed", G_CALLBACK (longpress_gesture_cb), entry);
history_service = ephy_embed_shell_get_global_history_service (ephy_embed_shell_get_default ());
bookmarks_manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
model = ephy_suggestion_model_new (history_service, bookmarks_manager);
dzl_suggestion_entry_set_model (DZL_SUGGESTION_ENTRY (entry), G_LIST_MODEL (model));
g_object_unref (model);
reader_mode = ephy_location_entry_get_reader_mode_widget (EPHY_LOCATION_ENTRY (controller->title_widget));
g_signal_connect (G_OBJECT (reader_mode), "button-press-event", G_CALLBACK (reader_mode_button_press_event_cb), controller);
g_object_bind_property (controller, "editable",
entry, "editable",
G_BINDING_SYNC_CREATE);
g_signal_connect_object (widget, "drag-data-received",
G_CALLBACK (entry_drag_data_received_cb),
controller, 0);
g_signal_connect_object (entry, "activate",
G_CALLBACK (entry_activate_cb),
controller, 0);
g_signal_connect_object (widget, "get-location",
G_CALLBACK (get_location_cb), controller, 0);
g_signal_connect_object (widget, "get-title",
G_CALLBACK (get_title_cb), controller, 0);
g_signal_connect_object (widget, "focus-in-event",
G_CALLBACK (focus_in_event_cb), controller, 0);
g_signal_connect_object (widget, "focus-out-event",
G_CALLBACK (focus_out_event_cb), controller, 0);
}
static void
ephy_location_controller_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
EphyLocationController *controller = EPHY_LOCATION_CONTROLLER (object);
switch (prop_id) {
case PROP_ADDRESS:
ephy_location_controller_set_address (controller, g_value_get_string (value));
break;
case PROP_EDITABLE:
controller->editable = g_value_get_boolean (value);
break;
case PROP_WINDOW:
controller->window = EPHY_WINDOW (g_value_get_object (value));
break;
case PROP_TITLE_WIDGET:
controller->title_widget = EPHY_TITLE_WIDGET (g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ephy_location_controller_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
EphyLocationController *controller = EPHY_LOCATION_CONTROLLER (object);
switch (prop_id) {
case PROP_ADDRESS:
g_value_set_string (value, ephy_location_controller_get_address (controller));
break;
case PROP_EDITABLE:
g_value_set_boolean (value, controller->editable);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ephy_location_controller_dispose (GObject *object)
{
EphyLocationController *controller = EPHY_LOCATION_CONTROLLER (object);
GtkWidget *notebook;
notebook = ephy_window_get_notebook (controller->window);
if (notebook == NULL ||
controller->title_widget == NULL) {
return;
}
g_clear_object (&controller->longpress_gesture);
if (EPHY_IS_LOCATION_ENTRY (controller->title_widget)) {
g_signal_handlers_disconnect_matched (controller, G_SIGNAL_MATCH_DATA,
0, 0, NULL, NULL, controller->title_widget);
g_signal_handlers_disconnect_matched (controller->title_widget, G_SIGNAL_MATCH_DATA,
0, 0, NULL, NULL, controller);
}
g_signal_handlers_disconnect_matched (notebook, G_SIGNAL_MATCH_DATA,
0, 0, NULL, NULL, controller);
controller->title_widget = NULL;
G_OBJECT_CLASS (ephy_location_controller_parent_class)->dispose (object);
}
static void
ephy_location_controller_class_init (EphyLocationControllerClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = ephy_location_controller_finalize;
object_class->dispose = ephy_location_controller_dispose;
object_class->constructed = ephy_location_controller_constructed;
object_class->get_property = ephy_location_controller_get_property;
object_class->set_property = ephy_location_controller_set_property;
/**
* EphyLocationController:address:
*
* The address of the current location.
*/
obj_properties[PROP_ADDRESS] =
g_param_spec_string ("address",
"Address",
"The address of the current location",
"",
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
/**
* EphyLocationController:editable:
*
* Whether the location bar entry can be edited.
*/
obj_properties[PROP_EDITABLE] =
g_param_spec_boolean ("editable",
"Editable",
"Whether the location bar entry can be edited",
TRUE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
/**
* EphyLocationController:window:
*
* The parent window.
*/
obj_properties[PROP_WINDOW] =
g_param_spec_object ("window",
"Window",
"The parent window",
G_TYPE_OBJECT,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
/**
* EphyLocationController:title-widget:
*
* The #EphyLocationController sets the address of the #EphyTitleWidget.
*/
obj_properties[PROP_TITLE_WIDGET] =
g_param_spec_object ("title-widget",
"Title widget",
"The title widget whose address will be managed",
G_TYPE_OBJECT,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
}
static void
ephy_location_controller_init (EphyLocationController *controller)
{
EphyEmbedShell *shell;
controller->address = g_strdup ("");
controller->editable = TRUE;
controller->sync_address_is_blocked = FALSE;
shell = ephy_embed_shell_get_default ();
controller->search_engine_manager = ephy_embed_shell_get_search_engine_manager (shell);
}
static void
ephy_location_controller_finalize (GObject *object)
{
EphyLocationController *controller = EPHY_LOCATION_CONTROLLER (object);
g_free (controller->address);
G_OBJECT_CLASS (ephy_location_controller_parent_class)->finalize (object);
}
/**
* ephy_location_controller_get_address:
* @controller: an #EphyLocationController
*
* Retrieves the currently loaded address.
*
* Returns: the current address
**/
const char *
ephy_location_controller_get_address (EphyLocationController *controller)
{
g_assert (EPHY_IS_LOCATION_CONTROLLER (controller));
return controller->address;
}
/**
* ephy_location_controller_set_address:
* @controller: an #EphyLocationController
* @address: new address
*
* Sets @address as the address of @controller.
**/
void
ephy_location_controller_set_address (EphyLocationController *controller,
const char *address)
{
g_assert (EPHY_IS_LOCATION_CONTROLLER (controller));
LOG ("set_address %s", address);
g_free (controller->address);
controller->address = g_strdup (address);
g_object_notify_by_pspec (G_OBJECT (controller), obj_properties[PROP_ADDRESS]);
}