using GLib; using Gtk; using System; using System.Collections; class CustomNotebookTest { static CustomNotebook cn; static Notebook nb; static int tabCount; public static int Main (string[] args) { Application.Init (); Window win = new Window ("Custom Notebook Test"); win.DeleteEvent += new DeleteEventHandler (OnQuit); VBox box = new VBox (false, 5); Button addButton = new Button ("Add Tab"); //addButton.Clicked += OnAddTab; Button rmButton = new Button ("Remove Tab"); rmButton.Clicked += OnRemoveTab; box.PackStart (addButton, false, false, 0); box.PackStart (rmButton, false, false, 0); HPaned paned = new HPaned (); cn = new CustomNotebook (); cn.BorderWidth = 5; cn.Scrollable = false; Gdk.Pixbuf icon = cn.RenderIcon (Stock.Execute, IconSize.Menu, ""); //for (tabCount = 0; tabCount < 3; tabCount++) cn.AppendPage (new Label ("Custom Notebook"), Stock.Execute, "extended tab" + tabCount); cn.AppendPage (new Label ("Custom Notebook"), Stock.Execute, "tab" + (tabCount + 1)); cn.AppendPage (new Label ("Custom Notebook"), Stock.Execute, "extended tab" + (tabCount + 2)); paned.Pack1 (cn, true, false); nb = new Notebook (); nb.BorderWidth = 5; nb.Scrollable = false; for (tabCount = 0; tabCount < 3; tabCount++) nb.AppendPage (new Label ("Regular Notebook"), new Label ("tab" + tabCount)); paned.Pack2 (nb, true, false); box.PackEnd (paned, true, true, 0); win.Add (box); win.ShowAll (); Application.Run (); return 0; } /*static void OnAddTab (object sender, EventArgs args) { cn.AppendPage (new Label ("Custom Notebook"), Stock.Execute, "tab" + tabCount + 1); cn.ShowAll (); nb.AppendPage (new Label ("Regular Notebook"), new Label ("tab" + tabCount + 1)); nb.ShowAll (); tabCount++; }*/ static void OnRemoveTab (object sender, EventArgs args) { } static void OnQuit (object sender, DeleteEventArgs args) { Application.Quit (); } } class CustomNotebookPage { private Gdk.Rectangle allocation; private Widget child = null; private Gdk.Pixbuf icon = null; private bool ellipsize = false; private string label = null; private Pango.Layout layout = null; private int layoutWidth = -1; private int layoutHeight = -1; private Requisition requisition; private string stockid = null; public Gdk.Rectangle Allocation { get { return allocation; } set { allocation = value; } } public Widget Child { get { return child; } set { child = value; } } public bool Ellipsize { get { return ellipsize; } set { ellipsize = value; } } public Gdk.Pixbuf Icon { get { if (icon == null && StockId != null) { icon = child.RenderIcon (StockId, IconSize.Menu, ""); } return icon; } set { icon = value; } } public string Label { get { return label; } set { label = value; layout = null; layoutWidth = -1; layoutHeight = -1; } } public Pango.Layout Layout { get { if (layout == null && Label != null) { layout = child.CreatePangoLayout (label); layout.GetPixelSize (out layoutWidth, out layoutHeight); } return layout; } } public int LayoutWidth { get { if (Layout != null) return layoutWidth; else return -1; } } public int LayoutHeight { get { if (Layout != null) return layoutHeight; else return -1; } } public Requisition Requisition { get { return requisition; } set { requisition = value; } } public string StockId { get { return stockid; } set { stockid = value; icon = null; } } public CustomNotebookPage (Widget child, string label) { Child = child; Label = label; } public CustomNotebookPage (Widget child, Gdk.Pixbuf icon, string label) { Child = child; Icon = icon; Label = label; } public CustomNotebookPage (Widget child, string stockid, string label) { Child = child; Label = label; StockId = stockid; } } class CustomNotebook : Container { private readonly int tabCurvature = 1; private readonly int tabOverlap = 2; private ArrayList pages = new ArrayList (); private bool closable; private bool scrollable; private PositionType tabPosition; private int tabHBorder; private int tabVBorder; public bool Closable { get { return closable; } set { closable = value; } } private CustomNotebookPage CurrentPage { get { return (CustomNotebookPage)pages[0]; } } public bool Scrollable { get { return scrollable; } set { scrollable = value; } } public Gtk.PositionType TabPosition { get { return tabPosition; } set { switch (value) { case PositionType.Left: case PositionType.Right: Console.WriteLine ("PositionType.Left or Right is not supported by this widget"); break; default: tabPosition = value; break; } } } /*static CustomNotebook () { Container.OverrideForall (CustomNotebook.GType); }*/ public CustomNotebook () : base () { closable = true; scrollable = false; tabPosition = PositionType.Top; tabHBorder = 2; tabVBorder = 2; WidgetFlags |= WidgetFlags.NoWindow; } public void AppendPage (Widget child, string label) { pages.Add (new CustomNotebookPage (child, label)); child.Parent = this; } public void AppendPage (Widget child, string stockid, string label) { pages.Add (new CustomNotebookPage (child, stockid, label)); child.Parent = this; } public void AppendPage (Widget child, Gdk.Pixbuf icon, string label) { pages.Add (new CustomNotebookPage (child, icon, label)); child.Parent = this; } private void EllipsizeLayout (Pango.Layout layout, int width) { if (width <= 0) { layout.SetText (""); return; } int layoutWidth, layoutHeight; layout.GetPixelSize (out layoutWidth, out layoutHeight); if (layoutWidth <= width) return; // Calculate ellipsis width. Pango.Layout ell = layout.Copy (); ell.SetText ("..."); int ellWidth, ellHeight; ell.GetPixelSize (out ellWidth, out ellHeight); if (width < ellWidth) { // Not even ellipsis fits, so hide text. layout.SetText (""); return; } // Shrink total available width by the width of the ellipsis. width -= ellWidth; string text = layout.Text; Console.WriteLine ("layout text = {0}", text); Console.WriteLine ("line count: {0}", layout.LineCount); Pango.LayoutLine line = layout.Lines[0]; //Console.WriteLine ("layout = {0}", line.layout.Text); //Console.WriteLine ("line = {0}", line.Length); int idx = 0, trailing = 0; if (line.XToIndex (width * 1024, out idx, out trailing)) { text = text.Substring (0, idx - 1); text += "..."; layout.SetText (text); } } protected override bool OnExposeEvent (Gdk.EventExpose args) { int x, y, width, height, gapX, gapWidth; int bw = (int)BorderWidth; x = Allocation.X + bw; y = Allocation.Y + bw; width = Allocation.Width - 2 * bw; height = Allocation.Height - 2 * bw; switch (TabPosition) { case PositionType.Top: y += CurrentPage.Allocation.Height; height -= CurrentPage.Allocation.Height; break; case PositionType.Bottom: height -= CurrentPage.Allocation.Height; break; case PositionType.Left: case PositionType.Right: break; } gapX = gapWidth = 0; switch (TabPosition) { case PositionType.Top: case PositionType.Bottom: gapX = CurrentPage.Allocation.X - Allocation.X - bw; gapWidth = CurrentPage.Allocation.Width; break; case PositionType.Left: case PositionType.Right: break; } Style.PaintBoxGap (Style, GdkWindow, StateType.Normal, ShadowType.Out, args.Area, this, "notebook", x, y, width, height, TabPosition, gapX, gapWidth); for (int i = pages.Count - 1; i >= 0; i--) { CustomNotebookPage page = (CustomNotebookPage)pages[i]; Gdk.Rectangle pageAlloc = page.Allocation; StateType state = page == CurrentPage ? StateType.Normal : StateType.Active; Style.PaintExtension (Style, GdkWindow, state, ShadowType.Out, args.Area, this, "tab", pageAlloc.X, pageAlloc.Y, pageAlloc.Width, pageAlloc.Height, PositionType.Bottom); // FIXME: Only add YThickness when TabPosition = Top; y = pageAlloc.Y + Style.YThickness + FocusLineWidth + tabVBorder; height = pageAlloc.Height - Style.YThickness - 2 * (tabVBorder + FocusLineWidth); if (page.Icon != null) { x = pageAlloc.X + (pageAlloc.Width + 1 - page.Icon.Width - page.LayoutWidth) / 2; int iconY = y + (height - page.Icon.Height) / 2; GdkWindow.DrawPixbuf (Style.BackgroundGC (State), page.Icon, 0, 0, x, iconY, page.Icon.Width, page.Icon.Height, Gdk.RgbDither.None, 0, 0); x += page.Icon.Width + 1; } else { x = pageAlloc.X + (pageAlloc.Width - page.LayoutWidth) / 2; } y += (height - page.LayoutHeight) / 2; if (page.Ellipsize) { width = pageAlloc.Width - (page.Icon.Width + 1); Pango.Layout layout = page.Layout; EllipsizeLayout (layout, width); Console.WriteLine ("ellLayout = {0}", layout.Text); Style.PaintLayout (Style, GdkWindow, State, true, args.Area, this, null, x, y, layout); } else { Style.PaintLayout (Style, GdkWindow, State, true, args.Area, this, null, x, y, page.Layout); } } return base.OnExposeEvent (args); } protected override void ForAll (bool include_internals, CallbackInvoker invoker) { foreach (CustomNotebookPage page in pages) { invoker.Invoke (page.Child); } } protected override void OnRealized () { WidgetFlags |= WidgetFlags.Realized; GdkWindow = ParentWindow; Style = Style.Attach (GdkWindow); } protected override void OnSizeAllocated (Gdk.Rectangle allocation) { base.OnSizeAllocated (allocation); if (pages.Count == 0) return; int bw = (int)BorderWidth; Gdk.Rectangle childAlloc; childAlloc.X = allocation.X + bw + Style.XThickness; childAlloc.Y = allocation.Y + bw + Style.YThickness; childAlloc.Width = Math.Max (1, allocation.Width - 2 * bw - 2 * Style.XThickness); childAlloc.Height = Math.Max (1, allocation.Height - 2 * bw - 2 * Style.YThickness); switch (TabPosition) { case PositionType.Top: childAlloc.Y += CurrentPage.Requisition.Height; childAlloc.Height = Math.Max (1, childAlloc.Height - CurrentPage.Requisition.Height); break; case PositionType.Bottom: childAlloc.Height = Math.Max (1, childAlloc.Height - CurrentPage.Requisition.Height); break; case PositionType.Left: case PositionType.Right: break; } foreach (CustomNotebookPage page in pages) { page.Child.SizeAllocate (childAlloc); } // gtk_notebook_pages_allocate. childAlloc.X = allocation.X + bw; childAlloc.Y = allocation.Y + bw; switch (TabPosition) { case PositionType.Top: childAlloc.Height = CurrentPage.Requisition.Height; break; case PositionType.Bottom: childAlloc.Y = (allocation.Y + allocation.Height - CurrentPage.Requisition.Height - bw); childAlloc.Height = CurrentPage.Requisition.Height; break; case PositionType.Left: case PositionType.Right: break; } bool ellipsize = false; int avgWidth = 0; int tabX = childAlloc.X; if (!scrollable) { int tabWidth = 0; foreach (CustomNotebookPage page in pages) { tabWidth += page.Requisition.Width; } Console.WriteLine ("total tabwidth: {0}", tabWidth); Console.WriteLine ("allocated width: {0}", childAlloc.Width); if (tabWidth > childAlloc.Width) { ellipsize = true; avgWidth = childAlloc.Width / pages.Count; tabWidth = childAlloc.Width; Console.WriteLine ("average tabwidth: {0}", avgWidth); int count = pages.Count; foreach (CustomNotebookPage page in pages) { if (page.Requisition.Width <= avgWidth) { count--; tabWidth -= page.Requisition.Width; } } Console.WriteLine ("number of pages exceeding that: {0}", count); Console.WriteLine ("space per page available: {0}", tabWidth / count); // FIXME: check for TabPosition. int maxWidth = tabWidth / count; foreach (CustomNotebookPage page in pages) { Gdk.Rectangle pageAlloc = page.Allocation; pageAlloc.X = tabX; pageAlloc.Y = childAlloc.Y; if (page.Requisition.Width > maxWidth) { pageAlloc.Width = maxWidth + tabOverlap; page.Ellipsize = true; } else { pageAlloc.Width = page.Requisition.Width + tabOverlap; } pageAlloc.Height = childAlloc.Height; tabX += pageAlloc.Width - tabOverlap; if (page != CurrentPage) { pageAlloc.Y += Style.YThickness; pageAlloc.Height -= Style.YThickness; } page.Allocation = pageAlloc; } } } else { switch (TabPosition) { case PositionType.Top: case PositionType.Bottom: foreach (CustomNotebookPage page in pages) { Gdk.Rectangle pageAlloc = page.Allocation; pageAlloc.X = tabX; pageAlloc.Y = childAlloc.Y; pageAlloc.Width = page.Requisition.Width + tabOverlap; pageAlloc.Height = childAlloc.Height; tabX += pageAlloc.Width - tabOverlap; if (page != CurrentPage) { pageAlloc.Y += Style.YThickness; pageAlloc.Height -= Style.YThickness; } page.Allocation = pageAlloc; } break; case PositionType.Left: case PositionType.Right: break; } } } protected override void OnSizeRequested (ref Requisition requisition) { requisition.Width = requisition.Height = 0; foreach (CustomNotebookPage page in pages) { if (!page.Child.Visible) continue; Requisition childReq = page.Child.SizeRequest (); requisition.Width = Math.Max (requisition.Width, childReq.Width); requisition.Height = Math.Max (requisition.Height, childReq.Height); } requisition.Width += 2 * Style.XThickness; requisition.Height += 2 * Style.YThickness; int tabWidth = 0; int tabHeight = 0; int tabMax = 0; int padding; foreach (CustomNotebookPage page in pages) { Requisition pageReq; if (page.Icon != null) { pageReq.Width = page.Icon.Width + page.LayoutWidth + Style.XThickness * 2; pageReq.Height = Math.Max (page.Icon.Height, page.LayoutHeight) + Style.YThickness * 2; } else { pageReq.Width = page.LayoutWidth + Style.XThickness * 2; pageReq.Height = page.LayoutHeight + Style.YThickness * 2; } switch (TabPosition) { case PositionType.Top: case PositionType.Bottom: pageReq.Height += (tabVBorder + FocusLineWidth) * 2; tabHeight = Math.Max (tabHeight, pageReq.Height); tabMax = Math.Max (tabMax, pageReq.Width); break; case PositionType.Left: case PositionType.Right: break; } page.Requisition = pageReq; } switch (TabPosition) { case PositionType.Top: case PositionType.Bottom: padding = 2 * (tabCurvature + FocusLineWidth + tabHBorder) - tabOverlap; tabMax += padding; Requisition pageReq; foreach (CustomNotebookPage page in pages) { pageReq = page.Requisition; pageReq.Width += padding; tabWidth += pageReq.Width; pageReq.Height = tabHeight; page.Requisition = pageReq; } /*if (!Scrollable) requisition.Width = Math.Max (requisition.Width, tabWidth + tabOverlap);*/ requisition.Height += tabHeight; break; case PositionType.Left: case PositionType.Right: break; } requisition.Width += (int)BorderWidth * 2; requisition.Height += (int)BorderWidth * 2; } }