diff --git a/BasicHTMLParser.java b/BasicHTMLParser.java
new file mode 100644
index 0000000..d55c2eb
--- /dev/null
+++ b/BasicHTMLParser.java
@@ -0,0 +1,328 @@
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.text.BreakIterator;
+import cafe.biskuteri.hinoki.Tree;
+
+interface
+BasicHTMLParser {
+
+ public static Tree
+ parse(String html)
+ {
+ List segments;
+ segments = distinguishTagsFromPcdata(html);
+ segments = evaluateHtmlEscapes(segments);
+
+ Tree document;
+ document = toNodes(segments);
+ document = distinguishEmojisFromText(document);
+ document = hierarchise(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);
+
+ return returnee;
+ }
+
+ private static List
+ evaluateHtmlEscapes(List strings)
+ {
+ List returnee = new ArrayList<>();
+
+ for (String string: strings)
+ {
+ 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);
+
+ returnee.add(empty(whole));
+ }
+
+ return returnee;
+ }
+
+ private static Tree
+ toNodes(List segments)
+ {
+ Tree returnee = 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;
+ }
+
+ 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);
+ }
+
+ returnee.add(node);
+ }
+
+ return returnee;
+ }
+
+ private static Tree
+ distinguishEmojisFromText(Tree nodes)
+ {
+ Tree returnee = new Tree();
+
+ for (Tree node: nodes)
+ {
+ if (!node.key.equals("text"))
+ {
+ returnee.add(node);
+ continue;
+ }
+
+ List segments;
+ segments = distinguishWhitespaceFromText(node.value);
+ StringBuilder b = new StringBuilder();
+ for (String segment: segments)
+ {
+ boolean starts = segment.startsWith(":");
+ boolean ends = segment.endsWith(":");
+ if (starts && ends)
+ {
+ Tree text = new Tree();
+ text.key = "text";
+ text.value = empty(b);
+ returnee.add(text);
+ Tree emoji = new Tree();
+ emoji.key = "emoji";
+ emoji.value = segment;
+ returnee.add(emoji);
+ }
+ else
+ {
+ b.append(segment);
+ }
+ }
+ if (b.length() > 0)
+ {
+ Tree text = new Tree();
+ text.key = "text";
+ text.value = empty(b);
+ returnee.add(text);
+ }
+ }
+
+ return returnee;
+ }
+
+ private static Tree
+ hierarchise(Tree nodes)
+ {
+ Tree root = new Tree();
+ root.add(new Tree<>("attributes", null));
+ root.get(0).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;
+
+ 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();
+
+ assert tagName.equals(
+ "/"
+ + parent.get("attributes").get(0).key
+ );
+
+ grandparent.get("children").add(parent);
+ }
+ else if (selfClosing)
+ {
+ Tree elem = new Tree();
+ node.key = "attributes";
+ elem.add(node);
+ elem.add(new Tree<>("children", null));
+
+ parents.peek().get("children").add(elem);
+ }
+ else
+ {
+ Tree elem = new Tree();
+ node.key = "attributes";
+ elem.add(node);
+ elem.add(new Tree<>("children", null));
+
+ parents.push(elem);
+ }
+ }
+ else
+ {
+ parents.peek().get("children").add(node);
+ }
+ }
+
+ 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 List
+ distinguishWhitespaceFromText(String text)
+ {
+ List returnee = new ArrayList<>();
+
+ StringBuilder segment = new StringBuilder();
+ boolean inWhitespace = false;
+ for (char c: text.toCharArray())
+ {
+ boolean w = Character.isWhitespace(c);
+ boolean change = w ^ inWhitespace;
+ if (change)
+ {
+ returnee.add(empty(segment));
+ inWhitespace = !inWhitespace;
+ }
+ segment.append(c);
+ }
+ returnee.add(empty(segment));
+
+ return returnee;
+ }
+
+}
\ No newline at end of file
diff --git a/ClipboardApi.java b/ClipboardApi.java
old mode 100644
new mode 100755
diff --git a/ComposeWindow.java b/ComposeWindow.java
old mode 100644
new mode 100755
diff --git a/ImageApi.java b/ImageApi.java
old mode 100644
new mode 100755
diff --git a/ImageWindow.java b/ImageWindow.java
old mode 100644
new mode 100755
diff --git a/JKomasto.java b/JKomasto.java
old mode 100644
new mode 100755
index 803c69f..a6f9cac
--- a/JKomasto.java
+++ b/JKomasto.java
@@ -436,7 +436,7 @@ Post {
{
Tree emoji = emojis.get(o);
String[] mapping = emojiUrls[o] = new String[2];
- mapping[0] = emoji.get("shortcode").value;
+ mapping[0] = ":" + emoji.get("shortcode").value + ":";
mapping[1] = emoji.get("url").value;
}
@@ -467,6 +467,22 @@ Account {
public Image
avatar;
+ public ZonedDateTime
+ creationDate;
+
+ public int
+ followedCount,
+ followerCount;
+
+ public int
+ postCount;
+
+ public String[][]
+ fields;
+
+ public String
+ description;
+
// ---%-@-%---
public void
@@ -501,6 +517,36 @@ Account {
name = displayName.isEmpty() ? username : displayName;
avatarUrl = entity.get("avatar").value;
+
+ 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 {
+ followedCount = (int)Double.parseDouble(c1);
+ followerCount = (int)Double.parseDouble(c2);
+ postCount = (int)Double.parseDouble(c3);
+ }
+ catch (NumberFormatException eNf) {
+ assert false;
+ }
+
+ Tree fs = entity.get("fields");
+ fields = new String[fs.size()][];
+ for (int o = 0; o < fields.length; ++o)
+ {
+ Tree f = fs.get(o);
+ String[] field = fields[o] = new String[3];
+ field[0] = f.get("name").value;
+ field[1] = f.get("value").value;
+ boolean v = f.get("verified_at").value != null;
+ field[2] = Boolean.toString(v);
+ }
+
+ description = entity.get("note").value;
}
}
@@ -543,11 +589,10 @@ Attachment {
public
Attachment(Tree entity)
{
- String u1 = entity.get("remote_url").value;
- String u2 = entity.get("text_url").value;
- String u3 = entity.get("url").value;
+ url = entity.get("remote_url").value;
+ if (url == null) url = entity.get("url").value;
- url = u1 != null ? u1 : u2 != null ? u2 : u3;
+ id = entity.get("id").value;
type = entity.get("type").value;
description = entity.get("description").value;
}
diff --git a/KDE_Dialog_Appear.wav b/KDE_Dialog_Appear.wav
old mode 100644
new mode 100755
diff --git a/LoginWindow.java b/LoginWindow.java
old mode 100644
new mode 100755
diff --git a/MastodonApi.java b/MastodonApi.java
old mode 100644
new mode 100755
index c3e397a..137af65
--- a/MastodonApi.java
+++ b/MastodonApi.java
@@ -15,8 +15,10 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
+import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
+import java.io.FileInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@@ -427,6 +429,52 @@ MastodonApi {
catch (IOException eIo) { handler.connectionFailed(eIo); }
}
+ public void
+ uploadFile(File file, RequestListener handler)
+ {
+ assert file != null;
+ assert file.canRead();
+
+ String token = accessToken.get("access_token").value;
+
+ String url = instanceUrl + "/api/v1/media/";
+ try
+ {
+ URL endpoint = new URL(url);
+ HttpURLConnection conn = cast(endpoint.openConnection());
+ String s1 = "Bearer " + token;
+ conn.setRequestProperty("Authorization", s1);
+ conn.setDoOutput(true);
+ conn.setRequestMethod("POST");
+ String s2 = "multipart/form-data; ";
+ String s3 = "boundary=\"MastodonMediaUpload\"";
+ conn.setRequestProperty("Content-Type", s2 + s3);
+ conn.connect();
+
+ OutputStream ostream = conn.getOutputStream();
+ Writer owriter = owriter(ostream);
+ InputStream istream = new FileInputStream(file);
+ // Let's see if this works!
+
+ String s4, s5, s6;
+ s4 = "--MastodonMediaUpload";
+ s5 = "Content-Disposition: form-data; name=file";
+ s6 = "Content-Type: application/octet-stream";
+ owriter.write(s4 + "\r\n");
+ owriter.write(s5 + "\r\n");
+ owriter.write(s6 + "\r\n\r\n");
+ int c; while ((c = istream.read()) != -1)
+ ostream.write(c);
+ owriter.write("\r\n" + s4 + "--\r\n");
+
+ istream.close();
+ ostream.close();
+
+ wrapResponseInTree(conn, handler);
+ }
+ catch (IOException eIo) { handler.connectionFailed(eIo); }
+ }
+
public void
monitorTimeline(
TimelineType type, ServerSideEventsListener handler)
@@ -448,7 +496,6 @@ MastodonApi {
HttpURLConnection conn = cast(endpoint.openConnection());
String s = "Bearer " + token;
conn.setRequestProperty("Authorization", s);
- conn.setReadTimeout(500);
conn.connect();
int code = conn.getResponseCode();
@@ -461,6 +508,7 @@ MastodonApi {
return;
}
+ conn.setReadTimeout(500);
Reader input = ireader(conn.getInputStream());
BufferedReader br = new BufferedReader(input);
Thread thread = Thread.currentThread();
diff --git a/NotificationsWindow.java b/NotificationsWindow.java
old mode 100644
new mode 100755
diff --git a/PostWindow.java b/PostWindow.java
old mode 100644
new mode 100755
index 2f7aa6b..19e08fb
--- a/PostWindow.java
+++ b/PostWindow.java
@@ -125,11 +125,10 @@ PostWindow extends JFrame {
public synchronized void
openAuthorProfile()
{
- TimelineWindow w = new TimelineWindow(primaire);
- w.showAuthorPosts(post.author.numId);
- w.showLatestPage();
- w.setLocationRelativeTo(this);
- w.setVisible(true);
+ ProfileWindow w = new ProfileWindow(primaire);
+ w.use(post.author);
+ w.setLocationRelativeTo(this);
+ w.setVisible(true);
}
public synchronized void
@@ -251,6 +250,26 @@ PostWindow extends JFrame {
display.setDeleteEnabled(false);
display.paintImmediately(display.getBounds());
+ 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"))
+ {
+ display.setCursor(null);
+ display.setDeleteEnabled(true);
+ display.paintImmediately(display.getBounds());
+ return;
+ }
+
api.deletePost(post.id, new RequestListener() {
public void
@@ -409,6 +428,8 @@ implements ActionListener {
public void
setHtml(String n)
{
+ BasicHTMLParser.parse(n);
+
RichTextPane.Builder b = new RichTextPane.Builder();
Tree nodes = RudimentaryHTMLParser.depthlessRead(n);
for (Tree node: nodes)
diff --git a/ProfileWindow.java b/ProfileWindow.java
new file mode 100755
index 0000000..4f2a97f
--- /dev/null
+++ b/ProfileWindow.java
@@ -0,0 +1,427 @@
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JLabel;
+import javax.swing.JButton;
+import javax.swing.JTextArea;
+import javax.swing.JScrollPane;
+import java.awt.Graphics;
+import java.awt.Cursor;
+import java.awt.Image;
+import java.awt.Dimension;
+import java.awt.Color;
+import java.awt.Shape;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.geom.Ellipse2D;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+
+class
+ProfileWindow extends JFrame {
+
+ private JKomasto
+ primaire;
+
+ private MastodonApi
+ api;
+
+ private Account
+ account;
+
+// - -%- -
+
+ private ProfileComponent
+ display;
+
+// ---%-@-%---
+
+ public void
+ use(Account account)
+ {
+ this.account = account;
+
+ account.resolveAvatar();
+ display.setAvatar(account.avatar);
+
+ display.setAccountID(account.id);
+ display.setDisplayName(account.name);
+
+ int n1 = account.followedCount;
+ int n2 = account.followerCount;
+ display.setFollowedAndFollowers(n1 + " & " + n2);
+
+ int n3 = account.postCount;
+ String hs;
+ if (n3 >= 1000) hs = "~" + (n3 / 1000) + "K";
+ else if (n3 >= 300) hs = "~" + (n3 / 100) + "00";
+ else hs = Integer.toString(n3);
+ hs += " posts since ";
+ switch (account.creationDate.getMonth())
+ {
+ case JANUARY: hs += "Jan"; break;
+ case FEBRUARY: hs += "Feb"; break;
+ case MARCH: hs += "Mar"; break;
+ case APRIL: hs += "Apr"; break;
+ case MAY: hs += "May"; break;
+ case JUNE: hs += "Jun"; break;
+ case JULY: hs += "Jul"; break;
+ case AUGUST: hs += "Aug"; break;
+ case SEPTEMBER: hs += "Sept"; break;
+ case OCTOBER: hs += "Oct"; break;
+ case NOVEMBER: hs += "Nov"; break;
+ case DECEMBER: hs += "Dec"; break;
+ /*
+ * (悪) We're hardcoding for English right now,
+ * but later we need to localise properly using
+ * Month#getDisplayName. Right now I'm just
+ * finishing this component ASAP for English.
+ */
+ }
+ hs += " " + account.creationDate.getYear();
+ display.setHistory(hs);
+
+ for (int i = 1; i <= 4; ++i)
+ {
+ if (i > account.fields.length)
+ {
+ display.setField(i, "", "");
+ continue;
+ }
+ String[] field = account.fields[i - 1];
+ display.setField(i, field[0], field[1]);
+ }
+
+ display.setDescription(account.description);
+
+ setTitle(account.name + " - JKomasto");
+ }
+
+// - -%- -
+
+ public void
+ seePosts()
+ {
+ display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+
+ TimelineWindow w = new TimelineWindow(primaire);
+ w.showAuthorPosts(account.numId);
+ w.showLatestPage();
+ w.setLocationRelativeTo(this);
+ w.setVisible(true);
+
+ display.setCursor(null);
+ }
+
+// ---%-@-%---
+
+ ProfileWindow(JKomasto primaire)
+ {
+ super("Profile window - JKomasto");
+
+ this.primaire = primaire;
+ this.api = primaire.getMastodonApi();
+
+ this.display = new ProfileComponent(this);
+ add(display);
+ pack();
+
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+ }
+
+}
+
+class
+ProfileComponent extends JPanel
+implements ActionListener {
+
+ private ProfileWindow
+ primaire;
+
+// - -5- -
+
+ private Image
+ avatar;
+
+ private JLabel
+ accountIdLabel,
+ accountId,
+ displayNameLabel,
+ displayName,
+ followedLabel,
+ followed,
+ historyLabel,
+ history,
+ field1Label,
+ field1,
+ field2Label,
+ field2,
+ field3Label,
+ field3,
+ field4Label,
+ field4;
+
+ private JTextArea
+ description;
+
+ private JScrollPane
+ scroll;
+
+ private JButton
+ seePosts;
+
+ private int
+ dx1, dx2, dx3, dx4, dy1, dy2, dy3, dy4;
+
+// ---%-@-%---
+
+ public void
+ setAvatar(Image avatar)
+ {
+ this.avatar = avatar;
+ }
+
+ public void
+ setAccountID(String id)
+ {
+ accountId.setText(id);
+ }
+
+ public void
+ setDisplayName(String name)
+ {
+ displayName.setText(name);
+ }
+
+ public void
+ setFollowedAndFollowers(String text)
+ {
+ followed.setText(text);
+ }
+
+ public void
+ setHistory(String text)
+ {
+ history.setText(text);
+ }
+
+ public void
+ setField(int index, String name, String value)
+ {
+ assert index >= 1 && index <= 4;
+ JLabel label = null, field = null;
+ switch (index)
+ {
+ case 1: label = field1Label; field = field1; break;
+ case 2: label = field2Label; field = field2; break;
+ case 3: label = field3Label; field = field3; break;
+ case 4: label = field4Label; field = field4; break;
+ }
+ label.setText(name);
+ field.setText(value);
+ }
+
+ public void
+ setDescription(String html)
+ {
+ description.setText(html);
+ }
+
+// - -%- -
+
+ public void
+ actionPerformed(ActionEvent eA)
+ {
+ assert eA.getSource() == seePosts;
+ primaire.seePosts();
+ }
+
+ protected void
+ paintComponent(Graphics g)
+ {
+ g.clearRect(0, 0, getWidth(), getHeight());
+
+ int w = getWidth(), h = getHeight();
+
+ int aw = 256;
+ int ah = 256;
+ int ax = (w - aw) / 2;
+ int ay = 10;
+ int acx = ax + (aw / 2);
+ int acy = ay + (ah / 2);
+ Shape defaultClip = g.getClip();
+ g.setClip(new Ellipse2D.Float(ax, ay, aw, ah));
+ g.drawImage(avatar, ax, ay, aw, ah, this);
+ g.setClip(defaultClip);
+
+ g.setColor(new Color(0, 0, 0, 50));
+ g.fillRect(0, acy - dy1, acx - dx1, 2);
+ g.fillRect(0, acy - dy2, acx - dx2, 2);
+ g.fillRect(0, acy + dy3, acx - dx3, 2);
+ g.fillRect(0, acy + dy4, acx - dx4, 2);
+ g.fillRect(acx + dx1, acy - dy1, w - (acx + dx1), 2);
+ g.fillRect(acx + dx2, acy - dy2, w - (acx + dx2), 2);
+ g.fillRect(acx + dx3, acy + dy3, w - (acx + dx3), 2);
+ g.fillRect(acx + dx4, acy + dy4, w - (acx + dx4), 2);
+
+ ((java.awt.Graphics2D)g).setRenderingHint(
+ java.awt.RenderingHints.KEY_ANTIALIASING,
+ java.awt.RenderingHints.VALUE_ANTIALIAS_ON
+ );
+ }
+
+ public void
+ doLayout()
+ {
+ final double TAU = 2 * Math.PI;
+
+ int w = getWidth(), h = getHeight();
+ int aw = 256;
+ int ah = 256;
+ int ax = (w - aw) / 2;
+ int ay = 10;
+ int acx = ax + (aw / 2);
+ int acy = ay + (ah / 2);
+
+ dx1 = (int)((aw * 11/20) * Math.cos(TAU * 45 / 360));
+ dx2 = (int)((aw * 11/20) * Math.cos(TAU * 15 / 360));
+ dx3 = dx2;
+ dx4 = dx1;
+ dy1 = (int)((ah / 2) * Math.sin(TAU * 45 / 360));
+ dy2 = (int)((ah / 2) * Math.sin(TAU * 15 / 360));
+ dy3 = dy2;
+ dy4 = dy1;
+
+ FontMetrics fm = getFontMetrics(field1.getFont());
+ int lh = fm.getAscent() * 9 / 8;
+
+ accountIdLabel.setLocation(10, acy - dy1 - lh - 1);
+ accountId.setLocation(10, acy - dy1 + 1);
+ accountIdLabel.setSize(acx - dx1 - 16, lh);
+ accountId.setSize(acx - dx1 - 24, lh);
+
+ displayNameLabel.setLocation(10, acy - dy2 - lh - 1);
+ displayName.setLocation(10, acy - dy2 + 1);
+ displayNameLabel.setSize(acx - dx2 - 16, lh);
+ displayName.setSize(acx - dx2 - 24, lh);
+
+ followedLabel.setLocation(10, acy + dy3 - lh - 1);
+ followed.setLocation(10, acy + dy3 + 1);
+ followedLabel.setSize(acx - dx3 - 24, lh);
+ followed.setSize(acx - dx3 - 16, lh);
+
+ historyLabel.setLocation(10, acy + dy4 - lh - 1);
+ history.setLocation(10, acy + dy4 + 1);
+ historyLabel.setSize(acx - dx4 - 24, lh);
+ history.setSize(acx - dx4 - 16, lh);
+
+ field1Label.setLocation(acx + dx1 + 16, acy - dy1 - lh - 1);
+ field1.setLocation(acx + dx1 + 24, acy - dy1 + 1);
+ field1Label.setSize(w - 10 - (acy + dx1 + 16), lh);
+ field1.setSize(w - 10 - (acy + dx1 + 24), lh);
+
+ field2Label.setLocation(acx + dx2 + 16, acy - dy2 - lh - 1);
+ field2.setLocation(acx + dx2 + 24, acy - dy2 + 1);
+ field2Label.setSize((w - 10) - (acy + dx2 + 16), lh);
+ field2.setSize((w - 10) - (acy + dx2 + 24), lh);
+
+ field3Label.setLocation(acx + dx3 + 24, acy + dy3 - lh - 1);
+ field3.setLocation(acx + dx3 + 16, acy + dy3 + 1);
+ field3Label.setSize((w - 10) - (acy + dx3 + 24), lh);
+ field3.setSize((w - 10) - (acy + dx3 + 16), lh);
+
+ field4Label.setLocation(acx + dx4 + 24, acy + dy4 - lh - 1);
+ field4.setLocation(acx + dx4 + 16, acy + dy4 + 1);
+ field4Label.setSize((w - 10) - (acy + dx4 + 24), lh);
+ field4.setSize((w - 10) - (acy + dx4 + 16), lh);
+
+ seePosts.setLocation(10, h - 10 - 24);
+ seePosts.setSize((w - 40) / 4, 24);
+
+ scroll.setLocation(10, (ay + ah) + 10);
+ scroll.setSize(w - 20, seePosts.getY() - 10 - scroll.getY());
+ }
+
+// ---%-@-%---
+
+ ProfileComponent(ProfileWindow primaire)
+ {
+ this.primaire = primaire;
+
+ Font f1 = new Font("VL Gothic", Font.PLAIN, 16);
+ Font f2 = new Font("VL Gothic", Font.PLAIN, 14);
+
+ int a = JLabel.RIGHT;
+ accountIdLabel = new JLabel("Account ID", a);
+ accountId = new JLabel("", a);
+ displayNameLabel = new JLabel("Display name", a);
+ displayName = new JLabel("", a);
+ followedLabel = new JLabel("Followed & followers", a);
+ followed = new JLabel("", a);
+ historyLabel = new JLabel("History", a);
+ history = new JLabel("", a);
+ field1Label = new JLabel("");
+ field1 = new JLabel("");
+ field2Label = new JLabel("");
+ field2 = new JLabel("");
+ field3Label = new JLabel("");
+ field3 = new JLabel("");
+ field4Label = new JLabel("");
+ field4 = new JLabel("");
+
+ accountIdLabel.setFont(f1);
+ accountId.setFont(f1);
+ displayNameLabel.setFont(f1);
+ displayName.setFont(f1);
+ followedLabel.setFont(f1);
+ followed.setFont(f1);
+ historyLabel.setFont(f1);
+ history.setFont(f2);
+ field1Label.setFont(f1);
+ field1.setFont(f2);
+ field2Label.setFont(f1);
+ field2.setFont(f2);
+ field3Label.setFont(f1);
+ field3.setFont(f2);
+ field4Label.setFont(f1);
+ field4.setFont(f2);
+
+ description = new JTextArea();
+ description.setEditable(false);
+ description.setLineWrap(true);
+ description.setBackground(null);
+ description.setFont(f1);
+
+ scroll = new JScrollPane(
+ description,
+ JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+ JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
+ );
+ scroll.setBorder(null);
+
+ seePosts = new JButton("See posts");
+ seePosts.addActionListener(this);
+
+ setLayout(null);
+ add(accountIdLabel);
+ add(accountId);
+ add(displayNameLabel);
+ add(displayName);
+ add(followedLabel);
+ add(followed);
+ add(historyLabel);
+ add(history);
+ add(field1Label);
+ add(field1);
+ add(field2Label);
+ add(field2);
+ add(field3Label);
+ add(field3);
+ add(field4Label);
+ add(field4);
+ add(scroll);
+ add(seePosts);
+ setPreferredSize(new Dimension(640, 480));
+ }
+
+}
diff --git a/RepliesWindow.java b/RepliesWindow.java
old mode 100644
new mode 100755
index b248b0f..aa632e6
--- a/RepliesWindow.java
+++ b/RepliesWindow.java
@@ -54,6 +54,7 @@ RepliesWindow extends JFrame {
postSelected(Tree post)
{
postWindow.readEntity(post);
+ postWindow.setVisible(true);
}
private Tree
diff --git a/RequestListener.java b/RequestListener.java
old mode 100644
new mode 100755
diff --git a/RichTextPane.java b/RichTextPane.java
old mode 100644
new mode 100755
diff --git a/RudimentaryHTMLParser.java b/RudimentaryHTMLParser.java
old mode 100644
new mode 100755
index 665f4a6..8b77e14
--- a/RudimentaryHTMLParser.java
+++ b/RudimentaryHTMLParser.java
@@ -1,7 +1,7 @@
import cafe.biskuteri.hinoki.Tree;
import java.util.List;
-import java.util.ListIterator;
+import java.util.ArrayList;
import java.io.StringReader;
import java.io.Reader;
import java.io.IOException;
@@ -165,72 +165,48 @@ RudimentaryHTMLParser {
private static Tree
pass3(Tree docu)
{
- ListIterator> it = docu.children.listIterator();
- while (it.hasNext())
- {
- Tree node = it.next();
- if (!node.key.equals("text")) continue;
+ Tree returnee = new Tree();
- it.remove();
- StringBuilder t = new StringBuilder();
- StringBuilder e = new StringBuilder();
- boolean emoji = false;
- char pc = ' ';
- for (char c: node.value.toCharArray())
+ for (Tree node: docu)
+ {
+ if (!node.key.equals("text"))
+ {
+ returnee.add(node);
+ continue;
+ }
+
+ StringBuilder value = new StringBuilder();
+ for (String segment: whitespaceSplit(node.value))
{
- if (!emoji && c == ':')
+ 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);
+
+ Tree emoji = new Tree();
+ emoji.key = "emoji";
+ emoji.value = segment;
+ returnee.add(emoji);
+ }
+ else
{
- emoji = true;
- if (t.length() > 0) {
- Tree text = new Tree();
- text.key = "text";
- text.value = empty(t);
- it.add(text);
- }
- pc = c;
- continue;
- }
- if (emoji && c == ':')
- {
- emoji = false;
- if (e.length() > 0)
- {
- Tree shortcode = new Tree();
- shortcode.key = "emoji";
- shortcode.value = empty(e);
- it.add(shortcode);
- }
- pc = c;
- continue;
- }
- if (emoji && Character.isWhitespace(c))
- {
- emoji = false;
- if (e.length() > 0) {
- t.append(':');
- t.append(empty(e));
- }
- }
- if (emoji) e.append((char)c);
- else t.append((char)c);
- pc = c;
- }
- if (emoji)
- {
- emoji = false;
- if (e.length() > 0) {
- t.append(':');
- t.append(empty(e));
+ value.append(segment);
}
}
- if (t.length() > 0) {
+ if (value.length() > 0)
+ {
Tree text = new Tree();
text.key = "text";
- text.value = empty(t);
- it.add(text);
+ text.value = empty(value);
+ returnee.add(text);
}
}
- return docu;
+ return returnee;
}
private static String
@@ -241,6 +217,26 @@ 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));
+
+ return returnee;
+ }
+
// ---%-@-%---
public static void
diff --git a/TimelineWindow.java b/TimelineWindow.java
old mode 100644
new mode 100755
index 07e3ebb..7f01aaf
--- a/TimelineWindow.java
+++ b/TimelineWindow.java
@@ -396,14 +396,10 @@ implements ActionListener {
{
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
- Tree accountDetails = api.getAccountDetails();
- assert accountDetails != null;
- String id = accountDetails.get("id").value;
-
- TimelineWindow w = new TimelineWindow(primaire);
- w.showAuthorPosts(id);
- w.showLatestPage();
- w.setLocationRelativeTo(this);
+ Tree accountDetails = api.getAccountDetails();
+ ProfileWindow w = new ProfileWindow(primaire);
+ w.use(new Account(accountDetails));
+ w.setLocationByPlatform(true);
w.setVisible(true);
display.setCursor(null);
@@ -469,7 +465,7 @@ implements ActionListener {
return;
}
- String id = null;
+ Tree openee = null;
if (query.startsWith("@")) query = query.substring(1);
List