midori/extensions/transfers.vala

584 lines
22 KiB
Vala
Raw Permalink Normal View History

2013-10-24 03:45:02 +00:00
/*
Copyright (C) 2009-2013 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.
*/
namespace Gtk {
extern static void widget_size_request (Gtk.Widget widget, out Gtk.Requisition requisition);
}
namespace Sokoke {
extern static void widget_get_text_size (Gtk.Widget widget, string sample, out int width, out int height);
}
namespace Transfers {
private class Transfer : GLib.Object {
2015-09-12 00:47:06 +00:00
internal uint poll_source = 0;
2013-10-24 03:45:02 +00:00
internal WebKit.Download download;
2015-09-12 00:47:06 +00:00
internal virtual signal void changed ()
{
string tooltip = Midori.Download.calculate_tooltip (download);
this.set_data ("tooltip", tooltip);
}
2013-10-24 03:45:02 +00:00
internal signal void remove ();
internal signal void removed ();
internal int action { get {
return Midori.Download.get_type (download);
} }
internal double progress { get {
return Midori.Download.get_progress (download);
} }
#if HAVE_WEBKIT2
public bool succeeded { get; protected set; default = false; }
public bool finished { get; protected set; default = false; }
internal string destination { get {
return download.destination;
} }
#else
internal bool succeeded { get {
return download.status == WebKit.DownloadStatus.FINISHED;
} }
internal bool finished { get {
return Midori.Download.is_finished (download);
} }
internal string destination { get {
return download.destination_uri;
} }
#endif
internal Transfer (WebKit.Download download) {
2015-09-12 00:47:06 +00:00
poll_source = Timeout.add(1000/10, () => {
changed ();
return true;
});
2013-10-24 03:45:02 +00:00
this.download = download;
#if HAVE_WEBKIT2
download.finished.connect (() => {
succeeded = finished = true;
changed ();
2015-09-12 00:47:06 +00:00
Source.remove (poll_source);
poll_source = 0;
2013-10-24 03:45:02 +00:00
});
download.failed.connect (() => {
succeeded = false;
finished = true;
changed ();
2015-09-12 00:47:06 +00:00
Source.remove (poll_source);
poll_source = 0;
2013-10-24 03:45:02 +00:00
});
#else
2015-09-12 00:47:06 +00:00
download.notify["status"].connect (() => {
changed ();
if (download.status == WebKit.DownloadStatus.FINISHED || download.status == WebKit.DownloadStatus.ERROR) {
Source.remove (poll_source);
poll_source = 0;
}
});
2013-10-24 03:45:02 +00:00
#endif
}
}
static bool pending_transfers (Katze.Array array) {
foreach (GLib.Object item in array.get_items ()) {
var transfer = item as Transfer;
if (!transfer.finished)
return true;
}
return false;
}
private class Sidebar : Gtk.VBox, Midori.Viewable {
Gtk.Toolbar? toolbar = null;
Gtk.ToolButton clear;
Gtk.ListStore store = new Gtk.ListStore (1, typeof (Transfer));
Gtk.TreeView treeview;
Katze.Array array;
public unowned string get_stock_id () {
return Midori.Stock.TRANSFER;
}
public unowned string get_label () {
return _("Transfers");
}
public Gtk.Widget get_toolbar () {
if (toolbar == null) {
toolbar = new Gtk.Toolbar ();
toolbar.set_icon_size (Gtk.IconSize.BUTTON);
toolbar.insert (new Gtk.ToolItem (), -1);
var separator = new Gtk.SeparatorToolItem ();
separator.draw = false;
separator.set_expand (true);
toolbar.insert (separator, -1);
clear = new Gtk.ToolButton.from_stock (Gtk.STOCK_CLEAR);
clear.label = _("Clear All");
clear.is_important = true;
clear.clicked.connect (clear_clicked);
clear.sensitive = !array.is_empty ();
toolbar.insert (clear, -1);
toolbar.show_all ();
}
return toolbar;
}
void clear_clicked () {
foreach (GLib.Object item in array.get_items ()) {
var transfer = item as Transfer;
if (transfer.finished)
transfer.remove ();
}
}
public Sidebar (Katze.Array array) {
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.CellRendererProgress renderer_progress = new Gtk.CellRendererProgress ();
column.pack_start (renderer_progress, true);
column.set_expand (true);
column.set_cell_data_func (renderer_progress, on_render_text);
treeview.append_column (column);
column = new Gtk.TreeViewColumn ();
Gtk.CellRendererPixbuf renderer_button = new Gtk.CellRendererPixbuf ();
column.pack_start (renderer_button, false);
column.set_cell_data_func (renderer_button, on_render_button);
treeview.append_column (column);
treeview.row_activated.connect (row_activated);
treeview.button_release_event.connect (button_released);
treeview.popup_menu.connect (menu_popup);
treeview.show ();
pack_start (treeview, true, true, 0);
this.array = array;
array.add_item.connect (transfer_added);
array.remove_item.connect_after (transfer_removed);
foreach (GLib.Object item in array.get_items ())
transfer_added (item);
}
void row_activated (Gtk.TreePath path, Gtk.TreeViewColumn column) {
Gtk.TreeIter iter;
if (store.get_iter (out iter, path)) {
Transfer transfer;
store.get (iter, 0, out transfer);
2015-09-12 00:47:06 +00:00
try {
if (Midori.Download.action_clear (transfer.download, treeview))
transfer.remove ();
} catch (Error error) {
// Failure to open is the only known possibility here
GLib.warning (_("Failed to open download: %s"), error.message);
}
2013-10-24 03:45:02 +00:00
}
}
bool button_released (Gdk.EventButton event) {
if (event.button == 3)
return show_popup_menu (event);
return false;
}
bool menu_popup () {
return show_popup_menu (null);
}
bool show_popup_menu (Gdk.EventButton? event) {
Gtk.TreeIter iter;
if (treeview.get_selection ().get_selected (null, out iter)) {
Transfer transfer;
store.get (iter, 0, out transfer);
var menu = new Gtk.Menu ();
var menuitem = new Gtk.ImageMenuItem.from_stock (Gtk.STOCK_OPEN, null);
menuitem.activate.connect (() => {
2015-09-12 00:47:06 +00:00
try {
Midori.Download.open (transfer.download, treeview);
} catch (Error error_open) {
GLib.warning (_("Failed to open download: %s"), error_open.message);
}
2013-10-24 03:45:02 +00:00
});
menuitem.sensitive = transfer.succeeded;
menu.append (menuitem);
menuitem = new Gtk.ImageMenuItem.with_mnemonic (_("Open Destination _Folder"));
menuitem.image = new Gtk.Image.from_stock (Gtk.STOCK_DIRECTORY, Gtk.IconSize.MENU);
menuitem.activate.connect (() => {
var folder = GLib.File.new_for_uri (transfer.destination);
2015-09-12 00:47:06 +00:00
(Midori.Browser.get_for_widget (this).tab as Midori.Tab).open_uri (folder.get_parent ().get_uri ());
2013-10-24 03:45:02 +00:00
});
menu.append (menuitem);
menuitem = new Gtk.ImageMenuItem.with_mnemonic (_("Copy Link Loc_ation"));
menuitem.activate.connect (() => {
string uri = transfer.destination;
get_clipboard (Gdk.SELECTION_PRIMARY).set_text (uri, -1);
get_clipboard (Gdk.SELECTION_CLIPBOARD).set_text (uri, -1);
});
menuitem.image = new Gtk.Image.from_stock (Gtk.STOCK_COPY, Gtk.IconSize.MENU);
menu.append (menuitem);
menu.show_all ();
Katze.widget_popup (treeview, menu, null, Katze.MenuPos.CURSOR);
return true;
}
return false;
}
int tree_sort_func (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b) {
Transfer transfer1, transfer2;
model.get (a, 0, out transfer1);
model.get (b, 0, out transfer2);
return (transfer1.finished ? 1 : 0) - (transfer2.finished ? 1 : 0);
}
2015-09-12 00:47:06 +00:00
void transfer_changed (GLib.Object item) {
2013-10-24 03:45:02 +00:00
treeview.queue_draw ();
}
void transfer_added (GLib.Object item) {
var transfer = item as Transfer;
Gtk.TreeIter iter;
store.append (out iter);
store.set (iter, 0, transfer);
2015-09-12 00:47:06 +00:00
transfer.changed.connect (() => transfer_changed (transfer));
2013-10-24 03:45:02 +00:00
clear.sensitive = true;
}
void transfer_removed (GLib.Object item) {
var transfer = item as Transfer;
transfer.changed.disconnect (transfer_changed);
Gtk.TreeIter iter;
if (store.iter_children (out iter, null)) {
do {
Transfer found;
store.get (iter, 0, out found);
if (transfer == found) {
store.remove (iter);
break;
}
} while (store.iter_next (ref iter));
}
if (array.is_empty ())
clear.sensitive = false;
}
void on_render_icon (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
Transfer transfer;
model.get (iter, 0, out transfer);
string content_type = Midori.Download.get_content_type (transfer.download, null);
var icon = GLib.ContentType.get_icon (content_type) as ThemedIcon;
icon.append_name ("text-html");
renderer.set ("gicon", icon,
"stock-size", Gtk.IconSize.DND,
"xpad", 1, "ypad", 12);
}
void on_render_text (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
Transfer transfer;
model.get (iter, 0, out transfer);
2015-09-12 00:47:06 +00:00
renderer.set ("text", transfer.get_data<string>("tooltip") ?? "",
2013-10-24 03:45:02 +00:00
"value", (int)(transfer.progress * 100));
}
void on_render_button (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
Transfer transfer;
model.get (iter, 0, out transfer);
string stock_id = Midori.Download.action_stock_id (transfer.download);
renderer.set ("stock-id", stock_id,
"stock-size", Gtk.IconSize.MENU);
}
}
private class TransferButton : Gtk.ToolItem {
Transfer transfer;
Gtk.ProgressBar progress;
Gtk.Image icon;
Gtk.Button button;
public TransferButton (Transfer transfer) {
this.transfer = transfer;
var box = new Gtk.HBox (false, 0);
progress = new Gtk.ProgressBar ();
#if HAVE_GTK3
progress.show_text = true;
#endif
progress.ellipsize = Pango.EllipsizeMode.MIDDLE;
2015-09-12 00:47:06 +00:00
string filename = Midori.Download.get_basename_for_display (transfer.destination);
2013-10-24 03:45:02 +00:00
progress.text = filename;
int width;
Sokoke.widget_get_text_size (progress, "M", out width, null);
progress.set_size_request (width * 10, 1);
box.pack_start (progress, false, false, 0);
icon = new Gtk.Image ();
button = new Gtk.Button ();
button.relief = Gtk.ReliefStyle.NONE;
button.focus_on_click = false;
button.clicked.connect (button_clicked);
button.add (icon);
box.pack_start (button, false, false, 0);
add (box);
show_all ();
transfer.changed.connect (transfer_changed);
transfer_changed ();
transfer.removed.connect (transfer_removed);
}
void button_clicked () {
2015-09-12 00:47:06 +00:00
try {
if (Midori.Download.action_clear (transfer.download, button))
transfer.remove ();
} catch (Error error) {
// Failure to open is the only known possibility here
GLib.warning (_("Failed to open download: %s"), error.message);
}
2013-10-24 03:45:02 +00:00
}
void transfer_changed () {
progress.fraction = Midori.Download.get_progress (transfer.download);
2015-09-12 00:47:06 +00:00
progress.tooltip_text = transfer.get_data<string>("tooltip") ?? "";
2013-10-24 03:45:02 +00:00
string stock_id = Midori.Download.action_stock_id (transfer.download);
icon.set_from_stock (stock_id, Gtk.IconSize.MENU);
}
void transfer_removed () {
destroy ();
}
}
private class Toolbar : Gtk.Toolbar {
Katze.Array array;
Gtk.ToolButton clear;
void clear_clicked () {
foreach (GLib.Object item in array.get_items ()) {
var transfer = item as Transfer;
if (transfer.finished)
array.remove_item (item);
}
}
public Toolbar (Katze.Array array) {
set_icon_size (Gtk.IconSize.BUTTON);
set_style (Gtk.ToolbarStyle.BOTH_HORIZ);
show_arrow = false;
clear = new Gtk.ToolButton.from_stock (Gtk.STOCK_CLEAR);
clear.label = _("Clear All");
clear.is_important = true;
clear.clicked.connect (clear_clicked);
clear.sensitive = !array.is_empty ();
insert (clear, -1);
clear.show ();
clear.sensitive = false;
this.array = array;
array.add_item.connect (transfer_added);
array.remove_item.connect_after (transfer_removed);
foreach (GLib.Object item in array.get_items ())
transfer_added (item);
}
void transfer_added (GLib.Object item) {
var transfer = item as Transfer;
/* Newest item on the left */
insert (new TransferButton (transfer), 0);
clear.sensitive = true;
show ();
Gtk.Requisition req;
Gtk.widget_size_request (parent, out req);
int reqwidth = req.width;
int winwidth;
(get_toplevel () as Gtk.Window).get_size (out winwidth, null);
if (reqwidth > winwidth)
clear_clicked ();
}
void transfer_removed (GLib.Object item) {
clear.sensitive = pending_transfers (array);
if (array.is_empty ())
hide ();
}
}
private class Manager : Midori.Extension {
internal Katze.Array array;
internal GLib.List<Gtk.Widget> widgets;
2015-09-12 00:47:06 +00:00
internal GLib.List<string> notifications;
internal uint notification_timeout;
2013-10-24 03:45:02 +00:00
void download_added (WebKit.Download download) {
var transfer = new Transfer (download);
transfer.remove.connect (transfer_remove);
transfer.changed.connect (transfer_changed);
array.remove_item.connect (transfer_removed);
array.add_item (transfer);
}
2015-09-12 00:47:06 +00:00
bool notification_timeout_triggered () {
notification_timeout = 0;
if (notifications.length () > 0) {
string filename = notifications.nth_data(0);
string msg;
if (notifications.length () == 1)
msg = _("The file '<b>%s</b>' has been downloaded.").printf (filename);
else
msg = _("'<b>%s</b>' and %d other files have been downloaded.").printf (filename, notifications.length ());
get_app ().send_notification (_("Transfer completed"), msg);
notifications = new GLib.List<string> ();
}
return false;
}
2013-10-24 03:45:02 +00:00
void transfer_changed (Transfer transfer) {
if (transfer.succeeded) {
/* FIXME: The following 2 blocks ought to be done in core */
if (transfer.action == Midori.DownloadType.OPEN) {
2015-09-12 00:47:06 +00:00
try {
if (Midori.Download.action_clear (transfer.download, widgets.nth_data (0)))
transfer.remove ();
} catch (Error error) {
// Failure to open is the only known possibility here
GLib.warning (_("Failed to open download: %s"), error.message);
}
2013-10-24 03:45:02 +00:00
}
string uri = transfer.destination;
2015-09-12 00:47:06 +00:00
string filename = Midori.Download.get_basename_for_display (uri);
2013-10-24 03:45:02 +00:00
var item = new Katze.Item ();
item.uri = uri;
item.name = filename;
Midori.Browser.update_history (item, "download", "create");
if (!Midori.Download.has_wrong_checksum (transfer.download))
Gtk.RecentManager.get_default ().add_item (uri);
2015-09-12 00:47:06 +00:00
notifications.append (filename);
if (notification_timeout == 0) {
notification_timeout_triggered ();
notification_timeout = Midori.Timeout.add_seconds (60, notification_timeout_triggered);
}
2013-10-24 03:45:02 +00:00
}
}
void transfer_remove (Transfer transfer) {
array.remove_item (transfer);
}
void transfer_removed (GLib.Object item) {
var transfer = item as Transfer;
transfer.removed ();
}
#if HAVE_GTK3
bool browser_closed (Gtk.Widget widget, Gdk.EventAny event) {
#else
bool browser_closed (Gtk.Widget widget, Gdk.Event event) {
#endif
var browser = widget as Midori.Browser;
if (pending_transfers (array)) {
var dialog = new Gtk.MessageDialog (browser,
Gtk.DialogFlags.DESTROY_WITH_PARENT,
Gtk.MessageType.WARNING, Gtk.ButtonsType.NONE,
2015-09-12 00:47:06 +00:00
"%s",
2013-10-24 03:45:02 +00:00
_("Some files are being downloaded"));
dialog.title = _("Some files are being downloaded");
dialog.add_buttons (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
_("_Quit Midori"), Gtk.ResponseType.ACCEPT);
dialog.format_secondary_text (
_("The transfers will be cancelled if Midori quits."));
bool cancel = dialog.run () != Gtk.ResponseType.ACCEPT;
dialog.destroy ();
return cancel;
}
return false;
}
void browser_added (Midori.Browser browser) {
var viewable = new Sidebar (array);
viewable.show ();
browser.panel.append_page (viewable);
widgets.append (viewable);
var toolbar = new Toolbar (array);
#if HAVE_GTK3
browser.statusbar.pack_end (toolbar, false, false);
#else
browser.statusbar.pack_start (toolbar, false, false);
#endif
widgets.append (toolbar);
// TODO: popover
// TODO: progress in dock item
browser.add_download.connect (download_added);
browser.delete_event.connect (browser_closed);
}
void activated (Midori.App app) {
array = new Katze.Array (typeof (Transfer));
widgets = new GLib.List<Gtk.Widget> ();
2015-09-12 00:47:06 +00:00
notifications = new GLib.List<string> ();
notification_timeout = 0;
2013-10-24 03:45:02 +00:00
foreach (var browser in app.get_browsers ())
browser_added (browser);
app.add_browser.connect (browser_added);
}
void deactivated () {
var app = get_app ();
app.add_browser.disconnect (browser_added);
foreach (var browser in app.get_browsers ()) {
browser.add_download.disconnect (download_added);
browser.delete_event.disconnect (browser_closed);
}
foreach (var widget in widgets)
widget.destroy ();
array.remove_item.disconnect (transfer_removed);
}
internal Manager () {
GLib.Object (name: _("Transfer Manager"),
description: _("View downloaded files"),
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 Transfers.Manager ();
}