From fa9a5edab0712701bf40980577a0d294abb847f1 Mon Sep 17 00:00:00 2001 From: Snowyfox Date: Thu, 2 Jun 2022 14:41:59 -0400 Subject: [PATCH] Normalised all tabs to 4 spaces, for easier web viewing. --- BasicHTMLParser.java | 614 +++++++++++++-------------- ClipboardApi.java | 54 +-- ComposeWindow.java | 562 ++++++++++++------------- ImageWindow.java | 466 ++++++++++----------- JKomasto.java | 406 +++++++++--------- JKomasto2.jar | Bin 430864 -> 436532 bytes LoginWindow.java | 554 ++++++++++++------------- MastodonApi.java | 192 ++++----- NotificationsWindow.java | 554 ++++++++++++------------- PostWindow.java | 484 +++++++++++----------- ProfileWindow.java | 6 +- RepliesWindow.java | 20 +- RequestListener.java | 12 +- RichTextPane.java | 658 ++++++++++++++--------------- RichTextPane2.java | 822 ++++++++++++++++++------------------- RichTextPane3.java | 804 ++++++++++++++++++------------------ RudimentaryHTMLParser.java | 118 +++--- TimelineWindow.java | 552 ++++++++++++------------- TwoToggleButton.java | 256 ++++++------ WindowUpdater.java | 116 +++--- 20 files changed, 3625 insertions(+), 3625 deletions(-) diff --git a/BasicHTMLParser.java b/BasicHTMLParser.java index bd106b8..386501d 100644 --- a/BasicHTMLParser.java +++ b/BasicHTMLParser.java @@ -29,308 +29,308 @@ import cafe.biskuteri.hinoki.Tree; interface BasicHTMLParser { - public static Tree - parse(String html) - { - List segments; - segments = distinguishTagsFromPcdata(html); + public static Tree + parse(String html) + { + List segments; + segments = distinguishTagsFromPcdata(html); - Tree document; - document = toNodes(segments); - document = splitText(document); - document = evaluateHtmlEscapes(document); - document = hierarchise(document); + Tree document; + document = toNodes(segments); + document = splitText(document); + document = evaluateHtmlEscapes(document); + document = hierarchise(document); - return document; - } + return document; + } -// - -%- - +// - -%- - - private static List - distinguishTagsFromPcdata(String html) - { - List returnee = new ArrayList<>(); - StringBuilder segment = new StringBuilder(); - boolean inTag = false; - for (char c: html.toCharArray()) - { - if (c == '<') - { - String addee = empty(segment); - if (!addee.isEmpty()) returnee.add(addee); - inTag = true; - segment.append(c); - } - else if (c == '>') - { - assert inTag; - assert segment.length() > 0; - segment.append(c); - returnee.add(empty(segment)); - inTag = false; - } - else - { - segment.append(c); - } - } - String addee = empty(segment); - if (!addee.isEmpty()) returnee.add(addee); + private static List + distinguishTagsFromPcdata(String html) + { + List returnee = new ArrayList<>(); + StringBuilder segment = new StringBuilder(); + boolean inTag = false; + for (char c: html.toCharArray()) + { + if (c == '<') + { + String addee = empty(segment); + if (!addee.isEmpty()) returnee.add(addee); + inTag = true; + segment.append(c); + } + else if (c == '>') + { + assert inTag; + assert segment.length() > 0; + segment.append(c); + returnee.add(empty(segment)); + inTag = false; + } + else + { + segment.append(c); + } + } + String addee = empty(segment); + if (!addee.isEmpty()) returnee.add(addee); - return returnee; - } + return returnee; + } - private static Tree - toNodes(List segments) - { - Tree returnee = new Tree(); + private static Tree + toNodes(List segments) + { + Tree returnee = new Tree(); - for (String segment: segments) - { - boolean isTag = segment.startsWith("<"); - Tree node = new Tree(); + for (String segment: segments) + { + boolean isTag = segment.startsWith("<"); + Tree node = new Tree(); - if (!isTag) - { - node.key = "text"; - node.value = segment; - returnee.add(node); - continue; - } + if (!isTag) + { + node.key = "text"; + node.value = segment; + returnee.add(node); + continue; + } - node.key = "tag"; + node.key = "tag"; - String key = null, value = null; - StringBuilder b = new StringBuilder(); - boolean inQuotes = false, inValue = false; - char[] chars = segment.toCharArray(); - for (int o = 1; o < chars.length - 1; ++o) - { - char c = chars[o]; - if (c == '"') - { - inQuotes = !inQuotes; - } - else if (inQuotes) - { - b.append(c); - } - else if (c == '=') - { - assert b.length() > 0; - key = empty(b); - inValue = true; - } - else if (Character.isWhitespace(c)) - { - if (b.length() > 0) - { - if (inValue) value = empty(b); - else key = empty(b); - Tree attr = new Tree(); - attr.key = key; - attr.value = value; - node.add(attr); - } - inValue = false; - } - else - { - b.append(c); - } - } - if (b.length() > 0) - { - if (inValue) value = empty(b); - else key = empty(b); - Tree attr = new Tree(); - attr.key = key; - attr.value = value; - node.add(attr); - } + String key = null, value = null; + StringBuilder b = new StringBuilder(); + boolean inQuotes = false, inValue = false; + char[] chars = segment.toCharArray(); + for (int o = 1; o < chars.length - 1; ++o) + { + char c = chars[o]; + if (c == '"') + { + inQuotes = !inQuotes; + } + else if (inQuotes) + { + b.append(c); + } + else if (c == '=') + { + assert b.length() > 0; + key = empty(b); + inValue = true; + } + else if (Character.isWhitespace(c)) + { + if (b.length() > 0) + { + if (inValue) value = empty(b); + else key = empty(b); + Tree attr = new Tree(); + attr.key = key; + attr.value = value; + node.add(attr); + } + inValue = false; + } + else + { + b.append(c); + } + } + if (b.length() > 0) + { + if (inValue) value = empty(b); + else key = empty(b); + Tree attr = new Tree(); + attr.key = key; + attr.value = value; + node.add(attr); + } - returnee.add(node); - } + returnee.add(node); + } - return returnee; - } + return returnee; + } - private static Tree - splitText(Tree nodes) - { - Tree returnee = new Tree<>(); + private static Tree + splitText(Tree nodes) + { + Tree returnee = new Tree<>(); - for (Tree node: nodes) - { - if (node.key.equals("tag")) - { - returnee.add(node); - continue; - } - assert node.key.equals("text"); + for (Tree node: nodes) + { + if (node.key.equals("tag")) + { + returnee.add(node); + continue; + } + assert node.key.equals("text"); - StringBuilder b = new StringBuilder(); - boolean alnum = false, calnum; - boolean space = false, cspace; - boolean emoji = false; - for (char c: node.value.toCharArray()) - { - calnum = isMastodonAlnum(c); - cspace = Character.isWhitespace(c); + StringBuilder b = new StringBuilder(); + boolean alnum = false, calnum; + boolean space = false, cspace; + boolean emoji = false; + for (char c: node.value.toCharArray()) + { + calnum = isMastodonAlnum(c); + cspace = Character.isWhitespace(c); - if (c == ':' && !emoji) - { + if (c == ':' && !emoji) + { // See note on #isMastodonAlnum. - if (b.length() > 0) - { - Tree addee = new Tree<>(); - addee.key = space ? "space" : "text"; - addee.value = empty(b); - returnee.add(addee); - } - emoji = true; - b.append(c); - } - else if (c == ':' && emoji) - { - assert !space; - b.append(c); - Tree addee = new Tree<>(); - addee.key = "emoji"; - addee.value = empty(b); - returnee.add(addee); - /* - * Technically, addee.value.length() - * could be zero, which probably means - * someone just put two colons in a row, - * maybe for Haskell source code. I'd - * be surprised if Mastodon didn't escape - * it. (If they did, the next step will - * handle them.) Anyways treating it as - * an empty emoji is the correct action. - */ - emoji = false; - calnum = false; - } - else if (cspace != space) - { if (b.length() > 0) - { - Tree addee = new Tree<>(); - addee.key = space ? "space" : "text"; - addee.value = empty(b); - returnee.add(addee); - } - b.append(c); - } - else - { - b.append(c); - } - /* - * We can specially handle special - * characters like \n, but I'll opt not to. - */ + { + Tree addee = new Tree<>(); + addee.key = space ? "space" : "text"; + addee.value = empty(b); + returnee.add(addee); + } + emoji = true; + b.append(c); + } + else if (c == ':' && emoji) + { + assert !space; + b.append(c); + Tree addee = new Tree<>(); + addee.key = "emoji"; + addee.value = empty(b); + returnee.add(addee); + /* + * Technically, addee.value.length() + * could be zero, which probably means + * someone just put two colons in a row, + * maybe for Haskell source code. I'd + * be surprised if Mastodon didn't escape + * it. (If they did, the next step will + * handle them.) Anyways treating it as + * an empty emoji is the correct action. + */ + emoji = false; + calnum = false; + } + else if (cspace != space) + { + if (b.length() > 0) + { + Tree addee = new Tree<>(); + addee.key = space ? "space" : "text"; + addee.value = empty(b); + returnee.add(addee); + } + b.append(c); + } + else + { + b.append(c); + } + /* + * We can specially handle special + * characters like \n, but I'll opt not to. + */ - alnum = calnum; - space = cspace; - } - if (b.length() > 0) - { - Tree addee = new Tree<>(); - addee.key = space ? "space" : "text"; - addee.value = empty(b); - returnee.add(addee); - } - } + alnum = calnum; + space = cspace; + } + if (b.length() > 0) + { + Tree addee = new Tree<>(); + addee.key = space ? "space" : "text"; + addee.value = empty(b); + returnee.add(addee); + } + } - return returnee; - } + return returnee; + } - private static Tree - evaluateHtmlEscapes(Tree nodes) - { - for (Tree node: nodes) - { - node.value = evaluateHtmlEscapes(node.value); - for (Tree attr: node) - { - attr.key = evaluateHtmlEscapes(attr.key); - attr.value = evaluateHtmlEscapes(attr.value); - } - } + private static Tree + evaluateHtmlEscapes(Tree nodes) + { + for (Tree node: nodes) + { + node.value = evaluateHtmlEscapes(node.value); + for (Tree attr: node) + { + attr.key = evaluateHtmlEscapes(attr.key); + attr.value = evaluateHtmlEscapes(attr.value); + } + } - return nodes; - } + return nodes; + } - private static Tree - hierarchise(Tree nodes) - { - Tree root = new Tree(); - root.key = "tag"; - root.add(new Tree<>("html", null)); - root.add(new Tree<>("children", null)); + private static Tree + hierarchise(Tree nodes) + { + Tree root = new Tree(); + root.key = "tag"; + root.add(new Tree<>("html", null)); + root.add(new Tree<>("children", null)); - Deque> parents = new LinkedList<>(); - parents.push(root); - for (Tree node: nodes) - { - if (node.key.equals("tag")) - { - assert node.size() > 0; - String tagName = node.get(0).key; + Deque> parents = new LinkedList<>(); + parents.push(root); + for (Tree node: nodes) + { + if (node.key.equals("tag")) + { + assert node.size() > 0; + String tagName = node.get(0).key; - assert node.get("children") == null; - node.add(new Tree<>("children", null)); + assert node.get("children") == null; + node.add(new Tree<>("children", null)); - boolean isClosing, selfClosing; - isClosing = tagName.startsWith("/"); - selfClosing = node.get("/") != null; - selfClosing |= tagName.equals("br"); - if (isClosing) - { - assert parents.size() > 1; + boolean isClosing, selfClosing; + isClosing = tagName.startsWith("/"); + selfClosing = node.get("/") != null; + selfClosing |= tagName.equals("br"); + if (isClosing) + { + assert parents.size() > 1; - Tree parent, grandparent; - parent = parents.pop(); - grandparent = parents.peek(); + Tree parent, grandparent; + parent = parents.pop(); + grandparent = parents.peek(); - String pTagName = parent.get(0).key; - assert tagName.equals("/" + pTagName); + String pTagName = parent.get(0).key; + assert tagName.equals("/" + pTagName); - grandparent.get("children").add(parent); - } - else if (selfClosing) - { - parents.peek().get("children").add(node); - } - else - { - parents.push(node); - } - } - else - { - parents.peek().get("children").add(node); - } - } + grandparent.get("children").add(parent); + } + else if (selfClosing) + { + parents.peek().get("children").add(node); + } + else + { + parents.push(node); + } + } + else + { + parents.peek().get("children").add(node); + } + } - assert parents.size() == 1; - return parents.pop(); - } + assert parents.size() == 1; + return parents.pop(); + } - private static String - empty(StringBuilder b) - { - String s = b.toString(); - b.delete(0, b.length()); - return s; - } + private static String + empty(StringBuilder b) + { + String s = b.toString(); + b.delete(0, b.length()); + return s; + } - private static boolean - isMastodonAlnum(char c) - { + private static boolean + isMastodonAlnum(char c) + { return Character.isLetterOrDigit(c); /* * Not joking. Mastodon is using the POSIX :alnum: regex @@ -343,44 +343,44 @@ BasicHTMLParser { * by text, then try again with the same emoji also * present elsewhere in the post at a valid position.) */ - } + } - private static String - evaluateHtmlEscapes(String string) - { - if (string == null) return string; + private static String + evaluateHtmlEscapes(String string) + { + if (string == null) return string; - StringBuilder whole = new StringBuilder(); - StringBuilder part = new StringBuilder(); - boolean inEscape = false; - for (char c: string.toCharArray()) - { - if (inEscape && c == ';') - { - part.append(c); - inEscape = false; - String v = empty(part); - if (v.equals("<")) part.append('<'); - if (v.equals(">")) part.append('>'); - if (v.equals("&")) part.append('&'); - if (v.equals(""")) part.append('"'); - if (v.equals("'")) part.append('\''); - if (v.equals("'")) part.append('\''); - } - else if (!inEscape && c == '&') - { - String v = empty(part); - if (!v.isEmpty()) whole.append(v); - part.append(c); - inEscape = true; - } - else - { - part.append(c); - } - } - String v = empty(part); - if (!v.isEmpty()) whole.append(v); - return whole.toString(); - } + StringBuilder whole = new StringBuilder(); + StringBuilder part = new StringBuilder(); + boolean inEscape = false; + for (char c: string.toCharArray()) + { + if (inEscape && c == ';') + { + part.append(c); + inEscape = false; + String v = empty(part); + if (v.equals("<")) part.append('<'); + if (v.equals(">")) part.append('>'); + if (v.equals("&")) part.append('&'); + if (v.equals(""")) part.append('"'); + if (v.equals("'")) part.append('\''); + if (v.equals("'")) part.append('\''); + } + else if (!inEscape && c == '&') + { + String v = empty(part); + if (!v.isEmpty()) whole.append(v); + part.append(c); + inEscape = true; + } + else + { + part.append(c); + } + } + String v = empty(part); + if (!v.isEmpty()) whole.append(v); + return whole.toString(); + } } diff --git a/ClipboardApi.java b/ClipboardApi.java index 07571eb..694cd2b 100644 --- a/ClipboardApi.java +++ b/ClipboardApi.java @@ -41,41 +41,41 @@ implements Transferable, ClipboardOwner { { assert string != null; instance.string = string; - Toolkit tk = Toolkit.getDefaultToolkit(); - Clipboard cb = tk.getSystemClipboard(); - cb.setContents(instance, instance); + Toolkit tk = Toolkit.getDefaultToolkit(); + Clipboard cb = tk.getSystemClipboard(); + cb.setContents(instance, instance); } // - -%- - - public String - getTransferData(DataFlavor flavour) - { + public String + getTransferData(DataFlavor flavour) + { assert flavour == DataFlavor.stringFlavor; return string; - } + } - public DataFlavor[] - getTransferDataFlavors() - { - return new DataFlavor[] { DataFlavor.stringFlavor }; - /* - * We should probably also support javaJVMLocalObjectMimeType, - * so that the compose window can ask for the List. - * Although also like, if we don't store emoji shortcodes in - * the image segments, there is no point. Anyways, what is - * important is the string format first, allowing us to - * copy links or large lengths of text. - */ - } + public DataFlavor[] + getTransferDataFlavors() + { + return new DataFlavor[] { DataFlavor.stringFlavor }; + /* + * We should probably also support javaJVMLocalObjectMimeType, + * so that the compose window can ask for the List. + * Although also like, if we don't store emoji shortcodes in + * the image segments, there is no point. Anyways, what is + * important is the string format first, allowing us to + * copy links or large lengths of text. + */ + } - public boolean - isDataFlavorSupported(DataFlavor flavour) - { - return flavour == DataFlavor.stringFlavor; - } + public boolean + isDataFlavorSupported(DataFlavor flavour) + { + return flavour == DataFlavor.stringFlavor; + } - public void - lostOwnership(Clipboard clipboard, Transferable contents) { } + public void + lostOwnership(Clipboard clipboard, Transferable contents) { } } diff --git a/ComposeWindow.java b/ComposeWindow.java index 9834c1d..02077f0 100644 --- a/ComposeWindow.java +++ b/ComposeWindow.java @@ -126,86 +126,86 @@ ComposeWindow extends JFrame { if (composition.contentWarning != null) assert !composition.contentWarning.trim().isEmpty(); - //tabs.setCursor(new Cursor(Cursor.WAIT_CURSOR)); - /* - * setCursor only works for components that are enabled. - * I don't think there's any technical reason for this, - * but it's what it is.. rely on the enablement visuals - * or a statusbar to indicate to user. - * - * If we really wanted it, I suspect we could use a glass - * pane (or a scroll pane with no scrolling) to have an - * enabled pass-through on top. - * - * Technically contentsDisplay and attachmentsDisplay - * themselves aren't disabled. But disabling the tab pane - * covers both the tab area and content area. We can't - * just the tab area, except maybe by disabling the - * individual tabs. - */ - tabs.setEnabled(false); - tabs.setSelectedComponent(contentsDisplay); - contentsDisplay.setSubmitting(true); - tabs.paintImmediately(tabs.getBounds()); + //tabs.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + /* + * setCursor only works for components that are enabled. + * I don't think there's any technical reason for this, + * but it's what it is.. rely on the enablement visuals + * or a statusbar to indicate to user. + * + * If we really wanted it, I suspect we could use a glass + * pane (or a scroll pane with no scrolling) to have an + * enabled pass-through on top. + * + * Technically contentsDisplay and attachmentsDisplay + * themselves aren't disabled. But disabling the tab pane + * covers both the tab area and content area. We can't + * just the tab area, except maybe by disabling the + * individual tabs. + */ + tabs.setEnabled(false); + tabs.setSelectedComponent(contentsDisplay); + contentsDisplay.setSubmitting(true); + tabs.paintImmediately(tabs.getBounds()); - boolean uploadsOkay = true; - for (Attachment a: composition.attachments) - { - if (a.id != null) continue; - // Assume it had already been uploaded. + boolean uploadsOkay = true; + for (Attachment a: composition.attachments) + { + if (a.id != null) continue; + // Assume it had already been uploaded. - api.uploadFile( - a.uploadee, a.description, - new RequestListener() { + api.uploadFile( + a.uploadee, a.description, + new RequestListener() { - public void - connectionFailed(IOException eIo) - { - JOptionPane.showMessageDialog( + public void + connectionFailed(IOException eIo) + { + JOptionPane.showMessageDialog( ComposeWindow.this, "Tried to upload attachment, failed..." + "\n" + eIo.getMessage() ); - } + } - public void - requestFailed(int httpCode, Tree json) - { - JOptionPane.showMessageDialog( + public void + requestFailed(int httpCode, Tree json) + { + JOptionPane.showMessageDialog( ComposeWindow.this, "Tried to upload attachment, failed..." - + "\n" + json.get("error").value + + "\n" + json.get("error").value + "\n(HTTP code: " + httpCode + ")" ); - } + } - public void - requestSucceeded(Tree json) - { - a.id = json.get("id").value; - } + public void + requestSucceeded(Tree json) + { + a.id = json.get("id").value; + } - }); - uploadsOkay &= a.id != null; - } + }); + uploadsOkay &= a.id != null; + } - if (!uploadsOkay) - { - contentsDisplay.setSubmitting(false); - tabs.setEnabled(true); - //tabs.setCursor(null); - return; - } + if (!uploadsOkay) + { + contentsDisplay.setSubmitting(false); + tabs.setEnabled(true); + //tabs.setCursor(null); + return; + } - int amt = composition.attachments.length; - String[] mediaIDs = new String[amt]; - for (int o = 0; o < mediaIDs.length; ++o) - mediaIDs[o] = composition.attachments[o].id; + int amt = composition.attachments.length; + String[] mediaIDs = new String[amt]; + for (int o = 0; o < mediaIDs.length; ++o) + mediaIDs[o] = composition.attachments[o].id; - api.submit( + api.submit( composition.text, composition.visibility, composition.replyToPostId, composition.contentWarning, - mediaIDs, + mediaIDs, new RequestListener() { public void @@ -239,8 +239,8 @@ ComposeWindow extends JFrame { ); contentsDisplay.setSubmitting(false); - tabs.setEnabled(true); - tabs.setCursor(null); + tabs.setEnabled(true); + tabs.setCursor(null); } // - -%- - @@ -254,14 +254,14 @@ ComposeWindow extends JFrame { d1.setVisibility(stringFor(composition.visibility)); d1.setContentWarning(composition.contentWarning); - AttachmentsComponent d2 = attachmentsDisplay; - d2.setAttachments(composition.attachments); + AttachmentsComponent d2 = attachmentsDisplay; + d2.setAttachments(composition.attachments); } private synchronized void syncCompositionToDisplay() { - Composition c = composition; + Composition c = composition; ComposeComponent d1 = contentsDisplay; c.text = d1.getText(); @@ -269,8 +269,8 @@ ComposeWindow extends JFrame { c.replyToPostId = nonEmpty(d1.getReplyToPostId()); c.contentWarning = nonEmpty(d1.getContentWarning()); - AttachmentsComponent d2 = attachmentsDisplay; - c.attachments = d2.getAttachments(); + AttachmentsComponent d2 = attachmentsDisplay; + c.attachments = d2.getAttachments(); } // - -%- - @@ -509,18 +509,18 @@ implements ActionListener, CaretListener, KeyListener { Border b3 = BorderFactory.createLineBorder(Color.GRAY); Border bc = BorderFactory.createCompoundBorder(b3, b2); - TextActionPopupMenu textActionPopup; - textActionPopup = new TextActionPopupMenu(); + TextActionPopupMenu textActionPopup; + textActionPopup = new TextActionPopupMenu(); reply = new JTextField(); JLabel replyLabel = new JLabel("In reply to: "); replyLabel.setLabelFor(reply); - reply.addMouseListener(textActionPopup); + reply.addMouseListener(textActionPopup); contentWarning = new JTextField(); JLabel cwLabel = new JLabel("Content warning: "); cwLabel.setLabelFor(contentWarning); - contentWarning.addMouseListener(textActionPopup); + contentWarning.addMouseListener(textActionPopup); JPanel top = new JPanel(); top.setOpaque(false); @@ -560,7 +560,7 @@ implements ActionListener, CaretListener, KeyListener { text.setBorder(bc); text.addCaretListener(this); text.addKeyListener(this); - text.addMouseListener(textActionPopup); + text.addMouseListener(textActionPopup); setLayout(new BorderLayout(0, 8)); add(top, BorderLayout.NORTH); @@ -592,7 +592,7 @@ implements ComponentListener, ActionListener { attachment2, attachment3, attachment4, - selected; + selected; private JButton add; @@ -607,83 +607,83 @@ implements ComponentListener, ActionListener { private JTextArea description; - private JFileChooser - chooser; + private JFileChooser + chooser; - private UndoManager - descriptionUndos; + private UndoManager + descriptionUndos; // ---%-@-%--- - public void - setAttachments(Attachment[] n) - { - working.clear(); - if (n != null) for (Attachment attachment: n) - { - working.add(attachment); - } - updateButtons(); - } + public void + setAttachments(Attachment[] n) + { + working.clear(); + if (n != null) for (Attachment attachment: n) + { + working.add(attachment); + } + updateButtons(); + } - public Attachment[] - getAttachments() - { - return working.toArray(new Attachment[0]); - } + public Attachment[] + getAttachments() + { + return working.toArray(new Attachment[0]); + } -// - -%- - +// - -%- - private void updateButtons() { Dimension sz = add.getPreferredSize(); - selections.removeAll(); - selected = null; + selections.removeAll(); + selected = null; if (working.size() > 0) - { - selections.add(attachment1); - Image i = working.get(0).image; - attachment1.setIcon(new ImageIcon(i)); - } - if (working.size() > 1) - { - selections.add(attachment2); - Image i = working.get(1).image; - attachment2.setIcon(new ImageIcon(i)); - } - if (working.size() > 2) - { - selections.add(attachment3); - Image i = working.get(2).image; - attachment3.setIcon(new ImageIcon(i)); - } + { + selections.add(attachment1); + Image i = working.get(0).image; + attachment1.setIcon(new ImageIcon(i)); + } + if (working.size() > 1) + { + selections.add(attachment2); + Image i = working.get(1).image; + attachment2.setIcon(new ImageIcon(i)); + } + if (working.size() > 2) + { + selections.add(attachment3); + Image i = working.get(2).image; + attachment3.setIcon(new ImageIcon(i)); + } if (working.size() > 3) - { - selections.add(attachment4); - Image i = working.get(3).image; - attachment4.setIcon(new ImageIcon(i)); - } - if (working.size() < 4) selections.add(add); + { + selections.add(attachment4); + Image i = working.get(3).image; + attachment4.setIcon(new ImageIcon(i)); + } + if (working.size() < 4) selections.add(add); - if (working.size() > 3) open(attachment4); - else if (working.size() > 2) open(attachment3); - else if (working.size() > 1) open(attachment2); - else if (working.size() > 0) open(attachment1); + if (working.size() > 3) open(attachment4); + else if (working.size() > 2) open(attachment3); + else if (working.size() > 1) open(attachment2); + else if (working.size() > 0) open(attachment1); - int bw = sz.width; + int bw = sz.width; int hgap = 4; int count = selections.getComponents().length; int w = count * bw + (count - 1) * hgap; int h = bw; selections.setPreferredSize(new Dimension(w, h)); selections.setMaximumSize(new Dimension(w, h)); - selections.revalidate(); + selections.revalidate(); - delete.setEnabled(selected != null); - revert.setEnabled(selected != null); - description.setEnabled(selected != null); + delete.setEnabled(selected != null); + revert.setEnabled(selected != null); + description.setEnabled(selected != null); } public void @@ -693,34 +693,34 @@ implements ComponentListener, ActionListener { if (src == add) { - int r = chooser.showOpenDialog(this); - if (r != JFileChooser.APPROVE_OPTION) return; + int r = chooser.showOpenDialog(this); + if (r != JFileChooser.APPROVE_OPTION) return; - File f = chooser.getSelectedFile(); + File f = chooser.getSelectedFile(); - Attachment a = new Attachment(); - a.uploadee = f; - a.description = ""; - a.type = "unknown"; + Attachment a = new Attachment(); + a.uploadee = f; + a.description = ""; + a.type = "unknown"; - String mime = "", primary = ""; - try - { - mime = Files.probeContentType(f.toPath()); - primary = mime.split("/")[0]; - // Too lazy to instantiate a - // javax.activation.MimeType. - } - catch (IOException eIo) { } - if (primary.equals("image")) - { - String urlr = f.toURI().toString(); - a.image = ImageApi.remote(urlr); - a.type = "image"; - } + String mime = "", primary = ""; + try + { + mime = Files.probeContentType(f.toPath()); + primary = mime.split("/")[0]; + // Too lazy to instantiate a + // javax.activation.MimeType. + } + catch (IOException eIo) { } + if (primary.equals("image")) + { + String urlr = f.toURI().toString(); + a.image = ImageApi.remote(urlr); + a.type = "image"; + } - if (selected != null) open(selected); - // Save first before resetting + if (selected != null) open(selected); + // Save first before resetting working.add(a); updateButtons(); @@ -728,87 +728,87 @@ implements ComponentListener, ActionListener { if (src == delete) { - assert selected != null; - working.remove(getAttachmentFor(selected)); - updateButtons(); + assert selected != null; + working.remove(getAttachmentFor(selected)); + updateButtons(); return; } - if (src != add && selections.isAncestorOf((Component)src)) - { - assert src instanceof JToggleButton; - if (src == selected) preview(getAttachmentFor(src)); - else open((JToggleButton)src); - return; - } + if (src != add && selections.isAncestorOf((Component)src)) + { + assert src instanceof JToggleButton; + if (src == selected) preview(getAttachmentFor(src)); + else open((JToggleButton)src); + return; + } if (src == revert) { - while (descriptionUndos.canUndo()) - descriptionUndos.undo(); - return; + while (descriptionUndos.canUndo()) + descriptionUndos.undo(); + return; } } - private Attachment - getAttachmentFor(Object button) - { - if (button == null) return null; - assert button instanceof JToggleButton; - assert selections.isAncestorOf((Component)button); + private Attachment + getAttachmentFor(Object button) + { + if (button == null) return null; + assert button instanceof JToggleButton; + assert selections.isAncestorOf((Component)button); - int index = 0; - if (button == attachment4) index = 4; - if (button == attachment3) index = 3; - if (button == attachment2) index = 2; - if (button == attachment1) index = 1; + int index = 0; + if (button == attachment4) index = 4; + if (button == attachment3) index = 3; + if (button == attachment2) index = 2; + if (button == attachment1) index = 1; - assert index != 0; - assert index <= working.size(); - return working.get(index - 1); - } + assert index != 0; + assert index <= working.size(); + return working.get(index - 1); + } - private void - open(JToggleButton button) - { - assert selections.isAncestorOf(button); + private void + open(JToggleButton button) + { + assert selections.isAncestorOf(button); - if (selected != null) - { - Attachment a = getAttachmentFor(selected); - a.description = description.getText(); - selected.setSelected(false); - } + if (selected != null) + { + Attachment a = getAttachmentFor(selected); + a.description = description.getText(); + selected.setSelected(false); + } - Attachment a = getAttachmentFor(button); - description.setText(a.description); - descriptionUndos.discardAllEdits(); - (selected = button).setSelected(true); - } + Attachment a = getAttachmentFor(button); + description.setText(a.description); + descriptionUndos.discardAllEdits(); + (selected = button).setSelected(true); + } - public void - componentHidden(ComponentEvent eC) - { - if (selected != null) open(selected); - } + public void + componentHidden(ComponentEvent eC) + { + if (selected != null) open(selected); + } - private void - preview(Attachment a) - { - ImageWindow w = new ImageWindow(); - w.showAttachments(new Attachment[] { a } ); - w.setTitle("Attachment preview"); - w.setVisible(true); - } + private void + preview(Attachment a) + { + ImageWindow w = new ImageWindow(); + w.showAttachments(new Attachment[] { a } ); + w.setTitle("Attachment preview"); + w.setVisible(true); + } - public void - componentShown(ComponentEvent eC) { } + public void + componentShown(ComponentEvent eC) { } - public void - componentMoved(ComponentEvent eC) { } + public void + componentMoved(ComponentEvent eC) { } - public void - componentResized(ComponentEvent eC) { } + public void + componentResized(ComponentEvent eC) { } // ---%-@-%--- @@ -824,15 +824,15 @@ implements ComponentListener, ActionListener { Border bc1 = BorderFactory.createCompoundBorder(b3, b2); Border bc2 = BorderFactory.createCompoundBorder(b4, b2); - TextActionPopupMenu textActionPopup; - textActionPopup = new TextActionPopupMenu(); + TextActionPopupMenu textActionPopup; + textActionPopup = new TextActionPopupMenu(); - chooser = new JFileChooser(); + chooser = new JFileChooser(); add = new JButton("+"); add.setPreferredSize(new Dimension(32, 32)); add.setMargin(new Insets(0, 0, 0, 0)); - add.addActionListener(this); + add.addActionListener(this); attachment1 = new JToggleButton("1"); attachment2 = new JToggleButton("2"); attachment3 = new JToggleButton("3"); @@ -841,28 +841,28 @@ implements ComponentListener, 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); + 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(); - Box top = Box.createHorizontalBox(); + Box top = Box.createHorizontalBox(); top.add(selections); top.add(Box.createGlue()); delete = new JButton("Delete"); revert = new JButton("Revert"); - JButton ml = new JButton("←"); + JButton ml = new JButton("←"); JButton mr = new JButton("→"); - delete.addActionListener(this); + delete.addActionListener(this); revert.addActionListener(this); - Box bottom = Box.createHorizontalBox(); + Box bottom = Box.createHorizontalBox(); bottom.add(ml); bottom.add(mr); bottom.add(Box.createHorizontalStrut(8)); @@ -876,14 +876,14 @@ implements ComponentListener, ActionListener { java.awt.Font f = description.getFont(); description.setFont(f.deriveFont(16f)); description.setBorder(bc1); - description.addMouseListener(textActionPopup); + description.addMouseListener(textActionPopup); descriptionLabel = new JLabel("Description"); descriptionLabel.setLabelFor(description); - descriptionUndos = new UndoManager(); - description.getDocument(). - addUndoableEditListener(descriptionUndos); + descriptionUndos = new UndoManager(); + description.getDocument(). + addUndoableEditListener(descriptionUndos); - updateButtons(); + updateButtons(); JPanel row1 = new JPanel(); row1.setOpaque(false); @@ -891,7 +891,7 @@ implements ComponentListener, ActionListener { row1.add(descriptionLabel, BorderLayout.NORTH); row1.add(description, BorderLayout.CENTER); - Box centre = Box.createVerticalBox(); + Box centre = Box.createVerticalBox(); centre.setBorder(b4); centre.add(row1); @@ -901,7 +901,7 @@ implements ComponentListener, ActionListener { add(bottom, BorderLayout.SOUTH); setBorder(b1); - this.addComponentListener(this); + this.addComponentListener(this); } } @@ -910,66 +910,66 @@ class TextActionPopupMenu extends JPopupMenu implements MouseListener, ActionListener { - private JMenuItem - copy, - cut, - paste; + private JMenuItem + copy, + cut, + paste; - private long - pressed; + private long + pressed; -// ---%-@-%--- +// ---%-@-%--- - public void - mousePressed(MouseEvent eM) - { - assert eM.getSource() instanceof JTextComponent; - if (!eM.isPopupTrigger()) return; - show((Component)eM.getSource(), eM.getX(), eM.getY()); - } + public void + mousePressed(MouseEvent eM) + { + assert eM.getSource() instanceof JTextComponent; + if (!eM.isPopupTrigger()) return; + show((Component)eM.getSource(), eM.getX(), eM.getY()); + } - public void - mouseReleased(MouseEvent eM) - { - if (eM.getClickCount() == 0) setVisible(false); - } + public void + mouseReleased(MouseEvent eM) + { + if (eM.getClickCount() == 0) setVisible(false); + } - public void - mouseClicked(MouseEvent eM) { } + public void + mouseClicked(MouseEvent eM) { } - public void - mouseEntered(MouseEvent eM) { } + public void + mouseEntered(MouseEvent eM) { } - public void - mouseExited(MouseEvent eM) { } + public void + mouseExited(MouseEvent eM) { } - public void - actionPerformed(ActionEvent eA) - { - assert getInvoker() instanceof JTextComponent; - JTextComponent inv = (JTextComponent)getInvoker(); + public void + actionPerformed(ActionEvent eA) + { + assert getInvoker() instanceof JTextComponent; + JTextComponent inv = (JTextComponent)getInvoker(); - if (eA.getSource() == copy) inv.copy(); - if (eA.getSource() == cut) inv.cut(); - if (eA.getSource() == paste) inv.paste(); - } + if (eA.getSource() == copy) inv.copy(); + if (eA.getSource() == cut) inv.cut(); + if (eA.getSource() == paste) inv.paste(); + } -// ---%-@-%--- +// ---%-@-%--- - public - TextActionPopupMenu() - { - copy = new JMenuItem("Copy"); - cut = new JMenuItem("Cut"); - paste = new JMenuItem("Paste"); - copy.addActionListener(this); - cut.addActionListener(this); - paste.addActionListener(this); + public + TextActionPopupMenu() + { + copy = new JMenuItem("Copy"); + cut = new JMenuItem("Cut"); + paste = new JMenuItem("Paste"); + copy.addActionListener(this); + cut.addActionListener(this); + paste.addActionListener(this); - add(cut); - add(copy); - add(paste); - } + add(cut); + add(copy); + add(paste); + } } diff --git a/ImageWindow.java b/ImageWindow.java index 6ca472b..3aba5b6 100644 --- a/ImageWindow.java +++ b/ImageWindow.java @@ -41,303 +41,303 @@ import java.net.MalformedURLException; class ImageWindow extends JFrame { - private Attachment[] - attachments; + private Attachment[] + attachments; - private int - offset; + private int + offset; -// - -%- - +// - -%- - - private ImageComponent - display; + private ImageComponent + display; -// ---%-@-%--- +// ---%-@-%--- - public synchronized void - showAttachments(Attachment[] attachments) - { - this.attachments = attachments; + public synchronized void + showAttachments(Attachment[] attachments) + { + this.attachments = attachments; - if (attachments.length == 0) { - display.setImage(null); - display.setNext(null); - display.setPrev(null); - display.repaint(); - return; - } + if (attachments.length == 0) { + display.setImage(null); + display.setNext(null); + display.setPrev(null); + display.repaint(); + return; + } - toImage(offset = 0); - } + toImage(offset = 0); + } - public void - toNextImage() - { - if (attachments.length == 0) return; - assert offset < attachments.length - 1; - toImage(++offset); - } + public void + toNextImage() + { + if (attachments.length == 0) return; + assert offset < attachments.length - 1; + toImage(++offset); + } - public void - toPrevImage() - { - if (attachments.length == 0) return; - assert offset > 0; - toImage(--offset); - } + public void + toPrevImage() + { + if (attachments.length == 0) return; + assert offset > 0; + toImage(--offset); + } -// - -%- - +// - -%- - - private synchronized void - toImage(int offset) - { - int last = attachments.length - 1; - assert offset >= 0; - assert offset < attachments.length; + private synchronized void + toImage(int offset) + { + int last = attachments.length - 1; + assert offset >= 0; + assert offset < attachments.length; - Attachment prev, curr, next; - curr = attachments[offset]; - next = offset < last ? attachments[offset + 1] : null; - prev = offset > 0 ? attachments[offset - 1] : null; + Attachment prev, curr, next; + curr = attachments[offset]; + next = offset < last ? attachments[offset + 1] : null; + prev = offset > 0 ? attachments[offset - 1] : null; - display.setImage(curr.image); - display.setNext(next != null ? next.image : null); - display.setPrev(prev != null ? prev.image : null); + display.setImage(curr.image); + display.setNext(next != null ? next.image : null); + display.setPrev(prev != null ? prev.image : null); - if (!curr.type.equals("image")) - display.setToolTipText( - display.getToolTipText() - + "\n(Media is of type '" + curr.type + "')" - ); + if (!curr.type.equals("image")) + display.setToolTipText( + display.getToolTipText() + + "\n(Media is of type '" + curr.type + "')" + ); - repaint(); - } + repaint(); + } -// ---%-@-%--- +// ---%-@-%--- - ImageWindow() - { - setDefaultCloseOperation(DISPOSE_ON_CLOSE); - setSize(600, 600); + ImageWindow() + { + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setSize(600, 600); - display = new ImageComponent(this); - showAttachments(new Attachment[0]); - setContentPane(display); - } + display = new ImageComponent(this); + showAttachments(new Attachment[0]); + setContentPane(display); + } } class ImageComponent extends JPanel implements - ActionListener, - MouseListener, MouseMotionListener, MouseWheelListener { + ActionListener, + MouseListener, MouseMotionListener, MouseWheelListener { - private ImageWindow - primaire; + private ImageWindow + primaire; -// - -%- - +// - -%- - - private Image - image, prevImage, nextImage; + private Image + image, prevImage, nextImage; - private JPanel - buttonArea; + private JPanel + buttonArea; - private JButton - prev, next, toggle; + private JButton + prev, next, toggle; - private boolean - scaleImage; + private boolean + scaleImage; - private int - xOffset, yOffset, zoomLevel; + private int + xOffset, yOffset, zoomLevel; - private int - dragX, dragY, xPOffset, yPOffset; + private int + dragX, dragY, xPOffset, yPOffset; -// ---%-@-%--- +// ---%-@-%--- - public void - setImage(Image image) - { - this.image = image; - if (image != null) { - Object p = image.getProperty("comment", this); - String desc = p instanceof String ? (String)p : null; - setToolTipText(desc); - } - xOffset = yOffset = xPOffset = yPOffset = 0; - zoomLevel = 100; - } + public void + setImage(Image image) + { + this.image = image; + if (image != null) { + Object p = image.getProperty("comment", this); + String desc = p instanceof String ? (String)p : null; + setToolTipText(desc); + } + xOffset = yOffset = xPOffset = yPOffset = 0; + zoomLevel = 100; + } - public void - setPrev(Image image) - { - prev.setEnabled(image != null); - prev.setText(image == null ? "<" : ""); - prev.setIcon(toIcon(image)); - } + public void + setPrev(Image image) + { + prev.setEnabled(image != null); + prev.setText(image == null ? "<" : ""); + prev.setIcon(toIcon(image)); + } - public void - setNext(Image image) - { - next.setEnabled(image != null); - next.setText(image == null ? ">" : ""); - next.setIcon(toIcon(image)); - } + public void + setNext(Image image) + { + next.setEnabled(image != null); + next.setText(image == null ? ">" : ""); + next.setIcon(toIcon(image)); + } -// - -%- - +// - -%- - - public void - actionPerformed(ActionEvent eA) - { - if (eA.getSource() == prev) primaire.toPrevImage(); - if (eA.getSource() == next) primaire.toNextImage(); - if (eA.getSource() == toggle) { - scaleImage = !scaleImage; - if (scaleImage) toggle.setText("Show unscaled"); - else toggle.setText("Show scaled to window"); - setImage(this.image); - repaint(); - } - } + public void + actionPerformed(ActionEvent eA) + { + if (eA.getSource() == prev) primaire.toPrevImage(); + if (eA.getSource() == next) primaire.toNextImage(); + if (eA.getSource() == toggle) { + scaleImage = !scaleImage; + if (scaleImage) toggle.setText("Show unscaled"); + else toggle.setText("Show scaled to window"); + setImage(this.image); + repaint(); + } + } - public void - mousePressed(MouseEvent eM) - { - dragX = eM.getX(); - dragY = eM.getY(); - } + public void + mousePressed(MouseEvent eM) + { + dragX = eM.getX(); + dragY = eM.getY(); + } - public void - mouseDragged(MouseEvent eM) - { - int dx = eM.getX() - dragX; - int dy = eM.getY() - dragY; - xPOffset = dx; - yPOffset = dy; - repaint(); - } + public void + mouseDragged(MouseEvent eM) + { + int dx = eM.getX() - dragX; + int dy = eM.getY() - dragY; + xPOffset = dx; + yPOffset = dy; + repaint(); + } - public void - mouseReleased(MouseEvent eM) - { - xOffset += xPOffset; - yOffset += yPOffset; - xPOffset = yPOffset = 0; - } + public void + mouseReleased(MouseEvent eM) + { + xOffset += xPOffset; + yOffset += yPOffset; + xPOffset = yPOffset = 0; + } - public void - mouseWheelMoved(MouseWheelEvent eMw) - { - zoomLevel += 10 * -eMw.getUnitsToScroll(); - if (zoomLevel < 50) zoomLevel = 50; - if (zoomLevel > 400) zoomLevel = 400; - repaint(); - } + public void + mouseWheelMoved(MouseWheelEvent eMw) + { + zoomLevel += 10 * -eMw.getUnitsToScroll(); + if (zoomLevel < 50) zoomLevel = 50; + if (zoomLevel > 400) zoomLevel = 400; + repaint(); + } - public void - mouseEntered(MouseEvent eM) { } + public void + mouseEntered(MouseEvent eM) { } - public void - mouseExited(MouseEvent eM) { } + public void + mouseExited(MouseEvent eM) { } - public void - mouseClicked(MouseEvent eM) { } + public void + mouseClicked(MouseEvent eM) { } - public void - mouseMoved(MouseEvent eM) { } + public void + mouseMoved(MouseEvent eM) { } -// - -%- - +// - -%- - - private static ImageIcon - toIcon(Image image) - { - if (image == null) return null; - return new ImageIcon(image); - } + private static ImageIcon + toIcon(Image image) + { + if (image == null) return null; + return new ImageIcon(image); + } -// ---%-@-%--- +// ---%-@-%--- - private class - Painter extends JPanel { + private class + Painter extends JPanel { - protected void - paintComponent(Graphics g) - { - if (image == null) - { - String str = + protected void + paintComponent(Graphics g) + { + if (image == null) + { + String str = "(There are no images being displayed.)"; - FontMetrics fm = g.getFontMetrics(); - int x = (getWidth() - fm.stringWidth(str)) / 2; - int y = (getHeight() + fm.getHeight()) / 2; - g.drawString(str, x, y); - return; - } - int wo = image.getWidth(this); - int ho = image.getHeight(this); - int wn, hn; - if (wo > ho) { - wn = scaleImage ? getWidth() : wo; - hn = ho * wn / wo; - } - else { - hn = scaleImage ? getHeight() : ho; - wn = wo * hn / ho; - } - wn = wn * zoomLevel / 100; - hn = hn * zoomLevel / 100; - int x = (getWidth() - wn) / 2; - int y = (getHeight() - hn) / 2; - x += xOffset + xPOffset; - y += yOffset + yPOffset; - g.drawImage(image, x, y, wn, hn, this); - } + FontMetrics fm = g.getFontMetrics(); + int x = (getWidth() - fm.stringWidth(str)) / 2; + int y = (getHeight() + fm.getHeight()) / 2; + g.drawString(str, x, y); + return; + } + int wo = image.getWidth(this); + int ho = image.getHeight(this); + int wn, hn; + if (wo > ho) { + wn = scaleImage ? getWidth() : wo; + hn = ho * wn / wo; + } + else { + hn = scaleImage ? getHeight() : ho; + wn = wo * hn / ho; + } + wn = wn * zoomLevel / 100; + hn = hn * zoomLevel / 100; + int x = (getWidth() - wn) / 2; + int y = (getHeight() - hn) / 2; + x += xOffset + xPOffset; + y += yOffset + yPOffset; + g.drawImage(image, x, y, wn, hn, this); + } - } + } -// ---%-@-%--- +// ---%-@-%--- - ImageComponent(ImageWindow primaire) - { - this.primaire = primaire; + ImageComponent(ImageWindow primaire) + { + this.primaire = primaire; - Dimension BUTTON_SIZE = new Dimension(48, 48); + Dimension BUTTON_SIZE = new Dimension(48, 48); - setOpaque(false); - scaleImage = true; - zoomLevel = 100; + setOpaque(false); + scaleImage = true; + zoomLevel = 100; - prev = new JButton(); - toggle = new JButton("Show unscaled"); - next = new JButton(); - prev.setPreferredSize(BUTTON_SIZE); - next.setPreferredSize(BUTTON_SIZE); - prev.addActionListener(this); - toggle.addActionListener(this); - next.addActionListener(this); + prev = new JButton(); + toggle = new JButton("Show unscaled"); + next = new JButton(); + prev.setPreferredSize(BUTTON_SIZE); + next.setPreferredSize(BUTTON_SIZE); + prev.addActionListener(this); + toggle.addActionListener(this); + next.addActionListener(this); - buttonArea = new JPanel(); - buttonArea.setOpaque(false); - buttonArea.add(prev); - buttonArea.add(toggle); - buttonArea.add(next); + buttonArea = new JPanel(); + buttonArea.setOpaque(false); + buttonArea.add(prev); + buttonArea.add(toggle); + buttonArea.add(next); - setPrev(null); - setNext(null); + setPrev(null); + setNext(null); - setLayout(new BorderLayout()); - add(buttonArea, BorderLayout.SOUTH); - add(new Painter(), BorderLayout.CENTER); + setLayout(new BorderLayout()); + add(buttonArea, BorderLayout.SOUTH); + add(new Painter(), BorderLayout.CENTER); - addMouseListener(this); - addMouseMotionListener(this); - addMouseWheelListener(this); - } + addMouseListener(this); + addMouseMotionListener(this); + addMouseWheelListener(this); + } } diff --git a/JKomasto.java b/JKomasto.java index 8643ca8..74cce76 100644 --- a/JKomasto.java +++ b/JKomasto.java @@ -60,20 +60,20 @@ JKomasto { private LoginWindow loginWindow; - private ImageWindow - mediaWindow; + private ImageWindow + mediaWindow; - private NotificationsWindow - notificationsWindow; + private NotificationsWindow + notificationsWindow; - private WindowUpdater - windowUpdater; + private WindowUpdater + windowUpdater; private MastodonApi api; - private Image - programIcon; + private Image + programIcon; // ---%-@-%--- @@ -85,10 +85,10 @@ JKomasto { { timelineWindow.setCursor(new Cursor(Cursor.WAIT_CURSOR)); - timelineWindow.showLatestPage(); + timelineWindow.showLatestPage(); notificationsWindow.showLatestPage(); timelineWindow.setVisible(true); - loginWindow.dispose(); + loginWindow.dispose(); timelineWindow.setCursor(null); } @@ -99,83 +99,83 @@ JKomasto { public ComposeWindow getComposeWindow() { return composeWindow; } - public ImageWindow + public ImageWindow getMediaWindow() { return mediaWindow; } - public NotificationsWindow + public NotificationsWindow getNotificationsWindow() { return notificationsWindow; } public WindowUpdater getWindowUpdater() { return windowUpdater; } - public Image - getProgramIcon() { return programIcon; } + public Image + getProgramIcon() { return programIcon; } // ---%-@-%--- - private static class - MetalTheme extends OceanTheme { + private static class + MetalTheme extends OceanTheme { - private ColorUIResource - lightPink = new ColorUIResource(246, 240, 240), - mildPink = new ColorUIResource(238, 233, 233), - white = new ColorUIResource(250, 250, 250), - darkPink = new ColorUIResource(242, 230, 230), - veryDarkPink = new ColorUIResource(164, 160, 160); + private ColorUIResource + lightPink = new ColorUIResource(246, 240, 240), + mildPink = new ColorUIResource(238, 233, 233), + white = new ColorUIResource(250, 250, 250), + darkPink = new ColorUIResource(242, 230, 230), + veryDarkPink = new ColorUIResource(164, 160, 160); -// -=%=- +// -=%=- - public ColorUIResource - getPrimary2() { return darkPink; } + public ColorUIResource + getPrimary2() { return darkPink; } - public ColorUIResource - getSecondary2() { return white; } + public ColorUIResource + getSecondary2() { return white; } - public ColorUIResource - getSecondary3() { return mildPink; } + public ColorUIResource + getSecondary3() { return mildPink; } - public ColorUIResource - getSecondary1() { return veryDarkPink; } + public ColorUIResource + getSecondary1() { return veryDarkPink; } - public ColorUIResource - getPrimary1() { return veryDarkPink; } + public ColorUIResource + getPrimary1() { return veryDarkPink; } - public void - addCustomEntriesToTable(UIDefaults table) - { - super.addCustomEntriesToTable(table); - table.put( - "TabbedPane.tabAreaBackground", - getPrimary1() - ); - table.put( - "TabbedPane.contentAreaColor", - getSecondary3() - ); - table.put( - "TabbedPane.selected", - getSecondary3() - ); - table.put( - "MenuBar.gradient", - java.util.Arrays.asList(new Object[] { - 1f, 0f, - getWhite(), - getSecondary3(), - getSecondary1() - }) - ); - } + public void + addCustomEntriesToTable(UIDefaults table) + { + super.addCustomEntriesToTable(table); + table.put( + "TabbedPane.tabAreaBackground", + getPrimary1() + ); + table.put( + "TabbedPane.contentAreaColor", + getSecondary3() + ); + table.put( + "TabbedPane.selected", + getSecondary3() + ); + table.put( + "MenuBar.gradient", + java.util.Arrays.asList(new Object[] { + 1f, 0f, + getWhite(), + getSecondary3(), + getSecondary1() + }) + ); + } - } + } // ---%-@-%--- public static void main(String... args) { - //System.setProperty("swing.boldMetal", "false"); - MetalLookAndFeel.setCurrentTheme(new MetalTheme()); + //System.setProperty("swing.boldMetal", "false"); + MetalLookAndFeel.setCurrentTheme(new MetalTheme()); new JKomasto().loginWindow.setVisible(true); } @@ -187,24 +187,24 @@ JKomasto { { api = new MastodonApi(); windowUpdater = new WindowUpdater(this); - programIcon = ImageApi.local("kettle"); + programIcon = ImageApi.local("kettle"); timelineWindow = new TimelineWindow(this); composeWindow = new ComposeWindow(this); autoViewWindow = new PostWindow(this); loginWindow = new LoginWindow(this); - mediaWindow = new ImageWindow(); - notificationsWindow = new NotificationsWindow(this); + mediaWindow = new ImageWindow(); + notificationsWindow = new NotificationsWindow(this); autoViewWindow.setTitle("Auto view - JKomasto"); composeWindow.dispose(); autoViewWindow.dispose(); timelineWindow.dispose(); - mediaWindow.dispose(); - notificationsWindow.dispose(); + mediaWindow.dispose(); + notificationsWindow.dispose(); - timelineWindow.setLocationByPlatform(true); + timelineWindow.setLocationByPlatform(true); loginWindow.setLocationByPlatform(true); } @@ -236,13 +236,13 @@ TimelineType { enum NotificationType { - MENTION, - BOOST, - FAVOURITE, - FOLLOW, - FOLLOWREQ, - POLL, - ALERT + MENTION, + BOOST, + FAVOURITE, + FOLLOW, + FOLLOWREQ, + POLL, + ALERT } @@ -257,8 +257,8 @@ TimelinePage { public String accountNumId, listId; - public Post[] - posts; + public Post[] + posts; // ---%-@-%--- @@ -279,14 +279,14 @@ TimelinePage { class Notification { - public NotificationType - type; + public NotificationType + type; - public String - id; + public String + id; - public String - postId, postText, actorNumId, actorName; + public String + postId, postText, actorNumId, actorName; } @@ -348,35 +348,35 @@ Post { assert text != null; if (approximateText != null) return; - Tree nodes; - nodes = RudimentaryHTMLParser.depthlessRead(text); - if (nodes.size() == 0) - { + Tree nodes; + nodes = RudimentaryHTMLParser.depthlessRead(text); + if (nodes.size() == 0) + { approximateText = "-"; return; } StringBuilder b = new StringBuilder(); - Tree first = nodes.get(0); - for (Tree node: nodes) - { - if (node.key.equals("tag")) - { - if (node.get(0).key.equals("br")) - b.append("; "); - if (node.get(0).key.equals("p") && node != first) - b.append("; "); - } - if (node.key.equals("emoji")) - { - b.append(":" + node.value + ":"); - } - if (node.key.equals("text")) - { - b.append(node.value); - } - } - approximateText = b.toString(); + Tree first = nodes.get(0); + for (Tree node: nodes) + { + if (node.key.equals("tag")) + { + if (node.get(0).key.equals("br")) + b.append("; "); + if (node.get(0).key.equals("p") && node != first) + b.append("; "); + } + if (node.key.equals("emoji")) + { + b.append(":" + node.value + ":"); + } + if (node.key.equals("text")) + { + b.append(node.value); + } + } + approximateText = b.toString(); } public void @@ -402,57 +402,57 @@ Post { public Post(Tree entity) { - id = entity.get("id").value; + id = entity.get("id").value; - uri = entity.get("url").value; - if (uri == null) uri = entity.get("uri").value; + uri = entity.get("url").value; + if (uri == null) uri = entity.get("uri").value; - author = new Account(entity.get("account")); + author = new Account(entity.get("account")); - String v = entity.get("visibility").value; - boolean p = v.equals("public"); - boolean u = v.equals("unlisted"); - boolean f = v.equals("private"); - boolean m = v.equals("direct"); - if (p) visibility = PostVisibility.PUBLIC; - if (u) visibility = PostVisibility.UNLISTED; - if (f) visibility = PostVisibility.FOLLOWERS; - if (m) visibility = PostVisibility.MENTIONED; + String v = entity.get("visibility").value; + boolean p = v.equals("public"); + boolean u = v.equals("unlisted"); + boolean f = v.equals("private"); + boolean m = v.equals("direct"); + if (p) visibility = PostVisibility.PUBLIC; + if (u) visibility = PostVisibility.UNLISTED; + if (f) visibility = PostVisibility.FOLLOWERS; + if (m) visibility = PostVisibility.MENTIONED; - dateTime = + dateTime = ZonedDateTime.parse(entity.get("created_at").value) .withZoneSameInstant(ZoneId.systemDefault()); - date = DATE_FORMAT.format(dateTime); - time = TIME_FORMAT.format(dateTime); + date = DATE_FORMAT.format(dateTime); + time = TIME_FORMAT.format(dateTime); - text = entity.get("content").value; - String st = entity.get("spoiler_text").value; - contentWarning = st.trim().isEmpty() ? null : st; + text = entity.get("content").value; + String st = entity.get("spoiler_text").value; + contentWarning = st.trim().isEmpty() ? null : st; - String favourited = entity.get("favourited").value; - String boosted = entity.get("reblogged").value; - this.favourited = favourited.equals("true"); - this.boosted = boosted.equals("true"); + String favourited = entity.get("favourited").value; + String boosted = entity.get("reblogged").value; + this.favourited = favourited.equals("true"); + this.boosted = boosted.equals("true"); - Tree media = entity.get("media_attachments"); - attachments = new Attachment[media.size()]; - for (int o = 0; o < attachments.length; ++o) - { + Tree media = entity.get("media_attachments"); + attachments = new Attachment[media.size()]; + for (int o = 0; o < attachments.length; ++o) + { attachments[o] = new Attachment(media.get(o)); } - Tree emojis = entity.get("emojis"); - emojiUrls = new String[emojis.size()][]; - for (int o = 0; o < emojiUrls.length; ++o) - { - Tree emoji = emojis.get(o); - String[] mapping = emojiUrls[o] = new String[2]; - mapping[0] = ":" + emoji.get("shortcode").value + ":"; - mapping[1] = emoji.get("url").value; - } + Tree emojis = entity.get("emojis"); + emojiUrls = new String[emojis.size()][]; + for (int o = 0; o < emojiUrls.length; ++o) + { + Tree emoji = emojis.get(o); + String[] mapping = emojiUrls[o] = new String[2]; + mapping[0] = ":" + emoji.get("shortcode").value + ":"; + mapping[1] = emoji.get("url").value; + } Tree boostedPost = entity.get("reblog"); - if (boostedPost.size() > 0) + if (boostedPost.size() > 0) this.boostedPost = new Post(boostedPost); Tree mentions = entity.get("mentions"); @@ -508,7 +508,7 @@ Account { resolveFormattedName() { assert name != null; - formattedName = + formattedName = new RichTextPane.Builder().text(name).finish(); } @@ -531,27 +531,27 @@ Account { numId = entity.get("id").value; id = entity.get("acct").value; - String displayName = entity.get("display_name").value; - String username = entity.get("username").value; - name = displayName.isEmpty() ? username : displayName; + String displayName = entity.get("display_name").value; + String username = entity.get("username").value; + name = displayName.isEmpty() ? username : displayName; - avatarUrl = entity.get("avatar").value; + avatarUrl = entity.get("avatar").value; - creationDate = + creationDate = ZonedDateTime.parse(entity.get("created_at").value) .withZoneSameInstant(ZoneId.systemDefault()); - String c1 = entity.get("following_count").value; - String c2 = entity.get("followers_count").value; - String c3 = entity.get("statuses_count").value; - try { + String c1 = entity.get("following_count").value; + String c2 = entity.get("followers_count").value; + String c3 = entity.get("statuses_count").value; + try { followedCount = (int)Double.parseDouble(c1); followerCount = (int)Double.parseDouble(c2); postCount = (int)Double.parseDouble(c3); - } - catch (NumberFormatException eNf) { + } + catch (NumberFormatException eNf) { assert false; - } + } Tree fs = entity.get("fields"); fields = new String[fs.size()][]; @@ -577,20 +577,20 @@ Attachment { public String id; - public String - type; + public String + type; - public String - url; + public String + url; - public String - description; + public String + description; - public Image - image; + public Image + image; - public File - uploadee; + public File + uploadee; // ---%-@-%--- @@ -639,8 +639,8 @@ Composition { public Attachment[] attachments; - private File - uploadee; + private File + uploadee; // ---%-@-%--- @@ -653,33 +653,33 @@ Composition { Composition c = new Composition(); Tree boosted = entity.get("reblog"); - if (boosted.size() > 0) entity = boosted; + if (boosted.size() > 0) entity = boosted; - String st = entity.get("spoiler_text").value; - String ri = entity.get("id").value; - c.contentWarning = st.trim().isEmpty() ? null : st; - c.replyToPostId = ri.trim().isEmpty() ? null : ri; + String st = entity.get("spoiler_text").value; + String ri = entity.get("id").value; + c.contentWarning = st.trim().isEmpty() ? null : st; + c.replyToPostId = ri.trim().isEmpty() ? null : ri; - Tree author = entity.get("account"); - String authorId = author.get("acct").value; - String authorNumId = author.get("id").value; - c.text = ""; - if (!authorNumId.equals(ownNumId)) + Tree author = entity.get("account"); + String authorId = author.get("acct").value; + String authorNumId = author.get("id").value; + c.text = ""; + if (!authorNumId.equals(ownNumId)) c.text = "@" + authorId + " "; - String visibility = entity.get("visibility").value; - boolean p = visibility.equals("public"); - boolean u = visibility.equals("unlisted"); - boolean f = visibility.equals("private"); - boolean m = visibility.equals("direct"); - assert p || u || f || m; - if (p) c.visibility = PostVisibility.PUBLIC; - if (u) c.visibility = PostVisibility.UNLISTED; - if (f) c.visibility = PostVisibility.FOLLOWERS; - if (m) c.visibility = PostVisibility.MENTIONED; - // Less eye strain arranged this way. + String visibility = entity.get("visibility").value; + boolean p = visibility.equals("public"); + boolean u = visibility.equals("unlisted"); + boolean f = visibility.equals("private"); + boolean m = visibility.equals("direct"); + assert p || u || f || m; + if (p) c.visibility = PostVisibility.PUBLIC; + if (u) c.visibility = PostVisibility.UNLISTED; + if (f) c.visibility = PostVisibility.FOLLOWERS; + if (m) c.visibility = PostVisibility.MENTIONED; + // Less eye strain arranged this way. - return c; + return c; } public static Composition @@ -693,17 +693,17 @@ Composition { c.replyToPostId = entity.get("in_reply_to_id").value; String visibility = entity.get("visibility").value; - boolean p = visibility.equals("public"); - boolean u = visibility.equals("unlisted"); - boolean f = visibility.equals("private"); - boolean m = visibility.equals("direct"); - assert p || u || f || m; - if (p) c.visibility = PostVisibility.PUBLIC; - if (u) c.visibility = PostVisibility.UNLISTED; - if (f) c.visibility = PostVisibility.FOLLOWERS; - if (m) c.visibility = PostVisibility.MENTIONED; + boolean p = visibility.equals("public"); + boolean u = visibility.equals("unlisted"); + boolean f = visibility.equals("private"); + boolean m = visibility.equals("direct"); + assert p || u || f || m; + if (p) c.visibility = PostVisibility.PUBLIC; + if (u) c.visibility = PostVisibility.UNLISTED; + if (f) c.visibility = PostVisibility.FOLLOWERS; + if (m) c.visibility = PostVisibility.MENTIONED; - return c; + return c; } public static Composition @@ -712,9 +712,9 @@ Composition { if (post.boostedPost != null) post = post.boostedPost; Composition c = new Composition(); - c.replyToPostId = post.id; + c.replyToPostId = post.id; c.visibility = post.visibility; - c.contentWarning = post.contentWarning; + c.contentWarning = post.contentWarning; StringBuilder text = new StringBuilder(); for (String id: post.mentions) diff --git a/JKomasto2.jar b/JKomasto2.jar index e6939333671ba4c18e10c728e2ee63370ec6ca45..0519244883a598745e75cae22e9f9f80fa5f1661 100644 GIT binary patch delta 72938 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 delta 67521 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%0AK2HyB1lU!>NXLu|^o(_Vqt7D*EL%2Cx&uqE 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 Tc7{rAK@k*_9MS%x#Nqc3crv|j diff --git a/LoginWindow.java b/LoginWindow.java index 008ca89..483e83f 100644 --- a/LoginWindow.java +++ b/LoginWindow.java @@ -50,8 +50,8 @@ LoginWindow extends JFrame { private JKomasto primaire; - private MastodonApi - api; + private MastodonApi + api; // - -%- - @@ -62,7 +62,7 @@ LoginWindow extends JFrame { serverContacted = false, haveAppCredentials = false, haveAccessToken = false, - haveAccountDetails = false; + haveAccountDetails = false; // ---%-@-%--- @@ -81,325 +81,325 @@ LoginWindow extends JFrame { b.append(prefix4 + " Have account details\n"); display.setText(b.toString()); - display.paintImmediately(display.getBounds(null)); + display.paintImmediately(display.getBounds(null)); } - public void - useCache() - { - display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); - try - { - api.loadCache(); - haveAppCredentials = true; - haveAccessToken = true; - display.setInstanceUrl(api.getInstanceUrl()); - updateStatusDisplay(); - } - catch (IOException eIo) - { - JOptionPane.showMessageDialog( - this, - "We couldn't get login details from the cache.." - + "\n" + eIo.getClass() + ": " + eIo.getMessage() - ); - display.setAutoLoginToggled(false); - } - display.setCursor(null); - if (!haveAccessToken) return; + public void + useCache() + { + display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + try + { + api.loadCache(); + haveAppCredentials = true; + haveAccessToken = true; + display.setInstanceUrl(api.getInstanceUrl()); + updateStatusDisplay(); + } + catch (IOException eIo) + { + JOptionPane.showMessageDialog( + this, + "We couldn't get login details from the cache.." + + "\n" + eIo.getClass() + ": " + eIo.getMessage() + ); + display.setAutoLoginToggled(false); + } + display.setCursor(null); + if (!haveAccessToken) return; - display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); - api.getAccountDetails(new RequestListener() { + display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + api.getAccountDetails(new RequestListener() { - public void - connectionFailed(IOException eIo) - { - JOptionPane.showMessageDialog( - LoginWindow.this, - "Tried to get account details, failed.." - + "\n" + eIo.getMessage() - ); - } + public void + connectionFailed(IOException eIo) + { + JOptionPane.showMessageDialog( + LoginWindow.this, + "Tried to get account details, failed.." + + "\n" + eIo.getMessage() + ); + } - public void - requestFailed(int httpCode, Tree json) - { - JOptionPane.showMessageDialog( - LoginWindow.this, - "Tried to get account details, failed.." - + "\n" + json.get("error").value - + "\n(HTTP error code: " + httpCode + ")" - ); - } + public void + requestFailed(int httpCode, Tree json) + { + JOptionPane.showMessageDialog( + LoginWindow.this, + "Tried to get account details, failed.." + + "\n" + json.get("error").value + + "\n(HTTP error code: " + httpCode + ")" + ); + } - public void - requestSucceeded(Tree json) - { - api.setAccountDetails(json); - serverContacted = true; - haveAccountDetails = true; - updateStatusDisplay(); - } + public void + requestSucceeded(Tree json) + { + api.setAccountDetails(json); + serverContacted = true; + haveAccountDetails = true; + updateStatusDisplay(); + } - }); - display.setCursor(null); - if (!haveAccountDetails) return; + }); + display.setCursor(null); + if (!haveAccountDetails) return; - display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); - try - { - api.saveToCache(); - } - catch (IOException eIo) - { - JOptionPane.showMessageDialog( - this, - "We couldn't save login details into the cache.." - + "\n" + eIo.getClass() + ": " + eIo.getMessage() - ); - } - display.setCursor(null); + display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + try + { + api.saveToCache(); + } + catch (IOException eIo) + { + JOptionPane.showMessageDialog( + this, + "We couldn't save login details into the cache.." + + "\n" + eIo.getClass() + ": " + eIo.getMessage() + ); + } + display.setCursor(null); - primaire.finishedLogin(); - } + primaire.finishedLogin(); + } - public void + public void useInstanceUrl() { if (display.isAutoLoginToggled()) { useCache(); return; } - String url = display.getInstanceUrl(); - if (url.trim().isEmpty()) { + String url = display.getInstanceUrl(); + if (url.trim().isEmpty()) { // Should we show an error dialog..? display.setInstanceUrl(""); return; - } - if (!hasProtocol(url)) { - url = "https://" + url; - display.setInstanceUrl(url); - display.paintImmediately(display.getBounds(null)); - } - serverContacted = false; - haveAppCredentials = false; - haveAccessToken = false; - haveAccountDetails = false; + } + if (!hasProtocol(url)) { + url = "https://" + url; + display.setInstanceUrl(url); + display.paintImmediately(display.getBounds(null)); + } + serverContacted = false; + haveAppCredentials = false; + haveAccessToken = false; + haveAccountDetails = false; - display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); - api.testUrlConnection(url, new RequestListener() { + display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + api.testUrlConnection(url, new RequestListener() { - public void - connectionFailed(IOException eIo) - { - JOptionPane.showMessageDialog( - LoginWindow.this, - "Tried to connect to URL, failed.." - + "\n" + eIo.getClass() + ": " + eIo.getMessage() - ); - } + public void + connectionFailed(IOException eIo) + { + JOptionPane.showMessageDialog( + LoginWindow.this, + "Tried to connect to URL, failed.." + + "\n" + eIo.getClass() + ": " + eIo.getMessage() + ); + } - public void - requestFailed(int httpCode, Tree response) - { - JOptionPane.showMessageDialog( - LoginWindow.this, - "Tried to connect to URL, failed.." - + "\n" + response.get("body").value - ); - } + public void + requestFailed(int httpCode, Tree response) + { + JOptionPane.showMessageDialog( + LoginWindow.this, + "Tried to connect to URL, failed.." + + "\n" + response.get("body").value + ); + } - public void - requestSucceeded(Tree response) - { - serverContacted = true; - updateStatusDisplay(); - } + public void + requestSucceeded(Tree response) + { + serverContacted = true; + updateStatusDisplay(); + } - }); - display.setCursor(null); + }); + display.setCursor(null); if (!serverContacted) return; - api.setInstanceUrl(url); - display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); - api.getAppCredentials(new RequestListener() { + api.setInstanceUrl(url); + display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + api.getAppCredentials(new RequestListener() { - public void - connectionFailed(IOException eIo) - { - JOptionPane.showMessageDialog( - LoginWindow.this, - "Tried to get app credentials, failed.." - + "\n" + eIo.getMessage() - ); - } + public void + connectionFailed(IOException eIo) + { + JOptionPane.showMessageDialog( + LoginWindow.this, + "Tried to get app credentials, failed.." + + "\n" + eIo.getMessage() + ); + } - public void - requestFailed(int httpCode, Tree json) - { - JOptionPane.showMessageDialog( - LoginWindow.this, - "Tried to get app credentials, failed.." - + "\n" + json.get("error").value - + "\n(HTTP error code: " + httpCode + ")" - ); - } + public void + requestFailed(int httpCode, Tree json) + { + JOptionPane.showMessageDialog( + LoginWindow.this, + "Tried to get app credentials, failed.." + + "\n" + json.get("error").value + + "\n(HTTP error code: " + httpCode + ")" + ); + } - public void - requestSucceeded(Tree json) - { - api.setAppCredentials(json); - haveAppCredentials = true; - updateStatusDisplay(); - } + public void + requestSucceeded(Tree json) + { + api.setAppCredentials(json); + haveAppCredentials = true; + updateStatusDisplay(); + } - }); - display.setCursor(null); + }); + display.setCursor(null); if (!haveAppCredentials) return; - display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); - URI uri = api.getAuthorisationURL(); + URI uri = api.getAuthorisationURL(); - final String MESSAGE1 = + final String MESSAGE1 = "We will need you to login through a web browser,\n" + "and they will give you an authorisation code\n" + "that you will paste here. Sorry..!"; - boolean supported = - Desktop.isDesktopSupported() - && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE); - if (supported) - { - JOptionPane.showMessageDialog( + boolean supported = + Desktop.isDesktopSupported() + && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE); + if (supported) + { + JOptionPane.showMessageDialog( LoginWindow.this, MESSAGE1, "Authorisation code renewal", JOptionPane.INFORMATION_MESSAGE ); - try { Desktop.getDesktop().browse(uri); } - catch (IOException eIo) { supported = false; } - } - if (!supported) - { - final String MESSAGE2 = - "\nWe cannot use Desktop.browse(URI) on your\n" - + "computer.. You'll have to open your web\n" - + "browser yourself, and copy this URL in."; + try { Desktop.getDesktop().browse(uri); } + catch (IOException eIo) { supported = false; } + } + if (!supported) + { + final String MESSAGE2 = + "\nWe cannot use Desktop.browse(URI) on your\n" + + "computer.. You'll have to open your web\n" + + "browser yourself, and copy this URL in."; - JTextField field = new JTextField(); - field.setText(uri.toString()); - field.setPreferredSize(new Dimension(120, 32)); - field.selectAll(); + JTextField field = new JTextField(); + field.setText(uri.toString()); + field.setPreferredSize(new Dimension(120, 32)); + field.selectAll(); - JOptionPane.showMessageDialog( - LoginWindow.this, - new Object[] { MESSAGE1, MESSAGE2, field }, - "Authorisation code renewal", - JOptionPane.INFORMATION_MESSAGE - ); - } - display.receiveAuthorisationCode(); - display.setCursor(null); + JOptionPane.showMessageDialog( + LoginWindow.this, + new Object[] { MESSAGE1, MESSAGE2, field }, + "Authorisation code renewal", + JOptionPane.INFORMATION_MESSAGE + ); + } + display.receiveAuthorisationCode(); + display.setCursor(null); } - public void - useAuthorisationCode() - { - String code = display.getAuthorisationCode(); + public void + useAuthorisationCode() + { + String code = display.getAuthorisationCode(); - display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); - api.getAccessToken(code, new RequestListener() { + display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + api.getAccessToken(code, new RequestListener() { - public void - connectionFailed(IOException eIo) - { - JOptionPane.showMessageDialog( - LoginWindow.this, - "Tried to get app token, failed.." - + "\n" + eIo.getMessage() - ); - } + public void + connectionFailed(IOException eIo) + { + JOptionPane.showMessageDialog( + LoginWindow.this, + "Tried to get app token, failed.." + + "\n" + eIo.getMessage() + ); + } - public void - requestFailed(int httpCode, Tree json) - { - JOptionPane.showMessageDialog( - LoginWindow.this, - "Tried to get app token, failed.." - + "\n" + json.get("error").value - + "\n(HTTP error code: " + httpCode + ")" - ); - } + public void + requestFailed(int httpCode, Tree json) + { + JOptionPane.showMessageDialog( + LoginWindow.this, + "Tried to get app token, failed.." + + "\n" + json.get("error").value + + "\n(HTTP error code: " + httpCode + ")" + ); + } - public void - requestSucceeded(Tree json) - { - api.setAccessToken(json); - haveAccessToken = true; - updateStatusDisplay(); - } + public void + requestSucceeded(Tree json) + { + api.setAccessToken(json); + haveAccessToken = true; + updateStatusDisplay(); + } - }); - display.setCursor(null); - if (!haveAccessToken) return; + }); + display.setCursor(null); + if (!haveAccessToken) return; - display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); - api.getAccountDetails(new RequestListener() { + display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + api.getAccountDetails(new RequestListener() { - public void - connectionFailed(IOException eIo) - { - JOptionPane.showMessageDialog( - LoginWindow.this, - "Tried to get account details, failed.." - + "\n" + eIo.getMessage() - ); - } + public void + connectionFailed(IOException eIo) + { + JOptionPane.showMessageDialog( + LoginWindow.this, + "Tried to get account details, failed.." + + "\n" + eIo.getMessage() + ); + } - public void - requestFailed(int httpCode, Tree json) - { - JOptionPane.showMessageDialog( - LoginWindow.this, - "Tried to get account details, failed.." - + "\n" + json.get("error").value - + "\n(HTTP error code: " + httpCode + ")" - ); - } + public void + requestFailed(int httpCode, Tree json) + { + JOptionPane.showMessageDialog( + LoginWindow.this, + "Tried to get account details, failed.." + + "\n" + json.get("error").value + + "\n(HTTP error code: " + httpCode + ")" + ); + } - public void - requestSucceeded(Tree json) - { - api.setAccountDetails(json); - haveAccountDetails = true; - updateStatusDisplay(); - } + public void + requestSucceeded(Tree json) + { + api.setAccountDetails(json); + haveAccountDetails = true; + updateStatusDisplay(); + } - }); - display.setCursor(null); - if (!haveAccountDetails) return; + }); + display.setCursor(null); + if (!haveAccountDetails) return; - display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); - try - { - api.saveToCache(); - } - catch (IOException eIo) - { - JOptionPane.showMessageDialog( - this, - "We couldn't save login details into the cache.." - + "\n" + eIo.getClass() + ": " + eIo.getMessage() - ); - } - display.setCursor(null); + display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + try + { + api.saveToCache(); + } + catch (IOException eIo) + { + JOptionPane.showMessageDialog( + this, + "We couldn't save login details into the cache.." + + "\n" + eIo.getClass() + ": " + eIo.getMessage() + ); + } + display.setCursor(null); - primaire.finishedLogin(); - } + primaire.finishedLogin(); + } -// - -%- - +// - -%- - - public static boolean - hasProtocol(String url) { return url.matches("^.+://.*"); } + public static boolean + hasProtocol(String url) { return url.matches("^.+://.*"); } // ---%-@-%--- @@ -407,7 +407,7 @@ LoginWindow extends JFrame { { super("JKomasto - Login"); this.primaire = primaire; - this.api = primaire.getMastodonApi(); + this.api = primaire.getMastodonApi(); setLocationByPlatform(true); setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); @@ -419,7 +419,7 @@ LoginWindow extends JFrame { setContentPane(display); pack(); - setIconImage(primaire.getProgramIcon()); + setIconImage(primaire.getProgramIcon()); } } @@ -451,7 +451,7 @@ implements ActionListener { private JPanel labelArea, forField, - accountsPanel; + accountsPanel; private JCheckBox autoLoginToggle; @@ -482,11 +482,11 @@ implements ActionListener { statusDisplay.setText(status); } - public void - setAutoLoginToggled(boolean a) { autoLoginToggle.setSelected(a); } + public void + setAutoLoginToggled(boolean a) { autoLoginToggle.setSelected(a); } - public boolean - isAutoLoginToggled() { return autoLoginToggle.isSelected(); } + public boolean + isAutoLoginToggled() { return autoLoginToggle.isSelected(); } public void actionPerformed(ActionEvent eA) @@ -502,11 +502,11 @@ implements ActionListener { { labelArea.remove(authorisationCodeLabel); forField.remove(authorisationCodeButton); - accountsPanel.remove(authorisationCodeField); + accountsPanel.remove(authorisationCodeField); labelArea.add(instanceUrlLabel, BorderLayout.NORTH); forField.add(instanceUrlButton, BorderLayout.EAST); - accountsPanel.add(instanceUrlField); - revalidate(); + accountsPanel.add(instanceUrlField); + revalidate(); } public void @@ -514,11 +514,11 @@ implements ActionListener { { labelArea.remove(instanceUrlLabel); forField.remove(instanceUrlButton); - accountsPanel.remove(instanceUrlField); + accountsPanel.remove(instanceUrlField); labelArea.add(authorisationCodeLabel, BorderLayout.NORTH); forField.add(authorisationCodeButton, BorderLayout.EAST); - accountsPanel.add(authorisationCodeField); - revalidate(); + accountsPanel.add(authorisationCodeField); + revalidate(); } // ---%-@-%--- @@ -595,7 +595,7 @@ implements ActionListener { statusDisplay.setBorder(bi); statusDisplay.setFont(f2); - receiveInstanceUrl(); + receiveInstanceUrl(); setLayout(new BorderLayout(0, 8)); add(accountsPanel, BorderLayout.NORTH); diff --git a/MastodonApi.java b/MastodonApi.java index d660bc0..e56da4b 100644 --- a/MastodonApi.java +++ b/MastodonApi.java @@ -295,7 +295,7 @@ MastodonApi { submit( String text, PostVisibility visibility, String replyTo, String contentWarning, - String[] mediaIDs, + String[] mediaIDs, RequestListener handler) { String token = accessToken.get("access_token").value; @@ -320,7 +320,7 @@ MastodonApi { HttpURLConnection conn = cast(endpoint.openConnection()); String s1 = "Bearer " + token; conn.setRequestProperty("Authorization", s1); - String s2 = Integer.toString(handler.hashCode()); + String s2 = Integer.toString(handler.hashCode()); conn.setRequestProperty("Idempotency-Key", s2); conn.setDoOutput(true); conn.setRequestMethod("POST"); @@ -335,9 +335,9 @@ MastodonApi { if (contentWarning != null) { output.write("&spoiler_text=" + contentWarning); } - for (String mediaID: mediaIDs) { - output.write("&media_ids[]=" + mediaID); - } + for (String mediaID: mediaIDs) { + output.write("&media_ids[]=" + mediaID); + } output.close(); @@ -457,77 +457,77 @@ MastodonApi { uploadFile(File file, String alt, RequestListener handler) { assert file != null; - assert alt != null; + assert alt != null; assert file.canRead(); String bct = - "multipart/form-data; " - + "boundary=\"JKomastoFileUpload\""; - String fsb = "--JKomastoFileUpload\r\n"; - String feb = "\r\n--JKomastoFileUpload--\r\n"; - String fcd = - "Content-Disposition: form-data; " - + "name=\"file\"; " - + "filename=\"" + file.getName() + "\"\r\n"; - String fct = "Content-Type: image/png\r\n\r\n"; - int contentLength = 0; - contentLength += fsb.length(); - contentLength += feb.length(); - contentLength += fcd.length(); - contentLength += fct.length(); - contentLength += file.length(); - /* - * (知) This was an absurdity to debug. Contrary to - * cURL, Java sets default values for some headers, - * some of which are restricted, meaning you can't - * arbitrarily change them. Content-Length is one - * of them, set to 2^14-1 bytes. I'm pretty sure - * the file I was uploading was under this, but - * anyways one of the two parties was stopping me - * from finishing transferring my form data. - * - * They didn't mention this in the Javadocs. - * I noticed HttpURLConnection#setChunkedStreamingMode - * and #setFixedLengthStreamingMode by accident. - * Turns out, the latter is how I do what cURL and - * Firefox are doing - precalculate the exact size - * of the body and set the content length to it. - * Unfortunately, this is not flexible, we have to - * be exact. Thankfully, my answers pass.. - * - * On the other side, Mastodon is obtuse as usual. - * They had code that basically throws a generic 500 - * upon any sort of error from their library[1]. What - * problem the library had with my requests, I could - * never know. There is an undocumented requirement - * that you must put a filename in the content - * disposition. That one I found by guessing. - * - * I solved this with the help of -Djavax.net.debug, - * which revealed to me how my headers and body - * differed from cURL and Firefox. If this issue - * happens again, I advise giving up. - * - * [1] app/controllers/api/v1/media_controller.rb - * #create. 3 March 2022 - */ + "multipart/form-data; " + + "boundary=\"JKomastoFileUpload\""; + String fsb = "--JKomastoFileUpload\r\n"; + String feb = "\r\n--JKomastoFileUpload--\r\n"; + String fcd = + "Content-Disposition: form-data; " + + "name=\"file\"; " + + "filename=\"" + file.getName() + "\"\r\n"; + String fct = "Content-Type: image/png\r\n\r\n"; + int contentLength = 0; + contentLength += fsb.length(); + contentLength += feb.length(); + contentLength += fcd.length(); + contentLength += fct.length(); + contentLength += file.length(); + /* + * (知) This was an absurdity to debug. Contrary to + * cURL, Java sets default values for some headers, + * some of which are restricted, meaning you can't + * arbitrarily change them. Content-Length is one + * of them, set to 2^14-1 bytes. I'm pretty sure + * the file I was uploading was under this, but + * anyways one of the two parties was stopping me + * from finishing transferring my form data. + * + * They didn't mention this in the Javadocs. + * I noticed HttpURLConnection#setChunkedStreamingMode + * and #setFixedLengthStreamingMode by accident. + * Turns out, the latter is how I do what cURL and + * Firefox are doing - precalculate the exact size + * of the body and set the content length to it. + * Unfortunately, this is not flexible, we have to + * be exact. Thankfully, my answers pass.. + * + * On the other side, Mastodon is obtuse as usual. + * They had code that basically throws a generic 500 + * upon any sort of error from their library[1]. What + * problem the library had with my requests, I could + * never know. There is an undocumented requirement + * that you must put a filename in the content + * disposition. That one I found by guessing. + * + * I solved this with the help of -Djavax.net.debug, + * which revealed to me how my headers and body + * differed from cURL and Firefox. If this issue + * happens again, I advise giving up. + * + * [1] app/controllers/api/v1/media_controller.rb + * #create. 3 March 2022 + */ - String token = accessToken.get("access_token").value; + String token = accessToken.get("access_token").value; String url = instanceUrl + "/api/v1/media/"; try { - String s1 = "?description=" + encode(alt); + String s1 = "?description=" + encode(alt); - URL endpoint = new URL(url + s1); + URL endpoint = new URL(url + s1); HttpURLConnection conn = cast(endpoint.openConnection()); String s2 = "Bearer " + token; conn.setRequestProperty("Authorization", s2); conn.setDoOutput(true); conn.setRequestMethod("POST"); - conn.setFixedLengthStreamingMode(contentLength); - conn.setRequestProperty("Content-Type", bct); - conn.setRequestProperty("Accept", "*/*"); - conn.connect(); + conn.setFixedLengthStreamingMode(contentLength); + conn.setRequestProperty("Content-Type", bct); + conn.setRequestProperty("Accept", "*/*"); + conn.connect(); OutputStream ostream = conn.getOutputStream(); InputStream istream = new FileInputStream(file); @@ -536,12 +536,12 @@ MastodonApi { ostream.write(fcd.getBytes()); ostream.write(fct.getBytes()); - int c; while ((c = istream.read()) != -1) - ostream.write(c); + int c; while ((c = istream.read()) != -1) + ostream.write(c); ostream.write(feb.getBytes()); - istream.close(); - ostream.close(); + istream.close(); + ostream.close(); doStandardJsonReturn(conn, handler); } @@ -550,7 +550,7 @@ MastodonApi { public void monitorTimeline( - TimelineType type, ServerSideEventsListener handler) + TimelineType type, ServerSideEventsListener handler) { String token = accessToken.get("access_token").value; @@ -584,7 +584,7 @@ MastodonApi { conn.setReadTimeout(500); Reader input = ireader(conn.getInputStream()); BufferedReader br = new BufferedReader(input); - Thread thread = Thread.currentThread(); + Thread thread = Thread.currentThread(); while (true) try { String line = br.readLine(); @@ -629,7 +629,7 @@ MastodonApi { int code = conn.getResponseCode(); if (code >= 300) { - Reader input = ireader(conn.getErrorStream()); + Reader input = ireader(conn.getErrorStream()); Tree response = fromPlain(input); input.close(); handler.requestFailed(code, response); @@ -663,13 +663,13 @@ MastodonApi { // - -%- - - private static String - deescape(String string) - { - if (string == null) return string; - string = string.replaceAll("\n", "\\\\n"); - return string; - } + private static String + deescape(String string) + { + if (string == null) return string; + string = string.replaceAll("\n", "\\\\n"); + return string; + } private static Tree fromPlain(Reader r) @@ -699,27 +699,27 @@ MastodonApi { } } - private static HttpURLConnection - cast(URLConnection conn) - { - return (HttpURLConnection)conn; - } + private static HttpURLConnection + cast(URLConnection conn) + { + return (HttpURLConnection)conn; + } - private static InputStreamReader - ireader(InputStream is) - throws IOException - { - assert is != null; - return new InputStreamReader(is); - } + private static InputStreamReader + ireader(InputStream is) + throws IOException + { + assert is != null; + return new InputStreamReader(is); + } - private static OutputStreamWriter - owriter(OutputStream os) - throws IOException - { - assert os != null; - return new OutputStreamWriter(os); - } + private static OutputStreamWriter + owriter(OutputStream os) + throws IOException + { + assert os != null; + return new OutputStreamWriter(os); + } // ---%-@-%--- diff --git a/NotificationsWindow.java b/NotificationsWindow.java index 0465c67..37bc9d0 100644 --- a/NotificationsWindow.java +++ b/NotificationsWindow.java @@ -48,80 +48,80 @@ import java.awt.event.ComponentEvent; class NotificationsWindow extends JFrame { - private JKomasto - primaire; + private JKomasto + primaire; - private List - notifications; + private List + notifications; - private MastodonApi - api; + private MastodonApi + api; -// - -%- - +// - -%- - - private NotificationsComponent - display; + private NotificationsComponent + display; - private boolean - showingLatest; + private boolean + showingLatest; -// - -%- - +// - -%- - - private static int - ROW_COUNT = NotificationsComponent.ROW_COUNT; + private static int + ROW_COUNT = NotificationsComponent.ROW_COUNT; -// ---%-@-%--- +// ---%-@-%--- - public synchronized void - readEntity(Tree entity) - { - notifications = new ArrayList<>(); - for (Tree t: entity) - { - Notification n = new Notification(); + public synchronized void + readEntity(Tree entity) + { + notifications = new ArrayList<>(); + for (Tree t: entity) + { + Notification n = new Notification(); - n.id = t.get("id").value; + n.id = t.get("id").value; - String type = t.get("type").value; - if (type.equals("favourite")) - n.type = NotificationType.FAVOURITE; - else if (type.equals("reblog")) - n.type = NotificationType.BOOST; - else if (type.equals("mention")) - n.type = NotificationType.MENTION; - else if (type.equals("follow")) - n.type = NotificationType.FOLLOW; - else if (type.equals("follow_request")) - n.type = NotificationType.FOLLOWREQ; - else if (type.equals("poll")) - n.type = NotificationType.POLL; - else if (type.equals("status")) - n.type = NotificationType.ALERT; + String type = t.get("type").value; + if (type.equals("favourite")) + n.type = NotificationType.FAVOURITE; + else if (type.equals("reblog")) + n.type = NotificationType.BOOST; + else if (type.equals("mention")) + n.type = NotificationType.MENTION; + else if (type.equals("follow")) + n.type = NotificationType.FOLLOW; + else if (type.equals("follow_request")) + n.type = NotificationType.FOLLOWREQ; + else if (type.equals("poll")) + n.type = NotificationType.POLL; + else if (type.equals("status")) + n.type = NotificationType.ALERT; - Tree actor = t.get("account"); - String aid, aname, adisp; - aid = actor.get("id").value; - aname = actor.get("username").value; - adisp = actor.get("display_name").value; - if (!adisp.isEmpty()) n.actorName = adisp; - else n.actorName = aname; - n.actorNumId = aid; + Tree actor = t.get("account"); + String aid, aname, adisp; + aid = actor.get("id").value; + aname = actor.get("username").value; + adisp = actor.get("display_name").value; + if (!adisp.isEmpty()) n.actorName = adisp; + else n.actorName = aname; + n.actorNumId = aid; - if (n.type != NotificationType.FOLLOW) - { + if (n.type != NotificationType.FOLLOW) + { Post post = new Post(t.get("status")); - post.resolveApproximateText(); - n.postId = post.id; - n.postText = post.approximateText; - } + post.resolveApproximateText(); + n.postId = post.id; + n.postText = post.approximateText; + } - notifications.add(n); - } - } + notifications.add(n); + } + } - public void - refresh() - { + public void + refresh() + { String firstId = null; if (!showingLatest) { @@ -134,53 +134,53 @@ NotificationsWindow extends JFrame { if (notifications.size() < ROW_COUNT) showLatestPage(); display.showNotifications(notifications); display.repaint(); - } - } + } + } - public void - showLatestPage() - { - if (fetchPage(null, null)) - { + public void + showLatestPage() + { + if (fetchPage(null, null)) + { display.showNotifications(notifications); showingLatest = true; primaire.getWindowUpdater().add(this); } - } + } - public void - showPrevPage() - { - assert !notifications.isEmpty(); - if (fetchPage(null, notifications.get(0).id)) - { + public void + showPrevPage() + { + assert !notifications.isEmpty(); + if (fetchPage(null, notifications.get(0).id)) + { if (notifications.size() < ROW_COUNT) showLatestPage(); display.showNotifications(notifications); showingLatest = false; primaire.getWindowUpdater().remove(this); - } - } + } + } - public void - showNextPage() - { - assert !notifications.isEmpty(); - int last = notifications.size() - 1; + public void + showNextPage() + { + assert !notifications.isEmpty(); + int last = notifications.size() - 1; if (fetchPage(notifications.get(last).id, null)) { display.showNotifications(notifications); showingLatest = false; primaire.getWindowUpdater().remove(this); - } - } + } + } -// - -%- - +// - -%- - - private boolean - fetchPage(String maxId, String minId) - { - display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); - class Handler implements RequestListener { + private boolean + fetchPage(String maxId, String minId) + { + display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + class Handler implements RequestListener { boolean succeeded = false; @@ -207,31 +207,31 @@ NotificationsWindow extends JFrame { } } Handler handler = new Handler(); - api.getNotifications(ROW_COUNT, maxId, minId, handler); - display.setCursor(null); - repaint(); - return handler.succeeded; - } + api.getNotifications(ROW_COUNT, maxId, minId, handler); + display.setCursor(null); + repaint(); + return handler.succeeded; + } -// ---%-@-%--- +// ---%-@-%--- - NotificationsWindow(JKomasto primaire) - { - super("Notifications"); - this.primaire = primaire; - this.api = primaire.getMastodonApi(); + NotificationsWindow(JKomasto primaire) + { + super("Notifications"); + this.primaire = primaire; + this.api = primaire.getMastodonApi(); - notifications = new ArrayList<>(); + notifications = new ArrayList<>(); - display = new NotificationsComponent(this); - display.setPreferredSize(new Dimension(256, 260)); - setContentPane(display); - pack(); + display = new NotificationsComponent(this); + display.setPreferredSize(new Dimension(256, 260)); + setContentPane(display); + pack(); - setIconImage(primaire.getProgramIcon()); - setDefaultCloseOperation(DISPOSE_ON_CLOSE); - setVisible(true); - } + setIconImage(primaire.getProgramIcon()); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setVisible(true); + } } @@ -239,88 +239,88 @@ class NotificationsComponent extends JPanel implements ActionListener { - private NotificationsWindow - primaire; + private NotificationsWindow + primaire; - private JButton - prev, next; + private JButton + prev, next; -// - -%- - +// - -%- - - private List - rows; + private List + rows; -// - -%- - +// - -%- - - static final int - ROW_COUNT = 10; + static final int + ROW_COUNT = 10; -// ---%-@-%--- +// ---%-@-%--- - public void - showNotifications(List notifications) - { - assert notifications.size() == rows.size(); - for (int o = 0; o < rows.size(); ++o) - { - Notification n = notifications.get(o); - NotificationComponent c = rows.get(o); - c.setName(n.actorName); - switch (n.type) - { - case MENTION: c.setType("mentioned"); break; - case BOOST: c.setType("boosted"); break; - case FAVOURITE: c.setType("favourited"); break; - case FOLLOW: c.setType("followed"); break; - case FOLLOWREQ: c.setType("req. follow"); break; - case POLL: c.setType("poll ended"); break; - case ALERT: c.setType("posted"); break; - } - c.setText(n.postText); - } - } + public void + showNotifications(List notifications) + { + assert notifications.size() == rows.size(); + for (int o = 0; o < rows.size(); ++o) + { + Notification n = notifications.get(o); + NotificationComponent c = rows.get(o); + c.setName(n.actorName); + switch (n.type) + { + case MENTION: c.setType("mentioned"); break; + case BOOST: c.setType("boosted"); break; + case FAVOURITE: c.setType("favourited"); break; + case FOLLOW: c.setType("followed"); break; + case FOLLOWREQ: c.setType("req. follow"); break; + case POLL: c.setType("poll ended"); break; + case ALERT: c.setType("posted"); break; + } + c.setText(n.postText); + } + } -// - -%- - +// - -%- - - public void - actionPerformed(ActionEvent eA) - { - if (eA.getSource() == prev) primaire.showPrevPage(); - if (eA.getSource() == next) primaire.showNextPage(); - } + public void + actionPerformed(ActionEvent eA) + { + if (eA.getSource() == prev) primaire.showPrevPage(); + if (eA.getSource() == next) primaire.showNextPage(); + } -// ---%-@-%--- +// ---%-@-%--- - NotificationsComponent(NotificationsWindow primaire) - { - this.primaire = primaire; + NotificationsComponent(NotificationsWindow primaire) + { + this.primaire = primaire; - Border b = BorderFactory.createEmptyBorder(8, 8, 8, 8); + Border b = BorderFactory.createEmptyBorder(8, 8, 8, 8); - rows = new ArrayList<>(); - for (int n = ROW_COUNT; n > 0; --n) - rows.add(new NotificationComponent()); + rows = new ArrayList<>(); + for (int n = ROW_COUNT; n > 0; --n) + rows.add(new NotificationComponent()); - prev = new JButton("<"); - next = new JButton(">"); - prev.addActionListener(this); - next.addActionListener(this); + prev = new JButton("<"); + next = new JButton(">"); + prev.addActionListener(this); + next.addActionListener(this); - JPanel centre = new JPanel(); - centre.setLayout(new GridLayout(ROW_COUNT, 1)); - for (NotificationComponent c: rows) centre.add(c); + JPanel centre = new JPanel(); + centre.setLayout(new GridLayout(ROW_COUNT, 1)); + for (NotificationComponent c: rows) centre.add(c); - Box bottom = Box.createHorizontalBox(); - bottom.add(Box.createGlue()); - bottom.add(prev); - bottom.add(Box.createHorizontalStrut(8)); - bottom.add(next); - bottom.setBorder(b); + Box bottom = Box.createHorizontalBox(); + bottom.add(Box.createGlue()); + bottom.add(prev); + bottom.add(Box.createHorizontalStrut(8)); + bottom.add(next); + bottom.setBorder(b); - setLayout(new BorderLayout()); - add(centre); - add(bottom, BorderLayout.SOUTH); - } + setLayout(new BorderLayout()); + add(centre); + add(bottom, BorderLayout.SOUTH); + } } @@ -328,145 +328,145 @@ class NotificationComponent extends JComponent implements ComponentListener { - private JLabel - type; + private JLabel + type; - private ImageComponent - typeImg; + private ImageComponent + typeImg; - private JLabel - name, text; + private JLabel + name, text; -// ---%-@-%--- +// ---%-@-%--- - public void - setType(String n) - { - type.setText(n); - typeImg.image = ImageApi.local(n + "Notification"); - } + public void + setType(String n) + { + type.setText(n); + typeImg.image = ImageApi.local(n + "Notification"); + } - public void - setName(String n) { name.setText(n); } + public void + setName(String n) { name.setText(n); } - public void - setText(String n) { text.setText(n); } + public void + setText(String n) { text.setText(n); } -// - -%- - +// - -%- - - public void - doLayout() - { - int w = getWidth(), h = getHeight(); + public void + doLayout() + { + int w = getWidth(), h = getHeight(); - int x0 = w * 0/20; - int x1 = w * 7/20; - int x2 = w * 13/20; - int x3 = w * 15/20; - int x4 = w * 20/20; + int x0 = w * 0/20; + int x1 = w * 7/20; + int x2 = w * 13/20; + int x3 = w * 15/20; + int x4 = w * 20/20; - name.setLocation(x0 + 4, 0); - name.setSize((x1 - 4) - (x0 + 4), h); + name.setLocation(x0 + 4, 0); + name.setSize((x1 - 4) - (x0 + 4), h); - type.setLocation(x1 + 4, 0); - type.setSize((x2 - 1) - (x1 + 4), h); + type.setLocation(x1 + 4, 0); + type.setSize((x2 - 1) - (x1 + 4), h); - typeImg.setLocation((x2 + 1), 2); - typeImg.setSize((x3 - 4) - (x2 + 1), (h - 2) - 2); + typeImg.setLocation((x2 + 1), 2); + typeImg.setSize((x3 - 4) - (x2 + 1), (h - 2) - 2); - text.setLocation(x3 + 4, 0); - text.setSize((x4 - 4) - (x3 + 4), h); - } + text.setLocation(x3 + 4, 0); + text.setSize((x4 - 4) - (x3 + 4), h); + } - public void - componentResized(ComponentEvent eC) { doLayout(); } + public void + componentResized(ComponentEvent eC) { doLayout(); } - public void - componentShown(ComponentEvent eC) { } + public void + componentShown(ComponentEvent eC) { } - public void - componentHidden(ComponentEvent eC) { } + public void + componentHidden(ComponentEvent eC) { } - public void - componentMoved(ComponentEvent eC) { } + public void + componentMoved(ComponentEvent eC) { } -// ---%-@-%--- +// ---%-@-%--- - private static class - ImageComponent extends JComponent { + private static class + ImageComponent extends JComponent { - private Image - image, - scaled; + private Image + image, + scaled; - private boolean - snapdown = false; + private boolean + snapdown = false; -// -=%=- +// -=%=- - protected void - paintComponent(Graphics g) - { - if (image == null) return; - int w = getWidth(), h = getHeight(); + protected void + paintComponent(Graphics g) + { + if (image == null) return; + int w = getWidth(), h = getHeight(); - int ow = image.getWidth(this); - int oh = image.getHeight(this); - int nh = h; - int nw = ow * nh/oh; - if (snapdown) - { - int sw, sh; - for (sw = 1; sw < nw; sw *= 2); - for (sh = 1; sh < nh; sh *= 2); - nw = sw / 2; - nh = sh / 2; - } + int ow = image.getWidth(this); + int oh = image.getHeight(this); + int nh = h; + int nw = ow * nh/oh; + if (snapdown) + { + int sw, sh; + for (sw = 1; sw < nw; sw *= 2); + for (sh = 1; sh < nh; sh *= 2); + nw = sw / 2; + nh = sh / 2; + } - if (scaled == null) - scaled = image.getScaledInstance( - nw, nh, - Image.SCALE_SMOOTH - ); - int x = (w - nw) / 2; - int y = (h - nh) / 2; - g.drawImage(scaled, x, y, this); - } + if (scaled == null) + scaled = image.getScaledInstance( + nw, nh, + Image.SCALE_SMOOTH + ); + int x = (w - nw) / 2; + int y = (h - nh) / 2; + g.drawImage(scaled, x, y, this); + } - } + } -// ---%-@-%--- +// ---%-@-%--- - NotificationComponent() - { - Font f = new Font("Dialog", Font.PLAIN, 12); - Font f1 = f.deriveFont(Font.PLAIN, 14); - Font f2 = f.deriveFont(Font.PLAIN, 11); - Font f3 = f.deriveFont(Font.ITALIC, 14); + NotificationComponent() + { + Font f = new Font("Dialog", Font.PLAIN, 12); + Font f1 = f.deriveFont(Font.PLAIN, 14); + Font f2 = f.deriveFont(Font.PLAIN, 11); + Font f3 = f.deriveFont(Font.ITALIC, 14); - Color c = new Color(0, 0, 0, 25); - Border b1 = BorderFactory.createMatteBorder(0, 0, 1, 0, c); + Color c = new Color(0, 0, 0, 25); + Border b1 = BorderFactory.createMatteBorder(0, 0, 1, 0, c); - name = new JLabel(); - name.setFont(f1); + name = new JLabel(); + name.setFont(f1); - type = new JLabel(); - type.setFont(f2); - type.setHorizontalAlignment(JLabel.RIGHT); + type = new JLabel(); + type.setFont(f2); + type.setHorizontalAlignment(JLabel.RIGHT); - typeImg = new ImageComponent(); + typeImg = new ImageComponent(); - text = new JLabel(); - text.setFont(f3); + text = new JLabel(); + text.setFont(f3); - setLayout(null); - add(name); - add(type); - add(typeImg); - add(text); + setLayout(null); + add(name); + add(type); + add(typeImg); + add(text); - this.addComponentListener(this); - setBorder(b1); - } + this.addComponentListener(this); + setBorder(b1); + } } diff --git a/PostWindow.java b/PostWindow.java index ce30b2e..3114c15 100644 --- a/PostWindow.java +++ b/PostWindow.java @@ -74,9 +74,9 @@ PostWindow extends JFrame { private MastodonApi api; - private Post - post, - wrapperPost; + private Post + post, + wrapperPost; // - -%- - @@ -109,21 +109,21 @@ PostWindow extends JFrame { } display.setAuthorName(post.author.name); - display.setAuthorId(post.author.id); + display.setAuthorId(post.author.id); - String oid = api.getAccountDetails().get("id").value; - String aid = post.author.numId; - display.setDeleteEnabled(aid.equals(oid)); + String oid = api.getAccountDetails().get("id").value; + String aid = post.author.numId; + display.setDeleteEnabled(aid.equals(oid)); - post.author.resolveAvatar(); - display.setAuthorAvatar(post.author.avatar); + post.author.resolveAvatar(); + display.setAuthorAvatar(post.author.avatar); display.setDate(post.date); display.setTime(post.time); - display.setEmojiUrls(post.emojiUrls); + display.setEmojiUrls(post.emojiUrls); - display.setHtml(post.text); + display.setHtml(post.text); display.setFavourited(post.favourited); display.setBoosted(post.boosted); @@ -137,29 +137,29 @@ PostWindow extends JFrame { post.resolveApproximateText(); this.setTitle(post.approximateText); - display.resetFocus(); + display.resetFocus(); repaint(); } - public void - readEntity(Tree post) - { + public void + readEntity(Tree post) + { use(new Post(post)); } public synchronized void openAuthorProfile() { - ProfileWindow w = new ProfileWindow(primaire); - w.use(post.author); - w.setLocationRelativeTo(this); - w.setVisible(true); + ProfileWindow w = new ProfileWindow(primaire); + w.use(post.author); + w.setLocationRelativeTo(this); + w.setVisible(true); } public synchronized void favourite(boolean favourited) { - display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); display.setFavouriteBoostEnabled(false); display.paintImmediately(display.getBounds()); RequestListener handler = new RequestListener() { @@ -188,11 +188,11 @@ PostWindow extends JFrame { public void requestSucceeded(Tree json) { - PostWindow.this.post.favourited = favourited; + PostWindow.this.post.favourited = favourited; } }; - api.setPostFavourited(post.id, favourited, handler); + api.setPostFavourited(post.id, favourited, handler); display.setCursor(null); display.setFavouriteBoostEnabled(true); display.repaint(); @@ -234,7 +234,7 @@ PostWindow extends JFrame { } }; - api.setPostBoosted(post.id, boosted, handler); + api.setPostBoosted(post.id, boosted, handler); display.setCursor(null); display.setFavouriteBoostEnabled(true); display.repaint(); @@ -246,9 +246,9 @@ PostWindow extends JFrame { String ownId = api.getAccountDetails().get("acct").value; Composition c = Composition.reply(this.post, ownId); ComposeWindow w = primaire.getComposeWindow(); - w.setComposition(c); - if (!w.isVisible()) - { + w.setComposition(c); + if (!w.isVisible()) + { w.setLocation(getX(), getY() + 100); w.setVisible(true); } @@ -271,62 +271,62 @@ PostWindow extends JFrame { display.setCursor(null); } - public synchronized void - deletePost(boolean redraft) - { - display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); - display.setDeleteEnabled(false); - display.paintImmediately(display.getBounds()); + public synchronized void + deletePost(boolean redraft) + { + display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + display.setDeleteEnabled(false); + display.paintImmediately(display.getBounds()); - final String S1 = + final String S1 = "Are you sure you'd like to delete this post?\n"; final String S2 = "Are you sure you'd like to delete this post?\n" + "You are redrafting, so a composition window\n" + "should open with its contents filled."; - JOptionPane dialog = new JOptionPane(); - dialog.setMessageType(JOptionPane.QUESTION_MESSAGE); - dialog.setMessage(redraft ? S2 : S1); - dialog.setOptions(new String[] { "No", "Yes" }); - String title = "Confirm delete"; - dialog.createDialog(this, title).setVisible(true); - if (!dialog.getValue().equals("Yes")) - { + JOptionPane dialog = new JOptionPane(); + dialog.setMessageType(JOptionPane.QUESTION_MESSAGE); + dialog.setMessage(redraft ? S2 : S1); + dialog.setOptions(new String[] { "No", "Yes" }); + String title = "Confirm delete"; + dialog.createDialog(this, title).setVisible(true); + if (!dialog.getValue().equals("Yes")) + { display.setCursor(null); display.setDeleteEnabled(true); display.paintImmediately(display.getBounds()); return; - } + } - api.deletePost(post.id, new RequestListener() { + api.deletePost(post.id, new RequestListener() { - public void - connectionFailed(IOException eIo) - { - JOptionPane.showMessageDialog( - PostWindow.this, - "Failed to delete post.." - + "\n" + eIo.getMessage() - ); - } + public void + connectionFailed(IOException eIo) + { + JOptionPane.showMessageDialog( + PostWindow.this, + "Failed to delete post.." + + "\n" + eIo.getMessage() + ); + } - public void - requestFailed(int httpCode, Tree json) - { - JOptionPane.showMessageDialog( - PostWindow.this, - "Failed to delete post.." - + "\n" + json.get("error").value - + "\n(HTTP code: " + httpCode + ")" - ); - } + public void + requestFailed(int httpCode, Tree json) + { + JOptionPane.showMessageDialog( + PostWindow.this, + "Failed to delete post.." + + "\n" + json.get("error").value + + "\n(HTTP code: " + httpCode + ")" + ); + } - public void - requestSucceeded(Tree json) - { - setVisible(false); + public void + requestSucceeded(Tree json) + { + setVisible(false); - if (redraft) + if (redraft) { Composition c = Composition.recover(json); ComposeWindow w = new ComposeWindow(primaire); @@ -334,36 +334,36 @@ PostWindow extends JFrame { w.setLocation(getX(), getY() + 100); w.setVisible(true); } - } + } - }); + }); - display.setCursor(null); - display.setDeleteEnabled(true); - display.paintImmediately(display.getBounds()); - if (!isVisible()) dispose(); - } + display.setCursor(null); + display.setDeleteEnabled(true); + display.paintImmediately(display.getBounds()); + if (!isVisible()) dispose(); + } - public synchronized void - copyPostId() - { + public synchronized void + copyPostId() + { ClipboardApi.serve(post.id); - } + } - public synchronized void - copyPostLink() - { + public synchronized void + copyPostLink() + { ClipboardApi.serve(post.uri); - } + } - public synchronized void - openReplies() - { + public synchronized void + openReplies() + { RepliesWindow w = new RepliesWindow(primaire, this); w.showFor(post.id); w.setLocation(getX(), getY() + 100); w.setVisible(true); - } + } // ---%-@-%--- @@ -380,7 +380,7 @@ PostWindow extends JFrame { display = new PostComponent(this); setContentPane(display); - setIconImage(primaire.getProgramIcon()); + setIconImage(primaire.getProgramIcon()); } } @@ -396,23 +396,23 @@ implements ActionListener { // - -%- - - private List - authorNameOr; + private List + authorNameOr; - private RichTextPane - authorName; + private RichTextPane + authorName; - private RichTextPane3 - body; + private RichTextPane3 + body; - private JScrollPane - bodyScrollPane; + private JScrollPane + bodyScrollPane; - private JLabel - authorId, time, date; + private JLabel + authorId, time, date; - private String[][] - emojiUrls; + private String[][] + emojiUrls; private TwoToggleButton favouriteBoost, @@ -423,32 +423,32 @@ implements ActionListener { profile, media; - private JPopupMenu - miscMenu; + private JPopupMenu + miscMenu; - private JMenuItem - openReplies, - copyPostId, - copyPostLink, - deletePost, - redraftPost; + private JMenuItem + openReplies, + copyPostId, + copyPostLink, + deletePost, + redraftPost; - private Image - backgroundImage; + private Image + backgroundImage; // ---%-@-%--- public void setAuthorName(String n) - { - authorNameOr = new RichTextPane.Builder().text(n).finish(); - } + { + authorNameOr = new RichTextPane.Builder().text(n).finish(); + } public void setAuthorId(String n) { authorId.setText(n); } - public void - setAuthorAvatar(Image n) { profile.setImage(n); } + public void + setAuthorAvatar(Image n) { profile.setImage(n); } public void setDate(String n) { date.setText(n); } @@ -456,24 +456,24 @@ implements ActionListener { public void setTime(String n) { time.setText(n); } - public void - setEmojiUrls(String[][] n) - { + public void + setEmojiUrls(String[][] n) + { emojiUrls = n; - Map emojis = new HashMap<>(); - for (String[] entry: n) - { - emojis.put(entry[0], ImageApi.remote(entry[1])); - } - body.setEmojis(emojis); + Map emojis = new HashMap<>(); + for (String[] entry: n) + { + emojis.put(entry[0], ImageApi.remote(entry[1])); + } + body.setEmojis(emojis); } - public void - setHtml(String n) - { + public void + setHtml(String n) + { body.setText(BasicHTMLParser.parse(n)); - } + } public void setFavourited(boolean a) @@ -497,19 +497,19 @@ implements ActionListener { favouriteBoost.setEnabled(a); } - public void - setDeleteEnabled(boolean a) - { - deletePost.setEnabled(a); - redraftPost.setEnabled(a); - } + public void + setDeleteEnabled(boolean a) + { + deletePost.setEnabled(a); + redraftPost.setEnabled(a); + } - public void - setMediaPreview(Image n) { media.setImage(n); } + public void + setMediaPreview(Image n) { media.setImage(n); } - public void - resetFocus() - { + public void + resetFocus() + { media.requestFocusInWindow(); } @@ -551,14 +551,14 @@ implements ActionListener { } else if (command.startsWith("reply")) { - primaire.reply(); + primaire.reply(); + } + else if (command.startsWith("misc")) + { + int rx = replyMisc.getWidth() / 2; + int ry = replyMisc.getHeight() - miscMenu.getHeight(); + miscMenu.show(replyMisc, rx, ry); } - else if (command.startsWith("misc")) - { - int rx = replyMisc.getWidth() / 2; - int ry = replyMisc.getHeight() - miscMenu.getHeight(); - miscMenu.show(replyMisc, rx, ry); - } return; } else miscMenu.setVisible(false); @@ -567,14 +567,14 @@ implements ActionListener { { if (command.startsWith("next")) { - body.nextPage(); + body.nextPage(); } else { - body.previousPage(); + body.previousPage(); } - // First time an interactive element - // doesn't call something in primaire.. + // First time an interactive element + // doesn't call something in primaire.. return; } @@ -582,13 +582,13 @@ implements ActionListener { { primaire.openMedia(); return; - } + } - if (src == openReplies) primaire.openReplies(); - if (src == copyPostId) primaire.copyPostId(); - if (src == copyPostLink) primaire.copyPostLink(); - if (src == deletePost) primaire.deletePost(false); - if (src == redraftPost) primaire.deletePost(true); + if (src == openReplies) primaire.openReplies(); + if (src == copyPostId) primaire.copyPostId(); + if (src == copyPostLink) primaire.copyPostLink(); + if (src == deletePost) primaire.deletePost(false); + if (src == redraftPost) primaire.deletePost(true); } @@ -597,28 +597,28 @@ implements ActionListener { { g.clearRect(0, 0, getWidth(), getHeight()); - int w1 = authorName.getWidth(); - FontMetrics fm1 = getFontMetrics(authorName.getFont()); - List lay1; - lay1 = RichTextPane.layout(authorNameOr, fm1, w1); - authorName.setText(lay1); + int w1 = authorName.getWidth(); + FontMetrics fm1 = getFontMetrics(authorName.getFont()); + List lay1; + lay1 = RichTextPane.layout(authorNameOr, fm1, w1); + authorName.setText(lay1); - if (backgroundImage != null) - { - int tw = backgroundImage.getWidth(this); - int th = backgroundImage.getHeight(this); - if (tw != -1) - for (int y = 0; y < getHeight(); y += th) - for (int x = 0; x < getWidth(); x += tw) - { - g.drawImage(backgroundImage, x, y, this); - } - } + if (backgroundImage != null) + { + int tw = backgroundImage.getWidth(this); + int th = backgroundImage.getHeight(this); + if (tw != -1) + for (int y = 0; y < getHeight(); y += th) + for (int x = 0; x < getWidth(); x += tw) + { + g.drawImage(backgroundImage, x, y, this); + } + } - ((java.awt.Graphics2D)g).setRenderingHint( - java.awt.RenderingHints.KEY_ANTIALIASING, - java.awt.RenderingHints.VALUE_ANTIALIAS_ON - ); + ((java.awt.Graphics2D)g).setRenderingHint( + java.awt.RenderingHints.KEY_ANTIALIASING, + java.awt.RenderingHints.VALUE_ANTIALIAS_ON + ); } // - -%- - @@ -648,18 +648,18 @@ implements ActionListener { { this.primaire = primaire; - emojiUrls = new String[0][]; + emojiUrls = new String[0][]; Border b = BorderFactory.createEmptyBorder(10, 10, 10, 10); - Font f1 = new Font("MotoyaLMaru", Font.PLAIN, 18); + Font f1 = new Font("MotoyaLMaru", Font.PLAIN, 18); Font f2 = new Font("MotoyaLMaru", Font.PLAIN, 14); - Font f3 = new Font("MotoyaLMaru", Font.PLAIN, 18); + Font f3 = new Font("MotoyaLMaru", Font.PLAIN, 18); profile = new RoundButton(); - favouriteBoost = new TwoToggleButton("favourite", "boost"); - replyMisc = new TwoToggleButton("reply", "misc"); - nextPrev = new TwoToggleButton("next", "prev"); - media = new RoundButton(); + favouriteBoost = new TwoToggleButton("favourite", "boost"); + replyMisc = new TwoToggleButton("reply", "misc"); + nextPrev = new TwoToggleButton("next", "prev"); + media = new RoundButton(); profile.addActionListener(this); favouriteBoost.addActionListener(this); replyMisc.addActionListener(this); @@ -669,22 +669,22 @@ implements ActionListener { openReplies = new JMenuItem("Browse thread"); copyPostId = new JMenuItem("Copy post ID"); copyPostLink = new JMenuItem("Copy post link"); - deletePost = new JMenuItem("Delete post"); - redraftPost = new JMenuItem("Delete and redraft post"); - openReplies.addActionListener(this); - copyPostId.addActionListener(this); - copyPostLink.addActionListener(this); - deletePost.addActionListener(this); - redraftPost.addActionListener(this); - miscMenu = new JPopupMenu(); - miscMenu.add(openReplies); - miscMenu.add(new JSeparator()); - miscMenu.add(copyPostId); - miscMenu.add(copyPostLink); - miscMenu.add(new JSeparator()); - miscMenu.add(deletePost); - miscMenu.add(new JSeparator()); - miscMenu.add(redraftPost); + deletePost = new JMenuItem("Delete post"); + redraftPost = new JMenuItem("Delete and redraft post"); + openReplies.addActionListener(this); + copyPostId.addActionListener(this); + copyPostLink.addActionListener(this); + deletePost.addActionListener(this); + redraftPost.addActionListener(this); + miscMenu = new JPopupMenu(); + miscMenu.add(openReplies); + miscMenu.add(new JSeparator()); + miscMenu.add(copyPostId); + miscMenu.add(copyPostLink); + miscMenu.add(new JSeparator()); + miscMenu.add(deletePost); + miscMenu.add(new JSeparator()); + miscMenu.add(redraftPost); Box buttons = Box.createVerticalBox(); buttons.setOpaque(false); @@ -702,59 +702,59 @@ implements ActionListener { left.setOpaque(false); left.add(buttons); - authorId = new JLabel(); - authorName = new RichTextPane(); - time = new JLabel(); - date = new JLabel(); - authorId.setFont(f2); - date.setFont(f2); - authorName.setFont(f1); - time.setFont(f1); + authorId = new JLabel(); + authorName = new RichTextPane(); + time = new JLabel(); + date = new JLabel(); + authorId.setFont(f2); + date.setFont(f2); + authorName.setFont(f1); + time.setFont(f1); - JPanel top1 = new JPanel(); - top1.setOpaque(false); - top1.setLayout(new BorderLayout(8, 0)); - top1.add(authorId); - top1.add(date, BorderLayout.EAST); - JPanel top2 = new JPanel(); - top2.setOpaque(false); - top2.setLayout(new BorderLayout(8, 0)); - top2.add(authorName); - top2.add(time, BorderLayout.EAST); - Box top = Box.createVerticalBox(); - top.add(top1); - top.add(Box.createVerticalStrut(2)); - top.add(top2); + JPanel top1 = new JPanel(); + top1.setOpaque(false); + top1.setLayout(new BorderLayout(8, 0)); + top1.add(authorId); + top1.add(date, BorderLayout.EAST); + JPanel top2 = new JPanel(); + top2.setOpaque(false); + top2.setLayout(new BorderLayout(8, 0)); + top2.add(authorName); + top2.add(time, BorderLayout.EAST); + Box top = Box.createVerticalBox(); + top.add(top1); + top.add(Box.createVerticalStrut(2)); + top.add(top2); - body = new RichTextPane3(); - body.setFont(f3); + body = new RichTextPane3(); + body.setFont(f3); - /* - bodyScrollPane = new JScrollPane( - body, - JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, - JScrollPane.HORIZONTAL_SCROLLBAR_NEVER - ); - JScrollBar vsb = bodyScrollPane.getVerticalScrollBar(); - vsb.setPreferredSize(new Dimension(0, 0)); - vsb.setUnitIncrement(16); - bodyScrollPane.setBorder(null); - bodyScrollPane.setFocusable(true); - */ + /* + bodyScrollPane = new JScrollPane( + body, + JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, + JScrollPane.HORIZONTAL_SCROLLBAR_NEVER + ); + JScrollBar vsb = bodyScrollPane.getVerticalScrollBar(); + vsb.setPreferredSize(new Dimension(0, 0)); + vsb.setUnitIncrement(16); + bodyScrollPane.setBorder(null); + bodyScrollPane.setFocusable(true); + */ - JPanel centre = new JPanel(); - centre.setOpaque(false); - centre.setLayout(new BorderLayout(0, 8)); - centre.add(top, BorderLayout.NORTH); - centre.add(body); + JPanel centre = new JPanel(); + centre.setOpaque(false); + centre.setLayout(new BorderLayout(0, 8)); + centre.add(top, BorderLayout.NORTH); + centre.add(body); setLayout(new BorderLayout(8, 0)); add(left, BorderLayout.WEST); - add(centre); + add(centre); - setBorder(b); + setBorder(b); - backgroundImage = ImageApi.local("postWindow"); + backgroundImage = ImageApi.local("postWindow"); } } diff --git a/ProfileWindow.java b/ProfileWindow.java index 5e560b7..47902d1 100644 --- a/ProfileWindow.java +++ b/ProfileWindow.java @@ -123,7 +123,7 @@ ProfileWindow extends JFrame { { display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); - TimelineWindow w = new TimelineWindow(primaire); + TimelineWindow w = new TimelineWindow(primaire); w.showAuthorPosts(account.numId); w.showLatestPage(); w.setLocationRelativeTo(this); @@ -269,9 +269,9 @@ implements ActionListener { int acx = ax + (aw / 2); int acy = ay + (ah / 2); Shape defaultClip = g.getClip(); - g.setClip(new Ellipse2D.Float(ax, ay, aw, ah)); + g.setClip(new Ellipse2D.Float(ax, ay, aw, ah)); g.drawImage(avatar, ax, ay, aw, ah, this); - g.setClip(defaultClip); + g.setClip(defaultClip); g.setColor(new Color(0, 0, 0, 50)); g.fillRect(0, acy - dy1, acx - dx1, 2); diff --git a/RepliesWindow.java b/RepliesWindow.java index 2763e76..0a649e6 100644 --- a/RepliesWindow.java +++ b/RepliesWindow.java @@ -189,7 +189,7 @@ RepliesWindow extends JFrame { setContentPane(display); setSize(384, 224); - setIconImage(primaire.getProgramIcon()); + setIconImage(primaire.getProgramIcon()); } } @@ -242,13 +242,13 @@ implements TreeSelectionListener { if (p == null) { assert false; - /* - * Besides descendants possibly not being in order, - * the top of the thread might be deleted and so - * thread.top gets set to the given post. Which - * sibling replies aren't replying to, resulting - * in assertion failure. - */ + /* + * Besides descendants possibly not being in order, + * the top of the thread might be deleted and so + * thread.top gets set to the given post. Which + * sibling replies aren't replying to, resulting + * in assertion failure. + */ continue; } @@ -257,8 +257,8 @@ implements TreeSelectionListener { } tree.setModel(new DefaultTreeModel(root)); - for (int o = 0; o < tree.getRowCount(); ++o) - tree.expandRow(o); + for (int o = 0; o < tree.getRowCount(); ++o) + tree.expandRow(o); } // - -%- - diff --git a/RequestListener.java b/RequestListener.java index fe1f115..37f096b 100644 --- a/RequestListener.java +++ b/RequestListener.java @@ -24,14 +24,14 @@ import java.io.IOException; interface RequestListener { - void - connectionFailed(IOException eIo); + void + connectionFailed(IOException eIo); - void - requestFailed(int httpCode, Tree json); + void + requestFailed(int httpCode, Tree json); - void - requestSucceeded(Tree json); + void + requestSucceeded(Tree json); } diff --git a/RichTextPane.java b/RichTextPane.java index 0f84d5d..20f0829 100644 --- a/RichTextPane.java +++ b/RichTextPane.java @@ -38,396 +38,396 @@ class RichTextPane extends JComponent implements MouseListener, MouseMotionListener, KeyListener { - private List - text; + private List + text; - private int - selectionStart, selectionEnd; + private int + selectionStart, selectionEnd; -// ---%-@-%--- +// ---%-@-%--- - public void - setText(List text) - { - this.text = text; - selectionStart = selectionEnd = -1; - } + public void + setText(List text) + { + this.text = text; + selectionStart = selectionEnd = -1; + } - public List - getSelection() - { - List returnee = new LinkedList<>(); - if (selectionEnd == -1) return returnee; + public List + getSelection() + { + List returnee = new LinkedList<>(); + if (selectionEnd == -1) return returnee; - if (selectionEnd < selectionStart) { - int t = selectionEnd; - selectionEnd = selectionStart; - selectionStart = t; - } - returnee.addAll(text.subList(selectionStart + 1, selectionEnd)); - return returnee; - } + if (selectionEnd < selectionStart) { + int t = selectionEnd; + selectionEnd = selectionStart; + selectionStart = t; + } + returnee.addAll(text.subList(selectionStart + 1, selectionEnd)); + return returnee; + } - public void - copySelection() - { + public void + copySelection() + { assert selectionEnd != -1; - StringBuilder b = new StringBuilder(); - for (Segment segment: getSelection()) - { - if (segment.link != null) b.append(segment.link); - else if (segment.text != null) b.append(segment.text); - } - ClipboardApi.serve(b.toString()); - } + StringBuilder b = new StringBuilder(); + for (Segment segment: getSelection()) + { + if (segment.link != null) b.append(segment.link); + else if (segment.text != null) b.append(segment.text); + } + ClipboardApi.serve(b.toString()); + } -// - -%- - +// - -%- - - protected void - paintComponent(Graphics g) - { - g.setFont(getFont()); - FontMetrics fm = g.getFontMetrics(getFont()); + protected void + paintComponent(Graphics g) + { + g.setFont(getFont()); + FontMetrics fm = g.getFontMetrics(getFont()); - if (isOpaque()) - g.clearRect(0, 0, getWidth(), getHeight()); + if (isOpaque()) + g.clearRect(0, 0, getWidth(), getHeight()); - ((java.awt.Graphics2D)g).setRenderingHint( - java.awt.RenderingHints.KEY_TEXT_ANTIALIASING, - java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON - ); + ((java.awt.Graphics2D)g).setRenderingHint( + java.awt.RenderingHints.KEY_TEXT_ANTIALIASING, + java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON + ); - int o = 0; - for (Segment segment: text) - { - if (segment.image != null) { - int ow = segment.image.getIconWidth(); - int oh = segment.image.getIconHeight(); - int h = fm.getAscent() + fm.getDescent(); - int w = h * ow / oh; - int x = segment.x; - int y = segment.y + fm.getDescent(); - // Interpret segment.y as specifying text baseline - Image img = segment.image.getImage(); - g.drawImage(img, x, y - h, w, h, this); - continue; - } + int o = 0; + for (Segment segment: text) + { + if (segment.image != null) { + int ow = segment.image.getIconWidth(); + int oh = segment.image.getIconHeight(); + int h = fm.getAscent() + fm.getDescent(); + int w = h * ow / oh; + int x = segment.x; + int y = segment.y + fm.getDescent(); + // Interpret segment.y as specifying text baseline + Image img = segment.image.getImage(); + g.drawImage(img, x, y - h, w, h, this); + continue; + } - if (o > selectionStart && o < selectionEnd) - { - int dx = fm.stringWidth(segment.text); - int dy1 = fm.getAscent(); - int dy2 = dy1 + fm.getDescent(); - g.setColor(new Color(0, 0, 0, 15)); - g.fillRect(segment.x, segment.y - dy1, dx, dy2); - g.setColor(getForeground()); - } + if (o > selectionStart && o < selectionEnd) + { + int dx = fm.stringWidth(segment.text); + int dy1 = fm.getAscent(); + int dy2 = dy1 + fm.getDescent(); + g.setColor(new Color(0, 0, 0, 15)); + g.fillRect(segment.x, segment.y - dy1, dx, dy2); + g.setColor(getForeground()); + } - if (segment.link != null) g.setColor(Color.BLUE); - g.drawString(segment.text, segment.x, segment.y); - g.setColor(getForeground()); + if (segment.link != null) g.setColor(Color.BLUE); + g.drawString(segment.text, segment.x, segment.y); + g.setColor(getForeground()); - ++o; - } - } + ++o; + } + } - public void - mousePressed(MouseEvent eM) - { - requestFocusInWindow(); - selectionStart = identify(eM.getX(), eM.getY()) - 2; - selectionEnd = -1; - repaint(); - } + public void + mousePressed(MouseEvent eM) + { + requestFocusInWindow(); + selectionStart = identify(eM.getX(), eM.getY()) - 2; + selectionEnd = -1; + repaint(); + } - public void - mouseDragged(MouseEvent eM) - { - selectionEnd = identify(eM.getX(), eM.getY()); - repaint(); - } + public void + mouseDragged(MouseEvent eM) + { + selectionEnd = identify(eM.getX(), eM.getY()); + repaint(); + } - private int - identify(int x, int y) - { - FontMetrics fm = getFontMetrics(getFont()); - int iy = fm.getAscent(); - int lh = fm.getAscent() + fm.getDescent(); - y -= fm.getDescent(); - if (y <= iy) y = iy; - else y += lh - ((y - iy) % lh); - /* - * Snaps y to the next baseline. Kind of obtuse, - * but it wasn't randomly derived, anyways - * you can test it for 13, 30, 47, etc. - */ + private int + identify(int x, int y) + { + FontMetrics fm = getFontMetrics(getFont()); + int iy = fm.getAscent(); + int lh = fm.getAscent() + fm.getDescent(); + y -= fm.getDescent(); + if (y <= iy) y = iy; + else y += lh - ((y - iy) % lh); + /* + * Snaps y to the next baseline. Kind of obtuse, + * but it wasn't randomly derived, anyways + * you can test it for 13, 30, 47, etc. + */ - int o = 0; - for (Segment segment: text) - { - if (segment.y == y && segment.x > x) break; - if (segment.y > y) break; - ++o; - } - return o; - } + int o = 0; + for (Segment segment: text) + { + if (segment.y == y && segment.x > x) break; + if (segment.y > y) break; + ++o; + } + return o; + } - public void - keyPressed(KeyEvent eK) - { - if (selectionEnd == -1) return; - if (eK.getKeyCode() != KeyEvent.VK_C) return; - if (!eK.isControlDown()) return; - copySelection(); - } + public void + keyPressed(KeyEvent eK) + { + if (selectionEnd == -1) return; + if (eK.getKeyCode() != KeyEvent.VK_C) return; + if (!eK.isControlDown()) return; + copySelection(); + } - public void - keyReleased(KeyEvent eK) { } + public void + keyReleased(KeyEvent eK) { } - public void - keyTyped(KeyEvent eK) { } + public void + keyTyped(KeyEvent eK) { } - public void - mouseClicked(MouseEvent eM) { } + public void + mouseClicked(MouseEvent eM) { } - public void - mouseReleased(MouseEvent eM) { } + public void + mouseReleased(MouseEvent eM) { } - public void - mouseEntered(MouseEvent eM) { } + public void + mouseEntered(MouseEvent eM) { } - public void - mouseExited(MouseEvent eM) { } + public void + mouseExited(MouseEvent eM) { } - public void - mouseMoved(MouseEvent eM) { } + public void + mouseMoved(MouseEvent eM) { } -// - -%- - +// - -%- - - public static List - layout(List text, FontMetrics fm, int width) - { - List copy = new LinkedList<>(); - for (Segment segment: text) copy.add(segment.clone()); - text = copy; - ListIterator cursor = text.listIterator(); - int x = 0, y = fm.getAscent(); - int dy = fm.getAscent() + fm.getDescent(); - while (cursor.hasNext()) - { - Segment curr = cursor.next(); + public static List + layout(List text, FontMetrics fm, int width) + { + List copy = new LinkedList<>(); + for (Segment segment: text) copy.add(segment.clone()); + text = copy; + ListIterator cursor = text.listIterator(); + int x = 0, y = fm.getAscent(); + int dy = fm.getAscent() + fm.getDescent(); + while (cursor.hasNext()) + { + Segment curr = cursor.next(); - int dx; - if (curr.image != null) { - int ow = curr.image.getIconWidth(); - int oh = curr.image.getIconHeight(); - int nh = fm.getAscent() + fm.getDescent(); - dx = nh * ow / oh; - } - else if (curr.text != null) { - dx = fm.stringWidth(curr.text); - } - else if (curr.link != null) { - curr.text = curr.link; - dx = fm.stringWidth(curr.link); - } - else { - assert false; - dx = 0; - } + int dx; + if (curr.image != null) { + int ow = curr.image.getIconWidth(); + int oh = curr.image.getIconHeight(); + int nh = fm.getAscent() + fm.getDescent(); + dx = nh * ow / oh; + } + else if (curr.text != null) { + dx = fm.stringWidth(curr.text); + } + else if (curr.link != null) { + curr.text = curr.link; + dx = fm.stringWidth(curr.link); + } + else { + assert false; + dx = 0; + } - boolean fits = x + dx < width; + boolean fits = x + dx < width; - if (fits || curr.spacer) - { - curr.x = x; - curr.y = y; - x += dx; - if (curr.spacer && curr.text.equals("\n")) { - y += dy; - x = 0; - } - continue; - } + if (fits || curr.spacer) + { + curr.x = x; + curr.y = y; + x += dx; + if (curr.spacer && curr.text.equals("\n")) { + y += dy; + x = 0; + } + continue; + } - boolean tooLong = dx > width; - boolean canFitChar = width >= fm.getMaxAdvance(); - boolean splittable = curr.image == null; - /* - * A bit of redundancy in my conditions, but the point is - * to exactly express the triggers in my mental model. - * The conditions should read more like English. - */ + boolean tooLong = dx > width; + boolean canFitChar = width >= fm.getMaxAdvance(); + boolean splittable = curr.image == null; + /* + * A bit of redundancy in my conditions, but the point is + * to exactly express the triggers in my mental model. + * The conditions should read more like English. + */ - if (!tooLong || (tooLong && !splittable)) - { - curr.x = 0; - curr.y = y += dy; - x = dx; - continue; - } + if (!tooLong || (tooLong && !splittable)) + { + curr.x = 0; + curr.y = y += dy; + x = dx; + continue; + } - assert tooLong && splittable; + assert tooLong && splittable; - String s = curr.text; - int splitOffset; - for (splitOffset = 0; splitOffset < s.length(); ++splitOffset) - { - String substring = s.substring(0, splitOffset + 1); - if (fm.stringWidth(substring) > width) break; - } - if (splitOffset == 0) splitOffset = 1; - /* - * I force a split even if our width supports no characters. - * Because if I don't split, the only alternatives to infinitely - * looping downwards is to emplace this segment or ignore it. - */ - Segment fitted = new Segment(); - fitted.text = s.substring(0, splitOffset); - fitted.link = curr.link; - fitted.x = x; - fitted.y = y; - cursor.add(fitted); - curr.text = s.substring(splitOffset); - y += dy; - x = 0; - cursor.add(curr); cursor.previous(); - /* - * I had to use a stack and return a new list because, - * splitting can turn a long segment into several spread - * over different lines. Here curr becomes the "after-split" - * and I push it back to the stack. - * - * If #layout wasn't a separate method, but rather only for - * graphical painting. Then I don't need a stack nor return - * a new list. I iterate over the given one, filling the - * nodes' geometric information, if a split occurs I save the - * "after-split" in a variable, which in the next iteration - * I use as curr instead of list.next(). The caller doesn't - * need to know the geometry of these intermediate segments. - */ - continue; - } + String s = curr.text; + int splitOffset; + for (splitOffset = 0; splitOffset < s.length(); ++splitOffset) + { + String substring = s.substring(0, splitOffset + 1); + if (fm.stringWidth(substring) > width) break; + } + if (splitOffset == 0) splitOffset = 1; + /* + * I force a split even if our width supports no characters. + * Because if I don't split, the only alternatives to infinitely + * looping downwards is to emplace this segment or ignore it. + */ + Segment fitted = new Segment(); + fitted.text = s.substring(0, splitOffset); + fitted.link = curr.link; + fitted.x = x; + fitted.y = y; + cursor.add(fitted); + curr.text = s.substring(splitOffset); + y += dy; + x = 0; + cursor.add(curr); cursor.previous(); + /* + * I had to use a stack and return a new list because, + * splitting can turn a long segment into several spread + * over different lines. Here curr becomes the "after-split" + * and I push it back to the stack. + * + * If #layout wasn't a separate method, but rather only for + * graphical painting. Then I don't need a stack nor return + * a new list. I iterate over the given one, filling the + * nodes' geometric information, if a split occurs I save the + * "after-split" in a variable, which in the next iteration + * I use as curr instead of list.next(). The caller doesn't + * need to know the geometry of these intermediate segments. + */ + continue; + } - return text; - } + return text; + } -// ---%-@-%--- +// ---%-@-%--- - public static class - Segment { + public static class + Segment { - public ImageIcon - image; + public ImageIcon + image; - public String - link; + public String + link; - public String - text; + public String + text; - public boolean - spacer; + public boolean + spacer; - public int - x, y; + public int + x, y; -// -=%=- +// -=%=- - public String - toString() - { - StringBuilder b = new StringBuilder(); - b.append(getClass().getName() + "["); - b.append("image=" + image); - b.append(",link=" + link); - b.append(",text=" + text); - b.append(",x=" + x); - b.append(",y=" + y); - b.append("]"); - return b.toString(); - } + public String + toString() + { + StringBuilder b = new StringBuilder(); + b.append(getClass().getName() + "["); + b.append("image=" + image); + b.append(",link=" + link); + b.append(",text=" + text); + b.append(",x=" + x); + b.append(",y=" + y); + b.append("]"); + return b.toString(); + } - public Segment - clone() - { - Segment segment = new Segment(); - segment.image = this.image; - segment.link = this.link; - segment.text = this.text; - segment.spacer = this.spacer; - segment.x = this.x; - segment.y = this.y; - return segment; - } + public Segment + clone() + { + Segment segment = new Segment(); + segment.image = this.image; + segment.link = this.link; + segment.text = this.text; + segment.spacer = this.spacer; + segment.x = this.x; + segment.y = this.y; + return segment; + } - } + } - public static class - Builder { + public static class + Builder { - private List - returnee; + private List + returnee; -// -=%=- +// -=%=- - public - Builder() { returnee = new LinkedList<>(); } + public + Builder() { returnee = new LinkedList<>(); } - public Builder - image(ImageIcon image, String text) - { - Segment segment = new Segment(); - segment.image = image; - segment.text = text; - returnee.add(segment); - return this; - } + public Builder + image(ImageIcon image, String text) + { + Segment segment = new Segment(); + segment.image = image; + segment.text = text; + returnee.add(segment); + return this; + } - public Builder - link(String link, String text) - { - Segment segment = new Segment(); - segment.link = link; - segment.text = text; - returnee.add(segment); - return this; - } + public Builder + link(String link, String text) + { + Segment segment = new Segment(); + segment.link = link; + segment.text = text; + returnee.add(segment); + return this; + } - public Builder - text(String text) - { - Segment segment = new Segment(); - segment.text = text; - returnee.add(segment); - return this; - } + public Builder + text(String text) + { + Segment segment = new Segment(); + segment.text = text; + returnee.add(segment); + return this; + } - public Builder - spacer(String text) - { - Segment segment = new Segment(); - segment.text = text; - segment.spacer = true; - returnee.add(segment); - return this; - } + public Builder + spacer(String text) + { + Segment segment = new Segment(); + segment.text = text; + segment.spacer = true; + returnee.add(segment); + return this; + } - public List - finish() { return returnee; } + public List + finish() { return returnee; } - } + } -// ---%-@-%--- +// ---%-@-%--- - RichTextPane() - { - text = new LinkedList<>(); + RichTextPane() + { + text = new LinkedList<>(); - addMouseListener(this); - addMouseMotionListener(this); - addKeyListener(this); - } + addMouseListener(this); + addMouseMotionListener(this); + addKeyListener(this); + } } diff --git a/RichTextPane2.java b/RichTextPane2.java index a3a8086..624d6eb 100644 --- a/RichTextPane2.java +++ b/RichTextPane2.java @@ -37,463 +37,463 @@ class RichTextPane2 extends JComponent implements ComponentListener { - private AttributedString - text; + private AttributedString + text; -// ---%-@-%--- +// ---%-@-%--- - public void - setText(Tree html, Tree emojiMap) - { - Tree commands = turnIntoCommands(html); + public void + setText(Tree html, Tree emojiMap) + { + Tree commands = turnIntoCommands(html); - class AStrSegment { - String text; - int offset; - Object[] values = new Object[Attribute.COUNT]; - /* - { - values[3] = (Boolean)true; - values[4] = (Integer)0; - values[5] = (Boolean)true; - values[6] = (Boolean)false; - } - */ - } - List segments = new ArrayList<>(); + class AStrSegment { + String text; + int offset; + Object[] values = new Object[Attribute.COUNT]; + /* + { + values[3] = (Boolean)true; + values[4] = (Integer)0; + values[5] = (Boolean)true; + values[6] = (Boolean)false; + } + */ + } + List segments = new ArrayList<>(); - int offset = 0; - for (Tree command: commands) - { - if (command.key.equals("text")) - { - StringBuilder b = new StringBuilder(); - Boolean cibl = null; - Boolean cwhi = null; - for (char c: command.value.toCharArray()) - { - Boolean ibl = isBasicLatin(c); - Boolean whi = Character.isWhitespace(c); - if (!ibl.equals(cibl) || !whi.equals(cwhi)) - { - if (b.length() > 0) - { - assert cibl != null && cwhi != null; - AStrSegment s = new AStrSegment(); - s.offset = offset; - s.text = b.toString(); - s.values[3] = cibl; - s.values[6] = cwhi; - segments.add(s); - offset += s.text.length(); - b.delete(0, b.length()); - } - cibl = ibl; - cwhi = whi; - } + int offset = 0; + for (Tree command: commands) + { + if (command.key.equals("text")) + { + StringBuilder b = new StringBuilder(); + Boolean cibl = null; + Boolean cwhi = null; + for (char c: command.value.toCharArray()) + { + Boolean ibl = isBasicLatin(c); + Boolean whi = Character.isWhitespace(c); + if (!ibl.equals(cibl) || !whi.equals(cwhi)) + { + if (b.length() > 0) + { + assert cibl != null && cwhi != null; + AStrSegment s = new AStrSegment(); + s.offset = offset; + s.text = b.toString(); + s.values[3] = cibl; + s.values[6] = cwhi; + segments.add(s); + offset += s.text.length(); + b.delete(0, b.length()); + } + cibl = ibl; + cwhi = whi; + } - b.append(c); - } - if (b.length() > 0) - { - AStrSegment s = new AStrSegment(); - s.offset = offset; - s.text = b.toString(); - s.values[3] = cibl; - s.values[6] = cwhi; - segments.add(s); - offset += s.text.length(); - } - } - else if (command.key.equals("emoji")) - { - AStrSegment s = new AStrSegment(); - s.offset = offset; - s.values[3] = true; - s.values[6] = false; + b.append(c); + } + if (b.length() > 0) + { + AStrSegment s = new AStrSegment(); + s.offset = offset; + s.text = b.toString(); + s.values[3] = cibl; + s.values[6] = cwhi; + segments.add(s); + offset += s.text.length(); + } + } + else if (command.key.equals("emoji")) + { + AStrSegment s = new AStrSegment(); + s.offset = offset; + s.values[3] = true; + s.values[6] = false; - String shortcode = command.value; - String url = null; - Tree m = emojiMap.get(shortcode); - if (m != null) url = m.value; - Image img = ImageApi.remote(url); - if (img != null) - { - s.text = " "; - s.values[0] = img; - s.values[1] = shortcode; - segments.add(s); - offset += 1; - } - else - { - s.text = shortcode; - s.values[0] = null; - s.values[1] = null; - segments.add(s); - offset += shortcode.length(); - } - } - else if (command.key.equals("link")) - { - AStrSegment s = new AStrSegment(); - s.offset = offset; - s.text = command.value; - s.values[2] = command.get("url").value; - s.values[3] = true; - s.values[6] = false; - /* - * Technically we're supposed to treat - * the anchor text like a text node. - * As in, it could be non-Basic-Latin.. - * I'll be Mastodon-specific again, and - * assume it's a URL or some @ string. - */ - } - } + String shortcode = command.value; + String url = null; + Tree m = emojiMap.get(shortcode); + if (m != null) url = m.value; + Image img = ImageApi.remote(url); + if (img != null) + { + s.text = " "; + s.values[0] = img; + s.values[1] = shortcode; + segments.add(s); + offset += 1; + } + else + { + s.text = shortcode; + s.values[0] = null; + s.values[1] = null; + segments.add(s); + offset += shortcode.length(); + } + } + else if (command.key.equals("link")) + { + AStrSegment s = new AStrSegment(); + s.offset = offset; + s.text = command.value; + s.values[2] = command.get("url").value; + s.values[3] = true; + s.values[6] = false; + /* + * Technically we're supposed to treat + * the anchor text like a text node. + * As in, it could be non-Basic-Latin.. + * I'll be Mastodon-specific again, and + * assume it's a URL or some @ string. + */ + } + } - AttributedString astr; - StringBuilder b = new StringBuilder(); - for (AStrSegment segment: segments) - { - b.append(segment.text); - } - astr = new AttributedString(b.toString()); - for (AStrSegment segment: segments) - { - Object[] v = segment.values; - astr.addAttribute( - Attribute.IMAGE, segment.values[0], - segment.offset, - segment.offset + segment.text.length() - ); - astr.addAttribute( - Attribute.ALT, segment.values[1], - segment.offset, - segment.offset + segment.text.length() - ); - astr.addAttribute( - Attribute.LINK, segment.values[2], - segment.offset, - segment.offset + segment.text.length() - ); - astr.addAttribute( - Attribute.BASICLATIN, segment.values[3], - segment.offset, - segment.offset + segment.text.length() - ); - astr.addAttribute( - Attribute.Y, segment.values[4], - segment.offset, - segment.offset + segment.text.length() - ); - astr.addAttribute( - Attribute.OFFSCREEN, segment.values[5], - segment.offset, - segment.offset + segment.text.length() - ); - astr.addAttribute( - Attribute.WHITESPACE, segment.values[6], - segment.offset, - segment.offset + segment.text.length() - ); - } + AttributedString astr; + StringBuilder b = new StringBuilder(); + for (AStrSegment segment: segments) + { + b.append(segment.text); + } + astr = new AttributedString(b.toString()); + for (AStrSegment segment: segments) + { + Object[] v = segment.values; + astr.addAttribute( + Attribute.IMAGE, segment.values[0], + segment.offset, + segment.offset + segment.text.length() + ); + astr.addAttribute( + Attribute.ALT, segment.values[1], + segment.offset, + segment.offset + segment.text.length() + ); + astr.addAttribute( + Attribute.LINK, segment.values[2], + segment.offset, + segment.offset + segment.text.length() + ); + astr.addAttribute( + Attribute.BASICLATIN, segment.values[3], + segment.offset, + segment.offset + segment.text.length() + ); + astr.addAttribute( + Attribute.Y, segment.values[4], + segment.offset, + segment.offset + segment.text.length() + ); + astr.addAttribute( + Attribute.OFFSCREEN, segment.values[5], + segment.offset, + segment.offset + segment.text.length() + ); + astr.addAttribute( + Attribute.WHITESPACE, segment.values[6], + segment.offset, + segment.offset + segment.text.length() + ); + } - this.text = astr; - componentResized(null); - } + this.text = astr; + componentResized(null); + } -// - -%- - +// - -%- - - public void - componentResized(ComponentEvent eC) - { - int w = getWidth(), h = getHeight(); + public void + componentResized(ComponentEvent eC) + { + int w = getWidth(), h = getHeight(); - // We're going to evaluate the - // line and off-screen attributes. + // We're going to evaluate the + // line and off-screen attributes. - FontMetrics fm = getFontMetrics(getFont()); - Graphics g = getGraphics(); - int x = 0, y = fm.getAscent(); + FontMetrics fm = getFontMetrics(getFont()); + Graphics g = getGraphics(); + int x = 0, y = fm.getAscent(); - AttributedCharacterIterator it; - it = text.getIterator(); + AttributedCharacterIterator it; + it = text.getIterator(); - while (it.getIndex() < it.getEndIndex()) - { - int start = it.getIndex(); - int end = it.getRunLimit(); + while (it.getIndex() < it.getEndIndex()) + { + int start = it.getIndex(); + int end = it.getRunLimit(); - Image img = (Image) - it.getAttribute(Attribute.IMAGE); - Boolean ibl = (Boolean) - it.getAttribute(Attribute.BASICLATIN); - Boolean whi = (Boolean) - it.getAttribute(Attribute.WHITESPACE); + Image img = (Image) + it.getAttribute(Attribute.IMAGE); + Boolean ibl = (Boolean) + it.getAttribute(Attribute.BASICLATIN); + Boolean whi = (Boolean) + it.getAttribute(Attribute.WHITESPACE); - assert ibl != null; - assert whi != null; + assert ibl != null; + assert whi != null; - if (img != null) - { - int ow = img.getWidth(this); - int oh = img.getHeight(this); - int nh = fm.getAscent() + fm.getDescent(); - int nw = ow * nh/oh; - if (x + nw > w) - { - y += fm.getAscent() + fm.getDescent(); - x = nw; - } - text.addAttribute( - Attribute.Y, (Integer)y, - start, end - ); - text.addAttribute( - Attribute.OFFSCREEN, (Boolean)(y > h), - start, end - ); - it.setIndex(end); - } - else - { - int p, xOff = 0; - for (p = end; p > start; --p) - { - Rectangle2D r; - r = fm.getStringBounds(it, start, p, g); - xOff = (int)r.getWidth(); - if (x + xOff < w) break; - } - if (p == end || whi) - { - x += xOff; - text.addAttribute( - Attribute.Y, (Integer)y, - start, end - ); - text.addAttribute( - Attribute.OFFSCREEN, (Boolean)(y > h), - start, end - ); - it.setIndex(end); - } - else if (p <= start) - { - y += fm.getAscent() + fm.getDescent(); - x = xOff; - text.addAttribute( - Attribute.Y, (Integer)y, - start, end - ); - text.addAttribute( - Attribute.OFFSCREEN, (Boolean)(y > h), - start, end - ); - it.setIndex(end); - } - else - { - text.addAttribute( - Attribute.Y, (Integer)y, - start, p - ); - text.addAttribute( - Attribute.OFFSCREEN, (Boolean)(y > h), - start, p - ); - y += fm.getAscent() + fm.getDescent(); - x = 0; - it.setIndex(p); - } - } - } + if (img != null) + { + int ow = img.getWidth(this); + int oh = img.getHeight(this); + int nh = fm.getAscent() + fm.getDescent(); + int nw = ow * nh/oh; + if (x + nw > w) + { + y += fm.getAscent() + fm.getDescent(); + x = nw; + } + text.addAttribute( + Attribute.Y, (Integer)y, + start, end + ); + text.addAttribute( + Attribute.OFFSCREEN, (Boolean)(y > h), + start, end + ); + it.setIndex(end); + } + else + { + int p, xOff = 0; + for (p = end; p > start; --p) + { + Rectangle2D r; + r = fm.getStringBounds(it, start, p, g); + xOff = (int)r.getWidth(); + if (x + xOff < w) break; + } + if (p == end || whi) + { + x += xOff; + text.addAttribute( + Attribute.Y, (Integer)y, + start, end + ); + text.addAttribute( + Attribute.OFFSCREEN, (Boolean)(y > h), + start, end + ); + it.setIndex(end); + } + else if (p <= start) + { + y += fm.getAscent() + fm.getDescent(); + x = xOff; + text.addAttribute( + Attribute.Y, (Integer)y, + start, end + ); + text.addAttribute( + Attribute.OFFSCREEN, (Boolean)(y > h), + start, end + ); + it.setIndex(end); + } + else + { + text.addAttribute( + Attribute.Y, (Integer)y, + start, p + ); + text.addAttribute( + Attribute.OFFSCREEN, (Boolean)(y > h), + start, p + ); + y += fm.getAscent() + fm.getDescent(); + x = 0; + it.setIndex(p); + } + } + } - text.addAttribute(TextAttribute.FONT, getFont()); + text.addAttribute(TextAttribute.FONT, getFont()); - repaint(); - } + repaint(); + } - protected void - paintComponent(Graphics g) - { - int w = getWidth(), h = getHeight(); - g.clearRect(0, 0, w, h); + protected void + paintComponent(Graphics g) + { + int w = getWidth(), h = getHeight(); + g.clearRect(0, 0, w, h); - FontMetrics fm = g.getFontMetrics(); + FontMetrics fm = g.getFontMetrics(); - AttributedCharacterIterator it; - it = text.getIterator(); + AttributedCharacterIterator it; + it = text.getIterator(); - ((java.awt.Graphics2D)g).setRenderingHint( - java.awt.RenderingHints.KEY_ANTIALIASING, - java.awt.RenderingHints.VALUE_ANTIALIAS_ON - ); + ((java.awt.Graphics2D)g).setRenderingHint( + java.awt.RenderingHints.KEY_ANTIALIASING, + java.awt.RenderingHints.VALUE_ANTIALIAS_ON + ); - int x = 0, y = fm.getAscent(); - while (it.getIndex() < it.getEndIndex()) - { - int start = it.getIndex(); - int end = it.getRunLimit(); + int x = 0, y = fm.getAscent(); + while (it.getIndex() < it.getEndIndex()) + { + int start = it.getIndex(); + int end = it.getRunLimit(); - Image img = (Image) - it.getAttribute(Attribute.IMAGE); - Boolean ibl = (Boolean) - it.getAttribute(Attribute.BASICLATIN); - Integer ny = (Integer) - it.getAttribute(Attribute.Y); + Image img = (Image) + it.getAttribute(Attribute.IMAGE); + Boolean ibl = (Boolean) + it.getAttribute(Attribute.BASICLATIN); + Integer ny = (Integer) + it.getAttribute(Attribute.Y); - if (ny > y) - { - y = ny; - x = 0; - } + if (ny > y) + { + y = ny; + x = 0; + } - if (img != null) - { - int ow = img.getWidth(this); - int oh = img.getHeight(this); - int nh = fm.getAscent() + fm.getDescent(); - int nw = ow * nh/oh; - int iy = y + fm.getDescent() - nh; - g.drawImage(img, x, iy, nw, nh, this); - x += nw; - } - else - { - Rectangle2D r; - r = fm.getStringBounds(it, start, end, g); - AttributedCharacterIterator sit; - sit = text.getIterator(null, start, end); - g.drawString(sit, x, y); - x += (int)r.getWidth(); - } - it.setIndex(end); - } - } + if (img != null) + { + int ow = img.getWidth(this); + int oh = img.getHeight(this); + int nh = fm.getAscent() + fm.getDescent(); + int nw = ow * nh/oh; + int iy = y + fm.getDescent() - nh; + g.drawImage(img, x, iy, nw, nh, this); + x += nw; + } + else + { + Rectangle2D r; + r = fm.getStringBounds(it, start, end, g); + AttributedCharacterIterator sit; + sit = text.getIterator(null, start, end); + g.drawString(sit, x, y); + x += (int)r.getWidth(); + } + it.setIndex(end); + } + } - public void - componentMoved(ComponentEvent eC) { } + public void + componentMoved(ComponentEvent eC) { } - public void - componentShown(ComponentEvent eC) { } + public void + componentShown(ComponentEvent eC) { } - public void - componentHidden(ComponentEvent eC) { } + public void + componentHidden(ComponentEvent eC) { } -// - -%- - +// - -%- - - private static Boolean - isBasicLatin(char c) - { - return true; - } + private static Boolean + isBasicLatin(char c) + { + return true; + } - private static String - toText(Tree node) - { - Tree children = node.get("children"); - if (children == null) - { - boolean text = node.key.equals("text"); - boolean emoji = node.key.equals("emoji"); - assert text || emoji; - return node.value; - } + private static String + toText(Tree node) + { + Tree children = node.get("children"); + if (children == null) + { + boolean text = node.key.equals("text"); + boolean emoji = node.key.equals("emoji"); + assert text || emoji; + return node.value; + } - StringBuilder b = new StringBuilder(); - for (Tree child: children) - { - b.append(toText(child)); - } - return b.toString(); - } + StringBuilder b = new StringBuilder(); + for (Tree child: children) + { + b.append(toText(child)); + } + return b.toString(); + } - private static Tree - turnIntoCommands(Tree tag) - { - assert tag.key.equals("tag"); - Tree returnee = new Tree(); + private static Tree + turnIntoCommands(Tree tag) + { + assert tag.key.equals("tag"); + Tree returnee = new Tree(); - String tagName = tag.get(0).key; - Tree children = tag.get("children"); + String tagName = tag.get(0).key; + Tree children = tag.get("children"); - if (tagName.equals("a")) - { - String url = tag.get("href").value; - Tree addee = new Tree<>(); - addee.key = "link"; - addee.value = toText(tag); - addee.add(new Tree<>("url", url)); - returnee.add(addee); - } - else if (tagName.equals("span")) - { - Tree addee = new Tree<>(); - addee.key = "text"; - addee.value = toText(tag); - returnee.add(addee); - } - else if (tagName.equals("br")) - { - returnee.add(new Tree<>("text", "\n")); - } - else - { - for (Tree child: children) - { - if (!child.key.equals("tag")) - { - returnee.add(child); - continue; - } - child = turnIntoCommands(child); - for (Tree command: child) - { - returnee.add(command); - } - } - if (tagName.equals("p")) - { - returnee.add(new Tree<>("text", "\n")); - returnee.add(new Tree<>("text", "\n")); - } - } + if (tagName.equals("a")) + { + String url = tag.get("href").value; + Tree addee = new Tree<>(); + addee.key = "link"; + addee.value = toText(tag); + addee.add(new Tree<>("url", url)); + returnee.add(addee); + } + else if (tagName.equals("span")) + { + Tree addee = new Tree<>(); + addee.key = "text"; + addee.value = toText(tag); + returnee.add(addee); + } + else if (tagName.equals("br")) + { + returnee.add(new Tree<>("text", "\n")); + } + else + { + for (Tree child: children) + { + if (!child.key.equals("tag")) + { + returnee.add(child); + continue; + } + child = turnIntoCommands(child); + for (Tree command: child) + { + returnee.add(command); + } + } + if (tagName.equals("p")) + { + returnee.add(new Tree<>("text", "\n")); + returnee.add(new Tree<>("text", "\n")); + } + } - return returnee; - } + return returnee; + } -// ---%-@-%--- +// ---%-@-%--- - public static class - Attribute extends AttributedCharacterIterator.Attribute { + public static class + Attribute extends AttributedCharacterIterator.Attribute { - public static final Attribute - IMAGE = new Attribute("IMAGE"), - ALT = new Attribute("ALT"), - LINK = new Attribute("LINK"), - BASICLATIN = new Attribute("BASICLATIN"), - Y = new Attribute("Y"), - OFFSCREEN = new Attribute("OFFSCREEN"), - WHITESPACE = new Attribute("WHITESPACE"); + public static final Attribute + IMAGE = new Attribute("IMAGE"), + ALT = new Attribute("ALT"), + LINK = new Attribute("LINK"), + BASICLATIN = new Attribute("BASICLATIN"), + Y = new Attribute("Y"), + OFFSCREEN = new Attribute("OFFSCREEN"), + WHITESPACE = new Attribute("WHITESPACE"); - public static final int - COUNT = 7; + public static final int + COUNT = 7; -// -=%=- +// -=%=- - private - Attribute(String name) { super(name); } + private + Attribute(String name) { super(name); } - } + } -// ---%-@-%--- +// ---%-@-%--- - RichTextPane2() - { - this.addComponentListener(this); - text = new AttributedString(""); - } + RichTextPane2() + { + this.addComponentListener(this); + text = new AttributedString(""); + } } diff --git a/RichTextPane3.java b/RichTextPane3.java index d5dacff..ef1eb81 100644 --- a/RichTextPane3.java +++ b/RichTextPane3.java @@ -45,232 +45,232 @@ implements ComponentListener, MouseListener, MouseMotionListener, KeyListener { - private Tree - html; + private Tree + html; - private Map - emojis; + private Map + emojis; - private Map, Position> - layout; + private Map, Position> + layout; - private Tree - layoutEnd, selStart, selEnd; + private Tree + layoutEnd, selStart, selEnd; - private int - startingLine, lastLine; + private int + startingLine, lastLine; -// ---%-@-%--- +// ---%-@-%--- - public void - setText(Tree html) - { + public void + setText(Tree html) + { assert html != null; - this.html = html; + this.html = html; - if (!isValid()) return; + if (!isValid()) return; - assert html.key != null; - assert html.key.equals("tag"); - assert html.get("children") != null; + assert html.key != null; + assert html.key.equals("tag"); + assert html.get("children") != null; - FontMetrics fm = getFontMetrics(getFont()); - Position cursor = new Position(0, 1); + FontMetrics fm = getFontMetrics(getFont()); + Position cursor = new Position(0, 1); - // Manually negate if first element is a break. - Tree children = html.get("children"); - if (children.size() > 0) - { - Tree first = children.get(0); - if (first.key.equals("tag")) - { - String tagName = first.get(0).key; - if (tagName.equals("br")) cursor.line -= 1; - if (tagName.equals("p")) cursor.line -= 2; - } - } + // Manually negate if first element is a break. + Tree children = html.get("children"); + if (children.size() > 0) + { + Tree first = children.get(0); + if (first.key.equals("tag")) + { + String tagName = first.get(0).key; + if (tagName.equals("br")) cursor.line -= 1; + if (tagName.equals("p")) cursor.line -= 2; + } + } - selStart = selEnd = null; - layout.clear(); - startingLine = 1; - layout(html, fm, cursor); - layout.put(layoutEnd, cursor.clone()); - lastLine = cursor.line; - repaint(); + selStart = selEnd = null; + layout.clear(); + startingLine = 1; + layout(html, fm, cursor); + layout.put(layoutEnd, cursor.clone()); + lastLine = cursor.line; + repaint(); - int iy = fm.getAscent(); - int lh = fm.getAscent() + fm.getDescent(); - int h = snap2(cursor.line, iy, lh); - h += fm.getDescent(); - setPreferredSize(new Dimension(1, h)); - } + int iy = fm.getAscent(); + int lh = fm.getAscent() + fm.getDescent(); + int h = snap2(cursor.line, iy, lh); + h += fm.getDescent(); + setPreferredSize(new Dimension(1, h)); + } - public void - setEmojis(Map emojis) - { - assert emojis != null; - this.emojis = emojis; - setText(html); - } + public void + setEmojis(Map emojis) + { + assert emojis != null; + this.emojis = emojis; + setText(html); + } - public void - previousPage() - { - int advance = getHeightInLines(); - if (startingLine < advance) startingLine = 1; - else startingLine -= advance; - repaint(); - } + public void + previousPage() + { + int advance = getHeightInLines(); + if (startingLine < advance) startingLine = 1; + else startingLine -= advance; + repaint(); + } - public void - nextPage() - { - int advance = getHeightInLines(); - if (lastLine - startingLine < advance) return; - else startingLine += advance; - repaint(); - } + public void + nextPage() + { + int advance = getHeightInLines(); + if (lastLine - startingLine < advance) return; + else startingLine += advance; + repaint(); + } -// - -%- - +// - -%- - - private void - layout(Tree node, FontMetrics fm, Position cursor) - { + private void + layout(Tree node, FontMetrics fm, Position cursor) + { assert cursor != null; - if (node.key.equals("space")) - { - int w = fm.stringWidth(node.value); - if (cursor.x + w < getWidth()) - { - layout.put(node, cursor.clone()); - cursor.x += w; - } - else - { - layout.put(node, cursor.clone()); - ++cursor.line; - cursor.x = 0; - } - } - else if (node.key.equals("text")) - { - int w = fm.stringWidth(node.value); - if (cursor.x + w < getWidth()) - { - layout.put(node, cursor.clone()); - cursor.x += w; - } - else if (w < getWidth()) - { - ++cursor.line; - cursor.x = 0; - layout.put(node, cursor.clone()); - cursor.x += w; - } - else - { - StringBuilder rem = new StringBuilder(); - rem.append(node.value); - int mw = getWidth(); - int aw = mw - cursor.x; + if (node.key.equals("space")) + { + int w = fm.stringWidth(node.value); + if (cursor.x + w < getWidth()) + { + layout.put(node, cursor.clone()); + cursor.x += w; + } + else + { + layout.put(node, cursor.clone()); + ++cursor.line; + cursor.x = 0; + } + } + else if (node.key.equals("text")) + { + int w = fm.stringWidth(node.value); + if (cursor.x + w < getWidth()) + { + layout.put(node, cursor.clone()); + cursor.x += w; + } + else if (w < getWidth()) + { + ++cursor.line; + cursor.x = 0; + layout.put(node, cursor.clone()); + cursor.x += w; + } + else + { + StringBuilder rem = new StringBuilder(); + rem.append(node.value); + int mw = getWidth(); + int aw = mw - cursor.x; - w = fm.charWidth(node.value.charAt(0)); - if (w >= aw) - { - ++cursor.line; - cursor.x = 0; - } + w = fm.charWidth(node.value.charAt(0)); + if (w >= aw) + { + ++cursor.line; + cursor.x = 0; + } - while (rem.length() > 0) - { - int l = 2; - for (; l <= rem.length(); ++l) - { - String substr = rem.substring(0, l); - w = fm.stringWidth(substr); - if (w >= aw) break; - } - String substr = rem.substring(0, --l); - w = fm.stringWidth(substr); + while (rem.length() > 0) + { + int l = 2; + for (; l <= rem.length(); ++l) + { + String substr = rem.substring(0, l); + w = fm.stringWidth(substr); + if (w >= aw) break; + } + String substr = rem.substring(0, --l); + w = fm.stringWidth(substr); - Tree temp = new Tree<>(); - temp.key = node.key; - temp.value = substr; - layout.put(temp, cursor.clone()); + Tree temp = new Tree<>(); + temp.key = node.key; + temp.value = substr; + layout.put(temp, cursor.clone()); - rem.delete(0, l); - boolean more = rem.length() != 0; - if (more) ++cursor.line; - cursor.x = more ? 0 : w; - aw = mw; - } - } - } - else if (node.key.equals("emoji")) - { - Image image = emojis.get(node.value); - int w; if (image != null) - { - int ow = image.getWidth(this); - int oh = image.getHeight(this); - int h = fm.getAscent() + fm.getDescent(); - w = ow * h/oh; - } - else - { - w = fm.stringWidth(node.value); - } + rem.delete(0, l); + boolean more = rem.length() != 0; + if (more) ++cursor.line; + cursor.x = more ? 0 : w; + aw = mw; + } + } + } + else if (node.key.equals("emoji")) + { + Image image = emojis.get(node.value); + int w; if (image != null) + { + int ow = image.getWidth(this); + int oh = image.getHeight(this); + int h = fm.getAscent() + fm.getDescent(); + w = ow * h/oh; + } + else + { + w = fm.stringWidth(node.value); + } - if (cursor.x + w < getWidth()) - { - layout.put(node, cursor.clone()); - cursor.x += w; - } - else - { - ++cursor.line; - cursor.x = 0; - layout.put(node, cursor.clone()); - cursor.x += w; - } - } - else if (node.key.equals("tag")) - { - String tagName = node.get(0).key; - Tree children = node.get("children"); + if (cursor.x + w < getWidth()) + { + layout.put(node, cursor.clone()); + cursor.x += w; + } + else + { + ++cursor.line; + cursor.x = 0; + layout.put(node, cursor.clone()); + cursor.x += w; + } + } + else if (node.key.equals("tag")) + { + String tagName = node.get(0).key; + Tree children = node.get("children"); - // We won't place tag nodes on the layout. + // We won't place tag nodes on the layout. - if (tagName.equals("br")) - { - ++cursor.line; - cursor.x = 0; - } - else if (tagName.equals("p")) - { - //cursor.line += 3/2; - cursor.line += 2; - // We don't have vertical cursor movement - // other than the line. Maybe fix in the - // future..? - cursor.x = 0; - } + if (tagName.equals("br")) + { + ++cursor.line; + cursor.x = 0; + } + else if (tagName.equals("p")) + { + //cursor.line += 3/2; + cursor.line += 2; + // We don't have vertical cursor movement + // other than the line. Maybe fix in the + // future..? + cursor.x = 0; + } - for (Tree child: children) - { + for (Tree child: children) + { // Shallow copy this child node, - Tree aug = new Tree<>(); + Tree aug = new Tree<>(); aug.key = child.key; aug.value = child.value; for (Tree gc: child) aug.add(gc); // Append all of our attributes. We'd like - // those like href to end up at the text - // nodes. This might collide with our - // child node's attributes, for now I'll - // assume that's not an issue. + // those like href to end up at the text + // nodes. This might collide with our + // child node's attributes, for now I'll + // assume that's not an issue. for (int o = 1; o < node.size(); ++o) { Tree attr = node.get(o); @@ -279,29 +279,29 @@ implements } layout(aug, fm, cursor); - } - } - else assert false; - } + } + } + else assert false; + } - protected void - paintComponent(Graphics g) - { + protected void + paintComponent(Graphics g) + { final Color LINK_COLOUR = Color.BLUE; final Color PLAIN_COLOUR = getForeground(); final Color SEL_COLOUR = new Color(0, 0, 0, 25); - g.setFont(getFont()); - FontMetrics fm = g.getFontMetrics(); - int iy = fm.getAscent(); - int lh = fm.getAscent() + fm.getDescent(); - int asc = fm.getAscent(); - int w = getWidth(), h = getHeight(); + g.setFont(getFont()); + FontMetrics fm = g.getFontMetrics(); + int iy = fm.getAscent(); + int lh = fm.getAscent() + fm.getDescent(); + int asc = fm.getAscent(); + int w = getWidth(), h = getHeight(); - if (isOpaque()) g.clearRect(0, 0, w, h); + if (isOpaque()) g.clearRect(0, 0, w, h); - if (selEnd != null) - { + if (selEnd != null) + { Position ssp = layout.get(selStart); assert ssp != null; Position sep = layout.get(selEnd); @@ -318,81 +318,81 @@ implements sep = ssp; } - int ls = 1 + ssp.line - startingLine; - int le = 1 + sep.line - startingLine; - int ys = snap2(ls, iy, lh) - asc; - int ye = snap2(le, iy, lh) - asc; + int ls = 1 + ssp.line - startingLine; + int le = 1 + sep.line - startingLine; + int ys = snap2(ls, iy, lh) - asc; + int ye = snap2(le, iy, lh) - asc; - g.setColor(SEL_COLOUR); + g.setColor(SEL_COLOUR); if (ssp.line == sep.line) { - g.fillRect(ssp.x, ys, sep.x - ssp.x, lh); + g.fillRect(ssp.x, ys, sep.x - ssp.x, lh); } else { - g.fillRect(ssp.x, ys, w - ssp.x, lh); - for (int l = ls + 1; l < le; ++l) + g.fillRect(ssp.x, ys, w - ssp.x, lh); + for (int l = ls + 1; l < le; ++l) { - int y = snap2(l, iy, lh) - asc; + int y = snap2(l, iy, lh) - asc; g.fillRect(0, y, w, lh); } - g.fillRect(0, ye, sep.x, lh); + g.fillRect(0, ye, sep.x, lh); } - } + } - ((java.awt.Graphics2D)g).setRenderingHint( - java.awt.RenderingHints.KEY_ANTIALIASING, - java.awt.RenderingHints.VALUE_ANTIALIAS_ON - ); + ((java.awt.Graphics2D)g).setRenderingHint( + java.awt.RenderingHints.KEY_ANTIALIASING, + java.awt.RenderingHints.VALUE_ANTIALIAS_ON + ); - g.setColor(getForeground()); - for (Tree node: layout.keySet()) - { - Position position = layout.get(node); - int x = position.x; - int line = 1 + position.line - startingLine; - int y = snap2(line, iy, lh); - if (y > h) continue; + g.setColor(getForeground()); + for (Tree node: layout.keySet()) + { + Position position = layout.get(node); + int x = position.x; + int line = 1 + position.line - startingLine; + int y = snap2(line, iy, lh); + if (y > h) continue; - if (node.key.equals("text")) - { + if (node.key.equals("text")) + { boolean isLink = node.get("href") != null; if (isLink) g.setColor(LINK_COLOUR); - g.drawString(node.value, x, y); - if (isLink) g.setColor(PLAIN_COLOUR); - } - else if (node.key.equals("emoji")) - { + g.drawString(node.value, x, y); + if (isLink) g.setColor(PLAIN_COLOUR); + } + else if (node.key.equals("emoji")) + { Image image = emojis.get(node.value); - Image scaled = emojis.get(node.value + "_scaled"); - if (scaled != null) - { - y -= asc; - g.drawImage(scaled, x, y, this); - } - else if (image != null) - { - scaled = image.getScaledInstance( - -1, fm.getAscent() + fm.getDescent(), - Image.SCALE_SMOOTH - ); - // I hope #getScaledInstance knows how to - // wait if the image is yet to be loaded. - emojis.put(node.value + "_scaled", scaled); - } - else - { - g.drawString(node.value, x, y); - } - } - else continue; - } - } + Image scaled = emojis.get(node.value + "_scaled"); + if (scaled != null) + { + y -= asc; + g.drawImage(scaled, x, y, this); + } + else if (image != null) + { + scaled = image.getScaledInstance( + -1, fm.getAscent() + fm.getDescent(), + Image.SCALE_SMOOTH + ); + // I hope #getScaledInstance knows how to + // wait if the image is yet to be loaded. + emojis.put(node.value + "_scaled", scaled); + } + else + { + g.drawString(node.value, x, y); + } + } + else continue; + } + } public void mousePressed(MouseEvent eM) { - if (eM.getButton() != MouseEvent.BUTTON1) return; + if (eM.getButton() != MouseEvent.BUTTON1) return; selStart = identifyNodeAt(eM.getX(), eM.getY()); selEnd = null; repaint(); @@ -408,71 +408,71 @@ implements repaint(); } - public void + public void mouseMoved(MouseEvent eM) - { - Tree h = identifyNodeAt(eM.getX(), eM.getY()); - if (h == null || h.get("href") == null) - { - setToolTipText(""); - } - else - { - setToolTipText(h.get("href").value); - } - } + { + Tree h = identifyNodeAt(eM.getX(), eM.getY()); + if (h == null || h.get("href") == null) + { + setToolTipText(""); + } + else + { + setToolTipText(h.get("href").value); + } + } - private Tree - identifyNodeAt(int x, int y) - { + private Tree + identifyNodeAt(int x, int y) + { FontMetrics fm = getFontMetrics(getFont()); int initial = fm.getAscent(); int advance = fm.getAscent() + fm.getDescent(); - int line = isnap2(y, initial, advance); + int line = isnap2(y, initial, advance); Tree returnee = null; - Position closest = new Position(0, 0); - for (Tree node: layout.keySet()) + Position closest = new Position(0, 0); + for (Tree node: layout.keySet()) { Position position = layout.get(node); assert position != null; - if (position.line != line) continue; + if (position.line != line) continue; if (position.x > x) continue; if (position.x >= closest.x) { - returnee = node; - closest = position; + returnee = node; + closest = position; } } return returnee; - } + } - private Tree - identifyNodeAfter(int x, int y) - { + private Tree + identifyNodeAfter(int x, int y) + { FontMetrics fm = getFontMetrics(getFont()); int initial = fm.getAscent(); int advance = fm.getAscent() + fm.getDescent(); - int line = isnap2(y, initial, advance); + int line = isnap2(y, initial, advance); Tree returnee = null; - Position closest = new Position(Integer.MAX_VALUE, 0); - for (Tree node: layout.keySet()) + Position closest = new Position(Integer.MAX_VALUE, 0); + for (Tree node: layout.keySet()) { Position position = layout.get(node); assert position != null; - if (position.line != line) continue; + if (position.line != line) continue; if (position.x < x) continue; if (position.x < closest.x) { - returnee = node; - closest = position; + returnee = node; + closest = position; } } return returnee; - } + } public void keyPressed(KeyEvent eK) @@ -494,14 +494,14 @@ implements } private String - getSelectedText() - { + getSelectedText() + { assert selStart != null && selEnd != null; Position ssp = layout.get(selStart); Position sep = layout.get(selEnd); assert ssp != null && sep != null; - if (ssp.compareTo(sep) > 0) + if (ssp.compareTo(sep) > 0) { Position temp = ssp; ssp = sep; @@ -515,8 +515,8 @@ implements Position position = layout.get(node); assert position != null; - boolean after = position.compareTo(ssp) >= 0; - boolean before = position.compareTo(sep) < 0; + boolean after = position.compareTo(ssp) >= 0; + boolean before = position.compareTo(sep) < 0; if (!(after && before)) continue; // Just throw them in a pile for now.. @@ -549,35 +549,35 @@ implements boolean s = node.key.equals("space"); assert t || e || s; b.append(node.value); - /* - * I actually want to copy the link if the node is - * associated with one. However, a link has - * multiple text nodes, so I'd end up copying - * multiple times. The correct action is to - * associate the nodes with the same link object, - * then mark that as copied. Or, associate the - * nodes with their superiors in the HTML, then - * walk up until we find an anchor with a href. - * Then again, have to mark that as copied too. - * - * I can also walk the HTML and copy any that are - * in the selected region, careful to copy an - * anchor's href in stead of the anchor contents. - * I'd need a guarantee that my walking order is - * the same as how they were rendered on the screen. - */ + /* + * I actually want to copy the link if the node is + * associated with one. However, a link has + * multiple text nodes, so I'd end up copying + * multiple times. The correct action is to + * associate the nodes with the same link object, + * then mark that as copied. Or, associate the + * nodes with their superiors in the HTML, then + * walk up until we find an anchor with a href. + * Then again, have to mark that as copied too. + * + * I can also walk the HTML and copy any that are + * in the selected region, careful to copy an + * anchor's href in stead of the anchor contents. + * I'd need a guarantee that my walking order is + * the same as how they were rendered on the screen. + */ } return b.toString(); - } + } - private int - getHeightInLines() - { - FontMetrics fm = getFontMetrics(getFont()); + private int + getHeightInLines() + { + FontMetrics fm = getFontMetrics(getFont()); int initial = fm.getAscent(); int advance = fm.getAscent() + fm.getDescent(); - return isnap2(getHeight(), initial, advance) - 1; - } + return isnap2(getHeight(), initial, advance) - 1; + } public void keyReleased(KeyEvent eK) { } @@ -597,107 +597,107 @@ implements public void mouseExited(MouseEvent eM) { } - public void - componentResized(ComponentEvent eC) { setText(html); } + public void + componentResized(ComponentEvent eC) { setText(html); } - public void - componentMoved(ComponentEvent eC) { } + public void + componentMoved(ComponentEvent eC) { } - public void - componentShown(ComponentEvent eC) { } + public void + componentShown(ComponentEvent eC) { } - public void - componentHidden(ComponentEvent eC) { } + public void + componentHidden(ComponentEvent eC) { } // - -%- - - private static int - snap2(int blocks, int initial, int advance) - { - return initial + (blocks - 1) * advance; - // If you'd like to go behind the first line 1, - // note that the first negative line is 0. - } + private static int + snap2(int blocks, int initial, int advance) + { + return initial + (blocks - 1) * advance; + // If you'd like to go behind the first line 1, + // note that the first negative line is 0. + } - private static int - isnap2(int units, int initial, int advance) - { - int offset = units - initial; - return 2 + bfloor(offset - 1, advance); - // Not yet sure how this behaves for negative numbers. - } + private static int + isnap2(int units, int initial, int advance) + { + int offset = units - initial; + return 2 + bfloor(offset - 1, advance); + // Not yet sure how this behaves for negative numbers. + } - private static int - bfloor(int units, int block) - { - if (units < 0) return (units / block) - 1; - else return units / block; - } + private static int + bfloor(int units, int block) + { + if (units < 0) return (units / block) - 1; + else return units / block; + } -// ---%-@-%--- +// ---%-@-%--- - private static class - Position { + private static class + Position { - int - x, line; + int + x, line; -// -=%=- +// -=%=- - public int - compareTo(Position other) - { - if (line < other.line) return -1; - if (line > other.line) return 1; - if (x < other.x) return -1; - if (x > other.x) return 1; - return 0; - } + public int + compareTo(Position other) + { + if (line < other.line) return -1; + if (line > other.line) return 1; + if (x < other.x) return -1; + if (x > other.x) return 1; + return 0; + } - public String - toString() - { + public String + toString() + { return "(" + x + "," + line + ")"; - } + } -// -=%=- +// -=%=- - public - Position(int x, int line) - { - this.x = x; - this.line = line; - } + public + Position(int x, int line) + { + this.x = x; + this.line = line; + } - public Position - clone() - { - return new Position(x, line); - } + public Position + clone() + { + return new Position(x, line); + } - } + } -// ---%-@-%--- +// ---%-@-%--- - RichTextPane3() - { - layout = new HashMap<>(); - layoutEnd = new Tree<>("text", ""); - emojis = new HashMap<>(); + RichTextPane3() + { + layout = new HashMap<>(); + layoutEnd = new Tree<>("text", ""); + emojis = new HashMap<>(); - Tree blank = new Tree<>(); - blank.key = "tag"; - blank.add(new Tree<>("html", null)); - blank.add(new Tree<>("children", null)); - setText(blank); + Tree blank = new Tree<>(); + blank.key = "tag"; + blank.add(new Tree<>("html", null)); + blank.add(new Tree<>("children", null)); + setText(blank); - this.addComponentListener(this); - this.addMouseListener(this); - this.addMouseMotionListener(this); - this.addKeyListener(this); - setFocusable(true); - // A keyboard user can still copy by tabbing in - // and selecting all. - } + this.addComponentListener(this); + this.addMouseListener(this); + this.addMouseMotionListener(this); + this.addKeyListener(this); + setFocusable(true); + // A keyboard user can still copy by tabbing in + // and selecting all. + } } diff --git a/RudimentaryHTMLParser.java b/RudimentaryHTMLParser.java index f03c147..494113a 100644 --- a/RudimentaryHTMLParser.java +++ b/RudimentaryHTMLParser.java @@ -31,18 +31,18 @@ RudimentaryHTMLParser { public static Tree depthlessRead(String html) { - try { - return pass3(pass2(pass1(html))); - } - catch (IOException eIo) { - assert false; - /* - * We use only StringReaders, which only throw an - * IOException when they are read after being closed. - * And we don't close them. - */ - return null; - } + try { + return pass3(pass2(pass1(html))); + } + catch (IOException eIo) { + assert false; + /* + * We use only StringReaders, which only throw an + * IOException when they are read after being closed. + * And we don't close them. + */ + return null; + } } // - -%- - @@ -51,10 +51,10 @@ RudimentaryHTMLParser { pass1(String html) throws IOException { - Reader r = new StringReader(html); + Reader r = new StringReader(html); Tree docu = new Tree(); StringBuilder text = new StringBuilder(); - StringBuilder emoji = new StringBuilder(); + StringBuilder emoji = new StringBuilder(); StringBuilder htmlEscape = new StringBuilder(); boolean quoted = false, inEmoji = false; int c; while ((c = r.read()) != -1) @@ -110,7 +110,7 @@ RudimentaryHTMLParser { continue; } } - text.append((char)c); + text.append((char)c); continue; } if (text.length() > 0) @@ -181,9 +181,9 @@ RudimentaryHTMLParser { return docu; } - private static Tree - pass3(Tree docu) - { + private static Tree + pass3(Tree docu) + { Tree returnee = new Tree(); for (Tree node: docu) @@ -194,39 +194,39 @@ RudimentaryHTMLParser { continue; } - StringBuilder value = new StringBuilder(); - for (String segment: whitespaceSplit(node.value)) - { - boolean st = segment.startsWith(":"); + StringBuilder value = new StringBuilder(); + for (String segment: whitespaceSplit(node.value)) + { + boolean st = segment.startsWith(":"); boolean ed = segment.endsWith(":"); if (st && ed) { Tree text = new Tree(); - text.key = "text"; - text.value = empty(value); - returnee.add(text); + text.key = "text"; + text.value = empty(value); + returnee.add(text); Tree emoji = new Tree(); emoji.key = "emoji"; - emoji.value = segment; + emoji.value = segment; returnee.add(emoji); } - else - { - value.append(segment); - } - } - if (value.length() > 0) - { - Tree text = new Tree(); - text.key = "text"; - text.value = empty(value); - returnee.add(text); - } - } - return returnee; - } + else + { + value.append(segment); + } + } + if (value.length() > 0) + { + Tree text = new Tree(); + text.key = "text"; + text.value = empty(value); + returnee.add(text); + } + } + return returnee; + } private static String empty(StringBuilder b) @@ -236,25 +236,25 @@ RudimentaryHTMLParser { return s; } - private static List - whitespaceSplit(String text) - { - List returnee = new ArrayList<>(); - StringBuilder segment = new StringBuilder(); - boolean isWhitespace = false; - for (char c: text.toCharArray()) - { - boolean diff = isWhitespace ^ Character.isWhitespace(c); - if (diff) { - returnee.add(empty(segment)); - isWhitespace = !isWhitespace; - } - segment.append(c); - } - returnee.add(empty(segment)); + private static List + whitespaceSplit(String text) + { + List returnee = new ArrayList<>(); + StringBuilder segment = new StringBuilder(); + boolean isWhitespace = false; + for (char c: text.toCharArray()) + { + boolean diff = isWhitespace ^ Character.isWhitespace(c); + if (diff) { + returnee.add(empty(segment)); + isWhitespace = !isWhitespace; + } + segment.append(c); + } + returnee.add(empty(segment)); - return returnee; - } + return returnee; + } // ---%-@-%--- diff --git a/TimelineWindow.java b/TimelineWindow.java index 3d424c3..ca3fad5 100644 --- a/TimelineWindow.java +++ b/TimelineWindow.java @@ -90,9 +90,9 @@ implements ActionListener { openMessages, openLocal, openFederated, - openNotifications, - openOwnProfile, - openProfile, + openNotifications, + openOwnProfile, + openProfile, createPost, openAutoPostView, quit; @@ -117,101 +117,101 @@ implements ActionListener { this.page = page; List previews; - previews = display.getPostPreviews(); + previews = display.getPostPreviews(); - int available = page.posts.length; - int max = previews.size(); - assert available <= max; + int available = page.posts.length; + int max = previews.size(); + assert available <= max; - for (int o = 0; o < available; ++o) - { - PostPreviewComponent preview = previews.get(o); - Post post = page.posts[o]; + for (int o = 0; o < available; ++o) + { + PostPreviewComponent preview = previews.get(o); + Post post = page.posts[o]; preview.setTopLeft(post.author.name); - if (post.boostedPost != null) - { + if (post.boostedPost != null) + { String s = "boosted by " + post.author.name; - preview.setTopLeft(s); - post = post.boostedPost; - } + preview.setTopLeft(s); + post = post.boostedPost; + } - String flags = ""; - if (post.attachments.length > 0) flags += "a"; - post.resolveRelativeTime(); - preview.setTopRight(flags + " " + post.relativeTime); + String flags = ""; + if (post.attachments.length > 0) flags += "a"; + post.resolveRelativeTime(); + preview.setTopRight(flags + " " + post.relativeTime); - post.resolveApproximateText(); - if (post.contentWarning != null) - preview.setBottom("(" + post.contentWarning + ")"); + post.resolveApproximateText(); + if (post.contentWarning != null) + preview.setBottom("(" + post.contentWarning + ")"); else preview.setBottom(post.approximateText); - } + } for (int o = available; o < max; ++o) { previews.get(o).reset(); } - boolean full = !(available < PREVIEW_COUNT); - display.setNextPageAvailable(full); - display.setPreviousPageAvailable(true); - display.resetFocus(); + boolean full = !(available < PREVIEW_COUNT); + display.setNextPageAvailable(full); + display.setPreviousPageAvailable(true); + display.resetFocus(); } - public void - readEntity(Tree postEntityArray) - { + public void + readEntity(Tree postEntityArray) + { TimelinePage page = new TimelinePage(postEntityArray); page.type = this.page.type; page.accountNumId = this.page.accountNumId; page.listId = this.page.listId; use(page); - } + } - public void - refresh() - { + public void + refresh() + { String firstId = null; if (!showingLatest) { assert page.posts != null; assert page.posts.length != 0; firstId = page.posts[0].id; - } + } display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); api.getTimelinePage( page.type, - PREVIEW_COUNT, firstId, null, - page.accountNumId, page.listId, + PREVIEW_COUNT, firstId, null, + page.accountNumId, page.listId, new RequestListener() { public void connectionFailed(IOException eIo) { - JOptionPane.showMessageDialog( - TimelineWindow.this, - "Failed to fetch page.." - + "\n" + eIo.getMessage() - ); + JOptionPane.showMessageDialog( + TimelineWindow.this, + "Failed to fetch page.." + + "\n" + eIo.getMessage() + ); } public void requestFailed(int httpCode, Tree json) { JOptionPane.showMessageDialog( - TimelineWindow.this, - "Failed to fetch page.." - + "\n" + json.get("error").value - + "\n(HTTP code: " + httpCode + ")" - ); + TimelineWindow.this, + "Failed to fetch page.." + + "\n" + json.get("error").value + + "\n(HTTP code: " + httpCode + ")" + ); } public void requestSucceeded(Tree json) { - if (json.size() < PREVIEW_COUNT) - { + if (json.size() < PREVIEW_COUNT) + { showLatestPage(); return; } @@ -221,37 +221,37 @@ implements ActionListener { } ); display.setCursor(null); - } + } - public void + public void showLatestPage() { display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); api.getTimelinePage( page.type, - PREVIEW_COUNT, null, null, - page.accountNumId, page.listId, + PREVIEW_COUNT, null, null, + page.accountNumId, page.listId, new RequestListener() { public void connectionFailed(IOException eIo) { - JOptionPane.showMessageDialog( - TimelineWindow.this, - "Failed to fetch page.." - + "\n" + eIo.getMessage() - ); + JOptionPane.showMessageDialog( + TimelineWindow.this, + "Failed to fetch page.." + + "\n" + eIo.getMessage() + ); } public void requestFailed(int httpCode, Tree json) { JOptionPane.showMessageDialog( - TimelineWindow.this, - "Failed to fetch page.." - + "\n" + json.get("error").value - + "\n(HTTP code: " + httpCode + ")" - ); + TimelineWindow.this, + "Failed to fetch page.." + + "\n" + json.get("error").value + + "\n(HTTP code: " + httpCode + ")" + ); } public void @@ -274,32 +274,32 @@ implements ActionListener { assert page.posts.length != 0; String lastId = page.posts[page.posts.length - 1].id; - display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); api.getTimelinePage( page.type, - PREVIEW_COUNT, lastId, null, - page.accountNumId, page.listId, + PREVIEW_COUNT, lastId, null, + page.accountNumId, page.listId, new RequestListener() { public void connectionFailed(IOException eIo) { - JOptionPane.showMessageDialog( - TimelineWindow.this, - "Failed to fetch page.." - + "\n" + eIo.getMessage() - ); + JOptionPane.showMessageDialog( + TimelineWindow.this, + "Failed to fetch page.." + + "\n" + eIo.getMessage() + ); } public void requestFailed(int httpCode, Tree json) { JOptionPane.showMessageDialog( - TimelineWindow.this, - "Failed to fetch page.." - + "\n" + json.get("error").value - + "\n(HTTP code: " + httpCode + ")" - ); + TimelineWindow.this, + "Failed to fetch page.." + + "\n" + json.get("error").value + + "\n(HTTP code: " + httpCode + ")" + ); } public void @@ -312,7 +312,7 @@ implements ActionListener { // quietly cancel. return; } - readEntity(json); + readEntity(json); showingLatest = false; windowUpdater.remove(TimelineWindow.this); } @@ -332,36 +332,36 @@ implements ActionListener { display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); api.getTimelinePage( page.type, - PREVIEW_COUNT, null, firstId, - page.accountNumId, page.listId, + PREVIEW_COUNT, null, firstId, + page.accountNumId, page.listId, new RequestListener() { public void connectionFailed(IOException eIo) { - JOptionPane.showMessageDialog( - TimelineWindow.this, - "Failed to fetch page.." - + "\n" + eIo.getMessage() - ); + JOptionPane.showMessageDialog( + TimelineWindow.this, + "Failed to fetch page.." + + "\n" + eIo.getMessage() + ); } public void requestFailed(int httpCode, Tree json) { JOptionPane.showMessageDialog( - TimelineWindow.this, - "Failed to fetch page.." - + "\n" + json.get("error").value - + "\n(HTTP code: " + httpCode + ")" - ); + TimelineWindow.this, + "Failed to fetch page.." + + "\n" + json.get("error").value + + "\n(HTTP code: " + httpCode + ")" + ); } public void requestSucceeded(Tree json) { - if (json.size() < PREVIEW_COUNT) - { + if (json.size() < PREVIEW_COUNT) + { showLatestPage(); return; } @@ -386,12 +386,12 @@ implements ActionListener { setTitle(toString(type) + " timeline - JKomasto"); String f = type.toString().toLowerCase(); - display.setBackgroundImage(ImageApi.local(f)); - /* - * (注) Java's image renderer draws images with transparency - * darker than GIMP does. Overcompensate in lightening. - */ - display.repaint(); + display.setBackgroundImage(ImageApi.local(f)); + /* + * (注) Java's image renderer draws images with transparency + * darker than GIMP does. Overcompensate in lightening. + */ + display.repaint(); } public synchronized TimelineType @@ -410,9 +410,9 @@ implements ActionListener { display.repaint(); } - public synchronized void - openOwnProfile() - { + public synchronized void + openOwnProfile() + { display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); Tree accountDetails = api.getAccountDetails(); @@ -422,118 +422,118 @@ implements ActionListener { w.setVisible(true); display.setCursor(null); - } + } - public void - openProfile() - { - String query = JOptionPane.showInputDialog( - this, - "Whose account do you want to see?\n" - + "Type an account name with the instance,\n" - + "or a display name if you can't remember.\n", - "Profile search", - JOptionPane.PLAIN_MESSAGE - ); - if (query == null) return; + public void + openProfile() + { + String query = JOptionPane.showInputDialog( + this, + "Whose account do you want to see?\n" + + "Type an account name with the instance,\n" + + "or a display name if you can't remember.\n", + "Profile search", + JOptionPane.PLAIN_MESSAGE + ); + if (query == null) return; - class Handler implements RequestListener { + class Handler implements RequestListener { - public Tree - json; + public Tree + json; -// -=%=- +// -=%=- - public void - connectionFailed(IOException eIo) - { - JOptionPane.showMessageDialog( - TimelineWindow.this, - "Tried to fetch accounts, but it failed.." - + "\n" + eIo.getMessage() - ); - } + public void + connectionFailed(IOException eIo) + { + JOptionPane.showMessageDialog( + TimelineWindow.this, + "Tried to fetch accounts, but it failed.." + + "\n" + eIo.getMessage() + ); + } - public void - requestFailed(int httpCode, Tree json) - { - JOptionPane.showMessageDialog( - TimelineWindow.this, - "Tried to fetch accounts, but it failed.." - + "\n" + json.get("error").value - + "\n(HTTP code: " + httpCode + ")" - ); - } + public void + requestFailed(int httpCode, Tree json) + { + JOptionPane.showMessageDialog( + TimelineWindow.this, + "Tried to fetch accounts, but it failed.." + + "\n" + json.get("error").value + + "\n(HTTP code: " + httpCode + ")" + ); + } - public void - requestSucceeded(Tree json) { this.json = json; } + public void + requestSucceeded(Tree json) { this.json = json; } - } - // (知) Have to create a named class because - // it has to hold the variable. - Handler handler = new Handler(); - api.getAccounts(query, handler); - if (handler.json == null) return; + } + // (知) Have to create a named class because + // it has to hold the variable. + Handler handler = new Handler(); + api.getAccounts(query, handler); + if (handler.json == null) return; - if (handler.json.size() == 0) - { - JOptionPane.showMessageDialog( - this, - "There were no results from the query.. ☹️" - ); - return; - } + if (handler.json.size() == 0) + { + JOptionPane.showMessageDialog( + this, + "There were no results from the query.. ☹️" + ); + return; + } - Tree openee = null; - if (query.startsWith("@")) query = query.substring(1); + Tree openee = null; + if (query.startsWith("@")) query = query.substring(1); - List message = new ArrayList<>(); - message.add("Maybe one of these?"); - ButtonGroup selGroup = new ButtonGroup(); - for (Tree account: handler.json) - { - String dname = account.get("display_name").value; - String acct = account.get("acct").value; - if (query.equals(acct)) { + List message = new ArrayList<>(); + message.add("Maybe one of these?"); + ButtonGroup selGroup = new ButtonGroup(); + for (Tree account: handler.json) + { + String dname = account.get("display_name").value; + String acct = account.get("acct").value; + if (query.equals(acct)) { openee = account; - break; - } - JRadioButton b = new JRadioButton(); - b.setText(dname + " (" + acct + ")"); - selGroup.add(b); - message.add(b); - } - if (openee == null) - { - int response = JOptionPane.showConfirmDialog( - this, - message.toArray(), - "Search results", - JOptionPane.OK_CANCEL_OPTION - ); - if (response == JOptionPane.CANCEL_OPTION) return; - for (int o = 1; o < message.size(); ++o) - { - JRadioButton b = (JRadioButton)message.get(o); - if (selGroup.isSelected(b.getModel())) - { + break; + } + JRadioButton b = new JRadioButton(); + b.setText(dname + " (" + acct + ")"); + selGroup.add(b); + message.add(b); + } + if (openee == null) + { + int response = JOptionPane.showConfirmDialog( + this, + message.toArray(), + "Search results", + JOptionPane.OK_CANCEL_OPTION + ); + if (response == JOptionPane.CANCEL_OPTION) return; + for (int o = 1; o < message.size(); ++o) + { + JRadioButton b = (JRadioButton)message.get(o); + if (selGroup.isSelected(b.getModel())) + { openee = handler.json.get(o - 1); - break; - } - } - if (openee == null) return; - /* - * It seems like this can happen if someone - * presses escape out of the confirm dialog. - * I don't know why that doesn't map to cancel. - */ - } + break; + } + } + if (openee == null) return; + /* + * It seems like this can happen if someone + * presses escape out of the confirm dialog. + * I don't know why that doesn't map to cancel. + */ + } ProfileWindow w = new ProfileWindow(primaire); w.use(new Account(openee)); w.setLocationByPlatform(true); w.setVisible(true); - } + } // - -%- - @@ -577,13 +577,13 @@ implements ActionListener { setTimelineType(TimelineType.LOCAL); showLatestPage(); } - if (src == openOwnProfile) + if (src == openOwnProfile) { - openOwnProfile(); + openOwnProfile(); } - if (src == openProfile) + if (src == openProfile) { - openProfile(); + openProfile(); } if (src == createPost) { @@ -595,15 +595,15 @@ implements ActionListener { w.setLocation(getX() + 10 + getWidth(), getY()); w.setVisible(true); } - if (src == openNotifications) + if (src == openNotifications) { - NotificationsWindow w = + NotificationsWindow w = primaire.getNotificationsWindow(); - if (!w.isVisible()) - { - w.setLocationByPlatform(true); - w.setVisible(true); - } + if (!w.isVisible()) + { + w.setLocationByPlatform(true); + w.setVisible(true); + } } if (src == flipToNewestPost) { @@ -650,17 +650,17 @@ implements ActionListener { openHome = new JMenuItem("Open home timeline"); openFederated = new JMenuItem("Open federated timeline"); - openNotifications = new JMenuItem("Open notifications"); - openOwnProfile = new JMenuItem("Open own profile"); - openProfile = new JMenuItem("Open profile.."); + openNotifications = new JMenuItem("Open notifications"); + openOwnProfile = new JMenuItem("Open own profile"); + openProfile = new JMenuItem("Open profile.."); createPost = new JMenuItem("Create a post"); openAutoPostView = new JMenuItem("Open auto post view"); quit = new JMenuItem("Quit"); openHome.addActionListener(this); openFederated.addActionListener(this); - openNotifications.addActionListener(this); - openOwnProfile.addActionListener(this); - openProfile.addActionListener(this); + openNotifications.addActionListener(this); + openOwnProfile.addActionListener(this); + openProfile.addActionListener(this); createPost.addActionListener(this); openAutoPostView.addActionListener(this); quit.addActionListener(this); @@ -672,9 +672,9 @@ implements ActionListener { programMenu.setMnemonic(KeyEvent.VK_P); programMenu.add(openHome); programMenu.add(openFederated); - programMenu.add(openNotifications); - programMenu.add(openOwnProfile); - programMenu.add(openProfile); + programMenu.add(openNotifications); + programMenu.add(openOwnProfile); + programMenu.add(openProfile); programMenu.add(new JSeparator()); programMenu.add(createPost); programMenu.add(openAutoPostView); @@ -696,8 +696,8 @@ implements ActionListener { display.setPreviousPageAvailable(false); setContentPane(display); - setTimelineType(TimelineType.HOME); - setIconImage(primaire.getProgramIcon()); + setTimelineType(TimelineType.HOME); + setIconImage(primaire.getProgramIcon()); } } @@ -727,8 +727,8 @@ implements private boolean hoverSelect; - private Image - backgroundImage; + private Image + backgroundImage; // - -%- - @@ -759,46 +759,46 @@ implements public void setHoverSelect(boolean n) { hoverSelect = n; } - public void - setBackgroundImage(Image n) { backgroundImage = n; } + public void + setBackgroundImage(Image n) { backgroundImage = n; } - public void - resetFocus() { postPreviews.get(0).requestFocusInWindow(); } + public void + resetFocus() { postPreviews.get(0).requestFocusInWindow(); } // - -%- - - protected void - paintComponent(Graphics g) - { + protected void + paintComponent(Graphics g) + { int w = getWidth(), h = getHeight(); - g.clearRect(0, 0, w, h); + g.clearRect(0, 0, w, h); - if (backgroundImage != null) - { - int b = h * 5 / 10; - int iw = backgroundImage.getWidth(this); - int ih = backgroundImage.getHeight(this); - if (ih > iw) { - ih = ih * b / iw; - iw = b; - } - else { - iw = iw * b / ih; - ih = b; - } - int x = w - iw, y = h - ih; - g.drawImage(backgroundImage, x, y, iw, ih, this); - } + if (backgroundImage != null) + { + int b = h * 5 / 10; + int iw = backgroundImage.getWidth(this); + int ih = backgroundImage.getHeight(this); + if (ih > iw) { + ih = ih * b / iw; + iw = b; + } + else { + iw = iw * b / ih; + ih = b; + } + int x = w - iw, y = h - ih; + g.drawImage(backgroundImage, x, y, iw, ih, this); + } - ((java.awt.Graphics2D)g).setRenderingHint( - java.awt.RenderingHints.KEY_ANTIALIASING, - java.awt.RenderingHints.VALUE_ANTIALIAS_ON - ); - } + ((java.awt.Graphics2D)g).setRenderingHint( + java.awt.RenderingHints.KEY_ANTIALIASING, + java.awt.RenderingHints.VALUE_ANTIALIAS_ON + ); + } - private void - select(Object c) - { + private void + select(Object c) + { assert c instanceof PostPreviewComponent; for (PostPreviewComponent p: postPreviews) @@ -814,25 +814,25 @@ implements p.repaint(); } } - } + } - private void - deselect(Object c) - { + private void + deselect(Object c) + { assert c instanceof PostPreviewComponent; PostPreviewComponent p = (PostPreviewComponent)c; p.setSelected(false); p.repaint(); - } + } - private void - open(Object c) - { + private void + open(Object c) + { int o = postPreviews.indexOf(c); assert o != -1; primaire.previewOpened(1 + o); - } + } public void focusGained(FocusEvent eF) { select(eF.getSource()); } @@ -840,14 +840,14 @@ implements public void focusLost(FocusEvent eF) { deselect(eF.getSource()); } - public void + public void mouseClicked(MouseEvent eM) { if (eM.getClickCount() == 2) open(eM.getSource()); else select(eM.getSource()); } - public void + public void mouseEntered(MouseEvent eM) { if (!hoverSelect) return; @@ -968,8 +968,8 @@ PostPreviewComponent extends JComponent { private JLabel topLeft, topRight, bottom; - private boolean - selected; + private boolean + selected; // ---%-@-%--- @@ -982,7 +982,7 @@ PostPreviewComponent extends JComponent { public void setBottom(String text) { bottom.setText(text); } - public void + public void reset() { setTopLeft(" "); @@ -1001,11 +1001,11 @@ PostPreviewComponent extends JComponent { protected void paintComponent(Graphics g) { - if (selected) - { - g.setColor(new Color(0, 0, 0, 25)); - g.fillRect(0, 0, getWidth(), getHeight()); - } + if (selected) + { + g.setColor(new Color(0, 0, 0, 25)); + g.fillRect(0, 0, getWidth(), getHeight()); + } } // ---%-@-%--- @@ -1032,20 +1032,20 @@ PostPreviewComponent extends JComponent { top.add(Box.createGlue()); top.add(topRight); - bottom = new JLabel(); + bottom = new JLabel(); bottom.setFont(f3); bottom.setOpaque(false); - JPanel left = new JPanel(); - left.setOpaque(false); - left.setLayout(new BorderLayout()); + JPanel left = new JPanel(); + left.setOpaque(false); + left.setLayout(new BorderLayout()); left.add(top, BorderLayout.NORTH); left.add(bottom); setFocusable(true); setOpaque(false); - setLayout(new BorderLayout()); - add(left); + setLayout(new BorderLayout()); + add(left); } } diff --git a/TwoToggleButton.java b/TwoToggleButton.java index b3f4f9d..01684d3 100644 --- a/TwoToggleButton.java +++ b/TwoToggleButton.java @@ -56,10 +56,10 @@ implements KeyListener, MouseListener, FocusListener { primaryToggled = false, secondaryToggled = false; - private Image + private Image primaryToggledIcon, secondaryToggledIcon, - primaryUntoggledIcon, + primaryUntoggledIcon, secondaryUntoggledIcon; private int @@ -116,7 +116,7 @@ implements KeyListener, MouseListener, FocusListener { // - -%- - - private void + private void announce(String name, boolean toggled) { ActionEvent eA = new ActionEvent( @@ -134,17 +134,17 @@ implements KeyListener, MouseListener, FocusListener { g.drawImage(button, 0, 0, this); if (!isEnabled()) g.drawImage(disabledOverlay, 0, 0, this); - if (isFocusOwner()) + if (isFocusOwner()) g.drawImage(selectedOverlay, 0, 0, this); - if (secondaryToggled) - g.drawImage(secondaryToggledIcon, 0, 0, this); - else - g.drawImage(secondaryUntoggledIcon, 0, 0, this); - if (primaryToggled) - g.drawImage(primaryToggledIcon, 0, 0, this); - else - g.drawImage(primaryUntoggledIcon, 0, 0, this); + if (secondaryToggled) + g.drawImage(secondaryToggledIcon, 0, 0, this); + else + g.drawImage(secondaryUntoggledIcon, 0, 0, this); + if (primaryToggled) + g.drawImage(primaryToggledIcon, 0, 0, this); + else + g.drawImage(primaryUntoggledIcon, 0, 0, this); } @@ -176,11 +176,11 @@ implements KeyListener, MouseListener, FocusListener { requestFocusInWindow(); } - public void - focusGained(FocusEvent eF) { repaint(); } + public void + focusGained(FocusEvent eF) { repaint(); } - public void - focusLost(FocusEvent eF) { repaint(); } + public void + focusLost(FocusEvent eF) { repaint(); } public void @@ -214,8 +214,8 @@ implements KeyListener, MouseListener, FocusListener { this.secondaryName = secondaryName; setModel(new DefaultButtonModel()); - setFocusable(true); - setOpaque(false); + setFocusable(true); + setOpaque(false); int w = button.getWidth(null); int h = button.getHeight(null); @@ -224,7 +224,7 @@ implements KeyListener, MouseListener, FocusListener { this.addKeyListener(this); this.addMouseListener(this); - this.addFocusListener(this); + this.addFocusListener(this); } private void @@ -232,17 +232,17 @@ implements KeyListener, MouseListener, FocusListener { { String p1 = "graphics/" + primaryName + "Toggled.png"; String p2 = "graphics/" + secondaryName + "Toggled.png"; - String p3 = "graphics/" + primaryName + "Untoggled.png"; + String p3 = "graphics/" + primaryName + "Untoggled.png"; String p4 = "graphics/" + secondaryName + "Untoggled.png"; URL u1 = getClass().getResource(p1); URL u2 = getClass().getResource(p2); - URL u3 = getClass().getResource(p3); - URL u4 = getClass().getResource(p4); + URL u3 = getClass().getResource(p3); + URL u4 = getClass().getResource(p4); if (u1 == null) primaryToggledIcon = null; else primaryToggledIcon = new ImageIcon(u1).getImage(); if (u2 == null) secondaryToggledIcon = null; else secondaryToggledIcon = new ImageIcon(u2).getImage(); - if (u3 == null) primaryUntoggledIcon = null; + if (u3 == null) primaryUntoggledIcon = null; else primaryUntoggledIcon = new ImageIcon(u3).getImage(); if (u4 == null) secondaryUntoggledIcon = null; else secondaryUntoggledIcon = new ImageIcon(u4).getImage(); @@ -270,16 +270,16 @@ class RoundButton extends AbstractButton implements KeyListener, MouseListener, FocusListener { - private Image - image; + private Image + image; -// - -%- - +// - -%- - - private Image - copy, - scaled; + private Image + copy, + scaled; - private int + private int nextEventID = ActionEvent.ACTION_FIRST; // - -%- - @@ -289,99 +289,99 @@ implements KeyListener, MouseListener, FocusListener { selectedOverlay, disabledOverlay; - private static Shape - roundClip; + private static Shape + roundClip; // ---%-@-%--- - public void - setImage(Image n) - { - image = n; - copy = null; - scaled = null; + public void + setImage(Image n) + { + image = n; + copy = null; + scaled = null; - if (image != null) - { - image.flush(); - prepareImage(image, this); - } - } + if (image != null) + { + image.flush(); + prepareImage(image, this); + } + } -// - -%- - +// - -%- - - public boolean - imageUpdate(Image img, int f, int x, int y, int w, int 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. + public boolean + imageUpdate(Image img, int f, int x, int y, int w, int 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; + if ((f & (ABORT|ERROR)) != 0) return false; - boolean all = (f & ALLBITS) != 0; - boolean frame = (f & FRAMEBITS) != 0; - boolean some = (f & SOMEBITS) != 0; + boolean all = (f & ALLBITS) != 0; + boolean frame = (f & FRAMEBITS) != 0; + boolean some = (f & SOMEBITS) != 0; - if (frame && img != this.image) return false; + if (frame && img != this.image) return false; - if (img == this.image && (all || frame)) - { - int ow = img.getWidth(null); - int oh = img.getHeight(null); + 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); - } + 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(); + 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; - } + 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 paintComponent(Graphics g) { - g.drawImage(button, 0, 0, this); + g.drawImage(button, 0, 0, this); if (!isEnabled()) g.drawImage(disabledOverlay, 0, 0, this); - if (isFocusOwner()) + if (isFocusOwner()) g.drawImage(selectedOverlay, 0, 0, this); - if (scaled == null) return; + if (scaled == null) return; - Rectangle b = roundClip.getBounds(); - Shape defaultClip = g.getClip(); - g.setClip(roundClip); - 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); + Rectangle b = roundClip.getBounds(); + Shape defaultClip = g.getClip(); + g.setClip(roundClip); + 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); } private void @@ -396,31 +396,31 @@ implements KeyListener, MouseListener, FocusListener { public void keyPressed(KeyEvent eK) { - if (eK.getKeyCode() != KeyEvent.VK_SPACE) return; - doClick(); + if (eK.getKeyCode() != KeyEvent.VK_SPACE) return; + doClick(); } - public void - mouseClicked(MouseEvent eM) { announce(); } + public void + mouseClicked(MouseEvent eM) { announce(); } - public void - focusGained(FocusEvent eF) { repaint(); } + public void + focusGained(FocusEvent eF) { repaint(); } - public void - focusLost(FocusEvent eF) { repaint(); } + public void + focusLost(FocusEvent eF) { repaint(); } - public void - mousePressed(MouseEvent eM) { } + public void + mousePressed(MouseEvent eM) { } - public void - mouseReleased(MouseEvent eM) { } + public void + mouseReleased(MouseEvent eM) { } - public void - mouseEntered(MouseEvent eM) { } + public void + mouseEntered(MouseEvent eM) { } - public void - mouseExited(MouseEvent eM) { } + public void + mouseExited(MouseEvent eM) { } public void keyReleased(KeyEvent eK) { } @@ -437,16 +437,16 @@ implements KeyListener, MouseListener, FocusListener { if (button == null) loadCommonImages(); setModel(new DefaultButtonModel()); - setFocusable(true); - setOpaque(false); + setFocusable(true); + setOpaque(false); int w = button.getWidth(null); int h = button.getHeight(null); setPreferredSize(new Dimension(w, h)); this.addKeyListener(this); - this.addMouseListener(this); - this.addFocusListener(this); + this.addMouseListener(this); + this.addFocusListener(this); } // - -%- - @@ -463,13 +463,13 @@ implements KeyListener, MouseListener, FocusListener { 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) - ); + int radius = 6; + roundClip = new Ellipse2D.Float( + radius, + radius, + button.getWidth(null) - (2 * radius), + button.getHeight(null) - (2 * radius) + ); } } diff --git a/WindowUpdater.java b/WindowUpdater.java index f2259fe..0b9f4c4 100644 --- a/WindowUpdater.java +++ b/WindowUpdater.java @@ -43,11 +43,11 @@ WindowUpdater { private List timelineWindows; - private List - notificationWindows; + private List + notificationWindows; - private Clip - notificationSound; + private Clip + notificationSound; private Connection publicConn, @@ -65,14 +65,14 @@ WindowUpdater { userConn.reevaluate(); } - public synchronized void - add(NotificationsWindow updatee) - { + public synchronized void + add(NotificationsWindow updatee) + { if (!notificationWindows.contains(updatee)) notificationWindows.add(updatee); - userConn.reevaluate(); - } + userConn.reevaluate(); + } public synchronized void remove(TimelineWindow updatee) @@ -82,7 +82,7 @@ WindowUpdater { userConn.reevaluate(); } - public synchronized void + public synchronized void remove(NotificationsWindow updatee) { notificationWindows.remove(updatee); @@ -144,21 +144,21 @@ WindowUpdater { stop() { stopping = true; - thread.interrupt(); - try - { + thread.interrupt(); + try + { thread.join(3000); /* * That thread should notice it is * interrupted ppromptly, and close. */ if (thread.isAlive()) printStackTrace(thread); - } - catch (InterruptedException eIt) - { + } + catch (InterruptedException eIt) + { assert false; - } - thread = null; + } + thread = null; } public void @@ -186,21 +186,21 @@ WindowUpdater { thread.notifyAll(); } event = new StringBuilder(); - data = new StringBuilder(); - api.monitorTimeline(type, this); - // monitorTimeline should not return until - // the connection is closed, or this thread - // is interrupted. + data = new StringBuilder(); + api.monitorTimeline(type, this); + // monitorTimeline should not return until + // the connection is closed, or this thread + // is interrupted. - System.err.println( + System.err.println( "Stopped monitoring." + thread + " " + Thread.currentThread() ); - if (thread == Thread.currentThread()) thread = null; - /* - * This isn't thread safe. But I'd like the - * restart after sleep mode, so. - */ + if (thread == Thread.currentThread()) thread = null; + /* + * This isn't thread safe. But I'd like the + * restart after sleep mode, so. + */ } public void @@ -209,7 +209,7 @@ WindowUpdater { if (line.startsWith(":")) return; if (line.isEmpty()) - { + { handle(event.toString(), data.toString()); event.delete(0, event.length()); data.delete(0, event.length()); @@ -299,38 +299,38 @@ WindowUpdater { this.api = primaire.getMastodonApi(); this.timelineWindows = new ArrayList<>(); - this.notificationWindows = new ArrayList<>(); + this.notificationWindows = new ArrayList<>(); - publicConn = new Connection(); - publicConn.type = TimelineType.FEDERATED; + publicConn = new Connection(); + publicConn.type = TimelineType.FEDERATED; - userConn = new Connection(); - userConn.type = TimelineType.HOME; + userConn = new Connection(); + userConn.type = TimelineType.HOME; - loadNotificationSound(); + loadNotificationSound(); } - void - loadNotificationSound() - { - URL url = getClass().getResource("KDE_Dialog_Appear.wav"); - try { - Clip clip = AudioSystem.getClip(); - clip.open(AudioSystem.getAudioInputStream(url)); - notificationSound = clip; - } - catch (LineUnavailableException eLu) { - assert false; - } - catch (UnsupportedAudioFileException eUa) { - assert false; - } - catch (IOException eIo) { - assert false; - } - catch (IllegalArgumentException eIa) { - assert false; - } - } + void + loadNotificationSound() + { + URL url = getClass().getResource("KDE_Dialog_Appear.wav"); + try { + Clip clip = AudioSystem.getClip(); + clip.open(AudioSystem.getAudioInputStream(url)); + notificationSound = clip; + } + catch (LineUnavailableException eLu) { + assert false; + } + catch (UnsupportedAudioFileException eUa) { + assert false; + } + catch (IOException eIo) { + assert false; + } + catch (IllegalArgumentException eIa) { + assert false; + } + } }