Implemented selection for RichTextPane.
Added reply and misc graphics.
@ -4,6 +4,7 @@ import javax.swing.JPanel;
|
|||||||
import javax.swing.JMenuBar;
|
import javax.swing.JMenuBar;
|
||||||
import javax.swing.JPopupMenu;
|
import javax.swing.JPopupMenu;
|
||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
|
import javax.swing.JSeparator;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
@ -39,7 +40,6 @@ import java.time.ZoneId;
|
|||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class
|
class
|
||||||
PostWindow extends JFrame
|
PostWindow extends JFrame
|
||||||
implements ActionListener {
|
implements ActionListener {
|
||||||
@ -125,6 +125,9 @@ implements ActionListener {
|
|||||||
}
|
}
|
||||||
else postDisplay.setMediaPreview(null);
|
else postDisplay.setMediaPreview(null);
|
||||||
|
|
||||||
|
String html = post.get("content").value;
|
||||||
|
setTitle(TimelineComponent.textApproximation(html));
|
||||||
|
|
||||||
postDisplay.resetFocus();
|
postDisplay.resetFocus();
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
@ -6,20 +6,55 @@ import java.awt.FontMetrics;
|
|||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Toolkit;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
|
import java.awt.event.MouseListener;
|
||||||
|
import java.awt.event.MouseMotionListener;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.KeyListener;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.awt.datatransfer.Clipboard;
|
||||||
|
import java.awt.datatransfer.ClipboardOwner;
|
||||||
|
import java.awt.datatransfer.Transferable;
|
||||||
|
import java.awt.datatransfer.DataFlavor;
|
||||||
|
|
||||||
class
|
class
|
||||||
RichTextPane extends JComponent {
|
RichTextPane extends JComponent
|
||||||
|
implements
|
||||||
|
MouseListener, MouseMotionListener, KeyListener,
|
||||||
|
Transferable, ClipboardOwner {
|
||||||
|
|
||||||
private List<Segment>
|
private List<Segment>
|
||||||
text;
|
text;
|
||||||
|
|
||||||
|
private int
|
||||||
|
selectionStart, selectionEnd;
|
||||||
|
|
||||||
// ---%-@-%---
|
// ---%-@-%---
|
||||||
|
|
||||||
public void
|
public void
|
||||||
setText(List<Segment> text) { this.text = text; }
|
setText(List<Segment> text)
|
||||||
|
{
|
||||||
|
this.text = text;
|
||||||
|
selectionStart = selectionEnd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// - -%- -
|
// - -%- -
|
||||||
|
|
||||||
@ -30,6 +65,7 @@ RichTextPane extends JComponent {
|
|||||||
FontMetrics fm = g.getFontMetrics(getFont());
|
FontMetrics fm = g.getFontMetrics(getFont());
|
||||||
g.clearRect(0, 0, getWidth(), getHeight());
|
g.clearRect(0, 0, getWidth(), getHeight());
|
||||||
|
|
||||||
|
int o = 0;
|
||||||
for (Segment segment: text)
|
for (Segment segment: text)
|
||||||
{
|
{
|
||||||
if (segment.image != null) {
|
if (segment.image != null) {
|
||||||
@ -44,13 +80,134 @@ RichTextPane extends JComponent {
|
|||||||
g.drawImage(img, x, y - h, w, h, this);
|
g.drawImage(img, x, y - h, w, h, this);
|
||||||
continue;
|
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 (segment.link != null) g.setColor(Color.BLUE);
|
if (segment.link != null) g.setColor(Color.BLUE);
|
||||||
g.drawString(segment.text, segment.x, segment.y);
|
g.drawString(segment.text, segment.x, segment.y);
|
||||||
g.setColor(getForeground());
|
g.setColor(getForeground());
|
||||||
|
|
||||||
|
++o;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int
|
||||||
|
identify(int x, int y)
|
||||||
|
{
|
||||||
|
FontMetrics fm = getFontMetrics(getFont());
|
||||||
|
int iy = fm.getAscent(), lh = fm.getHeight();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String
|
||||||
|
getTransferData(DataFlavor flavour)
|
||||||
|
{
|
||||||
|
assert flavour == DataFlavor.stringFlavor;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 void
|
||||||
|
keyPressed(KeyEvent eK)
|
||||||
|
{
|
||||||
|
if (selectionEnd == -1) return;
|
||||||
|
if (eK.getKeyCode() != KeyEvent.VK_C) return;
|
||||||
|
if (!eK.isControlDown()) return;
|
||||||
|
Toolkit tk = Toolkit.getDefaultToolkit();
|
||||||
|
Clipboard cb = tk.getSystemClipboard();
|
||||||
|
cb.setContents(this, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void
|
||||||
|
lostOwnership(Clipboard clipboard, Transferable contents) { }
|
||||||
|
|
||||||
|
public void
|
||||||
|
keyReleased(KeyEvent eK) { }
|
||||||
|
|
||||||
|
public void
|
||||||
|
keyTyped(KeyEvent eK) { }
|
||||||
|
|
||||||
|
public void
|
||||||
|
mouseClicked(MouseEvent eM) { }
|
||||||
|
|
||||||
|
public void
|
||||||
|
mouseReleased(MouseEvent eM) { }
|
||||||
|
|
||||||
|
public void
|
||||||
|
mouseEntered(MouseEvent eM) { }
|
||||||
|
|
||||||
|
public void
|
||||||
|
mouseExited(MouseEvent eM) { }
|
||||||
|
|
||||||
|
public void
|
||||||
|
mouseMoved(MouseEvent eM) { }
|
||||||
|
|
||||||
// - -%- -
|
// - -%- -
|
||||||
|
|
||||||
public static List<Segment>
|
public static List<Segment>
|
||||||
@ -272,6 +429,9 @@ RichTextPane extends JComponent {
|
|||||||
RichTextPane()
|
RichTextPane()
|
||||||
{
|
{
|
||||||
text = new LinkedList<>();
|
text = new LinkedList<>();
|
||||||
|
addMouseListener(this);
|
||||||
|
addMouseMotionListener(this);
|
||||||
|
addKeyListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -127,17 +127,22 @@ implements ActionListener {
|
|||||||
public void
|
public void
|
||||||
connectionFailed(IOException eIo)
|
connectionFailed(IOException eIo)
|
||||||
{
|
{
|
||||||
eIo.printStackTrace();
|
JOptionPane.showMessageDialog(
|
||||||
String s = eIo.getClass().getName();
|
TimelineWindow.this,
|
||||||
setTitle(s + " - JKomasto");
|
"Failed to fetch page.."
|
||||||
|
+ "\n" + eIo.getMessage()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void
|
public void
|
||||||
requestFailed(int httpCode, Tree<String> json)
|
requestFailed(int httpCode, Tree<String> json)
|
||||||
{
|
{
|
||||||
System.err.println(json.get("error").value);
|
JOptionPane.showMessageDialog(
|
||||||
setTitle(httpCode + " - JKomasto");
|
TimelineWindow.this,
|
||||||
// lol...
|
"Failed to fetch page.."
|
||||||
|
+ "\n" + json.get("error").value
|
||||||
|
+ "\n(HTTP code: " + httpCode + ")"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void
|
public void
|
||||||
@ -174,14 +179,22 @@ implements ActionListener {
|
|||||||
public void
|
public void
|
||||||
connectionFailed(IOException eIo)
|
connectionFailed(IOException eIo)
|
||||||
{
|
{
|
||||||
String s = eIo.getClass().getName();
|
JOptionPane.showMessageDialog(
|
||||||
setTitle(s + " - JKomasto");
|
TimelineWindow.this,
|
||||||
|
"Failed to fetch page.."
|
||||||
|
+ "\n" + eIo.getMessage()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void
|
public void
|
||||||
requestFailed(int httpCode, Tree<String> json)
|
requestFailed(int httpCode, Tree<String> json)
|
||||||
{
|
{
|
||||||
setTitle(httpCode + " - JKomasto");
|
JOptionPane.showMessageDialog(
|
||||||
|
TimelineWindow.this,
|
||||||
|
"Failed to fetch page.."
|
||||||
|
+ "\n" + json.get("error").value
|
||||||
|
+ "\n(HTTP code: " + httpCode + ")"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void
|
public void
|
||||||
@ -224,14 +237,22 @@ implements ActionListener {
|
|||||||
public void
|
public void
|
||||||
connectionFailed(IOException eIo)
|
connectionFailed(IOException eIo)
|
||||||
{
|
{
|
||||||
String s = eIo.getClass().getName();
|
JOptionPane.showMessageDialog(
|
||||||
setTitle(s + " - JKomasto");
|
TimelineWindow.this,
|
||||||
|
"Failed to fetch page.."
|
||||||
|
+ "\n" + eIo.getMessage()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void
|
public void
|
||||||
requestFailed(int httpCode, Tree<String> json)
|
requestFailed(int httpCode, Tree<String> json)
|
||||||
{
|
{
|
||||||
setTitle(httpCode + " - JKomasto");
|
JOptionPane.showMessageDialog(
|
||||||
|
TimelineWindow.this,
|
||||||
|
"Failed to fetch page.."
|
||||||
|
+ "\n" + json.get("error").value
|
||||||
|
+ "\n(HTTP code: " + httpCode + ")"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void
|
public void
|
||||||
@ -623,7 +644,7 @@ implements
|
|||||||
if (!cw.isEmpty())
|
if (!cw.isEmpty())
|
||||||
c.setBottom("(" + cw + ")");
|
c.setBottom("(" + cw + ")");
|
||||||
else
|
else
|
||||||
c.setBottom(textApproximation(html) + " ");
|
c.setBottom(textApproximation(html));
|
||||||
}
|
}
|
||||||
for (int o = posts.size(); o < postPreviews.size(); ++o)
|
for (int o = posts.size(); o < postPreviews.size(); ++o)
|
||||||
{
|
{
|
||||||
@ -786,6 +807,7 @@ implements
|
|||||||
{
|
{
|
||||||
StringBuilder returnee = new StringBuilder();
|
StringBuilder returnee = new StringBuilder();
|
||||||
Tree<String> nodes = RudimentaryHTMLParser.depthlessRead(html);
|
Tree<String> nodes = RudimentaryHTMLParser.depthlessRead(html);
|
||||||
|
if (nodes.size() == 0) return "-";
|
||||||
Tree<String> first = nodes.get(0);
|
Tree<String> first = nodes.get(0);
|
||||||
for (Tree<String> node: nodes)
|
for (Tree<String> node: nodes)
|
||||||
{
|
{
|
||||||
|
@ -115,14 +115,14 @@ implements KeyListener, MouseListener, FocusListener {
|
|||||||
if (isFocusOwner())
|
if (isFocusOwner())
|
||||||
g.drawImage(selectedOverlay, 0, 0, this);
|
g.drawImage(selectedOverlay, 0, 0, this);
|
||||||
|
|
||||||
if (primaryToggled)
|
|
||||||
g.drawImage(primaryToggledIcon, 0, 0, this);
|
|
||||||
else
|
|
||||||
g.drawImage(primaryUntoggledIcon, 0, 0, this);
|
|
||||||
if (secondaryToggled)
|
if (secondaryToggled)
|
||||||
g.drawImage(secondaryToggledIcon, 0, 0, this);
|
g.drawImage(secondaryToggledIcon, 0, 0, this);
|
||||||
else
|
else
|
||||||
g.drawImage(secondaryUntoggledIcon, 0, 0, this);
|
g.drawImage(secondaryUntoggledIcon, 0, 0, this);
|
||||||
|
if (primaryToggled)
|
||||||
|
g.drawImage(primaryToggledIcon, 0, 0, this);
|
||||||
|
else
|
||||||
|
g.drawImage(primaryUntoggledIcon, 0, 0, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BIN
graphics/miscToggled.png
Executable file
After Width: | Height: | Size: 2.5 KiB |
BIN
graphics/miscUntoggled.png
Executable file
After Width: | Height: | Size: 2.5 KiB |
BIN
graphics/replyToggled.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
graphics/replyUntoggled.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.3 KiB |