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);
|
||||
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<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
|
||||
Attachment(Tree<String> 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;
|
||||
}
|
||||
|
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.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();
|
||||
|
0
NotificationsWindow.java
Normal file → Executable file
31
PostWindow.java
Normal file → Executable file
@ -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<String> nodes = RudimentaryHTMLParser.depthlessRead(n);
|
||||
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)
|
||||
{
|
||||
postWindow.readEntity(post);
|
||||
postWindow.setVisible(true);
|
||||
}
|
||||
|
||||
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 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<String>
|
||||
pass3(Tree<String> docu)
|
||||
{
|
||||
ListIterator<Tree<String>> it = docu.children.listIterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Tree<String> node = it.next();
|
||||
if (!node.key.equals("text")) continue;
|
||||
Tree<String> returnee = new Tree<String>();
|
||||
|
||||
it.remove();
|
||||
StringBuilder t = new StringBuilder();
|
||||
StringBuilder e = new StringBuilder();
|
||||
boolean emoji = false;
|
||||
char pc = ' ';
|
||||
for (char c: node.value.toCharArray())
|
||||
for (Tree<String> 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<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;
|
||||
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));
|
||||
value.append(segment);
|
||||
}
|
||||
}
|
||||
if (t.length() > 0) {
|
||||
if (value.length() > 0)
|
||||
{
|
||||
Tree<String> text = new Tree<String>();
|
||||
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<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
|
||||
|
29
TimelineWindow.java
Normal file → Executable file
@ -396,14 +396,10 @@ implements ActionListener {
|
||||
{
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
|
||||
Tree<String> 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<String> 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<String> openee = null;
|
||||
if (query.startsWith("@")) query = query.substring(1);
|
||||
|
||||
List<Object> message = new ArrayList<>();
|
||||
@ -480,7 +476,7 @@ implements ActionListener {
|
||||
String dname = account.get("display_name").value;
|
||||
String acct = account.get("acct").value;
|
||||
if (query.equals(acct)) {
|
||||
id = account.get("id").value;
|
||||
openee = account;
|
||||
break;
|
||||
}
|
||||
JRadioButton b = new JRadioButton();
|
||||
@ -488,7 +484,7 @@ implements ActionListener {
|
||||
selGroup.add(b);
|
||||
message.add(b);
|
||||
}
|
||||
if (id == null)
|
||||
if (openee == null)
|
||||
{
|
||||
int response = JOptionPane.showConfirmDialog(
|
||||
this,
|
||||
@ -502,11 +498,11 @@ implements ActionListener {
|
||||
JRadioButton b = (JRadioButton)message.get(o);
|
||||
if (selGroup.isSelected(b.getModel()))
|
||||
{
|
||||
id = handler.json.get(o - 1).get("id").value;
|
||||
openee = handler.json.get(o - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (id == null) return;
|
||||
if (openee == null) return;
|
||||
/*
|
||||
* It seems like this can happen if someone
|
||||
* presses escape out of the confirm dialog.
|
||||
@ -514,10 +510,9 @@ implements ActionListener {
|
||||
*/
|
||||
}
|
||||
|
||||
TimelineWindow w = new TimelineWindow(primaire);
|
||||
w.showAuthorPosts(id);
|
||||
w.showLatestPage();
|
||||
w.setLocationRelativeTo(this);
|
||||
ProfileWindow w = new ProfileWindow(primaire);
|
||||
w.use(new Account(openee));
|
||||
w.setLocationByPlatform(true);
|
||||
w.setVisible(true);
|
||||
}
|
||||
|
||||
|
4
TwoToggleButton.java
Normal file → Executable file
@ -127,7 +127,7 @@ implements KeyListener, MouseListener, FocusListener {
|
||||
|
||||
|
||||
public void
|
||||
mouseClicked(MouseEvent eM)
|
||||
mousePressed(MouseEvent eM)
|
||||
{
|
||||
switch (eM.getButton()) {
|
||||
case MouseEvent.BUTTON1: togglePrimary(); break;
|
||||
@ -154,7 +154,7 @@ implements KeyListener, MouseListener, FocusListener {
|
||||
|
||||
|
||||
public void
|
||||
mousePressed(MouseEvent eM) { }
|
||||
mouseClicked(MouseEvent eM) { }
|
||||
|
||||
public void
|
||||
mouseReleased(MouseEvent eM) { }
|
||||
|
47
WindowUpdater.java
Normal file → Executable file
@ -36,7 +36,7 @@ WindowUpdater {
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public void
|
||||
public synchronized void
|
||||
add(TimelineWindow updatee)
|
||||
{
|
||||
if (!timelineWindows.contains(updatee))
|
||||
@ -46,7 +46,7 @@ WindowUpdater {
|
||||
userConn.reevaluate();
|
||||
}
|
||||
|
||||
public void
|
||||
public synchronized void
|
||||
add(NotificationsWindow updatee)
|
||||
{
|
||||
if (!notificationWindows.contains(updatee))
|
||||
@ -55,7 +55,7 @@ WindowUpdater {
|
||||
userConn.reevaluate();
|
||||
}
|
||||
|
||||
public void
|
||||
public synchronized void
|
||||
remove(TimelineWindow updatee)
|
||||
{
|
||||
timelineWindows.remove(updatee);
|
||||
@ -63,7 +63,7 @@ WindowUpdater {
|
||||
userConn.reevaluate();
|
||||
}
|
||||
|
||||
public void
|
||||
public synchronized void
|
||||
remove(NotificationsWindow updatee)
|
||||
{
|
||||
notificationWindows.remove(updatee);
|
||||
@ -136,11 +136,11 @@ WindowUpdater {
|
||||
{
|
||||
boolean hasUpdatee = false;
|
||||
|
||||
for (NotificationsWindow updatee: notificationWindows)
|
||||
if (responsibleFor(updatee)) hasUpdatee = true;
|
||||
for (NotificationsWindow w: notificationWindows)
|
||||
if (responsibleFor(w)) hasUpdatee = true;
|
||||
|
||||
for (TimelineWindow updatee: timelineWindows)
|
||||
if (responsibleFor(updatee)) hasUpdatee = true;
|
||||
for (TimelineWindow w: timelineWindows)
|
||||
if (responsibleFor(w)) hasUpdatee = true;
|
||||
|
||||
if (!hasUpdatee && thread != null) stop();
|
||||
if (hasUpdatee && thread == null) start();
|
||||
@ -161,6 +161,12 @@ WindowUpdater {
|
||||
// monitorTimeline should not return until
|
||||
// the connection is closed, or this thread
|
||||
// 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
|
||||
@ -204,23 +210,22 @@ WindowUpdater {
|
||||
notificationSound.start();
|
||||
}
|
||||
|
||||
for (TimelineWindow updatee: timelineWindows)
|
||||
synchronized (WindowUpdater.this)
|
||||
{
|
||||
if (!responsibleFor(updatee)) continue;
|
||||
updatee.refresh();
|
||||
/*
|
||||
* (悪) Note that we're in a separate thread,
|
||||
* 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 (TimelineWindow w: timelineWindows)
|
||||
{
|
||||
if (!responsibleFor(w)) continue;
|
||||
w.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
for (NotificationsWindow updatee: notificationWindows)
|
||||
synchronized (WindowUpdater.this)
|
||||
{
|
||||
if (!responsibleFor(updatee)) continue;
|
||||
updatee.refresh();
|
||||
for (NotificationsWindow w: notificationWindows)
|
||||
{
|
||||
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 |