Added partially-finished profile window.
Implemented hierarchical HTML parser.
328
BasicHTMLParser.java
Normal file
@ -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<String>
|
||||||
|
parse(String html)
|
||||||
|
{
|
||||||
|
List<String> segments;
|
||||||
|
segments = distinguishTagsFromPcdata(html);
|
||||||
|
segments = evaluateHtmlEscapes(segments);
|
||||||
|
|
||||||
|
Tree<String> document;
|
||||||
|
document = toNodes(segments);
|
||||||
|
document = distinguishEmojisFromText(document);
|
||||||
|
document = hierarchise(document);
|
||||||
|
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - -%- -
|
||||||
|
|
||||||
|
private static List<String>
|
||||||
|
distinguishTagsFromPcdata(String html)
|
||||||
|
{
|
||||||
|
List<String> 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<String>
|
||||||
|
evaluateHtmlEscapes(List<String> strings)
|
||||||
|
{
|
||||||
|
List<String> 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<String>
|
||||||
|
toNodes(List<String> segments)
|
||||||
|
{
|
||||||
|
Tree<String> returnee = new Tree<String>();
|
||||||
|
|
||||||
|
for (String segment: segments)
|
||||||
|
{
|
||||||
|
boolean isTag = segment.startsWith("<");
|
||||||
|
Tree<String> node = new Tree<String>();
|
||||||
|
|
||||||
|
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<String> attr = new Tree<String>();
|
||||||
|
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<String> attr = new Tree<String>();
|
||||||
|
attr.key = key;
|
||||||
|
attr.value = value;
|
||||||
|
node.add(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
returnee.add(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnee;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Tree<String>
|
||||||
|
distinguishEmojisFromText(Tree<String> nodes)
|
||||||
|
{
|
||||||
|
Tree<String> returnee = new Tree<String>();
|
||||||
|
|
||||||
|
for (Tree<String> node: nodes)
|
||||||
|
{
|
||||||
|
if (!node.key.equals("text"))
|
||||||
|
{
|
||||||
|
returnee.add(node);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> 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<String> text = new Tree<String>();
|
||||||
|
text.key = "text";
|
||||||
|
text.value = empty(b);
|
||||||
|
returnee.add(text);
|
||||||
|
Tree<String> emoji = new Tree<String>();
|
||||||
|
emoji.key = "emoji";
|
||||||
|
emoji.value = segment;
|
||||||
|
returnee.add(emoji);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
b.append(segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (b.length() > 0)
|
||||||
|
{
|
||||||
|
Tree<String> text = new Tree<String>();
|
||||||
|
text.key = "text";
|
||||||
|
text.value = empty(b);
|
||||||
|
returnee.add(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnee;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Tree<String>
|
||||||
|
hierarchise(Tree<String> nodes)
|
||||||
|
{
|
||||||
|
Tree<String> root = new Tree<String>();
|
||||||
|
root.add(new Tree<>("attributes", null));
|
||||||
|
root.get(0).add(new Tree<>("html", null));
|
||||||
|
root.add(new Tree<>("children", null));
|
||||||
|
|
||||||
|
Deque<Tree<String>> parents = new LinkedList<>();
|
||||||
|
parents.push(root);
|
||||||
|
for (Tree<String> 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<String> 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<String> elem = new Tree<String>();
|
||||||
|
node.key = "attributes";
|
||||||
|
elem.add(node);
|
||||||
|
elem.add(new Tree<>("children", null));
|
||||||
|
|
||||||
|
parents.peek().get("children").add(elem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Tree<String> elem = new Tree<String>();
|
||||||
|
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<String>
|
||||||
|
distinguishWhitespaceFromText(String text)
|
||||||
|
{
|
||||||
|
List<String> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
0
ClipboardApi.java
Normal file → Executable file
0
ComposeWindow.java
Normal file → Executable file
0
ImageApi.java
Normal file → Executable file
0
ImageWindow.java
Normal file → Executable file
55
JKomasto.java
Normal file → Executable file
@ -436,7 +436,7 @@ Post {
|
|||||||
{
|
{
|
||||||
Tree<String> emoji = emojis.get(o);
|
Tree<String> emoji = emojis.get(o);
|
||||||
String[] mapping = emojiUrls[o] = new String[2];
|
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;
|
mapping[1] = emoji.get("url").value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -467,6 +467,22 @@ Account {
|
|||||||
public Image
|
public Image
|
||||||
avatar;
|
avatar;
|
||||||
|
|
||||||
|
public ZonedDateTime
|
||||||
|
creationDate;
|
||||||
|
|
||||||
|
public int
|
||||||
|
followedCount,
|
||||||
|
followerCount;
|
||||||
|
|
||||||
|
public int
|
||||||
|
postCount;
|
||||||
|
|
||||||
|
public String[][]
|
||||||
|
fields;
|
||||||
|
|
||||||
|
public String
|
||||||
|
description;
|
||||||
|
|
||||||
// ---%-@-%---
|
// ---%-@-%---
|
||||||
|
|
||||||
public void
|
public void
|
||||||
@ -501,6 +517,36 @@ Account {
|
|||||||
name = displayName.isEmpty() ? username : displayName;
|
name = displayName.isEmpty() ? username : displayName;
|
||||||
|
|
||||||
avatarUrl = entity.get("avatar").value;
|
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<String> fs = entity.get("fields");
|
||||||
|
fields = new String[fs.size()][];
|
||||||
|
for (int o = 0; o < fields.length; ++o)
|
||||||
|
{
|
||||||
|
Tree<String> 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
|
public
|
||||||
Attachment(Tree<String> entity)
|
Attachment(Tree<String> entity)
|
||||||
{
|
{
|
||||||
String u1 = entity.get("remote_url").value;
|
url = entity.get("remote_url").value;
|
||||||
String u2 = entity.get("text_url").value;
|
if (url == null) url = entity.get("url").value;
|
||||||
String u3 = entity.get("url").value;
|
|
||||||
|
|
||||||
url = u1 != null ? u1 : u2 != null ? u2 : u3;
|
id = entity.get("id").value;
|
||||||
type = entity.get("type").value;
|
type = entity.get("type").value;
|
||||||
description = entity.get("description").value;
|
description = entity.get("description").value;
|
||||||
}
|
}
|
||||||
|
0
KDE_Dialog_Appear.wav
Normal file → Executable file
0
LoginWindow.java
Normal file → Executable file
50
MastodonApi.java
Normal file → Executable file
@ -15,8 +15,10 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.File;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
@ -427,6 +429,52 @@ MastodonApi {
|
|||||||
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
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
|
public void
|
||||||
monitorTimeline(
|
monitorTimeline(
|
||||||
TimelineType type, ServerSideEventsListener handler)
|
TimelineType type, ServerSideEventsListener handler)
|
||||||
@ -448,7 +496,6 @@ MastodonApi {
|
|||||||
HttpURLConnection conn = cast(endpoint.openConnection());
|
HttpURLConnection conn = cast(endpoint.openConnection());
|
||||||
String s = "Bearer " + token;
|
String s = "Bearer " + token;
|
||||||
conn.setRequestProperty("Authorization", s);
|
conn.setRequestProperty("Authorization", s);
|
||||||
conn.setReadTimeout(500);
|
|
||||||
conn.connect();
|
conn.connect();
|
||||||
|
|
||||||
int code = conn.getResponseCode();
|
int code = conn.getResponseCode();
|
||||||
@ -461,6 +508,7 @@ MastodonApi {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conn.setReadTimeout(500);
|
||||||
Reader input = ireader(conn.getInputStream());
|
Reader input = ireader(conn.getInputStream());
|
||||||
BufferedReader br = new BufferedReader(input);
|
BufferedReader br = new BufferedReader(input);
|
||||||
Thread thread = Thread.currentThread();
|
Thread thread = Thread.currentThread();
|
||||||
|
0
NotificationsWindow.java
Normal file → Executable file
31
PostWindow.java
Normal file → Executable file
@ -125,11 +125,10 @@ PostWindow extends JFrame {
|
|||||||
public synchronized void
|
public synchronized void
|
||||||
openAuthorProfile()
|
openAuthorProfile()
|
||||||
{
|
{
|
||||||
TimelineWindow w = new TimelineWindow(primaire);
|
ProfileWindow w = new ProfileWindow(primaire);
|
||||||
w.showAuthorPosts(post.author.numId);
|
w.use(post.author);
|
||||||
w.showLatestPage();
|
w.setLocationRelativeTo(this);
|
||||||
w.setLocationRelativeTo(this);
|
w.setVisible(true);
|
||||||
w.setVisible(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void
|
public synchronized void
|
||||||
@ -251,6 +250,26 @@ PostWindow extends JFrame {
|
|||||||
display.setDeleteEnabled(false);
|
display.setDeleteEnabled(false);
|
||||||
display.paintImmediately(display.getBounds());
|
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() {
|
api.deletePost(post.id, new RequestListener() {
|
||||||
|
|
||||||
public void
|
public void
|
||||||
@ -409,6 +428,8 @@ implements ActionListener {
|
|||||||
public void
|
public void
|
||||||
setHtml(String n)
|
setHtml(String n)
|
||||||
{
|
{
|
||||||
|
BasicHTMLParser.parse(n);
|
||||||
|
|
||||||
RichTextPane.Builder b = new RichTextPane.Builder();
|
RichTextPane.Builder b = new RichTextPane.Builder();
|
||||||
Tree<String> nodes = RudimentaryHTMLParser.depthlessRead(n);
|
Tree<String> nodes = RudimentaryHTMLParser.depthlessRead(n);
|
||||||
for (Tree<String> node: nodes)
|
for (Tree<String> node: nodes)
|
||||||
|
427
ProfileWindow.java
Executable file
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
1
RepliesWindow.java
Normal file → Executable file
@ -54,6 +54,7 @@ RepliesWindow extends JFrame {
|
|||||||
postSelected(Tree<String> post)
|
postSelected(Tree<String> post)
|
||||||
{
|
{
|
||||||
postWindow.readEntity(post);
|
postWindow.readEntity(post);
|
||||||
|
postWindow.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tree<String>
|
private Tree<String>
|
||||||
|
0
RequestListener.java
Normal file → Executable file
0
RichTextPane.java
Normal file → Executable file
112
RudimentaryHTMLParser.java
Normal file → Executable file
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import cafe.biskuteri.hinoki.Tree;
|
import cafe.biskuteri.hinoki.Tree;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
import java.util.ArrayList;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -165,72 +165,48 @@ RudimentaryHTMLParser {
|
|||||||
private static Tree<String>
|
private static Tree<String>
|
||||||
pass3(Tree<String> docu)
|
pass3(Tree<String> docu)
|
||||||
{
|
{
|
||||||
ListIterator<Tree<String>> it = docu.children.listIterator();
|
Tree<String> returnee = new Tree<String>();
|
||||||
while (it.hasNext())
|
|
||||||
{
|
|
||||||
Tree<String> node = it.next();
|
|
||||||
if (!node.key.equals("text")) continue;
|
|
||||||
|
|
||||||
it.remove();
|
for (Tree<String> node: docu)
|
||||||
StringBuilder t = new StringBuilder();
|
{
|
||||||
StringBuilder e = new StringBuilder();
|
if (!node.key.equals("text"))
|
||||||
boolean emoji = false;
|
{
|
||||||
char pc = ' ';
|
returnee.add(node);
|
||||||
for (char c: node.value.toCharArray())
|
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<String> text = new Tree<String>();
|
||||||
|
text.key = "text";
|
||||||
|
text.value = empty(value);
|
||||||
|
returnee.add(text);
|
||||||
|
|
||||||
|
Tree<String> emoji = new Tree<String>();
|
||||||
|
emoji.key = "emoji";
|
||||||
|
emoji.value = segment;
|
||||||
|
returnee.add(emoji);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
emoji = true;
|
value.append(segment);
|
||||||
if (t.length() > 0) {
|
|
||||||
Tree<String> text = new Tree<String>();
|
|
||||||
text.key = "text";
|
|
||||||
text.value = empty(t);
|
|
||||||
it.add(text);
|
|
||||||
}
|
|
||||||
pc = c;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (emoji && c == ':')
|
|
||||||
{
|
|
||||||
emoji = false;
|
|
||||||
if (e.length() > 0)
|
|
||||||
{
|
|
||||||
Tree<String> shortcode = new Tree<String>();
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (t.length() > 0) {
|
if (value.length() > 0)
|
||||||
|
{
|
||||||
Tree<String> text = new Tree<String>();
|
Tree<String> text = new Tree<String>();
|
||||||
text.key = "text";
|
text.key = "text";
|
||||||
text.value = empty(t);
|
text.value = empty(value);
|
||||||
it.add(text);
|
returnee.add(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return docu;
|
return returnee;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String
|
private static String
|
||||||
@ -241,6 +217,26 @@ RudimentaryHTMLParser {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<String>
|
||||||
|
whitespaceSplit(String text)
|
||||||
|
{
|
||||||
|
List<String> 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
|
public static void
|
||||||
|
29
TimelineWindow.java
Normal file → Executable file
@ -396,14 +396,10 @@ implements ActionListener {
|
|||||||
{
|
{
|
||||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||||
|
|
||||||
Tree<String> accountDetails = api.getAccountDetails();
|
Tree<String> accountDetails = api.getAccountDetails();
|
||||||
assert accountDetails != null;
|
ProfileWindow w = new ProfileWindow(primaire);
|
||||||
String id = accountDetails.get("id").value;
|
w.use(new Account(accountDetails));
|
||||||
|
w.setLocationByPlatform(true);
|
||||||
TimelineWindow w = new TimelineWindow(primaire);
|
|
||||||
w.showAuthorPosts(id);
|
|
||||||
w.showLatestPage();
|
|
||||||
w.setLocationRelativeTo(this);
|
|
||||||
w.setVisible(true);
|
w.setVisible(true);
|
||||||
|
|
||||||
display.setCursor(null);
|
display.setCursor(null);
|
||||||
@ -469,7 +465,7 @@ implements ActionListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String id = null;
|
Tree<String> openee = null;
|
||||||
if (query.startsWith("@")) query = query.substring(1);
|
if (query.startsWith("@")) query = query.substring(1);
|
||||||
|
|
||||||
List<Object> message = new ArrayList<>();
|
List<Object> message = new ArrayList<>();
|
||||||
@ -480,7 +476,7 @@ implements ActionListener {
|
|||||||
String dname = account.get("display_name").value;
|
String dname = account.get("display_name").value;
|
||||||
String acct = account.get("acct").value;
|
String acct = account.get("acct").value;
|
||||||
if (query.equals(acct)) {
|
if (query.equals(acct)) {
|
||||||
id = account.get("id").value;
|
openee = account;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
JRadioButton b = new JRadioButton();
|
JRadioButton b = new JRadioButton();
|
||||||
@ -488,7 +484,7 @@ implements ActionListener {
|
|||||||
selGroup.add(b);
|
selGroup.add(b);
|
||||||
message.add(b);
|
message.add(b);
|
||||||
}
|
}
|
||||||
if (id == null)
|
if (openee == null)
|
||||||
{
|
{
|
||||||
int response = JOptionPane.showConfirmDialog(
|
int response = JOptionPane.showConfirmDialog(
|
||||||
this,
|
this,
|
||||||
@ -502,11 +498,11 @@ implements ActionListener {
|
|||||||
JRadioButton b = (JRadioButton)message.get(o);
|
JRadioButton b = (JRadioButton)message.get(o);
|
||||||
if (selGroup.isSelected(b.getModel()))
|
if (selGroup.isSelected(b.getModel()))
|
||||||
{
|
{
|
||||||
id = handler.json.get(o - 1).get("id").value;
|
openee = handler.json.get(o - 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (id == null) return;
|
if (openee == null) return;
|
||||||
/*
|
/*
|
||||||
* It seems like this can happen if someone
|
* It seems like this can happen if someone
|
||||||
* presses escape out of the confirm dialog.
|
* presses escape out of the confirm dialog.
|
||||||
@ -514,10 +510,9 @@ implements ActionListener {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineWindow w = new TimelineWindow(primaire);
|
ProfileWindow w = new ProfileWindow(primaire);
|
||||||
w.showAuthorPosts(id);
|
w.use(new Account(openee));
|
||||||
w.showLatestPage();
|
w.setLocationByPlatform(true);
|
||||||
w.setLocationRelativeTo(this);
|
|
||||||
w.setVisible(true);
|
w.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
4
TwoToggleButton.java
Normal file → Executable file
@ -127,7 +127,7 @@ implements KeyListener, MouseListener, FocusListener {
|
|||||||
|
|
||||||
|
|
||||||
public void
|
public void
|
||||||
mouseClicked(MouseEvent eM)
|
mousePressed(MouseEvent eM)
|
||||||
{
|
{
|
||||||
switch (eM.getButton()) {
|
switch (eM.getButton()) {
|
||||||
case MouseEvent.BUTTON1: togglePrimary(); break;
|
case MouseEvent.BUTTON1: togglePrimary(); break;
|
||||||
@ -154,7 +154,7 @@ implements KeyListener, MouseListener, FocusListener {
|
|||||||
|
|
||||||
|
|
||||||
public void
|
public void
|
||||||
mousePressed(MouseEvent eM) { }
|
mouseClicked(MouseEvent eM) { }
|
||||||
|
|
||||||
public void
|
public void
|
||||||
mouseReleased(MouseEvent eM) { }
|
mouseReleased(MouseEvent eM) { }
|
||||||
|
47
WindowUpdater.java
Normal file → Executable file
@ -36,7 +36,7 @@ WindowUpdater {
|
|||||||
|
|
||||||
// ---%-@-%---
|
// ---%-@-%---
|
||||||
|
|
||||||
public void
|
public synchronized void
|
||||||
add(TimelineWindow updatee)
|
add(TimelineWindow updatee)
|
||||||
{
|
{
|
||||||
if (!timelineWindows.contains(updatee))
|
if (!timelineWindows.contains(updatee))
|
||||||
@ -46,7 +46,7 @@ WindowUpdater {
|
|||||||
userConn.reevaluate();
|
userConn.reevaluate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void
|
public synchronized void
|
||||||
add(NotificationsWindow updatee)
|
add(NotificationsWindow updatee)
|
||||||
{
|
{
|
||||||
if (!notificationWindows.contains(updatee))
|
if (!notificationWindows.contains(updatee))
|
||||||
@ -55,7 +55,7 @@ WindowUpdater {
|
|||||||
userConn.reevaluate();
|
userConn.reevaluate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void
|
public synchronized void
|
||||||
remove(TimelineWindow updatee)
|
remove(TimelineWindow updatee)
|
||||||
{
|
{
|
||||||
timelineWindows.remove(updatee);
|
timelineWindows.remove(updatee);
|
||||||
@ -63,7 +63,7 @@ WindowUpdater {
|
|||||||
userConn.reevaluate();
|
userConn.reevaluate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void
|
public synchronized void
|
||||||
remove(NotificationsWindow updatee)
|
remove(NotificationsWindow updatee)
|
||||||
{
|
{
|
||||||
notificationWindows.remove(updatee);
|
notificationWindows.remove(updatee);
|
||||||
@ -136,11 +136,11 @@ WindowUpdater {
|
|||||||
{
|
{
|
||||||
boolean hasUpdatee = false;
|
boolean hasUpdatee = false;
|
||||||
|
|
||||||
for (NotificationsWindow updatee: notificationWindows)
|
for (NotificationsWindow w: notificationWindows)
|
||||||
if (responsibleFor(updatee)) hasUpdatee = true;
|
if (responsibleFor(w)) hasUpdatee = true;
|
||||||
|
|
||||||
for (TimelineWindow updatee: timelineWindows)
|
for (TimelineWindow w: timelineWindows)
|
||||||
if (responsibleFor(updatee)) hasUpdatee = true;
|
if (responsibleFor(w)) hasUpdatee = true;
|
||||||
|
|
||||||
if (!hasUpdatee && thread != null) stop();
|
if (!hasUpdatee && thread != null) stop();
|
||||||
if (hasUpdatee && thread == null) start();
|
if (hasUpdatee && thread == null) start();
|
||||||
@ -161,6 +161,12 @@ WindowUpdater {
|
|||||||
// monitorTimeline should not return until
|
// monitorTimeline should not return until
|
||||||
// the connection is closed, or this thread
|
// the connection is closed, or this thread
|
||||||
// is interrupted.
|
// is interrupted.
|
||||||
|
|
||||||
|
if (thread == Thread.currentThread()) thread = null;
|
||||||
|
/*
|
||||||
|
* This isn't thread safe. But I'd like the
|
||||||
|
* restart after sleep mode, so.
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public void
|
public void
|
||||||
@ -204,23 +210,22 @@ WindowUpdater {
|
|||||||
notificationSound.start();
|
notificationSound.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (TimelineWindow updatee: timelineWindows)
|
synchronized (WindowUpdater.this)
|
||||||
{
|
{
|
||||||
if (!responsibleFor(updatee)) continue;
|
for (TimelineWindow w: timelineWindows)
|
||||||
updatee.refresh();
|
{
|
||||||
/*
|
if (!responsibleFor(w)) continue;
|
||||||
* (悪) Note that we're in a separate thread,
|
w.refresh();
|
||||||
* and our windows aren't thread-safe. We could
|
}
|
||||||
* probably make them a bit bananas asking
|
|
||||||
* for a refresh while they're in the middle
|
|
||||||
* of one. Could we add mutexes?
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (NotificationsWindow updatee: notificationWindows)
|
synchronized (WindowUpdater.this)
|
||||||
{
|
{
|
||||||
if (!responsibleFor(updatee)) continue;
|
for (NotificationsWindow w: notificationWindows)
|
||||||
updatee.refresh();
|
{
|
||||||
|
if (!responsibleFor(w)) continue;
|
||||||
|
w.refresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
0
graphics/Federated.xcf
Normal file → Executable file
0
graphics/Flags.xcf
Normal file → Executable file
0
graphics/Hourglass.xcf
Normal file → Executable file
0
graphics/boostToggled.png
Normal file → Executable file
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
0
graphics/boostUntoggled.png
Normal file → Executable file
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
0
graphics/button.png
Normal file → Executable file
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
0
graphics/disabledOverlay.png
Normal file → Executable file
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
0
graphics/favouriteToggled.png
Normal file → Executable file
Before Width: | Height: | Size: 353 B After Width: | Height: | Size: 353 B |
0
graphics/favouriteUntoggled.png
Normal file → Executable file
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
0
graphics/federated.png
Normal file → Executable file
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
0
graphics/miscToggled.png
Normal file → Executable file
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
0
graphics/miscUntoggled.png
Normal file → Executable file
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
0
graphics/ref1.png
Normal file → Executable file
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
0
graphics/replyToggled.png
Normal file → Executable file
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
0
graphics/replyUntoggled.png
Normal file → Executable file
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
0
graphics/selectedOverlay.png
Normal file → Executable file
Before Width: | Height: | Size: 313 B After Width: | Height: | Size: 313 B |
0
graphics/test1.png
Normal file → Executable file
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
0
graphics/test2.png
Normal file → Executable file
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
0
graphics/test3.png
Normal file → Executable file
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
0
graphics/test4.png
Normal file → Executable file
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |