using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Reflection; using System.Text.RegularExpressions; using System.Windows; using System.Windows.Input; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CtrEditor.Controls; using CtrEditor.ObjetosSim; using CtrEditor.Serialization; using Newtonsoft.Json; using Ookii.Dialogs.Wpf; namespace CtrEditor.PopUps { public partial class LibraryWindowViewModel : ObservableObject { private readonly MainViewModel _mainViewModel; private List _clipboardObjects = new List(); [ObservableProperty] private ObservableCollection libraryDirectories; [ObservableProperty] private LibraryDirectoryItem selectedLibraryDirectory; [ObservableProperty] private ObservableCollection libraries; [ObservableProperty] private LibraryItem selectedLibrary; [ObservableProperty] private ObservableCollection filteredObjectsByType; [ObservableProperty] private osBase selectedObject; // Nueva propiedad para el TreeView jerárquico [ObservableProperty] private ObservableCollection libraryTreeNodes; [ObservableProperty] private LibraryTreeNode selectedTreeNode; // Nueva propiedad para objetos seleccionados múltiples [ObservableProperty] private ObservableCollection selectableObjects; // Commands public ICommand AddLibraryDirectoryCommand { get; } public ICommand RemoveLibraryDirectoryCommand { get; } public ICommand CopyObjectCommand { get; } public ICommand PasteObjectCommand { get; } public ICommand DeleteObjectCommand { get; } public ICommand CreateNewLibraryCommand { get; } // Nuevos comandos para menú contextual public ICommand CreateLibraryInDirectoryCommand { get; } public ICommand RemoveDirectoryCommand { get; } public LibraryWindowViewModel(MainViewModel mainViewModel) { _mainViewModel = mainViewModel; LibraryDirectories = new ObservableCollection(); Libraries = new ObservableCollection(); FilteredObjectsByType = new ObservableCollection(); LibraryTreeNodes = new ObservableCollection(); SelectableObjects = new ObservableCollection(); // Initialize commands AddLibraryDirectoryCommand = new RelayCommand(AddLibraryDirectory); RemoveLibraryDirectoryCommand = new RelayCommand(RemoveLibraryDirectory, () => SelectedLibraryDirectory != null); CopyObjectCommand = new RelayCommand(CopyObject, () => HasSelectedObjects()); PasteObjectCommand = new RelayCommand(PasteObject, () => SelectedLibrary != null && (HasObjectsAvailableToPaste())); DeleteObjectCommand = new RelayCommand(DeleteObject, () => HasSelectedObjects() && SelectedLibrary != null && !SelectedLibrary.IsCurrentProject); CreateNewLibraryCommand = new RelayCommand(CreateNewLibrary, () => SelectedLibraryDirectory != null); // Nuevos comandos CreateLibraryInDirectoryCommand = new RelayCommand(CreateLibraryInDirectory, () => SelectedTreeNode?.IsDirectory == true); RemoveDirectoryCommand = new RelayCommand(RemoveDirectory, () => SelectedTreeNode?.IsDirectory == true && !SelectedTreeNode.IsCurrentProject); LoadLibraryDirectories(); RefreshLibraryTree(); } private bool HasSelectedObjects() { return SelectableObjects?.Any(o => o.IsSelected) == true || SelectedObject != null; } private bool HasObjectsAvailableToPaste() { // Verificar si hay objetos en el portapapeles interno o en el portapapeles del sistema return _clipboardObjects.Any() || (System.Windows.Clipboard.ContainsText() && IsValidObjectData(System.Windows.Clipboard.GetText())); } private bool IsValidObjectData(string text) { if (string.IsNullOrWhiteSpace(text)) return false; try { text = text.Trim(); // Validación básica: debe ser JSON que empiece con [ o { if (!(text.StartsWith("[") || text.StartsWith("{"))) return false; // Intentar parsearlo rápidamente sin deserializar completamente var token = Newtonsoft.Json.Linq.JToken.Parse(text); return token != null; } catch { return false; } } private List GetSelectedObjects() { var selected = new List(); // Primero agregar objetos seleccionados por checkbox if (SelectableObjects != null) { selected.AddRange(SelectableObjects.Where(o => o.IsSelected).Select(o => o.Object)); } // Si no hay objetos con checkbox seleccionados, usar la selección simple if (!selected.Any() && SelectedObject != null) { selected.Add(SelectedObject); } return selected; } partial void OnSelectedTreeNodeChanged(LibraryTreeNode value) { if (value?.IsDirectory == false) // Es una biblioteca { SelectedLibrary = value.Library; LoadObjectsFromLibrary(); } CommandManager.InvalidateRequerySuggested(); } partial void OnSelectedLibraryDirectoryChanged(LibraryDirectoryItem value) { RefreshLibraries(); CommandManager.InvalidateRequerySuggested(); } partial void OnSelectedLibraryChanged(LibraryItem value) { LoadObjectsFromLibrary(); CommandManager.InvalidateRequerySuggested(); } private osVisFilter _objectFilter; public void SetObjectFilter(osVisFilter filter) { _objectFilter = filter; if (SelectedLibrary != null) { UpdateFilterTypes(filter); } } private void LoadLibraryDirectories() { LibraryDirectories.Clear(); // Add current project directory LibraryDirectories.Add(new LibraryDirectoryItem { Path = _mainViewModel.directorioTrabajo, DisplayName = $"Proyecto Actual ({_mainViewModel.directorioTrabajo})", IsCurrentProject = true }); // Add saved library directories foreach (var directory in EstadoPersistente.Instance.LibraryDirectories) { if (Directory.Exists(directory)) { LibraryDirectories.Add(new LibraryDirectoryItem { Path = directory, DisplayName = directory, // Show full path instead of just folder name IsCurrentProject = false }); } } if (LibraryDirectories.Any()) { SelectedLibraryDirectory = LibraryDirectories.First(); } } private void RefreshLibraries() { Libraries.Clear(); if (SelectedLibraryDirectory == null) return; if (SelectedLibraryDirectory.IsCurrentProject) { // Add current project as a library Libraries.Add(new LibraryItem { Name = "Proyecto Actual", FilePath = null, IsCurrentProject = true, Objects = _mainViewModel.ObjetosSimulables.ToList() }); // Add JSON files from current project directory AddJsonFilesFromDirectory(SelectedLibraryDirectory.Path); } else { // Add JSON files from selected library directory AddJsonFilesFromDirectory(SelectedLibraryDirectory.Path); } } private void AddJsonFilesFromDirectory(string directoryPath) { if (!Directory.Exists(directoryPath)) return; var jsonFiles = Directory.GetFiles(directoryPath, "*.json", SearchOption.AllDirectories); foreach (var jsonFile in jsonFiles) { try { var relativePath = Path.GetRelativePath(directoryPath, jsonFile); var displayName = Path.GetFileNameWithoutExtension(jsonFile); var libraryItem = new LibraryItem { Name = displayName, FilePath = jsonFile, IsCurrentProject = false, Objects = LoadObjectsFromJsonFile(jsonFile) }; Libraries.Add(libraryItem); } catch (Exception ex) { // Log error but continue processing other files System.Diagnostics.Debug.WriteLine($"Error loading library {jsonFile}: {ex.Message}"); } } } private List LoadObjectsFromJsonFile(string filePath) { var objects = new List(); try { var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, NullValueHandling = NullValueHandling.Ignore }; string json = File.ReadAllText(filePath); // Try to detect format: check if it starts with '[' (AllPages.json format) or '{' (normal format) json = json.Trim(); if (json.StartsWith("[")) { // AllPages.json format: direct array of objects var directObjects = JsonConvert.DeserializeObject>(json, settings); if (directObjects != null) { objects.AddRange(directObjects); } } else if (json.StartsWith("{")) { // Normal format: SimulationData wrapper var simulationData = JsonConvert.DeserializeObject(json, settings); if (simulationData?.ObjetosSimulables != null) { objects.AddRange(simulationData.ObjetosSimulables); } } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error loading objects from {filePath}: {ex.Message}"); } return objects; } private void LoadObjectsFromLibrary() { FilteredObjectsByType.Clear(); SelectableObjects.Clear(); if (SelectedLibrary == null) return; // Crear objetos seleccionables con checkboxes foreach (var obj in SelectedLibrary.Objects) { SelectableObjects.Add(new SelectableObjectWrapper { Object = obj, IsSelected = false }); } // Update filter types and tags when library changes if (_objectFilter != null) { UpdateFilterTypes(_objectFilter); } ApplyFiltering(SelectedLibrary.Objects); } public void UpdateFilterTypes(osVisFilter filter) { if (SelectedLibrary?.Objects != null) { var types = SelectedLibrary.Objects.Select(o => o.GetType()).Distinct(); filter.UpdateAvailableTypes(types); filter.UpdateAvailableTags(SelectedLibrary.Objects); } } private void ApplyFiltering(List objects) { // Group objects by type var groupedObjects = objects .GroupBy(obj => obj.GetType()) .Select(group => new ObjectTypeGroup { Type = group.Key, TypeName = GetTypeDisplayName(group.Key), Objects = new ObservableCollection(group.ToList()) }) .OrderBy(group => group.TypeName) .ToList(); FilteredObjectsByType.Clear(); foreach (var group in groupedObjects) { FilteredObjectsByType.Add(group); } } public void OnFilterChanged(osVisFilterViewModel filterViewModel) { if (SelectedLibrary == null) return; var filteredObjects = new List(); foreach (var obj in SelectedLibrary.Objects) { bool isVisible = true; // Apply Show All filter if (!filterViewModel.ShowAll) { // Check type filter var typeFilter = filterViewModel.TypeFilters.FirstOrDefault(tf => tf.Type == obj.GetType()); if (typeFilter == null || !typeFilter.IsSelected) { isVisible = false; } // Check tag filters if (filterViewModel.TagFilters.Any() && filterViewModel.TagFilters.Any(tf => tf.IsSelected)) { var selectedTags = filterViewModel.TagFilters.Where(tf => tf.IsSelected).Select(tf => tf.TagName).ToList(); bool hasMatchingTag = obj.ListaEtiquetas.Any(tag => selectedTags.Contains(tag)); if (!hasMatchingTag) { isVisible = false; } } // Check search tags if (!string.IsNullOrWhiteSpace(filterViewModel.SearchTags)) { var searchTags = filterViewModel.SearchTags .Split(' ', StringSplitOptions.RemoveEmptyEntries) .Where(tag => tag.StartsWith("#") && tag.Length > 1) .Select(tag => tag.Substring(1).ToLowerInvariant()) .ToList(); if (searchTags.Any()) { bool hasMatchingSearchTag = searchTags.Any(searchTag => obj.ListaEtiquetas.Contains(searchTag)); if (!hasMatchingSearchTag) { isVisible = false; } } } // Check other filters if (filterViewModel.ShowCloned && !obj.Cloned) isVisible = false; if (filterViewModel.ShowAutoCreated && !obj.AutoCreated) isVisible = false; if (filterViewModel.ShowEnableOnAllPages && !obj.Enable_On_All_Pages) isVisible = false; if (filterViewModel.ShowOnThisPage && !obj.Show_On_This_Page) isVisible = false; } if (isVisible) { filteredObjects.Add(obj); } } ApplyFiltering(filteredObjects); } private string GetTypeDisplayName(Type type) { try { var methodInfo = type.GetMethod("NombreClase", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); return methodInfo != null ? methodInfo.Invoke(null, null)?.ToString() : type.Name; } catch { return type.Name; } } private void AddLibraryDirectory() { var dialog = new VistaFolderBrowserDialog(); if (dialog.ShowDialog() == true) { var path = dialog.SelectedPath; if (!EstadoPersistente.Instance.LibraryDirectories.Contains(path)) { EstadoPersistente.Instance.LibraryDirectories.Add(path); EstadoPersistente.Instance.GuardarEstado(); LoadLibraryDirectories(); } } } private void RemoveLibraryDirectory() { if (SelectedLibraryDirectory != null && !SelectedLibraryDirectory.IsCurrentProject) { var result = MessageBox.Show( $"¿Está seguro de que desea eliminar el directorio '{SelectedLibraryDirectory.DisplayName}' de la lista de bibliotecas?", "Confirmar eliminación", MessageBoxButton.YesNo, MessageBoxImage.Question); if (result == MessageBoxResult.Yes) { EstadoPersistente.Instance.LibraryDirectories.Remove(SelectedLibraryDirectory.Path); EstadoPersistente.Instance.GuardarEstado(); LoadLibraryDirectories(); } } } private void CopyObject() { var objectsToCopy = GetSelectedObjects(); if (objectsToCopy.Any()) { try { // Usar el mismo sistema de portapapeles que MainWindow // Crear copias usando la misma lógica que DuplicarObjeto var objectsCopy = new List(); var settings = new JsonSerializerSettings { Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore, TypeNameHandling = TypeNameHandling.All }; foreach (var obj in objectsToCopy) { // Prepare object for serialization obj.SalvarDatosNoSerializables(); // Serialize and deserialize to create a deep copy (same as DuplicarObjeto) var serializedData = JsonConvert.SerializeObject(obj, settings); var copiedObject = JsonConvert.DeserializeObject(serializedData, settings); if (copiedObject != null) { objectsCopy.Add(copiedObject); } // Restore object state obj.RestaurarDatosNoSerializables(); } // Copiar al portapapeles del sistema usando el mismo formato que MainWindow string jsonString = JsonConvert.SerializeObject(objectsCopy, settings); System.Windows.Clipboard.SetText(jsonString); MessageBox.Show($"{objectsCopy.Count} objeto(s) copiado(s) al portapapeles del sistema.", "Información", MessageBoxButton.OK, MessageBoxImage.Information); CommandManager.InvalidateRequerySuggested(); } catch (Exception ex) { MessageBox.Show($"Error al copiar objeto(s): {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } } private void PasteObject() { if (SelectedLibrary != null) { try { List objectsToPaste = new List(); // Si hay objetos en el portapapeles interno (modo anterior) if (_clipboardObjects.Any()) { objectsToPaste = _clipboardObjects.ToList(); } // Si no, intentar usar el portapapeles del sistema else if (System.Windows.Clipboard.ContainsText()) { string jsonString = System.Windows.Clipboard.GetText(); if (!string.IsNullOrWhiteSpace(jsonString)) { var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, NullValueHandling = NullValueHandling.Ignore, ObjectCreationHandling = ObjectCreationHandling.Replace, ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor }; // Intentar deserializar como lista de objetos try { objectsToPaste = JsonConvert.DeserializeObject>(jsonString, settings); } catch { // Si falla, intentar como objeto individual try { var singleObject = JsonConvert.DeserializeObject(jsonString, settings); if (singleObject != null) { objectsToPaste = new List { singleObject }; } } catch { MessageBox.Show("No se pudo deserializar el contenido del portapapeles como objetos válidos.", "Error", MessageBoxButton.OK, MessageBoxImage.Warning); return; } } } } if (!objectsToPaste.Any()) { MessageBox.Show("No hay objetos para pegar.", "Información", MessageBoxButton.OK, MessageBoxImage.Information); return; } foreach (var objToPaste in objectsToPaste) { // Create a new unique ID for the pasted object objToPaste.Id.ObtenerNuevaID(); // Update name to include ID string nombre = Regex.IsMatch(objToPaste.Nombre, @"_\d+$") ? Regex.Replace(objToPaste.Nombre, @"_\d+$", $"_{objToPaste.Id.Value}") : objToPaste.Nombre + "_" + objToPaste.Id.Value; objToPaste.Nombre = nombre; if (SelectedLibrary.IsCurrentProject) { // Add to current project _mainViewModel.ObjetosSimulables.Add(objToPaste); _mainViewModel.CrearUserControlDesdeObjetoSimulable(objToPaste); _mainViewModel.HasUnsavedChanges = true; } else { // Add to selected library file SelectedLibrary.Objects.Add(objToPaste); SaveLibraryToFile(SelectedLibrary); } } // Si se pegó en el proyecto actual, recargar la imagen actual if (SelectedLibrary.IsCurrentProject) { ReloadCurrentImage(); } RefreshLibraries(); LoadObjectsFromLibrary(); MessageBox.Show($"{objectsToPaste.Count} objeto(s) pegado(s).", "Información", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { MessageBox.Show($"Error al pegar objeto: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } } // Método para recargar la imagen actual después de pegar en el proyecto actual private void ReloadCurrentImage() { try { // Forzar recarga de la imagen actual para reflejar los cambios var currentImage = _mainViewModel.SelectedImage; if (!string.IsNullOrEmpty(currentImage)) { // Temporalmente cambiar a null y luego restaurar para forzar la recarga _mainViewModel.SelectedImage = null; _mainViewModel.SelectedImage = currentImage; } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error al recargar imagen actual: {ex.Message}"); } } private void DeleteObject() { var objectsToDelete = GetSelectedObjects(); if (objectsToDelete.Any() && SelectedLibrary != null && !SelectedLibrary.IsCurrentProject) { var message = objectsToDelete.Count == 1 ? $"¿Está seguro de que desea eliminar el objeto '{objectsToDelete.First().Nombre}'?" : $"¿Está seguro de que desea eliminar {objectsToDelete.Count} objetos?"; var result = MessageBox.Show(message, "Confirmar eliminación", MessageBoxButton.YesNo, MessageBoxImage.Question); if (result == MessageBoxResult.Yes) { try { foreach (var obj in objectsToDelete) { SelectedLibrary.Objects.Remove(obj); } SaveLibraryToFile(SelectedLibrary); LoadObjectsFromLibrary(); SelectedObject = null; var successMessage = objectsToDelete.Count == 1 ? "Objeto eliminado." : $"{objectsToDelete.Count} objetos eliminados."; MessageBox.Show(successMessage, "Información", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { MessageBox.Show($"Error al eliminar objeto(s): {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } } } private void CreateNewLibrary() { if (SelectedLibraryDirectory == null) return; var dialog = new Microsoft.Win32.SaveFileDialog { DefaultExt = ".json", Filter = "JSON files (*.json)|*.json", InitialDirectory = SelectedLibraryDirectory.Path, Title = "Crear nueva biblioteca" }; if (dialog.ShowDialog() == true) { try { var emptyLibrary = new SimulationData { ObjetosSimulables = new ObservableCollection(), UnitConverter = PixelToMeter.Instance.calc, PLC_ConnectionData = new LibS7Adv.PLCViewModel() }; var settings = new JsonSerializerSettings { Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore, TypeNameHandling = TypeNameHandling.All }; string json = JsonConvert.SerializeObject(emptyLibrary, settings); File.WriteAllText(dialog.FileName, json); RefreshLibraries(); MessageBox.Show("Nueva biblioteca creada.", "Información", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { MessageBox.Show($"Error al crear biblioteca: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } } private void SaveLibraryToFile(LibraryItem library) { if (library.IsCurrentProject || string.IsNullOrEmpty(library.FilePath)) return; try { var settings = new JsonSerializerSettings { Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore, TypeNameHandling = TypeNameHandling.All }; string json; // Detectar si es formato AllPages.json basándose en el nombre del archivo var fileName = Path.GetFileName(library.FilePath).ToLowerInvariant(); bool isAllPagesFormat = fileName.Contains("allpages") || fileName.StartsWith("allpages"); if (isAllPagesFormat) { // Formato AllPages.json: guardar como array directo de objetos json = JsonConvert.SerializeObject(library.Objects, settings); } else { // Formato normal: guardar con wrapper SimulationData var simulationData = new SimulationData { ObjetosSimulables = new ObservableCollection(library.Objects), UnitConverter = PixelToMeter.Instance.calc, PLC_ConnectionData = new LibS7Adv.PLCViewModel() }; json = JsonConvert.SerializeObject(simulationData, settings); } File.WriteAllText(library.FilePath, json); } catch (Exception ex) { MessageBox.Show($"Error al guardar biblioteca: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } private void RefreshLibraryTree() { LibraryTreeNodes.Clear(); foreach (var directory in LibraryDirectories) { var directoryNode = new LibraryTreeNode { DisplayName = directory.DisplayName, IsDirectory = true, IsCurrentProject = directory.IsCurrentProject, DirectoryPath = directory.Path, Children = new ObservableCollection() }; // Agregar bibliotecas como hijos del directorio if (directory.IsCurrentProject) { // Agregar proyecto actual directoryNode.Children.Add(new LibraryTreeNode { DisplayName = "Proyecto Actual", IsDirectory = false, Library = new LibraryItem { Name = "Proyecto Actual", IsCurrentProject = true, Objects = _mainViewModel.ObjetosSimulables.ToList() } }); } // Agregar archivos JSON if (Directory.Exists(directory.Path)) { var jsonFiles = Directory.GetFiles(directory.Path, "*.json", SearchOption.AllDirectories); foreach (var jsonFile in jsonFiles) { try { var displayName = Path.GetFileNameWithoutExtension(jsonFile); directoryNode.Children.Add(new LibraryTreeNode { DisplayName = displayName, IsDirectory = false, Library = new LibraryItem { Name = displayName, FilePath = jsonFile, IsCurrentProject = false, Objects = LoadObjectsFromJsonFile(jsonFile) } }); } catch { // Ignorar archivos que no se pueden cargar } } } LibraryTreeNodes.Add(directoryNode); } } private void CreateLibraryInDirectory() { if (SelectedTreeNode?.IsDirectory != true) return; var oldSelectedDirectory = SelectedLibraryDirectory; // Temporalmente cambiar el directorio seleccionado SelectedLibraryDirectory = LibraryDirectories.FirstOrDefault(d => d.Path == SelectedTreeNode.DirectoryPath); CreateNewLibrary(); RefreshLibraryTree(); // Restaurar el directorio seleccionado SelectedLibraryDirectory = oldSelectedDirectory; } private void RemoveDirectory() { if (SelectedTreeNode?.IsDirectory != true || SelectedTreeNode.IsCurrentProject) return; var result = MessageBox.Show($"¿Eliminar el directorio '{SelectedTreeNode.DisplayName}' de la lista de bibliotecas?", "Confirmar eliminación", MessageBoxButton.YesNo, MessageBoxImage.Question); if (result == MessageBoxResult.Yes) { EstadoPersistente.Instance.LibraryDirectories.Remove(SelectedTreeNode.DirectoryPath); EstadoPersistente.Instance.GuardarEstado(); LoadLibraryDirectories(); RefreshLibraryTree(); } } } // Support classes public class LibraryDirectoryItem { public string Path { get; set; } public string DisplayName { get; set; } public bool IsCurrentProject { get; set; } } public class LibraryItem { public string Name { get; set; } public string FilePath { get; set; } public bool IsCurrentProject { get; set; } public List Objects { get; set; } = new List(); public string DisplayName => IsCurrentProject ? $"{Name} (Proyecto)" : Name; } public class ObjectTypeGroup { public Type Type { get; set; } public string TypeName { get; set; } public ObservableCollection Objects { get; set; } = new ObservableCollection(); } }