Towel – Release 1.2.2

30 06 2011

Hoje está sendo liberado um release do projeto Towel com uma pequena alteração enviado por Paulo Henrique.

A unica diferença é que agora é possivel mudar o texto do botão que aparece no Popup do CalendarView.

Para isso, basta usar o método setTodayString do CalendarView.

Ex:

CalendarView view = new CalendarView();
view.setTodayString("Hoje");

Para baixar a nova versão, basta entrar na pagina Towel Project do blog e seguir o link para o github.





CalendarView

1 06 2011

Esse artigo é sobre uma classe do projeto Towel, para utiliza-la, baixe a biblioteca na pagina Towel Project desse blog.

Esse componente foi feito em 1999 pelo meu professor de Java enquanto ele fazia estagio na area, por esse motivo eu desculpo ele por ter usado null-layout, mas refatorei ele para ser mais generico e independente de LayoutManagers.

O CalendarView é um componente que exibe um JTextField e um JButton juntos.
O JTextField serve para digitar dadas no formato ‘dd/MM/yyyy’ e o JButton abre uma JWindow que permite escolher a data em um calendario.

Para utiliza-lo é bem simples:

import java.awt.event.*;
import javax.swing.*;

import com.towel.swing.calendar.CalendarView;

public class CalendarViewTest {
	public CalendarViewTest() {
		JFrame frame = new JFrame("CalendarView");
		JPanel content = new JPanel();
		final CalendarView view = new CalendarView();
		JButton button = new JButton("X");
		content.add(view);
		content.add(button);
		button.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				System.out.println(view.getSelectedDate());
			}
		});

		frame.setContentPane(content);
		frame.pack();
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
	}

	public static void main(String[] args) {
		new CalendarViewTest();
	}
}

Esse codigo exibe a seguinte janela:

E quando pressionado o botao “..” a JWindow aparece com o DatePicker para escolher uma data.

Escolhendo alguma data o valor é exibido no campo de texto.

Para obter o valor que o usuario digitou (em uma ação por exemplo), temos dois métodos:

  • getText() que retorna o texto do TextField
  • getSelectedDate() que retorna um objeto Calendar com a data selecionada pelo usuario.
  • E no caso do código de exemplo, ao pressionar ‘X’ é exibido o toString do objeto Calendar que for escolhido.

    Como sempre, ainda é possivel melhorar esse componente, e essa é a lista do que já pode ser feito:

  • Um Layout melhor para o JWindow.
  • Internacionalizar os textos (nome do mês, inicial do dia, botão “hoje”)
  • Poder digitar datas no formato MM/dd/yyyy




  • Towel – Release 1.2

    25 05 2011

    Hoje dia 25/05 está sendo liberado o novo release do projeto Towel.

    As novidades são:

    • FieldResolver consegue obter/setar os valores dos atributos através da hierarquia do objeto;
    • Não é obrigatório declarar as anotações @Resolvable em todos os atributos, nesses casos o DefaultFormatter vai ser utilizado e o FieldHandler será o FieldAcessHandler padrão;
    • Um novo componente enviado pelo meu professor de Java foi incluido no pacote com.towel.swing sob o nome de CalendarView, que é um DatePicker para Swing; (Artigo em breve)

    Para baixar a nova versão, basta entrar na pagina Towel Project do blog e seguir o link para o github.

    Mais uma coisa está acontecendo hoje:

    Dia 25/05 é o Towel Day, em homenagem a Douglas Adams.
    Esse release foi planejado. ;)

    Abraço a todos, e feliz dia da toalha!





    JTableView

    14 02 2011

    Algo que eu planejava a algum tempo é oferecer um modo onde seja possivel ter colunas em uma JTable com valores agregados como, soma, média, menor, maior e etc.., foi com essa intenção que criei as AggregateFunctions. O model que faz isso para as colunas está pronto, e é o AggregateModel.
    Mas encontrei alguns desafios não triviais para fazer isso funcionar, não existe nem na JTable nem no JScrollPane um modo de adicionar um footer, a unica maneira que pensei para resolver isso é criando uma classe que estende JPanel que tenha um JScrollPane com uma “Main Table” e outro com uma linha fixa com o model de agregação dos valores da tabela principal. Então para encapsular essa “gambiarra” criei a classe JTableView, com ela é possivel utilizar esse novo recurso. Mas ainda tem alguns poucos defeitos que ainda não consegui resolver (uma contribuição se alguem souber é bem vinda ^^).

    Para utiliza-la, é necessario instanciar o JTableView com um modelo qualquer e depois as funções que serão utilizadas nas colunas do footer.

    Como exemplo, usaremos o modelo “Person”  para exibir alguns valores e aplicaremos algumas funções nas colunas.

    Person.java

    public class Person {
    	@Resolvable(colName = "Name")
    	private String name;
    	@Resolvable(colName = "Age")
    	private int age;
    	@Resolvable(colName = "Live")
    	private boolean live;
    
    	public Person(String name, int age, boolean live) {
    		this.name = name;
    		this.age = age;
    		this.live = live;
    	}
    }
    

    Com isso criamos o modelo e adicionamos alguns dados.
    PS:Para quem não sabe como funciona o ObjectTableModel pode ver aqui.

    		ObjectTableModel model = new ObjectTableModel(
    				new AnnotationResolver(Person.class), "name,age,live");
    
    		model.setEditableDefault(true);
    
    		model.add(new Person("A", 10, true));
    		model.add(new Person("B", 20, true));
    		model.add(new Person("C", 30, false));
    		model.add(new Person("D", 40, true));
    		model.add(new Person("E", 50, true));
    

    PS: JTableView não funciona apenas com o ObjectTableModel, mas ele é o mais simples de ser utilizado em qualquer ocasião.

    Agora com o modelo pronto, só precisamos criar o JTableView e colocar as funções de agregações que queremos na tabela.

    		JTableView view = new JTableView(model);
                    //Os indices são 0 based.
    		view.getFooterModel().setFunction(0, new FuncConcat("-"));
    		view.getFooterModel().setFunction(1, new FuncSum());
    

    PS: Para saber como funciona a API de AggregateFunctions, ver em CollectionsUtils.

    Não é necessario ter uma função para todas as colunas, estas ficaram em branco.

    Agora só basta colocar esse componente em algum lugar, como em um JFrame e exibi-lo.

    		JFrame frame = new JFrame();
    		frame.getContentPane().add(view);
    
    		frame.pack();
    		frame.setLocationRelativeTo(null);
    		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		frame.setVisible(true);
    

    Com isso temos o seguinte resultado.

    Ao alteramos os valores na tabela principal o novo resultado é calculado no footer.

    Como eu disse, este componente não está perfeito, ele tem suporte a re-alocação de colunas, caso voce esteja clicando no header da main table e arrastando a coluna para outro lugar.

    Mas ela não tem suporte para resize de colunas, o resultado se esticar algumas colunas vai ser bem estranho, se alguém souber como resolver será muito interessante para o projeto.
    Então ainda não indico utilizar este componente a não ser em caso que deixem a tabela fixada e sem suporte para mudar o tamanho das colunas, uma versão melhorada desse componente vai ser liberado assim que for arrumado estes erros pendentes.





    DynamicFormatter

    12 02 2011

    Essa classe é uma contribuição de Felipe Priuli para o projeto Towel, e também foi ele que escreveu este artigo de como utiliza-lo.

    Esta classe implementa com.towel.bean.Formatter e foi criado, primeiramente, para ser utilizado no ObjectComboBoxModel, permitindo criar um Formatter dinamicamente para
    as classes que serão utilizada como model de um ComboBox. Em outras palavras, ao invés de criar um Formatter para cada classe é possível utilizar este Formatter
    para representar as classes de maneira genérica.

    Ele funciona permitindo adicionar FieldResolvers a uma lista que será utilizada para obter os valores dos objetos do modelo, para ver uma explicação detalhada de como o FieldResolver funciona, veja no artigo sobre ObjectTableModel.

    Vamos para o exemplo:

    Primeiro! Vamos criar a classe Person:

    public class Person {
            private String name;
            private int age;
            public Person(String str, double d) {
                    this.name = str;
                    this.age = (int) d;
            }
            public String toString() {
                    return "Name: " + name + " age: " + age;
            }
    }
    

    Nós queremos mostrar informações sobre a pessoa em cada iten do JComboBox, mas ao inves de criar um Formatter para a classe Pessoa, vamos utilizar o DynamicFormatter.

    Abaixo um exemplo de como criar um JComboBox de ‘Person’ utilizando o model ObjectComboBoxModel com o DynamicFormatter:

    import javax.swing.*;
    
    import com.towel.awt.ann.*r;
    import com.towel.bean.DynamicFormatter;
    import com.towel.combo.swing.ObjectComboBoxModel;
    import com.towel.el.FieldResolver;
    
    public class ComboBoxDynamicFormatterTest extends JFrame {
    	private ObjectComboBoxModel model;
    	@Action(method = "showPerson")
    	private JButton button;
    
    	public ComboBoxDynamicFormatterTest() {
    		super("ComboBoxModel");
    		model = new ObjectComboBoxModel();
    
    		DynamicFormatter formatter = new DynamicFormatter(
    				Person.class, " - ");
    		formatter.addField(new FieldResolver(Person.class, "name"));
    		formatter.addField(new FieldResolver(Person.class, "age"));
    
    		model.setFormatter(formatter);
    		// Adicionado as classes Person no model
    		model.add(new Person("A", 10.0));
    		model.add(new Person("B", 20.0));
    		model.add(new Person("C", 30.0));
    		model.add(new Person("D", 40.0));
    		model.add(new Person("E", 50.0));
    
    		JComboBox combo = new JComboBox(model);
    		JPanel cont = new JPanel();
    		cont.add(combo);
    		cont.add(button = new JButton("Show"));
    		setContentPane(cont);
    		pack();
    		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		setLocationRelativeTo(null);
    		setVisible(true);
    
    		new ActionManager(this);// Necessary to map @Action to the method
    	}
    
    	private void showPerson() {
    		Person p = model.getSelectedObject();
    		System.out.println(p.toString());
    	}
    
    	public static void main(String[] args) {
    		new ComboBoxDynamicFormatterTest();
    	}
    }
    

    Ele ainda tem a vantagem que se for necessario exibir um campo que seja GregorianCalendar, é possivel adicionar o Formatter ao FieldResolver desse atributo para exibir um valor mais amigavel do que o valor padrão do toString.

    Os métodos utilizados para criar os itens em um ComboBox de maneira generica, pela classe DynamicFormatter são:

    • addField(FieldResolver) : Ele permite adicionar um resolver a lista que será utilizada para montar a String que representa o objeto.
    • format(Object) : O método ‘format’ faz a conversão do objeto para texto. O texto é aquele que será mostrado no item do ComboBox.
    • setSeparator(String) : Este método permite que você indique o texto que irá separar o texto dos itens e é usado quando existir mais de um campo que irá aparecer no item de JComboBox
    • getFieldList() : Este método retorna uma lista de FieldResolver. O FieldResolver é responsável por obter os valores dos atributos (Fields) dos objetos, estes valores são então mostrados nos itens combobox.




    JImagePanel

    11 02 2011

    Essa classe está no projeto Towel, para utiliza-la, entre na pagina “Towel Project” e baixe a versão mais atual para adicionar ao classpath.

    JImagePanel é uma classe criada pelo ViniGodoy, é um JPanel que tem uma imagem de background no fundo e modificada por mim para ter suporte a exibir uma sequencia de imagens através do tempo.

    Com ela, é possivel criar telas em Swing com imagens de fundo e ainda ter componentes por cima, diferente do que seria possivel com um JLabel, e ela é extremamente simples de usar.

    Para utilizar:

    • Criar uma instancia com uma ou varias imagens;
    • Escolher o FillType, opcional, este é o modo de como a imagem vai se comportar quando a area disponivel for maior que ela, as opções são: RESIZE (crescer), CENTER (centralizado), SIDE_BY_SIDE (Lado a lado, assim como no Desktop do windows). Por default o FillType é RESIZE;
    • Adicionar ela a um Container, como um JFrame por exemplo;
    • Adicionar componentes sobre ela, isso é feito assim como em um JPanel;
    • Exibi-la.

    O seguinte código representa isso.

    import java.awt.Dimension;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    
    import javax.imageio.ImageIO;
    import javax.swing.JFrame;
    
    import com.towel.swing.img.JImagePanel;
    
    public class JImagePanelSingleTest {
    	public static void main(String[] args) throws Throwable {
    		JImagePanel panel = new JImagePanel(
    				loadImage("/home/marcos/imgs/1.png"));
    
    		JFrame frame = new JFrame();
    		frame.setPreferredSize(new Dimension(100, 100));
    		frame.add(panel);
    		frame.pack();
    		frame.setLocationRelativeTo(null);
    		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		frame.setVisible(true);
    	}
    
    	private static BufferedImage loadImage(String file) throws IOException {
    		return ImageIO.read(new File(file));
    	}
    }
    

    Com isso, temos um resultado como esse:

    Se não quisermos que aumente nossa imagem, podemos usar o FillType como no seguinte código.

    public class JImagePanelSingleTest {
    	public static void main(String[] args) throws Throwable {
    		JImagePanel panel = new JImagePanel(
    				loadImage("/home/marcos/imgs/1.png"));
    
    		panel.setFillType(JImagePanel.FillType.CENTER);
    
    		JFrame frame = new JFrame();
    		frame.setPreferredSize(new Dimension(100, 100));
    		frame.add(panel);
    		frame.pack();
    		frame.setLocationRelativeTo(null);
    		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		frame.setVisible(true);
    	}
    
    	private static BufferedImage loadImage(String file) throws IOException {
    		return ImageIO.read(new File(file));
    	}
    }
    

    Com isso temos o seguinte resultado:

    Para utilizar o modo em loop, basta usar o construtor que recebe um long e um array de imagens, o primeiro parametro é o tempo que levará para mudar entre uma imagem e outra em millisegundos.
    Assim que criado uma Thread é disparada para atualizar as imagens, essa Thread é daemon e não vai interferir caso a Thread principal termine.
    Também é possivel utilizar o FillType em modo de loop.

    O código é praticamente o mesmo, ficando assim:

    public class JImagePanelLoopTest {
    	public static void main(String[] args) throws Throwable {
    		JImagePanel panel = new JImagePanel(10, new BufferedImage[] {
    				loadImage("/home/marcos/imgs/1.png"),
    				loadImage("/home/marcos/imgs/2.png"),
    				loadImage("/home/marcos/imgs/3.png"),
    				loadImage("/home/marcos/imgs/4.png"),
    				loadImage("/home/marcos/imgs/5.png"),
    				loadImage("/home/marcos/imgs/6.png"),
    				loadImage("/home/marcos/imgs/7.png"),
    				loadImage("/home/marcos/imgs/8.png"),
    				loadImage("/home/marcos/imgs/9.png"),
    				loadImage("/home/marcos/imgs/10.png"),
    				loadImage("/home/marcos/imgs/11.png"),
    				loadImage("/home/marcos/imgs/12.png") });
    
    		JFrame frame = new JFrame();
    		frame.setPreferredSize(new Dimension(100, 100));
    		frame.add(panel);
    		frame.setLocationRelativeTo(null);
    		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		frame.setVisible(true);
    	}
    
    	private static BufferedImage loadImage(String file) throws IOException {
    		return ImageIO.read(new File(file));
    	}
    }
    

    O resultado da execução é uma animação, então não é possivel colocar uma imagem aqui.

    Ainda a fazer nessa classe é o seguinte:

    • Começar a animação com um método start() por exemplo
    • Oferecer um modo para executar a animação apenas uma vez e parar em alguma imagem.




    Towel v1.1

    9 02 2011

    Changelog da versão 1.1 do Towel.

    Pastas de imagens do TableFilter incluida.

    JImagePanel agora tem suporte para exibir uma sequencia de imagens e o ImageLoopPanel que havia antigamente foi depreciado.
    Artigo sobre como usar ele adicionado 11/02/2011, veja aqui.

    Felipe Priuli fez uma contribuição no projeto adicionando mais uma classe util, a DynamicFormatter, ela é um Formatter genérico que foi feita para facilitar o uso do ObjectComboBoxModel, mas ela é genérica e serve para qualquer caso que precise de um Formatter.

    Algo que eu planejava a algum tempo é oferecer um modo onde seja possivel ter colunas em uma JTable com valores agregados como, soma, média, menor, maior e etc.., foi com essa intenção que criei as AggregateFunctions. O model que faz isso para as colunas está pronto, e é o AggregateModel.
    Mas encontrei alguns desafios não triviais para fazer isso funcionar: Não existe nem na JTable nem no JScrollPane um modo de adicionar um footer, criei o componente JTableView, com ele é possivel utilizar esse novo recurso, mas ainda tem alguns defeitos que ainda não consegui resolver (uma contribuição se alguem souber é bem vinda :) ).

    Existe um novo pacote agora, com.towel.sound, ele contém classes para audio que foi migrada do JGF (Java Game Framework) um projeto que o ViniGodoy começou, que agora será descontinuado por que o Alegria irá realizar tudo que ele faz e mais ainda.

    Desenvolvido pelo ViniGodoy agora foi migrada para o projeto a classe HashBuilder, ela serve para calcular o hash dos objetos facilmente.

    Outro colaborador dessa versão é o Eric Yuzo que melhorou o ObjectTableModel, agora contém mais comentários e também foi alterado para utilizar o fireTableCellChanged no lugar de fireTableDataChanged que eu estava usando anteriormente.

    ObjectTableModel agora implementa Iterable, então é possivel percorrer a lista dos dados com o for-each.

    Alguém que ajudou bastante comentando muitas classes foi o Marco A. Biscaro (marcobiscaro do GUJ), graças a ele e outras contribuições agora o Java Doc está quase completo.

    Em breve vou gerar o Java Doc e hospedar em algum lugar, mas por enquanto já é possivel navegar pelas classes e ler os docs nas proprias classes.

    Estarei publicando os artigos desses recursos em pouco tempo.

    A pagina Towel Project foi expandida, vale a pena dar uma olhada e baixar a nova versão.





    Projeto Alegria

    29 12 2010

    Só repassando a noticia que o Vini postou.

    Eu e o ViniGodoy começamos no GitHub(mas mudamos para o Google Code por que o plugin do Github não é muito bom), um novo projeto chamado Alegria. A idéia é ser um Engine 2D para Java, completamente OpenSource, baseado na LWJGL.

    Esse projeto vai ser tocado nas horas vagas, como um hobby. Ele tem o objetivo de servir de estudo e ao mesmo tempo de base para as aulas de inteligência artificial e física pra jogos que o ViniGodoy leciona.

    Também pretendo desenvolver uns joguinhos com ele, por diversão (desde que encontre algum artista de bom coração).

    E lógico, o conteúdo aprendido vai gradualmente virar tutoriais e artigos para o Ponto V!

    Antes que perguntem:

    1. Por que Java?

    Primeiro, porque é uma linguagem que eu e o Vini gostamos. Somos membros ativos (tão ativos a ponto de sermos moderadores) do GUJ, um dos maiores fóruns de Java do Brasil. E, como somos os donos do projeto, foi uma escolha um tanto natural;

    Segundo, é bastante produtivo programar em Java. Nada de dangling pointers e outros traumas do C++. Queríamos uma linguagem para nos divertir programando e sem muita escovação de bits;

    E terceiro, porque é uma linguagem muito popular. Uma pena que não tenha sobrecarga de operadores, mas como integraremos o groovy, esse “detalhe” pode ser facilmente contornado.

    1.1. Mas eu amo C ou C++, fico sem alternativa?

    Não. Conheça os projetos Chien2D (em C) e GameSpaceLib 2.0 (C++). Ambos de pessoal fera da área e autores do Ponto V!

    2. Vai ser free?

    Sim, LGPL.

    3. Quando terei um build para baixar e usar no meu projeto?

    Não estamos prometendo prazos, nem datas, e nem que as primeiras versões serão estáveis e compatíveis entre si. Se déssemos esse tipo de promessa, a coisa viraria trabalho e não hobby.

    Entretanto, iremos traçar alguns objetivos mais ousados num futuro próximo, até para o projeto não morrer de vez (como por exemplo, participar de algum Game Jam ou da SBGames com ele). A definição destes dependerá do calendário para o ano que vem.

    4. Por que a LWJGL?

    Acreditamos que não há motivos para não se utilizar o hardware da placa de vídeo hoje em dia. Jogos como Aquaria, Penumbra ou World of Goo, mais do que provam essa teoria. A OpenAL ainda fornece suporte a som 5.1 ambiental, o que torna a experiência de games mais rica.

    5. Que versão da OpenGL será necessária para rodar o projeto?

    Versão 2.0, ou até superior. E sim, iremos tentar dar um bom suporte à Shaders.

    6. Por que 2D e não 3D?

    Não queremos concorrer com a JMonkeyEngine, mas não descartamos a hipótese de brincar um pouco com três dimensões no futuro.

    Bom, é isso. Aceitamos comentários, sugestões e novas idéias são sempre bem vindas.





    Towel v1.0!

    13 12 2010

    Bem, agora com o projeto com um novo nome(veja motivo aqui).

    Estou lançando a nova versão sob este novo nome, e como novidades temos o seguinte:

    1° Todos os packages foram renomeados de mark.utils. para com.towel.
    2° Foi adicionado a classe TableFilter(Auto-Filtro) criado pelo ViniGodoy no package com.towel.swing.table.
    3° Foi adicionado a classe JImagePanel criado pelo ViniGodoy no package com.towel.swing.img.

    Em breve escreverei um artigo sobre isso.

    E é isso, acessem a página Towel Project para ver como baixar a versão atualizada do projeto.





    MarkUtils renamed!

    9 12 2010

    Bem, a um pequeno tempo, esse nome levemente ego-centrico(diga-se de passagem e sem comentários) do meu projeto não me agrada muito.
    Mas por falta de opções deixei por isso mesmo, até que então, me veio um nome melhor para ele.

    Após consultar o publico (em fato, umas 6 pessoas) e ver que a idéia realmente não é ruim, decedi agora mudar o nome do projeto.

    Inspirado pela obra de Douglas Adams, autor da série The Hitchhiker’s Guide to The Galaxy, decedi finalmente mudar o nome para: “Towel”, é, toalha mesmo, e por que não? O que é mais util do que uma toalha?

    O Guia do Mochileiro das Galaxias faz algumas afirmações a respeito de toalhas.

    “Segundo ele, a toalha é um dos objetos mais úteis para um mochileiro interestelar. Em parte devido a seu valor prático: você pode usar a toalha como agasalho quando atravessar as frias luas de Beta de Jagla; pode deitar-se sobre ela nas reluzentes praias de areia marmórea de Santragino V; pode dormir debaixo dela sob as estrelas que brilham no mundo desértico de Kakrafoon; pode umedece-la e utiliza-la para lutar em um combate corpo a corpo; enrola-la em torno da cabeça para proteger-se de emanações tóxicas ou para evitar o olhar da Terrível Besta Voraz de Traal (um animal estonteantemente burro, que acha que, se você não pode vê-lo, ele também não pode ver você); você pode agitar a toalha em situações de emergência para pedir socorro; e, naturalmente, pode usa-la para enxugar-se com ela se ainda estiver razoavelmente limpa.
    Porém o mais importante é o imenso valor psicológico da toalha. Por algum motivo, quando um strag ( não-mochileiro ) descobre que um mochileiro tem uma toalha, ele automaticamente conclui que ele tem também escova de dentes, esponja, sabonete, lata de biscoitos, garrafinha de aguardente, bússola, mapa, barbante, repelente, capa de chuva, traje espacial, etc, etc. Além disso, o strag terá prazer em emprestar ao mochileiro qualquer um desses objetos, ou muitos outros, que o mochileiro por acaso tenha “acidentalmente perdido”. O que o strag vai pensar é que, se um sujeito é capaz de rodar por toda a Galáxia, acampar, pedir carona, lutar contra terríveis obstáculos, dar a volta por cima e ainda assim saber onde está sua toalha, esse sujeito claramente merece respeito.”

    Veja a versão 1.0 disponivel para download já com novidades.

    Se for encontrado algo com mark.utils é que deve ter passado despercebido, por favor me avisem.

    Cya!








    Seguir

    Obtenha todo post novo entregue na sua caixa de entrada.

    Junte-se a 153 outros seguidores