Moved image methods to ImageApi.

Added testing implementation of updater.
This commit is contained in:
Snowyfox 2022-04-14 00:38:49 -04:00
parent 28c7da2034
commit 511ca1aeef
22 changed files with 664 additions and 160 deletions

65
ComposeWindow.java Executable file → Normal file
View File

@ -9,6 +9,7 @@ import javax.swing.JButton;
import javax.swing.Box; import javax.swing.Box;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import java.awt.GridLayout;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
@ -52,6 +53,7 @@ ComposeWindow extends JFrame {
composition.text = ""; composition.text = "";
composition.visibility = PostVisibility.MENTIONED; composition.visibility = PostVisibility.MENTIONED;
composition.replyToPostId = null; composition.replyToPostId = null;
composition.contentWarning = null;
syncDisplayToComposition(); syncDisplayToComposition();
} }
@ -59,10 +61,16 @@ ComposeWindow extends JFrame {
submit() submit()
{ {
syncCompositionToDisplay(); syncCompositionToDisplay();
if (composition.replyToPostId != null)
assert !composition.replyToPostId.trim().isEmpty();
if (composition.contentWarning != null)
assert !composition.contentWarning.trim().isEmpty();
display.setSubmitting(true); display.setSubmitting(true);
api.submit( api.submit(
composition.text, composition.visibility, composition.text, composition.visibility,
composition.replyToPostId, composition.replyToPostId, composition.contentWarning,
new RequestListener() { new RequestListener() {
public void public void
@ -105,6 +113,7 @@ ComposeWindow extends JFrame {
display.setText(composition.text); display.setText(composition.text);
display.setReplyToPostId(composition.replyToPostId); display.setReplyToPostId(composition.replyToPostId);
display.setVisibility(stringFor(composition.visibility)); display.setVisibility(stringFor(composition.visibility));
display.setContentWarning(composition.contentWarning);
} }
private void private void
@ -113,7 +122,19 @@ ComposeWindow extends JFrame {
composition.text = display.getText(); composition.text = display.getText();
composition.visibility = composition.visibility =
visibilityFrom(display.getVisibility()); 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; text;
private JTextField private JTextField
reply; reply, contentWarning;
private JComboBox<String> private JComboBox<String>
visibility; visibility;
@ -217,6 +238,12 @@ implements ActionListener {
this.visibility.setSelectedIndex(3); this.visibility.setSelectedIndex(3);
} }
public void
setContentWarning(String contentWarning)
{
this.contentWarning.setText(contentWarning);
}
public String public String
getText() getText()
{ {
@ -229,6 +256,12 @@ implements ActionListener {
return reply.getText(); return reply.getText();
} }
public String
getContentWarning()
{
return contentWarning.getText();
}
public String public String
getVisibility() getVisibility()
{ {
@ -265,11 +298,21 @@ implements ActionListener {
{ {
this.primaire = primaire; this.primaire = primaire;
text = new JTextArea();
text.setLineWrap(true);
text.setWrapStyleWord(true);
reply = new JTextField(); 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[] { visibility = new JComboBox<>(new String[] {
"Public", "Public",
@ -289,11 +332,9 @@ implements ActionListener {
bottom.add(Box.createHorizontalStrut(8)); bottom.add(Box.createHorizontalStrut(8));
bottom.add(submit); bottom.add(submit);
JPanel top = new JPanel(); text = new JTextArea();
top.setOpaque(false); text.setLineWrap(true);
top.setLayout(new BorderLayout(8, 0)); text.setWrapStyleWord(true);
top.add(new JLabel("In reply to: "), BorderLayout.WEST);
top.add(reply);
setLayout(new BorderLayout(0, 8)); setLayout(new BorderLayout(0, 8));
add(top, BorderLayout.NORTH); add(top, BorderLayout.NORTH);

33
ImageApi.java Normal file
View 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;
}
}
}

View File

@ -90,7 +90,7 @@ ImageWindow extends JFrame {
display.getToolTipText() display.getToolTipText()
+ "\n(Media is of type '" + curr.type + "')" + "\n(Media is of type '" + curr.type + "')"
); );
repaint(); repaint();
} }
@ -99,7 +99,7 @@ ImageWindow extends JFrame {
ImageWindow() ImageWindow()
{ {
setDefaultCloseOperation(DISPOSE_ON_CLOSE); setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setSize(400, 400); setSize(600, 600);
display = new ImageComponent(this); display = new ImageComponent(this);
showAttachments(new Attachment[0]); showAttachments(new Attachment[0]);
@ -156,6 +156,7 @@ implements
setPrev(Image image) setPrev(Image image)
{ {
prev.setEnabled(image != null); prev.setEnabled(image != null);
prev.setText(image == null ? "<" : "");
prev.setIcon(toIcon(image)); prev.setIcon(toIcon(image));
} }
@ -163,6 +164,7 @@ implements
setNext(Image image) setNext(Image image)
{ {
next.setEnabled(image != null); next.setEnabled(image != null);
next.setText(image == null ? ">" : "");
next.setIcon(toIcon(image)); next.setIcon(toIcon(image));
} }
@ -183,14 +185,14 @@ implements
} }
public void public void
mousePressed(MouseEvent eM) mousePressed(MouseEvent eM)
{ {
dragX = eM.getX(); dragX = eM.getX();
dragY = eM.getY(); dragY = eM.getY();
} }
public void public void
mouseDragged(MouseEvent eM) mouseDragged(MouseEvent eM)
{ {
@ -221,10 +223,10 @@ implements
public void public void
mouseEntered(MouseEvent eM) { } mouseEntered(MouseEvent eM) { }
public void public void
mouseExited(MouseEvent eM) { } mouseExited(MouseEvent eM) { }
public void public void
mouseClicked(MouseEvent eM) { } mouseClicked(MouseEvent eM) { }
@ -250,7 +252,8 @@ implements
{ {
if (image == null) if (image == null)
{ {
String str = "(There are no images being displayed.)"; String str =
"(There are no images being displayed.)";
FontMetrics fm = g.getFontMetrics(); FontMetrics fm = g.getFontMetrics();
int x = (getWidth() - fm.stringWidth(str)) / 2; int x = (getWidth() - fm.stringWidth(str)) / 2;
int y = (getHeight() + fm.getHeight()) / 2; int y = (getHeight() + fm.getHeight()) / 2;
@ -285,15 +288,15 @@ implements
{ {
this.primaire = primaire; this.primaire = primaire;
Dimension BUTTON_SIZE = new Dimension(80, 60); Dimension BUTTON_SIZE = new Dimension(48, 48);
setOpaque(false); setOpaque(false);
scaleImage = true; scaleImage = true;
zoomLevel = 100; zoomLevel = 100;
prev = new JButton("<"); prev = new JButton();
toggle = new JButton("Show unscaled"); toggle = new JButton("Show unscaled");
next = new JButton(">"); next = new JButton();
prev.setPreferredSize(BUTTON_SIZE); prev.setPreferredSize(BUTTON_SIZE);
next.setPreferredSize(BUTTON_SIZE); next.setPreferredSize(BUTTON_SIZE);
prev.addActionListener(this); prev.addActionListener(this);
@ -306,6 +309,9 @@ implements
buttonArea.add(toggle); buttonArea.add(toggle);
buttonArea.add(next); buttonArea.add(next);
setPrev(null);
setNext(null);
setLayout(new BorderLayout()); setLayout(new BorderLayout());
add(buttonArea, BorderLayout.SOUTH); add(buttonArea, BorderLayout.SOUTH);
add(new Painter(), BorderLayout.CENTER); add(new Painter(), BorderLayout.CENTER);

22
JKomasto.java Executable file → Normal file
View File

@ -28,6 +28,9 @@ JKomasto {
private ImageWindow private ImageWindow
mediaWindow; mediaWindow;
private TimelineWindowUpdater
timelineWindowUpdater;
private MastodonApi private MastodonApi
api; api;
@ -52,6 +55,8 @@ JKomasto {
loginWindow.dispose(); loginWindow.dispose();
autoViewWindow.setCursor(null); autoViewWindow.setCursor(null);
timelineWindow.setCursor(null); timelineWindow.setCursor(null);
timelineWindowUpdater.addWindow(timelineWindow);
} }
public PostWindow public PostWindow
@ -87,6 +92,8 @@ JKomasto {
mediaWindow.dispose(); mediaWindow.dispose();
loginWindow.setLocationByPlatform(true); loginWindow.setLocationByPlatform(true);
loginWindow.setVisible(true); loginWindow.setVisible(true);
timelineWindowUpdater = new TimelineWindowUpdater(this);
} }
} }
@ -123,6 +130,9 @@ TimelinePage {
public TimelineType public TimelineType
type; type;
public String
accountNumId;
public List<Post> public List<Post>
posts; posts;
@ -143,6 +153,12 @@ Post {
public Image public Image
authorAvatar; authorAvatar;
public String
authorNumId;
public String
boosterName;
public ZonedDateTime public ZonedDateTime
date; date;
@ -170,6 +186,9 @@ Attachment {
public String public String
url; url;
public String
description;
public Image public Image
image; image;
@ -181,7 +200,8 @@ class
Composition { Composition {
public String public String
text; text,
contentWarning;
public PostVisibility public PostVisibility
visibility; visibility;

22
LoginWindow.java Executable file → Normal file
View File

@ -117,6 +117,7 @@ LoginWindow extends JFrame {
requestSucceeded(Tree<String> json) requestSucceeded(Tree<String> json)
{ {
api.setAccountDetails(json); api.setAccountDetails(json);
serverContacted = true;
haveAccountDetails = true; haveAccountDetails = true;
updateStatusDisplay(); updateStatusDisplay();
} }
@ -139,14 +140,21 @@ LoginWindow extends JFrame {
); );
} }
display.setCursor(null); display.setCursor(null);
primaire.finishedLogin(); primaire.finishedLogin();
} }
public void public void
useInstanceUrl() useInstanceUrl()
{ {
if (display.isAutoLoginToggled()) { useCache(); return; }
String url = display.getInstanceUrl(); String url = display.getInstanceUrl();
if (url.trim().isEmpty()) {
// Should we show an error dialog..?
display.setInstanceUrl("");
return;
}
if (!hasProtocol(url)) { if (!hasProtocol(url)) {
url = "https://" + url; url = "https://" + url;
display.setInstanceUrl(url); display.setInstanceUrl(url);
@ -157,10 +165,8 @@ LoginWindow extends JFrame {
haveAccessToken = false; haveAccessToken = false;
haveAccountDetails = false; haveAccountDetails = false;
if (display.isAutoLoginToggled()) { useCache(); return; }
display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.testUrlConnection(url, new RequestListener() { api.testUrlConnection(url, new RequestListener() {
public void public void
connectionFailed(IOException eIo) connectionFailed(IOException eIo)
@ -192,7 +198,7 @@ LoginWindow extends JFrame {
}); });
display.setCursor(null); display.setCursor(null);
if (!serverContacted) return; if (!serverContacted) return;
api.setInstanceUrl(url); api.setInstanceUrl(url);
display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getAppCredentials(new RequestListener() { api.getAppCredentials(new RequestListener() {
@ -259,12 +265,12 @@ LoginWindow extends JFrame {
"\nWe cannot use Desktop.browse(URI) on your\n" "\nWe cannot use Desktop.browse(URI) on your\n"
+ "computer.. You'll have to open your web\n" + "computer.. You'll have to open your web\n"
+ "browser yourself, and copy this URL in."; + "browser yourself, and copy this URL in.";
JTextField field = new JTextField(); JTextField field = new JTextField();
field.setText(uri.toString()); field.setText(uri.toString());
field.setPreferredSize(new Dimension(120, 32)); field.setPreferredSize(new Dimension(120, 32));
field.selectAll(); field.selectAll();
JOptionPane.showMessageDialog( JOptionPane.showMessageDialog(
LoginWindow.this, LoginWindow.this,
new Object[] { MESSAGE1, MESSAGE2, field }, new Object[] { MESSAGE1, MESSAGE2, field },
@ -283,7 +289,7 @@ LoginWindow extends JFrame {
display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getAccessToken(code, new RequestListener() { api.getAccessToken(code, new RequestListener() {
public void public void
connectionFailed(IOException eIo) connectionFailed(IOException eIo)
{ {

View File

@ -11,7 +11,9 @@ import java.io.InputStreamReader;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.FileReader; import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
class class
MastodonApi { MastodonApi {
@ -77,7 +79,8 @@ MastodonApi {
getAppCredentials(RequestListener handler) getAppCredentials(RequestListener handler)
{ {
assert instanceUrl != null; assert instanceUrl != null;
try { try
{
URL endpoint = new URL(instanceUrl + "/api/v1/apps"); URL endpoint = new URL(instanceUrl + "/api/v1/apps");
HttpURLConnection conn; HttpURLConnection conn;
conn = (HttpURLConnection)endpoint.openConnection(); conn = (HttpURLConnection)endpoint.openConnection();
@ -164,7 +167,8 @@ MastodonApi {
URL endpoint = new URL(instanceUrl + s); URL endpoint = new URL(instanceUrl + s);
HttpURLConnection conn; HttpURLConnection conn;
conn = (HttpURLConnection)endpoint.openConnection(); conn = (HttpURLConnection)endpoint.openConnection();
conn.setRequestProperty("Authorization", "Bearer " + token); String s2 = "Bearer " + token;
conn.setRequestProperty("Authorization", s2);
conn.connect(); conn.connect();
doStandardJsonReturn(conn, handler); doStandardJsonReturn(conn, handler);
@ -174,13 +178,18 @@ MastodonApi {
public void public void
getTimelinePage( getTimelinePage(
TimelineType type, int count, String maxId, String minId, TimelineType type, String accountId,
int count, String maxId, String minId,
RequestListener handler) RequestListener handler)
{ {
String token = accessToken.get("access_token").value; String token = accessToken.get("access_token").value;
String url = instanceUrl + "/api/v1"; String url = instanceUrl + "/api/v1";
switch (type) if (accountId != null)
{
url += "/accounts/" + accountId + "/statuses";
}
else switch (type)
{ {
case FEDERATED: case FEDERATED:
case LOCAL: url += "/timelines/public"; break; case LOCAL: url += "/timelines/public"; break;
@ -266,13 +275,15 @@ MastodonApi {
public void public void
submit( submit(
String text, PostVisibility visibility, String replyTo, String text, PostVisibility visibility,
String replyTo, String contentWarning,
RequestListener handler) RequestListener handler)
{ {
String token = accessToken.get("access_token").value; String token = accessToken.get("access_token").value;
String visibilityParam = "direct"; String visibilityParam = "direct";
switch (visibility) { switch (visibility)
{
case PUBLIC: visibilityParam = "public"; break; case PUBLIC: visibilityParam = "public"; break;
case UNLISTED: visibilityParam = "unlisted"; break; case UNLISTED: visibilityParam = "unlisted"; break;
case FOLLOWERS: visibilityParam = "private"; break; case FOLLOWERS: visibilityParam = "private"; break;
@ -283,7 +294,8 @@ MastodonApi {
String url = instanceUrl + "/api/v1/statuses"; String url = instanceUrl + "/api/v1/statuses";
try try
{ {
text = URLEncoder.encode(text, "UTF-8"); text = encode(text);
contentWarning = encode(contentWarning);
URL endpoint = new URL(url); URL endpoint = new URL(url);
HttpURLConnection conn; HttpURLConnection conn;
@ -303,6 +315,9 @@ MastodonApi {
if (replyTo != null) { if (replyTo != null) {
output.write("&in_reply_to_id=" + replyTo); output.write("&in_reply_to_id=" + replyTo);
} }
if (contentWarning != null) {
output.write("&spoiler_text=" + contentWarning);
}
output.close(); output.close();
doStandardJsonReturn(conn, handler); doStandardJsonReturn(conn, handler);
@ -310,10 +325,57 @@ MastodonApi {
catch (IOException eIo) { handler.connectionFailed(eIo); } 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 private void
doStandardJsonReturn(HttpURLConnection conn, RequestListener handler) doStandardJsonReturn(
HttpURLConnection conn, RequestListener handler)
throws IOException throws IOException
{ {
InputStreamReader input; InputStreamReader input;
@ -334,7 +396,8 @@ MastodonApi {
} }
private void private void
returnResponseInTree(HttpURLConnection conn, RequestListener handler) returnResponseInTree(
HttpURLConnection conn, RequestListener handler)
throws IOException throws IOException
{ {
InputStreamReader input; InputStreamReader input;
@ -371,6 +434,19 @@ MastodonApi {
return doc; 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 public void

39
PostWindow.java Executable file → Normal file
View File

@ -21,6 +21,7 @@ import java.awt.BorderLayout;
import java.awt.GridLayout; import java.awt.GridLayout;
import java.awt.Cursor; import java.awt.Cursor;
import java.awt.Image; import java.awt.Image;
import java.awt.Component;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.util.List; import java.util.List;
@ -98,11 +99,12 @@ implements ActionListener {
postDisplay.setText(post.text); postDisplay.setText(post.text);
postDisplay.setFavourited(post.favourited); postDisplay.setFavourited(post.favourited);
postDisplay.setBoosted(post.boosted); postDisplay.setBoosted(post.boosted);
postDisplay.setMediaPreview( postDisplay.setMediaPreview(
post.attachments.length == 0 post.attachments.length == 0
? null ? null
: post.attachments[0].image : post.attachments[0].image
); );
repliesDisplay.setReplies(replies); repliesDisplay.setReplies(replies);
postDisplay.resetFocus(); postDisplay.resetFocus();
repaint(); repaint();
@ -111,7 +113,11 @@ implements ActionListener {
public void public void
openAuthorProfile() openAuthorProfile()
{ {
TimelineWindow w = new TimelineWindow(primaire);
w.showAuthorPosts(post.authorNumId);
w.showLatestPage();
w.setLocationRelativeTo(this);
w.setVisible(true);
} }
public void public void
@ -151,8 +157,9 @@ implements ActionListener {
}; };
api.setPostFavourited(post.postId, favourited, handler); api.setPostFavourited(post.postId, favourited, handler);
postDisplay.setFavouriteBoostEnabled(true);
postDisplay.setCursor(null); postDisplay.setCursor(null);
postDisplay.setFavouriteBoostEnabled(true);
postDisplay.repaint();
} }
public void public void
@ -192,8 +199,9 @@ implements ActionListener {
}; };
api.setPostBoosted(post.postId, boosted, handler); api.setPostBoosted(post.postId, boosted, handler);
postDisplay.setFavouriteBoostEnabled(true);
postDisplay.setCursor(null); postDisplay.setCursor(null);
postDisplay.setFavouriteBoostEnabled(true);
postDisplay.repaint();
} }
public void public void
@ -217,7 +225,7 @@ implements ActionListener {
int l = Math.min(40, post.text.length()); int l = Math.min(40, post.text.length());
w.setTitle(post.text.substring(0, l)); w.setTitle(post.text.substring(0, l));
if (!w.isVisible()) { if (!w.isVisible()) {
w.setLocation(getX(), getY() + 100); w.setLocationRelativeTo(null);
w.setVisible(true); w.setVisible(true);
} }
} }
@ -227,7 +235,7 @@ implements ActionListener {
public void public void
actionPerformed(ActionEvent eA) actionPerformed(ActionEvent eA)
{ {
Object src = eA.getSource(); Component src = (Component)eA.getSource();
if (!(src instanceof JMenuItem)) return; if (!(src instanceof JMenuItem)) return;
String text = ((JMenuItem)src).getText(); String text = ((JMenuItem)src).getText();
@ -358,7 +366,7 @@ implements ActionListener {
public void public void
actionPerformed(ActionEvent eA) actionPerformed(ActionEvent eA)
{ {
Object src = eA.getSource(); Component src = (Component)eA.getSource();
String command = eA.getActionCommand(); String command = eA.getActionCommand();
if (src == profile) if (src == profile)
@ -412,8 +420,8 @@ implements ActionListener {
g.clearRect(0, 0, getWidth(), getHeight()); g.clearRect(0, 0, getWidth(), getHeight());
((java.awt.Graphics2D)g).setRenderingHint( ((java.awt.Graphics2D)g).setRenderingHint(
java.awt.RenderingHints.KEY_TEXT_ANTIALIASING, java.awt.RenderingHints.KEY_ANTIALIASING,
java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON java.awt.RenderingHints.VALUE_ANTIALIAS_ON
); );
Font f1 = new Font("IPAGothic", Font.PLAIN, 16); 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> private static List<String>
split(String string, int lineLength) split(String string, int lineLength)
{ {
@ -472,7 +473,7 @@ implements ActionListener {
if (word.length() >= lineLength) { if (word.length() >= lineLength) {
word = word.substring(0, lineLength - 4) + "..."; word = word.substring(0, lineLength - 4) + "...";
} }
if (word.equals("\n")) { if (word.matches("\n")) {
returnee.add(empty(line)); returnee.add(empty(line));
continue; continue;
} }
@ -504,9 +505,9 @@ implements ActionListener {
authorName = authorId = time = text = ""; authorName = authorId = time = text = "";
Dimension buttonSize = new Dimension(20, 40); Dimension buttonSize = new Dimension(20, 40);
Border b = BorderFactory.createEmptyBorder(10, 10, 10, 10);
profile = new RoundButton(); profile = new RoundButton();
//profile.setPreferredSize(buttonSize);
profile.addActionListener(this); profile.addActionListener(this);
favouriteBoost = new TwoToggleButton("favourite", "boost"); favouriteBoost = new TwoToggleButton("favourite", "boost");
@ -537,7 +538,7 @@ implements ActionListener {
Box buttons = Box.createVerticalBox(); Box buttons = Box.createVerticalBox();
buttons.setOpaque(false); buttons.setOpaque(false);
buttons.add(ibuttons); buttons.add(ibuttons);
buttons.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); buttons.setBorder(b);
setLayout(new BorderLayout()); setLayout(new BorderLayout());
add(buttons, BorderLayout.WEST); add(buttons, BorderLayout.WEST);

View File

@ -15,3 +15,17 @@ RequestListener {
requestSucceeded(Tree<String> json); requestSucceeded(Tree<String> json);
} }
interface
ServerSideEventsListener {
void
connectionFailed(IOException eIo);
void
requestFailed(int httpCode, Tree<String> json);
void
lineReceived(String line);
}

298
TimelineWindow.java Executable file → Normal file
View File

@ -9,7 +9,6 @@ import javax.swing.JMenuItem;
import javax.swing.JMenuBar; import javax.swing.JMenuBar;
import javax.swing.JSeparator; import javax.swing.JSeparator;
import javax.swing.Box; import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.GridBagLayout; import java.awt.GridBagLayout;
@ -30,6 +29,10 @@ import java.awt.event.ActionListener;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.MouseListener; import java.awt.event.MouseListener;
import java.awt.event.MouseEvent; 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 cafe.biskuteri.hinoki.Tree;
import java.io.IOException; import java.io.IOException;
@ -59,8 +62,6 @@ implements ActionListener {
private JMenuItem private JMenuItem
openHome, openHome,
// umm, what about the timeline that's like, notes that your
// post was favourited or replied to? those aren't messages..
openMessages, openMessages,
openLocal, openLocal,
openFederated, openFederated,
@ -82,21 +83,29 @@ implements ActionListener {
setTimelineType(TimelineType type) setTimelineType(TimelineType type)
{ {
page.type = type; page.type = type;
page.accountNumId = null;
String s1 = type.toString(); String s1 = type.toString();
s1 = s1.charAt(0) + s1.substring(1).toLowerCase(); s1 = s1.charAt(0) + s1.substring(1).toLowerCase();
setTitle(s1 + " - JKomasto"); setTitle(s1 + " - JKomasto");
String s2 = type.toString().toLowerCase(); String s2 = type.toString().toLowerCase();
s2 = "/graphics/" + s2 + ".png"; display.setBackgroundImage(ImageApi.local(s2));
URL url = getClass().getResource(s2); }
if (url != null) {
ImageIcon icon = new ImageIcon(url); public TimelineType
display.setBackgroundImage(icon.getImage()); getTimelineType() { return page.type; }
}
else { public void
display.setBackgroundImage(null); showAuthorPosts(String authorNumId)
} {
assert authorNumId != null;
page.type = TimelineType.FEDERATED;
page.accountNumId = authorNumId;
setTitle(authorNumId + " - JKomasto");
display.setBackgroundImage(ImageApi.local("profile"));
} }
public void public void
@ -104,12 +113,14 @@ implements ActionListener {
{ {
display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getTimelinePage( api.getTimelinePage(
page.type, PREVIEW_COUNT, null, null, page.type, page.accountNumId,
PREVIEW_COUNT, null, null,
new RequestListener() { new RequestListener() {
public void public void
connectionFailed(IOException eIo) connectionFailed(IOException eIo)
{ {
eIo.printStackTrace();
String s = eIo.getClass().getName(); String s = eIo.getClass().getName();
setTitle(s + " - JKomasto"); setTitle(s + " - JKomasto");
} }
@ -117,6 +128,7 @@ implements ActionListener {
public void public void
requestFailed(int httpCode, Tree<String> json) requestFailed(int httpCode, Tree<String> json)
{ {
System.err.println(json.get("error").value);
setTitle(httpCode + " - JKomasto"); setTitle(httpCode + " - JKomasto");
// lol... // lol...
} }
@ -124,9 +136,13 @@ implements ActionListener {
public void public void
requestSucceeded(Tree<String> json) requestSucceeded(Tree<String> json)
{ {
page.posts = toPosts(json); List<Post> posts = toPosts(json);
page.posts = posts;
display.setPosts(page.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)); display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getTimelinePage( api.getTimelinePage(
page.type, PREVIEW_COUNT, last.postId, null, page.type, page.accountNumId,
PREVIEW_COUNT, last.postId, null,
new RequestListener() { new RequestListener() {
public void public void
@ -162,9 +179,19 @@ implements ActionListener {
public void public void
requestSucceeded(Tree<String> json) 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); display.setPosts(page.posts);
boolean full = posts.size() >= PREVIEW_COUNT;
display.setNextPageAvailable(full);
display.setPreviousPageAvailable(true); display.setPreviousPageAvailable(true);
display.resetFocus();
} }
} }
@ -181,7 +208,8 @@ implements ActionListener {
display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getTimelinePage( api.getTimelinePage(
page.type, PREVIEW_COUNT, null, first.postId, page.type, page.accountNumId,
PREVIEW_COUNT, null, first.postId,
new RequestListener() { new RequestListener() {
public void public void
@ -200,17 +228,21 @@ implements ActionListener {
public void public void
requestSucceeded(Tree<String> json) 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.setPosts(page.posts);
display.setNextPageAvailable(true); display.setNextPageAvailable(true);
display.setPreviousPageAvailable(true); display.setPreviousPageAvailable(true);
display.resetFocus();
} }
} }
); );
display.setCursor(null); display.setCursor(null);
if (page.posts.size() < PREVIEW_COUNT) showLatestPage();
} }
// - -%- - // - -%- -
@ -221,6 +253,15 @@ implements ActionListener {
primaire.getAutoViewWindow().showPost(post); 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 public void
actionPerformed(ActionEvent eA) actionPerformed(ActionEvent eA)
{ {
@ -273,11 +314,18 @@ implements ActionListener {
private static List<Post> private static List<Post>
toPosts(Tree<String> json) toPosts(Tree<String> json)
{ {
List<Post> posts = new ArrayList<>(); List<Post> posts = new ArrayList<>();
for (Tree<String> post: json.children) for (Tree<String> post: json.children)
{ {
Post addee = new Post(); 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; addee.postId = post.get("id").value;
try { try {
@ -304,7 +352,7 @@ implements ActionListener {
b.append(" \n "); b.append(" \n ");
} }
if (node.get(0).key.equals("/p")) { if (node.get(0).key.equals("/p")) {
b.append(" \n\n "); b.append(" \n \n ");
} }
} }
if (node.key.equals("text")) { if (node.key.equals("text")) {
@ -323,51 +371,40 @@ implements ActionListener {
else addee.contentWarning = null; else addee.contentWarning = null;
Tree<String> account = post.get("account"); Tree<String> account = post.get("account");
if (post.get("reblog").size() != 0) { addee.authorId = account.get("acct").value;
account = post.get("reblog").get("account");
}
addee.authorId = account.get("acct").value;
addee.authorName = account.get("username").value; addee.authorName = account.get("username").value;
addee.authorNumId = account.get("id").value;
String s2 = account.get("display_name").value; String s2 = account.get("display_name").value;
if (!s2.isEmpty()) addee.authorName = s2; if (!s2.isEmpty()) addee.authorName = s2;
try { String s3 = account.get("avatar").value;
String av = account.get("avatar").value; addee.authorAvatar = ImageApi.remote(s3);
ImageIcon icon = new ImageIcon(new URL(av)); if (addee.authorAvatar == null) {
addee.authorAvatar = icon.getImage(); s3 = "defaultAvatar";
} addee.authorAvatar = ImageApi.local(s3);
catch (MalformedURLException eMu) { }
// Weird bug on their part.. We should
// probably react by using a default avatar.
}
String f = post.get("favourited").value; String f = post.get("favourited").value;
String b = post.get("reblogged").value; String b = post.get("reblogged").value;
addee.favourited = f.equals("true"); addee.favourited = f.equals("true");
addee.boosted = b.equals("true"); addee.boosted = b.equals("true");
Tree<String> a1 = post.get("media_attachments"); Tree<String> as1 = post.get("media_attachments");
Attachment[] a2 = new Attachment[a1.size()]; Attachment[] as2 = new Attachment[as1.size()];
for (int o = 0; o < a2.length; ++o) for (int o = 0; o < as2.length; ++o)
{ {
a2[o] = new Attachment(); Tree<String> a1 = as1.get(o);
a2[o].type = a1.get(o).get("type").value; Attachment a2 = as2[o] = new Attachment();
a2[o].url = a1.get(o).get("remote_url").value; a2.type = a1.get("type").value;
if (a2[o].url == null) String u1 = a1.get("remote_url").value;
a2[o].url = a1.get(o).get("text_url").value; String u2 = a1.get("text_url").value;
a2.url = u1 == null ? u2 : u1;
a2[o].image = null; a2.description = a1.get("description").value;
if (a2[o].type.equals("image")) try a2.image = null;
{ if (a2.type.equals("image"))
URL url = new URL(a2[o].url); a2.image = ImageApi.remote(a2.url);
ImageIcon icon = new ImageIcon(url);
String desc = a1.get(o).get("description").value;
icon.setDescription(desc);
a2[o].image = icon.getImage();
}
catch (MalformedURLException eMu) { }
} }
addee.attachments = a2; addee.attachments = as2;
posts.add(addee); posts.add(addee);
} }
@ -418,6 +455,7 @@ implements ActionListener {
flipToNewestPost.addActionListener(this); flipToNewestPost.addActionListener(this);
JMenu programMenu = new JMenu("Program"); JMenu programMenu = new JMenu("Program");
programMenu.setMnemonic(KeyEvent.VK_P);
programMenu.add(openHome); programMenu.add(openHome);
programMenu.add(openFederated); programMenu.add(openFederated);
programMenu.add(new JSeparator()); programMenu.add(new JSeparator());
@ -426,6 +464,7 @@ implements ActionListener {
programMenu.add(new JSeparator()); programMenu.add(new JSeparator());
programMenu.add(quit); programMenu.add(quit);
JMenu timelineMenu = new JMenu("Timeline"); JMenu timelineMenu = new JMenu("Timeline");
timelineMenu.setMnemonic(KeyEvent.VK_T);
timelineMenu.add(flipToNewestPost); timelineMenu.add(flipToNewestPost);
JMenuBar menuBar = new JMenuBar(); JMenuBar menuBar = new JMenuBar();
menuBar.add(programMenu); menuBar.add(programMenu);
@ -449,7 +488,9 @@ implements ActionListener {
class class
TimelineComponent extends JPanel TimelineComponent extends JPanel
implements ActionListener, MouseListener { implements
ActionListener, KeyListener,
MouseListener, FocusListener {
private TimelineWindow private TimelineWindow
primaire; primaire;
@ -494,14 +535,23 @@ implements ActionListener, MouseListener {
Post p = posts.get(o); Post p = posts.get(o);
c.setTopLeft(p.authorName); 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(); ZonedDateTime now = ZonedDateTime.now();
long d = ChronoUnit.SECONDS.between(p.date, now); long d = ChronoUnit.SECONDS.between(p.date, now);
long s = Math.abs(d); long s = Math.abs(d);
if (s < 30) c.setTopRight("now"); if (s < 30) t = "now";
else if (s < 60) c.setTopRight(d + "s"); else if (s < 60) t = d + "s";
else if (s < 3600) c.setTopRight((d / 60) + "m"); else if (s < 3600) t = (d / 60) + "m";
else if (s < 86400) c.setTopRight((d / 3600) + "h"); else if (s < 86400) t = (d / 3600) + "h";
else c.setTopRight((d / 86400) + "d"); else t = (d / 86400) + "d";
c.setTopRight(f + " " + t);
} }
if (p.contentWarning != null) if (p.contentWarning != null)
c.setBottom("(" + p.contentWarning + ")"); c.setBottom("(" + p.contentWarning + ")");
@ -533,11 +583,19 @@ implements ActionListener, MouseListener {
public void public void
setBackgroundImage(Image n) { backgroundImage = n; } setBackgroundImage(Image n) { backgroundImage = n; }
public void
resetFocus() { postPreviews.get(0).requestFocusInWindow(); }
// - -%- - // - -%- -
protected void protected void
paintComponent(Graphics g) paintComponent(Graphics g)
{ {
((java.awt.Graphics2D)g).setRenderingHint(
java.awt.RenderingHints.KEY_ANTIALIASING,
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
);
int w = getWidth(), h = getHeight(); int w = getWidth(), h = getHeight();
g.clearRect(0, 0, w, h); g.clearRect(0, 0, w, h);
int h2 = h * 5 / 10, w2 = h2; int h2 = h * 5 / 10, w2 = h2;
@ -545,40 +603,88 @@ implements ActionListener, MouseListener {
g.drawImage(backgroundImage, x, y, w2, h2, this); 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 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) mouseEntered(MouseEvent eM)
{ {
if (!hoverSelect) return; if (!hoverSelect) return;
mouseClicked(eM); 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 public void
mouseExited(MouseEvent eM) mouseExited(MouseEvent eM)
{ {
if (!hoverSelect) return; if (!hoverSelect) return;
int offset = postPreviews.indexOf(eM.getSource()); deselect(eM.getSource());
assert offset != -1;
postPreviews.get(offset).setSelected(false);
repaint();
} }
public void // () First time I'm using these two..!
mousePressed(MouseEvent eM) { }
public void 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 public void
actionPerformed(ActionEvent eA) 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) TimelineComponent(TimelineWindow primaire)
@ -608,6 +727,8 @@ implements ActionListener, MouseListener {
next = new JButton(">"); next = new JButton(">");
prev.setEnabled(false); prev.setEnabled(false);
next.setEnabled(false); next.setEnabled(false);
prev.setMnemonic(KeyEvent.VK_PAGE_UP);
next.setMnemonic(KeyEvent.VK_PAGE_DOWN);
prev.addActionListener(this); prev.addActionListener(this);
next.addActionListener(this); next.addActionListener(this);
@ -632,6 +753,8 @@ implements ActionListener, MouseListener {
PostPreviewComponent c = new PostPreviewComponent(); PostPreviewComponent c = new PostPreviewComponent();
c.reset(); c.reset();
c.addMouseListener(this); c.addMouseListener(this);
c.addFocusListener(this);
c.addKeyListener(this);
centre.add(c, constraints); centre.add(c, constraints);
postPreviews.add(c); postPreviews.add(c);
} }
@ -683,6 +806,9 @@ PostPreviewComponent extends JComponent {
else setBackground(new Color(0, 0, 0, 25)); else setBackground(new Color(0, 0, 0, 25));
} }
public boolean
getSelected() { return selected; }
// - -%- - // - -%- -
protected void protected void
@ -699,8 +825,6 @@ PostPreviewComponent extends JComponent {
public public
PostPreviewComponent() PostPreviewComponent()
{ {
selected = false;
Font f = new JLabel().getFont(); Font f = new JLabel().getFont();
Font f1 = f.deriveFont(Font.PLAIN, 12f); Font f1 = f.deriveFont(Font.PLAIN, 12f);
Font f2 = f.deriveFont(Font.ITALIC, 12f); Font f2 = f.deriveFont(Font.ITALIC, 12f);
@ -725,8 +849,8 @@ PostPreviewComponent extends JComponent {
bottom.setFont(f3); bottom.setFont(f3);
bottom.setOpaque(false); bottom.setOpaque(false);
setFocusable(true);
setOpaque(false); setOpaque(false);
setSelected(false);
setLayout(new BorderLayout()); setLayout(new BorderLayout());
add(top, BorderLayout.NORTH); add(top, BorderLayout.NORTH);
add(bottom); add(bottom);

177
TimelineWindowUpdater.java Normal file
View 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<>();
}
}

34
TwoToggleButton.java Executable file → Normal file
View File

@ -133,6 +133,7 @@ implements KeyListener, MouseListener, FocusListener {
case MouseEvent.BUTTON1: togglePrimary(); break; case MouseEvent.BUTTON1: togglePrimary(); break;
case MouseEvent.BUTTON3: toggleSecondary(); break; case MouseEvent.BUTTON3: toggleSecondary(); break;
} }
requestFocusInWindow();
} }
public void public void
@ -142,6 +143,7 @@ implements KeyListener, MouseListener, FocusListener {
case KeyEvent.VK_SPACE: togglePrimary(); break; case KeyEvent.VK_SPACE: togglePrimary(); break;
case KeyEvent.VK_ENTER: toggleSecondary(); break; case KeyEvent.VK_ENTER: toggleSecondary(); break;
} }
requestFocusInWindow();
} }
public void public void
@ -241,10 +243,12 @@ implements KeyListener, MouseListener, FocusListener {
private Image private Image
image; image;
// - -%- -
private int private int
nextEventID = ActionEvent.ACTION_FIRST; nextEventID = ActionEvent.ACTION_FIRST;
// - -%- - // - -%- -
private static Image private static Image
button, button,
@ -261,24 +265,20 @@ implements KeyListener, MouseListener, FocusListener {
protected void protected void
paintComponent(Graphics g) paintComponent(Graphics g)
{ {
((java.awt.Graphics2D)g).setRenderingHint( g.drawImage(button, 0, 0, this);
java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,
java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON
);
g.drawImage(button, 0, 0, this);
if (!isEnabled()) if (!isEnabled())
g.drawImage(disabledOverlay, 0, 0, this); g.drawImage(disabledOverlay, 0, 0, this);
if (isFocusOwner()) if (isFocusOwner())
g.drawImage(selectedOverlay, 0, 0, this); g.drawImage(selectedOverlay, 0, 0, this);
if (image == null) return; if (image == null) return;
int w1 = button.getWidth(this); int w1 = button.getWidth(this);
int h1 = button.getHeight(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 w2 = image.getWidth(this);
int h2 = image.getHeight(this); int h2 = image.getHeight(this);
if (w2 == -1) w2 = w1;
if (h2 == -1) h2 = h1;
if (h2 > w2) { if (h2 > w2) {
h2 = h2 * w1 / w2; h2 = h2 * w1 / w2;
w2 = w1; w2 = w1;
@ -287,8 +287,14 @@ implements KeyListener, MouseListener, FocusListener {
w2 = w2 * h1 / h2; w2 = w2 * h1 / h2;
h2 = h1; h2 = h1;
} }
Shape defaultClip, roundClip;
defaultClip = g.getClip();
roundClip = new Ellipse2D.Float(6, 6, w1 - 12, h1 - 12);
g.setClip(roundClip); 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); g.setClip(defaultClip);
} }
@ -343,13 +349,13 @@ implements KeyListener, MouseListener, FocusListener {
RoundButton() RoundButton()
{ {
if (button == null) loadCommonImages(); if (button == null) loadCommonImages();
setModel(new DefaultButtonModel()); setModel(new DefaultButtonModel());
setFocusable(true); setFocusable(true);
setOpaque(false); setOpaque(false);
int w = button.getWidth(null); int w = button.getWidth(this);
int h = button.getHeight(null); int h = button.getHeight(this);
setPreferredSize(new Dimension(w, h)); setPreferredSize(new Dimension(w, h));
this.addKeyListener(this); this.addKeyListener(this);

0
graphics/Flags.xcf Executable file → Normal file
View File

0
graphics/Hourglass.xcf Executable file → Normal file
View File

0
graphics/button.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

0
graphics/disabledOverlay.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

0
graphics/favouriteToggled.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 353 B

After

Width:  |  Height:  |  Size: 353 B

0
graphics/ref1.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

0
graphics/selectedOverlay.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 313 B

After

Width:  |  Height:  |  Size: 313 B

0
graphics/test1.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

0
graphics/test2.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

0
graphics/test3.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

0
graphics/test4.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB