Added buggy rich text pane.
Added rendering of emojis. Added run script.
0
ComposeWindow.java
Normal file → Executable file
0
ImageApi.java
Normal file → Executable file
6
JKomasto.java
Normal file → Executable file
@ -145,7 +145,8 @@ Post {
|
||||
|
||||
public String
|
||||
text,
|
||||
contentWarning;
|
||||
contentWarning,
|
||||
html;
|
||||
|
||||
public String
|
||||
authorId, authorName;
|
||||
@ -174,6 +175,9 @@ Post {
|
||||
public Attachment[]
|
||||
attachments;
|
||||
|
||||
public String[][]
|
||||
emojiUrls;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
0
LoginWindow.java
Normal file → Executable file
236
PostWindow.java
Normal file → Executable file
@ -26,6 +26,8 @@ import java.awt.event.ActionListener;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.net.URL;
|
||||
import java.net.MalformedURLException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.io.IOException;
|
||||
@ -96,7 +98,9 @@ implements ActionListener {
|
||||
postDisplay.setAuthorAvatar(post.authorAvatar);
|
||||
postDisplay.setDate(DATE_FORMAT.format(post.date));
|
||||
postDisplay.setTime(TIME_FORMAT.format(post.date));
|
||||
postDisplay.setEmojiUrls(post.emojiUrls);
|
||||
postDisplay.setText(post.text);
|
||||
postDisplay.setHtml(post.html);
|
||||
postDisplay.setFavourited(post.favourited);
|
||||
postDisplay.setBoosted(post.boosted);
|
||||
postDisplay.setMediaPreview(
|
||||
@ -275,6 +279,7 @@ implements ActionListener {
|
||||
|
||||
Post samplePost = new Post();
|
||||
samplePost.text = "This is a sample post.";
|
||||
samplePost.html = "";
|
||||
samplePost.authorId = "snowyfox@biskuteri.cafe";
|
||||
samplePost.authorName = "snowyfox";
|
||||
samplePost.date = ZonedDateTime.now();
|
||||
@ -283,6 +288,7 @@ implements ActionListener {
|
||||
samplePost.boosted = false;
|
||||
samplePost.favourited = true;
|
||||
samplePost.attachments = new Attachment[0];
|
||||
samplePost.emojiUrls = new String[0][];
|
||||
showPost(samplePost);
|
||||
|
||||
setContentPane(postDisplay);
|
||||
@ -299,11 +305,20 @@ implements ActionListener {
|
||||
private PostWindow
|
||||
primaire;
|
||||
|
||||
private String
|
||||
authorName, authorId, date, time, text;
|
||||
|
||||
// - -%- -
|
||||
|
||||
private List<RichTextPane.Segment>
|
||||
authorNameOr, bodyOr;
|
||||
|
||||
private RichTextPane
|
||||
authorName, body;
|
||||
|
||||
private JLabel
|
||||
authorId, time, date;
|
||||
|
||||
private String[][]
|
||||
emojiUrls;
|
||||
|
||||
private TwoToggleButton
|
||||
favouriteBoost,
|
||||
replyMisc,
|
||||
@ -316,22 +331,68 @@ implements ActionListener {
|
||||
// ---%-@-%---
|
||||
|
||||
public void
|
||||
setAuthorName(String n) { authorName = n; }
|
||||
setAuthorName(String n)
|
||||
{
|
||||
authorNameOr = new RichTextPane.Builder().text(n).finish();
|
||||
}
|
||||
|
||||
public void
|
||||
setAuthorId(String n) { authorId = n; }
|
||||
setAuthorId(String n) { authorId.setText(n); }
|
||||
|
||||
public void
|
||||
setAuthorAvatar(Image n) { profile.setImage(n); }
|
||||
|
||||
public void
|
||||
setDate(String n) { date = n; }
|
||||
setDate(String n) { date.setText(n); }
|
||||
|
||||
public void
|
||||
setTime(String n) { time = n; }
|
||||
setTime(String n) { time.setText(n); }
|
||||
|
||||
public void
|
||||
setText(String n) { text = n; }
|
||||
setText(String n) { }
|
||||
|
||||
public void
|
||||
setEmojiUrls(String[][] n) { emojiUrls = n; }
|
||||
|
||||
public void
|
||||
setHtml(String n)
|
||||
{
|
||||
RichTextPane.Builder b = new RichTextPane.Builder();
|
||||
Tree<String> nodes = RudimentaryHTMLParser.depthlessRead(n);
|
||||
for (Tree<String> node: nodes)
|
||||
{
|
||||
if (node.key.equals("tag"))
|
||||
{
|
||||
String tagName = node.get(0).key;
|
||||
if (tagName.equals("br"))
|
||||
b = b.spacer("\n");
|
||||
if (tagName.equals("/p"))
|
||||
b = b.spacer("\n").spacer("\n");
|
||||
if (tagName.equals("a"))
|
||||
b = b.link(node.get("href").value, null).spacer(" ");
|
||||
}
|
||||
if (node.key.equals("text"))
|
||||
{
|
||||
for (String word: node.value.split(" "))
|
||||
b = b.text(word).spacer(" ");
|
||||
}
|
||||
if (node.key.equals("emoji"))
|
||||
{
|
||||
String shortcode = node.value;
|
||||
String url = null;
|
||||
for (String[] entry: emojiUrls)
|
||||
if (entry[0].equals(shortcode)) url = entry[1];
|
||||
try {
|
||||
ImageIcon image = new ImageIcon(new URL(url));
|
||||
b = b.image(image, node.value);
|
||||
}
|
||||
catch (MalformedURLException eMu) {
|
||||
b = b.text(":×:");
|
||||
}
|
||||
}
|
||||
}
|
||||
bodyOr = b.finish();
|
||||
}
|
||||
|
||||
public void
|
||||
setFavourited(boolean a)
|
||||
@ -424,76 +485,12 @@ implements ActionListener {
|
||||
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
|
||||
);
|
||||
|
||||
Font f1 = new Font("IPAGothic", Font.PLAIN, 16);
|
||||
Font f2 = new Font("IPAGothic", Font.PLAIN, 14);
|
||||
FontMetrics fm1 = g.getFontMetrics(f1);
|
||||
FontMetrics fm2 = g.getFontMetrics(f2);
|
||||
|
||||
int x1 = 60;
|
||||
int x4 = getWidth() - 10;
|
||||
int x2 = x4 - fm2.stringWidth(date);
|
||||
int x3 = x4 - fm1.stringWidth(time);
|
||||
int y1 = 10;
|
||||
int y2 = y1 + fm2.getHeight();
|
||||
int y3 = y2 + fm1.getHeight();
|
||||
int y4 = y3 + 8;
|
||||
|
||||
Shape defaultClip = g.getClip();
|
||||
g.setClip(x1, y1, Math.min(x2, x3) - 8 - x1, y4 - y1);
|
||||
// First time I've used this method..
|
||||
// Cause, clearRect is not working.
|
||||
g.setFont(f2);
|
||||
g.drawString(authorId, x1, y2);
|
||||
g.setFont(f1);
|
||||
g.drawString(authorName, x1, y3);
|
||||
g.setClip(defaultClip);
|
||||
|
||||
g.setFont(f2);
|
||||
g.drawString(date, x2, y2);
|
||||
g.setFont(f1);
|
||||
g.drawString(time, x3, y3);
|
||||
|
||||
int y = y4;
|
||||
for (String line: split(text, 40)) {
|
||||
y += fm1.getHeight();
|
||||
g.drawString(line, x1, y);
|
||||
}
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
private static List<String>
|
||||
split(String string, int lineLength)
|
||||
{
|
||||
List<String> returnee = new ArrayList<>();
|
||||
|
||||
StringBuilder line = new StringBuilder();
|
||||
for (String word: string.split(" "))
|
||||
{
|
||||
if (word.length() >= lineLength) {
|
||||
word = word.substring(0, lineLength - 4) + "...";
|
||||
}
|
||||
if (word.matches("\n")) {
|
||||
returnee.add(empty(line));
|
||||
continue;
|
||||
}
|
||||
if (line.length() + word.length() > lineLength) {
|
||||
returnee.add(empty(line));
|
||||
}
|
||||
line.append(word);
|
||||
line.append(" ");
|
||||
}
|
||||
returnee.add(empty(line));
|
||||
|
||||
return returnee;
|
||||
}
|
||||
|
||||
private static String
|
||||
empty(StringBuilder b)
|
||||
{
|
||||
String s = b.toString();
|
||||
b.delete(0, b.length());
|
||||
return s;
|
||||
int w1 = authorName.getWidth();
|
||||
int w2 = body.getWidth();
|
||||
FontMetrics fm1 = getFontMetrics(authorName.getFont());
|
||||
FontMetrics fm2 = getFontMetrics(body.getFont());
|
||||
authorName.setText(RichTextPane.layout(authorNameOr, fm1, w1));
|
||||
body.setText(RichTextPane.layout(bodyOr, fm2, w2));
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
@ -502,48 +499,75 @@ implements ActionListener {
|
||||
{
|
||||
this.primaire = primaire;
|
||||
|
||||
authorName = authorId = time = text = "";
|
||||
emojiUrls = new String[0][];
|
||||
|
||||
Dimension buttonSize = new Dimension(20, 40);
|
||||
Border b = BorderFactory.createEmptyBorder(10, 10, 10, 10);
|
||||
Font f1 = new Font("IPAGothic", Font.PLAIN, 16);
|
||||
Font f2 = new Font("IPAGothic", Font.PLAIN, 13);
|
||||
|
||||
profile = new RoundButton();
|
||||
profile.addActionListener(this);
|
||||
|
||||
favouriteBoost = new TwoToggleButton("favourite", "boost");
|
||||
favouriteBoost.addActionListener(this);
|
||||
|
||||
replyMisc = new TwoToggleButton("reply", "misc");
|
||||
replyMisc.addActionListener(this);
|
||||
|
||||
nextPrev = new TwoToggleButton("next", "prev");
|
||||
nextPrev.addActionListener(this);
|
||||
|
||||
media = new RoundButton();
|
||||
//media.setPreferredSize(buttonSize);
|
||||
profile.addActionListener(this);
|
||||
favouriteBoost.addActionListener(this);
|
||||
replyMisc.addActionListener(this);
|
||||
nextPrev.addActionListener(this);
|
||||
media.addActionListener(this);
|
||||
|
||||
Box ibuttons = Box.createVerticalBox();
|
||||
ibuttons.setOpaque(false);
|
||||
ibuttons.add(profile);
|
||||
ibuttons.add(Box.createVerticalStrut(8));
|
||||
ibuttons.add(favouriteBoost);
|
||||
ibuttons.add(Box.createVerticalStrut(8));
|
||||
ibuttons.add(replyMisc);
|
||||
ibuttons.add(Box.createVerticalStrut(8));
|
||||
ibuttons.add(nextPrev);
|
||||
ibuttons.add(Box.createVerticalStrut(8));
|
||||
ibuttons.add(media);
|
||||
ibuttons.setMaximumSize(ibuttons.getPreferredSize());
|
||||
Box buttons = Box.createVerticalBox();
|
||||
buttons.setOpaque(false);
|
||||
buttons.add(ibuttons);
|
||||
buttons.setBorder(b);
|
||||
buttons.add(profile);
|
||||
buttons.add(Box.createVerticalStrut(8));
|
||||
buttons.add(favouriteBoost);
|
||||
buttons.add(Box.createVerticalStrut(8));
|
||||
buttons.add(replyMisc);
|
||||
buttons.add(Box.createVerticalStrut(8));
|
||||
buttons.add(nextPrev);
|
||||
buttons.add(Box.createVerticalStrut(8));
|
||||
buttons.add(media);
|
||||
buttons.setMaximumSize(buttons.getPreferredSize());
|
||||
Box left = Box.createVerticalBox();
|
||||
left.setOpaque(false);
|
||||
left.add(buttons);
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
add(buttons, BorderLayout.WEST);
|
||||
authorId = new JLabel();
|
||||
authorName = new RichTextPane();
|
||||
time = new JLabel();
|
||||
date = new JLabel();
|
||||
authorId.setFont(f2);
|
||||
date.setFont(f2);
|
||||
authorName.setFont(f1);
|
||||
time.setFont(f1);
|
||||
|
||||
setFont(getFont().deriveFont(14f));
|
||||
JPanel top1 = new JPanel();
|
||||
top1.setLayout(new BorderLayout(8, 0));
|
||||
top1.add(authorId);
|
||||
top1.add(date, BorderLayout.EAST);
|
||||
JPanel top2 = new JPanel();
|
||||
top2.setLayout(new BorderLayout(8, 0));
|
||||
top2.add(authorName);
|
||||
top2.add(time, BorderLayout.EAST);
|
||||
Box top = Box.createVerticalBox();
|
||||
top.add(top1);
|
||||
top.add(Box.createVerticalStrut(2));
|
||||
top.add(top2);
|
||||
|
||||
body = new RichTextPane();
|
||||
body.setFont(getFont().deriveFont(14f));
|
||||
|
||||
JPanel centre = new JPanel();
|
||||
centre.setOpaque(false);
|
||||
centre.setLayout(new BorderLayout(0, 8));
|
||||
centre.add(top, BorderLayout.NORTH);
|
||||
centre.add(body);
|
||||
|
||||
setLayout(new BorderLayout(8, 0));
|
||||
add(left, BorderLayout.WEST);
|
||||
add(centre);
|
||||
|
||||
setBorder(b);
|
||||
}
|
||||
|
||||
}
|
||||
|
251
RichTextPane.java
Normal file
@ -0,0 +1,251 @@
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.ImageIcon;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Image;
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.ListIterator;
|
||||
|
||||
class
|
||||
RichTextPane extends JComponent {
|
||||
|
||||
private List<Segment>
|
||||
text;
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public void
|
||||
setText(List<Segment> text) { this.text = text; }
|
||||
|
||||
// - -%- -
|
||||
|
||||
protected void
|
||||
paintComponent(Graphics g)
|
||||
{
|
||||
g.setFont(getFont());
|
||||
FontMetrics fm = g.getFontMetrics(getFont());
|
||||
g.clearRect(0, 0, getWidth(), getHeight());
|
||||
|
||||
for (Segment segment: text)
|
||||
{
|
||||
if (segment.image != null) {
|
||||
int ow = segment.image.getIconWidth();
|
||||
int oh = segment.image.getIconHeight();
|
||||
int h = fm.getHeight();
|
||||
int w = h * ow / oh;
|
||||
int x = segment.x, y = segment.y;
|
||||
Image img = segment.image.getImage();
|
||||
g.drawImage(img, x, y - h, w, h, this);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (segment.link != null) g.setColor(Color.BLUE);
|
||||
g.drawString(segment.text, segment.x, segment.y);
|
||||
g.setColor(getForeground());
|
||||
}
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
public static List<Segment>
|
||||
layout(List<Segment> text, FontMetrics fm, int width)
|
||||
{
|
||||
if (width < fm.getMaxAdvance()) return new LinkedList<>();
|
||||
|
||||
List<Segment> copy = new LinkedList<>();
|
||||
for (Segment segment: text) copy.add(segment.clone());
|
||||
text = copy;
|
||||
ListIterator<Segment> cursor = text.listIterator();
|
||||
int dy = fm.getHeight(), x = 0, y = dy;
|
||||
while (cursor.hasNext())
|
||||
{
|
||||
Segment curr = cursor.next();
|
||||
|
||||
int dx;
|
||||
if (curr.image != null) {
|
||||
int ow = curr.image.getIconWidth();
|
||||
int oh = curr.image.getIconHeight();
|
||||
dx = dy * ow / oh;
|
||||
}
|
||||
else if (curr.text != null) {
|
||||
dx = fm.stringWidth(curr.text);
|
||||
}
|
||||
else if (curr.link != null) {
|
||||
curr.text = curr.link;
|
||||
dx = fm.stringWidth(curr.link);
|
||||
}
|
||||
else {
|
||||
assert false;
|
||||
dx = 0;
|
||||
}
|
||||
|
||||
// If can readily fit, just do so.
|
||||
if (x + dx < width || curr.spacer) {
|
||||
curr.x = x;
|
||||
curr.y = y;
|
||||
x += dx;
|
||||
if (curr.spacer && curr.text.equals("\n")) {
|
||||
y += dy;
|
||||
x = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If image, or text that isn't long, just break.
|
||||
if (curr.image != null || dx < width / 3) {
|
||||
curr.x = 0;
|
||||
curr.y = y += dy;
|
||||
x = dx;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Greedily split string to fit into line.
|
||||
int offset = splitForFit(curr.text, fm, width - x);
|
||||
if (offset == 0) {
|
||||
cursor.add(curr); cursor.previous();
|
||||
y += dy;
|
||||
x = dx;
|
||||
continue;
|
||||
}
|
||||
Segment next = new Segment();
|
||||
next.text = curr.text.substring(offset);
|
||||
next.link = curr.link;
|
||||
cursor.add(next); cursor.previous();
|
||||
curr.text = curr.text.substring(0, offset);
|
||||
curr.x = x;
|
||||
curr.y = y;
|
||||
y += dy;
|
||||
x = 0;
|
||||
continue;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
private static int
|
||||
splitForFit(String s, FontMetrics fm, int width)
|
||||
{
|
||||
int max = 0;
|
||||
for (int o = 1; o < s.length(); max = o++)
|
||||
if (fm.stringWidth(s.substring(0, o)) > width) break;
|
||||
return max;
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public static class
|
||||
Segment {
|
||||
|
||||
public ImageIcon
|
||||
image;
|
||||
|
||||
public String
|
||||
link;
|
||||
|
||||
public String
|
||||
text;
|
||||
|
||||
public boolean
|
||||
spacer;
|
||||
|
||||
public int
|
||||
x, y;
|
||||
|
||||
// -=%=-
|
||||
|
||||
public String
|
||||
toString()
|
||||
{
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append(getClass().getName() + "[");
|
||||
b.append("image=" + image);
|
||||
b.append(",link=" + link);
|
||||
b.append(",text=" + text);
|
||||
b.append(",x=" + x);
|
||||
b.append(",y=" + y);
|
||||
b.append("]");
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
public Segment
|
||||
clone()
|
||||
{
|
||||
Segment segment = new Segment();
|
||||
segment.image = this.image;
|
||||
segment.link = this.link;
|
||||
segment.text = this.text;
|
||||
segment.spacer = this.spacer;
|
||||
segment.x = this.x;
|
||||
segment.y = this.y;
|
||||
return segment;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class
|
||||
Builder {
|
||||
|
||||
private List<Segment>
|
||||
returnee;
|
||||
|
||||
// -=%=-
|
||||
|
||||
public
|
||||
Builder() { returnee = new LinkedList<>(); }
|
||||
|
||||
public Builder
|
||||
image(ImageIcon image, String text)
|
||||
{
|
||||
Segment segment = new Segment();
|
||||
segment.image = image;
|
||||
segment.text = text;
|
||||
returnee.add(segment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder
|
||||
link(String link, String text)
|
||||
{
|
||||
Segment segment = new Segment();
|
||||
segment.link = link;
|
||||
segment.text = text;
|
||||
returnee.add(segment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder
|
||||
text(String text)
|
||||
{
|
||||
Segment segment = new Segment();
|
||||
segment.text = text;
|
||||
returnee.add(segment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder
|
||||
spacer(String text)
|
||||
{
|
||||
Segment segment = new Segment();
|
||||
segment.text = text;
|
||||
segment.spacer = true;
|
||||
returnee.add(segment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Segment>
|
||||
finish() { return returnee; }
|
||||
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
RichTextPane()
|
||||
{
|
||||
text = new LinkedList<>();
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
|
||||
import cafe.biskuteri.hinoki.Tree;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.io.StringReader;
|
||||
import java.io.Reader;
|
||||
import java.io.IOException;
|
||||
@ -9,9 +11,19 @@ RudimentaryHTMLParser {
|
||||
|
||||
public static Tree<String>
|
||||
depthlessRead(String html)
|
||||
throws IOException
|
||||
{
|
||||
return pass2(pass1(html));
|
||||
try {
|
||||
return pass3(pass2(pass1(html)));
|
||||
}
|
||||
catch (IOException eIo) {
|
||||
assert false;
|
||||
/*
|
||||
* We use only StringReaders, which only throw an
|
||||
* IOException when they are read after being closed.
|
||||
* And we don't close them.
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
@ -23,8 +35,9 @@ RudimentaryHTMLParser {
|
||||
Reader r = new StringReader(html);
|
||||
Tree<String> docu = new Tree<String>();
|
||||
StringBuilder text = new StringBuilder();
|
||||
StringBuilder emoji = new StringBuilder();
|
||||
StringBuilder htmlEscape = new StringBuilder();
|
||||
boolean quoted = false;
|
||||
boolean quoted = false, inEmoji = false;
|
||||
int c; while ((c = r.read()) != -1)
|
||||
{
|
||||
if (c == '&' || htmlEscape.length() > 0)
|
||||
@ -96,8 +109,7 @@ RudimentaryHTMLParser {
|
||||
{
|
||||
for (Tree<String> node: docu.children)
|
||||
{
|
||||
if (node.key.equals("text")) continue;
|
||||
assert node.key.equals("tag");
|
||||
if (!node.key.equals("tag")) continue;
|
||||
|
||||
Reader r = new StringReader(node.value);
|
||||
Tree<String> part = new Tree<String>();
|
||||
@ -149,6 +161,61 @@ RudimentaryHTMLParser {
|
||||
return docu;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
it.remove();
|
||||
StringBuilder t = new StringBuilder();
|
||||
StringBuilder e = new StringBuilder();
|
||||
boolean emoji = false;
|
||||
char pc = ' ';
|
||||
for (char c: node.value.toCharArray())
|
||||
{
|
||||
if (!emoji && c == ':')
|
||||
{
|
||||
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) e.append((char)c);
|
||||
else t.append((char)c);
|
||||
pc = c;
|
||||
}
|
||||
if (t.length() > 0) {
|
||||
Tree<String> text = new Tree<String>();
|
||||
text.key = "text";
|
||||
text.value = empty(t);
|
||||
it.add(text);
|
||||
}
|
||||
}
|
||||
return docu;
|
||||
}
|
||||
|
||||
private static String
|
||||
empty(StringBuilder b)
|
||||
{
|
||||
|
28
TestWindow.java
Normal file
@ -0,0 +1,28 @@
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
|
||||
class
|
||||
TestWindow extends JFrame {
|
||||
|
||||
TestWindow()
|
||||
{
|
||||
RichTextPane display = new RichTextPane();
|
||||
setContentPane(display);
|
||||
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||
setLocationByPlatform(true);
|
||||
setSize(320, 240);
|
||||
setVisible(true);
|
||||
setVisible(false);
|
||||
|
||||
String s = "This is a standard English sentence.";
|
||||
RichTextPane.Builder b = new RichTextPane.Builder();
|
||||
for (String word: s.split(" ")) b = b.text(word).spacer(" ");
|
||||
List<RichTextPane.Segment> text = b.finish();
|
||||
FontMetrics fm = display.getFontMetrics(display.getFont());
|
||||
RichTextPane.layout(text, fm, display.getWidth());
|
||||
display.setText(text);
|
||||
}
|
||||
|
||||
}
|
62
TimelineWindow.java
Normal file → Executable file
@ -339,54 +339,43 @@ implements ActionListener {
|
||||
addee.date = ZonedDateTime.now();
|
||||
}
|
||||
|
||||
try {
|
||||
String s2 = addee.html = post.get("content").value;
|
||||
StringBuilder b = new StringBuilder();
|
||||
Tree<String> nodes =
|
||||
RudimentaryHTMLParser
|
||||
.depthlessRead(post.get("content").value);
|
||||
for (Tree<String> node: nodes.children)
|
||||
Tree<String> nodes = RudimentaryHTMLParser.depthlessRead(s2);
|
||||
for (Tree<String> node: nodes)
|
||||
{
|
||||
if (node.key.equals("tag"))
|
||||
{
|
||||
if (node.get(0).key.equals("br")) {
|
||||
b.append(" \n ");
|
||||
}
|
||||
if (node.get(0).key.equals("/p")) {
|
||||
b.append(" \n \n ");
|
||||
}
|
||||
}
|
||||
if (node.key.equals("text")) {
|
||||
b.append(node.value);
|
||||
String tagName = node.get(0).key;
|
||||
if (tagName.equals("br")) b.append(" \n ");
|
||||
if (tagName.equals("/p")) b.append(" \n \n ");
|
||||
}
|
||||
if (node.key.equals("text")) b.append(node.value);
|
||||
if (node.key.equals("emoji")) b.append(node.value);
|
||||
}
|
||||
addee.text = b.toString();
|
||||
}
|
||||
catch (IOException eIo) {
|
||||
eIo.printStackTrace();
|
||||
assert false;
|
||||
}
|
||||
|
||||
String s = post.get("spoiler_text").value;
|
||||
if (!s.isEmpty()) addee.contentWarning = s;
|
||||
String s3 = post.get("spoiler_text").value;
|
||||
if (!s3.isEmpty()) addee.contentWarning = s3;
|
||||
else addee.contentWarning = null;
|
||||
|
||||
Tree<String> account = post.get("account");
|
||||
addee.authorId = account.get("acct").value;
|
||||
addee.authorName = account.get("username").value;
|
||||
addee.authorNumId = account.get("id").value;
|
||||
String s2 = account.get("display_name").value;
|
||||
if (!s2.isEmpty()) addee.authorName = s2;
|
||||
String s3 = account.get("avatar").value;
|
||||
addee.authorAvatar = ImageApi.remote(s3);
|
||||
String s4 = account.get("display_name").value;
|
||||
if (!s4.isEmpty()) addee.authorName = s4;
|
||||
String s5 = account.get("avatar").value;
|
||||
addee.authorAvatar = ImageApi.remote(s5);
|
||||
if (addee.authorAvatar == null) {
|
||||
s3 = "defaultAvatar";
|
||||
addee.authorAvatar = ImageApi.local(s3);
|
||||
s5 = "defaultAvatar";
|
||||
addee.authorAvatar = ImageApi.local(s5);
|
||||
}
|
||||
|
||||
String f = post.get("favourited").value;
|
||||
String b = post.get("reblogged").value;
|
||||
addee.favourited = f.equals("true");
|
||||
addee.boosted = b.equals("true");
|
||||
String s6 = post.get("favourited").value;
|
||||
String s7 = post.get("reblogged").value;
|
||||
addee.favourited = s6.equals("true");
|
||||
addee.boosted = s7.equals("true");
|
||||
|
||||
Tree<String> as1 = post.get("media_attachments");
|
||||
Attachment[] as2 = new Attachment[as1.size()];
|
||||
@ -406,6 +395,17 @@ implements ActionListener {
|
||||
}
|
||||
addee.attachments = as2;
|
||||
|
||||
Tree<String> es1 = post.get("emojis");
|
||||
String[][] es2 = new String[es1.size()][];
|
||||
for (int o = 0; o < es2.length; ++o)
|
||||
{
|
||||
Tree<String> e1 = es1.get(o);
|
||||
String[] e2 = es2[o] = new String[2];
|
||||
e2[0] = e1.get("shortcode").value;
|
||||
e2[1] = e1.get("url").value;
|
||||
}
|
||||
addee.emojiUrls = es2;
|
||||
|
||||
posts.add(addee);
|
||||
}
|
||||
return posts;
|
||||
|
0
TimelineWindowUpdater.java
Normal file → Executable file
0
TwoToggleButton.java
Normal file → Executable file
0
graphics/Flags.xcf
Normal file → Executable file
0
graphics/Hourglass.xcf
Normal file → Executable file
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/ref1.png
Normal file → Executable file
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 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 |