diff --git a/src/BrowserWindow.vala b/src/BrowserWindow.vala index 0985bfe..9bbffa2 100644 --- a/src/BrowserWindow.vala +++ b/src/BrowserWindow.vala @@ -51,6 +51,7 @@ public class Odysseus.BrowserWindow : Gtk.ApplicationWindow { private void init_layout() { tabs = new WebNotebook(); + var vtabs = new VerticalTabs(tabs); var header = new Header.HeaderBarWithMenus(); build_toolbar(header); set_titlebar(header); @@ -65,10 +66,12 @@ public class Odysseus.BrowserWindow : Gtk.ApplicationWindow { if (Gdk.WindowState.FULLSCREEN in evt.new_window_state) { tabs.tab_bar_behavior = DynamicNotebook.TabBarBehavior.NEVER; downloads.expand = false; - } else tabs.tab_bar_behavior = DynamicNotebook.TabBarBehavior.ALWAYS; + } else if (vtabs.position != 0) + tabs.tab_bar_behavior = DynamicNotebook.TabBarBehavior.NEVER; + else tabs.tab_bar_behavior = DynamicNotebook.TabBarBehavior.ALWAYS; return false; }); - prompt.add(tabs); + prompt.add(vtabs); downloads = new DownloadsBar(); downloads.expand = false; diff --git a/src/Widgets/VerticalTabs.vala b/src/Widgets/VerticalTabs.vala new file mode 100644 index 0000000..10b752d --- /dev/null +++ b/src/Widgets/VerticalTabs.vala @@ -0,0 +1,88 @@ +/** +* This file is part of Odysseus Web Browser (Copyright Adrian Cochrane 2021). +* +* Odysseus 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. +* +* Odysseus 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 Odysseus. If not, see . +*/ +/** Allows dragging a sidebar pane providing an alternative, vertical view of the tabs. */ +using Granite.Widgets; +public class Odysseus.VerticalTabs : Gtk.Paned { + DynamicNotebook inner; + SourceList tabs; + Gee.Map tab_map = new Gee.HashMap(); + + construct { + wide_handle = true; + orientation = Gtk.Orientation.HORIZONTAL; + position = 0; + } + + public VerticalTabs(Granite.Widgets.DynamicNotebook inner) { + this.inner = inner; pack2(inner, true, false); + var tabsGrid = new Gtk.Grid(); pack1(tabsGrid, true, true); + + tabsGrid.orientation = Gtk.Orientation.VERTICAL; + this.tabs = new SourceList(); + var actionbar = new Gtk.ActionBar(); + actionbar.get_style_context ().add_class (Gtk.STYLE_CLASS_INLINE_TOOLBAR); + + var add_button = new Gtk.Button.from_icon_name("list-add-symbolic", Gtk.IconSize.SMALL_TOOLBAR); + add_button.tooltip_text = inner.add_button_tooltip; + add_button.clicked.connect(() => inner.new_tab_requested()); + actionbar.add(add_button); + var rm_button = new Gtk.Button.from_icon_name("list-remove-symbolic", Gtk.IconSize.SMALL_TOOLBAR); + rm_button.tooltip_text = _("Close tab"); + rm_button.clicked.connect(() => inner.remove_tab(inner.current)); + tabs.notify["selected"].connect((pspec) => rm_button.sensitive = tabs.selected != null); + actionbar.add(rm_button); + + tabsGrid.add(tabs); + tabsGrid.add(actionbar); + + connect_events(); + } + + private void connect_events() { + inner.tab_added.connect(tab => { + var row = new SourceList.Item(); + tab.bind_property("label", row, "name", BindingFlags.SYNC_CREATE); + tab.bind_property("icon", row, "icon", BindingFlags.SYNC_CREATE); + WebTab webtab = tab as WebTab; + if (webtab != null) webtab.web.bind_property("uri", row, "tooltip", BindingFlags.SYNC_CREATE); + + tab_map[tab] = row; + tabs.root.add(row); + }); + inner.tab_removed.connect(tab => { + SourceList.Item item; + if (tab_map.unset(tab, out item)) item.parent.remove(item); + }); + // Can't as trivially handle inner.tab_reordered()... Not as important. + inner.tab_switched.connect(tab => { + Idle.add(() => { + tabs.selected = tab_map[inner.current]; + return Source.REMOVE; + }); + }); + + tabs.item_selected.connect(row => { + foreach (var entry in tab_map.entries) { + if (entry.@value == row) inner.current = entry.key; + } + }); + notify["position"].connect(pspec => { + inner.tab_bar_behavior = position == 0 ? + DynamicNotebook.TabBarBehavior.ALWAYS : DynamicNotebook.TabBarBehavior.NEVER; + }); + } +} diff --git a/src/meson.build b/src/meson.build index a561d2d..9c02b3c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -7,7 +7,7 @@ liberate = ['Liberate', 'Reader'] tokenized = ['AutomaticScrollBox', 'Completer', 'tokenized'] sources = ['Odysseus', 'BrowserWindow', 'Persistance'] -widgets = ['WebTab','ProgressBin','WebNotebook','DownloadButton','DownloadBar', 'Chromeless'] +widgets = ['WebTab','ProgressBin','WebNotebook','DownloadButton','DownloadBar', 'Chromeless', 'VerticalTabs'] header_widgets = ['AddressBar2', 'ButtonWithMenu', 'HeaderBarWithMenus'] overlay_widgets = ['FindToolbar', 'InfoContainer', 'Bookmarker'] traits = ['init', 'download-progress', 'download-window']