W tym wpisie korzystałem z materiałów udostępnionych przez moją córkę.
Ten wpis jest kontynuacją wpisu: Formatowanie tekstu podpowiedzi (bez użycia HTML)
Klasy
Przygotowujemy 4 klasy:
- ImageToolTip.java
- ImagetoolTipUI.java
- MyButton.java
- Main.java
ImageToolTip.java
package tooltip2; import java.awt.geom.*; import java.awt.image.*; import java.util.*; import javax.swing.*; import javax.swing.plaf.*; public class ImageToolTip extends JToolTip { private static final long serialVersionUID = 2381804711089908434L; private BufferedImage[] images = new BufferedImage[0]; private BufferedImage[] scaledImages = new BufferedImage[0]; private int[] afterTokenNos = new int[0]; /** * ustawia nowe ToolTipUI dla tej podpowiedzi * przekazujac do ToolTipUI obrazki i miejsce w ktorym * ma sie podpowiedz znalezc */ public ImageToolTip() { //przekazuje puste tablize o zerowej dlugosci setUI(new ImageToolTipUI(scaledImages, afterTokenNos)); } @Override public void setUI(ComponentUI newUI) { super.setUI(newUI); } /** * wstawia zeskalowany obrazek do */ public void setImage(BufferedImage image, double scale, int afterTokenNo) { AffineTransform af = AffineTransform.getScaleInstance(scale, scale); AffineTransformOp atop = new AffineTransformOp(af, AffineTransformOp.TYPE_BICUBIC); int len = images.length; //okresla dlugosc tablicy (poczatkowo jest zerowa images = Arrays.copyOf(images, len + 1);//przedluza tablice o 1 scaledImages = Arrays.copyOf(scaledImages, len + 1);//przedluza tablice o 1 afterTokenNos = Arrays.copyOf(afterTokenNos, len + 1);//przedluza tablice o 1 BufferedImage scaledImage = atop.filter(image, null);//skaluje obrazek images[len] = image;//wstawia obrazek do tablicy scaledImages[len] = scaledImage;//wstawia skalowany obrazek do tablicy afterTokenNos[len] = afterTokenNo;// wstawia token do tablicy setUI(new ImageToolTipUI(scaledImages, afterTokenNos));// ustawia // nowy ToolTipUI - przekazuje tablice obrazkow i tokenow do ToolTipUI } public void removeImage(int imageNumber) { images = shortenArray(images, imageNumber); scaledImages = shortenArray(scaledImages, imageNumber); afterTokenNos = shortenArray(afterTokenNos, imageNumber); setUI(new ImageToolTipUI(scaledImages, afterTokenNos));// ustawia // nowy ToolTipUI - przekazuje tablice obrazkow i tokenow do ToolTipUI } public void removeImages() { images = new BufferedImage[0]; scaledImages = new BufferedImage[0]; afterTokenNos = new int[0]; setUI(new ImageToolTipUI(scaledImages, afterTokenNos));// ustawia // nowy ToolTipUI - przekazuje tablice obrazkow i tokenow do ToolTipUI } public BufferedImage[] getImages() { return images; } public BufferedImage[] getScaledImages() { return scaledImages; } //zamienia nr slowa na nr tokenu (tokenami sa tez spacje) //dlatego mnozenie przez 2 i dodanie 1 (uwzglednienie tego slowa) public static int findTokenNo(String text, String wordToFind, String delimiter) { StringTokenizer st = new StringTokenizer(text, delimiter); ArrayList<String> al = new ArrayList<>(); while (st.hasMoreTokens()) { al.add(st.nextToken()); } return al.indexOf(wordToFind) * 2 + 1; } //skraca dlugosc tablicy obrazkow dla typow obiektowych public static <T> T[] shortenArray(T[] tabl, int indexDeleted) { int len = tabl.length; //sprawdza czy index miesci sie w tablicy if ((indexDeleted > len - 1) || (indexDeleted < 0)) { throw new IndexOutOfBoundsException("index poza tablicą"); } else {//jesli tak //to przepisuje rekordy za indeksem o 1 w lewo for (int i = indexDeleted; i < tabl.length - 1; i++) { tabl[i] = tabl[i + 1]; } } //a nastepnie skraca tablice o 1 return Arrays.copyOf(tabl, len - 1); } //to samo tylko dla typow prymitywnych public static double[] shortenArray(double[] tabl, int indexDeleted) { int len = tabl.length; if ((indexDeleted > len - 1) || (indexDeleted < 0)) { throw new IndexOutOfBoundsException("index poza tablicą"); } else { for (int i = indexDeleted; i < tabl.length - 1; i++) { tabl[i] = tabl[i + 1]; } } return Arrays.copyOf(tabl, len - 1); } public static int[] shortenArray(int[] tabl, int indexDeleted) { int len = tabl.length; if ((indexDeleted > len - 1) || (indexDeleted < 0)) { throw new IndexOutOfBoundsException("index poza tablicą"); } else { for (int i = indexDeleted; i < tabl.length - 1; i++) { tabl[i] = tabl[i + 1]; } } return Arrays.copyOf(tabl, len - 1); } }
ImagetoolTipUI.java
package tooltip2; import java.awt.*; import java.awt.image.*; import java.util.*; import javax.swing.*; import javax.swing.plaf.metal.*; /* * wyswietla podpowiedz uzywajac MetalLookAndFeel */ public class ImageToolTipUI extends MetalToolTipUI { private String[] tokeny;//tokeny private int rectW = 0;//szerokosc wyswietlonej podpowiedzi private final BufferedImage[] scaledImages;//skalowane obrazki private final int[] afterTokenNos;//pozycje obrazkow w tekscie private final int[] scaledWidths;//szerokosci obrazkow private final int[] scaledHeights;//wysokosci obrazkow public ImageToolTipUI(BufferedImage[] scaledImages, int[] afterTokenNos) { this.scaledImages = scaledImages; this.afterTokenNos = afterTokenNos; int len = scaledImages.length; scaledWidths = new int[len];//utowrzenie tablicy scaledHeights = new int[len];//j.w. //wypelnienie tablic for (int i = 0; i < scaledImages.length; i++) { scaledWidths[i] = scaledImages[i].getWidth(); scaledHeights[i] = scaledImages[i].getHeight(); } } @Override public void paint(Graphics g, JComponent c) { FontMetrics fm = g.getFontMetrics(g.getFont()); int w = c.getWidth();//szerokosc komponentu dla ktorego jest podpowiedz int h = c.getHeight();//j.w. ytlko wysokosc g.setColor(c.getBackground()); g.fillRect(0, 0, w, h); g.setFont(c.getFont()); g.setColor(c.getForeground()); int curX = 5;//polozenie tekstu na komponencie int curY = 15;//polozenie tekstu na komponencie int akt;//szerokosc aktualnie wypisywanego slowa @SuppressWarnings("unused") int rows = 1;//liczba linii tekstu if (afterTokenNos.length > 0) {//jesli istnieja obrazki for (int i = 0; i < tokeny.length; i++) {//przebieg przez tokeny for (int k = 0; k < afterTokenNos.length; k++) {//przebieg przez pozycje obrazkow if (i != afterTokenNos[k]) {//jesli po tym tokenie nie ma obrazka if (k == 0) {//ogranicznik. Gdyby go nie bylo to //kazde slowo byloby wyswietlone 3 razy, np. JestJestJest //,a tak wyswietla slowo tylko raz akt = fm.stringWidth(tokeny[i]);//sprawdza szerokosc stringu //w pikselach if (!tokeny[i].endsWith("\n")) {//jesli token nie ma znaku lamania if ((curX + akt) < rectW + 5) {//jesli sie miesci w ramce g.drawString(tokeny[i], curX, curY);//to wypisuje curX += akt;//zmienia pozycje "glowicy" } else {//jesli sie nie miesci curX = 5;//to przenosi "glowice" na poczatk curY += fm.getHeight();//i do nowego wiersza rows++;//dodaje nowy rzad g.drawString(tokeny[i], curX, curY);//wypisuje string curX += akt;//zmienia pozycje "glowicy" } } else { curX = 5;//to przenosi "glowice" na poczatk curY += fm.getHeight();//i do nowego wiersza rows++;//dodaje nowy rzad g.drawString(tokeny[i], curX, curY);//wypisuje string curX += akt;//zmienia pozycje "glowicy" } } } else {//jesli po tym tokenie jest obrazek curX = 5;//przenosi glwice na poczatek curY += fm.getHeight();//i do nowego wiersza g.drawImage(scaledImages[k], curX, curY,//rysuje obrazek scaledWidths[k], scaledHeights[k], null); curX = 5;//przenosi kursor na poczatek curY += scaledHeights[k] + fm.getHeight();//i do nowego wiersza rows += scaledHeights[k] / fm.getHeight() + 2;//uaktualnia liczbe //rzedow uwzgledniajac szerokosc obraz oraz linie ponad i pod obrazkiem } } } } else {//jesli nie istnieja obrazki for (String s : tokeny) { akt = fm.stringWidth(s);//sprawdza szerokosc stringu //w pikselach //w pikselach if (!s.endsWith("\n")) {//jesli token nie ma znaku lamania if ((curX + akt) < rectW + 5) {//jesli sie miesci w ramce g.drawString(s, curX, curY);//to wypisuje curX += akt;//zmienia pozycje "glowicy" } else {//jesli sie nie miesci curX = 5;//to przenosi "glowice" na poczatk curY += fm.getHeight();//i do nowego wiersza rows++;//dodaje nowy rzad g.drawString(s, curX, curY);//wypisuje string curX += akt;//zmienia pozycje "glowicy" } } else { curX = 5;//to przenosi "glowice" na poczatk curY += fm.getHeight();//i do nowego wiersza rows++;//dodaje nowy rzad g.drawString(s, curX, curY);//wypisuje string curX += akt;//zmienia pozycje "glowicy" } } } } /** * ta metoda wywolywana jest zawsze przed paint() */ @Override public Dimension getPreferredSize(JComponent c) { FontMetrics fm = c.getFontMetrics(c.getFont()); String tipText = ((JToolTip) c).getTipText(); if (tipText == null) { tipText = ""; } StringTokenizer st = new StringTokenizer(tipText, " ", true); int tokNum = st.countTokens(); tokeny = new String[tokNum]; int i = -1; int maxStrLen = 0; String maxString = null; while (st.hasMoreTokens()) { i++; String val = st.nextToken();//pobiera token tokeny[i] = val;//przypisuje token do tablicy int valLen = val.length();//sprawdza ilosc znakow w tokenie if (valLen > maxStrLen) {//sprawdza czy token jest najdluzszy maxStrLen = valLen;//jesli tak to ustawia dlugosc maxString = tokeny[i];//i pobiera ten string } } int textH = fm.getHeight();//wysokosc tekstu int textW = fm.stringWidth(tipText);//dlugosc stringu bez obrazkow int textArea = textH * textW;//powierzchnia zajmowana przez tekst rectW = (int) Math.round(Math.sqrt(textArea / 0.62));//zloty podzial int len1 = fm.stringWidth(maxString);//dlugosc najdluzszego slowa if (rectW < len1 + 5) {//jesli szerokosc jest mniejsza niz najdluzsze slowo rectW = len1 + 10;//powieksza obrazek } int curX = 5;//polozenie tekstu na scenie @SuppressWarnings("unused") int curY = 15;//polozenie tekstu na scenie int akt;//szerokosc aktualnie wypisywanego slowa int rows = 1;//liczba linii tekstu //wszystko dokladnie tak samo jak w metodzie paint if (afterTokenNos.length > 0) { for (i = 0; i < tokeny.length; i++) { for (int k = 0; k < afterTokenNos.length; k++) { if (i != afterTokenNos[k]) { if (k == 0) { akt = fm.stringWidth(tokeny[i]);//sprawdza szerokosc stringu //w pikselach if (!tokeny[i].endsWith("\n")) { if ((curX + akt) < rectW + 5) {//jesli sie miesci w ramce curX += akt;//zmienia pozycje "glowicy" } else {//jesli sie nie miesci curX = 5;//to przenosi "glowice" na poczatk curY += fm.getHeight();//i do nowego wiersza rows++;//dodaje nowy rzad curX += akt;//zmienia pozycje "glowicy" } } else { curX = 5;//to przenosi "glowice" na poczatk curY += fm.getHeight();//i do nowego wiersza rows++;//dodaje nowy rzad curX += akt;//zmienia pozycje "glowicy" } } } else { curY += textH; curX = 5; curY += scaledHeights[k] + textH; rows += scaledHeights[k] / textH + 2; } } } } else { for (i = 0; i < tokeny.length; i++) { akt = fm.stringWidth(tokeny[i]);//sprawdza szerokosc stringu //w pikselach //w pikselach if (!tokeny[i].endsWith("\n")) {//jesli token nie ma znaku lamania if ((curX + akt) < rectW + 5) {//jesli sie miesci w ramce curX += akt;//zmienia pozycje "glowicy" } else {//jesli sie nie miesci curX = 5;//to przenosi "glowice" na poczatk curY += fm.getHeight();//i do nowego wiersza rows++;//dodaje nowy rzad curX += akt;//zmienia pozycje "glowicy" } } else { curX = 5;//to przenosi "glowice" na poczatk curY += fm.getHeight();//i do nowego wiersza rows++;//dodaje nowy rzad curX += akt;//zmienia pozycje "glowicy" } } } int dismissDelay = 4000;//czas wyswietalnia if (rows > 4) { dismissDelay = rows * 1300;//uzalezniony od dlugosci tekstu do //przeczytania } //ustawienie tych wartosci ToolTipManager.sharedInstance().setDismissDelay(dismissDelay); //okreslenie wymiarow z uwzglednieniem margnesow(8) i (5) oraz //odstepow miedzytekstowych(+1) return new Dimension(rectW + 8, rows * (fm.getHeight() + 1) + 5); } }
MyButton.java
package tooltip2; import java.awt.*; import java.awt.image.*; import java.util.*; import javax.swing.*; public class MyButton extends JButton { private static final long serialVersionUID = 3602182470147405262L; private BufferedImage[] bimages = new BufferedImage[0]; private double[] scales = new double[0]; private int[] afterTokenNos = new int[0]; private final ImageToolTip tip = new ImageToolTip(); MyButton() { super(); } MyButton(Action a) { super(a); } MyButton(Icon icon) { super(icon); } MyButton(String text) { super(text); } MyButton(String text, Icon icon) { super(text, icon); } /** * Tworzy podpowiedz dla komponentu MyButton */ @Override public JToolTip createToolTip() { tip.setComponent(this); tip.setBackground(Color.GREEN); tip.setForeground(Color.BLUE); tip.setFont(new Font("Dialog", Font.ITALIC, 14)); return tip; } public void setToolTipBimage(BufferedImage bimage, double scale, int afterTokenNo) { int len = bimages.length; bimages = Arrays.copyOf(bimages, len + 1); scales = Arrays.copyOf(scales, len + 1); afterTokenNos = Arrays.copyOf(afterTokenNos, len + 1); bimages[len] = bimage; scales[len] = scale; afterTokenNos[len] = afterTokenNo; tip.setImage(bimage, scale, afterTokenNo); } public void removeToolTipBimage(int imageNumber) { bimages = ImageToolTip.shortenArray(bimages, imageNumber); scales = ImageToolTip.shortenArray(scales, imageNumber); afterTokenNos = ImageToolTip.shortenArray(afterTokenNos, imageNumber); tip.removeImage(imageNumber); } public void removeTooltipBimages() { bimages = new BufferedImage[0]; scales = new double[0]; afterTokenNos = new int[0]; tip.removeImages(); } }
Main.java
package tooltip2; import java.awt.*; import java.awt.image.*; import java.io.*; import javax.imageio.*; import javax.swing.*; /** * ramka + panel */ public class Main extends JFrame { private static final long serialVersionUID = 1L; public static void main(String[] args) { SwingUtilities.invokeLater(() -> new Main()); } public Main() { setLayout(null); setPreferredSize(new Dimension(800, 800)); int frameHeight = 800; int frameWidth = 800; setBounds((Toolkit.getDefaultToolkit().getScreenSize().width / 2) - (frameWidth / 2), (Toolkit.getDefaultToolkit() .getScreenSize().height / 2) - (frameHeight / 2), frameWidth, frameHeight); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setTitle("Ramka Graficzna"); // setResizable(false); JPanel panel = new JPanel(); panel.setLayout(null); panel.setBounds(0, 0, frameWidth, frameHeight); MyButton but = new MyButton("Button"); BufferedImage bimage = fileToBimage("swings/src/assets/smok.jpg"); BufferedImage bimage1 = fileToBimage("swings/src/assets/smok1.jpg"); BufferedImage bimage2 = fileToBimage("swings/src/assets/smok2.jpg"); String str = "Jest to moment konkretyzowania planów. Wskazuje na początek dziaia albo nowy fakt w życiu pytającego. Należy uważnie rozważyć karty sąsiednie. Karta ta oznacza przede wszystkim pracę, plany i dzieci. Może wskazywać na początek nowego, ważnego etapu w życiu pytającego. Spotkanie z drugim człowiekiem (w towarzystwie karty Diabeł - akt seksualny). Niezależność jest korzystna dla pytającego. Pytający może narzucić swoją wolę, kontynuować wysiłek i zrealizować plany. Karta ta często reprezentuje młodą osobę (o ile nie występuje w towarzystwie karty Pustelnik), albo pytającego, o ile nie przekroczył czterdziestki. Mag w sąsiedztwie karty Słoñce reprezentuje miłość męską."; but.setToolTipBimage(bimage, 0.2, ImageToolTip.findTokenNo(str, "planów.", " ")); but.setToolTipBimage(bimage1, 0.5, ImageToolTip.findTokenNo(str, "działania", " ")); but.setToolTipBimage(bimage2, 0.7, ImageToolTip.findTokenNo(str, "uważnie", " ")); but.removeToolTipBimage(0); but.setToolTipText(str); but.setBounds(100, 100, 100, 40); panel.add(but); add(panel); setVisible(true); } /** * Wczytuje obrazek z pliku i zamienia go na BufferedImage * Ta metoda, z nieznanych mi przyczyn nie chciała działać z filtrami * <code>RescaleOp</code> i <code>ConvolveOp</code>, czyli np. blur(), * sharp(), brightness() * * @param plik String - wskazuje plik z obrazkiem * @return BufferedImage - zwraca obrazek buforowany znajdujący się w pliku */ public static BufferedImage fileToBimage(String plik) { File f = new File(plik); BufferedImage bimage = null; try { bimage = ImageIO.read(f); } catch (IOException e) { e.printStackTrace(); } return bimage; } }
Wynik
Po uruchomieniu klasy Main i najechaniu kursorem myszy na przycisk zobaczymy: