From cf8a7b04a68737b626c5bd86818a45d2ae0ae30a Mon Sep 17 00:00:00 2001 From: Yana Stamcheva Date: Fri, 30 Oct 2009 11:37:19 +0000 Subject: [PATCH] Drag'n'Drop related implementations including: - dropping MetaContact-s or simple String addresses in the Call dialog would initiate an invite and hence the creation of a conference call or if such already exist just the adding of the new callee to the conference. - added the possibility to drag the number entered in the call field on the bottom of the contact list. - replaced previous implementation of Drag'n'Drop in the contact list with the default drag'n'drop mechanism used in java, thus allowing the dropping of contacts outside the contact list (like in an existing call, chat or simply as a string in any other desktop application) - extended and override the default TransferHandler to provide visual representation of the dragged object (special handling of MetaContacts is provided in the ContactListCellRenderer) and add support of MetaContact flavor. --- resources/languages/resources.properties | 3 +- .../lookandfeel/SIPCommCallComboBoxUI.java | 57 ++- .../impl/gui/main/call/CallComboBox.java | 3 +- .../impl/gui/main/call/CallDialog.java | 15 +- .../gui/main/call/CallTransferHandler.java | 183 ++++++++ .../impl/gui/main/call/OneToOneCallPanel.java | 2 + .../call/conference/ConferenceCallPanel.java | 2 + .../gui/main/chat/ChatConversationPanel.java | 13 +- .../gui/main/chat/ChatTransferHandler.java | 86 +++- .../gui/main/contactlist/ContactList.java | 426 +++++++----------- .../contactlist/ContactListCellRenderer.java | 66 ++- .../ContactListTransferHandler.java | 240 +++++++++- .../contactlist/ContactRightButtonMenu.java | 93 +--- .../main/contactlist/DefaultContactList.java | 58 +++ .../java/sip/communicator/util/GuiUtils.java | 50 +- .../util/swing/ExtendedTransferHandler.java | 318 ++++++++++++- .../sip/communicator/util/util.manifest.mf | 1 + 17 files changed, 1203 insertions(+), 413 deletions(-) create mode 100644 src/net/java/sip/communicator/impl/gui/main/call/CallTransferHandler.java diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties index 1f48d4f89..80a178035 100644 --- a/resources/languages/resources.properties +++ b/resources/languages/resources.properties @@ -107,7 +107,8 @@ service.gui.CONNECTION_FAILED_MSG=Connection failed for the following account: U service.gui.CONNECTION_EXPIRED_MSG=You are currently disconnected from the {0} server. service.gui.CONNECTION_REQUIRED_TO_JOIN=You have to be connected in order to join a chat room. Please connect and then try again. service.gui.CONNECTION_REQUIRED_TO_LEAVE=You have to be connected in order to leave a chat room. -service.gui.CONTACT_NOT_SUPPORTING_TELEPHONY=The choosen {0} contact doesn''t support telephony. +service.gui.CONTACT_NOT_SUPPORTING_TELEPHONY=The chosen {0} contact doesn''t support telephony. +service.gui.CONTACT_NOT_SUPPORTING_CHAT_CONF=The chosen {0} contact doesn''t support chat conferencing. service.gui.CONTACT_PAUSED_TYPING={0} paused typing the message service.gui.CONTACT_TYPING={0} is typing a message service.gui.CONTACT_TYPING_STATE_STALE=typing state not updated diff --git a/src/net/java/sip/communicator/impl/gui/lookandfeel/SIPCommCallComboBoxUI.java b/src/net/java/sip/communicator/impl/gui/lookandfeel/SIPCommCallComboBoxUI.java index b8ac425b4..561f18ac9 100644 --- a/src/net/java/sip/communicator/impl/gui/lookandfeel/SIPCommCallComboBoxUI.java +++ b/src/net/java/sip/communicator/impl/gui/lookandfeel/SIPCommCallComboBoxUI.java @@ -12,8 +12,19 @@ import javax.swing.plaf.*; import javax.swing.plaf.basic.*; +/** + * A custom implementation of the SIPCommComboBoxUI specially designed + * for the call combo box. + * @author Yana Stamcheva + */ public class SIPCommCallComboBoxUI extends SIPCommComboBoxUI { + /** + * Creates an instance of the SIPCommCallComboBoxUI for the given + * component. + * @param c the component for which we create the UI + * @return an instance of the SIPCommCallComboBoxUI + */ public static ComponentUI createUI(JComponent c) { return new SIPCommCallComboBoxUI(); @@ -32,7 +43,7 @@ protected ComboPopup createPopup() return popup; } - + private static class SIPCommComboPopup extends BasicComboPopup { private static final long serialVersionUID = 0L; @@ -46,18 +57,21 @@ public SIPCommComboPopup(JComboBox combo) * Makes the popup visible if it is hidden and makes it hidden if it is * visible. */ - protected void togglePopup() { - if ( isVisible() ) { + protected void togglePopup() + { + if ( isVisible() ) + { hide(); } - else { + else + { setListSelection(comboBox.getSelectedIndex()); Point location = getPopupLocation(); show( comboBox, location.x, location.y ); } } - + /** * Configures the popup portion of the combo box. This method is called * when the UI class is created. @@ -71,7 +85,7 @@ protected void configurePopup() { setDoubleBuffered( true ); setFocusable( false ); } - + /** * Sets the list selection index to the selectedIndex. This * method is used to synchronize the list selection with the @@ -79,28 +93,31 @@ protected void configurePopup() { * * @param selectedIndex the index to set the list */ - private void setListSelection(int selectedIndex) { - if ( selectedIndex == -1 ) { + private void setListSelection(int selectedIndex) + { + if ( selectedIndex == -1 ) list.clearSelection(); - } - else { + else + { list.setSelectedIndex( selectedIndex ); - list.ensureIndexIsVisible( selectedIndex ); + list.ensureIndexIsVisible( selectedIndex ); } } - + /** * Calculates the upper left location of the Popup. + * @return the Point indicating the location of the popup */ - private Point getPopupLocation() { + private Point getPopupLocation() + { Dimension popupSize = comboBox.getSize(); Insets insets = getInsets(); - // reduce the width of the scrollpane by the insets so that the popup - // is the same width as the combo box. + // reduce the width of the scrollpane by the insets so that the + // popup is the same width as the combo box. int popupHeight = getPopupHeightForRowCount( comboBox.getMaximumRowCount()); - + popupSize.setSize(popupSize.width - (insets.right + insets.left), popupHeight); Rectangle popupBounds = computePopupBounds( @@ -108,16 +125,16 @@ private Point getPopupLocation() { comboBox.getEditor().getEditorComponent().getBounds().y - popupHeight - 4, popupSize.width, popupSize.height); - + Dimension scrollSize = popupBounds.getSize(); Point popupLocation = popupBounds.getLocation(); - + scroller.setMaximumSize( scrollSize ); scroller.setPreferredSize( scrollSize ); scroller.setMinimumSize( scrollSize ); - + list.revalidate(); - + return popupLocation; } } diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallComboBox.java b/src/net/java/sip/communicator/impl/gui/main/call/CallComboBox.java index 0c1ec32be..6520495a1 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallComboBox.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallComboBox.java @@ -17,6 +17,7 @@ import net.java.sip.communicator.impl.gui.main.contactlist.*; import net.java.sip.communicator.service.callhistory.*; import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.util.swing.*; /** * The CallComboBox is a history editable combo box that is @@ -53,8 +54,8 @@ public CallComboBox(MainCallPanel parentCallPanel) JTextField textField = (JTextField) this.getEditor().getEditorComponent(); + textField.setTransferHandler(new ExtendedTransferHandler()); textField.getDocument().addDocumentListener(this); - textField.getActionMap().put("createCall", new CreateCallAction()); textField.getInputMap().put( KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "createCall"); diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallDialog.java b/src/net/java/sip/communicator/impl/gui/main/call/CallDialog.java index 890a60489..d87cec9b7 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallDialog.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallDialog.java @@ -44,7 +44,7 @@ public class CallDialog private final Container contentPane = getContentPane(); - private Component callPanel = null; + private JComponent callPanel = null; private final HoldButton holdButton; @@ -154,7 +154,6 @@ public CallDialog(Call call) this.callDurationTimer = new Timer(1000, new CallTimerListener()); this.callDurationTimer.setRepeats(true); - } /** @@ -541,12 +540,7 @@ public void actionPerformed(ActionEvent e) { if (isLastConference) { - if (call.getCallPeerCount() > 1) - { - ((ConferenceCallPanel) callPanel) - .removeCallPeerPanel(peer); - } - else + if (call.getCallPeerCount() == 1) { contentPane.remove(callPanel); CallPeer singlePeer = call.getCallPeers().next(); @@ -559,6 +553,11 @@ public void actionPerformed(ActionEvent e) isLastConference = false; } + else if (call.getCallPeerCount() > 1) + { + ((ConferenceCallPanel) callPanel) + .removeCallPeerPanel(peer); + } if (contentPane.isVisible()) pack(); diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallTransferHandler.java b/src/net/java/sip/communicator/impl/gui/main/call/CallTransferHandler.java new file mode 100644 index 000000000..66c289ea2 --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallTransferHandler.java @@ -0,0 +1,183 @@ +/* + * SIP Communicator, 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.gui.main.call; + +import java.awt.datatransfer.*; +import java.awt.im.*; +import java.io.*; +import java.util.*; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.customcontrols.*; +import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.swing.*; + +/** + * A TransferHandler that we use to handle dropping of MetaContacts or + * simple string addresses to an existing Call. Dropping of a such data + * in the CallDialog would result in the creation of a call conference. + * + * @author Yana Stamcheva + */ +public class CallTransferHandler + extends ExtendedTransferHandler +{ + private static final Logger logger + = Logger.getLogger(CallTransferHandler.class); + + private final Call call; + + /** + * Creates an instance of CallTransferHandler by specifying the + * call, to which dragged callees will be added. + * @param call the call to which the dragged callees will be added + */ + public CallTransferHandler(Call call) + { + this.call = call; + } + + /** + * Indicates whether a component will accept an import of the given + * set of data flavors prior to actually attempting to import it. We return + * true to indicate that the transfer with at least one of the + * given flavors would work and false to reject the transfer. + *

+ * @param comp component + * @param flavor the data formats available + * @return true if the data can be inserted into the component, false + * otherwise + * @throws NullPointerException if support is {@code null} + */ + public boolean canImport(JComponent comp, DataFlavor flavor[]) + { + for (int i = 0, n = flavor.length; i < n; i++) + { + if (flavor[i].equals(DataFlavor.stringFlavor) + || flavor[i].equals(metaContactDataFlavor)) + { + if (comp instanceof JPanel) + { + return true; + } + + return false; + } + } + + return false; + } + + /** + * Handles transfers to the chat panel from the clip board or a + * DND drop operation. The Transferable parameter contains the + * data that needs to be imported. + *

+ * @param comp the component to receive the transfer; + * @param t the data to import + * @return true if the data was inserted into the component and false + * otherwise + */ + public boolean importData(JComponent comp, Transferable t) + { + if (t.isDataFlavorSupported(metaContactDataFlavor)) + { + Object o = null; + + try + { + o = t.getTransferData(metaContactDataFlavor); + } + catch (UnsupportedFlavorException e) + { + if (logger.isDebugEnabled()) + logger.debug("Failed to drop meta contact.", e); + } + catch (IOException e) + { + if (logger.isDebugEnabled()) + logger.debug("Failed to drop meta contact.", e); + } + + if (o instanceof MetaContact) + { + Iterator contacts + = ((MetaContact) o).getContactsForProvider( + call.getProtocolProvider()); + + String callee = null; + if (contacts.hasNext()) + callee = contacts.next().getAddress(); + + if (callee != null) + { + CallManager.inviteToConferenceCall( + new String[]{callee}, call); + + return true; + } + else + new ErrorDialog(null, + GuiActivator.getResources().getI18NString( + "service.gui.ERROR"), + GuiActivator.getResources().getI18NString( + "service.gui.CONTACT_NOT_SUPPORTING_TELEPHONY", + new String[]{callee})) + .showDialog(); + } + } + else if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) + { + InputContext inputContext = comp.getInputContext(); + if (inputContext != null) + { + inputContext.endComposition(); + } + + try + { + BufferedReader reader = new BufferedReader( + DataFlavor.stringFlavor.getReaderForText(t)); + + final StringBuffer buffToCall = new StringBuffer(); + + String str; + while ((str = reader.readLine()) != null) + buffToCall.append(str); + + // Need to call this through invokeLater, because apparently + // the new thread created in inviteToConferenceCall method + // causes problems after the first drop. + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + CallManager.inviteToConferenceCall( + new String[]{buffToCall.toString()}, call); + } + }); + + return true; + } + catch (UnsupportedFlavorException e) + { + if (logger.isDebugEnabled()) + logger.debug("Failed to drop string.", e); + } + catch (IOException e) + { + if (logger.isDebugEnabled()) + logger.debug("Failed to drop string.", e); + } + } + return false; + } +} \ No newline at end of file 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 d6e015977..fa60a11d7 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 @@ -46,6 +46,8 @@ public OneToOneCallPanel( CallDialog callDialog, this.setBorder(BorderFactory .createEmptyBorder(5, 5, 5, 5)); + this.setTransferHandler(new CallTransferHandler(call)); + this.addCallPeerPanel(callPeer); } diff --git a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceCallPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceCallPanel.java index 3b1a39bc8..d9f65f56a 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceCallPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceCallPanel.java @@ -86,6 +86,8 @@ public ConferenceCallPanel(CallDialog callDialog, Call c) this.getViewport().setOpaque(false); this.getViewport().add(mainPanel); + + mainPanel.setTransferHandler(new CallTransferHandler(call)); } /** 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 1c5b307e2..bea55709a 100755 --- a/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java @@ -61,13 +61,12 @@ public class ChatConversationPanel * matches URLs for the purposed of turning them into links. */ private static final Pattern URL_PATTERN - = Pattern - .compile( - "(" - + "(\\bwww\\.[^\\s<>\"]+\\.[^\\s<>\"]+/*[?#]*(\\w+[&=;?]\\w+)*\\b)" // wwwURL - + "|" - + "(\\b\\w+://[^\\s<>\"]+/*[?#]*(\\w+[&=;?]\\w+)*\\b)" // protocolURL - + ")"); + = Pattern.compile( + "(" + + "(\\bwww\\.[^\\s<>\"]+\\.[^\\s<>\"]+/*[?#]*(\\w+[&=;?]\\w+)*\\b)" // wwwURL + + "|" + + "(\\b\\w+://[^\\s<>\"]+/*[?#]*(\\w+[&=;?]\\w+)*\\b)" // protocolURL + + ")"); /** * The compiled Pattern which matches {@link #smileyStrings}. diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/ChatTransferHandler.java b/src/net/java/sip/communicator/impl/gui/main/chat/ChatTransferHandler.java index e965a1ace..2b8f4ebc3 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/ChatTransferHandler.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/ChatTransferHandler.java @@ -14,6 +14,10 @@ import javax.swing.*; import javax.swing.text.*; +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.customcontrols.*; +import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.swing.*; @@ -53,6 +57,31 @@ public ChatTransferHandler(ChatPanel chatPanel) this.chatPanel = chatPanel; } + /** + * Indicates whether a component will accept an import of the given + * set of data flavors prior to actually attempting to import it. We return + * true to indicate that the transfer with at least one of the + * given flavors would work and false to reject the transfer. + *

+ * @param comp component + * @param flavor the data formats available + * @return true if the data can be inserted into the component, false + * otherwise + * @throws NullPointerException if support is {@code null} + */ + public boolean canImport(JComponent comp, DataFlavor flavor[]) + { + for (int i = 0, n = flavor.length; i < n; i++) + { + if (flavor[i].equals(metaContactDataFlavor)) + { + return true; + } + } + + return super.canImport(comp, flavor); + } + /** * Handles transfers to the chat panel from the clip board or a * DND drop operation. The Transferable parameter contains the @@ -62,7 +91,6 @@ public ChatTransferHandler(ChatPanel chatPanel) * @param t the data to import * @return true if the data was inserted into the component and false * otherwise - * @see #importData(TransferHandler.TransferSupport) */ @SuppressWarnings("unchecked") //the case is taken care of public boolean importData(JComponent comp, Transferable t) @@ -95,6 +123,58 @@ public boolean importData(JComponent comp, Transferable t) logger.debug("Failed to drop files.", e); } } + else if (t.isDataFlavorSupported(metaContactDataFlavor)) + { + Object o = null; + + try + { + o = t.getTransferData(metaContactDataFlavor); + } + catch (UnsupportedFlavorException e) + { + logger.debug("Failed to drop meta contact.", e); + } + catch (IOException e) + { + logger.debug("Failed to drop meta contact.", e); + } + + if (o instanceof MetaContact) + { + MetaContact metaContact = (MetaContact) o; + + ChatTransport currentChatTransport + = chatPanel.getChatSession().getCurrentChatTransport(); + + Iterator contacts = metaContact + .getContactsForProvider( + currentChatTransport.getProtocolProvider()); + + String contact = null; + if (contacts.hasNext()) + contact = contacts.next().getAddress(); + + if (contact != null) + { + ArrayList inviteList = new ArrayList(); + inviteList.add(contact); + chatPanel.inviteContacts( currentChatTransport, + inviteList, null); + + return true; + } + else + new ErrorDialog( + null, + GuiActivator.getResources().getI18NString( + "service.gui.ERROR"), + GuiActivator.getResources().getI18NString( + "service.gui.CONTACT_NOT_SUPPORTING_CHAT_CONF", + new String[]{contact})) + .showDialog(); + } + } else if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) { InputContext inputContext = comp.getInputContext(); @@ -126,11 +206,11 @@ else if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) } catch (UnsupportedFlavorException ufe) { - //ignore + logger.debug("Failed to drop string.", ufe); } catch (IOException ioe) { - //ignore + logger.debug("Failed to drop string.", ioe); } } return false; diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactList.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactList.java index cc893c556..9c5c28b9d 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactList.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactList.java @@ -8,7 +8,6 @@ import java.awt.*; import java.awt.event.*; -import java.awt.image.*; import java.util.*; import javax.swing.*; @@ -36,8 +35,7 @@ public class ContactList extends DefaultContactList implements MetaContactListListener, - MouseListener, - MouseMotionListener + MouseListener { private static final String ADD_OPERATION = "AddOperation"; @@ -70,8 +68,6 @@ public class ContactList private ContactRightButtonMenu contactRightButtonMenu; - private ContactListDraggable draggedElement; - /** * A list of all contacts that are currently "active". An "active" contact * is a contact that has been sent a message. The list is used to indicate @@ -114,35 +110,9 @@ private void initListeners() this.contactListService.addMetaContactListListener(this); this.addMouseListener(this); - this.addMouseMotionListener(this); - - this.addFocusListener(new FocusAdapter() - { - public void focusLost(FocusEvent e) - { - if (draggedElement != null) - { - draggedElement.setVisible(false); - draggedElement = null; - } - } - }); this.addKeyListener(new CListKeySearchListener(this)); - this.addKeyListener(new KeyAdapter() - { - public void keyPressed(KeyEvent e) - { - if ((e.getKeyCode() == KeyEvent.VK_ESCAPE) - && (draggedElement != null)) - { - draggedElement.setVisible(false); - draggedElement = null; - } - } - }); - this.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) @@ -157,6 +127,7 @@ public void valueChanged(ListSelectionEvent e) /** * Handles the MetaContactEvent. Refreshes the list model. + * @param evt the MetaContactEvent that notified us of the add */ public void metaContactAdded(MetaContactEvent evt) { @@ -166,6 +137,8 @@ public void metaContactAdded(MetaContactEvent evt) /** * Handles the MetaContactRenamedEvent. Refreshes the list when * a meta contact is renamed. + * @param evt the MetaContactRnamedEvent that notified us of the + * rename */ public void metaContactRenamed(MetaContactRenamedEvent evt) { @@ -175,16 +148,15 @@ public void metaContactRenamed(MetaContactRenamedEvent evt) /** * Handles the MetaContactModifiedEvent. * Indicates that a MetaContact has been modified. - * @param evt the MetaContactModifiedEvent containing the corresponding contact + * @param evt the MetaContactModifiedEvent containing the corresponding + * contact */ - public void metaContactModified(MetaContactModifiedEvent evt) - { - //dummy impl - } + public void metaContactModified(MetaContactModifiedEvent evt) {} /** * Handles the ProtoContactEvent. Refreshes the list when a * protocol contact has been added. + * @param evt the ProtoContactEvent that notified us of the add */ public void protoContactAdded(ProtoContactEvent evt) { @@ -194,6 +166,7 @@ public void protoContactAdded(ProtoContactEvent evt) /** * Handles the ProtoContactEvent. Refreshes the list when a * protocol contact has been removed. + * @param evt the ProtoContactEvent that notified us of the remove */ public void protoContactRemoved(ProtoContactEvent evt) { @@ -203,6 +176,7 @@ public void protoContactRemoved(ProtoContactEvent evt) /** * Handles the ProtoContactEvent. Refreshes the list when a * protocol contact has been moved. + * @param evt the ProtoContactEvent that notified us of the move */ public void protoContactMoved(ProtoContactEvent evt) { @@ -214,15 +188,18 @@ public void protoContactMoved(ProtoContactEvent evt) * Implements the MetaContactListListener.protoContactModified * method with an empty body since we are not interested in proto contact * specific changes (such as the persistent data) in the user interface. + * @param evt the ProtoContactEvent that notified us of the + * modification */ public void protoContactModified(ProtoContactEvent evt) { - //currently ignored + // currently ignored } /** * Handles the MetaContactEvent. Refreshes the list when a meta * contact has been removed. + * @param evt the MetaContactEvent that notified us of the remove */ public void metaContactRemoved(MetaContactEvent evt) { @@ -232,6 +209,7 @@ public void metaContactRemoved(MetaContactEvent evt) /** * Handles the MetaContactMovedEvent. Refreshes the list when a * meta contact has been moved. + * @param evt the MetaContactEvent that notified us of the move */ public void metaContactMoved(MetaContactMovedEvent evt) { @@ -242,6 +220,7 @@ public void metaContactMoved(MetaContactMovedEvent evt) /** * Handles the MetaContactGroupEvent. Refreshes the list model * when a new meta contact group has been added. + * @param evt the MetaContactGroupEvent that notified us of the add */ public void metaContactGroupAdded(MetaContactGroupEvent evt) { @@ -254,6 +233,8 @@ public void metaContactGroupAdded(MetaContactGroupEvent evt) /** * Handles the MetaContactGroupEvent. Refreshes the list when a * meta contact group has been modified. + * @param evt the MetaContactGroupEvent that notified us of the + * modification */ public void metaContactGroupModified(MetaContactGroupEvent evt) { @@ -266,6 +247,8 @@ public void metaContactGroupModified(MetaContactGroupEvent evt) /** * Handles the MetaContactGroupEvent. Refreshes the list when a * meta contact group has been removed. + * @param evt the MetaContactGroupEvent that notified us of the + * remove */ public void metaContactGroupRemoved(MetaContactGroupEvent evt) { @@ -281,6 +264,8 @@ public void metaContactGroupRemoved(MetaContactGroupEvent evt) * index to the index of the contact that was selected before the reordered * event. This way the selection depends on the contact and not on the * index. + * @param evt the MetaContactGroupEvent that notified us of the + * reordering */ public void childContactsReordered(MetaContactGroupEvent evt) { @@ -415,6 +400,11 @@ public void fireContactListEvent(MetaContact sourceContact, new ContactListEvent(sourceContact, protocolContact, eventID)); } + /** + * + * @param contactListListeners + * @param event + */ protected void fireContactListEvent( java.util.List contactListListeners, ContactListEvent event) @@ -459,6 +449,7 @@ protected void fireContactListEvent( * selected and the GroupRightButtonMenu is opened. * * When the middle mouse button is clicked on a cell, the cell is selected. + * @param e the MouseEvent that notified us of the click */ public void mouseClicked(MouseEvent e) { @@ -611,35 +602,12 @@ else if (component instanceof JPanel) } } - public void mouseEntered(MouseEvent e) - { - //dummy impl - } - - public void mouseExited(MouseEvent e) - { - //dummy impl - } - /** - * Handle a mouse pressed event over the contact list. - * - * The main thing done when the mouse is pressed over the contact list is, - * simply select the MetaContact on which the event has occured. - * - * A ContactListDraggable object is also built, based on the - * element on which the mouse has been pressed. If the user is iniating - * a drag'n drop operation, the ContactListDraggable object will - * be monitored in mouseDragged and the dnd operation will be - * completed in mouseReleased. + * Selects the contact or group under the right mouse click. + * @param e the MouseEvent that notified us of the press */ public void mousePressed(MouseEvent e) { - // Request the focus in the meta contact list when user clicks on it. - this.requestFocus(); - - draggedElement = null; - // Select the meta contact under the right button click. if ((e.getModifiers() & InputEvent.BUTTON2_MASK) != 0 || (e.getModifiers() & InputEvent.BUTTON3_MASK) != 0 @@ -650,189 +618,13 @@ public void mousePressed(MouseEvent e) if (index != -1) this.setSelectedIndex(index); } - - int selectedIndex = this.getSelectedIndex(); - Object selectedValue = this.getSelectedValue(); - - // If there's no index selected we have nothing to do here. - if (selectedIndex < 0) - return; - - ContactListCellRenderer renderer = (ContactListCellRenderer) this - .getCellRenderer().getListCellRendererComponent(this, - selectedValue, - selectedIndex, - true, - true); - - Point selectedCellPoint = this.indexToLocation(selectedIndex); - - int translatedX = e.getX() - selectedCellPoint.x; - - if (selectedValue instanceof MetaContact - && (e.getModifiers() & InputEvent.BUTTON1_MASK) != 0) - { - MetaContact mContact = (MetaContact) selectedValue; - - // get the component under the mouse - Component component - = this.getHorizontalComponent(renderer, translatedX); - - if (component instanceof JLabel) - { - Image image = new BufferedImage(component.getWidth(), - component.getHeight(), - BufferedImage.TYPE_INT_ARGB); - - Graphics g = image.getGraphics(); - - g.setColor(getBackground()); - g.fillRect(0, 0, image.getWidth(null), image.getHeight(null)); - - component.paint(image.getGraphics()); - draggedElement = new ContactListDraggable( this, - mContact, - null, - image); - } - } - - if (draggedElement != null) - { - mainFrame.setGlassPane(draggedElement); - - Point p = (Point) e.getPoint().clone(); - - p = SwingUtilities.convertPoint(e.getComponent(), p, draggedElement); - draggedElement.setLocation(p); - } - } - - /** - * If we are moving a Contact or MetaContact we - * update the coordinates of the dragged element and paint it at its new - * position. - */ - public void mouseDragged(MouseEvent e) - { - if (draggedElement != null) - { - if (!draggedElement.isVisible()) - draggedElement.setVisible(true); - Point p = (Point) e.getPoint().clone(); - p = SwingUtilities.convertPoint(e.getComponent(), p, draggedElement); - draggedElement.setLocation(p); - draggedElement.repaint(); - } - else - this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } - /** - * If we were performing a drag'n drop operation when the mouse is released, - * complete it by moving the Contact and/or MetaContact enclosed - * by the draggedElement from that MetaContact - * to the MetaContactGroup or MetaContact on which - * the drop occurs. - */ - public void mouseReleased(MouseEvent e) - { - int selectedIndex = this.locationToIndex(e.getPoint()); - - Object dest = listModel.getElementAt(selectedIndex); - - if (draggedElement != null) - { - if (dest instanceof MetaContact) - { - MetaContact contactDest = (MetaContact) dest; - if (draggedElement.getMetaContact() != contactDest) - { - if (draggedElement.getContact() != null) - { - new MoveContactToMetaContactThread( - draggedElement.getContact(), - contactDest).start(); - } - else - { - // we move all contacts from this meta contact - Iterator i - = draggedElement.getMetaContact().getContacts(); - while(i.hasNext()) - { - Contact contact = i.next(); + public void mouseEntered(MouseEvent e) {} - new MoveContactToMetaContactThread( - contact, - contactDest).start(); - } + public void mouseExited(MouseEvent e) {} - } - } - } - else if (dest instanceof MetaContactGroup) - { - MetaContactGroup contactDest = (MetaContactGroup) dest; - if (draggedElement.getContact() != null) - { - // there is a specific contact moved. if this contact - // has fellow in its meta contact parent, we move only - // this contact. Otherwise we move the whole meta contact - // as this contact is the only one inside it. - if (draggedElement.getMetaContact().getContactCount() > 1) - { - new MoveContactToGroupThread( - draggedElement.getContact(), - contactDest).start(); - } - else if (!contactDest.contains( - draggedElement.getMetaContact())) - { - new MoveMetaContactThread( - draggedElement.getMetaContact(), - contactDest).start(); - } - } - else if (!contactDest.contains(draggedElement.getMetaContact())) - { - try - { - new MoveMetaContactThread( - draggedElement.getMetaContact(), - contactDest).start(); - } - catch (Exception ex) - { - new ErrorDialog( - mainFrame, - GuiActivator.getResources().getI18NString( - "service.gui.MOVE_TO_GROUP"), - GuiActivator.getResources().getI18NString( - "service.gui.MOVE_CONTACT_ERROR"), - ex).showDialog(); - } - } - } - - draggedElement.setVisible(false); - draggedElement = null; - } - - setCursor(Cursor - .getPredefinedCursor((dest instanceof MetaContactGroup) ? Cursor.HAND_CURSOR - : Cursor.DEFAULT_CURSOR)); - } - - public void mouseMoved(MouseEvent e) - { - int selectedIndex = this.locationToIndex(e.getPoint()); - Object cell = listModel.getElementAt(selectedIndex); - - setCursor(Cursor - .getPredefinedCursor((cell instanceof MetaContactGroup) ? Cursor.HAND_CURSOR - : Cursor.DEFAULT_CURSOR)); - } + public void mouseReleased(MouseEvent e) {} /** * Returns the component positioned at the given x in the given container. @@ -964,8 +756,6 @@ else if (o instanceof MetaContactEvent) /** * Refreshes the given group content. - * - * @param group the group to update */ private class RefreshGroup implements Runnable @@ -1025,8 +815,6 @@ else if (operation.equals(REMOVE_OPERATION)) /** * Refreshes the given contact content. - * - * @param group the contact to refresh */ private class RefreshContact implements Runnable @@ -1098,6 +886,7 @@ public void modifyGroup(MetaContactGroup group) /** * Refreshes all the contact list. + * @param group the MetaContactGroup to add */ public void addGroup(MetaContactGroup group) { @@ -1116,6 +905,7 @@ public void addGroup(MetaContactGroup group) /** * Refreshes all the contact list. + * @param group the MetaContactGroup to remove */ public void removeGroup(MetaContactGroup group) { @@ -1163,6 +953,7 @@ public void refreshAll() /** * Adds the given contact to the contact list. + * @param contact the MetaContact to add */ public void addContact(MetaContact contact) { @@ -1182,6 +973,8 @@ public void addContact(MetaContact contact) /** * Refreshes all the contact list. + * @param event the MetaContactEvent, from which we get the contact + * to remove */ public void removeContact(MetaContactEvent event) { @@ -1230,7 +1023,7 @@ public GroupRightButtonMenu getGroupRightButtonMenu() * Sets the showOffline property. * * @param isShowOffline TRUE to show all offline users, FALSE to hide - * offline users. + * offline users. */ public void setShowOffline(boolean isShowOffline) { @@ -1290,6 +1083,17 @@ public MainFrame getMainFrame() return mainFrame; } + /** + * Moves the given srcContact to the destMetaContact. + * @param srcContact the Contact to move + * @param destMetaContact the destination MetaContact to move to + */ + public void moveContactToMetaContact( Contact srcContact, + MetaContact destMetaContact) + { + new MoveContactToMetaContactThread(srcContact, destMetaContact).start(); + } + /** * Moves the given Contact to the given MetaContact and * asks user for confirmation. @@ -1346,6 +1150,99 @@ public void run() } } + /** + * Moves the given srcMetaContact to the destMetaContact. + * @param srcMetaContact the MetaContact to move + * @param destMetaContact the destination MetaContact to move to + */ + public void moveMetaContactToMetaContact( MetaContact srcMetaContact, + MetaContact destMetaContact) + { + new MoveMetaContactToMetaContactThread(srcMetaContact, destMetaContact) + .start(); + } + + /** + * Moves the given Contact to the given MetaContact and + * asks user for confirmation. + */ + private class MoveMetaContactToMetaContactThread extends Thread + { + private final MetaContact srcMetaContact; + private final MetaContact destMetaContact; + + public MoveMetaContactToMetaContactThread( MetaContact srcContact, + MetaContact destMetaContact) + { + this.srcMetaContact = srcContact; + this.destMetaContact = destMetaContact; + } + + @SuppressWarnings("fallthrough") //intentional + public void run() + { + if (!ConfigurationManager.isMoveContactConfirmationRequested()) + { + // We move all subcontacts of the source MetaContact to the + // destination MetaContact. + this.moveAllSubcontacts(); + + return; + } + + String message = GuiActivator.getResources().getI18NString( + "service.gui.MOVE_SUBCONTACT_QUESTION", + new String[]{ srcMetaContact.getDisplayName(), + destMetaContact.getDisplayName()}); + + MessageDialog dialog = new MessageDialog( + mainFrame, + GuiActivator.getResources() + .getI18NString("service.gui.MOVE_CONTACT"), + message, + GuiActivator.getResources() + .getI18NString("service.gui.MOVE")); + + switch (dialog.showDialog()) + { + case MessageDialog.OK_DONT_ASK_CODE: + ConfigurationManager.setMoveContactConfirmationRequested(false); + // do fall through + + case MessageDialog.OK_RETURN_CODE: + // We move all subcontacts of the source MetaContact to the + // destination MetaContact. + this.moveAllSubcontacts(); + break; + } + } + + /** + * Move all subcontacts of the srcMetaContact to + * destMetaContact. + */ + private void moveAllSubcontacts() + { + Iterator contacts = srcMetaContact.getContacts(); + while(contacts.hasNext()) + { + mainFrame.getContactList().moveContact( + contacts.next(), destMetaContact); + } + } + } + + /** + * Moves the given srcContact to the destGroup. + * @param srcContact the Contact to move + * @param destGroup the destination MetaContactGroup to move to + */ + public void moveContactToGroup( Contact srcContact, + MetaContactGroup destGroup) + { + new MoveContactToGroupThread(srcContact, destGroup).start(); + } + /** * Moves the given Contact to the given MetaContactGroup * and asks user for confirmation. @@ -1357,7 +1254,7 @@ private class MoveContactToGroupThread extends Thread private final MetaContactGroup destGroup; public MoveContactToGroupThread(Contact srcContact, - MetaContactGroup destGroup) + MetaContactGroup destGroup) { this.srcContact = srcContact; this.destGroup = destGroup; @@ -1402,6 +1299,17 @@ public void run() } } + /** + * Moves the given srcContact to the destGroup. + * @param srcContact the MetaContact to move + * @param destGroup the destination MetaContactGroup to move to + */ + public void moveMetaContactToGroup( MetaContact srcContact, + MetaContactGroup destGroup) + { + new MoveMetaContactThread(srcContact, destGroup).start(); + } + /** * Moves the given MetaContact to the given MetaContactGroup * and asks user for confirmation. @@ -1425,8 +1333,15 @@ public void run() if (!ConfigurationManager.isMoveContactConfirmationRequested()) { // we move the specified contact - mainFrame.getContactList().moveMetaContact( - srcContact, destGroup); + try + { + mainFrame.getContactList().moveMetaContact( + srcContact, destGroup); + } + catch (MetaContactListException e) + { + + } return; } @@ -1549,19 +1464,6 @@ public void setMouseListener(MouseListener l) this.addMouseListener(l); } - /** - * Resets the contained mouse motion listeners and adds the given one. This - * allows other components to integrate the contact list by specifying their - * own mouse events. - * - * @param l the mouse listener to set. - */ - public void setMouseMotionListener(MouseMotionListener l) - { - this.removeMouseMotionListener(this); - this.addMouseMotionListener(l); - } - /** * Checks whether the group is closed. * diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListCellRenderer.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListCellRenderer.java index a1a868c13..5ac069319 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListCellRenderer.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListCellRenderer.java @@ -27,7 +27,8 @@ */ public class ContactListCellRenderer extends JPanel - implements ListCellRenderer + implements ListCellRenderer, + Icon { private static final int AVATAR_HEIGHT = 30; @@ -116,8 +117,10 @@ public ContactListCellRenderer() this.nameLabel.setPreferredSize(new Dimension(10, 17)); + this.rightLabel.setPreferredSize( + new Dimension(AVATAR_WIDTH, AVATAR_HEIGHT)); this.rightLabel.setFont(rightLabel.getFont().deriveFont(9f)); - this.rightLabel.setHorizontalAlignment(JLabel.RIGHT); + this.rightLabel.setHorizontalAlignment(JLabel.CENTER); this.add(nameLabel, BorderLayout.CENTER); this.add(rightLabel, BorderLayout.EAST); @@ -331,4 +334,63 @@ private void internalPaintComponent(Graphics g) 10, 10); } } + + /** + * Returns the height of this icon. + * @return the height of this icon + */ + public int getIconHeight() + { + return this.getHeight() + 10; + } + + /** + * Returns the width of this icon. + * @return the widht of this icon + */ + public int getIconWidth() + { + return this.getWidth() + 10; + } + + /** + * Draw the icon at the specified location. Paints this component as an + * icon. + * @param c the component which can be used as observer + * @param g the Graphics object used for painting + * @param x the position on the X coordinate + * @param y the position on the Y coordinate + */ + public void paintIcon(Component c, Graphics g, int x, int y) + { + Graphics2D g2 = (Graphics2D) g.create(); + try + { + AntialiasingManager.activateAntialiasing(g2); + + g2.setColor(Color.WHITE); + g2.setComposite(AlphaComposite. + getInstance(AlphaComposite.SRC_OVER, 0.8f)); + g2.fillRoundRect(x, y, + getIconWidth() - 1, getIconHeight() - 1, + 10, 10); + g2.setColor(Color.DARK_GRAY); + g2.drawRoundRect(x, y, + getIconWidth() - 1, getIconHeight() - 1, + 10, 10); + + // Indent component content from the border. + g2.translate(x + 5, y + 5); + + // Paint component. + super.paint(g2); + + // + g2.translate(x, y); + } + finally + { + g.dispose(); + } + } } diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTransferHandler.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTransferHandler.java index e5141a31b..7a177c620 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTransferHandler.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTransferHandler.java @@ -19,7 +19,7 @@ import net.java.sip.communicator.util.swing.*; /** - * A TransferHandler that we use to handle DnD operations of files in our + * A TransferHandler that we use to handle DnD operations in our * ContactList. * * @author Yana Stamcheva @@ -32,11 +32,61 @@ public class ContactListTransferHandler private final DefaultContactList contactList; + /** + * Creates an instance of ContactListTransferHandler passing the + * contactList which uses this TransferHandler. + * @param contactList the DefaultContactList which uses this + * TransferHandler + */ public ContactListTransferHandler(DefaultContactList contactList) { this.contactList = contactList; } + /** + * Creates a transferable for text pane components in order to enable drag + * and drop of text. + * @param component the component for which to create a + * Transferable + * @return the created Transferable + */ + public Transferable createTransferable(JComponent component) + { + if (component instanceof JList) + { + JList list = (JList) component; + return new ContactListTransferable( + list.getSelectedIndex(), list.getSelectedValue()); + } + + return super.createTransferable(component); + } + + /** + * Indicates whether a component will accept an import of the given + * set of data flavors prior to actually attempting to import it. We return + * true to indicate that the transfer with at least one of the + * given flavors would work and false to reject the transfer. + *

+ * @param comp component + * @param flavor the data formats available + * @return true if the data can be inserted into the component, false + * otherwise + * @throws NullPointerException if support is {@code null} + */ + public boolean canImport(JComponent comp, DataFlavor flavor[]) + { + for (int i = 0, n = flavor.length; i < n; i++) + { + if (flavor[i].equals(metaContactDataFlavor)) + { + return true; + } + } + + return super.canImport(comp, flavor); + } + /** * Handles transfers to the contact list from the clip board or a * DND drop operation. The Transferable parameter contains the @@ -83,15 +133,113 @@ public boolean importData(JComponent comp, Transferable t) GuiActivator.getUIService().getChatWindowManager() .openChat(chatPanel, false); } - - // Otherwise fire files dropped event. return true; } } } + else if (t.isDataFlavorSupported(metaContactDataFlavor)) + { + Object o = null; + + try + { + o = t.getTransferData(metaContactDataFlavor); + } + catch (UnsupportedFlavorException e) + { + if (logger.isDebugEnabled()) + logger.debug("Failed to drop meta contact.", e); + } + catch (IOException e) + { + if (logger.isDebugEnabled()) + logger.debug("Failed to drop meta contact.", e); + } + + if (o instanceof MetaContact + && comp instanceof ContactList) + { + MetaContact transferredContact = (MetaContact) o; + ContactList list = (ContactList) comp; + + Object dest = list.getSelectedValue(); + + if (transferredContact != null) + { + if (dest instanceof MetaContact) + { + MetaContact destContact = (MetaContact) dest; + if (transferredContact != destContact) + { + list.moveMetaContactToMetaContact( + transferredContact, destContact); + } + return true; + } + else if (dest instanceof MetaContactGroup) + { + MetaContactGroup destGroup = (MetaContactGroup) dest; + + if (transferredContact.getParentMetaContactGroup() + != destGroup) + { + list.moveMetaContactToGroup( + transferredContact, destGroup); + } + return true; + } + } + } + } return false; } + /** + * Overrides TransferHandler.getVisualRepresentation(Transferable t) + * in order to return a custom drag icon. + *

+ * The default parent implementation of this method returns null. + * + * @param t the data to be transferred; this value is expected to have been + * created by the createTransferable method + * @return the icon to show when dragging + */ + public Icon getVisualRepresentation(Transferable t) + { + ContactListCellRenderer renderer = null; + + if (t instanceof ContactListTransferable) + { + ContactListTransferable transferable = ((ContactListTransferable) t); + + try + { + renderer = (ContactListCellRenderer) + contactList.getCellRenderer() + .getListCellRendererComponent( + contactList, + transferable.getTransferData(metaContactDataFlavor), + transferable.getTransferIndex(), false, false); + } + catch (UnsupportedFlavorException e) + { + if (logger.isDebugEnabled()) + logger.debug( + "Unsupported flavor while" + + " obtaining transfer data.", e); + } + catch (IOException e) + { + if (logger.isDebugEnabled()) + logger.debug( + "The data for the request flavor" + + " is no longer available.", e); + } + } + + return renderer; + } + /** * Returns the ChatPanel corresponding to the currently selected * contact. @@ -109,13 +257,91 @@ private ChatPanel getChatPanel() MetaContact metaContact = (MetaContact) selectedObject; // Obtain the corresponding chat panel. - chatPanel - = GuiActivator - .getUIService() - .getChatWindowManager() + chatPanel = GuiActivator.getUIService().getChatWindowManager() .getContactChat(metaContact, true); } return chatPanel; } + + /** + * Transferable for JList that enables drag and drop of contacts. + */ + public class ContactListTransferable implements Transferable + { + private int transferredIndex; + + private Object transferredObject; + + /** + * Creates an instance of ContactListTransferable. + * @param index the index of the transferred object in the list + * @param o the transferred list object + */ + public ContactListTransferable(int index, Object o) + { + this.transferredIndex = index; + this.transferredObject = o; + } + + /** + * Returns supported flavors. + * @return an array of supported flavors + */ + public DataFlavor[] getTransferDataFlavors() + { + return new DataFlavor[] { metaContactDataFlavor, + DataFlavor.stringFlavor}; + } + + /** + * Returns true if the given flavor is supported, + * otherwise returns false. + * @param flavor the data flavor to verify + * @return true if the given flavor is supported, + * otherwise returns false + */ + public boolean isDataFlavorSupported(DataFlavor flavor) + { + return metaContactDataFlavor.equals(flavor) + || DataFlavor.stringFlavor.equals(flavor); + } + + /** + * Returns the selected text. + * @param flavor the flavor + * @return the selected text + * @exception UnsupportedFlavorException if the requested data flavor + * is not supported. + * @exception IOException if the data is no longer available in the + * requested flavor. + */ + public Object getTransferData(DataFlavor flavor) + throws UnsupportedFlavorException, + IOException + { + if (metaContactDataFlavor.equals(flavor)) + { + return transferredObject; + } + else if (DataFlavor.stringFlavor.equals(flavor)) + { + if (transferredObject instanceof MetaContact) + return ((MetaContact) transferredObject).getDisplayName(); + } + else + throw new UnsupportedFlavorException(flavor); + + return null; + } + + /** + * Returns the index of the transferred list cell. + * @return the index of the transferred list cell + */ + public int getTransferIndex() + { + return transferredIndex; + } + } } diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactRightButtonMenu.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactRightButtonMenu.java index 6d4636cbf..0c9808f99 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactRightButtonMenu.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactRightButtonMenu.java @@ -593,24 +593,7 @@ else if (itemName.startsWith(moveToPrefix)) = mainFrame.getGroupByID( itemName.substring(moveToPrefix.length())); - try - { - if(group != null) - { - mainFrame.getContactList(). - moveMetaContact(contactItem, group); - } - } - catch (Exception ex) - { - new ErrorDialog( - mainFrame, - GuiActivator.getResources().getI18NString( - "service.gui.MOVE_TO_GROUP"), - GuiActivator.getResources().getI18NString( - "service.gui.MOVE_CONTACT_ERROR"), - ex).showDialog(); - } + guiContactList.moveMetaContactToGroup(contactItem, group); } else if (itemName.startsWith(removeContactPrefix)) { @@ -820,12 +803,11 @@ public void groupSelected(ContactListEvent evt) if(moveAllContacts) { - mainFrame.getContactList() - .moveMetaContact(contactItem, sourceGroup); + guiContactList.moveMetaContactToGroup(contactItem, sourceGroup); } else if(contactToMove != null) { - new MoveSubcontactThread(sourceGroup).start(); + guiContactList.moveContactToGroup(contactToMove, sourceGroup); } guiContactList.setDisableOpenClose(false); @@ -869,7 +851,8 @@ private void moveContact(MetaContact toMetaContact) ErrorDialog.WARNING) .showDialog(); } - else { + else + { guiContactList.removeExcContactListListener(this); // FIXME: unset the special cursor after a subcontact has been moved @@ -878,71 +861,13 @@ private void moveContact(MetaContact toMetaContact) if(moveAllContacts) { - new MoveAllSubcontactsThread(toMetaContact).start(); + guiContactList.moveMetaContactToMetaContact( + contactItem, toMetaContact); } else if(contactToMove != null) { - new MoveSubcontactThread(toMetaContact).start(); - } - } - } - - /** - * Moves the previously chosen contact in the given meta group or meta - * contact. - */ - private class MoveSubcontactThread extends Thread - { - private MetaContact metaContact; - - private MetaContactGroup metaGroup; - - public MoveSubcontactThread(MetaContact metaContact) - { - this.metaContact = metaContact; - } - - public MoveSubcontactThread(MetaContactGroup metaGroup) - { - this.metaGroup = metaGroup; - } - - public void run() - { - if(metaContact != null) - { - mainFrame.getContactList() - .moveContact(contactToMove, metaContact); - } - else { - mainFrame.getContactList() - .moveContact(contactToMove, metaGroup); - } - } - } - - /** - * Moves all sub-contacts contained in the previously selected meta contact - * in the given meta contact. - */ - private class MoveAllSubcontactsThread extends Thread - { - private MetaContact metaContact; - - public MoveAllSubcontactsThread(MetaContact metaContact) - { - this.metaContact = metaContact; - } - - public void run() - { - Iterator i = contactItem.getContacts(); - - while(i.hasNext()) - { - Contact contact = i.next(); - mainFrame.getContactList() - .moveContact(contact, metaContact); + guiContactList + .moveContactToMetaContact(contactToMove, toMetaContact); } } } diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/DefaultContactList.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/DefaultContactList.java index fb7ea7a22..c5ac9a3f1 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/DefaultContactList.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/DefaultContactList.java @@ -30,6 +30,11 @@ public class DefaultContactList { private static final long serialVersionUID = 0L; + /** + * The cached mouse event. + */ + private MouseEvent cachedMouseEvent; + /** * Creates an instance of DefaultContactList. */ @@ -40,6 +45,7 @@ public DefaultContactList() this.getSelectionModel().setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + this.setDragEnabled(true); this.setTransferHandler(new ContactListTransferHandler(this)); this.setCellRenderer(new ContactListCellRenderer()); } @@ -277,4 +283,56 @@ else if(o instanceof ConferenceChatContact) } while (index != startIndex); return -1; } + + /** + * Processes the MouseEvent we have previously cached before + * invoking the parent fireSelectionValueChanged which would + * notify the JList ListSelectionListeners that the + * selection model has changed. + *

+ * Workaround provided by simon@tardell.se on 29-DEC-2002 for bug 4521075 + * http://bugs.sun.com/bugdatabase/view_bug.do;jsessionid=a13e98ab2364524506eb91505565?bug_id=4521075 + * "Drag gesture in JAVA different from Windows". The bug is also noticed + * on Mac Leopard. + * + * @param firstIndex the first selected index + * @param lastIndex the last selected index + * @param isAdjusting true if multiple changes are being made + */ + protected void fireSelectionValueChanged(int firstIndex, int lastIndex, + boolean isAdjusting) + { + if (cachedMouseEvent != null) + { + super.processMouseEvent(new MouseEvent( + (Component) cachedMouseEvent.getSource(), + cachedMouseEvent.getID(), + cachedMouseEvent.getWhen(), + cachedMouseEvent.getModifiers(), + cachedMouseEvent.getX(), + cachedMouseEvent.getY(), + cachedMouseEvent.getClickCount(), + cachedMouseEvent.isPopupTrigger())); + + cachedMouseEvent = null; + } + super.fireSelectionValueChanged(firstIndex, lastIndex, isAdjusting); + } + + /** + * Caches the incoming mouse event before passing it to the parent + * implementation of processMouseEvent. + *

+ * Workaround provided by simon@tardell.se on 29-DEC-2002 for bug 4521075 + * http://bugs.sun.com/bugdatabase/view_bug.do;jsessionid=a13e98ab2364524506eb91505565?bug_id=4521075 + * "Drag gesture in JAVA different from Windows". The bug is also noticed + * on Mac Leopard. + * @param event the MouseEvent to process + */ + protected void processMouseEvent(MouseEvent event) + { + if ((event.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0) + cachedMouseEvent= event; + super.processMouseEvent(event); + } } diff --git a/src/net/java/sip/communicator/util/GuiUtils.java b/src/net/java/sip/communicator/util/GuiUtils.java index 6fe6da03f..07f3c1de2 100644 --- a/src/net/java/sip/communicator/util/GuiUtils.java +++ b/src/net/java/sip/communicator/util/GuiUtils.java @@ -7,6 +7,8 @@ package net.java.sip.communicator.util; import java.awt.*; +import java.awt.font.*; +import java.awt.geom.*; import java.util.*; import javax.swing.*; @@ -53,7 +55,7 @@ public static String replaceSpecialRegExpChars(String text) { return text.replaceAll("([.()^&$*|])", "\\\\$1"); } - + /** * Returns the width in pixels of a text. * @param c the component where the text is contained @@ -66,6 +68,43 @@ public static int getStringWidth(Component c, String text) .getFontMetrics(c.getFont()), text); } + /** + * Returns the bounds of the given string. + * @param text the string to measure + * @return the bounds of the given string + */ + public static Rectangle2D getStringBounds(String text) + { + Font font = UIManager.getFont("Label.font"); + + FontRenderContext frc = new FontRenderContext(null, true, false); + + TextLayout layout = new TextLayout(text, font, frc); + + return layout.getBounds(); + } + + /** + * Counts occurrences of the needle character in the given + * text. + * @param text the text in which we search + * @param needle the character we're looking for + * @return the count of occurrences of the needle chat in the + * given text + */ + public static int countOccurrences(String text, char needle) + { + int count = 0; + for (char c : text.toCharArray()) + { + if (c == needle) + { + ++count; + } + } + return count; + } + /** * Compares the two dates. The comparison is based only on the day, month * and year values. Returns 0 if the two dates are equals, a value < 0 if @@ -173,6 +212,13 @@ public static String formatDate(final long date) return strBuf.toString(); } + /** + * Formats the given date as: Month DD, YYYY and appends it to the given + * dateStrBuf string buffer. + * @param date the date to format + * @param dateStrBuf the StringBuffer, where to append the + * formatted date + */ public static void formatDate(long date, StringBuffer dateStrBuf) { c1.setTimeInMillis(date); @@ -296,6 +342,8 @@ private static void formatTime(int time, StringBuffer timeStrBuf) /** * Formats the given long to X hour, Y min, Z sec. + * @param millis the time in milliseconds to format + * @return the formatted seconds */ public static String formatSeconds(long millis) { diff --git a/src/net/java/sip/communicator/util/swing/ExtendedTransferHandler.java b/src/net/java/sip/communicator/util/swing/ExtendedTransferHandler.java index 0d79c63a8..3aaba072e 100644 --- a/src/net/java/sip/communicator/util/swing/ExtendedTransferHandler.java +++ b/src/net/java/sip/communicator/util/swing/ExtendedTransferHandler.java @@ -6,12 +6,20 @@ */ package net.java.sip.communicator.util.swing; +import java.awt.*; import java.awt.datatransfer.*; +import java.awt.dnd.*; +import java.awt.event.*; +import java.awt.geom.*; +import java.awt.image.*; import java.io.*; import javax.swing.*; import javax.swing.text.*; +import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.util.*; + /** * A TransferHandler that we use to handle copying, pasting and DnD operations. * The string handler is heavily inspired by Sun's @@ -26,6 +34,19 @@ public class ExtendedTransferHandler extends TransferHandler { + /** + * The data flavor used when transferring MetaContacts. + */ + protected static final DataFlavor metaContactDataFlavor + = new DataFlavor(MetaContact.class, "MetaContact"); + + /** + * The drag icon that is the visual representation of the contained + * Transferable. + */ + protected static final ImageIcon dragIcon = UtilActivator.getResources() + .getImage("service.gui.icons.DRAG_ICON"); + /** * Returns the type of transfer actions supported by the source; * any bitwise-OR combination of COPY, MOVE @@ -42,11 +63,12 @@ public class ExtendedTransferHandler */ public int getSourceActions(JComponent c) { - return TransferHandler.COPY_OR_MOVE; + return TransferHandler.COPY; } - /** Indicates whether a component will accept an import of the given - * set of data flavors prior to actually attempting to import it. We return + /** + * Indicates whether a component will accept an import of the given + * set of data flavors prior to actually attempting to import it. We return * true to indicate that the transfer with at least one of the * given flavors would work and false to reject the transfer. *

@@ -79,22 +101,25 @@ else if (flavor[i].equals(DataFlavor.stringFlavor)) return false; } } - return false; } /** * Creates a transferable for text pane components in order to enable drag * and drop of text. + * @param component the component for which to create a + * Transferable + * @return the created Transferable */ - public Transferable createTransferable(JComponent comp) + public Transferable createTransferable(JComponent component) { - if (comp instanceof JTextPane) + if (component instanceof JTextPane + || component instanceof JTextField) { - return new SelectedTextTransferable((JTextPane) comp); + return new SelectedTextTransferable((JTextComponent) component); } - return super.createTransferable(comp); + return super.createTransferable(component); } /** @@ -131,7 +156,7 @@ public void exportToClipboard(JComponent comp, Document doc = textComponent.getDocument(); String srcData = doc.getText(startIndex, endIndex - startIndex); - StringSelection contents =new StringSelection(srcData); + StringSelection contents = new StringSelection(srcData); // this may throw an IllegalStateException, // but it will be caught and handled in the @@ -156,35 +181,294 @@ public void exportToClipboard(JComponent comp, */ public class SelectedTextTransferable implements Transferable { - private JTextPane textPane; + private JTextComponent textComponent; - public SelectedTextTransferable(JTextPane textPane) + /** + * Creates an instance of SelectedTextTransferable. + * @param component the text component + */ + public SelectedTextTransferable(JTextComponent component) { - this.textPane = textPane; + this.textComponent = component; } - // Returns supported flavors + /** + * Returns supported flavors. + * @return an array of supported flavors + */ public DataFlavor[] getTransferDataFlavors() { return new DataFlavor[]{DataFlavor.stringFlavor}; } - // Returns true if flavor is supported + /** + * Returns true if the given flavor is supported, + * otherwise returns false. + * @param flavor the data flavor to verify + * @return true if the given flavor is supported, + * otherwise returns false + */ public boolean isDataFlavorSupported(DataFlavor flavor) { return DataFlavor.stringFlavor.equals(flavor); } - // Returns Selected Text + /** + * Returns the selected text. + * @param flavor the flavor + * @return the selected text + * @exception IOException if the data is no longer available in the + * requested flavor. + * @exception UnsupportedFlavorException if the requested data flavor + * is not supported. + */ public Object getTransferData(DataFlavor flavor) - throws UnsupportedFlavorException, IOException + throws UnsupportedFlavorException, + IOException { if (!DataFlavor.stringFlavor.equals(flavor)) { throw new UnsupportedFlavorException(flavor); } - return textPane.getSelectedText(); + return textComponent.getSelectedText(); + } + } + + /** + * Overrides TransferHandler.getVisualRepresentation(Transferable t) + * in order to return a custom drag icon. + *

+ * The default parent implementation of this method returns null. + * + * @param t the data to be transferred; this value is expected to have been + * created by the createTransferable method + * @return the icon to show when dragging + */ + public Icon getVisualRepresentation(Transferable t) + { + Icon icon = null; + if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) + { + String text = null; + try + { + text = (String) t.getTransferData(DataFlavor.stringFlavor); + } + catch (UnsupportedFlavorException e) {} + catch (IOException e) {} + + if (text != null) + { + Rectangle2D bounds = GuiUtils.getStringBounds(text); + BufferedImage image = new BufferedImage( + (int) Math.ceil(bounds.getWidth()), + (int) Math.ceil(bounds.getHeight()), + BufferedImage.TYPE_INT_ARGB); + + Graphics g = image.getGraphics(); + AntialiasingManager.activateAntialiasing(g); + g.setColor(Color.BLACK); + // Don't know why if we draw the string on y = 0 it doesn't + // appear in the visible area. + g.drawString(text, 0, 10); + + icon = new ImageIcon(image); + } + } + + return icon; + } + + // Patch for bug 4816922 "No way to set drag icon: + // TransferHandler.getVisualRepresentation() is not used". + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4816922 + // The following workaround comes from bug comments section! + private static SwingDragGestureRecognizer recognizer = null; + + private static class SwingDragGestureRecognizer + extends DragGestureRecognizer + { + SwingDragGestureRecognizer(DragGestureListener dgl) + { + super(DragSource.getDefaultDragSource(), null, NONE, dgl); + } + + void gestured(JComponent c, MouseEvent e, int srcActions, int action) + { + setComponent(c); + setSourceActions(srcActions); + appendEvent(e); + fireDragGestureRecognized(action, e.getPoint()); } + + /** + * Registers this DragGestureRecognizer's Listeners with the Component. + */ + protected void registerListeners() {} + + /** + * Unregister this DragGestureRecognizer's Listeners with the Component. + *

+ * Subclasses must override this method. + */ + protected void unregisterListeners() {} } + + /** + * Overrides TransferHandler.exportAsDrag method in order to call + * our own SwingDragGestureRecognizer, which takes care of the + * visual representation icon. + * + * @param comp the component holding the data to be transferred; this + * argument is provided to enable sharing of TransferHandlers + * by multiple components + * @param e the event that triggered the transfer + * @param action the transfer action initially requested; this should + * be a value of either COPY or MOVE; + * the value may be changed during the course of the drag operation + */ + public void exportAsDrag(JComponent comp, InputEvent e, int action) + { + int srcActions = getSourceActions(comp); + int dragAction = srcActions & action; + + // only mouse events supported for drag operations + if (! (e instanceof MouseEvent)) + action = NONE; + + if (action != NONE && !GraphicsEnvironment.isHeadless()) + { + if (recognizer == null) + { + recognizer = new SwingDragGestureRecognizer(new DragHandler()); + } + recognizer.gestured(comp, (MouseEvent) e, srcActions, dragAction); + } + else + { + exportDone(comp, null, NONE); + } + } + + /** + * This is the default drag handler for drag and drop operations that + * use the TransferHandler. + */ + private static class DragHandler + implements DragGestureListener, + DragSourceListener + { + private boolean scrolls; + + // --- DragGestureListener methods ----------------------------------- + + /** + * A Drag gesture has been recognized. + * @param dge the DragGestureEvent that notified us + */ + public void dragGestureRecognized(DragGestureEvent dge) + { + JComponent c = (JComponent) dge.getComponent(); + ExtendedTransferHandler th + = (ExtendedTransferHandler) c.getTransferHandler(); + + Transferable t = th.createTransferable(c); + if (t != null) + { + scrolls = c.getAutoscrolls(); + c.setAutoscrolls(false); + try + { + Image img = null; + Icon icn = th.getVisualRepresentation(t); + + if (icn != null) + { + if (icn instanceof ImageIcon) + { + img = ((ImageIcon) icn).getImage(); + } + else + { + img = new BufferedImage(icn.getIconWidth(), + icn.getIconHeight(), + BufferedImage.TYPE_4BYTE_ABGR); + Graphics g = img.getGraphics(); + icn.paintIcon(c, g, 0, 0); + } + } + if (img == null) + { + dge.startDrag(null, t, this); + } + else + { + dge.startDrag(null, img, + new Point(0, -1 * img.getHeight(null)), t, this); + } + + return; + } + catch (RuntimeException re) + { + c.setAutoscrolls(scrolls); + } + } + + th.exportDone(c, t, NONE); + } + + // --- DragSourceListener methods ----------------------------------- + + /** + * As the hotspot enters a platform dependent drop site. + * @param e the DragSourceDragEvent containing the details of + * the drag + */ + public void dragEnter(DragSourceDragEvent e) + {} + + /** + * As the hotspot moves over a platform dependent drop site. + * @param e the DragSourceDragEvent containing the details of + * the drag + */ + public void dragOver(DragSourceDragEvent e) + { + } + + /** + * As the hotspot exits a platform dependent drop site. + * @param e the DragSourceDragEvent containing the details of + * the drag + */ + public void dragExit(DragSourceEvent e) + {} + + /** + * As the operation completes. + * @param e the DragSourceDragEvent containing the details of + * the drag + */ + public void dragDropEnd(DragSourceDropEvent e) + { + DragSourceContext dsc = e.getDragSourceContext(); + JComponent c = (JComponent) dsc.getComponent(); + + if (e.getDropSuccess()) + { + ((ExtendedTransferHandler) c.getTransferHandler()) + .exportDone(c, dsc.getTransferable(), e.getDropAction()); + } + else + { + ((ExtendedTransferHandler) c.getTransferHandler()) + .exportDone(c, dsc.getTransferable(), NONE); + } + c.setAutoscrolls(scrolls); + } + + public void dropActionChanged(DragSourceDragEvent dsde) {} + } } diff --git a/src/net/java/sip/communicator/util/util.manifest.mf b/src/net/java/sip/communicator/util/util.manifest.mf index f63e2a08c..594943a78 100644 --- a/src/net/java/sip/communicator/util/util.manifest.mf +++ b/src/net/java/sip/communicator/util/util.manifest.mf @@ -28,6 +28,7 @@ Import-Package: org.xml.sax, net.java.sip.communicator.service.resources, net.java.sip.communicator.service.keybindings, net.java.sip.communicator.service.configuration, + net.java.sip.communicator.service.contactlist, sun.awt.shell Export-Package: net.java.sip.communicator.util.xml, net.java.sip.communicator.util.swing.plaf,