From 94b11cf068c2e1747888c1269ba0b39b5f1238f3 Mon Sep 17 00:00:00 2001 From: Miguel Date: Sat, 14 Jun 2025 16:47:25 +0200 Subject: [PATCH] =?UTF-8?q?Se=20implement=C3=B3=20un=20sistema=20de=20gest?= =?UTF-8?q?i=C3=B3n=20de=20historial=20de=20deshacer=20(undo)=20en=20la=20?= =?UTF-8?q?aplicaci=C3=B3n,=20permitiendo=20capturar=20y=20restaurar=20est?= =?UTF-8?q?ados=20de=20objetos=20seleccionados.=20Se=20a=C3=B1adieron=20m?= =?UTF-8?q?=C3=A9todos=20para=20limpiar=20el=20historial=20y=20se=20mejor?= =?UTF-8?q?=C3=B3=20la=20interfaz=20de=20usuario=20para=20mostrar=20inform?= =?UTF-8?q?aci=C3=B3n=20sobre=20el=20estado=20del=20historial=20de=20desha?= =?UTF-8?q?cer.=20Adem=C3=A1s,=20se=20realizaron=20ajustes=20en=20la=20l?= =?UTF-8?q?=C3=B3gica=20de=20manipulaci=C3=B3n=20de=20objetos=20para=20ase?= =?UTF-8?q?gurar=20la=20correcta=20captura=20de=20estados=20antes=20de=20m?= =?UTF-8?q?ovimientos=20y=20redimensionamientos.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MainViewModel.cs | 11 + MainWindow.xaml.cs | 29 ++- ObjectManipulationManager.cs | 261 +++++++++++++++++++++-- ObjetosSim/Decorativos/ucFramePlate.xaml | 6 +- 4 files changed, 288 insertions(+), 19 deletions(-) diff --git a/MainViewModel.cs b/MainViewModel.cs index e3342b8..fe041c0 100644 --- a/MainViewModel.cs +++ b/MainViewModel.cs @@ -447,6 +447,9 @@ namespace CtrEditor { OnVisFilterChanged(MainWindow.VisFilter.FilterViewModel); } + + // Limpiar historial de undo al cargar un estado desde archivo + MainWindow?.ClearUndoHistory(); } } @@ -455,6 +458,9 @@ namespace CtrEditor { // Suponiendo que "SelectedImage" es una propiedad que al establecerse dispara "ImageSelected" directorioTrabajo = EstadoPersistente.Instance.directorio; + + // Limpiar historial de undo al cargar datos iniciales + MainWindow?.ClearUndoHistory(); } // Crear un nuevo Objeto @@ -794,6 +800,9 @@ namespace CtrEditor // Restaurar los rectángulos de selección si hay objetos seleccionados _objectManager.UpdateSelectionVisuals(); + + // Limpiar historial de undo al detener la simulación + MainWindow?.ClearUndoHistory(); } /// @@ -869,6 +878,8 @@ namespace CtrEditor foreach (var objetoSimulable in ObjetosSimulables) objetoSimulable.SetPLC(null); + // Limpiar historial de undo al desconectar del PLC + MainWindow?.ClearUndoHistory(); } private List objetosSimulablesLlamados = new List(); diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index e426ddd..94110f9 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -413,6 +413,9 @@ namespace CtrEditor private void MoveSelectedObjects(float deltaX, float deltaY) { + // Capturar estado antes de mover + _objectManager.CaptureUndoState(); + // Mover todos los objetos primero foreach (var obj in _objectManager.SelectedObjects) { @@ -521,6 +524,11 @@ namespace CtrEditor debugWindow.Show(); } + public void ClearUndoHistory() + { + _objectManager.ClearUndoHistory(); + } + private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e) { // Aceptar el evento si viene del canvas o de la imagen de fondo @@ -672,8 +680,11 @@ namespace CtrEditor contextMenu.Items.Add(pasteReplaceMenuItem); contextMenu.Items.Add(new Separator()); + } - // Opciones de bloqueo/desbloqueo + // Opciones de bloqueo/desbloqueo - mostrar siempre que haya objetos seleccionados + if (_objectManager.SelectedObjects.Count > 0) + { var lockSubmenu = new MenuItem { Header = "Bloqueo" }; // Verificar el estado actual de los objetos @@ -702,6 +713,17 @@ namespace CtrEditor contextMenu.Items.Add(lockSubmenu); contextMenu.Items.Add(new Separator()); } + + // Agregar información del sistema de undo + var undoHistoryCount = _objectManager.GetUndoHistoryCount(); + var canUndo = _objectManager.CanUndo(); + var undoInfoItem = new MenuItem + { + Header = $"Undo: {undoHistoryCount}/3 estados ({(canUndo ? "Ctrl+Z disponible" : "No disponible")})", + IsEnabled = false, + FontStyle = FontStyles.Italic + }; + contextMenu.Items.Add(undoInfoItem); contextMenu.IsOpen = true; contextMenu.PlacementTarget = ImagenEnTrabajoCanvas; @@ -755,6 +777,11 @@ namespace CtrEditor PasteObjectsFromJson(); e.Handled = true; } + else if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.Z) + { + _objectManager.PerformUndo(); + e.Handled = true; + } else if (_objectManager.SelectedObjects.Any()) { const float moveDistance = 0.01f; diff --git a/ObjectManipulationManager.cs b/ObjectManipulationManager.cs index 0e379e1..a63f41b 100644 --- a/ObjectManipulationManager.cs +++ b/ObjectManipulationManager.cs @@ -10,6 +10,58 @@ using Color = System.Windows.Media.Color; namespace CtrEditor { + // Clase para almacenar el estado básico de un objeto para undo + public class ObjectState + { + public int ObjectId { get; set; } + public float Left { get; set; } + public float Top { get; set; } + public float Angle { get; set; } + public float Width { get; set; } + public float Height { get; set; } + + public ObjectState(osBase obj) + { + ObjectId = obj.Id.Value; + Left = obj.Left; + Top = obj.Top; + Angle = obj.Angulo; + Width = obj.Ancho; + Height = obj.Alto; + } + + public ObjectState(ObjectState other) + { + ObjectId = other.ObjectId; + Left = other.Left; + Top = other.Top; + Angle = other.Angle; + Width = other.Width; + Height = other.Height; + } + } + + // Clase para almacenar un estado completo del workspace + public class UndoState + { + public List ObjectStates { get; set; } + public DateTime Timestamp { get; set; } + + public UndoState() + { + ObjectStates = new List(); + Timestamp = DateTime.Now; + } + + public UndoState(IEnumerable objects) : this() + { + foreach (var obj in objects) + { + ObjectStates.Add(new ObjectState(obj)); + } + } + } + public enum AlignmentType { Left, @@ -76,6 +128,11 @@ namespace CtrEditor private bool _isRectangleSelectionActive; private bool _selectedObjectsAreVisible; + // Sistema de Undo + private Stack _undoHistory = new Stack(); + private const int MaxUndoSteps = 3; + private bool _isApplyingUndo = false; + public bool IsRectangleSelectionActive { get => _isRectangleSelectionActive; @@ -615,6 +672,9 @@ namespace CtrEditor return; } + // Capturar estado antes de iniciar redimensionamiento + CaptureUndoState(); + _currentDraggingRectangle = sender as Rectangle; _lastMousePosition = e.GetPosition(_canvas); _currentDraggingRectangle.CaptureMouse(); @@ -625,25 +685,49 @@ namespace CtrEditor } var userControl = sender as UserControl; - if (userControl != null) + if (userControl != null && userControl.DataContext is osBase datos) { - if (userControl.DataContext is osBase datos) + bool isControlPressed = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl); + + // Determinar qué objetos serán afectados por el movimiento + List objectsToMove = new List(); + + if (!isControlPressed) { - HandleObjectSelection(userControl, datos); + // Si no está Ctrl presionado y vamos a hacer dragging + if (_selectedObjects.Contains(datos)) + { + // Si el objeto ya está seleccionado, vamos a mover todos los seleccionados + objectsToMove.AddRange(_selectedObjects); + } + else + { + // Si no está seleccionado, solo vamos a mover este objeto + objectsToMove.Add(datos); + } - // Actualizar el estado de objetos bloqueados después de la selección - hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement); + // Verificar si alguno de los objetos a mover está bloqueado + bool willMoveLockedObjects = objectsToMove.Any(obj => obj.Lock_movement); + + if (!willMoveLockedObjects) + { + // Capturar estado antes de iniciar movimiento + CaptureUndoStateForObjects(objectsToMove); + + userControl.CaptureMouse(); + _currentDraggingControl = userControl; + _isMovingUserControl = true; + InitializeDrag(e); + } } - - // Solo iniciar el arrastre si no se presionó Ctrl y no hay objetos bloqueados - if (!(Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) && !hasLockedObjects) - { - userControl.CaptureMouse(); - _currentDraggingControl = userControl; - _isMovingUserControl = true; - InitializeDrag(e); - } - else if (hasLockedObjects) + + // Manejar la selección después de capturar el estado + HandleObjectSelection(userControl, datos); + + // Actualizar el estado de objetos bloqueados después de la selección + hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement); + + if (hasLockedObjects && !isControlPressed) { // Si hay objetos bloqueados, no permitir el inicio del arrastre e.Handled = true; @@ -929,6 +1013,9 @@ namespace CtrEditor return; // No alinear si hay objetos bloqueados } + // Capturar estado antes de alinear + CaptureUndoState(); + var alignment = new ObjectAlignment(_selectedObjects, _selectedObjects.FirstOrDefault()); switch (alignmentType) @@ -1090,5 +1177,149 @@ namespace CtrEditor UpdateSelectionRectangle(currentPoint); } } + + // Métodos del sistema de Undo + public void CaptureUndoState() + { + // Solo capturar estado si no estamos aplicando un undo y si es permitido + if (_isApplyingUndo || !CanPerformUndo()) + return; + + // Solo capturar estado si hay objetos seleccionados + if (_selectedObjects.Count == 0) + return; + + if (_mainWindow.DataContext is MainViewModel viewModel) + { + // Capturar solo el estado de los objetos seleccionados + var undoState = new UndoState(_selectedObjects); + + // Mantener solo los últimos MaxUndoSteps estados + if (_undoHistory.Count >= MaxUndoSteps) + { + // Convertir a lista, remover el más antiguo, y volver a crear el stack + var tempList = _undoHistory.ToList(); + tempList.Reverse(); // Invertir para mantener el orden correcto + tempList.RemoveAt(0); // Remover el más antiguo + _undoHistory.Clear(); + foreach (var state in tempList) + { + _undoHistory.Push(state); + } + } + + _undoHistory.Push(undoState); + Console.WriteLine($"Estado capturado para undo de {_selectedObjects.Count} objetos. Historial: {_undoHistory.Count} estados"); + } + } + + // Método para capturar el estado de objetos específicos + public void CaptureUndoStateForObjects(IEnumerable objects) + { + // Solo capturar estado si no estamos aplicando un undo y si es permitido + if (_isApplyingUndo || !CanPerformUndo()) + return; + + var objectsList = objects.ToList(); + if (objectsList.Count == 0) + return; + + if (_mainWindow.DataContext is MainViewModel viewModel) + { + // Capturar solo el estado de los objetos especificados + var undoState = new UndoState(objectsList); + + // Mantener solo los últimos MaxUndoSteps estados + if (_undoHistory.Count >= MaxUndoSteps) + { + // Convertir a lista, remover el más antiguo, y volver a crear el stack + var tempList = _undoHistory.ToList(); + tempList.Reverse(); // Invertir para mantener el orden correcto + tempList.RemoveAt(0); // Remover el más antiguo + _undoHistory.Clear(); + foreach (var state in tempList) + { + _undoHistory.Push(state); + } + } + + _undoHistory.Push(undoState); + Console.WriteLine($"Estado capturado para undo de {objectsList.Count} objetos específicos. Historial: {_undoHistory.Count} estados"); + } + } + + public bool CanPerformUndo() + { + if (_mainWindow.DataContext is MainViewModel viewModel) + { + // Solo permitir undo si no estamos en simulación ni conectados + return !viewModel.IsSimulationRunning && !viewModel.IsConnected; + } + return false; + } + + public bool CanUndo() + { + return CanPerformUndo() && _undoHistory.Count > 0; + } + + public void PerformUndo() + { + if (!CanUndo()) + return; + + if (_mainWindow.DataContext is MainViewModel viewModel) + { + _isApplyingUndo = true; + + try + { + var undoState = _undoHistory.Pop(); + Console.WriteLine($"Aplicando undo. Estados restantes: {_undoHistory.Count}"); + + // Aplicar el estado a cada objeto + foreach (var objectState in undoState.ObjectStates) + { + var obj = viewModel.ObjetosSimulables.FirstOrDefault(o => o.Id.Value == objectState.ObjectId); + if (obj != null) + { + // Aplicar solo los parámetros básicos + obj.Left = objectState.Left; + obj.Top = objectState.Top; + obj.Angulo = objectState.Angle; + obj.Ancho = objectState.Width; + obj.Alto = objectState.Height; + + // Actualizar la posición visual + obj.OnMoveResizeRotate(); + } + } + + // Actualizar los visuales de selección + UpdateSelectionVisuals(); + + // Marcar como cambios sin guardar + viewModel.HasUnsavedChanges = true; + + Console.WriteLine("Undo aplicado exitosamente"); + } + finally + { + _isApplyingUndo = false; + } + } + } + + public void ClearUndoHistory() + { + _undoHistory.Clear(); + Console.WriteLine("Historial de undo limpiado"); + } + + // Método de conveniencia para obtener información del historial + public int GetUndoHistoryCount() + { + return _undoHistory.Count; + } } } \ No newline at end of file diff --git a/ObjetosSim/Decorativos/ucFramePlate.xaml b/ObjetosSim/Decorativos/ucFramePlate.xaml index c298dc1..a5ffbee 100644 --- a/ObjetosSim/Decorativos/ucFramePlate.xaml +++ b/ObjetosSim/Decorativos/ucFramePlate.xaml @@ -5,12 +5,13 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim" mc:Ignorable="d"> - - + + + @@ -24,6 +25,5 @@ Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}" Stroke="Blue" StrokeThickness="0.2" Visibility="{Binding ShowPlate, Converter={StaticResource BooleanToVisibilityConverter}}" /> - \ No newline at end of file