Browser
/* $Id$ */
/* Copyright (c) 2012-2021 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Desktop Browser */
/* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <libintl.h>
#include <System.h>
#include "Browser.h"
#define _(string) gettext(string)
#define N_(string) (string)
/* Favorites */
/* private */
/* types */
typedef struct _BrowserPlugin
{
BrowserPluginHelper * helper;
Mime * mime;
GList * selection;
/* widgets */
GtkWidget * widget;
GtkListStore * store;
GtkWidget * view;
GdkPixbuf * folder;
} Favorites;
typedef enum _FavoritesColumn
{
FC_ICON = 0,
FC_NAME,
FC_PATH
} FavoritesColumn;
#define FC_LAST FC_PATH
#define FC_COUNT (FC_LAST + 1)
/* prototypes */
/* plug-in */
static Favorites * _favorites_init(BrowserPluginHelper * helper);
static void _favorites_destroy(Favorites * favorites);
static GtkWidget * _favorites_get_widget(Favorites * favorites);
static void _favorites_refresh(Favorites * favorites, GList * selection);
/* accessors */
static gchar * _favorites_get_filename(void);
/* useful */
static int _favorites_save(Favorites * favorites);
/* callbacks */
static void _favorites_on_add(gpointer data);
static gboolean _favorites_on_filter_visible(GtkTreeModel * model,
GtkTreeIter * iter, gpointer data);
static void _favorites_on_remove(gpointer data);
static void _favorites_on_row_activated(GtkTreeView * view, GtkTreePath * path,
GtkTreeViewColumn * column, gpointer data);
/* public */
/* variables */
BrowserPluginDefinition plugin =
{
N_("Favorites"),
"user-bookmarks",
NULL,
_favorites_init,
_favorites_destroy,
_favorites_get_widget,
_favorites_refresh
};
/* private */
/* functions */
/* favorites_init */
static Favorites * _favorites_init(BrowserPluginHelper * helper)
{
Favorites * favorites;
GtkIconTheme * icontheme;
gint size;
GtkWidget * widget;
GtkToolItem * toolitem;
GtkTreeModel * model;
GtkCellRenderer * renderer;
GtkTreeViewColumn * column;
GtkTreeSelection * treesel;
GError * error = NULL;
if((favorites = object_new(sizeof(*favorites))) == NULL)
return NULL;
favorites->helper = helper;
favorites->mime = helper->get_mime(helper->browser);
favorites->selection = NULL;
icontheme = gtk_icon_theme_get_default();
gtk_icon_size_lookup(GTK_ICON_SIZE_BUTTON, &size, &size);
favorites->folder = gtk_icon_theme_load_icon(icontheme, "stock_folder",
size, GTK_ICON_LOOKUP_USE_BUILTIN, &error);
if(favorites->folder == NULL)
{
helper->error(helper->browser, error->message, 1);
g_error_free(error);
}
favorites->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
widget = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(widget),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
favorites->store = gtk_list_store_new(FC_COUNT, GDK_TYPE_PIXBUF,
G_TYPE_STRING, G_TYPE_STRING);
model = gtk_tree_model_filter_new(GTK_TREE_MODEL(favorites->store),
NULL);
gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(model),
_favorites_on_filter_visible, NULL, NULL);
favorites->view = gtk_tree_view_new_with_model(model);
gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(favorites->view),
FALSE);
/* columns */
renderer = gtk_cell_renderer_pixbuf_new();
column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
"pixbuf", FC_ICON, NULL);
gtk_tree_view_column_set_expand(column, FALSE);
gtk_tree_view_append_column(GTK_TREE_VIEW(favorites->view), column);
renderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
"text", FC_NAME, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(favorites->view), column);
/* selection */
treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(favorites->view));
gtk_tree_selection_set_mode(treesel, GTK_SELECTION_SINGLE);
/* signals */
g_signal_connect(favorites->view, "row-activated", G_CALLBACK(
_favorites_on_row_activated), favorites);
#if GTK_CHECK_VERSION(3, 0, 0)
gtk_container_add(GTK_CONTAINER(widget), favorites->view);
#else
gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(widget),
favorites->view);
#endif
gtk_box_pack_start(GTK_BOX(favorites->widget), widget, TRUE, TRUE, 0);
/* lower toolbar */
widget = gtk_toolbar_new();
gtk_toolbar_set_icon_size(GTK_TOOLBAR(widget), GTK_ICON_SIZE_MENU);
gtk_toolbar_set_style(GTK_TOOLBAR(widget), GTK_TOOLBAR_ICONS);
toolitem = gtk_tool_button_new_from_stock(GTK_STOCK_ADD);
#if GTK_CHECK_VERSION(2, 12, 0)
gtk_widget_set_tooltip_text(GTK_WIDGET(toolitem),
_("Add to bookmarks"));
#endif
g_signal_connect_swapped(toolitem, "clicked", G_CALLBACK(
_favorites_on_add), favorites);
gtk_toolbar_insert(GTK_TOOLBAR(widget), toolitem, -1);
toolitem = gtk_tool_button_new_from_stock(GTK_STOCK_REMOVE);
#if GTK_CHECK_VERSION(2, 12, 0)
gtk_widget_set_tooltip_text(GTK_WIDGET(toolitem),
_("Remove from bookmarks"));
#endif
g_signal_connect_swapped(toolitem, "clicked", G_CALLBACK(
_favorites_on_remove), favorites);
gtk_toolbar_insert(GTK_TOOLBAR(widget), toolitem, -1);
gtk_box_pack_start(GTK_BOX(favorites->widget), widget, FALSE, TRUE, 0);
gtk_widget_show_all(favorites->widget);
return favorites;
}
/* favorites_destroy */
static void _favorites_destroy(Favorites * favorites)
{
g_list_foreach(favorites->selection, (GFunc)g_free, NULL);
g_list_free(favorites->selection);
object_delete(favorites);
}
/* favorites_get_widget */
static GtkWidget * _favorites_get_widget(Favorites * favorites)
{
return favorites->widget;
}
/* favorites_refresh */
static void _refresh_add(Favorites * favorites, char const * buf);
static void _refresh_add_file(Favorites * favorites, gint size,
char const * buf);
static void _refresh_copy(gchar const * pathname, gpointer data);
static void _favorites_refresh(Favorites * favorites, GList * selection)
{
const char scheme[] = "file:///";
FILE * fp;
gchar * filename;
gint size;
char buf[512];
size_t len;
int c;
/* obtain the current selection */
g_list_foreach(favorites->selection, (GFunc)g_free, NULL);
g_list_free(favorites->selection);
favorites->selection = NULL;
g_list_foreach(selection, (GFunc)_refresh_copy, favorites);
/* refresh the bookmarks */
gtk_list_store_clear(favorites->store);
if((filename = _favorites_get_filename()) == NULL)
return;
fp = fopen(filename, "r");
g_free(filename);
if(fp == NULL)
return;
gtk_icon_size_lookup(GTK_ICON_SIZE_BUTTON, &size, &size);
while(fgets(buf, sizeof(buf), fp) != NULL)
{
if((len = strlen(buf)) == 0)
/* ignore empty lines */
continue;
else if(buf[len - 1] != '\n')
{
/* skip the rest of the current line */
while((c = fgetc(fp)) != EOF && c != '\n');
continue;
}
buf[len - 1] = '\0';
if(strncmp(buf, scheme, sizeof(scheme) - 1) == 0)
_refresh_add_file(favorites, size, buf);
else
_refresh_add(favorites, buf);
}
fclose(fp);
}
static void _refresh_add(Favorites * favorites, char const * buf)
{
GtkTreeIter iter;
#if GTK_CHECK_VERSION(2, 6, 0)
gtk_list_store_insert_with_values(favorites->store, &iter, -1,
#else
gtk_list_store_append(favorites->store, &iter);
gtk_list_store_set(favorites->store, &iter,
#endif
FC_PATH, buf, -1);
}
static void _refresh_add_file(Favorites * favorites, gint size,
char const * buf)
{
const char scheme[] = "file:///";
gchar * filename;
GtkTreeIter iter;
GdkPixbuf * pixbuf;
filename = g_path_get_basename(
&buf[sizeof(scheme) - 2]);
if((pixbuf = browser_vfs_mime_icon(favorites->mime,
&buf[sizeof(scheme) - 2], NULL,
NULL, NULL, size)) == NULL)
pixbuf = favorites->folder;
#if GTK_CHECK_VERSION(2, 6, 0)
gtk_list_store_insert_with_values(favorites->store, &iter, -1,
#else
gtk_list_store_append(favorites->store, &iter);
gtk_list_store_set(favorites->store, &iter,
#endif
FC_ICON, pixbuf, FC_NAME, filename,
FC_PATH, buf, -1);
g_free(filename);
}
static void _refresh_copy(gchar const * pathname, gpointer data)
{
Favorites * favorites = data;
favorites->selection = g_list_append(favorites->selection,
g_strdup(pathname));
}
/* accessors */
/* favorites_get_filename */
static gchar * _favorites_get_filename(void)
{
char const * home;
if((home = getenv("HOME")) == NULL)
home = g_get_home_dir();
#if GTK_CHECK_VERSION(3, 0, 0)
/* FIXME may depend on the environment (XDG_CONFIG_HOME) */
return g_build_filename(home, ".config", "gtk-3.0", "bookmarks", NULL);
#else
return g_build_filename(home, ".gtk-bookmarks", NULL);
#endif
}
/* useful */
/* favorites_save */
static int _favorites_save(Favorites * favorites)
{
GtkTreeModel * model = GTK_TREE_MODEL(favorites->store);
GtkTreeIter iter;
gchar * p;
FILE * fp;
gboolean valid;
if((p = _favorites_get_filename()) == NULL)
return -1;
fp = fopen(p, "w");
g_free(p);
if(fp == NULL)
return -1;
for(valid = gtk_tree_model_get_iter_first(model, &iter); valid == TRUE;
valid = gtk_tree_model_iter_next(model, &iter))
{
gtk_tree_model_get(model, &iter, FC_PATH, &p, -1);
if(p == NULL)
continue;
fprintf(fp, "%s\n", p);
g_free(p);
}
return fclose(fp);
}
/* callbacks */
/* favorites_on_add */
static void _on_add_filename(gchar const * pathname, gpointer data);
static void _favorites_on_add(gpointer data)
{
Favorites * favorites = data;
g_list_foreach(favorites->selection, (GFunc)_on_add_filename,
favorites);
}
static void _on_add_filename(gchar const * pathname, gpointer data)
{
const char scheme[] = "file://";
Favorites * favorites = data;
GtkTreeIter iter;
struct stat st;
gchar * filename;
String * path;
gint size = 24;
GdkPixbuf * pixbuf;
/* XXX ignore non-directories */
if(browser_vfs_stat(pathname, &st) != 0 || !S_ISDIR(st.st_mode))
return;
if((filename = g_path_get_basename(pathname)) == NULL)
return;
if((path = string_new_append(scheme, pathname, NULL)) == NULL)
return;
gtk_icon_size_lookup(GTK_ICON_SIZE_BUTTON, &size, &size);
if((pixbuf = browser_vfs_mime_icon(favorites->mime, pathname, NULL,
NULL, &st, size)) == NULL)
pixbuf = favorites->folder;
#if GTK_CHECK_VERSION(2, 6, 0)
gtk_list_store_insert_with_values(favorites->store, &iter, -1,
#else
gtk_list_store_append(favorites->store, &iter);
gtk_list_store_set(favorites->store, &iter,
#endif
FC_ICON, pixbuf, FC_NAME, filename, FC_PATH, path, -1);
string_delete(path);
g_free(filename);
_favorites_save(favorites);
}
/* favorites_on_filter_visible */
static gboolean _favorites_on_filter_visible(GtkTreeModel * model,
GtkTreeIter * iter, gpointer data)
{
const char scheme[] = "file:///";
gboolean ret;
gchar * path;
(void) data;
gtk_tree_model_get(model, iter, FC_PATH, &path, -1);
ret = (path != NULL && strncmp(path, scheme, sizeof(scheme) - 1) == 0)
? TRUE : FALSE;
g_free(path);
return ret;
}
/* favorites_on_remove */
static void _favorites_on_remove(gpointer data)
{
Favorites * favorites = data;
GtkTreeSelection * treesel;
GtkTreeModel * model;
GtkTreeIter fiter;
GtkTreeIter iter;
treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(favorites->view));
if(gtk_tree_selection_get_selected(treesel, &model, &fiter) != TRUE)
return;
gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(
model), &iter, &fiter);
gtk_list_store_remove(favorites->store, &iter);
_favorites_save(favorites);
}
/* favorites_on_row_activated */
static void _favorites_on_row_activated(GtkTreeView * view, GtkTreePath * path,
GtkTreeViewColumn * column, gpointer data)
{
const char scheme[] = "file:///";
Favorites * favorites = data;
GtkTreeModel * model;
GtkTreeIter fiter;
GtkTreeIter iter;
gchar * location;
(void) column;
model = gtk_tree_view_get_model(view);
if(gtk_tree_model_get_iter(model, &fiter, path) == FALSE)
return;
gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(
model), &iter, &fiter);
model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
gtk_tree_model_get(model, &iter, FC_PATH, &location, -1);
if(strncmp(location, scheme, sizeof(scheme) - 1) == 0)
favorites->helper->set_location(favorites->helper->browser,
&location[sizeof(scheme) - 2]);
g_free(location);
}