mirror of
https://gitlab.com/biskuteri-cafe/JKomasto2.git
synced 2024-11-20 06:34:49 +01:00
e6fea4c061
(Before this, JKomasto and sometimes the Mastodon web client would get '411 Record Not Found' when submitting the same text after deleting and redrafting. Presumably the Mastodon server caches both whether an idempotency key was fulfilled and which post it leads to, and for some reason it looks up the second and fails.)
853 lines
21 KiB
Java
853 lines
21 KiB
Java
|
|
import javax.swing.JFrame;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JTextArea;
|
|
import javax.swing.JTextField;
|
|
import javax.swing.JLabel;
|
|
import javax.swing.JComboBox;
|
|
import javax.swing.JButton;
|
|
import javax.swing.JSeparator;
|
|
import javax.swing.Box;
|
|
import javax.swing.BorderFactory;
|
|
import javax.swing.JOptionPane;
|
|
import javax.swing.JScrollPane;
|
|
import javax.swing.JTabbedPane;
|
|
import javax.swing.UIManager;
|
|
import javax.swing.JToggleButton;
|
|
import javax.swing.ButtonGroup;
|
|
import javax.swing.border.Border;
|
|
import javax.swing.JPopupMenu;
|
|
import javax.swing.JMenuItem;
|
|
import javax.swing.JFileChooser;
|
|
import javax.swing.ImageIcon;
|
|
import javax.swing.text.JTextComponent;
|
|
import java.nio.file.Files;
|
|
import java.awt.GridLayout;
|
|
import java.awt.BorderLayout;
|
|
import java.awt.Dimension;
|
|
import java.awt.event.ActionListener;
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.event.KeyListener;
|
|
import java.awt.event.KeyEvent;
|
|
import java.awt.event.MouseListener;
|
|
import java.awt.event.MouseEvent;
|
|
import java.awt.Cursor;
|
|
import java.awt.Color;
|
|
import java.awt.Font;
|
|
import java.awt.Insets;
|
|
import java.awt.FlowLayout;
|
|
import java.awt.Component;
|
|
import java.awt.Container;
|
|
import java.awt.Image;
|
|
import java.io.File;
|
|
import javax.swing.event.CaretListener;
|
|
import javax.swing.event.CaretEvent;
|
|
import java.util.List;
|
|
import java.util.ArrayList;
|
|
|
|
import cafe.biskuteri.hinoki.Tree;
|
|
import java.io.IOException;
|
|
|
|
class
|
|
ComposeWindow extends JFrame {
|
|
|
|
private JKomasto
|
|
primaire;
|
|
|
|
private MastodonApi
|
|
api;
|
|
|
|
// - -%- -
|
|
|
|
private Composition
|
|
composition;
|
|
|
|
private ComposeComponent
|
|
contentsDisplay;
|
|
|
|
private AttachmentsComponent
|
|
attachmentsDisplay;
|
|
|
|
private JTabbedPane
|
|
tabs;
|
|
|
|
// ---%-@-%---
|
|
|
|
public synchronized void
|
|
setComposition(Composition composition)
|
|
{
|
|
assert composition != null;
|
|
this.composition = composition;
|
|
syncDisplayToComposition();
|
|
}
|
|
|
|
public synchronized void
|
|
newComposition()
|
|
{
|
|
composition = new Composition();
|
|
composition.text = "";
|
|
composition.visibility = PostVisibility.UNLISTED;
|
|
composition.replyToPostId = null;
|
|
composition.contentWarning = null;
|
|
syncDisplayToComposition();
|
|
}
|
|
|
|
public synchronized void
|
|
submit()
|
|
{
|
|
syncCompositionToDisplay();
|
|
|
|
if (composition.replyToPostId != null)
|
|
assert !composition.replyToPostId.trim().isEmpty();
|
|
if (composition.contentWarning != null)
|
|
assert !composition.contentWarning.trim().isEmpty();
|
|
|
|
// Oh, this is gonna be hell.
|
|
/*
|
|
* (未) For every attachment in composition,
|
|
* attempt to upload it, then fill the media ID.
|
|
* Then change MastodonApi#submit to accept an
|
|
* array of media IDs. If any upload fails,
|
|
* show an error message then return.
|
|
*/
|
|
|
|
contentsDisplay.setSubmitting(true);
|
|
api.submit(
|
|
composition.text, composition.visibility,
|
|
composition.replyToPostId, composition.contentWarning,
|
|
new RequestListener() {
|
|
|
|
public void
|
|
connectionFailed(IOException eIo)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
ComposeWindow.this,
|
|
"Tried to submit post, failed..."
|
|
+ "\n" + eIo.getMessage()
|
|
);
|
|
}
|
|
|
|
public void
|
|
requestFailed(int httpCode, Tree<String> json)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
ComposeWindow.this,
|
|
"Tried to submit post, failed..."
|
|
+ "\n" + json.get("error").value
|
|
+ "\n(HTTP error code: " + httpCode + ")"
|
|
);
|
|
}
|
|
|
|
public void
|
|
requestSucceeded(Tree<String> json)
|
|
{
|
|
newComposition();
|
|
}
|
|
|
|
}
|
|
);
|
|
contentsDisplay.setSubmitting(false);
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
private synchronized void
|
|
syncDisplayToComposition()
|
|
{
|
|
ComposeComponent d1 = contentsDisplay;
|
|
d1.setText(composition.text);
|
|
d1.setReplyToPostId(composition.replyToPostId);
|
|
d1.setVisibility(stringFor(composition.visibility));
|
|
d1.setContentWarning(composition.contentWarning);
|
|
|
|
AttachmentsComponent d2 = attachmentsDisplay;
|
|
d2.setAttachments(composition.attachments);
|
|
}
|
|
|
|
private synchronized void
|
|
syncCompositionToDisplay()
|
|
{
|
|
Composition c = composition;
|
|
|
|
ComposeComponent d1 = contentsDisplay;
|
|
c.text = d1.getText();
|
|
c.visibility = visibilityFrom(d1.getVisibility());
|
|
c.replyToPostId = nonEmpty(d1.getReplyToPostId());
|
|
c.contentWarning = nonEmpty(d1.getContentWarning());
|
|
|
|
AttachmentsComponent d2 = attachmentsDisplay;
|
|
c.attachments = d2.getAttachments();
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
private static String
|
|
nonEmpty(String s)
|
|
{
|
|
if (s.trim().isEmpty()) return null;
|
|
return s;
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
ComposeWindow(JKomasto primaire)
|
|
{
|
|
super("Submit a new post");
|
|
this.primaire = primaire;
|
|
this.api = primaire.getMastodonApi();
|
|
|
|
Dimension sz = new Dimension(360, 270);
|
|
|
|
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
|
|
|
contentsDisplay = new ComposeComponent(this);
|
|
attachmentsDisplay = new AttachmentsComponent(this);
|
|
newComposition();
|
|
|
|
tabs = new JTabbedPane();
|
|
contentsDisplay.setPreferredSize(sz);
|
|
tabs.addTab("Text", contentsDisplay);
|
|
tabs.addTab("Media", attachmentsDisplay);
|
|
|
|
setBackground((Color)UIManager.get("TabbedPane.tabAreaBackground"));
|
|
setContentPane(tabs);
|
|
pack();
|
|
setIconImage(primaire.getProgramIcon());
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
private static final String
|
|
stringFor(PostVisibility visibility)
|
|
{
|
|
switch (visibility)
|
|
{
|
|
case PUBLIC: return "Public";
|
|
case UNLISTED: return "Unlisted";
|
|
case FOLLOWERS: return "Followers";
|
|
case MENTIONED: return "Mentioned";
|
|
}
|
|
|
|
assert false; return null;
|
|
}
|
|
|
|
private static final PostVisibility
|
|
visibilityFrom(String string)
|
|
{
|
|
if (string.equals("Public"))
|
|
return PostVisibility.PUBLIC;
|
|
if (string.equals("Unlisted"))
|
|
return PostVisibility.UNLISTED;
|
|
if (string.equals("Followers"))
|
|
return PostVisibility.FOLLOWERS;
|
|
if (string.equals("Mentioned"))
|
|
return PostVisibility.MENTIONED;
|
|
|
|
assert false; return null;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class
|
|
ComposeComponent extends JPanel
|
|
implements ActionListener, CaretListener, KeyListener {
|
|
|
|
private ComposeWindow
|
|
primaire;
|
|
|
|
// - -%- -
|
|
|
|
private JTextArea
|
|
text;
|
|
|
|
private JTextField
|
|
reply, contentWarning;
|
|
|
|
private JLabel
|
|
textLength;
|
|
|
|
private JComboBox<String>
|
|
visibility;
|
|
|
|
private JButton
|
|
submit;
|
|
|
|
// ---%-@-%---
|
|
|
|
public void
|
|
setText(String text)
|
|
{
|
|
this.text.setText(text);
|
|
}
|
|
|
|
public void
|
|
setReplyToPostId(String postId)
|
|
{
|
|
this.reply.setText(postId);
|
|
}
|
|
|
|
public void
|
|
setVisibility(String visibility)
|
|
{
|
|
if (visibility.equals("Public"))
|
|
this.visibility.setSelectedIndex(0);
|
|
else if (visibility.equals("Unlisted"))
|
|
this.visibility.setSelectedIndex(1);
|
|
else if (visibility.equals("Followers"))
|
|
this.visibility.setSelectedIndex(2);
|
|
else if (visibility.equals("Mentioned"))
|
|
this.visibility.setSelectedIndex(3);
|
|
}
|
|
|
|
public void
|
|
setContentWarning(String contentWarning)
|
|
{
|
|
this.contentWarning.setText(contentWarning);
|
|
}
|
|
|
|
public String
|
|
getText()
|
|
{
|
|
return text.getText();
|
|
}
|
|
|
|
public String
|
|
getReplyToPostId()
|
|
{
|
|
return reply.getText();
|
|
}
|
|
|
|
public String
|
|
getContentWarning()
|
|
{
|
|
return contentWarning.getText();
|
|
}
|
|
|
|
public String
|
|
getVisibility()
|
|
{
|
|
return (String)visibility.getSelectedItem();
|
|
}
|
|
|
|
public void
|
|
setSubmitting(boolean submitting)
|
|
{
|
|
if (submitting)
|
|
{
|
|
text.setEnabled(false);
|
|
visibility.setEnabled(false);
|
|
submit.setEnabled(false);
|
|
setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
|
}
|
|
else
|
|
{
|
|
text.setEnabled(true);
|
|
visibility.setEnabled(true);
|
|
submit.setEnabled(true);
|
|
setCursor(null);
|
|
}
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
public void
|
|
actionPerformed(ActionEvent eA)
|
|
{
|
|
if (eA.getSource() == submit)
|
|
primaire.submit();
|
|
}
|
|
|
|
public void
|
|
caretUpdate(CaretEvent eCa) { updateTextLength(); }
|
|
|
|
public void
|
|
keyPressed(KeyEvent eK)
|
|
{
|
|
boolean esc = eK.getKeyCode() == KeyEvent.VK_ESCAPE;
|
|
if (esc)
|
|
{
|
|
Container fcr = getFocusCycleRootAncestor();
|
|
fcr.getFocusTraversalPolicy()
|
|
.getComponentAfter(fcr, text)
|
|
.requestFocusInWindow();
|
|
}
|
|
else updateTextLength();
|
|
}
|
|
|
|
public void
|
|
keyReleased(KeyEvent eK) { }
|
|
|
|
public void
|
|
keyTyped(KeyEvent eK) { }
|
|
|
|
private void
|
|
updateTextLength()
|
|
{
|
|
int length = text.getText().length();
|
|
/*
|
|
* The web interface doesn't do this expensive thing.
|
|
* It has an upwards counter, incremented by I'm not
|
|
* sure what. Presumably they have some control over
|
|
* the text input. I'd rather not, cause I use a
|
|
* Japanese IME, I'm going to see how laggy this is.
|
|
* It raises our app's system requirements, but, I was
|
|
* going to transition it to multithreading anyways,
|
|
* I don't think we're going to be very cheap.. Which
|
|
* sucks, but the Mastodon API is not helping us here.
|
|
*/
|
|
textLength.setText(Integer.toString(length));
|
|
/*
|
|
* Another thing I could do is temporarily move the
|
|
* caret to the end and then find its position, then
|
|
* seek back. Not sure how much that would help, but
|
|
* if this is too laggy, that's what I'd try next.
|
|
*/
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
ComposeComponent(ComposeWindow primaire)
|
|
{
|
|
this.primaire = primaire;
|
|
|
|
Border b1 = BorderFactory.createEmptyBorder(8, 8, 8, 8);
|
|
Border b2 = BorderFactory.createEmptyBorder(4, 4, 4, 4);
|
|
Border b3 = BorderFactory.createLineBorder(Color.GRAY);
|
|
Border bc = BorderFactory.createCompoundBorder(b3, b2);
|
|
|
|
TextActionPopupMenu textActionPopup;
|
|
textActionPopup = new TextActionPopupMenu();
|
|
|
|
reply = new JTextField();
|
|
JLabel replyLabel = new JLabel("In reply to: ");
|
|
replyLabel.setLabelFor(reply);
|
|
reply.addMouseListener(textActionPopup);
|
|
|
|
contentWarning = new JTextField();
|
|
JLabel cwLabel = new JLabel("Content warning: ");
|
|
cwLabel.setLabelFor(contentWarning);
|
|
contentWarning.addMouseListener(textActionPopup);
|
|
|
|
JPanel top = new JPanel();
|
|
top.setOpaque(false);
|
|
top.setLayout(new GridLayout(2, 2, 8, 0));
|
|
top.add(replyLabel);
|
|
top.add(reply);
|
|
top.add(cwLabel);
|
|
top.add(contentWarning);
|
|
|
|
textLength = new JLabel("0");
|
|
textLength.setFont(textLength.getFont().deriveFont(14f));
|
|
|
|
visibility = new JComboBox<>(new String[] {
|
|
"Public",
|
|
"Unlisted",
|
|
"Followers",
|
|
"Mentioned"
|
|
// Where should we be saving strings..
|
|
});
|
|
visibility.setPreferredSize(new Dimension(48, 24));
|
|
|
|
submit = new JButton("Submit");
|
|
submit.addActionListener(this);
|
|
|
|
Box bottom = Box.createHorizontalBox();
|
|
bottom.add(Box.createGlue());
|
|
bottom.add(textLength);
|
|
bottom.add(Box.createHorizontalStrut(12));
|
|
bottom.add(visibility);
|
|
bottom.add(Box.createHorizontalStrut(12));
|
|
bottom.add(submit);
|
|
|
|
text = new JTextArea();
|
|
text.setLineWrap(true);
|
|
text.setWrapStyleWord(true);
|
|
text.setFont(text.getFont().deriveFont(16f));
|
|
text.setBorder(bc);
|
|
text.addCaretListener(this);
|
|
text.addKeyListener(this);
|
|
text.addMouseListener(textActionPopup);
|
|
|
|
setLayout(new BorderLayout(0, 8));
|
|
add(top, BorderLayout.NORTH);
|
|
add(text, BorderLayout.CENTER);
|
|
add(bottom, BorderLayout.SOUTH);
|
|
|
|
setBorder(b1);
|
|
}
|
|
|
|
}
|
|
|
|
class
|
|
AttachmentsComponent extends JPanel
|
|
implements ActionListener {
|
|
|
|
private ComposeWindow
|
|
primaire;
|
|
|
|
// - -%- -
|
|
|
|
private List<Attachment>
|
|
working;
|
|
|
|
private JPanel
|
|
selections;
|
|
|
|
private ButtonGroup
|
|
selectionsGroup;
|
|
|
|
private JToggleButton
|
|
attachment1,
|
|
attachment2,
|
|
attachment3,
|
|
attachment4;
|
|
|
|
private JButton
|
|
add;
|
|
|
|
private JButton
|
|
delete,
|
|
revert;
|
|
|
|
private JLabel
|
|
descriptionLabel;
|
|
|
|
private JTextArea
|
|
description;
|
|
|
|
private JFileChooser
|
|
chooser;
|
|
|
|
// ---%-@-%---
|
|
|
|
public void
|
|
setAttachments(Attachment[] n)
|
|
{
|
|
working.clear();
|
|
if (n != null) for (Attachment attachment: n)
|
|
{
|
|
working.add(attachment);
|
|
}
|
|
updateButtons();
|
|
}
|
|
|
|
public Attachment[]
|
|
getAttachments()
|
|
{
|
|
return working.toArray(new Attachment[0]);
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
private void
|
|
updateButtons()
|
|
{
|
|
Dimension sz = add.getPreferredSize();
|
|
|
|
selections.removeAll();
|
|
if (working.size() > 0) selections.add(attachment1);
|
|
if (working.size() > 1) selections.add(attachment2);
|
|
if (working.size() > 2) selections.add(attachment3);
|
|
if (working.size() > 3) selections.add(attachment4);
|
|
if (working.size() < 4) selections.add(add);
|
|
|
|
if (working.size() > 3)
|
|
{
|
|
attachment4.doClick();
|
|
Image i = working.get(3).image;
|
|
attachment4.setIcon(new ImageIcon(i));
|
|
}
|
|
else if (working.size() > 2)
|
|
{
|
|
attachment3.doClick();
|
|
Image i = working.get(2).image;
|
|
attachment3.setIcon(new ImageIcon(i));
|
|
}
|
|
else if (working.size() > 1)
|
|
{
|
|
attachment2.doClick();
|
|
Image i = working.get(1).image;
|
|
attachment2.setIcon(new ImageIcon(i));
|
|
}
|
|
else if (working.size() > 0)
|
|
{
|
|
attachment1.doClick();
|
|
Image i = working.get(0).image;
|
|
attachment1.setIcon(new ImageIcon(i));
|
|
}
|
|
else selectionsGroup.clearSelection();
|
|
|
|
int bw = sz.width;
|
|
int hgap = 4;
|
|
int count = selections.getComponents().length;
|
|
int w = count * bw + (count - 1) * hgap;
|
|
int h = bw;
|
|
selections.setPreferredSize(new Dimension(w, h));
|
|
selections.setMaximumSize(new Dimension(w, h));
|
|
|
|
selections.revalidate();
|
|
}
|
|
|
|
public void
|
|
actionPerformed(ActionEvent eA)
|
|
{
|
|
Object src = eA.getSource();
|
|
|
|
if (false)
|
|
{
|
|
// Clicked on filled attachment button.
|
|
}
|
|
|
|
if (src == add)
|
|
{
|
|
int r = chooser.showOpenDialog(this);
|
|
if (r != JFileChooser.APPROVE_OPTION) return;
|
|
|
|
File f = chooser.getSelectedFile();
|
|
|
|
Attachment a = new Attachment();
|
|
a.uploadee = f;
|
|
|
|
String mime = "", primary = "";
|
|
try
|
|
{
|
|
mime = Files.probeContentType(f.toPath());
|
|
primary = mime.split("/")[0];
|
|
// Too lazy to instantiate a
|
|
// javax.activation.MimeType.
|
|
}
|
|
catch (IOException eIo) { }
|
|
if (primary.equals("image"))
|
|
{
|
|
String urlr = f.toURI().toString();
|
|
a.image = ImageApi.remote(urlr);
|
|
}
|
|
|
|
working.add(a);
|
|
updateButtons();
|
|
}
|
|
|
|
if (src == delete)
|
|
{
|
|
Object sm = selectionsGroup.getSelection();
|
|
if (sm == attachment1.getModel())
|
|
{
|
|
assert working.size() > 0;
|
|
working.remove(0);
|
|
updateButtons();
|
|
}
|
|
if (sm == attachment2.getModel())
|
|
{
|
|
assert working.size() > 1;
|
|
working.remove(1);
|
|
updateButtons();
|
|
}
|
|
if (sm == attachment3.getModel())
|
|
{
|
|
assert working.size() > 2;
|
|
working.remove(2);
|
|
updateButtons();
|
|
}
|
|
if (sm == attachment4.getModel())
|
|
{
|
|
assert working.size() > 3;
|
|
working.remove(3);
|
|
updateButtons();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (src == revert)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
AttachmentsComponent(ComposeWindow primaire)
|
|
{
|
|
this.primaire = primaire;
|
|
|
|
Border b1 = BorderFactory.createEmptyBorder(8, 8, 8, 8);
|
|
Border b2 = BorderFactory.createEmptyBorder(4, 4, 4, 4);
|
|
Border b3 = BorderFactory.createLineBorder(Color.GRAY);
|
|
Border b4 = BorderFactory.createEmptyBorder(4, 8, 8, 8);
|
|
Border b5 = BorderFactory.createEtchedBorder();
|
|
Border bc1 = BorderFactory.createCompoundBorder(b3, b2);
|
|
Border bc2 = BorderFactory.createCompoundBorder(b4, b2);
|
|
|
|
TextActionPopupMenu textActionPopup;
|
|
textActionPopup = new TextActionPopupMenu();
|
|
|
|
chooser = new JFileChooser();
|
|
|
|
add = new JButton("+");
|
|
add.setPreferredSize(new Dimension(32, 32));
|
|
add.setMargin(new Insets(0, 0, 0, 0));
|
|
add.addActionListener(this);
|
|
attachment1 = new JToggleButton("1");
|
|
attachment2 = new JToggleButton("2");
|
|
attachment3 = new JToggleButton("3");
|
|
attachment4 = new JToggleButton("4");
|
|
attachment1.setMargin(add.getMargin());
|
|
attachment2.setMargin(add.getMargin());
|
|
attachment3.setMargin(add.getMargin());
|
|
attachment4.setMargin(add.getMargin());
|
|
|
|
selections = new JPanel();
|
|
selections.setOpaque(false);
|
|
selections.setLayout(new GridLayout(1, 0, 4, 0));
|
|
working = new ArrayList<Attachment>();
|
|
selectionsGroup = new ButtonGroup();
|
|
selectionsGroup.add(attachment1);
|
|
selectionsGroup.add(attachment2);
|
|
selectionsGroup.add(attachment3);
|
|
selectionsGroup.add(attachment4);
|
|
// Have to add selection listener to button group
|
|
updateButtons();
|
|
|
|
JButton del = new JButton("D");
|
|
JButton rev = new JButton("R");
|
|
JButton ml = new JButton("←");
|
|
JButton mr = new JButton("→");
|
|
del.setMargin(new Insets(0, 0, 0, 0));
|
|
//ml.setMargin(new Insets(0, 0, 0, 0));
|
|
//mr.setMargin(new Insets(0, 0, 0, 0));
|
|
rev.setMargin(new Insets(0, 0, 0, 0));
|
|
|
|
JPanel actions = new JPanel();
|
|
actions.setOpaque(false);
|
|
actions.setLayout(new GridLayout(1, 4, 4, 4));
|
|
actions.add(del);
|
|
actions.add(rev);
|
|
actions.add(ml);
|
|
actions.add(mr);
|
|
actions.setPreferredSize(new Dimension(108, 24));
|
|
actions.setMaximumSize(new Dimension(108, 24));
|
|
|
|
delete = new JButton("Delete");
|
|
revert = new JButton("Revert");
|
|
delete.addActionListener(this);
|
|
revert.addActionListener(this);
|
|
|
|
Box top = Box.createHorizontalBox();
|
|
top.add(selections);
|
|
top.add(Box.createGlue());
|
|
|
|
Box bottom = Box.createHorizontalBox();
|
|
bottom.add(ml);
|
|
bottom.add(mr);
|
|
bottom.add(Box.createHorizontalStrut(8));
|
|
bottom.add(delete);
|
|
bottom.add(Box.createGlue());
|
|
bottom.add(revert);
|
|
|
|
description = new JTextArea();
|
|
description.setLineWrap(true);
|
|
description.setWrapStyleWord(true);
|
|
java.awt.Font f = description.getFont();
|
|
description.setFont(f.deriveFont(16f));
|
|
description.setBorder(bc1);
|
|
description.addMouseListener(textActionPopup);
|
|
descriptionLabel = new JLabel("Description");
|
|
descriptionLabel.setLabelFor(description);
|
|
|
|
JPanel row1 = new JPanel();
|
|
row1.setOpaque(false);
|
|
row1.setLayout(new BorderLayout());
|
|
row1.add(descriptionLabel, BorderLayout.NORTH);
|
|
row1.add(description, BorderLayout.CENTER);
|
|
|
|
/*
|
|
Box row2 = Box.createHorizontalBox();
|
|
row2.add(Box.createGlue());
|
|
row2.add(delete);
|
|
row2.add(Box.createHorizontalStrut(8));
|
|
row2.add(revert);
|
|
*/
|
|
|
|
Box centre = Box.createVerticalBox();
|
|
centre.setBorder(b4);
|
|
centre.add(row1);
|
|
|
|
setLayout(new BorderLayout(8, 8));
|
|
add(centre, BorderLayout.CENTER);
|
|
add(top, BorderLayout.NORTH);
|
|
add(bottom, BorderLayout.SOUTH);
|
|
|
|
setBorder(b1);
|
|
}
|
|
|
|
}
|
|
|
|
class
|
|
TextActionPopupMenu extends JPopupMenu
|
|
implements MouseListener, ActionListener {
|
|
|
|
private JMenuItem
|
|
copy,
|
|
cut,
|
|
paste;
|
|
|
|
private long
|
|
pressed;
|
|
|
|
// ---%-@-%---
|
|
|
|
public void
|
|
mousePressed(MouseEvent eM)
|
|
{
|
|
assert eM.getSource() instanceof JTextComponent;
|
|
if (!eM.isPopupTrigger()) return;
|
|
show((Component)eM.getSource(), eM.getX(), eM.getY());
|
|
}
|
|
|
|
public void
|
|
mouseReleased(MouseEvent eM)
|
|
{
|
|
if (eM.getClickCount() == 0) setVisible(false);
|
|
}
|
|
|
|
public void
|
|
mouseClicked(MouseEvent eM) { }
|
|
|
|
public void
|
|
mouseEntered(MouseEvent eM) { }
|
|
|
|
public void
|
|
mouseExited(MouseEvent eM) { }
|
|
|
|
public void
|
|
actionPerformed(ActionEvent eA)
|
|
{
|
|
assert getInvoker() instanceof JTextComponent;
|
|
JTextComponent inv = (JTextComponent)getInvoker();
|
|
|
|
if (eA.getSource() == copy) inv.copy();
|
|
if (eA.getSource() == cut) inv.cut();
|
|
if (eA.getSource() == paste) inv.paste();
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
public
|
|
TextActionPopupMenu()
|
|
{
|
|
copy = new JMenuItem("Copy");
|
|
cut = new JMenuItem("Cut");
|
|
paste = new JMenuItem("Paste");
|
|
copy.addActionListener(this);
|
|
cut.addActionListener(this);
|
|
paste.addActionListener(this);
|
|
|
|
add(cut);
|
|
add(copy);
|
|
add(paste);
|
|
}
|
|
|
|
}
|
|
|