midori/extensions/open-with.vala

843 lines
34 KiB
Vala

/*
Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
#if HAVE_WIN32
namespace Sokoke {
extern static Gdk.Pixbuf get_gdk_pixbuf_from_win32_executable (string path);
}
#endif
namespace ExternalApplications {
/* Spawn the application specified by @app_info on the uri, trying to
remember the association between the content-type and the application
chosen
Returns whether the application was spawned successfully. */
bool open_app_info (AppInfo app_info, string uri, string content_type) {
Midori.URI.recursive_fork_protection (uri, true);
try {
var uris = new List<File> ();
uris.append (File.new_for_uri (uri));
app_info.launch (uris, null);
} catch (Error error) {
warning ("Failed to open \"%s\": %s", uri, error.message);
return false;
}
/* Failing to save the association is a non-fatal error so report success */
try {
new Associations ().remember (content_type, app_info);
} catch (Error error) {
warning ("Failed to save association for \"%s\": %s", uri, error.message);
}
return true;
}
class Associations : Object {
#if HAVE_WIN32
string config_dir;
string filename;
KeyFile keyfile;
public Associations () {
config_dir = Midori.Paths.get_extension_config_dir ("open-with");
filename = Path.build_filename (config_dir, "config");
keyfile = new KeyFile ();
try {
keyfile.load_from_file (filename, KeyFileFlags.NONE);
} catch (FileError.NOENT exist_error) {
/* It's no error if no config file exists */
} catch (Error error) {
warning ("Failed to load associations: %s", error.message);
}
}
/* Determine a handler command-line for @content_type and spawn it on @uri.
Returns whether a handler was found and spawned successfully. */
public bool open (string content_type, string uri) {
Midori.URI.recursive_fork_protection (uri, true);
try {
string commandline = keyfile.get_string ("mimes", content_type);
if ("%u" in commandline)
commandline = commandline.replace ("%u", Shell.quote (uri));
else if ("%F" in commandline)
commandline = commandline.replace ("%F", Shell.quote (Filename.from_uri (uri)));
return Process.spawn_command_line_async (commandline);
} catch (KeyFileError error) {
/* Not remembered before */
return false;
} catch (Error error) {
warning ("Failed to open \"%s\": %s", uri, error.message);
return false;
}
}
/* Save @app_info in the persistent store as the handler for @content_type */
public void remember (string content_type, AppInfo app_info) throws Error {
keyfile.set_string ("mimes", content_type, get_commandline (app_info));
FileUtils.set_contents (filename, keyfile.to_data ());
}
/* Save @commandline in the persistent store as the handler for @content_type */
public void remember_custom_commandline (string content_type, string commandline, string name, string uri) {
keyfile.set_string ("mimes", content_type, commandline);
try {
FileUtils.set_contents (filename, keyfile.to_data ());
} catch (Error error) {
warning ("Failed to remember custom command line for \"%s\": %s", uri, error.message);
}
open (content_type, uri);
}
}
#else
public Associations () {
}
/* Find a handler application for @content_type and spawn it on @uri.
Returns whether a handler was found and spawned successfully. */
public bool open (string content_type, string uri) {
var app_info = AppInfo.get_default_for_type (content_type, false);
if (app_info == null)
return false;
return open_app_info (app_info, uri, content_type);
}
/* Save @app_info as the last-used handler for @content_type */
public void remember (string content_type, AppInfo app_info) throws Error {
app_info.set_as_last_used_for_type (content_type);
app_info.set_as_default_for_type (content_type);
}
/* Save @commandline as a new system MIME handler for @content_type */
public void remember_custom_commandline (string content_type, string commandline, string name, string uri) {
try {
var app_info = AppInfo.create_from_commandline (commandline, name,
"%u" in commandline ? AppInfoCreateFlags.SUPPORTS_URIS : AppInfoCreateFlags.NONE);
open_app_info (app_info, uri, content_type);
} catch (Error error) {
warning ("Failed to remember custom command line for \"%s\": %s", uri, error.message);
}
}
}
#endif
static string get_commandline (AppInfo app_info) {
return app_info.get_commandline () ?? app_info.get_executable ();
}
/* Generate markup of the application's name followed by a description line. */
static string describe_app_info (AppInfo app_info) {
string name = app_info.get_display_name () ?? (Path.get_basename (app_info.get_executable ()));
string desc = app_info.get_description () ?? get_commandline (app_info);
return Markup.printf_escaped ("<b>%s</b>\n%s", name, desc);
}
static Icon? app_info_get_icon (AppInfo app_info) {
#if HAVE_WIN32
return Sokoke.get_gdk_pixbuf_from_win32_executable (app_info.get_executable ());
#else
return app_info.get_icon ();
#endif
}
class CustomizerDialog : Gtk.Dialog {
public Gtk.Entry name_entry;
public Gtk.Entry commandline_entry;
public CustomizerDialog (AppInfo app_info, Gtk.Widget widget) {
var browser = Midori.Browser.get_for_widget (widget);
transient_for = browser;
title = _("Custom…");
#if !HAVE_GTK3
has_separator = false;
#endif
destroy_with_parent = true;
set_icon_name (Gtk.STOCK_OPEN);
resizable = false;
add_buttons (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT);
var vbox = new Gtk.VBox (false, 8);
vbox.border_width = 8;
(get_content_area () as Gtk.Box).pack_start (vbox, true, true, 8);
var sizegroup = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL);
var label = new Gtk.Label (_("Name:"));
sizegroup.add_widget (label);
label.set_alignment (0.0f, 0.5f);
vbox.pack_start (label, false, false, 0);
name_entry = new Gtk.Entry ();
name_entry.activates_default = true;
sizegroup.add_widget (name_entry);
vbox.pack_start (name_entry, true, true, 0);
label = new Gtk.Label (_("Command Line:"));
sizegroup.add_widget (label);
label.set_alignment (0.0f, 0.5f);
vbox.pack_start (label, false, false, 0);
commandline_entry = new Gtk.Entry ();
commandline_entry.activates_default = true;
sizegroup.add_widget (name_entry);
sizegroup.add_widget (commandline_entry);
vbox.pack_start (commandline_entry, true, true, 0);
get_content_area ().show_all ();
set_default_response (Gtk.ResponseType.ACCEPT);
name_entry.text = app_info.get_name ();
commandline_entry.text = get_commandline (app_info);
}
}
private class Chooser : Gtk.VBox {
Gtk.ListStore store = new Gtk.ListStore (1, typeof (AppInfo));
Gtk.TreeView treeview;
List<AppInfo> available;
string content_type;
string uri;
public Chooser (string uri, string content_type) {
this.content_type = content_type;
this.uri = uri;
Gtk.TreeViewColumn column;
treeview = new Gtk.TreeView.with_model (store);
treeview.headers_visible = false;
store.set_sort_column_id (0, Gtk.SortType.ASCENDING);
store.set_sort_func (0, tree_sort_func);
column = new Gtk.TreeViewColumn ();
Gtk.CellRendererPixbuf renderer_icon = new Gtk.CellRendererPixbuf ();
column.pack_start (renderer_icon, false);
column.set_cell_data_func (renderer_icon, on_render_icon);
treeview.append_column (column);
column = new Gtk.TreeViewColumn ();
column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
Gtk.CellRendererText renderer_text = new Gtk.CellRendererText ();
column.pack_start (renderer_text, true);
column.set_expand (true);
column.set_cell_data_func (renderer_text, on_render_text);
treeview.append_column (column);
treeview.row_activated.connect (row_activated);
treeview.show ();
var scrolled = new Gtk.ScrolledWindow (null, null);
scrolled.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
scrolled.add (treeview);
pack_start (scrolled);
int height;
treeview.create_pango_layout ("a\nb").get_pixel_size (null, out height);
scrolled.set_size_request (-1, height * 5);
treeview.button_release_event.connect (button_released);
treeview.tooltip_text = _("Right-click a suggestion to customize it");
available = new List<AppInfo> ();
foreach (var app_info in AppInfo.get_all_for_type (content_type))
launcher_added (app_info, uri);
if (store.iter_n_children (null) < 1) {
foreach (var app_info in AppInfo.get_all ())
launcher_added (app_info, uri);
}
}
bool button_released (Gdk.EventButton event) {
if (event.button == 3)
return show_popup_menu (event);
return false;
}
bool show_popup_menu (Gdk.EventButton? event) {
Gtk.TreeIter iter;
if (treeview.get_selection ().get_selected (null, out iter)) {
AppInfo app_info;
store.get (iter, 0, out app_info);
var menu = new Gtk.Menu ();
var menuitem = new Gtk.ImageMenuItem.with_mnemonic (_("Custom…"));
menuitem.image = new Gtk.Image.from_stock (Gtk.STOCK_EDIT, Gtk.IconSize.MENU);
menuitem.activate.connect (() => {
customize_app_info (app_info, content_type, uri);
});
menu.append (menuitem);
menu.show_all ();
Katze.widget_popup (treeview, menu, null, Katze.MenuPos.CURSOR);
return true;
}
return false;
}
void customize_app_info (AppInfo app_info, string content_type, string uri) {
var dialog = new CustomizerDialog (app_info, this);
bool accept = dialog.run () == Gtk.ResponseType.ACCEPT;
if (accept) {
string name = dialog.name_entry.text;
string commandline = dialog.commandline_entry.text;
new Associations ().remember_custom_commandline (content_type, commandline, name, uri);
customized (app_info, content_type, uri);
}
dialog.destroy ();
}
public List<AppInfo> get_available () {
return available.copy ();
}
public AppInfo get_app_info () {
Gtk.TreeIter iter;
if (treeview.get_selection ().get_selected (null, out iter)) {
AppInfo app_info;
store.get (iter, 0, out app_info);
return app_info;
}
assert_not_reached ();
}
void on_render_icon (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
AppInfo app_info;
model.get (iter, 0, out app_info);
renderer.set ("gicon", app_info_get_icon (app_info),
"stock-size", Gtk.IconSize.DIALOG,
"xpad", 4);
}
void on_render_text (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
AppInfo app_info;
model.get (iter, 0, out app_info);
renderer.set ("markup", describe_app_info (app_info),
"ellipsize", Pango.EllipsizeMode.END);
}
void launcher_added (AppInfo app_info, string uri) {
if (!app_info.should_show ())
return;
Gtk.TreeIter iter;
store.append (out iter);
store.set (iter, 0, app_info);
available.append (app_info);
}
int tree_sort_func (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b) {
AppInfo app_info1, app_info2;
model.get (a, 0, out app_info1);
model.get (b, 0, out app_info2);
return strcmp (app_info1.get_display_name (), app_info2.get_display_name ());
}
void row_activated (Gtk.TreePath path, Gtk.TreeViewColumn column) {
Gtk.TreeIter iter;
if (store.get_iter (out iter, path)) {
AppInfo app_info;
store.get (iter, 0, out app_info);
selected (app_info);
}
}
public signal void selected (AppInfo app_info);
public signal void customized (AppInfo app_info, string content_type, string uri);
}
class ChooserDialog : Gtk.Dialog {
public Chooser chooser { get; private set; }
public ChooserDialog (string uri, string content_type, Gtk.Widget widget) {
string filename;
if (uri.has_prefix ("file://"))
filename = Midori.Download.get_basename_for_display (uri);
else
filename = uri;
var browser = Midori.Browser.get_for_widget (widget);
transient_for = browser;
title = _("Choose application");
#if !HAVE_GTK3
has_separator = false;
#endif
destroy_with_parent = true;
set_icon_name (Gtk.STOCK_OPEN);
resizable = true;
add_buttons (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT);
var vbox = new Gtk.VBox (false, 8);
vbox.border_width = 8;
(get_content_area () as Gtk.Box).pack_start (vbox, true, true, 8);
var label = new Gtk.Label (_("Select an application to open \"%s\"".printf (filename)));
label.ellipsize = Pango.EllipsizeMode.MIDDLE;
vbox.pack_start (label, false, false, 0);
if (uri == "")
label.no_show_all = true;
chooser = new Chooser (uri, content_type);
vbox.pack_start (chooser, true, true, 0);
get_content_area ().show_all ();
Gtk.Requisition req;
#if HAVE_GTK3
get_content_area ().get_preferred_size (null, out req);
#else
get_content_area ().size_request (out req);
#endif
(this as Gtk.Window).set_default_size (req.width*2, req.height*3/2);
set_default_response (Gtk.ResponseType.ACCEPT);
chooser.selected.connect ((app_info) => {
response (Gtk.ResponseType.ACCEPT);
});
chooser.customized.connect ((app_info, content_type, uri) => {
response (Gtk.ResponseType.CANCEL);
});
}
public AppInfo? open_with () {
show ();
bool accept = run () == Gtk.ResponseType.ACCEPT;
hide ();
if (!accept)
return null;
return chooser.get_app_info ();
}
}
class ChooserButton : Gtk.Button {
public AppInfo? app_info { get; set; }
public string? commandline { get; set; }
ChooserDialog dialog;
Gtk.Label app_name;
Gtk.Image icon;
public ChooserButton (string mime_type, string? commandline) {
string content_type = ContentType.from_mime_type (mime_type);
dialog = new ChooserDialog ("", content_type, this);
app_info = null;
foreach (var candidate in dialog.chooser.get_available ()) {
if (get_commandline (candidate) == commandline)
app_info = candidate;
}
var hbox = new Gtk.HBox (false, 4);
icon = new Gtk.Image ();
hbox.pack_start (icon, false, false, 0);
app_name = new Gtk.Label (null);
app_name.use_markup = true;
app_name.ellipsize = Pango.EllipsizeMode.END;
hbox.pack_start (app_name, true, true, 0);
add (hbox);
show_all ();
update_label ();
clicked.connect (() => {
dialog.transient_for = this.get_toplevel () as Gtk.Window;
app_info = dialog.open_with ();
string new_commandline = app_info != null ? get_commandline (app_info) : null;
commandline = new_commandline;
selected (new_commandline);
update_label ();
});
}
void update_label () {
app_name.label = app_info != null ? describe_app_info (app_info).replace ("\n", " ") : _("None");
icon.set_from_gicon (app_info != null ? app_info_get_icon (app_info) : null, Gtk.IconSize.BUTTON);
}
public signal void selected (string? commandline);
}
class Types : Gtk.VBox {
public Gtk.ListStore store = new Gtk.ListStore (2, typeof (string), typeof (AppInfo));
Gtk.TreeView treeview;
public Types () {
Gtk.TreeViewColumn column;
treeview = new Gtk.TreeView.with_model (store);
treeview.headers_visible = false;
store.set_sort_column_id (0, Gtk.SortType.ASCENDING);
store.set_sort_func (0, tree_sort_func);
column = new Gtk.TreeViewColumn ();
column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
Gtk.CellRendererPixbuf renderer_type_icon = new Gtk.CellRendererPixbuf ();
column.pack_start (renderer_type_icon, false);
column.set_cell_data_func (renderer_type_icon, on_render_type_icon);
treeview.append_column (column);
column = new Gtk.TreeViewColumn ();
column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
Gtk.CellRendererText renderer_type_text = new Gtk.CellRendererText ();
column.pack_start (renderer_type_text, true);
column.set_cell_data_func (renderer_type_text, on_render_type_text);
treeview.append_column (column);
column = new Gtk.TreeViewColumn ();
column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
Gtk.CellRendererPixbuf renderer_icon = new Gtk.CellRendererPixbuf ();
column.pack_start (renderer_icon, false);
column.set_cell_data_func (renderer_icon, on_render_icon);
treeview.append_column (column);
column = new Gtk.TreeViewColumn ();
column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
Gtk.CellRendererText renderer_text = new Gtk.CellRendererText ();
column.pack_start (renderer_text, true);
column.set_expand (true);
column.set_cell_data_func (renderer_text, on_render_text);
treeview.append_column (column);
treeview.row_activated.connect (row_activated);
treeview.show ();
var scrolled = new Gtk.ScrolledWindow (null, null);
scrolled.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
scrolled.add (treeview);
pack_start (scrolled);
int height;
treeview.create_pango_layout ("a\nb").get_pixel_size (null, out height);
scrolled.set_size_request (-1, height * 5);
foreach (unowned string content_type in ContentType.list_registered ())
launcher_added (content_type);
foreach (unowned string scheme in Vfs.get_default ().get_supported_uri_schemes ())
launcher_added ("x-scheme-handler/" + scheme);
treeview.size_allocate.connect_after ((allocation) => {
treeview.columns_autosize ();
});
}
void on_render_type_icon (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
string content_type;
store.get (iter, 0, out content_type);
renderer.set ("gicon", ContentType.get_icon (content_type),
"stock-size", Gtk.IconSize.BUTTON,
"xpad", 4);
}
void on_render_type_text (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
string content_type;
AppInfo app_info;
store.get (iter, 0, out content_type, 1, out app_info);
string desc, mime_type;
if (content_type.has_prefix ("x-scheme-handler/")) {
desc = content_type.split ("/")[1] + "://";
mime_type = "";
} else {
desc = ContentType.get_description (content_type);
mime_type = ContentType.get_mime_type (content_type);
}
renderer.set ("markup",
Markup.printf_escaped ("<b>%s</b>\n%s",
desc, mime_type),
#if HAVE_GTK3
"max-width-chars", 30,
#else
"width-chars", 30,
#endif
"ellipsize", Pango.EllipsizeMode.END);
}
void on_render_icon (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
AppInfo app_info;
model.get (iter, 1, out app_info);
renderer.set ("gicon", app_info_get_icon (app_info),
"stock-size", Gtk.IconSize.MENU,
"xpad", 4);
}
void on_render_text (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
AppInfo app_info;
model.get (iter, 1, out app_info);
renderer.set ("markup", describe_app_info (app_info),
"ellipsize", Pango.EllipsizeMode.END);
}
void launcher_added (string content_type) {
var app_info = AppInfo.get_default_for_type (content_type, false);
if (app_info == null)
return;
Gtk.TreeIter iter;
store.append (out iter);
store.set (iter, 0, content_type, 1, app_info);
}
int tree_sort_func (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b) {
string content_type1, content_type2;
model.get (a, 0, out content_type1);
model.get (b, 0, out content_type2);
return strcmp (content_type1, content_type2);
}
void row_activated (Gtk.TreePath path, Gtk.TreeViewColumn column) {
Gtk.TreeIter iter;
if (store.get_iter (out iter, path)) {
string content_type;
store.get (iter, 0, out content_type);
selected (content_type, iter);
}
}
public signal void selected (string content_type, Gtk.TreeIter iter);
}
private class Manager : Midori.Extension {
enum NextStep {
TRY_OPEN,
OPEN_WITH
}
bool open_uri (Midori.Tab tab, string uri) {
string content_type = get_content_type (uri, null);
return open_with_type (uri, content_type, tab, NextStep.TRY_OPEN);
}
bool navigation_requested (Midori.Tab tab, string uri) {
/* FIXME: Make this a list of known/internal uris to pass-thru */
if (uri.has_prefix ("file://") || Midori.URI.is_http (uri) || Midori.URI.is_blank (uri))
return false;
/* Don't find app for abp links, we should handle them internally */
if (uri.has_prefix("abp:"))
return true;
string content_type = get_content_type (uri, null);
open_with_type (uri, content_type, tab, NextStep.TRY_OPEN);
return true;
}
string get_content_type (string uri, string? mime_type) {
if (!uri.has_prefix ("file://") && !Midori.URI.is_http (uri)) {
string protocol = uri.split(":", 2)[0];
return "x-scheme-handler/" + protocol;
} else if (mime_type == null) {
string filename;
bool uncertain;
try {
filename = Filename.from_uri (uri);
} catch (Error error) {
filename = uri;
}
return ContentType.guess (filename, null, out uncertain);
}
return ContentType.from_mime_type (mime_type);
}
/* Returns %TRUE if the attempt to download and open failed immediately, %FALSE otherwise */
bool open_with_type (string uri, string content_type, Gtk.Widget widget, NextStep next_step) {
#if HAVE_WEBKIT2
return open_now (uri, content_type, widget, next_step);
#else
if (!Midori.URI.is_http (uri))
return open_now (uri, content_type, widget, next_step);
/* Don't download websites */
if (content_type == "application/octet-stream")
return open_now (uri, content_type, widget, next_step);
var download = new WebKit.Download (new WebKit.NetworkRequest (uri));
download.destination_uri = Midori.Download.prepare_destination_uri (download, null);
if (!Midori.Download.has_enough_space (download, download.destination_uri))
return false;
download.notify["status"].connect ((pspec) => {
if (download.status == WebKit.DownloadStatus.FINISHED) {
open_now (download.destination_uri, content_type, widget, next_step);
}
else if (download.status == WebKit.DownloadStatus.ERROR)
Midori.show_message_dialog (Gtk.MessageType.ERROR,
_("Download error"),
_("Cannot open '%s' because the download failed."
).printf (download.destination_uri), false);
});
download.start ();
return true;
#endif
}
/* If @next_step is %NextStep.TRY_OPEN, tries to pick a handler automatically.
If the automatic handler did not exist or could not run, asks for an application.
Returns whether an application was found and launched successfully. */
bool open_now (string uri, string content_type, Gtk.Widget widget, NextStep next_step) {
if (next_step == NextStep.TRY_OPEN && (new Associations ()).open (content_type, uri))
return true;
/* if opening directly failed or wasn't tried, ask for an association */
if (open_with (uri, content_type, widget) != null)
return true;
return false;
}
/* Returns the application chosen to open the uri+content_type if the application
was spawned successfully, %NULL if none was chosen or running was unsuccessful. */
AppInfo? open_with (string uri, string content_type, Gtk.Widget widget) {
var dialog = new ChooserDialog (uri, content_type, widget);
var app_info = dialog.open_with ();
dialog.destroy ();
if (uri == "")
return app_info;
if (app_info == null)
return app_info;
return open_app_info (app_info, uri, content_type) ? app_info : null;
}
void context_menu (Midori.Tab tab, WebKit.HitTestResult hit_test_result, Midori.ContextAction menu) {
if ((hit_test_result.context & WebKit.HitTestResultContext.LINK) != 0) {
string uri = hit_test_result.link_uri;
var action = new Gtk.Action ("OpenWith", _("Open _with…"), null, null);
action.activate.connect ((action) => {
open_with_type (uri, get_content_type (uri, null), tab, NextStep.OPEN_WITH);
});
menu.add (action);
}
#if !HAVE_WEBKIT2
if ((hit_test_result.context & WebKit.HitTestResultContext.IMAGE) != 0) {
string uri = hit_test_result.image_uri;
var action = new Gtk.Action ("OpenImageInViewer", _("Open in Image _Viewer"), null, null);
action.activate.connect ((action) => {
open_with_type (uri, get_content_type (uri, null), tab, NextStep.TRY_OPEN);
});
menu.add (action);
}
#endif
}
void show_preferences (Katze.Preferences preferences) {
var settings = get_app ().settings;
var category = preferences.add_category (_("File Types"), Gtk.STOCK_FILE);
preferences.add_group (null);
var sizegroup = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL);
var label = new Gtk.Label (_("Text Editor"));
sizegroup.add_widget (label);
label.set_alignment (0.0f, 0.5f);
preferences.add_widget (label, "indented");
var entry = new ChooserButton ("text/plain", settings.text_editor);
sizegroup.add_widget (entry);
entry.selected.connect ((commandline) => {
settings.text_editor = commandline;
});
preferences.add_widget (entry, "spanned");
label = new Gtk.Label (_("News Aggregator"));
sizegroup.add_widget (label);
label.set_alignment (0.0f, 0.5f);
preferences.add_widget (label, "indented");
entry = new ChooserButton ("application/rss+xml", settings.news_aggregator);
sizegroup.add_widget (entry);
entry.selected.connect ((commandline) => {
settings.news_aggregator = commandline;
});
preferences.add_widget (entry, "spanned");
var types = new Types ();
types.selected.connect ((content_type, iter) => {
var app_info = open_with ("", content_type, preferences);
if (app_info == null)
return;
try {
app_info.set_as_default_for_type (content_type);
types.store.set (iter, 1, app_info);
} catch (Error error) {
warning ("Failed to select default for \"%s\": %s", content_type, error.message);
}
});
category.pack_start (types, true, true, 0);
types.show_all ();
}
public void tab_added (Midori.Browser browser, Midori.View view) {
view.navigation_requested.connect_after (navigation_requested);
view.open_uri.connect (open_uri);
view.context_menu.connect (context_menu);
}
public void tab_removed (Midori.Browser browser, Midori.View view) {
view.navigation_requested.disconnect (navigation_requested);
view.open_uri.disconnect (open_uri);
view.context_menu.disconnect (context_menu);
}
void browser_added (Midori.Browser browser) {
foreach (var tab in browser.get_tabs ())
tab_added (browser, tab);
browser.add_tab.connect (tab_added);
browser.remove_tab.connect (tab_removed);
browser.show_preferences.connect (show_preferences);
}
void activated (Midori.App app) {
foreach (var browser in app.get_browsers ())
browser_added (browser);
app.add_browser.connect (browser_added);
}
void browser_removed (Midori.Browser browser) {
foreach (var tab in browser.get_tabs ())
tab_removed (browser, tab);
browser.add_tab.disconnect (tab_added);
browser.remove_tab.disconnect (tab_removed);
browser.show_preferences.disconnect (show_preferences);
}
void deactivated () {
var app = get_app ();
foreach (var browser in app.get_browsers ())
browser_removed (browser);
app.add_browser.disconnect (browser_added);
}
internal Manager () {
GLib.Object (name: "External Applications",
description: "Choose what to open unknown file types with",
version: "0.1" + Midori.VERSION_SUFFIX,
authors: "Christian Dywan <christian@twotoasts.de>");
this.activate.connect (activated);
this.deactivate.connect (deactivated);
}
}
}
public Midori.Extension extension_init () {
return new ExternalApplications.Manager ();
}