CtrEditor/MainWindow.xaml.cs

1107 lines
46 KiB
C#

using CtrEditor.ObjetosSim;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
using UserControl = System.Windows.Controls.UserControl;
using CtrEditor.Controls; // Add this using statement
using Newtonsoft.Json;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace CtrEditor
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
// Para el Canvas
private Point _lastMousePosition;
private bool _isDrawingCanvas = false;
private bool _isDraggingCanvas = false;
private Image imagenDeFondo;
internal ObjectManipulationManager _objectManager;
private Rectangle _panningArea;
// Temporizadores y animación
private DispatcherTimer _zoomTimer;
private double _targetZoomFactor;
private double _initialZoomFactor;
private double _currentZoomStep;
private Point _zoomCursorPosition;
private const int ZoomDuration = 500; // Duración del zoom en milisegundos
private int _ZoomDuration;
private const double MinZoomScale = 0.1; // Límite mínimo de zoom
private Stopwatch _stopwatch;
private dataDebug dataDebug = new dataDebug();
public MainWindow()
{
InitializeComponent();
_objectManager = new ObjectManipulationManager(this, ImagenEnTrabajoCanvas);
_panningArea = new Rectangle
{
Fill = Brushes.Transparent,
IsHitTestVisible = true
};
// Inicializar temporizador de zoom
_zoomTimer = new DispatcherTimer();
_zoomTimer.Interval = TimeSpan.FromMilliseconds(1);
_zoomTimer.Tick += ZoomTimer_Tick;
_stopwatch = new Stopwatch();
// Suscribir eventos
this.Loaded += MainWindow_Loaded;
ImagenEnTrabajoScrollViewer.PreviewMouseWheel += ImagenEnTrabajoCanvas_MouseWheel;
ImagenEnTrabajoCanvas.MouseDown += Canvas_MouseDown_Panning;
ImagenEnTrabajoCanvas.MouseMove += Canvas_MouseMove_Panning;
ImagenEnTrabajoCanvas.MouseUp += Canvas_MouseUp_Panning;
_panningArea.MouseDown += Canvas_MouseDown_Panning;
_panningArea.MouseRightButtonDown += Canvas_MouseRightButtonDown;
this.KeyDown += MainWindow_KeyDown;
ImagenEnTrabajoCanvas.MouseEnter += Canvas_MouseEnter;
this.Closed += MainWindow_Closed;
// Importante: Agregar el evento para el menú contextual
ImagenEnTrabajoCanvas.MouseRightButtonDown += Canvas_MouseRightButtonDown;
// Asegurarse de que estos eventos estén conectados
ImagenEnTrabajoCanvas.MouseUp += Canvas_MouseUp_Panning;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
if (DataContext is MainViewModel viewModel)
{
viewModel.MainWindow = this;
viewModel.ImageSelected += ViewModel_ImageSelected;
viewModel?.LoadInitialData();
viewModel.simulationManager.DebugCanvas = ImagenEnTrabajoCanvas;
viewModel.MainCanvas = ImagenEnTrabajoCanvas;
// Inicializar ObjectHierarchyView
ObjectHierarchy.Initialize(viewModel);
}
}
public void SuscribirEventos(UserControl userControl)
{
_objectManager.SuscribirEventos(userControl);
}
public void AgregarRegistrarUserControlCanvas(UserControl userControl)
{
if (userControl is IDataContainer dataContainer)
{
SuscribirEventos(userControl);
Canvas.SetZIndex(userControl, ((int)dataContainer.ZIndex_Base() + dataContainer.zIndex_fromFrames));
ImagenEnTrabajoCanvas.Children.Add(userControl);
}
}
public void EliminarUserControlDelCanvas(UserControl userControl)
{
if (ImagenEnTrabajoCanvas.Children.Contains(userControl))
{
ImagenEnTrabajoCanvas.Children.Remove(userControl);
}
}
private void LoadImageToCanvas(string imagePath)
{
BitmapImage bitmap = new BitmapImage(new Uri(imagePath, UriKind.Absolute));
if (imagenDeFondo == null)
{
imagenDeFondo = new Image();
ImagenEnTrabajoCanvas.Children.Add(imagenDeFondo);
}
imagenDeFondo.Source = bitmap;
RenderOptions.SetBitmapScalingMode(imagenDeFondo, BitmapScalingMode.HighQuality);
// Actualizar dimensiones del canvas
ImagenEnTrabajoCanvas.Width = bitmap.Width;
ImagenEnTrabajoCanvas.Height = bitmap.Height;
// Configurar el área de panning
_panningArea.Width = bitmap.Width * 4;
_panningArea.Height = bitmap.Height * 4;
// Posicionar el área de panning centrada respecto a la imagen
Canvas.SetLeft(_panningArea, -bitmap.Width / 4);
Canvas.SetTop(_panningArea, -bitmap.Height / 4);
Canvas.SetZIndex(_panningArea, -1); // Asegurar que está detrás de todo
// Asegurarse de que el área de panning está en el canvas
if (!ImagenEnTrabajoCanvas.Children.Contains(_panningArea))
{
ImagenEnTrabajoCanvas.Children.Add(_panningArea);
}
// Posicionar la imagen
Canvas.SetLeft(imagenDeFondo, 0);
Canvas.SetTop(imagenDeFondo, 0);
// Eliminar solo los ROIs, no la imagen de fondo ni el área de panning
for (int i = ImagenEnTrabajoCanvas.Children.Count - 1; i >= 0; i--)
{
var child = ImagenEnTrabajoCanvas.Children[i];
if (child != imagenDeFondo && child != _panningArea)
{
ImagenEnTrabajoCanvas.Children.RemoveAt(i);
}
}
}
private void Canvas_MouseUp_Panning(object sender, MouseButtonEventArgs e)
{
if (_isDraggingCanvas)
{
_isDraggingCanvas = false;
ImagenEnTrabajoCanvas.ReleaseMouseCapture();
Mouse.OverrideCursor = null;
if (_objectManager.SelectedObjects.Count > 0)
{
_objectManager.MakeResizeRectanglesNormal();
_objectManager.UpdateSelectionVisuals(); // Agregar esta línea
}
e.Handled = true;
}
}
private void Canvas_MouseDown_Panning(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed && !_isDrawingCanvas)
{
// Permitir el panning cuando se hace clic en el área de panning o en la imagen
if (e.Source == _panningArea ||
e.Source == imagenDeFondo ||
e.Source == ImagenEnTrabajoCanvas ||
(e.Source is Rectangle rect && rect == _objectManager._selectionRectangle))
{
ImagenEnTrabajoCanvas.Focus();
if (_objectManager.IsRectangleSelectionActive)
{
if (!_objectManager.HasActiveSelectionRectangle)
{
_objectManager.StartRectangleSelection(e.GetPosition(ImagenEnTrabajoCanvas));
}
else
{
_objectManager.FinishRectangleSelection(e.GetPosition(ImagenEnTrabajoCanvas));
}
}
else
{
_isDraggingCanvas = true;
_lastMousePosition = e.GetPosition(ImagenEnTrabajoScrollViewer);
ImagenEnTrabajoCanvas.CaptureMouse(); // Importante: siempre capturar en el Canvas principal
Mouse.OverrideCursor = Cursors.Hand;
if (DataContext is MainViewModel viewModel)
{
viewModel.SelectedItemOsList = null;
_objectManager.RemoveResizeRectangles();
}
}
e.Handled = true;
}
}
}
private void Canvas_MouseMove_Panning(object sender, MouseEventArgs e)
{
if (_objectManager.IsRectangleSelectionActive && _objectManager.HasActiveSelectionRectangle)
{
var currentPoint = e.GetPosition(ImagenEnTrabajoCanvas);
_objectManager.UpdateRectangleSelection(currentPoint);
}
else if (_isDraggingCanvas && e.LeftButton == MouseButtonState.Pressed)
{
var currentPosition = e.GetPosition(ImagenEnTrabajoScrollViewer);
var dx = currentPosition.X - _lastMousePosition.X;
var dy = currentPosition.Y - _lastMousePosition.Y;
_objectManager.MakeResizeRectanglesTransparent();
_objectManager.RemoveHighlightRectangles();
var transform = (TranslateTransform)((TransformGroup)ImagenEnTrabajoCanvas.RenderTransform)
.Children.First(t => t is TranslateTransform);
transform.X += dx;
transform.Y += dy;
_lastMousePosition = currentPosition;
e.Handled = true;
}
}
private void ImagenEnTrabajoCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
_objectManager.MakeResizeRectanglesTransparent();
_objectManager.RemoveHighlightRectangles();
_initialZoomFactor = ((ScaleTransform)((TransformGroup)ImagenEnTrabajoCanvas.RenderTransform)
.Children.First(t => t is ScaleTransform)).ScaleX;
double minZoomFactor = Math.Min(
ImagenEnTrabajoScrollViewer.ViewportWidth / ImagenEnTrabajoCanvas.ActualWidth,
ImagenEnTrabajoScrollViewer.ViewportHeight / ImagenEnTrabajoCanvas.ActualHeight) / 1.5;
_targetZoomFactor = e.Delta > 0 ?
_initialZoomFactor * 1.4 :
Math.Max(_initialZoomFactor * 0.75, minZoomFactor);
_zoomCursorPosition = e.GetPosition(ImagenEnTrabajoCanvas);
RenderOptions.SetBitmapScalingMode(ImagenEnTrabajoCanvas, BitmapScalingMode.LowQuality);
_ZoomDuration = !_zoomTimer.IsEnabled ? ZoomDuration : ZoomDuration / 3;
_stopwatch.Restart();
_zoomTimer.Start();
e.Handled = true;
}
private void ZoomTimer_Tick(object sender, EventArgs e)
{
double elapsedMilliseconds = _stopwatch.Elapsed.TotalMilliseconds;
if (elapsedMilliseconds >= _ZoomDuration)
{
_zoomTimer.Stop();
_stopwatch.Stop();
RenderOptions.SetBitmapScalingMode(ImagenEnTrabajoCanvas, BitmapScalingMode.HighQuality);
if (_objectManager.SelectedObjects.Count > 0)
{
_objectManager.MakeResizeRectanglesNormal();
_objectManager.UpdateSelectionVisuals(); // Agregar esta línea
}
return;
}
var tg = (TransformGroup)ImagenEnTrabajoCanvas.RenderTransform;
var st = (ScaleTransform)tg.Children.First(t => t is ScaleTransform);
var tt = (TranslateTransform)tg.Children.First(t => t is TranslateTransform);
double t = elapsedMilliseconds / _ZoomDuration;
double easeOutT = t * (2 - t);
double zoomFactor = _initialZoomFactor + (_targetZoomFactor - _initialZoomFactor) * easeOutT;
zoomFactor = Math.Max(zoomFactor, MinZoomScale);
Point cursorPosition = _zoomCursorPosition;
var relativeX = cursorPosition.X * st.ScaleX + tt.X;
var relativeY = cursorPosition.Y * st.ScaleY + tt.Y;
st.ScaleX = zoomFactor;
st.ScaleY = zoomFactor;
tt.X = relativeX - cursorPosition.X * st.ScaleX;
tt.Y = relativeY - cursorPosition.Y * st.ScaleY;
}
private void ListaOs_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0 && e.AddedItems[0] is osBase selectedObject)
{
// Siempre trabajar con selección única para las propiedades
CargarPropiedadesosDatos(selectedObject);
_objectManager.SelectObject(selectedObject);
}
else if (e.RemovedItems.Count > 0 && e.AddedItems.Count == 0)
{
// This handles the case when an item is deselected and no new item is selected
CargarPropiedadesosDatos(null);
_objectManager.RemoveResizeRectangles();
}
}
private void ImagePathButton_Click(object sender, RoutedEventArgs e)
{
var dlg = new Ookii.Dialogs.Wpf.VistaOpenFileDialog
{
Filter = "Image files (*.png;*.jpg;*.bmp)|*.png;*.jpg;*.bmp|All files (*.*)|*.*"
};
if (dlg.ShowDialog() == true)
{
// El DataContext de este botón es el PropertyItem asociado
if ((sender as Button)?.DataContext is Xceed.Wpf.Toolkit.PropertyGrid.PropertyItem propertyItem)
{
propertyItem.Value = dlg.FileName;
}
}
}
private void MainWindow_KeyDown(object sender, KeyEventArgs e)
{
// Only force canvas focus if PanelEdicion doesn't have focus
if (!PanelEdicion.IsKeyboardFocusWithin)
{
if (!ImagenEnTrabajoCanvas.IsFocused)
{
ImagenEnTrabajoCanvas.Focus();
}
}
if (DataContext is MainViewModel viewModel)
{
if (e.Key == Key.Delete)
{
// This will delete all selected objects
_objectManager.EliminarObjetosSeleccionados();
e.Handled = true;
}
else if (e.Key == Key.Escape)
{
viewModel.SelectedItemOsList = null;
_objectManager.ClearSelection();
_objectManager.RemoveResizeRectangles();
e.Handled = true;
}
else if (_objectManager.SelectedObjects.Any())
{
const float moveDistance = 0.1f;
switch (e.Key)
{
case Key.Left:
MoveSelectedObjects(-moveDistance, 0);
e.Handled = true;
break;
case Key.Right:
MoveSelectedObjects(moveDistance, 0);
e.Handled = true;
break;
case Key.Up:
MoveSelectedObjects(0, -moveDistance);
e.Handled = true;
break;
case Key.Down:
MoveSelectedObjects(0, moveDistance);
e.Handled = true;
break;
}
}
}
}
private void MoveSelectedObjects(float deltaX, float deltaY)
{
// Mover todos los objetos primero
foreach (var obj in _objectManager.SelectedObjects)
{
obj.Left += deltaX;
obj.Top += deltaY;
}
// Forzar una actualización del layout antes de actualizar los visuales
ImagenEnTrabajoCanvas.UpdateLayout();
// Ahora actualizar los visuales de selección
_objectManager.UpdateSelectionVisuals();
if (DataContext is MainViewModel viewModel)
{
viewModel.HasUnsavedChanges = true;
}
}
private void LockSelectedObjects(bool lockState)
{
foreach (var selectedObject in _objectManager.SelectedObjects)
{
selectedObject.Lock_movement = lockState;
}
// Actualizar los visuales de selección para reflejar el cambio de estado
_objectManager.UpdateSelectionVisuals();
// Marcar como cambios sin guardar
if (DataContext is MainViewModel viewModel)
{
viewModel.HasUnsavedChanges = true;
}
}
private void Canvas_MouseEnter(object sender, MouseEventArgs e)
{
if (e.OriginalSource == ImagenEnTrabajoCanvas)
{
//_objectManager.RemoveResizeRectangles();
}
}
private void MainWindow_Closed(object sender, EventArgs e)
{
if (DataContext is MainViewModel viewModel)
{
viewModel.ImageSelected -= ViewModel_ImageSelected;
}
}
private void CargarPropiedadesosDatos(osBase selectedObject)
{
if (DataContext is MainViewModel viewModel)
{
viewModel.CargarPropiedadesosDatos(selectedObject, PanelEdicion, Resources);
// If no object is selected, make sure to clear the properties panel
if (selectedObject == null)
{
PanelEdicion.ClearProperties();
}
}
}
public (float X, float Y) ObtenerCentroCanvasMeters()
{
var c = ObtenerCentroCanvasPixels();
return (PixelToMeter.Instance.calc.PixelsToMeters(c.X),
PixelToMeter.Instance.calc.PixelsToMeters(c.Y));
}
public (float X, float Y) ObtenerCentroCanvasPixels()
{
var tg = (TransformGroup)ImagenEnTrabajoCanvas.RenderTransform;
var st = (ScaleTransform)tg.Children.First(t => t is ScaleTransform);
var tt = (TranslateTransform)tg.Children.First(t => t is TranslateTransform);
double visibleWidth = ImagenEnTrabajoScrollViewer.ViewportWidth;
double visibleHeight = ImagenEnTrabajoScrollViewer.ViewportHeight;
double offsetX = ImagenEnTrabajoScrollViewer.HorizontalOffset;
double offsetY = ImagenEnTrabajoScrollViewer.VerticalOffset;
double centerX = offsetX + (visibleWidth / 2);
double centerY = offsetY + (visibleHeight / 2);
double canvasCenterX = (centerX - tt.X) / st.ScaleX;
double canvasCenterY = (centerY - tt.Y) / st.ScaleY;
return ((float)canvasCenterX, (float)canvasCenterY);
}
private void ViewModel_ImageSelected(object sender, string imagePath)
{
LoadImageToCanvas(imagePath);
}
public void DebugWindow()
{
var debugWindow = new wDebug
{
Data = dataDebug
};
debugWindow.Show();
}
private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
// Aceptar el evento si viene del canvas o de la imagen de fondo
if ((e.Source == ImagenEnTrabajoCanvas || e.Source == imagenDeFondo || e.Source is UserControl) &&
DataContext is MainViewModel viewModel)
{
ImagenEnTrabajoCanvas.Focus(); // Asegurar que el canvas tiene el foco
e.Handled = true;
ShowContextMenu(e.GetPosition(ImagenEnTrabajoCanvas));
}
}
private void ShowContextMenu(Point position)
{
var contextMenu = new ContextMenu();
if (DataContext is MainViewModel viewModel)
{
// Multi-selection checkbox
var multiSelectMenuItem = new MenuItem
{
Header = "Modo Multi-Selección",
IsCheckable = true,
StaysOpenOnClick = false,
IsChecked = viewModel.IsMultiSelectionActive
};
multiSelectMenuItem.Click += (s, e) =>
{
viewModel.IsMultiSelectionActive = multiSelectMenuItem.IsChecked;
// Si se desactiva la multi-selección, desactivar también la selección por rectángulo
if (!multiSelectMenuItem.IsChecked)
_objectManager.IsRectangleSelectionActive = false;
};
contextMenu.Items.Add(multiSelectMenuItem);
// Mostrar opción de selección por rectángulo solo cuando multi-selección está activa
if (viewModel.IsMultiSelectionActive)
{
var rectangleSelectMenuItem = new MenuItem
{
Header = "Selección por Rectángulo",
IsCheckable = true,
IsChecked = _objectManager.IsRectangleSelectionActive,
StaysOpenOnClick = false
};
rectangleSelectMenuItem.Click += (s, e) =>
{
_objectManager.IsRectangleSelectionActive = rectangleSelectMenuItem.IsChecked;
_objectManager.IsDraggingCanvas = false; // Desactivar panning cuando se activa la selección por rectángulo
};
contextMenu.Items.Add(rectangleSelectMenuItem);
}
// Si hay objetos seleccionados, agregar opciones de alineación
if (_objectManager.SelectedObjects.Count > 1)
{
contextMenu.Items.Add(new Separator());
var alignSubmenu = new MenuItem { Header = "Alinear" };
// Alineación horizontal
var alignLeftItem = new MenuItem { Header = "Izquierda" };
alignLeftItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.Left);
alignSubmenu.Items.Add(alignLeftItem);
var alignRightItem = new MenuItem { Header = "Derecha" };
alignRightItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.Right);
alignSubmenu.Items.Add(alignRightItem);
var alignCenterHItem = new MenuItem { Header = "Centro Horizontal" };
alignCenterHItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.CenterHorizontally);
alignSubmenu.Items.Add(alignCenterHItem);
// Alineación vertical
alignSubmenu.Items.Add(new Separator());
var alignTopItem = new MenuItem { Header = "Arriba" };
alignTopItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.Top);
alignSubmenu.Items.Add(alignTopItem);
var alignBottomItem = new MenuItem { Header = "Abajo" };
alignBottomItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.Bottom);
alignSubmenu.Items.Add(alignBottomItem);
var alignCenterVItem = new MenuItem { Header = "Centro Vertical" };
alignCenterVItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.CenterVertically);
alignSubmenu.Items.Add(alignCenterVItem);
// Distribución
alignSubmenu.Items.Add(new Separator());
var distributeHItem = new MenuItem { Header = "Distribuir Horizontalmente" };
distributeHItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.DistributeHorizontally);
alignSubmenu.Items.Add(distributeHItem);
var distributeVItem = new MenuItem { Header = "Distribuir Verticalmente" };
distributeVItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.DistributeVertically);
alignSubmenu.Items.Add(distributeVItem);
// Igualar tamaños
alignSubmenu.Items.Add(new Separator());
var equalWidthItem = new MenuItem { Header = "Igualar Ancho" };
equalWidthItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.EqualWidth);
alignSubmenu.Items.Add(equalWidthItem);
var equalHeightItem = new MenuItem { Header = "Igualar Alto" };
equalHeightItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.EqualHeight);
alignSubmenu.Items.Add(equalHeightItem);
var equalAngleItem = new MenuItem { Header = "Igualar Ángulo" };
equalAngleItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.EqualAngle);
alignSubmenu.Items.Add(equalAngleItem);
contextMenu.Items.Add(alignSubmenu);
}
// Add Edit Properties option when objects are selected
if (_objectManager.SelectedObjects.Count > 0)
{
var editPropertiesItem = new MenuItem { Header = "Edit Properties" };
editPropertiesItem.Click += (s, e) => viewModel.ShowMultiPropertyEditor(_objectManager.SelectedObjects);
contextMenu.Items.Add(editPropertiesItem);
contextMenu.Items.Add(new Separator());
// Agregar opción de copiar
var copyMenuItem = new MenuItem { Header = "Copiar (Ctrl+C)" };
copyMenuItem.Click += (s, e) => CopySelectedObjectsAsJson();
contextMenu.Items.Add(copyMenuItem);
contextMenu.Items.Add(new Separator());
}
// Agregar opciones de pegado
if (CanPasteFromClipboard())
{
var pasteMenuItem = new MenuItem { Header = "Pegar (Ctrl+V)" };
pasteMenuItem.Click += (s, e) => PasteObjectsFromJson();
contextMenu.Items.Add(pasteMenuItem);
var pasteReplaceMenuItem = new MenuItem { Header = "Pegar con Reemplazo" };
pasteReplaceMenuItem.Click += (s, e) => PasteObjectsFromJson(true);
contextMenu.Items.Add(pasteReplaceMenuItem);
contextMenu.Items.Add(new Separator());
// Opciones de bloqueo/desbloqueo
var lockSubmenu = new MenuItem { Header = "Bloqueo" };
// Verificar el estado actual de los objetos
int totalObjects = _objectManager.SelectedObjects.Count;
int lockedObjects = _objectManager.SelectedObjects.Count(obj => obj.Lock_movement);
int unlockedObjects = totalObjects - lockedObjects;
var lockAllItem = new MenuItem { Header = $"Bloquear Objetos ({unlockedObjects} desbloqueados)" };
lockAllItem.Click += (s, e) => LockSelectedObjects(true);
lockAllItem.IsEnabled = unlockedObjects > 0;
lockSubmenu.Items.Add(lockAllItem);
var unlockAllItem = new MenuItem { Header = $"Desbloquear Objetos ({lockedObjects} bloqueados)" };
unlockAllItem.Click += (s, e) => LockSelectedObjects(false);
unlockAllItem.IsEnabled = lockedObjects > 0;
lockSubmenu.Items.Add(unlockAllItem);
// Mostrar estado actual si hay mezcla
if (lockedObjects > 0 && unlockedObjects > 0)
{
lockSubmenu.Items.Add(new Separator());
var statusItem = new MenuItem { Header = $"Estado: {lockedObjects} bloqueados, {unlockedObjects} libres", IsEnabled = false };
lockSubmenu.Items.Add(statusItem);
}
contextMenu.Items.Add(lockSubmenu);
contextMenu.Items.Add(new Separator());
}
contextMenu.IsOpen = true;
contextMenu.PlacementTarget = ImagenEnTrabajoCanvas;
contextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.MousePoint;
}
}
private void Canvas_KeyDown(object sender, KeyEventArgs e)
{
// Only handle if PanelEdicion doesn't have focus
if (!PanelEdicion.IsKeyboardFocusWithin)
{
HandleKeyDown(e);
}
}
private void ScrollViewer_PreviewKeyDown(object sender, KeyEventArgs e)
{
// Only handle if PanelEdicion doesn't have focus
if (!PanelEdicion.IsKeyboardFocusWithin &&
(e.Key == Key.Left || e.Key == Key.Right || e.Key == Key.Up || e.Key == Key.Down))
{
HandleKeyDown(e);
e.Handled = true;
}
}
private void HandleKeyDown(KeyEventArgs e)
{
if (DataContext is MainViewModel viewModel)
{
if (e.Key == Key.Delete)
{
_objectManager.EliminarObjetosSeleccionados(); // Cambiar aquí
e.Handled = true;
}
else if (e.Key == Key.Escape)
{
viewModel.SelectedItemOsList = null;
_objectManager.ClearSelection();
_objectManager.RemoveResizeRectangles();
e.Handled = true;
}
else if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.C)
{
CopySelectedObjectsAsJson();
e.Handled = true;
}
else if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.V)
{
PasteObjectsFromJson();
e.Handled = true;
}
else if (_objectManager.SelectedObjects.Any())
{
const float moveDistance = 0.01f;
switch (e.Key)
{
case Key.Left:
MoveSelectedObjects(-moveDistance, 0);
e.Handled = true;
break;
case Key.Right:
MoveSelectedObjects(moveDistance, 0);
e.Handled = true;
break;
case Key.Up:
MoveSelectedObjects(0, -moveDistance);
e.Handled = true;
break;
case Key.Down:
MoveSelectedObjects(0, moveDistance);
e.Handled = true;
break;
}
}
}
}
public Image ImagenDeFondo => imagenDeFondo;
private void CopySelectedObjectsAsJson()
{
if (!_objectManager.SelectedObjects.Any())
return;
try
{
var objectsToCopy = new List<osBase>();
// Preparar objetos para serialización
foreach (var obj in _objectManager.SelectedObjects)
{
obj.SalvarDatosNoSerializables();
objectsToCopy.Add(obj);
}
// Crear configuración de serialización igual a la usada en el guardado
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
TypeNameHandling = TypeNameHandling.Auto,
ObjectCreationHandling = ObjectCreationHandling.Replace,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
};
// Serializar objetos
string jsonString = JsonConvert.SerializeObject(objectsToCopy, settings);
// Copiar al portapapeles
Clipboard.SetText(jsonString);
// Mostrar mensaje de confirmación
Console.WriteLine($"Copiados {objectsToCopy.Count} objeto(s) al portapapeles");
// Restaurar datos no serializables
foreach (var obj in _objectManager.SelectedObjects)
{
obj.RestaurarDatosNoSerializables();
}
}
catch (Exception ex)
{
MessageBox.Show($"Error al copiar objetos: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void PasteObjectsFromJson(bool replaceExistingIds = false)
{
if (!Clipboard.ContainsText())
return;
try
{
string jsonString = Clipboard.GetText();
// Validación básica del JSON
if (string.IsNullOrWhiteSpace(jsonString) || (!jsonString.TrimStart().StartsWith("[") && !jsonString.TrimStart().StartsWith("{")))
{
MessageBox.Show("El contenido del portapapeles no parece ser un JSON válido.", "Error", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// Crear configuración de deserialización
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
TypeNameHandling = TypeNameHandling.Auto,
ObjectCreationHandling = ObjectCreationHandling.Replace,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
};
List<osBase> pastedObjects = null;
// Intentar deserializar como lista de objetos
try
{
pastedObjects = JsonConvert.DeserializeObject<List<osBase>>(jsonString, settings);
}
catch
{
// Si falla, intentar como objeto individual
try
{
var singleObject = JsonConvert.DeserializeObject<osBase>(jsonString, settings);
if (singleObject != null)
{
pastedObjects = new List<osBase> { singleObject };
}
}
catch
{
MessageBox.Show("No se pudo deserializar el contenido del portapapeles como objetos válidos.", "Error", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
}
if (pastedObjects == null || !pastedObjects.Any())
{
MessageBox.Show("No se encontraron objetos válidos en el portapapeles.", "Error", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// Obtener posición del mouse en el canvas
var mousePosition = Mouse.GetPosition(ImagenEnTrabajoCanvas);
var mousePositionMeters = (
PixelToMeter.Instance.calc.PixelsToMeters((float)mousePosition.X),
PixelToMeter.Instance.calc.PixelsToMeters((float)mousePosition.Y)
);
// Calcular el centro de los objetos pegados para posicionarlos en el mouse
var centerX = pastedObjects.Average(obj => obj.Left);
var centerY = pastedObjects.Average(obj => obj.Top);
var offsetX = mousePositionMeters.Item1 - centerX;
var offsetY = mousePositionMeters.Item2 - centerY;
if (DataContext is MainViewModel viewModel)
{
viewModel.StopSimulation();
viewModel.DisconnectPLC();
// Limpiar selección actual
_objectManager.ClearSelection();
var newlyPastedObjects = new List<osBase>();
foreach (var obj in pastedObjects)
{
try
{
// Aplicar offset de posición
obj.Left += offsetX;
obj.Top += offsetY;
// Manejar IDs
if (!replaceExistingIds)
{
// Generar nuevo ID (comportamiento por defecto)
obj.Id.ObtenerNuevaID();
// Actualizar nombre si contiene ID
if (obj.Nombre.Contains("_"))
{
var parts = obj.Nombre.Split('_');
if (parts.Length > 1 && int.TryParse(parts.Last(), out _))
{
obj.Nombre = string.Join("_", parts.Take(parts.Length - 1)) + "_" + obj.Id.Value;
}
}
}
// Si replaceExistingIds es true, mantener los IDs originales
// Verificar que el objeto no existe ya si estamos reemplazando IDs
if (replaceExistingIds)
{
var existingObj = viewModel.ObjetosSimulables.FirstOrDefault(o => o.Id.Value == obj.Id.Value);
if (existingObj != null)
{
viewModel.RemoverObjetoSimulable(existingObj);
}
}
obj.CheckData();
viewModel.ObjetosSimulables.Add(obj);
viewModel.CrearUserControlDesdeObjetoSimulable(obj);
newlyPastedObjects.Add(obj);
}
catch (Exception ex)
{
MessageBox.Show($"Error al procesar objeto {obj.Nombre}: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
// Forzar actualización del layout para asegurar que los objetos estén renderizados
ImagenEnTrabajoCanvas.UpdateLayout();
// Usar Task.Delay para dar más tiempo al renderizado completo
Task.Delay(100).ContinueWith(_ =>
{
Application.Current.Dispatcher.Invoke(() =>
{
try
{
// Forzar una segunda actualización del layout
ImagenEnTrabajoCanvas.UpdateLayout();
// Validar que los objetos tengan representación visual antes de seleccionar
var validObjects = newlyPastedObjects.Where(obj =>
obj.VisualRepresentation != null &&
ImagenEnTrabajoCanvas.Children.Contains(obj.VisualRepresentation)).ToList();
Console.WriteLine($"Intentando seleccionar {validObjects.Count} de {newlyPastedObjects.Count} objetos pegados");
if (validObjects.Count > 0)
{
// Limpiar selección actual primero
_objectManager.ClearSelection();
// Seleccionar los objetos válidos uno por uno con verificación
foreach (var obj in validObjects)
{
try
{
if (obj.VisualRepresentation != null)
{
Console.WriteLine($"Seleccionando objeto: {obj.Nombre} (ID: {obj.Id.Value})");
_objectManager.SelectObject(obj);
}
}
catch (Exception objEx)
{
Console.WriteLine($"Error al seleccionar objeto {obj.Nombre}: {objEx.Message}");
}
}
// Actualizar visuales de selección con manejo de errores
try
{
_objectManager.UpdateSelectionVisuals();
Console.WriteLine($"Seleccionados exitosamente {_objectManager.SelectedObjects.Count} objetos");
}
catch (Exception visEx)
{
Console.WriteLine($"Error al actualizar visuales de selección: {visEx.Message}");
Console.WriteLine($"Stack trace: {visEx.StackTrace}");
}
}
else
{
Console.WriteLine("No se pudieron seleccionar los objetos pegados - representación visual no válida");
foreach (var obj in newlyPastedObjects)
{
Console.WriteLine($"Objeto {obj.Nombre}: VisualRep={obj.VisualRepresentation != null}, EnCanvas={obj.VisualRepresentation != null && ImagenEnTrabajoCanvas.Children.Contains(obj.VisualRepresentation)}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error general al seleccionar objetos pegados: {ex.Message}");
Console.WriteLine($"Stack trace: {ex.StackTrace}");
}
});
});
viewModel.HasUnsavedChanges = true;
Console.WriteLine($"Pegados {newlyPastedObjects.Count} objeto(s) desde el portapapeles");
}
}
catch (Exception ex)
{
MessageBox.Show($"Error al pegar objetos: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private bool CanPasteFromClipboard()
{
if (!Clipboard.ContainsText())
return false;
try
{
string jsonString = Clipboard.GetText();
// Validación básica del JSON
if (string.IsNullOrWhiteSpace(jsonString) ||
(!jsonString.TrimStart().StartsWith("[") && !jsonString.TrimStart().StartsWith("{")))
{
return false;
}
// Intentar una deserialización rápida para validar
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
ObjectCreationHandling = ObjectCreationHandling.Replace,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
};
// Intentar deserializar como lista de objetos
try
{
var pastedObjects = JsonConvert.DeserializeObject<List<osBase>>(jsonString, settings);
return pastedObjects != null && pastedObjects.Any();
}
catch
{
// Si falla como lista, intentar como objeto individual
try
{
var singleObject = JsonConvert.DeserializeObject<osBase>(jsonString, settings);
return singleObject != null;
}
catch
{
return false;
}
}
}
catch
{
return false;
}
}
}
public class FloatValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (string.IsNullOrEmpty(value?.ToString()))
return new ValidationResult(false, "El campo no puede estar vacío.");
if (float.TryParse(value.ToString(), NumberStyles.Float, cultureInfo, out float result))
return ValidationResult.ValidResult;
else
return new ValidationResult(false, "Ingrese un número válido.");
}
}
}