mirror of
https://gitlab.com/biskuteri-cafe/JKomasto2.git
synced 2024-11-20 03:54:50 +01:00
Normalised all tabs to 4 spaces, for easier web viewing.
This commit is contained in:
parent
2719fabef9
commit
fa9a5edab0
@ -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("<")) part.append('<');
|
||||
if (v.equals(">")) part.append('>');
|
||||
if (v.equals("&")) part.append('&');
|
||||
if (v.equals(""")) part.append('"');
|
||||
if (v.equals("'")) part.append('\'');
|
||||
if (v.equals("'")) 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("<")) part.append('<');
|
||||
if (v.equals(">")) part.append('>');
|
||||
if (v.equals("&")) part.append('&');
|
||||
if (v.equals(""")) part.append('"');
|
||||
if (v.equals("'")) part.append('\'');
|
||||
if (v.equals("'")) 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();
|
||||
}
|
||||
}
|
||||
|
@ -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) { }
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
466
ImageWindow.java
466
ImageWindow.java
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
406
JKomasto.java
406
JKomasto.java
@ -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)
|
||||
|
BIN
JKomasto2.jar
BIN
JKomasto2.jar
Binary file not shown.
554
LoginWindow.java
554
LoginWindow.java
@ -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);
|
||||
|
192
MastodonApi.java
192
MastodonApi.java
@ -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);
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
484
PostWindow.java
484
PostWindow.java
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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("");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user