Sort of fixed the uploadFile issue.

This commit is contained in:
Snowyfox 2022-05-20 09:14:32 -04:00
parent dc980c153c
commit fc2880325e
28 changed files with 359 additions and 161 deletions

0
BasicHTMLParser.java Normal file → Executable file
View File

0
ClipboardApi.java Normal file → Executable file
View File

0
ComposeWindow.java Normal file → Executable file
View File

0
ImageApi.java Normal file → Executable file
View File

0
ImageWindow.java Normal file → Executable file
View File

0
JKomasto.java Normal file → Executable file
View File

0
KDE_Dialog_Appear.wav Normal file → Executable file
View File

0
LoginWindow.java Normal file → Executable file
View File

92
MastodonApi.java Normal file → Executable file
View File

@ -435,40 +435,86 @@ MastodonApi {
assert file != null; assert file != null;
assert file.canRead(); assert file.canRead();
String token = accessToken.get("access_token").value; 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
*/
String token = accessToken.get("access_token").value;
String url = instanceUrl + "/api/v1/media/"; String url = instanceUrl + "/api/v1/media/";
try try
{ {
URL endpoint = new URL(url); URL endpoint = new URL(url);
HttpURLConnection conn = cast(endpoint.openConnection()); HttpURLConnection conn = cast(endpoint.openConnection());
String s1 = "Bearer " + token; String s1 = "Bearer " + token;
conn.setRequestProperty("Authorization", s1); conn.setRequestProperty("Authorization", s1);
conn.setDoOutput(true); conn.setDoOutput(true);
conn.setRequestMethod("POST"); conn.setRequestMethod("POST");
String s2 = "multipart/form-data; "; conn.setFixedLengthStreamingMode(contentLength);
String s3 = "boundary=\"MastodonMediaUpload\""; conn.setRequestProperty("Content-Type", bct);
conn.setRequestProperty("Content-Type", s2 + s3); conn.setRequestProperty("Accept", "*/*");
conn.connect(); conn.connect();
OutputStream ostream = conn.getOutputStream(); OutputStream ostream = conn.getOutputStream();
Writer owriter = owriter(ostream);
InputStream istream = new FileInputStream(file); InputStream istream = new FileInputStream(file);
// Let's see if this works!
ostream.write(fsb.getBytes());
ostream.write(fcd.getBytes());
ostream.write(fct.getBytes());
int c; while ((c = istream.read()) != -1)
ostream.write(c);
String s4, s5, s6; ostream.write(feb.getBytes());
s4 = "--MastodonMediaUpload"; istream.close();
s5 = "Content-Disposition: form-data; name=file"; ostream.close();
s6 = "Content-Type: application/octet-stream";
owriter.write(s4 + "\r\n");
owriter.write(s5 + "\r\n");
owriter.write(s6 + "\r\n\r\n");
int c; while ((c = istream.read()) != -1)
ostream.write(c);
owriter.write("\r\n" + s4 + "--\r\n");
istream.close();
ostream.close();
wrapResponseInTree(conn, handler); wrapResponseInTree(conn, handler);
} }
@ -556,7 +602,7 @@ MastodonApi {
int code = conn.getResponseCode(); int code = conn.getResponseCode();
if (code >= 300) if (code >= 300)
{ {
Reader input = ireader(conn.getErrorStream()); Reader input = ireader(conn.getErrorStream());
Tree<String> response = fromPlain(input); Tree<String> response = fromPlain(input);
input.close(); input.close();
handler.requestFailed(code, response); handler.requestFailed(code, response);
@ -636,6 +682,7 @@ MastodonApi {
ireader(InputStream is) ireader(InputStream is)
throws IOException throws IOException
{ {
assert is != null;
return new InputStreamReader(is); return new InputStreamReader(is);
} }
@ -643,6 +690,7 @@ MastodonApi {
owriter(OutputStream os) owriter(OutputStream os)
throws IOException throws IOException
{ {
assert os != null;
return new OutputStreamWriter(os); return new OutputStreamWriter(os);
} }

0
NotificationsWindow.java Normal file → Executable file
View File

35
PostWindow.java Normal file → Executable file
View File

@ -350,7 +350,7 @@ PostWindow extends JFrame {
this.primaire = primaire; this.primaire = primaire;
this.api = primaire.getMastodonApi(); this.api = primaire.getMastodonApi();
getContentPane().setPreferredSize(new Dimension(360, 270)); getContentPane().setPreferredSize(new Dimension(360, 260));
pack(); pack();
setDefaultCloseOperation(DISPOSE_ON_CLOSE); setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setLocationByPlatform(true); setLocationByPlatform(true);
@ -411,6 +411,9 @@ implements ActionListener {
deletePost, deletePost,
redraftPost; redraftPost;
private Image
backgroundImage;
// ---%-@-%--- // ---%-@-%---
public void public void
@ -485,7 +488,7 @@ implements ActionListener {
public void public void
resetFocus() resetFocus()
{ {
bodyScrollPane.getVerticalScrollBar().setValue(0); //bodyScrollPane.getVerticalScrollBar().setValue(0);
media.requestFocusInWindow(); media.requestFocusInWindow();
} }
@ -531,14 +534,16 @@ implements ActionListener {
if (src == nextPrev) if (src == nextPrev)
{ {
if (command.equals("next")) if (command.startsWith("next"))
{ {
body.nextPage();
} }
else else
{ {
body.previousPage();
} }
// First time an interactive element
// doesn't call something in primaire..
return; return;
} }
@ -567,6 +572,18 @@ implements ActionListener {
lay1 = RichTextPane.layout(authorNameOr, fm1, w1); lay1 = RichTextPane.layout(authorNameOr, fm1, w1);
authorName.setText(lay1); 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);
}
}
((java.awt.Graphics2D)g).setRenderingHint( ((java.awt.Graphics2D)g).setRenderingHint(
java.awt.RenderingHints.KEY_ANTIALIASING, java.awt.RenderingHints.KEY_ANTIALIASING,
java.awt.RenderingHints.VALUE_ANTIALIAS_ON java.awt.RenderingHints.VALUE_ANTIALIAS_ON
@ -644,10 +661,12 @@ implements ActionListener {
time.setFont(f1); time.setFont(f1);
JPanel top1 = new JPanel(); JPanel top1 = new JPanel();
top1.setOpaque(false);
top1.setLayout(new BorderLayout(8, 0)); top1.setLayout(new BorderLayout(8, 0));
top1.add(authorId); top1.add(authorId);
top1.add(date, BorderLayout.EAST); top1.add(date, BorderLayout.EAST);
JPanel top2 = new JPanel(); JPanel top2 = new JPanel();
top2.setOpaque(false);
top2.setLayout(new BorderLayout(8, 0)); top2.setLayout(new BorderLayout(8, 0));
top2.add(authorName); top2.add(authorName);
top2.add(time, BorderLayout.EAST); top2.add(time, BorderLayout.EAST);
@ -659,6 +678,7 @@ implements ActionListener {
body = new RichTextPane3(); body = new RichTextPane3();
body.setFont(f3); body.setFont(f3);
/*
bodyScrollPane = new JScrollPane( bodyScrollPane = new JScrollPane(
body, body,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
@ -669,18 +689,21 @@ implements ActionListener {
vsb.setUnitIncrement(16); vsb.setUnitIncrement(16);
bodyScrollPane.setBorder(null); bodyScrollPane.setBorder(null);
bodyScrollPane.setFocusable(true); bodyScrollPane.setFocusable(true);
*/
JPanel centre = new JPanel(); JPanel centre = new JPanel();
centre.setOpaque(false); centre.setOpaque(false);
centre.setLayout(new BorderLayout(0, 8)); centre.setLayout(new BorderLayout(0, 8));
centre.add(top, BorderLayout.NORTH); centre.add(top, BorderLayout.NORTH);
centre.add(bodyScrollPane); centre.add(body);
setLayout(new BorderLayout(8, 0)); setLayout(new BorderLayout(8, 0));
add(left, BorderLayout.WEST); add(left, BorderLayout.WEST);
add(centre); add(centre);
setBorder(b); setBorder(b);
backgroundImage = ImageApi.local("postWindow");
} }
} }

0
ProfileWindow.java Normal file → Executable file
View File

0
RepliesWindow.java Normal file → Executable file
View File

0
RequestListener.java Normal file → Executable file
View File

5
RichTextPane.java Normal file → Executable file
View File

@ -69,7 +69,9 @@ implements MouseListener, MouseMotionListener, KeyListener {
{ {
g.setFont(getFont()); g.setFont(getFont());
FontMetrics fm = g.getFontMetrics(getFont()); FontMetrics fm = g.getFontMetrics(getFont());
g.clearRect(0, 0, getWidth(), getHeight());
if (isOpaque())
g.clearRect(0, 0, getWidth(), getHeight());
((java.awt.Graphics2D)g).setRenderingHint( ((java.awt.Graphics2D)g).setRenderingHint(
java.awt.RenderingHints.KEY_TEXT_ANTIALIASING, java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,
@ -403,6 +405,7 @@ implements MouseListener, MouseMotionListener, KeyListener {
RichTextPane() RichTextPane()
{ {
text = new LinkedList<>(); text = new LinkedList<>();
addMouseListener(this); addMouseListener(this);
addMouseMotionListener(this); addMouseMotionListener(this);
addKeyListener(this); addKeyListener(this);

0
RichTextPane2.java Normal file → Executable file
View File

388
RichTextPane3.java Normal file → Executable file
View File

@ -1,7 +1,6 @@
import javax.swing.JComponent; import javax.swing.JComponent;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Point;
import java.awt.FontMetrics; import java.awt.FontMetrics;
import java.awt.Font; import java.awt.Font;
import java.awt.Image; import java.awt.Image;
@ -33,12 +32,15 @@ implements
private Map<String, Image> private Map<String, Image>
emojis; emojis;
private Map<Tree<String>, Point> private Map<Tree<String>, Position>
layout; layout;
private Tree<String> private Tree<String>
layoutEnd, selStart, selEnd; layoutEnd, selStart, selEnd;
private int
startingLine, endingLine;
// ---%-@-%--- // ---%-@-%---
public void public void
@ -55,31 +57,34 @@ implements
assert html.get("children") != null; assert html.get("children") != null;
FontMetrics fm = getFontMetrics(getFont()); FontMetrics fm = getFontMetrics(getFont());
int iy = fm.getAscent(); Position cursor = new Position(0, 1);
Point cursor = new Point(0, iy);
Tree<String> nodes = html.get("children"); // Manually negate if first element is a break.
if (nodes.size() > 0) Tree<String> children = html.get("children");
if (children.size() > 0)
{ {
Tree<String> first = nodes.get(0); Tree<String> first = children.get(0);
if (first.key.equals("tag")) if (first.key.equals("tag"))
{ {
String tagName = first.get(0).key; String tagName = first.get(0).key;
if (tagName.equals("p")) if (tagName.equals("br")) cursor.line -= 1;
{ if (tagName.equals("p")) cursor.line -= 2;
int lh = fm.getAscent() + fm.getDescent(); }
cursor.y -= lh * 2; }
}
}
}
selStart = selEnd = null; selStart = selEnd = null;
layout.clear(); layout.clear();
startingLine = 1;
layout(html, fm, cursor); layout(html, fm, cursor);
layout.put(layoutEnd, new Point(cursor)); layout.put(layoutEnd, cursor.clone());
endingLine = cursor.line;
repaint(); repaint();
setPreferredSize(new Dimension(1, cursor.y)); 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 public void
@ -90,26 +95,43 @@ implements
setText(html); setText(html);
} }
public void
previousPage()
{
int advance = getHeightInLines();
if (startingLine < advance) startingLine = 1;
else startingLine -= advance;
repaint();
}
public void
nextPage()
{
int advance = getHeightInLines();
if (endingLine - startingLine < advance) return;
else startingLine += advance;
repaint();
}
// - -%- - // - -%- -
private void private void
layout(Tree<String> node, FontMetrics fm, Point cursor) layout(Tree<String> node, FontMetrics fm, Position cursor)
{ {
assert cursor != null; assert cursor != null;
int lh = fm.getAscent() + fm.getDescent();
if (node.key.equals("space")) if (node.key.equals("space"))
{ {
int w = fm.stringWidth(node.value); int w = fm.stringWidth(node.value);
if (cursor.x + w < getWidth()) if (cursor.x + w < getWidth())
{ {
layout.put(node, new Point(cursor)); layout.put(node, cursor.clone());
cursor.x += w; cursor.x += w;
} }
else else
{ {
layout.put(node, new Point(cursor)); layout.put(node, cursor.clone());
cursor.y += lh; ++cursor.line;
cursor.x = 0; cursor.x = 0;
} }
} }
@ -118,14 +140,14 @@ implements
int w = fm.stringWidth(node.value); int w = fm.stringWidth(node.value);
if (cursor.x + w < getWidth()) if (cursor.x + w < getWidth())
{ {
layout.put(node, new Point(cursor)); layout.put(node, cursor.clone());
cursor.x += w; cursor.x += w;
} }
else if (w < getWidth()) else if (w < getWidth())
{ {
cursor.y += lh; ++cursor.line;
cursor.x = 0; cursor.x = 0;
layout.put(node, new Point(cursor)); layout.put(node, cursor.clone());
cursor.x += w; cursor.x += w;
} }
else else
@ -138,7 +160,7 @@ implements
w = fm.charWidth(node.value.charAt(0)); w = fm.charWidth(node.value.charAt(0));
if (w >= aw) if (w >= aw)
{ {
cursor.y += lh; ++cursor.line;
cursor.x = 0; cursor.x = 0;
} }
@ -157,18 +179,12 @@ implements
Tree<String> temp = new Tree<>(); Tree<String> temp = new Tree<>();
temp.key = node.key; temp.key = node.key;
temp.value = substr; temp.value = substr;
layout.put(temp, new Point(cursor)); layout.put(temp, cursor.clone());
rem.delete(0, l); rem.delete(0, l);
if (rem.length() == 0) boolean more = rem.length() != 0;
{ if (more) ++cursor.line;
cursor.x = w; cursor.x = more ? 0 : w;
}
else
{
cursor.y += lh;
cursor.x = 0;
}
aw = mw; aw = mw;
} }
} }
@ -176,28 +192,28 @@ implements
else if (node.key.equals("emoji")) else if (node.key.equals("emoji"))
{ {
Image image = emojis.get(node.value); Image image = emojis.get(node.value);
int w; int w; if (image != null)
if (image != null)
{ {
int ow = image.getWidth(this); int ow = image.getWidth(this);
int oh = image.getHeight(this); int oh = image.getHeight(this);
int h = lh; int h = fm.getAscent() + fm.getDescent();
w = ow * h/oh; w = ow * h/oh;
} }
else else
{ {
w = fm.stringWidth(node.value); w = fm.stringWidth(node.value);
} }
if (cursor.x + w < getWidth()) if (cursor.x + w < getWidth())
{ {
layout.put(node, new Point(cursor)); layout.put(node, cursor.clone());
cursor.x += w; cursor.x += w;
} }
else else
{ {
cursor.y += lh; ++cursor.line;
cursor.x = 0; cursor.x = 0;
layout.put(node, new Point(cursor)); layout.put(node, cursor.clone());
cursor.x += w; cursor.x += w;
} }
} }
@ -210,31 +226,32 @@ implements
if (tagName.equals("br")) if (tagName.equals("br"))
{ {
cursor.y += lh; ++cursor.line;
cursor.x = 0; cursor.x = 0;
} }
else if (tagName.equals("p")) else if (tagName.equals("p"))
{ {
//cursor.y += lh * 3/2; //cursor.line += 3/2;
cursor.y += lh * 2; cursor.line += 2;
// Our selection algorithm assumes equidistant // We don't have vertical cursor movement
// lines. Laziest fix is collect and sort line // other than the line. Maybe fix in the
// Ys from height. // future..?
cursor.x = 0; cursor.x = 0;
} }
for (Tree<String> child: children) for (Tree<String> child: children)
{ {
// Shallow copy this child node. // Shallow copy this child node,
Tree<String> aug = new Tree<>(); Tree<String> aug = new Tree<>();
aug.key = child.key; aug.key = child.key;
aug.value = child.value; aug.value = child.value;
for (Tree<String> gc: child) aug.add(gc); for (Tree<String> gc: child) aug.add(gc);
// Append all of our attributes. We'd like those // Append all of our attributes. We'd like
// like href to end up at the text nodes. This // those like href to end up at the text
// might collide with our child node's attributes, // nodes. This might collide with our
// for now I'll assume that's not an issue. // child node's attributes, for now I'll
// assume that's not an issue.
for (int o = 1; o < node.size(); ++o) for (int o = 1; o < node.size(); ++o)
{ {
Tree<String> attr = node.get(o); Tree<String> attr = node.get(o);
@ -257,80 +274,88 @@ implements
g.setFont(getFont()); g.setFont(getFont());
FontMetrics fm = g.getFontMetrics(); FontMetrics fm = g.getFontMetrics();
int iy = fm.getAscent();
int lh = fm.getAscent() + fm.getDescent();
int asc = fm.getAscent();
int w = getWidth(), h = getHeight();
((java.awt.Graphics2D)g).setRenderingHint( if (isOpaque()) g.clearRect(0, 0, w, h);
java.awt.RenderingHints.KEY_ANTIALIASING,
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
);
if (selEnd != null) if (selEnd != null)
{ {
Point ssp = layout.get(selStart); Position ssp = layout.get(selStart);
assert ssp != null; assert ssp != null;
Point sep = layout.get(selEnd); Position sep = layout.get(selEnd);
assert sep != null; assert sep != null;
/* /*
* () One way these can go null is if we clear * () One way these can go null is if we clear
* the layout but don't clear the selection. * the layout but don't clear the selection.
*/ */
boolean flip = ssp.y > sep.y; if (ssp.compareTo(sep) > 0)
flip |= sep.y == ssp.y && sep.x < ssp.x;
if (flip)
{ {
Point temp = ssp; Position temp = ssp;
ssp = sep; ssp = sep;
sep = ssp; sep = ssp;
} }
int w = getWidth(); int ls = 1 + ssp.line - startingLine;
int asc = fm.getAscent(); int le = 1 + sep.line - startingLine;
int lh = fm.getAscent() + fm.getDescent(); int ys = snap2(ls, iy, lh) - asc;
g.setColor(SEL_COLOUR); int ye = snap2(le, iy, lh) - asc;
if (ssp.y == sep.y)
g.setColor(SEL_COLOUR);
if (ssp.line == sep.line)
{ {
g.fillRect(ssp.x, ssp.y - asc, sep.x - ssp.x, lh); g.fillRect(ssp.x, ys, sep.x - ssp.x, lh);
} }
else else
{ {
g.fillRect(ssp.x, ssp.y - asc, w - ssp.x, lh); g.fillRect(ssp.x, ys, w - ssp.x, lh);
for (int y = ssp.y + lh; y < sep.y; y += lh) for (int l = ls + 1; l < le; ++l)
{ {
g.fillRect(0, y - asc, w, lh); int y = snap2(l, iy, lh) - asc;
g.fillRect(0, y, w, lh);
} }
g.fillRect(0, sep.y - asc, 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
);
g.setColor(getForeground()); g.setColor(getForeground());
for (Tree<String> node: layout.keySet()) for (Tree<String> node: layout.keySet())
{ {
if (node.key.equals("text")) 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"))
{ {
boolean isLink = node.get("href") != null; boolean isLink = node.get("href") != null;
if (isLink) g.setColor(LINK_COLOUR); if (isLink) g.setColor(LINK_COLOUR);
Point point = layout.get(node); g.drawString(node.value, x, y);
g.drawString(node.value, point.x, point.y);
if (isLink) g.setColor(PLAIN_COLOUR); if (isLink) g.setColor(PLAIN_COLOUR);
} }
else if (node.key.equals("emoji")) else if (node.key.equals("emoji"))
{ {
Point point = layout.get(node); Image image = emojis.get(node.value);
Image image = emojis.get(node.value);
if (image != null) if (image != null)
{ {
int ow = image.getWidth(this); int ow = image.getWidth(this);
int oh = image.getHeight(this); int oh = image.getHeight(this);
int nh = fm.getAscent() + fm.getDescent(); int nh = fm.getAscent() + fm.getDescent();
int nw = ow * nh/oh; int nw = ow * nh/oh;
int x = point.x; y -= asc;
int y = point.y - fm.getAscent();
g.drawImage(image, x, y, nw, nh, this); g.drawImage(image, x, y, nw, nh, this);
} }
else else
{ {
int x = point.x;
int y = point.y;
g.drawString(node.value, x, y); g.drawString(node.value, x, y);
} }
} }
@ -350,36 +375,74 @@ implements
mouseDragged(MouseEvent eM) mouseDragged(MouseEvent eM)
{ {
if (selStart == null) return; if (selStart == null) return;
selEnd = identifyNodeAt(eM.getX(), eM.getY()); selEnd = identifyNodeAfter(eM.getX(), eM.getY());
if (selEnd == null) selEnd = layoutEnd; if (selEnd == null) selEnd = layoutEnd;
repaint(); repaint();
} }
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);
}
}
private Tree<String> private Tree<String>
identifyNodeAt(int x, int y) identifyNodeAt(int x, int y)
{ {
FontMetrics fm = getFontMetrics(getFont()); FontMetrics fm = getFontMetrics(getFont());
int initial = fm.getAscent(); int initial = fm.getAscent();
int advance = fm.getAscent() + fm.getDescent(); int advance = fm.getAscent() + fm.getDescent();
y = snap(y, initial, advance); int line = isnap2(y, initial, advance);
Tree<String> returnee = null; Tree<String> returnee = null;
Position closest = new Position(0, 0);
int maxX = 0; for (Tree<String> node: layout.keySet())
for (Tree<String> node: layout.keySet())
{ {
Point point = layout.get(node); Position position = layout.get(node);
assert point != null; assert position != null;
if (point.y != y) continue; if (position.line != line) continue;
if (point.x > x) continue; if (position.x > x) continue;
if (point.x >= maxX) if (position.x >= closest.x)
{ {
maxX = point.x; returnee = node;
returnee = node; closest = position;
} }
} }
return returnee;
}
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);
Tree<String> returnee = null;
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.x < x) continue;
if (position.x < closest.x)
{
returnee = node;
closest = position;
}
}
return returnee; return returnee;
} }
@ -405,52 +468,47 @@ implements
{ {
assert selStart != null && selEnd != null; assert selStart != null && selEnd != null;
Point ssp = layout.get(selStart); Position ssp = layout.get(selStart);
Point sep = layout.get(selEnd); Position sep = layout.get(selEnd);
assert ssp != null && sep != null; assert ssp != null && sep != null;
boolean flip = ssp.y > sep.y; if (ssp.compareTo(sep) > 1)
flip |= sep.y == ssp.y && sep.x < ssp.x;
if (flip)
{ {
Point temp = ssp; Position temp = ssp;
ssp = sep; ssp = sep;
sep = ssp; sep = ssp;
} }
List<Tree<String>> selected = new ArrayList<>(); List<Tree<String>> selected = new ArrayList<>();
List<Point> points = new ArrayList<>(); List<Position> positions = new ArrayList<>();
for (Tree<String> node: layout.keySet()) for (Tree<String> node: layout.keySet())
{ {
Point point = layout.get(node); Position position = layout.get(node);
assert point != null; assert position != null;
boolean c1 = point.y == ssp.y && point.x >= ssp.x; boolean after = position.compareTo(ssp) > 0;
boolean c2 = point.y == sep.y && point.x < sep.x; boolean before = position.compareTo(sep) < 0;
boolean c3 = point.y > ssp.y && point.y < sep.y; if (!(after || before)) continue;
if (!(c1 || c2 || c3)) continue;
// Just throw them in a pile for now.. // Just throw them in a pile for now..
selected.add(node); selected.add(node);
points.add(point); positions.add(position);
} }
// Now sort them into reading order. // Now sort them into reading order.
Tree<String> n1, n2; Tree<String> n1, n2;
Point p1, p2; Position p1, p2;
for (int eo = 1; eo < points.size(); ++eo) for (int eo = 1; eo < positions.size(); ++eo)
for (int o = points.size() - 1; o >= eo; --o) for (int o = positions.size() - 1; o >= eo; --o)
{ {
n1 = selected.get(o - 1); n2 = selected.get(o); n1 = selected.get(o - 1); n2 = selected.get(o);
p1 = points.get(o - 1); p2 = points.get(o); p1 = positions.get(o - 1); p2 = positions.get(o);
boolean c1 = p2.y < p1.y; if (p2.compareTo(p1) > 0) continue;
boolean c2 = p2.y == p1.y && p2.x < p1.x;
if (!(c1 || c2)) continue;
selected.set(o - 1, n2); selected.set(o - 1, n2);
selected.set(o, n1); selected.set(o, n1);
points.set(o - 1, p2); positions.set(o - 1, p2);
points.set(o, p1); positions.set(o, p1);
} }
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
@ -460,11 +518,37 @@ implements
boolean e = node.key.equals("emoji"); boolean e = node.key.equals("emoji");
boolean s = node.key.equals("space"); boolean s = node.key.equals("space");
assert t || e || s; assert t || e || s;
b.append(node.value); // Same behaviour for all. 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.
*/
} }
return b.toString(); return b.toString();
} }
private int
getHeightInLines()
{
FontMetrics fm = getFontMetrics(getFont());
int initial = fm.getAscent();
int advance = fm.getAscent() + fm.getDescent();
return isnap2(getHeight(), initial, advance);
}
public void public void
keyReleased(KeyEvent eK) { } keyReleased(KeyEvent eK) { }
@ -477,9 +561,6 @@ implements
public void public void
mouseClicked(MouseEvent eM) { } mouseClicked(MouseEvent eM) { }
public void
mouseMoved(MouseEvent eM) { }
public void public void
mouseEntered(MouseEvent eM) { } mouseEntered(MouseEvent eM) { }
@ -501,15 +582,58 @@ implements
// - -%- - // - -%- -
private static int private static int
snap(int value, int initial, int advance) snap2(int value, int initial, int advance)
{ {
int offset = value - initial; return initial + (value - 1) * advance;
if (offset <= 0) offset = 0; // If you'd like to go behind the first line 1,
else { // note that the first negative line is 0.
int lines = 1 + ((offset - 1) / advance); }
offset = advance * lines;
} private static int
return initial + offset; isnap2(int value, int initial, int advance)
{
int offset = value - initial;
return 1 + ((offset - 1) / advance);
// Mostly correct for negative numbers. I just
// need this function to accept negative numbers,
// not give usable results.
}
// ---%-@-%---
private static class
Position {
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
Position(int x, int line)
{
this.x = x;
this.line = line;
}
public Position
clone()
{
return new Position(x, line);
}
} }
// ---%-@-%--- // ---%-@-%---

0
RudimentaryHTMLParser.java Normal file → Executable file
View File

0
TimelineWindow.java Normal file → Executable file
View File

0
TwoToggleButton.java Normal file → Executable file
View File

0
WindowUpdater.java Normal file → Executable file
View File

BIN
graphics/nextToggled.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
graphics/nextUntoggled.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
graphics/postWindow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 B

BIN
graphics/prevToggled.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
graphics/prevUntoggled.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

0
notifOptions.txt Normal file → Executable file
View File

0
notifOptions.txt~ Normal file → Executable file
View File