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 thread = getThread(postId); if (thread != null) display.showThread(thread); display.setCursor(null); if (thread == null) dispose(); } // - -%- - public void postSelected(Tree post) { postWindow.readEntity(post); } private Tree 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 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 json) { Tree ancestors = json.get("ancestors"); if (ancestors.size() == 0) topPostId = postId; else topPostId = ancestors.get(0).get("id").value; } }; class DescendantsGetter extends Handler { Tree descendants; // -=%=- public void requestSucceeded(Tree json) { descendants = json.get("descendants"); } }; class PostGetter extends Handler { Tree post; // -=%=- public void requestSucceeded(Tree 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 thread = new Tree(); 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 thread; // - -%- - private JTree tree; // ---%-@-%--- public void showThread(Tree thread) { Enumeration e; DefaultMutableTreeNode root; TreeItem item; item = new TreeItem(thread.get("top")); root = new DefaultMutableTreeNode(item); for (Tree 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 post; // -=%=- public String toString() { String html = post.get("content").value; return TimelineWindow.textApproximation(html); } // -=%=- TreeItem(Tree 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); } }