Moved image methods to ImageApi.
Added testing implementation of updater.
65
ComposeWindow.java
Executable file → Normal file
@ -9,6 +9,7 @@ import javax.swing.JButton;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JOptionPane;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.event.ActionListener;
|
||||
@ -52,6 +53,7 @@ ComposeWindow extends JFrame {
|
||||
composition.text = "";
|
||||
composition.visibility = PostVisibility.MENTIONED;
|
||||
composition.replyToPostId = null;
|
||||
composition.contentWarning = null;
|
||||
syncDisplayToComposition();
|
||||
}
|
||||
|
||||
@ -59,10 +61,16 @@ ComposeWindow extends JFrame {
|
||||
submit()
|
||||
{
|
||||
syncCompositionToDisplay();
|
||||
|
||||
if (composition.replyToPostId != null)
|
||||
assert !composition.replyToPostId.trim().isEmpty();
|
||||
if (composition.contentWarning != null)
|
||||
assert !composition.contentWarning.trim().isEmpty();
|
||||
|
||||
display.setSubmitting(true);
|
||||
api.submit(
|
||||
composition.text, composition.visibility,
|
||||
composition.replyToPostId,
|
||||
composition.replyToPostId, composition.contentWarning,
|
||||
new RequestListener() {
|
||||
|
||||
public void
|
||||
@ -105,6 +113,7 @@ ComposeWindow extends JFrame {
|
||||
display.setText(composition.text);
|
||||
display.setReplyToPostId(composition.replyToPostId);
|
||||
display.setVisibility(stringFor(composition.visibility));
|
||||
display.setContentWarning(composition.contentWarning);
|
||||
}
|
||||
|
||||
private void
|
||||
@ -113,7 +122,19 @@ ComposeWindow extends JFrame {
|
||||
composition.text = display.getText();
|
||||
composition.visibility =
|
||||
visibilityFrom(display.getVisibility());
|
||||
composition.replyToPostId = display.getReplyToPostId();
|
||||
composition.replyToPostId =
|
||||
nonEmpty(display.getReplyToPostId());
|
||||
composition.contentWarning =
|
||||
nonEmpty(display.getContentWarning());
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
private static String
|
||||
nonEmpty(String s)
|
||||
{
|
||||
if (s.trim().isEmpty()) return null;
|
||||
return s;
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
@ -182,7 +203,7 @@ implements ActionListener {
|
||||
text;
|
||||
|
||||
private JTextField
|
||||
reply;
|
||||
reply, contentWarning;
|
||||
|
||||
private JComboBox<String>
|
||||
visibility;
|
||||
@ -217,6 +238,12 @@ implements ActionListener {
|
||||
this.visibility.setSelectedIndex(3);
|
||||
}
|
||||
|
||||
public void
|
||||
setContentWarning(String contentWarning)
|
||||
{
|
||||
this.contentWarning.setText(contentWarning);
|
||||
}
|
||||
|
||||
public String
|
||||
getText()
|
||||
{
|
||||
@ -229,6 +256,12 @@ implements ActionListener {
|
||||
return reply.getText();
|
||||
}
|
||||
|
||||
public String
|
||||
getContentWarning()
|
||||
{
|
||||
return contentWarning.getText();
|
||||
}
|
||||
|
||||
public String
|
||||
getVisibility()
|
||||
{
|
||||
@ -265,11 +298,21 @@ implements ActionListener {
|
||||
{
|
||||
this.primaire = primaire;
|
||||
|
||||
text = new JTextArea();
|
||||
text.setLineWrap(true);
|
||||
text.setWrapStyleWord(true);
|
||||
|
||||
reply = new JTextField();
|
||||
JLabel replyLabel = new JLabel("In reply to: ");
|
||||
replyLabel.setLabelFor(reply);
|
||||
|
||||
contentWarning = new JTextField();
|
||||
JLabel cwLabel = new JLabel("Content warning: ");
|
||||
cwLabel.setLabelFor(contentWarning);
|
||||
|
||||
JPanel top = new JPanel();
|
||||
top.setOpaque(false);
|
||||
top.setLayout(new GridLayout(2, 2, 8, 0));
|
||||
top.add(replyLabel);
|
||||
top.add(reply);
|
||||
top.add(cwLabel);
|
||||
top.add(contentWarning);
|
||||
|
||||
visibility = new JComboBox<>(new String[] {
|
||||
"Public",
|
||||
@ -289,11 +332,9 @@ implements ActionListener {
|
||||
bottom.add(Box.createHorizontalStrut(8));
|
||||
bottom.add(submit);
|
||||
|
||||
JPanel top = new JPanel();
|
||||
top.setOpaque(false);
|
||||
top.setLayout(new BorderLayout(8, 0));
|
||||
top.add(new JLabel("In reply to: "), BorderLayout.WEST);
|
||||
top.add(reply);
|
||||
text = new JTextArea();
|
||||
text.setLineWrap(true);
|
||||
text.setWrapStyleWord(true);
|
||||
|
||||
setLayout(new BorderLayout(0, 8));
|
||||
add(top, BorderLayout.NORTH);
|
||||
|
33
ImageApi.java
Normal file
@ -0,0 +1,33 @@
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import java.awt.Image;
|
||||
import java.awt.Toolkit;
|
||||
import java.net.URL;
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
interface
|
||||
ImageApi {
|
||||
|
||||
public static Image
|
||||
local(String name)
|
||||
{
|
||||
String path = "/graphics/" + name + ".png";
|
||||
URL url = ImageApi.class.getResource(name);
|
||||
if (url == null) return null;
|
||||
return new ImageIcon(url).getImage();
|
||||
}
|
||||
|
||||
public static Image
|
||||
remote(String urlr)
|
||||
{
|
||||
try {
|
||||
URL url = new URL(urlr);
|
||||
Toolkit TK = Toolkit.getDefaultToolkit();
|
||||
return TK.createImage(url);
|
||||
}
|
||||
catch (MalformedURLException eMu) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -99,7 +99,7 @@ ImageWindow extends JFrame {
|
||||
ImageWindow()
|
||||
{
|
||||
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||
setSize(400, 400);
|
||||
setSize(600, 600);
|
||||
|
||||
display = new ImageComponent(this);
|
||||
showAttachments(new Attachment[0]);
|
||||
@ -156,6 +156,7 @@ implements
|
||||
setPrev(Image image)
|
||||
{
|
||||
prev.setEnabled(image != null);
|
||||
prev.setText(image == null ? "<" : "");
|
||||
prev.setIcon(toIcon(image));
|
||||
}
|
||||
|
||||
@ -163,6 +164,7 @@ implements
|
||||
setNext(Image image)
|
||||
{
|
||||
next.setEnabled(image != null);
|
||||
next.setText(image == null ? ">" : "");
|
||||
next.setIcon(toIcon(image));
|
||||
}
|
||||
|
||||
@ -250,7 +252,8 @@ implements
|
||||
{
|
||||
if (image == null)
|
||||
{
|
||||
String str = "(There are no images being displayed.)";
|
||||
String str =
|
||||
"(There are no images being displayed.)";
|
||||
FontMetrics fm = g.getFontMetrics();
|
||||
int x = (getWidth() - fm.stringWidth(str)) / 2;
|
||||
int y = (getHeight() + fm.getHeight()) / 2;
|
||||
@ -285,15 +288,15 @@ implements
|
||||
{
|
||||
this.primaire = primaire;
|
||||
|
||||
Dimension BUTTON_SIZE = new Dimension(80, 60);
|
||||
Dimension BUTTON_SIZE = new Dimension(48, 48);
|
||||
|
||||
setOpaque(false);
|
||||
scaleImage = true;
|
||||
zoomLevel = 100;
|
||||
|
||||
prev = new JButton("<");
|
||||
prev = new JButton();
|
||||
toggle = new JButton("Show unscaled");
|
||||
next = new JButton(">");
|
||||
next = new JButton();
|
||||
prev.setPreferredSize(BUTTON_SIZE);
|
||||
next.setPreferredSize(BUTTON_SIZE);
|
||||
prev.addActionListener(this);
|
||||
@ -306,6 +309,9 @@ implements
|
||||
buttonArea.add(toggle);
|
||||
buttonArea.add(next);
|
||||
|
||||
setPrev(null);
|
||||
setNext(null);
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
add(buttonArea, BorderLayout.SOUTH);
|
||||
add(new Painter(), BorderLayout.CENTER);
|
||||
|
22
JKomasto.java
Executable file → Normal file
@ -28,6 +28,9 @@ JKomasto {
|
||||
private ImageWindow
|
||||
mediaWindow;
|
||||
|
||||
private TimelineWindowUpdater
|
||||
timelineWindowUpdater;
|
||||
|
||||
private MastodonApi
|
||||
api;
|
||||
|
||||
@ -52,6 +55,8 @@ JKomasto {
|
||||
loginWindow.dispose();
|
||||
autoViewWindow.setCursor(null);
|
||||
timelineWindow.setCursor(null);
|
||||
|
||||
timelineWindowUpdater.addWindow(timelineWindow);
|
||||
}
|
||||
|
||||
public PostWindow
|
||||
@ -87,6 +92,8 @@ JKomasto {
|
||||
mediaWindow.dispose();
|
||||
loginWindow.setLocationByPlatform(true);
|
||||
loginWindow.setVisible(true);
|
||||
|
||||
timelineWindowUpdater = new TimelineWindowUpdater(this);
|
||||
}
|
||||
|
||||
}
|
||||
@ -123,6 +130,9 @@ TimelinePage {
|
||||
public TimelineType
|
||||
type;
|
||||
|
||||
public String
|
||||
accountNumId;
|
||||
|
||||
public List<Post>
|
||||
posts;
|
||||
|
||||
@ -143,6 +153,12 @@ Post {
|
||||
public Image
|
||||
authorAvatar;
|
||||
|
||||
public String
|
||||
authorNumId;
|
||||
|
||||
public String
|
||||
boosterName;
|
||||
|
||||
public ZonedDateTime
|
||||
date;
|
||||
|
||||
@ -170,6 +186,9 @@ Attachment {
|
||||
public String
|
||||
url;
|
||||
|
||||
public String
|
||||
description;
|
||||
|
||||
public Image
|
||||
image;
|
||||
|
||||
@ -181,7 +200,8 @@ class
|
||||
Composition {
|
||||
|
||||
public String
|
||||
text;
|
||||
text,
|
||||
contentWarning;
|
||||
|
||||
public PostVisibility
|
||||
visibility;
|
||||
|
10
LoginWindow.java
Executable file → Normal file
@ -117,6 +117,7 @@ LoginWindow extends JFrame {
|
||||
requestSucceeded(Tree<String> json)
|
||||
{
|
||||
api.setAccountDetails(json);
|
||||
serverContacted = true;
|
||||
haveAccountDetails = true;
|
||||
updateStatusDisplay();
|
||||
}
|
||||
@ -146,7 +147,14 @@ LoginWindow extends JFrame {
|
||||
public void
|
||||
useInstanceUrl()
|
||||
{
|
||||
if (display.isAutoLoginToggled()) { useCache(); return; }
|
||||
|
||||
String url = display.getInstanceUrl();
|
||||
if (url.trim().isEmpty()) {
|
||||
// Should we show an error dialog..?
|
||||
display.setInstanceUrl("");
|
||||
return;
|
||||
}
|
||||
if (!hasProtocol(url)) {
|
||||
url = "https://" + url;
|
||||
display.setInstanceUrl(url);
|
||||
@ -157,8 +165,6 @@ LoginWindow extends JFrame {
|
||||
haveAccessToken = false;
|
||||
haveAccountDetails = false;
|
||||
|
||||
if (display.isAutoLoginToggled()) { useCache(); return; }
|
||||
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
api.testUrlConnection(url, new RequestListener() {
|
||||
|
||||
|
@ -11,7 +11,9 @@ import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
class
|
||||
MastodonApi {
|
||||
@ -77,7 +79,8 @@ MastodonApi {
|
||||
getAppCredentials(RequestListener handler)
|
||||
{
|
||||
assert instanceUrl != null;
|
||||
try {
|
||||
try
|
||||
{
|
||||
URL endpoint = new URL(instanceUrl + "/api/v1/apps");
|
||||
HttpURLConnection conn;
|
||||
conn = (HttpURLConnection)endpoint.openConnection();
|
||||
@ -164,7 +167,8 @@ MastodonApi {
|
||||
URL endpoint = new URL(instanceUrl + s);
|
||||
HttpURLConnection conn;
|
||||
conn = (HttpURLConnection)endpoint.openConnection();
|
||||
conn.setRequestProperty("Authorization", "Bearer " + token);
|
||||
String s2 = "Bearer " + token;
|
||||
conn.setRequestProperty("Authorization", s2);
|
||||
conn.connect();
|
||||
|
||||
doStandardJsonReturn(conn, handler);
|
||||
@ -174,13 +178,18 @@ MastodonApi {
|
||||
|
||||
public void
|
||||
getTimelinePage(
|
||||
TimelineType type, int count, String maxId, String minId,
|
||||
TimelineType type, String accountId,
|
||||
int count, String maxId, String minId,
|
||||
RequestListener handler)
|
||||
{
|
||||
String token = accessToken.get("access_token").value;
|
||||
|
||||
String url = instanceUrl + "/api/v1";
|
||||
switch (type)
|
||||
if (accountId != null)
|
||||
{
|
||||
url += "/accounts/" + accountId + "/statuses";
|
||||
}
|
||||
else switch (type)
|
||||
{
|
||||
case FEDERATED:
|
||||
case LOCAL: url += "/timelines/public"; break;
|
||||
@ -266,13 +275,15 @@ MastodonApi {
|
||||
|
||||
public void
|
||||
submit(
|
||||
String text, PostVisibility visibility, String replyTo,
|
||||
String text, PostVisibility visibility,
|
||||
String replyTo, String contentWarning,
|
||||
RequestListener handler)
|
||||
{
|
||||
String token = accessToken.get("access_token").value;
|
||||
|
||||
String visibilityParam = "direct";
|
||||
switch (visibility) {
|
||||
switch (visibility)
|
||||
{
|
||||
case PUBLIC: visibilityParam = "public"; break;
|
||||
case UNLISTED: visibilityParam = "unlisted"; break;
|
||||
case FOLLOWERS: visibilityParam = "private"; break;
|
||||
@ -283,7 +294,8 @@ MastodonApi {
|
||||
String url = instanceUrl + "/api/v1/statuses";
|
||||
try
|
||||
{
|
||||
text = URLEncoder.encode(text, "UTF-8");
|
||||
text = encode(text);
|
||||
contentWarning = encode(contentWarning);
|
||||
|
||||
URL endpoint = new URL(url);
|
||||
HttpURLConnection conn;
|
||||
@ -303,6 +315,9 @@ MastodonApi {
|
||||
if (replyTo != null) {
|
||||
output.write("&in_reply_to_id=" + replyTo);
|
||||
}
|
||||
if (contentWarning != null) {
|
||||
output.write("&spoiler_text=" + contentWarning);
|
||||
}
|
||||
output.close();
|
||||
|
||||
doStandardJsonReturn(conn, handler);
|
||||
@ -310,10 +325,57 @@ MastodonApi {
|
||||
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
||||
}
|
||||
|
||||
public void
|
||||
monitorTimeline(
|
||||
TimelineType type, ServerSideEventsListener handler)
|
||||
{
|
||||
String token = accessToken.get("access_token").value;
|
||||
|
||||
String url = instanceUrl + "/api/v1/streaming";
|
||||
switch (type)
|
||||
{
|
||||
case FEDERATED: url += "/public"; break;
|
||||
case LOCAL: url += "/public/local"; break;
|
||||
case HOME:
|
||||
case NOTIFICATIONS: url += "/user"; break;
|
||||
default: assert false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
URL endpoint = new URL(url);
|
||||
HttpURLConnection conn;
|
||||
conn = (HttpURLConnection)endpoint.openConnection();
|
||||
String s = "Bearer " + token;
|
||||
conn.setRequestProperty("Authorization", s);
|
||||
conn.connect();
|
||||
|
||||
InputStreamReader input;
|
||||
int code = conn.getResponseCode();
|
||||
if (code >= 300)
|
||||
{
|
||||
input = new InputStreamReader(conn.getErrorStream());
|
||||
Tree<String> response = JsonConverter.convert(input);
|
||||
input.close();
|
||||
handler.requestFailed(code, response);
|
||||
return;
|
||||
}
|
||||
|
||||
input = new InputStreamReader(conn.getInputStream());
|
||||
BufferedReader br = new BufferedReader(input);
|
||||
while (true) {
|
||||
String line = br.readLine();
|
||||
if (line != null) handler.lineReceived(line);
|
||||
}
|
||||
}
|
||||
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
private void
|
||||
doStandardJsonReturn(HttpURLConnection conn, RequestListener handler)
|
||||
doStandardJsonReturn(
|
||||
HttpURLConnection conn, RequestListener handler)
|
||||
throws IOException
|
||||
{
|
||||
InputStreamReader input;
|
||||
@ -334,7 +396,8 @@ MastodonApi {
|
||||
}
|
||||
|
||||
private void
|
||||
returnResponseInTree(HttpURLConnection conn, RequestListener handler)
|
||||
returnResponseInTree(
|
||||
HttpURLConnection conn, RequestListener handler)
|
||||
throws IOException
|
||||
{
|
||||
InputStreamReader input;
|
||||
@ -371,6 +434,19 @@ MastodonApi {
|
||||
return doc;
|
||||
}
|
||||
|
||||
private static String
|
||||
encode(String s)
|
||||
{
|
||||
try {
|
||||
if (s == null) return null;
|
||||
return URLEncoder.encode(s, "UTF-8");
|
||||
}
|
||||
catch (UnsupportedEncodingException eUe) {
|
||||
assert false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public void
|
||||
|
37
PostWindow.java
Executable file → Normal file
@ -21,6 +21,7 @@ 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;
|
||||
@ -103,6 +104,7 @@ implements ActionListener {
|
||||
? null
|
||||
: post.attachments[0].image
|
||||
);
|
||||
|
||||
repliesDisplay.setReplies(replies);
|
||||
postDisplay.resetFocus();
|
||||
repaint();
|
||||
@ -111,7 +113,11 @@ implements ActionListener {
|
||||
public void
|
||||
openAuthorProfile()
|
||||
{
|
||||
|
||||
TimelineWindow w = new TimelineWindow(primaire);
|
||||
w.showAuthorPosts(post.authorNumId);
|
||||
w.showLatestPage();
|
||||
w.setLocationRelativeTo(this);
|
||||
w.setVisible(true);
|
||||
}
|
||||
|
||||
public void
|
||||
@ -151,8 +157,9 @@ implements ActionListener {
|
||||
|
||||
};
|
||||
api.setPostFavourited(post.postId, favourited, handler);
|
||||
postDisplay.setFavouriteBoostEnabled(true);
|
||||
postDisplay.setCursor(null);
|
||||
postDisplay.setFavouriteBoostEnabled(true);
|
||||
postDisplay.repaint();
|
||||
}
|
||||
|
||||
public void
|
||||
@ -192,8 +199,9 @@ implements ActionListener {
|
||||
|
||||
};
|
||||
api.setPostBoosted(post.postId, boosted, handler);
|
||||
postDisplay.setFavouriteBoostEnabled(true);
|
||||
postDisplay.setCursor(null);
|
||||
postDisplay.setFavouriteBoostEnabled(true);
|
||||
postDisplay.repaint();
|
||||
}
|
||||
|
||||
public void
|
||||
@ -217,7 +225,7 @@ implements ActionListener {
|
||||
int l = Math.min(40, post.text.length());
|
||||
w.setTitle(post.text.substring(0, l));
|
||||
if (!w.isVisible()) {
|
||||
w.setLocation(getX(), getY() + 100);
|
||||
w.setLocationRelativeTo(null);
|
||||
w.setVisible(true);
|
||||
}
|
||||
}
|
||||
@ -227,7 +235,7 @@ implements ActionListener {
|
||||
public void
|
||||
actionPerformed(ActionEvent eA)
|
||||
{
|
||||
Object src = eA.getSource();
|
||||
Component src = (Component)eA.getSource();
|
||||
if (!(src instanceof JMenuItem)) return;
|
||||
String text = ((JMenuItem)src).getText();
|
||||
|
||||
@ -358,7 +366,7 @@ implements ActionListener {
|
||||
public void
|
||||
actionPerformed(ActionEvent eA)
|
||||
{
|
||||
Object src = eA.getSource();
|
||||
Component src = (Component)eA.getSource();
|
||||
String command = eA.getActionCommand();
|
||||
|
||||
if (src == profile)
|
||||
@ -412,8 +420,8 @@ implements ActionListener {
|
||||
g.clearRect(0, 0, getWidth(), getHeight());
|
||||
|
||||
((java.awt.Graphics2D)g).setRenderingHint(
|
||||
java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||
java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON
|
||||
java.awt.RenderingHints.KEY_ANTIALIASING,
|
||||
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
|
||||
);
|
||||
|
||||
Font f1 = new Font("IPAGothic", Font.PLAIN, 16);
|
||||
@ -454,13 +462,6 @@ implements ActionListener {
|
||||
|
||||
// - -%- -
|
||||
|
||||
private static ImageIcon
|
||||
toIcon(Image image)
|
||||
{
|
||||
if (image == null) return null;
|
||||
return new ImageIcon(image);
|
||||
}
|
||||
|
||||
private static List<String>
|
||||
split(String string, int lineLength)
|
||||
{
|
||||
@ -472,7 +473,7 @@ implements ActionListener {
|
||||
if (word.length() >= lineLength) {
|
||||
word = word.substring(0, lineLength - 4) + "...";
|
||||
}
|
||||
if (word.equals("\n")) {
|
||||
if (word.matches("\n")) {
|
||||
returnee.add(empty(line));
|
||||
continue;
|
||||
}
|
||||
@ -504,9 +505,9 @@ implements ActionListener {
|
||||
authorName = authorId = time = text = "";
|
||||
|
||||
Dimension buttonSize = new Dimension(20, 40);
|
||||
Border b = BorderFactory.createEmptyBorder(10, 10, 10, 10);
|
||||
|
||||
profile = new RoundButton();
|
||||
//profile.setPreferredSize(buttonSize);
|
||||
profile.addActionListener(this);
|
||||
|
||||
favouriteBoost = new TwoToggleButton("favourite", "boost");
|
||||
@ -537,7 +538,7 @@ implements ActionListener {
|
||||
Box buttons = Box.createVerticalBox();
|
||||
buttons.setOpaque(false);
|
||||
buttons.add(ibuttons);
|
||||
buttons.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
buttons.setBorder(b);
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
add(buttons, BorderLayout.WEST);
|
||||
|
@ -15,3 +15,17 @@ RequestListener {
|
||||
requestSucceeded(Tree<String> json);
|
||||
|
||||
}
|
||||
|
||||
interface
|
||||
ServerSideEventsListener {
|
||||
|
||||
void
|
||||
connectionFailed(IOException eIo);
|
||||
|
||||
void
|
||||
requestFailed(int httpCode, Tree<String> json);
|
||||
|
||||
void
|
||||
lineReceived(String line);
|
||||
|
||||
}
|
||||
|
282
TimelineWindow.java
Executable file → Normal file
@ -9,7 +9,6 @@ import javax.swing.JMenuItem;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.BorderFactory;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.GridBagLayout;
|
||||
@ -30,6 +29,10 @@ import java.awt.event.ActionListener;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import cafe.biskuteri.hinoki.Tree;
|
||||
import java.io.IOException;
|
||||
@ -59,8 +62,6 @@ implements ActionListener {
|
||||
|
||||
private JMenuItem
|
||||
openHome,
|
||||
// umm, what about the timeline that's like, notes that your
|
||||
// post was favourited or replied to? those aren't messages..
|
||||
openMessages,
|
||||
openLocal,
|
||||
openFederated,
|
||||
@ -82,21 +83,29 @@ implements ActionListener {
|
||||
setTimelineType(TimelineType type)
|
||||
{
|
||||
page.type = type;
|
||||
page.accountNumId = null;
|
||||
|
||||
String s1 = type.toString();
|
||||
s1 = s1.charAt(0) + s1.substring(1).toLowerCase();
|
||||
setTitle(s1 + " - JKomasto");
|
||||
|
||||
String s2 = type.toString().toLowerCase();
|
||||
s2 = "/graphics/" + s2 + ".png";
|
||||
URL url = getClass().getResource(s2);
|
||||
if (url != null) {
|
||||
ImageIcon icon = new ImageIcon(url);
|
||||
display.setBackgroundImage(icon.getImage());
|
||||
}
|
||||
else {
|
||||
display.setBackgroundImage(null);
|
||||
display.setBackgroundImage(ImageApi.local(s2));
|
||||
}
|
||||
|
||||
public TimelineType
|
||||
getTimelineType() { return page.type; }
|
||||
|
||||
public void
|
||||
showAuthorPosts(String authorNumId)
|
||||
{
|
||||
assert authorNumId != null;
|
||||
|
||||
page.type = TimelineType.FEDERATED;
|
||||
page.accountNumId = authorNumId;
|
||||
|
||||
setTitle(authorNumId + " - JKomasto");
|
||||
display.setBackgroundImage(ImageApi.local("profile"));
|
||||
}
|
||||
|
||||
public void
|
||||
@ -104,12 +113,14 @@ implements ActionListener {
|
||||
{
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
api.getTimelinePage(
|
||||
page.type, PREVIEW_COUNT, null, null,
|
||||
page.type, page.accountNumId,
|
||||
PREVIEW_COUNT, null, null,
|
||||
new RequestListener() {
|
||||
|
||||
public void
|
||||
connectionFailed(IOException eIo)
|
||||
{
|
||||
eIo.printStackTrace();
|
||||
String s = eIo.getClass().getName();
|
||||
setTitle(s + " - JKomasto");
|
||||
}
|
||||
@ -117,6 +128,7 @@ implements ActionListener {
|
||||
public void
|
||||
requestFailed(int httpCode, Tree<String> json)
|
||||
{
|
||||
System.err.println(json.get("error").value);
|
||||
setTitle(httpCode + " - JKomasto");
|
||||
// lol...
|
||||
}
|
||||
@ -124,9 +136,13 @@ implements ActionListener {
|
||||
public void
|
||||
requestSucceeded(Tree<String> json)
|
||||
{
|
||||
page.posts = toPosts(json);
|
||||
List<Post> posts = toPosts(json);
|
||||
page.posts = posts;
|
||||
display.setPosts(page.posts);
|
||||
display.setNextPageAvailable(true);
|
||||
boolean full = posts.size() >= PREVIEW_COUNT;
|
||||
display.setNextPageAvailable(full);
|
||||
display.setPreviousPageAvailable(true);
|
||||
display.resetFocus();
|
||||
}
|
||||
|
||||
}
|
||||
@ -143,7 +159,8 @@ implements ActionListener {
|
||||
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
api.getTimelinePage(
|
||||
page.type, PREVIEW_COUNT, last.postId, null,
|
||||
page.type, page.accountNumId,
|
||||
PREVIEW_COUNT, last.postId, null,
|
||||
new RequestListener() {
|
||||
|
||||
public void
|
||||
@ -162,9 +179,19 @@ implements ActionListener {
|
||||
public void
|
||||
requestSucceeded(Tree<String> json)
|
||||
{
|
||||
page.posts = toPosts(json);
|
||||
List<Post> posts = toPosts(json);
|
||||
if (posts.size() == 0) {
|
||||
// We should probably say something
|
||||
// to the user here? For now, we
|
||||
// quietly cancel.
|
||||
return;
|
||||
}
|
||||
page.posts = posts;
|
||||
display.setPosts(page.posts);
|
||||
boolean full = posts.size() >= PREVIEW_COUNT;
|
||||
display.setNextPageAvailable(full);
|
||||
display.setPreviousPageAvailable(true);
|
||||
display.resetFocus();
|
||||
}
|
||||
|
||||
}
|
||||
@ -181,7 +208,8 @@ implements ActionListener {
|
||||
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
api.getTimelinePage(
|
||||
page.type, PREVIEW_COUNT, null, first.postId,
|
||||
page.type, page.accountNumId,
|
||||
PREVIEW_COUNT, null, first.postId,
|
||||
new RequestListener() {
|
||||
|
||||
public void
|
||||
@ -200,17 +228,21 @@ implements ActionListener {
|
||||
public void
|
||||
requestSucceeded(Tree<String> json)
|
||||
{
|
||||
page.posts = toPosts(json);
|
||||
List<Post> posts = toPosts(json);
|
||||
if (posts.size() < PREVIEW_COUNT) {
|
||||
showLatestPage();
|
||||
return;
|
||||
}
|
||||
page.posts = posts;
|
||||
display.setPosts(page.posts);
|
||||
display.setNextPageAvailable(true);
|
||||
display.setPreviousPageAvailable(true);
|
||||
display.resetFocus();
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
display.setCursor(null);
|
||||
|
||||
if (page.posts.size() < PREVIEW_COUNT) showLatestPage();
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
@ -221,6 +253,15 @@ implements ActionListener {
|
||||
primaire.getAutoViewWindow().showPost(post);
|
||||
}
|
||||
|
||||
public void
|
||||
postOpened(Post post)
|
||||
{
|
||||
PostWindow w = new PostWindow(primaire);
|
||||
w.showPost(post);
|
||||
w.setLocationRelativeTo(this);
|
||||
w.setVisible(true);
|
||||
}
|
||||
|
||||
public void
|
||||
actionPerformed(ActionEvent eA)
|
||||
{
|
||||
@ -278,6 +319,13 @@ implements ActionListener {
|
||||
{
|
||||
Post addee = new Post();
|
||||
|
||||
if (post.get("reblog").size() != 0) {
|
||||
Tree<String> a = post.get("account");
|
||||
String s = a.get("display_name").value;
|
||||
addee.boosterName = s;
|
||||
post = post.get("reblog");
|
||||
}
|
||||
|
||||
addee.postId = post.get("id").value;
|
||||
|
||||
try {
|
||||
@ -323,21 +371,16 @@ implements ActionListener {
|
||||
else addee.contentWarning = null;
|
||||
|
||||
Tree<String> account = post.get("account");
|
||||
if (post.get("reblog").size() != 0) {
|
||||
account = post.get("reblog").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;
|
||||
try {
|
||||
String av = account.get("avatar").value;
|
||||
ImageIcon icon = new ImageIcon(new URL(av));
|
||||
addee.authorAvatar = icon.getImage();
|
||||
}
|
||||
catch (MalformedURLException eMu) {
|
||||
// Weird bug on their part.. We should
|
||||
// probably react by using a default avatar.
|
||||
String s3 = account.get("avatar").value;
|
||||
addee.authorAvatar = ImageApi.remote(s3);
|
||||
if (addee.authorAvatar == null) {
|
||||
s3 = "defaultAvatar";
|
||||
addee.authorAvatar = ImageApi.local(s3);
|
||||
}
|
||||
|
||||
String f = post.get("favourited").value;
|
||||
@ -345,29 +388,23 @@ implements ActionListener {
|
||||
addee.favourited = f.equals("true");
|
||||
addee.boosted = b.equals("true");
|
||||
|
||||
Tree<String> a1 = post.get("media_attachments");
|
||||
Attachment[] a2 = new Attachment[a1.size()];
|
||||
for (int o = 0; o < a2.length; ++o)
|
||||
Tree<String> as1 = post.get("media_attachments");
|
||||
Attachment[] as2 = new Attachment[as1.size()];
|
||||
for (int o = 0; o < as2.length; ++o)
|
||||
{
|
||||
a2[o] = new Attachment();
|
||||
a2[o].type = a1.get(o).get("type").value;
|
||||
Tree<String> a1 = as1.get(o);
|
||||
Attachment a2 = as2[o] = new Attachment();
|
||||
|
||||
a2[o].url = a1.get(o).get("remote_url").value;
|
||||
if (a2[o].url == null)
|
||||
a2[o].url = a1.get(o).get("text_url").value;
|
||||
|
||||
a2[o].image = null;
|
||||
if (a2[o].type.equals("image")) try
|
||||
{
|
||||
URL url = new URL(a2[o].url);
|
||||
ImageIcon icon = new ImageIcon(url);
|
||||
String desc = a1.get(o).get("description").value;
|
||||
icon.setDescription(desc);
|
||||
a2[o].image = icon.getImage();
|
||||
a2.type = a1.get("type").value;
|
||||
String u1 = a1.get("remote_url").value;
|
||||
String u2 = a1.get("text_url").value;
|
||||
a2.url = u1 == null ? u2 : u1;
|
||||
a2.description = a1.get("description").value;
|
||||
a2.image = null;
|
||||
if (a2.type.equals("image"))
|
||||
a2.image = ImageApi.remote(a2.url);
|
||||
}
|
||||
catch (MalformedURLException eMu) { }
|
||||
}
|
||||
addee.attachments = a2;
|
||||
addee.attachments = as2;
|
||||
|
||||
posts.add(addee);
|
||||
}
|
||||
@ -418,6 +455,7 @@ implements ActionListener {
|
||||
flipToNewestPost.addActionListener(this);
|
||||
|
||||
JMenu programMenu = new JMenu("Program");
|
||||
programMenu.setMnemonic(KeyEvent.VK_P);
|
||||
programMenu.add(openHome);
|
||||
programMenu.add(openFederated);
|
||||
programMenu.add(new JSeparator());
|
||||
@ -426,6 +464,7 @@ implements ActionListener {
|
||||
programMenu.add(new JSeparator());
|
||||
programMenu.add(quit);
|
||||
JMenu timelineMenu = new JMenu("Timeline");
|
||||
timelineMenu.setMnemonic(KeyEvent.VK_T);
|
||||
timelineMenu.add(flipToNewestPost);
|
||||
JMenuBar menuBar = new JMenuBar();
|
||||
menuBar.add(programMenu);
|
||||
@ -449,7 +488,9 @@ implements ActionListener {
|
||||
|
||||
class
|
||||
TimelineComponent extends JPanel
|
||||
implements ActionListener, MouseListener {
|
||||
implements
|
||||
ActionListener, KeyListener,
|
||||
MouseListener, FocusListener {
|
||||
|
||||
private TimelineWindow
|
||||
primaire;
|
||||
@ -494,14 +535,23 @@ implements ActionListener, MouseListener {
|
||||
Post p = posts.get(o);
|
||||
c.setTopLeft(p.authorName);
|
||||
{
|
||||
String f = "";
|
||||
if (p.boosterName != null)
|
||||
f += "b";
|
||||
if (p.attachments.length > 0)
|
||||
f += "a";
|
||||
|
||||
String t;
|
||||
ZonedDateTime now = ZonedDateTime.now();
|
||||
long d = ChronoUnit.SECONDS.between(p.date, now);
|
||||
long s = Math.abs(d);
|
||||
if (s < 30) c.setTopRight("now");
|
||||
else if (s < 60) c.setTopRight(d + "s");
|
||||
else if (s < 3600) c.setTopRight((d / 60) + "m");
|
||||
else if (s < 86400) c.setTopRight((d / 3600) + "h");
|
||||
else c.setTopRight((d / 86400) + "d");
|
||||
if (s < 30) t = "now";
|
||||
else if (s < 60) t = d + "s";
|
||||
else if (s < 3600) t = (d / 60) + "m";
|
||||
else if (s < 86400) t = (d / 3600) + "h";
|
||||
else t = (d / 86400) + "d";
|
||||
|
||||
c.setTopRight(f + " " + t);
|
||||
}
|
||||
if (p.contentWarning != null)
|
||||
c.setBottom("(" + p.contentWarning + ")");
|
||||
@ -533,11 +583,19 @@ implements ActionListener, MouseListener {
|
||||
public void
|
||||
setBackgroundImage(Image n) { backgroundImage = n; }
|
||||
|
||||
public void
|
||||
resetFocus() { postPreviews.get(0).requestFocusInWindow(); }
|
||||
|
||||
// - -%- -
|
||||
|
||||
protected void
|
||||
paintComponent(Graphics g)
|
||||
{
|
||||
((java.awt.Graphics2D)g).setRenderingHint(
|
||||
java.awt.RenderingHints.KEY_ANTIALIASING,
|
||||
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
|
||||
);
|
||||
|
||||
int w = getWidth(), h = getHeight();
|
||||
g.clearRect(0, 0, w, h);
|
||||
int h2 = h * 5 / 10, w2 = h2;
|
||||
@ -545,6 +603,60 @@ implements ActionListener, MouseListener {
|
||||
g.drawImage(backgroundImage, x, y, w2, h2, this);
|
||||
}
|
||||
|
||||
private void
|
||||
select(Object c)
|
||||
{
|
||||
assert c instanceof PostPreviewComponent;
|
||||
PostPreviewComponent p = (PostPreviewComponent)c;
|
||||
|
||||
int offset = postPreviews.indexOf(p);
|
||||
assert offset != -1;
|
||||
if (offset < posts.size()) {
|
||||
primaire.postSelected(posts.get(offset));
|
||||
p.setSelected(true);
|
||||
}
|
||||
else {
|
||||
p.setSelected(false);
|
||||
}
|
||||
p.repaint();
|
||||
}
|
||||
|
||||
private void
|
||||
deselect(Object c)
|
||||
{
|
||||
assert c instanceof PostPreviewComponent;
|
||||
PostPreviewComponent p = (PostPreviewComponent)c;
|
||||
|
||||
p.setSelected(false);
|
||||
p.repaint();
|
||||
}
|
||||
|
||||
private void
|
||||
open(Object c)
|
||||
{
|
||||
assert c instanceof PostPreviewComponent;
|
||||
PostPreviewComponent p = (PostPreviewComponent)c;
|
||||
|
||||
int offset = postPreviews.indexOf(p);
|
||||
assert offset != -1;
|
||||
if (offset < posts.size()) {
|
||||
primaire.postOpened(posts.get(offset));
|
||||
}
|
||||
}
|
||||
|
||||
public void
|
||||
focusGained(FocusEvent eF) { select(eF.getSource()); }
|
||||
|
||||
public void
|
||||
focusLost(FocusEvent eF) { deselect(eF.getSource()); }
|
||||
|
||||
public void
|
||||
mouseClicked(MouseEvent eM)
|
||||
{
|
||||
if (eM.getClickCount() == 2) open(eM.getSource());
|
||||
else select(eM.getSource());
|
||||
}
|
||||
|
||||
public void
|
||||
mouseEntered(MouseEvent eM)
|
||||
{
|
||||
@ -552,33 +664,27 @@ implements ActionListener, MouseListener {
|
||||
mouseClicked(eM);
|
||||
}
|
||||
|
||||
// (知) First time I'm using one of these..!
|
||||
|
||||
public void
|
||||
mouseClicked(MouseEvent eM)
|
||||
{
|
||||
int offset = postPreviews.indexOf(eM.getSource());
|
||||
assert offset != -1;
|
||||
primaire.postSelected(posts.get(offset));
|
||||
postPreviews.get(offset).setSelected(true);
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void
|
||||
mouseExited(MouseEvent eM)
|
||||
{
|
||||
if (!hoverSelect) return;
|
||||
int offset = postPreviews.indexOf(eM.getSource());
|
||||
assert offset != -1;
|
||||
postPreviews.get(offset).setSelected(false);
|
||||
repaint();
|
||||
deselect(eM.getSource());
|
||||
}
|
||||
|
||||
public void
|
||||
mousePressed(MouseEvent eM) { }
|
||||
// (知) First time I'm using these two..!
|
||||
|
||||
public void
|
||||
mouseReleased(MouseEvent eM) { }
|
||||
keyPressed(KeyEvent eK)
|
||||
{
|
||||
if (eK.getKeyCode() != KeyEvent.VK_ENTER) return;
|
||||
|
||||
PostPreviewComponent selected = null;
|
||||
for (PostPreviewComponent c: postPreviews)
|
||||
if (c.getSelected()) selected = c;
|
||||
|
||||
if (selected == null) return;
|
||||
open(selected);
|
||||
}
|
||||
|
||||
public void
|
||||
actionPerformed(ActionEvent eA)
|
||||
@ -595,6 +701,19 @@ implements ActionListener, MouseListener {
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
public void
|
||||
mousePressed(MouseEvent eM) { }
|
||||
|
||||
public void
|
||||
mouseReleased(MouseEvent eM) { }
|
||||
|
||||
public void
|
||||
keyTyped(KeyEvent eK) { }
|
||||
|
||||
public void
|
||||
keyReleased(KeyEvent eK) { }
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
TimelineComponent(TimelineWindow primaire)
|
||||
@ -608,6 +727,8 @@ implements ActionListener, MouseListener {
|
||||
next = new JButton(">");
|
||||
prev.setEnabled(false);
|
||||
next.setEnabled(false);
|
||||
prev.setMnemonic(KeyEvent.VK_PAGE_UP);
|
||||
next.setMnemonic(KeyEvent.VK_PAGE_DOWN);
|
||||
prev.addActionListener(this);
|
||||
next.addActionListener(this);
|
||||
|
||||
@ -632,6 +753,8 @@ implements ActionListener, MouseListener {
|
||||
PostPreviewComponent c = new PostPreviewComponent();
|
||||
c.reset();
|
||||
c.addMouseListener(this);
|
||||
c.addFocusListener(this);
|
||||
c.addKeyListener(this);
|
||||
centre.add(c, constraints);
|
||||
postPreviews.add(c);
|
||||
}
|
||||
@ -683,6 +806,9 @@ PostPreviewComponent extends JComponent {
|
||||
else setBackground(new Color(0, 0, 0, 25));
|
||||
}
|
||||
|
||||
public boolean
|
||||
getSelected() { return selected; }
|
||||
|
||||
// - -%- -
|
||||
|
||||
protected void
|
||||
@ -699,8 +825,6 @@ PostPreviewComponent extends JComponent {
|
||||
public
|
||||
PostPreviewComponent()
|
||||
{
|
||||
selected = false;
|
||||
|
||||
Font f = new JLabel().getFont();
|
||||
Font f1 = f.deriveFont(Font.PLAIN, 12f);
|
||||
Font f2 = f.deriveFont(Font.ITALIC, 12f);
|
||||
@ -725,8 +849,8 @@ PostPreviewComponent extends JComponent {
|
||||
bottom.setFont(f3);
|
||||
bottom.setOpaque(false);
|
||||
|
||||
setFocusable(true);
|
||||
setOpaque(false);
|
||||
setSelected(false);
|
||||
setLayout(new BorderLayout());
|
||||
add(top, BorderLayout.NORTH);
|
||||
add(bottom);
|
||||
|
177
TimelineWindowUpdater.java
Normal file
@ -0,0 +1,177 @@
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.io.IOException;
|
||||
import cafe.biskuteri.hinoki.Tree;
|
||||
|
||||
class
|
||||
TimelineWindowUpdater {
|
||||
|
||||
private JKomasto
|
||||
primaire;
|
||||
|
||||
private MastodonApi
|
||||
api;
|
||||
|
||||
// - -%- -
|
||||
|
||||
private List<TimelineWindow>
|
||||
updatees;
|
||||
|
||||
private StringBuilder
|
||||
event, data;
|
||||
|
||||
// - -%- -
|
||||
|
||||
private Thread
|
||||
federated,
|
||||
local,
|
||||
home,
|
||||
notifications;
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public void
|
||||
addWindow(TimelineWindow updatee)
|
||||
{
|
||||
updatees.add(updatee);
|
||||
|
||||
Connection c = new Connection();
|
||||
c.type = updatee.getTimelineType();
|
||||
Thread t = new Thread(c);
|
||||
switch (c.type) {
|
||||
case FEDERATED:
|
||||
if (federated != null) return;
|
||||
federated = t; break;
|
||||
case LOCAL:
|
||||
if (local != null) return;
|
||||
local = t; break;
|
||||
case HOME:
|
||||
if (home != null) return;
|
||||
home = t; break;
|
||||
case NOTIFICATIONS:
|
||||
if (notifications != null) return;
|
||||
notifications = t; break;
|
||||
default: return;
|
||||
}
|
||||
t.start();
|
||||
|
||||
System.err.println(t);
|
||||
}
|
||||
|
||||
public void
|
||||
removeWindow(TimelineWindow updatee)
|
||||
{
|
||||
updatees.remove(updatee);
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
private void
|
||||
handle(TimelineType type, String event, String data)
|
||||
{
|
||||
System.err.println("Handling " + event + ".");
|
||||
|
||||
assert !data.isEmpty();
|
||||
if (event.isEmpty()) return;
|
||||
|
||||
boolean newPost = event.equals("update");
|
||||
boolean newNotif = event.equals("notification");
|
||||
if (!(newPost || newNotif)) return;
|
||||
|
||||
for (TimelineWindow updatee: filter(type)) {
|
||||
System.err.println("Refreshing " + updatee);
|
||||
updatee.showLatestPage();
|
||||
}
|
||||
}
|
||||
|
||||
private List<TimelineWindow>
|
||||
filter(TimelineType type)
|
||||
{
|
||||
List<TimelineWindow> returnee = new ArrayList<>();
|
||||
for (TimelineWindow updatee: updatees)
|
||||
if (updatee.getTimelineType() == type)
|
||||
returnee.add(updatee);
|
||||
return returnee;
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
private class
|
||||
Connection
|
||||
implements Runnable, ServerSideEventsListener {
|
||||
|
||||
private TimelineType
|
||||
type;
|
||||
|
||||
// -=-
|
||||
|
||||
private StringBuilder
|
||||
event, data;
|
||||
|
||||
// -=%=-
|
||||
|
||||
public void
|
||||
run()
|
||||
{
|
||||
event = new StringBuilder();
|
||||
data = new StringBuilder();
|
||||
api.monitorTimeline(type, this);
|
||||
// monitorTimeline should not return
|
||||
// until the connection is closed.
|
||||
System.err.println("Finit.");
|
||||
}
|
||||
|
||||
public void
|
||||
lineReceived(String line)
|
||||
{
|
||||
System.err.println("Line: " + line);
|
||||
|
||||
if (line.startsWith(":")) return;
|
||||
|
||||
if (line.isEmpty()) {
|
||||
handle(type, event.toString(), data.toString());
|
||||
event.delete(0, event.length());
|
||||
data.delete(0, event.length());
|
||||
}
|
||||
|
||||
if (line.startsWith("data: "))
|
||||
data.append(line.substring("data: ".length()));
|
||||
|
||||
if (line.startsWith("event: "))
|
||||
event.append(line.substring("event: ".length()));
|
||||
|
||||
/*
|
||||
* Note that I utterly ignore https://html.spec.whatwg.org
|
||||
* /multipage/server-sent-events.html#dispatchMessage.
|
||||
* That is because I am not a browser.
|
||||
*/
|
||||
}
|
||||
|
||||
public void
|
||||
connectionFailed(IOException eIo)
|
||||
{
|
||||
// sais pas dois-je faire..
|
||||
eIo.printStackTrace();
|
||||
}
|
||||
|
||||
public void
|
||||
requestFailed(int httpCode, Tree<String> json)
|
||||
{
|
||||
// mo shiranu
|
||||
System.err.println(httpCode + ", " + json.get("error").value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
TimelineWindowUpdater(JKomasto primaire)
|
||||
{
|
||||
this.primaire = primaire;
|
||||
this.api = primaire.getMastodonApi();
|
||||
|
||||
this.updatees = new ArrayList<>();
|
||||
}
|
||||
|
||||
}
|
26
TwoToggleButton.java
Executable file → Normal file
@ -133,6 +133,7 @@ implements KeyListener, MouseListener, FocusListener {
|
||||
case MouseEvent.BUTTON1: togglePrimary(); break;
|
||||
case MouseEvent.BUTTON3: toggleSecondary(); break;
|
||||
}
|
||||
requestFocusInWindow();
|
||||
}
|
||||
|
||||
public void
|
||||
@ -142,6 +143,7 @@ implements KeyListener, MouseListener, FocusListener {
|
||||
case KeyEvent.VK_SPACE: togglePrimary(); break;
|
||||
case KeyEvent.VK_ENTER: toggleSecondary(); break;
|
||||
}
|
||||
requestFocusInWindow();
|
||||
}
|
||||
|
||||
public void
|
||||
@ -241,6 +243,8 @@ implements KeyListener, MouseListener, FocusListener {
|
||||
private Image
|
||||
image;
|
||||
|
||||
// - -%- -
|
||||
|
||||
private int
|
||||
nextEventID = ActionEvent.ACTION_FIRST;
|
||||
|
||||
@ -261,11 +265,6 @@ implements KeyListener, MouseListener, FocusListener {
|
||||
protected void
|
||||
paintComponent(Graphics g)
|
||||
{
|
||||
((java.awt.Graphics2D)g).setRenderingHint(
|
||||
java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||
java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON
|
||||
);
|
||||
|
||||
g.drawImage(button, 0, 0, this);
|
||||
if (!isEnabled())
|
||||
g.drawImage(disabledOverlay, 0, 0, this);
|
||||
@ -273,12 +272,13 @@ implements KeyListener, MouseListener, FocusListener {
|
||||
g.drawImage(selectedOverlay, 0, 0, this);
|
||||
|
||||
if (image == null) return;
|
||||
|
||||
int w1 = button.getWidth(this);
|
||||
int h1 = button.getHeight(this);
|
||||
Shape defaultClip = g.getClip();
|
||||
Shape roundClip = new Ellipse2D.Float(6, 6, w1 - 12, h1 - 12);
|
||||
int w2 = image.getWidth(this);
|
||||
int h2 = image.getHeight(this);
|
||||
if (w2 == -1) w2 = w1;
|
||||
if (h2 == -1) h2 = h1;
|
||||
if (h2 > w2) {
|
||||
h2 = h2 * w1 / w2;
|
||||
w2 = w1;
|
||||
@ -287,8 +287,14 @@ implements KeyListener, MouseListener, FocusListener {
|
||||
w2 = w2 * h1 / h2;
|
||||
h2 = h1;
|
||||
}
|
||||
Shape defaultClip, roundClip;
|
||||
defaultClip = g.getClip();
|
||||
roundClip = new Ellipse2D.Float(6, 6, w1 - 12, h1 - 12);
|
||||
|
||||
g.setClip(roundClip);
|
||||
g.drawImage(image, 0, 0, w2, h2, this);
|
||||
g.drawImage(image, 0, 0, w2, h2, getParent());
|
||||
// I don't know why, but when we repaint ourselves, our
|
||||
// parent doesn't repaint, so nothing seems to happen.
|
||||
g.setClip(defaultClip);
|
||||
}
|
||||
|
||||
@ -348,8 +354,8 @@ implements KeyListener, MouseListener, FocusListener {
|
||||
setFocusable(true);
|
||||
setOpaque(false);
|
||||
|
||||
int w = button.getWidth(null);
|
||||
int h = button.getHeight(null);
|
||||
int w = button.getWidth(this);
|
||||
int h = button.getHeight(this);
|
||||
setPreferredSize(new Dimension(w, h));
|
||||
|
||||
this.addKeyListener(this);
|
||||
|
0
graphics/Flags.xcf
Executable file → Normal file
0
graphics/Hourglass.xcf
Executable file → Normal file
0
graphics/button.png
Executable file → Normal file
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
0
graphics/disabledOverlay.png
Executable file → Normal file
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
0
graphics/favouriteToggled.png
Executable file → Normal file
Before Width: | Height: | Size: 353 B After Width: | Height: | Size: 353 B |
0
graphics/ref1.png
Executable file → Normal file
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
0
graphics/selectedOverlay.png
Executable file → Normal file
Before Width: | Height: | Size: 313 B After Width: | Height: | Size: 313 B |
0
graphics/test1.png
Executable file → Normal file
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
0
graphics/test2.png
Executable file → Normal file
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
0
graphics/test3.png
Executable file → Normal file
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
0
graphics/test4.png
Executable file → Normal file
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |