/WebThing.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using Gtk;
using GtkSharp;
using WebKit;

namespace bytex64.WebThing {
    public enum AttachPoint {
        N, E, S, W, Interior
    }

    public class WebThing {
        private Gtk.Window _Window;
        public Gtk.Window Window {
            get { return _Window; }
        }
        public Gtk.Window w {
            get { return _Window; }
        }

        public ScrolledWindow ScrolledWindow {
            get { return Tabs.CurrentPageWidget as ScrolledWindow; }
        }
        public ScrolledWindow sw {
            get { return ScrolledWindow; }
        }

        public WebView WebView {
            get { return (Tabs.CurrentPageWidget as WebThingView).WebView; }
        }
        public WebView wv {
            get { return WebView; }
        }

        private Gtk.Notebook _Tabs;
        public Gtk.Notebook Tabs {
            get { return _Tabs; }
        }

        private Gtk.Table WidgetGrid;
        private Gtk.Alignment InteriorOverlay;

        public PluginManager Plugins;
        public SearchHandler Search;

        [DllImport ("SoupSettings.dll")]
        private static extern void soup_settings();

        // Main setup
        public void Run() {
            Application.Init();

            soup_settings();

            Config.ParseCommandLine();
            Config.Load();
            //Config.DumpOptions();

            // Initialize Window
            _Window = new Gtk.Window("WebThing");
            // The GTK+ docs say not to use this, but the defaults are
            // based on the executable name, which I don't like.
            _Window.SetWmclass("webthing", "WebThing");  
            _Window.Role = "browser";
            {
                int w, h;

                if (Config.Options.ContainsKey("Width"))
                    w = Convert.ToInt32(Config.Options["Width"]);
                else
                    w = 640;

                if (Config.Options.ContainsKey("Height"))
                    h = Convert.ToInt32(Config.Options["Height"]);
                else
                    h = 480;

                _Window.DefaultSize = new Gdk.Size(w, h);
            }
            _Window.Destroyed += delegate { Quit(); };

            // Initialize WidgetGrid
            // The WidgetGrid holds the Tabs (and thus WebKit) and
            // AttachPoint.Interior in the center, and allows widgets to
            // be added on the sides of the browser
            WidgetGrid = new Gtk.Table(3, 3, false);
            _Window.Add(WidgetGrid);

            // Initialize Tabs
            // Tabs are a core feature of WebThing, not a plugin.  By
            // default, they do nothing -- Tab manipulation is left to
            // plugins to define.
            _Tabs = new Gtk.Notebook();
            _Tabs.ShowBorder = false;
            _Tabs.Scrollable = true;
            _Tabs.SwitchPage += Tabs_SwitchPage;
            WidgetGrid.Attach(_Tabs, 1, 2, 1, 2);

            // InteriorOverlay goes over top of the Tabs to contain
            // widgets that hover over the content, like progress, or
            // inline search.  This is not a very solid idea ATM.
            InteriorOverlay = new Gtk.Alignment(1, 0, 0, 0);
            WidgetGrid.Attach(InteriorOverlay, 1, 2, 1, 2);

            _Window.ShowAll();

            // Load Plugins
            Plugins = new PluginManager(this);
            Plugins.Load();

            // Load Search Handlers
            Search = new SearchHandler(this);

            // Create a new, default WebThingView if one has not already
            // been created.
            if (Tabs.NPages == 0)
                OpenTab("http://dominionofawesome.com/");
            WebView.GrabFocus();

            Application.Run();
        }

        public void Quit() {
            Plugins.Deinit();
            Application.Quit();
        }

        // Widget attachment
        public void AttachWidget(Gtk.Widget widget, AttachPoint direction, AttachOptions xoptions, AttachOptions yoptions) {
            switch(direction) {
            case AttachPoint.N:
                WidgetGrid.Attach(widget, 0, 3, 0, 1, xoptions, yoptions, 0, 0);
                break;
            case AttachPoint.E:
                WidgetGrid.Attach(widget, 2, 3, 1, 2, xoptions, yoptions, 0, 0);
                break;
            case AttachPoint.S:
                WidgetGrid.Attach(widget, 0, 3, 2, 3, xoptions, yoptions, 0, 0);
                break;
            case AttachPoint.W:
                WidgetGrid.Attach(widget, 0, 1, 1, 2, xoptions, yoptions, 0, 0);
                break;
            case AttachPoint.Interior:
                InteriorOverlay.Add(widget);
                break;
            }
        }

        public void AttachWidget(Gtk.Widget widget, AttachPoint direction) {
            AttachWidget(widget, direction, 0, 0);
        }

        // Tab management
        public WebThingView NewTab() {
            WebThingView newview = NewWebThingView();
            Plugins.WebViewSetup(newview.WebView);
            return newview;
        }

        private WebThingView NewWebThingView() {
            WebThingView newview = new WebThingView();
            Tabs.AppendPage(newview, new Label("Blank"));
            newview.WebView.TitleChanged += delegate(object o, TitleChangedArgs e) {
                Tabs.SetTabLabelText((Gtk.Widget) newview, e.Title);
                if (newview == Tabs.CurrentPageWidget)
                    _Window.Title = e.Title + " - WebThing";
            };
            newview.Show();
            return newview;
        }

        public void CloseTab() {
            CloseTab(_Tabs.Page);
        }

        public void CloseTab(int tab) {
            if (_Tabs.NPages > 1) {
                try {
                    WebThingView view = _Tabs.GetNthPage(tab) as WebThingView;
                    _Tabs.RemovePage(tab);
                    view.Dispose();
                } catch (ArgumentOutOfRangeException) {
                    Console.WriteLine("Attempted to close tab out of range: {0}", tab);
                }
            }
        }

        private void Tabs_SwitchPage(object o, SwitchPageArgs e) {
            Gtk.Widget page = _Tabs.GetNthPage((int)e.PageNum);
            _Window.Title = _Tabs.GetTabLabelText(page) + " - WebThing";
        }

        // Uri loading
        private string GetUri(string query) {
            Uri u;
            try {
                u = new Uri(query);
                return u.ToString();
            } catch(UriFormatException) {
                try {
                    u = new Uri(String.Format("http://{0}", query));
                    return u.ToString();
                } catch (UriFormatException) {
                    return Search.Transform(query);
                }
            }
        }

        public bool Open(string query) {
            string uri = GetUri(query);
            if (uri == null) return false;
            
            wv.Open(uri);
            return true;
        }

        public bool OpenTab(string query) {
            WebThingView wtv = NewTab();
            string uri = GetUri(query);
            if (uri == null) return false;

            wtv.WebView.Open(uri);
            return true;
        }

        public void Scroll(double x, double y) {
            ScrolledWindow.Hadjustment.Value += x;
            if (ScrolledWindow.Hadjustment.Value > ScrolledWindow.Hadjustment.Upper - ScrolledWindow.Hadjustment.PageSize)
                ScrolledWindow.Hadjustment.Value = ScrolledWindow.Hadjustment.Upper - ScrolledWindow.Hadjustment.PageSize;
            ScrolledWindow.Vadjustment.Value += y;
            if (ScrolledWindow.Vadjustment.Value > ScrolledWindow.Vadjustment.Upper - ScrolledWindow.Vadjustment.PageSize)
                ScrolledWindow.Vadjustment.Value = ScrolledWindow.Vadjustment.Upper - ScrolledWindow.Vadjustment.PageSize;
        }

        public void Bump(int x, int y) {
            Scroll(x * ScrolledWindow.Hadjustment.StepIncrement, y * ScrolledWindow.Vadjustment.StepIncrement);
        }
    }
}