// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; using System.Collections.Generic; using System.Globalization; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.CSharp; using ICSharpCode.ILSpy.AvalonEdit; using ICSharpCode.ILSpy.Debugger.Models.TreeModel; using ICSharpCode.ILSpy.Debugger.Services; namespace ICSharpCode.ILSpy.Debugger.Tooltips { /// /// Default Control used as content of SharpDevelop debugger tooltips. /// internal partial class DebuggerTooltipControl : UserControl, ITooltip { private const double ChildPopupOpenXOffet = 16; private const double ChildPopupOpenYOffet = 15; private const int InitialItemsCount = 12; private const int VisibleItemsCount = 11; private bool showPins; private LazyItemsControl lazyGrid; private IEnumerable itemsSource; readonly TextLocation logicalPosition; public DebuggerTooltipControl(TextLocation logicalPosition) { this.logicalPosition = logicalPosition; InitializeComponent(); Loaded += new RoutedEventHandler(OnLoaded); } public DebuggerTooltipControl(TextLocation logicalPosition, ITreeNode node) : this(logicalPosition, new ITreeNode[] { node }) { } public DebuggerTooltipControl(TextLocation logicalPosition, IEnumerable nodes) : this(logicalPosition) { this.itemsSource = nodes; } public DebuggerTooltipControl(DebuggerTooltipControl parentControl, TextLocation logicalPosition, bool showPins = false) : this(logicalPosition) { this.parentControl = parentControl; this.showPins = showPins; } private void OnLoaded(object sender, RoutedEventArgs e) { if (!showPins) { dataGrid.Columns[4].Visibility = Visibility.Collapsed; } SetItemsSource(this.itemsSource); } public event RoutedEventHandler Closed; protected void OnClosed() { if (this.Closed != null) { this.Closed(this, new RoutedEventArgs()); } } public IEnumerable ItemsSource { get { return this.itemsSource; } } public void SetItemsSource(IEnumerable value) { if (value == null) return; this.itemsSource = value; this.lazyGrid = new LazyItemsControl(this.dataGrid, InitialItemsCount); // // HACK for updating the pins in tooltip // var observable = new List(); // this.itemsSource.ForEach(item => observable.Add(item)); // // // verify if at the line of the root there's a pin bookmark // ITextEditorProvider provider = WorkbenchSingleton.Workbench.ActiveViewContent as ITextEditorProvider; // var editor = provider.TextEditor; // if (editor != null) { // var pin = BookmarkManager.Bookmarks.Find( // b => b is PinBookmark && // b.Location.Line == logicalPosition.Line && // b.FileName == editor.FileName) as PinBookmark; // // if (pin != null) { // observable.ForEach(item => { // TODO: find a way not to use "observable" // if (pin.ContainsNode(item)) // item.IsPinned = true; // }); // } // } var source = new VirtualizingIEnumerable(value); lazyGrid.ItemsSource = source; this.dataGrid.AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(handleScroll)); if (this.lazyGrid.ItemsSourceTotalCount != null) { // hide up/down buttons if too few items btnUp.Visibility = btnDown.Visibility = this.lazyGrid.ItemsSourceTotalCount.Value <= VisibleItemsCount ? Visibility.Collapsed : Visibility.Visible; } } //public Location LogicalPosition { get; set; } /// public bool ShowAsPopup { get { return true; } } /// public bool Close(bool mouseClick) { if (mouseClick || (!mouseClick && !isChildExpanded)) { CloseChildPopups(); return true; } else { return false; } } private DebuggerPopup childPopup { get; set; } private DebuggerTooltipControl parentControl { get; set; } internal DebuggerPopup containingPopup { get; set; } bool isChildExpanded { get { return this.childPopup != null && this.childPopup.IsOpen; } } private ToggleButton expandedButton; /// /// Closes the child popup of this control, if it exists. /// public void CloseChildPopups() { if (this.expandedButton != null) { this.expandedButton.IsChecked = false; this.expandedButton = null; // nice simple example of indirect recursion this.childPopup.CloseSelfAndChildren(); } } public void CloseOnLostFocus() { // when we close, parent becomes leaf if (this.containingPopup != null) { this.containingPopup.IsLeaf = true; } if (!this.IsMouseOver) { if (this.containingPopup != null) { this.containingPopup.IsOpen = false; this.containingPopup.IsLeaf = false; } if (this.parentControl != null) { this.parentControl.CloseOnLostFocus(); } OnClosed(); } else { // leaf closed because of click inside this control - stop the closing chain if (this.expandedButton != null && !this.expandedButton.IsMouseOver) { this.expandedButton.IsChecked = false; this.expandedButton = null; } } } private void btnExpander_Click(object sender, RoutedEventArgs e) { var clickedButton = (ToggleButton)e.OriginalSource; var clickedNode = (ITreeNode)clickedButton.DataContext; // use device independent units, because child popup Left/Top are in independent units Point buttonPos = clickedButton.PointToScreen(new Point(0, 0)).TransformFromDevice(clickedButton); if (clickedButton.IsChecked.GetValueOrDefault(false)) { CloseChildPopups(); this.expandedButton = clickedButton; // open child Popup if (this.childPopup == null) { this.childPopup = new DebuggerPopup(this, logicalPosition, false); this.childPopup.Placement = PlacementMode.Absolute; } if (this.containingPopup != null) { this.containingPopup.IsLeaf = false; } this.childPopup.IsLeaf = true; this.childPopup.HorizontalOffset = buttonPos.X + ChildPopupOpenXOffet; this.childPopup.VerticalOffset = buttonPos.Y + ChildPopupOpenYOffet; this.childPopup.ItemsSource = clickedNode.ChildNodes; this.childPopup.Open(); } else { CloseChildPopups(); } } private void handleScroll(object sender, ScrollChangedEventArgs e) { if (this.lazyGrid == null) return; btnUp.IsEnabled = !this.lazyGrid.IsScrolledToStart; btnDown.IsEnabled = !this.lazyGrid.IsScrolledToEnd; } void BtnUp_Click(object sender, RoutedEventArgs e) { if (this.lazyGrid == null) return; this.lazyGrid.ScrollViewer.ScrollUp(1); } void BtnDown_Click(object sender, RoutedEventArgs e) { if (this.lazyGrid == null) return; this.lazyGrid.ScrollViewer.ScrollDown(1); } #region Edit value in tooltip void TextBox_KeyUp(object sender, KeyEventArgs e) { if (e.Key == Key.Escape) { dataGrid.Focus(); return; } if (e.Key == Key.Enter) { dataGrid.Focus(); // set new value var textBox = (TextBox)e.OriginalSource; var newValue = textBox.Text; var node = ((FrameworkElement)sender).DataContext as ITreeNode; SaveNewValue(node, textBox.Text); } } void TextBox_LostFocus(object sender, RoutedEventArgs e) { var textBox = (TextBox)e.OriginalSource; var newValue = textBox.Text; var node = ((FrameworkElement)sender).DataContext as ITreeNode; SaveNewValue(node, textBox.Text); } void SaveNewValue(ITreeNode node, string newValue) { if(node != null && node.SetText(newValue)) { // show adorner var adornerLayer = AdornerLayer.GetAdornerLayer(dataGrid); var adorners = adornerLayer.GetAdorners(dataGrid); if (adorners != null && adorners.Length != 0) adornerLayer.Remove(adorners[0]); SavedAdorner adorner = new SavedAdorner(dataGrid); adornerLayer.Add(adorner); } } #endregion #region Pining checked/unchecked void PinButton_Checked(object sender, RoutedEventArgs e) { // ITextEditorProvider provider = WorkbenchSingleton.Workbench.ActiveViewContent as ITextEditorProvider; // var editor = provider.TextEditor; // if (editor == null) return; // var node = (ITreeNode)(((ToggleButton)(e.OriginalSource)).DataContext); // // if (!string.IsNullOrEmpty(editor.FileName)) { // // // verify if at the line of the root there's a pin bookmark // var pin = BookmarkManager.Bookmarks.Find( // b => b is PinBookmark && // b.LineNumber == logicalPosition.Line && // b.FileName == editor.FileName) as PinBookmark; // // if (pin == null) { // pin = new PinBookmark(editor.FileName, logicalPosition); // // show pinned DebuggerPopup // if (pin.Popup == null) { // pin.Popup = new PinDebuggerControl(); // pin.Popup.Mark = pin; // Rect rect = new Rect(this.DesiredSize); // var point = this.PointToScreen(rect.TopRight); // pin.Popup.Location = new Point { X = 500, Y = point.Y - 150 }; // pin.Nodes.Add(node); // pin.Popup.ItemsSource = pin.Nodes; // } // // // do actions // pin.Popup.Open(); // BookmarkManager.AddMark(pin); // } // else // { // if (!pin.ContainsNode(node)) { // pin.Nodes.Add(node); // pin.Popup.ItemsSource = pin.Nodes; // } // } // } } void PinButton_Unchecked(object sender, RoutedEventArgs e) { // ITextEditorProvider provider = WorkbenchSingleton.Workbench.ActiveViewContent as ITextEditorProvider; // var editor = provider.TextEditor; // if (editor == null) return; // // if (!string.IsNullOrEmpty(editor.FileName)) { // // remove from pinned DebuggerPopup // var pin = BookmarkManager.Bookmarks.Find( // b => b is PinBookmark && // b.LineNumber == logicalPosition.Line && // b.FileName == editor.FileName) as PinBookmark; // if (pin == null) return; // // ToggleButton button = (ToggleButton)e.OriginalSource; // pin.RemoveNode((ITreeNode)button.DataContext); // pin.Popup.ItemsSource = pin.Nodes; // // remove if no more data pins are available // if (pin.Nodes.Count == 0) { // pin.Popup.Close(); // // BookmarkManager.RemoveMark(pin); // } // } } #endregion #region Saved Adorner class SavedAdorner : Adorner { public SavedAdorner(UIElement adornedElement) : base(adornedElement) { Loaded += delegate { Show(); }; } protected override void OnRender(DrawingContext drawingContext) { Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize); // Some arbitrary drawing implements. var formatedText = new FormattedText("Saved", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface(new FontFamily("Arial"), FontStyles.Normal, FontWeights.Black, FontStretches.Expanded), 8d, Brushes.Black); drawingContext.DrawText(formatedText, new Point(adornedElementRect.TopRight.X - formatedText.Width - 2, adornedElementRect.TopRight.Y)); } private void Show() { DoubleAnimation animation = new DoubleAnimation(); animation.From = 1; animation.To = 0; animation.Duration = new Duration(TimeSpan.FromSeconds(2)); animation.SetValue(Storyboard.TargetProperty, this); animation.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath(Rectangle.OpacityProperty)); Storyboard board = new Storyboard(); board.Children.Add(animation); board.Begin(this); } } #endregion } }