Normalised all tabs to 4 spaces, for easier web viewing.

This commit is contained in:
Snowyfox 2022-06-02 14:41:59 -04:00
parent 2719fabef9
commit fa9a5edab0
20 changed files with 3625 additions and 3625 deletions

View File

@ -29,308 +29,308 @@ import cafe.biskuteri.hinoki.Tree;
interface
BasicHTMLParser {
public static Tree<String>
parse(String html)
{
List<String> segments;
segments = distinguishTagsFromPcdata(html);
public static Tree<String>
parse(String html)
{
List<String> segments;
segments = distinguishTagsFromPcdata(html);
Tree<String> document;
document = toNodes(segments);
document = splitText(document);
document = evaluateHtmlEscapes(document);
document = hierarchise(document);
Tree<String> document;
document = toNodes(segments);
document = splitText(document);
document = evaluateHtmlEscapes(document);
document = hierarchise(document);
return document;
}
return document;
}
// - -%- -
// - -%- -
private static List<String>
distinguishTagsFromPcdata(String html)
{
List<String> returnee = new ArrayList<>();
StringBuilder segment = new StringBuilder();
boolean inTag = false;
for (char c: html.toCharArray())
{
if (c == '<')
{
String addee = empty(segment);
if (!addee.isEmpty()) returnee.add(addee);
inTag = true;
segment.append(c);
}
else if (c == '>')
{
assert inTag;
assert segment.length() > 0;
segment.append(c);
returnee.add(empty(segment));
inTag = false;
}
else
{
segment.append(c);
}
}
String addee = empty(segment);
if (!addee.isEmpty()) returnee.add(addee);
private static List<String>
distinguishTagsFromPcdata(String html)
{
List<String> returnee = new ArrayList<>();
StringBuilder segment = new StringBuilder();
boolean inTag = false;
for (char c: html.toCharArray())
{
if (c == '<')
{
String addee = empty(segment);
if (!addee.isEmpty()) returnee.add(addee);
inTag = true;
segment.append(c);
}
else if (c == '>')
{
assert inTag;
assert segment.length() > 0;
segment.append(c);
returnee.add(empty(segment));
inTag = false;
}
else
{
segment.append(c);
}
}
String addee = empty(segment);
if (!addee.isEmpty()) returnee.add(addee);
return returnee;
}
return returnee;
}
private static Tree<String>
toNodes(List<String> segments)
{
Tree<String> returnee = new Tree<String>();
private static Tree<String>
toNodes(List<String> segments)
{
Tree<String> returnee = new Tree<String>();
for (String segment: segments)
{
boolean isTag = segment.startsWith("<");
Tree<String> node = new Tree<String>();
for (String segment: segments)
{
boolean isTag = segment.startsWith("<");
Tree<String> node = new Tree<String>();
if (!isTag)
{
node.key = "text";
node.value = segment;
returnee.add(node);
continue;
}
if (!isTag)
{
node.key = "text";
node.value = segment;
returnee.add(node);
continue;
}
node.key = "tag";
node.key = "tag";
String key = null, value = null;
StringBuilder b = new StringBuilder();
boolean inQuotes = false, inValue = false;
char[] chars = segment.toCharArray();
for (int o = 1; o < chars.length - 1; ++o)
{
char c = chars[o];
if (c == '"')
{
inQuotes = !inQuotes;
}
else if (inQuotes)
{
b.append(c);
}
else if (c == '=')
{
assert b.length() > 0;
key = empty(b);
inValue = true;
}
else if (Character.isWhitespace(c))
{
if (b.length() > 0)
{
if (inValue) value = empty(b);
else key = empty(b);
Tree<String> attr = new Tree<String>();
attr.key = key;
attr.value = value;
node.add(attr);
}
inValue = false;
}
else
{
b.append(c);
}
}
if (b.length() > 0)
{
if (inValue) value = empty(b);
else key = empty(b);
Tree<String> attr = new Tree<String>();
attr.key = key;
attr.value = value;
node.add(attr);
}
String key = null, value = null;
StringBuilder b = new StringBuilder();
boolean inQuotes = false, inValue = false;
char[] chars = segment.toCharArray();
for (int o = 1; o < chars.length - 1; ++o)
{
char c = chars[o];
if (c == '"')
{
inQuotes = !inQuotes;
}
else if (inQuotes)
{
b.append(c);
}
else if (c == '=')
{
assert b.length() > 0;
key = empty(b);
inValue = true;
}
else if (Character.isWhitespace(c))
{
if (b.length() > 0)
{
if (inValue) value = empty(b);
else key = empty(b);
Tree<String> attr = new Tree<String>();
attr.key = key;
attr.value = value;
node.add(attr);
}
inValue = false;
}
else
{
b.append(c);
}
}
if (b.length() > 0)
{
if (inValue) value = empty(b);
else key = empty(b);
Tree<String> attr = new Tree<String>();
attr.key = key;
attr.value = value;
node.add(attr);
}
returnee.add(node);
}
returnee.add(node);
}
return returnee;
}
return returnee;
}
private static Tree<String>
splitText(Tree<String> nodes)
{
Tree<String> returnee = new Tree<>();
private static Tree<String>
splitText(Tree<String> nodes)
{
Tree<String> returnee = new Tree<>();
for (Tree<String> node: nodes)
{
if (node.key.equals("tag"))
{
returnee.add(node);
continue;
}
assert node.key.equals("text");
for (Tree<String> node: nodes)
{
if (node.key.equals("tag"))
{
returnee.add(node);
continue;
}
assert node.key.equals("text");
StringBuilder b = new StringBuilder();
boolean alnum = false, calnum;
boolean space = false, cspace;
boolean emoji = false;
for (char c: node.value.toCharArray())
{
calnum = isMastodonAlnum(c);
cspace = Character.isWhitespace(c);
StringBuilder b = new StringBuilder();
boolean alnum = false, calnum;
boolean space = false, cspace;
boolean emoji = false;
for (char c: node.value.toCharArray())
{
calnum = isMastodonAlnum(c);
cspace = Character.isWhitespace(c);
if (c == ':' && !emoji)
{
if (c == ':' && !emoji)
{
// See note on #isMastodonAlnum.
if (b.length() > 0)
{
Tree<String> addee = new Tree<>();
addee.key = space ? "space" : "text";
addee.value = empty(b);
returnee.add(addee);
}
emoji = true;
b.append(c);
}
else if (c == ':' && emoji)
{
assert !space;
b.append(c);
Tree<String> addee = new Tree<>();
addee.key = "emoji";
addee.value = empty(b);
returnee.add(addee);
/*
* Technically, addee.value.length()
* could be zero, which probably means
* someone just put two colons in a row,
* maybe for Haskell source code. I'd
* be surprised if Mastodon didn't escape
* it. (If they did, the next step will
* handle them.) Anyways treating it as
* an empty emoji is the correct action.
*/
emoji = false;
calnum = false;
}
else if (cspace != space)
{
if (b.length() > 0)
{
Tree<String> addee = new Tree<>();
addee.key = space ? "space" : "text";
addee.value = empty(b);
returnee.add(addee);
}
b.append(c);
}
else
{
b.append(c);
}
/*
* We can specially handle special
* characters like \n, but I'll opt not to.
*/
{
Tree<String> addee = new Tree<>();
addee.key = space ? "space" : "text";
addee.value = empty(b);
returnee.add(addee);
}
emoji = true;
b.append(c);
}
else if (c == ':' && emoji)
{
assert !space;
b.append(c);
Tree<String> addee = new Tree<>();
addee.key = "emoji";
addee.value = empty(b);
returnee.add(addee);
/*
* Technically, addee.value.length()
* could be zero, which probably means
* someone just put two colons in a row,
* maybe for Haskell source code. I'd
* be surprised if Mastodon didn't escape
* it. (If they did, the next step will
* handle them.) Anyways treating it as
* an empty emoji is the correct action.
*/
emoji = false;
calnum = false;
}
else if (cspace != space)
{
if (b.length() > 0)
{
Tree<String> addee = new Tree<>();
addee.key = space ? "space" : "text";
addee.value = empty(b);
returnee.add(addee);
}
b.append(c);
}
else
{
b.append(c);
}
/*
* We can specially handle special
* characters like \n, but I'll opt not to.
*/
alnum = calnum;
space = cspace;
}
if (b.length() > 0)
{
Tree<String> addee = new Tree<>();
addee.key = space ? "space" : "text";
addee.value = empty(b);
returnee.add(addee);
}
}
alnum = calnum;
space = cspace;
}
if (b.length() > 0)
{
Tree<String> addee = new Tree<>();
addee.key = space ? "space" : "text";
addee.value = empty(b);
returnee.add(addee);
}
}
return returnee;
}
return returnee;
}
private static Tree<String>
evaluateHtmlEscapes(Tree<String> nodes)
{
for (Tree<String> node: nodes)
{
node.value = evaluateHtmlEscapes(node.value);
for (Tree<String> attr: node)
{
attr.key = evaluateHtmlEscapes(attr.key);
attr.value = evaluateHtmlEscapes(attr.value);
}
}
private static Tree<String>
evaluateHtmlEscapes(Tree<String> nodes)
{
for (Tree<String> node: nodes)
{
node.value = evaluateHtmlEscapes(node.value);
for (Tree<String> attr: node)
{
attr.key = evaluateHtmlEscapes(attr.key);
attr.value = evaluateHtmlEscapes(attr.value);
}
}
return nodes;
}
return nodes;
}
private static Tree<String>
hierarchise(Tree<String> nodes)
{
Tree<String> root = new Tree<String>();
root.key = "tag";
root.add(new Tree<>("html", null));
root.add(new Tree<>("children", null));
private static Tree<String>
hierarchise(Tree<String> nodes)
{
Tree<String> root = new Tree<String>();
root.key = "tag";
root.add(new Tree<>("html", null));
root.add(new Tree<>("children", null));
Deque<Tree<String>> parents = new LinkedList<>();
parents.push(root);
for (Tree<String> node: nodes)
{
if (node.key.equals("tag"))
{
assert node.size() > 0;
String tagName = node.get(0).key;
Deque<Tree<String>> parents = new LinkedList<>();
parents.push(root);
for (Tree<String> node: nodes)
{
if (node.key.equals("tag"))
{
assert node.size() > 0;
String tagName = node.get(0).key;
assert node.get("children") == null;
node.add(new Tree<>("children", null));
assert node.get("children") == null;
node.add(new Tree<>("children", null));
boolean isClosing, selfClosing;
isClosing = tagName.startsWith("/");
selfClosing = node.get("/") != null;
selfClosing |= tagName.equals("br");
if (isClosing)
{
assert parents.size() > 1;
boolean isClosing, selfClosing;
isClosing = tagName.startsWith("/");
selfClosing = node.get("/") != null;
selfClosing |= tagName.equals("br");
if (isClosing)
{
assert parents.size() > 1;
Tree<String> parent, grandparent;
parent = parents.pop();
grandparent = parents.peek();
Tree<String> parent, grandparent;
parent = parents.pop();
grandparent = parents.peek();
String pTagName = parent.get(0).key;
assert tagName.equals("/" + pTagName);
String pTagName = parent.get(0).key;
assert tagName.equals("/" + pTagName);
grandparent.get("children").add(parent);
}
else if (selfClosing)
{
parents.peek().get("children").add(node);
}
else
{
parents.push(node);
}
}
else
{
parents.peek().get("children").add(node);
}
}
grandparent.get("children").add(parent);
}
else if (selfClosing)
{
parents.peek().get("children").add(node);
}
else
{
parents.push(node);
}
}
else
{
parents.peek().get("children").add(node);
}
}
assert parents.size() == 1;
return parents.pop();
}
assert parents.size() == 1;
return parents.pop();
}
private static String
empty(StringBuilder b)
{
String s = b.toString();
b.delete(0, b.length());
return s;
}
private static String
empty(StringBuilder b)
{
String s = b.toString();
b.delete(0, b.length());
return s;
}
private static boolean
isMastodonAlnum(char c)
{
private static boolean
isMastodonAlnum(char c)
{
return Character.isLetterOrDigit(c);
/*
* Not joking. Mastodon is using the POSIX :alnum: regex
@ -343,44 +343,44 @@ BasicHTMLParser {
* by text, then try again with the same emoji also
* present elsewhere in the post at a valid position.)
*/
}
}
private static String
evaluateHtmlEscapes(String string)
{
if (string == null) return string;
private static String
evaluateHtmlEscapes(String string)
{
if (string == null) return string;
StringBuilder whole = new StringBuilder();
StringBuilder part = new StringBuilder();
boolean inEscape = false;
for (char c: string.toCharArray())
{
if (inEscape && c == ';')
{
part.append(c);
inEscape = false;
String v = empty(part);
if (v.equals("&lt;")) part.append('<');
if (v.equals("&gt;")) part.append('>');
if (v.equals("&amp;")) part.append('&');
if (v.equals("&quot;")) part.append('"');
if (v.equals("&apos;")) part.append('\'');
if (v.equals("&#39;")) part.append('\'');
}
else if (!inEscape && c == '&')
{
String v = empty(part);
if (!v.isEmpty()) whole.append(v);
part.append(c);
inEscape = true;
}
else
{
part.append(c);
}
}
String v = empty(part);
if (!v.isEmpty()) whole.append(v);
return whole.toString();
}
StringBuilder whole = new StringBuilder();
StringBuilder part = new StringBuilder();
boolean inEscape = false;
for (char c: string.toCharArray())
{
if (inEscape && c == ';')
{
part.append(c);
inEscape = false;
String v = empty(part);
if (v.equals("&lt;")) part.append('<');
if (v.equals("&gt;")) part.append('>');
if (v.equals("&amp;")) part.append('&');
if (v.equals("&quot;")) part.append('"');
if (v.equals("&apos;")) part.append('\'');
if (v.equals("&#39;")) part.append('\'');
}
else if (!inEscape && c == '&')
{
String v = empty(part);
if (!v.isEmpty()) whole.append(v);
part.append(c);
inEscape = true;
}
else
{
part.append(c);
}
}
String v = empty(part);
if (!v.isEmpty()) whole.append(v);
return whole.toString();
}
}

View File

@ -41,41 +41,41 @@ implements Transferable, ClipboardOwner {
{
assert string != null;
instance.string = string;
Toolkit tk = Toolkit.getDefaultToolkit();
Clipboard cb = tk.getSystemClipboard();
cb.setContents(instance, instance);
Toolkit tk = Toolkit.getDefaultToolkit();
Clipboard cb = tk.getSystemClipboard();
cb.setContents(instance, instance);
}
// - -%- -
public String
getTransferData(DataFlavor flavour)
{
public String
getTransferData(DataFlavor flavour)
{
assert flavour == DataFlavor.stringFlavor;
return string;
}
}
public DataFlavor[]
getTransferDataFlavors()
{
return new DataFlavor[] { DataFlavor.stringFlavor };
/*
* We should probably also support javaJVMLocalObjectMimeType,
* so that the compose window can ask for the List<Segment>.
* Although also like, if we don't store emoji shortcodes in
* the image segments, there is no point. Anyways, what is
* important is the string format first, allowing us to
* copy links or large lengths of text.
*/
}
public DataFlavor[]
getTransferDataFlavors()
{
return new DataFlavor[] { DataFlavor.stringFlavor };
/*
* We should probably also support javaJVMLocalObjectMimeType,
* so that the compose window can ask for the List<Segment>.
* Although also like, if we don't store emoji shortcodes in
* the image segments, there is no point. Anyways, what is
* important is the string format first, allowing us to
* copy links or large lengths of text.
*/
}
public boolean
isDataFlavorSupported(DataFlavor flavour)
{
return flavour == DataFlavor.stringFlavor;
}
public boolean
isDataFlavorSupported(DataFlavor flavour)
{
return flavour == DataFlavor.stringFlavor;
}
public void
lostOwnership(Clipboard clipboard, Transferable contents) { }
public void
lostOwnership(Clipboard clipboard, Transferable contents) { }
}

View File

@ -126,86 +126,86 @@ ComposeWindow extends JFrame {
if (composition.contentWarning != null)
assert !composition.contentWarning.trim().isEmpty();
//tabs.setCursor(new Cursor(Cursor.WAIT_CURSOR));
/*
* setCursor only works for components that are enabled.
* I don't think there's any technical reason for this,
* but it's what it is.. rely on the enablement visuals
* or a statusbar to indicate to user.
*
* If we really wanted it, I suspect we could use a glass
* pane (or a scroll pane with no scrolling) to have an
* enabled pass-through on top.
*
* Technically contentsDisplay and attachmentsDisplay
* themselves aren't disabled. But disabling the tab pane
* covers both the tab area and content area. We can't
* just the tab area, except maybe by disabling the
* individual tabs.
*/
tabs.setEnabled(false);
tabs.setSelectedComponent(contentsDisplay);
contentsDisplay.setSubmitting(true);
tabs.paintImmediately(tabs.getBounds());
//tabs.setCursor(new Cursor(Cursor.WAIT_CURSOR));
/*
* setCursor only works for components that are enabled.
* I don't think there's any technical reason for this,
* but it's what it is.. rely on the enablement visuals
* or a statusbar to indicate to user.
*
* If we really wanted it, I suspect we could use a glass
* pane (or a scroll pane with no scrolling) to have an
* enabled pass-through on top.
*
* Technically contentsDisplay and attachmentsDisplay
* themselves aren't disabled. But disabling the tab pane
* covers both the tab area and content area. We can't
* just the tab area, except maybe by disabling the
* individual tabs.
*/
tabs.setEnabled(false);
tabs.setSelectedComponent(contentsDisplay);
contentsDisplay.setSubmitting(true);
tabs.paintImmediately(tabs.getBounds());
boolean uploadsOkay = true;
for (Attachment a: composition.attachments)
{
if (a.id != null) continue;
// Assume it had already been uploaded.
boolean uploadsOkay = true;
for (Attachment a: composition.attachments)
{
if (a.id != null) continue;
// Assume it had already been uploaded.
api.uploadFile(
a.uploadee, a.description,
new RequestListener() {
api.uploadFile(
a.uploadee, a.description,
new RequestListener() {
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
ComposeWindow.this,
"Tried to upload attachment, failed..."
+ "\n" + eIo.getMessage()
);
}
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
ComposeWindow.this,
"Tried to upload attachment, failed..."
+ "\n" + json.get("error").value
+ "\n" + json.get("error").value
+ "\n(HTTP code: " + httpCode + ")"
);
}
}
public void
requestSucceeded(Tree<String> json)
{
a.id = json.get("id").value;
}
public void
requestSucceeded(Tree<String> json)
{
a.id = json.get("id").value;
}
});
uploadsOkay &= a.id != null;
}
});
uploadsOkay &= a.id != null;
}
if (!uploadsOkay)
{
contentsDisplay.setSubmitting(false);
tabs.setEnabled(true);
//tabs.setCursor(null);
return;
}
if (!uploadsOkay)
{
contentsDisplay.setSubmitting(false);
tabs.setEnabled(true);
//tabs.setCursor(null);
return;
}
int amt = composition.attachments.length;
String[] mediaIDs = new String[amt];
for (int o = 0; o < mediaIDs.length; ++o)
mediaIDs[o] = composition.attachments[o].id;
int amt = composition.attachments.length;
String[] mediaIDs = new String[amt];
for (int o = 0; o < mediaIDs.length; ++o)
mediaIDs[o] = composition.attachments[o].id;
api.submit(
api.submit(
composition.text, composition.visibility,
composition.replyToPostId, composition.contentWarning,
mediaIDs,
mediaIDs,
new RequestListener() {
public void
@ -239,8 +239,8 @@ ComposeWindow extends JFrame {
);
contentsDisplay.setSubmitting(false);
tabs.setEnabled(true);
tabs.setCursor(null);
tabs.setEnabled(true);
tabs.setCursor(null);
}
// - -%- -
@ -254,14 +254,14 @@ ComposeWindow extends JFrame {
d1.setVisibility(stringFor(composition.visibility));
d1.setContentWarning(composition.contentWarning);
AttachmentsComponent d2 = attachmentsDisplay;
d2.setAttachments(composition.attachments);
AttachmentsComponent d2 = attachmentsDisplay;
d2.setAttachments(composition.attachments);
}
private synchronized void
syncCompositionToDisplay()
{
Composition c = composition;
Composition c = composition;
ComposeComponent d1 = contentsDisplay;
c.text = d1.getText();
@ -269,8 +269,8 @@ ComposeWindow extends JFrame {
c.replyToPostId = nonEmpty(d1.getReplyToPostId());
c.contentWarning = nonEmpty(d1.getContentWarning());
AttachmentsComponent d2 = attachmentsDisplay;
c.attachments = d2.getAttachments();
AttachmentsComponent d2 = attachmentsDisplay;
c.attachments = d2.getAttachments();
}
// - -%- -
@ -509,18 +509,18 @@ implements ActionListener, CaretListener, KeyListener {
Border b3 = BorderFactory.createLineBorder(Color.GRAY);
Border bc = BorderFactory.createCompoundBorder(b3, b2);
TextActionPopupMenu textActionPopup;
textActionPopup = new TextActionPopupMenu();
TextActionPopupMenu textActionPopup;
textActionPopup = new TextActionPopupMenu();
reply = new JTextField();
JLabel replyLabel = new JLabel("In reply to: ");
replyLabel.setLabelFor(reply);
reply.addMouseListener(textActionPopup);
reply.addMouseListener(textActionPopup);
contentWarning = new JTextField();
JLabel cwLabel = new JLabel("Content warning: ");
cwLabel.setLabelFor(contentWarning);
contentWarning.addMouseListener(textActionPopup);
contentWarning.addMouseListener(textActionPopup);
JPanel top = new JPanel();
top.setOpaque(false);
@ -560,7 +560,7 @@ implements ActionListener, CaretListener, KeyListener {
text.setBorder(bc);
text.addCaretListener(this);
text.addKeyListener(this);
text.addMouseListener(textActionPopup);
text.addMouseListener(textActionPopup);
setLayout(new BorderLayout(0, 8));
add(top, BorderLayout.NORTH);
@ -592,7 +592,7 @@ implements ComponentListener, ActionListener {
attachment2,
attachment3,
attachment4,
selected;
selected;
private JButton
add;
@ -607,83 +607,83 @@ implements ComponentListener, ActionListener {
private JTextArea
description;
private JFileChooser
chooser;
private JFileChooser
chooser;
private UndoManager
descriptionUndos;
private UndoManager
descriptionUndos;
// ---%-@-%---
public void
setAttachments(Attachment[] n)
{
working.clear();
if (n != null) for (Attachment attachment: n)
{
working.add(attachment);
}
updateButtons();
}
public void
setAttachments(Attachment[] n)
{
working.clear();
if (n != null) for (Attachment attachment: n)
{
working.add(attachment);
}
updateButtons();
}
public Attachment[]
getAttachments()
{
return working.toArray(new Attachment[0]);
}
public Attachment[]
getAttachments()
{
return working.toArray(new Attachment[0]);
}
// - -%- -
// - -%- -
private void
updateButtons()
{
Dimension sz = add.getPreferredSize();
selections.removeAll();
selected = null;
selections.removeAll();
selected = null;
if (working.size() > 0)
{
selections.add(attachment1);
Image i = working.get(0).image;
attachment1.setIcon(new ImageIcon(i));
}
if (working.size() > 1)
{
selections.add(attachment2);
Image i = working.get(1).image;
attachment2.setIcon(new ImageIcon(i));
}
if (working.size() > 2)
{
selections.add(attachment3);
Image i = working.get(2).image;
attachment3.setIcon(new ImageIcon(i));
}
{
selections.add(attachment1);
Image i = working.get(0).image;
attachment1.setIcon(new ImageIcon(i));
}
if (working.size() > 1)
{
selections.add(attachment2);
Image i = working.get(1).image;
attachment2.setIcon(new ImageIcon(i));
}
if (working.size() > 2)
{
selections.add(attachment3);
Image i = working.get(2).image;
attachment3.setIcon(new ImageIcon(i));
}
if (working.size() > 3)
{
selections.add(attachment4);
Image i = working.get(3).image;
attachment4.setIcon(new ImageIcon(i));
}
if (working.size() < 4) selections.add(add);
{
selections.add(attachment4);
Image i = working.get(3).image;
attachment4.setIcon(new ImageIcon(i));
}
if (working.size() < 4) selections.add(add);
if (working.size() > 3) open(attachment4);
else if (working.size() > 2) open(attachment3);
else if (working.size() > 1) open(attachment2);
else if (working.size() > 0) open(attachment1);
if (working.size() > 3) open(attachment4);
else if (working.size() > 2) open(attachment3);
else if (working.size() > 1) open(attachment2);
else if (working.size() > 0) open(attachment1);
int bw = sz.width;
int bw = sz.width;
int hgap = 4;
int count = selections.getComponents().length;
int w = count * bw + (count - 1) * hgap;
int h = bw;
selections.setPreferredSize(new Dimension(w, h));
selections.setMaximumSize(new Dimension(w, h));
selections.revalidate();
selections.revalidate();
delete.setEnabled(selected != null);
revert.setEnabled(selected != null);
description.setEnabled(selected != null);
delete.setEnabled(selected != null);
revert.setEnabled(selected != null);
description.setEnabled(selected != null);
}
public void
@ -693,34 +693,34 @@ implements ComponentListener, ActionListener {
if (src == add)
{
int r = chooser.showOpenDialog(this);
if (r != JFileChooser.APPROVE_OPTION) return;
int r = chooser.showOpenDialog(this);
if (r != JFileChooser.APPROVE_OPTION) return;
File f = chooser.getSelectedFile();
File f = chooser.getSelectedFile();
Attachment a = new Attachment();
a.uploadee = f;
a.description = "";
a.type = "unknown";
Attachment a = new Attachment();
a.uploadee = f;
a.description = "";
a.type = "unknown";
String mime = "", primary = "";
try
{
mime = Files.probeContentType(f.toPath());
primary = mime.split("/")[0];
// Too lazy to instantiate a
// javax.activation.MimeType.
}
catch (IOException eIo) { }
if (primary.equals("image"))
{
String urlr = f.toURI().toString();
a.image = ImageApi.remote(urlr);
a.type = "image";
}
String mime = "", primary = "";
try
{
mime = Files.probeContentType(f.toPath());
primary = mime.split("/")[0];
// Too lazy to instantiate a
// javax.activation.MimeType.
}
catch (IOException eIo) { }
if (primary.equals("image"))
{
String urlr = f.toURI().toString();
a.image = ImageApi.remote(urlr);
a.type = "image";
}
if (selected != null) open(selected);
// Save first before resetting
if (selected != null) open(selected);
// Save first before resetting
working.add(a);
updateButtons();
@ -728,87 +728,87 @@ implements ComponentListener, ActionListener {
if (src == delete)
{
assert selected != null;
working.remove(getAttachmentFor(selected));
updateButtons();
assert selected != null;
working.remove(getAttachmentFor(selected));
updateButtons();
return;
}
if (src != add && selections.isAncestorOf((Component)src))
{
assert src instanceof JToggleButton;
if (src == selected) preview(getAttachmentFor(src));
else open((JToggleButton)src);
return;
}
if (src != add && selections.isAncestorOf((Component)src))
{
assert src instanceof JToggleButton;
if (src == selected) preview(getAttachmentFor(src));
else open((JToggleButton)src);
return;
}
if (src == revert)
{
while (descriptionUndos.canUndo())
descriptionUndos.undo();
return;
while (descriptionUndos.canUndo())
descriptionUndos.undo();
return;
}
}
private Attachment
getAttachmentFor(Object button)
{
if (button == null) return null;
assert button instanceof JToggleButton;
assert selections.isAncestorOf((Component)button);
private Attachment
getAttachmentFor(Object button)
{
if (button == null) return null;
assert button instanceof JToggleButton;
assert selections.isAncestorOf((Component)button);
int index = 0;
if (button == attachment4) index = 4;
if (button == attachment3) index = 3;
if (button == attachment2) index = 2;
if (button == attachment1) index = 1;
int index = 0;
if (button == attachment4) index = 4;
if (button == attachment3) index = 3;
if (button == attachment2) index = 2;
if (button == attachment1) index = 1;
assert index != 0;
assert index <= working.size();
return working.get(index - 1);
}
assert index != 0;
assert index <= working.size();
return working.get(index - 1);
}
private void
open(JToggleButton button)
{
assert selections.isAncestorOf(button);
private void
open(JToggleButton button)
{
assert selections.isAncestorOf(button);
if (selected != null)
{
Attachment a = getAttachmentFor(selected);
a.description = description.getText();
selected.setSelected(false);
}
if (selected != null)
{
Attachment a = getAttachmentFor(selected);
a.description = description.getText();
selected.setSelected(false);
}
Attachment a = getAttachmentFor(button);
description.setText(a.description);
descriptionUndos.discardAllEdits();
(selected = button).setSelected(true);
}
Attachment a = getAttachmentFor(button);
description.setText(a.description);
descriptionUndos.discardAllEdits();
(selected = button).setSelected(true);
}
public void
componentHidden(ComponentEvent eC)
{
if (selected != null) open(selected);
}
public void
componentHidden(ComponentEvent eC)
{
if (selected != null) open(selected);
}
private void
preview(Attachment a)
{
ImageWindow w = new ImageWindow();
w.showAttachments(new Attachment[] { a } );
w.setTitle("Attachment preview");
w.setVisible(true);
}
private void
preview(Attachment a)
{
ImageWindow w = new ImageWindow();
w.showAttachments(new Attachment[] { a } );
w.setTitle("Attachment preview");
w.setVisible(true);
}
public void
componentShown(ComponentEvent eC) { }
public void
componentShown(ComponentEvent eC) { }
public void
componentMoved(ComponentEvent eC) { }
public void
componentMoved(ComponentEvent eC) { }
public void
componentResized(ComponentEvent eC) { }
public void
componentResized(ComponentEvent eC) { }
// ---%-@-%---
@ -824,15 +824,15 @@ implements ComponentListener, ActionListener {
Border bc1 = BorderFactory.createCompoundBorder(b3, b2);
Border bc2 = BorderFactory.createCompoundBorder(b4, b2);
TextActionPopupMenu textActionPopup;
textActionPopup = new TextActionPopupMenu();
TextActionPopupMenu textActionPopup;
textActionPopup = new TextActionPopupMenu();
chooser = new JFileChooser();
chooser = new JFileChooser();
add = new JButton("+");
add.setPreferredSize(new Dimension(32, 32));
add.setMargin(new Insets(0, 0, 0, 0));
add.addActionListener(this);
add.addActionListener(this);
attachment1 = new JToggleButton("1");
attachment2 = new JToggleButton("2");
attachment3 = new JToggleButton("3");
@ -841,28 +841,28 @@ implements ComponentListener, ActionListener {
attachment2.setMargin(add.getMargin());
attachment3.setMargin(add.getMargin());
attachment4.setMargin(add.getMargin());
attachment1.addActionListener(this);
attachment2.addActionListener(this);
attachment3.addActionListener(this);
attachment4.addActionListener(this);
attachment1.addActionListener(this);
attachment2.addActionListener(this);
attachment3.addActionListener(this);
attachment4.addActionListener(this);
selections = new JPanel();
selections.setOpaque(false);
selections.setLayout(new GridLayout(1, 0, 4, 0));
working = new ArrayList<Attachment>();
Box top = Box.createHorizontalBox();
Box top = Box.createHorizontalBox();
top.add(selections);
top.add(Box.createGlue());
delete = new JButton("Delete");
revert = new JButton("Revert");
JButton ml = new JButton("");
JButton ml = new JButton("");
JButton mr = new JButton("");
delete.addActionListener(this);
delete.addActionListener(this);
revert.addActionListener(this);
Box bottom = Box.createHorizontalBox();
Box bottom = Box.createHorizontalBox();
bottom.add(ml);
bottom.add(mr);
bottom.add(Box.createHorizontalStrut(8));
@ -876,14 +876,14 @@ implements ComponentListener, ActionListener {
java.awt.Font f = description.getFont();
description.setFont(f.deriveFont(16f));
description.setBorder(bc1);
description.addMouseListener(textActionPopup);
description.addMouseListener(textActionPopup);
descriptionLabel = new JLabel("Description");
descriptionLabel.setLabelFor(description);
descriptionUndos = new UndoManager();
description.getDocument().
addUndoableEditListener(descriptionUndos);
descriptionUndos = new UndoManager();
description.getDocument().
addUndoableEditListener(descriptionUndos);
updateButtons();
updateButtons();
JPanel row1 = new JPanel();
row1.setOpaque(false);
@ -891,7 +891,7 @@ implements ComponentListener, ActionListener {
row1.add(descriptionLabel, BorderLayout.NORTH);
row1.add(description, BorderLayout.CENTER);
Box centre = Box.createVerticalBox();
Box centre = Box.createVerticalBox();
centre.setBorder(b4);
centre.add(row1);
@ -901,7 +901,7 @@ implements ComponentListener, ActionListener {
add(bottom, BorderLayout.SOUTH);
setBorder(b1);
this.addComponentListener(this);
this.addComponentListener(this);
}
}
@ -910,66 +910,66 @@ class
TextActionPopupMenu extends JPopupMenu
implements MouseListener, ActionListener {
private JMenuItem
copy,
cut,
paste;
private JMenuItem
copy,
cut,
paste;
private long
pressed;
private long
pressed;
// ---%-@-%---
// ---%-@-%---
public void
mousePressed(MouseEvent eM)
{
assert eM.getSource() instanceof JTextComponent;
if (!eM.isPopupTrigger()) return;
show((Component)eM.getSource(), eM.getX(), eM.getY());
}
public void
mousePressed(MouseEvent eM)
{
assert eM.getSource() instanceof JTextComponent;
if (!eM.isPopupTrigger()) return;
show((Component)eM.getSource(), eM.getX(), eM.getY());
}
public void
mouseReleased(MouseEvent eM)
{
if (eM.getClickCount() == 0) setVisible(false);
}
public void
mouseReleased(MouseEvent eM)
{
if (eM.getClickCount() == 0) setVisible(false);
}
public void
mouseClicked(MouseEvent eM) { }
public void
mouseClicked(MouseEvent eM) { }
public void
mouseEntered(MouseEvent eM) { }
public void
mouseEntered(MouseEvent eM) { }
public void
mouseExited(MouseEvent eM) { }
public void
mouseExited(MouseEvent eM) { }
public void
actionPerformed(ActionEvent eA)
{
assert getInvoker() instanceof JTextComponent;
JTextComponent inv = (JTextComponent)getInvoker();
public void
actionPerformed(ActionEvent eA)
{
assert getInvoker() instanceof JTextComponent;
JTextComponent inv = (JTextComponent)getInvoker();
if (eA.getSource() == copy) inv.copy();
if (eA.getSource() == cut) inv.cut();
if (eA.getSource() == paste) inv.paste();
}
if (eA.getSource() == copy) inv.copy();
if (eA.getSource() == cut) inv.cut();
if (eA.getSource() == paste) inv.paste();
}
// ---%-@-%---
// ---%-@-%---
public
TextActionPopupMenu()
{
copy = new JMenuItem("Copy");
cut = new JMenuItem("Cut");
paste = new JMenuItem("Paste");
copy.addActionListener(this);
cut.addActionListener(this);
paste.addActionListener(this);
public
TextActionPopupMenu()
{
copy = new JMenuItem("Copy");
cut = new JMenuItem("Cut");
paste = new JMenuItem("Paste");
copy.addActionListener(this);
cut.addActionListener(this);
paste.addActionListener(this);
add(cut);
add(copy);
add(paste);
}
add(cut);
add(copy);
add(paste);
}
}

View File

@ -41,303 +41,303 @@ import java.net.MalformedURLException;
class
ImageWindow extends JFrame {
private Attachment[]
attachments;
private Attachment[]
attachments;
private int
offset;
private int
offset;
// - -%- -
// - -%- -
private ImageComponent
display;
private ImageComponent
display;
// ---%-@-%---
// ---%-@-%---
public synchronized void
showAttachments(Attachment[] attachments)
{
this.attachments = attachments;
public synchronized void
showAttachments(Attachment[] attachments)
{
this.attachments = attachments;
if (attachments.length == 0) {
display.setImage(null);
display.setNext(null);
display.setPrev(null);
display.repaint();
return;
}
if (attachments.length == 0) {
display.setImage(null);
display.setNext(null);
display.setPrev(null);
display.repaint();
return;
}
toImage(offset = 0);
}
toImage(offset = 0);
}
public void
toNextImage()
{
if (attachments.length == 0) return;
assert offset < attachments.length - 1;
toImage(++offset);
}
public void
toNextImage()
{
if (attachments.length == 0) return;
assert offset < attachments.length - 1;
toImage(++offset);
}
public void
toPrevImage()
{
if (attachments.length == 0) return;
assert offset > 0;
toImage(--offset);
}
public void
toPrevImage()
{
if (attachments.length == 0) return;
assert offset > 0;
toImage(--offset);
}
// - -%- -
// - -%- -
private synchronized void
toImage(int offset)
{
int last = attachments.length - 1;
assert offset >= 0;
assert offset < attachments.length;
private synchronized void
toImage(int offset)
{
int last = attachments.length - 1;
assert offset >= 0;
assert offset < attachments.length;
Attachment prev, curr, next;
curr = attachments[offset];
next = offset < last ? attachments[offset + 1] : null;
prev = offset > 0 ? attachments[offset - 1] : null;
Attachment prev, curr, next;
curr = attachments[offset];
next = offset < last ? attachments[offset + 1] : null;
prev = offset > 0 ? attachments[offset - 1] : null;
display.setImage(curr.image);
display.setNext(next != null ? next.image : null);
display.setPrev(prev != null ? prev.image : null);
display.setImage(curr.image);
display.setNext(next != null ? next.image : null);
display.setPrev(prev != null ? prev.image : null);
if (!curr.type.equals("image"))
display.setToolTipText(
display.getToolTipText()
+ "\n(Media is of type '" + curr.type + "')"
);
if (!curr.type.equals("image"))
display.setToolTipText(
display.getToolTipText()
+ "\n(Media is of type '" + curr.type + "')"
);
repaint();
}
repaint();
}
// ---%-@-%---
// ---%-@-%---
ImageWindow()
{
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setSize(600, 600);
ImageWindow()
{
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setSize(600, 600);
display = new ImageComponent(this);
showAttachments(new Attachment[0]);
setContentPane(display);
}
display = new ImageComponent(this);
showAttachments(new Attachment[0]);
setContentPane(display);
}
}
class
ImageComponent extends JPanel
implements
ActionListener,
MouseListener, MouseMotionListener, MouseWheelListener {
ActionListener,
MouseListener, MouseMotionListener, MouseWheelListener {
private ImageWindow
primaire;
private ImageWindow
primaire;
// - -%- -
// - -%- -
private Image
image, prevImage, nextImage;
private Image
image, prevImage, nextImage;
private JPanel
buttonArea;
private JPanel
buttonArea;
private JButton
prev, next, toggle;
private JButton
prev, next, toggle;
private boolean
scaleImage;
private boolean
scaleImage;
private int
xOffset, yOffset, zoomLevel;
private int
xOffset, yOffset, zoomLevel;
private int
dragX, dragY, xPOffset, yPOffset;
private int
dragX, dragY, xPOffset, yPOffset;
// ---%-@-%---
// ---%-@-%---
public void
setImage(Image image)
{
this.image = image;
if (image != null) {
Object p = image.getProperty("comment", this);
String desc = p instanceof String ? (String)p : null;
setToolTipText(desc);
}
xOffset = yOffset = xPOffset = yPOffset = 0;
zoomLevel = 100;
}
public void
setImage(Image image)
{
this.image = image;
if (image != null) {
Object p = image.getProperty("comment", this);
String desc = p instanceof String ? (String)p : null;
setToolTipText(desc);
}
xOffset = yOffset = xPOffset = yPOffset = 0;
zoomLevel = 100;
}
public void
setPrev(Image image)
{
prev.setEnabled(image != null);
prev.setText(image == null ? "<" : "");
prev.setIcon(toIcon(image));
}
public void
setPrev(Image image)
{
prev.setEnabled(image != null);
prev.setText(image == null ? "<" : "");
prev.setIcon(toIcon(image));
}
public void
setNext(Image image)
{
next.setEnabled(image != null);
next.setText(image == null ? ">" : "");
next.setIcon(toIcon(image));
}
public void
setNext(Image image)
{
next.setEnabled(image != null);
next.setText(image == null ? ">" : "");
next.setIcon(toIcon(image));
}
// - -%- -
// - -%- -
public void
actionPerformed(ActionEvent eA)
{
if (eA.getSource() == prev) primaire.toPrevImage();
if (eA.getSource() == next) primaire.toNextImage();
if (eA.getSource() == toggle) {
scaleImage = !scaleImage;
if (scaleImage) toggle.setText("Show unscaled");
else toggle.setText("Show scaled to window");
setImage(this.image);
repaint();
}
}
public void
actionPerformed(ActionEvent eA)
{
if (eA.getSource() == prev) primaire.toPrevImage();
if (eA.getSource() == next) primaire.toNextImage();
if (eA.getSource() == toggle) {
scaleImage = !scaleImage;
if (scaleImage) toggle.setText("Show unscaled");
else toggle.setText("Show scaled to window");
setImage(this.image);
repaint();
}
}
public void
mousePressed(MouseEvent eM)
{
dragX = eM.getX();
dragY = eM.getY();
}
public void
mousePressed(MouseEvent eM)
{
dragX = eM.getX();
dragY = eM.getY();
}
public void
mouseDragged(MouseEvent eM)
{
int dx = eM.getX() - dragX;
int dy = eM.getY() - dragY;
xPOffset = dx;
yPOffset = dy;
repaint();
}
public void
mouseDragged(MouseEvent eM)
{
int dx = eM.getX() - dragX;
int dy = eM.getY() - dragY;
xPOffset = dx;
yPOffset = dy;
repaint();
}
public void
mouseReleased(MouseEvent eM)
{
xOffset += xPOffset;
yOffset += yPOffset;
xPOffset = yPOffset = 0;
}
public void
mouseReleased(MouseEvent eM)
{
xOffset += xPOffset;
yOffset += yPOffset;
xPOffset = yPOffset = 0;
}
public void
mouseWheelMoved(MouseWheelEvent eMw)
{
zoomLevel += 10 * -eMw.getUnitsToScroll();
if (zoomLevel < 50) zoomLevel = 50;
if (zoomLevel > 400) zoomLevel = 400;
repaint();
}
public void
mouseWheelMoved(MouseWheelEvent eMw)
{
zoomLevel += 10 * -eMw.getUnitsToScroll();
if (zoomLevel < 50) zoomLevel = 50;
if (zoomLevel > 400) zoomLevel = 400;
repaint();
}
public void
mouseEntered(MouseEvent eM) { }
public void
mouseEntered(MouseEvent eM) { }
public void
mouseExited(MouseEvent eM) { }
public void
mouseExited(MouseEvent eM) { }
public void
mouseClicked(MouseEvent eM) { }
public void
mouseClicked(MouseEvent eM) { }
public void
mouseMoved(MouseEvent eM) { }
public void
mouseMoved(MouseEvent eM) { }
// - -%- -
// - -%- -
private static ImageIcon
toIcon(Image image)
{
if (image == null) return null;
return new ImageIcon(image);
}
private static ImageIcon
toIcon(Image image)
{
if (image == null) return null;
return new ImageIcon(image);
}
// ---%-@-%---
// ---%-@-%---
private class
Painter extends JPanel {
private class
Painter extends JPanel {
protected void
paintComponent(Graphics g)
{
if (image == null)
{
String str =
protected void
paintComponent(Graphics g)
{
if (image == null)
{
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;
g.drawString(str, x, y);
return;
}
int wo = image.getWidth(this);
int ho = image.getHeight(this);
int wn, hn;
if (wo > ho) {
wn = scaleImage ? getWidth() : wo;
hn = ho * wn / wo;
}
else {
hn = scaleImage ? getHeight() : ho;
wn = wo * hn / ho;
}
wn = wn * zoomLevel / 100;
hn = hn * zoomLevel / 100;
int x = (getWidth() - wn) / 2;
int y = (getHeight() - hn) / 2;
x += xOffset + xPOffset;
y += yOffset + yPOffset;
g.drawImage(image, x, y, wn, hn, this);
}
FontMetrics fm = g.getFontMetrics();
int x = (getWidth() - fm.stringWidth(str)) / 2;
int y = (getHeight() + fm.getHeight()) / 2;
g.drawString(str, x, y);
return;
}
int wo = image.getWidth(this);
int ho = image.getHeight(this);
int wn, hn;
if (wo > ho) {
wn = scaleImage ? getWidth() : wo;
hn = ho * wn / wo;
}
else {
hn = scaleImage ? getHeight() : ho;
wn = wo * hn / ho;
}
wn = wn * zoomLevel / 100;
hn = hn * zoomLevel / 100;
int x = (getWidth() - wn) / 2;
int y = (getHeight() - hn) / 2;
x += xOffset + xPOffset;
y += yOffset + yPOffset;
g.drawImage(image, x, y, wn, hn, this);
}
}
}
// ---%-@-%---
// ---%-@-%---
ImageComponent(ImageWindow primaire)
{
this.primaire = primaire;
ImageComponent(ImageWindow primaire)
{
this.primaire = primaire;
Dimension BUTTON_SIZE = new Dimension(48, 48);
Dimension BUTTON_SIZE = new Dimension(48, 48);
setOpaque(false);
scaleImage = true;
zoomLevel = 100;
setOpaque(false);
scaleImage = true;
zoomLevel = 100;
prev = new JButton();
toggle = new JButton("Show unscaled");
next = new JButton();
prev.setPreferredSize(BUTTON_SIZE);
next.setPreferredSize(BUTTON_SIZE);
prev.addActionListener(this);
toggle.addActionListener(this);
next.addActionListener(this);
prev = new JButton();
toggle = new JButton("Show unscaled");
next = new JButton();
prev.setPreferredSize(BUTTON_SIZE);
next.setPreferredSize(BUTTON_SIZE);
prev.addActionListener(this);
toggle.addActionListener(this);
next.addActionListener(this);
buttonArea = new JPanel();
buttonArea.setOpaque(false);
buttonArea.add(prev);
buttonArea.add(toggle);
buttonArea.add(next);
buttonArea = new JPanel();
buttonArea.setOpaque(false);
buttonArea.add(prev);
buttonArea.add(toggle);
buttonArea.add(next);
setPrev(null);
setNext(null);
setPrev(null);
setNext(null);
setLayout(new BorderLayout());
add(buttonArea, BorderLayout.SOUTH);
add(new Painter(), BorderLayout.CENTER);
setLayout(new BorderLayout());
add(buttonArea, BorderLayout.SOUTH);
add(new Painter(), BorderLayout.CENTER);
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
}
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
}
}

View File

@ -60,20 +60,20 @@ JKomasto {
private LoginWindow
loginWindow;
private ImageWindow
mediaWindow;
private ImageWindow
mediaWindow;
private NotificationsWindow
notificationsWindow;
private NotificationsWindow
notificationsWindow;
private WindowUpdater
windowUpdater;
private WindowUpdater
windowUpdater;
private MastodonApi
api;
private Image
programIcon;
private Image
programIcon;
// ---%-@-%---
@ -85,10 +85,10 @@ JKomasto {
{
timelineWindow.setCursor(new Cursor(Cursor.WAIT_CURSOR));
timelineWindow.showLatestPage();
timelineWindow.showLatestPage();
notificationsWindow.showLatestPage();
timelineWindow.setVisible(true);
loginWindow.dispose();
loginWindow.dispose();
timelineWindow.setCursor(null);
}
@ -99,83 +99,83 @@ JKomasto {
public ComposeWindow
getComposeWindow() { return composeWindow; }
public ImageWindow
public ImageWindow
getMediaWindow() { return mediaWindow; }
public NotificationsWindow
public NotificationsWindow
getNotificationsWindow() { return notificationsWindow; }
public WindowUpdater
getWindowUpdater() { return windowUpdater; }
public Image
getProgramIcon() { return programIcon; }
public Image
getProgramIcon() { return programIcon; }
// ---%-@-%---
private static class
MetalTheme extends OceanTheme {
private static class
MetalTheme extends OceanTheme {
private ColorUIResource
lightPink = new ColorUIResource(246, 240, 240),
mildPink = new ColorUIResource(238, 233, 233),
white = new ColorUIResource(250, 250, 250),
darkPink = new ColorUIResource(242, 230, 230),
veryDarkPink = new ColorUIResource(164, 160, 160);
private ColorUIResource
lightPink = new ColorUIResource(246, 240, 240),
mildPink = new ColorUIResource(238, 233, 233),
white = new ColorUIResource(250, 250, 250),
darkPink = new ColorUIResource(242, 230, 230),
veryDarkPink = new ColorUIResource(164, 160, 160);
// -=%=-
// -=%=-
public ColorUIResource
getPrimary2() { return darkPink; }
public ColorUIResource
getPrimary2() { return darkPink; }
public ColorUIResource
getSecondary2() { return white; }
public ColorUIResource
getSecondary2() { return white; }
public ColorUIResource
getSecondary3() { return mildPink; }
public ColorUIResource
getSecondary3() { return mildPink; }
public ColorUIResource
getSecondary1() { return veryDarkPink; }
public ColorUIResource
getSecondary1() { return veryDarkPink; }
public ColorUIResource
getPrimary1() { return veryDarkPink; }
public ColorUIResource
getPrimary1() { return veryDarkPink; }
public void
addCustomEntriesToTable(UIDefaults table)
{
super.addCustomEntriesToTable(table);
table.put(
"TabbedPane.tabAreaBackground",
getPrimary1()
);
table.put(
"TabbedPane.contentAreaColor",
getSecondary3()
);
table.put(
"TabbedPane.selected",
getSecondary3()
);
table.put(
"MenuBar.gradient",
java.util.Arrays.asList(new Object[] {
1f, 0f,
getWhite(),
getSecondary3(),
getSecondary1()
})
);
}
public void
addCustomEntriesToTable(UIDefaults table)
{
super.addCustomEntriesToTable(table);
table.put(
"TabbedPane.tabAreaBackground",
getPrimary1()
);
table.put(
"TabbedPane.contentAreaColor",
getSecondary3()
);
table.put(
"TabbedPane.selected",
getSecondary3()
);
table.put(
"MenuBar.gradient",
java.util.Arrays.asList(new Object[] {
1f, 0f,
getWhite(),
getSecondary3(),
getSecondary1()
})
);
}
}
}
// ---%-@-%---
public static void
main(String... args)
{
//System.setProperty("swing.boldMetal", "false");
MetalLookAndFeel.setCurrentTheme(new MetalTheme());
//System.setProperty("swing.boldMetal", "false");
MetalLookAndFeel.setCurrentTheme(new MetalTheme());
new JKomasto().loginWindow.setVisible(true);
}
@ -187,24 +187,24 @@ JKomasto {
{
api = new MastodonApi();
windowUpdater = new WindowUpdater(this);
programIcon = ImageApi.local("kettle");
programIcon = ImageApi.local("kettle");
timelineWindow = new TimelineWindow(this);
composeWindow = new ComposeWindow(this);
autoViewWindow = new PostWindow(this);
loginWindow = new LoginWindow(this);
mediaWindow = new ImageWindow();
notificationsWindow = new NotificationsWindow(this);
mediaWindow = new ImageWindow();
notificationsWindow = new NotificationsWindow(this);
autoViewWindow.setTitle("Auto view - JKomasto");
composeWindow.dispose();
autoViewWindow.dispose();
timelineWindow.dispose();
mediaWindow.dispose();
notificationsWindow.dispose();
mediaWindow.dispose();
notificationsWindow.dispose();
timelineWindow.setLocationByPlatform(true);
timelineWindow.setLocationByPlatform(true);
loginWindow.setLocationByPlatform(true);
}
@ -236,13 +236,13 @@ TimelineType {
enum
NotificationType {
MENTION,
BOOST,
FAVOURITE,
FOLLOW,
FOLLOWREQ,
POLL,
ALERT
MENTION,
BOOST,
FAVOURITE,
FOLLOW,
FOLLOWREQ,
POLL,
ALERT
}
@ -257,8 +257,8 @@ TimelinePage {
public String
accountNumId, listId;
public Post[]
posts;
public Post[]
posts;
// ---%-@-%---
@ -279,14 +279,14 @@ TimelinePage {
class
Notification {
public NotificationType
type;
public NotificationType
type;
public String
id;
public String
id;
public String
postId, postText, actorNumId, actorName;
public String
postId, postText, actorNumId, actorName;
}
@ -348,35 +348,35 @@ Post {
assert text != null;
if (approximateText != null) return;
Tree<String> nodes;
nodes = RudimentaryHTMLParser.depthlessRead(text);
if (nodes.size() == 0)
{
Tree<String> nodes;
nodes = RudimentaryHTMLParser.depthlessRead(text);
if (nodes.size() == 0)
{
approximateText = "-";
return;
}
StringBuilder b = new StringBuilder();
Tree<String> first = nodes.get(0);
for (Tree<String> node: nodes)
{
if (node.key.equals("tag"))
{
if (node.get(0).key.equals("br"))
b.append("; ");
if (node.get(0).key.equals("p") && node != first)
b.append("; ");
}
if (node.key.equals("emoji"))
{
b.append(":" + node.value + ":");
}
if (node.key.equals("text"))
{
b.append(node.value);
}
}
approximateText = b.toString();
Tree<String> first = nodes.get(0);
for (Tree<String> node: nodes)
{
if (node.key.equals("tag"))
{
if (node.get(0).key.equals("br"))
b.append("; ");
if (node.get(0).key.equals("p") && node != first)
b.append("; ");
}
if (node.key.equals("emoji"))
{
b.append(":" + node.value + ":");
}
if (node.key.equals("text"))
{
b.append(node.value);
}
}
approximateText = b.toString();
}
public void
@ -402,57 +402,57 @@ Post {
public
Post(Tree<String> entity)
{
id = entity.get("id").value;
id = entity.get("id").value;
uri = entity.get("url").value;
if (uri == null) uri = entity.get("uri").value;
uri = entity.get("url").value;
if (uri == null) uri = entity.get("uri").value;
author = new Account(entity.get("account"));
author = new Account(entity.get("account"));
String v = entity.get("visibility").value;
boolean p = v.equals("public");
boolean u = v.equals("unlisted");
boolean f = v.equals("private");
boolean m = v.equals("direct");
if (p) visibility = PostVisibility.PUBLIC;
if (u) visibility = PostVisibility.UNLISTED;
if (f) visibility = PostVisibility.FOLLOWERS;
if (m) visibility = PostVisibility.MENTIONED;
String v = entity.get("visibility").value;
boolean p = v.equals("public");
boolean u = v.equals("unlisted");
boolean f = v.equals("private");
boolean m = v.equals("direct");
if (p) visibility = PostVisibility.PUBLIC;
if (u) visibility = PostVisibility.UNLISTED;
if (f) visibility = PostVisibility.FOLLOWERS;
if (m) visibility = PostVisibility.MENTIONED;
dateTime =
dateTime =
ZonedDateTime.parse(entity.get("created_at").value)
.withZoneSameInstant(ZoneId.systemDefault());
date = DATE_FORMAT.format(dateTime);
time = TIME_FORMAT.format(dateTime);
date = DATE_FORMAT.format(dateTime);
time = TIME_FORMAT.format(dateTime);
text = entity.get("content").value;
String st = entity.get("spoiler_text").value;
contentWarning = st.trim().isEmpty() ? null : st;
text = entity.get("content").value;
String st = entity.get("spoiler_text").value;
contentWarning = st.trim().isEmpty() ? null : st;
String favourited = entity.get("favourited").value;
String boosted = entity.get("reblogged").value;
this.favourited = favourited.equals("true");
this.boosted = boosted.equals("true");
String favourited = entity.get("favourited").value;
String boosted = entity.get("reblogged").value;
this.favourited = favourited.equals("true");
this.boosted = boosted.equals("true");
Tree<String> media = entity.get("media_attachments");
attachments = new Attachment[media.size()];
for (int o = 0; o < attachments.length; ++o)
{
Tree<String> media = entity.get("media_attachments");
attachments = new Attachment[media.size()];
for (int o = 0; o < attachments.length; ++o)
{
attachments[o] = new Attachment(media.get(o));
}
Tree<String> emojis = entity.get("emojis");
emojiUrls = new String[emojis.size()][];
for (int o = 0; o < emojiUrls.length; ++o)
{
Tree<String> emoji = emojis.get(o);
String[] mapping = emojiUrls[o] = new String[2];
mapping[0] = ":" + emoji.get("shortcode").value + ":";
mapping[1] = emoji.get("url").value;
}
Tree<String> emojis = entity.get("emojis");
emojiUrls = new String[emojis.size()][];
for (int o = 0; o < emojiUrls.length; ++o)
{
Tree<String> emoji = emojis.get(o);
String[] mapping = emojiUrls[o] = new String[2];
mapping[0] = ":" + emoji.get("shortcode").value + ":";
mapping[1] = emoji.get("url").value;
}
Tree<String> boostedPost = entity.get("reblog");
if (boostedPost.size() > 0)
if (boostedPost.size() > 0)
this.boostedPost = new Post(boostedPost);
Tree<String> mentions = entity.get("mentions");
@ -508,7 +508,7 @@ Account {
resolveFormattedName()
{
assert name != null;
formattedName =
formattedName =
new RichTextPane.Builder().text(name).finish();
}
@ -531,27 +531,27 @@ Account {
numId = entity.get("id").value;
id = entity.get("acct").value;
String displayName = entity.get("display_name").value;
String username = entity.get("username").value;
name = displayName.isEmpty() ? username : displayName;
String displayName = entity.get("display_name").value;
String username = entity.get("username").value;
name = displayName.isEmpty() ? username : displayName;
avatarUrl = entity.get("avatar").value;
avatarUrl = entity.get("avatar").value;
creationDate =
creationDate =
ZonedDateTime.parse(entity.get("created_at").value)
.withZoneSameInstant(ZoneId.systemDefault());
String c1 = entity.get("following_count").value;
String c2 = entity.get("followers_count").value;
String c3 = entity.get("statuses_count").value;
try {
String c1 = entity.get("following_count").value;
String c2 = entity.get("followers_count").value;
String c3 = entity.get("statuses_count").value;
try {
followedCount = (int)Double.parseDouble(c1);
followerCount = (int)Double.parseDouble(c2);
postCount = (int)Double.parseDouble(c3);
}
catch (NumberFormatException eNf) {
}
catch (NumberFormatException eNf) {
assert false;
}
}
Tree<String> fs = entity.get("fields");
fields = new String[fs.size()][];
@ -577,20 +577,20 @@ Attachment {
public String
id;
public String
type;
public String
type;
public String
url;
public String
url;
public String
description;
public String
description;
public Image
image;
public Image
image;
public File
uploadee;
public File
uploadee;
// ---%-@-%---
@ -639,8 +639,8 @@ Composition {
public Attachment[]
attachments;
private File
uploadee;
private File
uploadee;
// ---%-@-%---
@ -653,33 +653,33 @@ Composition {
Composition c = new Composition();
Tree<String> boosted = entity.get("reblog");
if (boosted.size() > 0) entity = boosted;
if (boosted.size() > 0) entity = boosted;
String st = entity.get("spoiler_text").value;
String ri = entity.get("id").value;
c.contentWarning = st.trim().isEmpty() ? null : st;
c.replyToPostId = ri.trim().isEmpty() ? null : ri;
String st = entity.get("spoiler_text").value;
String ri = entity.get("id").value;
c.contentWarning = st.trim().isEmpty() ? null : st;
c.replyToPostId = ri.trim().isEmpty() ? null : ri;
Tree<String> author = entity.get("account");
String authorId = author.get("acct").value;
String authorNumId = author.get("id").value;
c.text = "";
if (!authorNumId.equals(ownNumId))
Tree<String> author = entity.get("account");
String authorId = author.get("acct").value;
String authorNumId = author.get("id").value;
c.text = "";
if (!authorNumId.equals(ownNumId))
c.text = "@" + authorId + " ";
String visibility = entity.get("visibility").value;
boolean p = visibility.equals("public");
boolean u = visibility.equals("unlisted");
boolean f = visibility.equals("private");
boolean m = visibility.equals("direct");
assert p || u || f || m;
if (p) c.visibility = PostVisibility.PUBLIC;
if (u) c.visibility = PostVisibility.UNLISTED;
if (f) c.visibility = PostVisibility.FOLLOWERS;
if (m) c.visibility = PostVisibility.MENTIONED;
// Less eye strain arranged this way.
String visibility = entity.get("visibility").value;
boolean p = visibility.equals("public");
boolean u = visibility.equals("unlisted");
boolean f = visibility.equals("private");
boolean m = visibility.equals("direct");
assert p || u || f || m;
if (p) c.visibility = PostVisibility.PUBLIC;
if (u) c.visibility = PostVisibility.UNLISTED;
if (f) c.visibility = PostVisibility.FOLLOWERS;
if (m) c.visibility = PostVisibility.MENTIONED;
// Less eye strain arranged this way.
return c;
return c;
}
public static Composition
@ -693,17 +693,17 @@ Composition {
c.replyToPostId = entity.get("in_reply_to_id").value;
String visibility = entity.get("visibility").value;
boolean p = visibility.equals("public");
boolean u = visibility.equals("unlisted");
boolean f = visibility.equals("private");
boolean m = visibility.equals("direct");
assert p || u || f || m;
if (p) c.visibility = PostVisibility.PUBLIC;
if (u) c.visibility = PostVisibility.UNLISTED;
if (f) c.visibility = PostVisibility.FOLLOWERS;
if (m) c.visibility = PostVisibility.MENTIONED;
boolean p = visibility.equals("public");
boolean u = visibility.equals("unlisted");
boolean f = visibility.equals("private");
boolean m = visibility.equals("direct");
assert p || u || f || m;
if (p) c.visibility = PostVisibility.PUBLIC;
if (u) c.visibility = PostVisibility.UNLISTED;
if (f) c.visibility = PostVisibility.FOLLOWERS;
if (m) c.visibility = PostVisibility.MENTIONED;
return c;
return c;
}
public static Composition
@ -712,9 +712,9 @@ Composition {
if (post.boostedPost != null) post = post.boostedPost;
Composition c = new Composition();
c.replyToPostId = post.id;
c.replyToPostId = post.id;
c.visibility = post.visibility;
c.contentWarning = post.contentWarning;
c.contentWarning = post.contentWarning;
StringBuilder text = new StringBuilder();
for (String id: post.mentions)

Binary file not shown.

View File

@ -50,8 +50,8 @@ LoginWindow extends JFrame {
private JKomasto
primaire;
private MastodonApi
api;
private MastodonApi
api;
// - -%- -
@ -62,7 +62,7 @@ LoginWindow extends JFrame {
serverContacted = false,
haveAppCredentials = false,
haveAccessToken = false,
haveAccountDetails = false;
haveAccountDetails = false;
// ---%-@-%---
@ -81,325 +81,325 @@ LoginWindow extends JFrame {
b.append(prefix4 + " Have account details\n");
display.setText(b.toString());
display.paintImmediately(display.getBounds(null));
display.paintImmediately(display.getBounds(null));
}
public void
useCache()
{
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
try
{
api.loadCache();
haveAppCredentials = true;
haveAccessToken = true;
display.setInstanceUrl(api.getInstanceUrl());
updateStatusDisplay();
}
catch (IOException eIo)
{
JOptionPane.showMessageDialog(
this,
"We couldn't get login details from the cache.."
+ "\n" + eIo.getClass() + ": " + eIo.getMessage()
);
display.setAutoLoginToggled(false);
}
display.setCursor(null);
if (!haveAccessToken) return;
public void
useCache()
{
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
try
{
api.loadCache();
haveAppCredentials = true;
haveAccessToken = true;
display.setInstanceUrl(api.getInstanceUrl());
updateStatusDisplay();
}
catch (IOException eIo)
{
JOptionPane.showMessageDialog(
this,
"We couldn't get login details from the cache.."
+ "\n" + eIo.getClass() + ": " + eIo.getMessage()
);
display.setAutoLoginToggled(false);
}
display.setCursor(null);
if (!haveAccessToken) return;
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getAccountDetails(new RequestListener() {
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getAccountDetails(new RequestListener() {
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get account details, failed.."
+ "\n" + eIo.getMessage()
);
}
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get account details, failed.."
+ "\n" + eIo.getMessage()
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get account details, failed.."
+ "\n" + json.get("error").value
+ "\n(HTTP error code: " + httpCode + ")"
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get account details, failed.."
+ "\n" + json.get("error").value
+ "\n(HTTP error code: " + httpCode + ")"
);
}
public void
requestSucceeded(Tree<String> json)
{
api.setAccountDetails(json);
serverContacted = true;
haveAccountDetails = true;
updateStatusDisplay();
}
public void
requestSucceeded(Tree<String> json)
{
api.setAccountDetails(json);
serverContacted = true;
haveAccountDetails = true;
updateStatusDisplay();
}
});
display.setCursor(null);
if (!haveAccountDetails) return;
});
display.setCursor(null);
if (!haveAccountDetails) return;
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
try
{
api.saveToCache();
}
catch (IOException eIo)
{
JOptionPane.showMessageDialog(
this,
"We couldn't save login details into the cache.."
+ "\n" + eIo.getClass() + ": " + eIo.getMessage()
);
}
display.setCursor(null);
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
try
{
api.saveToCache();
}
catch (IOException eIo)
{
JOptionPane.showMessageDialog(
this,
"We couldn't save login details into the cache.."
+ "\n" + eIo.getClass() + ": " + eIo.getMessage()
);
}
display.setCursor(null);
primaire.finishedLogin();
}
primaire.finishedLogin();
}
public void
public void
useInstanceUrl()
{
if (display.isAutoLoginToggled()) { useCache(); return; }
String url = display.getInstanceUrl();
if (url.trim().isEmpty()) {
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);
display.paintImmediately(display.getBounds(null));
}
serverContacted = false;
haveAppCredentials = false;
haveAccessToken = false;
haveAccountDetails = false;
}
if (!hasProtocol(url)) {
url = "https://" + url;
display.setInstanceUrl(url);
display.paintImmediately(display.getBounds(null));
}
serverContacted = false;
haveAppCredentials = false;
haveAccessToken = false;
haveAccountDetails = false;
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.testUrlConnection(url, new RequestListener() {
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.testUrlConnection(url, new RequestListener() {
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to connect to URL, failed.."
+ "\n" + eIo.getClass() + ": " + eIo.getMessage()
);
}
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to connect to URL, failed.."
+ "\n" + eIo.getClass() + ": " + eIo.getMessage()
);
}
public void
requestFailed(int httpCode, Tree<String> response)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to connect to URL, failed.."
+ "\n" + response.get("body").value
);
}
public void
requestFailed(int httpCode, Tree<String> response)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to connect to URL, failed.."
+ "\n" + response.get("body").value
);
}
public void
requestSucceeded(Tree<String> response)
{
serverContacted = true;
updateStatusDisplay();
}
public void
requestSucceeded(Tree<String> response)
{
serverContacted = true;
updateStatusDisplay();
}
});
display.setCursor(null);
});
display.setCursor(null);
if (!serverContacted) return;
api.setInstanceUrl(url);
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getAppCredentials(new RequestListener() {
api.setInstanceUrl(url);
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getAppCredentials(new RequestListener() {
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get app credentials, failed.."
+ "\n" + eIo.getMessage()
);
}
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get app credentials, failed.."
+ "\n" + eIo.getMessage()
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get app credentials, failed.."
+ "\n" + json.get("error").value
+ "\n(HTTP error code: " + httpCode + ")"
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get app credentials, failed.."
+ "\n" + json.get("error").value
+ "\n(HTTP error code: " + httpCode + ")"
);
}
public void
requestSucceeded(Tree<String> json)
{
api.setAppCredentials(json);
haveAppCredentials = true;
updateStatusDisplay();
}
public void
requestSucceeded(Tree<String> json)
{
api.setAppCredentials(json);
haveAppCredentials = true;
updateStatusDisplay();
}
});
display.setCursor(null);
});
display.setCursor(null);
if (!haveAppCredentials) return;
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
URI uri = api.getAuthorisationURL();
URI uri = api.getAuthorisationURL();
final String MESSAGE1 =
final String MESSAGE1 =
"We will need you to login through a web browser,\n"
+ "and they will give you an authorisation code\n"
+ "that you will paste here. Sorry..!";
boolean supported =
Desktop.isDesktopSupported()
&& Desktop.getDesktop().isSupported(Desktop.Action.BROWSE);
if (supported)
{
JOptionPane.showMessageDialog(
boolean supported =
Desktop.isDesktopSupported()
&& Desktop.getDesktop().isSupported(Desktop.Action.BROWSE);
if (supported)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
MESSAGE1,
"Authorisation code renewal",
JOptionPane.INFORMATION_MESSAGE
);
try { Desktop.getDesktop().browse(uri); }
catch (IOException eIo) { supported = false; }
}
if (!supported)
{
final String MESSAGE2 =
"\nWe cannot use Desktop.browse(URI) on your\n"
+ "computer.. You'll have to open your web\n"
+ "browser yourself, and copy this URL in.";
try { Desktop.getDesktop().browse(uri); }
catch (IOException eIo) { supported = false; }
}
if (!supported)
{
final String MESSAGE2 =
"\nWe cannot use Desktop.browse(URI) on your\n"
+ "computer.. You'll have to open your web\n"
+ "browser yourself, and copy this URL in.";
JTextField field = new JTextField();
field.setText(uri.toString());
field.setPreferredSize(new Dimension(120, 32));
field.selectAll();
JTextField field = new JTextField();
field.setText(uri.toString());
field.setPreferredSize(new Dimension(120, 32));
field.selectAll();
JOptionPane.showMessageDialog(
LoginWindow.this,
new Object[] { MESSAGE1, MESSAGE2, field },
"Authorisation code renewal",
JOptionPane.INFORMATION_MESSAGE
);
}
display.receiveAuthorisationCode();
display.setCursor(null);
JOptionPane.showMessageDialog(
LoginWindow.this,
new Object[] { MESSAGE1, MESSAGE2, field },
"Authorisation code renewal",
JOptionPane.INFORMATION_MESSAGE
);
}
display.receiveAuthorisationCode();
display.setCursor(null);
}
public void
useAuthorisationCode()
{
String code = display.getAuthorisationCode();
public void
useAuthorisationCode()
{
String code = display.getAuthorisationCode();
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getAccessToken(code, new RequestListener() {
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getAccessToken(code, new RequestListener() {
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get app token, failed.."
+ "\n" + eIo.getMessage()
);
}
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get app token, failed.."
+ "\n" + eIo.getMessage()
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get app token, failed.."
+ "\n" + json.get("error").value
+ "\n(HTTP error code: " + httpCode + ")"
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get app token, failed.."
+ "\n" + json.get("error").value
+ "\n(HTTP error code: " + httpCode + ")"
);
}
public void
requestSucceeded(Tree<String> json)
{
api.setAccessToken(json);
haveAccessToken = true;
updateStatusDisplay();
}
public void
requestSucceeded(Tree<String> json)
{
api.setAccessToken(json);
haveAccessToken = true;
updateStatusDisplay();
}
});
display.setCursor(null);
if (!haveAccessToken) return;
});
display.setCursor(null);
if (!haveAccessToken) return;
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getAccountDetails(new RequestListener() {
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getAccountDetails(new RequestListener() {
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get account details, failed.."
+ "\n" + eIo.getMessage()
);
}
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get account details, failed.."
+ "\n" + eIo.getMessage()
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get account details, failed.."
+ "\n" + json.get("error").value
+ "\n(HTTP error code: " + httpCode + ")"
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
LoginWindow.this,
"Tried to get account details, failed.."
+ "\n" + json.get("error").value
+ "\n(HTTP error code: " + httpCode + ")"
);
}
public void
requestSucceeded(Tree<String> json)
{
api.setAccountDetails(json);
haveAccountDetails = true;
updateStatusDisplay();
}
public void
requestSucceeded(Tree<String> json)
{
api.setAccountDetails(json);
haveAccountDetails = true;
updateStatusDisplay();
}
});
display.setCursor(null);
if (!haveAccountDetails) return;
});
display.setCursor(null);
if (!haveAccountDetails) return;
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
try
{
api.saveToCache();
}
catch (IOException eIo)
{
JOptionPane.showMessageDialog(
this,
"We couldn't save login details into the cache.."
+ "\n" + eIo.getClass() + ": " + eIo.getMessage()
);
}
display.setCursor(null);
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
try
{
api.saveToCache();
}
catch (IOException eIo)
{
JOptionPane.showMessageDialog(
this,
"We couldn't save login details into the cache.."
+ "\n" + eIo.getClass() + ": " + eIo.getMessage()
);
}
display.setCursor(null);
primaire.finishedLogin();
}
primaire.finishedLogin();
}
// - -%- -
// - -%- -
public static boolean
hasProtocol(String url) { return url.matches("^.+://.*"); }
public static boolean
hasProtocol(String url) { return url.matches("^.+://.*"); }
// ---%-@-%---
@ -407,7 +407,7 @@ LoginWindow extends JFrame {
{
super("JKomasto - Login");
this.primaire = primaire;
this.api = primaire.getMastodonApi();
this.api = primaire.getMastodonApi();
setLocationByPlatform(true);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
@ -419,7 +419,7 @@ LoginWindow extends JFrame {
setContentPane(display);
pack();
setIconImage(primaire.getProgramIcon());
setIconImage(primaire.getProgramIcon());
}
}
@ -451,7 +451,7 @@ implements ActionListener {
private JPanel
labelArea,
forField,
accountsPanel;
accountsPanel;
private JCheckBox
autoLoginToggle;
@ -482,11 +482,11 @@ implements ActionListener {
statusDisplay.setText(status);
}
public void
setAutoLoginToggled(boolean a) { autoLoginToggle.setSelected(a); }
public void
setAutoLoginToggled(boolean a) { autoLoginToggle.setSelected(a); }
public boolean
isAutoLoginToggled() { return autoLoginToggle.isSelected(); }
public boolean
isAutoLoginToggled() { return autoLoginToggle.isSelected(); }
public void
actionPerformed(ActionEvent eA)
@ -502,11 +502,11 @@ implements ActionListener {
{
labelArea.remove(authorisationCodeLabel);
forField.remove(authorisationCodeButton);
accountsPanel.remove(authorisationCodeField);
accountsPanel.remove(authorisationCodeField);
labelArea.add(instanceUrlLabel, BorderLayout.NORTH);
forField.add(instanceUrlButton, BorderLayout.EAST);
accountsPanel.add(instanceUrlField);
revalidate();
accountsPanel.add(instanceUrlField);
revalidate();
}
public void
@ -514,11 +514,11 @@ implements ActionListener {
{
labelArea.remove(instanceUrlLabel);
forField.remove(instanceUrlButton);
accountsPanel.remove(instanceUrlField);
accountsPanel.remove(instanceUrlField);
labelArea.add(authorisationCodeLabel, BorderLayout.NORTH);
forField.add(authorisationCodeButton, BorderLayout.EAST);
accountsPanel.add(authorisationCodeField);
revalidate();
accountsPanel.add(authorisationCodeField);
revalidate();
}
// ---%-@-%---
@ -595,7 +595,7 @@ implements ActionListener {
statusDisplay.setBorder(bi);
statusDisplay.setFont(f2);
receiveInstanceUrl();
receiveInstanceUrl();
setLayout(new BorderLayout(0, 8));
add(accountsPanel, BorderLayout.NORTH);

View File

@ -295,7 +295,7 @@ MastodonApi {
submit(
String text, PostVisibility visibility,
String replyTo, String contentWarning,
String[] mediaIDs,
String[] mediaIDs,
RequestListener handler)
{
String token = accessToken.get("access_token").value;
@ -320,7 +320,7 @@ MastodonApi {
HttpURLConnection conn = cast(endpoint.openConnection());
String s1 = "Bearer " + token;
conn.setRequestProperty("Authorization", s1);
String s2 = Integer.toString(handler.hashCode());
String s2 = Integer.toString(handler.hashCode());
conn.setRequestProperty("Idempotency-Key", s2);
conn.setDoOutput(true);
conn.setRequestMethod("POST");
@ -335,9 +335,9 @@ MastodonApi {
if (contentWarning != null) {
output.write("&spoiler_text=" + contentWarning);
}
for (String mediaID: mediaIDs) {
output.write("&media_ids[]=" + mediaID);
}
for (String mediaID: mediaIDs) {
output.write("&media_ids[]=" + mediaID);
}
output.close();
@ -457,77 +457,77 @@ MastodonApi {
uploadFile(File file, String alt, RequestListener handler)
{
assert file != null;
assert alt != null;
assert alt != null;
assert file.canRead();
String bct =
"multipart/form-data; "
+ "boundary=\"JKomastoFileUpload\"";
String fsb = "--JKomastoFileUpload\r\n";
String feb = "\r\n--JKomastoFileUpload--\r\n";
String fcd =
"Content-Disposition: form-data; "
+ "name=\"file\"; "
+ "filename=\"" + file.getName() + "\"\r\n";
String fct = "Content-Type: image/png\r\n\r\n";
int contentLength = 0;
contentLength += fsb.length();
contentLength += feb.length();
contentLength += fcd.length();
contentLength += fct.length();
contentLength += file.length();
/*
* () This was an absurdity to debug. Contrary to
* cURL, Java sets default values for some headers,
* some of which are restricted, meaning you can't
* arbitrarily change them. Content-Length is one
* of them, set to 2^14-1 bytes. I'm pretty sure
* the file I was uploading was under this, but
* anyways one of the two parties was stopping me
* from finishing transferring my form data.
*
* They didn't mention this in the Javadocs.
* I noticed HttpURLConnection#setChunkedStreamingMode
* and #setFixedLengthStreamingMode by accident.
* Turns out, the latter is how I do what cURL and
* Firefox are doing - precalculate the exact size
* of the body and set the content length to it.
* Unfortunately, this is not flexible, we have to
* be exact. Thankfully, my answers pass..
*
* On the other side, Mastodon is obtuse as usual.
* They had code that basically throws a generic 500
* upon any sort of error from their library[1]. What
* problem the library had with my requests, I could
* never know. There is an undocumented requirement
* that you must put a filename in the content
* disposition. That one I found by guessing.
*
* I solved this with the help of -Djavax.net.debug,
* which revealed to me how my headers and body
* differed from cURL and Firefox. If this issue
* happens again, I advise giving up.
*
* [1] app/controllers/api/v1/media_controller.rb
* #create. 3 March 2022
*/
"multipart/form-data; "
+ "boundary=\"JKomastoFileUpload\"";
String fsb = "--JKomastoFileUpload\r\n";
String feb = "\r\n--JKomastoFileUpload--\r\n";
String fcd =
"Content-Disposition: form-data; "
+ "name=\"file\"; "
+ "filename=\"" + file.getName() + "\"\r\n";
String fct = "Content-Type: image/png\r\n\r\n";
int contentLength = 0;
contentLength += fsb.length();
contentLength += feb.length();
contentLength += fcd.length();
contentLength += fct.length();
contentLength += file.length();
/*
* () This was an absurdity to debug. Contrary to
* cURL, Java sets default values for some headers,
* some of which are restricted, meaning you can't
* arbitrarily change them. Content-Length is one
* of them, set to 2^14-1 bytes. I'm pretty sure
* the file I was uploading was under this, but
* anyways one of the two parties was stopping me
* from finishing transferring my form data.
*
* They didn't mention this in the Javadocs.
* I noticed HttpURLConnection#setChunkedStreamingMode
* and #setFixedLengthStreamingMode by accident.
* Turns out, the latter is how I do what cURL and
* Firefox are doing - precalculate the exact size
* of the body and set the content length to it.
* Unfortunately, this is not flexible, we have to
* be exact. Thankfully, my answers pass..
*
* On the other side, Mastodon is obtuse as usual.
* They had code that basically throws a generic 500
* upon any sort of error from their library[1]. What
* problem the library had with my requests, I could
* never know. There is an undocumented requirement
* that you must put a filename in the content
* disposition. That one I found by guessing.
*
* I solved this with the help of -Djavax.net.debug,
* which revealed to me how my headers and body
* differed from cURL and Firefox. If this issue
* happens again, I advise giving up.
*
* [1] app/controllers/api/v1/media_controller.rb
* #create. 3 March 2022
*/
String token = accessToken.get("access_token").value;
String token = accessToken.get("access_token").value;
String url = instanceUrl + "/api/v1/media/";
try
{
String s1 = "?description=" + encode(alt);
String s1 = "?description=" + encode(alt);
URL endpoint = new URL(url + s1);
URL endpoint = new URL(url + s1);
HttpURLConnection conn = cast(endpoint.openConnection());
String s2 = "Bearer " + token;
conn.setRequestProperty("Authorization", s2);
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setFixedLengthStreamingMode(contentLength);
conn.setRequestProperty("Content-Type", bct);
conn.setRequestProperty("Accept", "*/*");
conn.connect();
conn.setFixedLengthStreamingMode(contentLength);
conn.setRequestProperty("Content-Type", bct);
conn.setRequestProperty("Accept", "*/*");
conn.connect();
OutputStream ostream = conn.getOutputStream();
InputStream istream = new FileInputStream(file);
@ -536,12 +536,12 @@ MastodonApi {
ostream.write(fcd.getBytes());
ostream.write(fct.getBytes());
int c; while ((c = istream.read()) != -1)
ostream.write(c);
int c; while ((c = istream.read()) != -1)
ostream.write(c);
ostream.write(feb.getBytes());
istream.close();
ostream.close();
istream.close();
ostream.close();
doStandardJsonReturn(conn, handler);
}
@ -550,7 +550,7 @@ MastodonApi {
public void
monitorTimeline(
TimelineType type, ServerSideEventsListener handler)
TimelineType type, ServerSideEventsListener handler)
{
String token = accessToken.get("access_token").value;
@ -584,7 +584,7 @@ MastodonApi {
conn.setReadTimeout(500);
Reader input = ireader(conn.getInputStream());
BufferedReader br = new BufferedReader(input);
Thread thread = Thread.currentThread();
Thread thread = Thread.currentThread();
while (true) try
{
String line = br.readLine();
@ -629,7 +629,7 @@ MastodonApi {
int code = conn.getResponseCode();
if (code >= 300)
{
Reader input = ireader(conn.getErrorStream());
Reader input = ireader(conn.getErrorStream());
Tree<String> response = fromPlain(input);
input.close();
handler.requestFailed(code, response);
@ -663,13 +663,13 @@ MastodonApi {
// - -%- -
private static String
deescape(String string)
{
if (string == null) return string;
string = string.replaceAll("\n", "\\\\n");
return string;
}
private static String
deescape(String string)
{
if (string == null) return string;
string = string.replaceAll("\n", "\\\\n");
return string;
}
private static Tree<String>
fromPlain(Reader r)
@ -699,27 +699,27 @@ MastodonApi {
}
}
private static HttpURLConnection
cast(URLConnection conn)
{
return (HttpURLConnection)conn;
}
private static HttpURLConnection
cast(URLConnection conn)
{
return (HttpURLConnection)conn;
}
private static InputStreamReader
ireader(InputStream is)
throws IOException
{
assert is != null;
return new InputStreamReader(is);
}
private static InputStreamReader
ireader(InputStream is)
throws IOException
{
assert is != null;
return new InputStreamReader(is);
}
private static OutputStreamWriter
owriter(OutputStream os)
throws IOException
{
assert os != null;
return new OutputStreamWriter(os);
}
private static OutputStreamWriter
owriter(OutputStream os)
throws IOException
{
assert os != null;
return new OutputStreamWriter(os);
}
// ---%-@-%---

View File

@ -48,80 +48,80 @@ import java.awt.event.ComponentEvent;
class
NotificationsWindow extends JFrame {
private JKomasto
primaire;
private JKomasto
primaire;
private List<Notification>
notifications;
private List<Notification>
notifications;
private MastodonApi
api;
private MastodonApi
api;
// - -%- -
// - -%- -
private NotificationsComponent
display;
private NotificationsComponent
display;
private boolean
showingLatest;
private boolean
showingLatest;
// - -%- -
// - -%- -
private static int
ROW_COUNT = NotificationsComponent.ROW_COUNT;
private static int
ROW_COUNT = NotificationsComponent.ROW_COUNT;
// ---%-@-%---
// ---%-@-%---
public synchronized void
readEntity(Tree<String> entity)
{
notifications = new ArrayList<>();
for (Tree<String> t: entity)
{
Notification n = new Notification();
public synchronized void
readEntity(Tree<String> entity)
{
notifications = new ArrayList<>();
for (Tree<String> t: entity)
{
Notification n = new Notification();
n.id = t.get("id").value;
n.id = t.get("id").value;
String type = t.get("type").value;
if (type.equals("favourite"))
n.type = NotificationType.FAVOURITE;
else if (type.equals("reblog"))
n.type = NotificationType.BOOST;
else if (type.equals("mention"))
n.type = NotificationType.MENTION;
else if (type.equals("follow"))
n.type = NotificationType.FOLLOW;
else if (type.equals("follow_request"))
n.type = NotificationType.FOLLOWREQ;
else if (type.equals("poll"))
n.type = NotificationType.POLL;
else if (type.equals("status"))
n.type = NotificationType.ALERT;
String type = t.get("type").value;
if (type.equals("favourite"))
n.type = NotificationType.FAVOURITE;
else if (type.equals("reblog"))
n.type = NotificationType.BOOST;
else if (type.equals("mention"))
n.type = NotificationType.MENTION;
else if (type.equals("follow"))
n.type = NotificationType.FOLLOW;
else if (type.equals("follow_request"))
n.type = NotificationType.FOLLOWREQ;
else if (type.equals("poll"))
n.type = NotificationType.POLL;
else if (type.equals("status"))
n.type = NotificationType.ALERT;
Tree<String> actor = t.get("account");
String aid, aname, adisp;
aid = actor.get("id").value;
aname = actor.get("username").value;
adisp = actor.get("display_name").value;
if (!adisp.isEmpty()) n.actorName = adisp;
else n.actorName = aname;
n.actorNumId = aid;
Tree<String> actor = t.get("account");
String aid, aname, adisp;
aid = actor.get("id").value;
aname = actor.get("username").value;
adisp = actor.get("display_name").value;
if (!adisp.isEmpty()) n.actorName = adisp;
else n.actorName = aname;
n.actorNumId = aid;
if (n.type != NotificationType.FOLLOW)
{
if (n.type != NotificationType.FOLLOW)
{
Post post = new Post(t.get("status"));
post.resolveApproximateText();
n.postId = post.id;
n.postText = post.approximateText;
}
post.resolveApproximateText();
n.postId = post.id;
n.postText = post.approximateText;
}
notifications.add(n);
}
}
notifications.add(n);
}
}
public void
refresh()
{
public void
refresh()
{
String firstId = null;
if (!showingLatest)
{
@ -134,53 +134,53 @@ NotificationsWindow extends JFrame {
if (notifications.size() < ROW_COUNT) showLatestPage();
display.showNotifications(notifications);
display.repaint();
}
}
}
}
public void
showLatestPage()
{
if (fetchPage(null, null))
{
public void
showLatestPage()
{
if (fetchPage(null, null))
{
display.showNotifications(notifications);
showingLatest = true;
primaire.getWindowUpdater().add(this);
}
}
}
public void
showPrevPage()
{
assert !notifications.isEmpty();
if (fetchPage(null, notifications.get(0).id))
{
public void
showPrevPage()
{
assert !notifications.isEmpty();
if (fetchPage(null, notifications.get(0).id))
{
if (notifications.size() < ROW_COUNT) showLatestPage();
display.showNotifications(notifications);
showingLatest = false;
primaire.getWindowUpdater().remove(this);
}
}
}
}
public void
showNextPage()
{
assert !notifications.isEmpty();
int last = notifications.size() - 1;
public void
showNextPage()
{
assert !notifications.isEmpty();
int last = notifications.size() - 1;
if (fetchPage(notifications.get(last).id, null))
{
display.showNotifications(notifications);
showingLatest = false;
primaire.getWindowUpdater().remove(this);
}
}
}
}
// - -%- -
// - -%- -
private boolean
fetchPage(String maxId, String minId)
{
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
class Handler implements RequestListener {
private boolean
fetchPage(String maxId, String minId)
{
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
class Handler implements RequestListener {
boolean
succeeded = false;
@ -207,31 +207,31 @@ NotificationsWindow extends JFrame {
}
}
Handler handler = new Handler();
api.getNotifications(ROW_COUNT, maxId, minId, handler);
display.setCursor(null);
repaint();
return handler.succeeded;
}
api.getNotifications(ROW_COUNT, maxId, minId, handler);
display.setCursor(null);
repaint();
return handler.succeeded;
}
// ---%-@-%---
// ---%-@-%---
NotificationsWindow(JKomasto primaire)
{
super("Notifications");
this.primaire = primaire;
this.api = primaire.getMastodonApi();
NotificationsWindow(JKomasto primaire)
{
super("Notifications");
this.primaire = primaire;
this.api = primaire.getMastodonApi();
notifications = new ArrayList<>();
notifications = new ArrayList<>();
display = new NotificationsComponent(this);
display.setPreferredSize(new Dimension(256, 260));
setContentPane(display);
pack();
display = new NotificationsComponent(this);
display.setPreferredSize(new Dimension(256, 260));
setContentPane(display);
pack();
setIconImage(primaire.getProgramIcon());
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setVisible(true);
}
setIconImage(primaire.getProgramIcon());
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setVisible(true);
}
}
@ -239,88 +239,88 @@ class
NotificationsComponent extends JPanel
implements ActionListener {
private NotificationsWindow
primaire;
private NotificationsWindow
primaire;
private JButton
prev, next;
private JButton
prev, next;
// - -%- -
// - -%- -
private List<NotificationComponent>
rows;
private List<NotificationComponent>
rows;
// - -%- -
// - -%- -
static final int
ROW_COUNT = 10;
static final int
ROW_COUNT = 10;
// ---%-@-%---
// ---%-@-%---
public void
showNotifications(List<Notification> notifications)
{
assert notifications.size() == rows.size();
for (int o = 0; o < rows.size(); ++o)
{
Notification n = notifications.get(o);
NotificationComponent c = rows.get(o);
c.setName(n.actorName);
switch (n.type)
{
case MENTION: c.setType("mentioned"); break;
case BOOST: c.setType("boosted"); break;
case FAVOURITE: c.setType("favourited"); break;
case FOLLOW: c.setType("followed"); break;
case FOLLOWREQ: c.setType("req. follow"); break;
case POLL: c.setType("poll ended"); break;
case ALERT: c.setType("posted"); break;
}
c.setText(n.postText);
}
}
public void
showNotifications(List<Notification> notifications)
{
assert notifications.size() == rows.size();
for (int o = 0; o < rows.size(); ++o)
{
Notification n = notifications.get(o);
NotificationComponent c = rows.get(o);
c.setName(n.actorName);
switch (n.type)
{
case MENTION: c.setType("mentioned"); break;
case BOOST: c.setType("boosted"); break;
case FAVOURITE: c.setType("favourited"); break;
case FOLLOW: c.setType("followed"); break;
case FOLLOWREQ: c.setType("req. follow"); break;
case POLL: c.setType("poll ended"); break;
case ALERT: c.setType("posted"); break;
}
c.setText(n.postText);
}
}
// - -%- -
// - -%- -
public void
actionPerformed(ActionEvent eA)
{
if (eA.getSource() == prev) primaire.showPrevPage();
if (eA.getSource() == next) primaire.showNextPage();
}
public void
actionPerformed(ActionEvent eA)
{
if (eA.getSource() == prev) primaire.showPrevPage();
if (eA.getSource() == next) primaire.showNextPage();
}
// ---%-@-%---
// ---%-@-%---
NotificationsComponent(NotificationsWindow primaire)
{
this.primaire = primaire;
NotificationsComponent(NotificationsWindow primaire)
{
this.primaire = primaire;
Border b = BorderFactory.createEmptyBorder(8, 8, 8, 8);
Border b = BorderFactory.createEmptyBorder(8, 8, 8, 8);
rows = new ArrayList<>();
for (int n = ROW_COUNT; n > 0; --n)
rows.add(new NotificationComponent());
rows = new ArrayList<>();
for (int n = ROW_COUNT; n > 0; --n)
rows.add(new NotificationComponent());
prev = new JButton("<");
next = new JButton(">");
prev.addActionListener(this);
next.addActionListener(this);
prev = new JButton("<");
next = new JButton(">");
prev.addActionListener(this);
next.addActionListener(this);
JPanel centre = new JPanel();
centre.setLayout(new GridLayout(ROW_COUNT, 1));
for (NotificationComponent c: rows) centre.add(c);
JPanel centre = new JPanel();
centre.setLayout(new GridLayout(ROW_COUNT, 1));
for (NotificationComponent c: rows) centre.add(c);
Box bottom = Box.createHorizontalBox();
bottom.add(Box.createGlue());
bottom.add(prev);
bottom.add(Box.createHorizontalStrut(8));
bottom.add(next);
bottom.setBorder(b);
Box bottom = Box.createHorizontalBox();
bottom.add(Box.createGlue());
bottom.add(prev);
bottom.add(Box.createHorizontalStrut(8));
bottom.add(next);
bottom.setBorder(b);
setLayout(new BorderLayout());
add(centre);
add(bottom, BorderLayout.SOUTH);
}
setLayout(new BorderLayout());
add(centre);
add(bottom, BorderLayout.SOUTH);
}
}
@ -328,145 +328,145 @@ class
NotificationComponent extends JComponent
implements ComponentListener {
private JLabel
type;
private JLabel
type;
private ImageComponent
typeImg;
private ImageComponent
typeImg;
private JLabel
name, text;
private JLabel
name, text;
// ---%-@-%---
// ---%-@-%---
public void
setType(String n)
{
type.setText(n);
typeImg.image = ImageApi.local(n + "Notification");
}
public void
setType(String n)
{
type.setText(n);
typeImg.image = ImageApi.local(n + "Notification");
}
public void
setName(String n) { name.setText(n); }
public void
setName(String n) { name.setText(n); }
public void
setText(String n) { text.setText(n); }
public void
setText(String n) { text.setText(n); }
// - -%- -
// - -%- -
public void
doLayout()
{
int w = getWidth(), h = getHeight();
public void
doLayout()
{
int w = getWidth(), h = getHeight();
int x0 = w * 0/20;
int x1 = w * 7/20;
int x2 = w * 13/20;
int x3 = w * 15/20;
int x4 = w * 20/20;
int x0 = w * 0/20;
int x1 = w * 7/20;
int x2 = w * 13/20;
int x3 = w * 15/20;
int x4 = w * 20/20;
name.setLocation(x0 + 4, 0);
name.setSize((x1 - 4) - (x0 + 4), h);
name.setLocation(x0 + 4, 0);
name.setSize((x1 - 4) - (x0 + 4), h);
type.setLocation(x1 + 4, 0);
type.setSize((x2 - 1) - (x1 + 4), h);
type.setLocation(x1 + 4, 0);
type.setSize((x2 - 1) - (x1 + 4), h);
typeImg.setLocation((x2 + 1), 2);
typeImg.setSize((x3 - 4) - (x2 + 1), (h - 2) - 2);
typeImg.setLocation((x2 + 1), 2);
typeImg.setSize((x3 - 4) - (x2 + 1), (h - 2) - 2);
text.setLocation(x3 + 4, 0);
text.setSize((x4 - 4) - (x3 + 4), h);
}
text.setLocation(x3 + 4, 0);
text.setSize((x4 - 4) - (x3 + 4), h);
}
public void
componentResized(ComponentEvent eC) { doLayout(); }
public void
componentResized(ComponentEvent eC) { doLayout(); }
public void
componentShown(ComponentEvent eC) { }
public void
componentShown(ComponentEvent eC) { }
public void
componentHidden(ComponentEvent eC) { }
public void
componentHidden(ComponentEvent eC) { }
public void
componentMoved(ComponentEvent eC) { }
public void
componentMoved(ComponentEvent eC) { }
// ---%-@-%---
// ---%-@-%---
private static class
ImageComponent extends JComponent {
private static class
ImageComponent extends JComponent {
private Image
image,
scaled;
private Image
image,
scaled;
private boolean
snapdown = false;
private boolean
snapdown = false;
// -=%=-
// -=%=-
protected void
paintComponent(Graphics g)
{
if (image == null) return;
int w = getWidth(), h = getHeight();
protected void
paintComponent(Graphics g)
{
if (image == null) return;
int w = getWidth(), h = getHeight();
int ow = image.getWidth(this);
int oh = image.getHeight(this);
int nh = h;
int nw = ow * nh/oh;
if (snapdown)
{
int sw, sh;
for (sw = 1; sw < nw; sw *= 2);
for (sh = 1; sh < nh; sh *= 2);
nw = sw / 2;
nh = sh / 2;
}
int ow = image.getWidth(this);
int oh = image.getHeight(this);
int nh = h;
int nw = ow * nh/oh;
if (snapdown)
{
int sw, sh;
for (sw = 1; sw < nw; sw *= 2);
for (sh = 1; sh < nh; sh *= 2);
nw = sw / 2;
nh = sh / 2;
}
if (scaled == null)
scaled = image.getScaledInstance(
nw, nh,
Image.SCALE_SMOOTH
);
int x = (w - nw) / 2;
int y = (h - nh) / 2;
g.drawImage(scaled, x, y, this);
}
if (scaled == null)
scaled = image.getScaledInstance(
nw, nh,
Image.SCALE_SMOOTH
);
int x = (w - nw) / 2;
int y = (h - nh) / 2;
g.drawImage(scaled, x, y, this);
}
}
}
// ---%-@-%---
// ---%-@-%---
NotificationComponent()
{
Font f = new Font("Dialog", Font.PLAIN, 12);
Font f1 = f.deriveFont(Font.PLAIN, 14);
Font f2 = f.deriveFont(Font.PLAIN, 11);
Font f3 = f.deriveFont(Font.ITALIC, 14);
NotificationComponent()
{
Font f = new Font("Dialog", Font.PLAIN, 12);
Font f1 = f.deriveFont(Font.PLAIN, 14);
Font f2 = f.deriveFont(Font.PLAIN, 11);
Font f3 = f.deriveFont(Font.ITALIC, 14);
Color c = new Color(0, 0, 0, 25);
Border b1 = BorderFactory.createMatteBorder(0, 0, 1, 0, c);
Color c = new Color(0, 0, 0, 25);
Border b1 = BorderFactory.createMatteBorder(0, 0, 1, 0, c);
name = new JLabel();
name.setFont(f1);
name = new JLabel();
name.setFont(f1);
type = new JLabel();
type.setFont(f2);
type.setHorizontalAlignment(JLabel.RIGHT);
type = new JLabel();
type.setFont(f2);
type.setHorizontalAlignment(JLabel.RIGHT);
typeImg = new ImageComponent();
typeImg = new ImageComponent();
text = new JLabel();
text.setFont(f3);
text = new JLabel();
text.setFont(f3);
setLayout(null);
add(name);
add(type);
add(typeImg);
add(text);
setLayout(null);
add(name);
add(type);
add(typeImg);
add(text);
this.addComponentListener(this);
setBorder(b1);
}
this.addComponentListener(this);
setBorder(b1);
}
}

View File

@ -74,9 +74,9 @@ PostWindow extends JFrame {
private MastodonApi
api;
private Post
post,
wrapperPost;
private Post
post,
wrapperPost;
// - -%- -
@ -109,21 +109,21 @@ PostWindow extends JFrame {
}
display.setAuthorName(post.author.name);
display.setAuthorId(post.author.id);
display.setAuthorId(post.author.id);
String oid = api.getAccountDetails().get("id").value;
String aid = post.author.numId;
display.setDeleteEnabled(aid.equals(oid));
String oid = api.getAccountDetails().get("id").value;
String aid = post.author.numId;
display.setDeleteEnabled(aid.equals(oid));
post.author.resolveAvatar();
display.setAuthorAvatar(post.author.avatar);
post.author.resolveAvatar();
display.setAuthorAvatar(post.author.avatar);
display.setDate(post.date);
display.setTime(post.time);
display.setEmojiUrls(post.emojiUrls);
display.setEmojiUrls(post.emojiUrls);
display.setHtml(post.text);
display.setHtml(post.text);
display.setFavourited(post.favourited);
display.setBoosted(post.boosted);
@ -137,29 +137,29 @@ PostWindow extends JFrame {
post.resolveApproximateText();
this.setTitle(post.approximateText);
display.resetFocus();
display.resetFocus();
repaint();
}
public void
readEntity(Tree<String> post)
{
public void
readEntity(Tree<String> post)
{
use(new Post(post));
}
public synchronized void
openAuthorProfile()
{
ProfileWindow w = new ProfileWindow(primaire);
w.use(post.author);
w.setLocationRelativeTo(this);
w.setVisible(true);
ProfileWindow w = new ProfileWindow(primaire);
w.use(post.author);
w.setLocationRelativeTo(this);
w.setVisible(true);
}
public synchronized void
favourite(boolean favourited)
{
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
display.setFavouriteBoostEnabled(false);
display.paintImmediately(display.getBounds());
RequestListener handler = new RequestListener() {
@ -188,11 +188,11 @@ PostWindow extends JFrame {
public void
requestSucceeded(Tree<String> json)
{
PostWindow.this.post.favourited = favourited;
PostWindow.this.post.favourited = favourited;
}
};
api.setPostFavourited(post.id, favourited, handler);
api.setPostFavourited(post.id, favourited, handler);
display.setCursor(null);
display.setFavouriteBoostEnabled(true);
display.repaint();
@ -234,7 +234,7 @@ PostWindow extends JFrame {
}
};
api.setPostBoosted(post.id, boosted, handler);
api.setPostBoosted(post.id, boosted, handler);
display.setCursor(null);
display.setFavouriteBoostEnabled(true);
display.repaint();
@ -246,9 +246,9 @@ PostWindow extends JFrame {
String ownId = api.getAccountDetails().get("acct").value;
Composition c = Composition.reply(this.post, ownId);
ComposeWindow w = primaire.getComposeWindow();
w.setComposition(c);
if (!w.isVisible())
{
w.setComposition(c);
if (!w.isVisible())
{
w.setLocation(getX(), getY() + 100);
w.setVisible(true);
}
@ -271,62 +271,62 @@ PostWindow extends JFrame {
display.setCursor(null);
}
public synchronized void
deletePost(boolean redraft)
{
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
display.setDeleteEnabled(false);
display.paintImmediately(display.getBounds());
public synchronized void
deletePost(boolean redraft)
{
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
display.setDeleteEnabled(false);
display.paintImmediately(display.getBounds());
final String S1 =
final String S1 =
"Are you sure you'd like to delete this post?\n";
final String S2 =
"Are you sure you'd like to delete this post?\n"
+ "You are redrafting, so a composition window\n"
+ "should open with its contents filled.";
JOptionPane dialog = new JOptionPane();
dialog.setMessageType(JOptionPane.QUESTION_MESSAGE);
dialog.setMessage(redraft ? S2 : S1);
dialog.setOptions(new String[] { "No", "Yes" });
String title = "Confirm delete";
dialog.createDialog(this, title).setVisible(true);
if (!dialog.getValue().equals("Yes"))
{
JOptionPane dialog = new JOptionPane();
dialog.setMessageType(JOptionPane.QUESTION_MESSAGE);
dialog.setMessage(redraft ? S2 : S1);
dialog.setOptions(new String[] { "No", "Yes" });
String title = "Confirm delete";
dialog.createDialog(this, title).setVisible(true);
if (!dialog.getValue().equals("Yes"))
{
display.setCursor(null);
display.setDeleteEnabled(true);
display.paintImmediately(display.getBounds());
return;
}
}
api.deletePost(post.id, new RequestListener() {
api.deletePost(post.id, new RequestListener() {
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
PostWindow.this,
"Failed to delete post.."
+ "\n" + eIo.getMessage()
);
}
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
PostWindow.this,
"Failed to delete post.."
+ "\n" + eIo.getMessage()
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
PostWindow.this,
"Failed to delete post.."
+ "\n" + json.get("error").value
+ "\n(HTTP code: " + httpCode + ")"
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
PostWindow.this,
"Failed to delete post.."
+ "\n" + json.get("error").value
+ "\n(HTTP code: " + httpCode + ")"
);
}
public void
requestSucceeded(Tree<String> json)
{
setVisible(false);
public void
requestSucceeded(Tree<String> json)
{
setVisible(false);
if (redraft)
if (redraft)
{
Composition c = Composition.recover(json);
ComposeWindow w = new ComposeWindow(primaire);
@ -334,36 +334,36 @@ PostWindow extends JFrame {
w.setLocation(getX(), getY() + 100);
w.setVisible(true);
}
}
}
});
});
display.setCursor(null);
display.setDeleteEnabled(true);
display.paintImmediately(display.getBounds());
if (!isVisible()) dispose();
}
display.setCursor(null);
display.setDeleteEnabled(true);
display.paintImmediately(display.getBounds());
if (!isVisible()) dispose();
}
public synchronized void
copyPostId()
{
public synchronized void
copyPostId()
{
ClipboardApi.serve(post.id);
}
}
public synchronized void
copyPostLink()
{
public synchronized void
copyPostLink()
{
ClipboardApi.serve(post.uri);
}
}
public synchronized void
openReplies()
{
public synchronized void
openReplies()
{
RepliesWindow w = new RepliesWindow(primaire, this);
w.showFor(post.id);
w.setLocation(getX(), getY() + 100);
w.setVisible(true);
}
}
// ---%-@-%---
@ -380,7 +380,7 @@ PostWindow extends JFrame {
display = new PostComponent(this);
setContentPane(display);
setIconImage(primaire.getProgramIcon());
setIconImage(primaire.getProgramIcon());
}
}
@ -396,23 +396,23 @@ implements ActionListener {
// - -%- -
private List<RichTextPane.Segment>
authorNameOr;
private List<RichTextPane.Segment>
authorNameOr;
private RichTextPane
authorName;
private RichTextPane
authorName;
private RichTextPane3
body;
private RichTextPane3
body;
private JScrollPane
bodyScrollPane;
private JScrollPane
bodyScrollPane;
private JLabel
authorId, time, date;
private JLabel
authorId, time, date;
private String[][]
emojiUrls;
private String[][]
emojiUrls;
private TwoToggleButton
favouriteBoost,
@ -423,32 +423,32 @@ implements ActionListener {
profile,
media;
private JPopupMenu
miscMenu;
private JPopupMenu
miscMenu;
private JMenuItem
openReplies,
copyPostId,
copyPostLink,
deletePost,
redraftPost;
private JMenuItem
openReplies,
copyPostId,
copyPostLink,
deletePost,
redraftPost;
private Image
backgroundImage;
private Image
backgroundImage;
// ---%-@-%---
public void
setAuthorName(String n)
{
authorNameOr = new RichTextPane.Builder().text(n).finish();
}
{
authorNameOr = new RichTextPane.Builder().text(n).finish();
}
public void
setAuthorId(String n) { authorId.setText(n); }
public void
setAuthorAvatar(Image n) { profile.setImage(n); }
public void
setAuthorAvatar(Image n) { profile.setImage(n); }
public void
setDate(String n) { date.setText(n); }
@ -456,24 +456,24 @@ implements ActionListener {
public void
setTime(String n) { time.setText(n); }
public void
setEmojiUrls(String[][] n)
{
public void
setEmojiUrls(String[][] n)
{
emojiUrls = n;
Map<String, Image> emojis = new HashMap<>();
for (String[] entry: n)
{
emojis.put(entry[0], ImageApi.remote(entry[1]));
}
body.setEmojis(emojis);
Map<String, Image> emojis = new HashMap<>();
for (String[] entry: n)
{
emojis.put(entry[0], ImageApi.remote(entry[1]));
}
body.setEmojis(emojis);
}
public void
setHtml(String n)
{
public void
setHtml(String n)
{
body.setText(BasicHTMLParser.parse(n));
}
}
public void
setFavourited(boolean a)
@ -497,19 +497,19 @@ implements ActionListener {
favouriteBoost.setEnabled(a);
}
public void
setDeleteEnabled(boolean a)
{
deletePost.setEnabled(a);
redraftPost.setEnabled(a);
}
public void
setDeleteEnabled(boolean a)
{
deletePost.setEnabled(a);
redraftPost.setEnabled(a);
}
public void
setMediaPreview(Image n) { media.setImage(n); }
public void
setMediaPreview(Image n) { media.setImage(n); }
public void
resetFocus()
{
public void
resetFocus()
{
media.requestFocusInWindow();
}
@ -551,14 +551,14 @@ implements ActionListener {
}
else if (command.startsWith("reply"))
{
primaire.reply();
primaire.reply();
}
else if (command.startsWith("misc"))
{
int rx = replyMisc.getWidth() / 2;
int ry = replyMisc.getHeight() - miscMenu.getHeight();
miscMenu.show(replyMisc, rx, ry);
}
else if (command.startsWith("misc"))
{
int rx = replyMisc.getWidth() / 2;
int ry = replyMisc.getHeight() - miscMenu.getHeight();
miscMenu.show(replyMisc, rx, ry);
}
return;
}
else miscMenu.setVisible(false);
@ -567,14 +567,14 @@ implements ActionListener {
{
if (command.startsWith("next"))
{
body.nextPage();
body.nextPage();
}
else
{
body.previousPage();
body.previousPage();
}
// First time an interactive element
// doesn't call something in primaire..
// First time an interactive element
// doesn't call something in primaire..
return;
}
@ -582,13 +582,13 @@ implements ActionListener {
{
primaire.openMedia();
return;
}
}
if (src == openReplies) primaire.openReplies();
if (src == copyPostId) primaire.copyPostId();
if (src == copyPostLink) primaire.copyPostLink();
if (src == deletePost) primaire.deletePost(false);
if (src == redraftPost) primaire.deletePost(true);
if (src == openReplies) primaire.openReplies();
if (src == copyPostId) primaire.copyPostId();
if (src == copyPostLink) primaire.copyPostLink();
if (src == deletePost) primaire.deletePost(false);
if (src == redraftPost) primaire.deletePost(true);
}
@ -597,28 +597,28 @@ implements ActionListener {
{
g.clearRect(0, 0, getWidth(), getHeight());
int w1 = authorName.getWidth();
FontMetrics fm1 = getFontMetrics(authorName.getFont());
List<RichTextPane.Segment> lay1;
lay1 = RichTextPane.layout(authorNameOr, fm1, w1);
authorName.setText(lay1);
int w1 = authorName.getWidth();
FontMetrics fm1 = getFontMetrics(authorName.getFont());
List<RichTextPane.Segment> lay1;
lay1 = RichTextPane.layout(authorNameOr, fm1, w1);
authorName.setText(lay1);
if (backgroundImage != null)
{
int tw = backgroundImage.getWidth(this);
int th = backgroundImage.getHeight(this);
if (tw != -1)
for (int y = 0; y < getHeight(); y += th)
for (int x = 0; x < getWidth(); x += tw)
{
g.drawImage(backgroundImage, x, y, this);
}
}
if (backgroundImage != null)
{
int tw = backgroundImage.getWidth(this);
int th = backgroundImage.getHeight(this);
if (tw != -1)
for (int y = 0; y < getHeight(); y += th)
for (int x = 0; x < getWidth(); x += tw)
{
g.drawImage(backgroundImage, x, y, this);
}
}
((java.awt.Graphics2D)g).setRenderingHint(
java.awt.RenderingHints.KEY_ANTIALIASING,
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
);
((java.awt.Graphics2D)g).setRenderingHint(
java.awt.RenderingHints.KEY_ANTIALIASING,
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
);
}
// - -%- -
@ -648,18 +648,18 @@ implements ActionListener {
{
this.primaire = primaire;
emojiUrls = new String[0][];
emojiUrls = new String[0][];
Border b = BorderFactory.createEmptyBorder(10, 10, 10, 10);
Font f1 = new Font("MotoyaLMaru", Font.PLAIN, 18);
Font f1 = new Font("MotoyaLMaru", Font.PLAIN, 18);
Font f2 = new Font("MotoyaLMaru", Font.PLAIN, 14);
Font f3 = new Font("MotoyaLMaru", Font.PLAIN, 18);
Font f3 = new Font("MotoyaLMaru", Font.PLAIN, 18);
profile = new RoundButton();
favouriteBoost = new TwoToggleButton("favourite", "boost");
replyMisc = new TwoToggleButton("reply", "misc");
nextPrev = new TwoToggleButton("next", "prev");
media = new RoundButton();
favouriteBoost = new TwoToggleButton("favourite", "boost");
replyMisc = new TwoToggleButton("reply", "misc");
nextPrev = new TwoToggleButton("next", "prev");
media = new RoundButton();
profile.addActionListener(this);
favouriteBoost.addActionListener(this);
replyMisc.addActionListener(this);
@ -669,22 +669,22 @@ implements ActionListener {
openReplies = new JMenuItem("Browse thread");
copyPostId = new JMenuItem("Copy post ID");
copyPostLink = new JMenuItem("Copy post link");
deletePost = new JMenuItem("Delete post");
redraftPost = new JMenuItem("Delete and redraft post");
openReplies.addActionListener(this);
copyPostId.addActionListener(this);
copyPostLink.addActionListener(this);
deletePost.addActionListener(this);
redraftPost.addActionListener(this);
miscMenu = new JPopupMenu();
miscMenu.add(openReplies);
miscMenu.add(new JSeparator());
miscMenu.add(copyPostId);
miscMenu.add(copyPostLink);
miscMenu.add(new JSeparator());
miscMenu.add(deletePost);
miscMenu.add(new JSeparator());
miscMenu.add(redraftPost);
deletePost = new JMenuItem("Delete post");
redraftPost = new JMenuItem("Delete and redraft post");
openReplies.addActionListener(this);
copyPostId.addActionListener(this);
copyPostLink.addActionListener(this);
deletePost.addActionListener(this);
redraftPost.addActionListener(this);
miscMenu = new JPopupMenu();
miscMenu.add(openReplies);
miscMenu.add(new JSeparator());
miscMenu.add(copyPostId);
miscMenu.add(copyPostLink);
miscMenu.add(new JSeparator());
miscMenu.add(deletePost);
miscMenu.add(new JSeparator());
miscMenu.add(redraftPost);
Box buttons = Box.createVerticalBox();
buttons.setOpaque(false);
@ -702,59 +702,59 @@ implements ActionListener {
left.setOpaque(false);
left.add(buttons);
authorId = new JLabel();
authorName = new RichTextPane();
time = new JLabel();
date = new JLabel();
authorId.setFont(f2);
date.setFont(f2);
authorName.setFont(f1);
time.setFont(f1);
authorId = new JLabel();
authorName = new RichTextPane();
time = new JLabel();
date = new JLabel();
authorId.setFont(f2);
date.setFont(f2);
authorName.setFont(f1);
time.setFont(f1);
JPanel top1 = new JPanel();
top1.setOpaque(false);
top1.setLayout(new BorderLayout(8, 0));
top1.add(authorId);
top1.add(date, BorderLayout.EAST);
JPanel top2 = new JPanel();
top2.setOpaque(false);
top2.setLayout(new BorderLayout(8, 0));
top2.add(authorName);
top2.add(time, BorderLayout.EAST);
Box top = Box.createVerticalBox();
top.add(top1);
top.add(Box.createVerticalStrut(2));
top.add(top2);
JPanel top1 = new JPanel();
top1.setOpaque(false);
top1.setLayout(new BorderLayout(8, 0));
top1.add(authorId);
top1.add(date, BorderLayout.EAST);
JPanel top2 = new JPanel();
top2.setOpaque(false);
top2.setLayout(new BorderLayout(8, 0));
top2.add(authorName);
top2.add(time, BorderLayout.EAST);
Box top = Box.createVerticalBox();
top.add(top1);
top.add(Box.createVerticalStrut(2));
top.add(top2);
body = new RichTextPane3();
body.setFont(f3);
body = new RichTextPane3();
body.setFont(f3);
/*
bodyScrollPane = new JScrollPane(
body,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
);
JScrollBar vsb = bodyScrollPane.getVerticalScrollBar();
vsb.setPreferredSize(new Dimension(0, 0));
vsb.setUnitIncrement(16);
bodyScrollPane.setBorder(null);
bodyScrollPane.setFocusable(true);
*/
/*
bodyScrollPane = new JScrollPane(
body,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
);
JScrollBar vsb = bodyScrollPane.getVerticalScrollBar();
vsb.setPreferredSize(new Dimension(0, 0));
vsb.setUnitIncrement(16);
bodyScrollPane.setBorder(null);
bodyScrollPane.setFocusable(true);
*/
JPanel centre = new JPanel();
centre.setOpaque(false);
centre.setLayout(new BorderLayout(0, 8));
centre.add(top, BorderLayout.NORTH);
centre.add(body);
JPanel centre = new JPanel();
centre.setOpaque(false);
centre.setLayout(new BorderLayout(0, 8));
centre.add(top, BorderLayout.NORTH);
centre.add(body);
setLayout(new BorderLayout(8, 0));
add(left, BorderLayout.WEST);
add(centre);
add(centre);
setBorder(b);
setBorder(b);
backgroundImage = ImageApi.local("postWindow");
backgroundImage = ImageApi.local("postWindow");
}
}

View File

@ -123,7 +123,7 @@ ProfileWindow extends JFrame {
{
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
TimelineWindow w = new TimelineWindow(primaire);
TimelineWindow w = new TimelineWindow(primaire);
w.showAuthorPosts(account.numId);
w.showLatestPage();
w.setLocationRelativeTo(this);
@ -269,9 +269,9 @@ implements ActionListener {
int acx = ax + (aw / 2);
int acy = ay + (ah / 2);
Shape defaultClip = g.getClip();
g.setClip(new Ellipse2D.Float(ax, ay, aw, ah));
g.setClip(new Ellipse2D.Float(ax, ay, aw, ah));
g.drawImage(avatar, ax, ay, aw, ah, this);
g.setClip(defaultClip);
g.setClip(defaultClip);
g.setColor(new Color(0, 0, 0, 50));
g.fillRect(0, acy - dy1, acx - dx1, 2);

View File

@ -189,7 +189,7 @@ RepliesWindow extends JFrame {
setContentPane(display);
setSize(384, 224);
setIconImage(primaire.getProgramIcon());
setIconImage(primaire.getProgramIcon());
}
}
@ -242,13 +242,13 @@ implements TreeSelectionListener {
if (p == null)
{
assert false;
/*
* Besides descendants possibly not being in order,
* the top of the thread might be deleted and so
* thread.top gets set to the given post. Which
* sibling replies aren't replying to, resulting
* in assertion failure.
*/
/*
* Besides descendants possibly not being in order,
* the top of the thread might be deleted and so
* thread.top gets set to the given post. Which
* sibling replies aren't replying to, resulting
* in assertion failure.
*/
continue;
}
@ -257,8 +257,8 @@ implements TreeSelectionListener {
}
tree.setModel(new DefaultTreeModel(root));
for (int o = 0; o < tree.getRowCount(); ++o)
tree.expandRow(o);
for (int o = 0; o < tree.getRowCount(); ++o)
tree.expandRow(o);
}
// - -%- -

View File

@ -24,14 +24,14 @@ import java.io.IOException;
interface
RequestListener {
void
connectionFailed(IOException eIo);
void
connectionFailed(IOException eIo);
void
requestFailed(int httpCode, Tree<String> json);
void
requestFailed(int httpCode, Tree<String> json);
void
requestSucceeded(Tree<String> json);
void
requestSucceeded(Tree<String> json);
}

View File

@ -38,396 +38,396 @@ class
RichTextPane extends JComponent
implements MouseListener, MouseMotionListener, KeyListener {
private List<Segment>
text;
private List<Segment>
text;
private int
selectionStart, selectionEnd;
private int
selectionStart, selectionEnd;
// ---%-@-%---
// ---%-@-%---
public void
setText(List<Segment> text)
{
this.text = text;
selectionStart = selectionEnd = -1;
}
public void
setText(List<Segment> text)
{
this.text = text;
selectionStart = selectionEnd = -1;
}
public List<Segment>
getSelection()
{
List<Segment> returnee = new LinkedList<>();
if (selectionEnd == -1) return returnee;
public List<Segment>
getSelection()
{
List<Segment> returnee = new LinkedList<>();
if (selectionEnd == -1) return returnee;
if (selectionEnd < selectionStart) {
int t = selectionEnd;
selectionEnd = selectionStart;
selectionStart = t;
}
returnee.addAll(text.subList(selectionStart + 1, selectionEnd));
return returnee;
}
if (selectionEnd < selectionStart) {
int t = selectionEnd;
selectionEnd = selectionStart;
selectionStart = t;
}
returnee.addAll(text.subList(selectionStart + 1, selectionEnd));
return returnee;
}
public void
copySelection()
{
public void
copySelection()
{
assert selectionEnd != -1;
StringBuilder b = new StringBuilder();
for (Segment segment: getSelection())
{
if (segment.link != null) b.append(segment.link);
else if (segment.text != null) b.append(segment.text);
}
ClipboardApi.serve(b.toString());
}
StringBuilder b = new StringBuilder();
for (Segment segment: getSelection())
{
if (segment.link != null) b.append(segment.link);
else if (segment.text != null) b.append(segment.text);
}
ClipboardApi.serve(b.toString());
}
// - -%- -
// - -%- -
protected void
paintComponent(Graphics g)
{
g.setFont(getFont());
FontMetrics fm = g.getFontMetrics(getFont());
protected void
paintComponent(Graphics g)
{
g.setFont(getFont());
FontMetrics fm = g.getFontMetrics(getFont());
if (isOpaque())
g.clearRect(0, 0, getWidth(), getHeight());
if (isOpaque())
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.Graphics2D)g).setRenderingHint(
java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,
java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON
);
int o = 0;
for (Segment segment: text)
{
if (segment.image != null) {
int ow = segment.image.getIconWidth();
int oh = segment.image.getIconHeight();
int h = fm.getAscent() + fm.getDescent();
int w = h * ow / oh;
int x = segment.x;
int y = segment.y + fm.getDescent();
// Interpret segment.y as specifying text baseline
Image img = segment.image.getImage();
g.drawImage(img, x, y - h, w, h, this);
continue;
}
int o = 0;
for (Segment segment: text)
{
if (segment.image != null) {
int ow = segment.image.getIconWidth();
int oh = segment.image.getIconHeight();
int h = fm.getAscent() + fm.getDescent();
int w = h * ow / oh;
int x = segment.x;
int y = segment.y + fm.getDescent();
// Interpret segment.y as specifying text baseline
Image img = segment.image.getImage();
g.drawImage(img, x, y - h, w, h, this);
continue;
}
if (o > selectionStart && o < selectionEnd)
{
int dx = fm.stringWidth(segment.text);
int dy1 = fm.getAscent();
int dy2 = dy1 + fm.getDescent();
g.setColor(new Color(0, 0, 0, 15));
g.fillRect(segment.x, segment.y - dy1, dx, dy2);
g.setColor(getForeground());
}
if (o > selectionStart && o < selectionEnd)
{
int dx = fm.stringWidth(segment.text);
int dy1 = fm.getAscent();
int dy2 = dy1 + fm.getDescent();
g.setColor(new Color(0, 0, 0, 15));
g.fillRect(segment.x, segment.y - dy1, dx, dy2);
g.setColor(getForeground());
}
if (segment.link != null) g.setColor(Color.BLUE);
g.drawString(segment.text, segment.x, segment.y);
g.setColor(getForeground());
if (segment.link != null) g.setColor(Color.BLUE);
g.drawString(segment.text, segment.x, segment.y);
g.setColor(getForeground());
++o;
}
}
++o;
}
}
public void
mousePressed(MouseEvent eM)
{
requestFocusInWindow();
selectionStart = identify(eM.getX(), eM.getY()) - 2;
selectionEnd = -1;
repaint();
}
public void
mousePressed(MouseEvent eM)
{
requestFocusInWindow();
selectionStart = identify(eM.getX(), eM.getY()) - 2;
selectionEnd = -1;
repaint();
}
public void
mouseDragged(MouseEvent eM)
{
selectionEnd = identify(eM.getX(), eM.getY());
repaint();
}
public void
mouseDragged(MouseEvent eM)
{
selectionEnd = identify(eM.getX(), eM.getY());
repaint();
}
private int
identify(int x, int y)
{
FontMetrics fm = getFontMetrics(getFont());
int iy = fm.getAscent();
int lh = fm.getAscent() + fm.getDescent();
y -= fm.getDescent();
if (y <= iy) y = iy;
else y += lh - ((y - iy) % lh);
/*
* Snaps y to the next baseline. Kind of obtuse,
* but it wasn't randomly derived, anyways
* you can test it for 13, 30, 47, etc.
*/
private int
identify(int x, int y)
{
FontMetrics fm = getFontMetrics(getFont());
int iy = fm.getAscent();
int lh = fm.getAscent() + fm.getDescent();
y -= fm.getDescent();
if (y <= iy) y = iy;
else y += lh - ((y - iy) % lh);
/*
* Snaps y to the next baseline. Kind of obtuse,
* but it wasn't randomly derived, anyways
* you can test it for 13, 30, 47, etc.
*/
int o = 0;
for (Segment segment: text)
{
if (segment.y == y && segment.x > x) break;
if (segment.y > y) break;
++o;
}
return o;
}
int o = 0;
for (Segment segment: text)
{
if (segment.y == y && segment.x > x) break;
if (segment.y > y) break;
++o;
}
return o;
}
public void
keyPressed(KeyEvent eK)
{
if (selectionEnd == -1) return;
if (eK.getKeyCode() != KeyEvent.VK_C) return;
if (!eK.isControlDown()) return;
copySelection();
}
public void
keyPressed(KeyEvent eK)
{
if (selectionEnd == -1) return;
if (eK.getKeyCode() != KeyEvent.VK_C) return;
if (!eK.isControlDown()) return;
copySelection();
}
public void
keyReleased(KeyEvent eK) { }
public void
keyReleased(KeyEvent eK) { }
public void
keyTyped(KeyEvent eK) { }
public void
keyTyped(KeyEvent eK) { }
public void
mouseClicked(MouseEvent eM) { }
public void
mouseClicked(MouseEvent eM) { }
public void
mouseReleased(MouseEvent eM) { }
public void
mouseReleased(MouseEvent eM) { }
public void
mouseEntered(MouseEvent eM) { }
public void
mouseEntered(MouseEvent eM) { }
public void
mouseExited(MouseEvent eM) { }
public void
mouseExited(MouseEvent eM) { }
public void
mouseMoved(MouseEvent eM) { }
public void
mouseMoved(MouseEvent eM) { }
// - -%- -
// - -%- -
public static List<Segment>
layout(List<Segment> text, FontMetrics fm, int width)
{
List<Segment> copy = new LinkedList<>();
for (Segment segment: text) copy.add(segment.clone());
text = copy;
ListIterator<Segment> cursor = text.listIterator();
int x = 0, y = fm.getAscent();
int dy = fm.getAscent() + fm.getDescent();
while (cursor.hasNext())
{
Segment curr = cursor.next();
public static List<Segment>
layout(List<Segment> text, FontMetrics fm, int width)
{
List<Segment> copy = new LinkedList<>();
for (Segment segment: text) copy.add(segment.clone());
text = copy;
ListIterator<Segment> cursor = text.listIterator();
int x = 0, y = fm.getAscent();
int dy = fm.getAscent() + fm.getDescent();
while (cursor.hasNext())
{
Segment curr = cursor.next();
int dx;
if (curr.image != null) {
int ow = curr.image.getIconWidth();
int oh = curr.image.getIconHeight();
int nh = fm.getAscent() + fm.getDescent();
dx = nh * ow / oh;
}
else if (curr.text != null) {
dx = fm.stringWidth(curr.text);
}
else if (curr.link != null) {
curr.text = curr.link;
dx = fm.stringWidth(curr.link);
}
else {
assert false;
dx = 0;
}
int dx;
if (curr.image != null) {
int ow = curr.image.getIconWidth();
int oh = curr.image.getIconHeight();
int nh = fm.getAscent() + fm.getDescent();
dx = nh * ow / oh;
}
else if (curr.text != null) {
dx = fm.stringWidth(curr.text);
}
else if (curr.link != null) {
curr.text = curr.link;
dx = fm.stringWidth(curr.link);
}
else {
assert false;
dx = 0;
}
boolean fits = x + dx < width;
boolean fits = x + dx < width;
if (fits || curr.spacer)
{
curr.x = x;
curr.y = y;
x += dx;
if (curr.spacer && curr.text.equals("\n")) {
y += dy;
x = 0;
}
continue;
}
if (fits || curr.spacer)
{
curr.x = x;
curr.y = y;
x += dx;
if (curr.spacer && curr.text.equals("\n")) {
y += dy;
x = 0;
}
continue;
}
boolean tooLong = dx > width;
boolean canFitChar = width >= fm.getMaxAdvance();
boolean splittable = curr.image == null;
/*
* A bit of redundancy in my conditions, but the point is
* to exactly express the triggers in my mental model.
* The conditions should read more like English.
*/
boolean tooLong = dx > width;
boolean canFitChar = width >= fm.getMaxAdvance();
boolean splittable = curr.image == null;
/*
* A bit of redundancy in my conditions, but the point is
* to exactly express the triggers in my mental model.
* The conditions should read more like English.
*/
if (!tooLong || (tooLong && !splittable))
{
curr.x = 0;
curr.y = y += dy;
x = dx;
continue;
}
if (!tooLong || (tooLong && !splittable))
{
curr.x = 0;
curr.y = y += dy;
x = dx;
continue;
}
assert tooLong && splittable;
assert tooLong && splittable;
String s = curr.text;
int splitOffset;
for (splitOffset = 0; splitOffset < s.length(); ++splitOffset)
{
String substring = s.substring(0, splitOffset + 1);
if (fm.stringWidth(substring) > width) break;
}
if (splitOffset == 0) splitOffset = 1;
/*
* I force a split even if our width supports no characters.
* Because if I don't split, the only alternatives to infinitely
* looping downwards is to emplace this segment or ignore it.
*/
Segment fitted = new Segment();
fitted.text = s.substring(0, splitOffset);
fitted.link = curr.link;
fitted.x = x;
fitted.y = y;
cursor.add(fitted);
curr.text = s.substring(splitOffset);
y += dy;
x = 0;
cursor.add(curr); cursor.previous();
/*
* I had to use a stack and return a new list because,
* splitting can turn a long segment into several spread
* over different lines. Here curr becomes the "after-split"
* and I push it back to the stack.
*
* If #layout wasn't a separate method, but rather only for
* graphical painting. Then I don't need a stack nor return
* a new list. I iterate over the given one, filling the
* nodes' geometric information, if a split occurs I save the
* "after-split" in a variable, which in the next iteration
* I use as curr instead of list.next(). The caller doesn't
* need to know the geometry of these intermediate segments.
*/
continue;
}
String s = curr.text;
int splitOffset;
for (splitOffset = 0; splitOffset < s.length(); ++splitOffset)
{
String substring = s.substring(0, splitOffset + 1);
if (fm.stringWidth(substring) > width) break;
}
if (splitOffset == 0) splitOffset = 1;
/*
* I force a split even if our width supports no characters.
* Because if I don't split, the only alternatives to infinitely
* looping downwards is to emplace this segment or ignore it.
*/
Segment fitted = new Segment();
fitted.text = s.substring(0, splitOffset);
fitted.link = curr.link;
fitted.x = x;
fitted.y = y;
cursor.add(fitted);
curr.text = s.substring(splitOffset);
y += dy;
x = 0;
cursor.add(curr); cursor.previous();
/*
* I had to use a stack and return a new list because,
* splitting can turn a long segment into several spread
* over different lines. Here curr becomes the "after-split"
* and I push it back to the stack.
*
* If #layout wasn't a separate method, but rather only for
* graphical painting. Then I don't need a stack nor return
* a new list. I iterate over the given one, filling the
* nodes' geometric information, if a split occurs I save the
* "after-split" in a variable, which in the next iteration
* I use as curr instead of list.next(). The caller doesn't
* need to know the geometry of these intermediate segments.
*/
continue;
}
return text;
}
return text;
}
// ---%-@-%---
// ---%-@-%---
public static class
Segment {
public static class
Segment {
public ImageIcon
image;
public ImageIcon
image;
public String
link;
public String
link;
public String
text;
public String
text;
public boolean
spacer;
public boolean
spacer;
public int
x, y;
public int
x, y;
// -=%=-
// -=%=-
public String
toString()
{
StringBuilder b = new StringBuilder();
b.append(getClass().getName() + "[");
b.append("image=" + image);
b.append(",link=" + link);
b.append(",text=" + text);
b.append(",x=" + x);
b.append(",y=" + y);
b.append("]");
return b.toString();
}
public String
toString()
{
StringBuilder b = new StringBuilder();
b.append(getClass().getName() + "[");
b.append("image=" + image);
b.append(",link=" + link);
b.append(",text=" + text);
b.append(",x=" + x);
b.append(",y=" + y);
b.append("]");
return b.toString();
}
public Segment
clone()
{
Segment segment = new Segment();
segment.image = this.image;
segment.link = this.link;
segment.text = this.text;
segment.spacer = this.spacer;
segment.x = this.x;
segment.y = this.y;
return segment;
}
public Segment
clone()
{
Segment segment = new Segment();
segment.image = this.image;
segment.link = this.link;
segment.text = this.text;
segment.spacer = this.spacer;
segment.x = this.x;
segment.y = this.y;
return segment;
}
}
}
public static class
Builder {
public static class
Builder {
private List<Segment>
returnee;
private List<Segment>
returnee;
// -=%=-
// -=%=-
public
Builder() { returnee = new LinkedList<>(); }
public
Builder() { returnee = new LinkedList<>(); }
public Builder
image(ImageIcon image, String text)
{
Segment segment = new Segment();
segment.image = image;
segment.text = text;
returnee.add(segment);
return this;
}
public Builder
image(ImageIcon image, String text)
{
Segment segment = new Segment();
segment.image = image;
segment.text = text;
returnee.add(segment);
return this;
}
public Builder
link(String link, String text)
{
Segment segment = new Segment();
segment.link = link;
segment.text = text;
returnee.add(segment);
return this;
}
public Builder
link(String link, String text)
{
Segment segment = new Segment();
segment.link = link;
segment.text = text;
returnee.add(segment);
return this;
}
public Builder
text(String text)
{
Segment segment = new Segment();
segment.text = text;
returnee.add(segment);
return this;
}
public Builder
text(String text)
{
Segment segment = new Segment();
segment.text = text;
returnee.add(segment);
return this;
}
public Builder
spacer(String text)
{
Segment segment = new Segment();
segment.text = text;
segment.spacer = true;
returnee.add(segment);
return this;
}
public Builder
spacer(String text)
{
Segment segment = new Segment();
segment.text = text;
segment.spacer = true;
returnee.add(segment);
return this;
}
public List<Segment>
finish() { return returnee; }
public List<Segment>
finish() { return returnee; }
}
}
// ---%-@-%---
// ---%-@-%---
RichTextPane()
{
text = new LinkedList<>();
RichTextPane()
{
text = new LinkedList<>();
addMouseListener(this);
addMouseMotionListener(this);
addKeyListener(this);
}
addMouseListener(this);
addMouseMotionListener(this);
addKeyListener(this);
}
}

View File

@ -37,463 +37,463 @@ class
RichTextPane2 extends JComponent
implements ComponentListener {
private AttributedString
text;
private AttributedString
text;
// ---%-@-%---
// ---%-@-%---
public void
setText(Tree<String> html, Tree<String> emojiMap)
{
Tree<String> commands = turnIntoCommands(html);
public void
setText(Tree<String> html, Tree<String> emojiMap)
{
Tree<String> commands = turnIntoCommands(html);
class AStrSegment {
String text;
int offset;
Object[] values = new Object[Attribute.COUNT];
/*
{
values[3] = (Boolean)true;
values[4] = (Integer)0;
values[5] = (Boolean)true;
values[6] = (Boolean)false;
}
*/
}
List<AStrSegment> segments = new ArrayList<>();
class AStrSegment {
String text;
int offset;
Object[] values = new Object[Attribute.COUNT];
/*
{
values[3] = (Boolean)true;
values[4] = (Integer)0;
values[5] = (Boolean)true;
values[6] = (Boolean)false;
}
*/
}
List<AStrSegment> segments = new ArrayList<>();
int offset = 0;
for (Tree<String> command: commands)
{
if (command.key.equals("text"))
{
StringBuilder b = new StringBuilder();
Boolean cibl = null;
Boolean cwhi = null;
for (char c: command.value.toCharArray())
{
Boolean ibl = isBasicLatin(c);
Boolean whi = Character.isWhitespace(c);
if (!ibl.equals(cibl) || !whi.equals(cwhi))
{
if (b.length() > 0)
{
assert cibl != null && cwhi != null;
AStrSegment s = new AStrSegment();
s.offset = offset;
s.text = b.toString();
s.values[3] = cibl;
s.values[6] = cwhi;
segments.add(s);
offset += s.text.length();
b.delete(0, b.length());
}
cibl = ibl;
cwhi = whi;
}
int offset = 0;
for (Tree<String> command: commands)
{
if (command.key.equals("text"))
{
StringBuilder b = new StringBuilder();
Boolean cibl = null;
Boolean cwhi = null;
for (char c: command.value.toCharArray())
{
Boolean ibl = isBasicLatin(c);
Boolean whi = Character.isWhitespace(c);
if (!ibl.equals(cibl) || !whi.equals(cwhi))
{
if (b.length() > 0)
{
assert cibl != null && cwhi != null;
AStrSegment s = new AStrSegment();
s.offset = offset;
s.text = b.toString();
s.values[3] = cibl;
s.values[6] = cwhi;
segments.add(s);
offset += s.text.length();
b.delete(0, b.length());
}
cibl = ibl;
cwhi = whi;
}
b.append(c);
}
if (b.length() > 0)
{
AStrSegment s = new AStrSegment();
s.offset = offset;
s.text = b.toString();
s.values[3] = cibl;
s.values[6] = cwhi;
segments.add(s);
offset += s.text.length();
}
}
else if (command.key.equals("emoji"))
{
AStrSegment s = new AStrSegment();
s.offset = offset;
s.values[3] = true;
s.values[6] = false;
b.append(c);
}
if (b.length() > 0)
{
AStrSegment s = new AStrSegment();
s.offset = offset;
s.text = b.toString();
s.values[3] = cibl;
s.values[6] = cwhi;
segments.add(s);
offset += s.text.length();
}
}
else if (command.key.equals("emoji"))
{
AStrSegment s = new AStrSegment();
s.offset = offset;
s.values[3] = true;
s.values[6] = false;
String shortcode = command.value;
String url = null;
Tree<String> m = emojiMap.get(shortcode);
if (m != null) url = m.value;
Image img = ImageApi.remote(url);
if (img != null)
{
s.text = " ";
s.values[0] = img;
s.values[1] = shortcode;
segments.add(s);
offset += 1;
}
else
{
s.text = shortcode;
s.values[0] = null;
s.values[1] = null;
segments.add(s);
offset += shortcode.length();
}
}
else if (command.key.equals("link"))
{
AStrSegment s = new AStrSegment();
s.offset = offset;
s.text = command.value;
s.values[2] = command.get("url").value;
s.values[3] = true;
s.values[6] = false;
/*
* Technically we're supposed to treat
* the anchor text like a text node.
* As in, it could be non-Basic-Latin..
* I'll be Mastodon-specific again, and
* assume it's a URL or some @ string.
*/
}
}
String shortcode = command.value;
String url = null;
Tree<String> m = emojiMap.get(shortcode);
if (m != null) url = m.value;
Image img = ImageApi.remote(url);
if (img != null)
{
s.text = " ";
s.values[0] = img;
s.values[1] = shortcode;
segments.add(s);
offset += 1;
}
else
{
s.text = shortcode;
s.values[0] = null;
s.values[1] = null;
segments.add(s);
offset += shortcode.length();
}
}
else if (command.key.equals("link"))
{
AStrSegment s = new AStrSegment();
s.offset = offset;
s.text = command.value;
s.values[2] = command.get("url").value;
s.values[3] = true;
s.values[6] = false;
/*
* Technically we're supposed to treat
* the anchor text like a text node.
* As in, it could be non-Basic-Latin..
* I'll be Mastodon-specific again, and
* assume it's a URL or some @ string.
*/
}
}
AttributedString astr;
StringBuilder b = new StringBuilder();
for (AStrSegment segment: segments)
{
b.append(segment.text);
}
astr = new AttributedString(b.toString());
for (AStrSegment segment: segments)
{
Object[] v = segment.values;
astr.addAttribute(
Attribute.IMAGE, segment.values[0],
segment.offset,
segment.offset + segment.text.length()
);
astr.addAttribute(
Attribute.ALT, segment.values[1],
segment.offset,
segment.offset + segment.text.length()
);
astr.addAttribute(
Attribute.LINK, segment.values[2],
segment.offset,
segment.offset + segment.text.length()
);
astr.addAttribute(
Attribute.BASICLATIN, segment.values[3],
segment.offset,
segment.offset + segment.text.length()
);
astr.addAttribute(
Attribute.Y, segment.values[4],
segment.offset,
segment.offset + segment.text.length()
);
astr.addAttribute(
Attribute.OFFSCREEN, segment.values[5],
segment.offset,
segment.offset + segment.text.length()
);
astr.addAttribute(
Attribute.WHITESPACE, segment.values[6],
segment.offset,
segment.offset + segment.text.length()
);
}
AttributedString astr;
StringBuilder b = new StringBuilder();
for (AStrSegment segment: segments)
{
b.append(segment.text);
}
astr = new AttributedString(b.toString());
for (AStrSegment segment: segments)
{
Object[] v = segment.values;
astr.addAttribute(
Attribute.IMAGE, segment.values[0],
segment.offset,
segment.offset + segment.text.length()
);
astr.addAttribute(
Attribute.ALT, segment.values[1],
segment.offset,
segment.offset + segment.text.length()
);
astr.addAttribute(
Attribute.LINK, segment.values[2],
segment.offset,
segment.offset + segment.text.length()
);
astr.addAttribute(
Attribute.BASICLATIN, segment.values[3],
segment.offset,
segment.offset + segment.text.length()
);
astr.addAttribute(
Attribute.Y, segment.values[4],
segment.offset,
segment.offset + segment.text.length()
);
astr.addAttribute(
Attribute.OFFSCREEN, segment.values[5],
segment.offset,
segment.offset + segment.text.length()
);
astr.addAttribute(
Attribute.WHITESPACE, segment.values[6],
segment.offset,
segment.offset + segment.text.length()
);
}
this.text = astr;
componentResized(null);
}
this.text = astr;
componentResized(null);
}
// - -%- -
// - -%- -
public void
componentResized(ComponentEvent eC)
{
int w = getWidth(), h = getHeight();
public void
componentResized(ComponentEvent eC)
{
int w = getWidth(), h = getHeight();
// We're going to evaluate the
// line and off-screen attributes.
// We're going to evaluate the
// line and off-screen attributes.
FontMetrics fm = getFontMetrics(getFont());
Graphics g = getGraphics();
int x = 0, y = fm.getAscent();
FontMetrics fm = getFontMetrics(getFont());
Graphics g = getGraphics();
int x = 0, y = fm.getAscent();
AttributedCharacterIterator it;
it = text.getIterator();
AttributedCharacterIterator it;
it = text.getIterator();
while (it.getIndex() < it.getEndIndex())
{
int start = it.getIndex();
int end = it.getRunLimit();
while (it.getIndex() < it.getEndIndex())
{
int start = it.getIndex();
int end = it.getRunLimit();
Image img = (Image)
it.getAttribute(Attribute.IMAGE);
Boolean ibl = (Boolean)
it.getAttribute(Attribute.BASICLATIN);
Boolean whi = (Boolean)
it.getAttribute(Attribute.WHITESPACE);
Image img = (Image)
it.getAttribute(Attribute.IMAGE);
Boolean ibl = (Boolean)
it.getAttribute(Attribute.BASICLATIN);
Boolean whi = (Boolean)
it.getAttribute(Attribute.WHITESPACE);
assert ibl != null;
assert whi != null;
assert ibl != null;
assert whi != null;
if (img != null)
{
int ow = img.getWidth(this);
int oh = img.getHeight(this);
int nh = fm.getAscent() + fm.getDescent();
int nw = ow * nh/oh;
if (x + nw > w)
{
y += fm.getAscent() + fm.getDescent();
x = nw;
}
text.addAttribute(
Attribute.Y, (Integer)y,
start, end
);
text.addAttribute(
Attribute.OFFSCREEN, (Boolean)(y > h),
start, end
);
it.setIndex(end);
}
else
{
int p, xOff = 0;
for (p = end; p > start; --p)
{
Rectangle2D r;
r = fm.getStringBounds(it, start, p, g);
xOff = (int)r.getWidth();
if (x + xOff < w) break;
}
if (p == end || whi)
{
x += xOff;
text.addAttribute(
Attribute.Y, (Integer)y,
start, end
);
text.addAttribute(
Attribute.OFFSCREEN, (Boolean)(y > h),
start, end
);
it.setIndex(end);
}
else if (p <= start)
{
y += fm.getAscent() + fm.getDescent();
x = xOff;
text.addAttribute(
Attribute.Y, (Integer)y,
start, end
);
text.addAttribute(
Attribute.OFFSCREEN, (Boolean)(y > h),
start, end
);
it.setIndex(end);
}
else
{
text.addAttribute(
Attribute.Y, (Integer)y,
start, p
);
text.addAttribute(
Attribute.OFFSCREEN, (Boolean)(y > h),
start, p
);
y += fm.getAscent() + fm.getDescent();
x = 0;
it.setIndex(p);
}
}
}
if (img != null)
{
int ow = img.getWidth(this);
int oh = img.getHeight(this);
int nh = fm.getAscent() + fm.getDescent();
int nw = ow * nh/oh;
if (x + nw > w)
{
y += fm.getAscent() + fm.getDescent();
x = nw;
}
text.addAttribute(
Attribute.Y, (Integer)y,
start, end
);
text.addAttribute(
Attribute.OFFSCREEN, (Boolean)(y > h),
start, end
);
it.setIndex(end);
}
else
{
int p, xOff = 0;
for (p = end; p > start; --p)
{
Rectangle2D r;
r = fm.getStringBounds(it, start, p, g);
xOff = (int)r.getWidth();
if (x + xOff < w) break;
}
if (p == end || whi)
{
x += xOff;
text.addAttribute(
Attribute.Y, (Integer)y,
start, end
);
text.addAttribute(
Attribute.OFFSCREEN, (Boolean)(y > h),
start, end
);
it.setIndex(end);
}
else if (p <= start)
{
y += fm.getAscent() + fm.getDescent();
x = xOff;
text.addAttribute(
Attribute.Y, (Integer)y,
start, end
);
text.addAttribute(
Attribute.OFFSCREEN, (Boolean)(y > h),
start, end
);
it.setIndex(end);
}
else
{
text.addAttribute(
Attribute.Y, (Integer)y,
start, p
);
text.addAttribute(
Attribute.OFFSCREEN, (Boolean)(y > h),
start, p
);
y += fm.getAscent() + fm.getDescent();
x = 0;
it.setIndex(p);
}
}
}
text.addAttribute(TextAttribute.FONT, getFont());
text.addAttribute(TextAttribute.FONT, getFont());
repaint();
}
repaint();
}
protected void
paintComponent(Graphics g)
{
int w = getWidth(), h = getHeight();
g.clearRect(0, 0, w, h);
protected void
paintComponent(Graphics g)
{
int w = getWidth(), h = getHeight();
g.clearRect(0, 0, w, h);
FontMetrics fm = g.getFontMetrics();
FontMetrics fm = g.getFontMetrics();
AttributedCharacterIterator it;
it = text.getIterator();
AttributedCharacterIterator it;
it = text.getIterator();
((java.awt.Graphics2D)g).setRenderingHint(
java.awt.RenderingHints.KEY_ANTIALIASING,
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
);
((java.awt.Graphics2D)g).setRenderingHint(
java.awt.RenderingHints.KEY_ANTIALIASING,
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
);
int x = 0, y = fm.getAscent();
while (it.getIndex() < it.getEndIndex())
{
int start = it.getIndex();
int end = it.getRunLimit();
int x = 0, y = fm.getAscent();
while (it.getIndex() < it.getEndIndex())
{
int start = it.getIndex();
int end = it.getRunLimit();
Image img = (Image)
it.getAttribute(Attribute.IMAGE);
Boolean ibl = (Boolean)
it.getAttribute(Attribute.BASICLATIN);
Integer ny = (Integer)
it.getAttribute(Attribute.Y);
Image img = (Image)
it.getAttribute(Attribute.IMAGE);
Boolean ibl = (Boolean)
it.getAttribute(Attribute.BASICLATIN);
Integer ny = (Integer)
it.getAttribute(Attribute.Y);
if (ny > y)
{
y = ny;
x = 0;
}
if (ny > y)
{
y = ny;
x = 0;
}
if (img != null)
{
int ow = img.getWidth(this);
int oh = img.getHeight(this);
int nh = fm.getAscent() + fm.getDescent();
int nw = ow * nh/oh;
int iy = y + fm.getDescent() - nh;
g.drawImage(img, x, iy, nw, nh, this);
x += nw;
}
else
{
Rectangle2D r;
r = fm.getStringBounds(it, start, end, g);
AttributedCharacterIterator sit;
sit = text.getIterator(null, start, end);
g.drawString(sit, x, y);
x += (int)r.getWidth();
}
it.setIndex(end);
}
}
if (img != null)
{
int ow = img.getWidth(this);
int oh = img.getHeight(this);
int nh = fm.getAscent() + fm.getDescent();
int nw = ow * nh/oh;
int iy = y + fm.getDescent() - nh;
g.drawImage(img, x, iy, nw, nh, this);
x += nw;
}
else
{
Rectangle2D r;
r = fm.getStringBounds(it, start, end, g);
AttributedCharacterIterator sit;
sit = text.getIterator(null, start, end);
g.drawString(sit, x, y);
x += (int)r.getWidth();
}
it.setIndex(end);
}
}
public void
componentMoved(ComponentEvent eC) { }
public void
componentMoved(ComponentEvent eC) { }
public void
componentShown(ComponentEvent eC) { }
public void
componentShown(ComponentEvent eC) { }
public void
componentHidden(ComponentEvent eC) { }
public void
componentHidden(ComponentEvent eC) { }
// - -%- -
// - -%- -
private static Boolean
isBasicLatin(char c)
{
return true;
}
private static Boolean
isBasicLatin(char c)
{
return true;
}
private static String
toText(Tree<String> node)
{
Tree<String> children = node.get("children");
if (children == null)
{
boolean text = node.key.equals("text");
boolean emoji = node.key.equals("emoji");
assert text || emoji;
return node.value;
}
private static String
toText(Tree<String> node)
{
Tree<String> children = node.get("children");
if (children == null)
{
boolean text = node.key.equals("text");
boolean emoji = node.key.equals("emoji");
assert text || emoji;
return node.value;
}
StringBuilder b = new StringBuilder();
for (Tree<String> child: children)
{
b.append(toText(child));
}
return b.toString();
}
StringBuilder b = new StringBuilder();
for (Tree<String> child: children)
{
b.append(toText(child));
}
return b.toString();
}
private static Tree<String>
turnIntoCommands(Tree<String> tag)
{
assert tag.key.equals("tag");
Tree<String> returnee = new Tree<String>();
private static Tree<String>
turnIntoCommands(Tree<String> tag)
{
assert tag.key.equals("tag");
Tree<String> returnee = new Tree<String>();
String tagName = tag.get(0).key;
Tree<String> children = tag.get("children");
String tagName = tag.get(0).key;
Tree<String> children = tag.get("children");
if (tagName.equals("a"))
{
String url = tag.get("href").value;
Tree<String> addee = new Tree<>();
addee.key = "link";
addee.value = toText(tag);
addee.add(new Tree<>("url", url));
returnee.add(addee);
}
else if (tagName.equals("span"))
{
Tree<String> addee = new Tree<>();
addee.key = "text";
addee.value = toText(tag);
returnee.add(addee);
}
else if (tagName.equals("br"))
{
returnee.add(new Tree<>("text", "\n"));
}
else
{
for (Tree<String> child: children)
{
if (!child.key.equals("tag"))
{
returnee.add(child);
continue;
}
child = turnIntoCommands(child);
for (Tree<String> command: child)
{
returnee.add(command);
}
}
if (tagName.equals("p"))
{
returnee.add(new Tree<>("text", "\n"));
returnee.add(new Tree<>("text", "\n"));
}
}
if (tagName.equals("a"))
{
String url = tag.get("href").value;
Tree<String> addee = new Tree<>();
addee.key = "link";
addee.value = toText(tag);
addee.add(new Tree<>("url", url));
returnee.add(addee);
}
else if (tagName.equals("span"))
{
Tree<String> addee = new Tree<>();
addee.key = "text";
addee.value = toText(tag);
returnee.add(addee);
}
else if (tagName.equals("br"))
{
returnee.add(new Tree<>("text", "\n"));
}
else
{
for (Tree<String> child: children)
{
if (!child.key.equals("tag"))
{
returnee.add(child);
continue;
}
child = turnIntoCommands(child);
for (Tree<String> command: child)
{
returnee.add(command);
}
}
if (tagName.equals("p"))
{
returnee.add(new Tree<>("text", "\n"));
returnee.add(new Tree<>("text", "\n"));
}
}
return returnee;
}
return returnee;
}
// ---%-@-%---
// ---%-@-%---
public static class
Attribute extends AttributedCharacterIterator.Attribute {
public static class
Attribute extends AttributedCharacterIterator.Attribute {
public static final Attribute
IMAGE = new Attribute("IMAGE"),
ALT = new Attribute("ALT"),
LINK = new Attribute("LINK"),
BASICLATIN = new Attribute("BASICLATIN"),
Y = new Attribute("Y"),
OFFSCREEN = new Attribute("OFFSCREEN"),
WHITESPACE = new Attribute("WHITESPACE");
public static final Attribute
IMAGE = new Attribute("IMAGE"),
ALT = new Attribute("ALT"),
LINK = new Attribute("LINK"),
BASICLATIN = new Attribute("BASICLATIN"),
Y = new Attribute("Y"),
OFFSCREEN = new Attribute("OFFSCREEN"),
WHITESPACE = new Attribute("WHITESPACE");
public static final int
COUNT = 7;
public static final int
COUNT = 7;
// -=%=-
// -=%=-
private
Attribute(String name) { super(name); }
private
Attribute(String name) { super(name); }
}
}
// ---%-@-%---
// ---%-@-%---
RichTextPane2()
{
this.addComponentListener(this);
text = new AttributedString("");
}
RichTextPane2()
{
this.addComponentListener(this);
text = new AttributedString("");
}
}

View File

@ -45,232 +45,232 @@ implements
ComponentListener,
MouseListener, MouseMotionListener, KeyListener {
private Tree<String>
html;
private Tree<String>
html;
private Map<String, Image>
emojis;
private Map<String, Image>
emojis;
private Map<Tree<String>, Position>
layout;
private Map<Tree<String>, Position>
layout;
private Tree<String>
layoutEnd, selStart, selEnd;
private Tree<String>
layoutEnd, selStart, selEnd;
private int
startingLine, lastLine;
private int
startingLine, lastLine;
// ---%-@-%---
// ---%-@-%---
public void
setText(Tree<String> html)
{
public void
setText(Tree<String> html)
{
assert html != null;
this.html = html;
this.html = html;
if (!isValid()) return;
if (!isValid()) return;
assert html.key != null;
assert html.key.equals("tag");
assert html.get("children") != null;
assert html.key != null;
assert html.key.equals("tag");
assert html.get("children") != null;
FontMetrics fm = getFontMetrics(getFont());
Position cursor = new Position(0, 1);
FontMetrics fm = getFontMetrics(getFont());
Position cursor = new Position(0, 1);
// Manually negate if first element is a break.
Tree<String> children = html.get("children");
if (children.size() > 0)
{
Tree<String> first = children.get(0);
if (first.key.equals("tag"))
{
String tagName = first.get(0).key;
if (tagName.equals("br")) cursor.line -= 1;
if (tagName.equals("p")) cursor.line -= 2;
}
}
// Manually negate if first element is a break.
Tree<String> children = html.get("children");
if (children.size() > 0)
{
Tree<String> first = children.get(0);
if (first.key.equals("tag"))
{
String tagName = first.get(0).key;
if (tagName.equals("br")) cursor.line -= 1;
if (tagName.equals("p")) cursor.line -= 2;
}
}
selStart = selEnd = null;
layout.clear();
startingLine = 1;
layout(html, fm, cursor);
layout.put(layoutEnd, cursor.clone());
lastLine = cursor.line;
repaint();
selStart = selEnd = null;
layout.clear();
startingLine = 1;
layout(html, fm, cursor);
layout.put(layoutEnd, cursor.clone());
lastLine = cursor.line;
repaint();
int iy = fm.getAscent();
int lh = fm.getAscent() + fm.getDescent();
int h = snap2(cursor.line, iy, lh);
h += fm.getDescent();
setPreferredSize(new Dimension(1, h));
}
int iy = fm.getAscent();
int lh = fm.getAscent() + fm.getDescent();
int h = snap2(cursor.line, iy, lh);
h += fm.getDescent();
setPreferredSize(new Dimension(1, h));
}
public void
setEmojis(Map<String, Image> emojis)
{
assert emojis != null;
this.emojis = emojis;
setText(html);
}
public void
setEmojis(Map<String, Image> emojis)
{
assert emojis != null;
this.emojis = emojis;
setText(html);
}
public void
previousPage()
{
int advance = getHeightInLines();
if (startingLine < advance) startingLine = 1;
else startingLine -= advance;
repaint();
}
public void
previousPage()
{
int advance = getHeightInLines();
if (startingLine < advance) startingLine = 1;
else startingLine -= advance;
repaint();
}
public void
nextPage()
{
int advance = getHeightInLines();
if (lastLine - startingLine < advance) return;
else startingLine += advance;
repaint();
}
public void
nextPage()
{
int advance = getHeightInLines();
if (lastLine - startingLine < advance) return;
else startingLine += advance;
repaint();
}
// - -%- -
// - -%- -
private void
layout(Tree<String> node, FontMetrics fm, Position cursor)
{
private void
layout(Tree<String> node, FontMetrics fm, Position cursor)
{
assert cursor != null;
if (node.key.equals("space"))
{
int w = fm.stringWidth(node.value);
if (cursor.x + w < getWidth())
{
layout.put(node, cursor.clone());
cursor.x += w;
}
else
{
layout.put(node, cursor.clone());
++cursor.line;
cursor.x = 0;
}
}
else if (node.key.equals("text"))
{
int w = fm.stringWidth(node.value);
if (cursor.x + w < getWidth())
{
layout.put(node, cursor.clone());
cursor.x += w;
}
else if (w < getWidth())
{
++cursor.line;
cursor.x = 0;
layout.put(node, cursor.clone());
cursor.x += w;
}
else
{
StringBuilder rem = new StringBuilder();
rem.append(node.value);
int mw = getWidth();
int aw = mw - cursor.x;
if (node.key.equals("space"))
{
int w = fm.stringWidth(node.value);
if (cursor.x + w < getWidth())
{
layout.put(node, cursor.clone());
cursor.x += w;
}
else
{
layout.put(node, cursor.clone());
++cursor.line;
cursor.x = 0;
}
}
else if (node.key.equals("text"))
{
int w = fm.stringWidth(node.value);
if (cursor.x + w < getWidth())
{
layout.put(node, cursor.clone());
cursor.x += w;
}
else if (w < getWidth())
{
++cursor.line;
cursor.x = 0;
layout.put(node, cursor.clone());
cursor.x += w;
}
else
{
StringBuilder rem = new StringBuilder();
rem.append(node.value);
int mw = getWidth();
int aw = mw - cursor.x;
w = fm.charWidth(node.value.charAt(0));
if (w >= aw)
{
++cursor.line;
cursor.x = 0;
}
w = fm.charWidth(node.value.charAt(0));
if (w >= aw)
{
++cursor.line;
cursor.x = 0;
}
while (rem.length() > 0)
{
int l = 2;
for (; l <= rem.length(); ++l)
{
String substr = rem.substring(0, l);
w = fm.stringWidth(substr);
if (w >= aw) break;
}
String substr = rem.substring(0, --l);
w = fm.stringWidth(substr);
while (rem.length() > 0)
{
int l = 2;
for (; l <= rem.length(); ++l)
{
String substr = rem.substring(0, l);
w = fm.stringWidth(substr);
if (w >= aw) break;
}
String substr = rem.substring(0, --l);
w = fm.stringWidth(substr);
Tree<String> temp = new Tree<>();
temp.key = node.key;
temp.value = substr;
layout.put(temp, cursor.clone());
Tree<String> temp = new Tree<>();
temp.key = node.key;
temp.value = substr;
layout.put(temp, cursor.clone());
rem.delete(0, l);
boolean more = rem.length() != 0;
if (more) ++cursor.line;
cursor.x = more ? 0 : w;
aw = mw;
}
}
}
else if (node.key.equals("emoji"))
{
Image image = emojis.get(node.value);
int w; if (image != null)
{
int ow = image.getWidth(this);
int oh = image.getHeight(this);
int h = fm.getAscent() + fm.getDescent();
w = ow * h/oh;
}
else
{
w = fm.stringWidth(node.value);
}
rem.delete(0, l);
boolean more = rem.length() != 0;
if (more) ++cursor.line;
cursor.x = more ? 0 : w;
aw = mw;
}
}
}
else if (node.key.equals("emoji"))
{
Image image = emojis.get(node.value);
int w; if (image != null)
{
int ow = image.getWidth(this);
int oh = image.getHeight(this);
int h = fm.getAscent() + fm.getDescent();
w = ow * h/oh;
}
else
{
w = fm.stringWidth(node.value);
}
if (cursor.x + w < getWidth())
{
layout.put(node, cursor.clone());
cursor.x += w;
}
else
{
++cursor.line;
cursor.x = 0;
layout.put(node, cursor.clone());
cursor.x += w;
}
}
else if (node.key.equals("tag"))
{
String tagName = node.get(0).key;
Tree<String> children = node.get("children");
if (cursor.x + w < getWidth())
{
layout.put(node, cursor.clone());
cursor.x += w;
}
else
{
++cursor.line;
cursor.x = 0;
layout.put(node, cursor.clone());
cursor.x += w;
}
}
else if (node.key.equals("tag"))
{
String tagName = node.get(0).key;
Tree<String> children = node.get("children");
// We won't place tag nodes on the layout.
// We won't place tag nodes on the layout.
if (tagName.equals("br"))
{
++cursor.line;
cursor.x = 0;
}
else if (tagName.equals("p"))
{
//cursor.line += 3/2;
cursor.line += 2;
// We don't have vertical cursor movement
// other than the line. Maybe fix in the
// future..?
cursor.x = 0;
}
if (tagName.equals("br"))
{
++cursor.line;
cursor.x = 0;
}
else if (tagName.equals("p"))
{
//cursor.line += 3/2;
cursor.line += 2;
// We don't have vertical cursor movement
// other than the line. Maybe fix in the
// future..?
cursor.x = 0;
}
for (Tree<String> child: children)
{
for (Tree<String> child: children)
{
// Shallow copy this child node,
Tree<String> aug = new Tree<>();
Tree<String> aug = new Tree<>();
aug.key = child.key;
aug.value = child.value;
for (Tree<String> gc: child) aug.add(gc);
// Append all of our attributes. We'd like
// those like href to end up at the text
// nodes. This might collide with our
// child node's attributes, for now I'll
// assume that's not an issue.
// those like href to end up at the text
// nodes. This might collide with our
// child node's attributes, for now I'll
// assume that's not an issue.
for (int o = 1; o < node.size(); ++o)
{
Tree<String> attr = node.get(o);
@ -279,29 +279,29 @@ implements
}
layout(aug, fm, cursor);
}
}
else assert false;
}
}
}
else assert false;
}
protected void
paintComponent(Graphics g)
{
protected void
paintComponent(Graphics g)
{
final Color LINK_COLOUR = Color.BLUE;
final Color PLAIN_COLOUR = getForeground();
final Color SEL_COLOUR = new Color(0, 0, 0, 25);
g.setFont(getFont());
FontMetrics fm = g.getFontMetrics();
int iy = fm.getAscent();
int lh = fm.getAscent() + fm.getDescent();
int asc = fm.getAscent();
int w = getWidth(), h = getHeight();
g.setFont(getFont());
FontMetrics fm = g.getFontMetrics();
int iy = fm.getAscent();
int lh = fm.getAscent() + fm.getDescent();
int asc = fm.getAscent();
int w = getWidth(), h = getHeight();
if (isOpaque()) g.clearRect(0, 0, w, h);
if (isOpaque()) g.clearRect(0, 0, w, h);
if (selEnd != null)
{
if (selEnd != null)
{
Position ssp = layout.get(selStart);
assert ssp != null;
Position sep = layout.get(selEnd);
@ -318,81 +318,81 @@ implements
sep = ssp;
}
int ls = 1 + ssp.line - startingLine;
int le = 1 + sep.line - startingLine;
int ys = snap2(ls, iy, lh) - asc;
int ye = snap2(le, iy, lh) - asc;
int ls = 1 + ssp.line - startingLine;
int le = 1 + sep.line - startingLine;
int ys = snap2(ls, iy, lh) - asc;
int ye = snap2(le, iy, lh) - asc;
g.setColor(SEL_COLOUR);
g.setColor(SEL_COLOUR);
if (ssp.line == sep.line)
{
g.fillRect(ssp.x, ys, sep.x - ssp.x, lh);
g.fillRect(ssp.x, ys, sep.x - ssp.x, lh);
}
else
{
g.fillRect(ssp.x, ys, w - ssp.x, lh);
for (int l = ls + 1; l < le; ++l)
g.fillRect(ssp.x, ys, w - ssp.x, lh);
for (int l = ls + 1; l < le; ++l)
{
int y = snap2(l, iy, lh) - asc;
int y = snap2(l, iy, lh) - asc;
g.fillRect(0, y, w, lh);
}
g.fillRect(0, ye, sep.x, lh);
g.fillRect(0, ye, sep.x, lh);
}
}
}
((java.awt.Graphics2D)g).setRenderingHint(
java.awt.RenderingHints.KEY_ANTIALIASING,
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
);
((java.awt.Graphics2D)g).setRenderingHint(
java.awt.RenderingHints.KEY_ANTIALIASING,
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
);
g.setColor(getForeground());
for (Tree<String> node: layout.keySet())
{
Position position = layout.get(node);
int x = position.x;
int line = 1 + position.line - startingLine;
int y = snap2(line, iy, lh);
if (y > h) continue;
g.setColor(getForeground());
for (Tree<String> node: layout.keySet())
{
Position position = layout.get(node);
int x = position.x;
int line = 1 + position.line - startingLine;
int y = snap2(line, iy, lh);
if (y > h) continue;
if (node.key.equals("text"))
{
if (node.key.equals("text"))
{
boolean isLink = node.get("href") != null;
if (isLink) g.setColor(LINK_COLOUR);
g.drawString(node.value, x, y);
if (isLink) g.setColor(PLAIN_COLOUR);
}
else if (node.key.equals("emoji"))
{
g.drawString(node.value, x, y);
if (isLink) g.setColor(PLAIN_COLOUR);
}
else if (node.key.equals("emoji"))
{
Image image = emojis.get(node.value);
Image scaled = emojis.get(node.value + "_scaled");
if (scaled != null)
{
y -= asc;
g.drawImage(scaled, x, y, this);
}
else if (image != null)
{
scaled = image.getScaledInstance(
-1, fm.getAscent() + fm.getDescent(),
Image.SCALE_SMOOTH
);
// I hope #getScaledInstance knows how to
// wait if the image is yet to be loaded.
emojis.put(node.value + "_scaled", scaled);
}
else
{
g.drawString(node.value, x, y);
}
}
else continue;
}
}
Image scaled = emojis.get(node.value + "_scaled");
if (scaled != null)
{
y -= asc;
g.drawImage(scaled, x, y, this);
}
else if (image != null)
{
scaled = image.getScaledInstance(
-1, fm.getAscent() + fm.getDescent(),
Image.SCALE_SMOOTH
);
// I hope #getScaledInstance knows how to
// wait if the image is yet to be loaded.
emojis.put(node.value + "_scaled", scaled);
}
else
{
g.drawString(node.value, x, y);
}
}
else continue;
}
}
public void
mousePressed(MouseEvent eM)
{
if (eM.getButton() != MouseEvent.BUTTON1) return;
if (eM.getButton() != MouseEvent.BUTTON1) return;
selStart = identifyNodeAt(eM.getX(), eM.getY());
selEnd = null;
repaint();
@ -408,71 +408,71 @@ implements
repaint();
}
public void
public void
mouseMoved(MouseEvent eM)
{
Tree<String> h = identifyNodeAt(eM.getX(), eM.getY());
if (h == null || h.get("href") == null)
{
setToolTipText("");
}
else
{
setToolTipText(h.get("href").value);
}
}
{
Tree<String> h = identifyNodeAt(eM.getX(), eM.getY());
if (h == null || h.get("href") == null)
{
setToolTipText("");
}
else
{
setToolTipText(h.get("href").value);
}
}
private Tree<String>
identifyNodeAt(int x, int y)
{
private Tree<String>
identifyNodeAt(int x, int y)
{
FontMetrics fm = getFontMetrics(getFont());
int initial = fm.getAscent();
int advance = fm.getAscent() + fm.getDescent();
int line = isnap2(y, initial, advance);
int line = isnap2(y, initial, advance);
Tree<String> returnee = null;
Position closest = new Position(0, 0);
for (Tree<String> node: layout.keySet())
Position closest = new Position(0, 0);
for (Tree<String> node: layout.keySet())
{
Position position = layout.get(node);
assert position != null;
if (position.line != line) continue;
if (position.line != line) continue;
if (position.x > x) continue;
if (position.x >= closest.x)
{
returnee = node;
closest = position;
returnee = node;
closest = position;
}
}
return returnee;
}
}
private Tree<String>
identifyNodeAfter(int x, int y)
{
private Tree<String>
identifyNodeAfter(int x, int y)
{
FontMetrics fm = getFontMetrics(getFont());
int initial = fm.getAscent();
int advance = fm.getAscent() + fm.getDescent();
int line = isnap2(y, initial, advance);
int line = isnap2(y, initial, advance);
Tree<String> returnee = null;
Position closest = new Position(Integer.MAX_VALUE, 0);
for (Tree<String> node: layout.keySet())
Position closest = new Position(Integer.MAX_VALUE, 0);
for (Tree<String> node: layout.keySet())
{
Position position = layout.get(node);
assert position != null;
if (position.line != line) continue;
if (position.line != line) continue;
if (position.x < x) continue;
if (position.x < closest.x)
{
returnee = node;
closest = position;
returnee = node;
closest = position;
}
}
return returnee;
}
}
public void
keyPressed(KeyEvent eK)
@ -494,14 +494,14 @@ implements
}
private String
getSelectedText()
{
getSelectedText()
{
assert selStart != null && selEnd != null;
Position ssp = layout.get(selStart);
Position sep = layout.get(selEnd);
assert ssp != null && sep != null;
if (ssp.compareTo(sep) > 0)
if (ssp.compareTo(sep) > 0)
{
Position temp = ssp;
ssp = sep;
@ -515,8 +515,8 @@ implements
Position position = layout.get(node);
assert position != null;
boolean after = position.compareTo(ssp) >= 0;
boolean before = position.compareTo(sep) < 0;
boolean after = position.compareTo(ssp) >= 0;
boolean before = position.compareTo(sep) < 0;
if (!(after && before)) continue;
// Just throw them in a pile for now..
@ -549,35 +549,35 @@ implements
boolean s = node.key.equals("space");
assert t || e || s;
b.append(node.value);
/*
* I actually want to copy the link if the node is
* associated with one. However, a link has
* multiple text nodes, so I'd end up copying
* multiple times. The correct action is to
* associate the nodes with the same link object,
* then mark that as copied. Or, associate the
* nodes with their superiors in the HTML, then
* walk up until we find an anchor with a href.
* Then again, have to mark that as copied too.
*
* I can also walk the HTML and copy any that are
* in the selected region, careful to copy an
* anchor's href in stead of the anchor contents.
* I'd need a guarantee that my walking order is
* the same as how they were rendered on the screen.
*/
/*
* I actually want to copy the link if the node is
* associated with one. However, a link has
* multiple text nodes, so I'd end up copying
* multiple times. The correct action is to
* associate the nodes with the same link object,
* then mark that as copied. Or, associate the
* nodes with their superiors in the HTML, then
* walk up until we find an anchor with a href.
* Then again, have to mark that as copied too.
*
* I can also walk the HTML and copy any that are
* in the selected region, careful to copy an
* anchor's href in stead of the anchor contents.
* I'd need a guarantee that my walking order is
* the same as how they were rendered on the screen.
*/
}
return b.toString();
}
}
private int
getHeightInLines()
{
FontMetrics fm = getFontMetrics(getFont());
private int
getHeightInLines()
{
FontMetrics fm = getFontMetrics(getFont());
int initial = fm.getAscent();
int advance = fm.getAscent() + fm.getDescent();
return isnap2(getHeight(), initial, advance) - 1;
}
return isnap2(getHeight(), initial, advance) - 1;
}
public void
keyReleased(KeyEvent eK) { }
@ -597,107 +597,107 @@ implements
public void
mouseExited(MouseEvent eM) { }
public void
componentResized(ComponentEvent eC) { setText(html); }
public void
componentResized(ComponentEvent eC) { setText(html); }
public void
componentMoved(ComponentEvent eC) { }
public void
componentMoved(ComponentEvent eC) { }
public void
componentShown(ComponentEvent eC) { }
public void
componentShown(ComponentEvent eC) { }
public void
componentHidden(ComponentEvent eC) { }
public void
componentHidden(ComponentEvent eC) { }
// - -%- -
private static int
snap2(int blocks, int initial, int advance)
{
return initial + (blocks - 1) * advance;
// If you'd like to go behind the first line 1,
// note that the first negative line is 0.
}
private static int
snap2(int blocks, int initial, int advance)
{
return initial + (blocks - 1) * advance;
// If you'd like to go behind the first line 1,
// note that the first negative line is 0.
}
private static int
isnap2(int units, int initial, int advance)
{
int offset = units - initial;
return 2 + bfloor(offset - 1, advance);
// Not yet sure how this behaves for negative numbers.
}
private static int
isnap2(int units, int initial, int advance)
{
int offset = units - initial;
return 2 + bfloor(offset - 1, advance);
// Not yet sure how this behaves for negative numbers.
}
private static int
bfloor(int units, int block)
{
if (units < 0) return (units / block) - 1;
else return units / block;
}
private static int
bfloor(int units, int block)
{
if (units < 0) return (units / block) - 1;
else return units / block;
}
// ---%-@-%---
// ---%-@-%---
private static class
Position {
private static class
Position {
int
x, line;
int
x, line;
// -=%=-
// -=%=-
public int
compareTo(Position other)
{
if (line < other.line) return -1;
if (line > other.line) return 1;
if (x < other.x) return -1;
if (x > other.x) return 1;
return 0;
}
public int
compareTo(Position other)
{
if (line < other.line) return -1;
if (line > other.line) return 1;
if (x < other.x) return -1;
if (x > other.x) return 1;
return 0;
}
public String
toString()
{
public String
toString()
{
return "(" + x + "," + line + ")";
}
}
// -=%=-
// -=%=-
public
Position(int x, int line)
{
this.x = x;
this.line = line;
}
public
Position(int x, int line)
{
this.x = x;
this.line = line;
}
public Position
clone()
{
return new Position(x, line);
}
public Position
clone()
{
return new Position(x, line);
}
}
}
// ---%-@-%---
// ---%-@-%---
RichTextPane3()
{
layout = new HashMap<>();
layoutEnd = new Tree<>("text", "");
emojis = new HashMap<>();
RichTextPane3()
{
layout = new HashMap<>();
layoutEnd = new Tree<>("text", "");
emojis = new HashMap<>();
Tree<String> blank = new Tree<>();
blank.key = "tag";
blank.add(new Tree<>("html", null));
blank.add(new Tree<>("children", null));
setText(blank);
Tree<String> blank = new Tree<>();
blank.key = "tag";
blank.add(new Tree<>("html", null));
blank.add(new Tree<>("children", null));
setText(blank);
this.addComponentListener(this);
this.addMouseListener(this);
this.addMouseMotionListener(this);
this.addKeyListener(this);
setFocusable(true);
// A keyboard user can still copy by tabbing in
// and selecting all.
}
this.addComponentListener(this);
this.addMouseListener(this);
this.addMouseMotionListener(this);
this.addKeyListener(this);
setFocusable(true);
// A keyboard user can still copy by tabbing in
// and selecting all.
}
}

View File

@ -31,18 +31,18 @@ RudimentaryHTMLParser {
public static Tree<String>
depthlessRead(String html)
{
try {
return pass3(pass2(pass1(html)));
}
catch (IOException eIo) {
assert false;
/*
* We use only StringReaders, which only throw an
* IOException when they are read after being closed.
* And we don't close them.
*/
return null;
}
try {
return pass3(pass2(pass1(html)));
}
catch (IOException eIo) {
assert false;
/*
* We use only StringReaders, which only throw an
* IOException when they are read after being closed.
* And we don't close them.
*/
return null;
}
}
// - -%- -
@ -51,10 +51,10 @@ RudimentaryHTMLParser {
pass1(String html)
throws IOException
{
Reader r = new StringReader(html);
Reader r = new StringReader(html);
Tree<String> docu = new Tree<String>();
StringBuilder text = new StringBuilder();
StringBuilder emoji = new StringBuilder();
StringBuilder emoji = new StringBuilder();
StringBuilder htmlEscape = new StringBuilder();
boolean quoted = false, inEmoji = false;
int c; while ((c = r.read()) != -1)
@ -110,7 +110,7 @@ RudimentaryHTMLParser {
continue;
}
}
text.append((char)c);
text.append((char)c);
continue;
}
if (text.length() > 0)
@ -181,9 +181,9 @@ RudimentaryHTMLParser {
return docu;
}
private static Tree<String>
pass3(Tree<String> docu)
{
private static Tree<String>
pass3(Tree<String> docu)
{
Tree<String> returnee = new Tree<String>();
for (Tree<String> node: docu)
@ -194,39 +194,39 @@ RudimentaryHTMLParser {
continue;
}
StringBuilder value = new StringBuilder();
for (String segment: whitespaceSplit(node.value))
{
boolean st = segment.startsWith(":");
StringBuilder value = new StringBuilder();
for (String segment: whitespaceSplit(node.value))
{
boolean st = segment.startsWith(":");
boolean ed = segment.endsWith(":");
if (st && ed)
{
Tree<String> text = new Tree<String>();
text.key = "text";
text.value = empty(value);
returnee.add(text);
text.key = "text";
text.value = empty(value);
returnee.add(text);
Tree<String> emoji = new Tree<String>();
emoji.key = "emoji";
emoji.value = segment;
emoji.value = segment;
returnee.add(emoji);
}
else
{
value.append(segment);
}
}
if (value.length() > 0)
{
Tree<String> text = new Tree<String>();
text.key = "text";
text.value = empty(value);
returnee.add(text);
}
}
return returnee;
}
else
{
value.append(segment);
}
}
if (value.length() > 0)
{
Tree<String> text = new Tree<String>();
text.key = "text";
text.value = empty(value);
returnee.add(text);
}
}
return returnee;
}
private static String
empty(StringBuilder b)
@ -236,25 +236,25 @@ RudimentaryHTMLParser {
return s;
}
private static List<String>
whitespaceSplit(String text)
{
List<String> returnee = new ArrayList<>();
StringBuilder segment = new StringBuilder();
boolean isWhitespace = false;
for (char c: text.toCharArray())
{
boolean diff = isWhitespace ^ Character.isWhitespace(c);
if (diff) {
returnee.add(empty(segment));
isWhitespace = !isWhitespace;
}
segment.append(c);
}
returnee.add(empty(segment));
private static List<String>
whitespaceSplit(String text)
{
List<String> returnee = new ArrayList<>();
StringBuilder segment = new StringBuilder();
boolean isWhitespace = false;
for (char c: text.toCharArray())
{
boolean diff = isWhitespace ^ Character.isWhitespace(c);
if (diff) {
returnee.add(empty(segment));
isWhitespace = !isWhitespace;
}
segment.append(c);
}
returnee.add(empty(segment));
return returnee;
}
return returnee;
}
// ---%-@-%---

View File

@ -90,9 +90,9 @@ implements ActionListener {
openMessages,
openLocal,
openFederated,
openNotifications,
openOwnProfile,
openProfile,
openNotifications,
openOwnProfile,
openProfile,
createPost,
openAutoPostView,
quit;
@ -117,101 +117,101 @@ implements ActionListener {
this.page = page;
List<PostPreviewComponent> previews;
previews = display.getPostPreviews();
previews = display.getPostPreviews();
int available = page.posts.length;
int max = previews.size();
assert available <= max;
int available = page.posts.length;
int max = previews.size();
assert available <= max;
for (int o = 0; o < available; ++o)
{
PostPreviewComponent preview = previews.get(o);
Post post = page.posts[o];
for (int o = 0; o < available; ++o)
{
PostPreviewComponent preview = previews.get(o);
Post post = page.posts[o];
preview.setTopLeft(post.author.name);
if (post.boostedPost != null)
{
if (post.boostedPost != null)
{
String s = "boosted by " + post.author.name;
preview.setTopLeft(s);
post = post.boostedPost;
}
preview.setTopLeft(s);
post = post.boostedPost;
}
String flags = "";
if (post.attachments.length > 0) flags += "a";
post.resolveRelativeTime();
preview.setTopRight(flags + " " + post.relativeTime);
String flags = "";
if (post.attachments.length > 0) flags += "a";
post.resolveRelativeTime();
preview.setTopRight(flags + " " + post.relativeTime);
post.resolveApproximateText();
if (post.contentWarning != null)
preview.setBottom("(" + post.contentWarning + ")");
post.resolveApproximateText();
if (post.contentWarning != null)
preview.setBottom("(" + post.contentWarning + ")");
else
preview.setBottom(post.approximateText);
}
}
for (int o = available; o < max; ++o)
{
previews.get(o).reset();
}
boolean full = !(available < PREVIEW_COUNT);
display.setNextPageAvailable(full);
display.setPreviousPageAvailable(true);
display.resetFocus();
boolean full = !(available < PREVIEW_COUNT);
display.setNextPageAvailable(full);
display.setPreviousPageAvailable(true);
display.resetFocus();
}
public void
readEntity(Tree<String> postEntityArray)
{
public void
readEntity(Tree<String> postEntityArray)
{
TimelinePage page = new TimelinePage(postEntityArray);
page.type = this.page.type;
page.accountNumId = this.page.accountNumId;
page.listId = this.page.listId;
use(page);
}
}
public void
refresh()
{
public void
refresh()
{
String firstId = null;
if (!showingLatest)
{
assert page.posts != null;
assert page.posts.length != 0;
firstId = page.posts[0].id;
}
}
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getTimelinePage(
page.type,
PREVIEW_COUNT, firstId, null,
page.accountNumId, page.listId,
PREVIEW_COUNT, firstId, null,
page.accountNumId, page.listId,
new RequestListener() {
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + eIo.getMessage()
);
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + eIo.getMessage()
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + json.get("error").value
+ "\n(HTTP code: " + httpCode + ")"
);
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + json.get("error").value
+ "\n(HTTP code: " + httpCode + ")"
);
}
public void
requestSucceeded(Tree<String> json)
{
if (json.size() < PREVIEW_COUNT)
{
if (json.size() < PREVIEW_COUNT)
{
showLatestPage();
return;
}
@ -221,37 +221,37 @@ implements ActionListener {
}
);
display.setCursor(null);
}
}
public void
public void
showLatestPage()
{
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getTimelinePage(
page.type,
PREVIEW_COUNT, null, null,
page.accountNumId, page.listId,
PREVIEW_COUNT, null, null,
page.accountNumId, page.listId,
new RequestListener() {
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + eIo.getMessage()
);
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + eIo.getMessage()
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + json.get("error").value
+ "\n(HTTP code: " + httpCode + ")"
);
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + json.get("error").value
+ "\n(HTTP code: " + httpCode + ")"
);
}
public void
@ -274,32 +274,32 @@ implements ActionListener {
assert page.posts.length != 0;
String lastId = page.posts[page.posts.length - 1].id;
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getTimelinePage(
page.type,
PREVIEW_COUNT, lastId, null,
page.accountNumId, page.listId,
PREVIEW_COUNT, lastId, null,
page.accountNumId, page.listId,
new RequestListener() {
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + eIo.getMessage()
);
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + eIo.getMessage()
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + json.get("error").value
+ "\n(HTTP code: " + httpCode + ")"
);
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + json.get("error").value
+ "\n(HTTP code: " + httpCode + ")"
);
}
public void
@ -312,7 +312,7 @@ implements ActionListener {
// quietly cancel.
return;
}
readEntity(json);
readEntity(json);
showingLatest = false;
windowUpdater.remove(TimelineWindow.this);
}
@ -332,36 +332,36 @@ implements ActionListener {
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getTimelinePage(
page.type,
PREVIEW_COUNT, null, firstId,
page.accountNumId, page.listId,
PREVIEW_COUNT, null, firstId,
page.accountNumId, page.listId,
new RequestListener() {
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + eIo.getMessage()
);
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + eIo.getMessage()
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + json.get("error").value
+ "\n(HTTP code: " + httpCode + ")"
);
TimelineWindow.this,
"Failed to fetch page.."
+ "\n" + json.get("error").value
+ "\n(HTTP code: " + httpCode + ")"
);
}
public void
requestSucceeded(Tree<String> json)
{
if (json.size() < PREVIEW_COUNT)
{
if (json.size() < PREVIEW_COUNT)
{
showLatestPage();
return;
}
@ -386,12 +386,12 @@ implements ActionListener {
setTitle(toString(type) + " timeline - JKomasto");
String f = type.toString().toLowerCase();
display.setBackgroundImage(ImageApi.local(f));
/*
* () Java's image renderer draws images with transparency
* darker than GIMP does. Overcompensate in lightening.
*/
display.repaint();
display.setBackgroundImage(ImageApi.local(f));
/*
* () Java's image renderer draws images with transparency
* darker than GIMP does. Overcompensate in lightening.
*/
display.repaint();
}
public synchronized TimelineType
@ -410,9 +410,9 @@ implements ActionListener {
display.repaint();
}
public synchronized void
openOwnProfile()
{
public synchronized void
openOwnProfile()
{
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
Tree<String> accountDetails = api.getAccountDetails();
@ -422,118 +422,118 @@ implements ActionListener {
w.setVisible(true);
display.setCursor(null);
}
}
public void
openProfile()
{
String query = JOptionPane.showInputDialog(
this,
"Whose account do you want to see?\n"
+ "Type an account name with the instance,\n"
+ "or a display name if you can't remember.\n",
"Profile search",
JOptionPane.PLAIN_MESSAGE
);
if (query == null) return;
public void
openProfile()
{
String query = JOptionPane.showInputDialog(
this,
"Whose account do you want to see?\n"
+ "Type an account name with the instance,\n"
+ "or a display name if you can't remember.\n",
"Profile search",
JOptionPane.PLAIN_MESSAGE
);
if (query == null) return;
class Handler implements RequestListener {
class Handler implements RequestListener {
public Tree<String>
json;
public Tree<String>
json;
// -=%=-
// -=%=-
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Tried to fetch accounts, but it failed.."
+ "\n" + eIo.getMessage()
);
}
public void
connectionFailed(IOException eIo)
{
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Tried to fetch accounts, but it failed.."
+ "\n" + eIo.getMessage()
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Tried to fetch accounts, but it failed.."
+ "\n" + json.get("error").value
+ "\n(HTTP code: " + httpCode + ")"
);
}
public void
requestFailed(int httpCode, Tree<String> json)
{
JOptionPane.showMessageDialog(
TimelineWindow.this,
"Tried to fetch accounts, but it failed.."
+ "\n" + json.get("error").value
+ "\n(HTTP code: " + httpCode + ")"
);
}
public void
requestSucceeded(Tree<String> json) { this.json = json; }
public void
requestSucceeded(Tree<String> json) { this.json = json; }
}
// () Have to create a named class because
// it has to hold the variable.
Handler handler = new Handler();
api.getAccounts(query, handler);
if (handler.json == null) return;
}
// () Have to create a named class because
// it has to hold the variable.
Handler handler = new Handler();
api.getAccounts(query, handler);
if (handler.json == null) return;
if (handler.json.size() == 0)
{
JOptionPane.showMessageDialog(
this,
"There were no results from the query.. ☹️"
);
return;
}
if (handler.json.size() == 0)
{
JOptionPane.showMessageDialog(
this,
"There were no results from the query.. ☹️"
);
return;
}
Tree<String> openee = null;
if (query.startsWith("@")) query = query.substring(1);
Tree<String> openee = null;
if (query.startsWith("@")) query = query.substring(1);
List<Object> message = new ArrayList<>();
message.add("Maybe one of these?");
ButtonGroup selGroup = new ButtonGroup();
for (Tree<String> account: handler.json)
{
String dname = account.get("display_name").value;
String acct = account.get("acct").value;
if (query.equals(acct)) {
List<Object> message = new ArrayList<>();
message.add("Maybe one of these?");
ButtonGroup selGroup = new ButtonGroup();
for (Tree<String> account: handler.json)
{
String dname = account.get("display_name").value;
String acct = account.get("acct").value;
if (query.equals(acct)) {
openee = account;
break;
}
JRadioButton b = new JRadioButton();
b.setText(dname + " (" + acct + ")");
selGroup.add(b);
message.add(b);
}
if (openee == null)
{
int response = JOptionPane.showConfirmDialog(
this,
message.toArray(),
"Search results",
JOptionPane.OK_CANCEL_OPTION
);
if (response == JOptionPane.CANCEL_OPTION) return;
for (int o = 1; o < message.size(); ++o)
{
JRadioButton b = (JRadioButton)message.get(o);
if (selGroup.isSelected(b.getModel()))
{
break;
}
JRadioButton b = new JRadioButton();
b.setText(dname + " (" + acct + ")");
selGroup.add(b);
message.add(b);
}
if (openee == null)
{
int response = JOptionPane.showConfirmDialog(
this,
message.toArray(),
"Search results",
JOptionPane.OK_CANCEL_OPTION
);
if (response == JOptionPane.CANCEL_OPTION) return;
for (int o = 1; o < message.size(); ++o)
{
JRadioButton b = (JRadioButton)message.get(o);
if (selGroup.isSelected(b.getModel()))
{
openee = handler.json.get(o - 1);
break;
}
}
if (openee == null) return;
/*
* It seems like this can happen if someone
* presses escape out of the confirm dialog.
* I don't know why that doesn't map to cancel.
*/
}
break;
}
}
if (openee == null) return;
/*
* It seems like this can happen if someone
* presses escape out of the confirm dialog.
* I don't know why that doesn't map to cancel.
*/
}
ProfileWindow w = new ProfileWindow(primaire);
w.use(new Account(openee));
w.setLocationByPlatform(true);
w.setVisible(true);
}
}
// - -%- -
@ -577,13 +577,13 @@ implements ActionListener {
setTimelineType(TimelineType.LOCAL);
showLatestPage();
}
if (src == openOwnProfile)
if (src == openOwnProfile)
{
openOwnProfile();
openOwnProfile();
}
if (src == openProfile)
if (src == openProfile)
{
openProfile();
openProfile();
}
if (src == createPost)
{
@ -595,15 +595,15 @@ implements ActionListener {
w.setLocation(getX() + 10 + getWidth(), getY());
w.setVisible(true);
}
if (src == openNotifications)
if (src == openNotifications)
{
NotificationsWindow w =
NotificationsWindow w =
primaire.getNotificationsWindow();
if (!w.isVisible())
{
w.setLocationByPlatform(true);
w.setVisible(true);
}
if (!w.isVisible())
{
w.setLocationByPlatform(true);
w.setVisible(true);
}
}
if (src == flipToNewestPost)
{
@ -650,17 +650,17 @@ implements ActionListener {
openHome = new JMenuItem("Open home timeline");
openFederated = new JMenuItem("Open federated timeline");
openNotifications = new JMenuItem("Open notifications");
openOwnProfile = new JMenuItem("Open own profile");
openProfile = new JMenuItem("Open profile..");
openNotifications = new JMenuItem("Open notifications");
openOwnProfile = new JMenuItem("Open own profile");
openProfile = new JMenuItem("Open profile..");
createPost = new JMenuItem("Create a post");
openAutoPostView = new JMenuItem("Open auto post view");
quit = new JMenuItem("Quit");
openHome.addActionListener(this);
openFederated.addActionListener(this);
openNotifications.addActionListener(this);
openOwnProfile.addActionListener(this);
openProfile.addActionListener(this);
openNotifications.addActionListener(this);
openOwnProfile.addActionListener(this);
openProfile.addActionListener(this);
createPost.addActionListener(this);
openAutoPostView.addActionListener(this);
quit.addActionListener(this);
@ -672,9 +672,9 @@ implements ActionListener {
programMenu.setMnemonic(KeyEvent.VK_P);
programMenu.add(openHome);
programMenu.add(openFederated);
programMenu.add(openNotifications);
programMenu.add(openOwnProfile);
programMenu.add(openProfile);
programMenu.add(openNotifications);
programMenu.add(openOwnProfile);
programMenu.add(openProfile);
programMenu.add(new JSeparator());
programMenu.add(createPost);
programMenu.add(openAutoPostView);
@ -696,8 +696,8 @@ implements ActionListener {
display.setPreviousPageAvailable(false);
setContentPane(display);
setTimelineType(TimelineType.HOME);
setIconImage(primaire.getProgramIcon());
setTimelineType(TimelineType.HOME);
setIconImage(primaire.getProgramIcon());
}
}
@ -727,8 +727,8 @@ implements
private boolean
hoverSelect;
private Image
backgroundImage;
private Image
backgroundImage;
// - -%- -
@ -759,46 +759,46 @@ implements
public void
setHoverSelect(boolean n) { hoverSelect = n; }
public void
setBackgroundImage(Image n) { backgroundImage = n; }
public void
setBackgroundImage(Image n) { backgroundImage = n; }
public void
resetFocus() { postPreviews.get(0).requestFocusInWindow(); }
public void
resetFocus() { postPreviews.get(0).requestFocusInWindow(); }
// - -%- -
protected void
paintComponent(Graphics g)
{
protected void
paintComponent(Graphics g)
{
int w = getWidth(), h = getHeight();
g.clearRect(0, 0, w, h);
g.clearRect(0, 0, w, h);
if (backgroundImage != null)
{
int b = h * 5 / 10;
int iw = backgroundImage.getWidth(this);
int ih = backgroundImage.getHeight(this);
if (ih > iw) {
ih = ih * b / iw;
iw = b;
}
else {
iw = iw * b / ih;
ih = b;
}
int x = w - iw, y = h - ih;
g.drawImage(backgroundImage, x, y, iw, ih, this);
}
if (backgroundImage != null)
{
int b = h * 5 / 10;
int iw = backgroundImage.getWidth(this);
int ih = backgroundImage.getHeight(this);
if (ih > iw) {
ih = ih * b / iw;
iw = b;
}
else {
iw = iw * b / ih;
ih = b;
}
int x = w - iw, y = h - ih;
g.drawImage(backgroundImage, x, y, iw, ih, this);
}
((java.awt.Graphics2D)g).setRenderingHint(
java.awt.RenderingHints.KEY_ANTIALIASING,
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
);
}
((java.awt.Graphics2D)g).setRenderingHint(
java.awt.RenderingHints.KEY_ANTIALIASING,
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
);
}
private void
select(Object c)
{
private void
select(Object c)
{
assert c instanceof PostPreviewComponent;
for (PostPreviewComponent p: postPreviews)
@ -814,25 +814,25 @@ implements
p.repaint();
}
}
}
}
private void
deselect(Object c)
{
private void
deselect(Object c)
{
assert c instanceof PostPreviewComponent;
PostPreviewComponent p = (PostPreviewComponent)c;
p.setSelected(false);
p.repaint();
}
}
private void
open(Object c)
{
private void
open(Object c)
{
int o = postPreviews.indexOf(c);
assert o != -1;
primaire.previewOpened(1 + o);
}
}
public void
focusGained(FocusEvent eF) { select(eF.getSource()); }
@ -840,14 +840,14 @@ implements
public void
focusLost(FocusEvent eF) { deselect(eF.getSource()); }
public void
public void
mouseClicked(MouseEvent eM)
{
if (eM.getClickCount() == 2) open(eM.getSource());
else select(eM.getSource());
}
public void
public void
mouseEntered(MouseEvent eM)
{
if (!hoverSelect) return;
@ -968,8 +968,8 @@ PostPreviewComponent extends JComponent {
private JLabel
topLeft, topRight, bottom;
private boolean
selected;
private boolean
selected;
// ---%-@-%---
@ -982,7 +982,7 @@ PostPreviewComponent extends JComponent {
public void
setBottom(String text) { bottom.setText(text); }
public void
public void
reset()
{
setTopLeft(" ");
@ -1001,11 +1001,11 @@ PostPreviewComponent extends JComponent {
protected void
paintComponent(Graphics g)
{
if (selected)
{
g.setColor(new Color(0, 0, 0, 25));
g.fillRect(0, 0, getWidth(), getHeight());
}
if (selected)
{
g.setColor(new Color(0, 0, 0, 25));
g.fillRect(0, 0, getWidth(), getHeight());
}
}
// ---%-@-%---
@ -1032,20 +1032,20 @@ PostPreviewComponent extends JComponent {
top.add(Box.createGlue());
top.add(topRight);
bottom = new JLabel();
bottom = new JLabel();
bottom.setFont(f3);
bottom.setOpaque(false);
JPanel left = new JPanel();
left.setOpaque(false);
left.setLayout(new BorderLayout());
JPanel left = new JPanel();
left.setOpaque(false);
left.setLayout(new BorderLayout());
left.add(top, BorderLayout.NORTH);
left.add(bottom);
setFocusable(true);
setOpaque(false);
setLayout(new BorderLayout());
add(left);
setLayout(new BorderLayout());
add(left);
}
}

View File

@ -56,10 +56,10 @@ implements KeyListener, MouseListener, FocusListener {
primaryToggled = false,
secondaryToggled = false;
private Image
private Image
primaryToggledIcon,
secondaryToggledIcon,
primaryUntoggledIcon,
primaryUntoggledIcon,
secondaryUntoggledIcon;
private int
@ -116,7 +116,7 @@ implements KeyListener, MouseListener, FocusListener {
// - -%- -
private void
private void
announce(String name, boolean toggled)
{
ActionEvent eA = new ActionEvent(
@ -134,17 +134,17 @@ implements KeyListener, MouseListener, FocusListener {
g.drawImage(button, 0, 0, this);
if (!isEnabled())
g.drawImage(disabledOverlay, 0, 0, this);
if (isFocusOwner())
if (isFocusOwner())
g.drawImage(selectedOverlay, 0, 0, this);
if (secondaryToggled)
g.drawImage(secondaryToggledIcon, 0, 0, this);
else
g.drawImage(secondaryUntoggledIcon, 0, 0, this);
if (primaryToggled)
g.drawImage(primaryToggledIcon, 0, 0, this);
else
g.drawImage(primaryUntoggledIcon, 0, 0, this);
if (secondaryToggled)
g.drawImage(secondaryToggledIcon, 0, 0, this);
else
g.drawImage(secondaryUntoggledIcon, 0, 0, this);
if (primaryToggled)
g.drawImage(primaryToggledIcon, 0, 0, this);
else
g.drawImage(primaryUntoggledIcon, 0, 0, this);
}
@ -176,11 +176,11 @@ implements KeyListener, MouseListener, FocusListener {
requestFocusInWindow();
}
public void
focusGained(FocusEvent eF) { repaint(); }
public void
focusGained(FocusEvent eF) { repaint(); }
public void
focusLost(FocusEvent eF) { repaint(); }
public void
focusLost(FocusEvent eF) { repaint(); }
public void
@ -214,8 +214,8 @@ implements KeyListener, MouseListener, FocusListener {
this.secondaryName = secondaryName;
setModel(new DefaultButtonModel());
setFocusable(true);
setOpaque(false);
setFocusable(true);
setOpaque(false);
int w = button.getWidth(null);
int h = button.getHeight(null);
@ -224,7 +224,7 @@ implements KeyListener, MouseListener, FocusListener {
this.addKeyListener(this);
this.addMouseListener(this);
this.addFocusListener(this);
this.addFocusListener(this);
}
private void
@ -232,17 +232,17 @@ implements KeyListener, MouseListener, FocusListener {
{
String p1 = "graphics/" + primaryName + "Toggled.png";
String p2 = "graphics/" + secondaryName + "Toggled.png";
String p3 = "graphics/" + primaryName + "Untoggled.png";
String p3 = "graphics/" + primaryName + "Untoggled.png";
String p4 = "graphics/" + secondaryName + "Untoggled.png";
URL u1 = getClass().getResource(p1);
URL u2 = getClass().getResource(p2);
URL u3 = getClass().getResource(p3);
URL u4 = getClass().getResource(p4);
URL u3 = getClass().getResource(p3);
URL u4 = getClass().getResource(p4);
if (u1 == null) primaryToggledIcon = null;
else primaryToggledIcon = new ImageIcon(u1).getImage();
if (u2 == null) secondaryToggledIcon = null;
else secondaryToggledIcon = new ImageIcon(u2).getImage();
if (u3 == null) primaryUntoggledIcon = null;
if (u3 == null) primaryUntoggledIcon = null;
else primaryUntoggledIcon = new ImageIcon(u3).getImage();
if (u4 == null) secondaryUntoggledIcon = null;
else secondaryUntoggledIcon = new ImageIcon(u4).getImage();
@ -270,16 +270,16 @@ class
RoundButton extends AbstractButton
implements KeyListener, MouseListener, FocusListener {
private Image
image;
private Image
image;
// - -%- -
// - -%- -
private Image
copy,
scaled;
private Image
copy,
scaled;
private int
private int
nextEventID = ActionEvent.ACTION_FIRST;
// - -%- -
@ -289,99 +289,99 @@ implements KeyListener, MouseListener, FocusListener {
selectedOverlay,
disabledOverlay;
private static Shape
roundClip;
private static Shape
roundClip;
// ---%-@-%---
public void
setImage(Image n)
{
image = n;
copy = null;
scaled = null;
public void
setImage(Image n)
{
image = n;
copy = null;
scaled = null;
if (image != null)
{
image.flush();
prepareImage(image, this);
}
}
if (image != null)
{
image.flush();
prepareImage(image, this);
}
}
// - -%- -
// - -%- -
public boolean
imageUpdate(Image img, int f, int x, int y, int w, int h)
{
// AbstractButton overrode this to refuse updates for
// images that aren't the button's icon. We don't use
// the icon, so we're overriding it back. Also, we have
// some async work to do regarding the images.
public boolean
imageUpdate(Image img, int f, int x, int y, int w, int h)
{
// AbstractButton overrode this to refuse updates for
// images that aren't the button's icon. We don't use
// the icon, so we're overriding it back. Also, we have
// some async work to do regarding the images.
if ((f & (ABORT|ERROR)) != 0) return false;
if ((f & (ABORT|ERROR)) != 0) return false;
boolean all = (f & ALLBITS) != 0;
boolean frame = (f & FRAMEBITS) != 0;
boolean some = (f & SOMEBITS) != 0;
boolean all = (f & ALLBITS) != 0;
boolean frame = (f & FRAMEBITS) != 0;
boolean some = (f & SOMEBITS) != 0;
if (frame && img != this.image) return false;
if (frame && img != this.image) return false;
if (img == this.image && (all || frame))
{
int ow = img.getWidth(null);
int oh = img.getHeight(null);
if (img == this.image && (all || frame))
{
int ow = img.getWidth(null);
int oh = img.getHeight(null);
if (copy == null)
{
int type = BufferedImage.TYPE_INT_ARGB;
copy = new BufferedImage(ow, oh, type);
}
if (copy == null)
{
int type = BufferedImage.TYPE_INT_ARGB;
copy = new BufferedImage(ow, oh, type);
}
Graphics g = copy.getGraphics();
g.drawImage(img, 0, 0, null);
g.dispose();
Graphics g = copy.getGraphics();
g.drawImage(img, 0, 0, null);
g.dispose();
int algo = Image.SCALE_SMOOTH;
Rectangle b = roundClip.getBounds();
int sw = ow > oh ? -1 : b.width;
int sh = oh > ow ? -1 : b.height;
scaled = copy.getScaledInstance(sw, sh, algo);
/*
* We create a scaled instance from a BufferedImage copy
* rather than this.image directly, to avoid a ClassCast
* Exception bug in the JDK, where ColorModel was
* incorrectly casting an int array of input data into
* a byte array. I'm not sure why that bug exists nor
* why they haven't noticed it, but.
*/
repaint();
}
if (img == scaled && (some || all))
{
repaint();
}
return all ? false : true;
}
int algo = Image.SCALE_SMOOTH;
Rectangle b = roundClip.getBounds();
int sw = ow > oh ? -1 : b.width;
int sh = oh > ow ? -1 : b.height;
scaled = copy.getScaledInstance(sw, sh, algo);
/*
* We create a scaled instance from a BufferedImage copy
* rather than this.image directly, to avoid a ClassCast
* Exception bug in the JDK, where ColorModel was
* incorrectly casting an int array of input data into
* a byte array. I'm not sure why that bug exists nor
* why they haven't noticed it, but.
*/
repaint();
}
if (img == scaled && (some || all))
{
repaint();
}
return all ? false : true;
}
protected void
paintComponent(Graphics g)
{
g.drawImage(button, 0, 0, this);
g.drawImage(button, 0, 0, this);
if (!isEnabled())
g.drawImage(disabledOverlay, 0, 0, this);
if (isFocusOwner())
if (isFocusOwner())
g.drawImage(selectedOverlay, 0, 0, this);
if (scaled == null) return;
if (scaled == null) return;
Rectangle b = roundClip.getBounds();
Shape defaultClip = g.getClip();
g.setClip(roundClip);
g.drawImage(scaled, b.x, b.y, this);
getParent().repaint();
// I don't know why, but when we repaint ourselves, our
// parent doesn't repaint, so nothing seems to happen.
g.setClip(defaultClip);
Rectangle b = roundClip.getBounds();
Shape defaultClip = g.getClip();
g.setClip(roundClip);
g.drawImage(scaled, b.x, b.y, this);
getParent().repaint();
// I don't know why, but when we repaint ourselves, our
// parent doesn't repaint, so nothing seems to happen.
g.setClip(defaultClip);
}
private void
@ -396,31 +396,31 @@ implements KeyListener, MouseListener, FocusListener {
public void
keyPressed(KeyEvent eK)
{
if (eK.getKeyCode() != KeyEvent.VK_SPACE) return;
doClick();
if (eK.getKeyCode() != KeyEvent.VK_SPACE) return;
doClick();
}
public void
mouseClicked(MouseEvent eM) { announce(); }
public void
mouseClicked(MouseEvent eM) { announce(); }
public void
focusGained(FocusEvent eF) { repaint(); }
public void
focusGained(FocusEvent eF) { repaint(); }
public void
focusLost(FocusEvent eF) { repaint(); }
public void
focusLost(FocusEvent eF) { repaint(); }
public void
mousePressed(MouseEvent eM) { }
public void
mousePressed(MouseEvent eM) { }
public void
mouseReleased(MouseEvent eM) { }
public void
mouseReleased(MouseEvent eM) { }
public void
mouseEntered(MouseEvent eM) { }
public void
mouseEntered(MouseEvent eM) { }
public void
mouseExited(MouseEvent eM) { }
public void
mouseExited(MouseEvent eM) { }
public void
keyReleased(KeyEvent eK) { }
@ -437,16 +437,16 @@ implements KeyListener, MouseListener, FocusListener {
if (button == null) loadCommonImages();
setModel(new DefaultButtonModel());
setFocusable(true);
setOpaque(false);
setFocusable(true);
setOpaque(false);
int w = button.getWidth(null);
int h = button.getHeight(null);
setPreferredSize(new Dimension(w, h));
this.addKeyListener(this);
this.addMouseListener(this);
this.addFocusListener(this);
this.addMouseListener(this);
this.addFocusListener(this);
}
// - -%- -
@ -463,13 +463,13 @@ implements KeyListener, MouseListener, FocusListener {
disabledOverlay = new ImageIcon(u2).getImage();
selectedOverlay = new ImageIcon(u3).getImage();
int radius = 6;
roundClip = new Ellipse2D.Float(
radius,
radius,
button.getWidth(null) - (2 * radius),
button.getHeight(null) - (2 * radius)
);
int radius = 6;
roundClip = new Ellipse2D.Float(
radius,
radius,
button.getWidth(null) - (2 * radius),
button.getHeight(null) - (2 * radius)
);
}
}

View File

@ -43,11 +43,11 @@ WindowUpdater {
private List<TimelineWindow>
timelineWindows;
private List<NotificationsWindow>
notificationWindows;
private List<NotificationsWindow>
notificationWindows;
private Clip
notificationSound;
private Clip
notificationSound;
private Connection
publicConn,
@ -65,14 +65,14 @@ WindowUpdater {
userConn.reevaluate();
}
public synchronized void
add(NotificationsWindow updatee)
{
public synchronized void
add(NotificationsWindow updatee)
{
if (!notificationWindows.contains(updatee))
notificationWindows.add(updatee);
userConn.reevaluate();
}
userConn.reevaluate();
}
public synchronized void
remove(TimelineWindow updatee)
@ -82,7 +82,7 @@ WindowUpdater {
userConn.reevaluate();
}
public synchronized void
public synchronized void
remove(NotificationsWindow updatee)
{
notificationWindows.remove(updatee);
@ -144,21 +144,21 @@ WindowUpdater {
stop()
{
stopping = true;
thread.interrupt();
try
{
thread.interrupt();
try
{
thread.join(3000);
/*
* That thread should notice it is
* interrupted ppromptly, and close.
*/
if (thread.isAlive()) printStackTrace(thread);
}
catch (InterruptedException eIt)
{
}
catch (InterruptedException eIt)
{
assert false;
}
thread = null;
}
thread = null;
}
public void
@ -186,21 +186,21 @@ WindowUpdater {
thread.notifyAll();
}
event = new StringBuilder();
data = new StringBuilder();
api.monitorTimeline(type, this);
// monitorTimeline should not return until
// the connection is closed, or this thread
// is interrupted.
data = new StringBuilder();
api.monitorTimeline(type, this);
// monitorTimeline should not return until
// the connection is closed, or this thread
// is interrupted.
System.err.println(
System.err.println(
"Stopped monitoring."
+ thread + " " + Thread.currentThread()
);
if (thread == Thread.currentThread()) thread = null;
/*
* This isn't thread safe. But I'd like the
* restart after sleep mode, so.
*/
if (thread == Thread.currentThread()) thread = null;
/*
* This isn't thread safe. But I'd like the
* restart after sleep mode, so.
*/
}
public void
@ -209,7 +209,7 @@ WindowUpdater {
if (line.startsWith(":")) return;
if (line.isEmpty())
{
{
handle(event.toString(), data.toString());
event.delete(0, event.length());
data.delete(0, event.length());
@ -299,38 +299,38 @@ WindowUpdater {
this.api = primaire.getMastodonApi();
this.timelineWindows = new ArrayList<>();
this.notificationWindows = new ArrayList<>();
this.notificationWindows = new ArrayList<>();
publicConn = new Connection();
publicConn.type = TimelineType.FEDERATED;
publicConn = new Connection();
publicConn.type = TimelineType.FEDERATED;
userConn = new Connection();
userConn.type = TimelineType.HOME;
userConn = new Connection();
userConn.type = TimelineType.HOME;
loadNotificationSound();
loadNotificationSound();
}
void
loadNotificationSound()
{
URL url = getClass().getResource("KDE_Dialog_Appear.wav");
try {
Clip clip = AudioSystem.getClip();
clip.open(AudioSystem.getAudioInputStream(url));
notificationSound = clip;
}
catch (LineUnavailableException eLu) {
assert false;
}
catch (UnsupportedAudioFileException eUa) {
assert false;
}
catch (IOException eIo) {
assert false;
}
catch (IllegalArgumentException eIa) {
assert false;
}
}
void
loadNotificationSound()
{
URL url = getClass().getResource("KDE_Dialog_Appear.wav");
try {
Clip clip = AudioSystem.getClip();
clip.open(AudioSystem.getAudioInputStream(url));
notificationSound = clip;
}
catch (LineUnavailableException eLu) {
assert false;
}
catch (UnsupportedAudioFileException eUa) {
assert false;
}
catch (IOException eIo) {
assert false;
}
catch (IllegalArgumentException eIa) {
assert false;
}
}
}