diff --git a/ClipboardApi.java b/ClipboardApi.java new file mode 100755 index 0000000..2f4e907 --- /dev/null +++ b/ClipboardApi.java @@ -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. + * 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) { } + +} diff --git a/MastodonApi.java b/MastodonApi.java index d05b122..9bd8032 100644 --- a/MastodonApi.java +++ b/MastodonApi.java @@ -180,22 +180,22 @@ MastodonApi { getTimelinePage( TimelineType type, int count, String maxId, String minId, - String accountId, String listId, + String accountId, String listId, RequestListener handler) { String token = accessToken.get("access_token").value; - assert !(accountId != null && listId != null); + assert !(accountId != null && listId != null); String url = instanceUrl + "/api/v1"; if (accountId != null) { url += "/accounts/" + accountId + "/statuses"; } - else if (listId != null) - { - url += "/lists/" + listId; - } + else if (listId != null) + { + url += "/lists/" + listId; + } else switch (type) { case FEDERATED: @@ -325,11 +325,11 @@ MastodonApi { catch (IOException eIo) { handler.connectionFailed(eIo); } } - public void - getNotifications( - int count, String maxId, String minId, - RequestListener handler) - { + public void + getNotifications( + int count, String maxId, String minId, + RequestListener handler) + { String token = accessToken.get("access_token").value; String url = instanceUrl + "/api/v1/notifications"; @@ -349,51 +349,93 @@ MastodonApi { doStandardJsonReturn(conn, handler); } catch (IOException eIo) { handler.connectionFailed(eIo); } - } + } - public void - deletePost(String postId, RequestListener handler) - { - String token = accessToken.get("access_token").value; + public void + deletePost(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.setRequestMethod("DELETE"); - conn.connect(); + 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.setRequestMethod("DELETE"); + conn.connect(); - doStandardJsonReturn(conn, handler); - } - catch (IOException eIo) { handler.connectionFailed(eIo); } - } + doStandardJsonReturn(conn, handler); + } + catch (IOException eIo) { handler.connectionFailed(eIo); } + } - public void - getAccounts(String query, RequestListener handler) - { - assert query != null; - String token = accessToken.get("access_token").value; + public void + getSpecificPost(String postId, RequestListener handler) + { + String token = accessToken.get("access_token").value; - String url = instanceUrl + "/api/v1/accounts/search"; - url += "?q=" + encode(query); + 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(); - 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); } + } - 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) + { + assert query != null; + String token = accessToken.get("access_token").value; + + String url = instanceUrl + "/api/v1/accounts/search"; + url += "?q=" + encode(query); + + 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 monitorTimeline( @@ -486,6 +528,25 @@ MastodonApi { handler.requestSucceeded(response); } +// - -%- - + + public static void + debugPrint(Tree tree) + { + debugPrint(tree, ""); + } + + public static void + debugPrint(Tree tree, String prefix) + { + System.err.print(prefix); + System.err.print(tree.key); + System.err.print(": "); + System.err.println(tree.value); + for (Tree child: tree) + debugPrint(child, prefix + " "); + } + // - -%- - private static Tree @@ -516,7 +577,7 @@ MastodonApi { } } -// ---%-@-%--- +// ---%-@-%--- public void loadCache() @@ -580,7 +641,7 @@ MastodonApi { w.close(); } -// - -%- - +// - -%- - private static String getCachePath() diff --git a/PostWindow.java b/PostWindow.java index ebcea31..af19740 100755 --- a/PostWindow.java +++ b/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 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 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 @@ -262,7 +258,7 @@ implements ActionListener { if (vs.equals("unlisted")) v = PostVisibility.UNLISTED; if (vs.equals("private")) v = PostVisibility.FOLLOWERS; if (vs.equals("direct")) v = PostVisibility.MENTIONED; - + Composition c = new Composition(); c.contentWarning = cw; c.text = id1.equals(id2) ? "" : "@" + authorId + " "; @@ -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) { @@ -354,7 +350,7 @@ implements ActionListener { } String cw = post.get("spoiler_text").value; - + String vs = post.get("visibility").value; PostVisibility v = null; if (vs.equals("public")) v = PostVisibility.PUBLIC; @@ -407,38 +403,47 @@ 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 post = this.post; + Tree reblogged = post.get("reblog"); + if (reblogged.size() > 0) post = reblogged; - public void - actionPerformed(ActionEvent eA) - { - Component src = (Component)eA.getSource(); - if (!(src instanceof JMenuItem)) return; - String text = ((JMenuItem)src).getText(); + ClipboardApi.serve(post.get("id").value); + } - 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. - */ - } - else if (text.equals("Replies")) - { - setContentPane(repliesDisplay); - revalidate(); - } - } + public void + copyPostLink() + { + Tree post = this.post; + Tree reblogged = post.get("reblog"); + if (reblogged.size() > 0) post = reblogged; + + String url = post.get("url").value; + if (url == null) url = post.get("uri").value; + + ClipboardApi.serve(url); + } + + public void + openReplies() + { + Tree post = this.post; + Tree 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 - replies; - -// - -%- - - - private JButton - prevPage, nextPage; - - private JLabel - pageLabel; - - private ReplyPreviewComponent[] - previews; - -// ---%-@-%--- - - public void - setReplies(List replies) - { - assert replies != null; - this.replies = replies; - displayPage(1); - } - -// - -%- - - - private void - displayPage(int pageNumber) - { - assert pageNumber > 0; - assert this.replies != null; - - List 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); - } - -} diff --git a/RepliesWindow.java b/RepliesWindow.java new file mode 100755 index 0000000..6ac0714 --- /dev/null +++ b/RepliesWindow.java @@ -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 thread = getThread(postId); + if (thread != null) display.showThread(thread); + display.setCursor(null); + if (thread == null) dispose(); + } + +// - -%- - + + public void + postSelected(Tree post) + { + postWindow.displayEntity(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 TimelineComponent.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); + } + +} diff --git a/RichTextPane.java b/RichTextPane.java index 42124e1..310c8b6 100644 --- a/RichTextPane.java +++ b/RichTextPane.java @@ -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 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 @@ -83,7 +89,7 @@ implements if (o > selectionStart && o < selectionEnd) { - int dx = fm.stringWidth(segment.text); + int dx = fm.stringWidth(segment.text); int dy1 = fm.getAscent(); int dy2 = dy1 + fm.getDescent(); g.setColor(new Color(0, 0, 0, 15)); @@ -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. - * 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) { } @@ -222,7 +190,7 @@ implements while (cursor.hasNext()) { Segment curr = cursor.next(); - + int dx; if (curr.image != null) { int ow = curr.image.getIconWidth(); @@ -243,7 +211,7 @@ implements } boolean fits = x + dx < width; - + if (fits || curr.spacer) { curr.x = x; @@ -434,4 +402,4 @@ implements addKeyListener(this); } -} \ No newline at end of file +} diff --git a/graphics/test1.png b/graphics/test1.png new file mode 100755 index 0000000..17d196e Binary files /dev/null and b/graphics/test1.png differ diff --git a/graphics/test2.png b/graphics/test2.png new file mode 100755 index 0000000..bc41328 Binary files /dev/null and b/graphics/test2.png differ diff --git a/graphics/test3.png b/graphics/test3.png new file mode 100755 index 0000000..86e400e Binary files /dev/null and b/graphics/test3.png differ diff --git a/graphics/test4.png b/graphics/test4.png new file mode 100755 index 0000000..7e0e599 Binary files /dev/null and b/graphics/test4.png differ diff --git a/notifOptions.txt b/notifOptions.txt new file mode 100644 index 0000000..3f312a5 --- /dev/null +++ b/notifOptions.txt @@ -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 \ No newline at end of file diff --git a/notifOptions.txt~ b/notifOptions.txt~ new file mode 100644 index 0000000..e6841de --- /dev/null +++ b/notifOptions.txt~ @@ -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 \ No newline at end of file diff --git a/run b/run index 4b3ab25..5920a19 100755 --- a/run +++ b/run @@ -1,12 +1,13 @@ #!/usr/bin/make -f CLASSPATH=.:../Hinoki:/usr/share/java/javax.json.jar +OPTIONS= c: - javac -cp $(CLASSPATH) *.java + javac -cp $(CLASSPATH) $(OPTIONS) *.java r: - java -cp $(CLASSPATH) -ea JKomasto + java -cp $(CLASSPATH) $(OPTIONS) -ea JKomasto cr: c r