Mini editor de texto com JTextPane. (aplicando, salvando e imprimindo texto com estilos)

Olá pessoal.
A alguns dias um cliente fez uma solicitação que me obrigou a suar a camisa fazendo a minha primeira implementação em swing.
O pessoal aqui “como sempre” ajudou e eu não sei se é por gratidão ou em função do espírito de fim de ano (Natal, Ano novo,…) gostaria de compartilhar o resultado do trabalho. :lol:

Agradecimento especial ao MarceloS e Mikhas.

Valew!!!


import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.text.AttributeSet;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.text.StyledEditorKit;

/**
 * <p>Title: CampoTextPane</p>
 * <p>Description: 
 *  Componente que apresenta uma caixa de texto juntamente com botões para aplicação de estilos no texto digitado.
 * 	São três (3) botões que disponibilizam o estilo negrito, itálico e sublinhado.
 * 	Para que os estilos aplicados estejam disponíveis para a posteridade é necessário que além do texto,
 * todo os estilos aplicados sejam persistidos. Sendo assim, ao utilizar esse componente é necessário ter em
 * mente que o campo que armazenará o conteúdo texto deverá suportar também a string que representa os estilos
 * aplicados.
 *  Sugiro fortemente a utilização de campos de armazenamento de tamanhos consideráveis como "LONGTEXT" do MySQL.
 * 	Esse componente também disponibiliza a obtenção do conteúdo informado no campo texto juntamente com todo
 * o estilo aplicado para fins de impressão em relatórios desenvolvidos em Jasper.</p>
 * @author Hudson de Paula Romualdo
 */
public class CampoTextPane extends JPanel{

	private JTextPane txtObservacoes = null;
	
	private StyledDocument doc;
	
	private Icon negritoIcone;
	private Icon italicoIcone;
	private Icon sublinhadoIcone;
	
	private String pdfFonteNome = "Helvetica";
	
	private final String NEGRITO    = "b";
	private final String ITALICO    = "i";
	private final String SUBLINHADO = "u";
	
	public final char CARACTER_SEPARADOR = '§'; //Caracter que separa o texto do vetor de estilos
	
	/**
	 * Construtor padrão
	 */
	public CampoTextPane() {
		super();
		this.exec();		
	}
	
	/**
	 * Construtor sobrescrito para inserção de ícones nos botões
	 * @param negritoIcone
	 * @param italicoIcone
	 * @param sublinhadoIcone
	 */
	public CampoTextPane(Icon negritoIcone, Icon italicoIcone, Icon sublinhadoIcone){
		super();
		
		this.negritoIcone = negritoIcone;
		this.italicoIcone = italicoIcone;
		this.sublinhadoIcone = sublinhadoIcone;
		
		this.exec();
	}
	
	/**
	 * Método que constroi o componente
	 */
	private void exec(){
		setLayout(new BorderLayout()); 

		final JPanel pnlPrincipal = new JPanel();
		pnlPrincipal.setLayout(new BorderLayout());
		add(pnlPrincipal, BorderLayout.CENTER);

		final JScrollPane scrollObservacoes = new JScrollPane();
		pnlPrincipal.add(scrollObservacoes, BorderLayout.CENTER);

		this.txtObservacoes = new JTextPane();
		this.txtObservacoes.addKeyListener(new KeyAdapter() {
			public void keyTyped(final KeyEvent e) {				
				
				//Impede que o caracter especial que separa o texto do vetor de estilos seja inserido
				if(e.getKeyChar() == CARACTER_SEPARADOR)
					e.setKeyChar('\u0000');			
			}
		});
		
		scrollObservacoes.setViewportView(this.txtObservacoes);
       
		final JPanel pnlControles = new JPanel();
		pnlControles.setPreferredSize(new Dimension(20, 0));
		pnlControles.setMinimumSize(new Dimension(0, 0));
		pnlControles.setLayout(null);
		add(pnlControles, BorderLayout.EAST);

        Action action = new StyledEditorKit.BoldAction();
        action.putValue(Action.NAME, "");

		final JButton btnNegrito = new JButton(action);
		btnNegrito.setToolTipText("Negrito");
		btnNegrito.setBounds(0, 0, 20, 20);
		
		if(this.negritoIcone != null)
			btnNegrito.setIcon(this.negritoIcone);
		
		pnlControles.add(btnNegrito);

        action = new StyledEditorKit.ItalicAction();
        action.putValue(Action.NAME, "");
        
		final JButton btnItalico = new JButton(action);
		btnItalico.setToolTipText("Itálico");
		btnItalico.setBounds(0, 20, 20, 20);
		
		if(this.italicoIcone != null)
			btnItalico.setIcon(this.italicoIcone);
		
		pnlControles.add(btnItalico);

        action = new StyledEditorKit.UnderlineAction();
        action.putValue(Action.NAME, "");
        
		final JButton btnSublinhado = new JButton(action);
		btnSublinhado.setToolTipText("Sublinhado");
		btnSublinhado.setBounds(0, 40, 20, 20);
		
		if(this.sublinhadoIcone != null)
			btnSublinhado.setIcon(this.sublinhadoIcone);
		
		pnlControles.add(btnSublinhado);
        
		this.doc = this.txtObservacoes.getStyledDocument();

		Style style = doc.addStyle("default", null);
		
		style = doc.addStyle(this.NEGRITO, null);
        StyleConstants.setBold(style, true);
        style = doc.addStyle(this.ITALICO, null);
        StyleConstants.setItalic(style, true);
        style = doc.addStyle(this.SUBLINHADO, null);
        StyleConstants.setUnderline(style, true);       
	}
	
	/**
	 * Método que retorna o conteúdo do campo texto sem o vetor de estilos (caso exista).
	 * @return String
	 */
	public String getTexto(){	
		return this.txtObservacoes.getText();
	}
	
	/**
	 * Método que retorna o conteúdo do campo texto com o vetor de estilos (caso exista).
	 *  Esse método deve ser utilizado sempre que o conteúdo do campo for persistido, caso contrário,
	 * todo os estilos aplicados serão perdidos.
	 * @return String
	 */
	public String getTextoPersistencia(){
		StringBuffer strTexto = new StringBuffer();

		//Armazenando texto
		strTexto.append(this.txtObservacoes.getText());
		
		//Caso nenhum conteúdo tenha sido adicionado
		if( strTexto.length() == 0 )
			return "";

		//Removendo caracter especial que separa o texto do vetor de estilos (caso tenha sido informado)
		while (true){			
			int indice = strTexto.indexOf(new Character(this.CARACTER_SEPARADOR).toString());		
			
			if( indice != -1)
				strTexto.deleteCharAt(indice);
			else
				break;
		}

		//Obtendo a lista de estilos aplicados no texto (caso exista);
		List listaEstilo = this.mapearVetorEstilo();
		
		if( listaEstilo.isEmpty() )
			return strTexto.toString();
			
		//Preparando String para receber as informações de estilo
		strTexto.append(new Character(this.CARACTER_SEPARADOR).toString());
		
		//Transformando lista de estilos em uma String
		for (int i = 0; i < listaEstilo.size(); i++) 
			strTexto.append(listaEstilo.get(i)).append(";");
		
		//Removendo o último ";" marcador de índice
		strTexto.replace(strTexto.lastIndexOf(";"), strTexto.toString().length() , "");
		
		return strTexto.toString();
	}

	/**
	 * Método que retorna o texto para ser impresso em relatório Jasper
	 * Para que os estilos aplicados sejam apresentados é necessário que a opção (Texto possui estilo)
	 * esteja marcada no campo que receberá o texto. 
	 * @return String
	 * @throws Exception 
	 */
	public String getTextoPDF() throws Exception{
		TextoPDF textoPDF = new TextoPDF(this);
		
		return textoPDF.getTextoPDF();
	}	
	
	/**
	 * Método que atribui conteúdo ao campo texto
	 * @param texto
	 */
	public void setTexto(String texto){
		
		//Caso o texto passado por parâmetro esteja nulo o processamento é interrompido
		if(texto == null)
			return;
		
		int indice = texto.indexOf(new Character(this.CARACTER_SEPARADOR).toString());

		//Caso um texto sem estilo seja informado
		if ( indice == -1 ){
			this.txtObservacoes.setText(texto);
			return;
		}

		//Atribuindo conteúdo ao campo texto
		this.txtObservacoes.setText(texto.substring(0,indice));

		//Separando o vetor de estilos do texto
		texto = texto.substring(indice + 1, texto.length());

		//Caso o vetor de estilos esteja vazio o processamento é interrompido
		if( texto.indexOf(";") < 0 )
			return;

		//Obtendo o vetor de estilos
		String[] vetorEstilo = texto.substring(0, texto.length()).split(";");				

		//Aplicando estilos
		for(int i = 0; i < vetorEstilo.length; i++ ){

 			int indice2 = this.getIndiceTexto(vetorEstilo[i]);			
			
			this.txtObservacoes.select(indice2, indice2);

			String estilo = this.getEstiloTexto(vetorEstilo[i]);
			
			this.aplicarEstilo(estilo);
		}

	}
	
	/**
	 * Método que atribui o nome da fonte utilizada nos arquivo PDF.
	 * @param pdfFonteNome the pdfFonteNome to set
	 */
	public final void setPdfFonteNome(String pdfFonteNome) {
		this.pdfFonteNome = pdfFonteNome;
	}
	
	/**
	 * Método que obtém o índice do caracter no qual deve ser aplicado o estilo.
	 * @param conteudo
	 * @return int
	 */
	private int getIndiceTexto(String conteudo){
		StringBuffer indice = new StringBuffer();
		
		for(int i = 0; i < conteudo.length(); i++){
			String caracter = conteudo.substring(i, i + 1);
			
			try{
				new Integer(caracter);
				indice.append(caracter);				
			}catch(Exception e){
				//Em caso de excepção, quer dizer que o resto do conteúdo é Estilo
				break;
			}
		}		
		return Integer.parseInt(indice.toString());
	}
	
	/**
	 * Método que obtém o estilo que deve ser aplicado para o caracter selecionado.
	 * @param estilo
	 * @return String
	 */
	private String getEstiloTexto(String conteudo){
		StringBuffer retorno = new StringBuffer();
		
		for(int i = 0; i < conteudo.length(); i++){
			String caracter = conteudo.substring(i, i + 1);
			
			try{
				new Integer(caracter);								
			}catch(Exception e){
				//Em caso de excepção, quer dizer que o estilo do conteúdo está sendo processado.
				retorno.append(caracter);
			}
		}		
		return retorno.toString();
	}
	
	/**
	 * Método que varre o conteúdo o conteúdo informado no campo texto armazenando qualquer estilo
	 * que por ventura tenha sido aplicado.
	 * @param indice      - posição onde o cursor se encontra no texto
	 * @param atributos   - atributos do caracter onde se encontra o cursor
	 * @param listaEstilo - lista que armazena os estilo processados
	 */
	private List mapearVetorEstilo(){
		
		List listaEstilo = new ArrayList();
		
		//Obtendo o estilo de cada um dos caracteres do texto
		for(int i = 0; i < this.txtObservacoes.getText().length(); i++){
			this.txtObservacoes.setCaretPosition(i);
			
			AttributeSet atributos = this.txtObservacoes.getCharacterAttributes();
			
			int contador = 0;
			
			StringBuffer conteudo = new StringBuffer(new Integer(i).toString());
			
	        Enumeration e = atributos.getAttributeNames();

	        while(e.hasMoreElements()) {            
	        	Object key = e.nextElement();
	        	Object attr = atributos.getAttribute(key);
	        	
	        	if( attr == null) continue;        	
	        	
	        	if( new Boolean(attr.toString()).booleanValue() )        		
	        		conteudo.append(key.toString().substring(0,1));
	        	
	        	contador++;
	        }

	        if( contador > 0 )
	        	listaEstilo.add(conteudo);
		}

		return listaEstilo;
	}
	
	/**
	 * Método que aplica estilo ao texto selecionado
	 * @param estilo
	 * @param sobrepor
	 */
	private void aplicarEstilo(String estilo){
		
		int inicioSelecao = this.txtObservacoes.getSelectionStart();
		int finalSelecao  = this.txtObservacoes.getSelectionEnd() - inicioSelecao;		
		
		//Para os casos onde o texto está sendo selecionado caracter por caracter
		if( finalSelecao < 1 ) finalSelecao = 1;
		
		//Caso mais de um estilo tenha sido aplicado a um determinado caracter
		for(int i = 0; i < estilo.length(); i++)		
			this.doc.setCharacterAttributes(inicioSelecao ,  finalSelecao,
					this.txtObservacoes.getStyle(estilo.substring(i, i + 1)), false);
	}
	
	/**
	 * Classe interna que encapsula os método que tratam o texto para impressão em PDF.
	 */
	private class TextoPDF{
		
		private final CampoTextPane campoTextPane;	
		
		private List lstTextoPDF = new LinkedList();
		
		/**
		 * Construtor
		 * @param campoTextPane
		 */
		public TextoPDF(CampoTextPane campoTextPane){
			this.campoTextPane = campoTextPane;
		}
		
		/**
		 * Método que retorna o texto para ser impresso em relatório Jasper
		 * @return String
		 * @throws Exception 
		 */
		public String getTextoPDF() throws Exception{
						
			//Obtendo o conteúdo
			String conteudo = this.campoTextPane.getTexto();

			//Atribuindo o texto ao vetor de conteúdo
			for(int i = 0; i < conteudo.length(); i++)
				this.lstTextoPDF.add(new Character(conteudo.charAt(i)).toString());
						
			//Obtendo o vetor de estilos
			List vetorEstilo = this.campoTextPane.mapearVetorEstilo();								
			
			//Aplicando estilos
			for(int i = 0; i < vetorEstilo.size(); i++ ){

				int indice = this.campoTextPane.getIndiceTexto(vetorEstilo.get(i).toString());			
				
				String caracter = this.lstTextoPDF.get(indice).toString();
				
				String estilo = this.campoTextPane.getEstiloTexto(vetorEstilo.get(i).toString());			
				
				this.lstTextoPDF.set(indice, this.aplicarEstiloPDF(estilo, caracter));
			}
			
			//Transformando vetor em texto
			StringBuffer textoPDF = new StringBuffer(); 
			for (Iterator iterator = this.lstTextoPDF.iterator(); iterator.hasNext();) 
				textoPDF.append((String) iterator.next());			
			
			return textoPDF.toString();		
		}
		
		/**
		 * Método que aplica estilo PDF ao texto.
		 * @param estilo
		 * @param caracter
		 * @return String
		 */
		private String aplicarEstiloPDF(String estilo, String caracter)throws Exception{
			StringBuffer conteudoPDF = new StringBuffer("<style ");
			
			try{			
				//Caso mais de um estilo tenha sido aplicado a um determinado caracter
				for(int i = 0; i < estilo.length(); i++)
					if( estilo.substring(i, i + 1).equals(this.campoTextPane.NEGRITO) )
						conteudoPDF.append(" isBold=\"true\" ");
					else if( estilo.substring(i, i + 1).equals(this.campoTextPane.ITALICO) )
						conteudoPDF.append(" isItalic=\"true\" ");
					else
						conteudoPDF.append(" isUnderline=\"true\" ");
	
				//Fonte PDF
				StringBuffer pdfFontName = new StringBuffer(this.campoTextPane.pdfFonteNome);
				
				if( estilo.indexOf(this.campoTextPane.ITALICO) > -1 && estilo.indexOf(this.campoTextPane.NEGRITO) > -1 ) 
					pdfFontName.append("-BoldOblique");
				else if( estilo.indexOf(this.campoTextPane.NEGRITO) > -1 ) 
					pdfFontName.append("-Bold");
				else if( estilo.indexOf(this.campoTextPane.ITALICO) > -1 ) 
					pdfFontName.append("-Oblique");			
				
				conteudoPDF.append(" pdfFontName=\"").append(pdfFontName).append("\" >");
				
				conteudoPDF.append(caracter).append("</style>");
			}catch(Exception e){
				throw new Exception("Falha ao aplicar o estilo PDF no texto");
			}
			return conteudoPDF.toString();
		}		
	}
}

Importante: obviamente correções e melhorias (as vezes agente inventa de “coçar a orelha com o cotovelo”) são bem vindas. :lol:

Ficou legal :slight_smile:

Usando a mesma lógica (StyleConstants.set…) também daria pra definir a cor do texto né? Vou guardar o código aqui pra olhar depois pq semestre que vem preciso fazer um compilador… :?

Sim, sim.

Trabalhando caracter por caracter (menor desempenho, maior flexibilidade) dá pra colocar cor e tudo mais. :slight_smile:

[]'s.

Opa. Cara, sou muito fraco em Java. Como faço pra rodar essa sua aplicação? Já vi um monte de aplicação, só que quando vou rodar diz que não tem um método principal. Como faço esse método principal?

Olá…segue abaixo.

import java.awt.Rectangle;
import javax.swing.JFrame;

public class Principal {

	public static void main(String[] args) {
		
		//Container
		JFrame frame = new JFrame("Campo de texto com Estilos");
		
		//Componente de texto com estilos
		CampoTextPane observacoes = new CampoTextPane();
		
		//Adicionando o componente ao container
		frame.add(observacoes);

		//Estabelecendo o tamanho do container e sua posição inicial na tela
		frame.setBounds(new Rectangle(300,300,500,300));
		
		//Tornando o container visível
		frame.setVisible(true);
	}
}

É só estudar um pouco e tirar as dúvidas aqui…o pessoal ajuda muito.

[]'s

Hudson, obrigado.
Um dia eu chego lá.
Obrigado pela ajuda.