[RESOLVIDO] Editar apenas parte do texto de um JComboBox editável

Boa tarde a todos.

Preciso de ajuda com um problema que não estou conseguindo chegar a uma solução, talvez não estou usando a lógica correta, segue:

Tenho um combo box editável, e nele são carregadas descrições específicas de um plano de contas contábil, como por exemplo:

  • Pago nota fiscal nr. * - *
  • Pago passagem a * conforme recibo nr. *
  • Pago aluguel a * ref. mês * c/r *

Porém, o usuário só poderá inserir/digitar informações nas posições onde estão os asteriscos, o restante do texto deve ser fixo, isto é, não pode ser apagada ou editada por outras informações (daí a ideia de usar o combo, pois o usuário apenas deverá entrar com as informações pré definidas).

Os asteriscos também não devem aparecer no combo, então antes de carregar as informações nele, eu faço um replaceAll e guardo a informação original (com os asteriscos) em uma variável quando o evento ItemStateChanged é disparado.

Atualmente estou adicionando eventos ao componente editor do combo, não sei se estou no caminho certo, porém ainda não funciona 100%:


       String complemento = "";
       String complementoAnterior = "";

        jComboBox.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
               
               //Aqui busco a descrição com os asteriscos no banco de dados e guardo na variável "complemento".

              // Ex.: complemento = "Pago nota fiscal nr. * - *";
                               
            }
                        
        });

        ((JTextField)jComboBox.getEditor().getEditorComponent()).addKeyListener(new java.awt.event.KeyAdapter() {
            
            // No evento keyPressed capturo a informação do combo e guardo na variável "complementoAnterior" antes do usuário digitar algo.

            public void keyPressed(java.awt.event.KeyEvent evt) { 
                
                // Guardo o texto na variável.
                complementoAnterior = ((JTextField)jComboBox.getEditor().getEditorComponent()).getText();

                 // Divido o complemento original em partes.
                String partes [] = complemento.split("\\*");
               
                for(String parte : partes){
                    
                    parte = parte.replaceAll("\\s+$", ""); // Remove os espaços da direita.
                    parte = parte.replaceAll("^\\s+", ""); // Remove os espaços da esquerda.
                    
                    // Verifico se o texto do combo não contém as partes do complemento original. Se não contiver, a variável recebe o valor do complemento original.
                    if(!((JTextField)jComboBox.getEditor().getEditorComponent()).getText().contains(parte)){                        
                        complementoAnterior = complemento.replaceAll("\\*", "");                        
                    }
                    
                }
                
            }

           // No evento keyReleased, caso não encontre as partes originais do complemento após o usuário inserir alguma informação, insiro no campo o texto capturado no evento anterior.
            
            public void keyReleased(java.awt.event.KeyEvent evt) {
                
                // Divido o complemento original em partes.
                String partes [] = complemento.split("\\*");
               
                for(String parte : partes){
                    
                    parte = parte.replaceAll("\\s+$", ""); // Remove os espaços da direita.
                    parte = parte.replaceAll("^\\s+", ""); // Remove os espaços da esquerda.
                    
                    // Verifico se o texto do combo não contém as partes do complemento original.
                    if(!((JTextField)jComboBox.getEditor().getEditorComponent()).getText().contains(parte)){

                        // Pego a posição do cursor no campo.
                        int posicao = ((JTextField)jComboBox.getEditor().getEditorComponent()).getCaretPosition();
                        
                        // Insere o texto anterior no campo, já que o usuário digitou a informação na posição errada.
                        ((JTextField)jComboBox.getEditor().getEditorComponent()).setText(complementoAnterior);
                        
                        // Após alterar o texto, a posição do cursor vai para o fim, então preciso voltar para a posição anterior.
                        if(complementoAnterior.length() > posicao){
                            ((JTextField)jComboBox.getEditor().getEditorComponent()).setCaretPosition(posicao);
                        }else{
                            ((JTextField)jComboBox.getEditor().getEditorComponent()).setCaretPosition(complementoAnterior.length());
                        }
                        
                    }
                    
                }
                
            }
            
        });

Da forma que está no código acima, os caracteres inseridos nas posições erradas são logo removidos, porém não gostaria que funcionasse dessa forma.

Outra tentativa que fiz, foi a seguinte:


       String complemento = "";

        jComboBox.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
               
               //Aqui busco a descrição com os asteriscos no banco de dados e guardo na variável "complemento".

              // Ex.: complemento = "Pago nota fiscal nr. * - *";
                               
            }
                        
        });

       public void keyReleased(java.awt.event.KeyEvent evt) { 
                
                // Divido o complemento original em partes.
                String partes [] = complemento.split("\\*");
               
                // Guardo a posição do cursor no campo.
                int posicao = ((JTextField)jComboBox.getEditor().getEditorComponent()).getCaretPosition();
                
                // Essa variável receberá as partes do complemento, conforme elas forem sendo percorridas no loop.
                String parte_aterior = "";
                
                // Guardo o texto do combo na variável "texto".
                String texto = ((JTextField)jComboBox.getEditor().getEditorComponent()).getText();
                
                for(String parte : partes){

                    /* Se a posição do cursor for maior/igual que o tamanho da primeira parte ou maior/igual que o tamanho da parte anterior, 
                    e se a posição for menor/igual que o início da parte do complemento no texto ou se a posição for maior/igual ao tamanho da parte,
                    o campo se torna editável, caso contrário o campo não é editável. */
                    if((posicao >= texto.indexOf(parte)+parte.length() || posicao >= texto.indexOf(parte_aterior)+parte_aterior.length()) && (posicao <= texto.indexOf(parte) || posicao >= texto.indexOf(parte)+parte.length())){
                        ((JTextField)jComboBox.getEditor().getEditorComponent()).setEditable(true);
                        //evt.consume();
                    }else{                    
                        ((JTextField)jComboBox.getEditor().getEditorComponent()).setEditable(false);
                        //evt.consume();
                    }
                                    
                    parte_aterior += parte;
                                                            
                }
                

Da forma que está no código acima, deveria funcionar exatamente como preciso, quando o cursor está na posição errada o campo não aceita caracteres porque não fica editável, quando está na posição correta ele fica editável, mas consigo apagar as partes do texto que deveriam ser fixas.

No sistema contábil atual do cliente, em Delphi, quando o cursor está na posição correta, o usuário consegue inserir/digitar informações, e quando não está, o texto não é editável, apenas é selecionável.

Portanto, gostaria de fazer funcionar da mesma forma em Java.

Essa é minha primeira postagem aqui no fórum, então me desculpem caso eu tenha me expressado mal.

Se alguém puder me ajudar, seja com dicas, exemplos e/ou sugestões, fico agradecido.

Obrigado.

Consegui resolver depois de dois dias, segue a solução:


    // Primeiramente usei essa classe para setá-la como Caret do componente editor, para que o suário não possa
    // selecionar o texto do combo box e substituir por outros caracteres, ou apagar o texto:
    public class NoTextSelectionCaret extends DefaultCaret {
        
        public NoTextSelectionCaret(JTextComponent textComponent) {
            setBlinkRate(textComponent.getCaret().getBlinkRate());
            textComponent.setHighlighter(null);
        }

        @Override
        public int getMark() {
            return getDot();
        }
    
    }

        String complemento = "";

        // Aqui seto o Caret da classe criada como Caret do componente editor.
        ((JTextField)jComboBox.getEditor().getEditorComponent()).setCaret(new NoTextSelectionCaret(((JTextField)jComboBox.getEditor().getEditorComponent())));

        // Aqui coloco o backgroud na cor branca para que o usuário não perceba quando o campo não está editável.
        ((JTextField)jComboBox.getEditor().getEditorComponent()).setBackground(new Color(255, 255, 255));
        
        ((JTextField)jComboBox.getEditor().getEditorComponent()).addFocusListener(new java.awt.event.FocusAdapter() {

            // Ao ganhar o foco no campo, busco pelo complemento original, com os asteriscos.
            public void focusGained(java.awt.event.FocusEvent evt) {
                
                // Aqui busco o complemento com os asteriscos no banco de dados e guardo na variável "complemento".
                // Ex.: complemento = "Pago nota fiscal nr. * - *";

                // Se o campo não está editável, seta o Caret como visível para que o suário não perceba que o campo não está editável.
                if(!((JTextField)jComboBox.getEditor().getEditorComponent()).isEditable()){
                    ((JTextField)jComboBox.getEditor().getEditorComponent()).getCaret().setVisible(true);
                }
                
            }
                        
        });
                
        jComboBox.addItemListener(new java.awt.event.ItemListener() {
            
            // Ao selecionar outro item no combo, também preciso buscar pelo complemento com os asteriscos no banco de dados.
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
               
                // Aqui busco o complemento com os asteriscos no banco de dados e guardo na variável "complemento" novamente.
                // Ex.: complemento = "Pago nota fiscal nr. * - *";
                
            }
                        
        });
                
        ((JTextField)jComboBox.getEditor().getEditorComponent()).addKeyListener(new java.awt.event.KeyAdapter() {
                        
            public void keyPressed(java.awt.event.KeyEvent evt) { 

                // Se as teclas pressionadas não são: seta para direita, seta para esquerda, home e end.
                if(evt.getKeyCode() != KeyEvent.VK_LEFT && evt.getKeyCode() != KeyEvent.VK_RIGHT && evt.getKeyCode() != KeyEvent.VK_HOME && evt.getKeyCode() != KeyEvent.VK_END){

                  // Divido o complemento original em partes.
                  String partes [] = complemento.split("\\*");

                  // Pego a posição do cursor no campo.
                  int posicao = ((JTextField)jComboBox.getEditor().getEditorComponent()).getCaretPosition();

                  // Essa variável receberá as partes do complemento, conforme elas forem sendo percorridas no loop.
                  String parte_aterior = "";

                  // Guardo o texto do combo na variável "texto".
                  String texto = ((JTextField)jComboBox.getEditor().getEditorComponent()).getText();

                  for(String parte : partes){

                        // Aqui verifico se a posição do Caret está de acordo com as posições editáveis do campo.
                        if(posicao > 0 && (posicao <= texto.indexOf(parte) || posicao >= (texto.indexOf(parte)+parte.length())) && (posicao <= texto.indexOf(parte_aterior) || posicao >= (texto.indexOf(parte_aterior)+parte_aterior.length()))){

                              // O combo box passa a ser editável.
                              ((JTextField)jComboBox.getEditor().getEditorComponent()).setEditable(true);

                              // Se a posição do Caret estiver em frente a posição de uma parte do texto não editável, bloqueia o backspace e consome o evento.
                              if(posicao == (texto.indexOf(parte)+parte.length())){
                                   if(evt.getKeyCode() == KeyEvent.VK_BACK_SPACE){
                                       evt.consume();
                                   }
                              }

                              // Se a posição do Caret for superior ao tamanho da parte do texto não editável e inferior ao tamanho da parte do texto, bloqueia o delete e consome o evento.
                              if(posicao >= texto.indexOf(parte) && posicao < (texto.indexOf(parte)+parte.length())){
                                  if(evt.getKeyCode() == KeyEvent.VK_DELETE){
                                      evt.consume();
                                  }
                              }

                        }else{

                              // Caso contrário, o combo box passa a não ser mais editável e paro a execução do loop.
                              ((JTextField)jComboBox.getEditor().getEditorComponent()).setEditable(false);

                              break;

                        }

                        // Dentro do loop a variável abaixo recebe o valor da parte do texto atual, para que no próximo laço eu consiga recuperar as informações da parte anterior do texto.
                        parte_aterior = parte;

                  }

            }else{

                  // Se não forem as teclas de direita, esquerda, home e end pressionadas, o combo box passa a ser não editável também.
                  ((JTextField)jComboBox.getEditor().getEditorComponent()).setEditable(false);
                            
            }
            
        });

É isso, pode até ser meio que na gambiarra, mas está funcionando de acordo como eu queria, apenas o texto do combo que não é selecionável para que o usuário não consiga colar outra informação por cima e substituir ou apagar todo ou parte do texto do combo.