org.kontalk.view.ThreadListView.java Source code

Java tutorial

Introduction

Here is the source code for org.kontalk.view.ThreadListView.java

Source

/*
 *  Kontalk Java client
 *  Copyright (C) 2014 Kontalk Devteam <devteam@kontalk.org>
 *
 *  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 <http://www.gnu.org/licenses/>.
 */

package org.kontalk.view;

import com.alee.extended.filechooser.WebFileChooserField;
import com.alee.extended.list.WebCheckBoxList;
import com.alee.extended.panel.GroupPanel;
import com.alee.extended.panel.GroupingType;
import com.alee.laf.button.WebButton;
import com.alee.laf.colorchooser.WebColorChooserDialog;
import com.alee.laf.label.WebLabel;
import com.alee.laf.menu.WebMenuItem;
import com.alee.laf.menu.WebPopupMenu;
import com.alee.laf.optionpane.WebOptionPane;
import com.alee.laf.radiobutton.WebRadioButton;
import com.alee.laf.rootpane.WebDialog;
import com.alee.laf.scroll.WebScrollPane;
import com.alee.laf.separator.WebSeparator;
import com.alee.laf.text.WebTextField;
import com.alee.utils.swing.DialogOptions;
import com.alee.utils.swing.UnselectableButtonGroup;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import javax.swing.JDialog;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.apache.commons.lang.StringUtils;
import org.kontalk.system.Config;
import org.kontalk.model.KonMessage;
import org.kontalk.model.KonThread;
import org.kontalk.model.KonThread.KonChatState;
import org.kontalk.model.KonThread.ViewSettings;
import org.kontalk.model.ThreadList;
import org.kontalk.model.User;
import org.kontalk.model.UserList;
import org.kontalk.util.Tr;
import org.kontalk.view.ThreadListView.ThreadItem;

/**
 * Show a brief list of all threads.
 * @author Alexander Bikadorov <abiku@cs.tu-berlin.de>
 */
final class ThreadListView extends TableView<ThreadItem, KonThread> {

    private final static Color DEFAULT_BG = Color.WHITE;

    private final View mView;
    private final ThreadList mThreadList;
    private final WebPopupMenu mPopupMenu;

    ThreadListView(final View view, ThreadList threadList) {
        mView = view;
        mThreadList = threadList;

        this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        // right click popup menu
        mPopupMenu = new WebPopupMenu();
        WebMenuItem editMenuItem = new WebMenuItem(Tr.tr("Edit Thread"));
        editMenuItem.setToolTipText(Tr.tr("Edit this thread"));
        editMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                ThreadItem t = ThreadListView.this.getSelectedItem();
                JDialog editUserDialog = new EditThreadDialog(t);
                editUserDialog.setVisible(true);
            }
        });
        mPopupMenu.add(editMenuItem);

        WebMenuItem deleteMenuItem = new WebMenuItem(Tr.tr("Delete Thread"));
        deleteMenuItem.setToolTipText(Tr.tr("Delete this thread"));
        deleteMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                String warningText = Tr.tr("Permanently delete all messages in this thread?");
                int selectedOption = WebOptionPane.showConfirmDialog(ThreadListView.this, warningText,
                        Tr.tr("Please Confirm"), WebOptionPane.OK_CANCEL_OPTION, WebOptionPane.WARNING_MESSAGE);
                if (selectedOption == WebOptionPane.OK_OPTION) {
                    ThreadItem threadItem = ThreadListView.this.getSelectedItem();
                    mThreadList.delete(threadItem.mValue.getID());
                }
            }
        });
        mPopupMenu.add(deleteMenuItem);

        // actions triggered by selection
        this.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                if (e.getValueIsAdjusting())
                    return;
                mView.selectedThreadChanged(ThreadListView.this.getSelectedValue());
            }
        });

        // actions triggered by mouse events
        this.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                check(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                check(e);
            }

            private void check(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    int row = ThreadListView.this.rowAtPoint(e.getPoint());
                    ThreadListView.this.setSelectedItem(row);
                    ThreadListView.this.showPopupMenu(e);
                }
            }
        });

        this.updateOnEDT(null);
    }

    @Override
    protected void updateOnEDT(Object arg) {
        // TODO, performance
        Set<ThreadItem> newItems = new HashSet<>();
        Set<KonThread> threads = mThreadList.getAll();
        for (KonThread thread : threads)
            if (!this.containsValue(thread))
                newItems.add(new ThreadItem(thread));
        this.sync(threads, newItems);
    }

    void selectLastThread() {
        int i = Config.getInstance().getInt(Config.VIEW_SELECTED_THREAD);
        if (i < 0)
            i = 0;
        this.setSelectedItem(i);
    }

    void save() {
        Config.getInstance().setProperty(Config.VIEW_SELECTED_THREAD, this.getSelectedRow());
    }

    private void showPopupMenu(MouseEvent e) {
        mPopupMenu.show(this, e.getX(), e.getY());
    }

    protected final class ThreadItem extends TableView<ThreadItem, KonThread>.TableItem {

        private final WebLabel mSubjectLabel;
        private final WebLabel mUserLabel;
        private final WebLabel mChatStateLabel;
        private Color mBackground;

        ThreadItem(KonThread thread) {
            super(thread);

            this.setMargin(5);
            this.setLayout(new BorderLayout(10, 5));

            mSubjectLabel = new WebLabel("foo");
            mSubjectLabel.setFontSize(14);
            this.add(mSubjectLabel, BorderLayout.NORTH);

            mUserLabel = new WebLabel();
            mUserLabel.setForeground(Color.GRAY);
            mUserLabel.setFontSize(11);
            this.add(mUserLabel, BorderLayout.CENTER);
            mChatStateLabel = new WebLabel();
            mChatStateLabel.setForeground(View.GREEN);
            mChatStateLabel.setFontSize(13);
            mChatStateLabel.setBoldFont();
            //mChatStateLabel.setMargin(0, 5, 0, 5);
            this.add(mChatStateLabel, BorderLayout.SOUTH);

            this.updateView(null);

            this.setBackground(mBackground);
        }

        @Override
        protected void render(int tableWidth, boolean isSelected) {
            if (isSelected)
                this.setBackground(View.BLUE);
            else
                this.setBackground(mBackground);
        }

        @Override
        protected String getTooltipText() {
            SortedSet<KonMessage> messageSet = this.mValue.getMessages();
            String lastActivity = messageSet.isEmpty() ? Tr.tr("no messages yet")
                    : TOOLTIP_DATE_FORMAT.format(messageSet.last().getDate());

            String html = "<html><body>" + Tr.tr("Last activity") + ": " + lastActivity + "<br>" + "";
            return html;
        }

        @Override
        protected void updateOnEDT(Object arg) {
            this.updateView(arg);
            // needed for background repaint
            ThreadListView.this.repaint();
        }

        private void updateView(Object arg) {
            if (arg == null || arg instanceof Boolean)
                mBackground = !mValue.isRead() ? View.LIGHT_BLUE : Color.WHITE;

            if (arg == null || arg instanceof String) {
                String subject = mValue.getSubject();
                if (subject.isEmpty())
                    subject = Tr.tr("<unnamed>");
                mSubjectLabel.setText(subject);
            }

            if (arg == null || arg instanceof Set) {
                List<String> nameList = new ArrayList<>(mValue.getUser().size());
                for (User user : mValue.getUser()) {
                    nameList.add(user.getName().isEmpty() ? Tr.tr("<unknown>") : user.getName());
                }
                mUserLabel.setText(StringUtils.join(nameList, ", "));
            }

            if (arg instanceof KonThread.KonChatState) {
                KonChatState state = (KonChatState) arg;
                String stateText = null;
                switch (state.getState()) {
                case composing:
                    stateText = Tr.tr("is writing...");
                    break;
                //case paused: activity = Tr.tr("has paused"); break;
                //case inactive: stateText = Tr.tr("is inactive"); break;
                }
                if (stateText == null) {
                    // 'inactive' is default
                    mChatStateLabel.setText("");
                    return;
                }

                if (mValue.getUser().size() > 1)
                    stateText = state.getUser().getName() + " " + stateText;

                mChatStateLabel.setText(stateText + " ");
            }
        }

        @Override
        protected boolean contains(String search) {
            for (User user : mValue.getUser()) {
                if (user.getName().toLowerCase().contains(search) || user.getJID().toLowerCase().contains(search))
                    return true;
            }
            return mValue.getSubject().toLowerCase().contains(search);
        }
    }

    private class EditThreadDialog extends WebDialog {

        private final ThreadItem mThreadItem;
        private final WebTextField mSubjectField;
        private final WebRadioButton mColorButton;
        private final WebButton mColorChooserButton;
        private final WebColorChooserDialog mColorChooser;
        private final WebRadioButton mImgButton;
        private final WebFileChooserField mImgChooser;
        WebCheckBoxList mParticipantsList;

        EditThreadDialog(ThreadItem threadItem) {

            mThreadItem = threadItem;

            this.setTitle(Tr.tr("Edit Thread"));
            this.setResizable(false);
            this.setModal(true);

            GroupPanel groupPanel = new GroupPanel(10, false);
            groupPanel.setMargin(5);

            // editable fields
            groupPanel.add(new WebLabel(Tr.tr("Subject:")));
            String subj = mThreadItem.mValue.getSubject();
            mSubjectField = new WebTextField(subj, 22);
            mSubjectField.setInputPrompt(subj);
            mSubjectField.setHideInputPromptOnFocus(false);
            groupPanel.add(mSubjectField);
            groupPanel.add(new WebSeparator(true, true));

            groupPanel.add(new WebLabel(Tr.tr("Custom Background")));
            mColorButton = new WebRadioButton(Tr.tr("Color:") + " ");
            Optional<Color> optBGColor = mThreadItem.mValue.getViewSettings().getBGColor();
            mColorButton.setSelected(optBGColor.isPresent());
            mColorButton.addItemListener(new ItemListener() {
                @Override
                public void itemStateChanged(ItemEvent e) {
                    mColorChooserButton.setEnabled(e.getStateChange() == ItemEvent.SELECTED);
                }
            });
            mColorChooserButton = new WebButton();
            mColorChooserButton.setEnabled(optBGColor.isPresent());
            mColorChooserButton.setMinimumHeight(25);
            Color oldColor = optBGColor.orElse(DEFAULT_BG);
            mColorChooserButton.setBottomBgColor(oldColor);
            mColorChooserButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    EditThreadDialog.this.editColor();
                }
            });
            mColorChooser = new WebColorChooserDialog(this);
            mColorChooser.setColor(oldColor);
            groupPanel.add(new GroupPanel(GroupingType.fillLast, mColorButton, mColorChooserButton));

            mImgButton = new WebRadioButton(Tr.tr("Image:") + " ");
            String imgPath = mThreadItem.mValue.getViewSettings().getImagePath();
            mImgButton.setSelected(!imgPath.isEmpty());
            mImgButton.addItemListener(new ItemListener() {
                @Override
                public void itemStateChanged(ItemEvent e) {
                    mImgChooser.setEnabled(e.getStateChange() == ItemEvent.SELECTED);
                    mImgChooser.getChooseButton().setEnabled(e.getStateChange() == ItemEvent.SELECTED);
                }
            });
            mImgChooser = View.createImageChooser(!imgPath.isEmpty(), imgPath);
            groupPanel.add(new GroupPanel(GroupingType.fillLast, mImgButton, mImgChooser));
            UnselectableButtonGroup.group(mColorButton, mImgButton);
            groupPanel.add(new WebSeparator());

            groupPanel.add(new WebLabel(Tr.tr("Participants:")));
            mParticipantsList = new WebCheckBoxList();
            mParticipantsList.setVisibleRowCount(10);
            for (User oneUser : UserList.getInstance().getAll()) {
                boolean selected = threadItem.mValue.getUser().contains(oneUser);
                mParticipantsList.getCheckBoxListModel().addCheckBoxElement(new UserElement(oneUser), selected);
            }
            final WebButton saveButton = new WebButton(Tr.tr("Save"));
            mParticipantsList.getModel().addListDataListener(new ListDataListener() {
                @Override
                public void intervalAdded(ListDataEvent e) {
                }

                @Override
                public void intervalRemoved(ListDataEvent e) {
                }

                @Override
                public void contentsChanged(ListDataEvent e) {
                    saveButton.setEnabled(!mParticipantsList.getCheckedValues().isEmpty());
                }
            });

            groupPanel.add(new WebScrollPane(mParticipantsList));
            groupPanel.add(new WebSeparator(true, true));

            this.add(groupPanel, BorderLayout.CENTER);

            // buttons
            WebButton cancelButton = new WebButton(Tr.tr("Cancel"));
            cancelButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    EditThreadDialog.this.dispose();
                }
            });

            saveButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (mParticipantsList.getCheckedValues().size() > 1) {
                        String infoText = Tr.tr("More than one receiver not supported (yet).");
                        WebOptionPane.showMessageDialog(ThreadListView.this, infoText, Tr.tr("Sorry"),
                                WebOptionPane.INFORMATION_MESSAGE);
                        return;
                    }
                    EditThreadDialog.this.saveThread();
                    EditThreadDialog.this.dispose();
                }
            });
            this.getRootPane().setDefaultButton(saveButton);

            GroupPanel buttonPanel = new GroupPanel(2, cancelButton, saveButton);
            buttonPanel.setLayout(new FlowLayout(FlowLayout.TRAILING));
            this.add(buttonPanel, BorderLayout.SOUTH);

            this.pack();
        }

        private void editColor() {
            mColorChooser.setVisible(true);
            if (mColorChooser.getResult() == DialogOptions.OK_OPTION) {
                mColorChooserButton.setBottomBgColor(mColorChooser.getColor());
            }
        }

        private void saveThread() {
            if (!mSubjectField.getText().equals(mThreadItem.mValue.getSubject())) {
                mThreadItem.mValue.setSubject(mSubjectField.getText());
            }
            List<?> participants = mParticipantsList.getCheckedValues();
            Set<User> threadUser = new HashSet<>();
            for (Object o : participants) {
                threadUser.add(((UserElement) o).user);
            }
            mThreadItem.mValue.setUser(threadUser);

            ViewSettings newSettings;
            if (mColorButton.isSelected())
                newSettings = new ViewSettings(mColorChooser.getColor());
            else if (mImgButton.isSelected() && !mImgChooser.getSelectedFiles().isEmpty())
                newSettings = new ViewSettings(mImgChooser.getSelectedFiles().get(0).getAbsolutePath());
            else
                newSettings = new ViewSettings();

            if (!newSettings.equals(mThreadItem.mValue.getViewSettings())) {
                mThreadItem.mValue.setViewSettings(newSettings);
            }
        }

        private class UserElement {
            User user;

            UserElement(User user) {
                this.user = user;
            }

            @Override
            public String toString() {
                String jid = "<" + View.shortenJID(user.getJID(), 40) + ">";
                String name = StringUtils.abbreviate(user.getName(), 24);
                return name.isEmpty() ? jid : name + " " + jid;
            }
        }
    }
}