2022-06-02 18:28:54 +02:00
|
|
|
/* copyright
|
|
|
|
|
|
|
|
This file is part of JKomasto2.
|
|
|
|
Written in 2022 by Usawashi <usawashi16@yahoo.co.jp>
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
copyright */
|
2022-04-29 19:44:38 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
// ---%-@-%---
|
|
|
|
|
2022-05-06 22:34:22 +02:00
|
|
|
public synchronized void
|
2022-04-29 19:44:38 +02:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
// - -%- -
|
|
|
|
|
2022-05-06 22:34:22 +02:00
|
|
|
public synchronized void
|
2022-04-29 19:44:38 +02:00
|
|
|
postSelected(Tree<String> post)
|
|
|
|
{
|
2022-05-02 03:44:10 +02:00
|
|
|
postWindow.readEntity(post);
|
2022-05-13 16:32:11 +02:00
|
|
|
postWindow.setVisible(true);
|
2022-04-29 19:44:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2022-05-06 15:57:17 +02:00
|
|
|
|
2022-06-02 20:41:59 +02:00
|
|
|
setIconImage(primaire.getProgramIcon());
|
2022-04-29 19:44:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2022-06-02 20:41:59 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2022-04-29 19:44:38 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
item = new TreeItem(desc);
|
|
|
|
p.add(new DefaultMutableTreeNode(item));
|
|
|
|
}
|
|
|
|
|
|
|
|
tree.setModel(new DefaultTreeModel(root));
|
2022-06-02 20:41:59 +02:00
|
|
|
for (int o = 0; o < tree.getRowCount(); ++o)
|
|
|
|
tree.expandRow(o);
|
2022-04-29 19:44:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// - -%- -
|
|
|
|
|
|
|
|
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()
|
|
|
|
{
|
2022-05-06 09:05:01 +02:00
|
|
|
Post post = new Post(this.post);
|
|
|
|
post.resolveApproximateText();
|
|
|
|
return post.approximateText;
|
2022-04-29 19:44:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// -=%=-
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|