diff options
110 files changed, 3803 insertions, 1248 deletions
@@ -13,3 +13,5 @@ obj test-results .nuget/ packages/ +.vs/ + diff --git a/TestApps/Gtk3Test/Gtk3Test.csproj b/TestApps/Gtk3Test/Gtk3Test.csproj index 243c6390..3267e869 100644 --- a/TestApps/Gtk3Test/Gtk3Test.csproj +++ b/TestApps/Gtk3Test/Gtk3Test.csproj @@ -17,6 +17,7 @@ <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <ConsolePause>False</ConsolePause> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -26,6 +27,7 @@ <WarningLevel>4</WarningLevel> <ConsolePause>False</ConsolePause> <DebugSymbols>true</DebugSymbols> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> <DebugSymbols>True</DebugSymbols> @@ -38,6 +40,7 @@ <CodeAnalysisIgnoreBuiltInRules>false</CodeAnalysisIgnoreBuiltInRules> <WarningLevel>4</WarningLevel> <Optimize>False</Optimize> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> <OutputPath>bin\x86\Release\</OutputPath> @@ -51,6 +54,7 @@ <Optimize>true</Optimize> <DebugSymbols>true</DebugSymbols> <ConsolePause>false</ConsolePause> + <LangVersion>6</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System" /> diff --git a/TestApps/GtkOnMacTest/GtkOnMacTest.csproj b/TestApps/GtkOnMacTest/GtkOnMacTest.csproj index c700e379..7972aa33 100644 --- a/TestApps/GtkOnMacTest/GtkOnMacTest.csproj +++ b/TestApps/GtkOnMacTest/GtkOnMacTest.csproj @@ -18,6 +18,7 @@ <WarningLevel>4</WarningLevel> <ConsolePause>false</ConsolePause> <PlatformTarget>x64</PlatformTarget> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -27,6 +28,7 @@ <WarningLevel>4</WarningLevel> <ConsolePause>false</ConsolePause> <DebugSymbols>true</DebugSymbols> + <LangVersion>6</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System" /> diff --git a/TestApps/GtkOnWindowsTest/GtkOnWindowsTest.csproj b/TestApps/GtkOnWindowsTest/GtkOnWindowsTest.csproj index 359c80d8..b4655bcd 100644 --- a/TestApps/GtkOnWindowsTest/GtkOnWindowsTest.csproj +++ b/TestApps/GtkOnWindowsTest/GtkOnWindowsTest.csproj @@ -18,6 +18,7 @@ <WarningLevel>4</WarningLevel> <PlatformTarget>x86</PlatformTarget> <ConsolePause>false</ConsolePause> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> <DebugType>pdbonly</DebugType> @@ -28,6 +29,7 @@ <PlatformTarget>x86</PlatformTarget> <ConsolePause>false</ConsolePause> <DebugSymbols>true</DebugSymbols> + <LangVersion>6</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System" /> diff --git a/TestApps/GtkTest/GtkTest.csproj b/TestApps/GtkTest/GtkTest.csproj index 67964760..0feba48a 100644 --- a/TestApps/GtkTest/GtkTest.csproj +++ b/TestApps/GtkTest/GtkTest.csproj @@ -17,6 +17,7 @@ <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <ConsolePause>False</ConsolePause> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -26,6 +27,7 @@ <WarningLevel>4</WarningLevel> <ConsolePause>False</ConsolePause> <DebugSymbols>true</DebugSymbols> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> <DebugSymbols>True</DebugSymbols> @@ -38,6 +40,7 @@ <CodeAnalysisIgnoreBuiltInRules>false</CodeAnalysisIgnoreBuiltInRules> <WarningLevel>4</WarningLevel> <Optimize>False</Optimize> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> <OutputPath>bin\x86\Release\</OutputPath> @@ -51,6 +54,7 @@ <Optimize>true</Optimize> <DebugSymbols>true</DebugSymbols> <ConsolePause>false</ConsolePause> + <LangVersion>6</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System" /> diff --git a/TestApps/MixedGtkMacTest/MixedGtkMacTest.csproj b/TestApps/MixedGtkMacTest/MixedGtkMacTest.csproj index 2e699db6..a9226b6e 100644 --- a/TestApps/MixedGtkMacTest/MixedGtkMacTest.csproj +++ b/TestApps/MixedGtkMacTest/MixedGtkMacTest.csproj @@ -17,6 +17,7 @@ <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <ConsolePause>False</ConsolePause> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -26,6 +27,7 @@ <WarningLevel>4</WarningLevel> <DebugSymbols>true</DebugSymbols> <ConsolePause>false</ConsolePause> + <LangVersion>6</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System" /> diff --git a/TestApps/Samples/MainWindow.cs b/TestApps/Samples/MainWindow.cs index 7523dc47..2c9e873c 100644 --- a/TestApps/Samples/MainWindow.cs +++ b/TestApps/Samples/MainWindow.cs @@ -109,6 +109,8 @@ namespace Samples AddSample (w, "Password Entry", typeof (PasswordEntries)); var treeview = AddSample (w, "TreeView", typeof(TreeViews)); AddSample (treeview, "Cell Bounds", typeof(TreeViewCellBounds)); + AddSample (treeview, "Custom Data Source", typeof (TreeViewCustomStore)); + AddSample (treeview, "Tree View Events", typeof (TreeViewEvents)); AddSample (w, "WebView", typeof(WebViewSample)); var n = AddSample (null, "Drawing", null); diff --git a/TestApps/Samples/Samples.csproj b/TestApps/Samples/Samples.csproj index 3bbe0b5a..01e07561 100644 --- a/TestApps/Samples/Samples.csproj +++ b/TestApps/Samples/Samples.csproj @@ -17,6 +17,7 @@ <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <ConsolePause>False</ConsolePause> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -26,6 +27,7 @@ <WarningLevel>4</WarningLevel> <ConsolePause>False</ConsolePause> <DebugSymbols>true</DebugSymbols> + <LangVersion>6</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System" /> @@ -118,6 +120,8 @@ <Compile Include="Samples\FolderSelectorSample.cs" /> <Compile Include="Samples\ListViewCombos.cs" /> <Compile Include="Samples\PopupWindows.cs" /> + <Compile Include="Samples\TreeViewCustomStore.cs" /> + <Compile Include="Samples\TreeViewEvents.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <ItemGroup> @@ -197,6 +201,30 @@ <Link>icons\zoom-in-16~sel%402x.png</Link> <LogicalName>zoom-in-16~sel@2x.png</LogicalName> </EmbeddedResource> + <EmbeddedResource Include="icons\add-simple-16.png"> + <LogicalName>add-simple-16.png</LogicalName> + </EmbeddedResource> + <EmbeddedResource Include="icons\add-simple-16%402x.png"> + <LogicalName>add-simple-16@2x.png</LogicalName> + </EmbeddedResource> + <EmbeddedResource Include="icons\remove-simple-16.png"> + <LogicalName>remove-simple-16.png</LogicalName> + </EmbeddedResource> + <EmbeddedResource Include="icons\remove-simple-16%402x.png"> + <LogicalName>remove-simple-16@2x.png</LogicalName> + </EmbeddedResource> + <EmbeddedResource Include="icons\remove-simple-disabled-16.png"> + <LogicalName>remove-simple-disabled-16.png</LogicalName> + </EmbeddedResource> + <EmbeddedResource Include="icons\remove-simple-disabled-16%402x.png"> + <LogicalName>remove-simple-disabled-16@2x.png</LogicalName> + </EmbeddedResource> + <EmbeddedResource Include="icons\add-simple-disabled-16.png"> + <LogicalName>add-simple-disabled-16.png</LogicalName> + </EmbeddedResource> + <EmbeddedResource Include="icons\add-simple-disabled-16%402x.png"> + <LogicalName>add-simple-disabled-16@2x.png</LogicalName> + </EmbeddedResource> </ItemGroup> <ProjectExtensions> <MonoDevelop> diff --git a/TestApps/Samples/Samples/ListView1.cs b/TestApps/Samples/Samples/ListView1.cs index 646cfdf8..808e554d 100644 --- a/TestApps/Samples/Samples/ListView1.cs +++ b/TestApps/Samples/Samples/ListView1.cs @@ -172,10 +172,12 @@ namespace Samples protected override void OnMouseMoved (MouseMovedEventArgs args) { - var data = GetValue (ValueField); - data.Value = (int) (100 * ((args.X - Bounds.X) / Bounds.Width)); - data.YPos = args.Y - Bounds.Y; - QueueDraw (); + if (Bounds.Contains (args.Position)) { + var data = GetValue (ValueField); + data.Value = Math.Min (100, (int)(100 * ((args.X - Bounds.X) / Bounds.Width))); + data.YPos = args.Y - Bounds.Y; + QueueDraw (); + } base.OnMouseMoved (args); } diff --git a/TestApps/Samples/Samples/TreeViewCustomStore.cs b/TestApps/Samples/Samples/TreeViewCustomStore.cs new file mode 100644 index 00000000..2572025b --- /dev/null +++ b/TestApps/Samples/Samples/TreeViewCustomStore.cs @@ -0,0 +1,292 @@ +// +// TreeViewCustomStore.cs +// +// Author: +// Lluis Sanchez <llsan@microsoft.com> +// +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using System.Linq; +using Xwt; +using Xwt.Drawing; + +namespace Samples +{ + public class TreeViewCustomStore: VBox + { + public TreeViewCustomStore () + { + DataNode root = new DataNode ("Root", + new DataNode [] { + new DataNode ("One", new DataNode [] { + new DataNode ("Elephant"), + new DataNode ("Tiger") + }), + new DataNode ("Two", new DataNode [] { + new DataNode ("Monkey"), + new DataNode ("Lion") + }), + } + ); + DataNode root2 = new DataNode ("Second Root", + new DataNode [] { + new DataNode ("One", new DataNode [] { + new DataNode ("Elephant"), + new DataNode ("Tiger") + }), + new DataNode ("Two", new DataNode [] { + new DataNode ("Monkey"), + new DataNode ("Lion") + }), + } + ); + + MyTreeSource source = new MyTreeSource (); + source.Add (root); + source.Add (root2); + source.Add (new InfiniteDataNode ("Infinite")); + + TreeView tree = new TreeView (); + + var col = new ListViewColumn ("Data"); + col.Views.Add (new ImageCellView (source.ImageField)); + col.Views.Add (new TextCellView (source.LabelField)); + tree.Columns.Add (col); + + PackStart (tree, true); + + var hbox = new HBox (); + var add = new Button ("Add nodes"); + var change = new Button ("Change nodes"); + var remove = new Button ("Remove nodes"); + + hbox.PackStart (add); + hbox.PackStart (change); + hbox.PackStart (remove); + PackStart (hbox); + + add.Clicked += delegate { + var node = new DataNode ("New child"); + root.InsertChild (1, node); + tree.SelectRow (node); + }; + + remove.Clicked += delegate { + if (root.Children.Count > 0) + root.RemoveChild (root.Children [root.Children.Count - 1]); + }; + + change.Clicked += delegate { + var sel = (DataNode)tree.SelectedRow; + if (sel != null) + sel.Name = "Modified"; + }; + + tree.DataSource = source; + } + } + + class DataNode: TreePosition + { + protected List<DataNode> children; + public DataNode Parent { get; set; } + public virtual List<DataNode> Children => children; + string name; + + public string Name { + get { + return name; + } + + set { + name = value; + GetEventSink ()?.NotifyNodeChanged (this, -1); + } + } + Image image; + + public Image Image { + get { + return image; + } + + set { + image = value; + GetEventSink ()?.NotifyNodeChanged (this, -1); + } + } + + public IEventSink EventSink { get; set; } + + IEventSink GetEventSink () + { + return EventSink ?? Parent?.GetEventSink (); + } + + public DataNode (string name, params DataNode[] children) + { + Name = name; + if (children.Length > 0) { + this.children = children.ToList (); + foreach (var c in children) + c.Parent = this; + } + } + + public void AddChild (DataNode node) + { + if (children == null) + children = new List<DataNode> (); + Children.Add (node); + node.Parent = this; + GetEventSink ()?.NotifyNodeAdded (node, Children.Count - 1); + } + + public void InsertChild (int index, DataNode node) + { + if (children == null) + children = new List<DataNode> (); + Children.Insert (index, node); + node.Parent = this; + GetEventSink ()?.NotifyNodeAdded (node, index); + } + + public void RemoveChild (DataNode node) + { + if (children != null) { + int i = children.IndexOf (node); + if (i != -1) { + children.RemoveAt (i); + GetEventSink ()?.NotifyNodeRemoved (this, i, node); + } + } + } + + public override string ToString() + { + return "[" + Name + "]"; + } + } + + class InfiniteDataNode: DataNode + { + public InfiniteDataNode (string name, params DataNode [] children) : base (name, children) + { + + } + + public override List<DataNode> Children { + get { + if (children == null) { + AddChild (new InfiniteDataNode ("Child 1")); + AddChild (new InfiniteDataNode ("Child 2")); + } + return base.Children; + } + } + } + + interface IEventSink + { + void NotifyNodeAdded (DataNode node, int childIndex); + void NotifyNodeRemoved (TreePosition parent, int childIndex, TreePosition child); + void NotifyNodeChanged (DataNode node, int childIndex); + } + + class MyTreeSource : ITreeDataSource, IEventSink + { + public DataField<string> LabelField = new DataField<string> (0); + public DataField<Image> ImageField = new DataField<Image> (1); + + List<DataNode> data = new List<DataNode> (); + + public Type [] ColumnTypes => new [] { typeof (string), typeof (Image) }; + + public event EventHandler<TreeNodeEventArgs> NodeInserted; + public event EventHandler<TreeNodeChildEventArgs> NodeDeleted; + public event EventHandler<TreeNodeEventArgs> NodeChanged; + public event EventHandler<TreeNodeOrderEventArgs> NodesReordered; + public event EventHandler Cleared; + + public void Add (DataNode node) + { + data.Add (node); + node.EventSink = this; + } + + public TreePosition GetChild (TreePosition pos, int index) + { + var node = (DataNode)pos; + var list = node != null ? node.Children : data; + if (list == null || index >= list.Count) + return null; + return list [index]; + } + + public int GetChildrenCount (TreePosition pos) + { + var node = (DataNode)pos; + var list = node != null ? node.Children : data; + return list != null ? list.Count : 0; + } + + public TreePosition GetParent (TreePosition pos) + { + var node = (DataNode)pos; + return node?.Parent; + } + + public object GetValue (TreePosition pos, int column) + { + var node = (DataNode)pos; + if (column == 0) + return node.Name; + if (column == 1) + return node.Image; + return null; + } + + public void SetValue (TreePosition pos, int column, object value) + { + var node = (DataNode)pos; + if (column == 0) + node.Name = (string) value; + if (column == 1) + node.Image = (Image) value; + } + + void IEventSink.NotifyNodeAdded (DataNode node, int index) + { + NodeInserted?.Invoke (this, new TreeNodeEventArgs (node, index)); + } + + void IEventSink.NotifyNodeChanged (DataNode node, int index) + { + NodeChanged?.Invoke (this, new TreeNodeEventArgs (node, index)); + } + + void IEventSink.NotifyNodeRemoved (TreePosition parent, int childIndex, TreePosition child) + { + NodeDeleted?.Invoke (this, new TreeNodeChildEventArgs (parent, childIndex, child)); + } + } +} diff --git a/TestApps/Samples/Samples/TreeViewEvents.cs b/TestApps/Samples/Samples/TreeViewEvents.cs new file mode 100644 index 00000000..fb65bfee --- /dev/null +++ b/TestApps/Samples/Samples/TreeViewEvents.cs @@ -0,0 +1,286 @@ +// +// TreeViewEvents.cs +// +// Author: +// Lluis Sanchez <llsan@microsoft.com> +// +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using Xwt; +using Xwt.Drawing; + +namespace Samples +{ + public class TreeViewEvents : VBox + { + DataField<TextNode> nodeField = new DataField<TextNode> (); + + public TreeViewEvents () + { + TreeStore store = new TreeStore (nodeField); + var tree = new TreeView (store); + tree.HeadersVisible = false; + tree.SelectionMode = SelectionMode.None; + tree.AnimationsEnabled = false; + tree.ButtonPressed += (s, e) => + { + // disable internal selection/dragging logic, which might collide with the mouse handling in this example + if (e.Button == PointerButton.Left) + e.Handled = true; + }; + + var col = new ListViewColumn () { Expands = true }; + var cellView = new ExpandableTextCellView (); + cellView.NodeField = nodeField; + col.Views.Add (cellView, true); + tree.Columns.Add (col); + + var node = store.AddNode ().SetValue (nodeField, new TextNode ("Root 1")); + var child = node.AddChild ().SetValue (nodeField, new TextNode ("Very long text. Very long text. Very long text. Very long text. Very long text. Very long text. Very long text. Very long text.")); + node = store.AddNode ().SetValue (nodeField, new TextNode ("Root 2")); + child = node.AddChild ().SetValue (nodeField, new TextNode ("Short text. Short text. Short text.")); + child.AddChild ().SetValue (nodeField, new TextNode ("Very long text. Very long text. Very long text. Very long text. Very long text. Very long text. Very long text. Very long text.")); + + + PackStart (tree, true); + } + } + + class TextNode + { + public TextNode (string text) + { + Text = text; + } + + public string Text { get; set; } + } + + class ExpandableTextCellView : CanvasCellView + { + public IDataField<TextNode> NodeField { get; set; } + + static Image addImage = Image.FromResource ("add-simple-16.png").WithSize (16); + static Image removeImage = Image.FromResource ("remove-simple-16.png").WithSize (16); + static Image addImageDisabled = Image.FromResource ("add-simple-disabled-16.png").WithSize (16); + static Image removeImageDisabled = Image.FromResource ("remove-simple-disabled-16.png").WithSize (16); + + const double MoreLinkSpacing = 3; + + Point pointerPosition; + + class ViewStatus + { + public bool Expanded { get; set; } + public double LastRenderWidth; + public double LastCalculatedHeight; + } + + // This could also be stored in the data store. In this example we keep it in + // an internal dictionary to clearly separate the data model from the view model. + // This is a simple implementation, it doesn't take into account that nodes could + // be removed + Dictionary<TextNode, ViewStatus> viewStatus = new Dictionary<TextNode, ViewStatus> (); + + // Used to track the selection + int selectionStart; + int selectionEnd; + TextNode selectionRow; + bool dragging; + + ViewStatus GetViewStatus (TextNode node) + { + ViewStatus status; + if (!viewStatus.TryGetValue (node, out status)) + status = viewStatus [node] = new ViewStatus (); + return status; + } + + protected override Size OnGetRequiredSize (SizeConstraint widthConstraint) + { + var node = GetValue (NodeField); + var status = GetViewStatus (node); + + TextLayout layout = new TextLayout (); + layout.Text = node.Text; + var textSize = layout.GetSize (); + + var maxWidth = widthConstraint.IsConstrained ? widthConstraint.AvailableSize : status.LastRenderWidth; + + // When in expanded mode, the height of the row depends on the width. Since we don't know the width, + // let's use the last width that was used for rendering. + + if (status.Expanded && maxWidth > 0 && textSize.Width > maxWidth) { + layout.Width = maxWidth - addImage.Width - MoreLinkSpacing; + textSize = layout.GetSize (); + } + + status.LastCalculatedHeight = textSize.Height; + + return new Size (30, textSize.Height); + } + + protected override void OnDraw (Context ctx, Rectangle cellArea) + { + TextLayout layout = new TextLayout (); + var node = GetValue (NodeField); + var status = GetViewStatus (node); + + // Store the width, it will be used for calculating height in OnGetRequiredSize() when in expanded mode. + status.LastRenderWidth = cellArea.Width; + + layout.Text = node.Text; + var textSize = layout.GetSize (); + + // Render the selection + if (selectionRow == node && selectionStart != selectionEnd) + layout.SetBackground (Colors.LightBlue, Math.Min (selectionStart, selectionEnd), Math.Abs (selectionEnd - selectionStart)); + + // Text doesn't fit. We need to render the expand icon + + if (textSize.Width > cellArea.Width) { + layout.Width = Math.Max (1, cellArea.Width - addImage.Width - MoreLinkSpacing); + if (!status.Expanded) + layout.Trimming = TextTrimming.WordElipsis; + else + textSize = layout.GetSize (); // The height may have changed. We need the real height since we check it at the end of the method + + // Draw the text + + ctx.DrawTextLayout (layout, cellArea.X, cellArea.Y); + + // Draw the image + + var imageRect = new Rectangle (cellArea.X + layout.Width + MoreLinkSpacing, cellArea.Y, addImage.Width, addImage.Height); + bool hover = pointerPosition != Point.Zero && imageRect.Contains (pointerPosition); + Image icon; + if (status.Expanded) + icon = hover ? removeImage : removeImageDisabled; + else + icon = hover ? addImage : addImageDisabled; + ctx.DrawImage (icon, imageRect.X, imageRect.Y); + } + else { + ctx.DrawTextLayout (layout, cellArea.X, cellArea.Y); + } + + // If the height required by the text is not the same as what was calculated in OnGetRequiredSize(), it means that + // the required height has changed. In that case call QueueResize(), so that OnGetRequiredSize() is called + // again and the row is properly resized. + + if (status.Expanded && textSize.Height != status.LastCalculatedHeight) + QueueResize (); + } + + void CalcLayout (out TextLayout layout, out Rectangle cellArea, out Rectangle expanderRect) + { + var node = GetValue (NodeField); + var status = GetViewStatus (node); + expanderRect = Rectangle.Zero; + cellArea = Bounds; + layout = new TextLayout (); + layout.Text = node.Text; + var textSize = layout.GetSize (); + if (textSize.Width > cellArea.Width) { + layout.Width = Math.Max (1, cellArea.Width - addImage.Width - MoreLinkSpacing); + if (!status.Expanded) + layout.Trimming = TextTrimming.WordElipsis; + var expanderX = cellArea.Right - addImage.Width; + if (expanderX > 0) + expanderRect = new Rectangle (expanderX, cellArea.Y, addImage.Width, addImage.Height); + } + } + + protected override void OnMouseMoved (MouseMovedEventArgs args) + { + TextLayout layout; + Rectangle cellArea, expanderRect; + CalcLayout (out layout, out cellArea, out expanderRect); + + if (expanderRect != Rectangle.Zero && expanderRect.Contains (args.Position)) { + pointerPosition = args.Position; + QueueDraw (); + } else if (pointerPosition != Point.Zero) { + pointerPosition = Point.Zero; + QueueDraw (); + } + + var layoutSize = layout.GetSize (); + var insideText = new Rectangle (cellArea.TopLeft, layoutSize).Contains (args.Position); + var node = GetValue (NodeField); + + if (dragging && insideText && selectionRow == node) { + var pos = layout.GetIndexFromCoordinates (args.Position.X - cellArea.X, args.Position.Y - cellArea.Y); + if (pos != -1) { + selectionEnd = pos; + QueueDraw (); + } + } else { + ParentWidget.Cursor = insideText ? CursorType.IBeam : CursorType.Arrow; + } + } + + protected override void OnButtonPressed (ButtonEventArgs args) + { + TextLayout layout; + Rectangle cellArea, expanderRect; + CalcLayout (out layout, out cellArea, out expanderRect); + + var node = GetValue (NodeField); + var status = GetViewStatus (node); + + if (expanderRect != Rectangle.Zero && expanderRect.Contains (args.Position)) { + status.Expanded = !status.Expanded; + QueueResize (); + return; + } + + var pos = layout.GetIndexFromCoordinates (args.Position.X - cellArea.X, args.Position.Y - cellArea.Y); + if (pos != -1) { + selectionStart = selectionEnd = pos; + selectionRow = node; + dragging = true; + } else + selectionRow = null; + + QueueDraw (); + + base.OnButtonPressed (args); + } + + protected override void OnButtonReleased (ButtonEventArgs args) + { + if (dragging) { + dragging = false; + QueueDraw (); + } + base.OnButtonReleased (args); + } + + protected override void OnMouseExited () + { + pointerPosition = Point.Zero; + ParentWidget.Cursor = CursorType.Arrow; + base.OnMouseExited (); + } + } +} diff --git a/TestApps/Samples/Samples/TreeViews.cs b/TestApps/Samples/Samples/TreeViews.cs index 67235596..1208a944 100644 --- a/TestApps/Samples/Samples/TreeViews.cs +++ b/TestApps/Samples/Samples/TreeViews.cs @@ -25,6 +25,7 @@ // THE SOFTWARE. using System; using Xwt; +using Xwt.Drawing; namespace Samples { @@ -147,6 +148,8 @@ namespace Samples view.DragStarted += delegate(object sender, DragStartedEventArgs e) { var val = store.GetNavigatorAt (view.SelectedRow).GetValue (text); e.DragOperation.Data.AddValue (val); + var img = Image.FromResource(GetType(), "class.png"); + e.DragOperation.SetDragImage(img, (int)img.Size.Width, (int)img.Size.Height); e.DragOperation.Finished += delegate(object s, DragFinishedEventArgs args) { Console.WriteLine ("D:" + args.DeleteSource); }; @@ -168,6 +171,16 @@ namespace Samples Console.WriteLine("Collapsed: " + val); }; + RadioButtonGroup group = new RadioButtonGroup (); + foreach (SelectionMode mode in Enum.GetValues(typeof (SelectionMode))) {
+ var radio = new RadioButton (mode.ToString ());
+ radio.Group = group;
+ radio.Activated += delegate {
+ view.SelectionMode = mode;
+ };
+ PackStart (radio);
+ } + int addCounter = 0; view.KeyPressed += (sender, e) => { if (e.Key == Key.Insert) { @@ -205,6 +218,12 @@ namespace Samples }; PackStart (removeButton); + Button clearButton = new Button("Clear"); + clearButton.Clicked += delegate (object sender, EventArgs e) { + store.Clear(); + }; + PackStart(clearButton); + var label = new Label (); PackStart (label); diff --git a/TestApps/Samples/icons/add-simple-16.png b/TestApps/Samples/icons/add-simple-16.png Binary files differnew file mode 100644 index 00000000..fc90983f --- /dev/null +++ b/TestApps/Samples/icons/add-simple-16.png diff --git a/TestApps/Samples/icons/add-simple-16@2x.png b/TestApps/Samples/icons/add-simple-16@2x.png Binary files differnew file mode 100644 index 00000000..bd0de7a0 --- /dev/null +++ b/TestApps/Samples/icons/add-simple-16@2x.png diff --git a/TestApps/Samples/icons/add-simple-disabled-16.png b/TestApps/Samples/icons/add-simple-disabled-16.png Binary files differnew file mode 100644 index 00000000..b88861e2 --- /dev/null +++ b/TestApps/Samples/icons/add-simple-disabled-16.png diff --git a/TestApps/Samples/icons/add-simple-disabled-16@2x.png b/TestApps/Samples/icons/add-simple-disabled-16@2x.png Binary files differnew file mode 100644 index 00000000..566ffe5d --- /dev/null +++ b/TestApps/Samples/icons/add-simple-disabled-16@2x.png diff --git a/TestApps/Samples/icons/remove-simple-16.png b/TestApps/Samples/icons/remove-simple-16.png Binary files differnew file mode 100644 index 00000000..96e1253c --- /dev/null +++ b/TestApps/Samples/icons/remove-simple-16.png diff --git a/TestApps/Samples/icons/remove-simple-16@2x.png b/TestApps/Samples/icons/remove-simple-16@2x.png Binary files differnew file mode 100644 index 00000000..2c740fef --- /dev/null +++ b/TestApps/Samples/icons/remove-simple-16@2x.png diff --git a/TestApps/Samples/icons/remove-simple-disabled-16.png b/TestApps/Samples/icons/remove-simple-disabled-16.png Binary files differnew file mode 100644 index 00000000..e37a8bfe --- /dev/null +++ b/TestApps/Samples/icons/remove-simple-disabled-16.png diff --git a/TestApps/Samples/icons/remove-simple-disabled-16@2x.png b/TestApps/Samples/icons/remove-simple-disabled-16@2x.png Binary files differnew file mode 100644 index 00000000..679d0ca8 --- /dev/null +++ b/TestApps/Samples/icons/remove-simple-disabled-16@2x.png diff --git a/TestApps/WpfTest/WpfTest.csproj b/TestApps/WpfTest/WpfTest.csproj index 1a9d21e4..bf9fbd3b 100644 --- a/TestApps/WpfTest/WpfTest.csproj +++ b/TestApps/WpfTest/WpfTest.csproj @@ -23,6 +23,7 @@ <CodeAnalysisFailOnMissingRules>false</CodeAnalysisFailOnMissingRules> <WarningLevel>4</WarningLevel> <Optimize>False</Optimize> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'"> <OutputPath>bin\Release\</OutputPath> @@ -34,6 +35,7 @@ <CodeAnalysisFailOnMissingRules>true</CodeAnalysisFailOnMissingRules> <WarningLevel>4</WarningLevel> <DebugSymbols>true</DebugSymbols> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> <DebugSymbols>True</DebugSymbols> @@ -47,6 +49,7 @@ <CodeAnalysisFailOnMissingRules>false</CodeAnalysisFailOnMissingRules> <WarningLevel>4</WarningLevel> <Optimize>False</Optimize> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> <OutputPath>bin\x86\Release\</OutputPath> @@ -60,6 +63,7 @@ <CodeAnalysisFailOnMissingRules>true</CodeAnalysisFailOnMissingRules> <WarningLevel>4</WarningLevel> <DebugSymbols>true</DebugSymbols> + <LangVersion>6</LangVersion> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\..\Xwt.WPF\Xwt.WPF.csproj"> diff --git a/TestApps/XamMacTest/Info.plist b/TestApps/XamMacTest/Info.plist index deb645ae..b305a41c 100644 --- a/TestApps/XamMacTest/Info.plist +++ b/TestApps/XamMacTest/Info.plist @@ -9,7 +9,7 @@ <key>CFBundleVersion</key> <string>1</string> <key>LSMinimumSystemVersion</key> - <string>10.6</string> + <string>10.7</string> <key>NSPrincipalClass</key> <string>NSApplication</string> </dict> diff --git a/TestApps/XamMacTest/Main.cs b/TestApps/XamMacTest/Main.cs index 49fbbd4f..cd44c1a5 100644 --- a/TestApps/XamMacTest/Main.cs +++ b/TestApps/XamMacTest/Main.cs @@ -7,9 +7,6 @@ namespace MacTest { static void Main (string [] args) { - //FIXME: remove this once mmp summorts xammac - ObjCRuntime.Dlfcn.dlopen ("/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/lib/libxammac.dylib", 0); - App.Run (ToolkitType.XamMac); } } diff --git a/TestApps/XamMacTest/XamMacTest.csproj b/TestApps/XamMacTest/XamMacTest.csproj index e60d3b2e..eb578e3a 100644 --- a/TestApps/XamMacTest/XamMacTest.csproj +++ b/TestApps/XamMacTest/XamMacTest.csproj @@ -4,11 +4,12 @@ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{2B7FF081-FE53-42F7-9D5D-D4B38E548F94}</ProjectGuid> - <ProjectTypeGuids>{948B3504-5B70-4649-8FE4-BDE1FB46EC69};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <ProjectTypeGuids>{A3F8F2AB-B479-4A4A-A458-A89E7DC349F1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <OutputType>Exe</OutputType> <RootNamespace>MacTest</RootNamespace> <AssemblyName>MacTest</AssemblyName> <SuppressXamMacMigration>True</SuppressXamMacMigration> + <UseXamMacFullFramework>true</UseXamMacFullFramework> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>True</DebugSymbols> @@ -26,6 +27,8 @@ <UseSGen>False</UseSGen> <UseRefCounting>false</UseRefCounting> <Profiling>false</Profiling> + <AOTMode>None</AOTMode> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -42,19 +45,17 @@ <UseRefCounting>false</UseRefCounting> <Profiling>false</Profiling> <DebugSymbols>true</DebugSymbols> + <AOTMode>None</AOTMode> + <LangVersion>6</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System" /> <Reference Include="System.Core" /> - <Reference Include="Xamarin.Mac"> - <HintPath>\Library\Frameworks\Xamarin.Mac.framework\Versions\Current\lib\x86_64\full\Xamarin.Mac.dll</HintPath> - </Reference> + <Reference Include="Xamarin.Mac" /> </ItemGroup> <ItemGroup> <Compile Include="Main.cs" /> </ItemGroup> - <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> - <Import Project="$(MSBuildExtensionsPath)\Mono\MonoMac\v0.0\Mono.MonoMac.targets" /> <ItemGroup> <None Include="Info.plist" /> </ItemGroup> @@ -72,13 +73,5 @@ <Name>Xwt.XamMac</Name> </ProjectReference> </ItemGroup> - <ProjectExtensions> - <MonoDevelop> - <Properties> - <Policies> - <DotNetNamingPolicy DirectoryNamespaceAssociation="None" ResourceNamePolicy="FileName" /> - </Policies> - </Properties> - </MonoDevelop> - </ProjectExtensions> + <Import Project="$(MSBuildExtensionsPath)\Xamarin\Mac\Xamarin.Mac.CSharp.targets" /> </Project> diff --git a/Testing/CoreTests/CoreTests.csproj b/Testing/CoreTests/CoreTests.csproj index 99839e81..6b748674 100644 --- a/Testing/CoreTests/CoreTests.csproj +++ b/Testing/CoreTests/CoreTests.csproj @@ -17,12 +17,14 @@ <DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <LangVersion>6</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
diff --git a/Testing/GtkTestRunner.csproj b/Testing/GtkTestRunner.csproj index c9ebd83e..9b13958f 100644 --- a/Testing/GtkTestRunner.csproj +++ b/Testing/GtkTestRunner.csproj @@ -19,6 +19,7 @@ <WarningLevel>4</WarningLevel> <ConsolePause>False</ConsolePause> <PlatformTarget>x86</PlatformTarget> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -29,6 +30,7 @@ <DebugSymbols>true</DebugSymbols> <ConsolePause>false</ConsolePause> <PlatformTarget>x86</PlatformTarget> + <LangVersion>6</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System" /> @@ -111,6 +113,7 @@ <Compile Include="Tests\DialogTests.cs" /> <Compile Include="Tests\ScrollableWidgetTests.cs" /> <Compile Include="GtkTestRunner\GtkInit.cs" /> + <Compile Include="GtkTests\PangoTests.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <ItemGroup> diff --git a/Testing/GtkTests/PangoTests.cs b/Testing/GtkTests/PangoTests.cs new file mode 100644 index 00000000..d2e4d575 --- /dev/null +++ b/Testing/GtkTests/PangoTests.cs @@ -0,0 +1,87 @@ +// +// PangoTests.cs +// +// Author: +// Vsevolod Kukol <sevoku@microsoft.com> +// +// Copyright (c) 2017 (c) Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using NUnit.Framework; +using Xwt.GtkBackend; + +namespace Xwt +{ + [TestFixture] + public class PangoUtilTests + { + [Test] + public void TextIndexerAscii() + { + var str = "Test"; + var indexer = new TextIndexer(str); + + Assert.AreEqual(0, indexer.IndexToByteIndex(0)); + Assert.AreEqual(0, indexer.ByteIndexToIndex(0)); + Assert.AreEqual(1, indexer.IndexToByteIndex(1)); + Assert.AreEqual(1, indexer.ByteIndexToIndex(1)); + Assert.AreEqual(2, indexer.IndexToByteIndex(2)); + Assert.AreEqual(2, indexer.ByteIndexToIndex(2)); + Assert.AreEqual(3, indexer.IndexToByteIndex(3)); + Assert.AreEqual(3, indexer.ByteIndexToIndex(3)); + } + + [Test] + public void TextIndexerUnicode() + { + var str = "バージョン"; + var indexer = new TextIndexer(str); + + Assert.AreEqual(0, indexer.IndexToByteIndex(0)); + Assert.AreEqual(3, indexer.IndexToByteIndex(1)); + Assert.AreEqual(6, indexer.IndexToByteIndex(2)); + + Assert.AreEqual(0, indexer.ByteIndexToIndex(0)); + Assert.AreEqual(0, indexer.ByteIndexToIndex(1)); + Assert.AreEqual(0, indexer.ByteIndexToIndex(2)); + Assert.AreEqual(1, indexer.ByteIndexToIndex(3)); + Assert.AreEqual(1, indexer.ByteIndexToIndex(4)); + Assert.AreEqual(1, indexer.ByteIndexToIndex(5)); + } + + [Test] + public void TextIndexerMixed() + { + var str = "バAジョン"; + var indexer = new TextIndexer(str); + + Assert.AreEqual(0, indexer.IndexToByteIndex(0)); + Assert.AreEqual(3, indexer.IndexToByteIndex(1)); + Assert.AreEqual(4, indexer.IndexToByteIndex(2)); + + Assert.AreEqual(0, indexer.ByteIndexToIndex(0)); + Assert.AreEqual(0, indexer.ByteIndexToIndex(1)); + Assert.AreEqual(0, indexer.ByteIndexToIndex(2)); + Assert.AreEqual(1, indexer.ByteIndexToIndex(3)); + Assert.AreEqual(2, indexer.ByteIndexToIndex(4)); + Assert.AreEqual(2, indexer.ByteIndexToIndex(5)); + } + } +} + diff --git a/Testing/MacTestRunner.csproj b/Testing/MacTestRunner.csproj index 6e40b9b9..e25404e2 100644 --- a/Testing/MacTestRunner.csproj +++ b/Testing/MacTestRunner.csproj @@ -29,6 +29,10 @@ <CreatePackage>False</CreatePackage> <UseRefCounting>false</UseRefCounting> <Profiling>false</Profiling> + <HttpClientHandler>HttpClientHandler</HttpClientHandler> + <LinkMode>None</LinkMode> + <AOTMode>None</AOTMode> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -47,6 +51,9 @@ <UseRefCounting>false</UseRefCounting> <Profiling>false</Profiling> <DebugSymbols>true</DebugSymbols> + <HttpClientHandler>HttpClientHandler</HttpClientHandler> + <AOTMode>None</AOTMode> + <LangVersion>6</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System" /> diff --git a/Testing/WpfTestRunner.csproj b/Testing/WpfTestRunner.csproj index 24f6d6e4..83f1b395 100644 --- a/Testing/WpfTestRunner.csproj +++ b/Testing/WpfTestRunner.csproj @@ -21,6 +21,7 @@ <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> <PlatformTarget>x86</PlatformTarget> @@ -31,6 +32,7 @@ <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <DebugSymbols>true</DebugSymbols> + <LangVersion>6</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="nunit-console-runner"> diff --git a/Xwt.Gtk.Mac/Xwt.Gtk.Mac.csproj b/Xwt.Gtk.Mac/Xwt.Gtk.Mac.csproj index a31fd49d..7635fe3a 100644 --- a/Xwt.Gtk.Mac/Xwt.Gtk.Mac.csproj +++ b/Xwt.Gtk.Mac/Xwt.Gtk.Mac.csproj @@ -21,6 +21,7 @@ <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <ConsolePause>false</ConsolePause> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -30,6 +31,7 @@ <WarningLevel>4</WarningLevel> <ConsolePause>false</ConsolePause> <DebugSymbols>true</DebugSymbols> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup> <XamMacPath>\Library\Frameworks\Xamarin.Mac.framework\Versions\Current\lib\x86_64\full\Xamarin.Mac.dll</XamMacPath> diff --git a/Xwt.Gtk.Windows/Xwt.Gtk.Windows.csproj b/Xwt.Gtk.Windows/Xwt.Gtk.Windows.csproj index 3f3a2246..a5ce1ccb 100644 --- a/Xwt.Gtk.Windows/Xwt.Gtk.Windows.csproj +++ b/Xwt.Gtk.Windows/Xwt.Gtk.Windows.csproj @@ -19,6 +19,7 @@ <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <ConsolePause>false</ConsolePause> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -28,6 +29,7 @@ <WarningLevel>4</WarningLevel> <ConsolePause>false</ConsolePause> <DebugSymbols>true</DebugSymbols> + <LangVersion>6</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="gdk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f, processorArchitecture=MSIL" /> diff --git a/Xwt.Gtk/Xwt.Gtk.csproj b/Xwt.Gtk/Xwt.Gtk.csproj index a4c35f3a..e090656d 100644 --- a/Xwt.Gtk/Xwt.Gtk.csproj +++ b/Xwt.Gtk/Xwt.Gtk.csproj @@ -23,6 +23,7 @@ <ConsolePause>False</ConsolePause> <AllowUnsafeBlocks>True</AllowUnsafeBlocks> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -34,6 +35,7 @@ <AllowUnsafeBlocks>True</AllowUnsafeBlocks> <DebugSymbols>true</DebugSymbols> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <LangVersion>6</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System" /> diff --git a/Xwt.Gtk/Xwt.Gtk3.csproj b/Xwt.Gtk/Xwt.Gtk3.csproj index 40fbee79..6801595c 100644 --- a/Xwt.Gtk/Xwt.Gtk3.csproj +++ b/Xwt.Gtk/Xwt.Gtk3.csproj @@ -21,6 +21,7 @@ <WarningLevel>4</WarningLevel> <ConsolePause>False</ConsolePause> <AllowUnsafeBlocks>True</AllowUnsafeBlocks> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -32,6 +33,7 @@ <AllowUnsafeBlocks>True</AllowUnsafeBlocks> <DefineConstants>XWT_GTK3</DefineConstants> <DebugSymbols>true</DebugSymbols> + <LangVersion>6</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System" /> diff --git a/Xwt.Gtk/Xwt.GtkBackend.CellViews/CellUtil.cs b/Xwt.Gtk/Xwt.GtkBackend.CellViews/CellUtil.cs index 2b6067cd..1bcf6911 100644 --- a/Xwt.Gtk/Xwt.GtkBackend.CellViews/CellUtil.cs +++ b/Xwt.Gtk/Xwt.GtkBackend.CellViews/CellUtil.cs @@ -158,6 +158,7 @@ namespace Xwt.GtkBackend Gtk.Widget EventRootWidget { get; } bool GetCellPosition (Gtk.CellRenderer r, int ex, int ey, out int cx, out int cy, out Gtk.TreeIter iter); void QueueDraw (object target, Gtk.TreeIter iter); + void QueueResize (object target, Gtk.TreeIter iter); TreeModel Model { get; } Gtk.TreeIter PressedIter { get; set; } CellViewBackend PressedCell { get; set; } diff --git a/Xwt.Gtk/Xwt.GtkBackend.CellViews/CellViewBackend.cs b/Xwt.Gtk/Xwt.GtkBackend.CellViews/CellViewBackend.cs index 62808a41..87fe116b 100644 --- a/Xwt.Gtk/Xwt.GtkBackend.CellViews/CellViewBackend.cs +++ b/Xwt.Gtk/Xwt.GtkBackend.CellViews/CellViewBackend.cs @@ -190,10 +190,11 @@ namespace Xwt.GtkBackend if (!buttonReleaseSubscribed) rendererTarget.EventRootWidget.ButtonReleaseEvent -= HandleButtonReleaseEvent; - var rect = rendererTarget.GetCellBounds (target, CellRenderer, iter); + var rect = rendererTarget.GetCellBackgroundBounds (target, CellRenderer, iter); if (captured || rect.Contains (cx, cy)) { ApplicationContext.InvokeUserCode (delegate { LoadData (rendererTarget.Model, iter); + SetCurrentEventRow (); var a = new ButtonEventArgs { X = args.Event.X, Y = args.Event.Y, @@ -212,7 +213,7 @@ namespace Xwt.GtkBackend int cx, cy; Gtk.TreeIter iter; if (rendererTarget.GetCellPosition (CellRenderer, (int)args.Event.X, (int)args.Event.Y, out cx, out cy, out iter)) { - var rect = rendererTarget.GetCellBounds (target, CellRenderer, iter); + var rect = rendererTarget.GetCellBackgroundBounds (target, CellRenderer, iter); if (rect.Contains (cx, cy)) { rendererTarget.PressedIter = iter; rendererTarget.PressedCell = this; @@ -226,6 +227,7 @@ namespace Xwt.GtkBackend } ApplicationContext.InvokeUserCode (delegate { LoadData (rendererTarget.Model, iter); + SetCurrentEventRow (); var a = new ButtonEventArgs { X = args.Event.X, Y = args.Event.Y, @@ -246,13 +248,14 @@ namespace Xwt.GtkBackend int cx, cy; Gtk.TreeIter iter; if (rendererTarget.GetCellPosition (CellRenderer, (int)args.Event.X, (int)args.Event.Y, out cx, out cy, out iter)) { - var rect = rendererTarget.GetCellBounds (target, CellRenderer, iter); + var rect = rendererTarget.GetCellBackgroundBounds (target, CellRenderer, iter); if (rect.Contains (cx, cy)) { if (enabledEvents.HasFlag (WidgetEvent.MouseMoved)) ApplicationContext.InvokeUserCode (delegate { LoadData (rendererTarget.Model, iter); - EventSink.OnMouseMoved (new MouseMovedEventArgs (args.Event.Time, cx, cy)); + SetCurrentEventRow (); + EventSink.OnMouseMoved (new MouseMovedEventArgs (args.Event.Time, cx, cy)); }); if (!mouseInsideCell) { @@ -260,6 +263,7 @@ namespace Xwt.GtkBackend if (enabledEvents.HasFlag (WidgetEvent.MouseEntered)) ApplicationContext.InvokeUserCode (delegate { LoadData (rendererTarget.Model, iter); + SetCurrentEventRow (); EventSink.OnMouseEntered (); }); } @@ -268,6 +272,7 @@ namespace Xwt.GtkBackend if (enabledEvents.HasFlag (WidgetEvent.MouseExited)) ApplicationContext.InvokeUserCode (delegate { LoadData (rendererTarget.Model, iter); + SetCurrentEventRow (); EventSink.OnMouseExited (); }); } @@ -361,6 +366,11 @@ namespace Xwt.GtkBackend { rendererTarget.QueueDraw (target, CurrentIter); } + + public void QueueResize () + { + rendererTarget.QueueResize (target, CurrentIter); + } } } diff --git a/Xwt.Gtk/Xwt.GtkBackend.CellViews/CustomCellRenderer.cs b/Xwt.Gtk/Xwt.GtkBackend.CellViews/CustomCellRenderer.cs index 2304625c..22670f38 100644 --- a/Xwt.Gtk/Xwt.GtkBackend.CellViews/CustomCellRenderer.cs +++ b/Xwt.Gtk/Xwt.GtkBackend.CellViews/CustomCellRenderer.cs @@ -38,7 +38,8 @@ namespace Xwt.GtkBackend bool isSelected; bool hasFocus; bool isPrelit; - bool isDrawing; + TreeIter lastIter; + bool shown; public override void Initialize (ICellViewFrontend cellView, ICellRendererTarget rendererTarget, object target) { @@ -57,17 +58,30 @@ namespace Xwt.GtkBackend hasFocus = (flags & CellRendererState.Focused) != 0; isPrelit = (flags & CellRendererState.Prelit) != 0; - isDrawing = true; + // Gtk will rerender the cell on every status change, hence we can expect the values + // set here to be valid until the geometry or any other status of the cell and tree changes. + // Setting shown=true ensures that those values are always reused instead if queried + // from the parent tree after the cell has been rendered. + shown = true; + } + + protected override void OnLoadData () + { + if (!CurrentIter.Equals (lastIter)) { + // if the current iter has changed all cached values from the last draw are invalid + shown = false; + lastIter = CurrentIter; + } + base.OnLoadData (); } internal void EndDrawing () { - isDrawing = false; } public override Rectangle CellBounds { get { - if (isDrawing) + if (shown) return cellArea; return base.CellBounds; } @@ -75,7 +89,7 @@ namespace Xwt.GtkBackend public override Rectangle BackgroundBounds { get { - if (isDrawing) + if (shown) return backgroundArea; return base.BackgroundBounds; } @@ -83,7 +97,7 @@ namespace Xwt.GtkBackend public override bool Selected { get { - if (isDrawing) + if (shown) return isSelected; return base.Selected; } @@ -91,7 +105,7 @@ namespace Xwt.GtkBackend public override bool HasFocus { get { - if (isDrawing) + if (shown) return hasFocus; return base.HasFocus; } @@ -99,7 +113,7 @@ namespace Xwt.GtkBackend public bool IsHighlighted { get { - if (isDrawing) + if (shown) return isPrelit; return false; } @@ -113,10 +127,25 @@ namespace Xwt.GtkBackend protected override void OnRender (Cairo.Context cr, Gtk.Widget widget, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, CellRendererState flags) { + int wx, wy, dx = 0, dy = 0; + var tree = widget as Gtk.TreeView; + // Tree coordinates must be converted to widget coordinates, + // otherwise custom cell bounds will have an offset to the parent widget + if (tree != null) { + tree.ConvertBinWindowToWidgetCoords (cell_area.X, cell_area.Y, out wx, out wy); + dx = wx - cell_area.X; + dy = wy - cell_area.Y; + cell_area.X += dx; + background_area.X += dx; + cell_area.Y += dy; + background_area.Y += dy; + } + Parent.StartDrawing (new Rectangle (background_area.X, background_area.Y, background_area.Width, background_area.Height), new Rectangle (cell_area.X, cell_area.Y, cell_area.Width, cell_area.Height), flags); CellView.ApplicationContext.InvokeUserCode (delegate { CairoContextBackend ctx = new CairoContextBackend (Util.GetScaleFactor (widget)); ctx.Context = cr; + ctx.Context.Translate(-dx, -dy); using (ctx) { CellView.Draw (ctx, new Rectangle (cell_area.X, cell_area.Y, cell_area.Width, cell_area.Height)); } @@ -127,8 +156,9 @@ namespace Xwt.GtkBackend protected override void OnGetSize (Gtk.Widget widget, ref Gdk.Rectangle cell_area, out int x_offset, out int y_offset, out int width, out int height) { Size size = new Size (); + var widthConstraint = cell_area.Width > 0 ? SizeConstraint.WithSize(cell_area.Width) : SizeConstraint.Unconstrained; CellView.ApplicationContext.InvokeUserCode (delegate { - size = CellView.GetRequiredSize (); + size = CellView.GetRequiredSize (widthConstraint); }); width = (int) size.Width; height = (int) size.Height; diff --git a/Xwt.Gtk/Xwt.GtkBackend/AccessibleBackend.cs b/Xwt.Gtk/Xwt.GtkBackend/AccessibleBackend.cs index 711cea70..6c68fbd6 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/AccessibleBackend.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/AccessibleBackend.cs @@ -135,7 +135,6 @@ namespace Xwt.GtkBackend } set { if (widget.Accessible is AtkValue) { - GLib.Value val = GLib.Value.Empty; (widget.Accessible as AtkValue)?.SetCurrentValue (new GLib.Value (value)); } else if (widget.Accessible is AtkEditableText) { var atkText = (widget.Accessible as AtkEditableText); diff --git a/Xwt.Gtk/Xwt.GtkBackend/BoxBackendGtk2.cs b/Xwt.Gtk/Xwt.GtkBackend/BoxBackendGtk2.cs index 9f12f4c1..fd4ccb84 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/BoxBackendGtk2.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/BoxBackendGtk2.cs @@ -24,6 +24,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; +using System.Linq; namespace Xwt.GtkBackend { @@ -34,6 +35,13 @@ namespace Xwt.GtkBackend if (!IsReallocating) QueueResize (); } + + protected override void OnSizeRequested (ref Gtk.Requisition requisition) + { + base.OnSizeRequested (ref requisition); + foreach (var c in children.Keys.ToArray ()) + c.SizeRequest (); + } } } diff --git a/Xwt.Gtk/Xwt.GtkBackend/ButtonBackend.cs b/Xwt.Gtk/Xwt.GtkBackend/ButtonBackend.cs index 2c055645..8bb0514b 100755 --- a/Xwt.Gtk/Xwt.GtkBackend/ButtonBackend.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/ButtonBackend.cs @@ -47,8 +47,12 @@ namespace Xwt.GtkBackend { NeedsEventBox = false; Widget = new Gtk.Button (); + Widget.Realized += (o, arg) => + { + if (Widget.IsRealized && Widget.CanDefault) + Widget.GrabDefault(); + }; base.Widget.Show (); - } protected new Gtk.Button Widget { @@ -83,6 +87,11 @@ namespace Xwt.GtkBackend } } + public virtual bool IsDefault { + get { return Widget.CanDefault; } + set { Widget.CanDefault = value; } + } + public override object Font { get { return base.Font; diff --git a/Xwt.Gtk/Xwt.GtkBackend/ComboBoxBackend.cs b/Xwt.Gtk/Xwt.GtkBackend/ComboBoxBackend.cs index dfed802e..1f00511a 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/ComboBoxBackend.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/ComboBoxBackend.cs @@ -184,6 +184,10 @@ namespace Xwt.GtkBackend { } + public void QueueResize (object target, Gtk.TreeIter iter) + { + } + #endregion } } diff --git a/Xwt.Gtk/Xwt.GtkBackend/CustomTreeModel.cs b/Xwt.Gtk/Xwt.GtkBackend/CustomTreeModel.cs index b7e5fdb4..8753ec62 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/CustomTreeModel.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/CustomTreeModel.cs @@ -33,15 +33,25 @@ using TreeModelImplementor = Gtk.ITreeModelImplementor; namespace Xwt.GtkBackend { - public class CustomTreeModel: TreeModelImplementor + public class CustomTreeModel: GLib.Object, TreeModelImplementor { ITreeDataSource source; - Dictionary<GCHandle,TreePosition> nodeHash = new Dictionary<GCHandle,TreePosition> (); - Dictionary<TreePosition,GCHandle> handleHash = new Dictionary<TreePosition,GCHandle> (); + Dictionary<TreePosition,NodeData> handleHash = new Dictionary<TreePosition,NodeData> (); Type[] colTypes; Gtk.TreeModelAdapter adapter; - + int stamp; + + /// <summary> + /// Stores information about the index of a noda and the GCHandle + /// used by the native reference + /// </summary> + class NodeData + { + public GCHandle Handle; + public int Index; + } + public CustomTreeModel (ITreeDataSource source) { this.source = source; @@ -57,55 +67,125 @@ namespace Xwt.GtkBackend public Gtk.TreeModelAdapter Store { get { return adapter; } } - - public IntPtr Handle { - get { - return IntPtr.Zero; - } - } - - Gtk.TreeIter IterFromNode (TreePosition node) + + public Gtk.TreeIter IterFromNode (TreePosition node, int index = -1) { - GCHandle gch; - if (!handleHash.TryGetValue (node, out gch)) { - gch = GCHandle.Alloc (node); - handleHash [node] = gch; - nodeHash [gch] = node; + // The returned iter will have a reference to the TreePosition + // through a GCHandle. At the same time we store a NodeData + // object to a hashtable, which contains the GCHandle and + // the index of the node + + NodeData data; + if (!handleHash.TryGetValue (node, out data)) { + // It is the first time the node is referenced. + // Store it in handleHash. + handleHash [node] = data = new NodeData { + Handle = GCHandle.Alloc (node), + Index = index + }; } + + // If the index of the node changed, update it in its NodeData + if (index != -1 && index != data.Index) + data.Index = index; + Gtk.TreeIter result = Gtk.TreeIter.Zero; - result.UserData = (IntPtr)gch; + result.UserData = (IntPtr)data.Handle; + result.Stamp = stamp; return result; } + + int GetCachedIndex (TreePosition node) + { + // Gets the index of a node, or -1 if that information is not available + NodeData data; + if (handleHash.TryGetValue (node, out data)) + return data.Index; + return -1; + } - TreePosition NodeFromIter (Gtk.TreeIter iter) + void CacheIndex (TreePosition node, int index) { - TreePosition node; - GCHandle gch = (GCHandle)iter.UserData; - nodeHash.TryGetValue (gch, out node); - return node; + // Stores the index of a node + NodeData data; + if (handleHash.TryGetValue (node, out data)) + data.Index = index; } + public bool NodeFromIter (Gtk.TreeIter iter, out TreePosition pos) + { + if (iter.UserData == IntPtr.Zero) { + pos = null; + return true; + } + if (iter.Stamp != stamp) { + // The iter has been invalidated + pos = null; + return false; + } + GCHandle gch = GCHandle.FromIntPtr (iter.UserData); + pos = (TreePosition) gch.Target; + return true; + } + #region TreeModelImplementor implementation void HandleNodesReordered (object sender, TreeNodeOrderEventArgs e) { - Gtk.TreeIter it = IterFromNode (e.Node); - adapter.EmitRowsReordered (GetPath (it), it, e.ChildrenOrder); + // Don't increase the stamp because no nodes have been removed, so all iters are still valid + // Update the cached indexes for all the children that have chanfed + for (int n = 0; n < e.ChildrenOrder.Length; n++) { + if (e.ChildrenOrder [n] != n) { + var child = source.GetChild (e.Node, n); + if (child != null) + CacheIndex (child, n); + } + } + adapter.EmitRowsReordered (GetPath (e.Node), IterFromNode (e.Node), e.ChildrenOrder); } void HandleNodeInserted (object sender, TreeNodeEventArgs e) { - Gtk.TreeIter it = IterFromNode (e.Node); - adapter.EmitRowInserted (GetPath (it), it); + // Don't increase the stamp because no nodes have been removed, so all iters are still valid + // Update the cached indexes + var parent = source.GetParent (e.Node); + var count = source.GetChildrenCount (parent); + for (int n = e.ChildIndex + 1; n < count; n++) { + var child = source.GetChild (parent, n); + if (child != null) + CacheIndex (child, n); + } + var iter = IterFromNode (e.Node, e.ChildIndex); + adapter.EmitRowInserted (GetPath (e.Node), iter); } void HandleNodeDeleted (object sender, TreeNodeChildEventArgs e) { + if (e.Node != null && !handleHash.ContainsKey (e.Node)) + return; + + NodeData data; + if (e.Child != null && handleHash.TryGetValue (e.Child, out data)) { + // Increase the model stamp since the node is gone and there may + // be iters referencing that node. Increasing the stamp will + // invalidate all those nodes + stamp++; + data.Handle.Free (); + handleHash.Remove (e.Child); + + // Update the indexes of the node following the deleted one + var count = source.GetChildrenCount (e.Node); + for (int n = e.ChildIndex; n < count; n++) { + var child = source.GetChild (e.Node, n); + if (child != null) + CacheIndex (child, n); + } + } + if (e.Node == null) { adapter.EmitRowDeleted (new Gtk.TreePath (new int[] { e.ChildIndex })); } else { - Gtk.TreeIter it = IterFromNode (e.Node); - var p = GetPath (it); + var p = GetPath (e.Node); int[] idx = new int [p.Indices.Length + 1]; p.Indices.CopyTo (idx, 0); idx [idx.Length - 1] = e.ChildIndex; @@ -115,8 +195,8 @@ namespace Xwt.GtkBackend void HandleNodeChanged (object sender, TreeNodeEventArgs e) { - Gtk.TreeIter it = IterFromNode (e.Node); - adapter.EmitRowChanged (GetPath (it), it); + if (handleHash.ContainsKey (e.Node)) + adapter.EmitRowChanged (GetPath (e.Node), IterFromNode (e.Node)); } public GLib.GType GetColumnType (int index) @@ -136,13 +216,20 @@ namespace Xwt.GtkBackend if (pos == null) return false; } - iter = IterFromNode (pos); + iter = IterFromNode (pos, indices[indices.Length - 1]); return true; } public Gtk.TreePath GetPath (Gtk.TreeIter iter) { - TreePosition pos = NodeFromIter (iter); + TreePosition pos; + if (NodeFromIter (iter, out pos)) + return GetPath (pos); + return Gtk.TreePath.NewFirst (); + } + + public Gtk.TreePath GetPath (TreePosition pos) + { List<int> idx = new List<int> (); while (pos != null) { var parent = source.GetParent (pos); @@ -155,34 +242,49 @@ namespace Xwt.GtkBackend int GetIndex (TreePosition parent, TreePosition pos) { + var res = GetCachedIndex (pos); + if (res != -1) + return res; + int n = 0; do { var c = source.GetChild (parent, n); if (c == null) return -1; - if (c == pos || c.Equals(pos)) + if (c == pos || c.Equals (pos)) { + CacheIndex (pos, n); return n; + } n++; } while (true); } public void GetValue (Gtk.TreeIter iter, int column, ref GLib.Value value) { - TreePosition pos = NodeFromIter (iter); + TreePosition pos; + if (!NodeFromIter (iter, out pos)) { + value = GLib.Value.Empty; + return; + } var v = source.GetValue (pos, column); - value = new GLib.Value (v); + if (v == null) + value = GLib.Value.Empty; + else + value = new GLib.Value (v); } public bool IterNext (ref Gtk.TreeIter iter) { - TreePosition pos = NodeFromIter (iter); + TreePosition pos; + if (!NodeFromIter (iter, out pos)) + return false; TreePosition parent = source.GetParent (pos); int i = GetIndex (parent, pos); - if (source.GetChildrenCount (parent) >= i) + if (i == -1) return false; pos = source.GetChild (parent, i + 1); if (pos != null) { - iter = IterFromNode (pos); + iter = IterFromNode (pos, i + 1); return true; } else return false; @@ -191,12 +293,14 @@ namespace Xwt.GtkBackend #if XWT_GTK3 public bool IterPrevious (ref Gtk.TreeIter iter) { - TreePosition pos = NodeFromIter (iter); + TreePosition pos; + if (!NodeFromIter (iter, out pos)) + return false; TreePosition parent = source.GetParent (pos); int i = GetIndex (parent, pos); pos = source.GetChild (parent, i - 1); if (pos != null) { - iter = IterFromNode (pos); + iter = IterFromNode (pos, i - 1); return true; } else return false; @@ -205,11 +309,13 @@ namespace Xwt.GtkBackend public bool IterChildren (out Gtk.TreeIter iter, Gtk.TreeIter parent) { - iter = Gtk.TreeIter.Zero; - TreePosition pos = NodeFromIter (parent); + iter = parent; + TreePosition pos; + if (!NodeFromIter (parent, out pos)) + return false; pos = source.GetChild (pos, 0); if (pos != null) { - iter = IterFromNode (pos); + iter = IterFromNode (pos, 0); return true; } else return false; @@ -217,23 +323,29 @@ namespace Xwt.GtkBackend public bool IterHasChild (Gtk.TreeIter iter) { - TreePosition pos = NodeFromIter (iter); + TreePosition pos; + if (!NodeFromIter (iter, out pos)) + return false; return source.GetChildrenCount (pos) != 0; } public int IterNChildren (Gtk.TreeIter iter) { - TreePosition pos = NodeFromIter (iter); + TreePosition pos; + if (!NodeFromIter (iter, out pos)) + return 0; return source.GetChildrenCount (pos); } public bool IterNthChild (out Gtk.TreeIter iter, Gtk.TreeIter parent, int n) { - iter = Gtk.TreeIter.Zero; - TreePosition pos = NodeFromIter (parent); + iter = parent; + TreePosition pos; + if (!NodeFromIter (parent, out pos)) + return false; pos = source.GetChild (pos, n); if (pos != null) { - iter = IterFromNode (pos); + iter = IterFromNode (pos, n); return true; } else return false; @@ -241,9 +353,12 @@ namespace Xwt.GtkBackend public bool IterParent (out Gtk.TreeIter iter, Gtk.TreeIter child) { - iter = Gtk.TreeIter.Zero; - TreePosition pos = NodeFromIter (iter); - pos = source.GetParent (pos); + iter = child; + TreePosition pos; + if (!NodeFromIter (iter, out pos)) + return false; + if (pos != null) + pos = source.GetParent (pos); if (pos != null) { iter = IterFromNode (pos); return true; diff --git a/Xwt.Gtk/Xwt.GtkBackend/GtkInterop.cs b/Xwt.Gtk/Xwt.GtkBackend/GtkInterop.cs index 7aceea0d..edcf3030 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/GtkInterop.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/GtkInterop.cs @@ -228,10 +228,12 @@ namespace Xwt.GtkBackend } } - internal class TextIndexer + public class TextIndexer { - int[] indexToByteIndex; - int[] byteIndexToIndex; + static readonly List<int> emptyList = new List<int> (); + static readonly int [] emptyArray = new int [0]; + int [] indexToByteIndex; + List<int> byteIndexToIndex; public TextIndexer (string text) { @@ -244,7 +246,7 @@ namespace Xwt.GtkBackend // if the index exceeds the byte index range, return the last byte index + 1 // telling pango to span the attribute to the end of the string // this happens if the string contains multibyte characters - return indexToByteIndex[i-1] + 1; + return indexToByteIndex[indexToByteIndex.Length-1] + 1; return indexToByteIndex[i]; } @@ -255,24 +257,27 @@ namespace Xwt.GtkBackend public void SetupTables (string text) { - if (text == null) { - this.indexToByteIndex = new int[0]; - this.byteIndexToIndex = new int[0]; + if (string.IsNullOrEmpty (text)) { + this.indexToByteIndex = emptyArray; + this.byteIndexToIndex = emptyList; return; } - var arr = text.ToCharArray (); int byteIndex = 0; - int[] indexToByteIndex = new int[arr.Length]; - var byteIndexToIndex = new List<int> (); - for (int i = 0; i < arr.Length; i++) { - indexToByteIndex[i] = byteIndex; - byteIndex += System.Text.Encoding.UTF8.GetByteCount (arr, i, 1); - while (byteIndexToIndex.Count < byteIndex) - byteIndexToIndex.Add (i); + int [] indexToByteIndex = new int [text.Length]; + var byteIndexToIndex = new System.Collections.Generic.List<int> (text.Length); + unsafe { + fixed (char* p = text) { + for (int i = 0; i < text.Length; i++) { + indexToByteIndex[i] = byteIndex; + byteIndex += System.Text.Encoding.UTF8.GetByteCount (p + i, 1); + while (byteIndexToIndex.Count < byteIndex) + byteIndexToIndex.Add (i); + } + } } this.indexToByteIndex = indexToByteIndex; - this.byteIndexToIndex = byteIndexToIndex.ToArray (); + this.byteIndexToIndex = byteIndexToIndex; } } } diff --git a/Xwt.Gtk/Xwt.GtkBackend/GtkWorkarounds.cs b/Xwt.Gtk/Xwt.GtkBackend/GtkWorkarounds.cs index 3808b2fa..9247d858 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/GtkWorkarounds.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/GtkWorkarounds.cs @@ -554,11 +554,11 @@ namespace Xwt.GtkBackend //introduced in GTK 2.20 [DllImport (GtkInterop.LIBGDK, CallingConvention = CallingConvention.Cdecl)] - extern static bool gdk_keymap_add_virtual_modifiers (IntPtr keymap, ref Gdk.ModifierType state); + extern static void gdk_keymap_add_virtual_modifiers (IntPtr keymap, ref Gdk.ModifierType state); //Custom patch in Mono Mac w/GTK+ 2.24.8+ [DllImport (GtkInterop.LIBGDK, CallingConvention = CallingConvention.Cdecl)] - extern static bool gdk_quartz_set_fix_modifiers (bool fix); + extern static void gdk_quartz_set_fix_modifiers (bool fix); static Gdk.Keymap keymap = Gdk.Keymap.Default; static Dictionary<ulong,MappedKeys> mappedKeys = new Dictionary<ulong,MappedKeys> (); diff --git a/Xwt.Gtk/Xwt.GtkBackend/MenuItemBackend.cs b/Xwt.Gtk/Xwt.GtkBackend/MenuItemBackend.cs index 960e114e..c7f6ee05 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/MenuItemBackend.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/MenuItemBackend.cs @@ -126,6 +126,15 @@ namespace Xwt.GtkBackend } } + public string TooltipText { + get { + return item.TooltipText; + } + set { + item.TooltipText = value; + } + } + public bool UseMnemonic { get { return label.UseUnderline; } set { label.UseUnderline = value; } diff --git a/Xwt.Gtk/Xwt.GtkBackend/RichTextViewBackend.cs b/Xwt.Gtk/Xwt.GtkBackend/RichTextViewBackend.cs index f85d6b79..88977cbd 100755 --- a/Xwt.Gtk/Xwt.GtkBackend/RichTextViewBackend.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/RichTextViewBackend.cs @@ -584,7 +584,10 @@ namespace Xwt.GtkBackend { if (!IsRealized) return; - var color = (Gdk.Color) StyleGetProperty ("link-color"); + var objColor = StyleGetProperty ("link-color"); + var color = Gdk.Color.Zero; + if (objColor != null) + color = (Gdk.Color) objColor; if (color.Equals (Gdk.Color.Zero)) color = Toolkit.CurrentEngine.Defaults.FallbackLinkColor.ToGtkValue (); if (Buffer != null) diff --git a/Xwt.Gtk/Xwt.GtkBackend/TableViewBackend.cs b/Xwt.Gtk/Xwt.GtkBackend/TableViewBackend.cs index 72de4f73..3e16979e 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/TableViewBackend.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/TableViewBackend.cs @@ -29,6 +29,7 @@ using Xwt.Backends; using Gtk; using System.Collections.Generic; using System.Linq; +using Gdk; #if XWT_GTK3 using TreeModel = Gtk.ITreeModel; #endif @@ -238,6 +239,7 @@ namespace Xwt.GtkBackend public void SetSelectionMode (SelectionMode mode) { switch (mode) { + case SelectionMode.None: Widget.Selection.Mode = Gtk.SelectionMode.None; break; case SelectionMode.Single: Widget.Selection.Mode = Gtk.SelectionMode.Single; break; case SelectionMode.Multiple: Widget.Selection.Mode = Gtk.SelectionMode.Multiple; break; } @@ -463,19 +465,36 @@ namespace Xwt.GtkBackend { Gtk.TreeViewColumn col; Gtk.TreePath path; - int cellx, celly; + int _cellx, _celly; cx = cy = 0; it = Gtk.TreeIter.Zero; - if (!Widget.GetPathAtPos (ex, ey, out path, out col, out cellx, out celly)) + if (!Widget.GetPathAtPos (ex, ey, out path, out col, out _cellx, out _celly)) return false; - if (!Widget.Model.GetIterFromString (out it, path.ToString ())) + if (!Widget.Model.GetIter (out it, path)) return false; - int sp, w; - if (col.CellGetPosition (r, out sp, out w)) { - if (cellx >= sp && cellx < sp + w) { + var cellArea = Widget.GetCellArea (path, col); + var cellx = ex - cellArea.X; + + var renderers = col.GetCellRenderers (); + int i = Array.IndexOf (renderers, r); + + int rendererX, rendererWidth; + if (col.CellGetPosition (r, out rendererX, out rendererWidth)) { + if (i < renderers.Length - 1) { + int nextX, _w; + // The width returned by CellGetPosition is not reliable. Calculate the width + // by getting the position of the next renderer. + if (col.CellGetPosition (renderers [i + 1], out nextX, out _w)) + rendererWidth = nextX - rendererX; + } else { + // Last renderer of the column. Its width is what's left in the cell area. + rendererWidth = cellArea.Width - rendererX; + } + + if (cellx >= rendererX && cellx < rendererX + rendererWidth) { Widget.ConvertBinWindowToWidgetCoords (ex, ey, out cx, out cy); return true; } @@ -492,16 +511,26 @@ namespace Xwt.GtkBackend Widget.QueueDrawArea (x, y, r.Width, r.Height); } + public void QueueResize (object target, Gtk.TreeIter iter) + { + var path = Widget.Model.GetPath (iter); + Widget.Model.EmitRowChanged (path, iter); + } + #endregion } class CustomTreeView: Gtk.TreeView { WidgetBackend backend; - + TreePath delayedSelection; + TreeViewColumn delayedSelectionColumn; + public CustomTreeView (WidgetBackend b) { backend = b; + base.DragBegin += (_, __) => + delayedSelection = null; } static CustomTreeView () @@ -514,6 +543,44 @@ namespace Xwt.GtkBackend GtkWorkarounds.RemoveKeyBindingFromClass (Gtk.TreeView.GType, Gdk.Key.BackSpace, Gdk.ModifierType.None); } + protected override bool OnButtonPressEvent (EventButton evnt) + { + if (Selection.Mode == Gtk.SelectionMode.Multiple) { + // If we are clicking on already selected row, delay the selection until we are certain that + // the user is not starting a DragDrop operation. + // This is needed to allow user to drag multiple selected rows. + TreePath treePath; + TreeViewColumn column; + GetPathAtPos ((int)evnt.X, (int)evnt.Y, out treePath, out column); + + var ctrlShiftMask = (evnt.State & (Gdk.ModifierType.ShiftMask | Gdk.ModifierType.ControlMask | Gdk.ModifierType.Mod2Mask)); + if (treePath != null && evnt.Button == 1 && this.Selection.PathIsSelected (treePath) && this.Selection.CountSelectedRows() > 1 && ctrlShiftMask == 0) { + delayedSelection = treePath; + delayedSelectionColumn = column; + Selection.SelectFunction = (_, __, ___, ____) => false; + var result = false; + try { + result = base.OnButtonPressEvent (evnt); + } finally { + Selection.SelectFunction = (_, __, ___, ____) => true; + } + return result; + } + } + return base.OnButtonPressEvent (evnt); + } + + protected override bool OnButtonReleaseEvent (EventButton evnt) + { + // Now, if mouse hadn't moved, we are certain that this was just a click. Proceed as usual. + if (delayedSelection != null) { + SetCursor (delayedSelection, delayedSelectionColumn, false); + delayedSelection = null; + delayedSelectionColumn = null; + } + return base.OnButtonReleaseEvent (evnt); + } + protected override void OnDragDataDelete (Gdk.DragContext context) { // This method is override to avoid the default implementation @@ -523,4 +590,3 @@ namespace Xwt.GtkBackend } } } - diff --git a/Xwt.Gtk/Xwt.GtkBackend/TextLayoutBackendHandler.cs b/Xwt.Gtk/Xwt.GtkBackend/TextLayoutBackendHandler.cs index 842e94c8..eedfa976 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/TextLayoutBackendHandler.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/TextLayoutBackendHandler.cs @@ -198,7 +198,7 @@ namespace Xwt.GtkBackend { var tl = (PangoBackend) backend; int index, trailing; - tl.Layout.XyToIndex ((int)x, (int)y, out index, out trailing); + tl.Layout.XyToIndex (Pango.Units.FromPixels ((int)x), Pango.Units.FromPixels ((int)y), out index, out trailing); return tl.TextIndexer.ByteIndexToIndex (index); } diff --git a/Xwt.Gtk/Xwt.GtkBackend/TreeStoreBackend.cs b/Xwt.Gtk/Xwt.GtkBackend/TreeStoreBackend.cs index fba2ba64..efdbe50e 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/TreeStoreBackend.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/TreeStoreBackend.cs @@ -71,6 +71,7 @@ namespace Xwt.GtkBackend public event EventHandler<TreeNodeChildEventArgs> NodeDeleted; public event EventHandler<TreeNodeEventArgs> NodeChanged; public event EventHandler<TreeNodeOrderEventArgs> NodesReordered; + public event EventHandler Cleared; IterPos GetIterPos (TreePosition pos) { @@ -86,6 +87,8 @@ namespace Xwt.GtkBackend { version++; Tree.Clear (); + if (Cleared != null) + Cleared (this, EventArgs.Empty); } public TreePosition GetChild (TreePosition pos, int index) @@ -212,7 +215,7 @@ namespace Xwt.GtkBackend IterPos tpos = GetIterPos (pos); Gtk.TreeIter it = tpos.Iter; var delPath = Tree.GetPath (it); - var eventArgs = new TreeNodeChildEventArgs (GetParent (tpos), delPath.Indices[delPath.Indices.Length - 1]); + var eventArgs = new TreeNodeChildEventArgs (GetParent (tpos), delPath.Indices[delPath.Indices.Length - 1], pos); Tree.Remove (ref it); if (NodeDeleted != null) NodeDeleted (this, eventArgs); diff --git a/Xwt.Gtk/Xwt.GtkBackend/TreeViewBackend.cs b/Xwt.Gtk/Xwt.GtkBackend/TreeViewBackend.cs index 6783094c..b43324e3 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/TreeViewBackend.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/TreeViewBackend.cs @@ -34,6 +34,7 @@ namespace Xwt.GtkBackend { Gtk.TreePath autoExpandPath; uint expandTimer; + CustomTreeModel customModel; protected new ITreeViewEventSink EventSink { get { return (ITreeViewEventSink)base.EventSink; } @@ -91,9 +92,9 @@ namespace Xwt.GtkBackend { Gtk.TreeIter it; if (Widget.Model.GetIter (out it, args.Path)) { - CurrentEventRow = new IterPos (-1, it); + CurrentEventRow = GetPositionFromIter (-1, it); ApplicationContext.InvokeUserCode (delegate { - EventSink.OnRowExpanded (new IterPos (-1, it)); + EventSink.OnRowExpanded (GetPositionFromIter (-1, it)); }); } } @@ -102,9 +103,9 @@ namespace Xwt.GtkBackend { Gtk.TreeIter it; if (Widget.Model.GetIter (out it, args.Path)) { - CurrentEventRow = new IterPos (-1, it); + CurrentEventRow = GetPositionFromIter (-1, it); ApplicationContext.InvokeUserCode (delegate { - EventSink.OnRowExpanding (new IterPos (-1, it)); + EventSink.OnRowExpanding (GetPositionFromIter (-1, it)); }); } } @@ -113,9 +114,9 @@ namespace Xwt.GtkBackend { Gtk.TreeIter it; if (Widget.Model.GetIter (out it, args.Path)) { - CurrentEventRow = new IterPos (-1, it); + CurrentEventRow = GetPositionFromIter (-1, it); ApplicationContext.InvokeUserCode (delegate { - EventSink.OnRowCollapsed (new IterPos (-1, it)); + EventSink.OnRowCollapsed (GetPositionFromIter (-1, it)); }); } } @@ -124,9 +125,9 @@ namespace Xwt.GtkBackend { Gtk.TreeIter it; if (Widget.Model.GetIter (out it, args.Path)) { - CurrentEventRow = new IterPos (-1, it); + CurrentEventRow = GetPositionFromIter (-1, it); ApplicationContext.InvokeUserCode (delegate { - EventSink.OnRowCollapsing (new IterPos (-1, it)); + EventSink.OnRowCollapsing (GetPositionFromIter (-1, it)); }); } } @@ -135,9 +136,9 @@ namespace Xwt.GtkBackend { Gtk.TreeIter it; if (Widget.Model.GetIter (out it, args.Path)) { - CurrentEventRow = new IterPos (-1, it); + CurrentEventRow = GetPositionFromIter (-1, it); ApplicationContext.InvokeUserCode (delegate { - EventSink.OnRowActivated (new IterPos (-1, it)); + EventSink.OnRowActivated (GetPositionFromIter (-1, it)); }); } } @@ -195,21 +196,24 @@ namespace Xwt.GtkBackend public void SetSource (ITreeDataSource source, IBackend sourceBackend) { TreeStoreBackend b = sourceBackend as TreeStoreBackend; + customModel = null; if (b == null) { - CustomTreeModel model = new CustomTreeModel (source); - Widget.Model = model.Store; + customModel = new CustomTreeModel (source); + Widget.Model = customModel.Store; } else Widget.Model = b.Store; } + public bool AnimationsEnabled { get; set; } + public TreePosition[] SelectedRows { get { var rows = Widget.Selection.GetSelectedRows (); - IterPos[] sel = new IterPos [rows.Length]; + TreePosition[] sel = new TreePosition [rows.Length]; for (int i = 0; i < rows.Length; i++) { Gtk.TreeIter it; Widget.Model.GetIter (out it, rows[i]); - sel[i] = new IterPos (-1, it); + sel[i] = GetPositionFromIter (-1, it); } return sel; } @@ -223,13 +227,13 @@ namespace Xwt.GtkBackend Gtk.TreeIter it; if (path != null && Widget.Model.GetIter (out it, path)) - return new IterPos (-1, it); + return GetPositionFromIter (-1, it); return null; } set { Gtk.TreePath path = new Gtk.TreePath(new [] { int.MaxValue }); // set invalid path to unfocus if (value != null) - path = Widget.Model.GetPath (((IterPos)value).Iter); + path = Widget.Model.GetPath (GetIterFromPosition (value)); Widget.SetCursor (path, null, false); } } @@ -241,44 +245,53 @@ namespace Xwt.GtkBackend public void SelectRow (TreePosition pos) { - Widget.Selection.SelectIter (((IterPos)pos).Iter); + Widget.Selection.SelectIter (GetIterFromPosition (pos)); } public void UnselectRow (TreePosition pos) { - Widget.Selection.UnselectIter (((IterPos)pos).Iter); + Widget.Selection.UnselectIter (GetIterFromPosition (pos)); } public bool IsRowSelected (TreePosition pos) { - return Widget.Selection.IterIsSelected (((IterPos)pos).Iter); + return Widget.Selection.IterIsSelected (GetIterFromPosition (pos)); } public bool IsRowExpanded (TreePosition pos) { - return Widget.GetRowExpanded (Widget.Model.GetPath (((IterPos)pos).Iter)); + return Widget.GetRowExpanded (Widget.Model.GetPath (GetIterFromPosition (pos))); } public void ExpandRow (TreePosition pos, bool expandedChildren) { - Widget.ExpandRow (Widget.Model.GetPath (((IterPos)pos).Iter), expandedChildren); + Widget.ExpandRow (Widget.Model.GetPath (GetIterFromPosition (pos)), expandedChildren); } public void CollapseRow (TreePosition pos) { - Widget.CollapseRow (Widget.Model.GetPath (((IterPos)pos).Iter)); + Widget.CollapseRow (Widget.Model.GetPath (GetIterFromPosition (pos))); } public void ScrollToRow (TreePosition pos) { - ScrollToRow (((IterPos)pos).Iter); + ScrollToRow (GetIterFromPosition (pos)); } public void ExpandToRow (TreePosition pos) { - Widget.ExpandToPath (Widget.Model.GetPath (((IterPos)pos).Iter)); + Widget.ExpandToPath (Widget.Model.GetPath (GetIterFromPosition (pos))); } - + + public bool BorderVisible { + get { + return ScrolledWindow.ShadowType != Gtk.ShadowType.None; + } + set { + ScrolledWindow.ShadowType = value ? Gtk.ShadowType.In : Gtk.ShadowType.None; + } + } + public bool HeadersVisible { get { return Widget.HeadersVisible; @@ -287,6 +300,15 @@ namespace Xwt.GtkBackend Widget.HeadersVisible = value; } } + + public bool UseAlternatingRowColors { + get { + return Widget.RulesHint; + } + set { + Widget.RulesHint = value; + } + } public bool GetDropTargetRow (double x, double y, out RowDropPosition pos, out TreePosition nodePosition) { @@ -300,7 +322,7 @@ namespace Xwt.GtkBackend Gtk.TreeIter it; Widget.Model.GetIter (out it, path); - nodePosition = new IterPos (-1, it); + nodePosition = GetPositionFromIter (-1, it); switch (tpos) { case Gtk.TreeViewDropPosition.After: pos = RowDropPosition.After; break; case Gtk.TreeViewDropPosition.Before: pos = RowDropPosition.Before; break; @@ -315,7 +337,7 @@ namespace Xwt.GtkBackend if (path != null) { Gtk.TreeIter iter; Widget.Model.GetIter (out iter, path); - return new IterPos (-1, iter); + return GetPositionFromIter (-1, iter); } return null; } @@ -324,7 +346,7 @@ namespace Xwt.GtkBackend { var col = GetCellColumn (cell); var cr = GetCellRenderer (cell); - Gtk.TreeIter iter = ((IterPos)pos).Iter; + Gtk.TreeIter iter = GetIterFromPosition (pos); var rect = includeMargin ? ((ICellRendererTarget)this).GetCellBackgroundBounds (col, cr, iter) : ((ICellRendererTarget)this).GetCellBounds (col, cr, iter); return rect; @@ -332,7 +354,7 @@ namespace Xwt.GtkBackend public Rectangle GetRowBounds (TreePosition pos, bool includeMargin) { - Gtk.TreeIter iter = ((IterPos)pos).Iter; + Gtk.TreeIter iter = GetIterFromPosition (pos); Rectangle rect = includeMargin ? GetRowBackgroundBounds (iter) : GetRowBounds (iter); return rect; } @@ -351,6 +373,23 @@ namespace Xwt.GtkBackend CurrentEventRow = toggledItem; } + + TreePosition GetPositionFromIter (int treeVersion, Gtk.TreeIter iter) + { + if (customModel != null) { + TreePosition pos; + customModel.NodeFromIter (iter, out pos); + return pos; + } + return new IterPos (treeVersion, iter); + } + + Gtk.TreeIter GetIterFromPosition (TreePosition position) + { + if (customModel != null) + return customModel.IterFromNode (position); + return ((IterPos)position).Iter; + } } } diff --git a/Xwt.Gtk/Xwt.GtkBackend/WidgetBackend.cs b/Xwt.Gtk/Xwt.GtkBackend/WidgetBackend.cs index c78649ed..97a8f983 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/WidgetBackend.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/WidgetBackend.cs @@ -192,6 +192,8 @@ namespace Xwt.GtkBackend ctype = Gdk.CursorType.Crosshair; else if (cursor == CursorType.Hand) ctype = Gdk.CursorType.Hand1; + else if (cursor == CursorType.Hand2 || cursor == CursorType.DragCopy) + ctype = Gdk.CursorType.Hand2; else if (cursor == CursorType.IBeam) ctype = Gdk.CursorType.Xterm; else if (cursor == CursorType.ResizeDown) @@ -206,6 +208,16 @@ namespace Xwt.GtkBackend ctype = Gdk.CursorType.SbHDoubleArrow; else if (cursor == CursorType.ResizeUpDown) ctype = Gdk.CursorType.SbVDoubleArrow; + else if (cursor == CursorType.ResizeNE) + ctype = Gdk.CursorType.TopRightCorner; + else if (cursor == CursorType.ResizeNW) + ctype = Gdk.CursorType.TopLeftCorner; + else if (cursor == CursorType.ResizeSE) + ctype = Gdk.CursorType.BottomRightCorner; + else if (cursor == CursorType.ResizeSW) + ctype = Gdk.CursorType.BottomLeftCorner; + else if (cursor == CursorType.NotAllowed) + ctype = Gdk.CursorType.XCursor; else if (cursor == CursorType.Move) ctype = Gdk.CursorType.Fleur; else if (cursor == CursorType.Wait) diff --git a/Xwt.WPF/Xwt.WPF.csproj b/Xwt.WPF/Xwt.WPF.csproj index 29498b6b..3661be48 100644 --- a/Xwt.WPF/Xwt.WPF.csproj +++ b/Xwt.WPF/Xwt.WPF.csproj @@ -19,6 +19,7 @@ <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>False</ConsolePause>
+ <LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@ <WarningLevel>4</WarningLevel>
<ConsolePause>False</ConsolePause>
<DebugSymbols>true</DebugSymbols>
+ <LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>True</SignAssembly>
diff --git a/Xwt.WPF/Xwt.WPFBackend.CellViews/CanvasCellViewBackend.cs b/Xwt.WPF/Xwt.WPFBackend.CellViews/CanvasCellViewBackend.cs index b6dc8dcb..99339464 100644 --- a/Xwt.WPF/Xwt.WPFBackend.CellViews/CanvasCellViewBackend.cs +++ b/Xwt.WPF/Xwt.WPFBackend.CellViews/CanvasCellViewBackend.cs @@ -14,6 +14,11 @@ namespace Xwt.WPFBackend CurrentElement.InvalidateVisual (); } + public void QueueResize () + { + CurrentElement.InvalidateVisual (); + } + public bool IsHighlighted { get { return false; diff --git a/Xwt.WPF/Xwt.WPFBackend/ButtonBackend.cs b/Xwt.WPF/Xwt.WPFBackend/ButtonBackend.cs index 067c86a5..3f1d1583 100644 --- a/Xwt.WPF/Xwt.WPFBackend/ButtonBackend.cs +++ b/Xwt.WPF/Xwt.WPFBackend/ButtonBackend.cs @@ -135,6 +135,18 @@ namespace Xwt.WPFBackend set { Button.Foreground = ResPool.GetSolidBrush (value.ToWpfColor()); } } + bool isDefault; + public virtual bool IsDefault { + get { return (Button as SWC.Button)?.IsDefault ?? isDefault; } + set { + var button = Button as SWC.Button; + if (button != null) + button.IsDefault = value; + else + isDefault = value; // just cache the value + } + } + public override void EnableEvent (object eventId) { base.EnableEvent (eventId); diff --git a/Xwt.WPF/Xwt.WPFBackend/ExTreeView.cs b/Xwt.WPF/Xwt.WPFBackend/ExTreeView.cs index 2abaa221..3af650e3 100644 --- a/Xwt.WPF/Xwt.WPFBackend/ExTreeView.cs +++ b/Xwt.WPF/Xwt.WPFBackend/ExTreeView.cs @@ -250,7 +250,7 @@ namespace Xwt.WPFBackend HashSet<object> oldItems = (e.OldItems != null) ? new HashSet<object> (e.OldItems.Cast<object> ()) : new HashSet<object> (); - + TraverseTree ((o, ti) => { if (newItems.Remove (o)) ti.IsSelected = true; @@ -260,10 +260,6 @@ namespace Xwt.WPFBackend return (newItems.Count + oldItems.Count > 0); }); } - - if (!changeActive) { - shiftStart = GetTreeItem (SelectedItems [0]); - } break; } @@ -328,8 +324,12 @@ namespace Xwt.WPFBackend foreach (var forEachItem in GetItemsBetween(shiftStart, shiftEnd)) SelectedItems.Remove(forEachItem.DataContext); shiftEnd = item; - foreach (var forEachItem in GetItemsBetween(shiftStart, shiftEnd)) - SelectedItems.Add(forEachItem.DataContext); + if (this.SelectionMode == SWC.SelectionMode.Single) { + SelectedItems.Add(item.DataContext); + } else { + foreach (var forEachItem in GetItemsBetween(shiftStart, shiftEnd)) + SelectedItems.Add(forEachItem.DataContext); + } } else { diff --git a/Xwt.WPF/Xwt.WPFBackend/ExTreeViewItem.cs b/Xwt.WPF/Xwt.WPFBackend/ExTreeViewItem.cs index 3939dedb..4ca5c95e 100644 --- a/Xwt.WPF/Xwt.WPFBackend/ExTreeViewItem.cs +++ b/Xwt.WPF/Xwt.WPFBackend/ExTreeViewItem.cs @@ -35,6 +35,8 @@ using Xwt.Backends; namespace Xwt.WPFBackend { + using Keyboard = System.Windows.Input.Keyboard;
+
public class ExTreeViewItem : TreeViewItem { @@ -125,15 +127,47 @@ namespace Xwt.WPFBackend protected override DependencyObject GetContainerForItemOverride() { return new ExTreeViewItem (this.view); - } - - protected override void OnMouseLeftButtonDown (MouseButtonEventArgs e) { - view.SelectItem(this); - e.Handled = true; - base.OnMouseLeftButtonDown(e); - } - - protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
+ }
+
+ protected override void OnMouseLeftButtonDown (MouseButtonEventArgs e)
+ {
+ var args = e.ToXwtButtonArgs (view.Backend.Widget);
+ view.Backend.Context.InvokeUserCode (delegate () {
+ view.Backend.EventSink.OnButtonPressed (args);
+ });
+ if (args.Handled) {
+ e.Handled = true;
+ return;
+ }
+
+ if (!view.SelectedItems.Contains (this.DataContext) || CtrlPressed)
+ view.SelectItem (this);
+ view.Backend.WidgetMouseDownForDragHandler (this, e); + + e.Handled = true;
+ base.OnMouseLeftButtonDown (e);
+ }
+
+ protected override void OnMouseLeftButtonUp (MouseButtonEventArgs e)
+ {
+ var args = e.ToXwtButtonArgs (view.Backend.Widget);
+ view.Backend.Context.InvokeUserCode (delegate () {
+ view.Backend.EventSink.OnButtonReleased (args);
+ });
+ if (args.Handled) {
+ e.Handled = true;
+ return;
+ }
+
+ if (view.SelectedItems.Contains (this.DataContext) && !CtrlPressed)
+ view.SelectItem (this);
+ view.Backend.WidgetMouseUpForDragHandler(this, e); +
+ e.Handled = true;
+ base.OnMouseLeftButtonUp (e);
+ }
+
+ protected override void OnMouseDoubleClick (MouseButtonEventArgs e)
{ if ((view.Backend as TreeViewBackend)?.RowActivatedEventEnabled == true && IsSelected)
{
@@ -194,6 +228,14 @@ namespace Xwt.WPFBackend { BringIntoView(); //We can't allow TreeViewItem(our base class) to get this message(OnGotFocus) because it will also select this item which we don't want - } + }
+
+ private bool CtrlPressed
+ {
+ get
+ {
+ return Keyboard.IsKeyDown (WKey.RightCtrl) || Keyboard.IsKeyDown (WKey.LeftCtrl);
+ }
+ }
} } diff --git a/Xwt.WPF/Xwt.WPFBackend/ListDataSource.cs b/Xwt.WPF/Xwt.WPFBackend/ListDataSource.cs index 18a98e6b..86a0c32b 100644 --- a/Xwt.WPF/Xwt.WPFBackend/ListDataSource.cs +++ b/Xwt.WPF/Xwt.WPFBackend/ListDataSource.cs @@ -99,7 +99,6 @@ namespace Xwt.WPFBackend if (row == this.rows.Count) return AddRow (); - row--; this.rows.Insert (row, new ValuesContainer (this.columnTypes.Length)); OnRowInserted (new ListRowEventArgs (row)); diff --git a/Xwt.WPF/Xwt.WPFBackend/MenuItemBackend.cs b/Xwt.WPF/Xwt.WPFBackend/MenuItemBackend.cs index 49ffae4f..f8c93877 100644 --- a/Xwt.WPF/Xwt.WPFBackend/MenuItemBackend.cs +++ b/Xwt.WPF/Xwt.WPFBackend/MenuItemBackend.cs @@ -31,6 +31,7 @@ using System.Collections.Generic; using System.Linq;
using System.Text;
using System.Windows;
+using System.Windows.Controls;
using SWC = System.Windows.Controls;
using SWMI = System.Windows.Media.Imaging;
using Xwt.Backends;
@@ -98,6 +99,21 @@ namespace Xwt.WPFBackend }
}
+ public string TooltipText
+ {
+ get { return menuItem.ToolTip == null ? null : ((ToolTip)menuItem.ToolTip).Content.ToString(); }
+ set
+ {
+ var tp = menuItem.ToolTip as ToolTip;
+ if (tp == null)
+ menuItem.ToolTip = tp = new ToolTip();
+ tp.Content = value ?? string.Empty;
+ ToolTipService.SetIsEnabled(menuItem, value != null);
+ if (tp.IsOpen && value == null)
+ tp.IsOpen = false;
+ }
+ }
+
public bool UseMnemonic {
get { return useMnemonic; }
set
diff --git a/Xwt.WPF/Xwt.WPFBackend/TextEntryBackend.cs b/Xwt.WPF/Xwt.WPFBackend/TextEntryBackend.cs index 8cecd6c1..02ec5470 100644 --- a/Xwt.WPF/Xwt.WPFBackend/TextEntryBackend.cs +++ b/Xwt.WPF/Xwt.WPFBackend/TextEntryBackend.cs @@ -90,7 +90,7 @@ namespace Xwt.WPFBackend public bool ReadOnly { get { return TextBox.IsReadOnly; } - set { TextBox.IsReadOnly = true; } + set { TextBox.IsReadOnly = value; } } public bool ShowFrame @@ -235,4 +235,4 @@ namespace Xwt.WPFBackend Context.InvokeUserCode (EventSink.OnSelectionChanged); } } -}
\ No newline at end of file +} diff --git a/Xwt.WPF/Xwt.WPFBackend/TreeStoreBackend.cs b/Xwt.WPF/Xwt.WPFBackend/TreeStoreBackend.cs index d4c5df13..81b12736 100755 --- a/Xwt.WPF/Xwt.WPFBackend/TreeStoreBackend.cs +++ b/Xwt.WPF/Xwt.WPFBackend/TreeStoreBackend.cs @@ -49,6 +49,7 @@ namespace Xwt.WPFBackend public event EventHandler<TreeNodeChildEventArgs> NodeDeleted; public event EventHandler<TreeNodeEventArgs> NodeChanged; public event EventHandler<TreeNodeOrderEventArgs> NodesReordered; + public event EventHandler Cleared; public Type[] ColumnTypes { @@ -181,6 +182,7 @@ namespace Xwt.WPFBackend public void Clear () { this.topNodes.Clear(); + Cleared?.Invoke(this, EventArgs.Empty); } public IEnumerator GetEnumerator () diff --git a/Xwt.WPF/Xwt.WPFBackend/TreeView.xaml b/Xwt.WPF/Xwt.WPFBackend/TreeView.xaml index 84053ccc..72f823db 100644 --- a/Xwt.WPF/Xwt.WPFBackend/TreeView.xaml +++ b/Xwt.WPF/Xwt.WPFBackend/TreeView.xaml @@ -1,4 +1,4 @@ -<ResourceDictionary +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:l="clr-namespace:Xwt.WPFBackend;assembly=Xwt.WPF"> @@ -9,17 +9,17 @@ <Setter.Value> <ControlTemplate TargetType="l:ExTreeViewItem"> <StackPanel> - <Border Name="HBorder" Background="{TemplateBinding Background}" BorderBrush="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" BorderThickness="1" Padding="{TemplateBinding Padding}"> + <Border Name="HBorder" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"> <GridViewRowPresenter x:Name="PART_Header" Content="{TemplateBinding Header}" Columns="{Binding Path=View.Columns, RelativeSource={RelativeSource AncestorType={x:Type l:ExTreeView}}}" /> </Border> <ItemsPresenter x:Name="ItemsHost" /> </StackPanel> - + <ControlTemplate.Triggers> <Trigger Property="IsExpanded" Value="False"> <Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed" /> </Trigger> - + <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="HasHeader" Value="False" /> @@ -27,7 +27,7 @@ </MultiTrigger.Conditions> <Setter TargetName="PART_Header" Property="MinWidth" Value="75" /> </MultiTrigger> - + <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="HasHeader" Value="False" /> @@ -35,36 +35,40 @@ </MultiTrigger.Conditions> <Setter Property="MinHeight" Value="19" /> </MultiTrigger> - - <Trigger Property="IsSelected" Value="True"> - <Setter TargetName="HBorder" Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" /> - <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" /> - </Trigger> - - <MultiTrigger> - <MultiTrigger.Conditions> - <Condition Property="IsSelected" Value="True" /> - <Condition Property="IsSelectionActive" Value="False" /> - </MultiTrigger.Conditions> - <Setter TargetName="HBorder" Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" /> - <Setter TargetName="HBorder" Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" /> - <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" /> - </MultiTrigger> - - <Trigger Property="IsEnabled" Value="False"> - <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" /> - </Trigger> - <MultiTrigger> - <MultiTrigger.Conditions> - <Condition Property="IsSelected" Value="False" /> - <Condition Property="IsFocused" Value="False" /> - </MultiTrigger.Conditions> - <Setter TargetName="HBorder" Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background}" /> - </MultiTrigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> + + <Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" /> + <Setter Property="BorderThickness" Value="1" /> + <Style.Triggers> + <Trigger Property="IsSelected" Value="True"> + <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" /> + <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" /> + </Trigger> + + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="IsSelected" Value="True" /> + <Condition Property="IsSelectionActive" Value="False" /> + </MultiTrigger.Conditions> + <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" /> + <Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" /> + <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" /> + </MultiTrigger> + + <Trigger Property="IsEnabled" Value="False"> + <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" /> + </Trigger> + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="IsSelected" Value="False" /> + <Condition Property="IsFocused" Value="False" /> + </MultiTrigger.Conditions> + <Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource Self}, Path=Background}" /> + </MultiTrigger> + </Style.Triggers> </Style> <Style TargetType="{x:Type l:ExTreeView}"> diff --git a/Xwt.WPF/Xwt.WPFBackend/TreeViewBackend.cs b/Xwt.WPF/Xwt.WPFBackend/TreeViewBackend.cs index 37147670..6ee8e43d 100644 --- a/Xwt.WPF/Xwt.WPFBackend/TreeViewBackend.cs +++ b/Xwt.WPF/Xwt.WPFBackend/TreeViewBackend.cs @@ -1,4 +1,4 @@ -// +// // TreeViewBackend.cs // // Author: @@ -116,6 +116,15 @@ namespace Xwt.WPFBackend } } + public bool BorderVisible { + get { + return Tree.BorderThickness.Left != 0; + } + set { + Tree.BorderThickness = value ? new Thickness (1) : new Thickness (0); + } + } + private bool headersVisible = true; public bool HeadersVisible { get { return this.headersVisible; } @@ -144,6 +153,17 @@ namespace Xwt.WPFBackend } } + public bool UseAlternatingRowColors {
+ get {
+ return Tree.AlternationCount == 2;
+ }
+ set {
+ Tree.AlternationCount = value ? 2 : 0;
+ }
+ } + + public bool AnimationsEnabled { get; set; } + public void SelectRow (TreePosition pos) { Tree.SelectedItems.Add (pos); diff --git a/Xwt.WPF/Xwt.WPFBackend/Util.cs b/Xwt.WPF/Xwt.WPFBackend/Util.cs index a678ba4d..0e9ca565 100644 --- a/Xwt.WPF/Xwt.WPFBackend/Util.cs +++ b/Xwt.WPF/Xwt.WPFBackend/Util.cs @@ -90,6 +90,21 @@ namespace Xwt.WPFBackend return null;
}
+
+ /// <summary>
+ /// Get the the parent System.Windows.Window. If that fails for whatever reason (which can happen if the WPF
+ /// visual tree isn't rooted with a System.Windows.Window, like in the case where it's rooted in a WinForms
+ /// component), then fallback to returning the WPF MainWindow.
+ /// <param name="element">WPF element</param>
+ /// <returns>ancestor System.Windows.Window or MainWindow</returns>
+ public static System.Windows.Window GetParentOrMainWindow (this FrameworkElement element)
+ {
+ System.Windows.Window parentWindow = GetParentWindow (element);
+ if (parentWindow != null)
+ return parentWindow;
+
+ return System.Windows.Application.Current.MainWindow;
+ }
}
class XwtWin32Window : System.Windows.Forms.IWin32Window
diff --git a/Xwt.WPF/Xwt.WPFBackend/WidgetBackend.cs b/Xwt.WPF/Xwt.WPFBackend/WidgetBackend.cs index 3033dea5..39f28bf9 100644 --- a/Xwt.WPF/Xwt.WPFBackend/WidgetBackend.cs +++ b/Xwt.WPF/Xwt.WPFBackend/WidgetBackend.cs @@ -439,7 +439,7 @@ namespace Xwt.WPFBackend Widget.Cursor = Cursors.Arrow;
else if (cursor == CursorType.Crosshair)
Widget.Cursor = Cursors.Cross;
- else if (cursor == CursorType.Hand)
+ else if (cursor == CursorType.Hand || cursor == CursorType.Hand2)
Widget.Cursor = Cursors.Hand;
else if (cursor == CursorType.IBeam)
Widget.Cursor = Cursors.IBeam;
@@ -455,6 +455,10 @@ namespace Xwt.WPFBackend Widget.Cursor = Cursors.SizeWE;
else if (cursor == CursorType.ResizeLeftRight)
widget.Cursor = Cursors.SizeWE;
+ else if (cursor == CursorType.ResizeNE || cursor == CursorType.ResizeSW)
+ widget.Cursor = Cursors.SizeNESW;
+ else if (cursor == CursorType.ResizeNW || cursor == CursorType.ResizeSE)
+ widget.Cursor = Cursors.SizeNWSE;
else if (cursor == CursorType.Move)
widget.Cursor = Cursors.SizeAll;
else if (cursor == CursorType.Wait)
@@ -463,6 +467,8 @@ namespace Xwt.WPFBackend widget.Cursor = Cursors.Help;
else if (cursor == CursorType.Invisible)
widget.Cursor = Cursors.None;
+ else if (cursor == CursorType.NotAllowed)
+ widget.Cursor = Cursors.No;
else
Widget.Cursor = Cursors.Arrow;
}
@@ -645,6 +651,11 @@ namespace Xwt.WPFBackend e.Handled = true;
}
+ internal void WidgetMouseDownForDragHandler(object o, MouseButtonEventArgs e) + { + SetupDragRect(e); + } + void WidgetMouseUpHandler (object o, MouseButtonEventArgs e)
{
var args = e.ToXwtButtonArgs (Widget);
@@ -684,6 +695,11 @@ namespace Xwt.WPFBackend return Widget.GetParentWindow ();
}
+ private SW.Window GetParentOrMainWindow ()
+ {
+ return Widget.GetParentOrMainWindow ();
+ }
+
public void DragStart (DragStartData data)
{
if (data.Data == null)
@@ -692,7 +708,7 @@ namespace Xwt.WPFBackend DataObject dataObj = data.Data.ToDataObject();
if (data.ImageBackend != null) {
- AdornedWindow = GetParentWindow ();
+ AdornedWindow = GetParentOrMainWindow ();
AdornedWindow.AllowDrop = true;
var e = (UIElement)AdornedWindow.Content;
@@ -742,6 +758,7 @@ namespace Xwt.WPFBackend DragDropInfo.TargetTypes = types == null ? new TransferDataType [0] : types;
Widget.MouseUp += WidgetMouseUpForDragHandler;
Widget.MouseMove += WidgetMouseMoveForDragHandler;
+ Widget.MouseDown += WidgetMouseDownForDragHandler; }
private void SetupDragRect (MouseEventArgs e)
@@ -752,7 +769,7 @@ namespace Xwt.WPFBackend DragDropInfo.DragRect = new Rect (loc.X - width / 2, loc.Y - height / 2, width, height);
}
- void WidgetMouseUpForDragHandler (object o, EventArgs e)
+ internal void WidgetMouseUpForDragHandler (object o, EventArgs e) {
DragDropInfo.DragRect = Rect.Empty;
}
@@ -765,7 +782,7 @@ namespace Xwt.WPFBackend return;
if (DragDropInfo.DragRect.IsEmpty)
- SetupDragRect (e);
+ return; if (DragDropInfo.DragRect.Contains (e.GetPosition (Widget)))
return;
@@ -858,7 +875,7 @@ namespace Xwt.WPFBackend void WidgetDragOverHandler (object sender, System.Windows.DragEventArgs e)
{
if (Adorner != null) {
- var w = GetParentWindow ();
+ var w = GetParentOrMainWindow ();
var v = (UIElement)w.Content;
if (w != AdornedWindow) {
diff --git a/Xwt.XamMac/Xwt.Mac.CellViews/CanvasTableCell.cs b/Xwt.XamMac/Xwt.Mac.CellViews/CanvasTableCell.cs index c3703f17..573a3342 100644 --- a/Xwt.XamMac/Xwt.Mac.CellViews/CanvasTableCell.cs +++ b/Xwt.XamMac/Xwt.Mac.CellViews/CanvasTableCell.cs @@ -30,19 +30,19 @@ using Xwt.Backends; namespace Xwt.Mac { - class CanvasTableCell: NSCell, ICellRenderer + class CanvasTableCell: NSView, ICanvasCellRenderer { - bool visible = true; + NSTrackingArea trackingArea; - public CanvasTableCell (IntPtr p): base (p) - { - } + public CompositeCell CellContainer { get; set; } - public CanvasTableCell () - { - } + public CellViewBackend Backend { get; set; } - public CompositeCell CellContainer { get; set; } + public NSView CellView { get { return this; } } + + // Since 10.12 or 10.13 views inside tables flip on data reload. + // IsFlipped enforces the correct orientation of the layer. + public override bool IsFlipped { get { return true; } } public void CopyFrom (object other) { @@ -52,46 +52,132 @@ namespace Xwt.Mac public void Fill () { - visible = Frontend.Visible; + Hidden = !Frontend.Visible; } ICanvasCellViewFrontend Frontend { get { return (ICanvasCellViewFrontend) Backend.Frontend; } } - public CellViewBackend Backend { get; set; } - + public override CGSize FittingSize { + get { + var size = CGSize.Empty; + Frontend.ApplicationContext.InvokeUserCode (delegate { + var s = Frontend.GetRequiredSize (SizeConstraint.Unconstrained); + size = new CGSize ((nfloat)s.Width, (nfloat)s.Height); + }); + return size; + } + } - public override CGSize CellSizeForBounds (CGRect bounds) + public Size GetRequiredSize(SizeConstraint widthConstraint) { - if (!visible) - return CGSize.Empty; - var size = new CGSize (); + var size = Size.Zero; Frontend.ApplicationContext.InvokeUserCode (delegate { - var s = Frontend.GetRequiredSize (); - size = new CGSize ((nfloat)s.Width, (nfloat)s.Height); + size = Frontend.GetRequiredSize (widthConstraint); }); - if (size.Width > bounds.Width) - size.Width = bounds.Width; - if (size.Height > bounds.Height) - size.Height = bounds.Height; return size; } - public override void DrawInteriorWithFrame (CGRect cellFrame, NSView inView) + public override void DrawRect (CGRect dirtyRect) { - if (!visible) - return; - CGContext ctx = NSGraphicsContext.CurrentContext.GraphicsPort; - - var backend = new CGContextBackend { - Context = ctx, - InverseViewTransform = ctx.GetCTM ().Invert () - }; + Backend.Load (this); Frontend.ApplicationContext.InvokeUserCode (delegate { - Frontend.Draw (backend, new Rectangle (cellFrame.X, cellFrame.Y, cellFrame.Width, cellFrame.Height)); + CGContext ctx = NSGraphicsContext.CurrentContext.GraphicsPort; + + var backend = new CGContextBackend { + Context = ctx, + InverseViewTransform = ctx.GetCTM ().Invert () + }; + var bounds = Backend.CellBounds; + backend.Context.ClipToRect (dirtyRect); + backend.Context.TranslateCTM ((nfloat)(-bounds.X), (nfloat)(-bounds.Y)); + Frontend.Draw (backend, new Rectangle (bounds.X, bounds.Y, bounds.Width, bounds.Height)); }); } + + public override void UpdateTrackingAreas () + { + if (trackingArea != null) { + RemoveTrackingArea (trackingArea); + trackingArea.Dispose (); + } + var options = NSTrackingAreaOptions.MouseMoved | NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.MouseEnteredAndExited; + trackingArea = new NSTrackingArea (Bounds, options, this, null); + AddTrackingArea (trackingArea); + } + + public override void RightMouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.RightMouseDown (theEvent); + } + + public override void RightMouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.RightMouseUp (theEvent); + } + + public override void MouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.MouseDown (theEvent); + } + + public override void MouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.MouseUp (theEvent); + } + + public override void OtherMouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.OtherMouseDown (theEvent); + } + + public override void OtherMouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.OtherMouseUp (theEvent); + } + + public override void MouseEntered (NSEvent theEvent) + { + this.HandleMouseEntered (theEvent); + base.MouseEntered (theEvent); + } + + public override void MouseExited (NSEvent theEvent) + { + this.HandleMouseExited (theEvent); + base.MouseExited (theEvent); + } + + public override void MouseMoved (NSEvent theEvent) + { + if (!this.HandleMouseMoved (theEvent)) + base.MouseMoved (theEvent); + } + + public override void MouseDragged (NSEvent theEvent) + { + if (!this.HandleMouseMoved (theEvent)) + base.MouseDragged (theEvent); + } + + public override void KeyDown (NSEvent theEvent) + { + if (!this.HandleKeyDown (theEvent)) + base.KeyDown (theEvent); + } + + public override void KeyUp (NSEvent theEvent) + { + if (!this.HandleKeyUp (theEvent)) + base.KeyUp (theEvent); + } } } diff --git a/Xwt.XamMac/Xwt.Mac.CellViews/CellUtil.cs b/Xwt.XamMac/Xwt.Mac.CellViews/CellUtil.cs index 759c2b65..8134e62b 100644 --- a/Xwt.XamMac/Xwt.Mac.CellViews/CellUtil.cs +++ b/Xwt.XamMac/Xwt.Mac.CellViews/CellUtil.cs @@ -27,21 +27,29 @@ using System; using System.Collections.Generic; using AppKit; +using CoreGraphics; using Xwt.Backends; namespace Xwt.Mac { static class CellUtil { - public static NSCell CreateCell (ApplicationContext context, NSTableView table, ICellSource source, ICollection<CellView> cells, int column) + public static CompositeCell CreateCellView (ApplicationContext context, ICellSource source, ICollection<CellView> cells, int column) { - CompositeCell c = new CompositeCell (context, Orientation.Horizontal, source); + CompositeCell c = new CompositeCell (context, source); foreach (var cell in cells) - c.AddCell ((ICellRenderer) CreateCell (table, c, cell, column)); + c.AddCell ((ICellRenderer) CreateCellView (source, cell, column)); return c; } + + public static void UpdateCellView (CompositeCell cellView, ICellSource source, ICollection<CellView> cells, int column) + { + cellView.ClearCells (); + foreach (var cell in cells) + cellView.AddCell ((ICellRenderer) CreateCellView (source, cell, column)); + } - static NSCell CreateCell (NSTableView table, CompositeCell source, CellView cell, int column) + static NSView CreateCellView (ICellSource source, CellView cell, int column) { ICellRenderer cr = null; @@ -57,10 +65,121 @@ namespace Xwt.Mac cr = new RadioButtonTableCell (); else throw new NotImplementedException (); - cr.Backend = new CellViewBackend (table, column); ICellViewFrontend fr = cell; - fr.AttachBackend (null, cr.Backend); - return (NSCell)cr; + CellViewBackend backend = null; + try { + //FIXME: although the cell views are based on XwtComponent, they don't implement + // the dynamic registration based backend creation and there is no way to + // identify whether the frontend has already a valid backend. + backend = cell.GetBackend () as CellViewBackend; + } catch (InvalidOperationException) { } + + if (backend == null) { + cr.Backend = new CellViewBackend (source.TableView, column); + fr.AttachBackend ((source as ViewBackend).Frontend, cr.Backend); + } else + cr.Backend = backend; + return (NSView)cr; + } + + public static bool HandleMouseDown (this ICellRenderer cell, NSEvent theEvent) + { + if (cell.Backend.GetIsEventEnabled (WidgetEvent.ButtonPressed)) { + CGPoint p = cell.CellView.ConvertPointFromEvent (theEvent); + if (cell.CellView.Bounds.Contains (p)) { + cell.Backend.Load (cell); + cell.CellContainer.SetCurrentEventRow (); + var offset = cell.Backend.CellBounds.Location; + ButtonEventArgs args = new ButtonEventArgs { + X = p.X + offset.X, + Y = p.Y + offset.Y, + Button = theEvent.GetPointerButton(), + MultiplePress = (int)theEvent.ClickCount + }; + cell.Backend.Context.InvokeUserCode (() => cell.Backend.EventSink.OnButtonPressed (args)); + return args.Handled; + } + } + return false; + } + + public static bool HandleMouseUp (this ICellRenderer cell, NSEvent theEvent) + { + if (cell.Backend.GetIsEventEnabled (WidgetEvent.ButtonReleased)) { + CGPoint p = cell.CellView.ConvertPointFromEvent (theEvent); + if (cell.CellView.Bounds.Contains (p)) { + cell.Backend.Load (cell); + cell.CellContainer.SetCurrentEventRow (); + var offset = cell.Backend.CellBounds.Location; + ButtonEventArgs args = new ButtonEventArgs { + X = p.X + offset.X, + Y = p.Y + offset.Y, + Button = theEvent.GetPointerButton (), + MultiplePress = (int)theEvent.ClickCount + }; + cell.Backend.Context.InvokeUserCode (() => cell.Backend.EventSink.OnButtonReleased (args)); + return args.Handled; + } + } + return false; + } + + public static void HandleMouseEntered (this ICellRenderer cell, NSEvent theEvent) + { + if (cell.Backend.GetIsEventEnabled (WidgetEvent.MouseEntered)) { + cell.Backend.Load (cell); + cell.CellContainer.SetCurrentEventRow (); + cell.Backend.Context.InvokeUserCode (cell.Backend.EventSink.OnMouseEntered); + } + } + + public static void HandleMouseExited (this ICellRenderer cell, NSEvent theEvent) + { + if (cell.Backend.GetIsEventEnabled (WidgetEvent.MouseExited)) { + cell.Backend.Load (cell); + cell.CellContainer.SetCurrentEventRow (); + cell.Backend.Context.InvokeUserCode (cell.Backend.EventSink.OnMouseExited); + } + } + + public static bool HandleMouseMoved (this ICellRenderer cell, NSEvent theEvent) + { + if (cell.Backend.GetIsEventEnabled (WidgetEvent.MouseMoved)) { + CGPoint p = cell.CellView.ConvertPointFromEvent (theEvent); + if (cell.CellView.Bounds.Contains (p)) { + cell.Backend.Load (cell); + cell.CellContainer.SetCurrentEventRow (); + var offset = cell.Backend.CellBounds.Location; + MouseMovedEventArgs args = new MouseMovedEventArgs ((long)TimeSpan.FromSeconds (theEvent.Timestamp).TotalMilliseconds, p.X + offset.X, p.Y + offset.Y); + cell.Backend.Context.InvokeUserCode (() => cell.Backend.EventSink.OnMouseMoved (args)); + return args.Handled; + } + } + return false; + } + + public static bool HandleKeyDown (this ICellRenderer cell, NSEvent theEvent) + { + if (cell.Backend.GetIsEventEnabled (WidgetEvent.KeyPressed)) { + cell.Backend.Load (cell); + cell.CellContainer.SetCurrentEventRow (); + var keyArgs = theEvent.ToXwtKeyEventArgs (); + cell.Backend.Context.InvokeUserCode (() => cell.Backend.EventSink.OnKeyPressed (keyArgs)); + return keyArgs.Handled; + } + return false; + } + + public static bool HandleKeyUp (this ICellRenderer cell, NSEvent theEvent) + { + if (cell.Backend.GetIsEventEnabled (WidgetEvent.KeyReleased)) { + cell.Backend.Load (cell); + cell.CellContainer.SetCurrentEventRow (); + var keyArgs = theEvent.ToXwtKeyEventArgs (); + cell.Backend.Context.InvokeUserCode (() => cell.Backend.EventSink.OnKeyReleased (keyArgs)); + return keyArgs.Handled; + } + return false; } } } diff --git a/Xwt.XamMac/Xwt.Mac.CellViews/CellViewBackend.cs b/Xwt.XamMac/Xwt.Mac.CellViews/CellViewBackend.cs index b7b3d9a6..6418facf 100644 --- a/Xwt.XamMac/Xwt.Mac.CellViews/CellViewBackend.cs +++ b/Xwt.XamMac/Xwt.Mac.CellViews/CellViewBackend.cs @@ -30,79 +30,92 @@ namespace Xwt.Mac { public class CellViewBackend: ICellViewBackend, ICanvasCellViewBackend { - NSTableView table; - int column; + WidgetEvent enabledEvents; public CellViewBackend (NSTableView table, int column) { - this.table = table; - this.column = column; + Table = table; + Column = column; } - public ICellViewFrontend Frontend { get; private set; } - public virtual void InitializeBackend (object frontend, ApplicationContext context) { Frontend = (ICellViewFrontend)frontend; + Context = context; } - public NSCell CurrentCell { get; set; } + public ICellViewFrontend Frontend { get; private set; } - public int Column { - get { - return column; - } - } + public ICellViewEventSink EventSink { get; private set; } - public int CurrentRow { get; set; } + public ApplicationContext Context { get; private set; } - internal ITablePosition CurrentPosition { get; set; } + internal NSView CurrentCellView { get; private set; } + public int Column { get; private set; } + + public NSTableView Table { get; internal set; } + + internal ITablePosition CurrentPosition { get; private set; } + + internal void Load (ICellRenderer cell) + { + CurrentCellView = (NSView)cell; + CurrentPosition = cell.CellContainer.TablePosition; + EventSink = Frontend.Load (cell.CellContainer); + } + public virtual void EnableEvent (object eventId) { + if (eventId is WidgetEvent) + enabledEvents |= (WidgetEvent)eventId; } - + public virtual void DisableEvent (object eventId) { + if (eventId is WidgetEvent) + enabledEvents &= ~(WidgetEvent)eventId; + } + + public bool GetIsEventEnabled (WidgetEvent eventId) + { + return enabledEvents.HasFlag (eventId); } public void QueueDraw () { - // nothing to be done here, NSTableView should handle this + CurrentCellView.NeedsDisplay = true; + } + + public void QueueResize () + { + CurrentCellView.NeedsDisplay = true; + ((ICellRenderer)CurrentCellView).CellContainer.InvalidateRowHeight (); } public Rectangle CellBounds { get { - if (CurrentPosition is TableRow) { - var r = table.GetCellFrame (column, ((TableRow)CurrentPosition).Row); - r = ((ICellRenderer)CurrentCell).CellContainer.GetCellRect (r, CurrentCell); - return new Rectangle (r.X, r.Y, r.Width, r.Height); - } - return Rectangle.Zero; + return CurrentCellView.ConvertRectToView (CurrentCellView.Frame, Table).ToXwtRect (); } } public Rectangle BackgroundBounds { get { - // TODO - return CellBounds; + return CurrentCellView.ConvertRectToView (CurrentCellView.Frame, ((ICellRenderer)CurrentCellView).CellContainer.Superview).ToXwtRect (); } } public bool Selected { get { - if (CurrentPosition is TableRow) { - return table.IsRowSelected (((TableRow)CurrentPosition).Row); - } - // TODO - return false; + if (CurrentPosition is TableRow) + return Table.IsRowSelected (((TableRow)CurrentPosition).Row); + return Table.IsRowSelected (Table.RowForView (CurrentCellView)); } } public bool HasFocus { get { - // TODO - return false; + return CurrentCellView?.Window != null && CurrentCellView.Window.FirstResponder == CurrentCellView; } } diff --git a/Xwt.XamMac/Xwt.Mac.CellViews/CheckBoxTableCell.cs b/Xwt.XamMac/Xwt.Mac.CellViews/CheckBoxTableCell.cs index f41c3ab6..2a00812f 100644 --- a/Xwt.XamMac/Xwt.Mac.CellViews/CheckBoxTableCell.cs +++ b/Xwt.XamMac/Xwt.Mac.CellViews/CheckBoxTableCell.cs @@ -25,35 +25,65 @@ // THE SOFTWARE. using System; using AppKit; +using CoreGraphics; +using Foundation; using Xwt.Backends; namespace Xwt.Mac { - class CheckBoxTableCell: NSButtonCell, ICellRenderer + class CheckBoxTableCell: NSButton, ICellRenderer { - bool visible = true; + NSTrackingArea trackingArea; public CheckBoxTableCell () { SetButtonType (NSButtonType.Switch); Activated += HandleActivated; - Title = ""; + Title = string.Empty; + } + + public override NSCellStateValue State { + get { + return base.State; + } + set { + // don't let Cocoa set the state for us + } } void HandleActivated (object sender, EventArgs e) { + Backend.Load (this); var cellView = Frontend; + var nextState = State; // store new state internally set by Cocoa + base.State = cellView.State.ToMacState (); // reset state to previous state from store CellContainer.SetCurrentEventRow (); - if (cellView.Editable && !cellView.RaiseToggled () && (cellView.StateField != null || cellView.ActiveField != null)) { + if (!cellView.RaiseToggled ()) { if (cellView.StateField != null) - CellContainer.SetValue (cellView.StateField, State.ToXwtState ()); + CellContainer.SetValue (cellView.StateField, nextState.ToXwtState ()); else if (cellView.ActiveField != null) - CellContainer.SetValue (cellView.ActiveField, State != NSCellStateValue.Off); + CellContainer.SetValue (cellView.ActiveField, nextState != NSCellStateValue.Off); } } - public CheckBoxTableCell (IntPtr p): base (p) + NSCellStateValue GetNextState () { + if (!AllowsMixedState) { + switch (State) { + case NSCellStateValue.Off: + case NSCellStateValue.Mixed: + return NSCellStateValue.On; + default: return NSCellStateValue.Off; + } + } else { + switch (State) { + case NSCellStateValue.Off: + return NSCellStateValue.Mixed; + case NSCellStateValue.Mixed: + return NSCellStateValue.On; + default: return NSCellStateValue.Off; + } + } } ICheckBoxCellViewFrontend Frontend { @@ -64,26 +94,36 @@ namespace Xwt.Mac public CompositeCell CellContainer { get; set; } + public NSView CellView { get { return this; } } + public void Fill () { var cellView = Frontend; AllowsMixedState = cellView.AllowMixed || cellView.State == CheckBoxState.Mixed; - State = cellView.State.ToMacState (); - Editable = cellView.Editable; - visible = cellView.Visible; + base.State = cellView.State.ToMacState (); + Enabled = cellView.Editable; + Hidden = !cellView.Visible; } - - public override CoreGraphics.CGSize CellSizeForBounds (CoreGraphics.CGRect bounds) - { - if (visible) - return base.CellSizeForBounds (bounds); - return CoreGraphics.CGSize.Empty; + + public virtual NSBackgroundStyle BackgroundStyle { + [Export ("backgroundStyle")] + get { + return Cell.BackgroundStyle; + } + [Export ("setBackgroundStyle:")] + set { + Cell.BackgroundStyle = value; + } } - public override void DrawInteriorWithFrame (CoreGraphics.CGRect cellFrame, NSView inView) - { - if (visible) - base.DrawInteriorWithFrame (cellFrame, inView); + static CGSize defaultSize = CGSize.Empty; + public override CGSize FittingSize { + get { + // CheckBox has always the same size, measure it only once + if (defaultSize.IsEmpty) + defaultSize = base.FittingSize; + return defaultSize; + } } public void CopyFrom (object other) @@ -91,6 +131,89 @@ namespace Xwt.Mac var ob = (CheckBoxTableCell)other; Backend = ob.Backend; } + + public override void UpdateTrackingAreas () + { + if (trackingArea != null) { + RemoveTrackingArea (trackingArea); + trackingArea.Dispose (); + } + var options = NSTrackingAreaOptions.MouseMoved | NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.MouseEnteredAndExited; + trackingArea = new NSTrackingArea (Bounds, options, this, null); + AddTrackingArea (trackingArea); + } + + public override void RightMouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.RightMouseDown (theEvent); + } + + public override void RightMouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.RightMouseUp (theEvent); + } + + public override void MouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.MouseDown (theEvent); + } + + public override void MouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.MouseUp (theEvent); + } + + public override void OtherMouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.OtherMouseDown (theEvent); + } + + public override void OtherMouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.OtherMouseUp (theEvent); + } + + public override void MouseEntered (NSEvent theEvent) + { + this.HandleMouseEntered (theEvent); + base.MouseEntered (theEvent); + } + + public override void MouseExited (NSEvent theEvent) + { + this.HandleMouseExited (theEvent); + base.MouseExited (theEvent); + } + + public override void MouseMoved (NSEvent theEvent) + { + if (!this.HandleMouseMoved (theEvent)) + base.MouseMoved (theEvent); + } + + public override void MouseDragged (NSEvent theEvent) + { + if (!this.HandleMouseMoved (theEvent)) + base.MouseDragged (theEvent); + } + + public override void KeyDown (NSEvent theEvent) + { + if (!this.HandleKeyDown (theEvent)) + base.KeyDown (theEvent); + } + + public override void KeyUp (NSEvent theEvent) + { + if (!this.HandleKeyUp (theEvent)) + base.KeyUp (theEvent); + } } } diff --git a/Xwt.XamMac/Xwt.Mac.CellViews/CompositeCell.cs b/Xwt.XamMac/Xwt.Mac.CellViews/CompositeCell.cs index 18849fff..1502aefe 100644 --- a/Xwt.XamMac/Xwt.Mac.CellViews/CompositeCell.cs +++ b/Xwt.XamMac/Xwt.Mac.CellViews/CompositeCell.cs @@ -24,24 +24,22 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. - using System; using System.Collections.Generic; using System.Linq; using AppKit; using CoreGraphics; using Foundation; +using ObjCRuntime; using Xwt.Backends; namespace Xwt.Mac { - class CompositeCell: NSCell, ICopiableObject, ICellDataSource + class CompositeCell : NSView, ICopiableObject, ICellDataSource, INSCopying { ICellSource source; NSObject val; List<ICellRenderer> cells = new List<ICellRenderer> (); - Orientation direction; - NSCell trackingCell; ITablePosition tablePosition; ApplicationContext context; @@ -51,16 +49,19 @@ namespace Xwt.Mac } } - public CompositeCell (ApplicationContext context, Orientation dir, ICellSource source) + public CompositeCell (ApplicationContext context, ICellSource source) { if (source == null) - throw new ArgumentNullException ("source"); - direction = dir; + throw new ArgumentNullException (nameof (source)); this.context = context; this.source = source; } - - public CompositeCell (IntPtr p): base (p) + + public CompositeCell (IntPtr p) : base (p) + { + } + + CompositeCell () { } @@ -87,11 +88,37 @@ namespace Xwt.Mac source.SetCurrentEventRow (tablePosition.Position); } - public override NSObject Copy (NSZone zone) + bool recalculatingHeight = false; + public void InvalidateRowHeight () + { + if (tablePosition != null && !recalculatingHeight) { + recalculatingHeight = true; + source.InvalidateRowHeight (tablePosition.Position); + recalculatingHeight = false; + } + } + + public double GetRequiredHeightForWidth (double width) + { + Fill (false); + double height = 0; + foreach (var c in GetCells (new CGSize (width, -1))) + height = Math.Max (height, c.Frame.Height); + return height; + } + + public override NSObject Copy () + { + var ob = (ICopiableObject)base.Copy (); + ob.CopyFrom (this); + return (NSObject)ob; + } + + NSObject INSCopying.Copy (NSZone zone) { - var ob = (ICopiableObject) base.Copy (zone); + var ob = (ICopiableObject)new CompositeCell (); ob.CopyFrom (this); - return (NSObject) ob; + return (NSObject)ob; } void ICopiableObject.CopyFrom (object other) @@ -99,33 +126,31 @@ namespace Xwt.Mac var ob = (CompositeCell)other; if (ob.source == null) throw new ArgumentException ("Cannot copy from a CompositeCell with a null `source`"); + Identifier = ob.Identifier; context = ob.context; source = ob.source; - val = ob.val; - tablePosition = ob.tablePosition; - direction = ob.direction; - trackingCell = ob.trackingCell; cells = new List<ICellRenderer> (); foreach (var c in ob.cells) { - var copy = (ICellRenderer) Activator.CreateInstance (c.GetType ()); + var copy = (ICellRenderer)Activator.CreateInstance (c.GetType ()); copy.CopyFrom (c); AddCell (copy); } if (tablePosition != null) - Fill (); + Fill (false); } - - public override NSObject ObjectValue { + + public virtual NSObject ObjectValue { + [Export ("objectValue")] get { return val; } + [Export ("setObjectValue:")] set { val = value; if (val is ITablePosition) { - tablePosition = (ITablePosition) val; + tablePosition = (ITablePosition)val; Fill (); - } - else if (val is NSNumber) { + } else if (val is NSNumber) { tablePosition = new TableRow () { Row = ((NSNumber)val).Int32Value }; @@ -135,202 +160,156 @@ namespace Xwt.Mac } } - public override bool IsOpaque { - get { - var b = base.IsOpaque; - return true; - } + internal ITablePosition TablePosition { + get { return tablePosition; } } - + public void AddCell (ICellRenderer cell) { cell.CellContainer = this; cells.Add (cell); + AddSubview ((NSView)cell); } - - public void Fill () + + public void ClearCells () + { + foreach (NSView cell in cells) { + cell.RemoveFromSuperview (); + } + cells.Clear (); + } + + public override CGRect Frame { + get { return base.Frame; } + set { + var oldSize = base.Frame.Size; + base.Frame = value; + if (oldSize != value.Size && tablePosition != null) { + Fill(false); + double height = 0; + foreach (var c in GetCells (value.Size)) { + c.Cell.Frame = c.Frame; + c.Cell.NeedsDisplay = true; + height = Math.Max (height, c.Frame.Height); + } + if (Math.Abs(value.Height - height) > double.Epsilon) + InvalidateRowHeight (); + + } + } + } + + public void Fill (bool reallocateCells = true) { foreach (var c in cells) { - c.Backend.CurrentCell = (NSCell) c; - c.Backend.CurrentPosition = tablePosition; - c.Backend.Frontend.Load (this); + c.Backend.Load (c); c.Fill (); } + if (!reallocateCells || Frame.IsEmpty) + return; - var s = CellSize; + foreach (var c in GetCells (Frame.Size)) { + c.Cell.Frame = c.Frame; + c.Cell.NeedsDisplay = true; + } } - IEnumerable<ICellRenderer> VisibleCells { - get { return cells.Where (c => c.Backend.Frontend.Visible); } + public NSView GetCellViewForBackend (ICellViewBackend backend) + { + return cells.FirstOrDefault (c => c.Backend == backend) as NSView; } CGSize CalcSize () { nfloat w = 0; nfloat h = 0; - foreach (NSCell c in VisibleCells) { - var s = c.CellSize; - if (direction == Orientation.Horizontal) { - w += s.Width; - if (s.Height > h) - h = s.Height; - } else { - h += s.Height; - if (s.Width > w) - w = s.Width; - } + foreach (var cell in cells) { + if (!cell.Backend.Frontend.Visible) + continue; + var c = (NSView)cell; + var s = c.FittingSize; + w += s.Width; + if (s.Height > h) + h = s.Height; } return new CGSize (w, h); } - public override CGSize CellSizeForBounds (CGRect bounds) - { - return CalcSize (); - } - - public override NSBackgroundStyle BackgroundStyle { + public override CGSize FittingSize { get { - return base.BackgroundStyle; - } - set { - base.BackgroundStyle = value; - foreach (NSCell c in cells) - c.BackgroundStyle = value; + return CalcSize (); } } - - public override NSCellStateValue State { - get { - return base.State; - } - set { - base.State = value; - foreach (NSCell c in cells) - c.State = value; - } - } - - public override bool Highlighted { + + static readonly Selector selSetBackgroundStyle = new Selector ("setBackgroundStyle:"); + + NSBackgroundStyle backgroundStyle; + + public virtual NSBackgroundStyle BackgroundStyle { + [Export ("backgroundStyle")] get { - return base.Highlighted; + return backgroundStyle; } + [Export ("setBackgroundStyle:")] set { - base.Highlighted = value; - foreach (NSCell c in cells) - c.Highlighted = value; - } - } - - public override void DrawInteriorWithFrame (CGRect cellFrame, NSView inView) - { - // FIXME: although ObjectValue seems to be set and Fill called correctly, - // the table flickers without an additional Fill call, especially - // on expansion/collapsing with partially hidden cells (row wise). - // Cocoa seems to be resetting some NSCell bits, which may be - // related to the deprecated NSCell mode. - if (tablePosition != null) - Fill (); - CGContext ctx = NSGraphicsContext.CurrentContext.GraphicsPort; - ctx.SaveState (); - ctx.AddRect (cellFrame); - ctx.Clip (); - foreach (CellPos cp in GetCells(cellFrame)) - cp.Cell.DrawInteriorWithFrame (cp.Frame, inView); - ctx.RestoreState (); - } - - public override void Highlight (bool flag, CGRect withFrame, NSView inView) - { - foreach (CellPos cp in GetCells(withFrame)) { - cp.Cell.Highlight (flag, cp.Frame, inView); + backgroundStyle = value; + foreach (NSView cell in cells) + if (cell.RespondsToSelector (selSetBackgroundStyle)) { + if (IntPtr.Size == 8) + Messaging.void_objc_msgSend_Int64 (cell.Handle, selSetBackgroundStyle.Handle, (long)value); + else + Messaging.void_objc_msgSend_int (cell.Handle, selSetBackgroundStyle.Handle, (int)value); + } else + cell.NeedsDisplay = true; } } - public override NSCellHit HitTest (NSEvent forEvent, CGRect inRect, NSView ofView) + List <CellPos> GetCells (CGSize cellSize) { - foreach (CellPos cp in GetCells(inRect)) { - var h = cp.Cell.HitTest (forEvent, cp.Frame, ofView); - if (h != NSCellHit.None) - return h; - } - return NSCellHit.None; - } + int nexpands = 0; + double requiredSize = 0; + double availableSize = cellSize.Width; - public override bool TrackMouse (NSEvent theEvent, CGRect cellFrame, NSView controlView, bool untilMouseUp) - { - var c = GetHitCell (theEvent, cellFrame, controlView); - if (c != null) - return c.Cell.TrackMouse (theEvent, c.Frame, controlView, untilMouseUp); - else - return base.TrackMouse (theEvent, cellFrame, controlView, untilMouseUp); - } + var cellFrames = new List<CellPos> (cells.Count); - public CGRect GetCellRect (CGRect cellFrame, NSCell cell) - { - foreach (var c in GetCells (cellFrame)) { - if (c.Cell == cell) - return c.Frame; + // Get the natural size of each child + foreach (var cell in cells) { + if (!cell.Backend.Frontend.Visible) + continue; + var cellPos = new CellPos { Cell = (NSView)cell, Frame = CGRect.Empty }; + cellFrames.Add (cellPos); + var size = cellPos.Cell.FittingSize; + cellPos.Frame.Width = size.Width; + requiredSize += size.Width; + if (cell.Backend.Frontend.Expands) + nexpands++; } - return CGRect.Empty; - } - CellPos GetHitCell (NSEvent theEvent, CGRect cellFrame, NSView controlView) - { - foreach (CellPos cp in GetCells(cellFrame)) { - var h = cp.Cell.HitTest (theEvent, cp.Frame, controlView); - if (h != NSCellHit.None) - return cp; - } - return null; - } - - IEnumerable<CellPos> GetCells (CGRect cellFrame) - { - if (direction == Orientation.Horizontal) { - - int nexpands = 0; - double requiredSize = 0; - double availableSize = cellFrame.Width; - - var sizes = new Dictionary<ICellRenderer, double> (); - - // Get the natural size of each child - foreach (var bp in VisibleCells) { - var s = ((NSCell)bp).CellSize; - sizes [bp] = s.Width; - requiredSize += s.Width; - if (bp.Backend.Frontend.Expands) - nexpands++; - } - - double remaining = availableSize - requiredSize; - if (remaining > 0) { - var expandRemaining = new SizeSplitter (remaining, nexpands); - foreach (var bp in VisibleCells) { - if (bp.Backend.Frontend.Expands) - sizes [bp] += (nfloat)expandRemaining.NextSizePart (); - } + double remaining = availableSize - requiredSize; + if (remaining > 0) { + var expandRemaining = new SizeSplitter (remaining, nexpands); + foreach (var cellFrame in cellFrames) { + if (((ICellRenderer)cellFrame.Cell).Backend.Frontend.Expands) + cellFrame.Frame.Width += (nfloat)expandRemaining.NextSizePart (); } + } - double x = cellFrame.X; - foreach (var s in sizes) { - yield return new CellPos () { Cell = (NSCell)s.Key, Frame = new CGRect (x, cellFrame.Y, s.Value, cellFrame.Height) }; - x += s.Value; - } - } else { - nfloat y = cellFrame.Y; - foreach (NSCell c in VisibleCells) { - var s = c.CellSize; - var f = new CGRect (cellFrame.X, y, s.Width, cellFrame.Height); - y += s.Height; - yield return new CellPos () { Cell = c, Frame = f }; - } + double x = 0; + foreach (var cellFrame in cellFrames) { + var width = cellFrame.Frame.Width; + var canvas = cellFrame.Cell as ICanvasCellRenderer; + var height = (canvas != null) ? canvas.GetRequiredSize (SizeConstraint.WithSize (width)).Height : cellFrame.Cell.FittingSize.Height; + // y-align only if the cell has a valid height, otherwise we're just recalculating the required size + var y = cellSize.Height > 0 ? (cellSize.Height - height) / 2 : 0; + cellFrame.Frame = new CGRect (x, y, width, height); + x += width; } + return cellFrames; } - + class CellPos { - public NSCell Cell; + public NSView Cell; public CGRect Frame; } @@ -354,10 +333,29 @@ namespace Xwt.Mac { rem--; return part + 1; - } - else + } else return part; } } + + bool isDisposed; + + public bool IsDisposed { + get { + try { + // Cocoa may dispose the native view in NSView based table mode + // in this case Handle and SuperHandle will become Zero. + return isDisposed || Handle == IntPtr.Zero || SuperHandle == IntPtr.Zero; + } catch { + return true; + } + } + } + + protected override void Dispose(bool disposing) + { + isDisposed = true; + base.Dispose(disposing); + } } } diff --git a/Xwt.XamMac/Xwt.Mac.CellViews/ICellRenderer.cs b/Xwt.XamMac/Xwt.Mac.CellViews/ICellRenderer.cs index e40cec63..82499dc4 100644 --- a/Xwt.XamMac/Xwt.Mac.CellViews/ICellRenderer.cs +++ b/Xwt.XamMac/Xwt.Mac.CellViews/ICellRenderer.cs @@ -24,6 +24,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +using AppKit; namespace Xwt.Mac { @@ -31,8 +32,14 @@ namespace Xwt.Mac { CellViewBackend Backend { get; set; } CompositeCell CellContainer { get; set; } + NSView CellView { get; } void Fill (); } + + interface ICanvasCellRenderer : ICellRenderer + { + Size GetRequiredSize (SizeConstraint widthConstraint); + } interface ITablePosition { diff --git a/Xwt.XamMac/Xwt.Mac.CellViews/ICellSource.cs b/Xwt.XamMac/Xwt.Mac.CellViews/ICellSource.cs index 95c51a7b..85355f00 100644 --- a/Xwt.XamMac/Xwt.Mac.CellViews/ICellSource.cs +++ b/Xwt.XamMac/Xwt.Mac.CellViews/ICellSource.cs @@ -25,6 +25,8 @@ // THE SOFTWARE. using System; +using System.Collections.Generic; +using AppKit; namespace Xwt.Mac { @@ -33,6 +35,8 @@ namespace Xwt.Mac object GetValue (object pos, int nField); void SetValue (object pos, int nField, object value); void SetCurrentEventRow (object pos); - nfloat RowHeight { get; set; } + void InvalidateRowHeight (object pos); + List<NSTableColumn> Columns { get; } + NSTableView TableView { get; } } } diff --git a/Xwt.XamMac/Xwt.Mac.CellViews/ImageTableCell.cs b/Xwt.XamMac/Xwt.Mac.CellViews/ImageTableCell.cs index cffd6fe9..5efb8374 100644 --- a/Xwt.XamMac/Xwt.Mac.CellViews/ImageTableCell.cs +++ b/Xwt.XamMac/Xwt.Mac.CellViews/ImageTableCell.cs @@ -1,4 +1,4 @@ -// +// // ImageTableCell.cs // // Author: @@ -24,67 +24,138 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -using System; +using System;
using AppKit; using CoreGraphics; using Xwt.Backends; namespace Xwt.Mac { - class ImageTableCell: NSImageCell, ICellRenderer + class ImageTableCell : NSImageView, ICellRenderer { - bool visible = true; + NSTrackingArea trackingArea; - public ImageTableCell () - { - } - - public ImageTableCell (IntPtr p): base (p) - { - } - IImageCellViewFrontend Frontend { - get { return (IImageCellViewFrontend) Backend.Frontend; } + get { return (IImageCellViewFrontend)Backend.Frontend; } } public CellViewBackend Backend { get; set; } public CompositeCell CellContainer { get; set; } + public NSView CellView { get { return this; } } + public void Fill () { - ObjectValue = Frontend.Image.ToImageDescription (CellContainer.Context).ToNSImage (); - visible = Frontend.Visible; + if (Frontend.Image != null) { + Image = Frontend.Image.ToImageDescription (Backend.Context).ToNSImage (); + SetFrameSize (Image.Size); + } else + SetFrameSize (CoreGraphics.CGSize.Empty); + Hidden = !Frontend.Visible; } - - public override CGSize CellSize { + + public override CoreGraphics.CGSize FittingSize { get { - NSImage img = ObjectValue as NSImage; - if (img != null) - return img.Size; - else - return base.CellSize; + if (Image == null) + return CGSize.Empty; + return Image.Size; } } - public override CGSize CellSizeForBounds (CGRect bounds) + public override void SizeToFit() { - if (visible) - return base.CellSizeForBounds (bounds); - return CGSize.Empty; + if (Frame.Size.IsEmpty && Image != null) + SetFrameSize (Image.Size); } - public override void DrawInteriorWithFrame (CGRect cellFrame, NSView inView) - { - if (visible) - base.DrawInteriorWithFrame (cellFrame, inView); - } - public void CopyFrom (object other) { var ob = (ImageTableCell)other; Backend = ob.Backend; } + + public override void UpdateTrackingAreas () + { + if (trackingArea != null) { + RemoveTrackingArea (trackingArea); + trackingArea.Dispose (); + } + var options = NSTrackingAreaOptions.MouseMoved | NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.MouseEnteredAndExited; + trackingArea = new NSTrackingArea (Bounds, options, this, null); + AddTrackingArea (trackingArea); + } + + public override void RightMouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.RightMouseDown (theEvent); + } + + public override void RightMouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.RightMouseUp (theEvent); + } + + public override void MouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.MouseDown (theEvent); + } + + public override void MouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.MouseUp (theEvent); + } + + public override void OtherMouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.OtherMouseDown (theEvent); + } + + public override void OtherMouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.OtherMouseUp (theEvent); + } + + public override void MouseEntered (NSEvent theEvent) + { + this.HandleMouseEntered (theEvent); + base.MouseEntered (theEvent); + } + + public override void MouseExited (NSEvent theEvent) + { + this.HandleMouseExited (theEvent); + base.MouseExited (theEvent); + } + + public override void MouseMoved (NSEvent theEvent) + { + if (!this.HandleMouseMoved (theEvent)) + base.MouseMoved (theEvent); + } + + public override void MouseDragged (NSEvent theEvent) + { + if (!this.HandleMouseMoved (theEvent)) + base.MouseDragged (theEvent); + } + + public override void KeyDown (NSEvent theEvent) + { + if (!this.HandleKeyDown (theEvent)) + base.KeyDown (theEvent); + } + + public override void KeyUp (NSEvent theEvent) + { + if (!this.HandleKeyUp (theEvent)) + base.KeyUp (theEvent); + } } } - diff --git a/Xwt.XamMac/Xwt.Mac.CellViews/RadioButtonTableCell.cs b/Xwt.XamMac/Xwt.Mac.CellViews/RadioButtonTableCell.cs index 9a08f28b..f98b005b 100644 --- a/Xwt.XamMac/Xwt.Mac.CellViews/RadioButtonTableCell.cs +++ b/Xwt.XamMac/Xwt.Mac.CellViews/RadioButtonTableCell.cs @@ -25,27 +25,21 @@ // THE SOFTWARE. using System; using Xwt.Backends; - -#if MONOMAC -using nint = System.Int32; -using nfloat = System.Single; -using MonoMac.AppKit; -#else using AppKit; -#endif +using CoreGraphics; namespace Xwt.Mac { - class RadioButtonTableCell: NSButtonCell, ICellRenderer + class RadioButtonTableCell: NSButton, ICellRenderer { - bool visible = true; + NSTrackingArea trackingArea; public RadioButtonTableCell () { SetButtonType (NSButtonType.Radio); AllowsMixedState = false; Activated += HandleActivated; - Title = ""; + Title = string.Empty; } public override NSCellStateValue State { @@ -53,30 +47,19 @@ namespace Xwt.Mac return base.State; } set { - if (base.State != value) - stateChanging = true; - base.State = value; + // don't let Cocoa set the state for us } } - bool stateChanging; void HandleActivated (object sender, EventArgs e) { - if (State == NSCellStateValue.On && stateChanging) { - var cellView = Frontend; - CellContainer.SetCurrentEventRow (); - Frontend.Load (CellContainer); - if (cellView.Editable && !cellView.RaiseToggled ()) { - if (cellView.ActiveField != null) - CellContainer.SetValue (cellView.ActiveField, State != NSCellStateValue.Off); - } else - base.State = NSCellStateValue.Off; + Backend.Load (this); + var cellView = Frontend; + CellContainer.SetCurrentEventRow (); + if (!cellView.RaiseToggled ()) { + if (cellView.ActiveField != null) + CellContainer.SetValue (cellView.ActiveField, true); } - stateChanging = false; - } - - public RadioButtonTableCell (IntPtr p): base (p) - { } IRadioButtonCellViewFrontend Frontend { @@ -87,31 +70,113 @@ namespace Xwt.Mac public CompositeCell CellContainer { get; set; } + public NSView CellView { get { return this; } } + + static CGSize defaultSize = CGSize.Empty; + public override CGSize FittingSize { + get { + // Radio NSButton has always the same size, measure it only once + if (defaultSize.IsEmpty) + defaultSize = base.FittingSize; + return defaultSize; + } + } + public void Fill () { var cellView = Frontend; base.State = cellView.Active ? NSCellStateValue.On : NSCellStateValue.Off; - Editable = cellView.Editable; - visible = cellView.Visible; + Enabled = cellView.Editable; + Hidden = !cellView.Visible; } - public override CoreGraphics.CGSize CellSizeForBounds (CoreGraphics.CGRect bounds) + public void CopyFrom (object other) { - if (visible) - return base.CellSizeForBounds (bounds); - return CoreGraphics.CGSize.Empty; + var ob = (RadioButtonTableCell)other; + Backend = ob.Backend; } - public override void DrawInteriorWithFrame (CoreGraphics.CGRect cellFrame, NSView inView) + public override void UpdateTrackingAreas () { - if (visible) - base.DrawInteriorWithFrame (cellFrame, inView); + if (trackingArea != null) { + RemoveTrackingArea (trackingArea); + trackingArea.Dispose (); + } + var options = NSTrackingAreaOptions.MouseMoved | NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.MouseEnteredAndExited; + trackingArea = new NSTrackingArea (Bounds, options, this, null); + AddTrackingArea (trackingArea); } - public void CopyFrom (object other) + public override void RightMouseDown (NSEvent theEvent) { - var ob = (RadioButtonTableCell)other; - Backend = ob.Backend; + if (!this.HandleMouseDown (theEvent)) + base.RightMouseDown (theEvent); + } + + public override void RightMouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.RightMouseUp (theEvent); + } + + public override void MouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.MouseDown (theEvent); + } + + public override void MouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.MouseUp (theEvent); + } + + public override void OtherMouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.OtherMouseDown (theEvent); + } + + public override void OtherMouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.OtherMouseUp (theEvent); + } + + public override void MouseEntered (NSEvent theEvent) + { + this.HandleMouseEntered (theEvent); + base.MouseEntered (theEvent); + } + + public override void MouseExited (NSEvent theEvent) + { + this.HandleMouseExited (theEvent); + base.MouseExited (theEvent); + } + + public override void MouseMoved (NSEvent theEvent) + { + if (!this.HandleMouseMoved (theEvent)) + base.MouseMoved (theEvent); + } + + public override void MouseDragged (NSEvent theEvent) + { + if (!this.HandleMouseMoved (theEvent)) + base.MouseDragged (theEvent); + } + + public override void KeyDown (NSEvent theEvent) + { + if (!this.HandleKeyDown (theEvent)) + base.KeyDown (theEvent); + } + + public override void KeyUp (NSEvent theEvent) + { + if (!this.HandleKeyUp (theEvent)) + base.KeyUp (theEvent); } } } diff --git a/Xwt.XamMac/Xwt.Mac.CellViews/TextTableCell.cs b/Xwt.XamMac/Xwt.Mac.CellViews/TextTableCell.cs index f51e763a..0d79d44a 100644 --- a/Xwt.XamMac/Xwt.Mac.CellViews/TextTableCell.cs +++ b/Xwt.XamMac/Xwt.Mac.CellViews/TextTableCell.cs @@ -24,61 +24,148 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. - -using System; using AppKit; +using Foundation; using Xwt.Backends; namespace Xwt.Mac { - class TextTableCell: NSCell, ICellRenderer + class TextTableCell : NSTextField, ICellRenderer { - bool visible = true; - - - public TextTableCell (): base ("") - { - Wraps = false; - } - - public TextTableCell (IntPtr p): base (p) + NSTrackingArea trackingArea; + public TextTableCell () { + Editable = false; + Bezeled = false; + DrawsBackground = false; } ITextCellViewFrontend Frontend { get { return (ITextCellViewFrontend) Backend.Frontend; } } + + public override bool AllowsVibrancy { + get { + // we don't support vibrancy + if (EffectiveAppearance.AllowsVibrancy) + return false; + return base.AllowsVibrancy; + } + } public CellViewBackend Backend { get; set; } public CompositeCell CellContainer { get; set; } + public NSView CellView { get { return this; } } + public void Fill () { if (Frontend.Markup != null) AttributedStringValue = FormattedText.FromMarkup (Frontend.Markup).ToAttributedString (); else StringValue = Frontend.Text ?? ""; - visible = Frontend.Visible; + Hidden = !Frontend.Visible; + } + + public virtual NSBackgroundStyle BackgroundStyle { + [Export ("backgroundStyle")] + get { + return Cell.BackgroundStyle; + } + [Export ("setBackgroundStyle:")] + set { + Cell.BackgroundStyle = value; + } } - public override CoreGraphics.CGSize CellSizeForBounds (CoreGraphics.CGRect bounds) + public void CopyFrom (object other) { - if (visible) - return base.CellSizeForBounds (bounds); - return CoreGraphics.CGSize.Empty; + var ob = (TextTableCell)other; + Backend = ob.Backend; } - public override void DrawInteriorWithFrame (CoreGraphics.CGRect cellFrame, NSView inView) + public override void UpdateTrackingAreas () { - if (visible) - base.DrawInteriorWithFrame (cellFrame, inView); + if (trackingArea != null) { + RemoveTrackingArea (trackingArea); + trackingArea.Dispose (); + } + var options = NSTrackingAreaOptions.MouseMoved | NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.MouseEnteredAndExited; + trackingArea = new NSTrackingArea (Bounds, options, this, null); + AddTrackingArea (trackingArea); } - public void CopyFrom (object other) + public override void RightMouseDown (NSEvent theEvent) { - var ob = (TextTableCell)other; - Backend = ob.Backend; + if (!this.HandleMouseDown (theEvent)) + base.RightMouseDown (theEvent); + } + + public override void RightMouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.RightMouseUp (theEvent); + } + + public override void MouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.MouseDown (theEvent); + } + + public override void MouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.MouseUp (theEvent); + } + + public override void OtherMouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.OtherMouseDown (theEvent); + } + + public override void OtherMouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.OtherMouseUp (theEvent); + } + + public override void MouseEntered (NSEvent theEvent) + { + this.HandleMouseEntered (theEvent); + base.MouseEntered (theEvent); + } + + public override void MouseExited (NSEvent theEvent) + { + this.HandleMouseExited (theEvent); + base.MouseExited (theEvent); + } + + public override void MouseMoved (NSEvent theEvent) + { + if (!this.HandleMouseMoved (theEvent)) + base.MouseMoved (theEvent); + } + + public override void MouseDragged (NSEvent theEvent) + { + if (!this.HandleMouseMoved (theEvent)) + base.MouseDragged (theEvent); + } + + public override void KeyDown (NSEvent theEvent) + { + if (!this.HandleKeyDown (theEvent)) + base.KeyDown (theEvent); + } + + public override void KeyUp (NSEvent theEvent) + { + if (!this.HandleKeyUp (theEvent)) + base.KeyUp (theEvent); } } } diff --git a/Xwt.XamMac/Xwt.Mac/ButtonBackend.cs b/Xwt.XamMac/Xwt.Mac/ButtonBackend.cs index 12a1b235..119f5d9b 100644 --- a/Xwt.XamMac/Xwt.Mac/ButtonBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/ButtonBackend.cs @@ -143,6 +143,17 @@ namespace Xwt.Mac break; } } + bool isDefault; + public bool IsDefault { + get { return isDefault; } + set { + isDefault = value; + if (Widget.Window != null && Widget.Window.DefaultButtonCell != Widget.Cell) + Widget.Window.DefaultButtonCell = Widget.Cell; + } + } + + #endregion @@ -223,6 +234,12 @@ namespace Xwt.Mac { } + public override void ViewDidMoveToWindow () + { + if ((Backend as ButtonBackend)?.IsDefault == true && Window != null) + Window.DefaultButtonCell = Cell; + } + void OnActivatedInternal () { if (ActivatedInternal == null) diff --git a/Xwt.XamMac/Xwt.Mac/ContextBackendHandler.cs b/Xwt.XamMac/Xwt.Mac/ContextBackendHandler.cs index b459f884..70f9f640 100644 --- a/Xwt.XamMac/Xwt.Mac/ContextBackendHandler.cs +++ b/Xwt.XamMac/Xwt.Mac/ContextBackendHandler.cs @@ -47,6 +47,7 @@ namespace Xwt.Mac { public object Pattern; public double GlobalAlpha = 1; + public CGColor GlobalColor = null; public ContextStatus Previous; } @@ -67,6 +68,7 @@ namespace Xwt.Mac ct.CurrentStatus = new ContextStatus { Pattern = ct.CurrentStatus.Pattern, GlobalAlpha = ct.CurrentStatus.GlobalAlpha, + GlobalColor = ct.CurrentStatus.GlobalColor, Previous = ct.CurrentStatus, }; } @@ -217,6 +219,8 @@ namespace Xwt.Mac { CGContextBackend gc = (CGContextBackend)backend; gc.CurrentStatus.Pattern = null; + // Store the current color for TextLayout using NSLayoutManager + gc.CurrentStatus.GlobalColor = color.ToCGColor (); CGContext ctx = gc.Context; ctx.SetFillColorSpace (Util.DeviceRGBColorSpace); ctx.SetStrokeColorSpace (Util.DeviceRGBColorSpace); @@ -285,7 +289,7 @@ namespace Xwt.Mac CGContext ctx = ((CGContextBackend)backend).Context; SetupContextForDrawing (ctx); var li = ApplicationContext.Toolkit.GetSafeBackend (layout); - MacTextLayoutBackendHandler.Draw (ctx, li, x, y); + MacTextLayoutBackendHandler.Draw ((CGContextBackend)backend, li, x, y); } public override void DrawImage (object backend, ImageDescription img, double x, double y) diff --git a/Xwt.XamMac/Xwt.Mac/DialogBackend.cs b/Xwt.XamMac/Xwt.Mac/DialogBackend.cs index 189c6be9..0d19d4cb 100644 --- a/Xwt.XamMac/Xwt.Mac/DialogBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/DialogBackend.cs @@ -162,25 +162,8 @@ namespace Xwt.Mac public void RunLoop (IWindowFrameBackend parent) { - if (parent != null) - StyleMask &= ~NSWindowStyle.Miniaturizable; - else - StyleMask |= NSWindowStyle.Miniaturizable; Visible = true; modalSessionRunning = true; - var win = parent as NSWindow ?? ApplicationContext.Toolkit.GetNativeWindow (parent) as NSWindow; - if (win != null) { - win.AddChildWindow (this, NSWindowOrderingMode.Above); - // always use NSWindow for alignment when running in guest mode and - // don't rely on AddChildWindow to position the window correctly - if (!(parent is WindowBackend)) { - var parentBounds = MacDesktopBackend.ToDesktopRect (win.ContentRectFor (win.Frame)); - var bounds = ((IWindowFrameBackend)this).Bounds; - bounds.X = parentBounds.Center.X - (Frame.Width / 2); - bounds.Y = parentBounds.Center.Y - (Frame.Height / 2); - ((IWindowFrameBackend)this).Bounds = bounds; - } - } NSApplication.SharedApplication.RunModalForWindow (this); } diff --git a/Xwt.XamMac/Xwt.Mac/IViewObject.cs b/Xwt.XamMac/Xwt.Mac/IViewObject.cs index d2d35057..febd3282 100644 --- a/Xwt.XamMac/Xwt.Mac/IViewObject.cs +++ b/Xwt.XamMac/Xwt.Mac/IViewObject.cs @@ -25,6 +25,8 @@ // THE SOFTWARE. using AppKit; +using CoreGraphics; +using System; namespace Xwt.Mac { @@ -33,5 +35,145 @@ namespace Xwt.Mac NSView View { get; } ViewBackend Backend { get; set; } } + + public static class IViewObjectExtensions + { + public static void UpdateEventTrackingArea (this IViewObject view, ref NSTrackingArea replaceArea) + { + if (view == null) + throw new ArgumentNullException (nameof (view)); + if (view.View == null) + throw new InvalidOperationException (); + if (replaceArea != null) { + view.View.RemoveTrackingArea (replaceArea); + replaceArea.Dispose (); + } + CGRect viewBounds = view.View.Bounds; + var options = NSTrackingAreaOptions.MouseMoved | NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.MouseEnteredAndExited; + var trackingArea = new NSTrackingArea (viewBounds, options, view.View, null); + view.View.AddTrackingArea (trackingArea); + } + + public static bool HandleMouseDown (this IViewObject view, NSEvent theEvent) + { + if (view == null) + throw new ArgumentNullException (nameof (view)); + if (theEvent == null) + throw new ArgumentNullException (nameof (theEvent)); + if (view.View == null) + throw new InvalidOperationException (); + CGPoint p = view.View.ConvertPointFromEvent (theEvent); + if (!view.View.Bounds.Contains (p)) + return false; + ButtonEventArgs args = new ButtonEventArgs (); + args.X = p.X; + args.Y = p.Y; + args.Button = theEvent.GetPointerButton (); + args.IsContextMenuTrigger = theEvent.TriggersContextMenu (); + args.MultiplePress = (int)theEvent.ClickCount; + view.Backend.ApplicationContext.InvokeUserCode (delegate + { + view.Backend.EventSink.OnButtonPressed (args); + }); + return args.Handled; + } + + public static bool HandleMouseUp (this IViewObject view, NSEvent theEvent) + { + if (view == null) + throw new ArgumentNullException (nameof (view)); + if (theEvent == null) + throw new ArgumentNullException (nameof (theEvent)); + if (view.View == null) + throw new InvalidOperationException (); + CGPoint p = view.View.ConvertPointFromEvent (theEvent); + if (!view.View.Bounds.Contains (p)) + return false; + ButtonEventArgs args = new ButtonEventArgs (); + args.X = p.X; + args.Y = p.Y; + args.Button = theEvent.GetPointerButton (); + args.MultiplePress = (int)theEvent.ClickCount; + view.Backend.ApplicationContext.InvokeUserCode (delegate { + view.Backend.EventSink.OnButtonReleased (args); + }); + return args.Handled; + } + + public static void HandleMouseEntered (this IViewObject view, NSEvent theEvent) + { + if (view == null) + throw new ArgumentNullException (nameof (view)); + view.Backend.ApplicationContext.InvokeUserCode (view.Backend.EventSink.OnMouseEntered); + } + + public static void HandleMouseExited (this IViewObject view, NSEvent theEvent) + { + if (view == null) + throw new ArgumentNullException (nameof (view)); + view.Backend.ApplicationContext.InvokeUserCode (view.Backend.EventSink.OnMouseExited); + } + + public static bool HandleMouseMoved (this IViewObject view, NSEvent theEvent) + { + if (view == null) + throw new ArgumentNullException (nameof (view)); + if (theEvent == null) + throw new ArgumentNullException (nameof (theEvent)); + if (view.View == null) + throw new InvalidOperationException (); + CGPoint p = view.View.ConvertPointFromEvent (theEvent); + if (!view.View.Bounds.Contains (p)) + return false; + MouseMovedEventArgs args = new MouseMovedEventArgs ((long)TimeSpan.FromSeconds (theEvent.Timestamp).TotalMilliseconds, p.X, p.Y); + view.Backend.ApplicationContext.InvokeUserCode (delegate { + view.Backend.EventSink.OnMouseMoved (args); + }); + return args.Handled; + } + + public static bool HandleKeyDown (this IViewObject view, NSEvent theEvent) + { + if (view == null) + throw new ArgumentNullException (nameof (view)); + if (theEvent == null) + throw new ArgumentNullException (nameof (theEvent)); + if (view.View == null) + throw new InvalidOperationException (); + + var keyArgs = theEvent.ToXwtKeyEventArgs (); + view.Backend.ApplicationContext.InvokeUserCode (delegate { + view.Backend.EventSink.OnKeyPressed (keyArgs); + }); + if (keyArgs.Handled) + return true; + + var textArgs = new TextInputEventArgs (theEvent.Characters); + if (!String.IsNullOrEmpty (theEvent.Characters)) + view.Backend.ApplicationContext.InvokeUserCode (delegate { + view.Backend.EventSink.OnTextInput (textArgs); + }); + if (textArgs.Handled) + return true; + + return false; + } + + public static bool HandleKeyUp (this IViewObject view, NSEvent theEvent) + { + if (view == null) + throw new ArgumentNullException (nameof (view)); + if (theEvent == null) + throw new ArgumentNullException (nameof (theEvent)); + if (view.View == null) + throw new InvalidOperationException (); + + var keyArgs = theEvent.ToXwtKeyEventArgs (); + view.Backend.ApplicationContext.InvokeUserCode (delegate { + view.Backend.EventSink.OnKeyReleased (keyArgs); + }); + return keyArgs.Handled; + } + } } diff --git a/Xwt.XamMac/Xwt.Mac/LabelBackend.cs b/Xwt.XamMac/Xwt.Mac/LabelBackend.cs index cab91899..ba87df67 100644 --- a/Xwt.XamMac/Xwt.Mac/LabelBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/LabelBackend.cs @@ -271,6 +271,30 @@ namespace Xwt.Mac { CGColor bgColor; + public CustomTextFieldCell () + { + } + + protected CustomTextFieldCell (IntPtr ptr) : base (ptr) + { + } + + /// <summary> + /// Like what happens for the ios designer, AppKit can sometimes clone the native `NSTextFieldCell` using the Copy (NSZone) + /// method. We *need* to ensure we can create a new managed wrapper for the cloned native object so we need the IntPtr + /// constructor. NOTE: By keeping this override in managed we ensure the new wrapper C# object is created ~immediately, + /// which makes it easier to debug issues. + /// </summary> + /// <returns>The copy.</returns> + /// <param name="zone">Zone.</param> + public override NSObject Copy(NSZone zone) + { + // Don't remove this override because the comment on this explains why we need this! + var newCell = (CustomTextFieldCell)base.Copy(zone); + newCell.bgColor = bgColor; + return newCell; + } + public void SetBackgroundColor (CGColor c) { bgColor = c; diff --git a/Xwt.XamMac/Xwt.Mac/ListBoxBackend.cs b/Xwt.XamMac/Xwt.Mac/ListBoxBackend.cs index aad70347..d2b9b65d 100644 --- a/Xwt.XamMac/Xwt.Mac/ListBoxBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/ListBoxBackend.cs @@ -32,7 +32,7 @@ namespace Xwt.Mac { public class ListBoxBackend: ListViewBackend, IListBoxBackend { - ListViewColumn column = new ListViewColumn (); + readonly ListViewColumn column = new ListViewColumn { Expands = true }; NSTableColumn columnHandle; public ListBoxBackend () @@ -64,38 +64,6 @@ namespace Xwt.Mac column.Views.Add (v); UpdateColumn (column, columnHandle, ListViewColumnChange.Cells); } - - public override void SetSource (IListDataSource source, IBackend sourceBackend) - { - base.SetSource (source, sourceBackend); - - source.RowInserted += HandleColumnSizeChanged; - source.RowDeleted += HandleColumnSizeChanged; - source.RowChanged += HandleColumnSizeChanged; - ResetColumnSize (source); - } - - void HandleColumnSizeChanged (object sender, ListRowEventArgs e) - { - var source = (IListDataSource)sender; - ResetColumnSize (source); - } - - void ResetColumnSize (IListDataSource source) - { - // Calculate size of column - // This is how Apple implements it; unfortunately, they don't expose this functionality in the API. - // https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSTableViewDelegate_Protocol/index.html#//apple_ref/occ/intfm/NSTableViewDelegate/tableView:sizeToFitWidthOfColumn: - nfloat w = 0; - for (var row = 0; row < source.RowCount; row++) { - using (var cell = Table.GetCell (0, row)) { - var size = cell.CellSize; - w = (nfloat)Math.Max (w, size.Width); - } - } - columnHandle.MinWidth = (nfloat)Math.Ceiling (w); - columnHandle.Width = (nfloat)Math.Ceiling (w); - } } } diff --git a/Xwt.XamMac/Xwt.Mac/ListViewBackend.cs b/Xwt.XamMac/Xwt.Mac/ListViewBackend.cs index c93d90b5..0566e606 100644 --- a/Xwt.XamMac/Xwt.Mac/ListViewBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/ListViewBackend.cs @@ -28,6 +28,8 @@ // THE SOFTWARE. using System; +using System.Collections.Generic; +using System.Linq; using AppKit; using CoreGraphics; using Foundation; @@ -37,12 +39,67 @@ namespace Xwt.Mac { public class ListViewBackend: TableViewBackend<NSTableView, IListViewEventSink>, IListViewBackend { + class ListDelegate: NSTableViewDelegate + { + public ListViewBackend Backend; + + public override nfloat GetRowHeight (NSTableView tableView, nint row) + { + // GetView() and GetRowView() can't be used here, hence we use cached + // sizes calculated by the backend or calcuate the height using the template view + nfloat height = Backend.RowHeights[(int)row]; + if (height <= -1) + height = Backend.RowHeights [(int)row] = Backend.CalcRowHeight (row, false); + return height; + } + + public override NSTableRowView CoreGetRowView (NSTableView tableView, nint row) + { + return tableView.GetRowView (row, false) ?? new TableRowView (); + } + + public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row) + { + var col = tableColumn as TableColumn; + var cell = tableView.MakeView (tableColumn.Identifier, this) as CompositeCell; + if (cell == null) + cell = col.CreateNewView (); + cell.ObjectValue = NSNumber.FromNInt (row); + return cell; + } + + public override nfloat GetSizeToFitColumnWidth (NSTableView tableView, nint column) + { + var tableColumn = Backend.Columns[(int)column] as TableColumn; + var width = tableColumn.HeaderCell.CellSize.Width; + + CompositeCell templateCell = null; + for (int i = 0; i < tableView.RowCount; i++) { + var cellView = tableView.GetView (column, i, false) as CompositeCell; + if (cellView == null) { // use template for invisible rows + cellView = templateCell ?? (templateCell = (tableColumn as TableColumn)?.DataView?.Copy () as CompositeCell); + if (cellView != null) + cellView.ObjectValue = NSNumber.FromInt32 (i); + } + width = (nfloat)Math.Max (width, cellView.FittingSize.Width); + } + return width; + } + + public override NSIndexSet GetSelectionIndexes(NSTableView tableView, NSIndexSet proposedSelectionIndexes) + { + return Backend.SelectionMode != SelectionMode.None ? proposedSelectionIndexes : new NSIndexSet(); + } + } + IListDataSource source; ListSource tsource; protected override NSTableView CreateView () { - return new NSTableViewBackend (EventSink, ApplicationContext); + var listView = new NSTableViewBackend (this); + listView.Delegate = new ListDelegate { Backend = this }; + return listView; } protected override string SelectionChangeEventName { @@ -87,16 +144,64 @@ namespace Xwt.Mac public virtual void SetSource (IListDataSource source, IBackend sourceBackend) { this.source = source; + + RowHeights = new List<nfloat> (); + for (int i = 0; i < source.RowCount; i++) + RowHeights.Add (-1); + tsource = new ListSource (source); Table.DataSource = tsource; //TODO: Reloading single rows would be slightly more efficient. // According to NSTableView.ReloadData() documentation, // only the visible rows are reloaded. - source.RowInserted += (sender, e) => Table.ReloadData(); - source.RowDeleted += (sender, e) => Table.ReloadData(); - source.RowChanged += (sender, e) => Table.ReloadData (NSIndexSet.FromIndex (e.Row), NSIndexSet.FromNSRange (new NSRange(0, Table.ColumnCount - 1))); - source.RowsReordered += (sender, e) => Table.ReloadData(); + source.RowInserted += (sender, e) => { + RowHeights.Insert (e.Row, -1); + Table.ReloadData (); + }; + source.RowDeleted += (sender, e) => { + RowHeights.RemoveAt (e.Row); + Table.ReloadData (); + }; + source.RowChanged += (sender, e) => { + UpdateRowHeight (e.Row); + Table.ReloadData (NSIndexSet.FromIndex (e.Row), NSIndexSet.FromNSRange (new NSRange (0, Table.ColumnCount))); + }; + source.RowsReordered += (sender, e) => { RowHeights.Clear (); Table.ReloadData (); }; + } + + public override void InvalidateRowHeight (object pos) + { + UpdateRowHeight((int)pos); + } + + List<nfloat> RowHeights = new List<nfloat> (); + bool updatingRowHeight; + public void UpdateRowHeight (nint row) + { + if (updatingRowHeight) + return; + RowHeights[(int)row] = CalcRowHeight (row); + Table.NoteHeightOfRowsWithIndexesChanged (NSIndexSet.FromIndex (row)); + } + + nfloat CalcRowHeight (nint row, bool tryReuse = true) + { + updatingRowHeight = true; + var height = Table.RowHeight; + + for (int i = 0; i < Columns.Count; i++) { + CompositeCell cell = tryReuse ? Table.GetView (i, row, false) as CompositeCell : null; + if (cell == null) { + cell = (Columns [i] as TableColumn)?.DataView as CompositeCell; + cell.ObjectValue = NSNumber.FromNInt (row); + height = (nfloat)Math.Max (height, cell.FittingSize.Height); + } else { + height = (nfloat)Math.Max (height, cell.GetRequiredHeightForWidth (cell.Frame.Width)); + } + } + updatingRowHeight = false; + return height; } public int[] SelectedRows { diff --git a/Xwt.XamMac/Xwt.Mac/MenuItemBackend.cs b/Xwt.XamMac/Xwt.Mac/MenuItemBackend.cs index 062775c3..0c5f9f28 100644 --- a/Xwt.XamMac/Xwt.Mac/MenuItemBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/MenuItemBackend.cs @@ -80,6 +80,18 @@ namespace Xwt.Mac } } + public string TooltipText + { + get + { + return item.ToolTip; + } + set + { + item.ToolTip = value; + } + } + public bool UseMnemonic { get diff --git a/Xwt.XamMac/Xwt.Mac/Messaging.cs b/Xwt.XamMac/Xwt.Mac/Messaging.cs index 8f6a20ca..bad8ab8b 100644 --- a/Xwt.XamMac/Xwt.Mac/Messaging.cs +++ b/Xwt.XamMac/Xwt.Mac/Messaging.cs @@ -57,5 +57,23 @@ namespace Xwt.Mac [DllImport (LIBOBJC_DYLIB, EntryPoint="objc_msgSend")] public static extern void void_objc_msgSend_bool (IntPtr handle, IntPtr sel, bool a1); + + //[DllImport (LIBOBJC_DYLIB, EntryPoint = "objc_msgSend")] + //public static extern long Int64_objc_msgSend (IntPtr receiver, IntPtr selector); + + //[DllImport (LIBOBJC_DYLIB, EntryPoint = "objc_msgSend")] + //public static extern int int_objc_msgSend (IntPtr receiver, IntPtr selector); + + [DllImport (LIBOBJC_DYLIB, EntryPoint = "objc_msgSend")] + public static extern void void_objc_msgSend_int (IntPtr receiver, IntPtr selector, int arg1); + + [DllImport (LIBOBJC_DYLIB, EntryPoint = "objc_msgSend")] + public static extern void void_objc_msgSend_Int64 (IntPtr receiver, IntPtr selector, long arg1); + + //[DllImport (LIBOBJC_DYLIB, EntryPoint = "objc_msgSendSuper")] + //public static extern void void_objc_msgSendSuper_int (IntPtr receiver, IntPtr selector, int arg1); + + //[DllImport (LIBOBJC_DYLIB, EntryPoint = "objc_msgSendSuper")] + //public static extern void void_objc_msgSendSuper_Int64 (IntPtr receiver, IntPtr selector, long arg1); } } diff --git a/Xwt.XamMac/Xwt.Mac/NSTableViewBackend.cs b/Xwt.XamMac/Xwt.Mac/NSTableViewBackend.cs index ad3b7ab3..c83d0d77 100644 --- a/Xwt.XamMac/Xwt.Mac/NSTableViewBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/NSTableViewBackend.cs @@ -32,29 +32,14 @@ using AppKit; using CoreGraphics; using Foundation; using Xwt.Backends; +using ObjCRuntime; namespace Xwt.Mac { - public class NSTableViewBackend : NSTableView + public class NSTableViewBackend : NSTableView, IViewObject { - IWidgetEventSink eventSink; - protected ApplicationContext context; NSTrackingArea trackingArea; // Captures Mouse Entered, Exited, and Moved events - class ListDelegate: NSTableViewDelegate - { - public override nfloat GetRowHeight (NSTableView tableView, nint row) - { - var height = tableView.RowHeight; - for (int i = 0; i < tableView.ColumnCount; i++) { - var cell = tableView.GetCell (i, row); - if (cell != null) - height = (nfloat) Math.Max (height, cell.CellSize.Height); - } - return height; - } - } - public override NSObject WeakDataSource { get { return base.WeakDataSource; } set { @@ -71,39 +56,29 @@ namespace Xwt.Mac internal void AutosizeColumns () { + if (DataSource == null || RowCount == 0) + return; var columns = TableColumns (); - foreach (var col in columns) - AutosizeColumn (col); - if (columns.Any (c => c.ResizingMask.HasFlag (NSTableColumnResizing.Autoresizing))) + if (columns.Length == 1 && columns[0].ResizingMask.HasFlag (NSTableColumnResizing.Autoresizing)) + return; + var needsSizeToFit = false; + for (nint i = 0; i < columns.Length; i++) { + AutosizeColumn (columns[i], i); + needsSizeToFit |= columns[i].ResizingMask.HasFlag (NSTableColumnResizing.Autoresizing); + } + if (needsSizeToFit) SizeToFit (); } - void AutosizeColumn (NSTableColumn tableColumn) + void AutosizeColumn (NSTableColumn tableColumn, nint colIndex) { - var column = IndexOfColumn (tableColumn); - - var s = tableColumn.HeaderCell.CellSize; + var contentWidth = tableColumn.HeaderCell.CellSize.Width; if (!tableColumn.ResizingMask.HasFlag (NSTableColumnResizing.UserResizingMask)) { - for (int i = 0; i < base.RowCount; i++) - { - var cell = base.GetCell (column, i); - s.Width = (nfloat)Math.Max (s.Width, cell.CellSize.Width); - } + contentWidth = Delegate.GetSizeToFitColumnWidth (this, colIndex); if (!tableColumn.ResizingMask.HasFlag (NSTableColumnResizing.Autoresizing)) - tableColumn.Width = s.Width; + tableColumn.Width = contentWidth; } - tableColumn.MinWidth = s.Width; - } - - nint IndexOfColumn (NSTableColumn tableColumn) - { - nint icol = -1; - foreach (var col in TableColumns ()) { - icol++; - if (col == tableColumn) - return icol; - } - return icol; + tableColumn.MinWidth = contentWidth; } public override void ReloadData () @@ -123,148 +98,115 @@ namespace Xwt.Mac { if (!columnResizeQueued) { columnResizeQueued = true; - Application.MainLoop.QueueExitAction (delegate { + (Backend.ApplicationContext.Toolkit.GetSafeBackend (Backend.ApplicationContext.Toolkit) as ToolkitEngineBackend).InvokeBeforeMainLoop (delegate { columnResizeQueued = false; AutosizeColumns (); }); } } - public NSTableViewBackend(IWidgetEventSink eventSink, ApplicationContext context) { - this.context = context; - this.eventSink = eventSink; - this.Delegate = new ListDelegate (); + public NSTableViewBackend (ListViewBackend viewBackend) { + Backend = viewBackend; AllowsColumnReordering = false; } - public ViewBackend Backend { get; set; } - public NSTableView View { + public NSView View { get { return this; } } + public override void ResetCursorRects () + { + base.ResetCursorRects (); + if (Backend.Cursor != null) + AddCursorRect (Bounds, Backend.Cursor); + } + public override void UpdateTrackingAreas () { - if (trackingArea != null) { - RemoveTrackingArea (trackingArea); - trackingArea.Dispose (); - } - CGRect viewBounds = this.Bounds; - var options = NSTrackingAreaOptions.MouseMoved | NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.MouseEnteredAndExited; - trackingArea = new NSTrackingArea (viewBounds, options, this, null); - AddTrackingArea (trackingArea); + this.UpdateEventTrackingArea (ref trackingArea); } public override void RightMouseDown (NSEvent theEvent) { - base.RightMouseDown (theEvent); - var p = ConvertPointFromView (theEvent.LocationInWindow, null); - ButtonEventArgs args = new ButtonEventArgs (); - args.X = p.X; - args.Y = p.Y; - args.Button = PointerButton.Right; - args.IsContextMenuTrigger = theEvent.TriggersContextMenu (); - context.InvokeUserCode (delegate { - eventSink.OnButtonPressed (args); - }); + if (!this.HandleMouseDown (theEvent)) + base.RightMouseDown (theEvent); } public override void RightMouseUp (NSEvent theEvent) { - base.RightMouseUp (theEvent); - var p = ConvertPointFromView (theEvent.LocationInWindow, null); - ButtonEventArgs args = new ButtonEventArgs (); - args.X = p.X; - args.Y = p.Y; - args.Button = PointerButton.Right; - context.InvokeUserCode (delegate { - eventSink.OnButtonReleased (args); - }); + if (!this.HandleMouseUp (theEvent)) + base.RightMouseUp (theEvent); } public override void MouseDown (NSEvent theEvent) { - base.MouseDown (theEvent); - var p = ConvertPointFromView (theEvent.LocationInWindow, null); - ButtonEventArgs args = new ButtonEventArgs (); - args.X = p.X; - args.Y = p.Y; - args.Button = PointerButton.Left; - args.IsContextMenuTrigger = theEvent.TriggersContextMenu (); - context.InvokeUserCode (delegate { - eventSink.OnButtonPressed (args); - }); + if (!this.HandleMouseDown (theEvent)) + base.MouseDown (theEvent); } public override void MouseUp (NSEvent theEvent) { - base.MouseUp (theEvent); - var p = ConvertPointFromView (theEvent.LocationInWindow, null); - ButtonEventArgs args = new ButtonEventArgs (); - args.X = p.X; - args.Y = p.Y; - args.Button = (PointerButton) (int)theEvent.ButtonNumber + 1; - context.InvokeUserCode (delegate { - eventSink.OnButtonReleased (args); - }); + if (!this.HandleMouseUp (theEvent)) + base.MouseUp (theEvent); + } + + public override void OtherMouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.OtherMouseDown (theEvent); + } + + public override void OtherMouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.OtherMouseUp (theEvent); } public override void MouseEntered (NSEvent theEvent) { - context.InvokeUserCode (eventSink.OnMouseEntered); + this.HandleMouseEntered (theEvent); } public override void MouseExited (NSEvent theEvent) { - context.InvokeUserCode (eventSink.OnMouseExited); + this.HandleMouseExited (theEvent); } public override void MouseMoved (NSEvent theEvent) { - var p = ConvertPointFromView (theEvent.LocationInWindow, null); - MouseMovedEventArgs args = new MouseMovedEventArgs ((long) TimeSpan.FromSeconds (theEvent.Timestamp).TotalMilliseconds, p.X, p.Y); - context.InvokeUserCode (delegate { - eventSink.OnMouseMoved (args); - }); + if (!this.HandleMouseMoved (theEvent)) + base.MouseMoved (theEvent); } - public override void MouseDragged (NSEvent theEvent) + public override void RightMouseDragged (NSEvent theEvent) { - var p = ConvertPointFromView (theEvent.LocationInWindow, null); - MouseMovedEventArgs args = new MouseMovedEventArgs ((long) TimeSpan.FromSeconds (theEvent.Timestamp).TotalMilliseconds, p.X, p.Y); - context.InvokeUserCode (delegate { - eventSink.OnMouseMoved (args); - }); + if (!this.HandleMouseMoved (theEvent)) + base.RightMouseDragged (theEvent); } - public override void KeyDown (NSEvent theEvent) + public override void MouseDragged (NSEvent theEvent) { - var keyArgs = theEvent.ToXwtKeyEventArgs (); - context.InvokeUserCode (delegate { - eventSink.OnKeyPressed (keyArgs); - }); - if (keyArgs.Handled) - return; + if (!this.HandleMouseMoved (theEvent)) + base.MouseDragged (theEvent); + } - var textArgs = new TextInputEventArgs (theEvent.Characters); - if (!String.IsNullOrEmpty(theEvent.Characters)) - context.InvokeUserCode (delegate { - eventSink.OnTextInput (textArgs); - }); - if (textArgs.Handled) - return; + public override void OtherMouseDragged (NSEvent theEvent) + { + if (!this.HandleMouseMoved (theEvent)) + base.OtherMouseDragged (theEvent); + } - base.KeyDown (theEvent); + public override void KeyDown (NSEvent theEvent) + { + if (!this.HandleKeyDown (theEvent)) + base.KeyDown (theEvent); } public override void KeyUp (NSEvent theEvent) { - var keyArgs = theEvent.ToXwtKeyEventArgs (); - context.InvokeUserCode (delegate { - eventSink.OnKeyReleased (keyArgs); - }); - if (!keyArgs.Handled) + if (!this.HandleKeyUp (theEvent)) base.KeyUp (theEvent); } } diff --git a/Xwt.XamMac/Xwt.Mac/OutlineViewBackend.cs b/Xwt.XamMac/Xwt.Mac/OutlineViewBackend.cs index df44f836..8684ded9 100644 --- a/Xwt.XamMac/Xwt.Mac/OutlineViewBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/OutlineViewBackend.cs @@ -34,23 +34,22 @@ using Xwt.Backends; namespace Xwt.Mac { - public class OutlineViewBackend : NSOutlineView + public class OutlineViewBackend : NSOutlineView, IViewObject { - ITreeViewEventSink eventSink; - protected ApplicationContext context; NSTrackingArea trackingArea; - public OutlineViewBackend (ITreeViewEventSink eventSink, ApplicationContext context) + public OutlineViewBackend (TreeViewBackend viewBackend) { - this.context = context; - this.eventSink = eventSink; + Backend = viewBackend; AllowsColumnReordering = false; } - public NSOutlineView View { + public NSView View { get { return this; } } + public ViewBackend Backend { get; set; } + public override NSObject WeakDataSource { get { return base.WeakDataSource; } set { @@ -59,6 +58,12 @@ namespace Xwt.Mac } } + bool animationsEnabled = true; + public bool AnimationsEnabled { + get { return animationsEnabled; } + set { animationsEnabled = value; } + } + public override void AddColumn (NSTableColumn tableColumn) { base.AddColumn (tableColumn); @@ -67,70 +72,84 @@ namespace Xwt.Mac internal void AutosizeColumns () { + if (DataSource == null || RowCount == 0) + return; var columns = TableColumns (); - foreach (var col in columns) - AutosizeColumn (col); - if (columns.Any (c => c.ResizingMask.HasFlag (NSTableColumnResizing.Autoresizing))) + if (columns.Length == 1 && columns[0].ResizingMask.HasFlag (NSTableColumnResizing.Autoresizing)) + return; + var needsSizeToFit = false; + for (nint i = 0; i < columns.Length; i++) { + AutosizeColumn (columns[i], i); + needsSizeToFit |= columns[i].ResizingMask.HasFlag (NSTableColumnResizing.Autoresizing); + } + if (needsSizeToFit) SizeToFit (); } - void AutosizeColumn (NSTableColumn tableColumn) + void AutosizeColumn (NSTableColumn tableColumn, nint colIndex) { - var column = IndexOfColumn (tableColumn); - - var s = tableColumn.HeaderCell.CellSize; + var contentWidth = tableColumn.HeaderCell.CellSize.Width; if (!tableColumn.ResizingMask.HasFlag (NSTableColumnResizing.UserResizingMask)) { - for (int i = 0; i < base.RowCount; i++) { - var cell = GetCell (column, i); - if (column == 0) - { // first column contains expanders - var f = GetCellFrame (column, i); - s.Width = (nfloat)Math.Max (s.Width, f.X + cell.CellSize.Width); - } - else - s.Width = (nfloat)Math.Max (s.Width, cell.CellSize.Width); - } + contentWidth = Delegate.GetSizeToFitColumnWidth (this, colIndex); if (!tableColumn.ResizingMask.HasFlag (NSTableColumnResizing.Autoresizing)) - tableColumn.Width = s.Width; + tableColumn.Width = contentWidth; } - tableColumn.MinWidth = s.Width; - } - - nint IndexOfColumn (NSTableColumn tableColumn) - { - nint icol = -1; - foreach (var col in TableColumns ()) { - icol++; - if (col == tableColumn) - return icol; - } - return icol; + tableColumn.MinWidth = contentWidth; } public override void ExpandItem (NSObject item) { + BeginExpandCollapseAnimation (); base.ExpandItem (item); + EndExpandCollapseAnimation (); QueueColumnResize (); } public override void ExpandItem (NSObject item, bool expandChildren) { + BeginExpandCollapseAnimation (); base.ExpandItem (item, expandChildren); + EndExpandCollapseAnimation (); QueueColumnResize (); } public override void CollapseItem (NSObject item) { + BeginExpandCollapseAnimation (); base.CollapseItem (item); + EndExpandCollapseAnimation (); QueueColumnResize (); } public override void CollapseItem (NSObject item, bool collapseChildren) { + BeginExpandCollapseAnimation (); base.CollapseItem (item, collapseChildren); + EndExpandCollapseAnimation (); QueueColumnResize (); } + public override void NoteHeightOfRowsWithIndexesChanged(NSIndexSet indexSet) + { + BeginExpandCollapseAnimation(); + base.NoteHeightOfRowsWithIndexesChanged(indexSet); + EndExpandCollapseAnimation(); + } + + void BeginExpandCollapseAnimation () + { + if (!AnimationsEnabled) { + NSAnimationContext.BeginGrouping (); + NSAnimationContext.CurrentContext.Duration = 0; + } + } + + void EndExpandCollapseAnimation () + { + if (!AnimationsEnabled) + NSAnimationContext.EndGrouping (); + } + public override void ReloadData () { base.ReloadData (); @@ -160,109 +179,105 @@ namespace Xwt.Mac { if (!columnResizeQueued) { columnResizeQueued = true; - Application.MainLoop.QueueExitAction (delegate { + (Backend.ApplicationContext.Toolkit.GetSafeBackend (Backend.ApplicationContext.Toolkit) as ToolkitEngineBackend).InvokeBeforeMainLoop (delegate { columnResizeQueued = false; AutosizeColumns (); }); } } + public override void ResetCursorRects () + { + base.ResetCursorRects (); + if (Backend.Cursor != null) + AddCursorRect (Bounds, Backend.Cursor); + } + public override void UpdateTrackingAreas () { - if (trackingArea != null) { - RemoveTrackingArea (trackingArea); - trackingArea.Dispose (); - } - var viewBounds = this.Bounds; - var options = NSTrackingAreaOptions.MouseMoved | NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.MouseEnteredAndExited; - trackingArea = new NSTrackingArea (viewBounds, options, this, null); - AddTrackingArea (trackingArea); + this.UpdateEventTrackingArea (ref trackingArea); } public override void RightMouseDown (NSEvent theEvent) { - base.RightMouseUp (theEvent); - var p = ConvertPointFromView (theEvent.LocationInWindow, null); - ButtonEventArgs args = new ButtonEventArgs (); - args.X = p.X; - args.Y = p.Y; - args.Button = PointerButton.Right; - args.IsContextMenuTrigger = theEvent.TriggersContextMenu (); - context.InvokeUserCode (delegate { - eventSink.OnButtonPressed (args); - }); + if (!this.HandleMouseDown (theEvent)) + base.RightMouseDown (theEvent); } public override void RightMouseUp (NSEvent theEvent) { - base.RightMouseUp (theEvent); - var p = ConvertPointFromView (theEvent.LocationInWindow, null); - ButtonEventArgs args = new ButtonEventArgs (); - args.X = p.X; - args.Y = p.Y; - args.Button = PointerButton.Right; - context.InvokeUserCode (delegate { - eventSink.OnButtonReleased (args); - }); + if (!this.HandleMouseUp (theEvent)) + base.RightMouseUp (theEvent); } public override void MouseDown (NSEvent theEvent) { - base.MouseDown (theEvent); - var p = ConvertPointFromView (theEvent.LocationInWindow, null); - ButtonEventArgs args = new ButtonEventArgs (); - args.X = p.X; - args.Y = p.Y; - args.Button = PointerButton.Left; - args.IsContextMenuTrigger = theEvent.TriggersContextMenu (); - context.InvokeUserCode (delegate { - eventSink.OnButtonPressed (args); - }); + if (!this.HandleMouseDown (theEvent)) + base.MouseDown (theEvent); } public override void MouseUp (NSEvent theEvent) { - base.MouseUp (theEvent); - var p = ConvertPointFromView (theEvent.LocationInWindow, null); - ButtonEventArgs args = new ButtonEventArgs (); - args.X = p.X; - args.Y = p.Y; - args.Button = (PointerButton) (int) theEvent.ButtonNumber + 1; - context.InvokeUserCode (delegate { - eventSink.OnButtonReleased (args); - }); + if (!this.HandleMouseUp (theEvent)) + base.MouseUp (theEvent); + } + + public override void OtherMouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.OtherMouseDown (theEvent); + } + + public override void OtherMouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.OtherMouseUp (theEvent); } public override void MouseEntered (NSEvent theEvent) { - base.MouseEntered (theEvent); - context.InvokeUserCode (eventSink.OnMouseEntered); + this.HandleMouseEntered (theEvent); } public override void MouseExited (NSEvent theEvent) { - base.MouseExited (theEvent); - context.InvokeUserCode (eventSink.OnMouseExited); + this.HandleMouseExited (theEvent); } public override void MouseMoved (NSEvent theEvent) { - base.MouseMoved (theEvent); - var p = ConvertPointFromView (theEvent.LocationInWindow, null); - MouseMovedEventArgs args = new MouseMovedEventArgs ((long) TimeSpan.FromSeconds (theEvent.Timestamp).TotalMilliseconds, p.X, p.Y); - context.InvokeUserCode (delegate { - eventSink.OnMouseMoved (args); - }); + if (!this.HandleMouseMoved (theEvent)) + base.MouseMoved (theEvent); + } + + public override void RightMouseDragged (NSEvent theEvent) + { + if (!this.HandleMouseMoved (theEvent)) + base.RightMouseDragged (theEvent); } public override void MouseDragged (NSEvent theEvent) { - base.MouseDragged (theEvent); - var p = ConvertPointFromView (theEvent.LocationInWindow, null); - MouseMovedEventArgs args = new MouseMovedEventArgs ((long) TimeSpan.FromSeconds (theEvent.Timestamp).TotalMilliseconds, p.X, p.Y); - context.InvokeUserCode (delegate { - eventSink.OnMouseMoved (args); - }); + if (!this.HandleMouseMoved (theEvent)) + base.MouseDragged (theEvent); + } + + public override void OtherMouseDragged (NSEvent theEvent) + { + if (!this.HandleMouseMoved (theEvent)) + base.OtherMouseDragged (theEvent); + } + + public override void KeyDown (NSEvent theEvent) + { + if (!this.HandleKeyDown (theEvent)) + base.KeyDown (theEvent); + } + + public override void KeyUp (NSEvent theEvent) + { + if (!this.HandleKeyUp (theEvent)) + base.KeyUp (theEvent); } } }
\ No newline at end of file diff --git a/Xwt.XamMac/Xwt.Mac/TableViewBackend.cs b/Xwt.XamMac/Xwt.Mac/TableViewBackend.cs index 726f8cd1..9a4dda4f 100644 --- a/Xwt.XamMac/Xwt.Mac/TableViewBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/TableViewBackend.cs @@ -27,8 +27,10 @@ using System; using System.Collections.Generic; using AppKit; +using CoreGraphics; using Foundation; using Xwt.Backends; +using Xwt.Drawing; namespace Xwt.Mac { @@ -40,11 +42,17 @@ namespace Xwt.Mac ScrollView scroll; NSObject selChangeObserver; NormalClipView clipView; - - public TableViewBackend () - { + + NSTableView ICellSource.TableView { get { return Table; } } + + List<NSTableColumn> ICellSource.Columns { + get { return cols; } } - + + protected List<NSTableColumn> Columns { + get { return cols; } + } + public override void Initialize () { Table = CreateView (); @@ -173,36 +181,23 @@ namespace Xwt.Mac { ApplicationContext.InvokeUserCode (EventSink.OnSelectionChanged); } + + public SelectionMode SelectionMode { get; private set; } public void SetSelectionMode (SelectionMode mode) { + SelectionMode = mode; Table.AllowsMultipleSelection = mode == SelectionMode.Multiple; + if (mode == SelectionMode.None && Table.SelectedRowCount > 0) + UnselectAll (); } public virtual NSTableColumn AddColumn (ListViewColumn col) { - var tcol = new NSTableColumn (); - tcol.Editable = true; + var tcol = new TableColumn (ApplicationContext, this, Table); cols.Add (tcol); - var c = CellUtil.CreateCell (ApplicationContext, Table, this, col.Views, cols.Count - 1); - tcol.DataCell = c; + tcol.UpdateColumn (col); Table.AddColumn (tcol); - var hc = new NSTableHeaderCell (); - hc.Title = col.Title ?? ""; - tcol.HeaderCell = hc; - tcol.HeaderCell.Alignment = col.Alignment.ToNSTextAlignment(); - - - if (col.CanResize) - tcol.ResizingMask |= NSTableColumnResizing.UserResizingMask; - else - tcol.ResizingMask &= ~NSTableColumnResizing.UserResizingMask; - if (col.Expands) - tcol.ResizingMask |= NSTableColumnResizing.Autoresizing; - else - tcol.ResizingMask &= ~NSTableColumnResizing.Autoresizing; - tcol.SizeToFit(); - Widget.InvalidateIntrinsicContentSize (); return tcol; } object IColumnContainerBackend.AddColumn (ListViewColumn col) @@ -212,72 +207,41 @@ namespace Xwt.Mac public void RemoveColumn (ListViewColumn col, object handle) { - Table.RemoveColumn ((NSTableColumn)handle); + var tcol = (NSTableColumn)handle; + cols.Remove (tcol); + Table.RemoveColumn (tcol); } public void UpdateColumn (ListViewColumn col, object handle, ListViewColumnChange change) { - NSTableColumn tcol = (NSTableColumn) handle; - - switch (change) { - case ListViewColumnChange.CanResize: - if (col.CanResize) - tcol.ResizingMask |= NSTableColumnResizing.UserResizingMask; - else - tcol.ResizingMask &= ~NSTableColumnResizing.UserResizingMask; - break; - case ListViewColumnChange.Expanding: - if (col.Expands) - tcol.ResizingMask |= NSTableColumnResizing.Autoresizing; - else - tcol.ResizingMask &= ~NSTableColumnResizing.Autoresizing; - break; - case ListViewColumnChange.Cells: - var c = CellUtil.CreateCell(ApplicationContext, Table, this, col.Views, cols.IndexOf(tcol)); - c.Alignment = col.Alignment.ToNSTextAlignment(); - tcol.DataCell = c; - break; - case ListViewColumnChange.Title: - tcol.HeaderCell.Title = col.Title ?? string.Empty; - if (!col.CanResize) - tcol.SizeToFit(); - break; - case ListViewColumnChange.Alignment: - tcol.HeaderCell.Alignment = col.Alignment.ToNSTextAlignment(); - break; - } + var tcol = handle as TableColumn; + if (tcol != null) + tcol.UpdateColumn (col, change); } public Rectangle GetCellBounds (int row, CellView cell, bool includeMargin) { + var rect = Rectangle.Zero; var cellBackend = cell.GetBackend () as CellViewBackend; - var r = Table.GetCellFrame (cellBackend.Column, row); - var container = Table.GetCell (cellBackend.Column, row) as CompositeCell; - r = container.GetCellRect (r, (NSCell)cellBackend.CurrentCell); - r.Y -= scroll.DocumentVisibleRect.Y; - r.X -= scroll.DocumentVisibleRect.X; - if (HeadersVisible) - r.Y += Table.HeaderView.Frame.Height; - return new Rectangle (r.X, r.Y, r.Width, r.Height); + var container = Table.GetView (cellBackend.Column, row, false) as CompositeCell; + if (container != null) { + var cellView = container.GetCellViewForBackend (cellBackend); + rect = cellView.ConvertRectToView (new CGRect (CGPoint.Empty, cellView.Frame.Size), Table).ToXwtRect (); + rect.Y -= scroll.DocumentVisibleRect.Y; + rect.X -= scroll.DocumentVisibleRect.X; + } + return rect; } public Rectangle GetRowBounds (int row, bool includeMargin) { var rect = Rectangle.Zero; - var columns = Table.TableColumns (); - - for (int i = 0; i < columns.Length; i++) - { - var r = Table.GetCellFrame (i, row); - if (rect == Rectangle.Zero) - rect = new Rectangle (r.X, r.Y, r.Width, r.Height); - else - rect = rect.Union (new Rectangle (r.X, r.Y, r.Width, r.Height)); + var rowView = Table.GetRowView (row, false); + if (rowView != null) { + rect = rowView.Frame.ToXwtRect (); + rect.Y -= scroll.DocumentVisibleRect.Y; + rect.X -= scroll.DocumentVisibleRect.X; } - rect.Y -= scroll.DocumentVisibleRect.Y; - rect.X -= scroll.DocumentVisibleRect.X; - if (HeadersVisible) - rect.Y += Table.HeaderView.Frame.Height; return rect; } @@ -307,11 +271,8 @@ namespace Xwt.Mac public abstract void SetCurrentEventRow (object pos); - nfloat ICellSource.RowHeight { - get { return Table.RowHeight; } - set { Table.RowHeight = value; } - } - + public abstract void InvalidateRowHeight (object pos); + public bool BorderVisible { get { return scroll.BorderType == NSBorderType.BezelBorder;} set { @@ -319,6 +280,11 @@ namespace Xwt.Mac } } + public bool UseAlternatingRowColors { + get { return Table.UsesAlternatingRowBackgroundColors; } + set { Table.UsesAlternatingRowBackgroundColors = value; } + } + public bool HeadersVisible { get { return Table.HeaderView != null; @@ -338,6 +304,170 @@ namespace Xwt.Mac get { return Table.GridStyleMask.ToXwtValue (); } set { Table.GridStyleMask = value.ToMacValue (); } } + + public override Color BackgroundColor + { + get { return Table.BackgroundColor.ToXwtColor (); } + set { Table.BackgroundColor = value.ToNSColor (); } + } + } + + class TableColumn : NSTableColumn + { + readonly ICellSource backend; + readonly ApplicationContext context; + + public CompositeCell DataView { get; private set; } + + List<WeakReference> CachedViews = new List<WeakReference> (); + + public TableColumn (ApplicationContext context, ICellSource backend, NSTableView table) + { + this.context = context; + Identifier = GetHashCode ().ToString (); // this is used to identify cached views + this.backend = backend; + TableView = table; + } + + public CompositeCell CreateNewView () + { + CleanViewCache (); + var view = DataView.Copy () as CompositeCell; + view.Identifier = Identifier; + // Cocoa will manage the native views in the background and eventually dispose + // them without letting us know. In order to keep track of the active views + // we store a weak ref for each view to not disturb the internal Cocoa caching login. + CachedViews.Add (new WeakReference (view)); + return view; + } + + void UpdateCachedViews (ICollection<CellView> cells) + { + if (CachedViews.Count == 0) + return; + var col = backend.Columns.IndexOf (this); + foreach (var cached in CachedViews) { + // update only the alive and not disposed views + if (cached.IsAlive) { + var view = cached.Target as CompositeCell; + if (view?.IsDisposed == false) + CellUtil.UpdateCellView (view, backend, cells, col); + } + } + + CleanViewCache (); + } + + void CleanViewCache () + { + // remove any GCd and/or disposed views + CachedViews.RemoveAll (c => { + if (!c.IsAlive) + return true; + var cell = c.Target as CompositeCell; + if (cell == null || cell.IsDisposed) + return true; + return false; + }); + } + + public void UpdateColumn (ListViewColumn col) + { + Editable = true; + var hc = new NSTableHeaderCell { + Title = col.Title ?? string.Empty + }; + HeaderCell = hc; + HeaderCell.Alignment = col.Alignment.ToNSTextAlignment (); + + DataView = CellUtil.CreateCellView (context, backend, col.Views, backend.Columns.IndexOf (this)); + DataView.Identifier = Identifier; + UpdateCachedViews (col.Views); + + if (col.CanResize) + ResizingMask |= NSTableColumnResizing.UserResizingMask; + else + ResizingMask &= ~NSTableColumnResizing.UserResizingMask; + if (col.Expands) + ResizingMask |= NSTableColumnResizing.Autoresizing; + else + ResizingMask &= ~NSTableColumnResizing.Autoresizing; + SizeToFit (); + TableView?.InvalidateIntrinsicContentSize (); + } + + public void UpdateColumn (ListViewColumn col, ListViewColumnChange change) + { + if (TableView == null) + throw new InvalidOperationException ("Add the column to a table first"); + switch (change) { + case ListViewColumnChange.CanResize: + if (col.CanResize) + ResizingMask |= NSTableColumnResizing.UserResizingMask; + else + ResizingMask &= ~NSTableColumnResizing.UserResizingMask; + break; + case ListViewColumnChange.Expanding: + if (col.Expands) + ResizingMask |= NSTableColumnResizing.Autoresizing; + else + ResizingMask &= ~NSTableColumnResizing.Autoresizing; + break; + case ListViewColumnChange.Cells: + DataView = CellUtil.CreateCellView (context, backend, col.Views, backend.Columns.IndexOf (this)); + DataView.Identifier = Identifier; + UpdateCachedViews (col.Views); + TableView.ReloadData (); + break; + case ListViewColumnChange.Title: + HeaderCell.Title = col.Title ?? string.Empty; + if (!col.CanResize) + SizeToFit (); + break; + case ListViewColumnChange.Alignment: + HeaderCell.Alignment = col.Alignment.ToNSTextAlignment (); + break; + } + } + } + + class TableRowView : NSTableRowView + { + + public override bool Selected { + get { + return base.Selected; + } + set { + base.Selected = value; + // the first time NSTableView is presented the background + // may be drawn already and it will not be redrawn even + // if Selection has been changed. + NeedsDisplay = true; + } + } + + public override void DrawSelection (CGRect dirtyRect) + { + if (EffectiveAppearance.Name == NSAppearance.NameVibrantDark && + SelectionHighlightStyle != NSTableViewSelectionHighlightStyle.None) { + (Selected ? NSColor.AlternateSelectedControl : BackgroundColor).SetFill (); + var path = NSBezierPath.FromRect (dirtyRect); + path.Fill (); + } else + base.DrawSelection (dirtyRect); + } + + public override void DrawBackground (CGRect dirtyRect) + { + if (Selected && EffectiveAppearance.Name == NSAppearance.NameVibrantDark && + SelectionHighlightStyle != NSTableViewSelectionHighlightStyle.None) { + (Selected ? NSColor.AlternateSelectedControl : BackgroundColor).SetFill (); + var path = NSBezierPath.FromRect (dirtyRect); + path.Fill (); + } else + base.DrawBackground (dirtyRect); + } } class ScrollView: NSScrollView, IViewObject diff --git a/Xwt.XamMac/Xwt.Mac/TextEntryBackend.cs b/Xwt.XamMac/Xwt.Mac/TextEntryBackend.cs index e6d81734..fe11c70f 100644 --- a/Xwt.XamMac/Xwt.Mac/TextEntryBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/TextEntryBackend.cs @@ -356,6 +356,29 @@ namespace Xwt.Mac } + protected CustomCell(IntPtr ptr) : base(ptr) + { + } + + /// <summary> + /// Like what happens for the ios designer, AppKit can sometimes clone the native `NSTextFieldCell` using the Copy (NSZone) + /// method. We *need* to ensure we can create a new managed wrapper for the cloned native object so we need the IntPtr + /// constructor. NOTE: By keeping this override in managed we ensure the new wrapper C# object is created ~immediately, + /// which makes it easier to debug issues. + /// </summary> + /// <returns>The copy.</returns> + /// <param name="zone">Zone.</param> + public override NSObject Copy(NSZone zone) + { + // Don't remove this override because the comment on this explains why we need this! + var newCell = (CustomCell)base.Copy(zone); + newCell.editor = editor; + newCell.selChangeObserver = selChangeObserver; + newCell.Context = Context; + newCell.EventSink = EventSink; + return newCell; + } + public override NSTextView FieldEditorForView (NSView aControlView) { if (editor == null) { diff --git a/Xwt.XamMac/Xwt.Mac/TextLayoutBackendHandler.cs b/Xwt.XamMac/Xwt.Mac/TextLayoutBackendHandler.cs index 918d23af..cd4fd5a1 100644 --- a/Xwt.XamMac/Xwt.Mac/TextLayoutBackendHandler.cs +++ b/Xwt.XamMac/Xwt.Mac/TextLayoutBackendHandler.cs @@ -26,6 +26,7 @@ using System; using System.Collections.Generic; +using System.Linq; using AppKit; using CoreGraphics; using CoreText; @@ -37,19 +38,291 @@ namespace Xwt.Mac { public class MacTextLayoutBackendHandler: TextLayoutBackendHandler { - class LayoutInfo + class LayoutInfo : IDisposable { - public string Text = String.Empty; - public NSFont Font; - public float? Width, Height; - public TextTrimming TextTrimming; - public Alignment TextAlignment; - readonly public List<TextAttribute> Attributes = new List<TextAttribute> (); - readonly public ApplicationContext ApplicationContext; + string text = String.Empty; + NSFont font; + float? width, height; + TextTrimming textTrimming; + Alignment textAlignment; + readonly public List<TextAttribute> Attributes; + readonly ApplicationContext ApplicationContext; + readonly NSTextStorage TextStorage; + readonly NSTextContainer TextContainer; + + public string Text + { + get { return text; } + set { + text = value; + Attributes.Clear (); + ResetAttributes (); + } + } + + public NSFont Font + { + get { return font; } + set { + font = value; + ResetAttributes (); + } + } + + public TextTrimming TextTrimming + { + get { return textTrimming; } + set { + textTrimming = value; + ResetAttributes (); + } + } + + public Alignment TextAlignment + { + get { return textAlignment; } + set + { + textAlignment = value; + ResetAttributes (); + } + } + + public float? Width + { + get { return width; } + set + { + width = value; + TextContainer.Size = new CGSize (value ?? double.MaxValue, TextContainer.Size.Height); + } + } + + public float? Height + { + get { return height; } + set + { + height = value; + TextContainer.Size = new CGSize (TextContainer.Size.Width, value ?? double.MaxValue); + } + } public LayoutInfo (ApplicationContext actx) { ApplicationContext = actx; + Attributes = new List<TextAttribute> (); + TextStorage = new NSTextStorage (); + TextContainer = new NSTextContainer { + LineFragmentPadding = 0.0f + }; + } + + public void AddAttribute (TextAttribute attribute) + { + Attributes.Add (attribute); + AddAttributeInternal (attribute); + } + + public void ClearAttributes () + { + Attributes.Clear (); + ResetAttributes (); + } + + void ResetAttributes (NSColor foregroundColorOverride = null) + { + // clear user attributes + TextStorage.SetAttributes (new NSDictionary (), new NSRange (0, TextStorage.Length)); + + var r = new NSRange (0, Text.Length); + + TextStorage.SetString (new NSAttributedString (Text)); + TextStorage.SetAlignment (TextAlignment.ToNSTextAlignment (), r); + + if (Font != null) + // set a global font + TextStorage.AddAttribute (NSStringAttributeKey.Font, Font, r); + + // paragraph style + TextContainer.LineBreakMode = TextTrimming == TextTrimming.WordElipsis ? NSLineBreakMode.TruncatingTail : NSLineBreakMode.ByWordWrapping; + var pstyle = NSParagraphStyle.DefaultParagraphStyle.MutableCopy () as NSMutableParagraphStyle; + pstyle.Alignment = TextAlignment.ToNSTextAlignment (); + if (TextTrimming == TextTrimming.WordElipsis) + pstyle.LineBreakMode = NSLineBreakMode.TruncatingTail; + TextStorage.AddAttribute (NSStringAttributeKey.ParagraphStyle, pstyle, r); + + // set foreground color override + if (foregroundColorOverride != null) + TextStorage.AddAttribute(NSStringAttributeKey.ForegroundColor, foregroundColorOverride, r); + + // restore user attributes + foreach (var att in Attributes) + AddAttributeInternal (att); + } + + void AddAttributeInternal (TextAttribute attribute) + { + var r = new NSRange (attribute.StartIndex, attribute.Count); + + if (attribute is BackgroundTextAttribute) + { + var xa = (BackgroundTextAttribute)attribute; + TextStorage.AddAttribute (NSStringAttributeKey.BackgroundColor, xa.Color.ToNSColor (), r); + } + else if (attribute is ColorTextAttribute) + { + var xa = (ColorTextAttribute)attribute; + TextStorage.AddAttribute (NSStringAttributeKey.ForegroundColor, xa.Color.ToNSColor (), r); + } + else if (attribute is UnderlineTextAttribute) + { + var xa = (UnderlineTextAttribute)attribute; + var style = xa.Underline ? NSUnderlineStyle.Single : NSUnderlineStyle.None; + TextStorage.AddAttribute(NSStringAttributeKey.UnderlineStyle, NSNumber.FromInt32 ((int)style), r); + } + else if (attribute is FontStyleTextAttribute) + { + var xa = (FontStyleTextAttribute)attribute; + if (xa.Style == FontStyle.Italic) + { + TextStorage.ApplyFontTraits (NSFontTraitMask.Italic, r); + } + else if (xa.Style == FontStyle.Oblique) + { + // copy Pango.Style.Oblique behaviour (25% skew) + TextStorage.AddAttribute (NSStringAttributeKey.Obliqueness, NSNumber.FromFloat ((float)0.25), r); + } + else + { + TextStorage.RemoveAttribute (NSStringAttributeKey.Obliqueness, r); + TextStorage.ApplyFontTraits (NSFontTraitMask.Unitalic, r); + } + } + else if (attribute is FontWeightTextAttribute) + { + var xa = (FontWeightTextAttribute)attribute; + NSRange er; + // get the effective font to modify for the given range + var ft = TextStorage.GetAttribute (NSStringAttributeKey.Font, attribute.StartIndex, out er, r) as NSFont; + ft = ft.WithWeight (xa.Weight); + TextStorage.AddAttribute (NSStringAttributeKey.Font, ft, r); + } + else if (attribute is LinkTextAttribute) + { + TextStorage.AddAttribute (NSStringAttributeKey.ForegroundColor, Toolkit.CurrentEngine.Defaults.FallbackLinkColor.ToNSColor (), r); + TextStorage.AddAttribute (NSStringAttributeKey.UnderlineStyle, NSNumber.FromInt32 ((int)NSUnderlineStyle.Single), r); + } + else if (attribute is StrikethroughTextAttribute) + { + var xa = (StrikethroughTextAttribute)attribute; + var style = xa.Strikethrough ? NSUnderlineStyle.Single : NSUnderlineStyle.None; + TextStorage.AddAttribute (NSStringAttributeKey.StrikethroughStyle, NSNumber.FromInt32 ((int)style), r); + } + else if (attribute is FontTextAttribute) + { + var xa = (FontTextAttribute)attribute; + var nf = ((FontData)ApplicationContext.Toolkit.GetSafeBackend (xa.Font)).Font; + TextStorage.AddAttribute (NSStringAttributeKey.Font, nf, r); + } + } + + public void Draw (CGContext ctx, CGColor foregroundColor, double x, double y) + { + bool tempForegroundSet = false; + // if no color attribute is set for the whole string, + // NSLayoutManager will use the default control foreground color. + // To override the default color we need to apply the current CGContext stroke color + // before all other attributes are set, otherwise it will remove all other foreground colors. + if (foregroundColor != null && !Attributes.Any (a => a is ColorTextAttribute && a.StartIndex == 0 && a.Count == Text.Length)) + { + // FIXME: we need to find a better way to accomplish this without the need to reset all attributes. + ResetAttributes(NSColor.FromCGColor(foregroundColor)); + tempForegroundSet = true; + } + + ctx.SaveState (); + NSGraphicsContext.GlobalSaveGraphicsState (); + var nsContext = NSGraphicsContext.FromCGContext (ctx, true); + NSGraphicsContext.CurrentContext = nsContext; + + using (var TextLayout = new NSLayoutManager ()) + { + TextLayout.AddTextContainer (TextContainer); + TextStorage.AddLayoutManager (TextLayout); + + TextLayout.DrawBackgroundForGlyphRange (new NSRange(0, Text.Length), new CGPoint (x, y)); + TextLayout.DrawGlyphsForGlyphRange (new NSRange(0, Text.Length), new CGPoint (x, y)); + TextStorage.RemoveLayoutManager (TextLayout); + TextLayout.RemoveTextContainer (0); + } + + // reset foreground color change + if (tempForegroundSet) + ResetAttributes(); + + NSGraphicsContext.GlobalRestoreGraphicsState (); + ctx.RestoreState (); + } + + public CGSize GetSize () + { + using (var TextLayout = new NSLayoutManager ()) + { + TextLayout.AddTextContainer (TextContainer); + TextStorage.AddLayoutManager (TextLayout); + TextLayout.GlyphRangeForBoundingRect (new CGRect (CGPoint.Empty, TextContainer.Size), TextContainer); + var s = TextLayout.GetUsedRectForTextContainer (TextContainer); + TextStorage.RemoveLayoutManager (TextLayout); + TextLayout.RemoveTextContainer (0); + return s.Size; + } + } + + public double GetBaseLine () + { + using (var line = new CTLine (TextStorage)) + { + nfloat ascent, descent, leading; + line.GetTypographicBounds (out ascent, out descent, out leading); + return ascent; + } + } + + public nuint GetIndexFromCoordinates (double x, double y) + { + using (var TextLayout = new NSLayoutManager ()) + { + TextLayout.AddTextContainer (TextContainer); + TextStorage.AddLayoutManager (TextLayout); + TextLayout.GlyphRangeForBoundingRect (new CGRect (CGPoint.Empty, TextContainer.Size), TextContainer); + nfloat fraction = 0; + var index = TextLayout.CharacterIndexForPoint (new CGPoint (x, y), TextContainer, ref fraction); + TextStorage.RemoveLayoutManager (TextLayout); + TextLayout.RemoveTextContainer (0); + return index; + } + } + + public CGPoint GetCoordinateFromIndex(int index) + { + using (var TextLayout = new NSLayoutManager ()) + { + TextLayout.AddTextContainer (TextContainer); + TextStorage.AddLayoutManager (TextLayout); + TextLayout.GlyphRangeForBoundingRect (new CGRect (CGPoint.Empty, TextContainer.Size), TextContainer); + var glyphIndex = TextLayout.GlyphIndexForCharacterAtIndex (index); + var p = TextLayout.LocationForGlyphAtIndex ((nint)glyphIndex); + TextStorage.RemoveLayoutManager (TextLayout); + TextLayout.RemoveTextContainer (0); + return p; + } + } + + public void Dispose () + { + TextStorage.Dispose (); + TextContainer.Dispose (); } } @@ -64,7 +337,7 @@ namespace Xwt.Mac li.Text = text == null ? String.Empty : text.Replace ("\r\n", "\n"); } - public override void SetFont (object backend, Xwt.Drawing.Font font) + public override void SetFont (object backend, Font font) { LayoutInfo li = (LayoutInfo)backend; li.Font = ((FontData)ApplicationContext.Toolkit.GetSafeBackend (font)).Font; @@ -82,10 +355,10 @@ namespace Xwt.Mac li.Height = value < 0 ? null : (float?)value; } - public override void SetTrimming (object backend, TextTrimming value) + public override void SetTrimming (object backend, TextTrimming textTrimming) { LayoutInfo li = (LayoutInfo)backend; - li.TextTrimming = value; + li.TextTrimming = textTrimming; } public override void SetAlignment (object backend, Alignment alignment) @@ -97,53 +370,13 @@ namespace Xwt.Mac public override Size GetSize (object backend) { LayoutInfo li = (LayoutInfo)backend; - using (CTFrame frame = CreateFrame (li)) { - if (frame == null) - return Size.Zero; - - Size result = Size.Zero; - CTLine [] lines = frame.GetLines (); - nfloat lineHeight = li.Font.Ascender - li.Font.Descender + li.Font.Leading; - - CTLine ellipsis = null; - bool ellipsize = li.Width.HasValue && li.TextTrimming == TextTrimming.WordElipsis; - if (ellipsize) - ellipsis = new CTLine (CreateAttributedString (li, "...")); - - // try to approximate Pango's layout - foreach (var line in lines) { - var l = line; - if (ellipsize) { // we need to create a new CTLine here because the framesetter already truncated the text for the line - l = new CTLine (CreateAttributedString (li, li.Text.Substring ((int)line.StringRange.Location))) - .GetTruncatedLine (li.Width.Value, CTLineTruncation.End, ellipsis); - line.Dispose (); - } - - result.Width = Math.Max (result.Width, GetLineWidth (l)); - result.Height += lineHeight; - - // clean up after ourselves as we go - l.Dispose (); - } - - // CoreText throws away trailing line breaks.. - if (li.Text.EndsWith ("\n")) - result.Height += lineHeight; - - result.Width = Math.Ceiling (result.Width); - result.Height = Math.Ceiling (result.Height); - return result; - } + return li.GetSize().ToXwtSize(); } public override double GetBaseline (object backend) { LayoutInfo li = (LayoutInfo)backend; - using (var line = new CTLine (CreateAttributedString (li))) { - nfloat ascent, descent, leading; - line.GetTypographicBounds (out ascent, out descent, out leading); - return (double)ascent; - } + return li.GetBaseLine(); } public override double GetMeanline (object backend) @@ -151,167 +384,40 @@ namespace Xwt.Mac LayoutInfo li = (LayoutInfo)backend; return GetBaseline (backend) - li.Font.XHeight / 2; } - - static CTFrame CreateFrame (LayoutInfo li) - { - if (string.IsNullOrEmpty (li.Text)) - return null; - - using (CTFramesetter framesetter = new CTFramesetter (CreateAttributedString (li))) { - CGPath path = new CGPath (); - bool ellipsize = li.Width.HasValue && li.TextTrimming == TextTrimming.WordElipsis; - path.AddRect (new CGRect (0, 0, li.Width.HasValue && !ellipsize ? li.Width.Value : float.MaxValue, li.Height ?? float.MaxValue)); - - return framesetter.GetFrame (new NSRange (0, li.Text.Length), path, null); - } - } - - static NSAttributedString CreateAttributedString (LayoutInfo li, string overrideText = null) - { - if (overrideText != null || li.Attributes.Count == 0) - return CreateAttributedString (overrideText ?? li.Text, li.Font); - - var ns = new NSMutableAttributedString (li.Text); - ns.BeginEditing (); - var r = new NSRange (0, li.Text.Length); - if (li.Font != null) - ns.AddAttribute (CTStringAttributeKey.Font, li.Font, r); - ns.AddAttribute (CTStringAttributeKey.ForegroundColorFromContext, new NSNumber (true), r); - - foreach (var att in li.Attributes) { - r = new NSRange (att.StartIndex, att.Count); - if (att is BackgroundTextAttribute) { - var xa = (BackgroundTextAttribute)att; - ns.AddAttribute (CTStringAttributeKey.BackgroundColor, xa.Color.ToNSColor (), r); - } else if (att is ColorTextAttribute) { - var xa = (ColorTextAttribute)att; - // FIXME: CTStringAttributeKey.ForegroundColor has no effect - ns.AddAttribute (CTStringAttributeKey.ForegroundColor, xa.Color.ToNSColor (), r); - } else if (att is UnderlineTextAttribute) { - var xa = (UnderlineTextAttribute)att; - var style = xa.Underline ? CTUnderlineStyle.Single : CTUnderlineStyle.None; - ns.AddAttribute (CTStringAttributeKey.UnderlineStyle, NSNumber.FromInt32 ((int)style), r); - } else if (att is FontStyleTextAttribute) { - var xa = (FontStyleTextAttribute)att; - if (xa.Style == FontStyle.Italic) { - ns.ApplyFontTraits (NSFontTraitMask.Italic, r); - } else if (xa.Style == FontStyle.Oblique) { - // FIXME: CoreText has no Obliqueness support - } else { - // FIXME: CoreText has no Obliqueness support - ns.ApplyFontTraits (NSFontTraitMask.Unitalic, r); - } - } else if (att is FontWeightTextAttribute) { - var xa = (FontWeightTextAttribute)att; - NSRange er; - // get the effective font to modify for the given range - var ft = ns.GetAttribute (CTStringAttributeKey.Font, att.StartIndex, out er, r) as NSFont; - ft = ft.WithWeight (xa.Weight); - ns.AddAttribute (CTStringAttributeKey.Font, ft, r); - } else if (att is LinkTextAttribute) { - ns.AddAttribute (CTStringAttributeKey.ForegroundColor, Toolkit.CurrentEngine.Defaults.FallbackLinkColor.ToNSColor (), r); - ns.AddAttribute (CTStringAttributeKey.UnderlineStyle, NSNumber.FromInt32 ((int)CTUnderlineStyle.Single), r); - } else if (att is StrikethroughTextAttribute) { - //FIXME: CoreText has no Strikethrough support - } else if (att is FontTextAttribute) { - var xa = (FontTextAttribute)att; - var nf = ((FontData)li.ApplicationContext.Toolkit.GetSafeBackend (xa.Font)).Font; - ns.AddAttribute (CTStringAttributeKey.Font, nf, r); - } - } - - ns.EndEditing (); - return ns; - } - - static NSAttributedString CreateAttributedString (string text, NSFont font) - { - NSDictionary dict; - if (font != null) { - dict = NSDictionary.FromObjectsAndKeys ( - new object [] { font, new NSNumber (true) }, - new object [] { CTStringAttributeKey.Font, CTStringAttributeKey.ForegroundColorFromContext } - ); - } else { - dict = NSDictionary.FromObjectsAndKeys ( - new object [] { new NSNumber (true) }, - new object [] { CTStringAttributeKey.ForegroundColorFromContext } - ); - } - return new NSAttributedString (text, dict); - } - internal static void Draw (CGContext ctx, object layout, double x, double y) + internal static void Draw (CGContextBackend ctx, object layout, double x, double y) { LayoutInfo li = (LayoutInfo)layout; - using (CTFrame frame = CreateFrame (li)) { - if (frame == null) - return; - - CTLine ellipsis = null; - bool ellipsize = li.Width.HasValue && li.TextTrimming == TextTrimming.WordElipsis; - if (ellipsize) - ellipsis = new CTLine (CreateAttributedString (li, "...")); - - nfloat lineHeight = li.Font.Ascender - li.Font.Descender + li.Font.Leading; - - ctx.SaveState (); - ctx.TextMatrix = CGAffineTransform.MakeScale (1f, -1f); - ctx.TranslateCTM ((float)x, (float)y + li.Font.Ascender); - foreach (var line in frame.GetLines ()) { - ctx.TextPosition = CGPoint.Empty; - // Determine final line - var ln = line; - if (ellipsize) { - // we need to create a new CTLine here because the framesetter already truncated the text for the line - ln = new CTLine (CreateAttributedString (li, li.Text.Substring ((int)line.StringRange.Location))) - .GetTruncatedLine (li.Width.Value, CTLineTruncation.End, ellipsis); - line.Dispose (); - } else if (li.Width.HasValue && li.TextAlignment != Alignment.Start) { - var tx = li.Width.Value - GetLineWidth (ln); - if (li.TextAlignment == Alignment.Center) - tx /= 2d; - ctx.TextPosition = new CGPoint ((nfloat)tx, 0); - } - ln.Draw (ctx); - ctx.TranslateCTM (0, lineHeight); - ln.Dispose (); - } - ctx.RestoreState (); - } + li.Draw (ctx.Context, ctx.CurrentStatus.GlobalColor, x, y); } public override void AddAttribute (object backend, TextAttribute attribute) { LayoutInfo li = (LayoutInfo)backend; - li.Attributes.Add (attribute); + li.AddAttribute (attribute); } public override void ClearAttributes (object backend) { LayoutInfo li = (LayoutInfo)backend; - li.Attributes.Clear (); + li.ClearAttributes (); } public override int GetIndexFromCoordinates (object backend, double x, double y) { - return 0; + LayoutInfo li = (LayoutInfo)backend; + return (int)li.GetIndexFromCoordinates (x, y); } public override Point GetCoordinateFromIndex (object backend, int index) { - return new Point (0,0); + LayoutInfo li = (LayoutInfo)backend; + return li.GetCoordinateFromIndex (index).ToXwtPoint (); } public override void Dispose (object backend) { - // nothing - } - - static double GetLineWidth (CTLine l) - { - // Pango does not consider trailing whitespace in its size - return l.GetTypographicBounds () - l.TrailingWhitespaceWidth; + ((LayoutInfo)backend).Dispose (); } } } diff --git a/Xwt.XamMac/Xwt.Mac/TreeViewBackend.cs b/Xwt.XamMac/Xwt.Mac/TreeViewBackend.cs index cd2a9db5..7c6be830 100644 --- a/Xwt.XamMac/Xwt.Mac/TreeViewBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/TreeViewBackend.cs @@ -64,27 +64,78 @@ namespace Xwt.Mac public override nfloat GetRowHeight (NSOutlineView outlineView, NSObject item) { - var height = outlineView.RowHeight; - var row = outlineView.RowForItem (item); - for (int i = 0; i < outlineView.ColumnCount; i++) { - var cell = outlineView.GetCell (i, row); - if (cell != null) - height = (nfloat) Math.Max (height, cell.CellSize.Height); - } + nfloat height; + var treeItem = (TreeItem)item; + if (!Backend.RowHeights.TryGetValue (treeItem, out height) || height <= 0) + height = Backend.RowHeights [treeItem] = Backend.CalcRowHeight (treeItem, false); + return height; } + + public override NSTableRowView RowViewForItem (NSOutlineView outlineView, NSObject item) + { + return outlineView.GetRowView (outlineView.RowForItem (item), false) ?? new TableRowView (); + } + + public override NSView GetView (NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item) + { + var col = tableColumn as TableColumn; + var cell = outlineView.MakeView (tableColumn.Identifier, this) as CompositeCell; + if (cell == null) + cell = col.CreateNewView (); + if (cell.ObjectValue != item) + cell.ObjectValue = item; + return cell; + } + + public override nfloat GetSizeToFitColumnWidth (NSOutlineView outlineView, nint column) + { + var tableColumn = Backend.Columns[(int)column] as TableColumn; + var width = tableColumn.HeaderCell.CellSize.Width; + + CompositeCell templateCell = null; + for (int i = 0; i < outlineView.RowCount; i++) { + var cellView = outlineView.GetView (column, i, false) as CompositeCell; + if (cellView == null) { // use template for invisible rows + cellView = templateCell ?? (templateCell = (tableColumn as TableColumn)?.DataView?.Copy () as CompositeCell); + if (cellView != null) + cellView.ObjectValue = outlineView.ItemAtRow (i); + } + if (cellView != null) { + if (column == 0) // first column contains expanders + width = (nfloat)Math.Max (width, cellView.Frame.X + cellView.FittingSize.Width); + else + width = (nfloat)Math.Max (width, cellView.FittingSize.Width); + } + } + return width; + } + + public override NSIndexSet GetSelectionIndexes(NSOutlineView outlineView, NSIndexSet proposedSelectionIndexes) + { + return Backend.SelectionMode != SelectionMode.None ? proposedSelectionIndexes : new NSIndexSet(); + } } - NSOutlineView Tree { - get { return (NSOutlineView) Table; } + OutlineViewBackend Tree { + get { return (OutlineViewBackend) Table; } } protected override NSTableView CreateView () { - var t = new OutlineViewBackend (EventSink, ApplicationContext); + var t = new OutlineViewBackend (this); t.Delegate = new TreeDelegate () { Backend = this }; return t; } + + public bool AnimationsEnabled { + get { + return Tree.AnimationsEnabled; + } + set { + Tree.AnimationsEnabled = value; + } + } protected override string SelectionChangeEventName { get { return "NSOutlineViewSelectionDidChangeNotification"; } @@ -103,13 +154,37 @@ namespace Xwt.Mac public void SetSource (ITreeDataSource source, IBackend sourceBackend) { this.source = source; + RowHeights.Clear (); tsource = new TreeSource (source); Tree.DataSource = tsource; - source.NodeInserted += (sender, e) => Tree.ReloadItem (tsource.GetItem (source.GetParent(e.Node)), true); - source.NodeDeleted += (sender, e) => Tree.ReloadItem (tsource.GetItem (e.Node), true); - source.NodeChanged += (sender, e) => Tree.ReloadItem (tsource.GetItem (e.Node), false); - source.NodesReordered += (sender, e) => Tree.ReloadItem (tsource.GetItem (e.Node), true); + source.NodeInserted += (sender, e) => { + var parent = tsource.GetItem (source.GetParent (e.Node)); + Tree.ReloadItem (parent, parent == null || Tree.IsItemExpanded (parent)); + }; + source.NodeDeleted += (sender, e) => { + var parent = tsource.GetItem (e.Node); + var item = tsource.GetItem(e.Child); + if (item != null) + RowHeights.Remove (null); + Tree.ReloadItem (parent, parent == null || Tree.IsItemExpanded (parent)); + }; + source.NodeChanged += (sender, e) => { + var item = tsource.GetItem (e.Node); + if (item != null) { + Tree.ReloadItem (item, false); + UpdateRowHeight (item); + } + }; + source.NodesReordered += (sender, e) => { + var parent = tsource.GetItem (e.Node); + Tree.ReloadItem (parent, parent == null || Tree.IsItemExpanded (parent)); + }; + source.Cleared += (sender, e) => + { + Tree.ReloadData (); + RowHeights.Clear (); + }; } public override object GetValue (object pos, int nField) @@ -121,6 +196,48 @@ namespace Xwt.Mac { source.SetValue ((TreePosition)pos, nField, value); } + + public override void InvalidateRowHeight (object pos) + { + UpdateRowHeight (tsource.GetItem((TreePosition)pos)); + } + + Dictionary<TreeItem, nfloat> RowHeights = new Dictionary<TreeItem, nfloat> (); + bool updatingRowHeight; + + void UpdateRowHeight (TreeItem pos) + { + if (updatingRowHeight) + return; + var row = Tree.RowForItem (pos); + if (row >= 0) { + // calculate new height now by reusing the visible cell to avoid using the template cell with unnecessary data reloads + // NOTE: cell reusing is not supported in Delegate.GetRowHeight and would require an other data reload to the template cell + RowHeights[pos] = CalcRowHeight (pos); + Table.NoteHeightOfRowsWithIndexesChanged (NSIndexSet.FromIndex (row)); + } else // Invalidate the height, to force recalculation in Delegate.GetRowHeight + RowHeights[pos] = -1; + } + + nfloat CalcRowHeight (TreeItem pos, bool tryReuse = true) + { + updatingRowHeight = true; + var height = Table.RowHeight; + var row = Tree.RowForItem (pos); + + for (int i = 0; i < Columns.Count; i++) { + CompositeCell cell = tryReuse && row >= 0 ? Tree.GetView (i, row, false) as CompositeCell : null; + if (cell == null) { + cell = (Columns [i] as TableColumn)?.DataView as CompositeCell; + cell.ObjectValue = pos; + height = (nfloat)Math.Max (height, cell.FittingSize.Height); + } else { + height = (nfloat)Math.Max (height, cell.GetRequiredHeightForWidth (cell.Frame.Width)); + } + } + updatingRowHeight = false; + return height; + } public TreePosition[] SelectedRows { get { @@ -143,6 +260,7 @@ namespace Xwt.Mac } set { SelectRow (value); + ScrollToRow (value); } } @@ -201,12 +319,19 @@ namespace Xwt.Mac public void ExpandToRow (TreePosition pos) { var p = source.GetParent (pos); + if (p == null) + return; + var s = new Stack<TreePosition> (); while (p != null) { - var it = tsource.GetItem (p); + s.Push (p); + p = source.GetParent (p); + } + + while (s.Count > 0) { + var it = tsource.GetItem (s.Pop ()); if (it == null) break; Tree.ExpandItem (it, false); - p = source.GetParent (p); } } @@ -295,10 +420,9 @@ namespace Xwt.Mac class TreeSource: NSOutlineViewDataSource { ITreeDataSource source; - - // TODO: remove unused positions + Dictionary<TreePosition,TreeItem> items = new Dictionary<TreePosition, TreeItem> (); - + public TreeSource (ITreeDataSource source) { this.source = source; @@ -307,6 +431,12 @@ namespace Xwt.Mac if (!items.ContainsKey (e.Node)) items.Add (e.Node, new TreeItem { Position = e.Node }); }; + source.NodeDeleted += (sender, e) => { + items.Remove (e.Child); + }; + source.Cleared += (sender, e) => { + items.Clear (); + }; } public TreeItem GetItem (TreePosition pos) diff --git a/Xwt.XamMac/Xwt.Mac/Util.cs b/Xwt.XamMac/Xwt.Mac/Util.cs index ce8774b9..aa9f1862 100644 --- a/Xwt.XamMac/Xwt.Mac/Util.cs +++ b/Xwt.XamMac/Xwt.Mac/Util.cs @@ -567,6 +567,18 @@ namespace Xwt.Mac return view.ConvertPointFromView(point, null); } + public static PointerButton GetPointerButton (this NSEvent theEvent) + { + switch (theEvent.ButtonNumber) { + case 0: return PointerButton.Left; + case 1: return PointerButton.Right; + case 2: return PointerButton.Middle; + case 3: return PointerButton.ExtendedButton1; + case 4: return PointerButton.ExtendedButton2; + } + return (PointerButton)0; + } + public static Accessibility.Role GetXwtRole (INSAccessibility widget) { var r = widget.AccessibilityRole; diff --git a/Xwt.XamMac/Xwt.Mac/ViewBackend.cs b/Xwt.XamMac/Xwt.Mac/ViewBackend.cs index 88e24791..026c5510 100644 --- a/Xwt.XamMac/Xwt.Mac/ViewBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/ViewBackend.cs @@ -222,6 +222,8 @@ namespace Xwt.Mac Cursor = NSCursor.CrosshairCursor; else if (cursor == CursorType.Hand) Cursor = NSCursor.OpenHandCursor; + else if (cursor == CursorType.Hand2) + Cursor = NSCursor.PointingHandCursor; else if (cursor == CursorType.IBeam) Cursor = NSCursor.IBeamCursor; else if (cursor == CursorType.ResizeDown) @@ -241,8 +243,14 @@ namespace Xwt.Mac Cursor = NSCursor.ArrowCursor; else if (cursor == CursorType.Move) Cursor = NSCursor.ClosedHandCursor; + else if (cursor == CursorType.DragCopy) + Cursor = NSCursor.DragCopyCursor; + else if (cursor == CursorType.NotAllowed) + Cursor = NSCursor.OperationNotAllowedCursor; else Cursor = NSCursor.ArrowCursor; + // immediately invalidate cursor rects, if the view is visible + ViewObject?.View?.Window?.InvalidateCursorRectsForView(ViewObject.View); } ~ViewBackend () diff --git a/Xwt.XamMac/Xwt.Mac/WidgetView.cs b/Xwt.XamMac/Xwt.Mac/WidgetView.cs index 269e32d4..e047d0df 100644 --- a/Xwt.XamMac/Xwt.Mac/WidgetView.cs +++ b/Xwt.XamMac/Xwt.Mac/WidgetView.cs @@ -82,133 +82,88 @@ namespace Xwt.Mac public override void UpdateTrackingAreas () { - if (trackingArea != null) { - RemoveTrackingArea (trackingArea); - trackingArea.Dispose (); - } - CGRect viewBounds = this.Bounds; - var options = NSTrackingAreaOptions.MouseMoved | NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.MouseEnteredAndExited; - trackingArea = new NSTrackingArea (viewBounds, options, this, null); - AddTrackingArea (trackingArea); + this.UpdateEventTrackingArea (ref trackingArea); } public override void RightMouseDown (NSEvent theEvent) { - CGPoint p = this.ConvertPointFromEvent(theEvent); - if (!Bounds.Contains(p)) - return; - ButtonEventArgs args = new ButtonEventArgs (); - args.X = p.X; - args.Y = p.Y; - args.Button = PointerButton.Right; - args.IsContextMenuTrigger = theEvent.TriggersContextMenu (); - context.InvokeUserCode (delegate { - eventSink.OnButtonPressed (args); - }); + if (!this.HandleMouseDown (theEvent)) + base.RightMouseDown (theEvent); } public override void RightMouseUp (NSEvent theEvent) { - CGPoint p = this.ConvertPointFromEvent(theEvent); - if (!Bounds.Contains(p)) - return; - ButtonEventArgs args = new ButtonEventArgs (); - args.X = p.X; - args.Y = p.Y; - args.Button = PointerButton.Right; - context.InvokeUserCode (delegate { - eventSink.OnButtonReleased (args); - }); + if (!this.HandleMouseUp (theEvent)) + base.RightMouseUp (theEvent); } public override void MouseDown (NSEvent theEvent) { - CGPoint p = this.ConvertPointFromEvent(theEvent); - if (!Bounds.Contains(p)) - return; - ButtonEventArgs args = new ButtonEventArgs (); - args.X = p.X; - args.Y = p.Y; - args.Button = PointerButton.Left; - args.IsContextMenuTrigger = theEvent.TriggersContextMenu (); - context.InvokeUserCode (delegate { - eventSink.OnButtonPressed (args); - }); + if (!this.HandleMouseDown (theEvent)) + base.MouseDown (theEvent); } public override void MouseUp (NSEvent theEvent) { - CGPoint p = this.ConvertPointFromEvent(theEvent); - if (!Bounds.Contains(p)) - return; - ButtonEventArgs args = new ButtonEventArgs (); - args.X = p.X; - args.Y = p.Y; - args.Button = (PointerButton) (int)theEvent.ButtonNumber + 1; - context.InvokeUserCode (delegate { - eventSink.OnButtonReleased (args); - }); + if (!this.HandleMouseUp (theEvent)) + base.MouseUp (theEvent); + } + + public override void OtherMouseDown (NSEvent theEvent) + { + if (!this.HandleMouseDown (theEvent)) + base.OtherMouseDown (theEvent); + } + + public override void OtherMouseUp (NSEvent theEvent) + { + if (!this.HandleMouseUp (theEvent)) + base.OtherMouseUp (theEvent); } public override void MouseEntered (NSEvent theEvent) { - context.InvokeUserCode (eventSink.OnMouseEntered); + this.HandleMouseEntered (theEvent); } public override void MouseExited (NSEvent theEvent) { - context.InvokeUserCode (eventSink.OnMouseExited); + this.HandleMouseExited (theEvent); } public override void MouseMoved (NSEvent theEvent) { - CGPoint p = this.ConvertPointFromEvent(theEvent); - if (!Bounds.Contains(p)) - return; - MouseMovedEventArgs args = new MouseMovedEventArgs ((long) TimeSpan.FromSeconds (theEvent.Timestamp).TotalMilliseconds, p.X, p.Y); - context.InvokeUserCode (delegate { - eventSink.OnMouseMoved (args); - }); + if (!this.HandleMouseMoved (theEvent)) + base.MouseMoved (theEvent); } - public override void MouseDragged (NSEvent theEvent) + public override void RightMouseDragged (NSEvent theEvent) { - CGPoint p = this.ConvertPointFromEvent(theEvent); - if (!Bounds.Contains(p)) - return; - MouseMovedEventArgs args = new MouseMovedEventArgs ((long) TimeSpan.FromSeconds (theEvent.Timestamp).TotalMilliseconds, p.X, p.Y); - context.InvokeUserCode (delegate { - eventSink.OnMouseMoved (args); - }); + if (!this.HandleMouseMoved (theEvent)) + base.RightMouseDragged (theEvent); } - public override void KeyDown (NSEvent theEvent) + public override void MouseDragged (NSEvent theEvent) { - var keyArgs = theEvent.ToXwtKeyEventArgs (); - context.InvokeUserCode (delegate { - eventSink.OnKeyPressed (keyArgs); - }); - if (keyArgs.Handled) - return; + if (!this.HandleMouseMoved (theEvent)) + base.MouseDragged (theEvent); + } - var textArgs = new TextInputEventArgs (theEvent.Characters); - if (!String.IsNullOrEmpty(theEvent.Characters)) - context.InvokeUserCode (delegate { - eventSink.OnTextInput (textArgs); - }); - if (textArgs.Handled) - return; + public override void OtherMouseDragged (NSEvent theEvent) + { + if (!this.HandleMouseMoved (theEvent)) + base.OtherMouseDragged (theEvent); + } - base.KeyDown (theEvent); + public override void KeyDown (NSEvent theEvent) + { + if (!this.HandleKeyDown (theEvent)) + base.KeyDown (theEvent); } public override void KeyUp (NSEvent theEvent) { - var keyArgs = theEvent.ToXwtKeyEventArgs (); - context.InvokeUserCode (delegate { - eventSink.OnKeyReleased (keyArgs); - }); - if (!keyArgs.Handled) + if (!this.HandleKeyUp (theEvent)) base.KeyUp (theEvent); } diff --git a/Xwt.XamMac/Xwt.Mac/WindowBackend.cs b/Xwt.XamMac/Xwt.Mac/WindowBackend.cs index 350aed24..29c1762c 100644 --- a/Xwt.XamMac/Xwt.Mac/WindowBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/WindowBackend.cs @@ -29,6 +29,7 @@ // THE SOFTWARE. using System; +using System.Linq; using AppKit; using CoreGraphics; using Foundation; @@ -110,11 +111,27 @@ namespace Xwt.Mac internal void InternalShow () { MakeKeyAndOrderFront (MacEngine.App); + if (ParentWindow != null) + { + if (!ParentWindow.ChildWindows.Contains(this)) + ParentWindow.AddChildWindow(this, NSWindowOrderingMode.Above); + + // always use NSWindow for alignment when running in guest mode and + // don't rely on AddChildWindow to position the window correctly + if (frontend.InitialLocation == WindowLocation.CenterParent && !(ParentWindow is WindowBackend)) + { + var parentBounds = MacDesktopBackend.ToDesktopRect(ParentWindow.ContentRectFor(ParentWindow.Frame)); + var bounds = ((IWindowFrameBackend)this).Bounds; + bounds.X = parentBounds.Center.X - (Frame.Width / 2); + bounds.Y = parentBounds.Center.Y - (Frame.Height / 2); + ((IWindowFrameBackend)this).Bounds = bounds; + } + } } public void Present () { - MakeKeyAndOrderFront (MacEngine.App); + InternalShow(); } public bool Visible { @@ -238,6 +255,8 @@ namespace Xwt.Mac PerformClose(this); else Close (); + if (ParentWindow != null) + ParentWindow.RemoveChildWindow(this); return closePerformed; } @@ -378,8 +397,21 @@ namespace Xwt.Mac void IWindowFrameBackend.SetTransientFor (IWindowFrameBackend window) { - // Generally, TransientFor is used to implement dialog, we reproduce the assumption here - Level = window == null ? NSWindowLevel.Normal : NSWindowLevel.ModalPanel; + if (!((IWindowFrameBackend)this).ShowInTaskbar) + StyleMask &= ~NSWindowStyle.Miniaturizable; + + var win = window as NSWindow ?? ApplicationContext.Toolkit.GetNativeWindow(window) as NSWindow; + + if (ParentWindow != win) { + // remove from the previous parent + if (ParentWindow != null) + ParentWindow.RemoveChildWindow(this); + + ParentWindow = win; + // A window must be visible to be added to a parent. See InternalShow(). + if (Visible) + ParentWindow.AddChildWindow(this, NSWindowOrderingMode.Above); + } } bool IWindowFrameBackend.Resizable { @@ -528,7 +560,7 @@ namespace Xwt.Mac if (child != null) { frame.X += (nfloat) frontend.Padding.Left; frame.Width -= (nfloat) (frontend.Padding.HorizontalSpacing); - frame.Y += (nfloat) frontend.Padding.Top; + frame.Y += (nfloat) (childView.IsFlipped ? frontend.Padding.Bottom : frontend.Padding.Top); frame.Height -= (nfloat) (frontend.Padding.VerticalSpacing); childView.Frame = frame; } diff --git a/Xwt.XamMac/Xwt.XamMac.csproj b/Xwt.XamMac/Xwt.XamMac.csproj index d56701cb..01bb962d 100644 --- a/Xwt.XamMac/Xwt.XamMac.csproj +++ b/Xwt.XamMac/Xwt.XamMac.csproj @@ -21,6 +21,7 @@ <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <AllowUnsafeBlocks>True</AllowUnsafeBlocks> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -30,6 +31,7 @@ <WarningLevel>4</WarningLevel> <AllowUnsafeBlocks>True</AllowUnsafeBlocks> <DebugSymbols>true</DebugSymbols> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup> <XamMacPath>\Library\Frameworks\Xamarin.Mac.framework\Versions\Current\lib\x86_64\full\Xamarin.Mac.dll</XamMacPath> diff --git a/Xwt/Xwt.Backends/IButtonBackend.cs b/Xwt/Xwt.Backends/IButtonBackend.cs index 13ca8cde..5dabf47e 100755 --- a/Xwt/Xwt.Backends/IButtonBackend.cs +++ b/Xwt/Xwt.Backends/IButtonBackend.cs @@ -32,6 +32,7 @@ namespace Xwt.Backends public interface IButtonBackend: IWidgetBackend { Color LabelColor { get; set; } + bool IsDefault { get; set; } void SetButtonStyle (ButtonStyle style); void SetButtonType (ButtonType type); void SetContent (string label, bool useMnemonic, ImageDescription image, ContentPosition position); diff --git a/Xwt/Xwt.Backends/ICanvasCellViewBackend.cs b/Xwt/Xwt.Backends/ICanvasCellViewBackend.cs index 4bb56cdc..345add6a 100644 --- a/Xwt/Xwt.Backends/ICanvasCellViewBackend.cs +++ b/Xwt/Xwt.Backends/ICanvasCellViewBackend.cs @@ -30,6 +30,7 @@ namespace Xwt.Backends public interface ICanvasCellViewBackend: ICellViewBackend { void QueueDraw (); + void QueueResize (); bool IsHighlighted { get; } } } diff --git a/Xwt/Xwt.Backends/ICanvasCellViewFrontend.cs b/Xwt/Xwt.Backends/ICanvasCellViewFrontend.cs index 9609f44d..85949233 100644 --- a/Xwt/Xwt.Backends/ICanvasCellViewFrontend.cs +++ b/Xwt/Xwt.Backends/ICanvasCellViewFrontend.cs @@ -33,7 +33,9 @@ namespace Xwt.Backends ApplicationContext ApplicationContext { get; } void Draw (object ctxBackend, Rectangle cellArea); Rectangle GetDrawingAreaForBounds (Rectangle cellBounds); + [Obsolete("Use GetRequiredSize (SizeConstraint)")] Size GetRequiredSize (); + Size GetRequiredSize (SizeConstraint widthConstraint); } public class CellViewStatus diff --git a/Xwt/Xwt.Backends/IMenuItemBackend.cs b/Xwt/Xwt.Backends/IMenuItemBackend.cs index 399c7ff1..1b2c47cb 100644 --- a/Xwt/Xwt.Backends/IMenuItemBackend.cs +++ b/Xwt/Xwt.Backends/IMenuItemBackend.cs @@ -51,6 +51,11 @@ namespace Xwt.Backends string Label { get; set; } /// <summary> + /// Gets or sets the tooltip text for the menu item. + /// </summary> + string TooltipText { get; set; } + + /// <summary> /// Gets or sets a value indicating whether this <see cref="Xwt.Backends.IMenuItemBackend"/> use a mnemonic. /// </summary> /// <value><c>true</c> if it use a mnemonic; otherwise, <c>false</c>.</value> diff --git a/Xwt/Xwt.Backends/ITreeViewBackend.cs b/Xwt/Xwt.Backends/ITreeViewBackend.cs index 392e4784..84276e8e 100644 --- a/Xwt/Xwt.Backends/ITreeViewBackend.cs +++ b/Xwt/Xwt.Backends/ITreeViewBackend.cs @@ -43,9 +43,12 @@ namespace Xwt.Backends void CollapseRow (TreePosition pos); void ScrollToRow (TreePosition pos); void ExpandToRow (TreePosition pos); - + + bool BorderVisible { get; set; } bool HeadersVisible { get; set; } GridLines GridLinesVisible { get; set; } + bool UseAlternatingRowColors { get; set; } + bool AnimationsEnabled { get; set; } bool GetDropTargetRow (double x, double y, out RowDropPosition pos, out TreePosition nodePosition); diff --git a/Xwt/Xwt.csproj b/Xwt/Xwt.csproj index 93763315..95d1caf4 100644 --- a/Xwt/Xwt.csproj +++ b/Xwt/Xwt.csproj @@ -22,6 +22,7 @@ <WarningLevel>4</WarningLevel> <ConsolePause>False</ConsolePause> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <LangVersion>6</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -32,6 +33,7 @@ <ConsolePause>False</ConsolePause> <DebugSymbols>true</DebugSymbols> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <LangVersion>6</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System" /> diff --git a/Xwt/Xwt/Button.cs b/Xwt/Xwt/Button.cs index 6e64113c..c523aed6 100755 --- a/Xwt/Xwt/Button.cs +++ b/Xwt/Xwt/Button.cs @@ -167,6 +167,11 @@ namespace Xwt get { return Backend.LabelColor; } set { Backend.LabelColor = value; } } + + public bool IsDefault { + get { return Backend.IsDefault; } + set { Backend.IsDefault = value; } + } protected virtual void OnClicked (EventArgs e) { diff --git a/Xwt/Xwt/CanvasCellView.cs b/Xwt/Xwt/CanvasCellView.cs index 8e6e58dc..4989f162 100644 --- a/Xwt/Xwt/CanvasCellView.cs +++ b/Xwt/Xwt/CanvasCellView.cs @@ -41,6 +41,15 @@ namespace Xwt } /// <summary> + /// Signals that the size of the cell may have changed, and the row + /// that contains it may need to be resized + /// </summary> + protected void QueueResize () + { + ((ICanvasCellViewBackend)BackendHost.Backend).QueueResize (); + } + + /// <summary> /// Called when the cell needs to be redrawn /// </summary> /// <param name='ctx'> @@ -55,10 +64,18 @@ namespace Xwt return cellBounds; } + [Obsolete("Use OnGetRequiredSize (SizeConstraint widthConstraint)")] protected virtual Size OnGetRequiredSize () { return new Size (); } + + protected virtual Size OnGetRequiredSize (SizeConstraint widthConstraint) + { + #pragma warning disable 618 + return OnGetRequiredSize (); + #pragma warning restore 618 + } #region ICanvasCellRenderer implementation @@ -77,7 +94,12 @@ namespace Xwt Size ICanvasCellViewFrontend.GetRequiredSize () { - return OnGetRequiredSize (); + return OnGetRequiredSize (SizeConstraint.Unconstrained); + } + + Size ICanvasCellViewFrontend.GetRequiredSize (SizeConstraint widthConstraint) + { + return OnGetRequiredSize (widthConstraint); } ApplicationContext ICanvasCellViewFrontend.ApplicationContext { diff --git a/Xwt/Xwt/CursorType.cs b/Xwt/Xwt/CursorType.cs index 22286862..f08658da 100644 --- a/Xwt/Xwt/CursorType.cs +++ b/Xwt/Xwt/CursorType.cs @@ -53,11 +53,18 @@ namespace Xwt public static readonly CursorType ResizeUp = new CursorType ("ResizeUp"); public static readonly CursorType ResizeDown = new CursorType ("ResizeDown"); public static readonly CursorType ResizeUpDown = new CursorType ("ResizeUpDown"); + public static readonly CursorType ResizeNE = new CursorType("ResizeNE"); + public static readonly CursorType ResizeNW = new CursorType("ResizeNW"); + public static readonly CursorType ResizeSE = new CursorType("ResizeSE"); + public static readonly CursorType ResizeSW = new CursorType("ResizeSW"); public static readonly CursorType Hand = new CursorType ("Hand"); + public static readonly CursorType Hand2 = new CursorType("Hand2"); public static readonly CursorType Move = new CursorType ("Move"); public static readonly CursorType Wait = new CursorType ("Watch"); public static readonly CursorType Help = new CursorType ("Help"); public static readonly CursorType Invisible = new CursorType ("Invisible"); + public static readonly CursorType DragCopy = new CursorType("DragCopy"); + public static readonly CursorType NotAllowed = new CursorType("NotAllowed"); class CursorTypeValueConverter: TypeConverter @@ -98,6 +105,8 @@ namespace Xwt return ct; } } + + public override string ToString() => id; } } diff --git a/Xwt/Xwt/DataField.cs b/Xwt/Xwt/DataField.cs index 39c1859d..29e60396 100644 --- a/Xwt/Xwt/DataField.cs +++ b/Xwt/Xwt/DataField.cs @@ -50,6 +50,11 @@ namespace Xwt Index = -1; } + public DataField (int index) + { + Index = index; + } + public int Index { get; private set; } void IDataFieldInternal.SetIndex (int index) diff --git a/Xwt/Xwt/ITreeDataSource.cs b/Xwt/Xwt/ITreeDataSource.cs index a19a81ec..3a6739db 100644 --- a/Xwt/Xwt/ITreeDataSource.cs +++ b/Xwt/Xwt/ITreeDataSource.cs @@ -44,29 +44,47 @@ namespace Xwt event EventHandler<TreeNodeChildEventArgs> NodeDeleted; event EventHandler<TreeNodeEventArgs> NodeChanged; event EventHandler<TreeNodeOrderEventArgs> NodesReordered; + event EventHandler Cleared; } public class TreeNodeEventArgs: EventArgs { - public TreeNodeEventArgs (TreePosition node) + public TreeNodeEventArgs (TreePosition node): this (node, -1) + { + } + + public TreeNodeEventArgs (TreePosition node, int childIndex) { Node = node; + ChildIndex = childIndex; } public TreePosition Node { get; private set; } + + public int ChildIndex { + get; + private set; + } } public class TreeNodeChildEventArgs: TreeNodeEventArgs { - public TreeNodeChildEventArgs (TreePosition parent, int childIndex): base (parent) + [Obsolete ("Use the constructor that takes the child object")] + public TreeNodeChildEventArgs (TreePosition parent, int childIndex): base (parent, childIndex) { - ChildIndex = childIndex; + } + +#pragma warning disable CS0618 // Type or member is obsolete + public TreeNodeChildEventArgs (TreePosition parent, int childIndex, TreePosition child): this (parent, childIndex) +#pragma warning restore CS0618 // Type or member is obsolete + { + Child = child; } - public int ChildIndex { + public TreePosition Child { get; private set; } @@ -74,7 +92,7 @@ namespace Xwt public class TreeNodeOrderEventArgs: TreeNodeEventArgs { - public TreeNodeOrderEventArgs (TreePosition parentNode, int[] childrenOrder): base (parentNode) + public TreeNodeOrderEventArgs (TreePosition parentNode, int[] childrenOrder): base (parentNode, -1) { ChildrenOrder = childrenOrder; } diff --git a/Xwt/Xwt/ListStore.cs b/Xwt/Xwt/ListStore.cs index 275db6c8..4b246acf 100644 --- a/Xwt/Xwt/ListStore.cs +++ b/Xwt/Xwt/ListStore.cs @@ -387,7 +387,7 @@ namespace Xwt { int count = list.Count; list.Clear (); - for (int n=0; n<count; n++) { + for (int n=count-1; n>=0; n--) { if (RowDeleted != null) RowDeleted (this, new ListRowEventArgs (n)); } diff --git a/Xwt/Xwt/MenuItem.cs b/Xwt/Xwt/MenuItem.cs index ad430029..8947f08b 100644 --- a/Xwt/Xwt/MenuItem.cs +++ b/Xwt/Xwt/MenuItem.cs @@ -107,6 +107,22 @@ namespace Xwt } /// <summary> + /// Gets or sets the tooltip text. + /// </summary> + /// <value>The tooltip text.</value> + [DefaultValue("")] + public string TooltipText + { + get { return Backend.TooltipText ?? ""; } + set + { + if (IsSeparator) + throw new NotSupportedException(); + Backend.TooltipText = value; + } + } + + /// <summary> /// Gets or sets a value indicating whether this <see cref="Xwt.Button"/> uses a mnemonic. /// </summary> /// <value><c>true</c> if it uses a mnemonic; otherwise, <c>false</c>.</value> diff --git a/Xwt/Xwt/Rectangle.cs b/Xwt/Xwt/Rectangle.cs index dc07fc92..17fee72f 100755 --- a/Xwt/Xwt/Rectangle.cs +++ b/Xwt/Xwt/Rectangle.cs @@ -56,6 +56,14 @@ namespace Xwt public Rectangle (Point loc, Size sz) : this (loc.X, loc.Y, sz.Width, sz.Height) {} + public Rectangle (Point point1, Point point2) + { + X = Math.Min (point1.X, point2.X); + Y = Math.Min (point1.Y, point2.Y); + Width = Math.Max (Math.Max (point1.X, point2.X) - X, 0); + Height = Math.Max (Math.Max (point1.Y, point2.Y) - Y, 0); + } + public static Rectangle FromLTRB (double left, double top, double right, double bottom) { return new Rectangle (left, top, right - left, bottom - top); @@ -168,6 +176,26 @@ namespace Xwt get { return X; } set { X = value; } } + public Point TopLeft { + get { + return new Point (Left, Top); + } + } + public Point TopRight { + get { + return new Point (Right, Top); + } + } + public Point BottomLeft { + get { + return new Point (Left, Bottom); + } + } + public Point BottomRight { + get { + return new Point (Right, Bottom); + } + } public bool IsEmpty { get { return (Width <= 0) || (Height <= 0); } diff --git a/Xwt/Xwt/SelectionMode.cs b/Xwt/Xwt/SelectionMode.cs index a9698fbf..9bde31be 100644 --- a/Xwt/Xwt/SelectionMode.cs +++ b/Xwt/Xwt/SelectionMode.cs @@ -31,7 +31,8 @@ namespace Xwt public enum SelectionMode { Single, - Multiple + Multiple, + None } } diff --git a/Xwt/Xwt/TreeStore.cs b/Xwt/Xwt/TreeStore.cs index b9200f8c..ccdc918b 100644 --- a/Xwt/Xwt/TreeStore.cs +++ b/Xwt/Xwt/TreeStore.cs @@ -167,6 +167,11 @@ namespace Xwt add { Backend.NodesReordered += value; } remove { Backend.NodesReordered -= value; } } + event EventHandler ITreeDataSource.Cleared + { + add { Backend.Cleared += value; } + remove { Backend.Cleared -= value; } + } TreePosition ITreeDataSource.GetChild (TreePosition pos, int index) { @@ -243,6 +248,7 @@ namespace Xwt public event EventHandler<TreeNodeChildEventArgs> NodeDeleted; public event EventHandler<TreeNodeEventArgs> NodeChanged; public event EventHandler<TreeNodeOrderEventArgs> NodesReordered; + public event EventHandler Cleared; public void InitializeBackend (object frontend, ApplicationContext context) { @@ -256,6 +262,8 @@ namespace Xwt public void Clear () { rootNodes.Clear (); + if (Cleared != null) + Cleared (this, EventArgs.Empty); } NodePosition GetPosition (TreePosition pos) @@ -288,7 +296,7 @@ namespace Xwt } node.Data [column] = value; if (NodeChanged != null) - NodeChanged (this, new TreeNodeEventArgs (pos)); + NodeChanged (this, new TreeNodeEventArgs (pos, n.NodeIndex)); } public object GetValue (TreePosition pos, int column) @@ -359,7 +367,7 @@ namespace Xwt var node = new NodePosition () { ParentList = np.ParentList, NodeId = nn.NodeId, NodeIndex = np.NodeIndex - 1, StoreVersion = version }; if (NodeInserted != null) - NodeInserted (this, new TreeNodeEventArgs (node)); + NodeInserted (this, new TreeNodeEventArgs (node, node.NodeIndex)); return node; } @@ -377,7 +385,7 @@ namespace Xwt var node = new NodePosition () { ParentList = np.ParentList, NodeId = nn.NodeId, NodeIndex = np.NodeIndex + 1, StoreVersion = version }; if (NodeInserted != null) - NodeInserted (this, new TreeNodeEventArgs (node)); + NodeInserted (this, new TreeNodeEventArgs (node, node.NodeIndex)); return node; } @@ -410,19 +418,24 @@ namespace Xwt var node = new NodePosition () { ParentList = list, NodeId = nn.NodeId, NodeIndex = list.Count - 1, StoreVersion = version }; if (NodeInserted != null) - NodeInserted (this, new TreeNodeEventArgs (node)); + NodeInserted (this, new TreeNodeEventArgs (node, node.NodeIndex)); return node; } public void Remove (TreePosition pos) { + // Remove all child nodes in reverse order and notify client of each removed child. + // This allows clients to keep track of all removed nodes, before the current node + // will be removed and invalidated. + for (int i = GetChildrenCount (pos) - 1; i >= 0 ; i--) + Remove (GetChild (pos, i)); NodePosition np = GetPosition (pos); np.ParentList.RemoveAt (np.NodeIndex); var parent = np.ParentList.Parent; var index = np.NodeIndex; version++; if (NodeDeleted != null) - NodeDeleted (this, new TreeNodeChildEventArgs (parent, index)); + NodeDeleted (this, new TreeNodeChildEventArgs (parent, index, pos)); } public TreePosition GetParent (TreePosition pos) diff --git a/Xwt/Xwt/TreeView.cs b/Xwt/Xwt/TreeView.cs index e4ba98e3..1710b3a4 100644 --- a/Xwt/Xwt/TreeView.cs +++ b/Xwt/Xwt/TreeView.cs @@ -125,7 +125,17 @@ namespace Xwt ITreeViewBackend Backend { get { return (ITreeViewBackend) BackendHost.Backend; } } - + + public bool BorderVisible { + get { return Backend.BorderVisible; } + set { Backend.BorderVisible = value; } + } + + public bool UseAlternatingRowColors { + get { return Backend.UseAlternatingRowColors; } + set { Backend.UseAlternatingRowColors = value; } + } + public ScrollPolicy VerticalScrollPolicy { get { return Backend.VerticalScrollPolicy; } set { Backend.VerticalScrollPolicy = value; } @@ -204,6 +214,24 @@ namespace Xwt get { return Backend.GridLinesVisible; } set { Backend.GridLinesVisible = value; } } + + /// <summary> + /// Gets or sets a value indicating whether native animations are enabled. + /// </summary> + /// <value><c>true</c> if animations enabled; otherwise, <c>false</c>.</value> + /// <remarks> + /// This property controls native TreeView animations (like slow expanding collapsing rows) + /// only and is not related to <see cref="T:Xwt.Motion.Animation"/>. + /// </remarks> + public bool AnimationsEnabled + { + get { + return Backend.AnimationsEnabled; + } + set { + Backend.AnimationsEnabled = value; + } + } /// <summary> /// Gets or sets the selection mode. |