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> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </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> </StackPanel>
</ScrollViewer> </ScrollViewer>
</Border> </Border>

View File

@ -46,6 +46,14 @@ namespace CtrEditor.Controls
{ {
FilterViewModel.UpdateTypeFilters(types); FilterViewModel.UpdateTypeFilters(types);
} }
/// <summary>
/// Updates the tag filters based on the provided objects
/// </summary>
public void UpdateAvailableTags(IEnumerable<osBase> objects)
{
FilterViewModel.UpdateTagFilters(objects);
}
} }
/// <summary> /// <summary>
@ -71,6 +79,12 @@ namespace CtrEditor.Controls
[ObservableProperty] [ObservableProperty]
private ObservableCollection<TypeFilterItem> typeFilters = new ObservableCollection<TypeFilterItem>(); private ObservableCollection<TypeFilterItem> typeFilters = new ObservableCollection<TypeFilterItem>();
[ObservableProperty]
private ObservableCollection<TagFilterItem> tagFilters = new ObservableCollection<TagFilterItem>();
[ObservableProperty]
private string searchTags = "";
partial void OnShowAllChanged(bool value) partial void OnShowAllChanged(bool value)
{ {
NotifyFilterChanged(); 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() private void NotifyFilterChanged()
{ {
if (this.Parent is osVisFilter filter) if (this.Parent is osVisFilter filter)
@ -120,7 +156,7 @@ namespace CtrEditor.Controls
filter.OnFilterChanged(); filter.OnFilterChanged();
} }
} }
[JsonIgnore] [JsonIgnore]
public osVisFilter Parent { get; set; } public osVisFilter Parent { get; set; }
@ -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) private void SubscribeToTypeFilter(TypeFilterItem filter)
{ {
filter.PropertyChanged += TypeFilter_PropertyChanged; filter.PropertyChanged += TypeFilter_PropertyChanged;
@ -175,6 +249,16 @@ namespace CtrEditor.Controls
filter.PropertyChanged -= TypeFilter_PropertyChanged; 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) private void TypeFilter_PropertyChanged(object sender, PropertyChangedEventArgs e)
{ {
if (e.PropertyName == nameof(TypeFilterItem.IsSelected)) 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> /// <summary>
/// Gets the display name for a type, using the NombreClase static method if available /// Gets the display name for a type, using the NombreClase static method if available
/// </summary> /// </summary>
@ -220,4 +312,26 @@ namespace CtrEditor.Controls
OnPropertyChanged(nameof(IsSelected)); 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 System.Collections.Specialized;
using CtrEditor.Serialization; // Add this line using CtrEditor.Serialization; // Add this line
using CtrEditor.Controls; // Add this using directive using CtrEditor.Controls; // Add this using directive
using CtrEditor.PopUps; // Add this using directive
namespace CtrEditor namespace CtrEditor
{ {
@ -105,6 +106,7 @@ namespace CtrEditor
public ICommand TBMultiPageExtractTagsCommand { get; } public ICommand TBMultiPageExtractTagsCommand { get; }
public ICommand TBMultiPageAnalizeCommand { get; } public ICommand TBMultiPageAnalizeCommand { get; }
public ICommand TBMultiPageMatrixCommand { get; } public ICommand TBMultiPageMatrixCommand { get; }
public ICommand TBLibraryManagerCommand { get; }
public ICommand TBTogglePLCConnectionCommand => new RelayCommand(() => public ICommand TBTogglePLCConnectionCommand => new RelayCommand(() =>
@ -127,7 +129,7 @@ namespace CtrEditor
private ObjectManipulationManager _objectManager; // Add this line private ObjectManipulationManager _objectManager; // Add this line
public MainWindow MainWindow public MainWindow MainWindow
{ {
get => mainWindow; get => mainWindow;
set set
{ {
@ -250,10 +252,10 @@ namespace CtrEditor
{ {
if (HasUnsavedChanges && !inhibitSaveChangesControl) if (HasUnsavedChanges && !inhibitSaveChangesControl)
{ {
var result = MessageBox.Show("There are unsaved changes. Do you want to save them?", var result = MessageBox.Show("There are unsaved changes. Do you want to save them?",
"Save Changes", "Save Changes",
MessageBoxButton.YesNo); MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes) if (result == MessageBoxResult.Yes)
{ {
SaveStateObjetosSimulables(); SaveStateObjetosSimulables();
@ -283,7 +285,7 @@ namespace CtrEditor
habilitarEliminarUserControl = _objectManager.SelectedObjects.Count > 0; habilitarEliminarUserControl = _objectManager.SelectedObjects.Count > 0;
} }
[ObservableProperty] [ObservableProperty]
private TipoSimulable selectedItem; private TipoSimulable selectedItem;
@ -317,6 +319,7 @@ namespace CtrEditor
private void UpdateVisFilterTypes() private void UpdateVisFilterTypes()
{ {
MainWindow?.VisFilter?.UpdateAvailableTypes(ObjetosSimulables.Select(o => o.GetType()).Distinct()); MainWindow?.VisFilter?.UpdateAvailableTypes(ObjetosSimulables.Select(o => o.GetType()).Distinct());
MainWindow?.VisFilter?.UpdateAvailableTags(ObjetosSimulables);
} }
// //
@ -331,9 +334,9 @@ namespace CtrEditor
datosDeTrabajo = new DatosDeTrabajo(); datosDeTrabajo = new DatosDeTrabajo();
// Initialize ObjetosSimulables first // Initialize ObjetosSimulables first
ObjetosSimulables = new ObservableCollection<osBase>(); ObjetosSimulables = new ObservableCollection<osBase>();
ObjetosSimulables = new ObservableCollection<osBase>(); ObjetosSimulables = new ObservableCollection<osBase>();
ListaOsBase = new ObservableCollection<TipoSimulable>(); ListaOsBase = new ObservableCollection<TipoSimulable>();
@ -342,7 +345,7 @@ namespace CtrEditor
_timerPLCUpdate = new DispatcherTimer(); _timerPLCUpdate = new DispatcherTimer();
_timerPLCUpdate.Interval = TimeSpan.FromMilliseconds(10); // Restaurado a 10ms _timerPLCUpdate.Interval = TimeSpan.FromMilliseconds(10); // Restaurado a 10ms
_timerPLCUpdate.Tick += OnRefreshEvent; _timerPLCUpdate.Tick += OnRefreshEvent;
InitializeTipoSimulableList(); InitializeTipoSimulableList();
@ -363,7 +366,7 @@ namespace CtrEditor
TBStartSimulationCommand = new RelayCommand(StartSimulation, () => !IsSimulationRunning); TBStartSimulationCommand = new RelayCommand(StartSimulation, () => !IsSimulationRunning);
TBStopSimulationCommand = new RelayCommand(StopSimulation, () => IsSimulationRunning); TBStopSimulationCommand = new RelayCommand(StopSimulation, () => IsSimulationRunning);
// Inicializar simulación de fluidos // Inicializar simulación de fluidos
FluidSimulation = new SimulationFluidsViewModel(this); FluidSimulation = new SimulationFluidsViewModel(this);
TBStartFluidSimulationCommand = new RelayCommand(StartFluidSimulation, () => !FluidSimulation.IsFluidSimulationRunning); TBStartFluidSimulationCommand = new RelayCommand(StartFluidSimulation, () => !FluidSimulation.IsFluidSimulationRunning);
@ -379,6 +382,7 @@ namespace CtrEditor
TBAssingPagesCommand = new RelayCommand(AssingPagesCommand); TBAssingPagesCommand = new RelayCommand(AssingPagesCommand);
TBMultiPageAnalizeCommand = new RelayCommand(MultiPageAnalizeCommand); TBMultiPageAnalizeCommand = new RelayCommand(MultiPageAnalizeCommand);
TBMultiPageMatrixCommand = new RelayCommand(MultiPageMatrixCommand); TBMultiPageMatrixCommand = new RelayCommand(MultiPageMatrixCommand);
TBLibraryManagerCommand = new RelayCommand(ShowLibraryManager);
stopwatch_Sim = new Stopwatch(); stopwatch_Sim = new Stopwatch();
stopwatch_Sim.Start(); stopwatch_Sim.Start();
@ -412,16 +416,46 @@ namespace CtrEditor
isVisible = false; 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 // Check other filters
if (filter.ShowCloned && !obj.Cloned) if (filter.ShowCloned && !obj.Cloned)
isVisible = false; isVisible = false;
if (filter.ShowAutoCreated && !obj.AutoCreated) if (filter.ShowAutoCreated && !obj.AutoCreated)
isVisible = false; isVisible = false;
if (filter.ShowEnableOnAllPages && !obj.Enable_On_All_Pages) if (filter.ShowEnableOnAllPages && !obj.Enable_On_All_Pages)
isVisible = false; isVisible = false;
if (filter.ShowOnThisPage && !obj.Show_On_This_Page) if (filter.ShowOnThisPage && !obj.Show_On_This_Page)
isVisible = false; isVisible = false;
} }
@ -441,13 +475,13 @@ namespace CtrEditor
if (SelectedImage != null) if (SelectedImage != null)
{ {
_stateSerializer.LoadState(SelectedImage); _stateSerializer.LoadState(SelectedImage);
// Aplicar los filtros actuales a los objetos recién cargados // Aplicar los filtros actuales a los objetos recién cargados
if (MainWindow?.VisFilter?.FilterViewModel != null) if (MainWindow?.VisFilter?.FilterViewModel != null)
{ {
OnVisFilterChanged(MainWindow.VisFilter.FilterViewModel); OnVisFilterChanged(MainWindow.VisFilter.FilterViewModel);
} }
// Limpiar historial de undo al cargar un estado desde archivo // Limpiar historial de undo al cargar un estado desde archivo
MainWindow?.ClearUndoHistory(); MainWindow?.ClearUndoHistory();
} }
@ -458,7 +492,7 @@ namespace CtrEditor
{ {
// Suponiendo que "SelectedImage" es una propiedad que al establecerse dispara "ImageSelected" // Suponiendo que "SelectedImage" es una propiedad que al establecerse dispara "ImageSelected"
directorioTrabajo = EstadoPersistente.Instance.directorio; directorioTrabajo = EstadoPersistente.Instance.directorio;
// Limpiar historial de undo al cargar datos iniciales // Limpiar historial de undo al cargar datos iniciales
MainWindow?.ClearUndoHistory(); MainWindow?.ClearUndoHistory();
} }
@ -538,17 +572,17 @@ namespace CtrEditor
{ {
// Create a copy of the selected objects to avoid issues during iteration // Create a copy of the selected objects to avoid issues during iteration
var objectsToDuplicate = _objectManager.SelectedObjects.ToList(); var objectsToDuplicate = _objectManager.SelectedObjects.ToList();
// Clear current selection before duplicating // Clear current selection before duplicating
_objectManager.ClearSelection(); _objectManager.ClearSelection();
// Track all newly created objects // Track all newly created objects
var newObjects = new List<osBase>(); var newObjects = new List<osBase>();
// Duplicate each object with a small offset // Duplicate each object with a small offset
float offsetX = 0.5f; float offsetX = 0.5f;
float offsetY = 0.5f; float offsetY = 0.5f;
foreach (var objToDuplicate in objectsToDuplicate) foreach (var objToDuplicate in objectsToDuplicate)
{ {
var newObj = DuplicarObjeto(objToDuplicate, offsetX, offsetY); var newObj = DuplicarObjeto(objToDuplicate, offsetX, offsetY);
@ -557,10 +591,10 @@ namespace CtrEditor
newObjects.Add(newObj); newObjects.Add(newObj);
} }
} }
// Force a complete layout update to ensure all controls are positioned // Force a complete layout update to ensure all controls are positioned
MainWindow.ImagenEnTrabajoCanvas.UpdateLayout(); MainWindow.ImagenEnTrabajoCanvas.UpdateLayout();
// Use a dispatcher to delay the selection until the UI has had time to fully render // Use a dispatcher to delay the selection until the UI has had time to fully render
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() =>
{ {
@ -571,7 +605,7 @@ namespace CtrEditor
{ {
double left = Canvas.GetLeft(newObj.VisualRepresentation); double left = Canvas.GetLeft(newObj.VisualRepresentation);
double top = Canvas.GetTop(newObj.VisualRepresentation); double top = Canvas.GetTop(newObj.VisualRepresentation);
// Only add to selection if the object has valid coordinates // Only add to selection if the object has valid coordinates
if (!double.IsNaN(left) && !double.IsNaN(top) && !double.IsInfinity(left) && !double.IsInfinity(top)) if (!double.IsNaN(left) && !double.IsNaN(top) && !double.IsInfinity(left) && !double.IsInfinity(top))
{ {
@ -579,17 +613,17 @@ namespace CtrEditor
} }
} }
} }
// Force another layout update before updating selection visuals // Force another layout update before updating selection visuals
MainWindow.ImagenEnTrabajoCanvas.UpdateLayout(); MainWindow.ImagenEnTrabajoCanvas.UpdateLayout();
// Update SelectedItemOsList if there are newly created objects // Update SelectedItemOsList if there are newly created objects
if (newObjects.Count > 0) if (newObjects.Count > 0)
{ {
// Set to the last duplicated object so it's visible in the property panel // Set to the last duplicated object so it's visible in the property panel
SelectedItemOsList = newObjects.LastOrDefault(); SelectedItemOsList = newObjects.LastOrDefault();
} }
// Now update selection visuals // Now update selection visuals
_objectManager.UpdateSelectionVisuals(); _objectManager.UpdateSelectionVisuals();
})); }));
@ -665,7 +699,7 @@ namespace CtrEditor
.Where(o => o.Show_On_This_Page && o.AutoCreated) .Where(o => o.Show_On_This_Page && o.AutoCreated)
.ToList(); .ToList();
foreach (var obj in osAutoCreated_List) foreach (var obj in osAutoCreated_List)
RemoverObjetoSimulable(obj); RemoverObjetoSimulable(obj);
} }
private void EliminarClonedCommand() private void EliminarClonedCommand()
@ -674,7 +708,7 @@ namespace CtrEditor
.Where(o => o.Show_On_This_Page && o.Cloned) .Where(o => o.Show_On_This_Page && o.Cloned)
.ToList(); .ToList();
foreach (var obj in osCloned_List) foreach (var obj in osCloned_List)
RemoverObjetoSimulable(obj); RemoverObjetoSimulable(obj);
} }
private void AssingPagesCommand() private void AssingPagesCommand()
@ -765,10 +799,10 @@ namespace CtrEditor
private void StartSimulation() private void StartSimulation()
{ {
// Detener simulación de fluidos si está ejecutándose // Detener simulación de fluidos si está ejecutándose
StopFluidSimulation(); StopFluidSimulation();
IsSimulationRunning = true; IsSimulationRunning = true;
// Ocultar rectángulos de selección antes de iniciar la simulación // Ocultar rectángulos de selección antes de iniciar la simulación
_objectManager.UpdateSelectionVisuals(); _objectManager.UpdateSelectionVisuals();
@ -795,36 +829,36 @@ namespace CtrEditor
{ {
simulationManager.Debug_ClearSimulationShapes(); simulationManager.Debug_ClearSimulationShapes();
Debug_SimulacionCreado = false; Debug_SimulacionCreado = false;
} }
_timerSimulacion.Stop(); _timerSimulacion.Stop();
// Restaurar los rectángulos de selección si hay objetos seleccionados // Restaurar los rectángulos de selección si hay objetos seleccionados
_objectManager.UpdateSelectionVisuals(); _objectManager.UpdateSelectionVisuals();
// Limpiar historial de undo al detener la simulación
MainWindow?.ClearUndoHistory();
}
/// <summary> // Limpiar historial de undo al detener la simulación
/// Inicia la simulación de fluidos independiente MainWindow?.ClearUndoHistory();
/// </summary> }
public void StartFluidSimulation()
{
// Detener simulación física si está ejecutándose
StopSimulation();
FluidSimulation.StartFluidSimulation();
CommandManager.InvalidateRequerySuggested();
}
/// <summary> /// <summary>
/// Detiene la simulación de fluidos independiente /// Inicia la simulación de fluidos independiente
/// </summary> /// </summary>
public void StopFluidSimulation() public void StartFluidSimulation()
{ {
FluidSimulation.StopFluidSimulation(); // Detener simulación física si está ejecutándose
CommandManager.InvalidateRequerySuggested(); StopSimulation();
}
FluidSimulation.StartFluidSimulation();
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) private void OnTickSimulacion(object sender, EventArgs e)
{ {
@ -833,7 +867,7 @@ namespace CtrEditor
// Detener el cronómetro y obtener el tiempo transcurrido en milisegundos // Detener el cronómetro y obtener el tiempo transcurrido en milisegundos
var elapsedMilliseconds = stopwatch_Sim.Elapsed.TotalMilliseconds - stopwatch_SimModel_last; var elapsedMilliseconds = stopwatch_Sim.Elapsed.TotalMilliseconds - stopwatch_SimModel_last;
stopwatch_SimModel_last = stopwatch_Sim.Elapsed.TotalMilliseconds; stopwatch_SimModel_last = stopwatch_Sim.Elapsed.TotalMilliseconds;
// Acumular tiempo para el promedio // Acumular tiempo para el promedio
accumulatedSimTime += elapsedMilliseconds; accumulatedSimTime += elapsedMilliseconds;
simSampleCount++; simSampleCount++;
@ -920,7 +954,7 @@ namespace CtrEditor
} }
stopwatch.Stop(); // Stop measuring time stopwatch.Stop(); // Stop measuring time
// Debug.WriteLine($"OnRefreshEvent: {stopwatch.Elapsed.TotalMilliseconds} ms"); // Debug.WriteLine($"OnRefreshEvent: {stopwatch.Elapsed.TotalMilliseconds} ms");
} }
private void OpenWorkDirectory() private void OpenWorkDirectory()
@ -956,16 +990,16 @@ namespace CtrEditor
{ {
// Remove the path if it already exists // Remove the path if it already exists
RecentDirectories.Remove(path); RecentDirectories.Remove(path);
// Add the new path at the beginning // Add the new path at the beginning
RecentDirectories.Insert(0, path); RecentDirectories.Insert(0, path);
// Keep only the last 10 entries // Keep only the last 10 entries
while (RecentDirectories.Count > 10) while (RecentDirectories.Count > 10)
{ {
RecentDirectories.RemoveAt(RecentDirectories.Count - 1); RecentDirectories.RemoveAt(RecentDirectories.Count - 1);
} }
UpdateRecentDirectories(); UpdateRecentDirectories();
} }
@ -1103,9 +1137,9 @@ namespace CtrEditor
var objectsList = selectedObjects.ToList(); var objectsList = selectedObjects.ToList();
// Crear una clave única basada en los IDs de los objetos seleccionados // Crear una clave única basada en los IDs de los objetos seleccionados
string key = string.Join("_", objectsList.Select(o => o.Id.Value).OrderBy(id => id)); string key = string.Join("_", objectsList.Select(o => o.Id.Value).OrderBy(id => id));
// Verificar si ya existe una ventana para esta selección // Verificar si ya existe una ventana para esta selección
if (_propertyEditorWindows.TryGetValue(key, out var existingWindow) && if (_propertyEditorWindows.TryGetValue(key, out var existingWindow) &&
existingWindow.IsVisible) existingWindow.IsVisible)
{ {
existingWindow.Activate(); existingWindow.Activate();
@ -1116,7 +1150,7 @@ namespace CtrEditor
var window = new Windows.MultiPropertyEditorWindow(objectsList, MainWindow); var window = new Windows.MultiPropertyEditorWindow(objectsList, MainWindow);
window.Closed += (s, e) => _propertyEditorWindows.Remove(key); window.Closed += (s, e) => _propertyEditorWindows.Remove(key);
_propertyEditorWindows[key] = window; _propertyEditorWindows[key] = window;
window.Show(); window.Show();
HasUnsavedChanges = true; HasUnsavedChanges = true;
_objectManager.UpdateSelectionVisuals(); _objectManager.UpdateSelectionVisuals();
@ -1127,6 +1161,37 @@ namespace CtrEditor
{ {
OnPropertyChanged(nameof(SelectedItemOsList)); 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 public class SimulationData

View File

@ -251,6 +251,12 @@
<TextBlock Text="Assing Pages" /> <TextBlock Text="Assing Pages" />
</StackPanel> </StackPanel>
</Button> </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> </ToolBar>
</ToolBarTray> </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

@ -106,8 +106,8 @@ namespace CtrEditor.ObjetosSim
LeftChanging(oldValue, newValue); LeftChanging(oldValue, newValue);
} }
public virtual void LeftChanged(float value) public virtual void LeftChanged(float value)
{ {
// Actualizar posición relativa si el movimiento no viene del FramePlate // Actualizar posición relativa si el movimiento no viene del FramePlate
UpdateFramePlateRelativePosition(); UpdateFramePlateRelativePosition();
} }
@ -128,8 +128,8 @@ namespace CtrEditor.ObjetosSim
TopChanging(oldValue, newValue); TopChanging(oldValue, newValue);
} }
public virtual void TopChanged(float value) public virtual void TopChanged(float value)
{ {
// Actualizar posición relativa si el movimiento no viene del FramePlate // Actualizar posición relativa si el movimiento no viene del FramePlate
UpdateFramePlateRelativePosition(); UpdateFramePlateRelativePosition();
} }
@ -176,6 +176,52 @@ namespace CtrEditor.ObjetosSim
public virtual void AnguloChanged(float value) { } public virtual void AnguloChanged(float value) { }
public virtual void AnguloChanging(float oldValue, float newValue) { } 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) public void Resize(double width, double height)
{ {
@ -334,7 +380,7 @@ namespace CtrEditor.ObjetosSim
{ {
FramePlate.PropertyChanged += OnFramePlatePropertyChanged; FramePlate.PropertyChanged += OnFramePlatePropertyChanged;
UpdateZIndex(FramePlate.Zindex_FramePlate); UpdateZIndex(FramePlate.Zindex_FramePlate);
// Calcular punto de pivot y posición relativa inicial // Calcular punto de pivot y posición relativa inicial
var pivotPoint = FramePlate.GetPivotPoint(); var pivotPoint = FramePlate.GetPivotPoint();
FramePlate_PivotX = pivotPoint.X; FramePlate_PivotX = pivotPoint.X;
@ -373,7 +419,7 @@ namespace CtrEditor.ObjetosSim
if (e.PropertyName == nameof(osFramePlate.Nombre)) if (e.PropertyName == nameof(osFramePlate.Nombre))
Group_Panel = ((osFramePlate)sender).Nombre; Group_Panel = ((osFramePlate)sender).Nombre;
if (e.PropertyName == nameof(osFramePlate.Top) || if (e.PropertyName == nameof(osFramePlate.Top) ||
e.PropertyName == nameof(osFramePlate.Left)) e.PropertyName == nameof(osFramePlate.Left))
{ {
UpdateOrbitalPosition(); UpdateOrbitalPosition();
@ -387,7 +433,7 @@ namespace CtrEditor.ObjetosSim
UpdateOrbitalPosition(); UpdateOrbitalPosition();
} }
if (e.PropertyName == nameof(osFramePlate.PivotCenterX) || if (e.PropertyName == nameof(osFramePlate.PivotCenterX) ||
e.PropertyName == nameof(osFramePlate.PivotCenterY)) e.PropertyName == nameof(osFramePlate.PivotCenterY))
{ {
// Cuando cambia el pivot, recalcular posición relativa // Cuando cambia el pivot, recalcular posición relativa
@ -407,32 +453,32 @@ namespace CtrEditor.ObjetosSim
// Actualizar punto de pivot actual (puede haber cambiado por los checkboxes) // Actualizar punto de pivot actual (puede haber cambiado por los checkboxes)
var currentPivot = FramePlate.GetPivotPoint(); var currentPivot = FramePlate.GetPivotPoint();
// Calcular el ángulo de rotación total desde la posición inicial // Calcular el ángulo de rotación total desde la posición inicial
float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle; float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle;
// Convertir ángulo a radianes // Convertir ángulo a radianes
float angleRad = deltaAngle * (float)Math.PI / 180.0f; float angleRad = deltaAngle * (float)Math.PI / 180.0f;
// Calcular la nueva posición orbital usando rotación de matriz // Calcular la nueva posición orbital usando rotación de matriz
float cosAngle = (float)Math.Cos(angleRad); float cosAngle = (float)Math.Cos(angleRad);
float sinAngle = (float)Math.Sin(angleRad); float sinAngle = (float)Math.Sin(angleRad);
// Rotar la posición relativa // Rotar la posición relativa
float rotatedX = cosAngle * FramePlate_RelativeX - sinAngle * FramePlate_RelativeY; float rotatedX = cosAngle * FramePlate_RelativeX - sinAngle * FramePlate_RelativeY;
float rotatedY = sinAngle * FramePlate_RelativeX + cosAngle * FramePlate_RelativeY; float rotatedY = sinAngle * FramePlate_RelativeX + cosAngle * FramePlate_RelativeY;
// Calcular nueva posición absoluta usando el punto de pivot actual // Calcular nueva posición absoluta usando el punto de pivot actual
float newLeft = currentPivot.X + rotatedX; float newLeft = currentPivot.X + rotatedX;
float newTop = currentPivot.Y + rotatedY; float newTop = currentPivot.Y + rotatedY;
// Actualizar directamente los campos sin disparar eventos de cambio // Actualizar directamente los campos sin disparar eventos de cambio
SetProperty(ref left, newLeft); SetProperty(ref left, newLeft);
SetProperty(ref top, newTop); SetProperty(ref top, newTop);
// Forzar actualización de la posición visual // Forzar actualización de la posición visual
ActualizarLeftTop(); ActualizarLeftTop();
// Llamar a OnMoveResizeRotate para actualizar la física // Llamar a OnMoveResizeRotate para actualizar la física
OnMoveResizeRotate(); OnMoveResizeRotate();
} }
@ -444,20 +490,20 @@ namespace CtrEditor.ObjetosSim
{ {
// Obtener punto de pivot actual // Obtener punto de pivot actual
var currentPivot = FramePlate.GetPivotPoint(); var currentPivot = FramePlate.GetPivotPoint();
// Recalcular posición relativa considerando la rotación actual del FramePlate // Recalcular posición relativa considerando la rotación actual del FramePlate
float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle; float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle;
float angleRad = deltaAngle * (float)Math.PI / 180.0f; float angleRad = deltaAngle * (float)Math.PI / 180.0f;
// Calcular la posición relativa actual respecto al pivot del FramePlate // Calcular la posición relativa actual respecto al pivot del FramePlate
float currentRelativeX = Left - currentPivot.X; float currentRelativeX = Left - currentPivot.X;
float currentRelativeY = Top - currentPivot.Y; float currentRelativeY = Top - currentPivot.Y;
// Si el FramePlate está rotado, necesitamos "desrotar" la posición para obtener // Si el FramePlate está rotado, necesitamos "desrotar" la posición para obtener
// la posición relativa en el sistema de coordenadas original // la posición relativa en el sistema de coordenadas original
float cosAngle = (float)Math.Cos(-angleRad); float cosAngle = (float)Math.Cos(-angleRad);
float sinAngle = (float)Math.Sin(-angleRad); float sinAngle = (float)Math.Sin(-angleRad);
FramePlate_RelativeX = cosAngle * currentRelativeX - sinAngle * currentRelativeY; FramePlate_RelativeX = cosAngle * currentRelativeX - sinAngle * currentRelativeY;
FramePlate_RelativeY = sinAngle * currentRelativeX + cosAngle * currentRelativeY; FramePlate_RelativeY = sinAngle * currentRelativeX + cosAngle * currentRelativeY;
} }
@ -470,23 +516,23 @@ namespace CtrEditor.ObjetosSim
// Obtener posición absoluta actual del objeto (antes del cambio de pivot) // Obtener posición absoluta actual del objeto (antes del cambio de pivot)
float currentLeft = Left; float currentLeft = Left;
float currentTop = Top; float currentTop = Top;
// Obtener nuevo punto de pivot // Obtener nuevo punto de pivot
var newPivot = FramePlate.GetPivotPoint(); var newPivot = FramePlate.GetPivotPoint();
// Calcular nueva posición relativa respecto al nuevo pivot // Calcular nueva posición relativa respecto al nuevo pivot
// Considerar la rotación actual para obtener posición relativa en sistema original // Considerar la rotación actual para obtener posición relativa en sistema original
float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle; float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle;
float angleRad = deltaAngle * (float)Math.PI / 180.0f; float angleRad = deltaAngle * (float)Math.PI / 180.0f;
// Posición relativa actual respecto al nuevo pivot // Posición relativa actual respecto al nuevo pivot
float currentRelativeX = currentLeft - newPivot.X; float currentRelativeX = currentLeft - newPivot.X;
float currentRelativeY = currentTop - newPivot.Y; float currentRelativeY = currentTop - newPivot.Y;
// "Desrotar" para obtener coordenadas en el sistema original // "Desrotar" para obtener coordenadas en el sistema original
float cosAngle = (float)Math.Cos(-angleRad); float cosAngle = (float)Math.Cos(-angleRad);
float sinAngle = (float)Math.Sin(-angleRad); float sinAngle = (float)Math.Sin(-angleRad);
FramePlate_RelativeX = cosAngle * currentRelativeX - sinAngle * currentRelativeY; FramePlate_RelativeX = cosAngle * currentRelativeX - sinAngle * currentRelativeY;
FramePlate_RelativeY = sinAngle * currentRelativeX + cosAngle * currentRelativeY; FramePlate_RelativeY = sinAngle * currentRelativeX + cosAngle * currentRelativeY;
} }
@ -666,7 +712,8 @@ namespace CtrEditor.ObjetosSim
// Filtrar texto por lista de caracteres permitidos // Filtrar texto por lista de caracteres permitidos
var filteredBlocks = result.TextBlocks var filteredBlocks = result.TextBlocks
.Where(block => block.Score > 0.5) // Solo bloques con confianza > 50% .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()), Text = new string(block.Text.Where(c => allowedChars.Contains(c)).ToArray()),
Score = block.Score 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 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 internal class EstadoPersistente
{ {
// Ruta donde se guardará el estado // Ruta donde se guardará el estado
@ -42,6 +54,10 @@ namespace CtrEditor
public List<ColumnPairMapping> ColumnPairs { get; set; } = new List<ColumnPairMapping>(); 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 // Propiedad pública con get y set para controlar el acceso a _strDirectorioTrabajo
public string directorio public string directorio
{ {
@ -57,21 +73,24 @@ namespace CtrEditor
public int newid public int newid
{ {
get { get
{
_id++; _id++;
return _id; } return _id;
}
set { _id = value; } set { _id = value; }
} }
// Constructor privado para evitar la instanciación externa // Constructor privado para evitar la instanciación externa
public EstadoPersistente() public EstadoPersistente()
{ {
} }
private EstadoPersistente Inizializar() private EstadoPersistente Inizializar()
{ {
_strDirectorioTrabajo = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); _strDirectorioTrabajo = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
RecentDirectories = new List<string>(); RecentDirectories = new List<string>();
LibraryWindow = new LibraryWindowSettings();
return this; return this;
} }
@ -110,10 +129,15 @@ namespace CtrEditor
if (File.Exists(_filePath)) if (File.Exists(_filePath))
{ {
string json = File.ReadAllText(_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 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 return new EstadoPersistente().Inizializar(); // Devuelve una nueva instancia si no existe el archivo
} }