/* Copyright (C) 2007 Felipe A. Lessa e Luciano H. O. Santos * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ package tirateima.gui.variaveis; import java.awt.Color; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Stack; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JScrollPane; import tirateima.IEstado; import tirateima.controlador.TiraTeimaLanguageException; /** * * Mostra um conjunto de variáveis. * * @author Felipe Lessa * @author Luciano Santos * @author Andrew Biller * @author Vinícius */ @SuppressWarnings("serial") public class Mostrador extends JScrollPane implements IEstado { private static final Comparator<Color> comparadorColor = new Comparator<Color>() { public int compare(Color c1, Color c2) { return new Integer(c1.getRGB()).compareTo(c2.getRGB()); } }; private static final Comparator<Variavel> comparadorVariavel = new Comparator<Variavel>() { public int compare(Variavel v1, Variavel v2) { return v1.getName().compareToIgnoreCase(v2.getName()); } }; /** Componentes representando elementos do programa em execução no Tira-Teima */ private Variaveis vars = new Variaveis(); private Setas setas = new Setas(); private List<Texto> textos = new ArrayList<Texto>(); /** Componentes removidos guardados para fins de restauração (reversão de comando) */ private Stack<Variavel> variaveisRemovidas = new Stack<Variavel>(); private Stack<Seta> setasRemovidas = new Stack<Seta>(); /** Conjunto de painéis que compõem o mostrador */ private JPanel painelPrincipal; private Painel painelVars = null; private Function function = null; private JPanel tudo = new JPanel(); /** Parâmetros de detalhes de visualização do mostrador*/ private double prop = -100.0; public static enum zoom {AUMENTA, REINICIA, DIMINUI} public Enum<zoom> acaoZoom = null; private Point ultimoPonto; /** * Cria um novo mostrador vazio. */ public Mostrador() { super(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); painelPrincipal = new JPanel(); painelPrincipal.setLayout(new GridBagLayout()); this.viewport.setView(painelPrincipal); } /** * A proporção atual de todas as janelas. * @return a proporção (1.0 = 100%). */ public double getProporcao() { return prop; } /** * Define a proporção a ser usada em todas as janelas. * @param nova_prop */ public void setProporcao(double nova_prop) { if (prop == nova_prop) return; //esconde durante o zoom: evita flicker na tela viewport.setVisible(false); if (painelVars != null) painelVars.setProporcao(nova_prop); prop = nova_prop; this.validate(); if (function != null) { function.setProporcao(nova_prop); } //mostra depois de desenhar o estado viewport.setVisible(true); } @Override public Insets getInsets() { // Força a proporção inicial if (prop <= 0) setProporcao(1.0); return super.getInsets(); } /** * Adiciona uma variável ao mostrador. * @param v variável a ser adicionada. */ public void adicionarVariavel(Variavel v) { if (function != null) { function.adicionarVariavel(v); } else { vars.adicionarVariavel(v); } } /** * Armazena uma variável removida para fins de restauração em caso de reversão de * comando * @param variavelRemovida */ public void armazenarVariavelRemovida(Variavel variavelRemovida) { if (function != null) { function.armazenarVariavelRemovida(variavelRemovida); } else { armazenarVariavel(variavelRemovida); } } /** * Restaura uma variável removida. * @param variavelRemovida */ public void restaurarVariavelRemovida() { if (function != null) { function.restaurarVariavelRemovida(); } else { //adiciona novamente uma variável excluída adicionarVariavel(restaurarVariavel()); } } /** * Restaura uma seta removida para fins de reversão de comando */ public void restaurarSetaRemovida() { Seta setaRemovida = restauraSeta(); if(setaRemovida != null){ if(!hasVariavel(setaRemovida.nome)){ throw new RuntimeException("Variavel " + setaRemovida.nome + " nao localizada."); } if (function != null) { function.adicionarSeta(setaRemovida.nome,setaRemovida); } else { setas.adicionarSeta(setaRemovida.nome, setaRemovida); } } } /** * Restaura uma seta removida. * @return */ private Seta restauraSeta(){ return setasRemovidas.pop(); } /** * Remove uma variável do mostrador. * @param nome nome da variável a ser removida. */ public Variavel removerVariavel(String nome) { return vars.removerVariavel(nome); } /** * Armazena uma variável em uma pilha de variáveis removidas * @param variavelRemovida */ private void armazenarVariavel(Variavel variavelRemovida) { this.variaveisRemovidas.push(variavelRemovida); } /** * Restaura uma variável removida de uma pilha * @return variavel removida */ private Variavel restaurarVariavel(){ return this.variaveisRemovidas.pop(); } /** * Modifica o valor de uma variável. * @param nome nome da variável a ser modificada. * @param valor seu novo valor. */ public boolean modificarVariavel(String nome, Object valor) { if (function != null) { if (function.modificarVariavel(nome, valor)) return true; } if (hasVariavel(nome)) { vars.modificarVariavel(nome, valor); return true; } return false; } /** * Cria uma seta em uma variável * @param nome * @param direcao * @param tamanho */ public void adicionarSeta(String nome, Seta s) { if(!hasVariavel(nome)){ throw new RuntimeException("Variavel " + nome + " nao localizada."); } if (function != null) { function.adicionarSeta(nome,s); } else { setas.adicionarSeta(nome, s); } } public void armazenarSetaRemovida(Seta setaRemovida){ this.setasRemovidas.push(setaRemovida); } /** * Retorna uma cópia de uma variável. * * @param nome * * @return */ public Variavel getCopiaVariavel(String nome) { return vars.getCopiaVariavel(nome); } public Object getEstado() { return new EstadoMostrador( new Painel(vars,setas,textos), function); } public void setEstado(Object estado) { //esconde durante o desenho: evita flicker na tela painelPrincipal.setVisible(false); painelPrincipal.removeAll(); GridBagConstraints gbc = new GridBagConstraints( 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.FIRST_LINE_START, new Insets(0, 0, 0, 0), 0, 0); if (estado != null) { EstadoMostrador e = (EstadoMostrador) estado; painelVars = e.painel; function = e.function; painelPrincipal.add(painelVars, gbc); painelVars.criar(); painelVars.setProporcao(prop < 0 ? 1.0 : prop); if (function != null) { function.setEstado(e.estadoFunction); gbc.gridy++; gbc.ipadx = 100; gbc.ipady = 100; gbc.fill = GridBagConstraints.HORIZONTAL; painelPrincipal.add(function, gbc); } Rectangle r1 = this.getBounds(); Rectangle r2 = viewport.getViewRect(); double x = r1.getCenterX() - r2.width/2, y = r1.getCenterY() - r2.height/2; Point p = new Point((int) Math.round(x), (int) Math.round(y)); viewport.setViewPosition(p); } validate(); repaint(); //mostra depois de ter desenhado o estado painelPrincipal.setVisible(Boolean.TRUE); } @Override public void processMouseEvent(MouseEvent e) { try { if (e.getID() != MouseEvent.MOUSE_PRESSED) { return; } ultimoPonto = e.getPoint(); } finally { super.processMouseEvent(e); } } @Override public void processMouseMotionEvent(MouseEvent e) { try { if (painelVars == null || e.getID() != MouseEvent.MOUSE_DRAGGED) { return; } int diffX = e.getX() - ultimoPonto.x; int diffY = e.getY() - ultimoPonto.y; Dimension s = viewport.getExtentSize(); Dimension m = painelVars.getSize(); Point p = viewport.getViewPosition(); double mult = -1.0; //Math.sqrt(prop); p.x += diffX * mult; p.y += diffY * mult; if (p.x < 0) p.x = 0; if (p.y < 0) p.y = 0; if (p.x + s.width > m.width) p.x = m.width - s.width; if (p.y + s.height > m.height) p.y = m.height - s.height; viewport.setViewPosition(p); ultimoPonto = e.getPoint(); } finally { super.processMouseMotionEvent(e); } } public void startFunction(Function f) { if (function != null) { function.startFunction(f); } else { function = f; } } public boolean endFunction() { if (function != null) { if (!function.endFunction()) { function = null; } return true; } return false; } public boolean hasVariavel(String nome) { return vars.contains(nome) || (function != null ? function.hasVariavel(nome) : false); } public boolean hasSeta(String nome) { return setas.contains(nome) || (function != null ? function.hasSeta(nome) : false); } /** * Contém as variáveis *sem* mostrá-las. */ protected class Variaveis { private HashMap<String, Variavel> variaveis; /** * Constrói um contâiner vazio. */ public Variaveis() { this.variaveis = new HashMap<String, Variavel>(); } /** * Adiciona uma variável ao contêiner. * @param v variável a ser adicionada. */ public void adicionarVariavel(Variavel v) { variaveis.put(v.getName(), v); } /** * Remove uma variável do contêiner. * @param nome nome da variável a ser removida. */ public Variavel removerVariavel(String nome) { return variaveis.remove(nome); } /** * Modifica o valor de uma variável no contêiner. * @param nome nome da variável a ser modificada. * @param valor seu novo valor. */ public void modificarVariavel(String nome, Object valor) { Variavel v = variaveis.get(nome); if (v == null) throw new RuntimeException("Variável " + nome + " não existe."); v.setValor(valor); } /** * Retorna uma cópia de uma variável. * * @param nome * @return */ public Variavel getCopiaVariavel(String nome) { Variavel v = variaveis.get(nome); if (v == null) throw new RuntimeException("Variável " + nome + " não existe."); return v.criarCopia(); } /** * Retorna uma cópia das variáveis como um HashMap separado * por cores. */ public HashMap<Color, ArrayList<Variavel>> criarCopia() { HashMap<Color, ArrayList<Variavel>> ret = new HashMap<Color, ArrayList<Variavel>>(); for (Variavel v : variaveis.values()) { Color cor = v.getCorTitulo(); Variavel copia = v.criarCopia(); if (ret.containsKey(cor)) { ret.get(cor).add(copia); } else { ArrayList<Variavel> novo = new ArrayList<Variavel>(); novo.add(copia); ret.put(cor, novo); } } return ret; } public boolean contains(String nome) { return variaveis.containsKey(nome); } } protected class Setas { private HashMap<String, Seta> setas; /** * Constroi um container vazio. */ public Setas(){ this.setas = new HashMap<String, Seta>(); } public HashMap<String, Seta> getSetas() { return setas; } /** * Adiciona uma seta * @param String nome a ser adicionado. * @param Seta s a ser adicionada. */ public void adicionarSeta(String nome, Seta s){ this.setas.put(nome, s); } /** * Remove uma seta */ public Seta removerSeta(String nome){ return setas.remove(nome); } /** * Descobre se uma seta esta contida no conjunto * @param nome * @return */ public boolean contains(String nome) { return setas.containsKey(nome); } public Setas criarCopia() { Setas setasCopia = new Setas(); for(Seta seta : setas.values()){ Seta setaCopia = seta.criarCopia(); setasCopia.adicionarSeta(seta.nome, setaCopia); } return setasCopia; } } /** * Painel que mostra variáveis *estaticamente*, i.e. o conteúdo * delas *não* deve ser alterado. Este é o estado! =) */ protected class Painel extends JPanel { private HashMap<Color, ArrayList<Variavel>> mapaVariaveis; private ArrayList<Janela> janelas = new ArrayList<Janela>(); private Setas setas; private List<Texto> textos; /** * Cria um painel. * @see #criar() */ public Painel(Variaveis v,Setas setas, List<Texto> textos) { super(); assert (v != null); this.mapaVariaveis = v.criarCopia(); this.setas = setas.criarCopia(); this.textos = Texto.copiarTextos(textos); setLayout(new GridBagLayout()); } public void adicionarSeta(String nome, Seta s) { if(this.setas == null){ this.setas = new Setas(); } this.setas.adicionarSeta(nome, s); } public void adicionarTexto(Texto texto) { if(this.textos == null){ this.textos = new ArrayList<Texto>(); } this.textos.add(texto); } /** * Cria os painéis interiores. Chame este método *após* adicionar * este painel a outro componente. * Desenha todas as variáveis na tela do mostrador * @throws Exception * @throws TiraTeimaLanguageException */ public void criar(){ if (mapaVariaveis == null) return; //Absolute Positioning (sem layout para permitir o posicionamento pelo usuario) tudo.setLayout(null); tudo.removeAll(); this.add(tudo); Color[] cores = mapaVariaveis.keySet().toArray(new Color[] {}); Arrays.sort(cores, comparadorColor); for (Color cor : cores) { Variavel[] vars = mapaVariaveis.get(cor).toArray(new Variavel[] {}); Arrays.sort(vars, comparadorVariavel); for (Variavel v : vars) { Janela j = new Janela(v); tudo.add(j); janelas.add(j); j.validate(); Dimension size = v.dimensao; Point point = v.posicao; if(point == null) point = j.getLocation(); j.setPreferredSize(size); j.setLocation(point); j.posicaoOriginal = point; if (this.setas.contains(v.nome)){ Seta seta = setas.setas.get(v.nome); tudo.add(seta); seta.validate(); Point posicaoOriginal = seta.calculaPosicaoOriginal(v); seta.posicaoOriginal = posicaoOriginal; seta.setLocation(posicaoOriginal); Integer posicaoSeta; Integer posicaoJanela = tudo.getComponentZOrder(j); //coloca a seta sobre a janela para que ela apareça if (posicaoJanela > 0){ posicaoSeta = posicaoJanela - 1; } else { posicaoSeta = 0; } tudo.setComponentZOrder(seta, posicaoSeta); } } } for(Texto t : this.textos){ tudo.add(t); t.setLocation(t.posicaoOriginal); } } /** * Define a proporção de todas as janelas contidos neste painel. * @param prop nova propoprção. */ public void setProporcao(double prop) { for (Janela j : janelas){ j.setProporcao(prop); } for (Seta s : setas.setas.values()){ s.setProporcao(prop); } for (Texto t : textos){ t.setProporcao(prop); } calculaZoom(janelas,setas,textos, prop); this.validate(); this.validate(); this.validate(); } /** * Reconfigura o tamanho da tela e o posicionamento dos objetos após o * zoom por causa do layout de posicionamento absoluto. * @param janelas * @param prop */ private void calculaZoom(ArrayList<Janela> janelas, Setas setas, List<Texto> textos, double prop){ prop = Math.nextUp(prop); Dimension tamanhoReal = new Dimension(0,0); for (Janela j : janelas){ recalculaTamanhoTudo(tamanhoReal, j); alteraPosicaoParaZoom(j,j.posicaoOriginal,prop); } for (Seta s : setas.setas.values()){ recalculaTamanhoTudo(tamanhoReal, s); alteraPosicaoParaZoom(s, s.posicaoOriginal, prop); } for(Texto t : textos){ recalculaTamanhoTudo(tamanhoReal,t); alteraPosicaoParaZoom(t, t.posicaoOriginal, prop); } //Configura o tamanho visível da tela com todas as variáveis em consideração tudo.setPreferredSize(new Dimension(tamanhoReal)); acaoZoom = null; validate(); repaint(); } /** * Altera a posicao de um componente para se adaptar ao zoom. * Ele aumenta o x e o y proporcionalmente ao paramentro "proporcao" * passado. * @param componente - o que sera reajustado (ou nao) * @param posicaoOriginal - posicao original do componente * @param proporcao - proporcao atual */ private void alteraPosicaoParaZoom(JComponent componente, Point posicaoOriginal, Double proporcao) { int tmp; //Aumenta a distancia entre as janelas também if(acaoZoom != null){ if(componente.getLocation().x > 0){ tmp = componente.getLocation().y; if((acaoZoom == zoom.AUMENTA || acaoZoom == zoom.DIMINUI) && proporcao > 1){ componente.setLocation((int) (posicaoOriginal.x * (proporcao)), tmp); } } if(componente.getLocation().y > 0){ tmp = componente.getLocation().x; if((acaoZoom == zoom.AUMENTA || acaoZoom == zoom.DIMINUI) && proporcao > 1){ componente.setLocation(tmp, (int) (posicaoOriginal.y * (proporcao))); } } //Reset de zoom if(acaoZoom == zoom.REINICIA){ componente.setLocation(posicaoOriginal); } //Atualizacao se refere a uma nova tela. Manter zoom escolhido }else{ if(componente.getLocation().x > 0){ tmp = componente.getLocation().y; if(proporcao > 1) componente.setLocation((int) (componente.getLocation().x * (proporcao)), tmp); } if(componente.getLocation().y > 0){ tmp = componente.getLocation().x; if(proporcao > 1) componente.setLocation(tmp, (int) (componente.getLocation().y * (proporcao))); } //Reset de zoom if(proporcao == 1){ componente.setLocation(posicaoOriginal); } } } /** * Altera o tamanho real do painel "tudo", que contem os elementos * graficos do mostrador. * Se encontrar um tamanho maior, reestabelece o valor real, que sera * usado como parametro para determinar o tamanho do painel "tudo" * @param real * @param componente */ private void recalculaTamanhoTudo(Dimension tamanhoReal, JComponent componente) { int novaAltura = (int) ((componente.getHeight() + componente.getY() + 100 )); int novaLargura = (int) (( componente.getWidth() + componente.getX() + 100)); if(novaAltura > tamanhoReal.height) tamanhoReal.height = novaAltura; if(novaLargura > tamanhoReal.width) tamanhoReal.width = novaLargura; } public void setTextos(List<Texto> textos) { this.textos = textos; } } private class EstadoMostrador { public Painel painel; public Function function; public Object estadoFunction = null; public EstadoMostrador(Painel painel, Function function) { this.painel = painel; this.function = function; if (function != null) this.estadoFunction = function.getEstado(); } } /** * Remove a seta relativa a variável, caso haja se ela for um ponteiro. * @param nome_var */ public Seta removerSeta(String nome_var) { if(setas.setas.containsKey(nome_var)){ return setas.setas.remove(nome_var); } else { return null; } } public Integer quantidadeDeVariaveis() { return this.painelVars.janelas.size(); } public void adicionarTexto(Texto texto) { if (function != null) { function.adicionarTexto(texto); } else { this.textos.add(texto); } } }