Compare commits

...

4 Commits

Author SHA1 Message Date
Miguel 9b710fcb00 Se implementó un sistema para gestionar múltiples ventanas de biblioteca, permitiendo la activación de una ventana existente o la creación de una nueva. Además, se mejoró la lógica de pegado de objetos, integrando la validación del contenido del portapapeles y la capacidad de pegar desde el portapapeles del sistema. Se añadió un método para recargar la imagen actual después de pegar en el proyecto actual, mejorando la experiencia del usuario al gestionar objetos en la biblioteca. 2025-06-18 11:49:22 +02:00
Miguel c03f6970d8 Se añadió la clase LibraryWindowSettings para gestionar la configuración de la ventana de la biblioteca, incluyendo propiedades para dimensiones y posición. Se implementó la persistencia de estas configuraciones al abrir y cerrar la ventana. Además, se mejoró la interfaz de usuario con un TreeView jerárquico para la gestión de bibliotecas y se añadieron comandos para crear y eliminar directorios de bibliotecas. Se implementó la selección múltiple de objetos en la ventana de la biblioteca, mejorando la experiencia del usuario al gestionar objetos. 2025-06-18 02:11:38 +02:00
Miguel c353f6c6ea Se añadió la funcionalidad de gestión de bibliotecas de objetos en la interfaz de usuario, incluyendo un nuevo comando para abrir la ventana de gestión de bibliotecas. Se incorporó una nueva propiedad en la clase EstadoPersistente para almacenar directorios de bibliotecas y se realizaron mejoras en la estructura del código para una mayor claridad y mantenimiento. 2025-06-18 01:24:28 +02:00
Miguel 67fa5eef3d Se implementó un sistema de filtrado por etiquetas en la interfaz de usuario, permitiendo a los usuarios buscar y seleccionar objetos basados en etiquetas personalizadas. Se añadieron nuevas propiedades y métodos en la clase osBase para gestionar etiquetas, así como mejoras en la lógica de actualización de filtros en función de los objetos disponibles. Además, se realizaron ajustes en la visualización y manejo de los filtros en el control osVisFilter. 2025-06-17 18:38:00 +02:00
14 changed files with 2228 additions and 97 deletions

View File

@ -46,6 +46,24 @@
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Separator between types and tags -->
<Separator Style="{StaticResource FilterSeparatorStyle}" />
<!-- Tag Filter Options -->
<TextBlock Text="Tags" Style="{StaticResource FilterHeaderStyle}" />
<TextBox Margin="0,2,0,5"
Text="{Binding SearchTags, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ToolTip="Buscar etiquetas (use # antes de cada etiqueta, ej: #motor #bomba)"
FontSize="11" />
<ItemsControl ItemsSource="{Binding TagFilters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding DisplayName}" IsChecked="{Binding IsSelected, Mode=TwoWay}"
Style="{StaticResource FilterCheckBoxStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</Border>

View File

@ -46,6 +46,14 @@ namespace CtrEditor.Controls
{
FilterViewModel.UpdateTypeFilters(types);
}
/// <summary>
/// Updates the tag filters based on the provided objects
/// </summary>
public void UpdateAvailableTags(IEnumerable<osBase> objects)
{
FilterViewModel.UpdateTagFilters(objects);
}
}
/// <summary>
@ -71,6 +79,12 @@ namespace CtrEditor.Controls
[ObservableProperty]
private ObservableCollection<TypeFilterItem> typeFilters = new ObservableCollection<TypeFilterItem>();
[ObservableProperty]
private ObservableCollection<TagFilterItem> tagFilters = new ObservableCollection<TagFilterItem>();
[ObservableProperty]
private string searchTags = "";
partial void OnShowAllChanged(bool value)
{
NotifyFilterChanged();
@ -113,6 +127,28 @@ namespace CtrEditor.Controls
}
}
partial void OnTagFiltersChanged(ObservableCollection<TagFilterItem> value)
{
if (value != null)
{
foreach (var filter in value)
{
filter.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(TagFilterItem.IsSelected))
{
NotifyFilterChanged();
}
};
}
}
}
partial void OnSearchTagsChanged(string value)
{
NotifyFilterChanged();
}
private void NotifyFilterChanged()
{
if (this.Parent is osVisFilter filter)
@ -165,6 +201,44 @@ namespace CtrEditor.Controls
}
}
/// <summary>
/// Updates the tag filters based on the provided objects
/// </summary>
public void UpdateTagFilters(IEnumerable<osBase> objects)
{
// Get all unique tags from all objects
var allTags = objects
.SelectMany(obj => obj.ListaEtiquetas)
.Distinct()
.OrderBy(tag => tag)
.ToList();
// Remove tags that are no longer present
var tagsToRemove = TagFilters
.Where(tf => !allTags.Contains(tf.TagName))
.ToList();
foreach (var item in tagsToRemove)
{
UnsubscribeFromTagFilter(item);
TagFilters.Remove(item);
}
// Add new tags that aren't already in the list
foreach (var tag in allTags)
{
if (!TagFilters.Any(tf => tf.TagName == tag))
{
var newFilter = new TagFilterItem(tag)
{
IsSelected = true
};
SubscribeToTagFilter(newFilter);
TagFilters.Add(newFilter);
}
}
}
private void SubscribeToTypeFilter(TypeFilterItem filter)
{
filter.PropertyChanged += TypeFilter_PropertyChanged;
@ -175,6 +249,16 @@ namespace CtrEditor.Controls
filter.PropertyChanged -= TypeFilter_PropertyChanged;
}
private void SubscribeToTagFilter(TagFilterItem filter)
{
filter.PropertyChanged += TagFilter_PropertyChanged;
}
private void UnsubscribeFromTagFilter(TagFilterItem filter)
{
filter.PropertyChanged -= TagFilter_PropertyChanged;
}
private void TypeFilter_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(TypeFilterItem.IsSelected))
@ -183,6 +267,14 @@ namespace CtrEditor.Controls
}
}
private void TagFilter_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(TagFilterItem.IsSelected))
{
NotifyFilterChanged();
}
}
/// <summary>
/// Gets the display name for a type, using the NombreClase static method if available
/// </summary>
@ -220,4 +312,26 @@ namespace CtrEditor.Controls
OnPropertyChanged(nameof(IsSelected));
}
}
/// <summary>
/// Represents a tag filter item with selection state
/// </summary>
public partial class TagFilterItem : ObservableObject
{
[ObservableProperty]
private bool isSelected;
public string TagName { get; }
public string DisplayName => "#" + TagName;
public TagFilterItem(string tagName)
{
TagName = tagName;
}
partial void OnIsSelectedChanged(bool value)
{
// Could add logic here if needed when selection changes
}
}
}

View File

@ -0,0 +1,110 @@
# Biblioteca de Objetos Simulables
## Descripción
La biblioteca de objetos simulables es una nueva funcionalidad que permite gestionar y organizar objetos simulables en bibliotecas independientes del proyecto actual. Esto facilita la reutilización de objetos entre diferentes proyectos.
## Características Principales
### 1. Gestión de Directorios de Biblioteca
- **Agregar directorios**: Permite agregar nuevos directorios que contendrán bibliotecas de objetos
- **Eliminar directorios**: Permite remover directorios de la lista de bibliotecas
- **Persistencia global**: Los directorios se mantienen independientemente del proyecto actual
### 2. Bibliotecas de Objetos
- **Proyecto actual**: El proyecto actual siempre aparece como una biblioteca
- **Archivos JSON**: Cada archivo JSON en los directorios de biblioteca se muestra como una biblioteca independiente
- **Visualización jerárquica**: Los objetos se organizan por tipo en un TreeView
### 3. Sistema de Filtros
- **Filtros por tipo**: Permite mostrar/ocultar tipos específicos de objetos
- **Filtros por etiquetas**: Filtrado por etiquetas usando # (ej: #motor #bomba)
- **Filtros especiales**:
- Cloned (objetos clonados)
- Auto Created (objetos creados automáticamente)
- Enable On All Pages (objetos globales)
- Show On This Page (objetos visibles en página actual)
### 4. Operaciones de Objetos
- **Copiar (Ctrl+C)**: Copia objetos seleccionados al portapapeles interno
- **Pegar (Ctrl+V)**: Pega objetos del portapapeles a la biblioteca seleccionada
- **Eliminar**: Elimina objetos de bibliotecas (no disponible para proyecto actual)
- **Visualizar propiedades**: PropertyGrid de solo lectura para inspeccionar objetos
### 5. Gestión de Bibliotecas
- **Crear nueva biblioteca**: Crea un archivo JSON vacío como nueva biblioteca
- **Cargar automáticamente**: Las bibliotecas se cargan automáticamente al seleccionar directorios
## Cómo Usar
### Acceso
1. Haga clic en el botón **"Biblioteca"** en la barra de herramientas principal (al lado de "Assing Pages")
2. Se abrirá la ventana modal de gestión de bibliotecas
### Configurar Directorios de Biblioteca
1. En la columna izquierda "Directorios de Biblioteca", haga clic en **"Agregar"**
2. Seleccione la carpeta que contendrá sus bibliotecas de objetos
3. El directorio se agregará a la lista y se guardará automáticamente
### Navegar Bibliotecas
1. Seleccione un directorio de la lista en la columna izquierda
2. En la columna central aparecerán todas las bibliotecas (archivos JSON) encontradas
3. Seleccione una biblioteca para ver sus objetos organizados por tipo
### Filtrar Objetos
1. Use la sección "Filtros" para mostrar/ocultar tipos específicos
2. Use el campo de búsqueda para filtrar por etiquetas (ej: `#motor #conveyor`)
3. Active los filtros especiales según sea necesario
### Copiar/Pegar Objetos
1. **Para copiar**: Seleccione un objeto y presione Ctrl+C o haga clic en "Copiar"
2. **Para pegar**: Seleccione la biblioteca destino y presione Ctrl+V o haga clic en "Pegar"
3. Los objetos se copiarán con nuevos IDs únicos
### Crear Nueva Biblioteca
1. Seleccione un directorio de biblioteca
2. Haga clic en **"Nueva Biblioteca"**
3. Elija nombre y ubicación para el archivo JSON
4. Se creará una biblioteca vacía lista para usar
## Casos de Uso
### Biblioteca de Componentes Estándar
- Cree una biblioteca con motores, sensores y actuadores estándar
- Reutilice estos componentes en múltiples proyectos
- Mantenga configuraciones consistentes entre proyectos
### Plantillas de Sistemas
- Guarde sistemas completos (ej: línea de ensamblaje básica)
- Use como punto de partida para nuevos proyectos
- Facilite la estandarización de diseños
### Backup y Versioning
- Mantenga copias de objetos importantes en bibliotecas externas
- Cree versiones de componentes con diferentes configuraciones
- Facilite la colaboración entre equipos
## Estructura de Archivos
Las bibliotecas se almacenan como archivos JSON con la misma estructura que los archivos de proyecto:
```json
{
"ObjetosSimulables": [...],
"UnitConverter": {...},
"PLC_ConnectionData": {...}
}
```
## Notas Técnicas
- Los directorios de biblioteca se almacenan en `EstadoPersistente.Instance.LibraryDirectories`
- Los objetos se serializan usando Newtonsoft.Json con `TypeNameHandling.All`
- La funcionalidad es independiente del directorio de trabajo actual
- Los filtros utilizan el mismo sistema que la aplicación principal (`osVisFilter`)
## Limitaciones
- No se pueden editar objetos directamente en las bibliotecas (solo copia/pegar)
- La eliminación solo está disponible para bibliotecas externas (no proyecto actual)
- Los archivos JSON deben tener estructura válida de SimulationData

View File

@ -24,6 +24,7 @@ using System.Text.RegularExpressions;
using System.Collections.Specialized;
using CtrEditor.Serialization; // Add this line
using CtrEditor.Controls; // Add this using directive
using CtrEditor.PopUps; // Add this using directive
namespace CtrEditor
{
@ -105,6 +106,7 @@ namespace CtrEditor
public ICommand TBMultiPageExtractTagsCommand { get; }
public ICommand TBMultiPageAnalizeCommand { get; }
public ICommand TBMultiPageMatrixCommand { get; }
public ICommand TBLibraryManagerCommand { get; }
public ICommand TBTogglePLCConnectionCommand => new RelayCommand(() =>
@ -317,6 +319,7 @@ namespace CtrEditor
private void UpdateVisFilterTypes()
{
MainWindow?.VisFilter?.UpdateAvailableTypes(ObjetosSimulables.Select(o => o.GetType()).Distinct());
MainWindow?.VisFilter?.UpdateAvailableTags(ObjetosSimulables);
}
//
@ -379,6 +382,7 @@ namespace CtrEditor
TBAssingPagesCommand = new RelayCommand(AssingPagesCommand);
TBMultiPageAnalizeCommand = new RelayCommand(MultiPageAnalizeCommand);
TBMultiPageMatrixCommand = new RelayCommand(MultiPageMatrixCommand);
TBLibraryManagerCommand = new RelayCommand(ShowLibraryManager);
stopwatch_Sim = new Stopwatch();
stopwatch_Sim.Start();
@ -412,6 +416,36 @@ namespace CtrEditor
isVisible = false;
}
// Check tag filters
if (filter.TagFilters.Any() && filter.TagFilters.Any(tf => tf.IsSelected))
{
var selectedTags = filter.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(filter.SearchTags))
{
var searchTags = filter.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 (filter.ShowCloned && !obj.Cloned)
isVisible = false;
@ -665,7 +699,7 @@ namespace CtrEditor
.Where(o => o.Show_On_This_Page && o.AutoCreated)
.ToList();
foreach (var obj in osAutoCreated_List)
RemoverObjetoSimulable(obj);
RemoverObjetoSimulable(obj);
}
private void EliminarClonedCommand()
@ -674,7 +708,7 @@ namespace CtrEditor
.Where(o => o.Show_On_This_Page && o.Cloned)
.ToList();
foreach (var obj in osCloned_List)
RemoverObjetoSimulable(obj);
RemoverObjetoSimulable(obj);
}
private void AssingPagesCommand()
@ -765,10 +799,10 @@ namespace CtrEditor
private void StartSimulation()
{
// Detener simulación de fluidos si está ejecutándose
StopFluidSimulation();
// Detener simulación de fluidos si está ejecutándose
StopFluidSimulation();
IsSimulationRunning = true;
IsSimulationRunning = true;
// Ocultar rectángulos de selección antes de iniciar la simulación
_objectManager.UpdateSelectionVisuals();
@ -795,36 +829,36 @@ namespace CtrEditor
{
simulationManager.Debug_ClearSimulationShapes();
Debug_SimulacionCreado = false;
}
_timerSimulacion.Stop();
}
_timerSimulacion.Stop();
// Restaurar los rectángulos de selección si hay objetos seleccionados
_objectManager.UpdateSelectionVisuals();
// Restaurar los rectángulos de selección si hay objetos seleccionados
_objectManager.UpdateSelectionVisuals();
// Limpiar historial de undo al detener la simulación
MainWindow?.ClearUndoHistory();
}
// Limpiar historial de undo al detener la simulación
MainWindow?.ClearUndoHistory();
}
/// <summary>
/// Inicia la simulación de fluidos independiente
/// </summary>
public void StartFluidSimulation()
{
// Detener simulación física si está ejecutándose
StopSimulation();
/// <summary>
/// Inicia la simulación de fluidos independiente
/// </summary>
public void StartFluidSimulation()
{
// Detener simulación física si está ejecutándose
StopSimulation();
FluidSimulation.StartFluidSimulation();
CommandManager.InvalidateRequerySuggested();
}
FluidSimulation.StartFluidSimulation();
CommandManager.InvalidateRequerySuggested();
}
/// <summary>
/// Detiene la simulación de fluidos independiente
/// </summary>
public void StopFluidSimulation()
{
FluidSimulation.StopFluidSimulation();
CommandManager.InvalidateRequerySuggested();
}
/// <summary>
/// Detiene la simulación de fluidos independiente
/// </summary>
public void StopFluidSimulation()
{
FluidSimulation.StopFluidSimulation();
CommandManager.InvalidateRequerySuggested();
}
private void OnTickSimulacion(object sender, EventArgs e)
{
@ -920,7 +954,7 @@ namespace CtrEditor
}
stopwatch.Stop(); // Stop measuring time
// Debug.WriteLine($"OnRefreshEvent: {stopwatch.Elapsed.TotalMilliseconds} ms");
// Debug.WriteLine($"OnRefreshEvent: {stopwatch.Elapsed.TotalMilliseconds} ms");
}
private void OpenWorkDirectory()
@ -1127,6 +1161,37 @@ namespace CtrEditor
{
OnPropertyChanged(nameof(SelectedItemOsList));
}
// Diccionario para manejar ventanas de biblioteca múltiples
private Dictionary<string, PopUps.LibraryWindow> _libraryWindows = new();
private void ShowLibraryManager()
{
const string libraryWindowKey = "LibraryManager";
// Verificar si ya existe una ventana de biblioteca
if (_libraryWindows.TryGetValue(libraryWindowKey, out var existingWindow) &&
existingWindow.IsVisible)
{
existingWindow.Activate();
return;
}
// Crear nueva ventana modeless
var libraryWindow = new PopUps.LibraryWindow();
var libraryViewModel = new PopUps.LibraryWindowViewModel(this);
libraryWindow.DataContext = libraryViewModel;
libraryWindow.Owner = MainWindow;
// Manejar el cierre de la ventana
libraryWindow.Closed += (s, e) => _libraryWindows.Remove(libraryWindowKey);
// Registrar la ventana
_libraryWindows[libraryWindowKey] = libraryWindow;
// Mostrar como modeless (no modal)
libraryWindow.Show();
}
}
public class SimulationData

View File

@ -251,6 +251,12 @@
<TextBlock Text="Assing Pages" />
</StackPanel>
</Button>
<Button Command="{Binding TBLibraryManagerCommand}" ToolTip="Gestión de Biblioteca de Objetos">
<StackPanel>
<Image Source="Icons/app.png" Width="16" Height="16" />
<TextBlock Text="Biblioteca" />
</StackPanel>
</Button>
</ToolBar>
</ToolBarTray>

View File

@ -0,0 +1,91 @@
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using CtrEditor.PopUps;
using Xceed.Wpf.Toolkit.PropertyGrid;
using Xceed.Wpf.Toolkit.PropertyGrid.Editors;
namespace CtrEditor.ObjetosSim
{
/// <summary>
/// Atributo para marcar propiedades que deben usar el editor de etiquetas
/// </summary>
public class TagEditorAttribute : Attribute
{
public TagEditorAttribute() { }
}
/// <summary>
/// Editor personalizado para etiquetas en PropertyGrid
/// </summary>
public class TagPropertyEditor : ITypeEditor
{
public FrameworkElement ResolveEditor(PropertyItem propertyItem)
{
var grid = new Grid();
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
// TextBox para mostrar/editar etiquetas directamente
var textBox = new TextBox();
textBox.SetBinding(TextBox.TextProperty, new Binding("Value")
{
Source = propertyItem,
Mode = BindingMode.TwoWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
Grid.SetColumn(textBox, 0);
// Botón para abrir el editor modal
var button = new Button
{
Content = "...",
Width = 25,
Margin = new Thickness(2, 0, 0, 0),
ToolTip = "Abrir editor de etiquetas"
};
button.Click += (sender, e) =>
{
try
{
// Obtener el objeto que se está editando
if (propertyItem.Instance is osBase osObject)
{
// Obtener el MainWindow para acceder a todos los objetos
var mainWindow = Application.Current.Windows
.OfType<MainWindow>()
.FirstOrDefault();
if (mainWindow?.DataContext is MainViewModel mainViewModel)
{
// Abrir el editor de etiquetas
var tagEditor = new TagEditorWindow(osObject, mainViewModel.ObjetosSimulables);
tagEditor.Owner = mainWindow;
if (tagEditor.ShowDialog() == true)
{
// La ventana ya actualiza el objeto directamente
// Actualizar el textbox con el nuevo valor
textBox.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget();
}
}
}
}
catch (Exception ex)
{
MessageBox.Show($"Error al abrir el editor de etiquetas: {ex.Message}",
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
};
Grid.SetColumn(button, 1);
grid.Children.Add(textBox);
grid.Children.Add(button);
return grid;
}
}
}

View File

@ -176,6 +176,52 @@ namespace CtrEditor.ObjetosSim
public virtual void AnguloChanged(float value) { }
public virtual void AnguloChanging(float oldValue, float newValue) { }
[ObservableProperty]
[property: Description("Etiquetas para clasificar el objeto. Use # antes de cada etiqueta (ej: #motor #bomba #critico)")]
[property: Category("General:")]
[property: Editor(typeof(TagPropertyEditor), typeof(TagPropertyEditor))]
[property: TagEditor]
private string etiquetas = "";
partial void OnEtiquetasChanged(string value)
{
EtiquetasChanged(value);
// Update the visibility filters when tags change
_mainViewModel?.MainWindow?.VisFilter?.UpdateAvailableTags(_mainViewModel.ObjetosSimulables);
}
public virtual void EtiquetasChanged(string value) { }
/// <summary>
/// Obtiene la lista de etiquetas parseadas desde el string
/// </summary>
[JsonIgnore]
public List<string> ListaEtiquetas
{
get
{
if (string.IsNullOrWhiteSpace(Etiquetas))
return new List<string>();
return Etiquetas
.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Where(tag => tag.StartsWith("#") && tag.Length > 1)
.Select(tag => tag.Substring(1).ToLowerInvariant())
.Distinct()
.ToList();
}
}
/// <summary>
/// Verifica si el objeto tiene una etiqueta específica
/// </summary>
/// <param name="etiqueta">La etiqueta a buscar (sin el #)</param>
/// <returns>True si el objeto tiene la etiqueta</returns>
public bool TieneEtiqueta(string etiqueta)
{
return ListaEtiquetas.Contains(etiqueta.ToLowerInvariant());
}
public void Resize(double width, double height)
{
@ -666,7 +712,8 @@ namespace CtrEditor.ObjetosSim
// Filtrar texto por lista de caracteres permitidos
var filteredBlocks = result.TextBlocks
.Where(block => block.Score > 0.5) // Solo bloques con confianza > 50%
.Select(block => new {
.Select(block => new
{
Text = new string(block.Text.Where(c => allowedChars.Contains(c)).ToArray()),
Score = block.Score
})

225
PopUps/LibraryWindow.xaml Normal file
View File

@ -0,0 +1,225 @@
<Window x:Class="CtrEditor.PopUps.LibraryWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:CtrEditor.Controls"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:local="clr-namespace:CtrEditor.PopUps"
xmlns:ObjetosSim="clr-namespace:CtrEditor.ObjetosSim"
xmlns:ctr="clr-namespace:CtrEditor"
Title="Biblioteca de Objetos Simulables"
Height="{Binding Source={x:Static ctr:EstadoPersistente.Instance}, Path=LibraryWindow.Height, Mode=TwoWay}"
Width="{Binding Source={x:Static ctr:EstadoPersistente.Instance}, Path=LibraryWindow.Width, Mode=TwoWay}"
Left="{Binding Source={x:Static ctr:EstadoPersistente.Instance}, Path=LibraryWindow.Left, Mode=TwoWay}"
Top="{Binding Source={x:Static ctr:EstadoPersistente.Instance}, Path=LibraryWindow.Top, Mode=TwoWay}"
WindowStartupLocation="Manual" ResizeMode="CanResize"
Loaded="Window_Loaded" Closing="Window_Closing">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
<!-- Estilo para nombres largos con wrapping -->
<Style x:Key="WrappingTextBlockStyle" TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="MaxWidth" Value="200"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<!-- Estilo para nombres largos con formato especial -->
<Style x:Key="WrappingProjectTextBlockStyle" TargetType="TextBlock" BasedOn="{StaticResource WrappingTextBlockStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsCurrentProject}" Value="True">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
</Style.Triggers>
</Style>
<!-- Menú contextual para directorios -->
<ContextMenu x:Key="DirectoryContextMenu">
<MenuItem Header="Nueva Biblioteca" Command="{Binding CreateLibraryInDirectoryCommand}"/>
<Separator/>
<MenuItem Header="Eliminar Directorio" Command="{Binding RemoveDirectoryCommand}"/>
</ContextMenu>
<!-- Estilo para TreeViewItem de directorios -->
<Style x:Key="DirectoryTreeViewItemStyle" TargetType="TreeViewItem">
<Setter Property="ContextMenu" Value="{StaticResource DirectoryContextMenu}"/>
</Style>
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="C" Modifiers="Ctrl" Command="{Binding CopyObjectCommand}" />
<KeyBinding Key="V" Modifiers="Ctrl" Command="{Binding PasteObjectCommand}" />
<KeyBinding Key="Delete" Command="{Binding DeleteObjectCommand}" />
</Window.InputBindings>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Source={x:Static ctr:EstadoPersistente.Instance}, Path=LibraryWindow.Column0Width, Mode=TwoWay}" MinWidth="200" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="{Binding Source={x:Static ctr:EstadoPersistente.Instance}, Path=LibraryWindow.Column2Width, Mode=TwoWay}" MinWidth="250" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="{Binding Source={x:Static ctr:EstadoPersistente.Instance}, Path=LibraryWindow.Column4Width, Mode=TwoWay}" MinWidth="250" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" MinWidth="300" />
</Grid.ColumnDefinitions>
<!-- Primera columna: TreeView jerárquico de directorios y bibliotecas -->
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="Bibliotecas" FontWeight="Bold" />
<TreeView Grid.Row="1" Name="LibraryTreeView"
ItemsSource="{Binding LibraryTreeNodes}"
SelectedItemChanged="LibraryTreeView_SelectedItemChanged">
<TreeView.ItemContainerStyleSelector>
<local:LibraryTreeItemStyleSelector DirectoryStyle="{StaticResource DirectoryTreeViewItemStyle}"/>
</TreeView.ItemContainerStyleSelector>
<TreeView.Resources>
<!-- Template for directory and library nodes -->
<HierarchicalDataTemplate DataType="{x:Type local:LibraryTreeNode}">
<HierarchicalDataTemplate.ItemsSource>
<Binding Path="Children"/>
</HierarchicalDataTemplate.ItemsSource>
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock Text="📁" Margin="0,0,5,0" VerticalAlignment="Center"
Visibility="{Binding IsDirectory, Converter={StaticResource BoolToVisConverter}}"/>
<TextBlock Text="📄" Margin="0,0,5,0" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsDirectory}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="{Binding DisplayName}" Style="{StaticResource WrappingProjectTextBlockStyle}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="5">
<Button Content="Agregar Directorio" Command="{Binding AddLibraryDirectoryCommand}" Margin="2" Padding="5,2" />
</StackPanel>
</Grid>
<!-- GridSplitter 1 -->
<GridSplitter Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Stretch"
Background="LightGray" Width="5" />
<!-- Segunda columna: Filtros -->
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="Filtros" FontWeight="Bold" />
<Expander Grid.Row="1" Header="Configuración de Filtros" IsExpanded="True">
<controls:osVisFilter x:Name="ObjectFilter" Margin="5"/>
</Expander>
</Grid>
<!-- GridSplitter 2 -->
<GridSplitter Grid.Column="3" HorizontalAlignment="Center" VerticalAlignment="Stretch"
Background="LightGray" Width="5" />
<!-- Tercera columna: Objetos con selección múltiple -->
<Grid Grid.Column="4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="Objetos" FontWeight="Bold" />
<!-- Controles de selección múltiple -->
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="5">
<Button Content="Seleccionar Todo" Click="SelectAllObjects_Click" Margin="2" Padding="5,2"/>
<Button Content="Deseleccionar Todo" Click="UnselectAllObjects_Click" Margin="2" Padding="5,2"/>
</StackPanel>
<!-- Listbox para objetos con checkboxes -->
<ListBox Grid.Row="2" Name="SelectableObjectsList"
ItemsSource="{Binding SelectableObjects}"
SelectionMode="Extended">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}"
Margin="0,0,5,0" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Object.Nombre}" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Object.Enable_On_All_Pages}" Value="True">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
<DataTrigger Binding="{Binding Object.Cloned}" Value="True">
<Setter Property="Foreground" Value="Orange" />
</DataTrigger>
<DataTrigger Binding="{Binding Object.AutoCreated}" Value="True">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- Botones de acción -->
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Center" Margin="5">
<Button Content="Copiar (Ctrl+C)" Command="{Binding CopyObjectCommand}" Margin="2" Padding="5,2" />
<Button Content="Pegar (Ctrl+V)" Command="{Binding PasteObjectCommand}" Margin="2" Padding="5,2" />
<Button Content="Eliminar" Command="{Binding DeleteObjectCommand}" Margin="2" Padding="5,2" />
</StackPanel>
</Grid>
<!-- GridSplitter 3 -->
<GridSplitter Grid.Column="5" HorizontalAlignment="Center" VerticalAlignment="Stretch"
Background="LightGray" Width="5" />
<!-- Cuarta columna: Propiedades del objeto seleccionado -->
<Grid Grid.Column="6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="Propiedades del Objeto" FontWeight="Bold" />
<xctk:PropertyGrid Grid.Row="1"
Name="ObjectPropertyGrid"
SelectedObject="{Binding SelectedObject}"
IsReadOnly="True"
ShowSearchBox="True"
ShowSortOptions="True"
ShowTitle="False"
Margin="5" />
<!-- Botones de control -->
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="5">
<Button Content="Cerrar" IsCancel="True" Click="CloseButton_Click"
Margin="2" Padding="15,5" MinWidth="75" />
</StackPanel>
</Grid>
</Grid>
</Window>

View File

@ -0,0 +1,155 @@
using System.Windows;
using System.Windows.Controls;
using CtrEditor.ObjetosSim;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
namespace CtrEditor.PopUps
{
/// <summary>
/// Interaction logic for LibraryWindow.xaml
/// </summary>
public partial class LibraryWindow : Window
{
public LibraryWindow()
{
InitializeComponent();
// Aplicar configuraciones persistentes
ApplyPersistedSettings();
// Suscribirse al evento StateChanged para persistir cambios de estado
StateChanged += LibraryWindow_StateChanged;
}
private void LibraryWindow_StateChanged(object sender, EventArgs e)
{
// Guardar el estado cuando cambia
EstadoPersistente.Instance.LibraryWindow.IsMaximized = WindowState == WindowState.Maximized;
EstadoPersistente.Instance.GuardarEstado();
}
private void ApplyPersistedSettings()
{
var settings = EstadoPersistente.Instance.LibraryWindow;
// Asegurar que la ventana esté dentro de los límites de la pantalla
var screenWidth = SystemParameters.WorkArea.Width;
var screenHeight = SystemParameters.WorkArea.Height;
if (settings.Left < 0 || settings.Left > screenWidth - 100)
settings.Left = 100;
if (settings.Top < 0 || settings.Top > screenHeight - 100)
settings.Top = 100;
if (settings.Width < 800 || settings.Width > screenWidth)
settings.Width = 1200;
if (settings.Height < 600 || settings.Height > screenHeight)
settings.Height = 700;
// Aplicar el estado de la ventana
WindowState = settings.IsMaximized ? WindowState.Maximized : WindowState.Normal;
}
private void LibraryTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (DataContext is LibraryWindowViewModel viewModel)
{
if (e.NewValue is LibraryTreeNode selectedNode)
{
viewModel.SelectedTreeNode = selectedNode;
// Si es una biblioteca (no directorio), establecer como biblioteca seleccionada
if (!selectedNode.IsDirectory && selectedNode.Library != null)
{
viewModel.SelectedLibrary = selectedNode.Library;
}
}
}
}
private void ObjectsTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (DataContext is LibraryWindowViewModel viewModel)
{
// Check if the selected item is an osBase object or an ObjectTypeGroup
if (e.NewValue is osBase selectedObject)
{
viewModel.SelectedObject = selectedObject;
}
else if (e.NewValue is ObjectTypeGroup typeGroup)
{
// If a type group is selected, clear the selected object
viewModel.SelectedObject = null;
}
else
{
viewModel.SelectedObject = null;
}
}
}
private void SelectAllObjects_Click(object sender, RoutedEventArgs e)
{
if (DataContext is LibraryWindowViewModel viewModel)
{
foreach (var item in viewModel.SelectableObjects)
{
item.IsSelected = true;
}
}
}
private void UnselectAllObjects_Click(object sender, RoutedEventArgs e)
{
if (DataContext is LibraryWindowViewModel viewModel)
{
foreach (var item in viewModel.SelectableObjects)
{
item.IsSelected = false;
}
}
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Setup filter events after window is loaded
if (DataContext is LibraryWindowViewModel viewModel)
{
ObjectFilter.FilterChanged += (s, args) => viewModel.OnFilterChanged(args.FilterViewModel);
viewModel.SetObjectFilter(ObjectFilter);
}
}
private void Window_Closing(object sender, CancelEventArgs e)
{
// Guardar configuraciones de la ventana antes de cerrar
SaveWindowSettings();
}
private void SaveWindowSettings()
{
var settings = EstadoPersistente.Instance.LibraryWindow;
if (WindowState == WindowState.Normal)
{
settings.Width = ActualWidth;
settings.Height = ActualHeight;
settings.Left = Left;
settings.Top = Top;
}
settings.IsMaximized = WindowState == WindowState.Maximized;
EstadoPersistente.Instance.GuardarEstado();
}
}
}

View File

@ -0,0 +1,48 @@
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
namespace CtrEditor.PopUps
{
// StyleSelector para TreeViewItems
public class LibraryTreeItemStyleSelector : StyleSelector
{
public Style DirectoryStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
if (item is LibraryTreeNode node && node.IsDirectory)
{
return DirectoryStyle;
}
return null;
}
}
// Clase para nodos del TreeView jerárquico
public class LibraryTreeNode : ObservableObject
{
public string DisplayName { get; set; }
public bool IsDirectory { get; set; }
public bool IsCurrentProject { get; set; }
public string DirectoryPath { get; set; }
public LibraryItem Library { get; set; }
public ObservableCollection<LibraryTreeNode> Children { get; set; } = new ObservableCollection<LibraryTreeNode>();
}
// Wrapper para objetos seleccionables
public class SelectableObjectWrapper : ObservableObject
{
public CtrEditor.ObjetosSim.osBase Object { get; set; }
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set => SetProperty(ref _isSelected, value);
}
}
}

View File

@ -0,0 +1,902 @@
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<osBase> _clipboardObjects = new List<osBase>();
[ObservableProperty]
private ObservableCollection<LibraryDirectoryItem> libraryDirectories;
[ObservableProperty]
private LibraryDirectoryItem selectedLibraryDirectory;
[ObservableProperty]
private ObservableCollection<LibraryItem> libraries;
[ObservableProperty]
private LibraryItem selectedLibrary;
[ObservableProperty]
private ObservableCollection<ObjectTypeGroup> filteredObjectsByType;
[ObservableProperty]
private osBase selectedObject;
// Nueva propiedad para el TreeView jerárquico
[ObservableProperty]
private ObservableCollection<LibraryTreeNode> libraryTreeNodes;
[ObservableProperty]
private LibraryTreeNode selectedTreeNode;
// Nueva propiedad para objetos seleccionados múltiples
[ObservableProperty]
private ObservableCollection<SelectableObjectWrapper> 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<LibraryDirectoryItem>();
Libraries = new ObservableCollection<LibraryItem>();
FilteredObjectsByType = new ObservableCollection<ObjectTypeGroup>();
LibraryTreeNodes = new ObservableCollection<LibraryTreeNode>();
SelectableObjects = new ObservableCollection<SelectableObjectWrapper>();
// 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<osBase> GetSelectedObjects()
{
var selected = new List<osBase>();
// 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<osBase> LoadObjectsFromJsonFile(string filePath)
{
var objects = new List<osBase>();
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<List<osBase>>(json, settings);
if (directObjects != null)
{
objects.AddRange(directObjects);
}
}
else if (json.StartsWith("{"))
{
// Normal format: SimulationData wrapper
var simulationData = JsonConvert.DeserializeObject<SimulationData>(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<osBase> 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<osBase>(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<osBase>();
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<osBase>();
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<osBase>(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<osBase> objectsToPaste = new List<osBase>();
// 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<List<osBase>>(jsonString, settings);
}
catch
{
// Si falla, intentar como objeto individual
try
{
var singleObject = JsonConvert.DeserializeObject<osBase>(jsonString, settings);
if (singleObject != null)
{
objectsToPaste = 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 (!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<osBase>(),
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<osBase>(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<LibraryTreeNode>()
};
// 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<osBase> Objects { get; set; } = new List<osBase>();
public string DisplayName => IsCurrentProject ? $"{Name} (Proyecto)" : Name;
}
public class ObjectTypeGroup
{
public Type Type { get; set; }
public string TypeName { get; set; }
public ObservableCollection<osBase> Objects { get; set; } = new ObservableCollection<osBase>();
}
}

111
PopUps/TagEditorWindow.xaml Normal file
View File

@ -0,0 +1,111 @@
<Window x:Class="CtrEditor.PopUps.TagEditorWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Editor de Etiquetas" Height="400" Width="500"
WindowStartupLocation="CenterOwner" ResizeMode="CanResize">
<Window.Resources>
<Style x:Key="TagButtonStyle" TargetType="Button">
<Setter Property="Margin" Value="2"/>
<Setter Property="Padding" Value="8,4"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="LightGray"/>
<Setter Property="Background" Value="LightBlue"/>
<Setter Property="Foreground" Value="DarkBlue"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
<Style x:Key="SelectedTagStyle" TargetType="Button" BasedOn="{StaticResource TagButtonStyle}">
<Setter Property="Background" Value="Navy"/>
<Setter Property="Foreground" Value="White"/>
</Style>
<Style x:Key="AvailableTagStyle" TargetType="Button" BasedOn="{StaticResource TagButtonStyle}">
<Setter Property="Background" Value="LightGray"/>
<Setter Property="Foreground" Value="Black"/>
</Style>
</Window.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Header -->
<TextBlock Grid.Row="0" Text="Editor de Etiquetas" FontSize="16" FontWeight="Bold" Margin="0,0,0,10"/>
<!-- Nueva etiqueta -->
<Grid Grid.Row="1" Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Nueva etiqueta:" VerticalAlignment="Center" Margin="0,0,10,0"/>
<TextBox Grid.Column="1" x:Name="txtNuevaEtiqueta"
Text="{Binding NuevaEtiqueta, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
KeyDown="TxtNuevaEtiqueta_KeyDown"/>
<Button Grid.Column="2" Content="Agregar"
Command="{Binding AgregarEtiquetaCommand}"
Margin="10,0,0,0" MinWidth="70"/>
</Grid>
<!-- Etiquetas del objeto actual -->
<TextBlock Grid.Row="2" Text="Etiquetas del objeto:" FontWeight="Bold" Margin="0,0,0,5"/>
<ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto" MaxHeight="120">
<ItemsControl ItemsSource="{Binding EtiquetasObjeto}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding DisplayName}"
Style="{StaticResource SelectedTagStyle}"
Command="{Binding DataContext.RemoverEtiquetaCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding TagName}"
ToolTip="Click para remover"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<!-- Etiquetas disponibles -->
<TextBlock Grid.Row="4" Text="Etiquetas disponibles:" FontWeight="Bold" Margin="0,10,0,5"/>
<ScrollViewer Grid.Row="5" VerticalScrollBarVisibility="Auto" MaxHeight="120">
<ItemsControl ItemsSource="{Binding EtiquetasDisponibles}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding DisplayName}"
Style="{StaticResource AvailableTagStyle}"
Command="{Binding DataContext.AgregarEtiquetaExistenteCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding TagName}"
ToolTip="Click para agregar"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<!-- Botones -->
<StackPanel Grid.Row="6" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,20,0,0">
<Button Content="Aplicar" Command="{Binding AplicarCommand}" Margin="0,0,10,0" MinWidth="70"/>
<Button Content="Cancelar" Command="{Binding CancelarCommand}" MinWidth="70"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,215 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CtrEditor.ObjetosSim;
namespace CtrEditor.PopUps
{
/// <summary>
/// Interaction logic for TagEditorWindow.xaml
/// </summary>
public partial class TagEditorWindow : Window
{
public TagEditorWindow(osBase objeto, IEnumerable<osBase> todosLosObjetos)
{
InitializeComponent();
DataContext = new TagEditorViewModel(objeto, todosLosObjetos, this);
}
private void TxtNuevaEtiqueta_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
if (DataContext is TagEditorViewModel viewModel)
{
viewModel.AgregarEtiquetaCommand.Execute(null);
}
}
}
}
public class TagInfo
{
public string TagName { get; set; }
public string DisplayName => "#" + TagName;
public TagInfo(string tagName)
{
TagName = tagName;
}
}
public partial class TagEditorViewModel : ObservableObject
{
private readonly osBase _objeto;
private readonly IEnumerable<osBase> _todosLosObjetos;
private readonly TagEditorWindow _window;
private readonly List<string> _etiquetasOriginales;
[ObservableProperty]
private string nuevaEtiqueta = "";
[ObservableProperty]
private ObservableCollection<TagInfo> etiquetasObjeto = new();
[ObservableProperty]
private ObservableCollection<TagInfo> etiquetasDisponibles = new();
public ICommand AgregarEtiquetaCommand { get; }
public ICommand AgregarEtiquetaExistenteCommand { get; }
public ICommand RemoverEtiquetaCommand { get; }
public ICommand AplicarCommand { get; }
public ICommand CancelarCommand { get; }
public TagEditorViewModel(osBase objeto, IEnumerable<osBase> todosLosObjetos, TagEditorWindow window)
{
_objeto = objeto;
_todosLosObjetos = todosLosObjetos;
_window = window;
// Guardar las etiquetas originales para poder cancelar
_etiquetasOriginales = objeto.ListaEtiquetas.ToList();
// Inicializar comandos
AgregarEtiquetaCommand = new RelayCommand(AgregarEtiqueta, () => !string.IsNullOrWhiteSpace(NuevaEtiqueta));
AgregarEtiquetaExistenteCommand = new RelayCommand<string>(AgregarEtiquetaExistente);
RemoverEtiquetaCommand = new RelayCommand<string>(RemoverEtiqueta);
AplicarCommand = new RelayCommand(Aplicar);
CancelarCommand = new RelayCommand(Cancelar);
// Cargar datos iniciales
CargarEtiquetasObjeto();
CargarEtiquetasDisponibles();
// Suscribirse al cambio de NuevaEtiqueta para actualizar el comando
PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(NuevaEtiqueta))
{
(AgregarEtiquetaCommand as IRelayCommand)?.NotifyCanExecuteChanged();
}
};
}
private void CargarEtiquetasObjeto()
{
EtiquetasObjeto.Clear();
foreach (var tag in _objeto.ListaEtiquetas.OrderBy(t => t))
{
EtiquetasObjeto.Add(new TagInfo(tag));
}
}
private void CargarEtiquetasDisponibles()
{
EtiquetasDisponibles.Clear();
// Obtener todas las etiquetas únicas de todos los objetos
var todasLasEtiquetas = _todosLosObjetos
.SelectMany(obj => obj.ListaEtiquetas)
.Distinct()
.OrderBy(tag => tag)
.ToList();
// Mostrar solo las que no están en el objeto actual
var etiquetasObjetoActual = _objeto.ListaEtiquetas;
foreach (var tag in todasLasEtiquetas.Except(etiquetasObjetoActual))
{
EtiquetasDisponibles.Add(new TagInfo(tag));
}
}
private void AgregarEtiqueta()
{
if (string.IsNullOrWhiteSpace(NuevaEtiqueta))
return;
// Limpiar la etiqueta (remover # si existe y convertir a minúsculas)
string tagLimpia = NuevaEtiqueta.Trim();
if (tagLimpia.StartsWith("#"))
tagLimpia = tagLimpia.Substring(1);
tagLimpia = tagLimpia.ToLowerInvariant();
// Validar que no esté vacía después de limpiar
if (string.IsNullOrWhiteSpace(tagLimpia))
return;
// Verificar que no existe ya
if (_objeto.ListaEtiquetas.Contains(tagLimpia))
return;
// Agregar la etiqueta al objeto
var etiquetasActuales = string.IsNullOrWhiteSpace(_objeto.Etiquetas) ?
new List<string>() :
_objeto.Etiquetas.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();
etiquetasActuales.Add("#" + tagLimpia);
_objeto.Etiquetas = string.Join(" ", etiquetasActuales);
// Actualizar las listas
CargarEtiquetasObjeto();
CargarEtiquetasDisponibles();
// Limpiar el campo
NuevaEtiqueta = "";
}
private void AgregarEtiquetaExistente(string tagName)
{
if (string.IsNullOrWhiteSpace(tagName) || _objeto.ListaEtiquetas.Contains(tagName))
return;
// Agregar la etiqueta al objeto
var etiquetasActuales = string.IsNullOrWhiteSpace(_objeto.Etiquetas) ?
new List<string>() :
_objeto.Etiquetas.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();
etiquetasActuales.Add("#" + tagName);
_objeto.Etiquetas = string.Join(" ", etiquetasActuales);
// Actualizar las listas
CargarEtiquetasObjeto();
CargarEtiquetasDisponibles();
}
private void RemoverEtiqueta(string tagName)
{
if (string.IsNullOrWhiteSpace(tagName))
return;
// Remover la etiqueta del objeto
var etiquetasActuales = _objeto.Etiquetas
.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Where(tag => !tag.Equals("#" + tagName, StringComparison.OrdinalIgnoreCase))
.ToList();
_objeto.Etiquetas = string.Join(" ", etiquetasActuales);
// Actualizar las listas
CargarEtiquetasObjeto();
CargarEtiquetasDisponibles();
}
private void Aplicar()
{
_window.DialogResult = true;
_window.Close();
}
private void Cancelar()
{
// Restaurar las etiquetas originales
var etiquetasOriginalesString = _etiquetasOriginales.Any() ?
string.Join(" ", _etiquetasOriginales.Select(tag => "#" + tag)) :
"";
_objeto.Etiquetas = etiquetasOriginalesString;
_window.DialogResult = false;
_window.Close();
}
}
}

View File

@ -22,6 +22,18 @@ namespace CtrEditor
public string TargetColumn { get; set; }
}
public class LibraryWindowSettings
{
public double Width { get; set; } = 1200;
public double Height { get; set; } = 700;
public double Left { get; set; } = 100;
public double Top { get; set; } = 100;
public double Column0Width { get; set; } = 300;
public double Column2Width { get; set; } = 350;
public double Column4Width { get; set; } = 350;
public bool IsMaximized { get; set; } = false;
}
internal class EstadoPersistente
{
// Ruta donde se guardará el estado
@ -42,6 +54,10 @@ namespace CtrEditor
public List<ColumnPairMapping> ColumnPairs { get; set; } = new List<ColumnPairMapping>();
public List<string> LibraryDirectories { get; set; } = new List<string>();
public LibraryWindowSettings LibraryWindow { get; set; } = new LibraryWindowSettings();
// Propiedad pública con get y set para controlar el acceso a _strDirectorioTrabajo
public string directorio
{
@ -57,9 +73,11 @@ namespace CtrEditor
public int newid
{
get {
get
{
_id++;
return _id; }
return _id;
}
set { _id = value; }
}
@ -72,6 +90,7 @@ namespace CtrEditor
{
_strDirectorioTrabajo = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
RecentDirectories = new List<string>();
LibraryWindow = new LibraryWindowSettings();
return this;
}
@ -110,10 +129,15 @@ namespace CtrEditor
if (File.Exists(_filePath))
{
string json = File.ReadAllText(_filePath);
return JsonSerializer.Deserialize<EstadoPersistente>(json);
var estado = JsonSerializer.Deserialize<EstadoPersistente>(json);
// Asegurar que LibraryWindow esté inicializado
if (estado.LibraryWindow == null)
estado.LibraryWindow = new LibraryWindowSettings();
return estado;
}
return new EstadoPersistente().Inizializar(); // Devuelve una nueva instancia si no existe el archivo
} catch
}
catch
{
return new EstadoPersistente().Inizializar(); // Devuelve una nueva instancia si no existe el archivo
}