Add a popover with a list of diagnostics to the diagnostics toolbar buttons

This commit is contained in:
Neil Brommer 2023-08-02 16:30:24 -07:00
parent 92695b8361
commit 7610ffaf38
10 changed files with 769 additions and 42 deletions

View File

@ -44,6 +44,9 @@
<Compile Update="AppDelegate.designer.cs">
<DependentUpon>AppDelegate.cs</DependentUpon>
</Compile>
<Compile Update="DiagnosticsPopoverController.designer.cs">
<DependentUpon>DiagnosticsPopoverController.cs</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Cauldron.Core\Cauldron.Core.csproj" />
@ -56,10 +59,12 @@
<None Remove="SourceWriter\" />
<None Remove="SourceWriter\LanguageFormats\" />
<None Remove="ScriptOutput.css" />
<None Remove="SourceList\" />
</ItemGroup>
<ItemGroup>
<Folder Include="SourceWriter\" />
<Folder Include="SourceWriter\LanguageFormats\" />
<Folder Include="SourceList\" />
</ItemGroup>
<ItemGroup>
<Content Include="ScriptOutput.css" />

View File

@ -0,0 +1,69 @@
using AppKit;
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
using System.Linq;
using Cauldron.Macos.SourceList;
using Foundation;
namespace Cauldron.Macos
{
public partial class DiagnosticsPopoverController : NSViewController
{
public DiagnosticSeverity Severity { get; set; }
public DiagnosticsPopoverController (ObjCRuntime.NativeHandle handle) : base (handle) { }
public override void ViewWillAppear()
{
base.ViewWillAppear();
// Build the list of diagnostics info
MainWindow window = NSApplication.SharedApplication.KeyWindow.WindowController
as MainWindow;
ImmutableArray<Diagnostic> diagnostics = window.Diagnostics;
this.DiagnosticsOutlineView.Initialize();
SourceListItem errors = new("Errors")
{
IsHeader = true
};
SourceListItem warnings = new("Warnings")
{
IsHeader = true
};
SourceListItem infos = new("Information")
{
IsHeader = true
};
foreach (var diagnostic in diagnostics)
{
SourceListItem item = new($"{diagnostic.Id} {diagnostic.GetMessage()}\n{diagnostic.Location}", "",
() =>
{
window.ScriptEditorTextBox.SetSelectedRange(
new NSRange(diagnostic.Location.SourceSpan.Start, diagnostic.Location.SourceSpan.End));
this.DismissController(this);
});
if (diagnostic.Severity == DiagnosticSeverity.Error)
errors.AddItem(item);
else if (diagnostic.Severity == DiagnosticSeverity.Warning)
warnings.AddItem(item);
else if (diagnostic.Severity == DiagnosticSeverity.Info)
infos.AddItem(item);
}
this.DiagnosticsOutlineView.AddItem(errors);
this.DiagnosticsOutlineView.AddItem(warnings);
this.DiagnosticsOutlineView.AddItem(infos);
this.DiagnosticsOutlineView.ReloadData();
this.DiagnosticsOutlineView.ExpandItem(null, true);
this.DiagnosticsOutlineView.UsesAutomaticRowHeights = true;
}
}
}

View File

@ -0,0 +1,26 @@
// WARNING
//
// This file has been generated automatically by Visual Studio to store outlets and
// actions made in the UI designer. If it is removed, they will be lost.
// Manual changes to this file may not be handled correctly.
//
using Foundation;
using System.CodeDom.Compiler;
namespace Cauldron.Macos
{
[Register ("DiagnosticsPopoverController")]
partial class DiagnosticsPopoverController
{
[Outlet]
Cauldron.Macos.SourceList.SourceListView DiagnosticsOutlineView { get; set; }
void ReleaseDesignerOutlets ()
{
if (DiagnosticsOutlineView != null) {
DiagnosticsOutlineView.Dispose ();
DiagnosticsOutlineView = null;
}
}
}
}

View File

@ -411,6 +411,104 @@
</objects>
<point key="canvasLocation" x="-23" y="-256"/>
</scene>
<!--DiagnosticsPopoverController-->
<scene sceneID="acn-Kt-JQh">
<objects>
<viewController id="kEs-j4-U6W" userLabel="DiagnosticsPopoverController" customClass="DiagnosticsPopoverController" sceneMemberID="viewController">
<scrollView key="view" borderType="none" autohidesScrollers="YES" horizontalLineScroll="24" horizontalPageScroll="10" verticalLineScroll="24" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="Hks-2F-bUK">
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" drawsBackground="NO" id="cqn-04-hFK">
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" tableStyle="sourceList" selectionHighlightStyle="sourceList" multipleSelection="NO" autosaveColumns="NO" rowHeight="24" usesAutomaticRowHeights="YES" viewBased="YES" floatsGroupRows="NO" indentationPerLevel="13" outlineTableColumn="5vQ-lc-XSU" id="ueH-VL-qmi" customClass="SourceListView">
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="0.0"/>
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn identifier="AutomaticTableColumnIdentifier.0" editable="NO" width="418" minWidth="16" maxWidth="1000" id="5vQ-lc-XSU">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" selectable="YES" editable="YES" title="Text Cell" id="44O-Er-1Ow">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="HeaderCell" id="hIJ-c2-KZG">
<rect key="frame" x="11" y="0.0" width="427" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VUY-g0-yC7">
<rect key="frame" x="0.0" y="1" width="427" height="14"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Header Cell" id="fCl-Sq-iCT">
<font key="font" metaFont="systemMedium" size="11"/>
<color key="textColor" name="headerColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="VUY-g0-yC7" id="fMF-Z0-GSj"/>
</connections>
</tableCellView>
<tableCellView identifier="DataCell" id="5UQ-dL-kb2">
<rect key="frame" x="11" y="17" width="427" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews>
<imageView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pak-Mk-Lui">
<rect key="frame" x="3" y="0.0" width="17" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" imageScaling="proportionallyDown" image="NSActionTemplate" id="0eY-jA-bCa"/>
</imageView>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Zl8-zy-VvA">
<rect key="frame" x="25" y="0.0" width="402" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="Lea-Pq-teY">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="imageView" destination="pak-Mk-Lui" id="QDN-q6-DTm"/>
<outlet property="textField" destination="Zl8-zy-VvA" id="aRe-EC-mDA"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
</outlineView>
</subviews>
<nil key="backgroundColor"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="fHS-rH-sWS">
<rect key="frame" x="0.0" y="284" width="450" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="gIS-un-96x">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<connections>
<outlet property="DiagnosticOutlineView" destination="ueH-VL-qmi" id="lAb-PB-nvq"/>
<outlet property="DiagnosticsOutlineView" destination="ueH-VL-qmi" id="D3c-Go-QWV"/>
<outlet property="DiagnotsicsOutlineView" destination="ueH-VL-qmi" id="Jcw-Pp-kBT"/>
</connections>
</viewController>
<customObject id="o6s-n2-s36" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-747" y="514"/>
</scene>
<!--Window Controller-->
<scene sceneID="ktg-sd-7li">
<objects>
@ -435,25 +533,30 @@
<toolbarItem implicitItemIdentifier="8EDDDA98-E8C5-404C-9832-8C97B1DAEF5C" label="Run Script" paletteLabel="Run Script" tag="-1" image="play.fill" catalog="system" bordered="YES" sizingBehavior="auto" autovalidates="NO" id="BH3-uw-K5S"/>
<toolbarItem implicitItemIdentifier="NSToolbarSpaceItem" id="ib2-fU-LMb"/>
<toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="GqC-ht-JU0"/>
<toolbarItem implicitItemIdentifier="1FDD22AF-AF38-4957-AC69-1E8377E11C5D" label="Script Status" paletteLabel="Script Status" bordered="YES" sizingBehavior="auto" autovalidates="NO" id="bj4-Kg-7Qc">
<toolbarItem implicitItemIdentifier="1FDD22AF-AF38-4957-AC69-1E8377E11C5D" label="Diagnostics" paletteLabel="Diagnostics" bordered="YES" autovalidates="NO" id="bj4-Kg-7Qc">
<nil key="toolTip"/>
<segmentedControl key="view" verticalHuggingPriority="750" id="63L-Ya-hDW">
<size key="minSize" width="114" height="23"/>
<size key="maxSize" width="150" height="26"/>
<segmentedControl key="view" identifier="DiagnosticsButtons" verticalHuggingPriority="750" id="63L-Ya-hDW">
<rect key="frame" x="0.0" y="14" width="114" height="23"/>
<autoresizingMask key="autoresizingMask"/>
<segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedRounded" trackingMode="momentary" id="cZb-8H-XcB" customClass="ScriptStatusToolbarItem">
<font key="font" metaFont="system"/>
<segments>
<segment label="0" alignment="left" width="32">
<segment label="0" alignment="left" width="32" enabled="NO">
<imageReference key="image" image="info.circle.fill" catalog="system" symbolScale="medium" renderingMode="original"/>
</segment>
<segment label="0" alignment="left" width="32" tag="1">
<segment label="0" alignment="left" width="32" enabled="NO" tag="1">
<imageReference key="image" image="exclamationmark.triangle.fill" catalog="system" symbolScale="medium" renderingMode="original"/>
</segment>
<segment label="0" alignment="left" toolTip="Error">
<segment label="0" alignment="left" toolTip="Error" enabled="NO">
<imageReference key="image" image="exclamationmark.circle.fill" catalog="system" symbolScale="medium" renderingMode="original"/>
</segment>
</segments>
</segmentedCell>
<connections>
<segue destination="kEs-j4-U6W" kind="popover" popoverAnchorView="63L-Ya-hDW" popoverBehavior="t" preferredEdge="maxY" id="q8J-cg-E0J"/>
</connections>
</segmentedControl>
</toolbarItem>
</allowedToolbarItems>
@ -478,7 +581,7 @@
<customObject id="Dyo-ea-3jV" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<objectController mode="entity" entityName="CSharpScriptDocument" automaticallyPreparesContent="YES" id="IPo-2l-BeA"/>
</objects>
<point key="canvasLocation" x="-23" y="73"/>
<point key="canvasLocation" x="-23.5" y="72.5"/>
</scene>
<!--Split View Controller-->
<scene sceneID="BCF-ui-EPb">
@ -531,18 +634,18 @@
<objects>
<viewController id="7dz-Mm-ph3" sceneMemberID="viewController">
<scrollView key="view" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" id="uWO-6X-rW0">
<rect key="frame" x="0.0" y="0.0" width="700" height="300"/>
<rect key="frame" x="0.0" y="0.0" width="700" height="400"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" drawsBackground="NO" id="v6T-mX-paQ">
<rect key="frame" x="0.0" y="0.0" width="700" height="300"/>
<rect key="frame" x="0.0" y="0.0" width="700" height="400"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView wantsLayer="YES" importsGraphics="NO" richText="NO" horizontallyResizable="YES" verticallyResizable="YES" spellingCorrection="YES" smartInsertDelete="YES" id="rAS-np-zOl" customClass="SourceTextView">
<rect key="frame" x="0.0" y="0.0" width="700" height="300"/>
<rect key="frame" x="0.0" y="0.0" width="700" height="400"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="700" height="300"/>
<size key="minSize" width="700" height="400"/>
<size key="maxSize" width="700" height="10000000"/>
<attributedString key="textStorage">
<fragment content="Cauldron">
@ -585,7 +688,7 @@
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="S8X-st-Qy9">
<rect key="frame" x="684" y="0.0" width="16" height="300"/>
<rect key="frame" x="684" y="0.0" width="16" height="400"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
@ -599,7 +702,7 @@
<objects>
<viewController id="71j-pP-grU" sceneMemberID="viewController">
<wkWebView key="view" wantsLayer="YES" id="8OW-Al-ci0">
<rect key="frame" x="0.0" y="0.0" width="700" height="300"/>
<rect key="frame" x="0.0" y="0.0" width="700" height="200"/>
<autoresizingMask key="autoresizingMask"/>
<wkWebViewConfiguration key="configuration">
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
@ -638,6 +741,7 @@
</scene>
</scenes>
<resources>
<image name="NSActionTemplate" width="20" height="20"/>
<image name="exclamationmark.circle.fill" catalog="system" width="15" height="15"/>
<image name="exclamationmark.triangle.fill" catalog="system" width="17" height="15"/>
<image name="info.circle.fill" catalog="system" width="15" height="15"/>

View File

@ -55,6 +55,7 @@ public partial class MainWindow : NSWindowController
#region Shared properties
public CancellationTokenSource ScriptCancellationTokenSource { get; set; }
public ImmutableArray<Diagnostic> Diagnostics { get; set; } = ImmutableArray<Diagnostic>.Empty;
#endregion
@ -92,6 +93,24 @@ public partial class MainWindow : NSWindowController
this.SetDocumentEdited(this.ScriptDocument.IsDocumentEdited);
}
public override void PrepareForSegue(NSStoryboardSegue segue, NSObject sender)
{
base.PrepareForSegue(segue, sender);
if (sender is NSSegmentedControl segmentedControl
&& segmentedControl.Identifier == "DiagnosticsButtons"
&& segue.DestinationController is DiagnosticsPopoverController diagPopover)
{
diagPopover.Severity = segmentedControl.SelectedSegment switch
{
0 => DiagnosticSeverity.Info,
1 => DiagnosticSeverity.Warning,
2 => DiagnosticSeverity.Error,
_ => DiagnosticSeverity.Info
};
}
}
public void UpdateDocument(object sender, EventArgs args)
{
this.ScriptDocument.ScriptText = new NSString(this.ScriptText);
@ -113,13 +132,10 @@ public partial class MainWindow : NSWindowController
this.ScriptCancellationTokenSource?.Cancel();
}
partial void NewTabMenuItemClicked(AppKit.NSMenuItem sender)
{
this.CreateNewTab();
}
public void UpdateScriptDiagnostics(ImmutableArray<Diagnostic> diagnostics)
{
this.Diagnostics = diagnostics;
ImmutableList<Diagnostic> infoDiagnostics = diagnostics
.Where(d => d.Severity == DiagnosticSeverity.Info)
.ToImmutableList();
@ -131,9 +147,15 @@ public partial class MainWindow : NSWindowController
.ToImmutableList();
this.DiagnosticsToolbarGroup.SetLabel(infoDiagnostics.Count.ToString(), 0);
this.DiagnosticsToolbarGroup.SetLabel(warningDiagnostics.Count.ToString(), 1);
this.DiagnosticsToolbarGroup.SetLabel(errorDiagnostics.Count.ToString(), 2);
this.DiagnosticsToolbarGroup.SetEnabled(infoDiagnostics.Count != 0, 0);
this.DiagnosticsToolbarGroup.SetLabel(warningDiagnostics.Count.ToString(), 1);
this.DiagnosticsToolbarGroup.SetEnabled(warningDiagnostics.Count != 0, 1);
this.DiagnosticsToolbarGroup.SetLabel(errorDiagnostics.Count.ToString(), 2);
this.DiagnosticsToolbarGroup.SetEnabled(errorDiagnostics.Count != 0, 2);
// Mark text in the
foreach (Diagnostic diagnostic in diagnostics)
{
int start = diagnostic.Location.SourceSpan.Start;
@ -161,7 +183,7 @@ public partial class MainWindow : NSWindowController
range);
else if (diagnostic.Severity == DiagnosticSeverity.Warning)
this.ScriptEditorTextBox.LayoutManager
.AddTemporaryAttribute(NSStringAttributeKey.UnderlineColor, NSColor.SystemGreen,
.AddTemporaryAttribute(NSStringAttributeKey.UnderlineColor, NSColor.SystemYellow,
range);
else if (diagnostic.Severity == DiagnosticSeverity.Info)
this.ScriptEditorTextBox.LayoutManager
@ -181,12 +203,4 @@ public partial class MainWindow : NSWindowController
this.RunScriptToolbarButton.Enabled = true;
}
}
public void CreateNewTab()
{
MainWindow newWindow = this.Storyboard.InstantiateInitialController()
as MainWindow;
this.Window.AddTabbedWindow(newWindow.Window, NSWindowOrderingMode.Above);
this.Window.SelectNextTab(this);
}
}

View File

@ -20,27 +20,18 @@ namespace Cauldron.Macos
[Action ("BtnRunScriptClicked:")]
partial void BtnRunScriptClicked (AppKit.NSToolbarItem sender);
[Action ("NewTabClicked:")]
partial void NewTabClicked (AppKit.NSToolbarItem sender);
[Action ("NewTabMenuItemClicked:")]
partial void NewTabMenuItemClicked (AppKit.NSMenuItem sender);
[Action ("NewTabMenuItemClicked2:")]
partial void NewTabMenuItemClicked2 (AppKit.NSMenuItem sender);
void ReleaseDesignerOutlets ()
{
if (RunScriptToolbarButton != null) {
RunScriptToolbarButton.Dispose ();
RunScriptToolbarButton = null;
}
if (DiagnosticsToolbarGroup != null) {
DiagnosticsToolbarGroup.Dispose ();
DiagnosticsToolbarGroup = null;
}
if (RunScriptToolbarButton != null) {
RunScriptToolbarButton.Dispose ();
RunScriptToolbarButton = null;
}
}
}
}

View File

@ -0,0 +1,96 @@
using System;
using System.Collections;
using System.Collections.Generic;
using AppKit;
using Foundation;
namespace Cauldron.Macos.SourceList
{
public class SourceListDataSource : NSOutlineViewDataSource
{
#region Private Variables
private SourceListView _controller;
#endregion
#region Public Variables
public List<SourceListItem> Items = new();
#endregion
#region Constructors
public SourceListDataSource(SourceListView controller)
{
// Initialize
this._controller = controller;
}
#endregion
#region Override Properties
public override nint GetChildrenCount(NSOutlineView outlineView, Foundation.NSObject item)
{
if (item == null)
{
return Items.Count;
}
else
{
return ((SourceListItem)item).Count;
}
}
public override bool ItemExpandable(NSOutlineView outlineView, Foundation.NSObject item)
{
return ((SourceListItem)item).HasChildren;
}
public override NSObject GetChild(NSOutlineView outlineView, nint childIndex, Foundation.NSObject item)
{
if (item == null)
{
return Items[(int)childIndex];
}
else
{
return ((SourceListItem)item)[(int)childIndex];
}
}
public override NSObject GetObjectValue(NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item)
{
return new NSString(((SourceListItem)item).Title);
}
#endregion
#region Internal Methods
internal SourceListItem ItemForRow(int row)
{
int index = 0;
// Look at each group
foreach (SourceListItem item in Items)
{
// Is the row inside this group?
if (row >= index && row <= (index + item.Count))
{
return item[row - index - 1];
}
// Move index
index += item.Count + 1;
}
// Not found
return null;
}
#endregion
}
}

View File

@ -0,0 +1,112 @@
using AppKit;
using CoreGraphics;
using Foundation;
namespace Cauldron.Macos.SourceList
{
public class SourceListDelegate : NSOutlineViewDelegate
{
#region Private variables
private SourceListView _controller;
#endregion
#region Constructors
public SourceListDelegate(SourceListView controller)
{
this._controller = controller;
}
#endregion
#region Override Methods
public override bool ShouldEditTableColumn(NSOutlineView outlineView,
NSTableColumn tableColumn, NSObject item)
{
return false;
}
public override NSCell GetCell(NSOutlineView outlineView, NSTableColumn tableColumn,
NSObject item)
{
nint row = outlineView.RowForItem(item);
return tableColumn.DataCellForRow(row);
}
public override bool IsGroupItem(NSOutlineView outlineView, NSObject item)
{
return ((SourceListItem)item).HasChildren;
}
public override NSView GetView(NSOutlineView outlineView, NSTableColumn tableColumn,
NSObject item)
{
NSTableCellView view;
// Is this a group item?
if (((SourceListItem)item).IsHeader)
{
view = (NSTableCellView)outlineView.MakeView("HeaderCell", this);
}
else
{
view = (NSTableCellView)outlineView.MakeView("DataCell", this);
view.ImageView.Image = ((SourceListItem)item).Icon;
view.TextField.LineBreakMode = NSLineBreakMode.CharWrapping;
view.TextField.UsesSingleLineMode = false;
view.TextField.MaximumNumberOfLines = 0;
}
view.TextField.StringValue = ((SourceListItem)item).Title;
view.TextField.SetBoundsSize(CalculateTextFieldHeight(view));
return view;
}
public override bool ShouldSelectItem(NSOutlineView outlineView, NSObject item)
{
return (outlineView.GetParent(item) != null);
}
public override void SelectionDidChange(NSNotification notification)
{
NSIndexSet selectedIndexes = _controller.SelectedRows;
// More than one item selected?
if (selectedIndexes.Count > 1)
{
// Not handling this case
}
else
{
// Grab the item
var item = _controller.Data.ItemForRow((int)selectedIndexes.FirstIndex);
// Was an item found?
if (item != null)
{
// Fire the clicked event for the item
item.RaiseClickedEvent();
// Inform caller of selection
_controller.RaiseItemSelected(item);
}
}
}
private static CGSize CalculateTextFieldHeight(NSTableCellView cell)
{
CGRect rect = new(0, 0, cell.TextField.Bounds.Width, double.MaxValue);
NSString str = new(cell.TextField.StringValue);
CGRect bounds = str.BoundingRectWithSize(rect.Size, 0,
new NSDictionary(NSStringAttributeKey.Font, cell.TextField.Font));
return new CGSize(cell.TextField.Bounds.Width, bounds.Size.Height);
}
#endregion
}
}

View File

@ -0,0 +1,241 @@
using System;
using System.Collections;
using System.Collections.Generic;
using AppKit;
using Foundation;
namespace Cauldron.Macos.SourceList
{
public class SourceListItem : NSObject, IEnumerator, IEnumerable
{
#region Private Properties
private string _title;
private NSImage _icon;
private string _tag;
private bool _isHeader = false;
private List<SourceListItem> _items = new();
#endregion
#region Computed Properties
public string Title
{
get { return _title; }
set { _title = value; }
}
public NSImage Icon
{
get { return _icon; }
set { _icon = value; }
}
public string Tag
{
get { return _tag; }
set { _tag = value; }
}
public bool IsHeader
{
get => this._isHeader;
set => this._isHeader = value;
}
#endregion
#region Indexer
public SourceListItem this[int index]
{
get
{
return _items[index];
}
set
{
_items[index] = value;
}
}
public int Count
{
get { return _items.Count; }
}
public bool HasChildren
{
get { return (Count > 0); }
}
#endregion
#region Enumerable Routines
private int _position = -1;
public IEnumerator GetEnumerator()
{
_position = -1;
return (IEnumerator)this;
}
public bool MoveNext()
{
_position++;
return (_position < _items.Count);
}
public void Reset()
{ _position = -1; }
public object Current
{
get
{
try
{
return _items[_position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
#endregion
#region Constructors
public SourceListItem() { }
public SourceListItem(string title)
{
this._title = title;
}
public SourceListItem(string title, string icon)
{
this._title = title;
this._icon = NSImage.ImageNamed(icon);
}
public SourceListItem(string title, string icon, ClickedDelegate clicked)
{
this._title = title;
this._icon = NSImage.ImageNamed(icon);
this.Clicked = clicked;
}
public SourceListItem(string title, NSImage icon)
{
this._title = title;
this._icon = icon;
}
public SourceListItem(string title, NSImage icon, ClickedDelegate clicked)
{
this._title = title;
this._icon = icon;
this.Clicked = clicked;
}
public SourceListItem(string title, NSImage icon, string tag)
{
this._title = title;
this._icon = icon;
this._tag = tag;
}
public SourceListItem(string title, NSImage icon, string tag, ClickedDelegate clicked)
{
this._title = title;
this._icon = icon;
this._tag = tag;
this.Clicked = clicked;
}
#endregion
#region Public Methods
public void AddItem(SourceListItem item)
{
_items.Add(item);
}
public void AddItem(string title)
{
_items.Add(new SourceListItem(title));
}
public void AddItem(string title, string icon)
{
_items.Add(new SourceListItem(title, icon));
}
public void AddItem(string title, string icon, ClickedDelegate clicked)
{
_items.Add(new SourceListItem(title, icon, clicked));
}
public void AddItem(string title, NSImage icon)
{
_items.Add(new SourceListItem(title, icon));
}
public void AddItem(string title, NSImage icon, ClickedDelegate clicked)
{
_items.Add(new SourceListItem(title, icon, clicked));
}
public void AddItem(string title, NSImage icon, string tag)
{
_items.Add(new SourceListItem(title, icon, tag));
}
public void AddItem(string title, NSImage icon, string tag, ClickedDelegate clicked)
{
_items.Add(new SourceListItem(title, icon, tag, clicked));
}
public void Insert(int n, SourceListItem item)
{
_items.Insert(n, item);
}
public void RemoveItem(SourceListItem item)
{
_items.Remove(item);
}
public void RemoveItem(int n)
{
_items.RemoveAt(n);
}
public void Clear()
{
_items.Clear();
}
#endregion
#region Events
public delegate void ClickedDelegate();
public event ClickedDelegate Clicked;
internal void RaiseClickedEvent()
{
this.Clicked?.Invoke();
}
#endregion
}
}

View File

@ -0,0 +1,69 @@
using System;
using AppKit;
using Foundation;
namespace Cauldron.Macos.SourceList
{
[Register("SourceListView")]
public class SourceListView : NSOutlineView
{
#region Computed Properties
public SourceListDataSource Data
{
get { return (SourceListDataSource)this.DataSource; }
}
#endregion
#region Constructors
public SourceListView() { }
public SourceListView(IntPtr handle) : base(handle) { }
public SourceListView(NSCoder coder) : base(coder) { }
public SourceListView(NSObjectFlag t) : base(t) { }
public SourceListView(ObjCRuntime.NativeHandle handle) : base(handle) { }
#endregion
#region Override Methods
public override void AwakeFromNib()
{
base.AwakeFromNib();
}
#endregion
#region Public Methods
public void Initialize()
{
this.DataSource = new SourceListDataSource(this);
this.Delegate = new SourceListDelegate(this);
}
public void AddItem(SourceListItem item)
{
Data?.Items.Add(item);
}
#endregion
#region Events
public delegate void ItemSelectedDelegate(SourceListItem item);
public event ItemSelectedDelegate ItemSelected;
internal void RaiseItemSelected(SourceListItem item)
{
this.ItemSelected?.Invoke(item);
}
#endregion
}
}