agrobot_base/AgroBase/OperationControl/Mapas/Controller/MapaOperacaoController.Edic...

1093 lines
27 KiB
C#

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<string> percursoAnterior =
_workspace
.Selecao
.RuasOrdenadas
.ToList();
List<string> 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;
}
}
}