Compare commits
4 Commits
99248e9112
...
9b710fcb00
Author | SHA1 | Date |
---|---|---|
|
9b710fcb00 | |
|
c03f6970d8 | |
|
c353f6c6ea | |
|
67fa5eef3d |
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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
|
127
MainViewModel.cs
127
MainViewModel.cs
|
@ -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(() =>
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -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,6 +416,36 @@ 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;
|
||||||
|
@ -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
|
// Limpiar historial de undo al detener la simulación
|
||||||
MainWindow?.ClearUndoHistory();
|
MainWindow?.ClearUndoHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inicia la simulación de fluidos independiente
|
/// Inicia la simulación de fluidos independiente
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void StartFluidSimulation()
|
public void StartFluidSimulation()
|
||||||
{
|
{
|
||||||
// Detener simulación física si está ejecutándose
|
// Detener simulación física si está ejecutándose
|
||||||
StopSimulation();
|
StopSimulation();
|
||||||
|
|
||||||
FluidSimulation.StartFluidSimulation();
|
FluidSimulation.StartFluidSimulation();
|
||||||
CommandManager.InvalidateRequerySuggested();
|
CommandManager.InvalidateRequerySuggested();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Detiene la simulación de fluidos independiente
|
/// Detiene la simulación de fluidos independiente
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void StopFluidSimulation()
|
public void StopFluidSimulation()
|
||||||
{
|
{
|
||||||
FluidSimulation.StopFluidSimulation();
|
FluidSimulation.StopFluidSimulation();
|
||||||
CommandManager.InvalidateRequerySuggested();
|
CommandManager.InvalidateRequerySuggested();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTickSimulacion(object sender, EventArgs e)
|
private void OnTickSimulacion(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
|
@ -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>
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,9 +73,11 @@ namespace CtrEditor
|
||||||
|
|
||||||
public int newid
|
public int newid
|
||||||
{
|
{
|
||||||
get {
|
get
|
||||||
|
{
|
||||||
_id++;
|
_id++;
|
||||||
return _id; }
|
return _id;
|
||||||
|
}
|
||||||
set { _id = value; }
|
set { _id = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,6 +90,7 @@ namespace CtrEditor
|
||||||
{
|
{
|
||||||
_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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue