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,