462 lines
No EOL
13 KiB
C#
462 lines
No EOL
13 KiB
C#
/*
|
|
Copyright (c) 2017, John Lewin
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions of source code must retain the above copyright notice, this
|
|
list of conditions and the following disclaimer.
|
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
The views and conclusions contained in the software and documentation are those
|
|
of the authors and should not be interpreted as representing official policies,
|
|
either expressed or implied, of the FreeBSD Project.
|
|
*/
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using MatterHackers.Agg;
|
|
using MatterHackers.Agg.Image;
|
|
using MatterHackers.Agg.Platform;
|
|
using MatterHackers.Agg.UI;
|
|
using MatterHackers.ImageProcessing;
|
|
using MatterHackers.MatterControl.Library;
|
|
using MatterHackers.MatterControl.PartPreviewWindow;
|
|
using MatterHackers.MatterControl.PrintQueue;
|
|
using MatterHackers.VectorMath;
|
|
|
|
namespace MatterHackers.MatterControl.CustomWidgets
|
|
{
|
|
public class ListViewItemBase : GuiWidget
|
|
{
|
|
protected ThemeConfig theme;
|
|
protected ListViewItem listViewItem;
|
|
protected View3DWidget view3DWidget;
|
|
protected bool mouseInBounds = false;
|
|
private bool mouseDownInBounds = false;
|
|
private Vector2 mouseDownAt;
|
|
|
|
private ImageBuffer overflowIcon;
|
|
|
|
public ImageWidget imageWidget;
|
|
protected int thumbWidth;
|
|
protected int thumbHeight;
|
|
|
|
public ListViewItemBase(ListViewItem listViewItem, int width, int height, ThemeConfig theme)
|
|
{
|
|
this.theme = theme;
|
|
this.listViewItem = listViewItem;
|
|
this.thumbWidth = width;
|
|
this.thumbHeight = height;
|
|
|
|
overflowIcon = StaticData.Instance.LoadIcon(Path.Combine("ViewTransformControls", "overflow.png"), 32, 32).SetToColor(theme.TextColor);
|
|
}
|
|
|
|
public bool HasMenu { get; set; } = false;
|
|
|
|
public async Task LoadItemThumbnail()
|
|
{
|
|
// On first draw, lookup and set best thumbnail
|
|
await ApplicationController.Instance.Library.LoadItemThumbnail(
|
|
this.SetSizedThumbnail,
|
|
(meshContentProvider) =>
|
|
{
|
|
// Store meshContentProvider reference
|
|
this.meshContentProvider = meshContentProvider;
|
|
|
|
// Schedule work
|
|
this.ScheduleRaytraceOperation();
|
|
},
|
|
listViewItem.Model,
|
|
listViewItem.Container,
|
|
this.thumbWidth,
|
|
this.thumbHeight,
|
|
theme);
|
|
}
|
|
|
|
private void ScheduleRaytraceOperation()
|
|
{
|
|
if (meshContentProvider == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ApplicationController.Instance.Thumbnails.QueueForGeneration(async () =>
|
|
{
|
|
// When dequeued for generation, ensure visible before raytracing. Off-screen widgets are dequeue and will reschedule if redrawn
|
|
if (!this.ActuallyVisibleOnScreen())
|
|
{
|
|
// Skip raytracing operation, requeue on next draw
|
|
raytraceSkipped = true;
|
|
raytracePending = false;
|
|
requeueRaytraceOnDraw = true;
|
|
}
|
|
else
|
|
{
|
|
raytraceSkipped = false;
|
|
requeueRaytraceOnDraw = false;
|
|
|
|
// Show processing image
|
|
this.SetUnsizedThumbnail(theme.GeneratingThumbnailIcon);
|
|
|
|
// Ask the MeshContentProvider to RayTrace the image
|
|
var thumbnail = await meshContentProvider.GetThumbnail(listViewItem.Model, thumbWidth, thumbHeight);
|
|
if (thumbnail != null)
|
|
{
|
|
requeueRaytraceOnDraw = false;
|
|
raytracePending = false;
|
|
|
|
if (GuiWidget.DeviceScale != 1
|
|
&& thumbnail.Width != thumbWidth * GuiWidget.DeviceScale)
|
|
{
|
|
thumbnail = thumbnail.CreateScaledImage(GuiWidget.DeviceScale);
|
|
}
|
|
|
|
if (thumbnail.Width != thumbWidth
|
|
|| thumbnail.Height != thumbHeight)
|
|
{
|
|
this.SetUnsizedThumbnail(thumbnail);
|
|
}
|
|
else
|
|
{
|
|
this.SetSizedThumbnail(thumbnail);
|
|
|
|
if (listViewItem.Container is ILibraryWritableContainer writableContainer)
|
|
{
|
|
writableContainer.SetThumbnail(listViewItem.Model, thumbWidth, thumbHeight, thumbnail);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
internal void EnsureSelection()
|
|
{
|
|
if (this.IsSelectableContent)
|
|
{
|
|
// Clear existing selection when item is not selected and control key is not press
|
|
if (!this.IsSelected
|
|
&& !Keyboard.IsKeyDown(Keys.Control))
|
|
{
|
|
listViewItem.ListView?.SelectedItems.Clear();
|
|
}
|
|
|
|
// Any mouse down ensures selection - mouse up will evaluate if DragDrop occurred and toggle selection if not
|
|
if (!listViewItem.ListView?.SelectedItems.Contains(listViewItem) == true)
|
|
{
|
|
listViewItem.ListView?.SelectedItems.Add(listViewItem);
|
|
}
|
|
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
internal void OnItemSelect()
|
|
{
|
|
if (this.IsSelectableContent
|
|
&& !hitDragThreshold)
|
|
{
|
|
if (toggleSelection)
|
|
{
|
|
listViewItem.ListView?.SelectedItems.Remove(listViewItem);
|
|
}
|
|
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
private bool IsSelectableContent
|
|
{
|
|
get
|
|
{
|
|
bool isContentItem = listViewItem.Model is ILibraryObject3D;
|
|
bool isValidStream = listViewItem.Model is ILibraryAssetStream stream
|
|
&& ApplicationController.Instance.Library.IsContentFileType(stream.FileName);
|
|
bool isContainerLink = listViewItem.Model is ILibraryContainerLink;
|
|
|
|
bool isGCode = (listViewItem.Model is FileSystemFileItem item
|
|
&& Path.GetExtension(item.Name).IndexOf(".gco", StringComparison.OrdinalIgnoreCase) == 0)
|
|
|| (listViewItem.Model is SDCardFileItem sdItem
|
|
&& Path.GetExtension(sdItem.Name).IndexOf(".gco", StringComparison.OrdinalIgnoreCase) == 0);
|
|
|
|
return isContentItem || isValidStream || isContainerLink || isGCode;
|
|
}
|
|
}
|
|
|
|
protected void SetUnsizedThumbnail(ImageBuffer thumbnail)
|
|
{
|
|
this.SetSizedThumbnail(
|
|
ApplicationController.Instance.Library.EnsureCorrectThumbnailSizing(
|
|
thumbnail,
|
|
thumbWidth,
|
|
thumbHeight,
|
|
(image) =>
|
|
{
|
|
SetSizedThumbnail(image);
|
|
}));
|
|
}
|
|
|
|
private void SetSizedThumbnail(ImageBuffer thumbnail)
|
|
{
|
|
if (thumbnail != null
|
|
&& this.imageWidget != null
|
|
&& (this.imageWidget.Image == null
|
|
|| !thumbnail.Equals(this.imageWidget.Image, 5)))
|
|
{
|
|
this.imageWidget.Image = thumbnail;
|
|
this.Invalidate();
|
|
}
|
|
}
|
|
|
|
public override Color BorderColor
|
|
{
|
|
get => (this.IsSelected || mouseInBounds) ? theme.PrimaryAccentColor : base.BorderColor;
|
|
set => base.BorderColor = value;
|
|
}
|
|
|
|
private bool hitDragThreshold = false;
|
|
|
|
private bool toggleSelection = false;
|
|
|
|
public override void OnMouseDown(MouseEventArgs mouseEvent)
|
|
{
|
|
mouseDownInBounds = true;
|
|
mouseDownAt = mouseEvent.Position;
|
|
hitDragThreshold = false;
|
|
|
|
// Used to toggle selection on selected items - revised to require control key
|
|
toggleSelection = this.IsSelected && Keyboard.IsKeyDown(Keys.Control);
|
|
|
|
this.EnsureSelection();
|
|
|
|
if (IsDoubleClick(mouseEvent))
|
|
{
|
|
listViewItem.OnDoubleClick();
|
|
}
|
|
|
|
// On mouse down update the view3DWidget reference that will be used in MouseMove and MouseUp
|
|
view3DWidget = ApplicationController.Instance.DragDropData.View3DWidget;
|
|
|
|
base.OnMouseDown(mouseEvent);
|
|
}
|
|
|
|
public override void OnLoad(EventArgs args)
|
|
{
|
|
foreach (var child in Children)
|
|
{
|
|
child.Selectable = false;
|
|
}
|
|
|
|
// On first draw, lookup and set best thumbnail
|
|
Task.Run(this.LoadItemThumbnail);
|
|
|
|
base.OnLoad(args);
|
|
}
|
|
|
|
public override void OnDraw(Graphics2D graphics2D)
|
|
{
|
|
if (requeueRaytraceOnDraw
|
|
&& !raytracePending
|
|
&& raytraceSkipped)
|
|
{
|
|
raytracePending = true;
|
|
|
|
// Requeue thumbnail generation
|
|
this.ScheduleRaytraceOperation();
|
|
}
|
|
|
|
if (this.mouseInBounds
|
|
&& this.HasMenu)
|
|
{
|
|
var bounds = this.LocalBounds;
|
|
graphics2D.Render(overflowIcon, new Point2D(bounds.Right - 32, bounds.Top - 32 - 3));
|
|
}
|
|
|
|
base.OnDraw(graphics2D);
|
|
}
|
|
|
|
public override void OnMouseMove(MouseEventArgs mouseEvent)
|
|
{
|
|
var delta = mouseDownAt - mouseEvent.Position;
|
|
|
|
// If mouseDown on us and we've moved past are drag determination threshold, notify view3DWidget
|
|
if (mouseDownInBounds && delta.Length > 40
|
|
&& view3DWidget != null
|
|
&& !(listViewItem.Model is MissingFileItem))
|
|
{
|
|
hitDragThreshold = true;
|
|
|
|
// Performs move and possible Scene add in View3DWidget
|
|
var listView = this.Parents<LibraryListView>().First();
|
|
var posRelListView = this.TransformToParentSpace(listView, mouseEvent.Position);
|
|
if (!listView.LocalBounds.Contains(posRelListView))
|
|
{
|
|
view3DWidget.ExternalDragOver(screenSpaceMousePosition: this.TransformToScreenSpace(mouseEvent.Position), sourceWidget: this.listViewItem.ListView);
|
|
}
|
|
}
|
|
|
|
base.OnMouseMove(mouseEvent);
|
|
}
|
|
|
|
public override void OnMouseUp(MouseEventArgs mouseEvent)
|
|
{
|
|
this.OnItemSelect();
|
|
|
|
if (view3DWidget?.DragOperationActive == true)
|
|
{
|
|
// Mouse and widget positions
|
|
var screenSpaceMousePosition = this.TransformToScreenSpace(mouseEvent.Position);
|
|
var meshViewerPosition = view3DWidget.Object3DControlLayer.TransformToScreenSpace(view3DWidget.Object3DControlLayer.LocalBounds);
|
|
|
|
// Notify of drag operation complete
|
|
view3DWidget.FinishDrop(mouseUpInBounds: meshViewerPosition.Contains(screenSpaceMousePosition));
|
|
}
|
|
|
|
view3DWidget = null;
|
|
|
|
mouseDownInBounds = false;
|
|
base.OnMouseUp(mouseEvent);
|
|
}
|
|
|
|
public override void OnMouseEnterBounds(MouseEventArgs mouseEvent)
|
|
{
|
|
base.OnMouseEnterBounds(mouseEvent);
|
|
mouseInBounds = true;
|
|
UpdateHoverState();
|
|
Invalidate();
|
|
}
|
|
|
|
public override void OnMouseLeaveBounds(MouseEventArgs mouseEvent)
|
|
{
|
|
mouseInBounds = false;
|
|
base.OnMouseLeaveBounds(mouseEvent);
|
|
UpdateHoverState();
|
|
Invalidate();
|
|
}
|
|
|
|
public override void OnKeyDown(KeyEventArgs keyEvent)
|
|
{
|
|
if (!keyEvent.Handled)
|
|
{
|
|
var listView = listViewItem.ListView;
|
|
if (listView != null
|
|
&& listView.SelectedItems.Count == 1
|
|
&& listView.SelectedItems.FirstOrDefault()?.Model is ILibraryItem firstItem
|
|
&& !firstItem.IsProtected
|
|
&& listView.ActiveContainer is ILibraryWritableContainer)
|
|
{
|
|
switch (keyEvent.KeyCode)
|
|
{
|
|
case Keys.F2:
|
|
firstItem.Rename();
|
|
listView.SelectedItems.Clear();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
base.OnKeyDown(keyEvent);
|
|
}
|
|
|
|
protected override void OnClick(MouseEventArgs mouseEvent)
|
|
{
|
|
var bounds = this.LocalBounds;
|
|
var hitRegion = new RectangleDouble(
|
|
new Vector2(bounds.Right - 32, bounds.Top),
|
|
new Vector2(bounds.Right, bounds.Top - 32));
|
|
|
|
if (this.HasMenu
|
|
&& listViewItem?.ListView?.MenuActions?.Any() == true
|
|
&& (hitRegion.Contains(mouseEvent.Position)
|
|
|| mouseEvent.Button == MouseButtons.Right))
|
|
{
|
|
var popupMenu = new PopupMenu(ApplicationController.Instance.MenuTheme);
|
|
|
|
foreach (var menuAction in listViewItem.ListView.MenuActions.Where(m => m.Scope == ActionScope.ListItem))
|
|
{
|
|
if (menuAction is MenuSeparator)
|
|
{
|
|
popupMenu.CreateSeparator();
|
|
}
|
|
else if (menuAction.IsEnabled(this.listViewItem.ListView.SelectedItems, this.listViewItem.ListView))
|
|
{
|
|
var item = popupMenu.CreateMenuItem(menuAction.Title, menuAction.Icon);
|
|
item.Click += (s, e) =>
|
|
{
|
|
popupMenu.Close();
|
|
menuAction.Action.Invoke(this.listViewItem.ListView.SelectedItems.Select(o => o.Model), this.listViewItem.ListView);
|
|
};
|
|
}
|
|
}
|
|
|
|
RectangleDouble popupBounds;
|
|
if (mouseEvent.Button == MouseButtons.Right)
|
|
{
|
|
popupBounds = new RectangleDouble(mouseEvent.X + 1, mouseEvent.Y + 1, mouseEvent.X + 1, mouseEvent.Y + 1);
|
|
}
|
|
else
|
|
{
|
|
popupBounds = new RectangleDouble(this.Width - 32, this.Height - 32, this.Width, this.Height);
|
|
}
|
|
|
|
popupMenu.ShowMenu(this, mouseEvent);
|
|
}
|
|
|
|
base.OnClick(mouseEvent);
|
|
}
|
|
|
|
protected virtual void UpdateColors()
|
|
{
|
|
}
|
|
|
|
protected virtual void UpdateHoverState()
|
|
{
|
|
}
|
|
|
|
public virtual bool IsHoverItem { get; set; }
|
|
|
|
public virtual bool EditMode { get; set; }
|
|
|
|
private bool isSelected = false;
|
|
private bool raytraceSkipped;
|
|
private bool requeueRaytraceOnDraw;
|
|
private bool raytracePending;
|
|
private MeshContentProvider meshContentProvider;
|
|
|
|
public bool IsSelected
|
|
{
|
|
get
|
|
{
|
|
return isSelected;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (isSelected != value)
|
|
{
|
|
// selectionCheckBox.Checked = value;
|
|
|
|
isSelected = value;
|
|
UpdateColors();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |