mirror of
https://gitlab.com/biskuteri-cafe/JKomasto2.git
synced 2025-01-08 22:34:45 +01:00
Added replies window, based on JTree
This commit is contained in:
parent
39526a145f
commit
9c75464b18
62
ClipboardApi.java
Executable file
62
ClipboardApi.java
Executable file
@ -0,0 +1,62 @@
|
||||
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.ClipboardOwner;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.Toolkit;
|
||||
|
||||
class
|
||||
ClipboardApi
|
||||
implements Transferable, ClipboardOwner {
|
||||
|
||||
private static final ClipboardApi
|
||||
instance = new ClipboardApi();
|
||||
|
||||
private static String
|
||||
string;
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public static void
|
||||
serve(String string)
|
||||
{
|
||||
assert string != null;
|
||||
instance.string = string;
|
||||
Toolkit tk = Toolkit.getDefaultToolkit();
|
||||
Clipboard cb = tk.getSystemClipboard();
|
||||
cb.setContents(instance, instance);
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
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 boolean
|
||||
isDataFlavorSupported(DataFlavor flavour)
|
||||
{
|
||||
return flavour == DataFlavor.stringFlavor;
|
||||
}
|
||||
|
||||
public void
|
||||
lostOwnership(Clipboard clipboard, Transferable contents) { }
|
||||
|
||||
}
|
@ -372,6 +372,48 @@ MastodonApi {
|
||||
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
||||
}
|
||||
|
||||
public void
|
||||
getSpecificPost(String postId, RequestListener handler)
|
||||
{
|
||||
String token = accessToken.get("access_token").value;
|
||||
|
||||
String url = instanceUrl + "/api/v1/statuses/" + postId;
|
||||
try
|
||||
{
|
||||
URL endpoint = new URL(url);
|
||||
HttpURLConnection conn;
|
||||
conn = (HttpURLConnection)endpoint.openConnection();
|
||||
String s1 = "Bearer " + token;
|
||||
conn.setRequestProperty("Authorization", s1);
|
||||
conn.connect();
|
||||
|
||||
doStandardJsonReturn(conn, handler);
|
||||
}
|
||||
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
||||
}
|
||||
|
||||
public void
|
||||
getPostContext(String postId, RequestListener handler)
|
||||
{
|
||||
String token = accessToken.get("access_token").value;
|
||||
|
||||
String s1 = instanceUrl + "/api/v1/statuses/";
|
||||
String s2 = postId + "/context";
|
||||
String url = s1 + s2;
|
||||
try
|
||||
{
|
||||
URL endpoint = new URL(url);
|
||||
HttpURLConnection conn;
|
||||
conn = (HttpURLConnection)endpoint.openConnection();
|
||||
String s3 = "Bearer " + token;
|
||||
conn.setRequestProperty("Authorization", s3);
|
||||
conn.connect();
|
||||
|
||||
doStandardJsonReturn(conn, handler);
|
||||
}
|
||||
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
||||
}
|
||||
|
||||
public void
|
||||
getAccounts(String query, RequestListener handler)
|
||||
{
|
||||
@ -486,6 +528,25 @@ MastodonApi {
|
||||
handler.requestSucceeded(response);
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
public static void
|
||||
debugPrint(Tree<String> tree)
|
||||
{
|
||||
debugPrint(tree, "");
|
||||
}
|
||||
|
||||
public static void
|
||||
debugPrint(Tree<String> tree, String prefix)
|
||||
{
|
||||
System.err.print(prefix);
|
||||
System.err.print(tree.key);
|
||||
System.err.print(": ");
|
||||
System.err.println(tree.value);
|
||||
for (Tree<String> child: tree)
|
||||
debugPrint(child, prefix + " ");
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
private static Tree<String>
|
||||
|
325
PostWindow.java
325
PostWindow.java
@ -41,8 +41,7 @@ import java.time.format.DateTimeFormatter;
|
||||
|
||||
|
||||
class
|
||||
PostWindow extends JFrame
|
||||
implements ActionListener {
|
||||
PostWindow extends JFrame {
|
||||
|
||||
private JKomasto
|
||||
primaire;
|
||||
@ -56,10 +55,7 @@ implements ActionListener {
|
||||
// - -%- -
|
||||
|
||||
private PostComponent
|
||||
postDisplay;
|
||||
|
||||
private RepliesComponent
|
||||
repliesDisplay;
|
||||
display;
|
||||
|
||||
// - -%- -
|
||||
|
||||
@ -83,21 +79,21 @@ implements ActionListener {
|
||||
|
||||
String an = author.get("display_name").value;
|
||||
if (an.isEmpty()) an = author.get("username").value;
|
||||
postDisplay.setAuthorName(an);
|
||||
postDisplay.setAuthorId(author.get("acct").value);
|
||||
display.setAuthorName(an);
|
||||
display.setAuthorId(author.get("acct").value);
|
||||
|
||||
String aid = author.get("id").value;
|
||||
String oid = api.getAccountDetails().get("id").value;
|
||||
postDisplay.setDeleteEnabled(aid.equals(oid));
|
||||
display.setDeleteEnabled(aid.equals(oid));
|
||||
|
||||
String avurl = author.get("avatar").value;
|
||||
postDisplay.setAuthorAvatar(ImageApi.remote(avurl));
|
||||
display.setAuthorAvatar(ImageApi.remote(avurl));
|
||||
|
||||
String sdate = post.get("created_at").value;
|
||||
ZonedDateTime date = ZonedDateTime.parse(sdate);
|
||||
date = date.withZoneSameInstant(ZoneId.systemDefault());
|
||||
postDisplay.setDate(DATE_FORMAT.format(date));
|
||||
postDisplay.setTime(TIME_FORMAT.format(date));
|
||||
display.setDate(DATE_FORMAT.format(date));
|
||||
display.setTime(TIME_FORMAT.format(date));
|
||||
|
||||
String[][] emojiUrls = new String[emojis.size()][];
|
||||
for (int o = 0; o < emojiUrls.length; ++o) {
|
||||
@ -106,13 +102,13 @@ implements ActionListener {
|
||||
emojiUrls[o][0] = emoji.get("shortcode").value;
|
||||
emojiUrls[o][1] = emoji.get("url").value;
|
||||
}
|
||||
postDisplay.setEmojiUrls(emojiUrls);
|
||||
display.setEmojiUrls(emojiUrls);
|
||||
|
||||
postDisplay.setHtml(post.get("content").value);
|
||||
display.setHtml(post.get("content").value);
|
||||
boolean f = post.get("favourited").value.equals("true");
|
||||
boolean b = post.get("reblogged").value.equals("true");
|
||||
postDisplay.setFavourited(f);
|
||||
postDisplay.setBoosted(b);
|
||||
display.setFavourited(f);
|
||||
display.setBoosted(b);
|
||||
|
||||
if (media.size() > 0)
|
||||
{
|
||||
@ -121,14 +117,14 @@ implements ActionListener {
|
||||
String u2 = first.get("text_url").value;
|
||||
String u3 = first.get("url").value;
|
||||
String purl = u1 != null ? u1 : u2 != null ? u2 : u3;
|
||||
postDisplay.setMediaPreview(ImageApi.remote(purl));
|
||||
display.setMediaPreview(ImageApi.remote(purl));
|
||||
}
|
||||
else postDisplay.setMediaPreview(null);
|
||||
else display.setMediaPreview(null);
|
||||
|
||||
String html = post.get("content").value;
|
||||
setTitle(TimelineComponent.textApproximation(html));
|
||||
|
||||
postDisplay.resetFocus();
|
||||
display.resetFocus();
|
||||
repaint();
|
||||
}
|
||||
|
||||
@ -153,9 +149,9 @@ implements ActionListener {
|
||||
Tree<String> boosted = post.get("reblog");
|
||||
if (boosted.size() > 0) post = boosted;
|
||||
|
||||
postDisplay.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
postDisplay.setFavouriteBoostEnabled(false);
|
||||
postDisplay.paintImmediately(postDisplay.getBounds());
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
display.setFavouriteBoostEnabled(false);
|
||||
display.paintImmediately(display.getBounds());
|
||||
RequestListener handler = new RequestListener() {
|
||||
|
||||
public void
|
||||
@ -189,9 +185,9 @@ implements ActionListener {
|
||||
};
|
||||
String postId = post.get("id").value;
|
||||
api.setPostFavourited(postId, favourited, handler);
|
||||
postDisplay.setCursor(null);
|
||||
postDisplay.setFavouriteBoostEnabled(true);
|
||||
postDisplay.repaint();
|
||||
display.setCursor(null);
|
||||
display.setFavouriteBoostEnabled(true);
|
||||
display.repaint();
|
||||
}
|
||||
|
||||
public void
|
||||
@ -201,9 +197,9 @@ implements ActionListener {
|
||||
Tree<String> boosted2 = post.get("reblog");
|
||||
if (boosted2.size() > 0) post = boosted2;
|
||||
|
||||
postDisplay.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
postDisplay.setFavouriteBoostEnabled(false);
|
||||
postDisplay.paintImmediately(postDisplay.getBounds());
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
display.setFavouriteBoostEnabled(false);
|
||||
display.paintImmediately(display.getBounds());
|
||||
RequestListener handler = new RequestListener() {
|
||||
|
||||
public void
|
||||
@ -237,9 +233,9 @@ implements ActionListener {
|
||||
};
|
||||
String postId = post.get("id").value;
|
||||
api.setPostBoosted(postId, boosted, handler);
|
||||
postDisplay.setCursor(null);
|
||||
postDisplay.setFavouriteBoostEnabled(true);
|
||||
postDisplay.repaint();
|
||||
display.setCursor(null);
|
||||
display.setFavouriteBoostEnabled(true);
|
||||
display.repaint();
|
||||
}
|
||||
|
||||
public void
|
||||
@ -310,9 +306,9 @@ implements ActionListener {
|
||||
public void
|
||||
deletePost(boolean redraft)
|
||||
{
|
||||
postDisplay.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
postDisplay.setDeleteEnabled(false);
|
||||
postDisplay.paintImmediately(postDisplay.getBounds());
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
display.setDeleteEnabled(false);
|
||||
display.paintImmediately(display.getBounds());
|
||||
|
||||
if (redraft)
|
||||
{
|
||||
@ -407,37 +403,46 @@ implements ActionListener {
|
||||
|
||||
});
|
||||
|
||||
postDisplay.setCursor(null);
|
||||
postDisplay.setDeleteEnabled(true);
|
||||
postDisplay.paintImmediately(postDisplay.getBounds());
|
||||
display.setCursor(null);
|
||||
display.setDeleteEnabled(true);
|
||||
display.paintImmediately(display.getBounds());
|
||||
if (!isVisible()) dispose();
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
public void
|
||||
copyPostId()
|
||||
{
|
||||
Tree<String> post = this.post;
|
||||
Tree<String> reblogged = post.get("reblog");
|
||||
if (reblogged.size() > 0) post = reblogged;
|
||||
|
||||
ClipboardApi.serve(post.get("id").value);
|
||||
}
|
||||
|
||||
public void
|
||||
actionPerformed(ActionEvent eA)
|
||||
copyPostLink()
|
||||
{
|
||||
Component src = (Component)eA.getSource();
|
||||
if (!(src instanceof JMenuItem)) return;
|
||||
String text = ((JMenuItem)src).getText();
|
||||
Tree<String> post = this.post;
|
||||
Tree<String> reblogged = post.get("reblog");
|
||||
if (reblogged.size() > 0) post = reblogged;
|
||||
|
||||
if (text.equals("Post"))
|
||||
{
|
||||
setContentPane(postDisplay);
|
||||
revalidate();
|
||||
/*
|
||||
* (知) Setting a content pane in itself doesn't
|
||||
* do anything to the content pane. Validation
|
||||
* of the validate root does. Which happens on
|
||||
* window realisation, or by manual call.
|
||||
*/
|
||||
String url = post.get("url").value;
|
||||
if (url == null) url = post.get("uri").value;
|
||||
|
||||
ClipboardApi.serve(url);
|
||||
}
|
||||
else if (text.equals("Replies"))
|
||||
|
||||
public void
|
||||
openReplies()
|
||||
{
|
||||
setContentPane(repliesDisplay);
|
||||
revalidate();
|
||||
}
|
||||
Tree<String> post = this.post;
|
||||
Tree<String> boosted = post.get("reblog");
|
||||
if (boosted.size() > 0) post = boosted;
|
||||
|
||||
RepliesWindow w = new RepliesWindow(primaire, this);
|
||||
w.showFor(post.get("id").value);
|
||||
w.setLocation(getX(), getY() + 100);
|
||||
w.setVisible(true);
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
@ -452,10 +457,9 @@ implements ActionListener {
|
||||
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||
setLocationByPlatform(true);
|
||||
|
||||
postDisplay = new PostComponent(this);
|
||||
repliesDisplay = new RepliesComponent();
|
||||
display = new PostComponent(this);
|
||||
|
||||
setContentPane(postDisplay);
|
||||
setContentPane(display);
|
||||
}
|
||||
|
||||
}
|
||||
@ -496,6 +500,9 @@ implements ActionListener {
|
||||
miscMenu;
|
||||
|
||||
private JMenuItem
|
||||
openReplies,
|
||||
copyPostId,
|
||||
copyPostLink,
|
||||
deletePost,
|
||||
redraftPost;
|
||||
|
||||
@ -667,17 +674,11 @@ implements ActionListener {
|
||||
return;
|
||||
}
|
||||
|
||||
if (src == deletePost)
|
||||
{
|
||||
primaire.deletePost(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (src == redraftPost)
|
||||
{
|
||||
primaire.deletePost(true);
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
@ -731,12 +732,24 @@ implements ActionListener {
|
||||
nextPrev.addActionListener(this);
|
||||
media.addActionListener(this);
|
||||
|
||||
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);
|
||||
|
||||
Box buttons = Box.createVerticalBox();
|
||||
@ -805,175 +818,3 @@ implements ActionListener {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
class
|
||||
RepliesComponent extends JPanel {
|
||||
|
||||
private List<RepliesComponent.Reply>
|
||||
replies;
|
||||
|
||||
// - -%- -
|
||||
|
||||
private JButton
|
||||
prevPage, nextPage;
|
||||
|
||||
private JLabel
|
||||
pageLabel;
|
||||
|
||||
private ReplyPreviewComponent[]
|
||||
previews;
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public void
|
||||
setReplies(List<RepliesComponent.Reply> replies)
|
||||
{
|
||||
assert replies != null;
|
||||
this.replies = replies;
|
||||
displayPage(1);
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
private void
|
||||
displayPage(int pageNumber)
|
||||
{
|
||||
assert pageNumber > 0;
|
||||
assert this.replies != null;
|
||||
|
||||
List<RepliesComponent.Reply> page;
|
||||
{
|
||||
int oS = (pageNumber - 1) * 8;
|
||||
int oE = Math.min(oS + 8, replies.size());
|
||||
if (oS > oE) page = new ArrayList<>();
|
||||
else page = this.replies.subList(oS, oE);
|
||||
}
|
||||
|
||||
for (int o = 0; o < page.size(); ++o)
|
||||
{
|
||||
assert o < previews.length;
|
||||
|
||||
ReplyPreviewComponent preview = previews[o];
|
||||
Reply reply = replies.get(o);
|
||||
preview.setAuthorName(reply.author);
|
||||
preview.setText(reply.text);
|
||||
preview.setVisible(true);
|
||||
}
|
||||
|
||||
for (int o = page.size(); o < previews.length; ++o)
|
||||
{
|
||||
ReplyPreviewComponent preview = previews[o];
|
||||
preview.setVisible(false);
|
||||
}
|
||||
|
||||
int pages = 1 + ((replies.size() - 1) / 8);
|
||||
pageLabel.setText(pageNumber + "/" + pages);
|
||||
prevPage.setEnabled(pageNumber > 1);
|
||||
nextPage.setEnabled(pageNumber < pages);
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public static class
|
||||
Reply {
|
||||
|
||||
public String
|
||||
author;
|
||||
|
||||
public String
|
||||
text;
|
||||
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
RepliesComponent()
|
||||
{
|
||||
prevPage = new JButton("<");
|
||||
nextPage = new JButton(">");
|
||||
prevPage.setEnabled(false);
|
||||
nextPage.setEnabled(false);
|
||||
|
||||
pageLabel = new JLabel();
|
||||
|
||||
Box bottom = Box.createHorizontalBox();
|
||||
bottom.add(Box.createGlue());
|
||||
bottom.add(prevPage);
|
||||
bottom.add(Box.createHorizontalStrut(8));
|
||||
bottom.add(pageLabel);
|
||||
bottom.add(Box.createHorizontalStrut(8));
|
||||
bottom.add(nextPage);
|
||||
|
||||
JPanel centre = new JPanel();
|
||||
centre.setOpaque(false);
|
||||
centre.setLayout(new GridLayout(0, 1, 0, 2));
|
||||
|
||||
previews = new ReplyPreviewComponent[8];
|
||||
for (int o = 0; o < previews.length; ++o)
|
||||
{
|
||||
previews[o] = new ReplyPreviewComponent();
|
||||
previews[o].setVisible(false);
|
||||
centre.add(previews[o]);
|
||||
}
|
||||
|
||||
setLayout(new BorderLayout(0, 8));
|
||||
add(centre, BorderLayout.CENTER);
|
||||
add(bottom, BorderLayout.SOUTH);
|
||||
|
||||
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||
|
||||
setReplies(new ArrayList<>());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class
|
||||
ReplyPreviewComponent extends JButton {
|
||||
|
||||
private String
|
||||
author;
|
||||
|
||||
private String
|
||||
text;
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
@Override
|
||||
public void
|
||||
setText(String text)
|
||||
{
|
||||
assert text != null;
|
||||
this.text = text;
|
||||
setText();
|
||||
}
|
||||
|
||||
public void
|
||||
setAuthorName(String author)
|
||||
{
|
||||
assert author != null;
|
||||
this.author = author;
|
||||
setText();
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
private void
|
||||
setText()
|
||||
{
|
||||
StringBuilder text = new StringBuilder();
|
||||
text.append(this.author);
|
||||
text.append(" @ ");
|
||||
text.append(this.text);
|
||||
super.setText(text.toString());
|
||||
}
|
||||
|
||||
protected void
|
||||
paintComponent(Graphics g)
|
||||
{
|
||||
g.drawString(getText(), 8, 2 * getHeight() / 3);
|
||||
}
|
||||
|
||||
}
|
||||
|
300
RepliesWindow.java
Executable file
300
RepliesWindow.java
Executable file
@ -0,0 +1,300 @@
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTree;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.tree.DefaultTreeModel;
|
||||
import javax.swing.tree.TreeNode;
|
||||
import javax.swing.tree.MutableTreeNode;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import javax.swing.tree.DefaultTreeCellRenderer;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
import javax.swing.event.TreeSelectionListener;
|
||||
import javax.swing.event.TreeSelectionEvent;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.BorderLayout;
|
||||
import java.util.Enumeration;
|
||||
import cafe.biskuteri.hinoki.Tree;
|
||||
import java.io.IOException;
|
||||
|
||||
class
|
||||
RepliesWindow extends JFrame {
|
||||
|
||||
private JKomasto
|
||||
primaire;
|
||||
|
||||
private MastodonApi
|
||||
api;
|
||||
|
||||
private PostWindow
|
||||
postWindow;
|
||||
|
||||
// - -%- -
|
||||
|
||||
private RepliesComponent
|
||||
display;
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public void
|
||||
showFor(String postId)
|
||||
{
|
||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
Tree<String> thread = getThread(postId);
|
||||
if (thread != null) display.showThread(thread);
|
||||
display.setCursor(null);
|
||||
if (thread == null) dispose();
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
public void
|
||||
postSelected(Tree<String> post)
|
||||
{
|
||||
postWindow.displayEntity(post);
|
||||
}
|
||||
|
||||
private Tree<String>
|
||||
getThread(String postId)
|
||||
{
|
||||
abstract class Handler implements RequestListener {
|
||||
|
||||
boolean
|
||||
failed = false;
|
||||
|
||||
// -=%=-
|
||||
|
||||
public void
|
||||
connectionFailed(IOException eIo)
|
||||
{
|
||||
JOptionPane.showMessageDialog(
|
||||
RepliesWindow.this,
|
||||
"Failed to fetch post context...."
|
||||
+ "\n" + eIo.getMessage()
|
||||
);
|
||||
failed = true;
|
||||
}
|
||||
|
||||
public void
|
||||
requestFailed(int httpCode, Tree<String> json)
|
||||
{
|
||||
JOptionPane.showMessageDialog(
|
||||
RepliesWindow.this,
|
||||
"Failed to fetch post context...."
|
||||
+ "\n" + json.get("error").value
|
||||
+ "\n(HTTP code: " + httpCode + ")"
|
||||
);
|
||||
failed = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TopPostIdGetter extends Handler {
|
||||
|
||||
String
|
||||
topPostId;
|
||||
|
||||
// -=%=-
|
||||
|
||||
public void
|
||||
requestSucceeded(Tree<String> json)
|
||||
{
|
||||
Tree<String> ancestors = json.get("ancestors");
|
||||
if (ancestors.size() == 0) topPostId = postId;
|
||||
else topPostId = ancestors.get(0).get("id").value;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class DescendantsGetter extends Handler {
|
||||
|
||||
Tree<String>
|
||||
descendants;
|
||||
|
||||
// -=%=-
|
||||
|
||||
public void
|
||||
requestSucceeded(Tree<String> json)
|
||||
{
|
||||
descendants = json.get("descendants");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class PostGetter extends Handler {
|
||||
|
||||
Tree<String>
|
||||
post;
|
||||
|
||||
// -=%=-
|
||||
|
||||
public void
|
||||
requestSucceeded(Tree<String> json)
|
||||
{
|
||||
post = json;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TopPostIdGetter phase1 = new TopPostIdGetter();
|
||||
api.getPostContext(postId, phase1);
|
||||
if (phase1.failed) return null;
|
||||
DescendantsGetter phase2 = new DescendantsGetter();
|
||||
api.getPostContext(phase1.topPostId, phase2);
|
||||
if (phase2.failed) return null;
|
||||
PostGetter phase3 = new PostGetter();
|
||||
api.getSpecificPost(phase1.topPostId, phase3);
|
||||
if (phase3.failed) return null;
|
||||
|
||||
Tree<String> thread = new Tree<String>();
|
||||
phase3.post.key = "top";
|
||||
thread.add(phase3.post);
|
||||
thread.add(phase2.descendants);
|
||||
return thread;
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
RepliesWindow(JKomasto primaire, PostWindow postWindow)
|
||||
{
|
||||
super("Thread");
|
||||
|
||||
this.primaire = primaire;
|
||||
this.api = primaire.getMastodonApi();
|
||||
this.postWindow = postWindow;
|
||||
|
||||
display = new RepliesComponent(this);
|
||||
setContentPane(display);
|
||||
setSize(384, 224);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class
|
||||
RepliesComponent extends JPanel
|
||||
implements TreeSelectionListener {
|
||||
|
||||
private RepliesWindow
|
||||
primaire;
|
||||
|
||||
private Tree<String>
|
||||
thread;
|
||||
|
||||
// - -%- -
|
||||
|
||||
private JTree
|
||||
tree;
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public void
|
||||
showThread(Tree<String> thread)
|
||||
{
|
||||
Enumeration<TreeNode> e;
|
||||
DefaultMutableTreeNode root;
|
||||
TreeItem item;
|
||||
item = new TreeItem(thread.get("top"));
|
||||
root = new DefaultMutableTreeNode(item);
|
||||
for (Tree<String> desc: thread.get("descendants"))
|
||||
{
|
||||
String target = desc.get("in_reply_to_id").value;
|
||||
assert target != null;
|
||||
|
||||
DefaultMutableTreeNode p = null;
|
||||
e = root.breadthFirstEnumeration();
|
||||
while (e.hasMoreElements())
|
||||
{
|
||||
DefaultMutableTreeNode node;
|
||||
node = (DefaultMutableTreeNode)e.nextElement();
|
||||
item = (TreeItem)node.getUserObject();
|
||||
|
||||
String postId = item.post.get("id").value;
|
||||
if (postId.equals(target))
|
||||
{
|
||||
p = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (p == null)
|
||||
{
|
||||
assert false;
|
||||
continue;
|
||||
}
|
||||
|
||||
item = new TreeItem(desc);
|
||||
p.add(new DefaultMutableTreeNode(item));
|
||||
}
|
||||
|
||||
tree.setModel(new DefaultTreeModel(root));
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
public void
|
||||
valueChanged(TreeSelectionEvent eT)
|
||||
{
|
||||
Object selected = eT.getPath().getLastPathComponent();
|
||||
assert selected instanceof DefaultMutableTreeNode;
|
||||
|
||||
TreeItem item = (TreeItem)
|
||||
((DefaultMutableTreeNode)selected)
|
||||
.getUserObject();
|
||||
|
||||
primaire.postSelected(item.post);
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
private static class
|
||||
TreeItem {
|
||||
|
||||
public Tree<String>
|
||||
post;
|
||||
|
||||
// -=%=-
|
||||
|
||||
public String
|
||||
toString()
|
||||
{
|
||||
String html = post.get("content").value;
|
||||
return TimelineComponent.textApproximation(html);
|
||||
}
|
||||
|
||||
// -=%=-
|
||||
|
||||
TreeItem(Tree<String> post)
|
||||
{
|
||||
this.post = post;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
RepliesComponent(RepliesWindow primaire)
|
||||
{
|
||||
this.primaire = primaire;
|
||||
|
||||
tree = new JTree();
|
||||
tree.setBackground(null);
|
||||
DefaultTreeCellRenderer renderer;
|
||||
renderer = new DefaultTreeCellRenderer();
|
||||
renderer.setBackgroundNonSelectionColor(null);
|
||||
renderer.setOpenIcon(null);
|
||||
renderer.setClosedIcon(null);
|
||||
renderer.setLeafIcon(null);
|
||||
tree.setCellRenderer(renderer);
|
||||
int mode = TreeSelectionModel.SINGLE_TREE_SELECTION;
|
||||
tree.getSelectionModel().setSelectionMode(mode);
|
||||
tree.addTreeSelectionListener(this);
|
||||
tree.setFont(tree.getFont().deriveFont(16f));
|
||||
|
||||
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
add(tree);
|
||||
}
|
||||
|
||||
}
|
@ -6,7 +6,6 @@ import java.awt.FontMetrics;
|
||||
import java.awt.Image;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Toolkit;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.ListIterator;
|
||||
@ -15,16 +14,10 @@ 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
|
||||
RichTextPane extends JComponent
|
||||
implements
|
||||
MouseListener, MouseMotionListener, KeyListener,
|
||||
Transferable, ClipboardOwner {
|
||||
implements MouseListener, MouseMotionListener, KeyListener {
|
||||
|
||||
private List<Segment>
|
||||
text;
|
||||
@ -56,6 +49,19 @@ implements
|
||||
return returnee;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
protected void
|
||||
@ -139,54 +145,16 @@ implements
|
||||
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);
|
||||
copySelection();
|
||||
}
|
||||
|
||||
|
||||
public void
|
||||
lostOwnership(Clipboard clipboard, Transferable contents) { }
|
||||
|
||||
public void
|
||||
keyReleased(KeyEvent eK) { }
|
||||
|
||||
|
BIN
graphics/test1.png
Executable file
BIN
graphics/test1.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
BIN
graphics/test2.png
Executable file
BIN
graphics/test2.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
graphics/test3.png
Executable file
BIN
graphics/test3.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
graphics/test4.png
Executable file
BIN
graphics/test4.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
10
notifOptions.txt
Normal file
10
notifOptions.txt
Normal file
@ -0,0 +1,10 @@
|
||||
KDE_Chimes_1.ogg
|
||||
KDE_Dialog_Appear.wav
|
||||
KDE_Event_1.ogg
|
||||
KDE_Event_2.ogg
|
||||
KDE_Logout_3.ogg
|
||||
KDE_TypeWriter_Bell.ogg
|
||||
KDE_Window_DeIconify.ogg
|
||||
KDE_Window_Iconify.ogg
|
||||
KDE_Window_UnMaximize.wav
|
||||
pop.wav
|
9
notifOptions.txt~
Normal file
9
notifOptions.txt~
Normal file
@ -0,0 +1,9 @@
|
||||
KDE_Chimes_1.ogg
|
||||
KDE_Dialog_Appear.wav
|
||||
KDE_Event_1.ogg
|
||||
KDE_Event_2.ogg
|
||||
KDE_Logout_3.ogg
|
||||
KDE_TypeWriter_Bell.ogg
|
||||
KDE_Window_DeIconify.ogg
|
||||
KDE_Window_Iconify.ogg
|
||||
KDE_Window_UnMaximize.wav
|
Loading…
Reference in New Issue
Block a user