mirror of
https://gitlab.com/biskuteri-cafe/JKomasto2.git
synced 2024-11-20 04:04:51 +01:00
761 lines
20 KiB
Java
761 lines
20 KiB
Java
/* 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 */
|
|
|
|
import javax.swing.JFrame;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JMenuBar;
|
|
import javax.swing.JPopupMenu;
|
|
import javax.swing.JMenuItem;
|
|
import javax.swing.JSeparator;
|
|
import javax.swing.JButton;
|
|
import javax.swing.JLabel;
|
|
import javax.swing.JScrollPane;
|
|
import javax.swing.JScrollBar;
|
|
import javax.swing.Box;
|
|
import javax.swing.BoxLayout;
|
|
import javax.swing.BorderFactory;
|
|
import javax.swing.border.Border;
|
|
import javax.swing.JOptionPane;
|
|
import javax.swing.ImageIcon;
|
|
import javax.swing.MenuSelectionManager;
|
|
import javax.swing.MenuElement;
|
|
import java.awt.Graphics;
|
|
import java.awt.Font;
|
|
import java.awt.FontMetrics;
|
|
import java.awt.Shape;
|
|
import java.awt.Dimension;
|
|
import java.awt.BorderLayout;
|
|
import java.awt.GridLayout;
|
|
import java.awt.Cursor;
|
|
import java.awt.Image;
|
|
import java.awt.Component;
|
|
import java.awt.event.ActionListener;
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.MediaTracker;
|
|
import java.util.List;
|
|
import java.util.ArrayList;
|
|
import java.net.URL;
|
|
import java.net.MalformedURLException;
|
|
import java.io.IOException;
|
|
import cafe.biskuteri.hinoki.Tree;
|
|
import java.text.BreakIterator;
|
|
import java.util.Locale;
|
|
import java.time.ZonedDateTime;
|
|
import java.time.ZoneId;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
import java.util.Map;
|
|
import java.util.HashMap;
|
|
|
|
|
|
class
|
|
PostWindow extends JFrame {
|
|
|
|
private JKomasto
|
|
primaire;
|
|
|
|
private MastodonApi
|
|
api;
|
|
|
|
private Post
|
|
post,
|
|
wrapperPost;
|
|
|
|
// - -%- -
|
|
|
|
private PostComponent
|
|
display;
|
|
|
|
// - -%- -
|
|
|
|
private static final DateTimeFormatter
|
|
DATE_FORMAT = DateTimeFormatter.ofPattern("d LLLL ''uu"),
|
|
TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm");
|
|
|
|
// ---%-@-%---
|
|
|
|
public synchronized void
|
|
use(Post post)
|
|
{
|
|
assert post != null;
|
|
|
|
if (post.boostedPost != null)
|
|
{
|
|
wrapperPost = post;
|
|
this.post = post.boostedPost;
|
|
post = post.boostedPost;
|
|
}
|
|
else
|
|
{
|
|
wrapperPost = null;
|
|
this.post = post;
|
|
}
|
|
|
|
display.setAuthorName(post.author.name);
|
|
display.setAuthorId(post.author.id);
|
|
|
|
String oid = api.getAccountDetails().get("id").value;
|
|
String aid = post.author.numId;
|
|
display.setDeleteEnabled(aid.equals(oid));
|
|
|
|
post.author.resolveAvatar();
|
|
display.setAuthorAvatar(post.author.avatar);
|
|
|
|
display.setDate(post.date);
|
|
display.setTime(post.time);
|
|
|
|
display.setEmojiUrls(post.emojiUrls);
|
|
|
|
display.setHtml(post.text);
|
|
display.setFavourited(post.favourited);
|
|
display.setBoosted(post.boosted);
|
|
|
|
if (post.attachments.length > 0)
|
|
{
|
|
post.attachments[0].resolveImage();
|
|
display.setMediaPreview(post.attachments[0].image);
|
|
}
|
|
else display.setMediaPreview(null);
|
|
|
|
post.resolveApproximateText();
|
|
this.setTitle(post.approximateText);
|
|
|
|
display.resetFocus();
|
|
repaint();
|
|
}
|
|
|
|
public void
|
|
readEntity(Tree<String> post)
|
|
{
|
|
use(new Post(post));
|
|
}
|
|
|
|
public synchronized void
|
|
openAuthorProfile()
|
|
{
|
|
ProfileWindow w = new ProfileWindow(primaire);
|
|
w.use(post.author);
|
|
w.setLocationRelativeTo(this);
|
|
w.setVisible(true);
|
|
}
|
|
|
|
public synchronized void
|
|
favourite(boolean favourited)
|
|
{
|
|
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
|
display.setFavouriteBoostEnabled(false);
|
|
display.paintImmediately(display.getBounds());
|
|
RequestListener handler = new RequestListener() {
|
|
|
|
public void
|
|
connectionFailed(IOException eIo)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
PostWindow.this,
|
|
"Tried to favourite post, failed.."
|
|
+ "\n" + eIo.getClass() + ": " + eIo.getMessage()
|
|
);
|
|
}
|
|
|
|
public void
|
|
requestFailed(int httpCode, Tree<String> json)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
PostWindow.this,
|
|
"Tried to favourite post, failed.."
|
|
+ "\n" + json.get("error").value
|
|
+ "\n(HTTP error code: " + httpCode + ")"
|
|
);
|
|
}
|
|
|
|
public void
|
|
requestSucceeded(Tree<String> json)
|
|
{
|
|
PostWindow.this.post.favourited = favourited;
|
|
}
|
|
|
|
};
|
|
api.setPostFavourited(post.id, favourited, handler);
|
|
display.setCursor(null);
|
|
display.setFavouriteBoostEnabled(true);
|
|
display.repaint();
|
|
}
|
|
|
|
public synchronized void
|
|
boost(boolean boosted)
|
|
{
|
|
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
|
display.setFavouriteBoostEnabled(false);
|
|
display.paintImmediately(display.getBounds());
|
|
RequestListener handler = new RequestListener() {
|
|
|
|
public void
|
|
connectionFailed(IOException eIo)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
PostWindow.this,
|
|
"Tried to boost post, failed.."
|
|
+ "\n" + eIo.getClass() + ": " + eIo.getMessage()
|
|
);
|
|
}
|
|
|
|
public void
|
|
requestFailed(int httpCode, Tree<String> json)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
PostWindow.this,
|
|
"Tried to boost post, failed.."
|
|
+ "\n" + json.get("error").value
|
|
+ "\n(HTTP error code: " + httpCode + ")"
|
|
);
|
|
}
|
|
|
|
public void
|
|
requestSucceeded(Tree<String> json)
|
|
{
|
|
PostWindow.this.post.boosted = boosted;
|
|
}
|
|
|
|
};
|
|
api.setPostBoosted(post.id, boosted, handler);
|
|
display.setCursor(null);
|
|
display.setFavouriteBoostEnabled(true);
|
|
display.repaint();
|
|
}
|
|
|
|
public synchronized void
|
|
reply()
|
|
{
|
|
String ownId = api.getAccountDetails().get("acct").value;
|
|
Composition c = Composition.reply(this.post, ownId);
|
|
ComposeWindow w = primaire.getComposeWindow();
|
|
w.setComposition(c);
|
|
if (!w.isVisible())
|
|
{
|
|
w.setLocation(getX(), getY() + 100);
|
|
w.setVisible(true);
|
|
}
|
|
}
|
|
|
|
public synchronized void
|
|
openMedia()
|
|
{
|
|
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
|
|
|
for (Attachment a: post.attachments) a.resolveImage();
|
|
|
|
ImageWindow w = new ImageWindow();
|
|
w.setTitle("Media - " + this.getTitle());
|
|
w.setIconImage(this.getIconImage());
|
|
w.showAttachments(post.attachments);
|
|
w.setLocationRelativeTo(null);
|
|
w.setVisible(true);
|
|
|
|
display.setCursor(null);
|
|
}
|
|
|
|
public synchronized void
|
|
deletePost(boolean redraft)
|
|
{
|
|
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
|
display.setDeleteEnabled(false);
|
|
display.paintImmediately(display.getBounds());
|
|
|
|
final String S1 =
|
|
"Are you sure you'd like to delete this post?\n";
|
|
final String S2 =
|
|
"Are you sure you'd like to delete this post?\n"
|
|
+ "You are redrafting, so a composition window\n"
|
|
+ "should open with its contents filled.";
|
|
JOptionPane dialog = new JOptionPane();
|
|
dialog.setMessageType(JOptionPane.QUESTION_MESSAGE);
|
|
dialog.setMessage(redraft ? S2 : S1);
|
|
dialog.setOptions(new String[] { "No", "Yes" });
|
|
String title = "Confirm delete";
|
|
dialog.createDialog(this, title).setVisible(true);
|
|
if (!dialog.getValue().equals("Yes"))
|
|
{
|
|
display.setCursor(null);
|
|
display.setDeleteEnabled(true);
|
|
display.paintImmediately(display.getBounds());
|
|
return;
|
|
}
|
|
|
|
api.deletePost(post.id, new RequestListener() {
|
|
|
|
public void
|
|
connectionFailed(IOException eIo)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
PostWindow.this,
|
|
"Failed to delete post.."
|
|
+ "\n" + eIo.getMessage()
|
|
);
|
|
}
|
|
|
|
public void
|
|
requestFailed(int httpCode, Tree<String> json)
|
|
{
|
|
JOptionPane.showMessageDialog(
|
|
PostWindow.this,
|
|
"Failed to delete post.."
|
|
+ "\n" + json.get("error").value
|
|
+ "\n(HTTP code: " + httpCode + ")"
|
|
);
|
|
}
|
|
|
|
public void
|
|
requestSucceeded(Tree<String> json)
|
|
{
|
|
setVisible(false);
|
|
|
|
if (redraft)
|
|
{
|
|
Composition c = Composition.recover(json);
|
|
ComposeWindow w = new ComposeWindow(primaire);
|
|
w.setComposition(c);
|
|
w.setLocation(getX(), getY() + 100);
|
|
w.setVisible(true);
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
display.setCursor(null);
|
|
display.setDeleteEnabled(true);
|
|
display.paintImmediately(display.getBounds());
|
|
if (!isVisible()) dispose();
|
|
}
|
|
|
|
public synchronized void
|
|
copyPostId()
|
|
{
|
|
ClipboardApi.serve(post.id);
|
|
}
|
|
|
|
public synchronized void
|
|
copyPostLink()
|
|
{
|
|
ClipboardApi.serve(post.uri);
|
|
}
|
|
|
|
public synchronized void
|
|
openReplies()
|
|
{
|
|
RepliesWindow w = new RepliesWindow(primaire, this);
|
|
w.showFor(post.id);
|
|
w.setLocation(getX(), getY() + 100);
|
|
w.setVisible(true);
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
PostWindow(JKomasto primaire)
|
|
{
|
|
this.primaire = primaire;
|
|
this.api = primaire.getMastodonApi();
|
|
|
|
getContentPane().setPreferredSize(new Dimension(360, 260));
|
|
pack();
|
|
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
|
setLocationByPlatform(true);
|
|
|
|
display = new PostComponent(this);
|
|
|
|
setContentPane(display);
|
|
setIconImage(primaire.getProgramIcon());
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class
|
|
PostComponent extends JPanel
|
|
implements ActionListener {
|
|
|
|
private PostWindow
|
|
primaire;
|
|
|
|
// - -%- -
|
|
|
|
private List<RichTextPane.Segment>
|
|
authorNameOr;
|
|
|
|
private RichTextPane
|
|
authorName;
|
|
|
|
private RichTextPane3
|
|
body;
|
|
|
|
private JScrollPane
|
|
bodyScrollPane;
|
|
|
|
private JLabel
|
|
authorId, time, date;
|
|
|
|
private String[][]
|
|
emojiUrls;
|
|
|
|
private TwoToggleButton
|
|
favouriteBoost,
|
|
replyMisc,
|
|
nextPrev;
|
|
|
|
private RoundButton
|
|
profile,
|
|
media;
|
|
|
|
private JPopupMenu
|
|
miscMenu;
|
|
|
|
private JMenuItem
|
|
openReplies,
|
|
copyPostId,
|
|
copyPostLink,
|
|
deletePost,
|
|
redraftPost;
|
|
|
|
private Image
|
|
backgroundImage;
|
|
|
|
// ---%-@-%---
|
|
|
|
public void
|
|
setAuthorName(String n)
|
|
{
|
|
authorNameOr = new RichTextPane.Builder().text(n).finish();
|
|
}
|
|
|
|
public void
|
|
setAuthorId(String n) { authorId.setText(n); }
|
|
|
|
public void
|
|
setAuthorAvatar(Image n) { profile.setImage(n); }
|
|
|
|
public void
|
|
setDate(String n) { date.setText(n); }
|
|
|
|
public void
|
|
setTime(String n) { time.setText(n); }
|
|
|
|
public void
|
|
setEmojiUrls(String[][] n)
|
|
{
|
|
emojiUrls = n;
|
|
|
|
Map<String, Image> emojis = new HashMap<>();
|
|
for (String[] entry: n)
|
|
{
|
|
emojis.put(entry[0], ImageApi.remote(entry[1]));
|
|
}
|
|
body.setEmojis(emojis);
|
|
}
|
|
|
|
public void
|
|
setHtml(String n)
|
|
{
|
|
body.setText(BasicHTMLParser.parse(n));
|
|
}
|
|
|
|
public void
|
|
setFavourited(boolean a)
|
|
{
|
|
favouriteBoost.removeActionListener(this);
|
|
favouriteBoost.setPrimaryToggled(a);
|
|
favouriteBoost.addActionListener(this);
|
|
}
|
|
|
|
public void
|
|
setBoosted(boolean a)
|
|
{
|
|
favouriteBoost.removeActionListener(this);
|
|
favouriteBoost.setSecondaryToggled(a);
|
|
favouriteBoost.addActionListener(this);
|
|
}
|
|
|
|
public void
|
|
setFavouriteBoostEnabled(boolean a)
|
|
{
|
|
favouriteBoost.setEnabled(a);
|
|
}
|
|
|
|
public void
|
|
setDeleteEnabled(boolean a)
|
|
{
|
|
deletePost.setEnabled(a);
|
|
redraftPost.setEnabled(a);
|
|
}
|
|
|
|
public void
|
|
setMediaPreview(Image n) { media.setImage(n); }
|
|
|
|
public void
|
|
resetFocus()
|
|
{
|
|
media.requestFocusInWindow();
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
public void
|
|
actionPerformed(ActionEvent eA)
|
|
{
|
|
Component src = (Component)eA.getSource();
|
|
String command = eA.getActionCommand();
|
|
|
|
if (src == profile)
|
|
{
|
|
primaire.openAuthorProfile();
|
|
return;
|
|
}
|
|
|
|
if (src == favouriteBoost)
|
|
{
|
|
if (command.equals("favouriteOn"))
|
|
primaire.favourite(true);
|
|
if (command.equals("favouriteOff"))
|
|
primaire.favourite(false);
|
|
if (command.equals("boostOn"))
|
|
primaire.boost(true);
|
|
if (command.equals("boostOff"))
|
|
primaire.boost(false);
|
|
return;
|
|
}
|
|
|
|
if (src == replyMisc)
|
|
{
|
|
if (miscMenu.isVisible())
|
|
{
|
|
Component sel = getSelected(miscMenu);
|
|
if (sel == null) return;
|
|
assert sel instanceof JMenuItem;
|
|
((JMenuItem)sel).doClick();
|
|
}
|
|
else if (command.startsWith("reply"))
|
|
{
|
|
primaire.reply();
|
|
}
|
|
else if (command.startsWith("misc"))
|
|
{
|
|
int rx = replyMisc.getWidth() / 2;
|
|
int ry = replyMisc.getHeight() - miscMenu.getHeight();
|
|
miscMenu.show(replyMisc, rx, ry);
|
|
}
|
|
return;
|
|
}
|
|
else miscMenu.setVisible(false);
|
|
|
|
if (src == nextPrev)
|
|
{
|
|
if (command.startsWith("next"))
|
|
{
|
|
body.nextPage();
|
|
}
|
|
else
|
|
{
|
|
body.previousPage();
|
|
}
|
|
// First time an interactive element
|
|
// doesn't call something in primaire..
|
|
return;
|
|
}
|
|
|
|
if (src == media)
|
|
{
|
|
primaire.openMedia();
|
|
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);
|
|
|
|
}
|
|
|
|
protected void
|
|
paintComponent(Graphics g)
|
|
{
|
|
g.clearRect(0, 0, getWidth(), getHeight());
|
|
|
|
int w1 = authorName.getWidth();
|
|
FontMetrics fm1 = getFontMetrics(authorName.getFont());
|
|
List<RichTextPane.Segment> lay1;
|
|
lay1 = RichTextPane.layout(authorNameOr, fm1, w1);
|
|
authorName.setText(lay1);
|
|
|
|
if (backgroundImage != null)
|
|
{
|
|
int tw = backgroundImage.getWidth(this);
|
|
int th = backgroundImage.getHeight(this);
|
|
if (tw != -1)
|
|
for (int y = 0; y < getHeight(); y += th)
|
|
for (int x = 0; x < getWidth(); x += tw)
|
|
{
|
|
g.drawImage(backgroundImage, x, y, this);
|
|
}
|
|
}
|
|
|
|
((java.awt.Graphics2D)g).setRenderingHint(
|
|
java.awt.RenderingHints.KEY_ANTIALIASING,
|
|
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
|
|
);
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
private static Component
|
|
getSelected(JPopupMenu menu)
|
|
{
|
|
MenuElement[] sel =
|
|
MenuSelectionManager.defaultManager()
|
|
.getSelectedPath();
|
|
/*
|
|
* (知) For some reason, the selection model of the
|
|
* JPopupMenu doesn't do anything. So we have to
|
|
* consult this apparently global menu manager.
|
|
*/
|
|
for (int o = 0; o < sel.length - 1; ++o)
|
|
{
|
|
if (sel[o] == menu)
|
|
return sel[o + 1].getComponent();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
PostComponent(PostWindow primaire)
|
|
{
|
|
this.primaire = primaire;
|
|
|
|
emojiUrls = new String[0][];
|
|
|
|
Border b = BorderFactory.createEmptyBorder(10, 10, 10, 10);
|
|
Font f1 = new Font("MotoyaLMaru", Font.PLAIN, 18);
|
|
Font f2 = new Font("MotoyaLMaru", Font.PLAIN, 14);
|
|
Font f3 = new Font("MotoyaLMaru", Font.PLAIN, 18);
|
|
|
|
profile = new RoundButton();
|
|
favouriteBoost = new TwoToggleButton("favourite", "boost");
|
|
replyMisc = new TwoToggleButton("reply", "misc");
|
|
nextPrev = new TwoToggleButton("next", "prev");
|
|
media = new RoundButton();
|
|
profile.addActionListener(this);
|
|
favouriteBoost.addActionListener(this);
|
|
replyMisc.addActionListener(this);
|
|
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();
|
|
buttons.setOpaque(false);
|
|
buttons.add(profile);
|
|
buttons.add(Box.createVerticalStrut(8));
|
|
buttons.add(favouriteBoost);
|
|
buttons.add(Box.createVerticalStrut(8));
|
|
buttons.add(replyMisc);
|
|
buttons.add(Box.createVerticalStrut(8));
|
|
buttons.add(nextPrev);
|
|
buttons.add(Box.createVerticalStrut(8));
|
|
buttons.add(media);
|
|
buttons.setMaximumSize(buttons.getPreferredSize());
|
|
Box left = Box.createVerticalBox();
|
|
left.setOpaque(false);
|
|
left.add(buttons);
|
|
|
|
authorId = new JLabel();
|
|
authorName = new RichTextPane();
|
|
time = new JLabel();
|
|
date = new JLabel();
|
|
authorId.setFont(f2);
|
|
date.setFont(f2);
|
|
authorName.setFont(f1);
|
|
time.setFont(f1);
|
|
|
|
JPanel top1 = new JPanel();
|
|
top1.setOpaque(false);
|
|
top1.setLayout(new BorderLayout(8, 0));
|
|
top1.add(authorId);
|
|
top1.add(date, BorderLayout.EAST);
|
|
JPanel top2 = new JPanel();
|
|
top2.setOpaque(false);
|
|
top2.setLayout(new BorderLayout(8, 0));
|
|
top2.add(authorName);
|
|
top2.add(time, BorderLayout.EAST);
|
|
Box top = Box.createVerticalBox();
|
|
top.add(top1);
|
|
top.add(Box.createVerticalStrut(2));
|
|
top.add(top2);
|
|
|
|
body = new RichTextPane3();
|
|
body.setFont(f3);
|
|
|
|
/*
|
|
bodyScrollPane = new JScrollPane(
|
|
body,
|
|
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
|
|
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
|
|
);
|
|
JScrollBar vsb = bodyScrollPane.getVerticalScrollBar();
|
|
vsb.setPreferredSize(new Dimension(0, 0));
|
|
vsb.setUnitIncrement(16);
|
|
bodyScrollPane.setBorder(null);
|
|
bodyScrollPane.setFocusable(true);
|
|
*/
|
|
|
|
JPanel centre = new JPanel();
|
|
centre.setOpaque(false);
|
|
centre.setLayout(new BorderLayout(0, 8));
|
|
centre.add(top, BorderLayout.NORTH);
|
|
centre.add(body);
|
|
|
|
setLayout(new BorderLayout(8, 0));
|
|
add(left, BorderLayout.WEST);
|
|
add(centre);
|
|
|
|
setBorder(b);
|
|
|
|
backgroundImage = ImageApi.local("postWindow");
|
|
}
|
|
|
|
}
|