mirror of
https://gitlab.com/biskuteri-cafe/JKomasto2.git
synced 2024-11-20 05:04:51 +01:00
dacf58ac20
The UI needs to be easier on the eyes, I'm working on it
837 lines
22 KiB
Java
837 lines
22 KiB
Java
|
|
import javax.swing.JFrame;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JMenuBar;
|
|
import javax.swing.JPopupMenu;
|
|
import javax.swing.JMenuItem;
|
|
import javax.swing.JSeparator;
|
|
import javax.swing.JButton;
|
|
import javax.swing.JLabel;
|
|
import javax.swing.JScrollPane;
|
|
import javax.swing.JScrollBar;
|
|
import javax.swing.Box;
|
|
import javax.swing.BoxLayout;
|
|
import javax.swing.BorderFactory;
|
|
import javax.swing.border.Border;
|
|
import javax.swing.JOptionPane;
|
|
import javax.swing.ImageIcon;
|
|
import java.awt.Graphics;
|
|
import java.awt.Font;
|
|
import java.awt.FontMetrics;
|
|
import java.awt.Shape;
|
|
import java.awt.Dimension;
|
|
import java.awt.BorderLayout;
|
|
import java.awt.GridLayout;
|
|
import java.awt.Cursor;
|
|
import java.awt.Image;
|
|
import java.awt.Component;
|
|
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.io.IOException;
|
|
import cafe.biskuteri.hinoki.Tree;
|
|
import java.text.BreakIterator;
|
|
import java.util.Locale;
|
|
import java.time.ZonedDateTime;
|
|
import java.time.ZoneId;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
|
|
class
|
|
PostWindow extends JFrame {
|
|
|
|
private JKomasto
|
|
primaire;
|
|
|
|
private MastodonApi
|
|
api;
|
|
|
|
private Tree<String>
|
|
post;
|
|
|
|
// - -%- -
|
|
|
|
private PostComponent
|
|
display;
|
|
|
|
// - -%- -
|
|
|
|
private static final DateTimeFormatter
|
|
DATE_FORMAT = DateTimeFormatter.ofPattern("d LLLL ''uu"),
|
|
TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm");
|
|
|
|
// ---%-@-%---
|
|
|
|
public void
|
|
displayEntity(Tree<String> post)
|
|
{
|
|
this.post = post;
|
|
|
|
Tree<String> boosted = post.get("reblog");
|
|
if (boosted.size() > 0) post = boosted;
|
|
|
|
Tree<String> author = post.get("account");
|
|
Tree<String> emojis = post.get("emojis");
|
|
Tree<String> media = post.get("media_attachments");
|
|
|
|
String an = author.get("display_name").value;
|
|
if (an.isEmpty()) an = author.get("username").value;
|
|
display.setAuthorName(an);
|
|
display.setAuthorId(author.get("acct").value);
|
|
|
|
String aid = author.get("id").value;
|
|
String oid = api.getAccountDetails().get("id").value;
|
|
display.setDeleteEnabled(aid.equals(oid));
|
|
|
|
String avurl = author.get("avatar").value;
|
|
display.setAuthorAvatar(ImageApi.remote(avurl));
|
|
|
|
String sdate = post.get("created_at").value;
|
|
ZonedDateTime date = ZonedDateTime.parse(sdate);
|
|
date = date.withZoneSameInstant(ZoneId.systemDefault());
|
|
display.setDate(DATE_FORMAT.format(date));
|
|
display.setTime(TIME_FORMAT.format(date));
|
|
|
|
String[][] emojiUrls = new String[emojis.size()][];
|
|
for (int o = 0; o < emojiUrls.length; ++o) {
|
|
Tree<String> emoji = emojis.get(o);
|
|
emojiUrls[o] = new String[2];
|
|
emojiUrls[o][0] = emoji.get("shortcode").value;
|
|
emojiUrls[o][1] = emoji.get("url").value;
|
|
}
|
|
display.setEmojiUrls(emojiUrls);
|
|
|
|
display.setHtml(post.get("content").value);
|
|
boolean f = post.get("favourited").value.equals("true");
|
|
boolean b = post.get("reblogged").value.equals("true");
|
|
display.setFavourited(f);
|
|
display.setBoosted(b);
|
|
|
|
if (media.size() > 0)
|
|
{
|
|
Tree<String> first = media.get(0);
|
|
String u1 = first.get("remote_url").value;
|
|
String u2 = first.get("text_url").value;
|
|
String u3 = first.get("url").value;
|
|
String purl = u1 != null ? u1 : u2 != null ? u2 : u3;
|
|
display.setMediaPreview(ImageApi.remote(purl));
|
|
}
|
|
else display.setMediaPreview(null);
|
|
|
|
String html = post.get("content").value;
|
|
setTitle(TimelineComponent.textApproximation(html));
|
|
|
|
display.resetFocus();
|
|
repaint();
|
|
}
|
|
|
|
public void
|
|
openAuthorProfile()
|
|
{
|
|
Tree<String> post = this.post;
|
|
Tree<String> boosted = post.get("reblog");
|
|
if (boosted.size() > 0) post = boosted;
|
|
|
|
TimelineWindow w = new TimelineWindow(primaire);
|
|
w.showAuthorPosts(post.get("account").get("id").value);
|
|
w.showLatestPage();
|
|
w.setLocationRelativeTo(this);
|
|
w.setVisible(true);
|
|
}
|
|
|
|
public void
|
|
favourite(boolean favourited)
|
|
{
|
|
Tree<String> post = this.post;
|
|
Tree<String> boosted = post.get("reblog");
|
|
if (boosted.size() > 0) post = boosted;
|
|
|
|
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
|
display.setFavouriteBoostEnabled(false);
|
|
display.paintImmediately(display.getBounds());
|
|
RequestListener handler = new RequestListener() {
|
|
|
|
public void
|
|
connectionFailed(IOException eIo)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
PostWindow.this,
|
|
"Tried to favourite post, failed.."
|
|
+ "\n" + eIo.getClass() + ": " + eIo.getMessage()
|
|
);
|
|
}
|
|
|
|
public void
|
|
requestFailed(int httpCode, Tree<String> json)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
PostWindow.this,
|
|
"Tried to favourite post, failed.."
|
|
+ "\n" + json.get("error").value
|
|
+ "\n(HTTP error code: " + httpCode + ")"
|
|
);
|
|
}
|
|
|
|
public void
|
|
requestSucceeded(Tree<String> json)
|
|
{
|
|
String n = Boolean.toString(favourited);
|
|
PostWindow.this.post.get("favourited").value = n;
|
|
}
|
|
|
|
};
|
|
String postId = post.get("id").value;
|
|
api.setPostFavourited(postId, favourited, handler);
|
|
display.setCursor(null);
|
|
display.setFavouriteBoostEnabled(true);
|
|
display.repaint();
|
|
}
|
|
|
|
public void
|
|
boost(boolean boosted)
|
|
{
|
|
Tree<String> post = this.post;
|
|
Tree<String> boosted2 = post.get("reblog");
|
|
if (boosted2.size() > 0) post = boosted2;
|
|
|
|
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
|
display.setFavouriteBoostEnabled(false);
|
|
display.paintImmediately(display.getBounds());
|
|
RequestListener handler = new RequestListener() {
|
|
|
|
public void
|
|
connectionFailed(IOException eIo)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
PostWindow.this,
|
|
"Tried to boost post, failed.."
|
|
+ "\n" + eIo.getClass() + ": " + eIo.getMessage()
|
|
);
|
|
}
|
|
|
|
public void
|
|
requestFailed(int httpCode, Tree<String> json)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
PostWindow.this,
|
|
"Tried to boost post, failed.."
|
|
+ "\n" + json.get("error").value
|
|
+ "\n(HTTP error code: " + httpCode + ")"
|
|
);
|
|
}
|
|
|
|
public void
|
|
requestSucceeded(Tree<String> json)
|
|
{
|
|
String n = Boolean.toString(boosted);
|
|
PostWindow.this.post.get("reblogged").value = n;
|
|
}
|
|
|
|
};
|
|
String postId = post.get("id").value;
|
|
api.setPostBoosted(postId, boosted, handler);
|
|
display.setCursor(null);
|
|
display.setFavouriteBoostEnabled(true);
|
|
display.repaint();
|
|
}
|
|
|
|
public void
|
|
reply()
|
|
{
|
|
Tree<String> post = this.post;
|
|
Tree<String> boosted = post.get("reblog");
|
|
if (boosted.size() > 0) post = boosted;
|
|
|
|
String authorId = post.get("account").get("acct").value;
|
|
String postId = post.get("id").value;
|
|
String cw = post.get("spoiler_text").value;
|
|
|
|
String id1 = post.get("account").get("id").value;
|
|
String id2 = api.getAccountDetails().get("id").value;
|
|
|
|
String vs = post.get("visibility").value;
|
|
PostVisibility v = null;
|
|
if (vs.equals("public")) v = PostVisibility.PUBLIC;
|
|
if (vs.equals("unlisted")) v = PostVisibility.UNLISTED;
|
|
if (vs.equals("private")) v = PostVisibility.FOLLOWERS;
|
|
if (vs.equals("direct")) v = PostVisibility.MENTIONED;
|
|
|
|
Composition c = new Composition();
|
|
c.contentWarning = cw;
|
|
c.text = id1.equals(id2) ? "" : "@" + authorId + " ";
|
|
c.visibility = v;
|
|
c.replyToPostId = postId;
|
|
|
|
ComposeWindow w = primaire.getComposeWindow();
|
|
w.setLocation(getX(), getY() + 100);
|
|
w.setVisible(true);
|
|
w.setComposition(c);
|
|
}
|
|
|
|
public void
|
|
openMedia()
|
|
{
|
|
Tree<String> post = this.post;
|
|
Tree<String> boosted = post.get("reblog");
|
|
if (boosted.size() > 0) post = boosted;
|
|
Tree<String> media = post.get("media_attachments");
|
|
|
|
Attachment[] as = new Attachment[media.size()];
|
|
for (int o = 0; o < as.length; ++o)
|
|
{
|
|
Tree<String> medium = media.get(o);
|
|
String u1 = medium.get("remote_url").value;
|
|
String u2 = medium.get("text_url").value;
|
|
String u3 = medium.get("url").value;
|
|
|
|
Attachment a = as[o] = new Attachment();
|
|
a.url = u1 != null ? u1 : u2 != null ? u2 : u3;
|
|
a.type = medium.get("type").value;
|
|
a.description = medium.get("description").value;
|
|
a.image = ImageApi.remote(a.url);
|
|
}
|
|
|
|
ImageWindow w = primaire.getMediaWindow();
|
|
w.setTitle(post.get("id").value);
|
|
w.showAttachments(as);
|
|
if (!w.isVisible()) {
|
|
w.setLocationRelativeTo(null);
|
|
w.setVisible(true);
|
|
}
|
|
}
|
|
|
|
public void
|
|
deletePost(boolean redraft)
|
|
{
|
|
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
|
display.setDeleteEnabled(false);
|
|
display.paintImmediately(display.getBounds());
|
|
|
|
if (redraft)
|
|
{
|
|
String html = post.get("content").value;
|
|
StringBuilder b = new StringBuilder();
|
|
Tree<String> nodes;
|
|
nodes = RudimentaryHTMLParser.depthlessRead(html);
|
|
// We have to salvage whatever we can from the HTML.
|
|
for (Tree<String> node: nodes)
|
|
{
|
|
if (node.key.equals("text"))
|
|
{
|
|
b.append(node.value);
|
|
}
|
|
if (node.key.equals("emoji"))
|
|
{
|
|
b.append(":" + node.value + ":");
|
|
}
|
|
if (node.key.equals("tag"))
|
|
{
|
|
if (node.get(0).key.equals("/p"))
|
|
b.append("\n\n");
|
|
if (node.get(0).key.equals("br"))
|
|
b.append("\n");
|
|
if (node.get(0).key.equals("a")) {
|
|
b.append(node.get("href").value);
|
|
b.append(" ");
|
|
continue;
|
|
/*
|
|
* We don't omit the contents of the <a>
|
|
* which is an automatic label, but we'll
|
|
* need a non-depthless parser to omit that.
|
|
* For now prioritise not losing anything
|
|
* from our composition.
|
|
*/
|
|
}
|
|
// I think that's all. I hope.
|
|
}
|
|
}
|
|
|
|
String cw = post.get("spoiler_text").value;
|
|
|
|
String vs = post.get("visibility").value;
|
|
PostVisibility v = null;
|
|
if (vs.equals("public")) v = PostVisibility.PUBLIC;
|
|
if (vs.equals("unlisted")) v = PostVisibility.UNLISTED;
|
|
if (vs.equals("private")) v = PostVisibility.FOLLOWERS;
|
|
if (vs.equals("direct")) v = PostVisibility.MENTIONED;
|
|
|
|
String replyTo = post.get("in_reply_to_id").value;
|
|
|
|
Composition c = new Composition();
|
|
c.contentWarning = cw;
|
|
c.text = b.toString();
|
|
c.visibility = v;
|
|
c.replyToPostId = replyTo;
|
|
|
|
ComposeWindow w = primaire.getComposeWindow();
|
|
w.setLocation(getX(), getY() + 100);
|
|
w.setVisible(true);
|
|
w.setComposition(c);
|
|
}
|
|
|
|
api.deletePost(post.get("id").value, new RequestListener() {
|
|
|
|
public void
|
|
connectionFailed(IOException eIo)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
PostWindow.this,
|
|
"Failed to delete post.."
|
|
+ "\n" + eIo.getMessage()
|
|
);
|
|
}
|
|
|
|
public void
|
|
requestFailed(int httpCode, Tree<String> json)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
PostWindow.this,
|
|
"Failed to delete post.."
|
|
+ "\n" + json.get("error").value
|
|
+ "\n(HTTP code: " + httpCode + ")"
|
|
);
|
|
}
|
|
|
|
public void
|
|
requestSucceeded(Tree<String> json)
|
|
{
|
|
setVisible(false);
|
|
}
|
|
|
|
});
|
|
|
|
display.setCursor(null);
|
|
display.setDeleteEnabled(true);
|
|
display.paintImmediately(display.getBounds());
|
|
if (!isVisible()) dispose();
|
|
}
|
|
|
|
public void
|
|
copyPostId()
|
|
{
|
|
Tree<String> post = this.post;
|
|
Tree<String> reblogged = post.get("reblog");
|
|
if (reblogged.size() > 0) post = reblogged;
|
|
|
|
ClipboardApi.serve(post.get("id").value);
|
|
}
|
|
|
|
public void
|
|
copyPostLink()
|
|
{
|
|
Tree<String> post = this.post;
|
|
Tree<String> reblogged = post.get("reblog");
|
|
if (reblogged.size() > 0) post = reblogged;
|
|
|
|
String url = post.get("url").value;
|
|
if (url == null) url = post.get("uri").value;
|
|
|
|
ClipboardApi.serve(url);
|
|
}
|
|
|
|
public void
|
|
openReplies()
|
|
{
|
|
Tree<String> post = this.post;
|
|
Tree<String> boosted = post.get("reblog");
|
|
if (boosted.size() > 0) post = boosted;
|
|
|
|
RepliesWindow w = new RepliesWindow(primaire, this);
|
|
w.showFor(post.get("id").value);
|
|
w.setLocation(getX(), getY() + 100);
|
|
w.setVisible(true);
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
PostWindow(JKomasto primaire)
|
|
{
|
|
this.primaire = primaire;
|
|
this.api = primaire.getMastodonApi();
|
|
|
|
getContentPane().setPreferredSize(new Dimension(360, 270));
|
|
pack();
|
|
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
|
setLocationByPlatform(true);
|
|
|
|
display = new PostComponent(this);
|
|
|
|
setContentPane(display);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class
|
|
PostComponent extends JPanel
|
|
implements ActionListener {
|
|
|
|
private PostWindow
|
|
primaire;
|
|
|
|
// - -%- -
|
|
|
|
private List<RichTextPane.Segment>
|
|
authorNameOr, bodyOr;
|
|
|
|
private RichTextPane
|
|
authorName, body;
|
|
|
|
private JLabel
|
|
authorId, time, date;
|
|
|
|
private String[][]
|
|
emojiUrls;
|
|
|
|
private TwoToggleButton
|
|
favouriteBoost,
|
|
replyMisc,
|
|
nextPrev;
|
|
|
|
private RoundButton
|
|
profile,
|
|
media;
|
|
|
|
private JPopupMenu
|
|
miscMenu;
|
|
|
|
private JMenuItem
|
|
openReplies,
|
|
copyPostId,
|
|
copyPostLink,
|
|
deletePost,
|
|
redraftPost;
|
|
|
|
// ---%-@-%---
|
|
|
|
public void
|
|
setAuthorName(String n)
|
|
{
|
|
authorNameOr = new RichTextPane.Builder().text(n).finish();
|
|
}
|
|
|
|
public void
|
|
setAuthorId(String n) { authorId.setText(n); }
|
|
|
|
public void
|
|
setAuthorAvatar(Image n) { profile.setImage(n); }
|
|
|
|
public void
|
|
setDate(String n) { date.setText(n); }
|
|
|
|
public void
|
|
setTime(String n) { time.setText(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"))
|
|
{
|
|
BreakIterator it = BreakIterator.getWordInstance(Locale.ROOT);
|
|
String text = node.value;
|
|
it.setText(text);
|
|
int start = it.first(), end = it.next();
|
|
while (end != BreakIterator.DONE)
|
|
{
|
|
String word = text.substring(start, end);
|
|
char c = word.isEmpty() ? ' ' : word.charAt(0);
|
|
boolean w = Character.isWhitespace(c);
|
|
b = w ? b.spacer(word) : b.text(word);
|
|
start = end;
|
|
end = it.next();
|
|
}
|
|
}
|
|
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(":" + shortcode + ":");
|
|
}
|
|
}
|
|
}
|
|
bodyOr = b.finish();
|
|
}
|
|
|
|
public void
|
|
setFavourited(boolean a)
|
|
{
|
|
favouriteBoost.removeActionListener(this);
|
|
favouriteBoost.setPrimaryToggled(a);
|
|
favouriteBoost.addActionListener(this);
|
|
}
|
|
|
|
public void
|
|
setBoosted(boolean a)
|
|
{
|
|
favouriteBoost.removeActionListener(this);
|
|
favouriteBoost.setSecondaryToggled(a);
|
|
favouriteBoost.addActionListener(this);
|
|
}
|
|
|
|
public void
|
|
setFavouriteBoostEnabled(boolean a)
|
|
{
|
|
favouriteBoost.setEnabled(a);
|
|
}
|
|
|
|
public void
|
|
setDeleteEnabled(boolean a)
|
|
{
|
|
deletePost.setEnabled(a);
|
|
redraftPost.setEnabled(a);
|
|
}
|
|
|
|
public void
|
|
setMediaPreview(Image n) { media.setImage(n); }
|
|
|
|
public void
|
|
resetFocus() { media.requestFocusInWindow(); }
|
|
|
|
// - -%- -
|
|
|
|
public void
|
|
actionPerformed(ActionEvent eA)
|
|
{
|
|
Component src = (Component)eA.getSource();
|
|
String command = eA.getActionCommand();
|
|
|
|
if (src == profile)
|
|
{
|
|
primaire.openAuthorProfile();
|
|
return;
|
|
}
|
|
|
|
if (src == favouriteBoost)
|
|
{
|
|
if (command.equals("favouriteOn"))
|
|
primaire.favourite(true);
|
|
if (command.equals("favouriteOff"))
|
|
primaire.favourite(false);
|
|
if (command.equals("boostOn"))
|
|
primaire.boost(true);
|
|
if (command.equals("boostOff"))
|
|
primaire.boost(false);
|
|
return;
|
|
}
|
|
|
|
if (src == replyMisc)
|
|
{
|
|
if (command.startsWith("reply"))
|
|
primaire.reply();
|
|
if (command.startsWith("misc"))
|
|
{
|
|
int rx = replyMisc.getWidth() / 2;
|
|
int ry = replyMisc.getHeight() - miscMenu.getHeight();
|
|
miscMenu.show(replyMisc, rx, ry);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (src == nextPrev)
|
|
{
|
|
if (command.equals("next"))
|
|
{
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (src == media)
|
|
{
|
|
primaire.openMedia();
|
|
return;
|
|
}
|
|
|
|
if (src == openReplies) primaire.openReplies();
|
|
if (src == copyPostId) primaire.copyPostId();
|
|
if (src == copyPostLink) primaire.copyPostLink();
|
|
if (src == deletePost) primaire.deletePost(false);
|
|
if (src == redraftPost) primaire.deletePost(true);
|
|
|
|
}
|
|
|
|
protected void
|
|
paintComponent(Graphics g)
|
|
{
|
|
g.clearRect(0, 0, getWidth(), getHeight());
|
|
|
|
int w1 = authorName.getWidth();
|
|
int w2 = body.getWidth();
|
|
FontMetrics fm1 = getFontMetrics(authorName.getFont());
|
|
FontMetrics fm2 = getFontMetrics(body.getFont());
|
|
List<RichTextPane.Segment> lay1, lay2;
|
|
lay1 = RichTextPane.layout(authorNameOr, fm1, w1);
|
|
lay2 = RichTextPane.layout(bodyOr, fm2, w2);
|
|
|
|
authorName.setText(lay1);
|
|
body.setText(lay2);
|
|
|
|
int maxY = 0; for (RichTextPane.Segment s: lay2)
|
|
{
|
|
if (s.y > maxY) maxY = s.y;
|
|
}
|
|
body.setPreferredSize(new Dimension(1, maxY + 10));
|
|
|
|
((java.awt.Graphics2D)g).setRenderingHint(
|
|
java.awt.RenderingHints.KEY_ANTIALIASING,
|
|
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
|
|
);
|
|
/*
|
|
* I suspect the reason why we are losing antialiasing,
|
|
* is that the JScrollPane asks us to paint once and
|
|
* then on scroll, it repaints from its capture of the
|
|
* viewport contents, rather than ask us to repaint.
|
|
* Then it wouldn't set the hint.
|
|
*
|
|
* We're scheduled to remove the scrolling functionality
|
|
* in favour of pages, so, we don't need to intercept
|
|
* the scroller for now. Though I feel like it would've
|
|
* made more sense to allow setting a widget's rendering
|
|
* hints.
|
|
*/
|
|
}
|
|
|
|
|
|
// ---%-@-%---
|
|
|
|
PostComponent(PostWindow primaire)
|
|
{
|
|
this.primaire = primaire;
|
|
|
|
emojiUrls = new String[0][];
|
|
|
|
Border b = BorderFactory.createEmptyBorder(10, 10, 10, 10);
|
|
Font f1 = new Font("Dialog", Font.PLAIN, 18);
|
|
Font f2 = new Font("IPAGothic", Font.PLAIN, 14);
|
|
Font f3 = new Font("Dialog", Font.PLAIN, 20);
|
|
|
|
profile = new RoundButton();
|
|
favouriteBoost = new TwoToggleButton("favourite", "boost");
|
|
replyMisc = new TwoToggleButton("reply", "misc");
|
|
nextPrev = new TwoToggleButton("next", "prev");
|
|
media = new RoundButton();
|
|
profile.addActionListener(this);
|
|
favouriteBoost.addActionListener(this);
|
|
replyMisc.addActionListener(this);
|
|
nextPrev.addActionListener(this);
|
|
media.addActionListener(this);
|
|
|
|
openReplies = new JMenuItem("Browse thread");
|
|
copyPostId = new JMenuItem("Copy post ID");
|
|
copyPostLink = new JMenuItem("Copy post link");
|
|
deletePost = new JMenuItem("Delete post");
|
|
redraftPost = new JMenuItem("Delete and redraft post");
|
|
openReplies.addActionListener(this);
|
|
copyPostId.addActionListener(this);
|
|
copyPostLink.addActionListener(this);
|
|
deletePost.addActionListener(this);
|
|
redraftPost.addActionListener(this);
|
|
miscMenu = new JPopupMenu();
|
|
miscMenu.add(openReplies);
|
|
miscMenu.add(new JSeparator());
|
|
miscMenu.add(copyPostId);
|
|
miscMenu.add(copyPostLink);
|
|
miscMenu.add(new JSeparator());
|
|
miscMenu.add(deletePost);
|
|
miscMenu.add(new JSeparator());
|
|
miscMenu.add(redraftPost);
|
|
|
|
Box buttons = Box.createVerticalBox();
|
|
buttons.setOpaque(false);
|
|
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);
|
|
|
|
authorId = new JLabel();
|
|
authorName = new RichTextPane();
|
|
time = new JLabel();
|
|
date = new JLabel();
|
|
authorId.setFont(f2);
|
|
date.setFont(f2);
|
|
authorName.setFont(f1);
|
|
time.setFont(f1);
|
|
|
|
JPanel top1 = new JPanel();
|
|
top1.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(f3);
|
|
|
|
JScrollPane scroll = new JScrollPane(
|
|
body,
|
|
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
|
|
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
|
|
);
|
|
JScrollBar vsb = scroll.getVerticalScrollBar();
|
|
vsb.setPreferredSize(new Dimension(0, 0));
|
|
vsb.setUnitIncrement(16);
|
|
scroll.setBorder(null);
|
|
scroll.setFocusable(true);
|
|
|
|
JPanel centre = new JPanel();
|
|
centre.setOpaque(false);
|
|
centre.setLayout(new BorderLayout(0, 8));
|
|
centre.add(top, BorderLayout.NORTH);
|
|
centre.add(scroll);
|
|
|
|
setLayout(new BorderLayout(8, 0));
|
|
add(left, BorderLayout.WEST);
|
|
add(centre);
|
|
|
|
setBorder(b);
|
|
}
|
|
|
|
}
|