557 lines
21 KiB
Vala
557 lines
21 KiB
Vala
/*
|
|
Copyright (C) 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 Midori {
|
|
protected class Tally : Gtk.EventBox {
|
|
public Midori.Tab tab { get; set; }
|
|
Gtk.Spinner spinner;
|
|
public Gtk.Label label;
|
|
Gtk.HBox box;
|
|
public Gtk.Image icon;
|
|
Gtk.Alignment align;
|
|
Gtk.Button close;
|
|
|
|
public bool close_button_left { get; set; default = false; }
|
|
public bool close_button_visible { get; set; default = false; }
|
|
|
|
protected Tally (Midori.Tab tab) {
|
|
this.tab = tab;
|
|
box = new Gtk.HBox (false, 1);
|
|
add (box);
|
|
|
|
spinner = new Gtk.Spinner ();
|
|
spinner.active = true;
|
|
/* Ensure the spinner is the size of the icon */
|
|
int icon_size = 16;
|
|
Gtk.icon_size_lookup_for_settings (get_settings (),
|
|
Gtk.IconSize.MENU, out icon_size, null);
|
|
spinner.set_size_request (icon_size, icon_size);
|
|
box.pack_start (spinner, false, false, 0);
|
|
label = new Gtk.Label (null);
|
|
label.set_alignment (0.5f, 0.5f);
|
|
label.set_padding (0, 0);
|
|
box.pack_start (label, true, true, 0);
|
|
close = new Gtk.Button ();
|
|
close.relief = Gtk.ReliefStyle.NONE;
|
|
close.focus_on_click = false;
|
|
#if !HAVE_GTK3
|
|
close.name = "midori-close-button";
|
|
close.style_set.connect (close_style_set);
|
|
#endif
|
|
icon = new Gtk.Image.from_gicon (new ThemedIcon.with_default_fallbacks ("window-close-symbolic"), Gtk.IconSize.MENU);
|
|
close.add (icon);
|
|
align = new Gtk.Alignment (1.0f, 0.5f, 0.0f, 0.0f);
|
|
align.add (close);
|
|
box.pack_start (align, false, false, 0);
|
|
close.clicked.connect (close_clicked);
|
|
icon = new Gtk.Image.from_gicon (new ThemedIcon.with_default_fallbacks ("text-html-symbolic"), Gtk.IconSize.MENU);
|
|
box.pack_start (icon, false, false, 0);
|
|
box.show_all ();
|
|
|
|
tab.notify["uri"].connect (uri_changed);
|
|
tab.notify["title"].connect (title_changed);
|
|
tab.notify["icon"].connect (icon_changed);
|
|
tab.notify["minimized"].connect (minimized_changed);
|
|
tab.notify["progress"].connect (progress_changed);
|
|
tab.colors_changed.connect (colors_changed);
|
|
update_label ();
|
|
label.visible = !tab.minimized;
|
|
spinner.visible = tab.progress > 0.0;
|
|
icon.visible = !spinner.visible;
|
|
update_color ();
|
|
|
|
notify["close-button-left"].connect (close_button_left_changed);
|
|
notify_property ("close-button-left");
|
|
notify["close-button-visible"].connect (close_button_visible_changed);
|
|
notify_property ("close-button-visible");
|
|
}
|
|
|
|
#if !HAVE_GTK3
|
|
void close_style_set (Gtk.Style? previous_style) {
|
|
Gtk.Requisition size;
|
|
close.child.size_request (out size);
|
|
close.set_size_request (size.width, size.height);
|
|
}
|
|
#endif
|
|
|
|
void close_clicked () {
|
|
tab.destroy ();
|
|
}
|
|
|
|
void uri_changed (GLib.ParamSpec pspec) {
|
|
label.label = tab.uri;
|
|
}
|
|
|
|
void title_changed (GLib.ParamSpec pspec) {
|
|
update_label ();
|
|
}
|
|
|
|
void update_label () {
|
|
string? title;
|
|
tab.get ("title", out title);
|
|
label.label = Midori.Tab.get_display_title (title, tab.uri);
|
|
/* Use computed label below! */
|
|
label.ellipsize = Midori.Tab.get_display_ellipsize (label.label, tab.uri);
|
|
tooltip_text = label.label;
|
|
}
|
|
|
|
void icon_changed (GLib.ParamSpec pspec) {
|
|
Icon? icon;
|
|
tab.get ("icon", out icon);
|
|
this.icon.set_from_gicon (icon, Gtk.IconSize.MENU);
|
|
}
|
|
|
|
void colors_changed () {
|
|
update_color ();
|
|
}
|
|
|
|
void update_color () {
|
|
visible_window = tab.fg_color != null || tab.bg_color != null;
|
|
label.modify_fg (Gtk.StateType.NORMAL, tab.fg_color);
|
|
label.modify_fg (Gtk.StateType.ACTIVE, tab.fg_color);
|
|
modify_bg (Gtk.StateType.NORMAL, tab.bg_color);
|
|
modify_bg (Gtk.StateType.ACTIVE, tab.bg_color);
|
|
}
|
|
|
|
void close_button_left_changed (GLib.ParamSpec pspec) {
|
|
if (close_button_left) {
|
|
box.reorder_child (align, 0);
|
|
box.reorder_child (label, 1);
|
|
box.reorder_child (icon, 2);
|
|
box.reorder_child (spinner, 3);
|
|
} else {
|
|
box.reorder_child (spinner, 0);
|
|
box.reorder_child (icon, 1);
|
|
box.reorder_child (label, 2);
|
|
box.reorder_child (align, 3);
|
|
}
|
|
}
|
|
|
|
void close_button_visible_changed (GLib.ParamSpec pspec) {
|
|
align.visible = !tab.minimized && close_button_visible;
|
|
}
|
|
|
|
void minimized_changed (GLib.ParamSpec pspec) {
|
|
label.visible = !tab.minimized;
|
|
notify_property ("close-button-visible");
|
|
}
|
|
|
|
void progress_changed (GLib.ParamSpec pspec) {
|
|
spinner.visible = tab.progress > 0.0;
|
|
icon.visible = !spinner.visible;
|
|
}
|
|
}
|
|
|
|
public class Notebook : Gtk.EventBox {
|
|
public Gtk.Notebook notebook;
|
|
int last_tab_size = 0;
|
|
|
|
#if !HAVE_GTK3
|
|
static const string style_fixup = """
|
|
style "midori-close-button-style"
|
|
{
|
|
GtkWidget::focus-padding = 0
|
|
GtkWidget::focus-line-width = 0
|
|
xthickness = 0
|
|
ythickness = 0
|
|
}
|
|
widget "*.midori-close-button" style "midori-close-button-style"
|
|
""";
|
|
#endif
|
|
|
|
/* Since: 0.5.7 */
|
|
public uint count { get; private set; default = 0; }
|
|
/* Since: 0.5.7 */
|
|
public int index { get; set; default = -1; }
|
|
/* Since: 0.5.7 */
|
|
public Midori.Tab? tab { get; set; default = null; }
|
|
/* Since: 0.5.7 */
|
|
private Midori.Tab? previous { get; set; default = null; }
|
|
|
|
/* Since: 0.5.7 */
|
|
public bool close_buttons_left { get; set; default = true; }
|
|
/* Since: 0.5.7 */
|
|
public bool close_buttons_visible { get; set; default = true; }
|
|
/* Since: 0.5.7 */
|
|
public bool labels_visible { get; set; default = true; }
|
|
|
|
/* Since: 0.5.7 */
|
|
public signal void tab_context_menu (Midori.Tab tab, ContextAction menu);
|
|
/* Since: 0.5.7 */
|
|
public signal void context_menu (ContextAction menu);
|
|
/* The current tab is about to switch, but the old tab still has focus.
|
|
Since: 0.5.7 */
|
|
public signal void tab_switched (Midori.Tab? old, Midori.Tab @new);
|
|
/* A tab is about to move to a new position.
|
|
Since: 0.5.7 */
|
|
public signal void tab_moved (Midori.Tab tab, uint new_index);
|
|
/* A tab is being dragging out of the window.
|
|
Since: 0.5.7 */
|
|
public signal void tab_detached (Midori.Tab tab, int x, int y);
|
|
/* Since: 0.5.7 */
|
|
public signal void new_tab ();
|
|
|
|
[CCode (type = "GtkWidget*")]
|
|
public Notebook () {
|
|
visible_window = false;
|
|
notebook = new Gtk.Notebook ();
|
|
notebook.visible = notebook.scrollable = true;
|
|
notebook.show_border = false;
|
|
notebook.set ("group-name", PACKAGE_NAME);
|
|
add (notebook);
|
|
|
|
#if HAVE_GTK3
|
|
get_style_context ().add_class ("dynamic-notebook");
|
|
#else
|
|
/* Remove the inner border between scrollbars and window border */
|
|
Gtk.RcStyle rcstyle = new Gtk.RcStyle ();
|
|
rcstyle.xthickness = 0;
|
|
notebook.modify_style (rcstyle);
|
|
Gtk.rc_parse_string (style_fixup);
|
|
#endif
|
|
notify["index"].connect (index_changed);
|
|
notify["tab"].connect (tab_changed);
|
|
notify["labels-visible"].connect (labels_visible_changed);
|
|
notify["close-buttons-visible"].connect (close_buttons_visible_changed);
|
|
notify["close-buttons-left"].connect (close_buttons_left_changed);
|
|
|
|
notebook.size_allocate.connect (size_allocated);
|
|
notebook.switch_page.connect (page_switched);
|
|
notebook.page_reordered.connect (page_moved);
|
|
notebook.create_window.connect (window_created);
|
|
|
|
var add = new Gtk.Button ();
|
|
add.tooltip_text = _("Open a new tab");
|
|
add.relief = Gtk.ReliefStyle.NONE;
|
|
add.add (new Gtk.Image.from_gicon (new ThemedIcon.with_default_fallbacks ("tab-new-symbolic"), Gtk.IconSize.MENU));
|
|
add.show_all ();
|
|
notebook.set_action_widget (add, Gtk.PackType.START);
|
|
add.clicked.connect (()=>{
|
|
new_tab ();
|
|
});
|
|
take_incoming_uris (add);
|
|
|
|
button_press_event.connect (button_pressed);
|
|
}
|
|
|
|
void take_incoming_uris (Gtk.Widget widget) {
|
|
Gtk.drag_dest_set (widget, Gtk.DestDefaults.ALL, (Gtk.TargetEntry[])null, Gdk.DragAction.COPY);
|
|
Gtk.drag_dest_add_text_targets (widget);
|
|
Gtk.drag_dest_add_uri_targets (widget);
|
|
widget.drag_drop.connect (uri_dropped);
|
|
widget.drag_data_received.connect (uri_received);
|
|
}
|
|
|
|
bool uri_dropped (Gtk.Widget widget, Gdk.DragContext context, int x, int y, uint timestamp) {
|
|
Gtk.drag_finish (context, false, false, timestamp);
|
|
return true;
|
|
}
|
|
|
|
void uri_received (Gtk.Widget widget, Gdk.DragContext context, int x, int y, Gtk.SelectionData data, uint ttype, uint timestamp) {
|
|
string[] uri = data.get_uris ();
|
|
string drag_uri = uri != null ? uri[0] : data.get_text ();
|
|
Midori.Tab drag_tab;
|
|
if (widget is Tally)
|
|
drag_tab = (widget as Tally).tab;
|
|
else {
|
|
new_tab ();
|
|
// Browser will have focussed the new tab
|
|
drag_tab = tab;
|
|
}
|
|
drag_tab.web_view.load_uri (drag_uri);
|
|
}
|
|
|
|
|
|
~Notebook () {
|
|
notebook.size_allocate.disconnect (size_allocated);
|
|
notebook.switch_page.disconnect (page_switched);
|
|
notebook.page_reordered.disconnect (page_moved);
|
|
notebook.create_window.disconnect (window_created);
|
|
}
|
|
|
|
/* Since: 0.5.8 */
|
|
public ContextAction get_context_action () {
|
|
var menu = new Midori.ContextAction ("NotebookContextMenu", null, null, null);
|
|
uint counter = 0;
|
|
foreach (var child in notebook.get_children ()) {
|
|
var tab = child as Midori.Tab;
|
|
var tally = notebook.get_tab_label (tab) as Tally;
|
|
var action = new Midori.ContextAction.escaped ("Tab%u".printf (counter), tally.label.label, null, null);
|
|
action.gicon = tally.icon.gicon;
|
|
action.activate.connect (()=>{
|
|
notebook.set_current_page (notebook.page_num (tab));
|
|
});
|
|
menu.add (action);
|
|
counter++;
|
|
}
|
|
context_menu (menu);
|
|
return menu;
|
|
}
|
|
|
|
bool button_pressed (Gdk.EventButton event) {
|
|
/* Propagate events in logical label area */
|
|
foreach (var child in notebook.get_children ()) {
|
|
var tally = notebook.get_tab_label (tab) as Tally;
|
|
Gtk.Allocation size;
|
|
tally.get_allocation (out size);
|
|
if (tally.get_mapped ()
|
|
&& event.x_root >= size.x
|
|
&& event.x_root <= (size.x + size.width)) {
|
|
tally.button_press_event (event);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (event.type == Gdk.EventType.2BUTTON_PRESS && event.button == 1
|
|
|| event.button == 2) {
|
|
new_tab ();
|
|
return true;
|
|
}
|
|
else if (event.button == 3) {
|
|
var menu = get_context_action ();
|
|
var popup = menu.create_menu (null, false);
|
|
popup.show ();
|
|
popup.attach_to_widget (this, null);
|
|
popup.popup (null, null, null, event.button, event.time);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void insert (Midori.Tab tab, int index) {
|
|
var tally = new Tally (tab);
|
|
tally.close_button_left = close_buttons_left;
|
|
tally.close_button_visible = close_buttons_visible;
|
|
tally.button_press_event.connect (tab_button_pressed);
|
|
tally.show ();
|
|
tally.set_size_request (tab.minimized ? -1 : last_tab_size, -1);
|
|
take_incoming_uris (tally);
|
|
|
|
/* Minimum requirements for any tab */
|
|
tab.can_focus = tab.visible = true;
|
|
notebook.insert_page (tab, tally, index);
|
|
notebook.set_tab_reorderable (tab, true);
|
|
notebook.set_tab_detachable (tab, true);
|
|
tab.destroy.connect (tab_removed);
|
|
tab.notify["minimized"].connect (tab_minimized);
|
|
count++;
|
|
tab.ref ();
|
|
relayout ();
|
|
}
|
|
|
|
void tab_removed () {
|
|
count--;
|
|
if (count > 0)
|
|
relayout ();
|
|
}
|
|
|
|
void relayout () {
|
|
Gtk.Allocation size;
|
|
notebook.get_allocation (out size);
|
|
resize (size.width);
|
|
}
|
|
|
|
/* Since: 0.5.8 */
|
|
public ContextAction get_tab_context_action (Midori.Tab tab) {
|
|
var menu = new Midori.ContextAction ("TabContextMenu", null, null, null);
|
|
tab_context_menu (tab, menu);
|
|
var action_window = new Midori.ContextAction ("TabWindowNew", _("Open in New _Window"), null, "window-new");
|
|
action_window.activate.connect (()=>{
|
|
tab_detached (tab, 128, 128);
|
|
});
|
|
menu.add (action_window);
|
|
var action_minimize = new Midori.ContextAction ("TabMinimize", tab.minimized ? _("Show Tab _Label") : _("Show Tab _Icon Only"), null, null);
|
|
action_minimize.activate.connect (()=>{
|
|
tab.minimized = !tab.minimized;
|
|
});
|
|
menu.add (action_minimize);
|
|
var action_right = new Midori.ContextAction ("TabCloseRight", ngettext ("Close Tab to the R_ight", "Close Tabs to the R_ight", count - 1), null, null);
|
|
action_right.sensitive = count > 1;
|
|
action_right.activate.connect (()=>{
|
|
bool found_tab = false;
|
|
foreach (var child in notebook.get_children ()) {
|
|
if (found_tab)
|
|
child.destroy ();
|
|
else
|
|
found_tab = child == tab;
|
|
}
|
|
});
|
|
menu.add (action_right);
|
|
var action_other = new Midori.ContextAction ("TabCloseOther", ngettext ("Close Ot_her Tab", "Close Ot_her Tabs", count - 1), null, null);
|
|
action_other.sensitive = count > 1;
|
|
action_other.activate.connect (()=>{
|
|
foreach (var child in notebook.get_children ())
|
|
if (child != tab)
|
|
child.destroy ();
|
|
});
|
|
menu.add (action_other);
|
|
var action_close = new Midori.ContextAction ("TabClose", null, null, Gtk.STOCK_CLOSE);
|
|
action_close.activate.connect (()=>{
|
|
tab.destroy ();
|
|
});
|
|
menu.add (action_close);
|
|
return menu;
|
|
}
|
|
|
|
bool tab_button_pressed (Gtk.Widget label, Gdk.EventButton event) {
|
|
Tally tally = label as Tally;
|
|
if (event.button == 1) {
|
|
/* Leave switching and dragging up to the notebook */
|
|
return false;
|
|
} else if (event.button == 2)
|
|
tally.tab.destroy ();
|
|
else if (event.button == 3) {
|
|
var menu = get_tab_context_action (tally.tab);
|
|
var popup = menu.create_menu (null, false);
|
|
popup.show ();
|
|
popup.attach_to_widget (this, null);
|
|
popup.popup (null, null, null, event.button, event.time);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public void move (Midori.Tab tab, int index) {
|
|
notebook.reorder_child (tab, index);
|
|
}
|
|
|
|
/* Chain up drawing manually to circumvent parent checks */
|
|
#if HAVE_GTK3
|
|
public override bool draw (Cairo.Context cr) {
|
|
notebook.draw (cr);
|
|
return true;
|
|
}
|
|
#else
|
|
public override bool expose_event (Gdk.EventExpose event) {
|
|
notebook.expose_event (event);
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
public override void forall_internal (bool include_internal, Gtk.Callback callback) {
|
|
if (include_internal)
|
|
callback (notebook);
|
|
foreach (var child in notebook.get_children ())
|
|
callback (child);
|
|
}
|
|
|
|
/* Can't override Gtk.Container.remove because it checks the parent */
|
|
public new void remove (Midori.Tab tab) {
|
|
return_if_fail (notebook.get_children ().find (tab) != null);
|
|
|
|
notebook.remove (tab);
|
|
tab.destroy.disconnect (tab_removed);
|
|
tab.notify["minimized"].disconnect (tab_minimized);
|
|
tab_removed ();
|
|
tab.unref ();
|
|
}
|
|
|
|
void tab_minimized (GLib.ParamSpec pspec) {
|
|
var tally = notebook.get_tab_label (tab) as Tally;
|
|
tally.set_size_request (tab.minimized ? -1 : last_tab_size, -1);
|
|
}
|
|
|
|
public Midori.Tab get_nth_tab (int index) {
|
|
return notebook.get_nth_page (index) as Midori.Tab;
|
|
}
|
|
|
|
public int get_tab_index (Midori.Tab tab) {
|
|
return notebook.page_num (tab);
|
|
}
|
|
|
|
void index_changed (GLib.ParamSpec pspec) {
|
|
notebook.set_current_page (index);
|
|
}
|
|
|
|
void tab_changed (GLib.ParamSpec pspec) {
|
|
notebook.set_current_page (notebook.page_num (tab));
|
|
}
|
|
|
|
void labels_visible_changed (GLib.ParamSpec pspec) {
|
|
notebook.show_tabs = labels_visible;
|
|
}
|
|
|
|
void close_buttons_visible_changed (GLib.ParamSpec pspec) {
|
|
foreach (var child in notebook.get_children ()) {
|
|
var tally = notebook.get_tab_label (child) as Tally;
|
|
tally.close_button_visible = close_buttons_visible;
|
|
}
|
|
}
|
|
|
|
void close_buttons_left_changed (GLib.ParamSpec pspec) {
|
|
foreach (var child in notebook.get_children ()) {
|
|
var tally = notebook.get_tab_label (child) as Tally;
|
|
tally.close_button_left = close_buttons_left;
|
|
}
|
|
}
|
|
|
|
#if HAVE_GTK3
|
|
void size_allocated (Gtk.Allocation allocation) {
|
|
#else
|
|
void size_allocated (Gdk.Rectangle allocation) {
|
|
#endif
|
|
if (labels_visible && count > 0)
|
|
resize (allocation.width);
|
|
}
|
|
|
|
#if HAVE_GTK3
|
|
void page_switched (Gtk.Widget new_tab, uint new_index) {
|
|
#else
|
|
void page_switched (Gtk.NotebookPage new_tab, uint new_index) {
|
|
#endif
|
|
tab_switched (previous, new_tab as Tab);
|
|
previous = (Midori.Tab)new_tab;
|
|
|
|
notify["index"].disconnect (index_changed);
|
|
notify["tab"].disconnect (tab_changed);
|
|
index = (int)new_index;
|
|
tab = (Midori.Tab)new_tab;
|
|
notify["index"].connect (index_changed);
|
|
notify["tab"].connect (tab_changed);
|
|
}
|
|
|
|
void page_moved (Gtk.Widget moving_tab, uint new_index) {
|
|
tab_moved (moving_tab as Midori.Tab, new_index);
|
|
/* Indices change, current tab is not in the same position */
|
|
notify["index"].disconnect (index_changed);
|
|
index = (int)get_tab_index (tab);
|
|
notify["index"].connect (index_changed);
|
|
}
|
|
|
|
unowned Gtk.Notebook window_created (Gtk.Widget tab, int x, int y) {
|
|
tab_detached (tab as Tab, x, y);
|
|
/* The API allows now, the cast is due to bindings not having ? */
|
|
return (Gtk.Notebook)null;
|
|
}
|
|
|
|
void resize (int new_size) {
|
|
int n = int.max (1, (int)count);
|
|
new_size /= n;
|
|
int icon_size = 16;
|
|
Gtk.icon_size_lookup_for_settings (get_settings (),
|
|
Gtk.IconSize.MENU, out icon_size, null);
|
|
int max_size = 150;
|
|
int min_size = icon_size;
|
|
if (close_buttons_visible)
|
|
min_size += icon_size;
|
|
new_size = new_size.clamp (min_size, max_size);
|
|
if ((new_size - last_tab_size).abs () < 3)
|
|
return;
|
|
|
|
last_tab_size = new_size;
|
|
foreach (var child in notebook.get_children ()) {
|
|
var tab = child as Midori.Tab;
|
|
var tally = notebook.get_tab_label (child) as Tally;
|
|
tally.set_size_request (tab.minimized ? -1 : last_tab_size, -1);
|
|
}
|
|
}
|
|
}
|
|
}
|