Package frost.messaging.frost.gui

Source Code of frost.messaging.frost.gui.MessageFrame

/*
MessageFrame.java / Frost
Copyright (C) 2001  Frost Project <jtcfrost.sourceforge.net>

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 2 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, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package frost.messaging.frost.gui;

import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.TreeMap;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.ComboBoxEditor;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.WindowConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.Document;
import javax.swing.text.DocumentFilter;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;

import frost.Core;
import frost.MainFrame;
import frost.SettingsClass;
import frost.ext.AltEdit;
import frost.ext.AltEditCallbackInterface;
import frost.gui.BoardsChooser;
import frost.gui.ScrollableBar;
import frost.gui.SmileyChooserDialog;
import frost.gui.SortedTable;
import frost.gui.model.SortedTableModel;
import frost.gui.model.TableMember;
import frost.identities.Identity;
import frost.identities.LocalIdentity;
import frost.messaging.frost.BoardAttachment;
import frost.messaging.frost.FileAttachment;
import frost.messaging.frost.FrostMessageObject;
import frost.messaging.frost.FrostUnsentMessageObject;
import frost.messaging.frost.UnsentMessagesManager;
import frost.messaging.frost.boards.Board;
import frost.storage.perst.messages.MessageStorage;
import frost.util.DateFun;
import frost.util.FileAccess;
import frost.util.Mixed;
import frost.util.gui.ImmutableArea;
import frost.util.gui.ImmutableAreasDocument;
import frost.util.gui.JSkinnablePopupMenu;
import frost.util.gui.MiscToolkit;
import frost.util.gui.TextComponentClipboardMenu;
import frost.util.gui.textpane.AntialiasedTextArea;
import frost.util.gui.translation.Language;
import frost.util.gui.translation.LanguageEvent;
import frost.util.gui.translation.LanguageListener;

@SuppressWarnings("serial")
public class MessageFrame extends JFrame implements AltEditCallbackInterface {

    private static final Logger logger = Logger.getLogger(MessageFrame.class.getName());

    private final Language language;

    private final Listener listener = new Listener();

    private boolean initialized = false;

    private final Window parentWindow;

    private Board board;
    private String repliedMsgId;
    private final SettingsClass frostSettings;

    private MFAttachedBoardsTable boardsTable;
    private MFAttachedFilesTable filesTable;
    private MFAttachedBoardsTableModel boardsTableModel;
    private MFAttachedFilesTableModel filesTableModel;

    private JSplitPane messageSplitPane = null;
    private JSplitPane attachmentsSplitPane = null;
    private JScrollPane filesTableScrollPane;
    private JScrollPane boardsTableScrollPane;

    private JSkinnablePopupMenu attFilesPopupMenu;
    private JSkinnablePopupMenu attBoardsPopupMenu;
    private MessageBodyPopupMenu messageBodyPopupMenu;

    private final JButton Bsend = new JButton(MiscToolkit.loadImageIcon("/data/toolbar/mail-forward.png"));
    private final JButton Bcancel = new JButton(MiscToolkit.loadImageIcon("/data/toolbar/user-trash.png"));
    private final JButton BattachFile = new JButton(MiscToolkit.loadImageIcon("/data/toolbar/mail-attachment.png"));
    private final JButton BattachBoard = new JButton(MiscToolkit.loadImageIcon("/data/toolbar/internet-group-chat.png"));

    private final JCheckBox sign = new JCheckBox();
    private final JCheckBox encrypt = new JCheckBox();
    private JComboBox buddies;

    private final JLabel Lboard = new JLabel();
    private final JLabel Lfrom = new JLabel();
    private final JLabel Lsubject = new JLabel();
    private final JTextField TFboard = new JTextField(); // Board (To)
    private final JTextField subjectTextField = new JTextField(); // Subject
    private final JButton BchooseSmiley = new JButton(MiscToolkit.loadImageIcon("/data/toolbar/face-smile.png"));

    private final AntialiasedTextArea messageTextArea = new AntialiasedTextArea(); // Text
    private ImmutableArea headerArea = null;
//    private TextHighlighter textHighlighter = null;
    private String oldSender = null;
    private String currentSignature = null;

    private FrostMessageObject repliedMessage = null;

    private JComboBox ownIdentitiesComboBox = null;

    private static int openInstanceCount = 0;

    public MessageFrame(final SettingsClass newSettings, final Window tparentWindow) {
        super();
        parentWindow = tparentWindow;
        this.language = Language.getInstance();
        frostSettings = newSettings;

        incOpenInstanceCount();

        final String fontName = frostSettings.getValue(SettingsClass.MESSAGE_BODY_FONT_NAME);
        final int fontStyle = frostSettings.getIntValue(SettingsClass.MESSAGE_BODY_FONT_STYLE);
        final int fontSize = frostSettings.getIntValue(SettingsClass.MESSAGE_BODY_FONT_SIZE);
        Font tofFont = new Font(fontName, fontStyle, fontSize);
        if (!tofFont.getFamily().equals(fontName)) {
            logger.severe("The selected font was not found in your system\n"
                    + "That selection will be changed to \"Monospaced\".");
            frostSettings.setValue(SettingsClass.MESSAGE_BODY_FONT_NAME, "Monospaced");
            tofFont = new Font("Monospaced", fontStyle, fontSize);
        }
        messageTextArea.setFont(tofFont);
        messageTextArea.setAntiAliasEnabled(frostSettings.getBoolValue(SettingsClass.MESSAGE_BODY_ANTIALIAS));
        final ImmutableAreasDocument messageDocument = new ImmutableAreasDocument();
        headerArea = new ImmutableArea(messageDocument);
        messageDocument.addImmutableArea(headerArea); // user must not change the header of the message
        messageTextArea.setDocument(messageDocument);
//        textHighlighter = new TextHighlighter(Color.LIGHT_GRAY);
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(final WindowEvent e) {
                windowIsClosing();
            }
            @Override
            public void windowClosed(final WindowEvent e) {
                windowWasClosed();
            }
        });
        setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
    }

    private void windowIsClosing() {
        final String title = language.getString("MessageFrame.discardMessage.title");
        final String text = language.getString("MessageFrame.discardMessage.text");
        final int answer = JOptionPane.showConfirmDialog(
                this,
                text,
                title,
                JOptionPane.YES_NO_OPTION,
                JOptionPane.WARNING_MESSAGE);
        if( answer == JOptionPane.YES_OPTION ) {
            dispose();
        }
    }

    private void windowWasClosed() {
        decOpenInstanceCount();
    }

    private void attachBoards_actionPerformed(final ActionEvent e) {

        // get and sort all boards
        final List<Board> allBoards = MainFrame.getInstance().getFrostMessageTab().getTofTreeModel().getAllBoards();
        if (allBoards.size() == 0) {
            return;
        }
        Collections.sort(allBoards);

        final BoardsChooser chooser = new BoardsChooser(this, allBoards);
        chooser.setLocationRelativeTo(this);
        final List<Board> chosenBoards = chooser.runDialog();
        if (chosenBoards == null || chosenBoards.size() == 0) { // nothing chosed or cancelled
            return;
        }

        for (int i = 0; i < chosenBoards.size(); i++) {
            final Board chosedBoard = chosenBoards.get(i);

            String privKey = chosedBoard.getPrivateKey();

            if (privKey != null) {
                final int answer =
                    JOptionPane.showConfirmDialog(this,
                        language.formatMessage("MessageFrame.attachBoard.sendPrivateKeyConfirmationDialog.body", chosedBoard.getName()),
                        language.getString("MessageFrame.attachBoard.sendPrivateKeyConfirmationDialog.title"),
                        JOptionPane.YES_NO_OPTION);
                if (answer == JOptionPane.NO_OPTION) {
                    privKey = null; // don't provide privkey
                }
            }
            // build a new board because maybe privKey shouldn't be uploaded
            final Board aNewBoard =
                new Board(chosedBoard.getName(), chosedBoard.getPublicKey(), privKey, chosedBoard.getDescription());
            final MFAttachedBoard ab = new MFAttachedBoard(aNewBoard);
            boardsTableModel.addRow(ab);
        }
        positionDividers();
    }

    private void attachFile_actionPerformed(final ActionEvent e) {
        final String lastUsedDirectory = frostSettings.getValue(SettingsClass.DIR_LAST_USED);
        final JFileChooser fc = new JFileChooser(lastUsedDirectory);
        fc.setDialogTitle(language.getString("MessageFrame.fileChooser.title"));
        fc.setFileHidingEnabled(false);
        fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
        fc.setMultiSelectionEnabled(true);

        final int returnVal = fc.showOpenDialog(MessageFrame.this);
        if( returnVal == JFileChooser.APPROVE_OPTION ) {
            final File[] selectedFiles = fc.getSelectedFiles();
            for( final File element : selectedFiles ) {
                // for convinience remember last used directory
                frostSettings.setValue(SettingsClass.DIR_LAST_USED, element.getPath());

                // collect all choosed files + files in all choosed directories
                final List<File> allFiles = FileAccess.getAllEntries(element);
                for (int j = 0; j < allFiles.size(); j++) {
                    final File aFile = allFiles.get(j);
                    final MFAttachedFile af = new MFAttachedFile( aFile );
                    filesTableModel.addRow( af );
                }
            }
        }
        positionDividers();
    }

    /**
     * Finally called to start composing a message. Uses alternate editor if configured.
     */
    private void composeMessage(
            final Board newBoard,
            final String newSubject,
            final String inReplyTo,
            String newText,
            final boolean isReply,
            final Identity recipient,
            final LocalIdentity senderId,   // if given compose encrypted reply
            final FrostMessageObject msg) {

        repliedMessage = msg;

        if (isReply) {
            newText += "\n\n";
        }

        if (frostSettings.getBoolValue("useAltEdit")) {
            // build our transfer object that the parser will provide us in its callback
            final TransferObject to = new TransferObject();
            to.newBoard = newBoard;
            to.newSubject = newSubject;
            to.inReplyTo = inReplyTo;
            to.newText = newText;
            to.isReply = isReply;
            to.recipient = recipient;
            to.senderId = senderId;
            // create a temporary editText that is show in alternate editor
            // the editor will return only new text to us
            final DateTime now = new DateTime(DateTimeZone.UTC);
            final String date = DateFun.FORMAT_DATE_EXT.print(now)
            + " - "
            + DateFun.FORMAT_TIME_EXT.print(now);
            final String fromLine = "----- (sender) ----- " + date + " -----";
            final String editText = newText + fromLine + "\n\n";

            final AltEdit ae = new AltEdit(newSubject, editText, MainFrame.getInstance(), to, this);
            ae.start();
        } else {
            // invoke frame directly, no alternate editor
            composeMessageContinued(newBoard, newSubject, inReplyTo, newText, null, isReply, recipient, senderId);
        }
    }

    public void altEditCallback(final Object toObj, String newAltSubject, final String newAltText) {
        final TransferObject to = (TransferObject)toObj;
        if( newAltSubject == null ) {
            newAltSubject = to.newSubject; // use original subject
        }
        composeMessageContinued(
                to.newBoard,
                newAltSubject,
                to.inReplyTo,
                to.newText,
                newAltText,
                to.isReply,
                to.recipient,
                to.senderId);
    }

    /**
     * This method is either invoked by ComposeMessage OR by the callback of the AltEdit class.
     */
    private void composeMessageContinued(
        final Board newBoard,
        final String newSubject,
        final String inReplyTo,
        String newText,
        final String altEditText,
        final boolean isReply,
        final Identity recipient,       // if given compose encrypted reply
        final LocalIdentity senderId)   // if given compose encrypted reply
    {
        headerArea.setEnabled(false);
        board = newBoard;
        repliedMsgId = inReplyTo; // maybe null

        String from;
        boolean isInitializedSigned;
        if( senderId != null ) {
            // encrypted reply!
            from = senderId.getUniqueName();
            isInitializedSigned = true;
        } else {
            // use remembered sender name, maybe per board
            String userName = Core.frostSettings.getValue("userName."+board.getBoardFilename());
            if( userName == null || userName.length() == 0 ) {
                userName = Core.frostSettings.getValue(SettingsClass.LAST_USED_FROMNAME);
            }
            if( Core.getIdentities().isMySelf(userName) ) {
                // isSigned
                from = userName;
                isInitializedSigned = true;
            } else if( userName.indexOf("@") > 0 ) {
                // invalid, only LocalIdentities are allowed to contain an @
                from = "Anonymous";
                isInitializedSigned = false;
            } else {
                from = userName;
                isInitializedSigned = false;
            }
        }
        oldSender = from;

        enableEvents(AWTEvent.WINDOW_EVENT_MASK);
        try {
            initialize(newBoard, newSubject);
        } catch (final Exception e) {
            logger.log(Level.SEVERE, "Exception thrown in composeMessage(...)", e);
        }

        sign.setEnabled(false);

        final ImageIcon signedIcon = MiscToolkit.loadImageIcon("/data/signed.gif");
        final ImageIcon unsignedIcon = MiscToolkit.loadImageIcon("/data/unsigned.gif");
        sign.setDisabledSelectedIcon(signedIcon);
        sign.setDisabledIcon(unsignedIcon);
        sign.setSelectedIcon(signedIcon);
        sign.setIcon(unsignedIcon);

        sign.addItemListener(new ItemListener() {
            public void itemStateChanged(final ItemEvent e) {
                updateSignToolTip();
            }
        });

        // maybe prepare to reply to an encrypted message
        if( recipient != null ) {
            // set correct sender identity
            for(int x=0; x < getOwnIdentitiesComboBox().getItemCount(); x++) {
                final Object obj = getOwnIdentitiesComboBox().getItemAt(x);
                if( obj instanceof LocalIdentity ) {
                    final LocalIdentity li = (LocalIdentity)obj;
                    if( senderId.getUniqueName().equals(li.getUniqueName()) ) {
                        getOwnIdentitiesComboBox().setSelectedIndex(x);
                        break;
                    }
                }
            }
            getOwnIdentitiesComboBox().setEnabled(false);
            // set and lock controls (after we set the identity, the itemlistener would reset the controls!)
            sign.setSelected(true);
            encrypt.setSelected(true);
            buddies.removeAllItems();
            buddies.addItem(recipient);
            buddies.setSelectedItem(recipient);
            // dont allow to disable signing/encryption
            encrypt.setEnabled(false);
            buddies.setEnabled(false);
        } else {
            if( isInitializedSigned ) {
                // set saved sender identity
                for(int x=0; x < getOwnIdentitiesComboBox().getItemCount(); x++) {
                    final Object obj = getOwnIdentitiesComboBox().getItemAt(x);
                    if( obj instanceof LocalIdentity ) {
                        final LocalIdentity li = (LocalIdentity)obj;
                        if( from.equals(li.getUniqueName()) ) {
                            getOwnIdentitiesComboBox().setSelectedIndex(x);
                            sign.setSelected(true);
                            getOwnIdentitiesComboBox().setEditable(false);
                            break;
                        }
                    }
                }
            } else {
                // initialized unsigned/anonymous
                getOwnIdentitiesComboBox().setSelectedIndex(0);
                getOwnIdentitiesComboBox().getEditor().setItem(from);
                sign.setSelected(false);
                getOwnIdentitiesComboBox().setEditable(true);
            }

            if( sign.isSelected() && buddies.getItemCount() > 0 ) {
                encrypt.setEnabled(true);
            } else {
                encrypt.setEnabled(false);
            }
            encrypt.setSelected(false);
            buddies.setEnabled(false);
        }

        updateSignToolTip();

        // prepare message text
        final DateTime now = new DateTime(DateTimeZone.UTC);
        final String date = DateFun.FORMAT_DATE_EXT.print(now)
                        + " - "
                        + DateFun.FORMAT_TIME_EXT.print(now);
        final String fromLine = "----- " + from + " ----- " + date + " -----";

        final int headerAreaStart = newText.length();// begin of non-modifiable area
        newText += fromLine + "\n\n";
        final int headerAreaEnd = newText.length() - 2; // end of non-modifiable area

        if( altEditText != null ) {
            newText += altEditText; // maybe append text entered in alternate editor
        }

        // later set cursor to this position in text
        final int caretPos = newText.length();

        // set sig if msg is marked as signed
        currentSignature = null;
        if( sign.isSelected()  ) {
            // maybe append a signature
            final LocalIdentity li = (LocalIdentity)getOwnIdentitiesComboBox().getSelectedItem();
            if( li.getSignature() != null ) {
                currentSignature = "\n-- \n" + li.getSignature();
                newText += currentSignature;
            }
        }

        messageTextArea.setText(newText);
        headerArea.setStartPos(headerAreaStart);
        headerArea.setEndPos(headerAreaEnd);
        headerArea.setEnabled(true);

//        textHighlighter.highlight(messageTextArea, headerAreaStart, headerAreaEnd-headerAreaStart, true);

        setVisible(true);

        // reset the splitpanes
        positionDividers();

        // Properly positions the caret (AKA cursor)
        messageTextArea.requestFocusInWindow();
        messageTextArea.getCaret().setDot(caretPos);
        messageTextArea.getCaret().setVisible(true);
    }

    public void composeNewMessage(final Board newBoard, final String newSubject, final String newText) {
        composeMessage(newBoard, newSubject, null, newText, false, null, null, null);
    }

    public void composeReply(
            final Board newBoard,
            final String newSubject,
            final String inReplyTo,
            final String newText,
            final FrostMessageObject msg) {
        composeMessage(newBoard, newSubject, inReplyTo, newText, true, null, null, msg);
    }

    public void composeEncryptedReply(
            final Board newBoard,
            final String newSubject,
            final String inReplyTo,
            final String newText,
            final Identity recipient,
            final LocalIdentity senderId,
            final FrostMessageObject msg) {
        composeMessage(newBoard, newSubject, inReplyTo, newText, true, recipient, senderId, msg);
    }

    @Override
    public void dispose() {
        if (initialized) {
            language.removeLanguageListener(listener);
            initialized = false;
        }
        super.dispose();
    }

    private MessageBodyPopupMenu getMessageBodyPopupMenu() {
        if (messageBodyPopupMenu == null) {
            messageBodyPopupMenu = new MessageBodyPopupMenu(messageTextArea);
        }
        return messageBodyPopupMenu;
    }

    private void initialize(final Board targetBoard, final String subject) throws Exception {
        if (!initialized) {
            refreshLanguage();
            language.addLanguageListener(listener);

            final ImageIcon frameIcon = MiscToolkit.loadImageIcon("/data/toolbar/mail-message-new.png");
            setIconImage(frameIcon.getImage());
            setResizable(true);

            boardsTableModel = new MFAttachedBoardsTableModel();
            boardsTable = new MFAttachedBoardsTable(boardsTableModel);
            boardsTableScrollPane = new JScrollPane(boardsTable);
            boardsTableScrollPane.setWheelScrollingEnabled(true);
            boardsTable.addMouseListener(listener);

            filesTableModel = new MFAttachedFilesTableModel();
            filesTable = new MFAttachedFilesTable(filesTableModel);
            filesTableScrollPane = new JScrollPane(filesTable);
            filesTableScrollPane.setWheelScrollingEnabled(true);
            filesTable.addMouseListener(listener);

// FIXME: option to show own identities in list, or to hide them
            final List<Identity> budList = Core.getIdentities().getAllGOODIdentities();
            Identity id = null;
            if( repliedMessage != null ) {
                id = repliedMessage.getFromIdentity();
            }
            if( budList.size() > 0 || id != null ) {
                Collections.sort( budList, new BuddyComparator() );
                if( id != null ) {
                    if( id.isGOOD() == true ) {
                        budList.remove(id); // remove before put to top of list
                    }
                    // add id to top of list in case the user enables 'encrypt'
                    budList.add(0, id);
                }
                buddies = new JComboBox(new Vector<Identity>(budList));
                buddies.setSelectedItem(budList.get(0));
            } else {
                buddies = new JComboBox();
            }
            buddies.setMaximumSize(new Dimension(300, 25)); // dirty fix for overlength combobox on linux

            MiscToolkit.configureButton(Bsend, "MessageFrame.toolbar.tooltip.sendMessage", language);
            MiscToolkit.configureButton(Bcancel, "Common.cancel", language);
            MiscToolkit.configureButton(BattachFile, "MessageFrame.toolbar.tooltip.addFileAttachments", language);
            MiscToolkit.configureButton(BattachBoard, "MessageFrame.toolbar.tooltip.addBoardAttachments", language);
            MiscToolkit.configureButton(BchooseSmiley, "MessageFrame.toolbar.tooltip.chooseSmiley", language);
            BchooseSmiley.setFocusable(false);

            TFboard.setEditable(false);
            TFboard.setText(targetBoard.getName());

            new TextComponentClipboardMenu(TFboard, language);
            new TextComponentClipboardMenu((TextComboBoxEditor)getOwnIdentitiesComboBox().getEditor(), language);
            new TextComponentClipboardMenu(subjectTextField, language);
            subjectTextField.setText(subject);
            messageTextArea.setLineWrap(true);
            messageTextArea.setWrapStyleWord(true);
            messageTextArea.addMouseListener(listener);

            sign.setOpaque(false);
            encrypt.setOpaque(false);

            //------------------------------------------------------------------------
            // Actionlistener
            //------------------------------------------------------------------------
            Bsend.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    send_actionPerformed(e);
                }
            });
            Bcancel.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    windowIsClosing();
                }
            });
            BattachFile.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    attachFile_actionPerformed(e);
                }
            });
            BattachBoard.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    attachBoards_actionPerformed(e);
                }
            });
            encrypt.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    encrypt_actionPerformed(e);
                }
            });
            BchooseSmiley.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    chooseSmiley_actionPerformed(e);
                }
            });

            //------------------------------------------------------------------------
            // Append objects
            //------------------------------------------------------------------------
            final JPanel panelMain = new JPanel(new BorderLayout()); // Main Panel

            final JPanel panelHeader = new JPanel(new BorderLayout()); // header (toolbar and textfields)
            final JPanel panelTextfields = new JPanel(new GridBagLayout());

            final JToolBar panelToolbar = new JToolBar(); // toolbar
            panelToolbar.setRollover(true);
            panelToolbar.setFloatable(false);

            final JScrollPane bodyScrollPane = new JScrollPane(messageTextArea); // Textscrollpane
            bodyScrollPane.setWheelScrollingEnabled(true);
            bodyScrollPane.setMinimumSize(new Dimension(100, 50));

            // FIXME: add a smiley chooser right beside the subject textfield!

            // text fields
            final GridBagConstraints constraints = new GridBagConstraints();
            final Insets insets = new Insets(0, 3, 0, 3);
            final Insets insets0 = new Insets(0, 0, 0, 0);
            constraints.fill = GridBagConstraints.NONE;
            constraints.anchor = GridBagConstraints.WEST;
            constraints.weighty = 0.0;
            constraints.weightx = 0.0;

            constraints.insets = insets;

            constraints.gridx = 0;
            constraints.gridy = 0;

            constraints.fill = GridBagConstraints.NONE;
            constraints.gridwidth = 1;
            constraints.insets = insets;
            constraints.weightx = 0.0;
            panelTextfields.add(Lboard, constraints);

            constraints.gridx = 1;

            constraints.fill = GridBagConstraints.HORIZONTAL;
            constraints.gridwidth = 2;
            constraints.insets = insets0;
            constraints.weightx = 1.0;
            panelTextfields.add(TFboard, constraints);

            constraints.gridx = 0;
            constraints.gridy++;

            constraints.fill = GridBagConstraints.NONE;
            constraints.gridwidth = 1;
            constraints.insets = insets;
            constraints.weightx = 0.0;
            panelTextfields.add(Lfrom, constraints);

            constraints.gridx = 1;

            constraints.fill = GridBagConstraints.HORIZONTAL;
            constraints.gridwidth = 2;
            constraints.insets = insets0;
            constraints.weightx = 1.0;
            panelTextfields.add(getOwnIdentitiesComboBox(), constraints);

            constraints.gridx = 0;
            constraints.gridy++;

            constraints.fill = GridBagConstraints.NONE;
            constraints.gridwidth = 1;
            constraints.insets = insets;
            constraints.weightx = 0.0;
            panelTextfields.add(Lsubject, constraints);

            constraints.gridx = 1;

            constraints.fill = GridBagConstraints.HORIZONTAL;
            constraints.gridwidth = 1;
            constraints.insets = insets0;
            constraints.weightx = 1.0;
            panelTextfields.add(subjectTextField, constraints);

            constraints.gridx = 2;

            constraints.fill = GridBagConstraints.NONE;
            constraints.gridwidth = 1;
            constraints.insets = insets;
            constraints.weightx = 0.0;
            panelTextfields.add(BchooseSmiley, constraints);

            // toolbar
            panelToolbar.add(Bsend);
            panelToolbar.add(Bcancel);
            panelToolbar.addSeparator();
            panelToolbar.add(BattachFile);
            panelToolbar.add(BattachBoard);
            panelToolbar.addSeparator();
            panelToolbar.add(sign);
            panelToolbar.addSeparator();
            panelToolbar.add(encrypt);
            panelToolbar.add(buddies);
//            panelButtons.add(addAttachedFilesToUploadTable);

            final ScrollableBar panelButtonsScrollable = new ScrollableBar(panelToolbar);

            panelHeader.add(panelButtonsScrollable, BorderLayout.PAGE_START);
//            panelToolbar.add(panelButtons, BorderLayout.PAGE_START);
            panelHeader.add(panelTextfields, BorderLayout.CENTER);

            //Put everything together
            attachmentsSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, filesTableScrollPane,
                    boardsTableScrollPane);
            attachmentsSplitPane.setResizeWeight(0.5);
            attachmentsSplitPane.setDividerSize(3);
            attachmentsSplitPane.setDividerLocation(0.5);

            messageSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, bodyScrollPane,
                    attachmentsSplitPane);
            messageSplitPane.setDividerSize(0);
            messageSplitPane.setDividerLocation(1.0);
            messageSplitPane.setResizeWeight(1.0);

            panelMain.add(panelHeader, BorderLayout.NORTH);
            panelMain.add(messageSplitPane, BorderLayout.CENTER);

            getContentPane().setLayout(new BorderLayout());
            getContentPane().add(panelMain, BorderLayout.CENTER);

            initPopupMenu();

            pack();

            // window is now packed to needed size. Check if packed width is smaller than
            // 75% of the parent frame and use the larger size.
            // pack is needed to ensure that all dialog elements are shown (was problem on linux).
            int width = getWidth();
            if( width < (int)(parentWindow.getWidth() * 0.75) ) {
                width = (int)(parentWindow.getWidth() * 0.75);
            }

            setSize( width, (int)(parentWindow.getHeight() * 0.75) ); // always set height to 75% of parent
            setLocationRelativeTo(parentWindow);

            initialized = true;
        }
    }

    protected void initPopupMenu() {
        attFilesPopupMenu = new JSkinnablePopupMenu();
        attBoardsPopupMenu = new JSkinnablePopupMenu();

        final JMenuItem removeFiles = new JMenuItem(language.getString("MessageFrame.attachmentTables.popupmenu.remove"));
        final JMenuItem removeBoards = new JMenuItem(language.getString("MessageFrame.attachmentTables.popupmenu.remove"));

        removeFiles.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(final ActionEvent e) {
                removeSelectedItemsFromTable(filesTable);
            }
        });
        removeBoards.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(final ActionEvent e) {
                removeSelectedItemsFromTable(boardsTable);
            }
        });

        attFilesPopupMenu.add( removeFiles );
        attBoardsPopupMenu.add( removeBoards );
    }

    private void positionDividers() {
        final int attachedFiles = filesTableModel.getRowCount();
        final int attachedBoards = boardsTableModel.getRowCount();
        if (attachedFiles == 0 && attachedBoards == 0) {
            // Neither files nor boards
            messageSplitPane.setBottomComponent(null);
            messageSplitPane.setDividerSize(0);
            return;
        }
        messageSplitPane.setDividerSize(3);
        messageSplitPane.setDividerLocation(0.75);
        if (attachedFiles != 0 && attachedBoards == 0) {
            //Only files
            messageSplitPane.setBottomComponent(filesTableScrollPane);
            return;
        }
        if (attachedFiles == 0 && attachedBoards != 0) {
            //Only boards
            messageSplitPane.setBottomComponent(boardsTableScrollPane);
            return;
        }
        if (attachedFiles != 0 && attachedBoards != 0) {
            //Both files and boards
            messageSplitPane.setBottomComponent(attachmentsSplitPane);
            attachmentsSplitPane.setTopComponent(filesTableScrollPane);
            attachmentsSplitPane.setBottomComponent(boardsTableScrollPane);
        }
    }

    private void refreshLanguage() {
        setTitle(language.getString("MessageFrame.createMessage.title"));

        Bsend.setToolTipText(language.getString("MessageFrame.toolbar.tooltip.sendMessage"));
        Bcancel.setToolTipText(language.getString("Common.cancel"));
        BattachFile.setToolTipText(language.getString("MessageFrame.toolbar.tooltip.addFileAttachments"));
        BattachBoard.setToolTipText(language.getString("MessageFrame.toolbar.tooltip.addBoardAttachments"));

        encrypt.setText(language.getString("MessageFrame.toolbar.encryptFor"));

        Lboard.setText(language.getString("MessageFrame.board") + ": ");
        Lfrom.setText(language.getString("MessageFrame.from") + ": ");
        Lsubject.setText(language.getString("MessageFrame.subject") + ": ");

        updateSignToolTip();
    }

    private void updateSignToolTip() {
        final boolean isSelected = sign.isSelected();
        if( isSelected ) {
            sign.setToolTipText(language.getString("MessagePane.toolbar.tooltip.isSigned"));
        } else {
            sign.setToolTipText(language.getString("MessagePane.toolbar.tooltip.isUnsigned"));
        }
    }

    protected void removeSelectedItemsFromTable( final JTable tbl ) {
      final SortedTableModel<? extends TableMember<?>> m = (SortedTableModel<? extends TableMember<?>>)tbl.getModel();
        final int[] sel = tbl.getSelectedRows();
        for(int x=sel.length-1; x>=0; x--)
        {
            m.removeRow(sel[x]);
        }
        positionDividers();
    }

    private void chooseSmiley_actionPerformed(final ActionEvent e) {
        final SmileyChooserDialog dlg = new SmileyChooserDialog(this);
        final int x = this.getX() + BchooseSmiley.getX();
        final int y = this.getY() + BchooseSmiley.getY();
        String chosedSmileyText = dlg.startDialog(x, y);
        if( chosedSmileyText != null && chosedSmileyText.length() > 0 ) {
            chosedSmileyText += " ";
            // paste into document
            try {
                final Caret caret = messageTextArea.getCaret();
                final int p0 = Math.min(caret.getDot(), caret.getMark());
                final int p1 = Math.max(caret.getDot(), caret.getMark());

                final Document document = messageTextArea.getDocument();
               
                // FIXME: maybe check for a blank before insert of smiley text???
                if (document instanceof PlainDocument) {
                    ((PlainDocument) document).replace(p0, p1 - p0, chosedSmileyText, null);
                } else {
                    if (p0 != p1) {
                        document.remove(p0, p1 - p0);
                    }
                    document.insertString(p0, chosedSmileyText, null);
                }
            } catch (final Throwable ble) {
                logger.log(Level.SEVERE, "Problem while pasting text.", ble);
            }
        }
        // finally set focus back to message window
        messageTextArea.requestFocusInWindow();
    }

    private void send_actionPerformed(final ActionEvent e) {

        LocalIdentity senderId = null;
        String from;
        if( getOwnIdentitiesComboBox().getSelectedItem() instanceof LocalIdentity ) {
            senderId = (LocalIdentity)getOwnIdentitiesComboBox().getSelectedItem();
            from = senderId.getUniqueName();
        } else {
            from = getOwnIdentitiesComboBox().getEditor().getItem().toString();
        }

        final String subject = subjectTextField.getText().trim();
        subjectTextField.setText(subject); // if a pbl occurs show the subject we checked
        final String text = messageTextArea.getText().trim();

        if( subject.equals("No subject") ) {
            final int n = JOptionPane.showConfirmDialog( this,
                                language.getString("MessageFrame.defaultSubjectWarning.text"),
                                language.getString("MessageFrame.defaultSubjectWarning.title"),
                                JOptionPane.YES_NO_OPTION,
                                JOptionPane.QUESTION_MESSAGE);
            if( n == JOptionPane.YES_OPTION ) {
                return;
            }
        }

        if( subject.length() == 0) {
            JOptionPane.showMessageDialog( this,
                                language.getString("MessageFrame.noSubjectError.text"),
                                language.getString("MessageFrame.noSubjectError.title"),
                                JOptionPane.ERROR);
            return;
        }
        if( from.length() == 0) {
            JOptionPane.showMessageDialog( this,
                                language.getString("MessageFrame.noSenderError.text"),
                                language.getString("MessageFrame.noSenderError.title"),
                                JOptionPane.ERROR);
            return;
        }
        final int maxTextLength = (60*1024);
        final int msgSize = text.length() + subject.length() + from.length() + ((repliedMsgId!=null)?repliedMsgId.length():0);
        if( msgSize > maxTextLength ) {
            JOptionPane.showMessageDialog( this,
                    language.formatMessage("MessageFrame.textTooLargeError.text",
                            Integer.toString(text.length()),
                            Integer.toString(maxTextLength)),
                    language.getString("MessageFrame.textTooLargeError.title"),
                    JOptionPane.ERROR_MESSAGE);
            return;
        }
        final int idLinePos = headerArea.getStartPos();
        final int idLineLen = headerArea.getEndPos() - headerArea.getStartPos();
        if( text.length() == headerArea.getEndPos() ) {
            JOptionPane.showMessageDialog( this,
                    language.getString("MessageFrame.noContentError.text"),
                    language.getString("MessageFrame.noContentError.title"),
                    JOptionPane.ERROR_MESSAGE);
            return;
        }

        // for convinience set last used user
        if( from.indexOf("@") < 0 ) {
            // only save anonymous usernames
            frostSettings.setValue(SettingsClass.LAST_USED_FROMNAME, from);
        }
        frostSettings.setValue("userName."+board.getBoardFilename(), from);

        final FrostUnsentMessageObject newMessage = new FrostUnsentMessageObject();
        newMessage.setMessageId(Mixed.createUniqueId()); // new message, create a new unique msg id
        newMessage.setInReplyTo(repliedMsgId);
        newMessage.setBoard(board);
        newMessage.setFromName(from);
        newMessage.setSubject(subject);
        newMessage.setContent(text);
        newMessage.setIdLinePos(idLinePos);
        newMessage.setIdLineLen(idLineLen);

        // MessageUploadThread will set date + time !

        // attach all files and boards the user chosed
        if( filesTableModel.getRowCount() > 0 ) {
            for(int x=0; x < filesTableModel.getRowCount(); x++) {
                final MFAttachedFile af = (MFAttachedFile)filesTableModel.getRow(x);
                final File aChosedFile = af.getFile();
                final FileAttachment fa = new FileAttachment(aChosedFile);
                newMessage.addAttachment(fa);
            }
            newMessage.setHasFileAttachments(true);
        }
        if( boardsTableModel.getRowCount() > 0 ) {
            for(int x=0; x < boardsTableModel.getRowCount(); x++) {
                final MFAttachedBoard ab = (MFAttachedBoard)boardsTableModel.getRow(x);
                final Board aChosedBoard = ab.getBoardObject();
                final BoardAttachment ba = new BoardAttachment(aChosedBoard);
                newMessage.addAttachment(ba);
            }
            newMessage.setHasBoardAttachments(true);
        }

        Identity recipient = null;
        if( encrypt.isSelected() ) {
            recipient = (Identity)buddies.getSelectedItem();
            if( recipient == null ) {
                JOptionPane.showMessageDialog( this,
                        language.getString("MessageFrame.encryptErrorNoRecipient.body"),
                        language.getString("MessageFrame.encryptErrorNoRecipient.title"),
                        JOptionPane.ERROR);
                return;
            }
            newMessage.setRecipientName(recipient.getUniqueName());
        }

        UnsentMessagesManager.addNewUnsentMessage(newMessage);

//        // zip the xml file and check for maximum size
//        File tmpFile = FileAccess.createTempFile("msgframe_", "_tmp");
//        tmpFile.deleteOnExit();
//        if( mo.saveToFile(tmpFile) == true ) {
//            File zipFile = new File(tmpFile.getPath() + ".zipped");
//            zipFile.delete(); // just in case it already exists
//            zipFile.deleteOnExit(); // so that it is deleted when Frost exits
//            FileAccess.writeZipFile(FileAccess.readByteArray(tmpFile), "entry", zipFile);
//            long zipLen = zipFile.length();
//            tmpFile.delete();
//            zipFile.delete();
//            if( zipLen > 30000 ) { // 30000 because data+metadata must be smaller than 32k
//                JOptionPane.showMessageDialog( this,
//                        "The zipped message is too large ("+zipLen+" bytes, "+30000+" allowed)! Remove some text.",
//                        "Message text too large!",
//                        JOptionPane.ERROR_MESSAGE);
//                return;
//            }
//        } else {
//            JOptionPane.showMessageDialog( this,
//                    "Error verifying the resulting message size.",
//                    "Error",
//                    JOptionPane.ERROR_MESSAGE);
//            return;
//        }

        // TODO: if user deletes the unsent msg then the replied state keeps (see below)
        //  We would have to set the replied state after the msg was successfully sent, because
        //  we can't remove the state later, maybe the msg was replied twice and we would remove
        //  the replied state from first reply...

        // set isReplied to replied message
        if( repliedMessage != null ) {
            if( repliedMessage.isReplied() == false ) {
                repliedMessage.setReplied(true);
                final FrostMessageObject saveMsg = repliedMessage;
                final Thread saver = new Thread() {
                    @Override
                    public void run() {
                        // save the changed isreplied state into the database
                        MessageStorage.inst().updateMessage(saveMsg);
                    }
                };
                saver.start();
            }
        }

        setVisible(false);
        dispose();
    }

    private void senderChanged(final LocalIdentity selectedId) {

        boolean isSigned;
        if( selectedId == null ) {
            isSigned = false;
        } else {
            isSigned = true;
        }

        sign.setSelected(isSigned);

        if (isSigned) {
            if( buddies.getItemCount() > 0 ) {
                encrypt.setEnabled(true);
                if( encrypt.isSelected() ) {
                    buddies.setEnabled(true);
                } else {
                    buddies.setEnabled(false);
                }
            }

            removeSignatureFromText(currentSignature); // remove signature if existing
            currentSignature = addSignatureToText(selectedId.getSignature()); // add new signature if not existing
        } else {
            encrypt.setSelected(false);
            encrypt.setEnabled(false);
            buddies.setEnabled(false);
            removeSignatureFromText(currentSignature); // remove signature if existing
            currentSignature = null;
        }
    }

    private String addSignatureToText(final String sig) {
        if( sig == null ) {
            return null;
        }
        final String newSig = "\n-- \n" + sig;
        if (!messageTextArea.getText().endsWith(newSig)) {
            try {
                messageTextArea.getDocument().insertString(messageTextArea.getText().length(), newSig, null);
            } catch (final BadLocationException e1) {
                logger.log(Level.SEVERE, "Error while updating the signature ", e1);
            }
        }
        return newSig;
    }

    private void removeSignatureFromText(final String sig) {
        if( sig == null ) {
            return;
        }
        if (messageTextArea.getText().endsWith(sig)) {
            try {
                messageTextArea.getDocument().remove(messageTextArea.getText().length()-sig.length(), sig.length());
            } catch (final BadLocationException e1) {
                logger.log(Level.SEVERE, "Error while updating the signature ", e1);
            }
        }
    }

    private void encrypt_actionPerformed(final ActionEvent e) {
        if( encrypt.isSelected() ) {
            buddies.setEnabled(true);
        } else {
            buddies.setEnabled(false);
        }
    }

    protected void updateHeaderArea(final String sender) {
        if( !headerArea.isEnabled() ) {
            return; // ignore updates
        }
        if( sender == null || oldSender == null || oldSender.equals(sender) ) {
            return;
        }
        try {
            // TODO: add grey background! highlighter mit headerArea um pos zu finden
            headerArea.setEnabled(false);
            messageTextArea.getDocument().remove(headerArea.getStartPos() + 6, oldSender.length());
            messageTextArea.getDocument().insertString(headerArea.getStartPos() + 6, sender, null);
            oldSender = sender;
            headerArea.setEnabled(true);
//            textHighlighter.highlight(messageTextArea, headerArea.getStartPos(), headerArea.getEndPos()-headerArea.getStartPos(), true);
        } catch (final BadLocationException exception) {
            logger.log(Level.SEVERE, "Error while updating the message header", exception);
        }
//        String s= messageTextArea.getText().substring(headerArea.getStartPos(), headerArea.getEndPos());
//        System.out.println("DBG: "+headerArea.getStartPos()+" ; "+headerArea.getEndPos()+": '"+s+"'");

// DBG: 0 ; 77: '----- blubb2@xpDZ5ZfXK9wYiHB_hkVGRCwJl54 ----- 2006.10.13 - 18:20:12GMT -----'
// DBG: 39 ; 119: '----- wegdami t@plewLcBTHKmPwpWakJNpUdvWSR8 ----- 2006.10.13 - 18:20:12GMT -----'
    }

    class TextComboBoxEditor extends JTextField implements ComboBoxEditor {
        boolean isSigned;
        public TextComboBoxEditor() {
            super();
        }
        public Component getEditorComponent() {
            return this;
        }
        public void setItem(final Object arg0) {
            if( arg0 instanceof LocalIdentity ) {
                isSigned = true;
            } else {
                isSigned = false;
            }
            setText(arg0.toString());
        }
        public Object getItem() {
            return getText();
        }
        public boolean isSigned() {
            return isSigned;
        }
    }

    private JComboBox getOwnIdentitiesComboBox() {
        if( ownIdentitiesComboBox == null ) {
            ownIdentitiesComboBox = new JComboBox();
            ownIdentitiesComboBox.addItem("Anonymous");
            // sort own unique names
            final TreeMap<String,LocalIdentity> sortedIds = new TreeMap<String,LocalIdentity>();
            for( final Object element : Core.getIdentities().getLocalIdentities() ) {
                final LocalIdentity li = (LocalIdentity)element;
                sortedIds.put(li.getUniqueName(), li);
            }
            for( final Object element : sortedIds.values() ) {
                ownIdentitiesComboBox.addItem(element);
            }

            final TextComboBoxEditor editor = new TextComboBoxEditor();

            editor.getDocument().addDocumentListener(new DocumentListener() {
                public void changedUpdate(final DocumentEvent e) {
                    updateHeaderArea2();
                }
                public void insertUpdate(final DocumentEvent e) {
                    updateHeaderArea2();
                }
                public void removeUpdate(final DocumentEvent e) {
                    updateHeaderArea2();
                }
                private void updateHeaderArea2() {
                    final String sender = getOwnIdentitiesComboBox().getEditor().getItem().toString();
                    updateHeaderArea(sender);
                }
            });

            final AbstractDocument doc = (AbstractDocument) editor.getDocument();
            doc.setDocumentFilter(new DocumentFilter() {
                @Override
                public void insertString(final DocumentFilter.FilterBypass fb, final int offset, String string,
                        final AttributeSet attr) throws BadLocationException
                {
                    if (((TextComboBoxEditor)getOwnIdentitiesComboBox().getEditor()).isSigned() == false ) {
                        string = string.replaceAll("@","");
                    }
                    super.insertString(fb, offset, string, attr);
                }
                @Override
                public void replace(final DocumentFilter.FilterBypass fb, final int offset, final int length, String string,
                        final AttributeSet attrs) throws BadLocationException
                {
                    if (((TextComboBoxEditor)getOwnIdentitiesComboBox().getEditor()).isSigned() == false ) {
                        string = string.replaceAll("@","");
                    }
                    super.replace(fb, offset, length, string, attrs);
                }
            });

            ownIdentitiesComboBox.setEditor(editor);

            ownIdentitiesComboBox.setEditable(true);

//            ownIdentitiesComboBox.getEditor().selectAll();
            ownIdentitiesComboBox.addItemListener(new java.awt.event.ItemListener() {
                public void itemStateChanged(final java.awt.event.ItemEvent e) {
                    if( e.getStateChange() == ItemEvent.DESELECTED ) {
                        return;
                    }
                    LocalIdentity selectedId = null;
                    if( ownIdentitiesComboBox.getSelectedIndex() == 0 ) {
                        ownIdentitiesComboBox.setEditable(true); // original anonymous
//                        ownIdentitiesComboBox.getEditor().selectAll();
                    } else if( ownIdentitiesComboBox.getSelectedIndex() < 0 ) {
                        ownIdentitiesComboBox.setEditable(true); // own value, anonymous
//                        ownIdentitiesComboBox.getEditor().selectAll();
                    } else {
                        ownIdentitiesComboBox.setEditable(false);
                        selectedId = (LocalIdentity) ownIdentitiesComboBox.getSelectedItem();
                    }
                    final String sender = getOwnIdentitiesComboBox().getSelectedItem().toString();
                    updateHeaderArea(sender);
                    senderChanged(selectedId);
                }
            });
        }
        return ownIdentitiesComboBox;
    }

    class BuddyComparator implements Comparator<Identity> {
        public int compare(final Identity id1, final Identity id2) {
            final String s1 = id1.getUniqueName();
            final String s2 = id2.getUniqueName();
            return s1.toLowerCase().compareTo( s2.toLowerCase() );
        }
    }

    private class Listener implements MouseListener, LanguageListener {
        protected void maybeShowPopup(final MouseEvent e) {
            if (e.isPopupTrigger()) {
                if (e.getSource() == boardsTable) {
                    attBoardsPopupMenu.show(boardsTable, e.getX(), e.getY());
                }
                if (e.getSource() == filesTable) {
                    attFilesPopupMenu.show(filesTable, e.getX(), e.getY());
                }
                if (e.getSource() == messageTextArea) {
                    getMessageBodyPopupMenu().show(messageTextArea, e.getX(), e.getY());
                }
            }
        }
        public void mouseClicked(final MouseEvent event) {}
        public void mouseEntered(final MouseEvent event) {}
        public void mouseExited(final MouseEvent event) {}
        public void mousePressed(final MouseEvent event) {
            maybeShowPopup(event);
        }
        public void mouseReleased(final MouseEvent event) {
            maybeShowPopup(event);
        }
        public void languageChanged(final LanguageEvent event) {
            refreshLanguage();
        }
    }

    private class MessageBodyPopupMenu
        extends JSkinnablePopupMenu
        implements ActionListener, ClipboardOwner {

        private Clipboard clipboard;

        private final JTextComponent sourceTextComponent;

        private final JMenuItem cutItem = new JMenuItem();
        private final JMenuItem copyItem = new JMenuItem();
        private final JMenuItem pasteItem = new JMenuItem();
        private final JMenuItem cancelItem = new JMenuItem();

        public MessageBodyPopupMenu(final JTextComponent sourceTextComponent) {
            super();
            this.sourceTextComponent = sourceTextComponent;
            initialize();
        }

        public void actionPerformed(final ActionEvent e) {
            if (e.getSource() == cutItem) {
                cutSelectedText();
            }
            if (e.getSource() == copyItem) {
                copySelectedText();
            }
            if (e.getSource() == pasteItem) {
                pasteText();
            }
        }

        private void copySelectedText() {
            final StringSelection selection = new StringSelection(sourceTextComponent.getSelectedText());
            clipboard.setContents(selection, this);
        }

        private void cutSelectedText() {
            final StringSelection selection = new StringSelection(sourceTextComponent.getSelectedText());
            clipboard.setContents(selection, this);

            final int start = sourceTextComponent.getSelectionStart();
            final int end = sourceTextComponent.getSelectionEnd();
            try {
                sourceTextComponent.getDocument().remove(start, end - start);
            } catch (final BadLocationException ble) {
                logger.log(Level.SEVERE, "Problem while cutting text.", ble);
            }
        }

        private void pasteText() {
            final Transferable clipboardContent = clipboard.getContents(this);
            try {
                final String text = (String) clipboardContent.getTransferData(DataFlavor.stringFlavor);

                final Caret caret = sourceTextComponent.getCaret();
                final int p0 = Math.min(caret.getDot(), caret.getMark());
                final int p1 = Math.max(caret.getDot(), caret.getMark());

                final Document document = sourceTextComponent.getDocument();

                if (document instanceof PlainDocument) {
                    ((PlainDocument) document).replace(p0, p1 - p0, text, null);
                } else {
                    if (p0 != p1) {
                        document.remove(p0, p1 - p0);
                    }
                    document.insertString(p0, text, null);
                }
            } catch (final IOException ioe) {
                logger.log(Level.SEVERE, "Problem while pasting text.", ioe);
            } catch (final UnsupportedFlavorException ufe) {
                logger.log(Level.SEVERE, "Problem while pasting text.", ufe);
            } catch (final BadLocationException ble) {
                logger.log(Level.SEVERE, "Problem while pasting text.", ble);
            }
        }

        private void initialize() {
            refreshLanguage();

            final Toolkit toolkit = Toolkit.getDefaultToolkit();
            clipboard = toolkit.getSystemClipboard();

            cutItem.addActionListener(this);
            copyItem.addActionListener(this);
            pasteItem.addActionListener(this);

            add(cutItem);
            add(copyItem);
            add(pasteItem);
            addSeparator();
            add(cancelItem);
        }

        private void refreshLanguage() {
            cutItem.setText(language.getString("Common.cut"));
            copyItem.setText(language.getString("Common.copy"));
            pasteItem.setText(language.getString("Common.paste"));
            cancelItem.setText(language.getString("Common.cancel"));
        }

        public void lostOwnership(final Clipboard nclipboard, final Transferable contents) {}

        @Override
        public void show(final Component invoker, final int x, final int y) {
            if (sourceTextComponent.getSelectedText() != null) {
                cutItem.setEnabled(true);
                copyItem.setEnabled(true);
            } else {
                cutItem.setEnabled(false);
                copyItem.setEnabled(false);
            }
            final Transferable clipboardContent = clipboard.getContents(this);
            if ((clipboardContent != null) &&
                    (clipboardContent.isDataFlavorSupported(DataFlavor.stringFlavor))) {
                pasteItem.setEnabled(true);
            } else {
                pasteItem.setEnabled(false);
            }
            super.show(invoker, x, y);
        }
    }

    private class MFAttachedBoard extends TableMember.BaseTableMember<MFAttachedBoard> {
        Board aBoard;

        public MFAttachedBoard(final Board ab) {
            aBoard = ab;
        }

        public Board getBoardObject() {
            return aBoard;
        }

    public Comparable<?> getValueAt(final int column) {
            switch (column) {
                case 0 : return aBoard.getName();
                case 1 : return (aBoard.getPublicKey() == null) ? "N/A" : aBoard.getPublicKey();
                case 2 : return (aBoard.getPrivateKey() == null) ? "N/A" : aBoard.getPrivateKey();
                case 3 : return (aBoard.getDescription() == null) ? "N/A" : aBoard.getDescription();
            }
            return "*ERR*";
        }
    }

    private class MFAttachedBoardsTable extends SortedTable<MFAttachedBoard> {
        public MFAttachedBoardsTable(final MFAttachedBoardsTableModel m) {
            super(m);
            // set column sizes
            final int[] widths = {250, 80, 80};
            for (int i = 0; i < widths.length; i++) {
                getColumnModel().getColumn(i).setPreferredWidth(widths[i]);
            }
            // default for sort: sort by name ascending ?
            sortedColumnIndex = 0;
            sortedColumnAscending = true;
            resortTable();
        }
    }

    private class MFAttachedBoardsTableModel extends SortedTableModel<MFAttachedBoard> {
        protected final Class<?> columnClasses[] = {
            String.class,
            String.class,
            String.class,
            String.class
        };
        protected final String columnNames[] = {
            language.getString("MessageFrame.boardAttachmentTable.boardname"),
            language.getString("MessageFrame.boardAttachmentTable.publicKey"),
            language.getString("MessageFrame.boardAttachmentTable.privateKey"),
            language.getString("MessageFrame.boardAttachmentTable.description")
        };

        public MFAttachedBoardsTableModel() {
            super();
        }
        @Override
        public Class<?> getColumnClass(final int column) {
            if( column >= 0 && column < columnClasses.length ) {
                return columnClasses[column];
            }
            return null;
        }
        @Override
        public int getColumnCount() {
            return columnNames.length;
        }
        @Override
        public String getColumnName(final int column) {
            if( column >= 0 && column < columnNames.length ) {
                return columnNames[column];
            }
            return null;
        }
        @Override
        public boolean isCellEditable(final int row, final int col) {
            return false;
        }
        @Override
        public void setValueAt(final Object aValue, final int row, final int column) {}
    }

    private class MFAttachedFile extends TableMember.BaseTableMember<MFAttachedFile>  {
        File aFile;
       
        public MFAttachedFile(final File af) {
            aFile = af;
        }
       
        public File getFile() {
            return aFile;
        }
       
    public Comparable<?> getValueAt(final int column)  {
            switch(column) {
                case 0: return aFile.getName();
                case 1: return Long.toString(aFile.length());
            }
            throw new IndexOutOfBoundsException("Nu such column: " + Integer.toString(column));
        }
    }

  private class MFAttachedFilesTable extends SortedTable<MFAttachedFile> {
        public MFAttachedFilesTable(final MFAttachedFilesTableModel m) {
            super(m);
           
            // set column sizes
            final int[] widths = {250, 80};
            for (int i = 0; i < widths.length; i++) {
                getColumnModel().getColumn(i).setPreferredWidth(widths[i]);
            }
           
            // default for sort: sort by name ascending ?
            sortedColumnIndex = 0;
            sortedColumnAscending = true;
            resortTable();
        }
    }

    private class MFAttachedFilesTableModel extends SortedTableModel<MFAttachedFile> {
     
        protected final Class<?> columnClasses[] = {
            String.class,
            String.class
        };
       
        protected final String columnNames[] = {
            language.getString("MessageFrame.fileAttachmentTable.filename"),
            language.getString("MessageFrame.fileAttachmentTable.size")
        };
       
        public MFAttachedFilesTableModel() {
            super();
        }
       
        @Override
        public Class<?> getColumnClass(final int column) {
            if( column >= 0 && column < columnClasses.length ) {
                return columnClasses[column];
            }
            return null;
        }
       
        @Override
        public int getColumnCount() {
            return columnNames.length;
        }
       
        @Override
        public String getColumnName(final int column) {
            if( column >= 0 && column < columnNames.length ) {
                return columnNames[column];
            }
            return null;
        }
       
        @Override
        public boolean isCellEditable(final int row, final int col) {
            return false;
        }
    }

    private class TransferObject {
        public Board newBoard;
        public String newSubject;
        public String inReplyTo;
        public String newText;
        public boolean isReply;
        public Identity recipient = null;;
        public LocalIdentity senderId = null;
    }

    public static synchronized int getOpenInstanceCount() {
        return openInstanceCount;
    }
    private static synchronized void incOpenInstanceCount() {
        openInstanceCount++;
    }
    private static synchronized void decOpenInstanceCount() {
        if( openInstanceCount > 0 ) { // paranoia
            openInstanceCount--;
        }
    }
}
TOP

Related Classes of frost.messaging.frost.gui.MessageFrame

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.