Java tutorial
/* * 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.panel.GroupPanel; import com.alee.extended.panel.GroupingType; import com.alee.laf.button.WebButton; import com.alee.laf.checkbox.WebCheckBox; 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.panel.WebPanel; import com.alee.laf.rootpane.WebDialog; import com.alee.laf.separator.WebSeparator; import com.alee.laf.text.WebTextField; import com.alee.managers.tooltip.TooltipManager; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.HashSet; import java.util.Observable; import java.util.Observer; import java.util.Set; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import org.apache.commons.lang.StringEscapeUtils; import org.kontalk.model.User; import org.kontalk.model.UserList; import org.kontalk.system.Control; import org.kontalk.util.Tr; import org.kontalk.util.XMPPUtils; import static org.kontalk.view.TableView.TOOLTIP_DATE_FORMAT; import org.kontalk.view.UserListView.UserItem; /** * Display all user (aka contacts) in a brief list. * @author Alexander Bikadorov <abiku@cs.tu-berlin.de> */ final class UserListView extends TableView<UserItem, User> implements Observer { private final View mView; private final UserList mUserList; private final UserPopupMenu mPopupMenu; UserListView(final View view, UserList userList) { super(); mView = view; mUserList = userList; this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); //this.setDragEnabled(true); // right click popup menu mPopupMenu = new UserPopupMenu(); // actions triggered by mouse events this.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { mView.selectThreadByUser(UserListView.this.getSelectedValue()); } } @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 = UserListView.this.rowAtPoint(e.getPoint()); UserListView.this.setSelectedItem(row); UserListView.this.showPopupMenu(e); } } }); this.updateOnEDT(null); } @Override protected void updateOnEDT(Object arg) { Set<UserItem> newItems = new HashSet<>(); Set<User> user = mUserList.getAll(); for (User oneUser : user) if (!this.containsValue(oneUser)) newItems.add(new UserItem(oneUser)); this.sync(user, newItems); } private void showPopupMenu(MouseEvent e) { // note: only work when right click does also selection mPopupMenu.show(this.getSelectedItem(), this, e.getX(), e.getY()); } /** One item in the contact list representing a user. */ final class UserItem extends TableView<UserItem, User>.TableItem { private final WebLabel mNameLabel; private final WebLabel mJIDLabel; private Color mBackround; UserItem(User user) { super(user); //this.setPaintFocus(true); this.setMargin(5); this.setLayout(new BorderLayout(10, 5)); mNameLabel = new WebLabel("foo"); mNameLabel.setFontSize(14); this.add(mNameLabel, BorderLayout.CENTER); mJIDLabel = new WebLabel("foo"); mJIDLabel.setForeground(Color.GRAY); mJIDLabel.setFontSize(11); this.add(mJIDLabel, BorderLayout.SOUTH); this.updateOnEDT(null); } @Override public String getTooltipText() { String html = "<html><body>"; //"<h3>Header</h3>" + if (mValue.getOnline() == User.Online.YES) html += Tr.tr("Online") + "<br>"; if (!mValue.getStatus().isEmpty()) { String status = StringEscapeUtils.escapeHtml(mValue.getStatus()); html += Tr.tr("Status") + ": " + status + "<br>"; } if (mValue.getOnline() != User.Online.YES) { String lastSeen = !mValue.getLastSeen().isPresent() ? Tr.tr("?") : TOOLTIP_DATE_FORMAT.format(mValue.getLastSeen().get()); html += Tr.tr("Last seen") + ": " + lastSeen + "<br>"; } if (mValue.isBlocked()) { html += Tr.tr("Contact is blocked!") + "<br>"; } return html + "</body></html>"; } @Override protected void render(int tableWidth, boolean isSelected) { if (isSelected) this.setBackground(View.BLUE); else this.setBackground(mBackround); } @Override protected boolean contains(String search) { return mValue.getName().toLowerCase().contains(search) || mValue.getJID().toLowerCase().contains(search); } @Override protected void updateOnEDT(Object arg) { // may have changed (of user): JID, name, online String jid = mValue.getJID(); if (XMPPUtils.isHash(jid)) ; jid = View.shortenUserName(jid, 9); mJIDLabel.setText(jid); String name = !mValue.getName().isEmpty() ? mValue.getName() : Tr.tr("<unknown>"); mNameLabel.setText(name); mBackround = mValue.getOnline() == User.Online.YES ? View.LIGHT_BLUE : Color.WHITE; this.setBackground(mBackround); } } private class UserPopupMenu extends WebPopupMenu { UserItem mItem; WebMenuItem mBlockMenuItem; WebMenuItem mUnblockMenuItem; UserPopupMenu() { WebMenuItem newMenuItem = new WebMenuItem(Tr.tr("New Thread")); newMenuItem.setToolTipText(Tr.tr("Creates a new thread for this contact")); newMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { Set<User> user = new HashSet<>(); user.add(mItem.mValue); UserListView.this.mView.callCreateNewThread(user); } }); this.add(newMenuItem); WebMenuItem editMenuItem = new WebMenuItem(Tr.tr("Edit Contact")); editMenuItem.setToolTipText(Tr.tr("Edit this contact")); editMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { EditUserDialog editUserDialog = new EditUserDialog(mItem); mItem.mValue.addObserver(editUserDialog); editUserDialog.setVisible(true); } }); this.add(editMenuItem); mBlockMenuItem = new WebMenuItem(Tr.tr("Block Contact")); mBlockMenuItem.setToolTipText(Tr.tr("Block all messages from this contact")); mBlockMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { UserListView.this.mView.callSetUserBlocking(mItem.mValue, true); } }); this.add(mBlockMenuItem); mUnblockMenuItem = new WebMenuItem(Tr.tr("Unblock Contact")); mUnblockMenuItem.setToolTipText(Tr.tr("Unblock this contact")); mUnblockMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { UserListView.this.mView.callSetUserBlocking(mItem.mValue, false); } }); this.add(mUnblockMenuItem); WebMenuItem deleteMenuItem = new WebMenuItem(Tr.tr("Delete Contact")); deleteMenuItem.setToolTipText(Tr.tr("Delete this contact")); deleteMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { // TODO delete threads/messages too? android client may add it // to roster again? useful at all? only self-created contacts? } }); // see above //this.add(deleteMenuItem); } void show(UserItem item, Component invoker, int x, int y) { mItem = item; if (mItem.mValue.isBlocked()) { mBlockMenuItem.setVisible(false); mUnblockMenuItem.setVisible(true); } else { mBlockMenuItem.setVisible(true); mUnblockMenuItem.setVisible(false); } Control.Status status = UserListView.this.mView.getCurrentStatus(); mBlockMenuItem.setEnabled(status == Control.Status.CONNECTED); mUnblockMenuItem.setEnabled(status == Control.Status.CONNECTED); this.show(invoker, x, y); } } private class EditUserDialog extends WebDialog implements Observer { private final UserItem mItem; private final WebTextField mNameField; private final WebLabel mKeyLabel; private final WebLabel mFPLabel; private final WebTextField mFPField; private final WebCheckBox mEncryptionBox; private String mJID; EditUserDialog(UserItem item) { mItem = item; this.setTitle(Tr.tr("Edit Contact")); this.setResizable(false); this.setModal(true); GroupPanel groupPanel = new GroupPanel(10, false); groupPanel.setMargin(5); // editable fields WebPanel namePanel = new WebPanel(); namePanel.setLayout(new BorderLayout(10, 5)); namePanel.add(new WebLabel(Tr.tr("Display Name:")), BorderLayout.WEST); mNameField = new WebTextField(); mNameField.setHideInputPromptOnFocus(false); namePanel.add(mNameField, BorderLayout.CENTER); groupPanel.add(namePanel); groupPanel.add(new WebSeparator(true, true)); mKeyLabel = new WebLabel(); WebButton updButton = new WebButton(View.getIcon("ic_ui_refresh.png")); String updText = Tr.tr("Update key"); TooltipManager.addTooltip(updButton, updText); updButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { UserListView.this.mView.callRequestKey(EditUserDialog.this.mItem.mValue); } }); groupPanel.add(new GroupPanel(6, mKeyLabel, updButton)); mFPLabel = new WebLabel(Tr.tr("Fingerprint:") + " "); mFPField = View.createTextField(""); String fpText = Tr.tr("The unique ID of this contact's key"); TooltipManager.addTooltip(mFPField, fpText); groupPanel.add(new GroupPanel(mFPLabel, mFPField)); this.updateOnEDT(); mEncryptionBox = new WebCheckBox(Tr.tr("Use Encryption")); mEncryptionBox.setAnimated(false); mEncryptionBox.setSelected(mItem.mValue.getEncrypted()); String encText = Tr.tr("Encrypt and sign all messages send to this contact"); TooltipManager.addTooltip(mEncryptionBox, encText); groupPanel.add(new GroupPanel(mEncryptionBox, new WebSeparator())); groupPanel.add(new WebSeparator(true, true)); final int l = 50; mJID = mItem.mValue.getJID(); final WebTextField jidField = new WebTextField(View.shortenJID(mJID, l)); jidField.setDrawBorder(false); jidField.setMinimumHeight(20); jidField.setInputPrompt(mJID); jidField.setHideInputPromptOnFocus(false); jidField.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent e) { jidField.setText(mJID); jidField.setDrawBorder(true); } @Override public void focusLost(FocusEvent e) { mJID = jidField.getText(); jidField.setText(View.shortenJID(mJID, l)); jidField.setDrawBorder(false); } }); String jidText = Tr.tr("The unique address of this contact"); TooltipManager.addTooltip(jidField, jidText); groupPanel.add(new GroupPanel(GroupingType.fillLast, new WebLabel("JID: "), jidField)); 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) { EditUserDialog.this.close(); } }); final WebButton saveButton = new WebButton("Save"); saveButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (!EditUserDialog.this.isConfirmed()) return; EditUserDialog.this.save(); EditUserDialog.this.close(); } }); this.getRootPane().setDefaultButton(saveButton); GroupPanel buttonPanel = new GroupPanel(2, cancelButton, saveButton); buttonPanel.setLayout(new FlowLayout(FlowLayout.TRAILING)); this.add(buttonPanel, BorderLayout.SOUTH); this.pack(); } @Override public void update(Observable o, final Object arg) { if (SwingUtilities.isEventDispatchThread()) { this.updateOnEDT(); return; } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { EditUserDialog.this.updateOnEDT(); } }); } private void updateOnEDT() { // may have changed: user name and/or key mNameField.setText(mItem.mValue.getName()); mNameField.setInputPrompt(mItem.mValue.getName()); String hasKey = "<html>" + Tr.tr("Encryption Key") + ": "; if (mItem.mValue.hasKey()) { hasKey += Tr.tr("Available") + "</html>"; TooltipManager.removeTooltips(mKeyLabel); mFPField.setText(mItem.mValue.getFingerprint()); mFPLabel.setVisible(true); mFPField.setVisible(true); } else { hasKey += "<font color='red'>" + Tr.tr("Not Available") + "</font></html>"; String keyText = Tr.tr("The key for this user could not yet be received"); TooltipManager.addTooltip(mKeyLabel, keyText); mFPLabel.setVisible(false); mFPField.setVisible(false); } mKeyLabel.setText(hasKey); } private boolean isConfirmed() { if (!mJID.equals(mItem.mValue.getJID())) { String warningText = Tr.tr("Changing the JID is only useful in very rare cases. Are you sure?"); int selectedOption = WebOptionPane.showConfirmDialog(this, warningText, Tr.tr("Please Confirm"), WebOptionPane.OK_CANCEL_OPTION, WebOptionPane.WARNING_MESSAGE); if (selectedOption != WebOptionPane.OK_OPTION) { return false; } } return true; } private void save() { String newName = mNameField.getText(); if (!newName.equals(mItem.mValue.getName())) { mItem.mValue.setName(mNameField.getText()); } mItem.mValue.setEncrypted(mEncryptionBox.isSelected()); if (!mJID.isEmpty() && !mJID.equals(mItem.mValue.getJID())) { mItem.mValue.setJID(mJID); } } private void close() { this.dispose(); this.mItem.mValue.deleteObserver(this); } } }