mirror of
https://gitlab.com/biskuteri-cafe/JKomasto2.git
synced 2024-11-20 05:34:51 +01:00
721 lines
18 KiB
Java
Executable File
721 lines
18 KiB
Java
Executable File
|
|
import javax.swing.JFrame;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JMenuBar;
|
|
import javax.swing.JMenu;
|
|
import javax.swing.JMenuItem;
|
|
import javax.swing.JButton;
|
|
import javax.swing.JLabel;
|
|
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.event.ActionListener;
|
|
import java.awt.event.ActionEvent;
|
|
import java.util.List;
|
|
import java.util.ArrayList;
|
|
import java.time.ZonedDateTime;
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.io.IOException;
|
|
import cafe.biskuteri.hinoki.Tree;
|
|
|
|
|
|
class
|
|
PostWindow extends JFrame
|
|
implements ActionListener {
|
|
|
|
private JKomasto
|
|
primaire;
|
|
|
|
private MastodonApi
|
|
api;
|
|
|
|
private Post
|
|
post;
|
|
|
|
// - -%- -
|
|
|
|
private PostComponent
|
|
postDisplay;
|
|
|
|
private RepliesComponent
|
|
repliesDisplay;
|
|
|
|
// - -%- -
|
|
|
|
private static final DateTimeFormatter
|
|
DATE_FORMAT = DateTimeFormatter.ofPattern("d LLLL ''uu"),
|
|
TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm");
|
|
|
|
// ---%-@-%---
|
|
|
|
public void
|
|
showPost(Post post)
|
|
{
|
|
assert post != null;
|
|
this.post = post;
|
|
|
|
List<RepliesComponent.Reply> replies = null;
|
|
{
|
|
List<Post> posts = null;
|
|
// We should make a request to JKomasto here.
|
|
}
|
|
if (replies == null)
|
|
{
|
|
RepliesComponent.Reply reply1, reply2, reply3;
|
|
reply1 = new RepliesComponent.Reply();
|
|
reply1.author = "Black tea";
|
|
reply1.text = "Rich..";
|
|
reply2 = new RepliesComponent.Reply();
|
|
reply2.author = "Green tea";
|
|
reply2.text = "Clean!";
|
|
reply3 = new RepliesComponent.Reply();
|
|
reply3.author = "Coffee";
|
|
reply3.text = "sleepy..";
|
|
|
|
replies = new ArrayList<>();
|
|
replies.add(reply1);
|
|
replies.add(reply2);
|
|
replies.add(reply3);
|
|
}
|
|
|
|
postDisplay.setAuthorName(post.authorName);
|
|
postDisplay.setAuthorId(post.authorId);
|
|
postDisplay.setAuthorAvatar(post.authorAvatar);
|
|
postDisplay.setDate(DATE_FORMAT.format(post.date));
|
|
postDisplay.setTime(TIME_FORMAT.format(post.date));
|
|
postDisplay.setText(post.text);
|
|
postDisplay.setFavourited(post.favourited);
|
|
postDisplay.setBoosted(post.boosted);
|
|
postDisplay.setMediaPreview(
|
|
post.attachments.length == 0
|
|
? null
|
|
: post.attachments[0].image
|
|
);
|
|
repliesDisplay.setReplies(replies);
|
|
postDisplay.resetFocus();
|
|
repaint();
|
|
}
|
|
|
|
public void
|
|
openAuthorProfile()
|
|
{
|
|
|
|
}
|
|
|
|
public void
|
|
favourite(boolean favourited)
|
|
{
|
|
postDisplay.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
|
postDisplay.setFavouriteBoostEnabled(false);
|
|
postDisplay.paintImmediately(postDisplay.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)
|
|
{
|
|
post.favourited = favourited;
|
|
}
|
|
|
|
};
|
|
api.setPostFavourited(post.postId, favourited, handler);
|
|
postDisplay.setFavouriteBoostEnabled(true);
|
|
postDisplay.setCursor(null);
|
|
}
|
|
|
|
public void
|
|
boost(boolean boosted)
|
|
{
|
|
postDisplay.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
|
postDisplay.setFavouriteBoostEnabled(false);
|
|
postDisplay.paintImmediately(postDisplay.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)
|
|
{
|
|
post.boosted = boosted;
|
|
}
|
|
|
|
};
|
|
api.setPostBoosted(post.postId, boosted, handler);
|
|
postDisplay.setFavouriteBoostEnabled(true);
|
|
postDisplay.setCursor(null);
|
|
}
|
|
|
|
public void
|
|
reply()
|
|
{
|
|
ComposeWindow w = primaire.getComposeWindow();
|
|
w.setLocation(getX(), getY() + 100);
|
|
w.setVisible(true);
|
|
Composition c = new Composition();
|
|
c.text = "@" + post.authorId + " ";
|
|
c.visibility = PostVisibility.PUBLIC;
|
|
c.replyToPostId = post.postId;
|
|
w.setComposition(c);
|
|
}
|
|
|
|
public void
|
|
openMedia()
|
|
{
|
|
ImageWindow w = primaire.getMediaWindow();
|
|
w.showAttachments(post.attachments);
|
|
int l = Math.min(40, post.text.length());
|
|
w.setTitle(post.text.substring(0, l));
|
|
if (!w.isVisible()) {
|
|
w.setLocation(getX(), getY() + 100);
|
|
w.setVisible(true);
|
|
}
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
public void
|
|
actionPerformed(ActionEvent eA)
|
|
{
|
|
Object src = eA.getSource();
|
|
if (!(src instanceof JMenuItem)) return;
|
|
String text = ((JMenuItem)src).getText();
|
|
|
|
if (text.equals("Post"))
|
|
{
|
|
setContentPane(postDisplay);
|
|
revalidate();
|
|
/*
|
|
* (知) Setting a content pane in itself doesn't
|
|
* do anything to the content pane. Validation
|
|
* of the validate root does. Which happens on
|
|
* window realisation, or by manual call.
|
|
*/
|
|
}
|
|
else if (text.equals("Replies"))
|
|
{
|
|
setContentPane(repliesDisplay);
|
|
revalidate();
|
|
}
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
PostWindow(JKomasto primaire)
|
|
{
|
|
this.primaire = primaire;
|
|
this.api = primaire.getMastodonApi();
|
|
|
|
getContentPane().setPreferredSize(new Dimension(360, 270));
|
|
pack();
|
|
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
|
setLocationByPlatform(true);
|
|
|
|
postDisplay = new PostComponent(this);
|
|
|
|
repliesDisplay = new RepliesComponent();
|
|
|
|
Post samplePost = new Post();
|
|
samplePost.text = "This is a sample post.";
|
|
samplePost.authorId = "snowyfox@biskuteri.cafe";
|
|
samplePost.authorName = "snowyfox";
|
|
samplePost.date = ZonedDateTime.now();
|
|
samplePost.visibility = PostVisibility.MENTIONED;
|
|
samplePost.postId = "000000000";
|
|
samplePost.boosted = false;
|
|
samplePost.favourited = true;
|
|
samplePost.attachments = new Attachment[0];
|
|
showPost(samplePost);
|
|
|
|
setContentPane(postDisplay);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class
|
|
PostComponent extends JPanel
|
|
implements ActionListener {
|
|
|
|
private PostWindow
|
|
primaire;
|
|
|
|
private String
|
|
authorName, authorId, date, time, text;
|
|
|
|
// - -%- -
|
|
|
|
private TwoToggleButton
|
|
favouriteBoost,
|
|
replyMisc,
|
|
nextPrev;
|
|
|
|
private RoundButton
|
|
profile,
|
|
media;
|
|
|
|
// ---%-@-%---
|
|
|
|
public void
|
|
setAuthorName(String n) { authorName = n; }
|
|
|
|
public void
|
|
setAuthorId(String n) { authorId = n; }
|
|
|
|
public void
|
|
setAuthorAvatar(Image n) { profile.setImage(n); }
|
|
|
|
public void
|
|
setDate(String n) { date = n; }
|
|
|
|
public void
|
|
setTime(String n) { time = n; }
|
|
|
|
public void
|
|
setText(String n) { text = n; }
|
|
|
|
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
|
|
setMediaPreview(Image n) { media.setImage(n); }
|
|
|
|
public void
|
|
resetFocus() { media.requestFocusInWindow(); }
|
|
|
|
// - -%- -
|
|
|
|
public void
|
|
actionPerformed(ActionEvent eA)
|
|
{
|
|
Object src = 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();
|
|
return;
|
|
}
|
|
|
|
if (src == nextPrev)
|
|
{
|
|
if (command.equals("next"))
|
|
{
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (src == media)
|
|
{
|
|
primaire.openMedia();
|
|
return;
|
|
}
|
|
}
|
|
|
|
protected void
|
|
paintComponent(Graphics g)
|
|
{
|
|
g.clearRect(0, 0, getWidth(), getHeight());
|
|
|
|
((java.awt.Graphics2D)g).setRenderingHint(
|
|
java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,
|
|
java.awt.RenderingHints.VALUE_TEXT_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 ImageIcon
|
|
toIcon(Image image)
|
|
{
|
|
if (image == null) return null;
|
|
return new ImageIcon(image);
|
|
}
|
|
|
|
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.equals("\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;
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
PostComponent(PostWindow primaire)
|
|
{
|
|
this.primaire = primaire;
|
|
|
|
authorName = authorId = time = text = "";
|
|
|
|
Dimension buttonSize = new Dimension(20, 40);
|
|
|
|
profile = new RoundButton();
|
|
//profile.setPreferredSize(buttonSize);
|
|
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);
|
|
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(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
|
|
|
setLayout(new BorderLayout());
|
|
add(buttons, BorderLayout.WEST);
|
|
|
|
setFont(getFont().deriveFont(14f));
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class
|
|
RepliesComponent extends JPanel {
|
|
|
|
private List<RepliesComponent.Reply>
|
|
replies;
|
|
|
|
// - -%- -
|
|
|
|
private JButton
|
|
prevPage, nextPage;
|
|
|
|
private JLabel
|
|
pageLabel;
|
|
|
|
private ReplyPreviewComponent[]
|
|
previews;
|
|
|
|
// ---%-@-%---
|
|
|
|
public void
|
|
setReplies(List<RepliesComponent.Reply> replies)
|
|
{
|
|
assert replies != null;
|
|
this.replies = replies;
|
|
displayPage(1);
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
private void
|
|
displayPage(int pageNumber)
|
|
{
|
|
assert pageNumber > 0;
|
|
assert this.replies != null;
|
|
|
|
List<RepliesComponent.Reply> page;
|
|
{
|
|
int oS = (pageNumber - 1) * 8;
|
|
int oE = Math.min(oS + 8, replies.size());
|
|
if (oS > oE) page = new ArrayList<>();
|
|
else page = this.replies.subList(oS, oE);
|
|
}
|
|
|
|
for (int o = 0; o < page.size(); ++o)
|
|
{
|
|
assert o < previews.length;
|
|
|
|
ReplyPreviewComponent preview = previews[o];
|
|
Reply reply = replies.get(o);
|
|
preview.setAuthorName(reply.author);
|
|
preview.setText(reply.text);
|
|
preview.setVisible(true);
|
|
}
|
|
|
|
for (int o = page.size(); o < previews.length; ++o)
|
|
{
|
|
ReplyPreviewComponent preview = previews[o];
|
|
preview.setVisible(false);
|
|
}
|
|
|
|
int pages = 1 + ((replies.size() - 1) / 8);
|
|
pageLabel.setText(pageNumber + "/" + pages);
|
|
prevPage.setEnabled(pageNumber > 1);
|
|
nextPage.setEnabled(pageNumber < pages);
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
public static class
|
|
Reply {
|
|
|
|
public String
|
|
author;
|
|
|
|
public String
|
|
text;
|
|
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
RepliesComponent()
|
|
{
|
|
prevPage = new JButton("<");
|
|
nextPage = new JButton(">");
|
|
prevPage.setEnabled(false);
|
|
nextPage.setEnabled(false);
|
|
|
|
pageLabel = new JLabel();
|
|
|
|
Box bottom = Box.createHorizontalBox();
|
|
bottom.add(Box.createGlue());
|
|
bottom.add(prevPage);
|
|
bottom.add(Box.createHorizontalStrut(8));
|
|
bottom.add(pageLabel);
|
|
bottom.add(Box.createHorizontalStrut(8));
|
|
bottom.add(nextPage);
|
|
|
|
JPanel centre = new JPanel();
|
|
centre.setOpaque(false);
|
|
centre.setLayout(new GridLayout(0, 1, 0, 2));
|
|
|
|
previews = new ReplyPreviewComponent[8];
|
|
for (int o = 0; o < previews.length; ++o)
|
|
{
|
|
previews[o] = new ReplyPreviewComponent();
|
|
previews[o].setVisible(false);
|
|
centre.add(previews[o]);
|
|
}
|
|
|
|
setLayout(new BorderLayout(0, 8));
|
|
add(centre, BorderLayout.CENTER);
|
|
add(bottom, BorderLayout.SOUTH);
|
|
|
|
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
|
|
|
setReplies(new ArrayList<>());
|
|
}
|
|
|
|
}
|
|
|
|
|
|
class
|
|
ReplyPreviewComponent extends JButton {
|
|
|
|
private String
|
|
author;
|
|
|
|
private String
|
|
text;
|
|
|
|
// ---%-@-%---
|
|
|
|
@Override
|
|
public void
|
|
setText(String text)
|
|
{
|
|
assert text != null;
|
|
this.text = text;
|
|
setText();
|
|
}
|
|
|
|
public void
|
|
setAuthorName(String author)
|
|
{
|
|
assert author != null;
|
|
this.author = author;
|
|
setText();
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
private void
|
|
setText()
|
|
{
|
|
StringBuilder text = new StringBuilder();
|
|
text.append(this.author);
|
|
text.append(" @ ");
|
|
text.append(this.text);
|
|
super.setText(text.toString());
|
|
}
|
|
|
|
protected void
|
|
paintComponent(Graphics g)
|
|
{
|
|
g.drawString(getText(), 8, 2 * getHeight() / 3);
|
|
}
|
|
|
|
}
|