diff options
author | Yana Stamcheva <yana@jitsi.org> | 2010-05-03 12:27:34 +0000 |
---|---|---|
committer | Yana Stamcheva <yana@jitsi.org> | 2010-05-03 12:27:34 +0000 |
commit | afcafe3061f9abaa52633b727fe34769d4cc0e04 (patch) | |
tree | a36c09734602576e9a8388794142420f06ce649c /src | |
parent | c454284e43c6db33c1d13f4a1c9d9e06d7726d9f (diff) | |
download | jitsi-afcafe3061f9abaa52633b727fe34769d4cc0e04.zip jitsi-afcafe3061f9abaa52633b727fe34769d4cc0e04.tar.gz jitsi-afcafe3061f9abaa52633b727fe34769d4cc0e04.tar.bz2 |
- Introduces new contact list data model that allows adding of external contact sources and hence the search in such sources.
- As part of the support for external contact sources, implements a call history external source and its user interface.
- Addresses issue #706 Indicate missed calls
Diffstat (limited to 'src')
64 files changed, 5085 insertions, 1270 deletions
diff --git a/src/net/java/sip/communicator/impl/callhistory/CallHistoryActivator.java b/src/net/java/sip/communicator/impl/callhistory/CallHistoryActivator.java index 37118c7..049ca9c 100755 --- a/src/net/java/sip/communicator/impl/callhistory/CallHistoryActivator.java +++ b/src/net/java/sip/communicator/impl/callhistory/CallHistoryActivator.java @@ -6,15 +6,22 @@ */ package net.java.sip.communicator.impl.callhistory; +import java.util.*; + import org.osgi.framework.*; + import net.java.sip.communicator.service.history.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.resources.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.service.callhistory.*; +import net.java.sip.communicator.service.contactsource.*; /** - * Activates the CallHistoryService + * Activates the <tt>CallHistoryService</tt>. * * @author Damian Minkov + * @author Yana Stamcheva */ public class CallHistoryActivator implements BundleActivator @@ -22,18 +29,39 @@ public class CallHistoryActivator private static Logger logger = Logger.getLogger(CallHistoryActivator.class); - private CallHistoryServiceImpl callHistoryService = null; + /** + * The bundle context. + */ + public static BundleContext bundleContext; + + /** + * The <tt>CallHistoryServiceImpl</tt> instantiated in the start method + * of this bundle. + */ + private static CallHistoryServiceImpl callHistoryService = null; + + /** + * The service responsible for resources. + */ + private static ResourceManagementService resourcesService; + + /** + * The map containing all registered + */ + private static final Map<Object, ProtocolProviderFactory> + providerFactoriesMap = new Hashtable<Object, ProtocolProviderFactory>(); /** * Initialize and start call history * - * @param bundleContext BundleContext + * @param bc the <tt>BundleContext</tt> * @throws Exception */ - public void start(BundleContext bundleContext) throws Exception + public void start(BundleContext bc) throws Exception { - try{ + bundleContext = bc; + try{ logger.logEntry(); ServiceReference refHistory = bundleContext.getServiceReference( @@ -53,6 +81,10 @@ public class CallHistoryActivator bundleContext.registerService( CallHistoryService.class.getName(), callHistoryService, null); + bundleContext.registerService( + ContactSourceService.class.getName(), + new CallHistoryContactSource(), null); + logger.info("Call History Service ...[REGISTERED]"); } finally @@ -62,9 +94,89 @@ public class CallHistoryActivator } + /** + * Stops this bundle. + * @param bundleContext the <tt>BundleContext</tt> + * @throws Exception if the stop operation goes wrong + */ public void stop(BundleContext bundleContext) throws Exception { if(callHistoryService != null) callHistoryService.stop(bundleContext); } + + /** + * Returns the instance of <tt>CallHistoryService</tt> created in this + * activator. + * @return the instance of <tt>CallHistoryService</tt> created in this + * activator + */ + public static CallHistoryService getCallHistoryService() + { + return callHistoryService; + } + + /** + * Returns the <tt>ResourceManagementService</tt>, through which we will + * access all resources. + * + * @return the <tt>ResourceManagementService</tt>, through which we will + * access all resources. + */ + public static ResourceManagementService getResources() + { + if (resourcesService == null) + { + ServiceReference serviceReference = bundleContext + .getServiceReference(ResourceManagementService.class.getName()); + + if(serviceReference == null) + return null; + + resourcesService = (ResourceManagementService) bundleContext + .getService(serviceReference); + } + return resourcesService; + } + + /** + * Returns all <tt>ProtocolProviderFactory</tt>s obtained from the bundle + * context. + * + * @return all <tt>ProtocolProviderFactory</tt>s obtained from the bundle + * context + */ + public static Map<Object, ProtocolProviderFactory> + getProtocolProviderFactories() + { + ServiceReference[] serRefs = null; + try + { + // get all registered provider factories + serRefs = + bundleContext.getServiceReferences( + ProtocolProviderFactory.class.getName(), null); + + } + catch (InvalidSyntaxException e) + { + logger.error("LoginManager : " + e); + } + + if (serRefs != null) + { + for (int i = 0; i < serRefs.length; i++) + { + + ProtocolProviderFactory providerFactory + = (ProtocolProviderFactory) bundleContext + .getService(serRefs[i]); + + providerFactoriesMap.put(serRefs[i] + .getProperty(ProtocolProviderFactory.PROTOCOL), + providerFactory); + } + } + return providerFactoriesMap; + } } diff --git a/src/net/java/sip/communicator/impl/callhistory/CallHistoryContactSource.java b/src/net/java/sip/communicator/impl/callhistory/CallHistoryContactSource.java new file mode 100644 index 0000000..74d4249 --- /dev/null +++ b/src/net/java/sip/communicator/impl/callhistory/CallHistoryContactSource.java @@ -0,0 +1,153 @@ +/* + * 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.callhistory; + +import java.util.*; + +import net.java.sip.communicator.service.callhistory.*; +import net.java.sip.communicator.service.contactsource.*; + +/** + * The <tt>CallHistoryContactSource</tt> is the contact source for the call + * history. + * + * @author Yana Stamcheva + */ +public class CallHistoryContactSource implements ContactSourceService +{ + /** + * The display name of this contact source. + */ + private static final String CALL_HISTORY_NAME = "Call history"; + + /** + * Returns the display name of this contact source. + * @return the display name of this contact source + */ + public String getDisplayName() + { + return CALL_HISTORY_NAME; + } + + /** + * Queries this contact source for the given <tt>searchString</tt>. + * @param queryString the string to search for + * @return the created query + */ + public ContactQuery queryContactSource(String queryString) + { + if (queryString != null && queryString.length() > 0) + return new CallHistoryQuery( + CallHistoryActivator.getCallHistoryService() + .findByPeer(queryString)); + else + return new CallHistoryQuery( + CallHistoryActivator.getCallHistoryService() + .findLast(50)); + } + + /** + * The <tt>CallHistoryQuery</tt> contains information about a current query + * to the contact source. + */ + private class CallHistoryQuery implements ContactQuery + { + /** + * A list of all registered query listeners. + */ + private final List<ContactQueryListener> queryListeners + = new LinkedList<ContactQueryListener>(); + + /** + * A list of all source contact results. + */ + private final List<SourceContact> sourceContacts + = new LinkedList<SourceContact>(); + + /** + * Creates a <tt>CallHistoryQuery</tt>. + * @param callRecords a collection of the result call records + */ + public CallHistoryQuery(Collection<CallRecord> callRecords) + { + Iterator<CallRecord> recordsIter = callRecords.iterator(); + + while (recordsIter.hasNext()) + { + sourceContacts.add( + new CallHistorySourceContact( + CallHistoryContactSource.this, + recordsIter.next())); + } + } + + /** + * Adds the given <tt>ContactQueryListener</tt> to the list of query + * listeners. + * @param l the <tt>ContactQueryListener</tt> to add + */ + public void addContactQueryListener(ContactQueryListener l) + { + synchronized (queryListeners) + { + queryListeners.add(l); + } + } + + /** + * This query could not be canceled. + */ + public void cancel() + { + + } + + /** + * Removes the given <tt>ContactQueryListener</tt> from the list of + * query listeners. + * @param l the <tt>ContactQueryListener</tt> to remove + */ + public void removeContactQueryListener(ContactQueryListener l) + { + synchronized (queryListeners) + { + queryListeners.remove(l); + } + } + + /** + * Returns a list containing the results of this query. + * @return a list containing the results of this query + */ + public List<SourceContact> getQueryResults() + { + return sourceContacts; + } + + /** + * Returns the <tt>ContactSourceService</tt>, where this query was first + * initiated. + * @return the <tt>ContactSourceService</tt>, where this query was first + * initiated + */ + public ContactSourceService getContactSource() + { + return CallHistoryContactSource.this; + } + } + + /** + * Returns the identifier of this contact source. Some of the common + * identifiers are defined here (For example the CALL_HISTORY identifier + * should be returned by all call history implementations of this interface) + * @return the identifier of this contact source + */ + public String getIdentifier() + { + return CALL_HISTORY; + } +} diff --git a/src/net/java/sip/communicator/impl/callhistory/CallHistoryServiceImpl.java b/src/net/java/sip/communicator/impl/callhistory/CallHistoryServiceImpl.java index 48fbbb6..93d83f4 100644 --- a/src/net/java/sip/communicator/impl/callhistory/CallHistoryServiceImpl.java +++ b/src/net/java/sip/communicator/impl/callhistory/CallHistoryServiceImpl.java @@ -42,8 +42,9 @@ public class CallHistoryServiceImpl Logger.getLogger(CallHistoryServiceImpl.class); private static String[] STRUCTURE_NAMES = - new String[] { "callStart", "callEnd", "dir", "callParticipantIDs", - "callParticipantStart", "callParticipantEnd","callParticipantStates" }; + new String[] { "accountUID", "callStart", "callEnd", "dir", + "callParticipantIDs", "callParticipantStart", + "callParticipantEnd", "callParticipantStates" }; private static HistoryRecordStructure recordStructure = new HistoryRecordStructure(STRUCTURE_NAMES); @@ -59,8 +60,10 @@ public class CallHistoryServiceImpl private Object syncRoot_HistoryService = new Object(); - private final Map<CallHistorySearchProgressListener, SearchProgressWrapper> progressListeners = - new Hashtable<CallHistorySearchProgressListener, SearchProgressWrapper>(); + private final Map<CallHistorySearchProgressListener, SearchProgressWrapper> + progressListeners + = new Hashtable<CallHistorySearchProgressListener, + SearchProgressWrapper>(); private final List<CallRecordImpl> currentCallRecords = new Vector<CallRecordImpl>(); @@ -68,22 +71,29 @@ public class CallHistoryServiceImpl private final CallChangeListener historyCallChangeListener = new HistoryCallChangeListener(); + private HistoryReader historyReader; + + /** + * Returns the underlying history service. + * @return the underlying history service + */ public HistoryService getHistoryService() { return historyService; } /** - * Returns all the calls made by all the contacts - * in the supplied metacontact after the given date + * Returns all the calls made by all the contacts in the supplied + * <tt>contact</tt> after the given date. * * @param contact MetaContact which contacts participate in * the returned calls * @param startDate Date the start date of the calls - * @return Collection of CallRecords with CallPeerRecord + * @return the <tt>CallHistoryQuery</tt>, corresponding to this find * @throws RuntimeException */ - public Collection<CallRecord> findByStartDate(MetaContact contact, Date startDate) + public Collection<CallRecord> findByStartDate( + MetaContact contact, Date startDate) throws RuntimeException { throw new UnsupportedOperationException("Not implemented yet!"); @@ -93,7 +103,7 @@ public class CallHistoryServiceImpl * Returns all the calls made after the given date * * @param startDate Date the start date of the calls - * @return Collection of CallRecords with CallPeerRecord + * @return the <tt>CallHistoryQuery</tt>, corresponding to this find * @throws RuntimeException */ public Collection<CallRecord> findByStartDate(Date startDate) @@ -104,16 +114,16 @@ public class CallHistoryServiceImpl { // the default ones History history = this.getHistory(null, null); - HistoryReader reader = history.getReader(); - addHistorySearchProgressListeners(reader, 1); + historyReader = history.getReader(); + addHistorySearchProgressListeners(historyReader, 1); QueryResultSet<HistoryRecord> rs - = reader.findByStartDate(startDate); + = historyReader.findByStartDate(startDate); while (rs.hasNext()) { HistoryRecord hr = rs.next(); result.add(convertHistoryRecordToCallRecord(hr)); } - removeHistorySearchProgressListeners(reader); + removeHistorySearchProgressListeners(historyReader); } catch (IOException ex) { @@ -133,7 +143,8 @@ public class CallHistoryServiceImpl * @return Collection of CallRecords with CallPeerRecord * @throws RuntimeException */ - public Collection<CallRecord> findByEndDate(MetaContact contact, Date endDate) + public Collection<CallRecord> findByEndDate(MetaContact contact, + Date endDate) throws RuntimeException { throw new UnsupportedOperationException("Not implemented yet!"); @@ -146,22 +157,25 @@ public class CallHistoryServiceImpl * @return Collection of CallRecords with CallPeerRecord * @throws RuntimeException */ - public Collection<CallRecord> findByEndDate(Date endDate) throws RuntimeException + public Collection<CallRecord> findByEndDate(Date endDate) + throws RuntimeException { - TreeSet<CallRecord> result = new TreeSet<CallRecord>(new CallRecordComparator()); + TreeSet<CallRecord> result + = new TreeSet<CallRecord>(new CallRecordComparator()); try { // the default ones History history = this.getHistory(null, null); - HistoryReader reader = history.getReader(); - addHistorySearchProgressListeners(reader, 1); - QueryResultSet<HistoryRecord> rs = reader.findByEndDate(endDate); + historyReader = history.getReader(); + addHistorySearchProgressListeners(historyReader, 1); + QueryResultSet<HistoryRecord> rs + = historyReader.findByEndDate(endDate); while (rs.hasNext()) { HistoryRecord hr = rs.next(); result.add(convertHistoryRecordToCallRecord(hr)); } - removeHistorySearchProgressListeners(reader); + removeHistorySearchProgressListeners(historyReader); } catch (IOException ex) { @@ -181,7 +195,8 @@ public class CallHistoryServiceImpl * @return Collection of CallRecords with CallPeerRecord * @throws RuntimeException */ - public Collection<CallRecord> findByPeriod(MetaContact contact, Date startDate, Date endDate) + public Collection<CallRecord> findByPeriod(MetaContact contact, + Date startDate, Date endDate) throws RuntimeException { throw new UnsupportedOperationException("Not implemented yet!"); @@ -195,24 +210,25 @@ public class CallHistoryServiceImpl * @return Collection of CallRecords with CallPeerRecord * @throws RuntimeException */ - public Collection<CallRecord> findByPeriod(Date startDate, Date endDate) throws - RuntimeException + public Collection<CallRecord> findByPeriod(Date startDate, Date endDate) + throws RuntimeException { - TreeSet<CallRecord> result = new TreeSet<CallRecord>(new CallRecordComparator()); + TreeSet<CallRecord> result + = new TreeSet<CallRecord>(new CallRecordComparator()); try { // the default ones History history = this.getHistory(null, null); - HistoryReader reader = history.getReader(); - addHistorySearchProgressListeners(reader, 1); + historyReader = history.getReader(); + addHistorySearchProgressListeners(historyReader, 1); QueryResultSet<HistoryRecord> rs - = reader.findByPeriod(startDate, endDate); + = historyReader.findByPeriod(startDate, endDate); while (rs.hasNext()) { HistoryRecord hr = rs.next(); result.add(convertHistoryRecordToCallRecord(hr)); } - removeHistorySearchProgressListeners(reader); + removeHistorySearchProgressListeners(historyReader); } catch (IOException ex) { @@ -247,13 +263,14 @@ public class CallHistoryServiceImpl */ public Collection<CallRecord> findLast(int count) throws RuntimeException { - TreeSet<CallRecord> result = new TreeSet<CallRecord>(new CallRecordComparator()); + TreeSet<CallRecord> result + = new TreeSet<CallRecord>(new CallRecordComparator()); try { // the default ones History history = this.getHistory(null, null); - QueryResultSet<HistoryRecord> rs - = history.getReader().findLast(count); + historyReader = history.getReader(); + QueryResultSet<HistoryRecord> rs = historyReader.findLast(count); while (rs.hasNext()) { HistoryRecord hr = rs.next(); @@ -277,21 +294,22 @@ public class CallHistoryServiceImpl public Collection<CallRecord> findByPeer(String address) throws RuntimeException { - TreeSet<CallRecord> result = new TreeSet<CallRecord>(new CallRecordComparator()); + TreeSet<CallRecord> result + = new TreeSet<CallRecord>(new CallRecordComparator()); try { // the default ones History history = this.getHistory(null, null); - HistoryReader reader = history.getReader(); - addHistorySearchProgressListeners(reader, 1); + historyReader = history.getReader(); + addHistorySearchProgressListeners(historyReader, 1); QueryResultSet<HistoryRecord> rs - = reader.findByKeyword(address, "callParticipantIDs"); + = historyReader.findByKeyword(address, "callParticipantIDs"); while (rs.hasNext()) { HistoryRecord hr = rs.next(); result.add(convertHistoryRecordToCallRecord(hr)); } - removeHistorySearchProgressListeners(reader); + removeHistorySearchProgressListeners(historyReader); } catch (IOException ex) { @@ -364,19 +382,21 @@ public class CallHistoryServiceImpl String propName = hr.getPropertyNames()[i]; String value = hr.getPropertyValues()[i]; - if(propName.equals(STRUCTURE_NAMES[0])) - result.setStartTime(new Date(Long.parseLong(value))); + if (propName.equals(STRUCTURE_NAMES[0])) + result.setProtocolProvider(getProtocolProvider(value)); else if(propName.equals(STRUCTURE_NAMES[1])) - result.setEndTime(new Date(Long.parseLong(value))); + result.setStartTime(new Date(Long.parseLong(value))); else if(propName.equals(STRUCTURE_NAMES[2])) - result.setDirection(value); + result.setEndTime(new Date(Long.parseLong(value))); else if(propName.equals(STRUCTURE_NAMES[3])) - callPeerIDs = getCSVs(value); + result.setDirection(value); else if(propName.equals(STRUCTURE_NAMES[4])) - callPeerStart = getCSVs(value); + callPeerIDs = getCSVs(value); else if(propName.equals(STRUCTURE_NAMES[5])) - callPeerEnd = getCSVs(value); + callPeerStart = getCSVs(value); else if(propName.equals(STRUCTURE_NAMES[6])) + callPeerEnd = getCSVs(value); + else if(propName.equals(STRUCTURE_NAMES[7])) callPeerStates = getStates(value); } @@ -557,10 +577,12 @@ public class CallHistoryServiceImpl * @param source Contact * @param destination Contact */ - private void writeCall(CallRecord callRecord, Contact source, - Contact destination) + private void writeCall( CallRecordImpl callRecord, + Contact source, + Contact destination) { - try { + try + { History history = this.getHistory(source, destination); HistoryWriter historyWriter = history.getWriter(); @@ -589,6 +611,8 @@ public class CallHistoryServiceImpl } historyWriter.addRecord(new String[] { + callRecord.getSourceCall().getProtocolProvider() + .getAccountID().getAccountUniqueID(), String.valueOf(callRecord.getStartTime().getTime()), String.valueOf(callRecord.getEndTime().getTime()), callRecord.getDirection(), @@ -596,8 +620,10 @@ public class CallHistoryServiceImpl callPeerStartTime.toString(), callPeerEndTime.toString(), callPeerStates.toString()}, - new Date()); // this date is when the history record is written - } catch (IOException e) + new Date()); // this date is when the history + // record is written + } + catch (IOException e) { logger.error("Could not add call to history", e); } @@ -646,9 +672,11 @@ public class CallHistoryServiceImpl */ public void serviceChanged(ServiceEvent serviceEvent) { - Object sService = bundleContext.getService(serviceEvent.getServiceReference()); + Object sService + = bundleContext.getService(serviceEvent.getServiceReference()); - logger.trace("Received a service event for: " + sService.getClass().getName()); + logger.trace("Received a service event for: " + + sService.getClass().getName()); // we don't care if the source service is not a protocol provider if (! (sService instanceof ProtocolProviderService)) @@ -667,13 +695,12 @@ public class CallHistoryServiceImpl { this.handleProviderRemoved( (ProtocolProviderService) sService); } - } /** * Used to attach the Call History Service to existing or - * just registered protocol provider. Checks if the provider has implementation - * of OperationSetBasicTelephony + * just registered protocol provider. Checks if the provider has + * implementation of OperationSetBasicTelephony * * @param provider ProtocolProviderService */ @@ -779,34 +806,6 @@ public class CallHistoryServiceImpl } /** - * Gets all the history readers for the contacts in the given MetaContact - * - * @param contact MetaContact - * @return Hashtable - */ - private Map<Contact, HistoryReader> getHistoryReaders(MetaContact contact) - { - Map<Contact, HistoryReader> readers = - new Hashtable<Contact, HistoryReader>(); - Iterator<Contact> iter = contact.getContacts(); - while (iter.hasNext()) - { - Contact item = iter.next(); - - try - { - History history = this.getHistory(null, item); - readers.put(item, history.getReader()); - } - catch (IOException e) - { - logger.error("Could not read history", e); - } - } - return readers; - } - - /** * CallListener implementation for incoming calls * @param event CallEvent */ @@ -830,17 +829,8 @@ public class CallHistoryServiceImpl */ public void callEnded(CallEvent event) { - CallRecordImpl callRecord = findCallRecord(event.getSourceCall()); - - // no such call - if (callRecord == null) - return; - - callRecord.setEndTime(new Date()); - - writeCall(callRecord, null, null); - - currentCallRecords.remove(callRecord); + // We store the call in the callStateChangeEvent where we + // have more information on the previous state of the call. } /** @@ -1027,9 +1017,11 @@ public class CallHistoryServiceImpl */ private int getProgressMapping(int historyProgress) { - currentProgress += (historyProgress - lastHistoryProgress)/contactCount; + currentProgress + += (historyProgress - lastHistoryProgress)/contactCount; - if(historyProgress == HistorySearchProgressListener.PROGRESS_MAXIMUM_VALUE) + if(historyProgress + == HistorySearchProgressListener.PROGRESS_MAXIMUM_VALUE) { currentContactCount++; lastHistoryProgress = 0; @@ -1074,18 +1066,93 @@ public class CallHistoryServiceImpl /** * Receive events for adding or removing peers from a call */ - private class HistoryCallChangeListener - extends CallChangeAdapter + private class HistoryCallChangeListener implements CallChangeListener { + /** + * Indicates that a new call peer has joined the source call. + * + * @param evt the <tt>CallPeerEvent</tt> containing the source call + * and call peer. + */ public void callPeerAdded(CallPeerEvent evt) { handlePeerAdded(evt.getSourceCallPeer()); } + /** + * Indicates that a call peer has left the source call. + * + * @param evt the <tt>CallPeerEvent</tt> containing the source call + * and call peer. + */ public void callPeerRemoved(CallPeerEvent evt) { handlePeerRemoved(evt.getSourceCallPeer(), evt.getSourceCall()); } + + /** + * A dummy implementation of this listener's callStateChanged() method. + * + * @param evt the <tt>CallChangeEvent</tt> instance containing the source + * calls and its old and new state. + */ + public void callStateChanged(CallChangeEvent evt) + { + CallRecordImpl callRecord = findCallRecord(evt.getSourceCall()); + + // no such call + if (callRecord == null) + return; + + if (evt.getNewValue().equals(CallState.CALL_ENDED) + && evt.getOldValue().equals(CallState.CALL_INITIALIZATION)) + { + callRecord.setEndTime(callRecord.getStartTime()); + } + else + callRecord.setEndTime(new Date()); + + writeCall(callRecord, null, null); + + currentCallRecords.remove(callRecord); + } + } + + /** + * Returns the <tt>ProtocolProviderService</tt> corresponding to the given + * account identifier. + * @param accountUID the identifier of the account. + * @return the <tt>ProtocolProviderService</tt> corresponding to the given + * account identifier + */ + private ProtocolProviderService getProtocolProvider(String accountUID) + { + for (ProtocolProviderFactory providerFactory + : CallHistoryActivator.getProtocolProviderFactories().values()) + { + ServiceReference serRef; + + for (AccountID accountID : providerFactory.getRegisteredAccounts()) + { + if (accountID.getAccountUniqueID().equals(accountUID)) + { + serRef = providerFactory.getProviderForAccount(accountID); + + return (ProtocolProviderService) CallHistoryActivator + .bundleContext.getService(serRef); + } + } + } + return null; + } + + /** + * Cancels the current find. If there's no find going on, then does nothing. + */ + public void cancelCurrentFind() + { + if (historyReader != null) + historyReader.cancelCurrentFind(); } } diff --git a/src/net/java/sip/communicator/impl/callhistory/CallHistorySourceContact.java b/src/net/java/sip/communicator/impl/callhistory/CallHistorySourceContact.java new file mode 100644 index 0000000..b9f7c8c --- /dev/null +++ b/src/net/java/sip/communicator/impl/callhistory/CallHistorySourceContact.java @@ -0,0 +1,266 @@ +/* + * 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.callhistory; + +import java.util.*; + +import net.java.sip.communicator.service.callhistory.*; +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +/** + * The <tt>CallHistorySourceContact</tt> is an implementation of the + * <tt>SourceContact</tt> interface based on a <tt>CallRecord</tt>. + * + * @author Yana Stamcheva + */ +public class CallHistorySourceContact implements SourceContact +{ + /** + * The parent <tt>CallHistoryContactSource</tt>, where this contact is + * contained. + */ + private final CallHistoryContactSource contactSource; + + /** + * The corresponding call record. + */ + private final CallRecord callRecord; + + /** + * The incoming call icon. + */ + private static final byte[] incomingIcon + = CallHistoryActivator.getResources() + .getImageInBytes("service.gui.icons.INCOMING_CALL"); + + /** + * The outgoing call icon. + */ + private static byte[] outgoingIcon + = CallHistoryActivator.getResources() + .getImageInBytes("service.gui.icons.OUTGOING_CALL"); + + /** + * The missed call icon. + */ + private static byte[] missedCallIcon + = CallHistoryActivator.getResources() + .getImageInBytes("service.gui.icons.MISSED_CALL"); + + /** + * A list of all contact details. + */ + private final List<ContactDetail> contactDetails + = new LinkedList<ContactDetail>(); + + /** + * The display name of this contact. + */ + private String displayName = ""; + + /** + * The display details of this contact. + */ + private final String displayDetails; + + /** + * Creates an instance of <tt>CallHistorySourceContact</tt> + * @param contactSource + * @param callRecord + */ + public CallHistorySourceContact(CallHistoryContactSource contactSource, + CallRecord callRecord) + { + this.contactSource = contactSource; + this.callRecord = callRecord; + + this.initPeerDetails(); + + this.displayDetails + = CallHistoryActivator.getResources() + .getI18NString("service.gui.AT") + ": " + + getDateString(callRecord.getStartTime().getTime()) + + " " + CallHistoryActivator.getResources() + .getI18NString("service.gui.DURATION") + ": " + + GuiUtils.formatTime( + GuiUtils.substractDates( + callRecord.getEndTime(), callRecord.getStartTime())); + } + + /** + * Initializes peer details. + */ + private void initPeerDetails() + { + Iterator<CallPeerRecord> recordsIter + = callRecord.getPeerRecords().iterator(); + + while (recordsIter.hasNext()) + { + String peerAddress = recordsIter.next().getPeerAddress(); + + if (displayName.length() > 0) + displayName += "," + peerAddress; + else + displayName += peerAddress; + + if (peerAddress != null) + { + ContactDetail contactDetail = new ContactDetail(peerAddress); + + Map<Class<? extends OperationSet>, ProtocolProviderService> + preferredProviders = null; + if (callRecord.getProtocolProvider() != null) + { + preferredProviders + = new Hashtable<Class<? extends OperationSet>, + ProtocolProviderService>(); + + preferredProviders.put( OperationSetBasicTelephony.class, + callRecord.getProtocolProvider()); + + contactDetail + .setPreferredProviders(preferredProviders); + } + + // Set supported operation sets. + LinkedList<Class<? extends OperationSet>> supportedOpSets + = new LinkedList<Class<? extends OperationSet>>(); + + supportedOpSets.add(OperationSetBasicTelephony.class); + contactDetail.setSupportedOpSets(supportedOpSets); + + contactDetails.add(contactDetail); + } + } + } + + /** + * Returns a list of available contact details. + * @return a list of available contact details + */ + public List<ContactDetail> getContactDetails() + { + return new LinkedList<ContactDetail>(contactDetails); + } + + /** + * Returns the parent <tt>ContactSourceService</tt> from which this contact + * came from. + * @return the parent <tt>ContactSourceService</tt> from which this contact + * came from + */ + public ContactSourceService getContactSource() + { + return contactSource; + } + + /** + * Returns the display details of this search contact. This could be any + * important information that should be shown to the user. + * + * @return the display details of the search contact + */ + public String getDisplayDetails() + { + return displayDetails; + } + + /** + * Returns the display name of this search contact. This is a user-friendly + * name that could be shown in the user interface. + * + * @return the display name of this search contact + */ + public String getDisplayName() + { + return displayName; + } + + /** + * An image (or avatar) corresponding to this search contact. If such is + * not available this method will return null. + * + * @return the byte array of the image or null if no image is available + */ + public byte[] getImage() + { + if (callRecord.getDirection().equals(CallRecord.IN)) + { + if (callRecord.getStartTime().equals(callRecord.getEndTime())) + return missedCallIcon; + else + return incomingIcon; + } + else if (callRecord.getDirection().equals(CallRecord.OUT)) + return outgoingIcon; + + return null; + } + + /** + * Returns a list of all <tt>ContactDetail</tt>s supporting the given + * <tt>OperationSet</tt> class. + * @param operationSet the <tt>OperationSet</tt> class we're looking for + * @return a list of all <tt>ContactDetail</tt>s supporting the given + * <tt>OperationSet</tt> class + */ + public List<ContactDetail> getContactDetails( + Class<? extends OperationSet> operationSet) + { + // We support only call details. + if (!operationSet.equals(OperationSetBasicTelephony.class)) + return null; + + return new LinkedList<ContactDetail>(contactDetails); + } + + /** + * Returns the preferred <tt>ContactDetail</tt> for a given + * <tt>OperationSet</tt> class. + * @param operationSet the <tt>OperationSet</tt> class, for which we would + * like to obtain a <tt>ContactDetail</tt> + * @return the preferred <tt>ContactDetail</tt> for a given + * <tt>OperationSet</tt> class + */ + public ContactDetail getPreferredContactDetail( + Class<? extends OperationSet> operationSet) + { + // We support only call details. + if (!operationSet.equals(OperationSetBasicTelephony.class)) + return null; + + return contactDetails.get(0); + } + + /** + * Returns the date string to show for the given date. + * + * @param date the date to format + * @return the date string to show for the given date + */ + public static String getDateString(long date) + { + String time = GuiUtils.formatTime(date); + + // If the current date we don't go in there and we'll return just the + // time. + if (GuiUtils.compareDatesOnly(date, System.currentTimeMillis()) < 0) + { + StringBuffer dateStrBuf = new StringBuffer(); + + GuiUtils.formatDate(date, dateStrBuf); + dateStrBuf.append(" "); + dateStrBuf.append(time); + return dateStrBuf.toString(); + } + + return time; + } +} diff --git a/src/net/java/sip/communicator/impl/callhistory/CallRecordImpl.java b/src/net/java/sip/communicator/impl/callhistory/CallRecordImpl.java index c52728a..bda0907 100644 --- a/src/net/java/sip/communicator/impl/callhistory/CallRecordImpl.java +++ b/src/net/java/sip/communicator/impl/callhistory/CallRecordImpl.java @@ -96,4 +96,13 @@ public class CallRecordImpl { this.direction = direction; } + + /** + * Sets the given <tt>ProtocolProviderService</tt> used for the call. + * @param pps the <tt>ProtocolProviderService</tt> to set + */ + public void setProtocolProvider(ProtocolProviderService pps) + { + this.protocolProvider = pps; + } } diff --git a/src/net/java/sip/communicator/impl/callhistory/callhistory.manifest.mf b/src/net/java/sip/communicator/impl/callhistory/callhistory.manifest.mf index b8db32f..44a6baf 100644 --- a/src/net/java/sip/communicator/impl/callhistory/callhistory.manifest.mf +++ b/src/net/java/sip/communicator/impl/callhistory/callhistory.manifest.mf @@ -14,6 +14,9 @@ Import-Package: org.osgi.framework, net.java.sip.communicator.service.protocol, net.java.sip.communicator.service.protocol.icqconstants, net.java.sip.communicator.service.protocol.event, + net.java.sip.communicator.service.contactsource, + net.java.sip.communicator.service.resources Export-Package: net.java.sip.communicator.service.callhistory, net.java.sip.communicator.service.callhistory.event -Metadata-Location: /net/java/sip/communicator/impl/msghistory/callhistory.metadata.xml +Metadata-Location: net.java.sip.communicator.impl.msghistory, + callhistory.metadata.xml diff --git a/src/net/java/sip/communicator/impl/contactlist/MetaContactGroupImpl.java b/src/net/java/sip/communicator/impl/contactlist/MetaContactGroupImpl.java index 4d6a449..924aac9 100644 --- a/src/net/java/sip/communicator/impl/contactlist/MetaContactGroupImpl.java +++ b/src/net/java/sip/communicator/impl/contactlist/MetaContactGroupImpl.java @@ -68,6 +68,22 @@ public class MetaContactGroupImpl private final MetaContactListServiceImpl mclServiceImpl; /** + * The user-specific key-value associations stored in this instance. + * <p> + * Like the Widget implementation of Eclipse SWT, the storage type takes + * into account that there are likely to be many + * <code>MetaContactGroupImpl</code> instances and <code>Map</code>s are + * thus likely to impose increased memory use. While an array may very well + * perform worse than a <code>Map</code> with respect to search, the + * mechanism of user-defined key-value associations explicitly states that + * it is not guaranteed to be optimized for any particular use and only + * covers the most basic cases and performance-savvy code will likely + * implement a more optimized solution anyway. + * </p> + */ + private Object[] data; + + /** * Creates an instance of the root meta contact group. * * @param mclServiceImpl @@ -85,6 +101,8 @@ public class MetaContactGroupImpl * specified meta contact uid. This constructor MUST NOT be used for nothing * purposes else but restoring contacts extracted from the contactlist.xml * + * @param mclServiceImpl the implementation of the + * <tt>MetaContactListService</tt>, to which this group belongs * @param groupName the name of the group to create * @param metaUID a UID that has been stored earlier or null when a new * UID needs to be created. @@ -932,8 +950,98 @@ public class MetaContactGroupImpl return subgroups.remove(group); } + /** + * Returns the implementation of the <tt>MetaContactListService</tt>, to + * which this group belongs. + * @return the implementation of the <tt>MetaContactListService</tt> + */ final MetaContactListServiceImpl getMclServiceImpl() { return mclServiceImpl; } + + /** + * Implements {@link MetaContactGroup#getData(Object)}. + * @return the data value corresponding to the given key + */ + public Object getData(Object key) + { + if (key == null) + throw new NullPointerException("key"); + + int index = dataIndexOf(key); + + return (index == -1) ? null : data[index + 1]; + } + + /** + * Implements {@link MetaContactGroup#setData(Object, Object)}. + * @param key the of the data + * @param value the value of the data + */ + public void setData(Object key, Object value) + { + if (key == null) + throw new NullPointerException("key"); + + int index = dataIndexOf(key); + + if (index == -1) + { + + /* + * If value is null, remove the association with key (or just don't + * add it). + */ + if (data == null) + if (value != null) + data = new Object[] { key, value }; + else if (value == null) + { + int length = data.length - 2; + + if (length > 0) + { + Object[] newData = new Object[length]; + + System.arraycopy(data, 0, newData, 0, index); + System.arraycopy( + data, index + 2, newData, index, length - index); + data = newData; + } + else + data = null; + } + else + { + int length = data.length; + Object[] newData = new Object[length + 2]; + + System.arraycopy(data, 0, newData, 0, length); + data[length++] = key; + data[length++] = value; + data = newData; + } + } + else + data[index + 1] = value; + } + + /** + * Determines the index in <code>#data</code> of a specific key. + * + * @param key + * the key to retrieve the index in <code>#data</code> of + * @return the index in <code>#data</code> of the specified <code>key</code> + * if it is contained; <tt>-1</tt> if <code>key</code> is not + * contained in <code>#data</code> + */ + private int dataIndexOf(Object key) + { + if (data != null) + for (int index = 0; index < data.length; index += 2) + if (key.equals(data[index])) + return index; + return -1; + } } diff --git a/src/net/java/sip/communicator/impl/gui/GuiActivator.java b/src/net/java/sip/communicator/impl/gui/GuiActivator.java index 782465d..0e65b4e 100644 --- a/src/net/java/sip/communicator/impl/gui/GuiActivator.java +++ b/src/net/java/sip/communicator/impl/gui/GuiActivator.java @@ -16,6 +16,7 @@ import net.java.sip.communicator.service.browserlauncher.*; import net.java.sip.communicator.service.callhistory.*; import net.java.sip.communicator.service.configuration.*; import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.contactsource.*; import net.java.sip.communicator.service.desktop.*; import net.java.sip.communicator.service.fileaccess.*; import net.java.sip.communicator.service.gui.*; @@ -71,6 +72,8 @@ public class GuiActivator implements BundleActivator private static MediaService mediaService; + private static List<ContactSourceService> contactSources; + private static final Map<Object, ProtocolProviderFactory> providerFactoriesMap = new Hashtable<Object, ProtocolProviderFactory>(); @@ -549,6 +552,44 @@ public class GuiActivator implements BundleActivator } /** + * Returns a list of all registered contact sources. + * @return a list of all registered contact sources + */ + public static List<ContactSourceService> getContactSources() + { + if (contactSources != null) + return contactSources; + + contactSources = new Vector<ContactSourceService>(); + + ServiceReference[] serRefs = null; + try + { + // get all registered provider factories + serRefs = + bundleContext.getServiceReferences( + ContactSourceService.class.getName(), null); + } + catch (InvalidSyntaxException e) + { + logger.error("GuiActivator : " + e); + } + + if (serRefs != null) + { + for (int i = 0; i < serRefs.length; i++) + { + ContactSourceService contactSource + = (ContactSourceService) bundleContext + .getService(serRefs[i]); + + contactSources.add(contactSource); + } + } + return contactSources; + } + + /** * Sets the <tt>contactList</tt> component currently used to show the * contact list. * @param list the contact list object to set diff --git a/src/net/java/sip/communicator/impl/gui/main/MainFrame.java b/src/net/java/sip/communicator/impl/gui/main/MainFrame.java index 6cca385..6c5815b 100755 --- a/src/net/java/sip/communicator/impl/gui/main/MainFrame.java +++ b/src/net/java/sip/communicator/impl/gui/main/MainFrame.java @@ -39,7 +39,7 @@ import net.java.sip.communicator.util.swing.*; import org.osgi.framework.*; /** - * The main application window. This class is the core of this ui + * The main application window. This class is the core of this UI * implementation. It stores all available protocol providers and their * operation sets, as well as all registered accounts, the * <tt>MetaContactListService</tt> and all sent messages that aren't @@ -166,10 +166,20 @@ public class MainFrame */ this.addWindowListener(new WindowAdapter() { + /** + * Invoked when a window has been closed. + */ public void windowClosed(WindowEvent event) { MainFrame.this.windowClosed(event); } + /** + * Invoked when a window has been opened. + */ + public void windowOpened(WindowEvent e) + { + GuiActivator.getContactList().requestFocusInWindow(); + } }); this.initTitleFont(); @@ -223,9 +233,11 @@ public class MainFrame northPanel.add(accountStatusPanel, BorderLayout.CENTER); - TransparentPanel searchPanel = new TransparentPanel(new BorderLayout()); + TransparentPanel searchPanel + = new TransparentPanel(new BorderLayout(2, 0)); searchPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5)); searchPanel.add(searchField); + searchPanel.add(new CallHistoryButton(), BorderLayout.EAST); centerPanel.add(searchPanel, BorderLayout.NORTH); centerPanel.add(contactListPanel, BorderLayout.CENTER); @@ -359,7 +371,7 @@ public class MainFrame presence.addProviderPresenceStatusListener( new GUIProviderPresenceStatusListener()); presence.addContactPresenceStatusListener( - contactListPanel.getContactList()); + TreeContactList.presenceFilter); } // Obtain the basic instant messaging operation set. diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallHistoryButton.java b/src/net/java/sip/communicator/impl/gui/main/call/CallHistoryButton.java new file mode 100644 index 0000000..909dec8 --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallHistoryButton.java @@ -0,0 +1,177 @@ +/* + * 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.*; +import java.awt.event.*; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.main.contactlist.*; +import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.util.swing.*; + +/** + * The <tt>CallHistoryButton</tt> is the button shown on the top of the contact + * list. + * @author Yana Stamcheva + */ +public class CallHistoryButton + extends SIPCommTextButton + implements MissedCallsListener +{ + /** + * The history icon. + */ + private final Image historyImage + = ImageLoader.getImage(ImageLoader.CALL_HISTORY_BUTTON); + + /** + * The history pressed icon. + */ + private final Image pressedImage + = ImageLoader.getImage(ImageLoader.CALL_HISTORY_BUTTON_PRESSED); + + /** + * Indicates if the history is visible. + */ + private boolean isHistoryVisible = false; + + /** + * Indicates if this button currently shows the number of missed calls or + * the just the history icon. + */ + private boolean isMissedCallView = false; + + /** + * The tool tip shown when there are missed calls. + */ + private final static String missedCallsToolTip + = GuiActivator.getResources().getI18NString( + "service.gui.MISSED_CALLS_TOOL_TIP"); + + /** + * The tool tip shown by default over the history button. + */ + private final static String callHistoryToolTip + = GuiActivator.getResources().getI18NString( + "service.gui.CALL_HISTORY_TOOL_TIP"); + + /** + * The tool tip shown when we're in history view. + */ + private final static String showContactListToolTip + = GuiActivator.getResources().getI18NString( + "service.gui.SHOW_CONTACT_LIST_TOOL_TIP"); + + /** + * Creates a <tt>CallHistoryButton</tt>. + */ + public CallHistoryButton() + { + super(""); + + this.setBgImage(historyImage); + + CallManager.setMissedCallsListener(this); + + this.setPreferredSize(new Dimension(29, 22)); + this.setForeground(Color.WHITE); + this.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + this.setFont(getFont().deriveFont(Font.BOLD, 10f)); + this.setToolTipText(callHistoryToolTip); + this.setBackground(new Color(255, 255, 255, 160)); + + this.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + if (isHistoryVisible && !isMissedCallView) + { + TreeContactList.searchFilter + .setSearchSourceType(SearchFilter.DEFAULT_SOURCE); + GuiActivator.getContactList() + .setDefaultFilter(TreeContactList.presenceFilter); + GuiActivator.getContactList().applyFilter( + TreeContactList.presenceFilter); + + isHistoryVisible = false; + } + else + { + TreeContactList.searchFilter + .setSearchSourceType(SearchFilter.HISTORY_SOURCE); + GuiActivator.getContactList() + .setDefaultFilter(TreeContactList.historyFilter); + GuiActivator.getContactList() + .applyFilter(TreeContactList.historyFilter); + + CallManager.clearMissedCalls(); + + isHistoryVisible = true; + } + setHistoryView(); + + GuiActivator.getContactList().requestFocusInWindow(); + repaint(); + } + }); + } + + /** + * Indicates that missed calls count has changed. + * @param newCallCount the new call count + */ + public void missedCallCountChanged(int newCallCount) + { + if (newCallCount > 0) + { + setMissedCallsView(newCallCount); + } + else if (newCallCount <= 0) + { + setHistoryView(); + } + + this.revalidate(); + this.repaint(); + } + + /** + * Sets the history view. + */ + private void setHistoryView() + { + isMissedCallView = false; + + if (isHistoryVisible) + { + setBgImage(pressedImage); + setToolTipText(showContactListToolTip); + } + else + { + setBgImage(historyImage); + setToolTipText(callHistoryToolTip); + } + setText(""); + } + + /** + * Sets the missed calls view of this button. + * @param callCount the missed calls count + */ + private void setMissedCallsView(int callCount) + { + isMissedCallView = true; + this.setBgImage(null); + this.setToolTipText(missedCallsToolTip); + this.setBackground(new Color(200, 0, 0)); + this.setText(new Integer(callCount).toString()); + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java b/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java index 7d242d6..8b5b117 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java @@ -40,6 +40,17 @@ public class CallManager = new Hashtable<Call, CallDialog>(); /** + * Indicates the number of missed calls that the user should be notified + * about. + */ + private static int missedCalls = 0; + + /** + * Listener notified for changes in missed calls count. + */ + private static MissedCallsListener missedCallsListener; + + /** * A call listener. */ public static class GuiCallListener implements CallListener @@ -214,7 +225,7 @@ public class CallManager ProtocolProviderService telProvider = null; int status = 0; - Vector<ProtocolProviderService> telProviders = getTelephonyProviders(); + List<ProtocolProviderService> telProviders = getTelephonyProviders(); for (ProtocolProviderService provider : telProviders) { @@ -398,10 +409,10 @@ public class CallManager * Returns a list of all currently registered telephony providers. * @return a list of all currently registered telephony providers */ - public static Vector<ProtocolProviderService> getTelephonyProviders() + public static List<ProtocolProviderService> getTelephonyProviders() { - Vector<ProtocolProviderService> telephonyProviders - = new Vector<ProtocolProviderService>(); + List<ProtocolProviderService> telephonyProviders + = new LinkedList<ProtocolProviderService>(); for (ProtocolProviderFactory providerFactory : GuiActivator .getProtocolProviderFactories().values()) @@ -429,6 +440,44 @@ public class CallManager } /** + * Sets the given <tt>MissedCallsListener</tt> that would be notified on + * any changes in missed calls count. + * @param l the listener to set + */ + public static void setMissedCallsListener(MissedCallsListener l) + { + missedCallsListener = l; + } + + /** + * Adds a missed call. + */ + public static void addMissedCall() + { + missedCalls ++; + fireMissedCallCountChangeEvent(missedCalls); + } + + /** + * Clears the count of missed calls. Sets it to 0. + */ + public static void clearMissedCalls() + { + missedCalls = 0; + } + + /** + * Notifies interested <tt>MissedCallListener</tt> that the count has + * changed. + * @param missedCallsCount the new missed calls count + */ + private static void fireMissedCallCountChangeEvent(int missedCallsCount) + { + if (missedCallsListener != null) + missedCallsListener.missedCallCountChanged(missedCallsCount); + } + + /** * Creates a call from a given Contact or a given String. */ private static class CreateCallThread 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 index f5a3f5a..34b08b7 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallTransferHandler.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallTransferHandler.java @@ -16,13 +16,13 @@ import javax.swing.*; import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.customcontrols.*; import net.java.sip.communicator.impl.gui.main.contactlist.*; -import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.contactsource.*; 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 <tt>MetaContact</tt>s or + * A TransferHandler that we use to handle dropping of <tt>UIContact</tt>s or * simple string addresses to an existing <tt>Call</tt>. Dropping of a such data * in the <tt>CallDialog</tt> would result in the creation of a call conference. * @@ -31,9 +31,21 @@ import net.java.sip.communicator.util.swing.*; public class CallTransferHandler extends ExtendedTransferHandler { + /** + * The data flavor used when transferring <tt>UIContact</tt>s. + */ + protected static final DataFlavor uiContactDataFlavor + = new DataFlavor(UIContact.class, "UIContact"); + + /** + * The logger. + */ private static final Logger logger = Logger.getLogger(CallTransferHandler.class); + /** + * The call corresponding to the transfer. + */ private final Call call; /** @@ -63,7 +75,7 @@ public class CallTransferHandler for (int i = 0, n = flavor.length; i < n; i++) { if (flavor[i].equals(DataFlavor.stringFlavor) - || flavor[i].equals(metaContactDataFlavor)) + || flavor[i].equals(uiContactDataFlavor)) { if (comp instanceof JPanel) { @@ -89,13 +101,13 @@ public class CallTransferHandler */ public boolean importData(JComponent comp, Transferable t) { - if (t.isDataFlavorSupported(metaContactDataFlavor)) + if (t.isDataFlavorSupported(uiContactDataFlavor)) { Object o = null; try { - o = t.getTransferData(metaContactDataFlavor); + o = t.getTransferData(uiContactDataFlavor); } catch (UnsupportedFlavorException e) { @@ -110,16 +122,29 @@ public class CallTransferHandler if (o instanceof ContactNode) { - MetaContact metaContact = ((ContactNode) o).getMetaContact(); + UIContact uiContact + = ((ContactNode) o).getContactDescriptor(); + ProtocolProviderService callProvider = call.getProtocolProvider(); - Iterator<Contact> contacts - = metaContact.getContactsForProvider(callProvider); + Iterator<UIContactDetail> contactDetails + = uiContact.getContactDetailsForOperationSet( + OperationSetBasicTelephony.class).iterator(); String callee = null; - if (contacts.hasNext()) - callee = contacts.next().getAddress(); + while (contactDetails.hasNext()) + { + UIContactDetail detail = contactDetails.next(); + + ProtocolProviderService detailProvider + = detail.getPreferredProtocolProvider( + OperationSetBasicTelephony.class); + + if (detailProvider != null + && detailProvider.equals(callProvider)) + callee = detail.getAddress(); + } if (callee != null) { @@ -137,7 +162,7 @@ public class CallTransferHandler new String[]{ callProvider.getAccountID().getService(), callProvider.getAccountID().getUserID(), - metaContact.getDisplayName()})) + uiContact.getDisplayName()})) .showDialog(); } } diff --git a/src/net/java/sip/communicator/impl/gui/main/call/ChooseCallAccountPopupMenu.java b/src/net/java/sip/communicator/impl/gui/main/call/ChooseCallAccountPopupMenu.java index 7ae58b4..992b174 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/ChooseCallAccountPopupMenu.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/ChooseCallAccountPopupMenu.java @@ -15,6 +15,7 @@ import javax.swing.*; import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.main.chat.*; +import net.java.sip.communicator.impl.gui.main.contactlist.*; import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.service.protocol.*; @@ -28,6 +29,9 @@ import net.java.sip.communicator.service.protocol.*; public class ChooseCallAccountPopupMenu extends JPopupMenu { + /** + * The invoker component. + */ private final JComponent invoker; /** @@ -64,8 +68,8 @@ public class ChooseCallAccountPopupMenu for (Object o : telephonyObjects) { - if (o instanceof Contact) - this.addTelephonyContactItem((Contact) o); + if (o instanceof UIContactDetail) + this.addTelephonyContactItem((UIContactDetail) o); else if (o instanceof ChatTransport) this.addTelephonyChatTransportItem((ChatTransport) o); } @@ -116,7 +120,7 @@ public class ChooseCallAccountPopupMenu * telephony contact. * @param telephonyContact the telephony contact to add */ - private void addTelephonyContactItem(final Contact telephonyContact) + private void addTelephonyContactItem(final UIContactDetail telephonyContact) { final ContactMenuItem contactItem = new ContactMenuItem(telephonyContact); @@ -125,8 +129,10 @@ public class ChooseCallAccountPopupMenu { public void actionPerformed(ActionEvent e) { - CallManager.createCall( telephonyContact.getProtocolProvider(), - telephonyContact); + CallManager.createCall( + telephonyContact.getPreferredProtocolProvider( + OperationSetBasicTelephony.class), + telephonyContact.getAddress()); ChooseCallAccountPopupMenu.this.setVisible(false); } }); @@ -229,21 +235,26 @@ public class ChooseCallAccountPopupMenu */ private class ContactMenuItem extends JMenuItem { - private final Contact contact; + private final UIContactDetail contact; - public ContactMenuItem(Contact contact) + public ContactMenuItem(UIContactDetail contact) { this.contact = contact; this.setText(contact.getDisplayName()); - BufferedImage contactIcon - = Constants.getStatusIcon(contact.getPresenceStatus()); + BufferedImage contactIcon = null; + PresenceStatus status = contact.getPresenceStatus(); + + if (status != null) + contactIcon = Constants.getStatusIcon(status); + else + contactIcon = Constants.getStatusIcon(Constants.OFFLINE_STATUS); if (contactIcon != null) this.setIcon(new ImageIcon(contactIcon)); } - public Contact getContact() + public UIContactDetail getContact() { return contact; } diff --git a/src/net/java/sip/communicator/impl/gui/main/call/MissedCallsListener.java b/src/net/java/sip/communicator/impl/gui/main/call/MissedCallsListener.java new file mode 100644 index 0000000..1c42239 --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/call/MissedCallsListener.java @@ -0,0 +1,23 @@ +/* + * 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; + +/** + * The <tt>MissedCallsListener</tt> listens for changes in the missed calls + * count. It is notified each time when a missed calls is registered by the + * <tt>CallManager</tt>. + * + * @author Yana Stamcheva + */ +public interface MissedCallsListener +{ + /** + * Indicates the missed calls count has changed. + * @param newCallCount the new missed calls count + */ + public void missedCallCountChanged(int newCallCount); +} diff --git a/src/net/java/sip/communicator/impl/gui/main/call/ReceivedCallDialog.java b/src/net/java/sip/communicator/impl/gui/main/call/ReceivedCallDialog.java index d293ff8..333fe74 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/ReceivedCallDialog.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/ReceivedCallDialog.java @@ -230,6 +230,8 @@ public class ReceivedCallDialog if (sourceCall.equals(incomingCall)) { + CallManager.addMissedCall(); + this.dispose(); } } 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 c2fe947..c086590 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 @@ -17,6 +17,7 @@ import javax.swing.text.*; import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.customcontrols.*; import net.java.sip.communicator.impl.gui.main.contactlist.*; +import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*; import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.util.*; @@ -26,7 +27,7 @@ import net.java.sip.communicator.util.swing.*; * A TransferHandler that we use to handle copying, pasting and DnD operations * in our <tt>ChatPanel</tt>. The string handler is heavily inspired * by Sun's <tt>DefaultTransferHandler</tt> with the main difference being that - * we only accept pasting of plain text. We do this in order to avoid html + * we only accept pasting of plain text. We do this in order to avoid HTML * support problems that appear when pasting formatted text into our editable * area. * @@ -37,6 +38,12 @@ public class ChatTransferHandler extends ExtendedTransferHandler { /** + * The data flavor used when transferring <tt>UIContact</tt>s. + */ + protected static final DataFlavor uiContactDataFlavor + = new DataFlavor(UIContact.class, "UIContact"); + + /** * This class logger. */ private final static Logger logger @@ -74,7 +81,7 @@ public class ChatTransferHandler { for (int i = 0, n = flavor.length; i < n; i++) { - if (flavor[i].equals(metaContactDataFlavor)) + if (flavor[i].equals(uiContactDataFlavor)) { return true; } @@ -124,13 +131,13 @@ public class ChatTransferHandler logger.debug("Failed to drop files.", e); } } - else if (t.isDataFlavorSupported(metaContactDataFlavor)) + else if (t.isDataFlavorSupported(uiContactDataFlavor)) { Object o = null; try { - o = t.getTransferData(metaContactDataFlavor); + o = t.getTransferData(uiContactDataFlavor); } catch (UnsupportedFlavorException e) { @@ -143,14 +150,18 @@ public class ChatTransferHandler if (o instanceof ContactNode) { - MetaContact metaContact - = ((ContactNode) o).getMetaContact(); + UIContact uiContact + = ((ContactNode) o).getContactDescriptor(); + + // We only support drag&drop for MetaContacts for now. + if (!(uiContact instanceof MetaUIContact)) + return false; ChatTransport currentChatTransport = chatPanel.getChatSession().getCurrentChatTransport(); - Iterator<Contact> contacts = metaContact - .getContactsForProvider( + Iterator<Contact> contacts = ((MetaContact) uiContact + .getDescriptor()).getContactsForProvider( currentChatTransport.getProtocolProvider()); String contact = null; @@ -173,7 +184,7 @@ public class ChatTransferHandler "service.gui.ERROR"), GuiActivator.getResources().getI18NString( "service.gui.CONTACT_NOT_SUPPORTING_CHAT_CONF", - new String[]{metaContact.getDisplayName()})) + new String[]{uiContact.getDisplayName()})) .showDialog(); } } diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/CallHistoryFilter.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/CallHistoryFilter.java new file mode 100644 index 0000000..f5e85af --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/CallHistoryFilter.java @@ -0,0 +1,154 @@ +/* + * 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.contactlist; + +import java.util.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*; +import net.java.sip.communicator.service.contactsource.*; + +/** + * The <tt>CallHistoryFilter</tt> is a filter over the history contact sources. + * + * @author Yana Stamcheva + */ +public class CallHistoryFilter + implements ContactListFilter, + ContactQueryListener +{ + /** + * The <tt>ContactListTreeModel</tt>, where the results of this filter are + * stored + */ + private ContactListTreeModel resultTreeModel; + + /** + * The current <tt>ContactQuery</tt>. + */ + private ContactQuery currentQuery; + + /** + * Applies this filter and stores the result in the given <tt>treeModel</tt>. + * + * @param treeModel the <tt>ContactListTreeModel</tt>, where the results + * of this filter are stored + */ + public void applyFilter(ContactListTreeModel treeModel) + { + this.resultTreeModel = treeModel; + + Collection<ExternalContactSource> contactSources + = TreeContactList.getContactSources(); + + for (ExternalContactSource contactSource : contactSources) + { + ContactSourceService sourceService + = contactSource.getContactSourceService(); + + if (!sourceService.getIdentifier() + .equals(ContactSourceService.CALL_HISTORY)) + continue; + + // We're in a case of call history contact source. + currentQuery = sourceService.queryContactSource(""); + + // Add first available results. + this.addMatching( currentQuery.getQueryResults(), + contactSource); + + currentQuery.addContactQueryListener(this); + } + } + + public boolean isMatching(UIContact uiContact) + { + return false; + } + + public boolean isMatching(UIGroup uiGroup) + { + return false; + } + + /** + * Adds matching <tt>sourceContacts</tt> to the result tree model. + * @param sourceContacts the list of <tt>SourceContact</tt>s to add + * @param uiSource the <tt>ExternalContactSource</tt>, which contacts + * we're adding + */ + private void addMatching( List<SourceContact> sourceContacts, + ExternalContactSource uiSource) + { + Iterator<SourceContact> contactsIter = sourceContacts.iterator(); + + while (contactsIter.hasNext()) + { + addHistoryContact(contactsIter.next(), uiSource); + } + } + + /** + * Indicates that a contact has been received for a query. + * @param event the <tt>ContactReceivedEvent</tt> that notified us + */ + public void contactReceived(ContactReceivedEvent event) + { + synchronized (resultTreeModel) + { + ExternalContactSource sourceUI + = TreeContactList.getContactSource( + event.getQuerySource().getContactSource()); + + addHistoryContact(event.getContact(), sourceUI); + } + } + + /** + * Indicates that the query status has changed. + * @param event the <tt>ContactQueryStatusEvent</tt> that notified us + */ + public void queryStatusChanged(ContactQueryStatusEvent event) + { + int eventType = event.getEventType(); + + // Remove the current query when it's stopped for some reason. + // QUERY_COMPLETED, QUERY_COMPLETED, QUERY_ERROR + currentQuery = null; + + if (eventType == ContactQueryStatusEvent.QUERY_ERROR) + { + //TODO: Show the error to the user?? + } + + event.getQuerySource().removeContactQueryListener(this); + } + + /** + * Adds the given <tt>sourceContact</tt> to the contact list. + * @param sourceContact the <tt>SourceContact</tt> to add + * @param uiSource the UI adapter for the original contact source + */ + private void addHistoryContact( SourceContact sourceContact, + ExternalContactSource uiSource) + { + GuiActivator.getContactList() + .addContact(resultTreeModel, + uiSource.getUIContact(sourceContact), + false, + false); + } + + /** + * Stops this filter current queries. + */ + public void stopFilter() + { + if (currentQuery != null) + currentQuery.cancel(); + } +} 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 73ca46c..c713ad6 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 @@ -66,7 +66,7 @@ public class ContactList private GroupRightButtonMenu groupRightButtonMenu; - private ContactRightButtonMenu contactRightButtonMenu; + private MetaContactRightButtonMenu contactRightButtonMenu; /** * A list of all contacts that are currently "active". An "active" contact @@ -391,15 +391,16 @@ public class ContactList * <tt>ContactListListener</tt>s that a contact is selected. * * @param sourceContact the contact that this event is about - * @param protocolContact the protocol contact the this event is about * @param eventID the id indicating the exact type of the event to fire. + * @param clickCount */ - public void fireContactListEvent(MetaContact sourceContact, - Contact protocolContact, int eventID) + public void fireContactListEvent( MetaContact sourceContact, + int eventID, + int clickCount) { fireContactListEvent( contactListListeners, - new ContactListEvent(sourceContact, protocolContact, eventID)); + new ContactListEvent(sourceContact, eventID, clickCount)); } /** @@ -420,11 +421,8 @@ public class ContactList case ContactListEvent.CONTACT_CLICKED: listener.contactClicked(event); break; - case ContactListEvent.PROTOCOL_CONTACT_CLICKED: - listener.protocolContactClicked(event); - break; case ContactListEvent.GROUP_CLICKED: - listener.groupSelected(event); + listener.groupClicked(event); break; default: logger.error("Unknown event type " + event.getEventID()); @@ -542,10 +540,6 @@ public class ContactList { MetaContact contact = (MetaContact) selectedValue; - // get the component under the mouse - Component component = this.getHorizontalComponent(renderer, - translatedX); - // Right click and Ctrl+LeftClick on the contact label opens // Popup menu if ((e.getModifiers() & InputEvent.BUTTON3_MASK) != 0 @@ -630,40 +624,6 @@ public class ContactList } /** - * Runs the info window for the specified contact at the appropriate - * position. - */ - private class RunInfoWindow - implements Runnable - { - private final MetaContact contactItem; - - private final Point p; - - private RunInfoWindow(Point p, MetaContact contactItem) - { - - this.p = p; - this.contactItem = contactItem; - } - - public void run() - { - ContactInfoDialog contactInfoPanel - = new ContactInfoDialog(mainFrame, contactItem); - - SwingUtilities.convertPointToScreen(p, ContactList.this); - - // TODO: to calculate popup window position properly. - contactInfoPanel.setPopupLocation(p.x - 140, p.y - 15); - - contactInfoPanel.setVisible(true); - - contactInfoPanel.requestFocusInWindow(); - } - } - - /** * Takes care of keeping the contact list up to date. */ private class ContactListRefresh @@ -977,7 +937,7 @@ public class ContactList * * @return the right button menu for a contact */ - public ContactRightButtonMenu getContactRightButtonMenu() + public MetaContactRightButtonMenu getContactRightButtonMenu() { return contactRightButtonMenu; } diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListEvent.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListEvent.java index ac17ae8..986c520 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListEvent.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListEvent.java @@ -8,12 +8,14 @@ package net.java.sip.communicator.impl.gui.main.contactlist; import java.util.*; -import net.java.sip.communicator.service.contactlist.*; -import net.java.sip.communicator.service.protocol.*; - +/** + * The <tt>ContactListEvent</tt> is triggered when a contact or a group is + * clicked in the contact list. + * @author Yana Stamcheva + */ public class ContactListEvent - extends EventObject{ - + extends EventObject +{ private int eventID = -1; /** @@ -23,16 +25,10 @@ public class ContactListEvent public static final int CONTACT_CLICKED = 1; /** - * Indicates that the ContactListEvent instance was triggered by - * selecting a protocol contact in the contact list. - */ - public static final int PROTOCOL_CONTACT_CLICKED = 2; - - /** * Indicates that the ContactListEvent instance was triggered by selecting * a group in the contact list. */ - public static final int GROUP_CLICKED = 3; + public static final int GROUP_CLICKED = 2; /** * Indicated the number of click accompanying the event @@ -40,12 +36,6 @@ public class ContactListEvent private int clickCount; /** - * Specific <tt>Contact</tt> of this <tt>MetaContact</tt> involved in - * the event if any, null otherwise. - */ - private Contact sourceProtoContact; - - /** * Creates a new ContactListEvent according to the specified parameters. * @param source the MetaContact which was selected * @param eventID one of the XXX_SELECTED static fields indicating the @@ -56,27 +46,12 @@ public class ContactListEvent public ContactListEvent(Object source, int eventID, int clickCount) { super(source); - + this.eventID = eventID; this.clickCount = clickCount; } /** - * Creates a new ContactListEvent according to the specified parameters. - * @param source the MetaContact which was selected - * @param protocolContact the protocol specifique contact which was selected - * @param eventID one of the XXX_SELECTED static fields indicating the - * nature of the event. - */ - public ContactListEvent(Object source, - Contact protocolContact, int eventID) - { - super(source); - this.eventID = eventID; - this.sourceProtoContact = protocolContact; - } - - /** * Returns an event id specifying whether the type of this event * (CONTACT_SELECTED or PROTOCOL_CONTACT_SELECTED) * @return one of the XXX_SELECTED int fields of this class. @@ -87,36 +62,27 @@ public class ContactListEvent } /** - * Returns the MetaContact for which this event occured. - * @return the MetaContact for which this event occured + * Returns the <tt>UIContactDescriptor</tt> for which this event occured. + * @return the </tt>UIContactDescriptor</tt> for which this event occured */ - public MetaContact getSourceContact() + public UIContact getSourceContact() { - if(getSource() instanceof MetaContact) - return (MetaContact)getSource(); - + if(getSource() instanceof UIContact) + return (UIContact) getSource(); + return null; } /** - * Returns the MetaContactGroup for which this event occured. - * @return the MetaContactGroup for which this event occured + * Returns the <tt>UIGroupDescriptor</tt> for which this event occured. + * @return the <tt>UIGroupDescriptor</tt> for which this event occured */ - public MetaContactGroup getSourceGroup() + public UIGroup getSourceGroup() { - if(getSource() instanceof MetaContactGroup) - return (MetaContactGroup)getSource(); - - return null; - } + if(getSource() instanceof UIGroup) + return (UIGroup) getSource(); - /** - * Returns the protocol contact for which this event occured. - * @return the protocol contact for which this event occured - */ - public Contact getSourceProtoContact() - { - return sourceProtoContact; + return null; } /** diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListFilter.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListFilter.java index 6dfd34c..3cd9156 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListFilter.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListFilter.java @@ -6,33 +6,41 @@ */ package net.java.sip.communicator.impl.gui.main.contactlist; -import net.java.sip.communicator.service.contactlist.*; - /** * The <tt>ContactListFilter</tt> is an interface meant to be implemented by * modules interested in filtering the contact list. An implementation of this - * interface should be able answer if a <tt>MetaContact</tt> or a - * <tt>MetaContactGroup</tt> is matching the corresponding filter. + * interface should be able to answer if an <tt>UIContact</tt> or an + * <tt>UIGroup</tt> is matching the corresponding filter. * * @author Yana Stamcheva */ public interface ContactListFilter { /** - * Returns <tt>true</tt> if the given <tt>metaContact</tt> is matching this - * filter, otherwise returns <tt>false</tt> - * @param metaContact the <tt>MetaContact</tt> to check - * @return <tt>true</tt> if the given <tt>metaContact</tt> is matching this - * filter, otherwise returns <tt>false</tt> + * Indicates if the given <tt>uiGroup</tt> is matching the current filter. + * @param uiContact the <tt>UIContact</tt> to check + * @return <tt>true</tt> to indicate that the given <tt>uiContact</tt> + * matches this filter, <tt>false</tt> - otherwise + */ + public boolean isMatching(UIContact uiContact); + + /** + * Indicates if the given <tt>uiGroup</tt> is matching the current filter. + * @param uiGroup the <tt>UIGroup</tt> to check + * @return <tt>true</tt> to indicate that the given <tt>uiGroup</tt> + * matches this filter, <tt>false</tt> - otherwise + */ + public boolean isMatching(UIGroup uiGroup); + + /** + * Applies this filter to any interested sources and stores the result in + * the given <tt>treeModel</tt>. + * @param treeModel the <tt>ContactListTreeModel</tt> to store the result in */ - public boolean isMatching(MetaContact metaContact); + public void applyFilter(ContactListTreeModel treeModel); /** - * Returns <tt>true</tt> if the given <tt>metaGroup</tt> is matching this - * filter, otherwise returns <tt>false</tt> - * @param metaGroup the <tt>MetaContactGroup</tt> to check - * @return <tt>true</tt> if the given <tt>metaGroup</tt> is matching this - * filter, otherwise returns <tt>false</tt> + * Stops this filter current queries. */ - public boolean isMatching(MetaContactGroup metaGroup); + public void stopFilter(); } diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListListener.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListListener.java index 5706fcc..b936727 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListListener.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListListener.java @@ -9,28 +9,24 @@ package net.java.sip.communicator.impl.gui.main.contactlist; import java.util.*; /** - * Listens for events coming from the contact list. - * + * Listens for events coming from mouse events over the contact list. For + * example a contact been clicked or a group been selected. + * * @author Yana Stamcheva */ public interface ContactListListener extends EventListener { /** - * - * @param evt + * Indicates that a group has been selected. + * @param evt the <tt>ContactListEvent</tt> that has been triggered from + * the user selection */ - public void groupSelected(ContactListEvent evt); - + public void groupClicked(ContactListEvent evt); + /** - * - * @param evt + * Indicates that a contact has been clicked. + * @param evt the <tt>ContactListEvent</tt> that has been triggered from + * the user click */ public void contactClicked(ContactListEvent evt); - - /** - * - * @param evt - */ - public void protocolContactClicked(ContactListEvent evt); - } diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListNode.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListNode.java index 4798042..9452c15 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListNode.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListNode.java @@ -7,9 +7,9 @@ package net.java.sip.communicator.impl.gui.main.contactlist; /** - * The <tt>ContactListNode</tt> represents a node in the contact list. An - * implementation of this interface should be able to determine the index of - * this node in the <tt>MetaContactListService</tt>. + * The <tt>ContactListNode</tt> represents a node in the contact list data + * model. An implementation of this interface should be able to determine the + * index of this node in its contact source. * * @author Yana Stamcheva */ @@ -19,5 +19,5 @@ public interface ContactListNode * Returns the index of this node in the <tt>MetaContactListService</tt>. * @return the index of this node in the <tt>MetaContactListService</tt> */ - public int getMetaContactListIndex(); + public int getSourceIndex(); } diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListPane.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListPane.java index f79fb92..5b27c3e 100755 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListPane.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListPane.java @@ -92,9 +92,21 @@ public class ContactListPane public void initList(MetaContactListService contactListService) { this.contactList = new TreeContactList(); - + // We should first set the contact list to the GuiActivator, so that + // anybody could get it from there. GuiActivator.setContactList(contactList); + // By default we set the current filter to be the presence filter. + new Thread() + { + public void run() + { + TreeContactList.presenceFilter + .setShowOffline(ConfigurationManager.isShowOffline()); + contactList.applyFilter(TreeContactList.presenceFilter); + } + }.start(); + TransparentPanel transparentPanel = new TransparentPanel(new BorderLayout()); @@ -146,7 +158,13 @@ public class ContactListPane if (evt.getClickCount() < 2) return; - MetaContact metaContact = evt.getSourceContact(); + UIContact descriptor = evt.getSourceContact(); + + // We're currently only interested in MetaContacts. + if (!(descriptor.getDescriptor() instanceof MetaContact)) + return; + + MetaContact metaContact = (MetaContact) descriptor.getDescriptor(); // Searching for the right proto contact to use as default for the // chat conversation. @@ -200,24 +218,10 @@ public class ContactListPane * Implements the ContactListListener.groupSelected method. * @param evt the <tt>ContactListEvent</tt> that notified us */ - public void groupSelected(ContactListEvent evt) + public void groupClicked(ContactListEvent evt) {} /** - * Implements the ContactListListener.protocolContactSelected method. - * @param evt the <tt>ContactListEvent</tt> that notified us - */ - public void protocolContactClicked(ContactListEvent evt) - { - Contact protoContact = evt.getSourceProtoContact(); - - ContactEventHandler contactHandler = mainFrame - .getContactHandler(protoContact.getProtocolProvider()); - - contactHandler.contactClicked(protoContact, evt.getClickCount()); - } - - /** * When a message is received determines whether to open a new chat window * or chat window tab, or to indicate that a message is received from a * contact which already has an open chat. When the chat is found checks if @@ -660,26 +664,6 @@ public class ContactListPane } } - /** - * Opens chat window when the selected value is a MetaContact and opens a - * group when the selected value is a MetaContactGroup. - */ - private class ContactListPanelEnterAction extends AbstractAction - { - public void actionPerformed(ActionEvent e) - { - Object selectedValue - = contactList.getSelectionPath().getLastPathComponent(); - - if (selectedValue instanceof MetaContact) - { - MetaContact contact = (MetaContact) selectedValue; - - chatWindowManager.startChat(contact); - } - } - } - private void initPluginComponents() { // Search for plugin components registered through the OSGI bundle 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 82e56b2..1e6f799 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 @@ -15,6 +15,7 @@ import javax.swing.tree.*; import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.main.chat.*; +import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*; import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.swing.*; @@ -28,9 +29,21 @@ import net.java.sip.communicator.util.swing.*; public class ContactListTransferHandler extends ExtendedTransferHandler { + /** + * The data flavor used when transferring <tt>UIContact</tt>s. + */ + protected static final DataFlavor uiContactDataFlavor + = new DataFlavor(UIContact.class, "UIContact"); + + /** + * The logger. + */ private static final Logger logger = Logger.getLogger(ContactListTransferHandler.class); + /** + * The contact list, where this transfer happens. + */ private final DefaultTreeContactList contactList; /** @@ -81,7 +94,7 @@ public class ContactListTransferHandler { for (int i = 0, n = flavor.length; i < n; i++) { - if (flavor[i].equals(metaContactDataFlavor)) + if (flavor[i].equals(uiContactDataFlavor)) { return true; } @@ -140,13 +153,13 @@ public class ContactListTransferHandler } } } - else if (t.isDataFlavorSupported(metaContactDataFlavor)) + else if (t.isDataFlavorSupported(uiContactDataFlavor)) { Object o = null; try { - o = t.getTransferData(metaContactDataFlavor); + o = t.getTransferData(uiContactDataFlavor); } catch (UnsupportedFlavorException e) { @@ -162,8 +175,13 @@ public class ContactListTransferHandler if (o instanceof ContactNode && comp instanceof TreeContactList) { - MetaContact transferredContact - = ((ContactNode) o).getMetaContact(); + UIContact transferredContact + = ((ContactNode) o).getContactDescriptor(); + + // We support darg&drop for MetaContacts only. + if (!(transferredContact instanceof MetaUIContact)) + return false; + TreeContactList list = (TreeContactList) comp; Object dest @@ -173,25 +191,36 @@ public class ContactListTransferHandler { if (dest instanceof ContactNode) { - MetaContact destContact - = ((ContactNode) dest).getMetaContact(); + UIContact destContact + = ((ContactNode) dest).getContactDescriptor(); + + // We support darg&drop for MetaContacts only for now. + if (!(destContact instanceof MetaUIContact)) + return false; + if (transferredContact != destContact) { MetaContactListManager.moveMetaContactToMetaContact( - transferredContact, destContact); + (MetaContact) transferredContact.getDescriptor(), + (MetaContact) destContact.getDescriptor()); } return true; } else if (dest instanceof GroupNode) { - MetaContactGroup destGroup - = ((GroupNode) dest).getMetaContactGroup(); + UIGroup destGroup + = ((GroupNode) dest).getGroupDescriptor(); + + // We support darg&drop for MetaContacts only for now. + if (!(destGroup instanceof MetaUIGroup)) + return false; - if (transferredContact.getParentMetaContactGroup() - != destGroup) + if (!transferredContact + .getParentGroup().equals(destGroup)) { MetaContactListManager.moveMetaContactToGroup( - transferredContact, destGroup); + (MetaContact) transferredContact.getDescriptor(), + (MetaContactGroup) destGroup.getDescriptor()); } return true; } @@ -225,7 +254,7 @@ public class ContactListTransferHandler contactList.getCellRenderer() .getTreeCellRendererComponent( contactList, - transferable.getTransferData(metaContactDataFlavor), + transferable.getTransferData(uiContactDataFlavor), true, // is selected false, // is expanded true, // is leaf @@ -303,7 +332,7 @@ public class ContactListTransferHandler */ public DataFlavor[] getTransferDataFlavors() { - return new DataFlavor[] { metaContactDataFlavor, + return new DataFlavor[] { uiContactDataFlavor, DataFlavor.stringFlavor}; } @@ -316,7 +345,7 @@ public class ContactListTransferHandler */ public boolean isDataFlavorSupported(DataFlavor flavor) { - return metaContactDataFlavor.equals(flavor) + return uiContactDataFlavor.equals(flavor) || DataFlavor.stringFlavor.equals(flavor); } @@ -333,7 +362,7 @@ public class ContactListTransferHandler throws UnsupportedFlavorException, IOException { - if (metaContactDataFlavor.equals(flavor)) + if (uiContactDataFlavor.equals(flavor)) { return transferredObject; } @@ -341,7 +370,7 @@ public class ContactListTransferHandler { if (transferredObject instanceof ContactNode) return ((ContactNode) transferredObject) - .getMetaContact().getDisplayName(); + .getContactDescriptor().getDisplayName(); } else throw new UnsupportedFlavorException(flavor); diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeCellRenderer.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeCellRenderer.java index de4271a..978cdef 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeCellRenderer.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeCellRenderer.java @@ -10,7 +10,6 @@ import java.awt.*; import java.awt.event.*; import java.awt.image.*; -import java.util.*; import java.util.List; import javax.swing.*; @@ -72,13 +71,6 @@ public class ContactListTreeCellRenderer private static final int EXTENDED_AVATAR_WIDTH = 45; /** - * The key of the user data in <tt>MetaContact</tt> which specifies - * the avatar cached from previous invocations. - */ - private static final String AVATAR_DATA_KEY - = ContactListCellRenderer.class.getName() + ".avatar"; - - /** * The icon used for opened groups. */ private final ImageIcon openedGroupIcon = @@ -108,7 +100,7 @@ public class ContactListTreeCellRenderer /** * The status message label. */ - private final JLabel statusMessageLabel = new JLabel(); + private final JLabel displayDetailsLabel = new JLabel(); /** * The call button. @@ -148,7 +140,7 @@ public class ContactListTreeCellRenderer /** * The icon showing the contact status. */ - protected final ImageIcon statusIcon = new ImageIcon(); + protected ImageIcon statusIcon = new ImageIcon(); /** * Indicates if the current list cell is selected. @@ -194,8 +186,8 @@ public class ContactListTreeCellRenderer this.setOpaque(false); this.nameLabel.setOpaque(false); - this.statusMessageLabel.setFont(getFont().deriveFont(9f)); - this.statusMessageLabel.setForeground(Color.GRAY); + this.displayDetailsLabel.setFont(getFont().deriveFont(9f)); + this.displayDetailsLabel.setForeground(Color.GRAY); this.rightLabel.setFont(rightLabel.getFont().deriveFont(9f)); this.rightLabel.setHorizontalAlignment(JLabel.RIGHT); @@ -239,27 +231,56 @@ public class ContactListTreeCellRenderer { public void actionPerformed(ActionEvent e) { + ChooseCallAccountPopupMenu chooseAccountDialog = null; + if (treeNode != null && treeNode instanceof ContactNode) { - List<Contact> telephonyContacts - = ((ContactNode) treeNode).getMetaContact() - .getContactsForOperationSet( + List<UIContactDetail> telephonyContacts + = ((ContactNode) treeNode).getContactDescriptor() + .getContactDetailsForOperationSet( OperationSetBasicTelephony.class); if (telephonyContacts.size() == 1) { - Contact contact = telephonyContacts.get(0); - CallManager.createCall( - telephonyContacts.get(0).getProtocolProvider(), - contact); + UIContactDetail detail + = telephonyContacts.get(0); + + ProtocolProviderService preferredProvider + = detail.getPreferredProtocolProvider( + OperationSetBasicTelephony.class); + + if (preferredProvider != null) + CallManager.createCall( + preferredProvider, + detail.getAddress()); + else + { + List<ProtocolProviderService> providers + = CallManager.getTelephonyProviders(); + + int providersCount = providers.size(); + + if (providersCount == 1) + CallManager.createCall( + providers.get(0), + detail.getAddress()); + else if (providersCount > 1) + chooseAccountDialog + = new ChooseCallAccountPopupMenu( + tree, providers); + } } else if (telephonyContacts.size() > 1) { - ChooseCallAccountPopupMenu chooseAccountDialog + chooseAccountDialog = new ChooseCallAccountPopupMenu( tree, telephonyContacts); + } + // If the choose dialog is created we're going to show it. + if (chooseAccountDialog != null) + { Point location = new Point(callButton.getX(), callButton.getY() + callButton.getHeight()); @@ -281,8 +302,16 @@ public class ContactListTreeCellRenderer { if (treeNode != null && treeNode instanceof ContactNode) { - GuiActivator.getUIService().getChatWindowManager() - .startChat(((ContactNode) treeNode).getMetaContact()); + UIContact contactDescriptor + = ((ContactNode) treeNode).getContactDescriptor(); + + if (contactDescriptor.getDescriptor() + instanceof MetaContact) + { + GuiActivator.getUIService().getChatWindowManager() + .startChat( + (MetaContact) contactDescriptor.getDescriptor()); + } } } }); @@ -318,9 +347,10 @@ public class ContactListTreeCellRenderer if (value instanceof ContactNode) { - MetaContact metaContact = ((ContactNode) value).getMetaContact(); + UIContact contact + = ((ContactNode) value).getContactDescriptor(); - String displayName = metaContact.getDisplayName(); + String displayName = contact.getDisplayName(); if (displayName == null || displayName.length() < 1) { @@ -330,14 +360,13 @@ public class ContactListTreeCellRenderer this.nameLabel.setText(displayName); - if(contactList.isContactActive(metaContact)) + if(contactList.isContactActive(contact)) { statusIcon.setImage(msgReceivedImage); } else { - statusIcon.setImage(Constants.getStatusIcon( - contactList.getMetaContactStatus(metaContact))); + statusIcon = contact.getStatusIcon(); } this.statusLabel.setIcon(statusIcon); @@ -348,30 +377,35 @@ public class ContactListTreeCellRenderer // Initializes status message components if the given meta contact // contains a status message. - this.initStatusMessage(metaContact); + this.initDisplayDetails(contact); - this.initButtonsPanel(metaContact); + this.initButtonsPanel(contact); + + ImageIcon avatar = isSelected + ? contact.getAvatar( + isSelected, EXTENDED_AVATAR_WIDTH, EXTENDED_AVATAR_HEIGHT) + : contact.getAvatar( + isSelected, AVATAR_WIDTH, AVATAR_HEIGHT); - ImageIcon avatar = getAvatar(metaContact); if (avatar != null) this.rightLabel.setIcon(avatar); this.rightLabel.setText(""); - this.setToolTipText(metaContact.getMetaUID()); + this.setToolTipText(contact.getDescriptor().toString()); } else if (value instanceof GroupNode) { - MetaContactGroup groupItem - = ((GroupNode) value).getMetaContactGroup(); + UIGroup groupItem + = ((GroupNode) value).getGroupDescriptor(); - this.nameLabel.setText(groupItem.getGroupName()); + this.nameLabel.setText(groupItem.getDisplayName()); this.nameLabel.setFont(this.getFont().deriveFont(Font.BOLD)); if (groupForegroundColor != null) this.nameLabel.setForeground(groupForegroundColor); - this.remove(statusMessageLabel); + this.remove(displayDetailsLabel); this.remove(callButton); this.remove(chatButton); @@ -382,74 +416,18 @@ public class ContactListTreeCellRenderer // We have no photo icon for groups. this.rightLabel.setIcon(null); - this.rightLabel.setText( groupItem.countOnlineChildContacts() - + "/" + groupItem.countChildContacts()); + + if (groupItem.countChildContacts() >= 0) + this.rightLabel.setText( groupItem.countOnlineChildContacts() + + "/" + groupItem.countChildContacts()); - this.setToolTipText(groupItem.getMetaUID()); + this.setToolTipText(groupItem.getDescriptor().toString()); } return this; } /** - * Gets the avatar of a specific <tt>MetaContact</tt> in the form of an - * <tt>ImageIcon</tt> value. - * - * @param metaContact the <tt>MetaContact</tt> to retrieve the avatar of - * @return an <tt>ImageIcon</tt> which represents the avatar of the - * specified <tt>MetaContact</tt> - */ - private ImageIcon getAvatar(MetaContact metaContact) - { - byte[] avatarBytes = metaContact.getAvatar(true); - ImageIcon avatar = null; - - // If there'rs no avatar we have nothing more to do here. - if((avatarBytes == null) || (avatarBytes.length <= 0)) - return null; - - // If the cell is selected we return a zoomed version of the avatar - // image. - if (isSelected) - return ImageUtils.getScaledRoundedIcon( - avatarBytes, - EXTENDED_AVATAR_WIDTH, - EXTENDED_AVATAR_HEIGHT); - - // In any other case try to get the avatar from the cache. - Object[] avatarCache = (Object[]) metaContact.getData(AVATAR_DATA_KEY); - if ((avatarCache != null) && (avatarCache[0] == avatarBytes)) - avatar = (ImageIcon) avatarCache[1]; - - // Just - int avatarWidth = AVATAR_WIDTH; - int avatarHeight = AVATAR_HEIGHT; - - // If the avatar isn't available or it's not up-to-date, create it. - if (avatar == null) - avatar = ImageUtils.getScaledRoundedIcon( - avatarBytes, - avatarWidth, - avatarHeight); - - // Cache the avatar in case it has changed. - if (avatarCache == null) - { - if (avatar != null) - metaContact.setData( - AVATAR_DATA_KEY, - new Object[] { avatarBytes, avatar }); - } - else - { - avatarCache[0] = avatarBytes; - avatarCache[1] = avatar; - } - - return avatar; - } - - /** * Paints a customized background. * * @param g the <tt>Graphics</tt> object through which we paint @@ -571,35 +549,25 @@ public class ContactListTreeCellRenderer } /** - * Returns the first found status message for the given - * <tt>metaContact</tt>. - * @param metaContact the <tt>MetaContact</tt>, for which we'd like to - * obtain a status message + * Initializes the display details component for the given + * <tt>UIContact</tt>. + * @param contact the <tt>UIContact</tt>, for which we initialize the + * details component */ - private void initStatusMessage(MetaContact metaContact) + private void initDisplayDetails(UIContact contact) { - statusMessageLabel.setText(""); - this.remove(statusMessageLabel); + displayDetailsLabel.setText(""); + this.remove(displayDetailsLabel); - String statusMessage = null; - Iterator<Contact> protoContacts = metaContact.getContacts(); - - while (protoContacts.hasNext()) - { - Contact protoContact = protoContacts.next(); - - statusMessage = protoContact.getStatusMessage(); - if (statusMessage != null && statusMessage.length() > 0) - break; - } + String displayDetails = contact.getDisplayDetails(); - if (statusMessage != null && statusMessage.length() > 0) + if (displayDetails != null && displayDetails.length() > 0) { // Replace all occurrences of new line with slash. - statusMessage = Html2Text.extractText(statusMessage); - statusMessage = statusMessage.replaceAll("\n|<br>|<br/>", " / "); + displayDetails = Html2Text.extractText(displayDetails); + displayDetails = displayDetails.replaceAll("\n|<br>|<br/>", " / "); - statusMessageLabel.setText(statusMessage); + displayDetailsLabel.setText(displayDetails); constraints.anchor = GridBagConstraints.WEST; constraints.fill = GridBagConstraints.NONE; @@ -610,16 +578,16 @@ public class ContactListTreeCellRenderer constraints.gridwidth = 2; constraints.gridheight = 1; - this.add(statusMessageLabel, constraints); + this.add(displayDetailsLabel, constraints); } } /** * Initializes buttons panel. - * @param metaContact the <tt>MetaContact</tt> for which we initialize the + * @param uiContact the <tt>UIContact</tt> for which we initialize the * button panel */ - private void initButtonsPanel(MetaContact metaContact) + private void initButtonsPanel(UIContact uiContact) { this.remove(callButton); this.remove(chatButton); @@ -628,13 +596,13 @@ public class ContactListTreeCellRenderer return; int statusMessageLabelHeight = 0; - if (statusMessageLabel.getText().length() > 0) + if (displayDetailsLabel.getText().length() > 0) statusMessageLabelHeight = 20; else statusMessageLabelHeight = 15; - Contact imContact = metaContact.getDefaultContact( - OperationSetBasicInstantMessaging.class); + UIContactDetail imContact = uiContact.getDefaultContactDetail( + OperationSetBasicInstantMessaging.class); if (imContact != null) { @@ -656,8 +624,8 @@ public class ContactListTreeCellRenderer 28, 28); } - Contact telephonyContact - = metaContact.getDefaultContact(OperationSetBasicTelephony.class); + UIContactDetail telephonyContact + = uiContact.getDefaultContactDetail(OperationSetBasicTelephony.class); if (telephonyContact != null) { diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeModel.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeModel.java index fbabca2..b1069a2 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeModel.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeModel.java @@ -6,10 +6,9 @@ */ package net.java.sip.communicator.impl.gui.main.contactlist; +import javax.swing.*; import javax.swing.tree.*; -import net.java.sip.communicator.service.contactlist.*; - /** * The data model of the contact list. * @@ -18,91 +17,78 @@ import net.java.sip.communicator.service.contactlist.*; public class ContactListTreeModel extends DefaultTreeModel { - private GroupNode rootGroupNode; + /** + * The root node. + */ + private final GroupNode rootGroupNode; /** - * Creates the <tt>ContactListTreeModel</tt> by specifying the - * <tt>rootMetaGroup</tt>, which would correspond to the root of our data - * model. - * @param rootMetaGroup the root <tt>MetaContactGroup</tt>, which would - * correspond to our root node + * Creates an instance of <tt>ContactListTreeModel</tt>. */ - public ContactListTreeModel(MetaContactGroup rootMetaGroup) + public ContactListTreeModel() { super(null); - rootGroupNode = new GroupNode(this, rootMetaGroup); + RootUIGroup rootDescriptor = new RootUIGroup(); + rootGroupNode = new GroupNode(this, rootDescriptor); + rootDescriptor.setGroupNode(rootGroupNode); this.setRoot(rootGroupNode); } /** - * Creates the <tt>ContactListTreeModel</tt> by specifying the - * root node. - * @param root the root node - */ - public ContactListTreeModel(TreeNode root) - { - super(root); - } - - /** * Returns the root group node. * @return the root group node */ - @Override public GroupNode getRoot() { return rootGroupNode; } /** - * Returns the <tt>GroupNode</tt> corresponding to the given - * <tt>metaGroup</tt>. This method will look in deep. - * @param metaGroup the <tt>MetaContactGroup</tt>, which corresponding node - * we're looking for - * @return the <tt>GroupNode</tt> corresponding to the given - * <tt>metaGroup</tt> + * Returns the first found child <tt>ContactNode</tt>. + * @return the first found child <tt>ContactNode</tt> or <tt>null</tt> + * if there is no ContactNode. */ - public GroupNode findGroupNodeByMetaGroup(MetaContactGroup metaGroup) + public ContactNode findFirstContactNode() { - if (metaGroup.equals(rootGroupNode.getMetaContactGroup())) - return rootGroupNode; - else - return rootGroupNode.findGroupNode(metaGroup); + return findFirstContactNode(rootGroupNode); } /** - * Returns the <tt>ContactNode</tt> corresponding to the given - * <tt>metaContact</tt>. This method will look in deep. - * @param metaContact the <tt>MetaContact</tt>, which corresponding node - * we're looking for - * @return the <tt>ContactNode</tt> corresponding to the given - * <tt>metaContact</tt> + * Clears all dependencies in the abstraction path (i.e. GroupNode - UIGroup + * - MetaContactGroup or ContactNode - UIContact - SourceContact). */ - public ContactNode findContactNodeByMetaContact(MetaContact metaContact) + public void clearDependencies() { - MetaContactGroup parentGroup = metaContact.getParentMetaContactGroup(); - - if (parentGroup == null) - return null; - - GroupNode parentGroupNode = findGroupNodeByMetaGroup(parentGroup); - - if (parentGroupNode != null) - return parentGroupNode.findContactNode(metaContact); - - return null; + clearDependencies(rootGroupNode); } /** - * Returns the first found child <tt>ContactNode</tt>. - * @return the first found child <tt>ContactNode</tt> or <tt>null</tt> - * if there is no ContactNode. + * Clears all dependencies for all children in the given <tt>groupNode</tt> + * (i.e. GroupNode - UIGroup - MetaContactGroup or ContactNode - UIContact + * - SourceContact). + * @param groupNode the <tt>TreeNode</tt> in which we clear dependencies */ - public ContactNode findFirstContactNode() + private void clearDependencies(TreeNode groupNode) { - return findFirstContactNode(rootGroupNode); + for (int i = 0; i < groupNode.getChildCount(); i ++) + { + TreeNode treeNode = groupNode.getChildAt(i); + + if (treeNode instanceof ContactNode) + { + ((ContactNode) treeNode).getContactDescriptor() + .setContactNode(null); + } + else if (treeNode instanceof GroupNode) + { + ((GroupNode) treeNode).getGroupDescriptor() + .setGroupNode(null); + + clearDependencies(treeNode); + } + } } /** @@ -113,14 +99,128 @@ public class ContactListTreeModel private ContactNode findFirstContactNode(GroupNode parentNode) { // If the parent node has no children we have nothing to do here. - if (parentNode.getChildCount() ==0) + if (parentNode.getChildCount() == 0) return null; TreeNode treeNode = parentNode.getFirstChild(); if (treeNode instanceof GroupNode) - return findFirstContactNode((GroupNode)treeNode); + return findFirstContactNode((GroupNode) treeNode); else return (ContactNode)treeNode; } + + /** + * The <tt>RootUIGroup</tt> is the root group in this contact list model. + */ + private class RootUIGroup + implements UIGroup + { + /** + * The corresponding group node. + */ + private GroupNode groupNode; + + /** + * Returns null to indicate that this group has no parent. + * @return null + */ + public UIGroup getParentGroup() + { + return null; + } + + /** + * This group is not attached to a contact source, so we return the + * first index. + * @return 0 + */ + public int getSourceIndex() + { + return 0; + } + + /** + * This group should never be collapsed. + * @return false + */ + public boolean isGroupCollapsed() + { + return false; + } + + /** + * Returns null to indicate that this group has no display name. + * @return null + */ + public String getDisplayName() + { + return null; + } + + /** + * As this group is not attached to a contact source it has no child + * contacts. + * @return 0 + */ + public int countChildContacts() + { + return 0; + } + + /** + * As this group is not attached to a contact source it has no child + * contacts. + * @return 0 + */ + public int countOnlineChildContacts() + { + return 0; + } + + /** + * Returns the descriptor of this group, just a string. + * @return the descriptor of this group + */ + public Object getDescriptor() + { + return "RootGroup"; + } + + /** + * Returns null to indicate that this group has no identifier. + * @return null + */ + public String getId() + { + return null; + } + + /** + * Returns the corresponding <tt>GroupNode</tt>. + * @return the corresponding <tt>GroupNode</tt> + */ + public GroupNode getGroupNode() + { + return groupNode; + } + + /** + * Sets the corresponding <tt>GroupNode</tt>. + * @param groupNode the <tt>GroupNode</tt> to set + */ + public void setGroupNode(GroupNode groupNode) + { + this.groupNode = groupNode; + } + + /** + * This group is not visible to the user. + * @return null + */ + public JPopupMenu getRightButtonMenu() + { + return null; + } + } } diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactNode.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactNode.java index 888ca65..2ddffb0 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactNode.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactNode.java @@ -8,11 +8,9 @@ package net.java.sip.communicator.impl.gui.main.contactlist; import javax.swing.tree.*; -import net.java.sip.communicator.service.contactlist.*; - /** * The <tt>ContactNode</tt> is a <tt>ContactListNode</tt> corresponding to a - * given <tt>MetaContact</tt>. + * given <tt>UIContact</tt>. * * @author Yana Stamcheva */ @@ -21,9 +19,9 @@ public class ContactNode implements ContactListNode { /** - * The <tt>MetaContact</tt> corresponding to this contact node. + * The <tt>UIContact</tt> corresponding to this contact node. */ - private MetaContact metaContact; + private UIContact contact; /** * Indicates if this node is currently active. Has unread messages waiting. @@ -32,32 +30,31 @@ public class ContactNode /** * Creates a <tt>ContactNode</tt> by specifying the corresponding - * <tt>metaContact</tt>. - * @param metaContact the <tt>MetaContact</tt> corresponding to this node + * <tt>contact</tt>. + * @param contact the <tt>UIContact</tt> corresponding to this node */ - public ContactNode(MetaContact metaContact) + public ContactNode(UIContact contact) { - super(metaContact); - this.metaContact = metaContact; + super(contact); + this.contact = contact; } /** - * Returns the corresponding <tt>MetaContact</tt>. - * @return the corresponding <tt>MetaContact</tt> + * Returns the corresponding <tt>UIContact</tt>. + * @return the corresponding <tt>UIContact</tt> */ - public MetaContact getMetaContact() + public UIContact getContactDescriptor() { - return (MetaContact) getUserObject(); + return (UIContact) getUserObject(); } /** - * Returns the index of this contact node in its parent group in - * the <tt>MetaContactListService</tt>. - * @return the index in the <tt>MetaContactListService</tt> + * Returns the index of this contact node in its parent group. + * @return the index of this contact node in its parent group */ - public int getMetaContactListIndex() + public int getSourceIndex() { - return metaContact.getParentMetaContactGroup().indexOf(metaContact); + return contact.getSourceIndex(); } /** diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/DefaultTreeContactList.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/DefaultTreeContactList.java index 22ce7c6..c1fe795 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/DefaultTreeContactList.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/DefaultTreeContactList.java @@ -8,7 +8,6 @@ package net.java.sip.communicator.impl.gui.main.contactlist; import java.awt.*; import java.awt.event.*; -import java.util.*; import javax.swing.*; import javax.swing.event.*; @@ -16,9 +15,9 @@ import javax.swing.tree.*; import net.java.sip.communicator.impl.gui.lookandfeel.*; import net.java.sip.communicator.impl.gui.main.chat.*; +import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*; import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.service.contactlist.*; -import net.java.sip.communicator.service.protocol.*; /** * DeafultContactlist used to display <code>JList</code>s with contacts. @@ -73,40 +72,28 @@ public class DefaultTreeContactList * Dummy method used and overridden from classes extending this * functionality such as ContactList. * - * @param metaContact the <tt>MetaContact</tt> to verify + * @param contact the <tt>MetaContact</tt> to verify * @return TRUE if the given <tt>MetaContact</tt> is active, FALSE - * otherwise */ - public boolean isContactActive(MetaContact metaContact) + public boolean isContactActive(UIContact contact) { return false; } /** - * Returns the general status of the given MetaContact. Detects the status - * using the priority status table. The priority is defined on the - * "availability" factor and here the most "available" status is returned. + * Checks if the given contact is currently active. + * Dummy method used and overridden from classes extending this + * functionality such as ContactList. * - * @param metaContact The metaContact for which the status is asked. - * @return PresenceStatus The most "available" status from all subcontact - * statuses. + * @param metaContact the <tt>MetaContact</tt> to verify + * @return TRUE if the given <tt>MetaContact</tt> is active, FALSE - + * otherwise */ - public PresenceStatus getMetaContactStatus(MetaContact metaContact) + public boolean isContactActive(MetaContact metaContact) { - PresenceStatus status = null; - Iterator<Contact> i = metaContact.getContacts(); - while (i.hasNext()) { - Contact protoContact = i.next(); - PresenceStatus contactStatus = protoContact.getPresenceStatus(); - - if (status == null) { - status = contactStatus; - } else { - status = (contactStatus.compareTo(status) > 0) ? contactStatus - : status; - } - } - return status; + return isContactActive( + MetaContactListSource.getUIContact(metaContact)); } /** @@ -125,51 +112,26 @@ public class DefaultTreeContactList Object element = path.getLastPathComponent(); - ExtendedTooltip tip = new ExtendedTooltip(true); + ExtendedTooltip tip = null; if (element instanceof ContactNode) { - MetaContact metaContact = ((ContactNode) element).getMetaContact(); - - byte[] avatarImage = metaContact.getAvatar(); - - if (avatarImage != null && avatarImage.length > 0) - tip.setImage(new ImageIcon(avatarImage)); - - tip.setTitle(metaContact.getDisplayName()); - - Iterator<Contact> i = metaContact.getContacts(); + UIContact contact + = ((ContactNode) element).getContactDescriptor(); - String statusMessage = null; - Contact protocolContact; - while (i.hasNext()) + tip = contact.getToolTip(); + if (tip == null) { - protocolContact = i.next(); - - ImageIcon protocolStatusIcon - = new ImageIcon( - protocolContact.getPresenceStatus().getStatusIcon()); - - String contactAddress = protocolContact.getAddress(); - //String statusMessage = protocolContact.getStatusMessage(); - - tip.addLine(protocolStatusIcon, contactAddress); - - // Set the first found status message. - if (statusMessage == null - && protocolContact.getStatusMessage() != null - && protocolContact.getStatusMessage().length() > 0) - statusMessage = protocolContact.getStatusMessage(); + tip = new ExtendedTooltip(true); + tip.setTitle(contact.getDisplayName()); } - - if (statusMessage != null) - tip.setBottomText(statusMessage); } else if (element instanceof GroupNode) { - MetaContactGroup metaGroup - = ((GroupNode) element).getMetaContactGroup(); + UIGroup group + = ((GroupNode) element).getGroupDescriptor(); - tip.setTitle(metaGroup.getGroupName()); + tip = new ExtendedTooltip(true); + tip.setTitle(group.getDisplayName()); } else if (element instanceof ChatContact) { @@ -177,6 +139,7 @@ public class DefaultTreeContactList ImageIcon avatarImage = chatContact.getAvatar(); + tip = new ExtendedTooltip(true); if (avatarImage != null) tip.setImage(avatarImage); diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/GroupNode.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/GroupNode.java index 78f09bf..dd65cc2 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/GroupNode.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/GroupNode.java @@ -10,12 +10,9 @@ import java.util.*; import javax.swing.tree.*; -import net.java.sip.communicator.impl.gui.utils.*; -import net.java.sip.communicator.service.contactlist.*; - /** * The <tt>GroupNode</tt> is a <tt>ContactListNode</tt> corresponding to a - * given <tt>MetaContactGroup</tt>. + * given <tt>UIGroup</tt>. * * @author Yana Stamcheva */ @@ -24,9 +21,14 @@ public class GroupNode implements ContactListNode { /** - * The corresponding <tt>MetaContactGroup</tt>. + * The parent contact list model. */ - private final MetaContactGroup metaGroup; + private final ContactListTreeModel treeModel; + + /** + * The corresponding <tt>UIGroup</tt>. + */ + private final UIGroup group; /** * A node comparator used to sort the list of children. @@ -40,50 +42,50 @@ public class GroupNode /** * Creates a <tt>GroupNode</tt> by specifying the parent <tt>treeModel</tt> - * and the corresponding <tt>metaGroup</tt> in the - * <tt>MetaContactListService</tt>. + * and the corresponding <tt>uiGroup</tt>. * * @param treeModel the parent tree model containing this group - * @param metaGroup the corresponding <tt>MetaContactGroup</tt> + * @param uiGroup the corresponding <tt>UIGroup</tt> */ - public GroupNode(ContactListTreeModel treeModel, MetaContactGroup metaGroup) + public GroupNode( ContactListTreeModel treeModel, + UIGroup uiGroup) { - super(metaGroup, true); + super(uiGroup, true); - this.metaGroup = metaGroup; + this.treeModel = treeModel; + this.group = uiGroup; - isCollapsed = ConfigurationManager - .isContactListGroupCollapsed(metaGroup.getMetaUID()); + isCollapsed = group.isGroupCollapsed(); } /** - * Creates a <tt>ContactNode</tt> for the given <tt>metaContact</tt> and - * adds it to this group. - * @param metaContact the <tt>MetaContact</tt> to add + * Creates a <tt>ContactNode</tt> for the given <tt>uiContact</tt> + * and adds it to this group. + * @param uiContact the <tt>UIContact</tt> to add * @return the created <tt>ContactNode</tt> */ - public ContactNode addMetaContact(MetaContact metaContact) + public ContactNode addContact(UIContact uiContact) { - ContactNode contactNode = new ContactNode(metaContact); + ContactNode contactNode = new ContactNode(uiContact); + uiContact.setContactNode(contactNode); + this.add(contactNode); return contactNode; } /** - * Creates a <tt>ContactNode</tt> for the given <tt>metaContact</tt>, + * Creates a <tt>ContactNode</tt> for the given <tt>uiContact</tt>, * adds it to this group and performs a sort at the end. - * @param treeModel the <tt>ContactListTreeModel</tt> to which the given - * <tt>metaContact</tt> is added - * @param metaContact the <tt>MetaContact</tt> to add + * @param uiContact the <tt>UIContact</tt> to add * @param isRefreshView indicates if the view should be refreshed * @return the created <tt>ContactNode</tt> */ @SuppressWarnings("unchecked") - public ContactNode sortedAddMetaContact(ContactListTreeModel treeModel, - MetaContact metaContact, - boolean isRefreshView) + public ContactNode sortedAddContact(UIContact uiContact, + boolean isRefreshView) { - ContactNode contactNode = new ContactNode(metaContact); + ContactNode contactNode = new ContactNode(uiContact); + uiContact.setContactNode(contactNode); this.add(contactNode); @@ -91,22 +93,19 @@ public class GroupNode Collections.sort(children, nodeComparator); if (isRefreshView) - this.fireNodeInserted(treeModel, getIndex(contactNode)); + this.fireNodeInserted(getIndex(contactNode)); return contactNode; } /** - * Removes the node corresponding to the given <tt>MetaContact</tt> from - * this group. - * @param treeModel the <tt>ContactListTreeModel</tt> from which the - * given <tt>metaContact</tt> is removed - * @param metaContact the <tt>MetaContact</tt> to remove + * Removes the node corresponding to the given <tt>uiContact</tt> from this + * group. + * @param uiContact the <tt>UIContact</tt> to remove */ - public void removeMetaContact( ContactListTreeModel treeModel, - MetaContact metaContact) + public void removeContact(UIContact uiContact) { - ContactNode contactNode = findContactNode(metaContact); + ContactNode contactNode = findContactNode(uiContact); if (contactNode != null) { @@ -115,38 +114,35 @@ public class GroupNode // checks verifying if the node belongs to this parent. children.removeElementAt(index); contactNode.setParent(null); + uiContact.setContactNode(null); - fireNodeRemoved(treeModel, contactNode, index); + fireNodeRemoved(contactNode, index); } } /** - * Creates a <tt>GroupNode</tt> for the given <tt>metaGroup</tt> and adds it - * to this group. - * @param treeModel the <tt>ContactListTreeModel</tt> to which the given - * <tt>metaGroup</tt> is added - * @param metaGroup the <tt>MetaContactGroup</tt> to add + * Creates a <tt>GroupNode</tt> for the given <tt>uiGroup</tt> and + * adds it to this group. + * @param uiGroup the <tt>UIGroup</tt> to add * @return the created <tt>GroupNode</tt> */ - public GroupNode addMetaContactGroup( ContactListTreeModel treeModel, - MetaContactGroup metaGroup) + public GroupNode addContactGroup(UIGroup uiGroup) { - GroupNode groupNode = new GroupNode(treeModel, metaGroup); + GroupNode groupNode = new GroupNode(treeModel, uiGroup); + uiGroup.setGroupNode(groupNode); + this.add(groupNode); return groupNode; } /** - * Removes the node corresponding to the given <tt>metaGroup</tt> from this + * Removes the node corresponding to the given <tt>uiGroup</tt> from this * group node. - * @param treeModel the <tt>ContactListTreeModel</tt> from which the given - * <tt>metaGroup</tt> is removed - * @param metaGroup the <tt>MetaContactGroup</tt> to remove + * @param uiGroup the <tt>UIGroup</tt> to remove */ - public void removeMetaContactGroup( ContactListTreeModel treeModel, - MetaContactGroup metaGroup) + public void removeContactGroup(UIGroup uiGroup) { - GroupNode groupNode = findGroupNode(metaGroup); + GroupNode groupNode = uiGroup.getGroupNode(); if (groupNode != null) { @@ -155,26 +151,47 @@ public class GroupNode // checks verifying if the node belongs to this parent. children.removeElementAt(index); groupNode.setParent(null); + uiGroup.setGroupNode(null); + + fireNodeRemoved(groupNode, index); + } + } + + /** + * Removes all of this node's children, setting their parents to null. + * If this node has no children, this method does nothing. + */ + public void removeAllChildren() + { + for (int i = getChildCount()-1; i >= 0; i--) + { + TreeNode treeNode = getChildAt(i); + + if (treeNode instanceof ContactNode) + ((ContactNode) treeNode).getContactDescriptor() + .setContactNode(null); + else if (treeNode instanceof GroupNode) + ((GroupNode) treeNode).getGroupDescriptor() + .setGroupNode(null); - fireNodeRemoved(treeModel, groupNode, index); + children.removeElementAt(i); + ((DefaultMutableTreeNode) treeNode).setParent(null); } } /** - * Creates a <tt>GroupNode</tt> for the given <tt>metaGroup</tt>, adds it - * to this group node and performs a sort at the end. - * @param treeModel the <tt>ContactListTreeModel</tt> to which the given - * <tt>metaGroup</tt> is added - * @param metaGroup the <tt>MetaContactGroup</tt> to add + * Creates a <tt>GroupNode</tt> for the given <tt>uiGroup</tt>, + * adds it to this group node and performs a sort at the end. + * @param uiGroup the <tt>UIGroup</tt> to add * @param isRefreshView indicates if the view should be refreshed * @return the created <tt>GroupNode</tt> */ @SuppressWarnings("unchecked") - public GroupNode sortedAddMetaContactGroup( ContactListTreeModel treeModel, - MetaContactGroup metaGroup, - boolean isRefreshView) + public GroupNode sortedAddContactGroup( UIGroup uiGroup, + boolean isRefreshView) { - GroupNode groupNode = new GroupNode(treeModel, metaGroup); + GroupNode groupNode = new GroupNode(treeModel, uiGroup); + uiGroup.setGroupNode(groupNode); this.add(groupNode); @@ -182,77 +199,51 @@ public class GroupNode Collections.sort(children, nodeComparator); if (isRefreshView) - this.fireNodeInserted(treeModel, getIndex(groupNode)); + this.fireNodeInserted(getIndex(groupNode)); return groupNode; } /** - * Returns the <tt>MetaContactGroup</tt> corresponding to this - * <tt>GroupNode</tt>. - * @return the <tt>MetaContactGroup</tt> corresponding to this - * <tt>GroupNode</tt> + * Returns the <tt>UIGroup</tt> corresponding to this <tt>GroupNode</tt>. + * @return the <tt>UIGroup</tt> corresponding to this <tt>GroupNode</tt> */ - public MetaContactGroup getMetaContactGroup() + public UIGroup getGroupDescriptor() { - return (MetaContactGroup) getUserObject(); - } - - /** - * Finds the <tt>GroupNode</tt> corresponding to the given - * <tt>metaGroup</tt> in the children of this node. - * @param metaGroup the <tt>MetaContactGroup</tt>, which node we're looking - * for - * @return the corresponding <tt>GroupNode</tt> or null if no group node - * was found - */ - @SuppressWarnings("unchecked") - public GroupNode findGroupNode(MetaContactGroup metaGroup) - { - Enumeration<TreeNode> children = children(); - while(children.hasMoreElements()) - { - TreeNode treeNode = children.nextElement(); - if (treeNode instanceof GroupNode - && ((GroupNode)treeNode).getMetaContactGroup().equals(metaGroup)) - return (GroupNode) treeNode; - } - return null; + return (UIGroup) getUserObject(); } /** * Finds the <tt>ContactNode</tt> corresponding to the given - * <tt>metaContact</tt> in the children of this node. - * @param metaContact the <tt>MetaContact</tt>, which node we're looking for - * @return the corresponding <tt>ContactNode</tt> or null if no contact node - * was found + * <tt>uiContact</tt> in the children of this node. + * @param uiContact the <tt>UIContact</tt>, which node we're looking for + * @return the corresponding <tt>ContactNode</tt> or null if no contact + * node was found */ @SuppressWarnings("unchecked") - public ContactNode findContactNode(MetaContact metaContact) + public ContactNode findContactNode(UIContact uiContact) { Enumeration<TreeNode> children = children(); while(children.hasMoreElements()) { TreeNode treeNode = children.nextElement(); if (treeNode instanceof ContactNode - && ((ContactNode)treeNode).getMetaContact().equals(metaContact)) + && ((ContactNode) treeNode).getContactDescriptor() + .equals(uiContact)) + { return (ContactNode) treeNode; + } } return null; } /** - * Returns the index of this node in its parent group in the - * <tt>MetaContactListService</tt>. - * @return the index of this node in its parent group in the - * <tt>MetaContactListService</tt> + * Returns the index of this node in its parent group. + * @return the index of this node in its parent group */ - public int getMetaContactListIndex() + public int getSourceIndex() { - MetaContactGroup parentGroup = metaGroup.getParentMetaContactGroup(); - if (parentGroup != null) - return parentGroup.indexOf(metaGroup); - else return 0; //this is the root group + return group.getSourceIndex(); } /** @@ -267,7 +258,7 @@ public class GroupNode { Collections.sort(children, nodeComparator); - fireNodesChanged(treeModel); + fireNodesChanged(); } } @@ -285,11 +276,9 @@ public class GroupNode /** * Notifies all interested listeners that a node has been inserted at the * given <tt>index</tt>. - * @param treeModel the <tt>ContactListTreeModel</tt> which should be - * notified * @param index the index of the newly inserted node */ - private void fireNodeInserted(ContactListTreeModel treeModel, int index) + private void fireNodeInserted(int index) { int[] newIndexs = new int[1]; newIndexs[0] = index; @@ -299,13 +288,10 @@ public class GroupNode /** * Notifies all interested listeners that <tt>node</tt> has been removed * from the given <tt>index</tt>. - * @param treeModel the <tt>ContactListTreeModel</tt> which should - * be notified * @param node the node that has been removed * @param index the index of the removed node */ - private void fireNodeRemoved( - ContactListTreeModel treeModel, ContactListNode node, int index) + private void fireNodeRemoved(ContactListNode node, int index) { int[] removedIndexs = new int[1]; removedIndexs[0] = index; @@ -314,10 +300,8 @@ public class GroupNode /** * Notifies all interested listeners that all nodes has changed. - * @param treeModel the <tt>ContactListTreeModel</tt> which should - * be notified */ - private void fireNodesChanged(ContactListTreeModel treeModel) + private void fireNodesChanged() { int childCount = getChildCount(); int[] changedIndexs = new int[childCount]; @@ -328,16 +312,25 @@ public class GroupNode } /** - * * Note: this comparator imposes orderings that are inconsistent with * equals. */ private class NodeComparator implements Comparator<ContactListNode> { + /** + * Compares its two arguments for order. Returns a negative integer, + * zero, or a positive integer as the first argument is less than, equal + * to, or greater than the second. + * @param node1 the first <tt>ContactListNode</tt> to compare + * @param node2 the second <tt>ContactListNode</tt> to compare + * @return -1 if the first node should be positioned before the second + * one, 1 if the first argument should be positioned after the second + * one, 0 if there's no matter + */ public int compare(ContactListNode node1, ContactListNode node2) { - int index1 = node1.getMetaContactListIndex(); - int index2 = node2.getMetaContactListIndex(); + int index1 = node1.getSourceIndex(); + int index2 = node2.getSourceIndex(); // Child groups are shown after child contacts. if (node1 instanceof GroupNode && node2 instanceof ContactNode) @@ -346,6 +339,14 @@ public class GroupNode if (node1 instanceof ContactNode && node2 instanceof GroupNode) return -1; + // If the first index is unknown then we position it to the end. + if (index1 < 0) + return 1; + + // If the second index is unknown then we position it to the end. + if (index2 < 0) + return -1; + if (index1 > index2) return 1; else if (index1 < index2) return -1; else return 0; diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactRightButtonMenu.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/MetaContactRightButtonMenu.java index 5a75dc0..b055980 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactRightButtonMenu.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/MetaContactRightButtonMenu.java @@ -19,6 +19,7 @@ import net.java.sip.communicator.impl.gui.event.*; import net.java.sip.communicator.impl.gui.main.*; import net.java.sip.communicator.impl.gui.main.call.*; import net.java.sip.communicator.impl.gui.main.chat.history.*; +import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*; import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.service.gui.*; @@ -35,7 +36,7 @@ import org.osgi.framework.*; * * @author Yana Stamcheva */ -public class ContactRightButtonMenu +public class MetaContactRightButtonMenu extends JPopupMenu implements ActionListener, PluginComponentListener, @@ -46,7 +47,8 @@ public class ContactRightButtonMenu */ private static final long serialVersionUID = 3033031652970285857L; - private final Logger logger = Logger.getLogger(ContactRightButtonMenu.class); + private final Logger logger + = Logger.getLogger(MetaContactRightButtonMenu.class); private static final String allContactsString = GuiActivator.getResources().getI18NString("service.gui.ALL_CONTACTS"); @@ -54,8 +56,9 @@ public class ContactRightButtonMenu private static final String moveToString = GuiActivator.getResources() .getI18NString("service.gui.MOVE_TO_GROUP"); - private static final String moveSubcontactString = GuiActivator.getResources() - .getI18NString("service.gui.MOVE_SUBCONTACT"); + private static final String moveSubcontactString + = GuiActivator.getResources() + .getI18NString("service.gui.MOVE_SUBCONTACT"); private static final String removeContactString = GuiActivator.getResources() @@ -71,7 +74,8 @@ public class ContactRightButtonMenu = GuiActivator.getResources().getI18NString("service.gui.SEND_FILE"); private static final String renameContactString - = GuiActivator.getResources().getI18NString("service.gui.RENAME_CONTACT"); + = GuiActivator.getResources() + .getI18NString("service.gui.RENAME_CONTACT"); private static final String viewHistoryString = GuiActivator.getResources().getI18NString("service.gui.VIEW_HISTORY"); @@ -97,7 +101,8 @@ public class ContactRightButtonMenu private final JMenuItem sendMessageItem = new JMenuItem( sendMessageString, - new ImageIcon(ImageLoader.getImage(ImageLoader.SEND_MESSAGE_16x16_ICON))); + new ImageIcon(ImageLoader + .getImage(ImageLoader.SEND_MESSAGE_16x16_ICON))); private final JMenuItem sendFileItem = new JMenuItem( sendFileString, @@ -105,7 +110,8 @@ public class ContactRightButtonMenu private final JMenuItem sendSmsItem = new JMenuItem( sendSmsString, - new ImageIcon(ImageLoader.getImage(ImageLoader.SEND_MESSAGE_16x16_ICON))); + new ImageIcon(ImageLoader + .getImage(ImageLoader.SEND_MESSAGE_16x16_ICON))); private final JMenuItem renameContactItem = new JMenuItem( renameContactString, @@ -137,16 +143,14 @@ public class ContactRightButtonMenu /** * Creates an instance of ContactRightButtonMenu. - * @param contactItem The MetaContact for which the menu is opened. - * @param contactList The contact list over which this menu is shown. + * @param contactItem The MetaContact for which the menu is opened */ - public ContactRightButtonMenu( MetaContact contactItem, - TreeContactList contactList) + public MetaContactRightButtonMenu( MetaContact contactItem) { super(); this.mainFrame = GuiActivator.getUIService().getMainFrame(); - this.contactList = contactList; + this.contactList = GuiActivator.getContactList(); this.contactItem = contactItem; @@ -621,11 +625,19 @@ public class ContactRightButtonMenu * the selected contact to the selected group. * @param evt the <tt>ContactListEvent</tt> has */ - public void groupSelected(ContactListEvent evt) + public void groupClicked(ContactListEvent evt) { this.moveDialog.dispose(); - MetaContactGroup sourceGroup = evt.getSourceGroup(); + UIGroup sourceGroup = evt.getSourceGroup(); + + // TODO: may be show a warning message to tell the user that she should + // select another group. + if (!(sourceGroup instanceof MetaUIGroup)) + return; + + MetaContactGroup metaGroup + = (MetaContactGroup) sourceGroup.getDescriptor(); contactList.removeContactListListener(this); @@ -636,12 +648,12 @@ public class ContactRightButtonMenu if(moveAllContacts) { MetaContactListManager - .moveMetaContactToGroup(contactItem, sourceGroup); + .moveMetaContactToGroup(contactItem, metaGroup); } else if(contactToMove != null) { MetaContactListManager - .moveContactToGroup(contactToMove, sourceGroup); + .moveContactToGroup(contactToMove, metaGroup); } contactList.setGroupClickConsumed(false); @@ -654,17 +666,12 @@ public class ContactRightButtonMenu */ public void contactClicked(ContactListEvent evt) { - this.moveContact(evt.getSourceContact()); - } + UIContact descriptor = evt.getSourceContact(); + // We're only interested in MetaContacts here. + if (!(descriptor instanceof MetaUIContact)) + return; - /** - * Implements ContactListListener.contactSelected method in order - * to move the chosen sub-contact when a meta contact is selected. - * @param evt the <tt>ContactListEvent</tt> that notified us - */ - public void protocolContactClicked(ContactListEvent evt) - { - this.moveContact(evt.getSourceContact()); + this.moveContact((MetaContact) descriptor.getDescriptor()); } /** @@ -766,48 +773,4 @@ public class ContactRightButtonMenu protoContact.getPresenceStatus().getStatusIcon()), protoContact.getProtocolProvider()); } - - /** - * A menu item that performs an action related to a specific protocol - * provider. - * - */ - private static class ProviderAwareMenuItem extends JMenuItem - { - /** - * An eclipse generated serialVersionUID. - */ - private static final long serialVersionUID = 6343418726839985645L; - - private ProtocolProviderService provider = null; - - /** - * Initializes the menu item and stores a reference to the specified - * provider. - * - * @param provider the provider that we are related to - * @param text the text string for this menu - * @param icon the icon to display when showing this menu - */ - public ProviderAwareMenuItem(ProtocolProviderService provider, - String text, - Icon icon) - { - super(text, icon); - - this.provider = provider; - } - - /** - * Returns a reference to the <tt>ProtocolProviderService</tt> that - * this item is related to. - * - * @return a reference to the <tt>ProtocolProviderService</tt> that - * this item is related to. - */ - public ProtocolProviderService getProvider() - { - return provider; - } - } } diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/PresenceFilter.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/PresenceFilter.java index cdc0c54..d552b29 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/PresenceFilter.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/PresenceFilter.java @@ -6,8 +6,16 @@ */ package net.java.sip.communicator.impl.gui.main.contactlist; +import java.util.*; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*; +import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; /** * The <tt>PresenceFilter</tt> is used to filter offline contacts from the @@ -16,11 +24,79 @@ import net.java.sip.communicator.service.protocol.*; * @author Yana Stamcheva */ public class PresenceFilter - implements ContactListFilter + implements ContactListFilter, + ContactPresenceStatusListener { + /** + * Indicates if this presence filter shows or hides the offline contacts. + */ private boolean isShowOffline; /** + * Indicates if there's a presence filtering going on. + */ + private boolean isFiltering = false; + + private final Object lockedFiltering = new Object(); + + /** + * Creates an instance of <tt>PresenceFilter</tt>. + */ + public PresenceFilter() + { + this.setShowOffline(ConfigurationManager.isShowOffline()); + } + + /** + * Applies this filter. This filter is applied over the + * <tt>MetaContactListService</tt>. + * @param treeModel the model to which we should add the results from the + * filtering operation + */ + public void applyFilter(ContactListTreeModel treeModel) + { + isFiltering = true; + + synchronized (lockedFiltering) + { + addMatching(GuiActivator.getContactListService().getRoot(), + treeModel); + } + + isFiltering = false; + } + + /** + * Indicates if the given <tt>uiContact</tt> is matching this filter. + * @param uiContact the <tt>UIContact</tt> to check + * @return <tt>true</tt> if the given <tt>uiContact</tt> is matching + * this filter, otherwise returns <tt>false</tt> + */ + public boolean isMatching(UIContact uiContact) + { + Object descriptor = uiContact.getDescriptor(); + if (descriptor instanceof MetaContact) + return isMatching((MetaContact) descriptor); + + return false; + } + + /** + * Indicates if the given <tt>uiGroup</tt> is matching this filter. + * @param uiGroup the <tt>UIGroup</tt> to check + * @return <tt>true</tt> if the given <tt>uiGroup</tt> is matching + * this filter, otherwise returns <tt>false</tt> + */ + public boolean isMatching(UIGroup uiGroup) + { + Object descriptor = uiGroup.getDescriptor(); + if (descriptor instanceof MetaContactGroup) + return isMatching((MetaContactGroup) descriptor); + + return false; + } + + /** * Sets the show offline property. * @param isShowOffline indicates if offline contacts are shown */ @@ -47,7 +123,7 @@ public class PresenceFilter * @return <tt>true</tt> if the given <tt>MetaContact</tt> is matching this * filter */ - public boolean isMatching(MetaContact metaContact) + private boolean isMatching(MetaContact metaContact) { return isShowOffline || isContactOnline(metaContact); } @@ -59,7 +135,7 @@ public class PresenceFilter * @return <tt>true</tt> if the given <tt>MetaContactGroup</tt> is matching * this filter */ - public boolean isMatching(MetaContactGroup metaGroup) + private boolean isMatching(MetaContactGroup metaGroup) { return (isShowOffline || metaGroup.countOnlineChildContacts() > 0) ? true @@ -85,4 +161,106 @@ public class PresenceFilter return defaultContact.getPresenceStatus().getStatus() >= PresenceStatus.ONLINE_THRESHOLD; } + + /** + * Adds all contacts contained in the given <tt>MetaContactGroup</tt> + * matching the current filter and not contained in the contact list. + * @param metaGroup the <tt>MetaContactGroup</tt>, which matching contacts + * to add + * @param resultTreeModel the <tt>ContactListTreeModel</tt>, where results + * should be added + */ + private void addMatching( MetaContactGroup metaGroup, + ContactListTreeModel resultTreeModel) + { + Iterator<MetaContact> childContacts = metaGroup.getChildContacts(); + + while(childContacts.hasNext() && isFiltering) + { + MetaContact metaContact = childContacts.next(); + + if(isMatching(metaContact)) + GuiActivator.getContactList().addContact( + resultTreeModel, + MetaContactListSource.createUIContact(metaContact), + true, + false); + } + + Iterator<MetaContactGroup> subgroups = metaGroup.getSubgroups(); + while(subgroups.hasNext() && isFiltering) + { + MetaContactGroup subgroup = subgroups.next(); + + if (subgroup.countChildContacts() == 0 + && subgroup.countSubgroups() == 0 + && isMatching(subgroup)) + GuiActivator.getContactList().addGroup( + resultTreeModel, + MetaContactListSource.createUIGroup(subgroup), + false); + else + addMatching(subgroup, resultTreeModel); + } + } + + /** + * Indicates that a contact has changed its status. + * + * @param evt the presence event containing information about the + * contact status change + */ + public void contactPresenceStatusChanged( + final ContactPresenceStatusChangeEvent evt) + { + final Contact sourceContact = evt.getSourceContact(); + + final MetaContact metaContact = GuiActivator.getContactListService() + .findMetaContactByContact(sourceContact); + + if (metaContact == null + || (evt.getOldStatus() == evt.getNewStatus())) + return; + + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + UIContact cDescriptor + = MetaContactListSource.getUIContact(metaContact); + + if (cDescriptor == null) + cDescriptor = MetaContactListSource + .createUIContact(metaContact); + + synchronized(lockedFiltering) + { + if (GuiActivator.getContactList().getCurrentFilter() + .equals(PresenceFilter.this) + && isMatching(metaContact)) + { + if (cDescriptor.getContactNode() == null) + GuiActivator.getContactList() + .addContact(cDescriptor); + else + GuiActivator.getContactList() + .nodeChanged(cDescriptor.getContactNode()); + } + else + { + GuiActivator.getContactList() + .removeContact(cDescriptor); + } + } + } + }); + } + + /** + * Stops this filter current queries. + */ + public void stopFilter() + { + isFiltering = false; + } } diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/SearchField.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/SearchField.java index 77a97cc..eec1066 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/SearchField.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/SearchField.java @@ -10,6 +10,7 @@ import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.main.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.swing.*; +import net.java.sip.communicator.util.swing.event.*; import net.java.sip.communicator.util.swing.plaf.*; /** @@ -19,7 +20,7 @@ import net.java.sip.communicator.util.swing.plaf.*; */ public class SearchField extends SIPCommTextField - implements DocumentListener + implements TextFieldChangeListener { private final Logger logger = Logger.getLogger(SearchField.class); @@ -52,7 +53,7 @@ public class SearchField this.setPreferredSize(new Dimension(100, 22)); this.setDragEnabled(true); - this.getDocument().addDocumentListener(this); + this.addTextChangeListener(this); InputMap imap = getInputMap(JComponent.WHEN_FOCUSED); imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "escape"); @@ -70,9 +71,8 @@ public class SearchField /** * Handles the change when a char has been inserted in the field. - * @param e the <tt>DocumentEvent</tt> that notified us */ - public void insertUpdate(DocumentEvent e) + public void textInserted() { // Should explicitly check if there's a text, because the default text // triggers also an insertUpdate event. @@ -85,9 +85,8 @@ public class SearchField /** * Handles the change when a char has been removed from the field. - * @param e the <tt>DocumentEvent</tt> that notified us */ - public void removeUpdate(DocumentEvent e) + public void textRemoved() { scheduleUpdate(); } @@ -171,10 +170,10 @@ public class SearchField */ public void updateContactListView(String filterString) { + TreeContactList contactList = GuiActivator.getContactList(); + if (filterString != null && filterString.length() > 0) { - TreeContactList contactList = GuiActivator.getContactList(); - boolean hasMatching = contactList.applyFilter(TreeContactList.searchFilter); @@ -198,9 +197,7 @@ public class SearchField } else { - TreeContactList contactList = GuiActivator.getContactList(); - - contactList.applyFilter(TreeContactList.presenceFilter); + contactList.applyDefaultFilter(); enableUnknownContactView(false); } diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/SearchFilter.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/SearchFilter.java index 63106de..e56ba58 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/SearchFilter.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/SearchFilter.java @@ -9,8 +9,9 @@ package net.java.sip.communicator.impl.gui.main.contactlist; import java.util.*; import java.util.regex.*; -import net.java.sip.communicator.service.contactlist.*; -import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*; +import net.java.sip.communicator.service.contactsource.*; /** * The <tt>SearchFilter</tt> is a <tt>ContactListFilter</tt> that filters the @@ -19,19 +20,137 @@ import net.java.sip.communicator.service.protocol.*; * @author Yana Stamcheva */ public class SearchFilter - implements ContactListFilter + implements ContactListFilter, + ContactQueryListener { + /** + * The default contact source search type. + */ + public static final int DEFAULT_SOURCE = 0; + + /** + * The history contact source search type. + */ + public static final int HISTORY_SOURCE = 1; + + /** + * The string, which we're searching. + */ + private String filterString; + + /** + * The pattern to filter. + */ private Pattern filterPattern; /** + * The <tt>ContactListTreeModel</tt>, where results from the search + * are added. + */ + private ContactListTreeModel resultTreeModel; + + /** + * The <tt>MetaContactListSource</tt> to search in. + */ + private final MetaContactListSource mclSource; + + /** + * The list of external contact sources to search in. + */ + private Collection<ExternalContactSource> contactSources; + + /** + * The current operating query. + */ + private ContactQuery currentQuery; + + /** + * The type of the search source. One of the above defined DEFAUT_SOURCE or + * HISTORY_SOURCE. + */ + private int searchSourceType; + + /** + * Creates an instance of <tt>SearchFilter</tt>. + */ + public SearchFilter() + { + this.mclSource = new MetaContactListSource(); + } + + /** + * Applies this filter and stores the result in the given <tt>treeModel</tt>. + * @param treeModel the <tt>ContactListTreeModel</tt>, in which we store + * results + */ + public void applyFilter(ContactListTreeModel treeModel) + { + resultTreeModel = treeModel; + + if (contactSources == null) + contactSources = TreeContactList.getContactSources(); + + if (searchSourceType == DEFAULT_SOURCE) + // First add the MetaContactListSource + mclSource.filter(filterPattern, treeModel); + + for (ExternalContactSource contactSource : contactSources) + { + ContactSourceService sourceService + = contactSource.getContactSourceService(); + if (sourceService instanceof ExtendedContactSourceService) + currentQuery + = ((ExtendedContactSourceService) sourceService) + .queryContactSource(filterPattern); + else + currentQuery = sourceService.queryContactSource(filterString); + + // Add first available results. + this.addMatching(currentQuery.getQueryResults()); + + currentQuery.addContactQueryListener(this); + } + } + + /** + * Indicates if the given <tt>uiGroup</tt> matches this filter. + * @param uiGroup the <tt>UIGroup</tt> to check + * @return <tt>true</tt> if the given <tt>uiGroup</tt> matches the current + * filter, <tt>false</tt> - otherwise + */ + public boolean isMatching(UIContact uiGroup) + { + Iterator<String> searchStrings = uiGroup.getSearchStrings(); + + while (searchStrings != null && searchStrings.hasNext()) + { + if (isMatching(searchStrings.next())) + return true; + } + return false; + } + + /** + * For all groups we return false. If some of the child contacts of this + * group matches this filter the group would be automatically added when + * the contact is added in the list. + * @param uiGroup the <tt>UIGroup</tt> to check + * @return false + */ + public boolean isMatching(UIGroup uiGroup) + { + return false; + } + + /** * Creates the <tt>SearchFilter</tt> by specifying the string used for * filtering. - * @param filterString the String used for filtering + * @param filter the String used for filtering */ - public void setFilterString(String filterString) + public void setFilterString(String filter) { // First escape all special characters from the given filter string. - filterString = Pattern.quote(filterString); + this.filterString = Pattern.quote(filter); // Then create the pattern. // By default, case-insensitive matching assumes that only characters @@ -45,60 +164,138 @@ public class SearchFilter } /** - * Checks if the given <tt>metaContact</tt> is matching the current filter. - * A <tt>MetaContact</tt> would be matching the filter if one of the - * following is true:<br> - * - its display name contains the filter string - * - at least one of its child protocol contacts has a display name or an - * address that contains the filter string. - * @param metaContact the <tt>MetaContact</tt> to check - * @return <tt>true</tt> to indicate that the given <tt>metaContact</tt> is + * Stops the current query. + */ + public void stopFilter() + { + if (currentQuery != null) + currentQuery.cancel(); + } + + /** + * Checks if the given <tt>contact</tt> is matching the current filter. + * A <tt>SourceContact</tt> would be matching the filter if its display + * name is matching the search string. + * @param contact the <tt>ContactListContactDescriptor</tt> to check + * @return <tt>true</tt> to indicate that the given <tt>contact</tt> is * matching the current filter, otherwise returns <tt>false</tt> */ - public boolean isMatching(MetaContact metaContact) + private boolean isMatching(SourceContact contact) { - Matcher matcher = filterPattern.matcher(metaContact.getDisplayName()); + return isMatching(contact.getDisplayName()); + } + + /** + * Indicates if the given string matches this filter. + * @param text the text to check + * @return <tt>true</tt> to indicate that the given <tt>text</tt> matches + * this filter, <tt>false</tt> - otherwise + */ + private boolean isMatching(String text) + { + Matcher matcher = filterPattern.matcher(text); if(matcher.find()) return true; - Iterator<Contact> contacts = metaContact.getContacts(); - while (contacts.hasNext()) + return false; + } + + /** + * Indicates that a contact has been received for a query. + * @param event the <tt>ContactReceivedEvent</tt> that notified us + */ + public void contactReceived(ContactReceivedEvent event) + { + synchronized (resultTreeModel) { - Contact contact = contacts.next(); + addSourceContact(event.getContact()); + } + } - matcher = filterPattern.matcher(contact.getDisplayName()); + /** + * Indicates that the status of a query has changed. + * @param event the <tt>ContactQueryStatusEvent</tt> that notified us + */ + public void queryStatusChanged(ContactQueryStatusEvent event) + { + int eventType = event.getEventType(); - if (matcher.find()) - return true; + // Remove the current query when it's stopped for some reason. + // QUERY_COMPLETED, QUERY_COMPLETED, QUERY_ERROR + currentQuery = null; - matcher = filterPattern.matcher(contact.getAddress()); + if (eventType == ContactQueryStatusEvent.QUERY_ERROR) + { + //TODO: Show the error to the user?? + } - if (matcher.find()) - return true; + event.getQuerySource().removeContactQueryListener(this); + } + + /** + * Adds the list of <tt>sourceContacts</tt> in the current result tree model. + * @param sourceContacts the list of <tt>SourceContact</tt>s to add + */ + private void addMatching(List<SourceContact> sourceContacts) + { + Iterator<SourceContact> contactsIter = sourceContacts.iterator(); + while (contactsIter.hasNext()) + { + addSourceContact(contactsIter.next()); } - return false; } /** - * Checks if the given <tt>metaGroup</tt> is matching the current filter. A - * group is matching the current filter only if it contains at least one - * child <tt>MetaContact</tt>, which is matching the current filter. - * @param metaGroup the <tt>MetaContactGroup</tt> to check - * @return <tt>true</tt> to indicate that the given <tt>metaGroup</tt> is - * matching the current filter, otherwise returns <tt>false</tt> + * Adds the given <tt>sourceContact</tt> to the result tree model. + * @param sourceContact the <tt>SourceContact</tt> to add + */ + private void addSourceContact(SourceContact sourceContact) + { + ContactSourceService contactSource + = sourceContact.getContactSource(); + + ExternalContactSource sourceUI + = TreeContactList.getContactSource(contactSource); + + if (sourceUI != null + // ExtendedContactSourceService has already matched the + // SourceContact over the pattern + && (contactSource instanceof ExtendedContactSourceService) + || isMatching(sourceContact)) + GuiActivator.getContactList().addContact( + resultTreeModel, + sourceUI.getUIContact(sourceContact), + true, + false); + } + + /** + * Sets the search source type: DEFAULT_SOURCE or HISTORY_SOURCE. + * @param searchSourceType the type of the search source to set */ - public boolean isMatching(MetaContactGroup metaGroup) + public void setSearchSourceType(int searchSourceType) { - Iterator<MetaContact> contacts = metaGroup.getChildContacts(); + this.searchSourceType = searchSourceType; - while (contacts.hasNext()) + switch(searchSourceType) { - MetaContact metaContact = contacts.next(); + case DEFAULT_SOURCE: + contactSources = TreeContactList.getContactSources(); + break; + case HISTORY_SOURCE: + { + ExternalContactSource historySource + = TreeContactList.getContactSource( + ContactSourceService.CALL_HISTORY); - if (isMatching(metaContact)) - return true; + Collection<ExternalContactSource> historySources + = new LinkedList<ExternalContactSource>(); + + historySources.add(historySource); + contactSources = historySources; + break; + } } - return false; } } diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/SourceContactRightButtonMenu.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/SourceContactRightButtonMenu.java new file mode 100644 index 0000000..59bac1b --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/SourceContactRightButtonMenu.java @@ -0,0 +1,136 @@ +/* + * 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.contactlist; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.main.call.*; +import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*; +import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.swing.*; + +/** + * The right button menu for external contact sources. + * @see ExternalContactSource + */ +public class SourceContactRightButtonMenu + extends JPopupMenu +{ + private final SourceContact sourceContact; + + /** + * Creates an instance of <tt>SourceContactRightButtonMenu</tt> by + * specifying the <tt>SourceContact</tt>, for which this menu is created. + * @param sourceContact the <tt>SourceContact</tt>, for which this menu is + * created + */ + public SourceContactRightButtonMenu(SourceContact sourceContact) + { + this.sourceContact = sourceContact; + + this.initItems(); + } + + /** + * Initializes menu items. + */ + private void initItems() + { + ContactDetail cDetail = sourceContact + .getPreferredContactDetail(OperationSetBasicTelephony.class); + + if (cDetail != null) + add(initCallMenu()); + } + + /** + * Initializes the call menu. + * @return the call menu + */ + private Component initCallMenu() + { + SIPCommMenu callContactMenu = new SIPCommMenu( + GuiActivator.getResources().getI18NString("service.gui.CALL")); + callContactMenu.setIcon(new ImageIcon(ImageLoader + .getImage(ImageLoader.CALL_16x16_ICON))); + + Iterator<ContactDetail> details + = sourceContact.getContactDetails(OperationSetBasicTelephony.class) + .iterator(); + + while (details.hasNext()) + { + final ContactDetail detail = details.next(); + // add all the contacts that support telephony to the call menu + JMenuItem callContactItem = new JMenuItem(); + callContactItem.setName(detail.getContactAddress()); + callContactItem.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + ProtocolProviderService protocolProvider + = detail.getPreferredProtocolProvider( + OperationSetBasicTelephony.class); + + if (protocolProvider != null) + CallManager.createCall( protocolProvider, + detail.getContactAddress()); + else + CallManager.createCall(detail.getContactAddress()); + } + }); + callContactMenu.add(callContactItem); + } + return callContactMenu; + } + +// private Component initIMMenu() +// { +// SIPCommMenu callContactMenu = new SIPCommMenu( +// GuiActivator.getResources().getI18NString( +// "service.gui.SEND_MESSAGE")); +// callContactMenu.setIcon(new ImageIcon(ImageLoader +// .getImage(ImageLoader.SEND_MESSAGE_16x16_ICON))); +// +// Iterator<ContactDetail> details +// = sourceContact.getContactDetails( +// OperationSetBasicInstantMessaging.class).iterator(); +// +// while (details.hasNext()) +// { +// final ContactDetail detail = details.next(); +// // add all the contacts that support telephony to the call menu +// JMenuItem callContactItem = new JMenuItem(); +// callContactItem.setName(detail.getContactAddress()); +// callContactItem.addActionListener(new ActionListener() +// { +// public void actionPerformed(ActionEvent e) +// { +// ProtocolProviderService protocolProvider +// = detail.getPreferredProtocolProvider( +// OperationSetBasicInstantMessaging.class); +// +// if (protocolProvider != null) +// CallManager.createCall( protocolProvider, +// detail.getContactAddress()); +// else +// GuiActivator.getUIService().getChatWindowManager() +// .startChat(contactItem); +// } +// }); +// callContactMenu.add(callContactItem); +// } +// return callContactMenu; +// } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/TreeContactList.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/TreeContactList.java index b708ce3..50903e7 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/TreeContactList.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/TreeContactList.java @@ -14,12 +14,14 @@ import javax.swing.*; import javax.swing.event.*; import javax.swing.tree.*; +import org.osgi.framework.*; + import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.main.contactlist.contactsource.*; import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.service.contactlist.event.*; -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.contactsource.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.swing.*; @@ -31,7 +33,6 @@ import net.java.sip.communicator.util.swing.*; public class TreeContactList extends DefaultTreeContactList implements MetaContactListListener, - ContactPresenceStatusListener, MouseListener, MouseMotionListener, TreeExpansionListener @@ -39,10 +40,11 @@ public class TreeContactList /** * The logger. */ - private static final Logger logger = Logger.getLogger(TreeContactList.class); + private static final Logger logger + = Logger.getLogger(TreeContactList.class); /** - * The tree model. + * The default tree model. */ private ContactListTreeModel treeModel; @@ -83,6 +85,18 @@ public class TreeContactList public static final SearchFilter searchFilter = new SearchFilter(); /** + * The call history filter. + */ + public static final CallHistoryFilter historyFilter + = new CallHistoryFilter(); + + /** + * The default filter is initially set to the PresenceFilter. But anyone + * could change it by calling setDefaultFilter(). + */ + private ContactListFilter defaultFilter = presenceFilter; + + /** * The current filter. */ private ContactListFilter currentFilter; @@ -98,6 +112,9 @@ public class TreeContactList */ private MouseListener[] originalMouseListeners; + private static final Collection<ExternalContactSource> contactSources + = new LinkedList<ExternalContactSource>(); + /** * Indicates if we're currently filtering the tree model. If there's an * ongoing filtering this variable would be set to true, otherwise the value @@ -123,18 +140,15 @@ public class TreeContactList GuiActivator.getContactListService().addMetaContactListListener(this); - treeModel = new ContactListTreeModel( - GuiActivator.getContactListService().getRoot()); - - // By default we set the current filter to be the presence filter. - presenceFilter.setShowOffline(ConfigurationManager.isShowOffline()); - applyFilter(presenceFilter); + treeModel = new ContactListTreeModel(); // We hide the root node as it doesn't represent a real group. if (isRootVisible()) setRootVisible(false); this.initKeyActions(); + + this.initContactSources(); } /** @@ -146,6 +160,9 @@ public class TreeContactList { final MetaContactGroup metaGroup = evt.getSourceMetaContactGroup(); + final UIGroup uiGroup + = MetaContactListSource.getUIGroup(metaGroup); + SwingUtilities.invokeLater(new Runnable() { public void run() @@ -156,10 +173,9 @@ public class TreeContactList // the temporary model. synchronized (tempTreeModel) { - if (currentFilter.isMatching(metaGroup)) + if (uiGroup != null) { - GroupNode groupNode - = treeModel.findGroupNodeByMetaGroup(metaGroup); + GroupNode groupNode = uiGroup.getGroupNode(); if (groupNode != null) groupNode.sort(treeModel); @@ -178,6 +194,9 @@ public class TreeContactList { final MetaContact metaContact = evt.getSourceMetaContact(); + final UIContact uiContact + = MetaContactListSource.createUIContact(metaContact); + SwingUtilities.invokeLater(new Runnable() { public void run() @@ -188,8 +207,10 @@ public class TreeContactList // the temporary model. synchronized (tempTreeModel) { - if (currentFilter.isMatching(metaContact)) - addContact(metaContact); + if (currentFilter.isMatching(uiContact)) + addContact(uiContact); + else + MetaContactListSource.removeUIContact(metaContact); } } }); @@ -204,6 +225,9 @@ public class TreeContactList { final MetaContactGroup metaGroup = evt.getSourceMetaContactGroup(); + final UIGroup uiGroup + = MetaContactListSource.createUIGroup(metaGroup); + SwingUtilities.invokeLater(new Runnable() { public void run() @@ -214,8 +238,10 @@ public class TreeContactList // the temporary model. synchronized (tempTreeModel) { - if (currentFilter.isMatching(metaGroup)) - addGroup(metaGroup); + if (currentFilter.isMatching(uiGroup)) + addGroup(uiGroup); + else + MetaContactListSource.removeUIGroup(metaGroup); } } }); @@ -230,6 +256,9 @@ public class TreeContactList { final MetaContactGroup metaGroup = evt.getSourceMetaContactGroup(); + final UIGroup uiGroup + = MetaContactListSource.getUIGroup(metaGroup); + SwingUtilities.invokeLater(new Runnable() { public void run() @@ -240,10 +269,9 @@ public class TreeContactList // the temporary model. synchronized (tempTreeModel) { - if (currentFilter.isMatching(metaGroup)) + if (uiGroup != null) { - GroupNode groupNode - = treeModel.findGroupNodeByMetaGroup(metaGroup); + GroupNode groupNode = uiGroup.getGroupNode(); if (groupNode != null) treeModel.nodeChanged(groupNode); @@ -261,6 +289,10 @@ public class TreeContactList */ public void metaContactGroupRemoved(final MetaContactGroupEvent evt) { + final UIGroup uiGroup + = MetaContactListSource.getUIGroup( + evt.getSourceMetaContactGroup()); + SwingUtilities.invokeLater(new Runnable() { public void run() @@ -271,7 +303,7 @@ public class TreeContactList // the temporary model. synchronized (tempTreeModel) { - removeGroup(evt.getSourceMetaContactGroup()); + removeGroup(uiGroup); } } }); @@ -284,7 +316,9 @@ public class TreeContactList */ public void metaContactModified(MetaContactModifiedEvent evt) { - final MetaContact metaContact = evt.getSourceMetaContact(); + final UIContact uiContact + = MetaContactListSource.getUIContact( + evt.getSourceMetaContact()); SwingUtilities.invokeLater(new Runnable() { @@ -296,11 +330,10 @@ public class TreeContactList // the temporary model. synchronized (tempTreeModel) { - if (currentFilter.isMatching(metaContact)) + if (uiContact != null) { ContactNode contactNode - = treeModel.findContactNodeByMetaContact( - metaContact); + = uiContact.getContactNode(); if (contactNode != null) treeModel.nodeChanged(contactNode); @@ -317,7 +350,15 @@ public class TreeContactList */ public void metaContactMoved(final MetaContactMovedEvent evt) { - final MetaContact metaContact = evt.getSourceMetaContact(); + final UIContact uiContact + = MetaContactListSource.getUIContact( + evt.getSourceMetaContact()); + + final UIGroup oldUIGroup + = MetaContactListSource.getUIGroup(evt.getOldParent()); + + final UIGroup newUIGroup + = MetaContactListSource.getUIGroup(evt.getNewParent()); SwingUtilities.invokeLater(new Runnable() { @@ -329,22 +370,15 @@ public class TreeContactList // the temporary model. synchronized (tempTreeModel) { - if (currentFilter.isMatching(metaContact)) + if (currentFilter.isMatching(uiContact)) { - GroupNode oldParent - = treeModel.findGroupNodeByMetaGroup( - evt.getOldParent()); - GroupNode newParent - = treeModel.findGroupNodeByMetaGroup( - evt.getNewParent()); + GroupNode oldParent = oldUIGroup.getGroupNode(); + GroupNode newParent = newUIGroup.getGroupNode(); if (oldParent != null) - oldParent - .removeMetaContact(treeModel, metaContact); - + oldParent.removeContact(uiContact); if (newParent != null) - newParent.sortedAddMetaContact(treeModel, - metaContact, true); + newParent.sortedAddContact(uiContact, true); } } } @@ -369,8 +403,11 @@ public class TreeContactList // the temporary model. synchronized (tempTreeModel) { - removeContact( evt.getSourceMetaContact(), - evt.getParentGroup()); + UIContact uiContact = MetaContactListSource.getUIContact( + evt.getSourceMetaContact()); + + if (uiContact != null) + removeContact(uiContact); } } }); @@ -383,7 +420,9 @@ public class TreeContactList */ public void metaContactRenamed(MetaContactRenamedEvent evt) { - final MetaContact metaContact = evt.getSourceMetaContact(); + final UIContact uiContact + = MetaContactListSource.getUIContact( + evt.getSourceMetaContact()); SwingUtilities.invokeLater(new Runnable() { @@ -395,11 +434,9 @@ public class TreeContactList // the temporary model. synchronized (tempTreeModel) { - if (currentFilter.isMatching(metaContact)) + if (uiContact != null) { - ContactNode contactNode - = treeModel.findContactNodeByMetaContact( - metaContact); + ContactNode contactNode = uiContact.getContactNode(); if (contactNode != null) treeModel.nodeChanged(contactNode); @@ -416,7 +453,9 @@ public class TreeContactList */ public void metaContactAvatarUpdated(MetaContactAvatarUpdateEvent evt) { - final MetaContact metaContact = evt.getSourceMetaContact(); + final UIContact uiContact + = MetaContactListSource.getUIContact( + evt.getSourceMetaContact()); SwingUtilities.invokeLater(new Runnable() { @@ -428,11 +467,9 @@ public class TreeContactList // the temporary model. synchronized (tempTreeModel) { - if (currentFilter.isMatching(metaContact)) + if (uiContact != null) { - ContactNode contactNode - = treeModel.findContactNodeByMetaContact( - metaContact); + ContactNode contactNode = uiContact.getContactNode(); if (contactNode != null) treeModel.nodeChanged(contactNode); @@ -450,22 +487,34 @@ public class TreeContactList */ public void protoContactAdded(ProtoContactEvent evt) { - MetaContact parent = evt.getNewParent(); + final MetaContact metaContact = evt.getNewParent(); - // We synchronize the matching and all MetaContactListener - // events on the tempTreeModel in order to prevent modification - // to be done on the actual treeModel while we're working with - // the temporary model. - synchronized (tempTreeModel) - { - ContactNode contactNode - = treeModel.findContactNodeByMetaContact(parent); + final UIContact parentUIContact + = MetaContactListSource.getUIContact(metaContact); - if (contactNode == null && currentFilter.isMatching(parent)) + SwingUtilities.invokeLater(new Runnable() + { + public void run() { - addContact(parent); + // We synchronize the matching and all MetaContactListener + // events on the tempTreeModel in order to prevent modification + // to be done on the actual treeModel while we're working with + // the temporary model. + synchronized (tempTreeModel) + { + if (parentUIContact == null) + { + UIContact uiContact + = MetaContactListSource.createUIContact(metaContact); + + if (currentFilter.isMatching(uiContact)) + addContact(uiContact); + else + MetaContactListSource.removeUIContact(metaContact); + } + } } - } + }); } public void protoContactModified(ProtoContactEvent evt) {} @@ -478,35 +527,46 @@ public class TreeContactList */ public void protoContactMoved(ProtoContactEvent evt) { - // Remove old parent if not matching. - MetaContact oldParent = evt.getOldParent(); + final MetaContact oldParent = evt.getOldParent(); + final MetaContact newParent = evt.getNewParent(); - // We synchronize the matching and all MetaContactListener - // events on the tempTreeModel in order to prevent modification - // to be done on the actual treeModel while we're working with - // the temporary model. - synchronized (tempTreeModel) + SwingUtilities.invokeLater(new Runnable() { - ContactNode oldContactNode - = treeModel.findContactNodeByMetaContact(oldParent); - - if (oldContactNode != null && !currentFilter.isMatching(oldParent)) + public void run() { - removeContact(oldParent, - oldParent.getParentMetaContactGroup()); - } + // We synchronize the matching and all MetaContactListener + // events on the tempTreeModel in order to prevent modification + // to be done on the actual treeModel while we're working with + // the temporary model. + synchronized (tempTreeModel) + { + final UIContact oldUIContact + = MetaContactListSource.getUIContact(oldParent); + + // Remove old parent if not matching. + if (oldUIContact != null + && !currentFilter.isMatching(oldUIContact)) + { + removeContact(oldUIContact); + } - // Add new parent if matching. - MetaContact newParent = evt.getNewParent(); + // Add new parent if matching. + UIContact newUIContact + = MetaContactListSource.getUIContact(newParent); - ContactNode newContactNode - = treeModel.findContactNodeByMetaContact(newParent); + if (newUIContact == null) + { + UIContact uiContact + = MetaContactListSource.createUIContact(newParent); - if (newContactNode == null && currentFilter.isMatching(newParent)) - { - addContact(newParent); + if (currentFilter.isMatching(uiContact)) + addContact(uiContact); + else + MetaContactListSource.removeUIContact(newParent); + } + } } - } + }); } /** @@ -517,23 +577,26 @@ public class TreeContactList */ public void protoContactRemoved(ProtoContactEvent evt) { - MetaContact oldParent = evt.getOldParent(); + final MetaContact oldParent = evt.getOldParent(); - // We synchronize the matching and all MetaContactListener - // events on the tempTreeModel in order to prevent modification - // to be done on the actual treeModel while we're working with - // the temporary model. - synchronized (tempTreeModel) + SwingUtilities.invokeLater(new Runnable() { - ContactNode contactNode - = treeModel.findContactNodeByMetaContact(oldParent); - - if (contactNode != null && !currentFilter.isMatching(oldParent)) + public void run() { - removeContact(oldParent, - oldParent.getParentMetaContactGroup()); + // We synchronize the matching and all MetaContactListener + // events on the tempTreeModel in order to prevent modification + // to be done on the actual treeModel while we're working with + // the temporary model. + synchronized (tempTreeModel) + { + UIContact oldUIContact + = MetaContactListSource.getUIContact(oldParent); + + if (oldUIContact != null) + removeContact(oldUIContact); + } } - } + }); } /** @@ -574,7 +637,8 @@ public class TreeContactList synchronized (tempTreeModel) { ContactNode contactNode - = treeModel.findContactNodeByMetaContact(metaContact); + = MetaContactListSource.getUIContact(metaContact) + .getContactNode(); if (contactNode != null) { @@ -586,7 +650,7 @@ public class TreeContactList // SystrayService stray = GuiActivator.getSystrayService(); // // if (stray != null) -// stray.setSystrayIcon(SystrayService.ENVELOPE_IMG_TYPE); +// stray.setSystrayIcon(SystrayService.ENVELOPE_IMG_TYPE); } else activeContacts.remove(contactNode); @@ -599,14 +663,13 @@ public class TreeContactList /** * Returns <tt>true</tt> if the given <tt>metaContact</tt> has been * previously set to active, otherwise returns <tt>false</tt>. - * @param metaContact the <tt>MetaContact</tt> to check + * @param contact the <tt>UIContact</tt> to check * @return <tt>true</tt> if the given <tt>metaContact</tt> has been * previously set to active, otherwise returns <tt>false</tt> */ - public boolean isContactActive(MetaContact metaContact) + public boolean isContactActive(UIContact contact) { - ContactNode contactNode - = treeModel.findContactNodeByMetaContact(metaContact); + ContactNode contactNode = contact.getContactNode(); if (contactNode != null) return contactNode.isActive(); @@ -614,39 +677,49 @@ public class TreeContactList } /** - * Adds the given <tt>MetaContact</tt> to this list default tree model and - * refreshes the tree view. - * @param metaContact the <tt>MetaContact</tt> to add + * Adds the given <tt>contact</tt> to this list. + * @param contact the <tt>UIContact</tt> to add */ - private void addContact(MetaContact metaContact) + public void addContact(UIContact contact) { - addContact(treeModel, metaContact, true); + addContact(treeModel, contact, true, true); } /** - * Adds the given <tt>metaContact</tt> to the given <tt>treeModel</tt>, by - * specifying if the view should be refreshed. - * @param treeModel the <tt>ContactListTreeModel</tt> to which to add the - * given <tt>metaContact</tt> - * @param metaContact the <tt>MetaContact</tt> to add + * Adds the given <tt>contact</tt> to this list. + * @param treeModel the <tt>ContactListTreeModel</tt>, to which this contact + * should be added + * @param contact the <tt>UIContact</tt> to add + * @param isSorted indicates if the contact should be sorted regarding to + * the <tt>GroupNode</tt> policy * @param isRefreshView indicates if the view should be refreshed after - * the contact adding + * adding the contact */ - private void addContact(ContactListTreeModel treeModel, - MetaContact metaContact, + public void addContact( ContactListTreeModel treeModel, + UIContact contact, + boolean isSorted, boolean isRefreshView) { - MetaContactGroup metaGroup = metaContact.getParentMetaContactGroup(); + UIGroup group = contact.getParentGroup(); - GroupNode groupNode = treeModel.findGroupNodeByMetaGroup(metaGroup); + GroupNode groupNode; + if (group == null) + groupNode = treeModel.getRoot(); + else + { + groupNode = group.getGroupNode(); - if (groupNode == null) - groupNode = addGroup(treeModel, metaGroup, isRefreshView); + if (groupNode == null) + groupNode = addGroup(treeModel, group, isRefreshView); + } - groupNode.sortedAddMetaContact(treeModel, metaContact, isRefreshView); + if (isSorted) + groupNode.sortedAddContact(contact, isRefreshView); + else + groupNode.addContact(contact); - if (currentFilter.equals(presenceFilter) - && !groupNode.isCollapsed() + if ((!currentFilter.equals(presenceFilter) + || !groupNode.isCollapsed()) && isRefreshView) this.expandGroup(treeModel, groupNode); } @@ -654,21 +727,22 @@ public class TreeContactList /** * Removes the node corresponding to the given <tt>MetaContact</tt> from * this list. - * @param metaContact the <tt>MetaContact</tt> to remove - * @param parentMetaGroup the <tt>MetaContactGroup</tt> that is the parent - * of this <tt>metaContact</tt> + * @param contact the <tt>UIContact</tt> to remove */ - private void removeContact( MetaContact metaContact, - MetaContactGroup parentMetaGroup) + public void removeContact(UIContact contact) { - GroupNode parentGroupNode - = treeModel.findGroupNodeByMetaGroup(parentMetaGroup); + UIGroup parentGroup = contact.getParentGroup(); + + if (parentGroup == null) + return; + + GroupNode parentGroupNode = parentGroup.getGroupNode(); // Nothing more to do here if we didn't find the parent. if (parentGroupNode == null) return; - parentGroupNode.removeMetaContact(treeModel, metaContact); + parentGroupNode.removeContact(contact); // If the parent group is empty remove it. if (parentGroupNode.getChildCount() < 1 @@ -680,38 +754,44 @@ public class TreeContactList /** * Adds the given group to this list. - * @param metaGroup the <tt>MetaContactGroup</tt> to add + * @param group the <tt>UIGroup</tt> to add * @return the created <tt>GroupNode</tt> corresponding to the group */ - private GroupNode addGroup(MetaContactGroup metaGroup) + public GroupNode addGroup(UIGroup group) { - return addGroup(treeModel, metaGroup, true); + return addGroup(treeModel, group, true); } /** - * Adds the given <tt>metaGroup</tt> to the given <tt>treeModel</tt>. - * @param treeModel the <tt>ContactListTreeModel</tt>, to which to add the - * given <tt>metaGroup</tt> - * @param metaGroup the <tt>MetaContactGroup</tt> to add - * @param isRefreshView indicates if the view should be refresh after the - * group adding + * Adds the given group to this list. + * @param treeModel the <tt>ContactListTreeModel</tt>, to which the given + * <tt>group</tt> should be added + * @param group the <tt>UIGroup</tt> to add + * @param isRefreshView indicates if the view should be refreshed after + * adding the group * @return the created <tt>GroupNode</tt> corresponding to the group */ - private GroupNode addGroup( ContactListTreeModel treeModel, - MetaContactGroup metaGroup, + public GroupNode addGroup( ContactListTreeModel treeModel, + UIGroup group, boolean isRefreshView) { - MetaContactGroup parentGroup = metaGroup.getParentMetaContactGroup(); + UIGroup parentGroup + = group.getParentGroup(); - GroupNode parentGroupNode - = treeModel.findGroupNodeByMetaGroup(parentGroup); - - GroupNode groupNode = null; - if (parentGroupNode == null) - addGroup(parentGroup); + GroupNode parentGroupNode; + if (parentGroup == null) + parentGroupNode = treeModel.getRoot(); else - groupNode = parentGroupNode - .sortedAddMetaContactGroup(treeModel, metaGroup, isRefreshView); + { + parentGroupNode = parentGroup.getGroupNode(); + + if (parentGroupNode == null) + parentGroupNode + = addGroup(treeModel, parentGroup, isRefreshView); + } + + GroupNode groupNode = parentGroupNode + .sortedAddContactGroup(group, isRefreshView); expandPath(new TreePath(treeModel.getRoot().getPath())); @@ -720,147 +800,26 @@ public class TreeContactList /** * Removes the given group and its children from the list. - * @param metaGroup the <tt>MetaContactGroup</tt> to remove + * @param group the <tt>UIGroup</tt> to remove */ - private void removeGroup(MetaContactGroup metaGroup) + private void removeGroup(UIGroup group) { - MetaContactGroup parentGroup = metaGroup.getParentMetaContactGroup(); + UIGroup parentGroup = group.getParentGroup(); GroupNode parentGroupNode - = treeModel.findGroupNodeByMetaGroup(parentGroup); + = parentGroup.getGroupNode(); // Nothing more to do here if we didn't find the parent. if (parentGroupNode == null) return; - parentGroupNode.removeMetaContactGroup(treeModel, metaGroup); + parentGroupNode.removeContactGroup(group); // If the parent group is empty remove it. if (parentGroupNode.getChildCount() < 1) treeModel.removeNodeFromParent(parentGroupNode); } - private void addAllMatching(ContactListTreeModel treeModel) - { - addMatching(treeModel, - GuiActivator.getContactListService().getRoot()); - } - - /** - * Removes all contacts contained in the given <tt>MetaContactGroup</tt> not - * matching the current filter. - * @param metaGroup the <tt>MetaContactGroup</tt>, which unmatching contacts - * to remove - */ - private void removeUnmatching(MetaContactGroup metaGroup) - { - Iterator<MetaContact> childContacts = metaGroup.getChildContacts(); - - while(childContacts.hasNext()) - { - MetaContact metaContact = childContacts.next(); - if(!currentFilter.isMatching(metaContact)) - removeContact(metaContact, - metaContact.getParentMetaContactGroup()); - } - - Iterator<MetaContactGroup> subgroups = metaGroup.getSubgroups(); - while(subgroups.hasNext()) - { - removeUnmatching(subgroups.next()); - } - } - - /** - * Adds all contacts contained in the given <tt>MetaContactGroup</tt> - * matching the current filter and not contained in the contact list. - * @param treeModel the tree model to add to - * @param metaGroup the <tt>MetaContactGroup</tt>, which matching contacts - * to add - */ - private void addMatching( ContactListTreeModel treeModel, - MetaContactGroup metaGroup) - { - Iterator<MetaContact> childContacts = metaGroup.getChildContacts(); - - // Check also the isFiltering variable in order to be sure that we - // should continue with this filtering or it has been interrupted and - // a new one is scheduled. - while(childContacts.hasNext() && isFiltering) - { - MetaContact metaContact = childContacts.next(); - - if(currentFilter.isMatching(metaContact)) - addContact(treeModel, metaContact, false); - } - - Iterator<MetaContactGroup> subgroups = metaGroup.getSubgroups(); - - // Check also the isFiltering variable in order to be sure that we - // should continue with this filtering or it has been interrupted and - // a new one is scheduled. - while(subgroups.hasNext() && isFiltering) - { - MetaContactGroup subgroup = subgroups.next(); - - if (subgroup.countChildContacts() == 0 - && subgroup.countSubgroups() == 0 - && currentFilter.isMatching(subgroup)) - addGroup(treeModel, subgroup, false); - else - addMatching(treeModel, subgroup); - } - } - - /** - * Indicates that a contact has changed its status. - * - * @param evt the presence event containing information about the - * contact status change - */ - public void contactPresenceStatusChanged( - final ContactPresenceStatusChangeEvent evt) - { - final Contact sourceContact = evt.getSourceContact(); - - final MetaContact metaContact = GuiActivator.getContactListService() - .findMetaContactByContact(sourceContact); - - if (metaContact == null - || (evt.getOldStatus() == evt.getNewStatus())) - return; - - SwingUtilities.invokeLater(new Runnable() - { - public void run() - { - // We synchronize the matching and all MetaContactListener - // events on the tempTreeModel in order to prevent modification - // to be done on the actual treeModel while we're working with - // the temporary model. - synchronized (tempTreeModel) - { - ContactNode contactNode = treeModel - .findContactNodeByMetaContact(metaContact); - - if (contactNode == null - && currentFilter.isMatching(metaContact)) - { - addContact(metaContact); - } - else if (contactNode != null) - { - if (!currentFilter.isMatching(metaContact)) - removeContact(metaContact, - metaContact.getParentMetaContactGroup()); - else - treeModel.nodeChanged(contactNode); - } - } - } - }); - } - /** * Adds a listener for <tt>ContactListEvent</tt>s. * @@ -904,13 +863,25 @@ public class TreeContactList */ public void stopFiltering() { + currentFilter.stopFilter(); this.isFiltering = false; } /** - * Applies the given <tt>filter</tt>. This is a filter over the content of - * the contact list. - * @param filter the new filter to apply + * Applies the default filter. + * @return <tt>true</tt> to indicate that the filter has found a match, + * <tt>false</tt> if no matches were found and the contact list is then + * empty. + */ + public boolean applyDefaultFilter() + { + return applyFilter(defaultFilter); + } + + /** + * Applies the given <tt>filter</tt> and changes the content of the + * contact list according to it. + * @param filter the new filter to set * @return <tt>true</tt> to indicate that the filter has found a match, * <tt>false</tt> if no matches were found and the contact list is then * empty. @@ -924,15 +895,16 @@ public class TreeContactList if (currentFilter == null || !currentFilter.equals(filter)) this.currentFilter = filter; - tempTreeModel = new ContactListTreeModel( - GuiActivator.getContactListService().getRoot()); + tempTreeModel = new ContactListTreeModel(); // We synchronize the matching and all MetaContactListener events on // the searchTreeModel in order to prevent modification to be done on // the actual treeModel while we're working with the temporary model. synchronized (tempTreeModel) { - addAllMatching(tempTreeModel); + treeModel.clearDependencies(); + + currentFilter.applyFilter(tempTreeModel); treeModel = tempTreeModel; } @@ -977,6 +949,15 @@ public class TreeContactList } /** + * Sets the default filter to the given <tt>filter</tt>. + * @param filter the <tt>ContactListFilter</tt> to set as default + */ + public void setDefaultFilter(ContactListFilter filter) + { + this.defaultFilter = filter; + } + + /** * Returns the currently applied filter. * @return the currently applied filter */ @@ -1035,9 +1016,10 @@ public class TreeContactList } /** - * - * @param contactListListeners - * @param event + * Notifies all interested listeners that a <tt>ContactListEvent</tt> has + * occurred. + * @param contactListListeners the list of listeners to notify + * @param event the <tt>ContactListEvent</tt> to trigger */ protected void fireContactListEvent( java.util.List<ContactListListener> contactListListeners, @@ -1052,11 +1034,8 @@ public class TreeContactList case ContactListEvent.CONTACT_CLICKED: listener.contactClicked(event); break; - case ContactListEvent.PROTOCOL_CONTACT_CLICKED: - listener.protocolContactClicked(event); - break; case ContactListEvent.GROUP_CLICKED: - listener.groupSelected(event); + listener.groupClicked(event); break; default: logger.error("Unknown event type " + event.getEventID()); @@ -1089,11 +1068,13 @@ public class TreeContactList { TreePath treePath = getPathForRow(i); + Object c = treePath.getLastPathComponent(); + GroupNode groupNode = null; - if (!(treePath.getLastPathComponent() instanceof GroupNode)) + if (!(c instanceof GroupNode)) continue; else - groupNode = (GroupNode) treePath.getLastPathComponent(); + groupNode = (GroupNode) c; if (groupNode != null && (currentFilter.equals(searchFilter) @@ -1138,13 +1119,13 @@ public class TreeContactList if (lastComponent instanceof ContactNode) { fireContactListEvent( - ((ContactNode)lastComponent).getMetaContact(), + ((ContactNode) lastComponent).getContactDescriptor(), ContactListEvent.CONTACT_CLICKED, e.getClickCount()); } else if (lastComponent instanceof GroupNode) { fireContactListEvent( - ((GroupNode)lastComponent).getMetaContactGroup(), + ((GroupNode) lastComponent).getGroupDescriptor(), ContactListEvent.GROUP_CLICKED, e.getClickCount()); } @@ -1197,37 +1178,35 @@ public class TreeContactList // Open message window, right button menu when mouse is pressed. if (lastComponent instanceof ContactNode) { - ContactNode contactNode = (ContactNode) lastComponent; + UIContact uiContact + = ((ContactNode) lastComponent).getContactDescriptor(); fireContactListEvent( - contactNode.getMetaContact(), + uiContact, ContactListEvent.CONTACT_CLICKED, e.getClickCount()); - // Right click and Ctrl+LeftClick on the contact label opens - // Popup menu if ((e.getModifiers() & InputEvent.BUTTON3_MASK) != 0 - || (e.isControlDown() && !e.isMetaDown())) + || (e.isControlDown() && !e.isMetaDown())) { - rightButtonMenu = new ContactRightButtonMenu( - contactNode.getMetaContact(), this); + rightButtonMenu = uiContact.getRightButtonMenu(); openRightButtonMenu(e.getPoint()); } } else if (lastComponent instanceof GroupNode) { - GroupNode groupNode = (GroupNode) lastComponent; + UIGroup uiGroup + = ((GroupNode) lastComponent).getGroupDescriptor(); fireContactListEvent( - groupNode.getMetaContactGroup(), + uiGroup, ContactListEvent.GROUP_CLICKED, e.getClickCount()); if ((e.getModifiers() & InputEvent.BUTTON3_MASK) != 0 - || (e.isControlDown() && !e.isMetaDown())) + || (e.isControlDown() && !e.isMetaDown())) { - rightButtonMenu = new GroupRightButtonMenu( - GuiActivator.getUIService().getMainFrame(), - groupNode.getMetaContactGroup()); + rightButtonMenu = uiGroup.getRightButtonMenu(); + openRightButtonMenu(e.getPoint()); } } @@ -1313,9 +1292,10 @@ public class TreeContactList && currentFilter.equals(presenceFilter)) { GroupNode groupNode = (GroupNode) collapsedNode; - ConfigurationManager - .setContactListGroupCollapsed( - groupNode.getMetaContactGroup().getMetaUID(), true); + String id = groupNode.getGroupDescriptor().getId(); + if (id != null) + ConfigurationManager + .setContactListGroupCollapsed(id, true); } } @@ -1334,9 +1314,10 @@ public class TreeContactList && currentFilter.equals(presenceFilter)) { GroupNode groupNode = (GroupNode) collapsedNode; - ConfigurationManager - .setContactListGroupCollapsed( - groupNode.getMetaContactGroup().getMetaUID(), false); + String id = groupNode.getGroupDescriptor().getId(); + if (id != null) + ConfigurationManager + .setContactListGroupCollapsed(id, false); } } @@ -1442,21 +1423,23 @@ public class TreeContactList /** * Starts a chat with the currently selected contact if any, otherwise - * nothing happens. + * nothing happens. A chat is started with only <tt>MetaContact</tt>s for + * now. */ public void startSelectedContactChat() { TreePath selectionPath = getSelectionPath(); if (selectionPath != null - && selectionPath.getLastPathComponent() - instanceof ContactNode) + && selectionPath.getLastPathComponent() instanceof ContactNode) { - ContactNode contactNode - = (ContactNode) selectionPath.getLastPathComponent(); + UIContact uiContact + = ((ContactNode) selectionPath.getLastPathComponent()) + .getContactDescriptor(); - GuiActivator.getUIService().getChatWindowManager() - .startChat(contactNode.getMetaContact()); + if (uiContact instanceof MetaUIContact) + GuiActivator.getUIService().getChatWindowManager() + .startChat((MetaContact) uiContact.getDescriptor()); } } @@ -1494,4 +1477,121 @@ public class TreeContactList setRowHeight(0); setToggleClickCount(1); } + + /** + * Indicates that a node has been changed. Transfers the event to the + * default tree model. + * @param node the <tt>TreeNode</tt> that has been refreshed + */ + public void nodeChanged(TreeNode node) + { + treeModel.nodeChanged(node); + } + + /** + * Initializes the list of available contact sources for this contact list. + */ + private void initContactSources() + { + for (ContactSourceService contactSource + : GuiActivator.getContactSources()) + { + contactSources.add(new ExternalContactSource(contactSource)); + } + GuiActivator.bundleContext.addServiceListener( + new ContactSourceServiceListener()); + } + + /** + * Returns the list of registered contact sources to search in. + * @return the list of registered contact sources to search in + */ + public static Collection<ExternalContactSource> getContactSources() + { + return contactSources; + } + + /** + * Returns the <tt>ExternalContactSource</tt> corresponding to the given + * <tt>ContactSourceService</tt>. + * @param contactSource the <tt>ContactSourceService</tt>, which + * corresponding external source implementation we're looking for + * @return the <tt>ExternalContactSource</tt> corresponding to the given + * <tt>ContactSourceService</tt> + */ + public static ExternalContactSource getContactSource( + ContactSourceService contactSource) + { + Iterator<ExternalContactSource> extSourcesIter + = contactSources.iterator(); + + while (extSourcesIter.hasNext()) + { + ExternalContactSource extSource = extSourcesIter.next(); + + if (extSource.getContactSourceService().equals(contactSource)) + return extSource; + } + return null; + } + + /** + * Returns the contact source with the given identifier. + * @param identifier the identifier we're looking for + * @return the contact source with the given identifier + */ + public static ExternalContactSource getContactSource(String identifier) + { + Iterator<ExternalContactSource> extSourcesIter + = contactSources.iterator(); + + while (extSourcesIter.hasNext()) + { + ExternalContactSource extSource = extSourcesIter.next(); + + if (extSource.getContactSourceService().getIdentifier() + .equals(identifier)) + return extSource; + } + return null; + } + + /** + * Listens for adding and removing of <tt>ContactSourceService</tt> + * implementations. + */ + private class ContactSourceServiceListener + implements ServiceListener + { + public void serviceChanged(ServiceEvent event) + { + ServiceReference serviceRef = event.getServiceReference(); + + // if the event is caused by a bundle being stopped, we don't want + // to know + if (serviceRef.getBundle().getState() == Bundle.STOPPING) + return; + + Object service = GuiActivator.bundleContext.getService(serviceRef); + + // we don't care if the source service is + // not a contact source service + if (!(service instanceof ContactSourceService)) + return; + + switch (event.getType()) + { + case ServiceEvent.REGISTERED: + contactSources.add( + new ExternalContactSource((ContactSourceService) service)); + break; + case ServiceEvent.UNREGISTERING: + ExternalContactSource cSource + = getContactSource((ContactSourceService) service); + if (cSource != null) + contactSources.remove(cSource); + break; + } + } + } } diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/UIContact.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/UIContact.java new file mode 100644 index 0000000..6f8c92a --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/UIContact.java @@ -0,0 +1,128 @@ +/* + * 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.contactlist; + +import java.util.*; +import java.util.List; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.service.protocol.*; + +/** + * The <tt>UIContact</tt> represents the user interface contact contained in the + * contact list component. + * + * @author Yana Stamcheva + */ +public interface UIContact +{ + /** + * Returns the descriptor of this contact. + * @return the descriptor of this contact + */ + public Object getDescriptor(); + + /** + * Returns the display name of this contact. + * @return the display name of this contact + */ + public String getDisplayName(); + + /** + * Returns the display details of this contact. These would be shown + * whenever the contact is selected. + * @return the display details of this contact + */ + public String getDisplayDetails(); + + /** + * Returns the index of this contact in its source. + * @return the source index + */ + public int getSourceIndex(); + + /** + * Returns the avatar of this contact. + * @param isSelected indicates if the contact is selected + * @param width the width of the avatar + * @param height the height of the avatar + * @return the avatar of this contact + */ + public ImageIcon getAvatar(boolean isSelected, int width, int height); + + /** + * Returns the status icon of this contact or null if no status is + * available. + * @return the status icon of this contact or null if no status is + * available + */ + public ImageIcon getStatusIcon(); + + /** + * Creates a tool tip for this contact. If such tooltip is + * provided it would be shown on mouse over over this <tt>UIContact</tt>. + * @return the tool tip for this contact descriptor + */ + public ExtendedTooltip getToolTip(); + + /** + * Returns the right button menu component. + * @return the right button menu component + */ + public JPopupMenu getRightButtonMenu(); + + /** + * Returns the parent group. + * @return the parent group + */ + public UIGroup getParentGroup(); + + /** + * Returns an <tt>Iterator</tt> over a list of the search strings of this + * contact. + * @return an <tt>Iterator</tt> over a list of the search strings of this + * contact + */ + public Iterator<String> getSearchStrings(); + + /** + * Returns the corresponding <tt>ContactNode</tt>. The <tt>ContactNode</tt> + * is the real node that is stored in the contact list component data model. + * @return the corresponding <tt>ContactNode</tt> + */ + public ContactNode getContactNode(); + + /** + * Sets the given <tt>contactNode</tt>. The <tt>ContactNode</tt> + * is the real node that is stored in the contact list component data model. + * @param contactNode the <tt>ContactNode</tt> that corresponds to this + * <tt>UIGroup</tt> + */ + public void setContactNode(ContactNode contactNode); + + /** + * Returns the default <tt>ContactDetail</tt> to use for any operations + * depending to the given <tt>OperationSet</tt> class. + * @param opSetClass the <tt>OperationSet</tt> class we're interested in + * @return the default <tt>ContactDetail</tt> to use for any operations + * depending to the given <tt>OperationSet</tt> class + */ + public UIContactDetail getDefaultContactDetail( + Class<? extends OperationSet> opSetClass); + + /** + * Returns a list of all <tt>UIContactDetail</tt>s corresponding to the + * given <tt>OperationSet</tt> class. + * @param opSetClass the <tt>OperationSet</tt> class we're looking for + * @return a list of all <tt>UIContactDetail</tt>s corresponding to the + * given <tt>OperationSet</tt> class + */ + public List<UIContactDetail> getContactDetailsForOperationSet( + Class<? extends OperationSet> opSetClass); +} diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/UIContactDetail.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/UIContactDetail.java new file mode 100644 index 0000000..3011735 --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/UIContactDetail.java @@ -0,0 +1,90 @@ +/* + * 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.contactlist; + +import net.java.sip.communicator.service.protocol.*; + +/** + * The <tt>UIContactDetail</tt> corresponds to a particular contact detail, + * phone number, IM identifier, email, etc. which has it's preferred mode of + * transport <tt>ProtocolProviderService</tt>. + * + * @author Yana Stamcheva + */ +public abstract class UIContactDetail +{ + /** + * The address of this detail. + */ + private final String address; + + /** + * The display name of this detail. + */ + private final String displayName; + + /** + * The <tt>ProtocolProviderService</tt> corresponding to this detail. + */ + private final ProtocolProviderService protocolProvider; + + /** + * Creates a <tt>UIContactDetail</tt> by specifying the contact + * <tt>address</tt>, the <tt>displayName</tt> and <tt>preferredProvider</tt>. + * @param address the contact address + * @param displayName the contact display name + * @param preferredProvider the preferred protocol provider + */ + public UIContactDetail( + String address, + String displayName, + ProtocolProviderService preferredProvider) + { + this.address = address; + this.displayName = displayName; + this.protocolProvider = preferredProvider; + } + + /** + * Returns the display name of this detail. + * @return the display name of this detail + */ + public String getDisplayName() + { + return displayName; + } + + /** + * Returns the address of this detail. + * @return the address of this detail + */ + public String getAddress() + { + return address; + } + + /** + * Returns the protocol provider preferred for contacting this detail for + * the given <tt>OperationSet</tt> class. + * @param opSetClass the <tt>OperationSet</tt> class for which we're looking + * for provider + * @return the protocol provider preferred for contacting this detail + */ + public ProtocolProviderService getPreferredProtocolProvider( + Class<? extends OperationSet> opSetClass) + { + return protocolProvider; + } + + /** + * Returns the <tt>PresenceStatus</tt> of this <tt>ContactDetail</tt> or + * null if the detail doesn't support presence. + * @return the <tt>PresenceStatus</tt> of this <tt>ContactDetail</tt> or + * null if the detail doesn't support presence + */ + public abstract PresenceStatus getPresenceStatus(); +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/UIGroup.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/UIGroup.java new file mode 100644 index 0000000..baf57e2 --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/UIGroup.java @@ -0,0 +1,89 @@ +/* + * 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.contactlist; + +import javax.swing.*; + +/** + * The <tt>UIGroup</tt> represents the user interface contact list group. + * + * @author Yana Stamcheva + */ +public interface UIGroup +{ + /** + * Returns the descriptor of the group. This would be the underlying object + * that should provide all other necessary information for the group. + * @return the descriptor of the group + */ + public Object getDescriptor(); + + /** + * The display name of the group. The display name is the name to be shown + * in the contact list group row. + * @return the display name of the group + */ + public String getDisplayName(); + + /** + * Returns the index of this group in its source. In other words this is + * the descriptor index. + * @return the index of this group in its source + */ + public int getSourceIndex(); + + /** + * Returns the parent group. + * @return the parent group + */ + public UIGroup getParentGroup(); + + /** + * Indicates if the group is collapsed or expanded. + * @return <tt>true</tt> to indicate that the group is collapsed, + * <tt>false</tt> to indicate that it's expanded + */ + public boolean isGroupCollapsed(); + + /** + * Returns the count of online child contacts. + * @return the count of online child contacts + */ + public int countOnlineChildContacts(); + + /** + * Returns the child contacts count. + * @return child contacts count + */ + public int countChildContacts(); + + /** + * Returns the identifier of this group. + * @return the identifier of this group + */ + public String getId(); + + /** + * Returns the <tt>GroupNode</tt> corresponding to this <tt>UIGroup</tt>. + * The is the actual node used in the contact list component data model. + * @return the <tt>GroupNode</tt> corresponding to this <tt>UIGroup</tt> + */ + public GroupNode getGroupNode(); + + /** + * Sets the <tt>GroupNode</tt> corresponding to this <tt>UIGroup</tt>. + * @param groupNode the <tt>GroupNode</tt> to set. The is the actual + * node used in the contact list component data model. + */ + public void setGroupNode(GroupNode groupNode); + + /** + * Returns the right button menu for this group. + * @return the right button menu component for this group + */ + public JPopupMenu getRightButtonMenu(); +} diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/UnknownContactPanel.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/UnknownContactPanel.java index 5ff7aa0..9db2fb0 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/UnknownContactPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/UnknownContactPanel.java @@ -85,7 +85,7 @@ public class UnknownContactPanel if (searchText == null) return; - Vector<ProtocolProviderService> telephonyProviders + List<ProtocolProviderService> telephonyProviders = CallManager.getTelephonyProviders(); if (telephonyProviders.size() == 1) diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/ExternalContactSource.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/ExternalContactSource.java new file mode 100644 index 0000000..f631b86 --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/ExternalContactSource.java @@ -0,0 +1,201 @@ +/* + * 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.contactlist.contactsource; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.main.contactlist.*; +import net.java.sip.communicator.service.contactsource.*; + +/** + * The <tt>ExternalContactSource</tt> is the UI abstraction of the + * <tt>ContactSourceService</tt>. + * + * @author Yana Stamcheva + */ +public class ExternalContactSource +{ + /** + * The <tt>SourceUIGroup</tt> containing all contacts from this source. + */ + private final SourceUIGroup sourceUIGroup; + + private final ContactSourceService contactSource; + + /** + * Creates an <tt>ExternalContactSource</tt> based on the given + * <tt>ContactSourceService</tt>. + * @param contactSource the <tt>ContactSourceService</tt>, on which this + * <tt>ExternalContactSource</tt> is based + */ + public ExternalContactSource(ContactSourceService contactSource) + { + this.contactSource = contactSource; + sourceUIGroup + = new SourceUIGroup(contactSource.getDisplayName()); + } + + /** + * Returns the corresponding <tt>ContactSourceService</tt>. + * @return the corresponding <tt>ContactSourceService</tt> + */ + public ContactSourceService getContactSourceService() + { + return contactSource; + } + + /** + * Returns the UI group for this contact source. There's only one group + * descriptor per external source. + * @return the group descriptor + */ + public UIGroup getUIGroup() + { + return sourceUIGroup; + } + + /** + * Returns the <tt>UIContact</tt> corresponding to the given + * <tt>sourceContact</tt>. + * @param sourceContact the <tt>SourceContact</tt>, for which we search a + * corresponding <tt>UIContact</tt> + * @return the <tt>UIContact</tt> corresponding to the given + * <tt>sourceContact</tt> + */ + public UIContact getUIContact(SourceContact sourceContact) + { + return new SourceUIContact(sourceContact, sourceUIGroup); + } + + /** + * The <tt>SourceUIGroup</tt> is the implementation of the UIGroup for the + * <tt>ExternalContactSource</tt>. It takes the name of the source and + * sets it as a group name. + */ + private class SourceUIGroup + implements UIGroup + { + /** + * The display name of the group. + */ + private final String displayName; + + /** + * The corresponding group node. + */ + private GroupNode groupNode; + + /** + * Creates an instance of <tt>SourceUIGroup</tt>. + * @param name the name of the group + */ + public SourceUIGroup(String name) + { + this.displayName = name; + } + + /** + * Returns null to indicate that this group doesn't have a parent group + * and can be added directly to the root group. + * @return null + */ + public UIGroup getParentGroup() + { + return null; + } + + /** + * Returns -1 to indicate that this group doesn't have a source index. + * @return -1 + */ + public int getSourceIndex() + { + return -1; + } + + /** + * Returns <tt>false</tt> to indicate that this group is always opened. + * @return false + */ + public boolean isGroupCollapsed() + { + return false; + } + + /** + * Returns the display name of this group. + * @return the display name of this group + */ + public String getDisplayName() + { + return displayName; + } + + /** + * Returns -1 to indicate that the child count is unknown. + * @return -1 + */ + public int countChildContacts() + { + return -1; + } + + /** + * Returns -1 to indicate that the child count is unknown. + * @return -1 + */ + public int countOnlineChildContacts() + { + return -1; + } + + /** + * Returns the display name of the group. + * @return the display name of the group + */ + public Object getDescriptor() + { + return displayName; + } + + /** + * Returns null to indicate that this group doesn't have an identifier. + * @return null + */ + public String getId() + { + return null; + } + + /** + * Returns the corresponding <tt>GroupNode</tt>. + * @return the corresponding <tt>GroupNode</tt> + */ + public GroupNode getGroupNode() + { + return groupNode; + } + + /** + * Sets the given <tt>groupNode</tt>. + * @param groupNode the <tt>GroupNode</tt> to set + */ + public void setGroupNode(GroupNode groupNode) + { + this.groupNode = groupNode; + } + + /** + * Returns the right button menu for this group. + * @return null + */ + public JPopupMenu getRightButtonMenu() + { + return null; + } + } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/MetaContactListSource.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/MetaContactListSource.java new file mode 100644 index 0000000..fa3181e --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/MetaContactListSource.java @@ -0,0 +1,244 @@ +/* + * 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.contactlist.contactsource; + +import java.util.*; +import java.util.regex.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.main.contactlist.*; +import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.protocol.*; + +/** + * The <tt>MetaContactListSource</tt> is an abstraction of the + * <tt>MetaContactListService</tt>, which makes the correspondence between a + * <tt>MetaContact</tt> and an <tt>UIContact</tt> and between a + * <tt>MetaContactGroup</tt> and an <tt>UIGroup</tt>. It is also responsible + * for filtering of the <tt>MetaContactListService</tt> through a given pattern. + * + * @author Yana Stamcheva + */ +public class MetaContactListSource +{ + /** + * The data key of the MetaContactDescriptor object used to store a + * reference to this object in its corresponding MetaContact. + */ + public static final String UI_CONTACT_DATA_KEY + = MetaUIContact.class.getName() + ".uiContactDescriptor"; + + /** + * The data key of the MetaGroupDescriptor object used to store a + * reference to this object in its corresponding MetaContactGroup. + */ + public static final String UI_GROUP_DATA_KEY + = MetaUIGroup.class.getName() + ".uiGroupDescriptor"; + + /** + * Returns the <tt>UIContact</tt> corresponding to the given + * <tt>MetaContact</tt>. + * @param metaContact the <tt>MetaContact</tt>, which corresponding UI + * contact we're looking for + * @return the <tt>UIContact</tt> corresponding to the given + * <tt>MetaContact</tt> + */ + public static UIContact getUIContact(MetaContact metaContact) + { + return (UIContact) metaContact.getData(UI_CONTACT_DATA_KEY); + } + + /** + * Returns the <tt>UIGroup</tt> corresponding to the given + * <tt>MetaContactGroup</tt>. + * @param metaGroup the <tt>MetaContactGroup</tt>, which UI group we're + * looking for + * @return the <tt>UIGroup</tt> corresponding to the given + * <tt>MetaContactGroup</tt> + */ + public static UIGroup getUIGroup(MetaContactGroup metaGroup) + { + return (UIGroup) metaGroup.getData(UI_GROUP_DATA_KEY); + } + + /** + * Creates a <tt>UIContact</tt> for the given <tt>metaContact</tt>. + * @param metaContact the <tt>MetaContact</tt> for which we would like to + * create an <tt>UIContact</tt> + * @return an <tt>UIContact</tt> for the given <tt>metaContact</tt> + */ + public static UIContact createUIContact(MetaContact metaContact) + { + UIGroup uiGroup = null; + MetaContactGroup parentMetaGroup + = metaContact.getParentMetaContactGroup(); + if (parentMetaGroup != null + && !parentMetaGroup.equals( + GuiActivator.getContactListService().getRoot())) + { + uiGroup = MetaContactListSource.getUIGroup(parentMetaGroup); + + if (uiGroup == null) + uiGroup = MetaContactListSource.createUIGroup(parentMetaGroup); + } + + MetaUIContact descriptor + = new MetaUIContact(metaContact, uiGroup); + metaContact.setData(UI_CONTACT_DATA_KEY, descriptor); + + return descriptor; + } + + /** + * Removes the <tt>UIContact</tt> from the given <tt>metaContact</tt>. + * @param metaContact the <tt>MetaContact</tt>, which corresponding UI + * contact we would like to remove + */ + public static void removeUIContact(MetaContact metaContact) + { + metaContact.setData(UI_CONTACT_DATA_KEY, null); + } + + /** + * Creates a <tt>UIGroupDescriptor</tt> for the given <tt>metaGroup</tt>. + * @param metaGroup the <tt>MetaContactGroup</tt> for which we would like to + * create an <tt>UIContact</tt> + * @return a <tt>UIGroup</tt> for the given <tt>metaGroup</tt> + */ + public static UIGroup createUIGroup(MetaContactGroup metaGroup) + { + MetaUIGroup descriptor + = new MetaUIGroup(metaGroup); + metaGroup.setData(UI_GROUP_DATA_KEY, descriptor); + + return descriptor; + } + + /** + * Removes the descriptor from the given <tt>metaGroup</tt>. + * @param metaGroup the <tt>MetaContactGroup</tt>, which descriptor we + * would like to remove + */ + public static void removeUIGroup( + MetaContactGroup metaGroup) + { + metaGroup.setData(UI_GROUP_DATA_KEY, null); + } + + /** + * Filters the <tt>MetaContactListService</tt> to match the given + * <tt>filterPattern</tt> and stores the result in the given + * <tt>treeModel</tt>. + * @param filterPattern the pattern to filter through + * @param treeModel the <tt>ContactListTreeModel</tt>, in which we store + * the results + */ + public void filter(Pattern filterPattern, ContactListTreeModel treeModel) + { + filter(filterPattern, treeModel, + GuiActivator.getContactListService().getRoot()); + } + + /** + * Filters the children in the given <tt>MetaContactGroup</tt> to match the + * given <tt>filterPattern</tt> and stores the result in the given + * <tt>treeModel</tt>. + * @param filterPattern the pattern to filter through + * @param treeModel the <tt>ContactListTreeModel</tt>, in which we store + * the results + * @param parentGroup the <tt>MetaContactGroup</tt> to filter + */ + private void filter(Pattern filterPattern, + ContactListTreeModel treeModel, + MetaContactGroup parentGroup) + { + Iterator<MetaContact> childContacts = parentGroup.getChildContacts(); + + while (childContacts.hasNext()) + { + MetaContact metaContact = childContacts.next(); + + if (isMatching(filterPattern, metaContact)) + { + GuiActivator.getContactList().addContact( + treeModel, + MetaContactListSource.createUIContact(metaContact), + true, + false); + } + } + + Iterator<MetaContactGroup> subgroups = parentGroup.getSubgroups(); + while (subgroups.hasNext()) + { + MetaContactGroup subgroup = subgroups.next(); + + filter(filterPattern, treeModel, subgroup); + } + } + + /** + * Checks if the given <tt>metaContact</tt> is matching the given + * <tt>filterPattern</tt>. + * A <tt>MetaContact</tt> would be matching the filter if one of the + * following is true:<br> + * - its display name contains the filter string + * - at least one of its child protocol contacts has a display name or an + * address that contains the filter string. + * @param filterPattern the filter pattern to check for matches + * @param metaContact the <tt>MetaContact</tt> to check + * @return <tt>true</tt> to indicate that the given <tt>metaContact</tt> is + * matching the current filter, otherwise returns <tt>false</tt> + */ + private boolean isMatching(Pattern filterPattern, MetaContact metaContact) + { + Matcher matcher = filterPattern.matcher(metaContact.getDisplayName()); + + if(matcher.find()) + return true; + + Iterator<Contact> contacts = metaContact.getContacts(); + while (contacts.hasNext()) + { + Contact contact = contacts.next(); + + matcher = filterPattern.matcher(contact.getDisplayName()); + + if (matcher.find()) + return true; + + matcher = filterPattern.matcher(contact.getAddress()); + + if (matcher.find()) + return true; + } + return false; + } + + /** + * Checks if the given <tt>metaGroup</tt> is matching the current filter. A + * group is matching the current filter only if it contains at least one + * child <tt>MetaContact</tt>, which is matching the current filter. + * @param filterPattern the filter pattern to check for matches + * @param metaGroup the <tt>MetaContactGroup</tt> to check + * @return <tt>true</tt> to indicate that the given <tt>metaGroup</tt> is + * matching the current filter, otherwise returns <tt>false</tt> + */ + public boolean isMatching(Pattern filterPattern, MetaContactGroup metaGroup) + { + Iterator<MetaContact> contacts = metaGroup.getChildContacts(); + + while (contacts.hasNext()) + { + MetaContact metaContact = contacts.next(); + + if (isMatching(filterPattern, metaContact)) + return true; + } + return false; + } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/MetaUIContact.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/MetaUIContact.java new file mode 100644 index 0000000..7a1188e --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/MetaUIContact.java @@ -0,0 +1,408 @@ +/* + * 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.contactlist.contactsource; + +import java.util.*; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.main.contactlist.*; +import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +/** + * The <tt>MetaUIContact</tt> is the implementation of the UIContact interface + * for the <tt>MetaContactListService</tt>. This implementation is based on the + * <tt>MetaContact</tt>. + * + * @author Yana Stamcheva + */ +public class MetaUIContact + implements UIContact +{ + /** + * The key of the user data in <tt>MetaContact</tt> which specifies + * the avatar cached from previous invocations. + */ + private static final String AVATAR_DATA_KEY + = MetaUIContact.class.getName() + ".avatar"; + + /** + * A list of all search strings available for the underlying + * <tt>MetaContact</tt>. + */ + private final List<String> searchStrings = new LinkedList<String>(); + + /** + * The <tt>MetaContact</tt>, on which this implementation is based. + */ + private MetaContact metaContact; + + /** + * The corresponding <tt>ContactNode</tt> in the contact list component + * data model. + */ + private ContactNode contactNode; + + /** + * The parent <tt>UIGroup</tt> of this contact. + */ + private UIGroup parentUIGroup; + + /** + * Creates an instance of <tt>MetaUIContact</tt> by specifying the + * underlying <tt>MetaContact</tt>, on which it's based. + * @param metaContact the <tt>MetaContact</tt>, on which this implementation + * is based + * @param group the parent <tt>UIGroup</tt> + */ + public MetaUIContact(MetaContact metaContact, UIGroup group) + { + this.metaContact = metaContact; + this.parentUIGroup = group; + + initSearchStrings(); + } + + /** + * Returns the underlying <tt>MetaContact</tt>. + * @return the underlying <tt>MetaContact</tt> + */ + public Object getDescriptor() + { + return metaContact; + } + + /** + * Returns the display name of this <tt>MetaUIContact</tt>. + * @return the display name of this <tt>MetaUIContact</tt> + */ + public String getDisplayName() + { + return metaContact.getDisplayName(); + } + + /** + * Returns the index of the underlying <tt>MetaContact</tt> in its + * <tt>MetaContactListService</tt> parent group. + * @return the source index of the underlying <tt>MetaContact</tt> + */ + public int getSourceIndex() + { + return metaContact.getParentMetaContactGroup().indexOf(metaContact); + } + + /** + * Returns an <tt>Iterator</tt> over a list of strings, which can be used + * to find this contact. + * @return an <tt>Iterator</tt> over a list of search strings + */ + public Iterator<String> getSearchStrings() + { + return searchStrings.iterator(); + } + + /** + * Returns the general status icon of the given MetaContact. Detects the + * status using the priority status table. The priority is defined on + * the "availability" factor and here the most "available" status is + * returned. + * + * @return PresenceStatus The most "available" status from all + * sub-contact statuses. + */ + public ImageIcon getStatusIcon() + { + PresenceStatus status = null; + Iterator<Contact> i = metaContact.getContacts(); + while (i.hasNext()) { + Contact protoContact = i.next(); + PresenceStatus contactStatus = protoContact.getPresenceStatus(); + + if (status == null) + status = contactStatus; + else + status = (contactStatus.compareTo(status) > 0) + ? contactStatus + : status; + } + + if (status != null) + return new ImageIcon(Constants.getStatusIcon(status)); + + return null; + } + + /** + * Returns the parent <tt>UIGroup</tt>. + * @return the parent <tt>UIGroup</tt> + */ + public UIGroup getParentGroup() + { + return parentUIGroup; + } + + /** + * Returns the default <tt>ContactDetail</tt> to use for any operations + * depending to the given <tt>OperationSet</tt> class. + * @param opSetClass the <tt>OperationSet</tt> class we're interested in + * @return the default <tt>ContactDetail</tt> to use for any operations + * depending to the given <tt>OperationSet</tt> class + */ + public UIContactDetail getDefaultContactDetail( + Class<? extends OperationSet> opSetClass) + { + List<UIContactDetail> details + = getContactDetailsForOperationSet(opSetClass); + + if (details != null && !details.isEmpty()) + return details.get(0); + + return null; + } + + /** + * Returns a list of <tt>UIContactDetail</tt>s supporting the given + * <tt>OperationSet</tt> class. + * @param opSetClass the <tt>OperationSet</tt> class we're interested in + * @return a list of <tt>UIContactDetail</tt>s supporting the given + * <tt>OperationSet</tt> class + */ + public List<UIContactDetail> getContactDetailsForOperationSet( + Class<? extends OperationSet> opSetClass) + { + List<UIContactDetail> resultList + = new LinkedList<UIContactDetail>(); + + Iterator<Contact> contacts + = metaContact.getContactsForOperationSet(opSetClass).iterator(); + + while (contacts.hasNext()) + { + resultList.add(new MetaContactDetail(contacts.next())); + } + return resultList; + } + + /** + * Gets the avatar of a specific <tt>MetaContact</tt> in the form of an + * <tt>ImageIcon</tt> value. + * + * @param isSelected indicates if the contact is selected + * @param width the desired icon width + * @param height the desired icon height + * @return an <tt>ImageIcon</tt> which represents the avatar of the + * specified <tt>MetaContact</tt> + */ + public ImageIcon getAvatar( + boolean isSelected, int width, int height) + { + byte[] avatarBytes = metaContact.getAvatar(true); + ImageIcon avatar = null; + + // If there'rs no avatar we have nothing more to do here. + if((avatarBytes == null) || (avatarBytes.length <= 0)) + return null; + + // If the cell is selected we return a zoomed version of the avatar + // image. + if (isSelected) + return ImageUtils.getScaledRoundedIcon( + avatarBytes, + width, + height); + + // In any other case try to get the avatar from the cache. + Object[] avatarCache + = (Object[]) metaContact.getData(AVATAR_DATA_KEY); + + if ((avatarCache != null) && (avatarCache[0] == avatarBytes)) + avatar = (ImageIcon) avatarCache[1]; + + // Just + int avatarWidth = width; + int avatarHeight = height; + + // If the avatar isn't available or it's not up-to-date, create it. + if (avatar == null) + avatar = ImageUtils.getScaledRoundedIcon( + avatarBytes, + avatarWidth, + avatarHeight); + + // Cache the avatar in case it has changed. + if (avatarCache == null) + { + if (avatar != null) + metaContact.setData( + AVATAR_DATA_KEY, + new Object[] { avatarBytes, avatar }); + } + else + { + avatarCache[0] = avatarBytes; + avatarCache[1] = avatar; + } + + return avatar; + } + + /** + * Returns the display details for the underlying <tt>MetaContact</tt>. + * @return the display details for the underlying <tt>MetaContact</tt> + */ + public String getDisplayDetails() + { + String statusMessage = null; + Iterator<Contact> protoContacts = metaContact.getContacts(); + + while (protoContacts.hasNext()) + { + Contact protoContact = protoContacts.next(); + + statusMessage = protoContact.getStatusMessage(); + if (statusMessage != null && statusMessage.length() > 0) + break; + } + + return statusMessage; + } + + /** + * Returns the tool tip opened on mouse over. + * @return the tool tip opened on mouse over + */ + public ExtendedTooltip getToolTip() + { + ExtendedTooltip tip = new ExtendedTooltip(true); + + byte[] avatarImage = metaContact.getAvatar(); + + if (avatarImage != null && avatarImage.length > 0) + tip.setImage(new ImageIcon(avatarImage)); + + tip.setTitle(metaContact.getDisplayName()); + + Iterator<Contact> i = metaContact.getContacts(); + + String statusMessage = null; + Contact protocolContact; + while (i.hasNext()) + { + protocolContact = i.next(); + + ImageIcon protocolStatusIcon + = new ImageIcon( + protocolContact.getPresenceStatus().getStatusIcon()); + + String contactAddress = protocolContact.getAddress(); + //String statusMessage = protocolContact.getStatusMessage(); + + tip.addLine(protocolStatusIcon, contactAddress); + + // Set the first found status message. + if (statusMessage == null + && protocolContact.getStatusMessage() != null + && protocolContact.getStatusMessage().length() > 0) + statusMessage = protocolContact.getStatusMessage(); + } + + if (statusMessage != null) + tip.setBottomText(statusMessage); + + return tip; + } + + /** + * Returns the corresponding <tt>ContactNode</tt> in the contact list + * component data model. + * @return the corresponding <tt>ContactNode</tt> + */ + public ContactNode getContactNode() + { + return contactNode; + } + + /** + * Sets the corresponding <tt>ContactNode</tt>. + * @param contactNode the corresponding <tt>ContactNode</tt> in the contact + * list component data model + */ + public void setContactNode(ContactNode contactNode) + { + this.contactNode = contactNode; + if (contactNode == null) + MetaContactListSource.removeUIContact(metaContact); + } + + /** + * Initializes all search strings for this <tt>MetaUIGroup</tt>. + */ + private void initSearchStrings() + { + searchStrings.add(metaContact.getDisplayName()); + + Iterator<Contact> contacts = metaContact.getContacts(); + while (contacts.hasNext()) + { + Contact contact = contacts.next(); + + searchStrings.add(contact.getDisplayName()); + searchStrings.add(contact.getAddress()); + } + } + + /** + * The implementation of the <tt>UIContactDetail</tt> interface for the + * <tt>MetaContactListService</tt>. + */ + private class MetaContactDetail extends UIContactDetail + { + /** + * The underlying protocol contact. + */ + private Contact contact; + + /** + * Creates an instance of <tt>MetaContactDetail</tt> by specifying the + * underlying protocol <tt>Contact</tt>. + * @param contact the protocol contact, on which this implementation + * is based + */ + public MetaContactDetail(Contact contact) + { + super( contact.getAddress(), + contact.getDisplayName(), + contact.getProtocolProvider()); + + this.contact = contact; + } + + /** + * Returns the presence status of the underlying protocol + * <tt>Contact</tt>. + * @return the presence status of the underlying protocol + * <tt>Contact</tt> + */ + public PresenceStatus getPresenceStatus() + { + return contact.getPresenceStatus(); + } + } + + /** + * Returns the right button menu component. + * @return the right button menu component + */ + public JPopupMenu getRightButtonMenu() + { + return new MetaContactRightButtonMenu(metaContact); + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/MetaUIGroup.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/MetaUIGroup.java new file mode 100644 index 0000000..1a3ae48 --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/MetaUIGroup.java @@ -0,0 +1,164 @@ +/* + * 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.contactlist.contactsource; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.main.contactlist.*; +import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.service.contactlist.*; + +/** + * The <tt>MetaUIGroup</tt> is the implementation of the UIGroup for the + * <tt>MetaContactListService</tt>. This implementation is based on the + * <tt>MetaContactGroup</tt>. + * + * @author Yana Stamcheva + */ +public class MetaUIGroup + implements UIGroup +{ + /** + * The <tt>MetaContactGroup</tt>, on which this UI group is based. + */ + private final MetaContactGroup metaGroup; + + /** + * The corresponding <tt>GroupNode</tt> in the contact list component data + * model. + */ + private GroupNode groupNode; + + /** + * Creates an instance of <tt>MetaUIGroup</tt> by specifying the underlying + * <tt>MetaContactGroup</tt>. + * @param metaGroup the <tt>MetaContactGroup</tt>, on which this UI group + * is based + */ + public MetaUIGroup(MetaContactGroup metaGroup) + { + this.metaGroup = metaGroup; + } + + /** + * Returns the underlying <tt>MetaContactGroup</tt>. + * @return the underlying <tt>MetaContactGroup</tt> + */ + public Object getDescriptor() + { + return metaGroup; + } + + /** + * Returns the index of the underlying <tt>MetaContactGroup</tt> in its + * <tt>MetaContactListService</tt> parent group. + * @return the source index of the underlying <tt>MetaContactGroup</tt> + */ + public int getSourceIndex() + { + return metaGroup.getParentMetaContactGroup().indexOf(metaGroup); + } + + /** + * Returns the parent <tt>UIGroup</tt>. + * @return the parent <tt>UIGroup</tt> + */ + public UIGroup getParentGroup() + { + MetaContactGroup parentGroup = metaGroup.getParentMetaContactGroup(); + + if (parentGroup != null + && !parentGroup.equals( + GuiActivator.getContactListService().getRoot())) + return new MetaUIGroup(parentGroup); + + return null; + } + + /** + * Indicates if this group was collapsed. + * @return <tt>true</tt> to indicate that this group has been collapsed, + * <tt>false</tt> - otherwise + */ + public boolean isGroupCollapsed() + { + return ConfigurationManager + .isContactListGroupCollapsed(metaGroup.getMetaUID()); + } + + /** + * Returns the display name of the underlying <tt>MetaContactGroup</tt>. + * @return the display name of the underlying <tt>MetaContactGroup</tt> + */ + public String getDisplayName() + { + return metaGroup.getGroupName(); + } + + /** + * Returns the count of child contacts of the underlying + * <tt>MetaContactGroup</tt>. + * @return the count of child contacts + */ + public int countChildContacts() + { + return metaGroup.countChildContacts(); + } + + /** + * Returns the count of online child contacts of the underlying + * <tt>MetaContactGroup</tt>. + * @return the count of online child contacts + */ + public int countOnlineChildContacts() + { + return metaGroup.countOnlineChildContacts(); + } + + /** + * Returns the identifier of the underlying <tt>MetaContactGroup</tt>. + * @return the identifier of the underlying <tt>MetaContactGroup</tt> + */ + public String getId() + { + return metaGroup.getMetaUID(); + } + + /** + * Returns the corresponding <tt>GroupNode</tt>. + * @return the corresponding <tt>GroupNode</tt> + */ + public GroupNode getGroupNode() + { + return groupNode; + } + + /** + * Sets the corresponding <tt>GroupNode</tt>. + * @param groupNode the corresponding <tt>GroupNoe</tt> in the contact list + * component data model + */ + public void setGroupNode(GroupNode groupNode) + { + this.groupNode = groupNode; + if (groupNode == null) + MetaContactListSource.removeUIGroup(metaGroup); + } + + /** + * Returns the <tt>JPopupMenu</tt> opened on a right button click over this + * group in the contact list. + * @return the <tt>JPopupMenu</tt> opened on a right button click over this + * group in the contact list + */ + public JPopupMenu getRightButtonMenu() + { + return new GroupRightButtonMenu( + GuiActivator.getUIService().getMainFrame(), metaGroup); + } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/SourceUIContact.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/SourceUIContact.java new file mode 100644 index 0000000..16ef84e --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/contactsource/SourceUIContact.java @@ -0,0 +1,282 @@ +/* + * 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.contactlist.contactsource; + +import java.util.*; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.main.contactlist.*; +import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +/** + * The <tt>SourceUIContact</tt> is the implementation of the UIContact for the + * <tt>ExternalContactSource</tt>. + * + * @author Yana Stamcheva + */ +public class SourceUIContact + implements UIContact +{ + /** + * The corresponding <tt>SourceContact</tt>, on which this abstraction is + * based. + */ + private final SourceContact sourceContact; + + /** + * The corresponding <tt>ContactNode</tt> in the contact list component. + */ + private ContactNode contactNode; + + /** + * The parent <tt>UIGroup</tt>. + */ + private UIGroup uiGroup; + + /** + * Creates an instance of <tt>SourceUIContact</tt> by specifying the + * <tt>SourceContact</tt>, on which this abstraction is based and the + * parent <tt>UIGroup</tt>. + * + * @param contact the <tt>SourceContact</tt>, on which this abstraction + * is based + * @param parentGroup the parent <tt>UIGroup</tt> + */ + public SourceUIContact( SourceContact contact, + UIGroup parentGroup) + { + this.sourceContact = contact; + this.uiGroup = parentGroup; + } + + /** + * Returns the display name of the underlying <tt>SourceContact</tt>. + * @return the display name + */ + public String getDisplayName() + { + return sourceContact.getDisplayName(); + } + + /** + * Returns the parent <tt>UIGroup</tt>. + * @return the parent <tt>UIGroup</tt> + */ + public UIGroup getParentGroup() + { + return uiGroup; + } + + /** + * Returns -1 to indicate that the source index of the underlying + * <tt>SourceContact</tt> is unknown. + * @return -1 + */ + public int getSourceIndex() + { + return -1; + } + + /** + * Returns null to indicate unknown status of the underlying + * <tt>SourceContact</tt>. + * @return null + */ + public ImageIcon getStatusIcon() + { + return null; + } + + /** + * Returns the image corresponding to the underlying <tt>SourceContact</tt>. + * @param isSelected indicates if the contact is currently selected in the + * contact list component + * @param width the desired image width + * @param height the desired image height + * @return the image + */ + public ImageIcon getAvatar(boolean isSelected, int width, int height) + { + ImageIcon icon = new ImageIcon(sourceContact.getImage()); + + if (icon.getIconWidth() > width + || icon.getIconHeight() > height) + { + icon = ImageUtils + .getScaledRoundedIcon(icon.getImage(), width, height); + } + + return icon; + } + + /** + * Returns the default <tt>ContactDetail</tt> to use for any operations + * depending to the given <tt>OperationSet</tt> class. + * @param opSetClass the <tt>OperationSet</tt> class we're interested in + * @return the default <tt>ContactDetail</tt> to use for any operations + * depending to the given <tt>OperationSet</tt> class + */ + public UIContactDetail getDefaultContactDetail( + Class<? extends OperationSet> opSetClass) + { + List<UIContactDetail> details + = getContactDetailsForOperationSet(opSetClass); + + if (details != null && !details.isEmpty()) + return details.get(0); + return null; + } + + /** + * Returns the underlying <tt>SourceContact</tt> this abstraction is about. + * @return the underlying <tt>SourceContact</tt> + */ + public Object getDescriptor() + { + return sourceContact; + } + + /** + * Returns the display details for the underlying <tt>SourceContact</tt>. + * @return the display details for the underlying <tt>SourceContact</tt> + */ + public String getDisplayDetails() + { + return sourceContact.getDisplayDetails(); + } + + /** + * Returns a list of <tt>UIContactDetail</tt>s supporting the given + * <tt>OperationSet</tt> class. + * @param opSetClass the <tt>OperationSet</tt> class we're interested in + * @return a list of <tt>UIContactDetail</tt>s supporting the given + * <tt>OperationSet</tt> class + */ + public List<UIContactDetail> getContactDetailsForOperationSet( + Class<? extends OperationSet> opSetClass) + { + List<UIContactDetail> resultList + = new LinkedList<UIContactDetail>(); + + Iterator<ContactDetail> details + = sourceContact.getContactDetails().iterator(); + + while (details.hasNext()) + { + ContactDetail detail = details.next(); + + if (detail.getSupportedOperationSets().contains(opSetClass)) + resultList.add(new SourceContactDetail(detail, opSetClass)); + } + return resultList; + } + + public Iterator<String> getSearchStrings() + { + return null; + } + + /** + * Returns the corresponding <tt>ContactNode</tt> from the contact list + * component. + * @return the corresponding <tt>ContactNode</tt> + */ + public ContactNode getContactNode() + { + return contactNode; + } + + /** + * Sets the corresponding <tt>ContactNode</tt>. + * @param contactNode the corresponding <tt>ContactNode</tt> + */ + public void setContactNode(ContactNode contactNode) + { + this.contactNode = contactNode; + } + + /** + * The implementation of the <tt>UIContactDetail</tt> interface for the + * external source <tt>ContactDetail</tt>s. + */ + private class SourceContactDetail extends UIContactDetail + { + /** + * Creates an instance of <tt>SourceContactDetail</tt> by specifying + * the underlying <tt>detail</tt> and the <tt>OperationSet</tt> class + * for it. + * @param detail the underlying <tt>ContactDetail</tt> + * @param opSetClass the <tt>OperationSet</tt> class for the + * preferred protocol provider + */ + public SourceContactDetail( ContactDetail detail, + Class<? extends OperationSet> opSetClass) + { + super( detail.getContactAddress(), + detail.getContactAddress(), + detail.getPreferredProtocolProvider(opSetClass)); + } + + /** + * Returns null to indicate that this detail doesn't support presence. + * @return null + */ + public PresenceStatus getPresenceStatus() + { + return null; + } + } + + /** + * Returns the <tt>JPopupMenu</tt> opened on a right button click over this + * <tt>SourceUIContact</tt>. + * @return the <tt>JPopupMenu</tt> opened on a right button click over this + * <tt>SourceUIContact</tt> + */ + public JPopupMenu getRightButtonMenu() + { + return new SourceContactRightButtonMenu(sourceContact); + } + + /** + * Returns the tool tip opened on mouse over. + * @return the tool tip opened on mouse over + */ + public ExtendedTooltip getToolTip() + { + ExtendedTooltip tip = new ExtendedTooltip(true); + + byte[] avatarImage = sourceContact.getImage(); + + if (avatarImage != null && avatarImage.length > 0) + tip.setImage(new ImageIcon(avatarImage)); + + tip.setTitle(sourceContact.getDisplayName()); + + Iterator<ContactDetail> details + = sourceContact.getContactDetails().iterator(); + + ContactDetail contactDetail; + while (details.hasNext()) + { + contactDetail = details.next(); + + String contactAddress = contactDetail.getContactAddress(); + //String statusMessage = protocolContact.getStatusMessage(); + + tip.addLine(null, contactAddress); + } + + tip.setBottomText(getDisplayDetails()); + + return tip; + } +} diff --git a/src/net/java/sip/communicator/impl/gui/main/menus/ToolsMenu.java b/src/net/java/sip/communicator/impl/gui/main/menus/ToolsMenu.java index fd16561..6cb7a4e 100644 --- a/src/net/java/sip/communicator/impl/gui/main/menus/ToolsMenu.java +++ b/src/net/java/sip/communicator/impl/gui/main/menus/ToolsMenu.java @@ -119,8 +119,14 @@ public class ToolsMenu TreeContactList.presenceFilter.setShowOffline(!isShowOffline); - GuiActivator.getContactList() - .applyFilter(TreeContactList.presenceFilter); + new Thread() + { + public void run() + { + GuiActivator.getContactList() + .applyFilter(TreeContactList.presenceFilter); + } + }.start(); ConfigurationManager.setShowOffline(!isShowOffline); diff --git a/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf b/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf index 66b3c0b..8139b8e 100644 --- a/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf +++ b/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf @@ -32,6 +32,7 @@ Import-Package: org.osgi.framework, net.java.sip.communicator.service.systray, net.java.sip.communicator.service.neomedia, net.java.sip.communicator.service.neomedia.device, + net.java.sip.communicator.service.contactsource, net.java.sip.communicator.util, net.java.sip.communicator.util.swing, net.java.sip.communicator.util.swing.border, diff --git a/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java b/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java index 3648667..3f287cc 100644 --- a/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java +++ b/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java @@ -278,6 +278,18 @@ public class ImageLoader = new ImageID("service.gui.buttons.CHAT_CALL"); /** + * The call history button image. + */ + public static final ImageID CALL_HISTORY_BUTTON + = new ImageID("service.gui.buttons.CALL_HISTORY_BUTTON"); + + /** + * The call history pressed button image. + */ + public static final ImageID CALL_HISTORY_BUTTON_PRESSED + = new ImageID("service.gui.buttons.CALL_HISTORY_BUTTON_PRESSED"); + + /** * The chat button small pressed image. */ public static final ImageID CHAT_BUTTON_SMALL_PRESSED diff --git a/src/net/java/sip/communicator/impl/history/HistoryReaderImpl.java b/src/net/java/sip/communicator/impl/history/HistoryReaderImpl.java index 1f2adcc..b6ddf02 100644 --- a/src/net/java/sip/communicator/impl/history/HistoryReaderImpl.java +++ b/src/net/java/sip/communicator/impl/history/HistoryReaderImpl.java @@ -10,6 +10,8 @@ import java.util.*; import org.w3c.dom.*; +import sun.nio.cs.ext.*; + import net.java.sip.communicator.service.history.*; import net.java.sip.communicator.service.history.event.*; import net.java.sip.communicator.service.history.records.*; @@ -17,6 +19,7 @@ import net.java.sip.communicator.service.history.records.*; /** * @author Alexander Pelov * @author Damian Minkov + * @author Yana Stamcheva */ public class HistoryReaderImpl implements HistoryReader @@ -30,6 +33,15 @@ public class HistoryReaderImpl private static String REGEXP_SENSITIVE_START = "(?s)^.*"; private static String REGEXP_INSENSITIVE_START = "(?si)^.*"; + /** + * Indicates if the current find should be canceled. + */ + private boolean isFindCanceled = false; + + /** + * Creates an instance of <tt>HistoryReaderImpl</tt>. + * @param historyImpl the parent History implementation + */ protected HistoryReaderImpl(HistoryImpl historyImpl) { this.historyImpl = historyImpl; @@ -45,7 +57,8 @@ public class HistoryReaderImpl * Thrown if an exception occurs during the execution of the * query, such as internal IO error. */ - public synchronized QueryResultSet<HistoryRecord> findByStartDate(Date startDate) + public synchronized QueryResultSet<HistoryRecord> findByStartDate( + Date startDate) throws RuntimeException { return find(startDate, null, null, null, false); @@ -498,22 +511,26 @@ public class HistoryReaderImpl TreeSet<HistoryRecord> result = new TreeSet<HistoryRecord>(new HistoryRecordComparator()); - Vector<String> filelist = - filterFilesByDate(this.historyImpl.getFileList(), startDate, endDate); + Vector<String> filelist + = filterFilesByDate(this.historyImpl.getFileList(), + startDate, endDate); - double currentProgress = HistorySearchProgressListener.PROGRESS_MINIMUM_VALUE; - double fileProgressStep = HistorySearchProgressListener.PROGRESS_MAXIMUM_VALUE; + double currentProgress + = HistorySearchProgressListener.PROGRESS_MINIMUM_VALUE; + double fileProgressStep + = HistorySearchProgressListener.PROGRESS_MAXIMUM_VALUE; if(filelist.size() != 0) - fileProgressStep = - HistorySearchProgressListener.PROGRESS_MAXIMUM_VALUE / filelist.size(); + fileProgressStep + = HistorySearchProgressListener.PROGRESS_MAXIMUM_VALUE + / filelist.size(); // start progress - minimum value fireProgressStateChanged(startDate, endDate, keywords, HistorySearchProgressListener.PROGRESS_MINIMUM_VALUE); Iterator<String> fileIterator = filelist.iterator(); - while (fileIterator.hasNext()) + while (fileIterator.hasNext() && !isFindCanceled) { String filename = fileIterator.next(); @@ -530,7 +547,7 @@ public class HistoryReaderImpl nodesProgressStep = fileProgressStep / nodes.getLength(); Node node; - for (int i = 0; i < nodes.getLength(); i++) + for (int i = 0; i < nodes.getLength() && !isFindCanceled; i++) { node = nodes.item(i); @@ -560,13 +577,17 @@ public class HistoryReaderImpl } // if maximum value is not reached fire an event - if((int)currentProgress < HistorySearchProgressListener.PROGRESS_MAXIMUM_VALUE) + if((int)currentProgress + < HistorySearchProgressListener.PROGRESS_MAXIMUM_VALUE) { fireProgressStateChanged(startDate, endDate, keywords, HistorySearchProgressListener. PROGRESS_MAXIMUM_VALUE); } + // Before returning we want to reset the isFindCanceled to false. + isFindCanceled = false; + return new OrderedQueryResultSet<HistoryRecord>(result); } @@ -902,4 +923,12 @@ public class HistoryReaderImpl compareTo(h2.getTimestamp()); } } + + /** + * Cancels the current find. If there's no find going on, then does nothing. + */ + public void cancelCurrentFind() + { + isFindCanceled = true; + } } diff --git a/src/net/java/sip/communicator/service/callhistory/CallHistoryQuery.java b/src/net/java/sip/communicator/service/callhistory/CallHistoryQuery.java new file mode 100644 index 0000000..6465c6c --- /dev/null +++ b/src/net/java/sip/communicator/service/callhistory/CallHistoryQuery.java @@ -0,0 +1,53 @@ +/* + * 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.service.callhistory; + +import java.util.*; + +import net.java.sip.communicator.service.callhistory.event.*; + +/** + * The <tt>CallHistoryQuery</tt> corresponds to a query made to the + * <tt>CallHistoryService</tt>. It allows to be canceled, to listen for changes + * in the results and to obtain initial results if available. + * + * @author Yana Stamcheva + */ +public interface CallHistoryQuery +{ + /** + * Cancels this query. + */ + public void cancel(); + + /** + * Returns a collection of the initial results for this query. It's up to + * the implementation to determine, which and how many the initial results + * would be. + * <p> + * This method is meant to be used in order to return first fast initial + * results and then notify interested parties of additional results through + * the <tt>CallHistoryQueryListener</tt>, which should improve user + * experience when waiting for results. + * @return a collection of the initial results for this query + */ + public Collection<CallRecord> getCallRecords(); + + /** + * Adds the given <tt>CallHistoryQueryListener</tt> to the list of + * listeners interested in query result changes. + * @param l the <tt>CallHistoryQueryListener</tt> to add + */ + public void addCallRecordsListener(CallHistoryQueryListener l); + + /** + * Removes the given <tt>CallHistoryQueryListener</tt> from the list of + * listeners interested in query result changes. + * @param l the <tt>CallHistoryQueryListener</tt> to remove + */ + public void removeCallRecordsListener(CallHistoryQueryListener l); +} diff --git a/src/net/java/sip/communicator/service/callhistory/CallHistoryService.java b/src/net/java/sip/communicator/service/callhistory/CallHistoryService.java index 93168bc..dc5f8ff 100644 --- a/src/net/java/sip/communicator/service/callhistory/CallHistoryService.java +++ b/src/net/java/sip/communicator/service/callhistory/CallHistoryService.java @@ -21,7 +21,7 @@ public interface CallHistoryService { /** * Returns all the calls made by all the contacts - * in the supplied metacontact after the given date + * in the supplied <tt>contact</tt> after the given date. * * @param contact MetaContact which contacts participate in * the returned calls @@ -29,12 +29,13 @@ public interface CallHistoryService * @return Collection of CallRecords with CallPeerRecord * @throws RuntimeException */ - public Collection<CallRecord> findByStartDate(MetaContact contact, Date startDate) + public Collection<CallRecord> findByStartDate( MetaContact contact, + Date startDate) throws RuntimeException; /** * Returns all the calls made by all the contacts - * in the supplied metacontact before the given date + * in the supplied <tt>contact</tt> before the given date. * * @param contact MetaContact which contacts participate in * the returned calls @@ -42,12 +43,13 @@ public interface CallHistoryService * @return Collection of CallRecords with CallPeerRecord * @throws RuntimeException */ - public Collection<CallRecord> findByEndDate(MetaContact contact, Date endDate) + public Collection<CallRecord> findByEndDate(MetaContact contact, + Date endDate) throws RuntimeException; /** * Returns all the calls made by all the contacts - * in the supplied metacontact between the given dates + * in the supplied <tt>contact</tt> between the given dates. * * @param contact MetaContact which contacts participate in * the returned calls @@ -56,12 +58,14 @@ public interface CallHistoryService * @return Collection of CallRecords with CallPeerRecord * @throws RuntimeException */ - public Collection<CallRecord> findByPeriod(MetaContact contact, Date startDate, Date endDate) + public Collection<CallRecord> findByPeriod( MetaContact contact, + Date startDate, + Date endDate) throws RuntimeException; /** - * Returns all the calls made after the given date + * Returns all the calls made after the given date. * * @param startDate Date the start date of the calls * @return Collection of CallRecords with CallPeerRecord @@ -71,7 +75,7 @@ public interface CallHistoryService throws RuntimeException; /** - * Returns all the calls made before the given date + * Returns all the calls made before the given date. * * @param endDate Date the end date of the calls * @return Collection of CallRecords with CallPeerRecord @@ -81,7 +85,7 @@ public interface CallHistoryService throws RuntimeException; /** - * Returns all the calls made between the given dates + * Returns all the calls made between the given dates. * * @param startDate Date the start date of the calls * @param endDate Date the end date of the calls @@ -93,7 +97,7 @@ public interface CallHistoryService /** * Returns the supplied number of recent calls made by all the contacts - * in the supplied metacontact + * in the supplied <tt>contact</tt>. * * @param contact MetaContact which contacts participate in * the returned calls @@ -105,8 +109,7 @@ public interface CallHistoryService throws RuntimeException; /** - * Returns the supplied number of recent calls made by all the contacts - * in the supplied metacontact + * Returns the supplied number of recent calls. * * @param count calls count * @return Collection of CallRecords with CallPeerRecord @@ -129,12 +132,19 @@ public interface CallHistoryService * * @param listener HistorySearchProgressListener */ - public void addSearchProgressListener(CallHistorySearchProgressListener listener); + public void addSearchProgressListener( + CallHistorySearchProgressListener listener); /** * Removing progress listener * * @param listener HistorySearchProgressListener */ - public void removeSearchProgressListener(CallHistorySearchProgressListener listener); + public void removeSearchProgressListener( + CallHistorySearchProgressListener listener); + + /** + * Cancels the current find. If there's no find going on, then does nothing. + */ + public void cancelCurrentFind(); } diff --git a/src/net/java/sip/communicator/service/callhistory/CallRecord.java b/src/net/java/sip/communicator/service/callhistory/CallRecord.java index 3308a44..31729ad 100644 --- a/src/net/java/sip/communicator/service/callhistory/CallRecord.java +++ b/src/net/java/sip/communicator/service/callhistory/CallRecord.java @@ -1,31 +1,62 @@ +/* + * 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.service.callhistory; import java.util.*; +import net.java.sip.communicator.service.protocol.*; + /** * Structure used for encapsulating data when writing or reading - * Call History Data. Also These records are uesd for returning data - * from the Call History Service + * Call History Data. Also these records are used for returning data + * from the Call History Service. * * @author Damian Minkov + * @author Yana Stamcheva */ public class CallRecord { /** - * Possible directions of the call + * The outgoing call direction. */ public final static String OUT = "out"; + + /** + * The incoming call direction. + */ public final static String IN = "in"; + /** + * Indicates the direction of the call - IN or OUT. + */ protected String direction = null; + /** + * A list of all peer records corresponding to this call record. + */ protected final List<CallPeerRecord> peerRecords = new Vector<CallPeerRecord>(); + /** + * The start call date. + */ protected Date startTime = null; + + /** + * The end call date. + */ protected Date endTime = null; /** + * The protocol provider through which the call was made. + */ + protected ProtocolProviderService protocolProvider; + + /** * Creates CallRecord */ public CallRecord() @@ -101,4 +132,14 @@ public class CallRecord { return startTime; } + + /** + * Returns the protocol provider used for the call. Could be null if the + * record has not saved the provider. + * @return the protocol provider used for the call + */ + public ProtocolProviderService getProtocolProvider() + { + return protocolProvider; + } } diff --git a/src/net/java/sip/communicator/service/callhistory/event/CallHistoryQueryListener.java b/src/net/java/sip/communicator/service/callhistory/event/CallHistoryQueryListener.java new file mode 100644 index 0000000..b646036 --- /dev/null +++ b/src/net/java/sip/communicator/service/callhistory/event/CallHistoryQueryListener.java @@ -0,0 +1,26 @@ +/* + * 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.service.callhistory.event; + +/** + * The <tt>CallHistoryQueryListener</tt> listens for changes in the result of + * a given <tt>CallHistoryQuery</tt>. When a query to the call history is + * started, this listener would be notified every time new results are available + * for this query. + * + * @author Yana Stamcheva + */ +public interface CallHistoryQueryListener +{ + /** + * Indicates that new <tt>CallRecord</tt>s are received as a result of the + * query. + * @param event the <tt>CallRecordsEvent</tt> containing information about + * the query results. + */ + public void callRecordsReceived(CallRecordsEvent event); +} diff --git a/src/net/java/sip/communicator/service/callhistory/event/CallRecordsEvent.java b/src/net/java/sip/communicator/service/callhistory/event/CallRecordsEvent.java new file mode 100644 index 0000000..2e3b0f0 --- /dev/null +++ b/src/net/java/sip/communicator/service/callhistory/event/CallRecordsEvent.java @@ -0,0 +1,61 @@ +/* + * 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.service.callhistory.event; + +import java.util.*; + +import net.java.sip.communicator.service.callhistory.*; + +/** + * The <tt>CallRecordsEvent</tt> indicates that one or more + * <tt>CallRecord</tt>s has been received as a result of a + * <tt>CallHistoryQuery</tt>. + * + * @author Yana Stamcheva + */ +public class CallRecordsEvent + extends EventObject +{ + /** + * A collection of call records received as a result of a given + * <tt>query</tt>. + */ + private final Collection<CallRecord> callRecords; + + /** + * Creates a <tt>ContactReceivedEvent</tt> by specifying the contact search + * source and the received <tt>searchContact</tt>. + * @param query the source that triggered this event + * @param callRecords the call records received as a result from the given + * <tt>query</tt> + */ + public CallRecordsEvent(CallHistoryQuery query, + Collection<CallRecord> callRecords) + { + super(query); + + this.callRecords = callRecords; + } + + /** + * Returns the <tt>ContactQuery</tt> that triggered this event. + * @return the <tt>ContactQuery</tt> that triggered this event + */ + public CallHistoryQuery getQuerySource() + { + return (CallHistoryQuery) source; + } + + /** + * Returns the collection of <tt>CallRecord</tt>s this event is about. + * @return the collection of <tt>CallRecord</tt>s this event is about + */ + public Collection<CallRecord> getCallRecords() + { + return callRecords; + } +} diff --git a/src/net/java/sip/communicator/service/contactlist/MetaContactGroup.java b/src/net/java/sip/communicator/service/contactlist/MetaContactGroup.java index f218b37..c27a762 100644 --- a/src/net/java/sip/communicator/service/contactlist/MetaContactGroup.java +++ b/src/net/java/sip/communicator/service/contactlist/MetaContactGroup.java @@ -291,4 +291,40 @@ public interface MetaContactGroup * @return a String uniquely identifying this meta contact. */ public String getMetaUID(); + + /** + * Gets the user data associated with this instance and a specific key. + * + * @param key + * the key of the user data associated with this instance to be + * retrieved + * @return an <code>Object</code> which represents the value associated with + * this instance and the specified <code>key</code>; <tt>null</tt> + * if no association with the specified <code>key</code> exists in + * this instance + */ + public Object getData(Object key); + + /** + * Sets a user-specific association in this instance in the form of a + * key-value pair. If the specified <code>key</code> is already associated + * in this instance with a value, the existing value is overwritten with the + * specified <code>value</code>. + * <p> + * The user-defined association created by this method and stored in this + * instance is not serialized by this instance and is thus only meant for + * runtime use. + * </p> + * <p> + * The storage of the user data is implementation-specific and is thus not + * guaranteed to be optimized for execution time and memory use. + * </p> + * + * @param key + * the key to associate in this instance with the specified value + * @param value + * the value to be associated in this instance with the specified + * <code>key</code> + */ + public void setData(Object key, Object value); } diff --git a/src/net/java/sip/communicator/service/contactsource/ContactDetail.java b/src/net/java/sip/communicator/service/contactsource/ContactDetail.java index 1985a7f..d4f08e8 100644 --- a/src/net/java/sip/communicator/service/contactsource/ContactDetail.java +++ b/src/net/java/sip/communicator/service/contactsource/ContactDetail.java @@ -67,17 +67,14 @@ public class ContactDetail * Creates a <tt>ContactDetail</tt> by specifying the corresponding contact * address and a mapping of preferred <tt>ProtocolProviderServices</tt> for * a specific <tt>OperationSet</tt>. - * @param contactAddress the contact address corresponding to this detail * @param preferredProviders a mapping of preferred * <tt>ProtocolProviderService</tt>s for specific <tt>OperationSet</tt> * classes */ - public ContactDetail(String contactAddress, + public void setPreferredProviders( Map<Class<? extends OperationSet>, ProtocolProviderService> preferredProviders) { - this(contactAddress); - this.preferredProviders = preferredProviders; } @@ -86,16 +83,13 @@ public class ContactDetail * address and a list of all <tt>supportedOpSets</tt>, indicating what are * the supporting actions with this contact detail (e.g. sending a message, * making a call, etc.) - * @param contactAddress the address of the contact * @param supportedOpSets a list of all <tt>supportedOpSets</tt>, indicating * what are the supporting actions with this contact detail (e.g. sending a * message, making a call, etc.) */ - public ContactDetail( String contactAddress, + public void setSupportedOpSets( List<Class<? extends OperationSet>> supportedOpSets) { - this(contactAddress); - this.supportedOpSets = supportedOpSets; } @@ -119,7 +113,10 @@ public class ContactDetail public ProtocolProviderService getPreferredProtocolProvider( Class<? extends OperationSet> opSetClass) { - return preferredProviders.get(opSetClass); + if (preferredProviders != null && preferredProviders.size() > 0) + return preferredProviders.get(opSetClass); + + return null; } /** @@ -132,30 +129,4 @@ public class ContactDetail { return supportedOpSets; } - - /** - * Sets the list of supported <tt>OperationSet</tt> classes. These are meant - * to indicate what are the supported actions (sending an IM message, - * making a call, etc.). - * @param opSets the list of supported <tt>OperationSet</tt> classes - */ - public void setSupportedOperationSets( - List<Class<? extends OperationSet>> opSets) - { - this.supportedOpSets = opSets; - } - - /** - * Sets a mapping of preferred <tt>ProtocolProviderServices</tt> for - * <tt>OperationSet</tt> classes. - * @param preferredProviders a mapping of preferred - * <tt>ProtocolProviderService</tt>s for specific <tt>OperationSet</tt> - * classes - */ - public void setPreferredProtocolProviders( - Map<Class<? extends OperationSet>, ProtocolProviderService> - preferredProviders) - { - this.preferredProviders = preferredProviders; - } } diff --git a/src/net/java/sip/communicator/service/contactsource/ContactQuery.java b/src/net/java/sip/communicator/service/contactsource/ContactQuery.java index fd33745..1da6d7a 100644 --- a/src/net/java/sip/communicator/service/contactsource/ContactQuery.java +++ b/src/net/java/sip/communicator/service/contactsource/ContactQuery.java @@ -6,6 +6,8 @@ */ package net.java.sip.communicator.service.contactsource; +import java.util.*; + /** * The <tt>ContactQuery</tt> corresponds to a particular query made through the * <tt>ContactSourceService</tt>. Each query once started could be @@ -17,6 +19,20 @@ package net.java.sip.communicator.service.contactsource; public interface ContactQuery { /** + * Returns the <tt>ContactSourceService</tt>, where this query was first + * initiated. + * @return the <tt>ContactSourceService</tt>, where this query was first + * initiated + */ + public ContactSourceService getContactSource(); + + /** + * Returns the list of <tt>SourceContact</tt>s returned by this query. + * @return the list of <tt>SourceContact</tt>s returned by this query + */ + public List<SourceContact> getQueryResults(); + + /** * Cancels this query. */ public void cancel(); diff --git a/src/net/java/sip/communicator/service/contactsource/ContactSourceService.java b/src/net/java/sip/communicator/service/contactsource/ContactSourceService.java index 78f1bbd..6d49947 100644 --- a/src/net/java/sip/communicator/service/contactsource/ContactSourceService.java +++ b/src/net/java/sip/communicator/service/contactsource/ContactSourceService.java @@ -6,8 +6,6 @@ */ package net.java.sip.communicator.service.contactsource; -import net.java.sip.communicator.service.protocol.*; - /** * The <tt>ContactSourceService</tt> interface is meant to be implemented * by modules supporting large lists of contacts and wanting to enable searching @@ -17,6 +15,16 @@ import net.java.sip.communicator.service.protocol.*; */ public interface ContactSourceService { + public static final String CALL_HISTORY = "CallHistory"; + + /** + * Returns the identifier of this contact source. Some of the common + * identifiers are defined here (For example the CALL_HISTORY identifier + * should be returned by all call history implementations of this interface) + * @return the identifier of this contact source + */ + public String getIdentifier(); + /** * Returns a user-friendly string that identifies this contact source. * @return the display name of this contact source @@ -29,11 +37,4 @@ public interface ContactSourceService * @return the created query */ public ContactQuery queryContactSource(String queryString); - - /** - * Returns the telephony provider preferred for calling items from this - * source. - * @return the preferred telephony provider - */ - public ProtocolProviderService getPreferredTelephonyProvider(); } diff --git a/src/net/java/sip/communicator/service/contactsource/SourceContact.java b/src/net/java/sip/communicator/service/contactsource/SourceContact.java index 24f33a1..99b47ca 100644 --- a/src/net/java/sip/communicator/service/contactsource/SourceContact.java +++ b/src/net/java/sip/communicator/service/contactsource/SourceContact.java @@ -51,6 +51,16 @@ public interface SourceContact public List<ContactDetail> getContactDetails(); /** + * Returns a list of all <tt>ContactDetail</tt>s supporting the given + * <tt>OperationSet</tt> class. + * @param operationSet the <tt>OperationSet</tt> class we're looking for + * @return a list of all <tt>ContactDetail</tt>s supporting the given + * <tt>OperationSet</tt> class + */ + public List<ContactDetail> getContactDetails( + Class<? extends OperationSet> operationSet); + + /** * Returns the preferred <tt>ContactDetail</tt> for a given * <tt>OperationSet</tt> class. * @param operationSet the <tt>OperationSet</tt> class, for which we would diff --git a/src/net/java/sip/communicator/service/contactsource/contactsource.manifest.mf b/src/net/java/sip/communicator/service/contactsource/contactsource.manifest.mf index acc02d3..cbb4aa2 100644 --- a/src/net/java/sip/communicator/service/contactsource/contactsource.manifest.mf +++ b/src/net/java/sip/communicator/service/contactsource/contactsource.manifest.mf @@ -3,5 +3,6 @@ Bundle-Description: ContactSource Service. Bundle-Vendor: sip-communicator.org Bundle-Version: 0.0.1 System-Bundle: yes -Import-Package: org.osgi.framework +Import-Package: org.osgi.framework, + net.java.sip.communicator.service.protocol Export-Package: net.java.sip.communicator.service.contactsource diff --git a/src/net/java/sip/communicator/service/history/HistoryReader.java b/src/net/java/sip/communicator/service/history/HistoryReader.java index 235f3e3..94d2cab 100644 --- a/src/net/java/sip/communicator/service/history/HistoryReader.java +++ b/src/net/java/sip/communicator/service/history/HistoryReader.java @@ -28,7 +28,8 @@ public interface HistoryReader { * Thrown if an exception occurs during the execution of the * query, such as internal IO error. */ - public QueryResultSet<HistoryRecord> findByStartDate(Date startDate) throws RuntimeException; + public QueryResultSet<HistoryRecord> findByStartDate(Date startDate) + throws RuntimeException; /** * Searches the history for all records with timestamp before @@ -40,7 +41,8 @@ public interface HistoryReader { * Thrown if an exception occurs during the execution of the * query, such as internal IO error. */ - public QueryResultSet<HistoryRecord> findByEndDate(Date endDate) throws RuntimeException; + public QueryResultSet<HistoryRecord> findByEndDate(Date endDate) + throws RuntimeException; /** * Searches the history for all records with timestamp between @@ -53,7 +55,8 @@ public interface HistoryReader { * Thrown if an exception occurs during the execution of the * query, such as internal IO error. */ - public QueryResultSet<HistoryRecord> findByPeriod(Date startDate, Date endDate) + public QueryResultSet<HistoryRecord> findByPeriod( Date startDate, + Date endDate) throws RuntimeException; /** @@ -66,7 +69,9 @@ public interface HistoryReader { * Thrown if an exception occurs during the execution of the * query, such as internal IO error. */ - public QueryResultSet<HistoryRecord> findByKeyword(String keyword, String field) throws RuntimeException; + public QueryResultSet<HistoryRecord> findByKeyword( String keyword, + String field) + throws RuntimeException; /** * Searches the history for all records containing the <tt>keyword</tt>. @@ -79,7 +84,9 @@ public interface HistoryReader { * Thrown if an exception occurs during the execution of the * query, such as internal IO error. */ - public QueryResultSet<HistoryRecord> findByKeyword(String keyword, String field, boolean caseSensitive) + public QueryResultSet<HistoryRecord> findByKeyword( String keyword, + String field, + boolean caseSensitive) throws RuntimeException; /** @@ -92,7 +99,9 @@ public interface HistoryReader { * Thrown if an exception occurs during the execution of the * query, such as internal IO error. */ - public QueryResultSet<HistoryRecord> findByKeywords(String[] keywords, String field) throws RuntimeException; + public QueryResultSet<HistoryRecord> findByKeywords(String[] keywords, + String field) + throws RuntimeException; /** * Searches the history for all records containing all <tt>keywords</tt>. @@ -105,7 +114,9 @@ public interface HistoryReader { * Thrown if an exception occurs during the execution of the * query, such as internal IO error. */ - public QueryResultSet<HistoryRecord> findByKeywords(String[] keywords, String field, boolean caseSensitive) + public QueryResultSet<HistoryRecord> findByKeywords(String[] keywords, + String field, + boolean caseSensitive) throws RuntimeException; /** @@ -121,8 +132,10 @@ public interface HistoryReader { * Thrown if an exception occurs during the execution of the * query, such as internal IO error. */ - public QueryResultSet<HistoryRecord> findByPeriod(Date startDate, Date endDate, - String[] keywords, String field) + public QueryResultSet<HistoryRecord> findByPeriod( Date startDate, + Date endDate, + String[] keywords, + String field) throws UnsupportedOperationException; /** @@ -139,8 +152,11 @@ public interface HistoryReader { * Thrown if an exception occurs during the execution of the * query, such as internal IO error. */ - public QueryResultSet<HistoryRecord> findByPeriod(Date startDate, Date endDate, - String[] keywords, String field, boolean caseSensitive) + public QueryResultSet<HistoryRecord> findByPeriod( Date startDate, + Date endDate, + String[] keywords, + String field, + boolean caseSensitive) throws UnsupportedOperationException; /** @@ -160,7 +176,9 @@ public interface HistoryReader { * @return QueryResultSet the found records * @throws RuntimeException */ - public QueryResultSet<HistoryRecord> findFirstRecordsAfter(Date date, int count) throws RuntimeException; + public QueryResultSet<HistoryRecord> findFirstRecordsAfter( Date date, + int count) + throws RuntimeException; /** * Returns the supplied number of recent messages before the given date @@ -170,22 +188,26 @@ public interface HistoryReader { * @return QueryResultSet the found records * @throws RuntimeException */ - public QueryResultSet<HistoryRecord> findLastRecordsBefore(Date date, int count) throws RuntimeException; + public QueryResultSet<HistoryRecord> findLastRecordsBefore( Date date, + int count) + throws RuntimeException; /** * Adding progress listener for monitoring progress of search process * * @param listener HistorySearchProgressListener */ - public void addSearchProgressListener(HistorySearchProgressListener listener); + public void addSearchProgressListener( + HistorySearchProgressListener listener); /** * Removing progress listener * * @param listener HistorySearchProgressListener */ - public void removeSearchProgressListener(HistorySearchProgressListener listener); - + public void removeSearchProgressListener( + HistorySearchProgressListener listener); + /** * Total count of records that current history reader will read through * @@ -196,4 +218,9 @@ public interface HistoryReader { */ public int countRecords() throws UnsupportedOperationException; + + /** + * Cancels the current find. If there's no find going on, then does nothing. + */ + public void cancelCurrentFind(); } diff --git a/src/net/java/sip/communicator/util/swing/ExtendedTransferHandler.java b/src/net/java/sip/communicator/util/swing/ExtendedTransferHandler.java index 0a693cc..8a16e1b 100644 --- a/src/net/java/sip/communicator/util/swing/ExtendedTransferHandler.java +++ b/src/net/java/sip/communicator/util/swing/ExtendedTransferHandler.java @@ -17,14 +17,13 @@ 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 * <tt>DefaultTransferHandler</tt> with the main difference being that - * we only accept pasting of plain text. We do this in order to avoid html + * we only accept pasting of plain text. We do this in order to avoid HTML * support problems that appear when pasting formatted text into our editable * area. * @@ -35,12 +34,6 @@ public class ExtendedTransferHandler extends TransferHandler { /** - * The data flavor used when transferring <tt>MetaContact</tt>s. - */ - protected static final DataFlavor metaContactDataFlavor - = new DataFlavor(MetaContact.class, "MetaContact"); - - /** * Returns the type of transfer actions supported by the source; * any bitwise-OR combination of <tt>COPY</tt>, <tt>MOVE</tt> * and <tt>LINK</tt>. diff --git a/src/net/java/sip/communicator/util/swing/SIPCommTextButton.java b/src/net/java/sip/communicator/util/swing/SIPCommTextButton.java index b2a7029..4ec8fb4 100644 --- a/src/net/java/sip/communicator/util/swing/SIPCommTextButton.java +++ b/src/net/java/sip/communicator/util/swing/SIPCommTextButton.java @@ -27,12 +27,14 @@ public class SIPCommTextButton extends JButton private final float[] borderColor = Color.DARK_GRAY.getRGBComponents(null); + private Image bgImage; + /** * Creates a <tt>SIPCommTextButton</tt>. */ public SIPCommTextButton() { - this(null); + this("", null); } /** @@ -41,8 +43,15 @@ public class SIPCommTextButton extends JButton */ public SIPCommTextButton(String text) { + this(text, null); + } + + public SIPCommTextButton(String text, Image bgImage) + { super(text); + this.bgImage = bgImage; + MouseRolloverHandler mouseHandler = new MouseRolloverHandler(); this.addMouseListener(mouseHandler); @@ -55,11 +64,15 @@ public class SIPCommTextButton extends JButton * Explicitly remove all borders that may be set from the current look * and feel. */ - this.setBorder(BorderFactory.createEmptyBorder(4, 10, 4, 10)); this.setContentAreaFilled(false); this.setUI(new BasicButtonUI()); } + public void setBgImage(Image image) + { + this.bgImage = image; + } + /** * Overrides the <code>paintComponent</code> method of <tt>JButton</tt> to * paint the button background and icon, and all additional effects of this @@ -101,19 +114,37 @@ public class SIPCommTextButton extends JButton visibility /= 2; - g.setColor(getBackground()); - g.fillRoundRect(1, 1, - this.getWidth() - 2, this.getHeight() - 2, - 20, 20); - if (visibility != 0.0f) { g.setColor(new Color(borderColor[0], borderColor[1], borderColor[2], visibility)); - g.drawRoundRect(0, 0, - this.getWidth() - 1, this.getHeight() - 1, + + if (bgImage != null) + g.fillRoundRect((this.getWidth() - bgImage.getWidth(null))/2, + (this.getHeight() - bgImage.getHeight(null))/2, + bgImage.getWidth(null) - 1, + bgImage.getHeight(null) - 1, + 20, 20); + else + g.fillRoundRect(0, 0, + this.getWidth() - 1, this.getHeight() - 1, + 20, 20); + } + + if (bgImage != null) + { + g.drawImage(bgImage, + (this.getWidth() - bgImage.getWidth(null))/2, + (this.getHeight() - bgImage.getHeight(null))/2, null); + } + else + { + g.setColor(getBackground()); + g.fillRoundRect(1, 1, + this.getWidth() - 2, this.getHeight() - 2, 20, 20); } + } /** diff --git a/src/net/java/sip/communicator/util/swing/SIPCommTextField.java b/src/net/java/sip/communicator/util/swing/SIPCommTextField.java index b9d24bf..4af8d57 100644 --- a/src/net/java/sip/communicator/util/swing/SIPCommTextField.java +++ b/src/net/java/sip/communicator/util/swing/SIPCommTextField.java @@ -8,8 +8,12 @@ package net.java.sip.communicator.util.swing; import java.awt.*; import java.awt.event.*; +import java.util.*; import javax.swing.*; +import javax.swing.event.*; + +import net.java.sip.communicator.util.swing.event.*; /** * The <tt>SIPCommTextField</tt> is a <tt>JTextField</tt> that offers the @@ -21,9 +25,24 @@ public class SIPCommTextField extends JTextField implements MouseListener, FocusListener, - KeyListener + KeyListener, + DocumentListener { - private final String defaultText; + /** + * The default text. + */ + private String defaultText; + + /** + * A list of all listeners registered for text field change events. + */ + private Collection<TextFieldChangeListener> changeListeners + = new LinkedList<TextFieldChangeListener>(); + + /** + * Indicates if the default text is currently visible. + */ + private boolean isDefaultTextVisible; /** * Creates an instance of <tt>SIPCommTextField</tt> by specifying the text @@ -34,7 +53,11 @@ public class SIPCommTextField { super(text); - this.defaultText = text; + if (text != null && text.length() > 0) + { + this.defaultText = text; + isDefaultTextVisible = true; + } this.setFont(getFont().deriveFont(10f)); this.setForeground(Color.GRAY); @@ -43,6 +66,7 @@ public class SIPCommTextField this.addFocusListener(this); this.addKeyListener(this); + this.getDocument().addDocumentListener(this); } /** @@ -154,4 +178,71 @@ public class SIPCommTextField } public void keyReleased(KeyEvent e){} + + /** + * Adds the given <tt>TextFieldChangeListener</tt> to the list of listeners + * notified on changes of the text contained in this field. + * @param l the <tt>TextFieldChangeListener</tt> to add + */ + public void addTextChangeListener(TextFieldChangeListener l) + { + synchronized (changeListeners) + { + changeListeners.add(l); + } + } + + /** + * Removes the given <tt>TextFieldChangeListener</tt> from the list of + * listeners notified on changes of the text contained in this field. + * @param l the <tt>TextFieldChangeListener</tt> to add + */ + public void removeTextChangeListener(TextFieldChangeListener l) + { + synchronized (changeListeners) + { + changeListeners.remove(l); + } + } + + public void changedUpdate(DocumentEvent e) {} + + /** + * Handles the change when a char has been inserted in the field. + * @param e the <tt>DocumentEvent</tt> that notified us + */ + public void insertUpdate(DocumentEvent e) + { + if(!super.getText().equals(defaultText)) + fireTextFieldChangeListener(0); + else + isDefaultTextVisible = true; + } + + /** + * Handles the change when a char has been removed from the field. + * @param e the <tt>DocumentEvent</tt> that notified us + */ + public void removeUpdate(DocumentEvent e) + { + if (!isDefaultTextVisible) + fireTextFieldChangeListener(1); + else + isDefaultTextVisible = false; + } + + /** + * Notifies all registered <tt>TextFieldChangeListener</tt>s that a change + * has occurred in the text contained in this field. + * @param eventType the type of the event to transfer + */ + private void fireTextFieldChangeListener(int eventType) + { + for (TextFieldChangeListener l : changeListeners) + switch (eventType) + { + case 0: l.textInserted(); break; + case 1: l.textRemoved(); break; + } + } } diff --git a/src/net/java/sip/communicator/util/swing/event/TextFieldChangeListener.java b/src/net/java/sip/communicator/util/swing/event/TextFieldChangeListener.java new file mode 100644 index 0000000..7a3c76b --- /dev/null +++ b/src/net/java/sip/communicator/util/swing/event/TextFieldChangeListener.java @@ -0,0 +1,27 @@ +/* + * 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.util.swing.event; + +/** + * The <tt>TextFieldChangeListener</tt> listens for any changes in the text + * contained in a <tt>SIPCommTextField</tt>. It is notified every time a char + * is inserted or removed from the field. + * + * @author Yana Stamcheva + */ +public interface TextFieldChangeListener +{ + /** + * Indicates that a text has been removed from the text field. + */ + public void textRemoved(); + + /** + * Indicates that a text has been inserted to the text field. + */ + public void textInserted(); +} |