using OperationControl.Controls; using OperationControl.Mapas.Editing; using OperationControl.Mapas.Interaction; using OperationControl.Mapas.Models; namespace OperationControl.Mapas { public sealed partial class MapaOperacaoController { public bool PodeDesfazerEdicao => _estadoEdicao.EmEdicao && _edicaoService .PodeDesfazer( _estadoEdicao ); public bool PodeRefazerEdicao => _estadoEdicao.EmEdicao && _edicaoService .PodeRefazer( _estadoEdicao ); private bool ValidarDocumentoAntesDoApply(MapaDocumento documento) { if (documento == null) { throw new ArgumentNullException( nameof(documento) ); } MapaResultadoValidacao resultado = _validationService .Validar( documento ); /* * Avisos não bloqueiam o Apply. * * Mas ficam registrados para diagnóstico. */ if ( resultado.QuantidadeAvisos > 0 ) { Log( "[MAPA][EDICAO][VALIDACAO] " + $"Avisos={resultado.QuantidadeAvisos}" ); foreach ( string aviso in resultado.Avisos) { Log( "[MAPA][EDICAO][VALIDACAO][AVISO] " + aviso ); } } /* * Sem erros: * documento aprovado. */ if (resultado.Valido) { Log( "[MAPA][EDICAO][VALIDACAO] " + $"OK | " + $"Ruas={documento.QuantidadeRuas} | " + $"Avisos={resultado.QuantidadeAvisos}" ); return true; } /* * ERRO: * * absolutamente nada oficial é alterado. */ Log( "[MAPA][EDICAO][VALIDACAO][FALHA] " + $"Erros={resultado.QuantidadeErros} | " + $"Avisos={resultado.QuantidadeAvisos}" ); foreach ( string erro in resultado.Erros) { Log( "[MAPA][EDICAO][VALIDACAO][ERRO] " + erro ); } ValidacaoEdicaoFalhou? .Invoke( resultado ); return false; } /* * ========================================================= * SESSÃO DE EDIÇÃO * ========================================================= */ public void IniciarEdicao(MapViewControl? mapaControl) { if ( mapaControl?.Mapa?.Map == null ) { throw new ArgumentNullException( nameof(mapaControl) ); } if (_workspace.Documento == null) { throw new InvalidOperationException( "Não existe mapa carregado para edição." ); } /* * Se ainda não há sessão, cria. */ if (!_estadoEdicao.EmEdicao) { _edicaoService .Iniciar( _estadoEdicao, _workspace.Documento ); /* * Toda nova sessão começa no modo * mais abrangente e previsível. */ _estadoRecorte .Limpar(); } RegistrarMapa( mapaControl ); /* * Guarda o contexto somente na primeira entrada * desta View na sessão de edição. */ if ( !_mapasEmEdicao.Contains( mapaControl ) ) { _modoInteracaoAntesEdicao[ mapaControl ] = mapaControl.ModoInteracao; } _mapasEmEdicao.Add( mapaControl ); DefinirModoInteracao( mapaControl, MapaModoInteracao.Edicao ); _ferramentaEdicaoAtual = MapaFerramentaEdicao.Selecionar; RenderizarEdicaoNoMapa( mapaControl ); SincronizarCockpitEdicao(); EstadoEdicaoAlterado? .Invoke(); Log( "[MAPA][EDICAO] " + $"Modo edição iniciado | " + $"Ruas=" + $"{_estadoEdicao.DocumentoTrabalho?.QuantidadeRuas ?? 0}" ); } public bool AplicarEdicao() { if ( !_estadoEdicao.EmEdicao || _estadoEdicao.DocumentoTrabalho == null ) { return false; } /* * O botão ficará desabilitado quando não houver * alterações, mas mantemos a proteção também aqui. */ if (!EdicaoModificada) { Log( "[MAPA][EDICAO][APLICAR] " + "Nenhuma alteração pendente." ); return false; } if (_workspace.Documento == null) { throw new InvalidOperationException( "Não existe documento oficial para receber a edição." ); } /* * ===================================================== * 1. CONSTRUÇÃO DO NOVO ESTADO FORA DO WORKSPACE * ===================================================== * * ObterResultado já devolve uma cópia independente. * * Nada oficial é alterado até toda a preparação * abaixo terminar com sucesso. */ MapaDocumento novoDocumento = _edicaoService .ObterResultado( _estadoEdicao ); /* * O resultado agora representa uma alteração * posterior à fonte originalmente carregada. */ novoDocumento.Modificado = true; /* * ===================================================== * 2. VALIDAR DOCUMENTO EDITADO * ===================================================== * * Esta é a última fronteira antes do mapa * poder se tornar oficial. * * Importante: * * novoDocumento é uma cópia independente. * * Portanto, se a validação falhar: * * - Workspace continua intacto; * - DocumentoFonte continua intacto; * - DocumentoTrabalho continua disponível; * - Undo/Redo continuam disponíveis; * - sessão de edição continua aberta. */ if (!ValidarDocumentoAntesDoApply(novoDocumento)) { return false; } /* * ===================================================== * 3. RECONCILIAR RUAS DA MISSÃO * ===================================================== */ var idsExistentes = novoDocumento .Ruas .Where( x => x != null && !string.IsNullOrWhiteSpace( x.Id ) ) .Select( x => x.Id ) .ToHashSet( StringComparer.OrdinalIgnoreCase ); List percursoAnterior = _workspace .Selecao .RuasOrdenadas .ToList(); List percursoReconciliado = percursoAnterior .Where( id => idsExistentes.Contains( id ) ) .ToList(); int ruasPercursoRemovidas = percursoAnterior.Count - percursoReconciliado.Count; var novaSelecao = new MapaSelecaoOperacao(); /* * DefinirSelecao valida tudo antes de alterar * novaSelecao. */ _selectionService .DefinirSelecao( novoDocumento, novaSelecao, percursoReconciliado ); /* * ===================================================== * 3. CLONAR ROTA DE RETORNO * ===================================================== * * Poderíamos reutilizar a mesma referência porque * Apply não altera a rota, mas é melhor manter * a transação completamente independente. */ var novaRotaRetorno = new MapaRotaRetorno(); var contratoRota = _rotaRetornoService .ExportarContrato( _workspace.RotaRetorno ) .ToList(); _rotaRetornoService .DefinirPeloContrato( novaRotaRetorno, contratoRota ); /* * Guardamos as Views antes de desmontar * a sessão. */ var mapasEdicao = _mapasEmEdicao .ToArray(); int ruasAntes = _workspace.Documento.QuantidadeRuas; int ruasDepois = novoDocumento.QuantidadeRuas; /* * ===================================================== * 4. COMMIT * ===================================================== * * Importante: * * DefinirEstado NÃO altera DocumentoFonte. */ _workspace.DefinirEstado( novoDocumento, novaSelecao, novaRotaRetorno ); /* * ===================================================== * 5. ENCERRAR A SESSÃO DE EDIÇÃO * ===================================================== */ _edicaoService .Cancelar( _estadoEdicao ); _estadoRecorte.Limpar(); foreach ( var mapa in mapasEdicao) { mapa.DefinirEstadoEditor( ativo: false, ferramenta: MapaFerramentaEdicao.Nenhuma, quantidadeSelecionadas: 0, modificado: false, podeDesfazer: false, podeRefazer: false ); /* * Remove a representação do DocumentoTrabalho. */ RemoverRenderEdicao( mapa ); MapaModoInteracao modoRestaurar = _modoInteracaoAntesEdicao .TryGetValue( mapa, out var anterior ) ? anterior : MapaModoInteracao.Visualizacao; _mapasEmEdicao.Remove( mapa ); _modoInteracaoAntesEdicao.Remove( mapa ); DefinirModoInteracao( mapa, modoRestaurar ); } _ferramentaEdicaoAtual = MapaFerramentaEdicao.Nenhuma; _mapasEmEdicao.Clear(); _modoInteracaoAntesEdicao.Clear(); /* * ===================================================== * 6. RECONSTRUIR TODAS AS VIEWS DO MAPA * ===================================================== * * Não apenas a View que estava editando. * * Pode haver outro MapViewControl registrado mostrando * o documento oficial antigo. */ InvalidarRendersWorkspace(); foreach ( var mapa in _mapasRegistrados .ToArray()) { PrepararMapa( mapa, mapa.ModoInteracao, enquadrarDocumento: false ); } /* * Estado oficial mudou E sessão de edição acabou. */ EstadoEdicaoAlterado? .Invoke(); EstadoMapaAlterado? .Invoke(); Log( "[MAPA][EDICAO][APLICAR] " + $"Ruas={ruasAntes}->{ruasDepois} | " + $"Percurso={percursoAnterior.Count}" + $"->{percursoReconciliado.Count} | " + $"RemovidasDoPercurso={ruasPercursoRemovidas} | " + $"Fonte={QuantidadeRuasFonte}" ); return true; } public void CancelarEdicao(MapViewControl? mapaControl = null) { if (!_estadoEdicao.EmEdicao) return; /* * A sessão de edição é global ao Controller. * * Mesmo que o chamador informe uma View específica, * cancelar a sessão precisa retirar TODAS as Views que * estejam mostrando o DocumentoTrabalho. */ var mapasEdicao = _mapasEmEdicao .ToArray(); _edicaoService .Cancelar( _estadoEdicao ); _estadoRecorte.Limpar(); foreach ( var mapa in mapasEdicao) { mapa.DefinirEstadoEditor( ativo: false, ferramenta: MapaFerramentaEdicao.Nenhuma, quantidadeSelecionadas: 0, modificado: false, podeDesfazer: false, podeRefazer: false ); RemoverRenderEdicao( mapa ); MapaModoInteracao modoRestaurar = _modoInteracaoAntesEdicao .TryGetValue( mapa, out var anterior ) ? anterior : MapaModoInteracao.Visualizacao; _mapasEmEdicao.Remove(mapa); _modoInteracaoAntesEdicao.Remove(mapa); /* * Volta imediatamente para a * representação OFICIAL. */ PrepararMapa( mapa, modoRestaurar, enquadrarDocumento: false ); } _ferramentaEdicaoAtual = MapaFerramentaEdicao.Nenhuma; _mapasEmEdicao.Clear(); _modoInteracaoAntesEdicao.Clear(); EstadoEdicaoAlterado? .Invoke(); Log( "[MAPA][EDICAO] " + "Sessão cancelada." ); } /* * ========================================================= * SELEÇÃO DE EDIÇÃO * ========================================================= */ public bool AlternarRuaEdicao(string ruaId) { bool selecionada = _edicaoService .AlternarRua( _estadoEdicao, ruaId ); foreach ( var mapa in _mapasEmEdicao .ToArray()) { if ( _rendersMapa .TryGetValue( mapa, out var render ) ) { _edicaoRenderService .AtualizarEstiloRua( render, _estadoEdicao, ruaId ); mapa.Mapa.Refresh(); } } SincronizarCockpitEdicao(); EstadoEdicaoAlterado? .Invoke(); Log( "[MAPA][EDICAO][SELECAO] " + $"Rua={ruaId} | " + $"Selecionada={selecionada} | " + $"Total={QuantidadeRuasSelecionadasEdicao}" ); return selecionada; } public void SelecionarRuaEdicao(string ruaId) { _edicaoService .SelecionarRua( _estadoEdicao, ruaId ); } public void RemoverRuaSelecaoEdicao(string ruaId) { _edicaoService .RemoverRuaSelecao( _estadoEdicao, ruaId ); } public void LimparSelecaoEdicao() { if (!_estadoEdicao.EmEdicao) return; _edicaoService .LimparSelecao( _estadoEdicao ); foreach ( var mapa in _mapasEmEdicao .ToArray()) { if ( _rendersMapa .TryGetValue( mapa, out var render ) ) { _edicaoRenderService .AplicarSelecao( render, _estadoEdicao ); mapa.Mapa.Refresh(); } } SincronizarCockpitEdicao(); EstadoEdicaoAlterado? .Invoke(); Log( "[MAPA][EDICAO][SELECAO] " + "Seleção limpa." ); } public void DefinirFerramentaEdicao(MapaFerramentaEdicao ferramenta) { if (!_estadoEdicao.EmEdicao) return; if (_ferramentaEdicaoAtual == ferramenta) { return; } _ferramentaEdicaoAtual = ferramenta; if (ferramenta == MapaFerramentaEdicao.RecorteBordaGnss) { _estadoRecorte.AtivarBordaGnssManual(); } AtualizarVisualBordaGnss(); SincronizarCockpitEdicao(); Log( "[MAPA][EDICAO][FERRAMENTA] " + $"Atual={ferramenta}" ); } private void SincronizarCockpitEdicao() { foreach ( var mapa in _mapasEmEdicao .ToArray()) { mapa.DefinirEstadoEditor( ativo: _estadoEdicao.EmEdicao, ferramenta: _ferramentaEdicaoAtual, quantidadeSelecionadas: QuantidadeRuasSelecionadasEdicao, modificado: EdicaoModificada, podeDesfazer: PodeDesfazerEdicao, podeRefazer: PodeRefazerEdicao, escopoRecorte: _estadoRecorte.Escopo, bordaGnss: CriarResumoBordaGnss(), possuiMapaFonte: _workspace.PossuiDocumentoFonte ); } } /* * ========================================================= * OPERAÇÕES DE EDIÇÃO * ========================================================= */ public int RemoverRuasSelecionadasEdicao() { if (!_estadoEdicao.EmEdicao) return 0; if (!_estadoEdicao.PossuiSelecao) return 0; int selecionadasAntes = _estadoEdicao .QuantidadeSelecionadas; int removidas = _edicaoService .RemoverRuasSelecionadas( _estadoEdicao ); if (removidas <= 0) return 0; /* * Aqui houve alteração estrutural: * * ruas deixaram de existir. * * Portanto atualizar somente estilo * não é suficiente. Reconstruímos * o render do DocumentoTrabalho. */ RenderizarDocumentoEdicaoAtual(); /* * Atualiza: * * seleção = 0 * modificado = true * Undo = habilitado * Redo = conforme histórico */ SincronizarCockpitEdicao(); EstadoEdicaoAlterado? .Invoke(); Log( "[MAPA][EDICAO][REMOVER] " + $"Selecionadas={selecionadasAntes} | " + $"Removidas={removidas} | " + $"Restantes=" + $"{_estadoEdicao.DocumentoTrabalho?.QuantidadeRuas ?? 0} | " + $"Undo={PodeDesfazerEdicao} | " + $"Redo={PodeRefazerEdicao}" ); return removidas; } public bool RestaurarMapaFonteEdicao() { if ( !_estadoEdicao.EmEdicao || _estadoEdicao.DocumentoTrabalho == null ) { return false; } if ( !_workspace.PossuiDocumentoFonte || _workspace.DocumentoFonte == null ) { Log( "[MAPA][EDICAO][RESTAURAR-FONTE] " + "Documento-fonte indisponível." ); return false; } int ruasAntes = _estadoEdicao .DocumentoTrabalho .QuantidadeRuas; bool alterado = _edicaoService .RestaurarDocumentoFonte( _estadoEdicao, _workspace.DocumentoFonte ); if (!alterado) { Log( "[MAPA][EDICAO][RESTAURAR-FONTE] " + "Documento de trabalho já corresponde " + "ao mapa original." ); return false; } /* * Qualquer gesto ou levantamento temporário * deixa de fazer sentido diante de uma * restauração completa. */ _estadoRecorte .Limpar(); _ferramentaEdicaoAtual = MapaFerramentaEdicao .Selecionar; /* * Se você já está usando o preview GNSS * que construímos: */ AtualizarVisualBordaGnss(); /* * Houve mudança estrutural completa. * * Não basta atualizar estilo. */ RenderizarDocumentoEdicaoAtual(); SincronizarCockpitEdicao(); EstadoEdicaoAlterado? .Invoke(); Log( "[MAPA][EDICAO][RESTAURAR-FONTE] " + $"Ruas={ruasAntes}" + $"->{_estadoEdicao.DocumentoTrabalho.QuantidadeRuas} | " + $"Fonte={_workspace.DocumentoFonte.QuantidadeRuas} | " + $"Undo={PodeDesfazerEdicao}" ); return true; } public MapaDocumento ObterResultadoEdicao() { return _edicaoService .ObterResultado( _estadoEdicao ); } public bool DesfazerEdicao() { if (!_estadoEdicao.EmEdicao) return false; bool alterado = _edicaoService .Desfazer( _estadoEdicao ); if (!alterado) return false; RenderizarDocumentoEdicaoAtual(); SincronizarCockpitEdicao(); EstadoEdicaoAlterado? .Invoke(); Log( "[MAPA][EDICAO][UNDO] " + $"Ruas=" + $"{_estadoEdicao.DocumentoTrabalho?.QuantidadeRuas ?? 0} | " + $"Undo={_estadoEdicao.QuantidadeHistoricoDesfazer} | " + $"Redo={_estadoEdicao.QuantidadeHistoricoRefazer}" ); return true; } public bool RefazerEdicao() { if (!_estadoEdicao.EmEdicao) return false; bool alterado = _edicaoService .Refazer( _estadoEdicao ); if (!alterado) return false; RenderizarDocumentoEdicaoAtual(); SincronizarCockpitEdicao(); EstadoEdicaoAlterado? .Invoke(); Log( "[MAPA][EDICAO][REDO] " + $"Ruas=" + $"{_estadoEdicao.DocumentoTrabalho?.QuantidadeRuas ?? 0} | " + $"Undo={_estadoEdicao.QuantidadeHistoricoDesfazer} | " + $"Redo={_estadoEdicao.QuantidadeHistoricoRefazer}" ); return true; } } }