diff --git a/.gitignore b/.gitignore index b57a2e0e2..591e1fe44 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ sip-communicator.utest.bin release lib/accounts.properties .DS_Store +nbproject/private diff --git a/lib/installer-exclude/ice4j.jar b/lib/installer-exclude/ice4j.jar index 31773f731..29f5cc609 100644 Binary files a/lib/installer-exclude/ice4j.jar and b/lib/installer-exclude/ice4j.jar differ diff --git a/lib/installer-exclude/libjitsi.jar b/lib/installer-exclude/libjitsi.jar index f9a0ca7cc..3a63713e7 100644 Binary files a/lib/installer-exclude/libjitsi.jar and b/lib/installer-exclude/libjitsi.jar differ diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 000000000..338575b80 --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,29 @@ +auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs=true +auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width=4 +auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab=4 +auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.tab-size=8 +auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width=80 +auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap=none +auxiliary.org-netbeans-modules-editor-indent.CodeStyle.usedProfile=project +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineImplements=true +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineThrows=true +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement=NEW_LINE +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs=true +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.importGroupsOrder=* +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width=4 +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement=NEW_LINE +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement=NEW_LINE +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeCatchOnNewLine=true +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeElseOnNewLine=true +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeFinallyOnNewLine=true +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeWhileOnNewLine=true +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab=4 +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size=8 +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width=80 +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-line-wrap=words +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.usePackageImport=true +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.useSingleClassImport=false +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsKeyword=WRAP_ALWAYS +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsList=WRAP_ALWAYS +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsKeyword=WRAP_ALWAYS +auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsList=WRAP_ALWAYS diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties index f9d4e12d8..65e1d8121 100644 --- a/resources/languages/resources.properties +++ b/resources/languages/resources.properties @@ -720,23 +720,24 @@ identity cannot
be automatically verified. \ Do you want to continue connecting?

\ For more information, click "Show Certificate". service.gui.CONTINUE_ANYWAY=Continue anyway -service.gui.CERT_INFO_ISSUED_TO=Issued To +service.gui.CERT_INFO_CHAIN=Certificate Chain: +service.gui.CERT_INFO_ISSUED_TO=Issued To service.gui.CERT_INFO_CN=Common Name: service.gui.CERT_INFO_O=Organization: service.gui.CERT_INFO_C=Country Name: service.gui.CERT_INFO_ST=State or Province Name: service.gui.CERT_INFO_L=Locality Name: -service.gui.CERT_INFO_ISSUED_BY=Issued By +service.gui.CERT_INFO_ISSUED_BY=Issued By service.gui.CERT_INFO_OU=Organizational Unit: -service.gui.CERT_INFO_VALIDITY=Validity +service.gui.CERT_INFO_VALIDITY=Validity service.gui.CERT_INFO_ISSUED_ON=Issued On: service.gui.CERT_INFO_EXPIRES_ON=Expires On: -service.gui.CERT_INFO_FINGERPRINTS=Fingerprints -service.gui.CERT_INFO_CERT_DETAILS=Certificate Info +service.gui.CERT_INFO_FINGERPRINTS=Fingerprints +service.gui.CERT_INFO_CERT_DETAILS=Certificate Info service.gui.CERT_INFO_SER_NUM=Serial Number: service.gui.CERT_INFO_VER=Version: service.gui.CERT_INFO_SIGN_ALG=Signature Algorithm: -service.gui.CERT_INFO_PUB_KEY_INFO=Public Key Info +service.gui.CERT_INFO_PUB_KEY_INFO=Public Key Info service.gui.CERT_INFO_ALG=Algorithm: service.gui.CERT_INFO_PUB_KEY=Public Key: service.gui.CERT_INFO_KEY_BYTES_PRINT={0} bytes: {1} @@ -1702,8 +1703,11 @@ plugin.reconnectplugin.NETWORK_DOWN=Network connectivity lost! plugin.chatconfig.TITLE=Chat plugin.chatconfig.replacement.TITLE=Image/Video: plugin.chatconfig.replacement.ENABLE_SMILEY_STATUS=Enable smiley replacement -plugin.chatconfig.replacement.ENABLE_REPLACEMENT_STATUS=Enable Image/Video replacement -plugin.chatconfig.replacement.ENABLE_REPLACEMENT_PROPOSAL=Show proposals for Image/Video replacement +plugin.chatconfig.replacement.REPLACEMENT_TITLE=Image/Video replacement +plugin.chatconfig.replacement.ENABLE_REPLACEMENT_STATUS=Enable +plugin.chatconfig.replacement.ENABLE_REPLACEMENT_PROPOSAL=Disable (show preview link) +plugin.chatconfig.replacement.DISABLE_REPLACEMENT=Disable +plugin.chatconfig.replacement.CONFIGURE_REPLACEMENT=Change Image/Video replacement settings plugin.chatconfig.replacement.REPLACEMENT_SOURCES=Sources: plugin.chatconfig.spellcheck.TITLE=SpellCheck diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java index 0b84eee51..51fc66963 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java @@ -1674,7 +1674,7 @@ private void initializeUserInterfaceHierarchy() settingsPanel.add(recordButton); if (showHideVideoButton != null) settingsPanel.add(showHideVideoButton); - if (mergeButton != null) + if (transferCallButton != null) settingsPanel.add(transferCallButton); if (videoButton != null) settingsPanel.add(videoButton); diff --git a/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPanel.java index 582a114e8..a32b70f48 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPanel.java @@ -118,7 +118,14 @@ public OneToOneCallPanel( addCallPeerPanel(callPeer, uiVideoHandler); - setPreferredSize(new Dimension(400, 400)); + int preferredHeight = 400; + if(GuiActivator.getConfigurationService().getBoolean( + OneToOneCallPeerPanel.HIDE_PLACEHOLDER_PIC_PROP, + false)) + { + preferredHeight = 128; + } + setPreferredSize(new Dimension(400, preferredHeight)); setTransferHandler(new CallTransferHandler(call)); this.callContainer.addPropertyChangeListener( @@ -457,4 +464,4 @@ public void updateHoldButtonState() { callContainer.updateHoldButtonState(); } -} \ No newline at end of file +} diff --git a/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java index a0d9df77d..39bd9bac3 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java @@ -60,6 +60,13 @@ public class OneToOneCallPeerPanel */ private static final long serialVersionUID = 0L; + /** + * Property to disable showing the dummy picture for the peer when no + * streams present. + */ + public static final String HIDE_PLACEHOLDER_PIC_PROP + = "net.java.sip.communicator.impl.gui.main.call.HIDE_PLACEHOLDER_PIC"; + /** * The CallPeer, which is rendered in this panel. */ @@ -277,7 +284,15 @@ public OneToOneCallPeerPanel( detailsChangeListener); securityPanel = SecurityPanel.create(this, callPeer, null); - photoLabel = new JLabel(getPhotoLabelIcon()); + ImageIcon icon = getPhotoLabelIcon(); + if(icon != null) + { + photoLabel = new JLabel(icon); + } + else + { + photoLabel = new JLabel(); + } center = createCenter(); statusBar = createStatusBar(); @@ -345,6 +360,21 @@ public OneToOneCallPeerPanel( updateViewFromModel(); } + /** + * Set size of the {@link #photoLabel} + */ + private void sizePhotoLabel() + { + if(photoLabel.getIcon() != null) + { + photoLabel.setPreferredSize(new Dimension(90, 90)); + } + else + { + photoLabel.setPreferredSize(new Dimension(0, 0)); + } + } + /** * Creates the Component hierarchy of the central area of this * CallPeerPanel which displays the photo of the CallPeer @@ -352,7 +382,7 @@ public OneToOneCallPeerPanel( */ private VideoContainer createCenter() { - photoLabel.setPreferredSize(new Dimension(90, 90)); + sizePhotoLabel(); return createVideoContainer(photoLabel); } @@ -584,6 +614,22 @@ public String getPeerName() return peerName; } + /** + * Tests a provided boolean property name, returning false if it should be + * hidden. + * + * @param componentHidePropertyName the name of the boolean property to + * check. + * @return false if the component should be hidden, true otherwise. + * + */ + private boolean isComponentEnabled(String componentHidePropertyName) + { + return !GuiActivator.getConfigurationService().getBoolean( + componentHidePropertyName, + false); + } + /** * Gets the Icon to be displayed in {@link #photoLabel}. * @@ -592,7 +638,7 @@ public String getPeerName() private ImageIcon getPhotoLabelIcon() { return - (peerImage == null) + (peerImage == null && isComponentEnabled(HIDE_PLACEHOLDER_PIC_PROP)) ? new ImageIcon( ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO)) : peerImage; @@ -677,10 +723,18 @@ public void loadSkin() if(peerImage == null) { - photoLabel.setIcon( - new ImageIcon( - ImageLoader.getImage( - ImageLoader.DEFAULT_USER_PHOTO))); + if(isComponentEnabled(HIDE_PLACEHOLDER_PIC_PROP)) + { + photoLabel.setIcon( + new ImageIcon( + ImageLoader.getImage( + ImageLoader.DEFAULT_USER_PHOTO))); + } + else + { + photoLabel.setIcon(null); + } + sizePhotoLabel(); } } @@ -1108,6 +1162,7 @@ public void setPeerImage(byte[] image) public void run() { photoLabel.setIcon(peerImage); + sizePhotoLabel(); photoLabel.repaint(); } }); @@ -1115,6 +1170,7 @@ public void run() else { photoLabel.setIcon(peerImage); + sizePhotoLabel(); photoLabel.repaint(); } } @@ -1582,4 +1638,4 @@ public void setInterested(boolean value) this.interested = value; } } -} \ No newline at end of file +} diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java b/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java index 716616408..016a576b2 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java @@ -28,6 +28,7 @@ import net.java.sip.communicator.impl.gui.main.chat.history.*; import net.java.sip.communicator.impl.gui.main.chat.menus.*; import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.impl.gui.utils.Constants; import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.plugin.desktoputil.SwingWorker; import net.java.sip.communicator.service.gui.*; @@ -41,6 +42,8 @@ import net.java.sip.communicator.util.skin.*; import org.jitsi.util.*; +import org.osgi.framework.*; + /** * The ChatConversationPanel is the panel, where all sent and received * messages appear. All data is stored in an HTML document. An external CSS file @@ -136,11 +139,26 @@ public class ChatConversationPanel */ private String currentHref; + /** + * The currently shown href, is it an img element. + */ + private boolean isCurrentHrefImg = false; + /** * The copy link item, contained in the right mouse click menu. */ private final JMenuItem copyLinkItem; + /** + * The copy link item, contained in the right mouse click menu. + */ + private final JMenuItem configureReplacementItem; + + /** + * The configure replacement item separator. + */ + private final JSeparator configureReplacementSeparator = new JSeparator(); + /** * The open link item, contained in the right mouse click menu. */ @@ -310,6 +328,30 @@ public void actionPerformed(ActionEvent e) GuiActivator.getResources().getI18nMnemonic( "service.gui.COPY_LINK")); + configureReplacementItem = new JMenuItem( + GuiActivator.getResources().getI18NString( + "plugin.chatconfig.replacement.CONFIGURE_REPLACEMENT"), + GuiActivator.getResources().getImage( + "service.gui.icons.CONFIGURE_ICON")); + + configureReplacementItem.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + final ConfigurationContainer configContainer + = GuiActivator.getUIService().getConfigurationContainer(); + + ConfigurationForm chatConfigForm = getChatConfigForm(); + + if (chatConfigForm != null) + { + configContainer.setSelected(chatConfigForm); + + configContainer.setVisible(true); + } + } + }); + this.isSimpleTheme = ConfigurationUtils.isChatSimpleThemeEnabled(); /* @@ -1171,11 +1213,14 @@ public void hyperlinkUpdate(HyperlinkEvent e) { String href = e.getDescription(); + this.isCurrentHrefImg + = e.getSourceElement().getName().equals("img"); this.currentHref = href; } else if (e.getEventType() == HyperlinkEvent.EventType.EXITED) { this.currentHref = ""; + this.isCurrentHrefImg = false; } } @@ -1228,7 +1273,7 @@ else if ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0 catch (URISyntaxException e1) { logger.error("Failed to open hyperlink in chat window. " + - "Error was: Invalid URL - " + currentHref); + "Error was: Invalid URL - " + currentHref); return; } if("jitsi".equals(uri.getScheme())) @@ -1261,12 +1306,19 @@ private void openContextMenu(Point p) rightButtonMenu.insert(openLinkItem, 0); rightButtonMenu.insert(copyLinkItem, 1); rightButtonMenu.insert(copyLinkSeparator, 2); + if(isCurrentHrefImg) + { + rightButtonMenu.insert(configureReplacementItem, 3); + rightButtonMenu.insert(configureReplacementSeparator, 4); + } } else { rightButtonMenu.remove(openLinkItem); rightButtonMenu.remove(copyLinkItem); rightButtonMenu.remove(copyLinkSeparator); + rightButtonMenu.remove(configureReplacementItem); + rightButtonMenu.remove(configureReplacementSeparator); } if (chatTextPane.getSelectedText() != null) @@ -1956,6 +2008,60 @@ private String getElementContent(String elementId, String message) return null; } + /** + * Returns the first available advanced configuration form. + * + * @return the first available advanced configuration form + */ + public static ConfigurationForm getChatConfigForm() + { + // General configuration forms only. + String osgiFilter = "(" + + ConfigurationForm.FORM_TYPE + + "="+ConfigurationForm.GENERAL_TYPE+")"; + + ServiceReference[] confFormsRefs = null; + try + { + confFormsRefs = GuiActivator.bundleContext + .getServiceReferences( + ConfigurationForm.class.getName(), + osgiFilter); + } + catch (InvalidSyntaxException ex) + {} + + String chatConfigFormClassName = + "net.java.sip.communicator.plugin.chatconfig.ChatConfigPanel"; + + if(confFormsRefs != null) + { + for (int i = 0; i < confFormsRefs.length; i++) + { + ConfigurationForm form + = (ConfigurationForm) GuiActivator.bundleContext + .getService(confFormsRefs[i]); + + if (form instanceof LazyConfigurationForm) + { + LazyConfigurationForm lazyConfigForm + = (LazyConfigurationForm) form; + + if (lazyConfigForm.getFormClassName() + .equals(chatConfigFormClassName)) + return form; + } + else if (form.getClass().getName() + .equals(chatConfigFormClassName)) + { + return form; + } + } + } + + return null; + } + /** * Extends SIPCommHTMLEditorKit to keeps track of created ImageView for * the gif images in order to flush them whenever they are no longer visible @@ -2322,6 +2428,24 @@ private void processText(String plainText, msgBuff.append(group); } } + else if (isProposalEnabled) + { + msgBuff.append(group); + msgBuff.append( + " " + + GuiActivator.getResources(). + getI18NString("service.gui.SHOW_PREVIEW")); + + showPreview.getMsgIDandPositionToLink() + .put( + messageID + "#" + linkCounter++, group); + showPreview.getLinkToReplacement() + .put( + group, temp); + } else if (isEnabled && isEnabledForSource) { if (isDirectImage) @@ -2355,24 +2479,6 @@ else if (isEnabled && isEnabledForSource) msgBuff.append("\">"); } } - else if (isProposalEnabled) - { - msgBuff.append(group); - msgBuff.append( - " " - + GuiActivator.getResources(). - getI18NString("service.gui.SHOW_PREVIEW")); - - showPreview.getMsgIDandPositionToLink() - .put( - messageID + "#" + linkCounter++, group); - showPreview.getLinkToReplacement() - .put( - group, temp); - } else { msgBuff.append(group); diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/ShowPreviewDialog.java b/src/net/java/sip/communicator/impl/gui/main/chat/ShowPreviewDialog.java index 199d61686..ef6789516 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/ShowPreviewDialog.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/ShowPreviewDialog.java @@ -15,12 +15,9 @@ import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.plugin.desktoputil.SwingWorker; import net.java.sip.communicator.service.gui.*; -import net.java.sip.communicator.service.replacement.*; import net.java.sip.communicator.service.replacement.directimage.*; import net.java.sip.communicator.util.*; -import org.jitsi.service.configuration.*; - public class ShowPreviewDialog extends SIPCommDialog implements ActionListener, @@ -38,9 +35,6 @@ public class ShowPreviewDialog private static final Logger logger = Logger.getLogger(ShowPreviewDialog.class); - ConfigurationService cfg - = GuiActivator.getConfigurationService(); - /** * The Ok button. */ @@ -54,12 +48,7 @@ public class ShowPreviewDialog /** * Checkbox that indicates whether or not to show this dialog next time. */ - private final JCheckBox enableReplacementProposal; - - /** - * Checkbox that indicates whether or not to show previews automatically - */ - private final JCheckBox enableReplacement; + private final JCheckBox configureReplacement; /** * The ChatConversationPanel that this dialog is associated with. @@ -147,23 +136,14 @@ public class ShowPreviewDialog warningPanel.add(Box.createHorizontalStrut(10)); warningPanel.add(descriptionMsg); - enableReplacement - = new JCheckBox( + configureReplacement + = new SIPCommCheckBox( GuiActivator.getResources().getI18NString( - "plugin.chatconfig.replacement.ENABLE_REPLACEMENT_STATUS")); - enableReplacement.setOpaque(false); - enableReplacement.setSelected( - cfg.getBoolean(ReplacementProperty.REPLACEMENT_ENABLE, true)); - enableReplacementProposal - = new JCheckBox( - GuiActivator.getResources().getI18NString( - "plugin.chatconfig.replacement.ENABLE_REPLACEMENT_PROPOSAL")); - enableReplacementProposal.setOpaque(false); + "plugin.chatconfig.replacement.CONFIGURE_REPLACEMENT")); - JPanel checkBoxPanel = new TransparentPanel(); - checkBoxPanel.setLayout(new BoxLayout(checkBoxPanel, BoxLayout.Y_AXIS)); - checkBoxPanel.add(enableReplacement); - checkBoxPanel.add(enableReplacementProposal); + JPanel checkBoxPanel = new TransparentPanel( + new FlowLayout(FlowLayout.CENTER)); + checkBoxPanel.add(configureReplacement); JPanel buttonsPanel = new TransparentPanel(new FlowLayout(FlowLayout.CENTER)); @@ -186,10 +166,6 @@ public void actionPerformed(ActionEvent arg0) { if (arg0.getSource().equals(okButton)) { - cfg.setProperty(ReplacementProperty.REPLACEMENT_ENABLE, - enableReplacement.isSelected()); - cfg.setProperty(ReplacementProperty.REPLACEMENT_PROPOSAL - , enableReplacementProposal.isSelected()); SwingWorker worker = new SwingWorker() { /** @@ -275,6 +251,26 @@ else if (arg0.getSource().equals(cancelButton)) { this.setVisible(false); } + + // Shows chat config panel + if(configureReplacement.isSelected()) + { + ConfigurationContainer configContainer + = GuiActivator.getUIService().getConfigurationContainer(); + + ConfigurationForm chatConfigForm = + ChatConversationPanel.getChatConfigForm(); + + if(chatConfigForm != null) + { + configContainer.setSelected(chatConfigForm); + + configContainer.setVisible(true); + } + + // reset for next dialog appearance + configureReplacement.setSelected(false); + } } @Override @@ -283,11 +279,6 @@ public void chatLinkClicked(URI url) String action = url.getPath(); if (action.equals("/SHOWPREVIEW")) { - enableReplacement.setSelected( - cfg.getBoolean(ReplacementProperty.REPLACEMENT_ENABLE, true)); - enableReplacementProposal.setSelected( - cfg.getBoolean(ReplacementProperty.REPLACEMENT_PROPOSAL, true)); - currentMessageID = url.getQuery(); currentLinkPosition = url.getFragment(); @@ -325,4 +316,16 @@ Map getLinkToReplacement() { return linkToReplacement; } + + /** + * All functions implemented in this method will be invoked when user + * presses the Escape key. + * + * @param escaped true if this frame has been closed by pressing + * the Esc key; otherwise, false + */ + protected void close(boolean escaped) + { + cancelButton.doClick(); + } } diff --git a/src/net/java/sip/communicator/impl/gui/main/configforms/ConfigurationFrame.java b/src/net/java/sip/communicator/impl/gui/main/configforms/ConfigurationFrame.java index be0877602..104382eac 100644 --- a/src/net/java/sip/communicator/impl/gui/main/configforms/ConfigurationFrame.java +++ b/src/net/java/sip/communicator/impl/gui/main/configforms/ConfigurationFrame.java @@ -253,6 +253,7 @@ public void run() this.configList.setSelectedIndex(0); } super.setVisible(isVisible); + super.toFront(); } /** diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallJabberGTalkImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallJabberGTalkImpl.java index ef935b845..8155cf455 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallJabberGTalkImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/AbstractCallJabberGTalkImpl.java @@ -87,7 +87,7 @@ public T getPeer(String sid) { for(T peer : getCallPeerList()) { - if (peer.getSID().equals(sid)) + if (peer.getSID() != null && peer.getSID().equals(sid)) return peer; } return null; diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ScServiceDiscoveryManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/ScServiceDiscoveryManager.java index e068084f7..7a1c21cde 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ScServiceDiscoveryManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ScServiceDiscoveryManager.java @@ -393,6 +393,16 @@ public void interceptPacket(Packet packet) caps.getNode() + "#" + caps.getVersion(), this); + // Remove old capabilities extension if present + PacketExtension oldCaps + = packet.getExtension( + CapsPacketExtension.ELEMENT_NAME, + CapsPacketExtension.NAMESPACE); + if (oldCaps != null) + { + packet.removeExtension(oldCaps); + } + // Put new capabilities extension packet.addExtension(caps); } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/IceUdpTransportPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/IceUdpTransportPacketExtension.java index f09fcaf7d..3ebb57671 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/IceUdpTransportPacketExtension.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/IceUdpTransportPacketExtension.java @@ -232,7 +232,24 @@ public void addChildExtension(PacketExtension childExtension) else if(childExtension instanceof CandidatePacketExtension) addCandidate((CandidatePacketExtension) childExtension); - else if (childExtension instanceof DtlsFingerprintPacketExtension) + else super.addChildExtension(childExtension); } + + /** + * Checks whether an 'rtcp-mux' extension has been added to this + * IceUdpTransportPacketExtension. + * @return true if this IceUdpTransportPacketExtension + * has a child with the 'rtcp-mux' name. + */ + public boolean isRtcpMux() + { + for (PacketExtension packetExtension : getChildExtensions()) + { + if (RtcpmuxPacketExtension.ELEMENT_NAME + .equals(packetExtension.getElementName())) + return true; + } + return false; + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java index 4cefe8f02..bc46b8f3e 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java @@ -156,6 +156,13 @@ public JingleIQProvider() ConferenceDescriptionPacketExtension.NAMESPACE, new DefaultPacketExtensionProvider( CallIdPacketExtension.class)); + + //rtcp-mux + providerManager.addExtensionProvider( + RtcpmuxPacketExtension.ELEMENT_NAME, + IceUdpTransportPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider( + RtcpmuxPacketExtension.class)); } /** diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/RtcpmuxPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/RtcpmuxPacketExtension.java new file mode 100644 index 000000000..21577ee63 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/RtcpmuxPacketExtension.java @@ -0,0 +1,31 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; + +/** + * An 'rtcp-mux' extension. + * @author Boris Grozev + */ +public class RtcpmuxPacketExtension + extends AbstractPacketExtension +{ + /** + * The name of the "encryption" element. + */ + public static final String ELEMENT_NAME = "rtcp-mux"; + + /** + * Creates a new instance of RtcpmuxPacketExtension. + */ + public RtcpmuxPacketExtension() + { + super(null, ELEMENT_NAME); + } +} + diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/MediaPresenceExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/MediaPresenceExtension.java new file mode 100644 index 000000000..74d6fd4dd --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jitsimeet/MediaPresenceExtension.java @@ -0,0 +1,163 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jitsimeet; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; +import org.jivesoftware.smack.provider.*; + +/** + * Packet extension used by Jitsi Meet to advertise local media SSRCs in MUC + * presence. + * + * @author Pawel Domas + */ +public class MediaPresenceExtension + extends AbstractPacketExtension +{ + /** + * The namespace of this packet extension. + */ + public static final String NAMESPACE = "http://estos.de/ns/mjs"; + + /** + * XML element name of this packet extension. + */ + public static final String ELEMENT_NAME = "media"; + + /** + * Creates an MediaPresenceExtension instance. + * + */ + public MediaPresenceExtension() + { + super(NAMESPACE, ELEMENT_NAME); + } + + /** + * Registers packet Jitsi Meet media presence packet extensions in given + * providerManager. + * + * @param providerManager the ProviderManager to which media + * presence extensions will be registered to. + */ + public static void registerExtensions(ProviderManager providerManager) + { + // + providerManager.addExtensionProvider( + MediaPresenceExtension.ELEMENT_NAME, + MediaPresenceExtension.NAMESPACE, + new DefaultPacketExtensionProvider( + MediaPresenceExtension.class)); + + // + providerManager.addExtensionProvider( + MediaPresenceExtension.Source.ELEMENT_NAME, + MediaPresenceExtension.Source.NAMESPACE, + new DefaultPacketExtensionProvider( + Source.class)); + } + + /** + * Source extension element that specifies into about media SSRC. + */ + public static class Source + extends AbstractPacketExtension + { + /** + * Source XML element name. + */ + public final static String ELEMENT_NAME = "source"; + + /** + * No namespace attached. + */ + public final static String NAMESPACE = ""; + + /** + * SSRC media type attribute name. Can be 'audio' or 'video'. + */ + public final static String MEDIA_TYPE_ATTR_NAME = "type"; + + /** + * SSRC number attribute name. + */ + public final static String SOURCE_ATTR_NAME = "ssrc"; + + /** + * Media direction as used in SDP('sendrecv', 'recvonly', 'sendonly'). + */ + public final static String DIRECTION_ATTR_NAME = "direction"; + + /** + * Creates new instance of Source packet extension with default + * 'sendrecv' direction set. + */ + public Source() + { + super(NAMESPACE, ELEMENT_NAME); + + // Default direction + setDirection("sendrecv"); + } + + /** + * Returns media direction + * (values like used in SDP 'sendrecv', 'recvonly'...). + */ + public String getDirection() + { + return getAttributeAsString(DIRECTION_ATTR_NAME); + } + + /** + * Returns media synchronization source identifier. + */ + public String getSSRC() + { + return getAttributeAsString(SOURCE_ATTR_NAME); + } + + /** + * Sets media type of the media represented by this source packet + * extensions. + * @param mediaType media type string('audio' or 'video'). + */ + public void setMediaType(String mediaType) + { + setAttribute(MEDIA_TYPE_ATTR_NAME, mediaType); + } + + /** + * Direction of the media represented by this source packet extension. + * @param direction media direction like used in SDP + * ('sendrecv, 'recvonly'...). + */ + public void setDirection(String direction) + { + setAttribute(DIRECTION_ATTR_NAME, direction); + } + + /** + * Sets synchronization source identifier of the media represented by + * this source packet extension. + * @param ssrc synchronization source identifier to set. + */ + public void setSSRC(String ssrc) + { + setAttribute(SOURCE_ATTR_NAME, ssrc); + } + + /** + * @return type of the media(audio or video) represented by this source + * packet extension. + */ + public String getMediaType() + { + return getAttributeAsString(MEDIA_TYPE_ATTR_NAME); + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/rayo/EndExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/rayo/EndExtension.java new file mode 100644 index 000000000..affb7f7cc --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/rayo/EndExtension.java @@ -0,0 +1,104 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.rayo; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; +import org.jivesoftware.smack.packet.*; + +/** + * 'End' Rayo packet extension used to notify the client about call ended event. + * + * @author Pawel Domas + */ +public class EndExtension + extends AbstractPacketExtension +{ + /** + * XML element name of this extension. + */ + public static final String ELEMENT_NAME = "end"; + + /** + * End reason. + */ + private ReasonExtension reason; + + /** + * Creates new instance. + */ + protected EndExtension() + { + super(RayoIqProvider.NAMESPACE, ELEMENT_NAME); + } + + /** + * Checks if given elementName is valid end reason element. + * @param elementName the XML element name to check. + * @return true if given elementName is valid end reason + * element. + */ + public static boolean isValidReason(String elementName) + { + return ReasonExtension.BUSY.equals(elementName) + || ReasonExtension.ERROR.equals(elementName) + || ReasonExtension.HANGUP.equals(elementName) + || ReasonExtension.HANGUP_COMMND.equals(elementName) + || ReasonExtension.REJECTED.equals(elementName) + || ReasonExtension.TIMEOUT.equals(elementName); + } + + + /** + * Returns {@link ReasonExtension} associated with this instance. + * @return {@link ReasonExtension} associated with this instance. + */ + public ReasonExtension getReason() + { + return reason; + } + + /** + * Sets new {@link ReasonExtension} for this EndExtension instance. + * @param newReason the new {@link ReasonExtension} to set. + */ + public void setReason(ReasonExtension newReason) + { + if (this.reason != null) + { + getChildExtensions().remove(this.reason); + } + + this.reason = newReason; + + addChildExtension(newReason); + } + + /** + * Creates 'Presence' packet containing call ended Rayo notification that + * contains specified end reason. + * @param from source JID of this event. + * @param to destination JID. + * @param reason call end reason string. One of {@link ReasonExtension} + * static constants. + * @return 'Presence' packet containing call ended Rayo notification. + */ + public static Presence createEnd(String from, String to, String reason) + { + Presence presence = new Presence(Presence.Type.unavailable); + + presence.setFrom(from); + + presence.setTo(to); + + EndExtension end = new EndExtension(); + end.setReason(new ReasonExtension(reason)); + + presence.addExtension(end); + + return presence; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/rayo/HeaderExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/rayo/HeaderExtension.java new file mode 100644 index 000000000..08546531a --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/rayo/HeaderExtension.java @@ -0,0 +1,78 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.rayo; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; + +/** + * Header packet extension optionally included in {@link RayoIqProvider.RayoIq}. + * Holds 'name' and 'value' attributes. + * + * @author Pawel Domas + */ +public class HeaderExtension + extends AbstractPacketExtension +{ + /** + * XML element name. + */ + public static final String ELEMENT_NAME = "header"; + + /** + * The name of 'name' attribute. + */ + public static final String NAME_ATTR_NAME = "name"; + + /** + * The name of 'value' attribute. + */ + public static final String VALUE_ATTR_NAME = "value"; + + /** + * Creates new instance of HeaderPacketExtension. + */ + public HeaderExtension() + { + super(null, ELEMENT_NAME); + } + + /** + * Return the value of 'name' attribute. + * @return the value of 'name' attribute. + */ + public String getName() + { + return getAttributeAsString(NAME_ATTR_NAME); + } + + /** + * Sets new value for 'name' attribute of this extension. + * @param name the new value to set for 'name' attribute. + */ + public void setName(String name) + { + setAttribute(NAME_ATTR_NAME, name); + } + + /** + * Returns the value of 'value' attribute. + * @return the value of 'value' attribute. + */ + public String getValue() + { + return getAttributeAsString(VALUE_ATTR_NAME); + } + + /** + * Sets new value for the 'value' attribute. + * @param value new value for the 'value' attribute to set. + */ + public void setValue(String value) + { + setAttribute(VALUE_ATTR_NAME, value); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/rayo/RayoIqProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/rayo/RayoIqProvider.java new file mode 100644 index 000000000..4d94ed9e8 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/rayo/RayoIqProvider.java @@ -0,0 +1,587 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.rayo; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; +import org.jitsi.util.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.provider.*; +import org.xmlpull.v1.*; + +import java.util.*; + +/** + * Provider handles parsing of Rayo IQ stanzas and converting objects back to + * their XML representation. + * + * FIXME: implements only the minimum required to start and hang up a call + * + * @author Pawel Domas + */ +public class RayoIqProvider + implements IQProvider +{ + /** + * Rayo namespace. + */ + public final static String NAMESPACE = "urn:xmpp:rayo:1"; + + /** + * Registers this IQ provider into given ProviderManager. + * @param providerManager the ProviderManager to which this + * instance wil be bound to. + */ + public void registerRayoIQs(ProviderManager providerManager) + { + // + providerManager.addIQProvider( + DialIq.ELEMENT_NAME, + NAMESPACE, + this); + + // + providerManager.addIQProvider( + RefIq.ELEMENT_NAME, + NAMESPACE, + this); + + // + providerManager.addIQProvider( + HangUp.ELEMENT_NAME, + NAMESPACE, + this); + + // presence extension + providerManager.addExtensionProvider( + EndExtension.ELEMENT_NAME, + NAMESPACE, + new DefaultPacketExtensionProvider( + EndExtension.class)); + + //
extension + providerManager.addExtensionProvider( + HeaderExtension.ELEMENT_NAME, + "", + new DefaultPacketExtensionProvider( + HeaderExtension.class)); + } + + /** + * {@inheritDoc} + */ + @Override + public IQ parseIQ(XmlPullParser parser) + throws Exception + { + String namespace = parser.getNamespace(); + + // Check the namespace + if (!NAMESPACE.equals(namespace)) + { + return null; + } + + String rootElement = parser.getName(); + + RayoIq iq; + DialIq dial; + RefIq ref; + //End end = null; + + if (DialIq.ELEMENT_NAME.equals(rootElement)) + { + iq = dial = new DialIq(); + String src = parser.getAttributeValue("", DialIq.SRC_ATTR_NAME); + String dst = parser.getAttributeValue("", DialIq.DST_ATTR_NAME); + + // Destination is mandatory + if (StringUtils.isNullOrEmpty(dst)) + return null; + + dial.setSource(src); + dial.setDestination(dst); + } + else if (RefIq.ELEMENT_NAME.equals(rootElement)) + { + iq = ref = new RefIq(); + String uri = parser.getAttributeValue("", RefIq.URI_ATTR_NAME); + + if (StringUtils.isNullOrEmpty(uri)) + return null; + + ref.setUri(uri); + } + else if (HangUp.ELEMENT_NAME.equals(rootElement)) + { + iq = new HangUp(); + } + /*else if (End.ELEMENT_NAME.equals(rootElement)) + { + iq = end = new End(); + }*/ + else + { + return null; + } + + boolean done = false; + HeaderExtension header = null; + //ReasonExtension reason = null; + + while (!done) + { + switch (parser.next()) + { + case XmlPullParser.END_TAG: + { + String name = parser.getName(); + + if (rootElement.equals(name)) + { + done = true; + } + else if (HeaderExtension.ELEMENT_NAME.equals( + name)) + { + if (header != null) + { + iq.addExtension(header); + + header = null; + } + } + /*else if (End.isValidReason(name)) + { + if (end != null && reason != null) + { + end.setReason(reason); + + reason = null; + } + }*/ + break; + } + + case XmlPullParser.START_TAG: + { + String name = parser.getName(); + + if (HeaderExtension.ELEMENT_NAME.equals(name)) + { + header = new HeaderExtension(); + + String nameAttr + = parser.getAttributeValue( + "", HeaderExtension.NAME_ATTR_NAME); + + header.setName(nameAttr); + + String valueAttr + = parser.getAttributeValue( + "", HeaderExtension.VALUE_ATTR_NAME); + + header.setValue(valueAttr); + } + /*else if (End.isValidReason(name)) + { + reason = new ReasonPacketExtension(name); + + String platformCode + = parser.getAttributeValue( + "", ReasonPacketExtension.PLATFORM_CODE_ATTRIBUTE); + + if (!StringUtils.isNullOrEmpty(platformCode)) + { + reason.setPlatformCode(platformCode); + } + }*/ + break; + } + + case XmlPullParser.TEXT: + { + // Parse some text here + break; + } + } + } + + return iq; + } + + /** + * Base class for all Ray IQs. Takes care of
extension handling + * as well as other functions shared by all IQs. + */ + public static abstract class RayoIq + extends IQ + { + /** + * The XML element name that will be used. + */ + private final String elementName; + + /** + * Creates new instance of RayoIq. + * + * @param elementName the name of XML element that will be used. + */ + protected RayoIq(String elementName) + { + this.elementName = elementName; + } + + /** + * Implementing classes should print their attributes if any in XML + * format to given out StringBuilder. + * @param out the StringBuilder instance used to construct XML + * representation of this element. + */ + protected abstract void printAttributes(StringBuilder out); + + /** + * {@inheritDoc} + */ + @Override + public String getChildElementXML() + { + StringBuilder xml = new StringBuilder(); + + xml.append('<').append(elementName); + xml.append(" xmlns='").append(NAMESPACE).append("' "); + + printAttributes(xml); + + Collection extensions = getExtensions(); + if (extensions.size() > 0) + { + xml.append(">"); + for (PacketExtension extension : extensions) + { + xml.append(extension.toXML()); + } + xml.append(""); + } + else + { + xml.append("/>"); + } + + return xml.toString(); + } + + /** + * Returns value of the header extension with given name + * (if any). + * @param name the name of header extension which value we want to + * retrieve. + * @return value of header extension with given name if it + * exists or null otherwise. + */ + public String getHeader(String name) + { + HeaderExtension header = findHeader(name); + + return header != null ? header.getValue() : null; + } + + private HeaderExtension findHeader(String name) + { + for(PacketExtension ext: getExtensions()) + { + if (ext instanceof HeaderExtension) + { + HeaderExtension header = (HeaderExtension) ext; + + if(name.equals(header.getName())) + return header; + } + } + return null; + } + + /** + * Adds 'header' extension to this Rayo IQ with given name and value + * attributes. + * @param name the attribute name of the 'header' extension to be added. + * @param value the 'value' attribute of the 'header' extension that + * will be added to this IQ. + */ + public void setHeader(String name, String value) + { + HeaderExtension headerExt = findHeader(name); + + if (headerExt == null) + { + headerExt = new HeaderExtension(); + + headerExt.setName(name); + + addExtension(headerExt); + } + + headerExt.setValue(value); + } + } + + /** + * The 'dial' IQ used to initiate new outgoing call session in Rayo + * protocol. + */ + public static class DialIq + extends RayoIq + { + /** + * The name of XML element for this IQ. + */ + public static final String ELEMENT_NAME = "dial"; + + /** + * The name of source URI/address attribute. Referred as "source" to + * avoid confusion with "getFrom" and "setFrom" in {@link IQ} class. + */ + public static final String SRC_ATTR_NAME = "from"; + + /** + * The name of destination URI/address attribute. Referred as "source" + * to avoid confusion with "getFrom" and "setFrom" in {@link IQ} class. + */ + public static final String DST_ATTR_NAME = "to"; + + /** + * Source URI/address. + */ + private String source; + + /** + * Destination URI/address. + */ + private String destination; + + /** + * Creates new instance of DialIq. + */ + public DialIq() + { + super(DialIq.ELEMENT_NAME); + } + + /** + * Creates new DialIq for given source and destination + * addresses. + * @param to the destination address/call URI to be used. + * @param from the source address that will be set on + * new DialIq instance. + * @return new DialIq parametrized with given source and + * destination addresses. + */ + public static DialIq create(String to, String from) + { + DialIq dialIq = new DialIq(); + + dialIq.setSource(from); + + dialIq.setDestination(to); + + return dialIq; + } + + /** + * Return source address value set on this DialIq. + * @return source address value of this DialIq. + */ + public String getSource() + { + return source; + } + + /** + * Sets new source address value on this DialIq. + * @param source the new source address value to be set. + */ + public void setSource(String source) + { + this.source = source; + } + + /** + * Returns destination address/call URI associated with this instance. + * @return destination address/call URI associated with this instance. + */ + public String getDestination() + { + return destination; + } + + /** + * Sets new destination address/call URI on this DialIq. + * @param destination the new destination address/call URI to set. + */ + public void setDestination(String destination) + { + this.destination = destination; + } + + /** + * {@inheritDoc} + */ + @Override + protected void printAttributes(StringBuilder out) + { + String src = getSource(); + if (!StringUtils.isNullOrEmpty(src)) + out.append(SRC_ATTR_NAME).append("='") + .append(src).append("' "); + + String dst = getDestination(); + if (!StringUtils.isNullOrEmpty(dst)) + out.append(DST_ATTR_NAME).append("='") + .append(dst).append("' "); + } + } + + /** + * Rayo 'ref' IQ sent by the server as a reply to 'dial' request. Holds + * created call's resource in 'uri' attribute. + */ + public static class RefIq + extends RayoIq + { + /** + * XML element name of RefIq. + */ + public static final String ELEMENT_NAME = "ref"; + + /** + * Name of the URI attribute that stores call resource reference. + */ + public static final String URI_ATTR_NAME = "uri"; + + /** + * Call resource/uri reference. + */ + private String uri; + + /** + * Creates new RefIq. + */ + protected RefIq() + { + super(RefIq.ELEMENT_NAME); + } + + /** + * Creates new RefIq parametrized with given call uri. + * @param uri the call URI to be set on newly created RefIq. + * @return new RefIq parametrized with given call uri. + */ + public static RefIq create(String uri) + { + RefIq refIq = new RefIq(); + + refIq.setUri(uri); + + return refIq; + } + + /** + * Creates result RefIq for given requestIq + * parametrized with given call uri. + * @param requestIq the request IQ which 'from', 'to' and 'id' + * attributes will be used for constructing result IQ. + * @param uri the call URI that will be included in newly created + * RefIq. + * @return result RefIq for given requestIq + * parametrized with given call uri. + */ + public static RefIq createResult(IQ requestIq, String uri) + { + RefIq refIq = create(uri); + + refIq.setType(IQ.Type.RESULT); + refIq.setPacketID(requestIq.getPacketID()); + refIq.setFrom(requestIq.getTo()); + refIq.setTo(requestIq.getFrom()); + + return refIq; + } + + /** + * {@inheritDoc} + */ + @Override + protected void printAttributes(StringBuilder out) + { + String uri = getUri(); + if (!StringUtils.isNullOrEmpty(uri)) + out.append(URI_ATTR_NAME).append("='") + .append(uri).append("' "); + } + + /** + * Sets given call uri value on this instance. + * @param uri the call uri to be stored in this instance. + */ + public void setUri(String uri) + { + this.uri = uri; + } + + /** + * Returns call URI held by this instance. + * @return the call URI held by this instance. + */ + public String getUri() + { + return uri; + } + } + + /** + * Rayo hangup IQ is sent by the controlling agent to tell the server that + * call whose resource is mentioned in IQ's 'to' attribute should be + * terminated. Server immediately replies with result IQ which means that + * hangup operation is now scheduled. After it is actually executed presence + * indication with {@link EndExtension} is sent through the presence to + * confirm the operation. + */ + public static class HangUp + extends RayoIq + { + /** + * The name of 'hangup' element. + */ + public static final String ELEMENT_NAME = "hangup"; + + /** + * Creates new instance of HangUp IQ. + */ + protected HangUp() + { + super(ELEMENT_NAME); + } + + /** + * Creates new, parametrized instance of {@link HangUp} IQ. + * @param from source JID. + * @param to the destination address/call URI to be ended by this IQ. + * @return new, parametrized instance of {@link HangUp} IQ. + */ + public static HangUp create(String from, String to) + { + HangUp hangUp = new HangUp(); + hangUp.setFrom(from); + hangUp.setTo(to); + hangUp.setType(Type.SET); + + return hangUp; + } + + @Override + protected void printAttributes(StringBuilder out){ } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/rayo/ReasonExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/rayo/ReasonExtension.java new file mode 100644 index 000000000..447c04fbc --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/rayo/ReasonExtension.java @@ -0,0 +1,88 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.rayo; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; + +/** + * Rayo 'reason' packet extension. + * + * @author Pawel Domas + */ +public class ReasonExtension + extends AbstractPacketExtension +{ + /** + * The name of optional platform code attribute. + */ + public static final String PLATFORM_CODE_ATTRIBUTE = "platform-code"; + + /** + * Indication that the call ended due to a normal hangup by the remote + * party. + */ + public static final String HANGUP = "hangup"; + + /** + * Indication that the call ended due to a normal hangup triggered by + * a hangup command. + */ + public static final String HANGUP_COMMND = "hangup-command"; + + /** + * Indication that the call ended due to a timeout in contacting the remote + * party. + */ + public static final String TIMEOUT = "timeout"; + + /** + * Indication that the call ended due to being rejected by the remote party + * subsequent to being accepted. + */ + public static final String BUSY = "busy"; + + /** + * Indication that the call ended due to being rejected by the remote party + * before being accepted. + */ + public static final String REJECTED = "rejected"; + + /** + * Indication that the call ended due to a system error. + */ + public static final String ERROR = "error"; + + /** + * Creates an {@link ReasonExtension} instance for the specified + * namespace and elementName. + * + * @param elementName the name of the element + */ + public ReasonExtension(String elementName) + { + super(null, elementName); + } + + /** + * Returns the value of platform code attribute. + * @return the value of platform code attribute. + */ + public String getPlatformCode() + { + return getAttributeAsString(PLATFORM_CODE_ATTRIBUTE); + } + + /** + * Sets new value of platform code attribute. Pass null to remove. + * @param code new value of platform code attribute. Pass null to + * remove. + */ + public void setPlatformCode(String code) + { + setAttribute(PLATFORM_CODE_ATTRIBUTE, code); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java index b55999f70..075a318ae 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java @@ -357,10 +357,41 @@ public CallPeerSipImpl invite(Address calleeAddress, Request invite = messageFactory.createInviteRequest(calleeAddress, cause); + // Transport preference + String forceTransport = null; + javax.sip.address.URI calleeURI = calleeAddress.getURI(); + if(calleeURI.getScheme().toLowerCase().equals("sips")) + { + // MUST use TLS + forceTransport = "TLS"; + logger.trace("detected SIPS URI, must use TLS"); + } + else if(calleeURI.isSipURI() && calleeURI instanceof SipURI) + { + SipURI _calleeURI = (SipURI)calleeURI; + // check for a transport parameter + forceTransport = _calleeURI.getTransportParam(); + if(forceTransport != null) + { + logger.trace("got transport parameter: " + forceTransport); + } + } + // Transaction ClientTransaction inviteTransaction = null; - SipProvider jainSipProvider - = getProtocolProvider().getDefaultJainSipProvider(); + SipProvider jainSipProvider; + if(forceTransport != null) + { + logger.trace("trying to use transport: " + forceTransport); + jainSipProvider = getProtocolProvider() + .getJainSipProvider(forceTransport); + } + else + { + logger.trace("trying default transport"); + jainSipProvider = getProtocolProvider() + .getDefaultJainSipProvider(); + } try { diff --git a/src/net/java/sip/communicator/impl/protocol/sip/UriHandlerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/UriHandlerSipImpl.java index 49b2807fb..5aa1b0fbe 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/UriHandlerSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/UriHandlerSipImpl.java @@ -316,11 +316,15 @@ public void handleUri(String uri) { // make sure that we prompt for registration only if it is really // required by the provider. + boolean handled = false; if (exc.getErrorCode() == OperationFailedException.PROVIDER_NOT_REGISTERED) { - promptForRegistration(uri, provider); + handled = promptForRegistration(uri, provider); + } + if(!handled) + { + showErrorMessage("Failed to create a call to " + uri, exc); } - showErrorMessage("Failed to create a call to " + uri, exc); } catch (ParseException exc) { @@ -335,8 +339,9 @@ public void handleUri(String uri) * * @param uri the uri that the user would like us to call after registering. * @param provider the provider that we may have to reregister. + * @return true if user tried to start registration */ - private void promptForRegistration(String uri, + private boolean promptForRegistration(String uri, ProtocolProviderService provider) { int answer = @@ -351,7 +356,9 @@ private void promptForRegistration(String uri, if (answer == PopupDialog.YES_OPTION) { new ProtocolRegistrationThread(uri, provider).start(); + return true; } + return false; } /** diff --git a/src/net/java/sip/communicator/plugin/chatconfig/replacement/ReplacementConfigPanel.java b/src/net/java/sip/communicator/plugin/chatconfig/replacement/ReplacementConfigPanel.java index b3a7c3ed7..af0fce553 100644 --- a/src/net/java/sip/communicator/plugin/chatconfig/replacement/ReplacementConfigPanel.java +++ b/src/net/java/sip/communicator/plugin/chatconfig/replacement/ReplacementConfigPanel.java @@ -19,6 +19,7 @@ import net.java.sip.communicator.service.replacement.*; import org.jitsi.service.configuration.*; +import org.jitsi.service.resources.*; /** * The ConfigurationForm that would be added in the chat configuration @@ -42,12 +43,17 @@ public class ReplacementConfigPanel /** * Checkbox to enable/disable replacements other than smileys. */ - private JCheckBox enableReplacement; + private JRadioButton enableReplacement; /** * Checkbox to enable/disable proposal messages for image/video replacement. */ - private JCheckBox enableReplacementProposal; + private JRadioButton enableReplacementProposal; + + /** + * Checkbox to disable image/video replacement. + */ + private JRadioButton disableReplacement; /** * Jtable to list all the available replacement sources. @@ -77,16 +83,17 @@ public ReplacementConfigPanel() */ private Component createMainPanel() { - JPanel mainPanel = new TransparentPanel(new BorderLayout()); + JPanel mainPanel = new TransparentPanel(); mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + ResourceManagementService R = ChatConfigActivator.getResources(); + enableSmiley = - new SIPCommCheckBox(ChatConfigActivator.getResources() - .getI18NString( + new SIPCommCheckBox(R.getI18NString( "plugin.chatconfig.replacement.ENABLE_SMILEY_STATUS")); - mainPanel.add(enableSmiley, BorderLayout.WEST); + mainPanel.add(enableSmiley); enableSmiley.addActionListener(new ActionListener() { @@ -99,32 +106,34 @@ public void actionPerformed(ActionEvent e) mainPanel.add(Box.createVerticalStrut(10)); + JPanel replacementPanel = new TransparentPanel(); + replacementPanel.setLayout(new BoxLayout(replacementPanel, BoxLayout.Y_AXIS)); + replacementPanel.setBorder( + BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(R.getI18NString( + "plugin.chatconfig.replacement.REPLACEMENT_TITLE")), + BorderFactory.createEmptyBorder(3, 3, 3, 3))); + enableReplacement = - new SIPCommCheckBox(ChatConfigActivator.getResources() - .getI18NString( + new SIPCommRadioButton(R.getI18NString( "plugin.chatconfig.replacement.ENABLE_REPLACEMENT_STATUS")); - mainPanel.add(enableReplacement, BorderLayout.WEST); + replacementPanel.add(enableReplacement); enableReplacement.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { saveData(); - table.revalidate(); - table.repaint(); } }); - mainPanel.add(Box.createVerticalStrut(10)); - enableReplacementProposal = - new SIPCommCheckBox(ChatConfigActivator.getResources() - .getI18NString( - "plugin.chatconfig.replacement.ENABLE_REPLACEMENT_PROPOSAL")); + new SIPCommRadioButton(R.getI18NString( + "plugin.chatconfig.replacement.ENABLE_REPLACEMENT_PROPOSAL" + )); - mainPanel.add(enableReplacementProposal, BorderLayout.WEST); + replacementPanel.add(enableReplacementProposal); enableReplacementProposal.addActionListener(new ActionListener(){ @@ -133,8 +142,25 @@ public void actionPerformed(ActionEvent arg0) saveData(); } }); + disableReplacement = new SIPCommRadioButton(R.getI18NString( + "plugin.chatconfig.replacement.DISABLE_REPLACEMENT")); + + replacementPanel.add(disableReplacement); - // the Jtable to list all the available sources + disableReplacement.addActionListener(new ActionListener(){ + + public void actionPerformed(ActionEvent arg0) + { + saveData(); + } + }); + + ButtonGroup replacementGroup = new ButtonGroup(); + replacementGroup.add(enableReplacement); + replacementGroup.add(enableReplacementProposal); + replacementGroup.add(disableReplacement); + + // the JTable to list all the available sources table = new JTable(); table.setShowGrid(false); table.setTableHeader(null); @@ -144,18 +170,19 @@ public void actionPerformed(ActionEvent arg0) JScrollPane tablePane = new JScrollPane(table); tablePane.setOpaque(false); - tablePane.setPreferredSize(new Dimension(mainPanel.getWidth(), 150)); + tablePane.setPreferredSize( + new Dimension(replacementPanel.getWidth(), 150)); tablePane.setAlignmentX(LEFT_ALIGNMENT); JPanel container = new TransparentPanel(new BorderLayout()); - container.setPreferredSize(new Dimension(mainPanel.getWidth(), 200)); + container.setPreferredSize( + new Dimension(replacementPanel.getWidth(), 200)); container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS)); JLabel label = - new JLabel(ChatConfigActivator.getResources().getI18NString( + new JLabel(R.getI18NString( "plugin.chatconfig.replacement.REPLACEMENT_SOURCES")); - label.setDisplayedMnemonic(ChatConfigActivator.getResources() - .getI18nMnemonic( + label.setDisplayedMnemonic(R.getI18nMnemonic( "plugin.chatconfig.replacement.REPLACEMENT_SOURCES")); label.setLabelFor(table); @@ -202,8 +229,10 @@ public void valueChanged(ListSelectionEvent e) table.setDefaultRenderer(table.getColumnClass(1), new FixedTableCellRenderer()); - mainPanel.add(Box.createVerticalStrut(10)); - mainPanel.add(container, BorderLayout.WEST); + replacementPanel.add(Box.createVerticalStrut(10)); + replacementPanel.add(container); + + mainPanel.add(replacementPanel); return mainPanel; } @@ -216,24 +245,25 @@ private void initValues() ConfigurationService configService = ChatConfigActivator.getConfigurationService(); - boolean e = - configService.getBoolean(ReplacementProperty - .getPropertyName(ReplacementServiceSmileyImpl.SMILEY_SOURCE), - true); - this.enableSmiley.setSelected(e); + this.enableSmiley.setSelected( + configService.getBoolean( + ReplacementProperty.getPropertyName( + ReplacementServiceSmileyImpl.SMILEY_SOURCE), + true)); - e = - configService.getBoolean(ReplacementProperty.REPLACEMENT_ENABLE, - true); - this.enableReplacement.setSelected(e); + this.enableReplacement.setSelected( + configService.getBoolean( + ReplacementProperty.REPLACEMENT_ENABLE, true)); - this.enableReplacementProposal.setEnabled(!e); - e = - configService.getBoolean(ReplacementProperty.REPLACEMENT_PROPOSAL, - true); - this.enableReplacementProposal.setSelected(e); + this.enableReplacementProposal.setSelected( + configService.getBoolean( + ReplacementProperty.REPLACEMENT_PROPOSAL, true)); - this.table.setEnabled(e); + this.disableReplacement.setSelected( + !this.enableReplacement.isSelected() + && !this.enableReplacementProposal.isSelected()); + + this.table.setEnabled(enableReplacement.isSelected()); } /** @@ -251,15 +281,13 @@ private void saveData() configService.setProperty(ReplacementProperty.REPLACEMENT_ENABLE, Boolean.toString(enableReplacement.isSelected())); - boolean e = enableReplacement.isSelected(); - - enableReplacementProposal.setEnabled(!e); configService.setProperty( - "plugin.chatconfig.replacement.proposal.enable" - , Boolean.toString(!e && enableReplacementProposal.isSelected())); + "plugin.chatconfig.replacement.proposal.enable", + Boolean.toString(enableReplacementProposal.isSelected())); table.getSelectionModel().clearSelection(); - table.setEnabled(e); + table.setEnabled(enableReplacement.isSelected() + || enableReplacementProposal.isSelected()); } /** diff --git a/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java b/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java index 62415b5ef..ed09959e3 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java @@ -11,6 +11,7 @@ import java.security.cert.*; import javax.swing.*; +import javax.swing.border.*; import net.java.sip.communicator.service.certificate.*; import net.java.sip.communicator.util.*; @@ -51,9 +52,9 @@ class VerifyCertificateDialogImpl private static final int MAX_MSG_PANE_HEIGHT = 800; /** - * The certificate to show. + * The certificates to show. */ - Certificate cert; + Certificate[] certs; /** * A text that describes why the verification failed. @@ -110,8 +111,7 @@ public VerifyCertificateDialogImpl(Certificate[] certs, R.getI18NString("service.gui.CERT_DIALOG_TITLE")); setModal(true); - // for now shows only the first certificate from the chain - this.cert = certs[0]; + this.certs = certs; this.message = message; setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); @@ -224,34 +224,11 @@ private void actionShowCertificate() return; } - certPanel.setLayout(new BorderLayout()); + certPanel.setLayout(new BorderLayout(5, 5)); + certPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); certPanel.add(alwaysTrustCheckBox, BorderLayout.NORTH); - Component certInfoPane = null; - if(cert instanceof X509Certificate) - { - certInfoPane = new X509CertificatePanel((X509Certificate)cert); - } - else - { - JTextArea textArea = new JTextArea(); - textArea.setOpaque(false); - textArea.setEditable(false); - textArea.setText(cert.toString()); - certInfoPane = textArea; - } - - final JScrollPane certScroll = new JScrollPane(certInfoPane); - certScroll.setPreferredSize(new Dimension(300, 300)); - certPanel.add(certScroll, BorderLayout.CENTER); - - SwingUtilities.invokeLater(new Runnable() - { - public void run() - { - certScroll.getVerticalScrollBar().setValue(0); - } - }); + certPanel.add(new X509CertificatePanel(certs), BorderLayout.CENTER); certButton.setText(R.getI18NString("service.gui.HIDE_CERT")); diff --git a/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java b/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java index 3c6bf336f..943a20401 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java @@ -39,9 +39,9 @@ public class ViewCertificateFrame private static final int MAX_MSG_PANE_HEIGHT = 800; /** - * The certificate to show. + * The certificates to show. */ - Certificate cert; + Certificate[] certs; /** * A text that describes why the verification failed. @@ -74,8 +74,7 @@ public ViewCertificateFrame(Certificate[] certs, setTitle(title != null ? title : R.getI18NString("service.gui.CERT_DIALOG_TITLE")); - // for now shows only the first certificate from the chain - this.cert = certs[0]; + this.certs = certs; this.message = message; setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); @@ -123,31 +122,8 @@ private void init() this.getContentPane().add(contentPane, BorderLayout.CENTER); - Component certInfoPane; - if(cert instanceof X509Certificate) - { - certInfoPane = new X509CertificatePanel((X509Certificate)cert); - } - else - { - JTextArea textArea = new JTextArea(); - textArea.setOpaque(false); - textArea.setEditable(false); - textArea.setText(cert.toString()); - certInfoPane = textArea; - } - - final JScrollPane certScroll = new JScrollPane(certInfoPane); - certScroll.setPreferredSize(new Dimension(300, 600)); - certPanel.add(certScroll, BorderLayout.CENTER); - - SwingUtilities.invokeLater(new Runnable() - { - public void run() - { - certScroll.getVerticalScrollBar().setValue(0); - } - }); + certPanel.add(new X509CertificatePanel(certs), BorderLayout.CENTER); + setPreferredSize(null); pack(); diff --git a/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java b/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java index dde05a449..ff4865113 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java @@ -9,14 +9,18 @@ import java.awt.*; import java.security.*; import java.security.cert.*; +import java.security.cert.Certificate; import java.security.interfaces.*; -import java.text.*; import java.util.*; import javax.naming.*; import javax.naming.ldap.*; import javax.security.auth.x500.*; import javax.swing.*; +import javax.swing.border.*; +import javax.swing.event.*; +import javax.swing.text.*; +import javax.swing.tree.*; import org.jitsi.service.resources.*; @@ -28,42 +32,171 @@ public class X509CertificatePanel { private static final long serialVersionUID = -8368302061995971947L; + private final JEditorPane infoTextPane = new JEditorPane(); + + private final ResourceManagementService R + = DesktopUtilActivator.getResources(); + /** - * Constructs a X509 certificate panel. + * Constructs a X509 certificate panel from a single certificate. + * If a chain is available instead use the second constructor. + * This constructor is kept for backwards compatibility and for convenience + * when there is only one certificate of interest. * * @param certificate X509Certificate object */ - public X509CertificatePanel(X509Certificate certificate) + public X509CertificatePanel(Certificate certificate) + { + this(new Certificate[] + { + certificate + }); + } + + /** + * Constructs a X509 certificate panel. + * + * @param certificates X509Certificate objects + */ + public X509CertificatePanel(Certificate[] certificates) { - ResourceManagementService R = DesktopUtilActivator.getResources(); - DateFormat dateFormatter - = DateFormat.getDateInstance(DateFormat.MEDIUM); + setLayout(new BorderLayout(5, 5)); + + // Certificate chain list + TransparentPanel topPanel = new TransparentPanel(new BorderLayout()); + topPanel.add(new JLabel("" + + R.getI18NString("service.gui.CERT_INFO_CHAIN") + + ""), BorderLayout.NORTH); + + DefaultMutableTreeNode top = new DefaultMutableTreeNode(); + DefaultMutableTreeNode previous = top; + for (int i = certificates.length - 1; i >= 0; i--) + { + Certificate cert = certificates[i]; + DefaultMutableTreeNode next = new DefaultMutableTreeNode(cert); + previous.add(next); + previous = next; + } + JTree tree = new JTree(top); + tree.setBorder(new BevelBorder(BevelBorder.LOWERED)); + tree.setRootVisible(false); + tree.setExpandsSelectedPaths(true); + tree.getSelectionModel().setSelectionMode( + TreeSelectionModel.SINGLE_TREE_SELECTION); + tree.setCellRenderer(new DefaultTreeCellRenderer() + { + + @Override + public Component getTreeCellRendererComponent(JTree tree, + Object value, boolean sel, boolean expanded, boolean leaf, + int row, boolean hasFocus) + { + JLabel component = (JLabel) super.getTreeCellRendererComponent( + tree, value, sel, expanded, leaf, row, hasFocus); + if (value instanceof DefaultMutableTreeNode) + { + Object o = ((DefaultMutableTreeNode) value).getUserObject(); + if (o instanceof X509Certificate) + { + component.setText( + getSimplifiedName((X509Certificate) o)); + } + else + { + // We don't know how to represent this certificate type, + // let's use the first 20 characters + String text = o.toString(); + if (text.length() > 20) + { + text = text.substring(0, 20); + } + component.setText(text); + } + } + return component; + } + + }); + tree.getSelectionModel().addTreeSelectionListener( + new TreeSelectionListener() + { + + @Override + public void valueChanged(TreeSelectionEvent e) + { + valueChangedPerformed(e); + } + }); + tree.setSelectionPath(new TreePath((( + (DefaultTreeModel)tree.getModel()).getPathToRoot(previous)))); + topPanel.add(tree, BorderLayout.CENTER); + + add(topPanel, BorderLayout.NORTH); + + // Certificate details pane + Caret caret = infoTextPane.getCaret(); + if (caret instanceof DefaultCaret) + { + ((DefaultCaret) caret).setUpdatePolicy(DefaultCaret.NEVER_UPDATE); + } - Insets valueInsets = new Insets(2,10,0,0); - Insets titleInsets = new Insets(10,5,0,0); + /* + * Make JEditorPane respect our default font because we will be using it + * to just display text. + */ + infoTextPane.putClientProperty( + JEditorPane.HONOR_DISPLAY_PROPERTIES, + true); + + infoTextPane.setOpaque(false); + infoTextPane.setEditable(false); + infoTextPane.setContentType("text/html"); + infoTextPane.setText(toString(certificates[0])); + + final JScrollPane certScroll = new JScrollPane(infoTextPane); + certScroll.setPreferredSize(new Dimension(300, 500)); + add(certScroll, BorderLayout.CENTER); + } - setLayout(new GridBagLayout()); + /** + * Creates a String representation of the given object. + * @param certificate to print + * @return the String representation + */ + private String toString(Object certificate) + { + final StringBuilder sb = new StringBuilder(); + sb.append("\n"); - int currentRow = 0; + if (certificate instanceof X509Certificate) + { + renderX509(sb, (X509Certificate) certificate); + } + else + { + sb.append("
\n");
+            sb.append(certificate.toString());
+            sb.append("
\n"); + } - GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.WEST; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.insets = new Insets(2,5,0,0); - constraints.gridx = 0; - constraints.weightx = 0; - constraints.weighty = 0; - constraints.gridy = currentRow++; + sb.append(""); + return sb.toString(); + } + /** + * Appends an HTML representation of the given X509Certificate. + * @param sb StringBuilder to append to + * @param certificate to print + */ + private void renderX509(StringBuilder sb, X509Certificate certificate) + { X500Principal issuer = certificate.getIssuerX500Principal(); X500Principal subject = certificate.getSubjectX500Principal(); - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_ISSUED_TO")), - constraints); + sb.append("\n"); // subject - constraints.insets = valueInsets; + addTitle(sb, R.getI18NString("service.gui.CERT_INFO_ISSUED_TO")); try { for(Rdn name : new LdapName(subject.getName()).getRdns()) @@ -75,47 +208,31 @@ public X509CertificatePanel(X509Certificate certificate) if ((lbl == null) || ("!" + lblKey + "!").equals(lbl)) lbl = nameType; - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel(lbl), constraints); - + final String value; Object nameValue = name.getValue(); if (nameValue instanceof byte[]) { byte[] nameValueAsByteArray = (byte[]) nameValue; - lbl + value = getHex(nameValueAsByteArray) + " (" + new String(nameValueAsByteArray) + ")"; } else - lbl = nameValue.toString(); + value = nameValue.toString(); - constraints.gridx = 1; - add(new JLabel(lbl), constraints); + addField(sb, lbl, value); } } catch (InvalidNameException ine) { - constraints.gridy = currentRow++; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_CN")), - constraints); - constraints.gridx = 1; - add( - new JLabel(subject.getName()), - constraints); + addField(sb, R.getI18NString("service.gui.CERT_INFO_CN"), + subject.getName()); } // issuer - constraints.gridy = currentRow++; - constraints.gridx = 0; - constraints.insets = titleInsets; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_ISSUED_BY")), - constraints); - constraints.insets = valueInsets; + addTitle(sb, R.getI18NString("service.gui.CERT_INFO_ISSUED_BY")); try { for(Rdn name : new LdapName(issuer.getName()).getRdns()) @@ -127,268 +244,133 @@ public X509CertificatePanel(X509Certificate certificate) if ((lbl == null) || ("!" + lblKey + "!").equals(lbl)) lbl = nameType; - constraints.gridy = currentRow++; - constraints.gridx = 0; - constraints.gridx = 0; - add(new JLabel(lbl), constraints); - + final String value; Object nameValue = name.getValue(); if (nameValue instanceof byte[]) { byte[] nameValueAsByteArray = (byte[]) nameValue; - lbl + value = getHex(nameValueAsByteArray) + " (" + new String(nameValueAsByteArray) + ")"; } else - lbl = nameValue.toString(); + value = nameValue.toString(); - constraints.gridx = 1; - add(new JLabel(lbl), constraints); + addField(sb, lbl, value); } } catch (InvalidNameException ine) { - constraints.gridy = currentRow++; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_CN")), - constraints); - constraints.gridx = 1; - add( - new JLabel(issuer.getName()), - constraints); + addField(sb, R.getI18NString("service.gui.CERT_INFO_CN"), + issuer.getName()); } // validity - constraints.gridy = currentRow++; - constraints.gridx = 0; - constraints.insets = titleInsets; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_VALIDITY")), - constraints); - constraints.insets = valueInsets; - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_ISSUED_ON")), - constraints); - constraints.gridx = 1; - add( - new JLabel(dateFormatter.format(certificate.getNotBefore())), - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_EXPIRES_ON")), - constraints); - constraints.gridx = 1; - add( - new JLabel(dateFormatter.format(certificate.getNotAfter())), - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - constraints.insets = titleInsets; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_FINGERPRINTS")), - constraints); - constraints.insets = valueInsets; + addTitle(sb, R.getI18NString("service.gui.CERT_INFO_VALIDITY")); + addField(sb, R.getI18NString("service.gui.CERT_INFO_ISSUED_ON"), + certificate.getNotBefore().toString()); + addField(sb, R.getI18NString("service.gui.CERT_INFO_EXPIRES_ON"), + certificate.getNotAfter().toString()); + addTitle(sb, R.getI18NString("service.gui.CERT_INFO_FINGERPRINTS")); try { String sha1String = getThumbprint(certificate, "SHA1"); String md5String = getThumbprint(certificate, "MD5"); - JTextArea sha1Area = new JTextArea(sha1String); - sha1Area.setLineWrap(false); - sha1Area.setOpaque(false); - sha1Area.setWrapStyleWord(true); - sha1Area.setEditable(false); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel("SHA1:"), - constraints); - - constraints.gridx = 1; - add( - sha1Area, - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel("MD5:"), - constraints); - - JTextArea md5Area = new JTextArea(md5String); - md5Area.setLineWrap(false); - md5Area.setOpaque(false); - md5Area.setWrapStyleWord(true); - md5Area.setEditable(false); - - constraints.gridx = 1; - add( - md5Area, - constraints); + addField(sb, "SHA1:", sha1String); + addField(sb, "MD5:", md5String); } - catch (Exception e) + catch (CertificateException e) { // do nothing as we cannot show this value } - constraints.gridy = currentRow++; - constraints.gridx = 0; - constraints.insets = titleInsets; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_CERT_DETAILS")), - constraints); - constraints.insets = valueInsets; - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_SER_NUM")), - constraints); - constraints.gridx = 1; - add( - new JLabel(certificate.getSerialNumber().toString()), - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_VER")), - constraints); - constraints.gridx = 1; - add( - new JLabel(String.valueOf(certificate.getVersion())), - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_SIGN_ALG")), - constraints); - constraints.gridx = 1; - add( - new JLabel(String.valueOf(certificate.getSigAlgName())), - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - constraints.insets = titleInsets; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_PUB_KEY_INFO")), - constraints); - constraints.insets = valueInsets; - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_ALG")), - constraints); - constraints.gridx = 1; - add( - new JLabel(certificate.getPublicKey().getAlgorithm()), - constraints); + addTitle(sb, R.getI18NString("service.gui.CERT_INFO_CERT_DETAILS")); + + addField(sb, R.getI18NString("service.gui.CERT_INFO_SER_NUM"), + certificate.getSerialNumber().toString()); + + addField(sb, R.getI18NString("service.gui.CERT_INFO_VER"), + String.valueOf(certificate.getVersion())); + + addField(sb, R.getI18NString("service.gui.CERT_INFO_SIGN_ALG"), + String.valueOf(certificate.getSigAlgName())); + + addTitle(sb, R.getI18NString("service.gui.CERT_INFO_PUB_KEY_INFO")); + + addField(sb, R.getI18NString("service.gui.CERT_INFO_ALG"), + certificate.getPublicKey().getAlgorithm()); if(certificate.getPublicKey().getAlgorithm().equals("RSA")) { RSAPublicKey key = (RSAPublicKey)certificate.getPublicKey(); - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_PUB_KEY")), - constraints); - - JTextArea pubkeyArea = new JTextArea( + addField(sb, R.getI18NString("service.gui.CERT_INFO_PUB_KEY"), R.getI18NString( "service.gui.CERT_INFO_KEY_BYTES_PRINT", new String[]{ String.valueOf(key.getModulus().toByteArray().length-1), key.getModulus().toString(16) })); - pubkeyArea.setLineWrap(false); - pubkeyArea.setOpaque(false); - pubkeyArea.setWrapStyleWord(true); - pubkeyArea.setEditable(false); - - constraints.gridx = 1; - add( - pubkeyArea, - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_EXP")), - constraints); - constraints.gridx = 1; - add( - new JLabel(key.getPublicExponent().toString()), - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_KEY_SIZE")), - constraints); - constraints.gridx = 1; - add( - new JLabel(R.getI18NString( + + addField(sb, R.getI18NString("service.gui.CERT_INFO_EXP"), + key.getPublicExponent().toString()); + + addField(sb, R.getI18NString("service.gui.CERT_INFO_KEY_SIZE"), + R.getI18NString( "service.gui.CERT_INFO_KEY_BITS_PRINT", new String[]{ - String.valueOf(key.getModulus().bitLength())})), - constraints); + String.valueOf(key.getModulus().bitLength())})); } else if(certificate.getPublicKey().getAlgorithm().equals("DSA")) { DSAPublicKey key = (DSAPublicKey)certificate.getPublicKey(); - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel("Y:"), constraints); - - JTextArea yArea = new JTextArea(key.getY().toString(16)); - yArea.setLineWrap(false); - yArea.setOpaque(false); - yArea.setWrapStyleWord(true); - yArea.setEditable(false); - - constraints.gridx = 1; - add( - yArea, - constraints); + addField(sb, "Y:", key.getY().toString(16)); } - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_SIGN")), - constraints); - - JTextArea signArea = new JTextArea( + addField(sb, R.getI18NString("service.gui.CERT_INFO_SIGN"), R.getI18NString( "service.gui.CERT_INFO_KEY_BYTES_PRINT", new String[]{ String.valueOf(certificate.getSignature().length), getHex(certificate.getSignature()) })); - signArea.setLineWrap(false); - signArea.setOpaque(false); - signArea.setWrapStyleWord(true); - signArea.setEditable(false); - - constraints.gridx = 1; - add( - signArea, - constraints); + + sb.append("
\n"); + } + + /** + * Add a title. + * + * @param sb StringBuilder to append to + * @param title to print + */ + private void addTitle(StringBuilder sb, String title) + { + sb.append("

") + .append(title).append("

\n"); + } + + /** + * Add a field. + * @param sb StringBuilder to append to + * @param field name of the certificate field + * @param value to print + */ + private void addField(StringBuilder sb, String field, String value) + { + sb.append("") + .append("") + .append(field).append("") + .append("").append(value).append("") + .append("\n"); } /** @@ -450,4 +432,62 @@ private static String getThumbprint(X509Certificate cert, String algorithm) } return sb.toString(); } + + /** + * Construct a "simplified name" based on the subject DN from the + * certificate. The purpose is to have something shorter to display in the + * list. The name used is one of the following DN parts, if + * available, otherwise the complete DN: + * 'CN', 'OU' or else 'O'. + * @param cert to read subject DN from + * @return the simplified name + */ + private static String getSimplifiedName(X509Certificate cert) + { + final HashMap parts = new HashMap(); + try + { + for (Rdn name : new LdapName( + cert.getSubjectX500Principal().getName()).getRdns()) + { + if (name.getType() != null && name.getValue() != null) + { + parts.put(name.getType(), name.getValue().toString()); + } + } + } + catch (InvalidNameException ignored) // NOPMD + { + } + + String result = parts.get("CN"); + if (result == null) + { + result = parts.get("OU"); + } + if (result == null) + { + result = parts.get("O"); + } + if (result == null) + { + result = cert.getSubjectX500Principal().getName(); + } + return result; + } + + /** + * Called when the selection changed in the tree. + * Loads the selected certificate. + * @param e the event + */ + private void valueChangedPerformed(TreeSelectionEvent e) + { + Object o = e.getNewLeadSelectionPath().getLastPathComponent(); + if (o instanceof DefaultMutableTreeNode) + { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) o; + infoTextPane.setText(toString(node.getUserObject())); + } + } }