From 39cb81d7581e3359f73d2f672340ebe169b4aa09 Mon Sep 17 00:00:00 2001 From: Snowyfox Date: Wed, 1 Jun 2022 20:44:06 -0400 Subject: [PATCH] I think I fixed it.. The image scaling issue Added prototype for media descriptions --- ComposeWindow.java | 102 ++++++++++++++++++++++++++--------- ImageApi.java | 5 ++ MastodonApi.java | 11 ++-- PostWindow.java | 49 +---------------- TwoToggleButton.java | 108 ++++++++++++++++++++++++++----------- graphics/nextToggled.png | Bin 3704 -> 3646 bytes graphics/nextUntoggled.png | Bin 3703 -> 3646 bytes 7 files changed, 166 insertions(+), 109 deletions(-) diff --git a/ComposeWindow.java b/ComposeWindow.java index 2858fc0..0089040 100644 --- a/ComposeWindow.java +++ b/ComposeWindow.java @@ -130,7 +130,9 @@ ComposeWindow extends JFrame { if (a.id != null) continue; // Assume it had already been uploaded. - api.uploadFile(a.uploadee, new RequestListener() { + api.uploadFile( + a.uploadee, a.description, + new RequestListener() { public void connectionFailed(IOException eIo) @@ -561,9 +563,6 @@ implements ActionListener { private JPanel selections; - private ButtonGroup - selectionsGroup; - private JToggleButton attachment1, attachment2, @@ -621,29 +620,29 @@ implements ActionListener { if (working.size() > 3) { - attachment4.doClick(); Image i = working.get(3).image; attachment4.setIcon(new ImageIcon(i)); } else if (working.size() > 2) { - attachment3.doClick(); Image i = working.get(2).image; attachment3.setIcon(new ImageIcon(i)); } else if (working.size() > 1) { - attachment2.doClick(); Image i = working.get(1).image; attachment2.setIcon(new ImageIcon(i)); } else if (working.size() > 0) { - attachment1.doClick(); Image i = working.get(0).image; attachment1.setIcon(new ImageIcon(i)); } - else selectionsGroup.clearSelection(); + + attachment4.setSelected(working.size() > 3); + attachment3.setSelected(working.size() > 2); + attachment2.setSelected(working.size() > 1); + attachment1.setSelected(working.size() > 0); int bw = sz.width; int hgap = 4; @@ -661,11 +660,6 @@ implements ActionListener { { Object src = eA.getSource(); - if (false) - { - // Clicked on filled attachment button. - } - if (src == add) { int r = chooser.showOpenDialog(this); @@ -697,40 +691,93 @@ implements ActionListener { if (src == delete) { - Object sm = selectionsGroup.getSelection(); - if (sm == attachment1.getModel()) + if (attachment1.isSelected()) { assert working.size() > 0; working.remove(0); - updateButtons(); } - if (sm == attachment2.getModel()) + if (attachment2.isSelected()) { assert working.size() > 1; working.remove(1); - updateButtons(); } - if (sm == attachment3.getModel()) + if (attachment3.isSelected()) { assert working.size() > 2; working.remove(2); - updateButtons(); } - if (sm == attachment4.getModel()) + if (attachment4.isSelected()) { assert working.size() > 3; working.remove(3); - updateButtons(); } + updateButtons(); return; } + if (src != add && selections.isAncestorOf((Component)src)) + { + int iCurr = getSelectedIndex(); + int iNext = getIndex(src); + System.err.println(iCurr + ":" + iNext); + assert iNext != 0; + if (iCurr == iNext) { + save(); + return; + } + if (iCurr != 0) save(); + load(iNext); + } + if (src == revert) { - return; + assert getSelectedIndex() != 0; + // Should the controls be disabled until + // there is an attachment? + load(getSelectedIndex()); } } + private int + getIndex(Object src) + { + if (src == attachment4) return 4; + if (src == attachment3) return 3; + if (src == attachment2) return 2; + if (src == attachment1) return 1; + return 0; + } + + private int + getSelectedIndex() + { + if (attachment4.isSelected()) return 4; + if (attachment3.isSelected()) return 3; + if (attachment2.isSelected()) return 2; + if (attachment1.isSelected()) return 1; + return 0; + } + + private void + save() + { + int index = getSelectedIndex(); + assert index != 0; + + Attachment a = working.get(index - 1); + a.description = this.description.getText(); + } + + private void + load(int index) + { + assert index > 0; + assert working.size() >= index; + + Attachment a = working.get(index - 1); + this.description.setText(a.description); + } + // ---%-@-%--- AttachmentsComponent(ComposeWindow primaire) @@ -762,17 +809,20 @@ implements ActionListener { attachment2.setMargin(add.getMargin()); attachment3.setMargin(add.getMargin()); attachment4.setMargin(add.getMargin()); + attachment1.addActionListener(this); + attachment2.addActionListener(this); + attachment3.addActionListener(this); + attachment4.addActionListener(this); selections = new JPanel(); selections.setOpaque(false); selections.setLayout(new GridLayout(1, 0, 4, 0)); working = new ArrayList(); - selectionsGroup = new ButtonGroup(); + ButtonGroup selectionsGroup = new ButtonGroup(); selectionsGroup.add(attachment1); selectionsGroup.add(attachment2); selectionsGroup.add(attachment3); selectionsGroup.add(attachment4); - // Have to add selection listener to button group updateButtons(); JButton del = new JButton("D"); diff --git a/ImageApi.java b/ImageApi.java index d41d1e8..22eb144 100644 --- a/ImageApi.java +++ b/ImageApi.java @@ -2,6 +2,11 @@ import javax.swing.ImageIcon; import java.awt.Image; import java.awt.Toolkit; +import java.awt.MediaTracker; +import java.awt.Graphics; +import java.awt.Component; +import java.awt.image.ImageObserver; +import java.awt.image.BufferedImage; import java.net.URL; import java.net.MalformedURLException; diff --git a/MastodonApi.java b/MastodonApi.java index 4624b18..9ac4b6c 100644 --- a/MastodonApi.java +++ b/MastodonApi.java @@ -435,9 +435,10 @@ MastodonApi { } public void - uploadFile(File file, RequestListener handler) + uploadFile(File file, String alt, RequestListener handler) { assert file != null; + assert alt != null; assert file.canRead(); String bct = @@ -496,10 +497,12 @@ MastodonApi { String url = instanceUrl + "/api/v1/media/"; try { - URL endpoint = new URL(url); + String s1 = "?description=" + encode(alt); + + URL endpoint = new URL(url + s1); HttpURLConnection conn = cast(endpoint.openConnection()); - String s1 = "Bearer " + token; - conn.setRequestProperty("Authorization", s1); + String s2 = "Bearer " + token; + conn.setRequestProperty("Authorization", s2); conn.setDoOutput(true); conn.setRequestMethod("POST"); conn.setFixedLengthStreamingMode(contentLength); diff --git a/PostWindow.java b/PostWindow.java index bfd42e4..8b42487 100644 --- a/PostWindow.java +++ b/PostWindow.java @@ -429,54 +429,7 @@ implements ActionListener { setAuthorId(String n) { authorId.setText(n); } public void - setAuthorAvatar(Image n) - { - try { - MediaTracker mt = new MediaTracker(this); - mt.addImage(n, 0); - mt.waitForID(0); - } - catch (InterruptedException eIt) { - assert false; - } - /* - * Blocks until the image loads. We have to do this because - * there is an unfixed JDK bug in Image#getScaledInstance - * (which RoundButton uses) where when you pass it an AWT - * image, it incorrectly sets the ColorModel's transfer type, - * causing a ClassCastException, and returns a valid image - * with blank data. It seems using loaded images does not - * replicate the issue, which would explain why the bug - * hasn't been found and fixed.. - * - * For future readers wondering. JDK 11, up to JDK 15. - * java.awt.image.ColorModel#getAlpha(:845), who was called - * sun.awt.image.ImageRepresentation#convertToRGB, who was - * called by AreaAveragingScaleFilter trying to #setPixels. - * Cause RoundButton was using Image.SCALE_SMOOTH. - * - * I guess it kind of makes sense, why would I try to get a - * scaled instance of an image that hadn't finished loading. - * An intelligent class would render the awt.Image as-is until - * it has completely loaded, then call #getScaledInstance - * instead. I won't have RoundButton do that for now, as - * my other classes like RichTextPane3 might have this too. - * - * If you asked me how I'd generalise the solution. My - * expectation had been that #getScaledInstance waits for - * the image too, returning a null image until it's done. - * So I'd put a #getScaledInstance in ImageApi, which does - * this, using MediaTracker. That assumes what I'm doing - * right now definitely works. - */ - /* - * (悪) It doesn't work. I might be forced to use - * BufferedImage with ImageIO. Or have - * ImageApi#getScaledInstance spam until it works. - */ - - profile.setImage(n); - } + setAuthorAvatar(Image n) { profile.setImage(n); } public void setDate(String n) { date.setText(n); } diff --git a/TwoToggleButton.java b/TwoToggleButton.java index 3906675..98314f7 100644 --- a/TwoToggleButton.java +++ b/TwoToggleButton.java @@ -8,6 +8,8 @@ import java.awt.Graphics; import java.awt.Dimension; import java.awt.Shape; import java.awt.Rectangle; +import java.awt.Component; +import java.awt.image.BufferedImage; import java.awt.geom.Ellipse2D; import java.awt.event.MouseListener; import java.awt.event.MouseEvent; @@ -255,6 +257,7 @@ implements KeyListener, MouseListener, FocusListener { // - -%- - private Image + copy, scaled; private int @@ -267,22 +270,78 @@ implements KeyListener, MouseListener, FocusListener { selectedOverlay, disabledOverlay; + private static Shape + roundClip; + // ---%-@-%--- public void setImage(Image n) { image = n; + copy = null; scaled = null; + + if (image != null) + { + image.flush(); + prepareImage(image, this); + } } // - -%- - public boolean - imageUpdate(Image i, int f, int x, int y, int w, int h) + imageUpdate(Image img, int f, int x, int y, int w, int h) { - if ((f | WIDTH) != 0) repaint(); - return super.imageUpdate(i, f, x, y, w, h); + // AbstractButton overrode this to refuse updates for + // images that aren't the button's icon. We don't use + // the icon, so we're overriding it back. Also, we have + // some async work to do regarding the images. + + if ((f & (ABORT|ERROR)) != 0) return false; + + boolean all = (f & ALLBITS) != 0; + boolean frame = (f & FRAMEBITS) != 0; + boolean some = (f & SOMEBITS) != 0; + + if (frame && img != this.image) return false; + + if (img == this.image && (all || frame)) + { + int ow = img.getWidth(null); + int oh = img.getHeight(null); + + if (copy == null) + { + int type = BufferedImage.TYPE_INT_ARGB; + copy = new BufferedImage(ow, oh, type); + } + + Graphics g = copy.getGraphics(); + g.drawImage(img, 0, 0, null); + g.dispose(); + + int algo = Image.SCALE_SMOOTH; + Rectangle b = roundClip.getBounds(); + int sw = ow > oh ? -1 : b.width; + int sh = oh > ow ? -1 : b.height; + scaled = copy.getScaledInstance(sw, sh, algo); + /* + * We create a scaled instance from a BufferedImage copy + * rather than this.image directly, to avoid a ClassCast + * Exception bug in the JDK, where ColorModel was + * incorrectly casting an int array of input data into + * a byte array. I'm not sure why that bug exists nor + * why they haven't noticed it, but. + */ + repaint(); + } + if (img == scaled && (some || all)) + { + repaint(); + } + return all ? false : true; } protected void @@ -294,34 +353,13 @@ implements KeyListener, MouseListener, FocusListener { if (isFocusOwner()) g.drawImage(selectedOverlay, 0, 0, this); - if (image == null) return; - - int ow = image.getWidth(this); - int oh = image.getHeight(this); - int nx = 6; - int ny = 6; - int nw = button.getWidth(this) - 12; - int nh = button.getHeight(this) - 12; - Shape defaultClip, roundClip; - defaultClip = g.getClip(); - roundClip = new Ellipse2D.Float(nx, ny, nw, nh); - - if (scaled == null) - { - int sw = ow > oh ? -1 : nw; - int sh = oh > ow ? -1 : nh; - // We do not defend against unloaded images. There is - // a bug on those anyways, see comment in PostWindow. - scaled = image.getScaledInstance( - sw, sh, - Image.SCALE_SMOOTH - ); - } - // (知) Be careful to be idempotent when calling - // async methods like #drawImage. + if (scaled == null) return; + Rectangle b = roundClip.getBounds(); + Shape defaultClip = g.getClip(); g.setClip(roundClip); - g.drawImage(scaled, nx, ny, getParent()); + g.drawImage(scaled, b.x, b.y, this); + getParent().repaint(); // I don't know why, but when we repaint ourselves, our // parent doesn't repaint, so nothing seems to happen. g.setClip(defaultClip); @@ -383,8 +421,8 @@ implements KeyListener, MouseListener, FocusListener { setFocusable(true); setOpaque(false); - int w = button.getWidth(this); - int h = button.getHeight(this); + int w = button.getWidth(null); + int h = button.getHeight(null); setPreferredSize(new Dimension(w, h)); this.addKeyListener(this); @@ -405,6 +443,14 @@ implements KeyListener, MouseListener, FocusListener { button = new ImageIcon(u1).getImage(); disabledOverlay = new ImageIcon(u2).getImage(); selectedOverlay = new ImageIcon(u3).getImage(); + + int radius = 6; + roundClip = new Ellipse2D.Float( + radius, + radius, + button.getWidth(null) - (2 * radius), + button.getHeight(null) - (2 * radius) + ); } } diff --git a/graphics/nextToggled.png b/graphics/nextToggled.png index f288ec443972e56f0c4d1909a3fd233abbc2339e..3d288abfc03edede69b9ef696295a8ab0faeb903 100755 GIT binary patch delta 3557 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 delta 3615 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 diff --git a/graphics/nextUntoggled.png b/graphics/nextUntoggled.png index 4f70b6cb4b435ceb2b7cb5a5272bf3ab2bbc8c2a..04cb7a3a09b2723920450179c50f2c7036a97a27 100755 GIT binary patch delta 3557 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 delta 3614 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 kM513Zgb+dqA%u`s4xUYNg^rj