/*
* Kontalk Java client
* Copyright (C) 2016 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 javax.swing.Box;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Optional;
import com.alee.extended.layout.FormLayout;
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.optionpane.WebOptionPane;
import com.alee.laf.panel.WebPanel;
import com.alee.laf.separator.WebSeparator;
import com.alee.laf.text.WebTextArea;
import com.alee.laf.text.WebTextField;
import com.alee.managers.tooltip.TooltipManager;
import org.kontalk.misc.JID;
import org.kontalk.model.Contact;
import org.kontalk.util.Tr;
import org.kontalk.view.AvatarLoader.AvatarImg;
import org.kontalk.view.ComponentUtils.LabelTextField;
/**
* Show and edit contact details.
*
* @author Alexander Bikadorov {@literal <bikaejkb@mail.tu-berlin.de>}
*/
final class ContactDetails extends WebPanel implements ObserverTrait {
private static final Map<Contact, ContactDetails> CACHE = new HashMap<>();
private final View mView;
private final Contact mContact;
private final ComponentUtils.EditableAvatarImage mAvatarImage;
private final WebTextField mNameField;
private final WebLabel mSubscrStatus;
private final WebButton mSubscrButton;
private final WebLabel mKeyStatus;
private final WebLabel mFPLabel;
private final WebButton mUpdateButton;
private final WebTextArea mFPArea;
private final WebCheckBox mEncryptionBox;
private ContactDetails(View view, Contact contact) {
mView = view;
mContact = contact;
GroupPanel groupPanel = new GroupPanel(View.GAP_BIG, false);
groupPanel.setMargin(View.MARGIN_BIG);
groupPanel.add(new WebLabel(Tr.tr("Contact details")).setBoldFont());
groupPanel.add(new WebSeparator(true, true));
WebPanel mainPanel = new WebPanel(new FormLayout(View.GAP_DEFAULT, View.GAP_DEFAULT));
// editable components
mAvatarImage = new ComponentUtils.EditableAvatarImage(View.AVATAR_DETAIL_SIZE) {
@Override
void onImageChange(Optional<BufferedImage> optImage) {
if (optImage.isPresent())
mView.getControl().setCustomContactAvatar(mContact, optImage.get());
else
mView.getControl().unsetCustomContactAvatar(mContact);
}
@Override
AvatarImg defaultImage() {
return AvatarLoader.load(mContact, View.AVATAR_DETAIL_SIZE);
}
@Override
boolean canRemove() {
return mContact.hasCustomAvatarSet();
}
@Override
protected void update() {
if (mContact.hasCustomAvatarSet())
return;
super.update();
}
};
int size = View.AVATAR_DETAIL_SIZE + View.MARGIN_DEFAULT;
mainPanel.add(new WebPanel(false, mAvatarImage)
.setPreferredWidth(size).setPreferredHeight(size));
mainPanel.add(Box.createGlue());
mainPanel.add(new WebLabel(Tr.tr("Display Name:")));
mNameField = new LabelTextField(View.MAX_NAME_LENGTH, 15, this) {
@Override
protected String labelText() {
return mContact.getName();
}
@Override
protected String editText() {
return mContact.getName();
}
@Override
protected void onFocusLost() {
ContactDetails.this.saveName(this.getText().trim());
}
};
mNameField.setFontSizeAndStyle(14, true, false);
mainPanel.add(mNameField);
mainPanel.add(new WebLabel("Jabber ID:"));
LabelTextField jidField =
new LabelTextField(View.MAX_JID_LENGTH, 20, this) {
@Override
protected String labelText() {
return Utils.jid(mContact.getJID(), View.PRETTY_JID_LENGTH);
}
@Override
protected String editText() {
return mContact.getJID().asUnescapedString();
}
@Override
protected void onFocusLost() {
ContactDetails.this.saveJID(JID.bare(this.getText().trim()));
}
};
String jidText = Tr.tr("The unique address of this contact");
TooltipManager.addTooltip(jidField, jidText);
mainPanel.add(jidField);
mainPanel.add(new WebLabel(Tr.tr("Authorization:")));
mSubscrStatus = new WebLabel();
String subscrText = Tr.tr("Permission to view presence status and public key");
TooltipManager.addTooltip(mSubscrStatus, subscrText);
mSubscrButton = new WebButton(Tr.tr("Request"));
String reqText = Tr.tr("Request status authorization from contact");
TooltipManager.addTooltip(mSubscrButton, reqText);
mSubscrButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
mView.getControl().sendSubscriptionRequest(mContact);
}
});
mainPanel.add(new GroupPanel(GroupingType.fillFirst,
View.GAP_DEFAULT, mSubscrStatus, mSubscrButton));
groupPanel.add(mainPanel);
groupPanel.add(new WebSeparator(true, true));
WebPanel keyPanel = new WebPanel(new FormLayout(View.GAP_DEFAULT, View.GAP_DEFAULT));
keyPanel.add(new WebLabel(Tr.tr("Public Key")+":"));
mKeyStatus = new WebLabel();
mUpdateButton = new WebButton(Utils.getIcon("ic_ui_reload.png"));
String updText = Tr.tr("Update key");
TooltipManager.addTooltip(mUpdateButton, updText);
mUpdateButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
mView.getControl().requestKey(ContactDetails.this.mContact);
}
});
keyPanel.add(new GroupPanel(GroupingType.fillFirst,
View.GAP_DEFAULT, mKeyStatus, mUpdateButton));
mFPLabel = new WebLabel(Tr.tr("Fingerprint:"));
keyPanel.add(mFPLabel);
mFPArea = Utils.createFingerprintArea();
String fpText = Tr.tr("The unique ID of this contact's key");
TooltipManager.addTooltip(mFPArea, fpText);
mFPLabel.setAlignmentY(Component.TOP_ALIGNMENT);
keyPanel.add(mFPArea);
// set everything that can change
this.updateOnEDT(null, null);
groupPanel.add(keyPanel);
mEncryptionBox = new WebCheckBox(Tr.tr("Use Encryption"));
mEncryptionBox.setAnimated(false);
mEncryptionBox.setSelected(mContact.getEncrypted());
String encText = Tr.tr("Encrypt and sign all messages send to this contact");
TooltipManager.addTooltip(mEncryptionBox, encText);
mEncryptionBox.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
mContact.setEncrypted(mEncryptionBox.isSelected());
}
});
groupPanel.add(new GroupPanel(mEncryptionBox, Box.createGlue()));
this.add(groupPanel, BorderLayout.WEST);
WebPanel gradientPanel = new WebPanel(false) {
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
int w = this.getWidth();
int h = this.getHeight();
BufferedImage mCached = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D cachedG = mCached.createGraphics();
GradientPaint p2 = new GradientPaint(
0, 0, this.getBackground(),
w, 0, Color.LIGHT_GRAY);
cachedG.setPaint(p2);
cachedG.fillRect(0, 0, w, h);
g.drawImage(mCached, 0, 0, this.getWidth(), this.getHeight(), null);
}
};
this.add(gradientPanel, BorderLayout.CENTER);
}
void setRenameFocus() {
mNameField.requestFocusInWindow();
}
@Override
public void updateOnEDT(Observable o, Object arg) {
// may have changed: avatar...
mAvatarImage.update();
// ...contact name...
mNameField.setText(mContact.getName());
mNameField.setInputPrompt(mContact.getName());
Contact.Subscription subscription = mContact.getSubScription();
String auth = Tr.tr("Unknown");
switch(subscription) {
case PENDING: auth = Tr.tr("Awaiting reply"); break;
case SUBSCRIBED: auth = Tr.tr("Authorized"); break;
case UNSUBSCRIBED: auth = Tr.tr("Not authorized"); break;
}
// ...subscription...
mSubscrButton.setVisible(subscription != Contact.Subscription.SUBSCRIBED);
mSubscrButton.setEnabled(subscription == Contact.Subscription.UNSUBSCRIBED);
mSubscrStatus.setText(auth);
// ...and/or key
String hasKey = "<html>";
if (mContact.hasKey()) {
hasKey += Tr.tr("Available")+"</html>";
TooltipManager.removeTooltips(mKeyStatus);
mFPArea.setText(Utils.fingerprint(mContact.getFingerprint()));
mFPLabel.setVisible(true);
mFPArea.setVisible(true);
} else {
hasKey += "<font color='red'>"+Tr.tr("Not Available")+"</font></html>";
String keyText = Tr.tr("The public key for this contact could not yet be received");
TooltipManager.addTooltip(mKeyStatus, keyText);
mFPLabel.setVisible(false);
mFPArea.setVisible(false);
}
mKeyStatus.setText(hasKey);
mUpdateButton.setEnabled(mContact.isKontalkUser() &&
subscription == Contact.Subscription.SUBSCRIBED);
}
private void saveName(String name) {
if (name.equals(mContact.getName()))
return;
mView.getControl().changeName(mContact, name);
}
private void saveJID(JID jid) {
if (!jid.isValid() || jid.equals(mContact.getJID()))
// TODO feedback for invalid jid
return;
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)
mView.getControl().changeJID(mContact, jid);
}
static ContactDetails instance(View view, Contact contact) {
if (!CACHE.containsKey(contact)) {
ContactDetails newContactDetails = new ContactDetails(view, contact);
contact.addObserver(newContactDetails);
CACHE.put(contact, newContactDetails);
}
return CACHE.get(contact);
}
}