diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | bcb704366cb5e333a626c18c308c7e0448a8e69f (patch) | |
tree | f0d6ab7d78ecdd9207cf46536376b44b91a1ca71 /kopete/libkopete | |
download | tdenetwork-bcb704366cb5e333a626c18c308c7e0448a8e69f.tar.gz tdenetwork-bcb704366cb5e333a626c18c308c7e0448a8e69f.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdenetwork@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kopete/libkopete')
269 files changed, 44762 insertions, 0 deletions
diff --git a/kopete/libkopete/API-TODO b/kopete/libkopete/API-TODO new file mode 100644 index 00000000..ea63082b --- /dev/null +++ b/kopete/libkopete/API-TODO @@ -0,0 +1,248 @@ +This file is a listing of (proposed) changes to be made to libkopete's API. + + + Buddy Icons: +============== + +Some support for buddy icons is needed in libkopete. Maybe just a simple contact property and a +metacontact property computed from it will do. + + + Properties: +============= + +The current properties system is a bit of a mess. With the PluginDataObjects, the ContactProperties, +KopeteContact's serializeProperties, and the various properties which are stored specially but +shouldn't be (such as KopeteGroup::expanded) it's hard to know where you are. I (Richard) would like +to replace this whole set of different property systems with a unified one. The way I see this +working is as follows: +- An object is created for each property. That object represents the property, and knows how to read + set store and manipulate that property. +- Properties on objects supporting them (KopeteContact, KopeteMetaContact, KopeteGroup, and so on) + will be accessed in a uniform manner, eg: + contact(myProperty) = 42; + metaContact(displayName) = i18n("Ford Prefect"); + The exact notation is not finalised yet. Maybe X[...] or X.property(...) would be clearer? +- New types of properties with different serialization requirements and so on can easily be created +- MetaContact properties can be computed from Contact properties (eg, for buddy icon, away message, + idle time and so on) by writing an appropriate property class. +- This system would be extended to plugin configuration, so plugin authors can easily deal with + configuration variables without having to use strings as keys. + + + KopeteMessageManager: +======================= + +KopeteMessageManager should allow any number of views to be attached, from 0 +to infinity. + +A lot of code should be moved from the KopeteViewManager to the KopeteMessageManager. +Allowing the creation of chatwindow plugin more easy + +The chat window should be restructured so each ChatView object has its own +send button. (-that's not a part of the libkopete api-) + + + KopeteMessage: +================ + +KopeteMessage should be reorganised to use QDomDocument or something similar +to store its contents - the purpose of this is so libkopete can provide a +uniform way of dealing with messages; the emoticon and link-adding filters +don't then need to grok HTML in order to be able to mangle a message + + + KopeteContactList +=================== + +Add to KopeteMetaContact a statusPixmap() function to return the pixmap +associated with its name. Take implementation from KopeteMetaContactLVI. +Use this function in the Kopete::MimeSourceFactory so we get MCs being +grayed if they're idle in tooltips too. + +KopeteContactList::removeGroup should remove the group from all +metacontacts. Move code from KopeteContactListView to KopeteContactList for +this. + +KopeteContactList::findContact and KopeteMetaContact::findContact should maybe be removed, +or at least contains ptr to accounts insteads of id. + +KopeteContact::slotDeleteContact should be renamed, maybe return a bool to cancel the deletion of the contact. + +Add an iconName() function to contacts, metacontacts and accounts returning a string that +can be put in an <img src="%1"> in any rich text to give the appropriate icon. See the +MimeSourceFactory. + +KCL::selectedMetaContacts really doesn't belong here. A contact list shouldn't +have a concept of selected items (this is action- and UI-specific knowledge). +The only place this is used is when plugins' actions need to be enabled and +disabled, or applied. Find a better way to do this UI-specific task. +KCL::selectedGroups can be removed outright. + + + KopeteEmoticon +================ + +Allow emoticons and emoticon sets to be flagged as being for only a specific protocol. +Allow the user to have more than one emoticon set enabled at once, and to set priorities. +This way, the user will be able to have a base theme, a set of MSN-specific emoticons, a +set of Gadu-Gadu-specific emoticons and so on. + +Possibly move emoticon support into a plugin? + + + KopeteOnlineStatus +==================== + +Add an Unknown status to KopeteOnlineStatus for when the status of a +contact is unknown (in the case that they've not authorised you, or the +protocol does not provide presence information) and a status for Unreachable +(in the case that your account is offline, etc). The crucial difference is +that a contact with Unknown status may be reachable (though that contact +should probably be avoided for messaging unless nothing else is available). + +More granular away settings: see Bug 57297. +The number of different global statuses (away / busy / be right back) should +be extended to a configurable list where each element contains the name of the +status, the default away message, and the KOS for every account)... though +perhaps this would be better placed in an 'advanced status' plugin? + +Add a way to register automatically KOS. The code for the right-click menu in +the Kopete account tray is duplicated all over the place; this should be +done automatically by the account tray code. + + + KopeteAccount +=============== + +KopeteAccount::password should be split in two method: KopeteAccount::password which return the +remembered password if any, but which does not try to ask it to the user. and getPassword which +acts like the acutal function. + +<lilachaze> KopeteAccount will soon have no ::password. instead, use Kopete::PasswordedAccount, + and acct.password().request(...) or acct.password().cachedValue() + + + DCOP +====== +The DCOP interface needs to be totally re-done so as to be useful, and to hopefully not rely on +display names. Obsolete functions previously used only for DCOP should be removed from KopeteContactList +where applicable. + + + KopeteTransferManager +======================= + +The file transfer mechanisms should be available to plugins... ie, a plugin should be able to +both initiate file transfers, and intercept and possibly cancel file transfer requests, the exact +same as plugins can ( will ) be able to filter KopeteMessages ( see below ). + + + Message Processing +==================== + +Some sort of async message processing API needs to be designed and implemented +Richard's proposal: (email questions to the list or to kde@metafoo.co.uk) +- how do we order the various message filters available properly? + they give us a processing stage, and an offset within that stage. the + stages will be something like: + for an incoming message: + - Start - message was just received (History) + - ToSent - convert from received format to sent format (GPG) + - ToDesired - convert to how the user wants the message (Translator, AutoReplace) + ToDesired+Before - Highlight + - Format - decorate the message (without changing the content) (Links, Emoticons, TextEffect) + - Finished - message is now ready for display (ChatWindow / MessageQueue) + for an outgoing message: + - Start - user just hit Send + - Parse - process commands (CommandHandler, Alias, Now Listening) + Parse+After - History + - ToDesired - convert to how the user wanted to send (Translator, AutoReplace) + - Format - decorate the message (without changing the content) (TextEffect) + - ToSent - convert to the format to send in (GPG) + - Finished - message is now ready for sending (Protocols) + There should be a number of offsets defined for when to do the + processing, within a stage, such as: + - Before - before any other processing in this stage + - VeryEarly + - Early + - Normal + - Late + - VeryLate + - After - after any other processing in this stage +- how do we construct a set of message filters for a particular message + manager? + - message filters register themselves with the filter manager, with a + message direction, a stage and an offset within that stage. + - each registered message filter factory gets queried (in stage/offset + order) by the object creating the filter chain. it either returns a + new filter for the chain, or returns NULL (meaning this filter is not + needed in this chain). + - the signals in one filter are connected to the slots in the next. any + sent/received message is handed to the first filter in the appropriate + chain. +- how long does a filter chain live for? + - it's created when it's first needed (when a message is sent / received + and no chain already exists to process it, or when a chatwindow is + opened) + - it's reference counted + - the MessageQueue / ChatWindow holds a reference to its chains + - the chain knows how many messages are in it (the messages unregister + themselves when they're destroyed) + - this makes it trivial to implement 65803 - stay in chatwindows when no + window is open - just make the Kopete::Contact hold a reference to the + receive chain +- interactions with the chat manager + - the chat manager (or possibly just 'chat') is an abstraction for a + conversation between our client/user and some other computer/user. it's + a bit like the message manager we have now, but more sophisticated. + - the send and receive chains are fundamentally linked - they are owned + by the same chat manager (which has a chainFor(MessageDirection) + function) + - when a chain's reference count drops to 0, it stays alive until + all the messages in it have been processed, but calls to + chainFor(Outgoing) will create a new chain. if we want, we can + guarantee messages from the old chain get sent over the wire before + ones from the new chain, but it's probably not essential. +- interactions with a chat view + - the ChatWindow component above is actually the ChatWindowFilter. it's + owned by the filter chain, and so should not be a QWidget. + - when a chat view is closed, it drops its reference to the various + message chains. but the receive chain will still exist if there's an + incoming message that's still being processed. therefore: + - the chatwindow prompts you if you ask it to be closed and there are + messages left in its receive chain + - the chatwindow filter will *drop* messages that reach it if there's no + chatview available to send them to. but that's ok since the user will + already have been prompted about this. +- problems with this design + - when the receive chain is closed (refcount drops to 0), it's not + necessarily the case that messages in it still need to be processed. + for instance, if you don't use the History plugin, or all the messages + are already past it, it probably doesn't matter if they're dropped. we + should somehow allow the filters to prevent destruction of the part of + the chain before them, and if none of them does, destroy it. + + + + Invitation Handling Proposal: +=============================== + +Invitations is framework that allow others network applications (Games, Desktop +Sharing, Video conference, [file transfer?], ...) to initiate the communication +with kopete. (like Windows Messenger does) + +The user has two ways to initiate such as thing: + +- in the application itself, they could (with the help of KABC) select a + contactable contact; the invitaiton is transported to Kopete. +- in Kopete, in the chat window, an "tools" menu, with all possible actions. + + + + Blacklist support: +==================== + +<roie> BlackList API is added. Protocols maintainers, please check if a contact + is blocked before passing messages to MessageManager. + I will also attach the GUI to block() and unblock() in the near future.
\ No newline at end of file diff --git a/kopete/libkopete/Makefile.am b/kopete/libkopete/Makefile.am new file mode 100644 index 00000000..47e6b9cf --- /dev/null +++ b/kopete/libkopete/Makefile.am @@ -0,0 +1,71 @@ +if compile_LIBKOPETE_COMPAT +COMPAT_DIR = compat +COMPAT_LIBS = compat/libkopetecompat.la +endif + + +include ../../admin/Doxyfile.am +DOXYGEN_REFERENCES = kio kdecore kdeui +DOXYGEN_EXCLUDE = compat +DOXYGEN_SET_PROJECT_NAME = libkopete + +SUBDIRS = $(COMPAT_DIR) private ui . avdevice + +METASOURCES = AUTO + +AM_CPPFLAGS = -DKDE_NO_COMPAT -DQT_NO_COMPAT -DQT_NO_CAST_ASCII -DQT_NO_ASCII_CAST \ + $(KOPETE_INCLUDES) -I$(top_srcdir)/kopete/libkopete/private \ + -I$(top_srcdir)/kopete/libkopete/ui $(all_includes) + +lib_LTLIBRARIES = libkopete.la + +libkopete_la_SOURCES = knotification.cpp connectionmanager.cpp kopeteonlinestatus.cpp kopeteonlinestatusmanager.cpp \ + kopeteprotocol.cpp kopetecontact.cpp kopetepluginmanager.cpp kopeteplugin.cpp \ + kopetemessage.cpp kopetechatsession.cpp kopetechatsessionmanager.cpp \ + kopetecontactlist.cpp kopetemetacontact.cpp kopeteawaydialog.cpp kopetetransfermanager.cpp \ + kopetegroup.cpp kcautoconfigmodule.cpp kopeteaccountmanager.cpp kopeteaccount.cpp \ + kopetecontactlistelement.cpp kopetecommandhandler.cpp kopeteaway.cpp \ + kopeteawayaction.cpp kautoconfig.cpp kopetewalletmanager.cpp kopetecontactproperty.cpp \ + kopetepassword.cpp kopeteglobal.cpp kopeteuiglobal.cpp kopetepasswordedaccount.cpp \ + kopetemimetypehandler.cpp kopetetask.cpp kopetemimesourcefactory.cpp \ + kopeteeventpresentation.cpp kopetenotifyevent.cpp kopetenotifydataobject.cpp kopeteblacklister.cpp \ + kopetemessageevent.cpp kopetemessagehandler.cpp kopetemessagehandlerchain.cpp \ + kopetesimplemessagehandler.cpp kopeteproperties.cpp kabcpersistence.cpp connectionmanager.skel \ + clientiface.stub managedconnectionaccount.cpp networkstatuscommon.h kopeteconfig.kcfgc kopeteutils.cpp \ + kopeteprefs.cpp kopetepicture.cpp webcamwidget.cpp + +libkopete_la_LDFLAGS = -no-undefined -version-info 1:0:0 $(all_libraries) +libkopete_la_LIBADD = -lkabc ui/libkopeteui.la $(COMPAT_LIBS) $(LIB_KIO) $(LIB_XSS) $(LIB_XRENDER) + +kde_kcfg_DATA = kopete.kcfg + +#AM_CXXFLAGS = -DQT_PLUGIN +#kde_widget_LTLIBRARIES = libkopetewidgets.la +#libkopetewidgets_la_LDFLAGS = $(KDE_PLUGIN) -module $(all_libraries) +#libkopetewidgets_la_LIBADD = $(LIB_KIO) libkopete.la ui/libkopeteui.la +#libkopetewidgets_la_SOURCES = ui/kopetewidgets.cpp + +kopetewidgets.cpp: $(srcdir)/kopete.widgets + $(MAKEKDEWIDGETS) -o kopetewidgets.cpp $(srcdir)/kopete.widgets + +rcdir = $(kde_datadir)/kopete +rc_DATA = kopetecommandui.rc + +servicetype_DATA = kopeteplugin.desktop kopeteprotocol.desktop kopeteui.desktop +servicetypedir = $(kde_servicetypesdir) + +kopeteincludedir = $(includedir)/kopete +kopeteinclude_HEADERS = kopeteaccount.h kopeteaccountmanager.h kopeteawayaction.h kopeteawaydialog.h kopeteaway.h \ + kopeteblacklister.h kopetecommandhandler.h kopetecontact.h kopetecontactlistelement.h kopetecontactlist.h \ + kopetecontactproperty.h kopeteeventpresentation.h kopete_export.h kopeteglobal.h kopetegroup.h \ + kopetemessageevent.h kopetemessage.h kopetemessagehandlerchain.h kopetemessagehandler.h \ + kopetechatsession.h kopetechatsessionmanager.h kopetemetacontact.h kopetemimetypehandler.h \ + kopeteonlinestatus.h kopeteonlinestatusmanager.h kopetepasswordedaccount.h \ + kopetepassword.h kopeteplugin.h kopeteprotocol.h kopetesimplemessagehandler.h kopetetask.h \ + kopetetransfermanager.h kopeteuiglobal.h kabcpersistence.h managedconnectionaccount.h \ + kopetenotifydataobject.h kopeteversion.h kopeteprefs.h kopetepicture.h webcamwidget.h \ + kopetepluginmanager.h + +# vim: set noet: + +noinst_HEADERS = kopeteblacklister.h kopeteconfig.h diff --git a/kopete/libkopete/PORTING b/kopete/libkopete/PORTING new file mode 100644 index 00000000..b1d8e8a6 --- /dev/null +++ b/kopete/libkopete/PORTING @@ -0,0 +1,93 @@ +Porting from Kopete 0.9 to Kopete 0.10 + + +*) KopetePluginManager has been renamed Kopete::PluginManager + .) QMap<KPluginInfo *, KopetePlugin *> loadedPlugins( const QString &category = QString::null ) const; + the QMap has been replaced by a QPtrList<KopetePlugin> the KPluginInfo is not interesting here. + .) addressBookFields( KopetePlugin *p ) has been removed + .) pluginName, pluginId, pluginIcon has been removed (they are acessible from pluginInfo) + .) KPluginInfo *pluginInfo( KopetePlugin* ) has been added + + +*) KopetePlugin has been renamed Kopete::Plugin + .) addressBookFields, addressBookIndexField and addAddressBookField have been removed (they were useless) + .) customChatWindowPopupActions( const KopeteMessage &, DOM::Node &node ) has been removed. + It was used to show the channel's menu when you right click on a IRC channel. Better to show the contact menu in the chatwindow for every contact. + .) KPluginInfo *pluginInfo() + + +*) KopeteProtocol has been renamed Kopete::Protocol + .) protocolAction() has been removed + .) the broken canSendOffline() has been removed, and we can uses capabilities insteads + .) (set)RichTextCapabilities has been renamed to (set)Capabilities + + +*) KopeteAccountManager has been renamed to Kopete::AccountManager + .) manager() has been renamed to self() + .) registerAccount is now public, and MUST be called manualy on account creation + .) autoConnect has been merged with connectAll which only connect accounts with the flag autoConnect + .) accountReady has been renamed again to accountRegistered + .) accountUnregistered signal takes now a const Account. + + +*) KopeteAccount has been renamed to Kopete::Account + .) Account no longer inerits from PluginDataObject. you can access directly to the config with configGroup() + The config now uses the KConfig cache dirrectly and not internal cache. + .) loaded() has been removed since properties are available dirrectly in the constructor. + .) setAccountId and accountIdChanged has been removed + .) password() setPassword() rememberPassword() has been removed. if the account has a pssword, uses PasswordedAccount + .) addContactToMetaContact has been renamed createContact. it has loose his displayName param, uses the metacontact one if you want. + .) addContact has been splitted into addMetaContact and addContact + .) connect now takes the initial status as parameter + .) autoLogin has been renamed autoConnect for consistency + + +*) KopeteContactList has been renamed to Kopete::ContactList + .) contactList() has been renamed to self() + .) getGroup has been renamed to group() or findGroup() + .) findContact now return a Contact + .) findContactByDisplayName has been renamed findMetaContactByDisplayName + .) metaContactDeleted signal has been renamed metaContactRemoved for consistency + + +*) KopeteMetaContact has been renamed to Kopete::MetaContact + .) persistentDataChanged take no more argument + .) isTopLevel has been removed + .) groupSyncMode has been removed because it is broken + + +*) KopeteContact has been renamed to Kopete::Contact + .) displayName has finaly been totaly removed + .) slotDeleteContact has been renamed to deleteContact + .) slotUserInfo has been renamed userInfo and is now pure virtual + + +*) KopeteGroup has been renamed to Kopete::Group + .) renamed() signal has been renamed into displayNameChanged() for consistancy + .) internalName has been removed + + +*) KopetePluginDataObject has been replaced Kopete::ContactListElement + + +*) KopeteOnlineStatus has been renamed Kopete::OnlineStatus + .) caption has been removed (moved to OnlineStatusManager + + +*) OnlineStatusIconCache has been replaced by OnlineStatusManager + + +*) KopeteMessage => Kopete::Message + + +*) KopeteMessageManager => Kopete::ChatSession + .) there are not anymore mmId() + .) user() has been renamed myself() + .) closed() has been renamed chatSessionDestroyed(); + .) typingMsg has been renamed userTyping + + +*) KopeteMessageManagerFactory => Kopete::ChatSessionManager + .) every function with messageManager has been renamed with ChatSession + .) addKopeteMessageManager => registerChatSession +
\ No newline at end of file diff --git a/kopete/libkopete/avdevice/Makefile.am b/kopete/libkopete/avdevice/Makefile.am new file mode 100644 index 00000000..a234f797 --- /dev/null +++ b/kopete/libkopete/avdevice/Makefile.am @@ -0,0 +1,18 @@ +INCLUDES =$(GLINC) $(all_includes) +AM_CPPFLAGS = -DKDE_NO_COMPAT -DQT_NO_COMPAT -DQT_NO_CAST_ASCII -DQT_NO_ASCII_CAST \ + $(KOPETE_INCLUDES) -I$(top_srcdir)/kopete/libkopete/private \ + -I$(top_srcdir)/kopete/libkopete/ui $(all_includes) +METASOURCES = AUTO +lib_LTLIBRARIES = libkopete_videodevice.la +noinst_LTLIBRARIES = libkvideoio.la +libkopete_videodevice_la_LDFLAGS = $(KDE_RPATH) $(all_libraries) + +noinst_HEADERS = kxv.h qvideo.h qvideostream.h videocontrol.h videodevice.h \ + videodevicemodelpool.h videodevicepool.h videoinput.h \ + sonix_compress.h bayer.h +libkopete_videodevice_la_SOURCES = videocontrol.cpp videodevice.cpp \ + videodevicemodelpool.cpp videodevicepool.cpp videoinput.cpp \ + sonix_compress.cpp bayer.cpp +libkvideoio_la_LDFLAGS = -no-undefined $(all_libraries) -version-info 1:0:0 +libkvideoio_la_SOURCES = kxv.cpp qvideo.cpp qvideostream.cpp +libkvideoio_la_LIBADD = $(LIB_QT) $(LIB_KDECORE) $(GLLIB) diff --git a/kopete/libkopete/avdevice/bayer.cpp b/kopete/libkopete/avdevice/bayer.cpp new file mode 100644 index 00000000..69189bae --- /dev/null +++ b/kopete/libkopete/avdevice/bayer.cpp @@ -0,0 +1,118 @@ +/* + * BAYER2RGB24 ROUTINE TAKEN FROM: + * + * Sonix SN9C101 based webcam basic I/F routines + * Copyright (C) 2004 Takafumi Mizuno <taka-qce@ls-a.jp> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +void bayer2rgb24(unsigned char *dst, unsigned char *src, long int WIDTH, long int HEIGHT) +{ + long int i; + unsigned char *rawpt, *scanpt; + long int size; + + rawpt = src; + scanpt = dst; + size = WIDTH*HEIGHT; + + for ( i = 0; i < size; i++ ) + { + if ( (i/WIDTH) % 2 == 0 ) + { + if ( (i % 2) == 0 ) + { + // B + if ( (i > WIDTH) && ((i % WIDTH) > 0) ) + { + *scanpt++ = (*(rawpt-WIDTH-1)+*(rawpt-WIDTH+1)+*(rawpt+WIDTH-1)+*(rawpt+WIDTH+1))/4; // R + *scanpt++ = (*(rawpt-1)+*(rawpt+1)+*(rawpt+WIDTH)+*(rawpt-WIDTH))/4; // G + *scanpt++ = *rawpt; // B + } + else + { + // first line or left column + *scanpt++ = *(rawpt+WIDTH+1); // R + *scanpt++ = (*(rawpt+1)+*(rawpt+WIDTH))/2; // G + *scanpt++ = *rawpt; // B + } + } + else + { + // (B)G + if ( (i > WIDTH) && ((i % WIDTH) < (WIDTH-1)) ) + { + *scanpt++ = (*(rawpt+WIDTH)+*(rawpt-WIDTH))/2; // R + *scanpt++ = *rawpt; // G + *scanpt++ = (*(rawpt-1)+*(rawpt+1))/2; // B + } + else + { + // first line or right column + *scanpt++ = *(rawpt+WIDTH); // R + *scanpt++ = *rawpt; // G + *scanpt++ = *(rawpt-1); // B + } + } + } + else + { + if ( (i % 2) == 0 ) + { + // G(R) + if ( (i < (WIDTH*(HEIGHT-1))) && ((i % WIDTH) > 0) ) + { + *scanpt++ = (*(rawpt-1)+*(rawpt+1))/2; // R + *scanpt++ = *rawpt; // G + *scanpt++ = (*(rawpt+WIDTH)+*(rawpt-WIDTH))/2; // B + } + else + { + // bottom line or left column + *scanpt++ = *(rawpt+1); /* R */ + *scanpt++ = *rawpt; /* G */ + *scanpt++ = *(rawpt-WIDTH); /* B */ + } + } + else + { + // R + if ( i < (WIDTH*(HEIGHT-1)) && ((i % WIDTH) < (WIDTH-1)) ) + { + *scanpt++ = *rawpt; // R + *scanpt++ = (*(rawpt-1)+*(rawpt+1)+*(rawpt-WIDTH)+*(rawpt+WIDTH))/4; // G + *scanpt++ = (*(rawpt-WIDTH-1)+*(rawpt-WIDTH+1)+*(rawpt+WIDTH-1)+*(rawpt+WIDTH+1))/4; // B + } + else + { + // bottom line or right column + *scanpt++ = *rawpt; /* R */ + *scanpt++ = (*(rawpt-1)+*(rawpt-WIDTH))/2; /* G */ + *scanpt++ = *(rawpt-WIDTH-1); /* B */ + } + } + } + rawpt++; + } +} + diff --git a/kopete/libkopete/avdevice/bayer.h b/kopete/libkopete/avdevice/bayer.h new file mode 100644 index 00000000..af6d8baf --- /dev/null +++ b/kopete/libkopete/avdevice/bayer.h @@ -0,0 +1,30 @@ +/* + * BAYER2RGB24 ROUTINE TAKEN FROM: + * + * Sonix SN9C101 based webcam basic I/F routines + * Copyright (C) 2004 Takafumi Mizuno <taka-qce@ls-a.jp> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + +void bayer2rgb24 (unsigned char *dst, unsigned char *src, long int WIDTH, long int HEIGHT); diff --git a/kopete/libkopete/avdevice/kxv.cpp b/kopete/libkopete/avdevice/kxv.cpp new file mode 100644 index 00000000..661bdfad --- /dev/null +++ b/kopete/libkopete/avdevice/kxv.cpp @@ -0,0 +1,711 @@ +/* + * KDE Xv interface + * + * Copyright (C) 2001 George Staikos (staikos@kde.org) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <assert.h> + +#include <qwindowdefs.h> +#include <qwidget.h> + +#include <kdebug.h> + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "kxv.h" + + +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/StringDefs.h> +#include <X11/Xatom.h> +#ifdef HAVE_XSHM +extern "C" { +#include <sys/shm.h> +#include <X11/extensions/XShm.h> +} +#endif + +#ifdef HAVE_LIBXV +#include <X11/extensions/Xv.h> +#include <X11/extensions/Xvlib.h> +#endif + +#ifdef HAVE_LIBXVMC +#include <X11/extensions/XvMC.h> +#include <X11/extensions/XvMClib.h> +#endif + + +KXv::KXv() +{ + xv_adaptors = 0; + _devs.setAutoDelete(true); +} + + +KXv::~KXv() +{ + kdDebug() << "KXv::~KXv: Close Xv connection." << endl; + _devs.clear(); + +#ifdef HAVE_LIBXV + if (xv_adaptors > 0) + XvFreeAdaptorInfo((XvAdaptorInfo *)xv_adaptor_info); +#endif +} + + +KXvDeviceList& KXv::devices() +{ + return _devs; +} + + +bool KXv::haveXv() +{ +#ifndef HAVE_LIBXV + return false; +#else + unsigned int tmp; + if (Success != XvQueryExtension(qt_xdisplay(), + &tmp, + &tmp, + &tmp, + &tmp, + &tmp)) + return false; + + return true; +#endif +} + + +KXv* KXv::connect(Drawable d) +{ + KXv *xvptr; + + xvptr = new KXv; + if (!xvptr->init(d)) { + kdDebug() << "KXv::connect: Xv init failed." << endl; + delete xvptr; + return NULL; + } + + kdDebug() << "KXv::connect: Xv init completed." << endl; + return xvptr; +} + + +bool KXv::init(Drawable d) +{ +#ifndef HAVE_LIBXV + return false; +#else + if (Success != XvQueryExtension(qt_xdisplay(), + &xv_version, + &xv_release, + &xv_request, + &xv_event, + &xv_error)) { + kdWarning() << "KXv::init: Xv extension not available." << endl; + return false; + } + +#ifdef HAVE_LIBXVMC + // Causes crashes for some people. + // if (Success == XvMCQueryExtension(qt_xdisplay(),0,0)) { + // kdDebug() << "Found XvMC!" << endl; + // } +#endif + + if (Success != XvQueryAdaptors(qt_xdisplay(), + d, + &xv_adaptors, + (XvAdaptorInfo **)&xv_adaptor_info)) { + // Note technically fatal... what to do? + kdWarning() << "KXv::init: XvQueryAdaptors failed." << endl; + } + + XvAdaptorInfo *ai = (XvAdaptorInfo *)xv_adaptor_info; + + for (unsigned int i = 0; i < xv_adaptors; i++) { + KXvDevice *xvd = new KXvDevice; + xvd->xv_type = ai[i].type; + xvd->xv_port = ai[i].base_id; + xvd->xv_name = ai[i].name; + xvd->xv_adaptor = i; + xvd->xv_nvisualformats = ai[i].num_formats; + xvd->xv_visualformats = ai[i].formats; + if (ai[i].type & XvInputMask && + ai[i].type & XvVideoMask ) { + kdDebug() << "KXv::init: Xv VideoMask port " << ai[i].base_id << " was found." + << " Device is: " << ai[i].name << "." << endl; + } + if (ai[i].type & XvInputMask && + ai[i].type & XvImageMask ) { + kdDebug() << "KXv::init: Xv ImageMask port " << ai[i].base_id << " was found." + << " Device is: " << ai[i].name << "." << endl; + } + + if (xvd->init()) { + _devs.append(xvd); + } else { + delete xvd; + } + } + + return true; +#endif +} + +bool KXvDevice::grabStill(QImage* /*pix*/, int /*dw*/, int /*dh*/) +{ +#ifndef HAVE_LIBXV + return false; +#else + return false; +#endif +} + +int KXvDevice::displayImage(QWidget *widget, const unsigned char *const data, int w, int h, int dw, int dh) +{ + if (!widget) + return -1; + return displayImage(widget->winId(), data, w, h, 0, 0, w, h, dw, dh); +} + +int KXvDevice::displayImage(QWidget *widget, const unsigned char *const data, int w, int h, int x, int y, int sw, int sh, int dw, int dh) +{ + if (!widget) + return -1; + return displayImage(widget->winId(), data, w, h, x, y, sw, sh, dw, dh); +} + +int KXvDevice::displayImage(Window win, const unsigned char *const data, int w, int h, int dw, int dh) +{ + return displayImage(win, data, w, h, 0, 0, w, h, dw, dh); +} + +int KXvDevice::displayImage(Window win, const unsigned char *const data, int w, int h, int x, int y, int sw, int sh, int dw, int dh) +{ +#ifndef HAVE_LIBXV + return -1; +#else + Q_ASSERT(xv_port != -1); + + // Must be a video capable device! + if (!(xv_type & XvImageMask) || !(xv_type & XvInputMask)) { + kdWarning() << "KXvDevice::displayImage: This is not a video capable device." << endl; + return -1; + } + + if (xv_image_w != w || xv_image_h != h || !xv_image) + rebuildImage(w, h, _shm); + + if (!xv_image) + return -1; + + if (win != xv_last_win && xv_gc) { + XFreeGC(qt_xdisplay(), xv_gc); + xv_gc = 0; + } + + if (!xv_gc) { + xv_last_win = win; + xv_gc = XCreateGC(qt_xdisplay(), win, 0, NULL); + } + + int rc = 0; + Q_ASSERT(xv_image); + if (!_shm) { + static_cast<XvImage*>(xv_image)->data = + (char *)const_cast<unsigned char*>(data); + rc = XvPutImage(qt_xdisplay(), xv_port, win, xv_gc, + static_cast<XvImage*>(xv_image), x, y, sw, sh, 0, 0, dw, dh); + } else { +#ifdef HAVE_XSHM + memcpy(static_cast<XvImage*>(xv_image)->data, data, static_cast<XvImage*>(xv_image)->data_size); + rc = XvShmPutImage(qt_xdisplay(), xv_port, win, xv_gc, + static_cast<XvImage*>(xv_image), x, y, sw, sh, 0, 0, dw, dh, 0); +#endif + } + + XSync(qt_xdisplay(), False); + return rc; +#endif +} + + +bool KXvDevice::startVideo(QWidget *w, int dw, int dh) +{ + if (!w) return false; + return startVideo(w->winId(), dw, dh); +} + + +bool KXvDevice::startVideo(Window w, int dw, int dh) +{ +#ifndef HAVE_LIBXV + return false; +#else + int sx = 0, sy = 0, dx = 0, dy = 0, sw = dw, sh = dh; + + // Must be a video capable device! + if (!(xv_type & XvVideoMask) || !(xv_type & XvInputMask)) { + kdWarning() << "KXvDevice::startVideo: This is not a video capable device." << endl; + return false; + } + + if (videoStarted) stopVideo(); + + if (xv_port == -1) { + kdWarning() << "KXvDevice::startVideo: No xv_port." << endl; + return false; + } + + if (w != xv_last_win && xv_gc) { + XFreeGC(qt_xdisplay(), xv_gc); + xv_gc = 0; + } + + if (!xv_gc) { + xv_last_win = w; + xv_gc = XCreateGC(qt_xdisplay(), w, 0, NULL); + } + + if (-1 != xv_encoding) { + sw = ((XvEncodingInfo *)xv_encoding_info)[xv_encoding].width; + sh = ((XvEncodingInfo *)xv_encoding_info)[xv_encoding].height; + } + + // xawtv does this here: + // ng_ratio_fixup(&dw, &dh, &dx, &dy); + + kdDebug() << "XvPutVideo: " << qt_xdisplay() + << " " << xv_port << " " << w << " " << xv_gc + << " " << sx << " " << sy << " " << sw << " " << sh + << " " << dx << " " << dy << " " << dw << " " << dh << endl; + XvPutVideo(qt_xdisplay(), xv_port, w, xv_gc, sx, sy, sw, sh, dx, dy, dw, dh); + + videoStarted = true; + videoWindow = w; + return true; +#endif +} + +bool KXvDevice::stopVideo() +{ +#ifndef HAVE_LIBXV + return false; +#else + if (!videoStarted) + return true; + if (xv_port == -1) { + kdWarning() << "KXvDevice::stopVideo: No xv_port." << endl; + return false; + } + + XvStopVideo(qt_xdisplay(), xv_port, videoWindow); + videoStarted = false; + return true; +#endif +} + + +KXvDevice::KXvDevice() +{ + xv_encoding_info = NULL; + xv_formatvalues = NULL; + xv_attr = NULL; + xv_port = -1; + xv_encoding = -1; + xv_name = QString::null; + xv_type = -1; + xv_adaptor = -1; + _shm = false; +#ifdef HAVE_LIBXV + xv_imageformat = 0x32595559; // FIXME (YUY2) +#ifdef HAVE_XSHM + if (!XShmQueryExtension(qt_xdisplay())) { + _haveShm = false; + } else { + _shm = true; + _haveShm = true; + } + xv_shminfo = new XShmSegmentInfo; +#else + xv_shminfo = 0; +#endif +#endif + xv_gc = 0; + xv_last_win = 0; + videoStarted = false; + _attrs.setAutoDelete(true); + xv_image = 0; + xv_image_w = 320; + xv_image_h = 200; +} + + +KXvDevice::~KXvDevice() +{ +#ifdef HAVE_LIBXV + _attrs.clear(); + if (videoStarted) stopVideo(); + if (xv_encoding_info) + XvFreeEncodingInfo((XvEncodingInfo *)xv_encoding_info); + XFree(xv_formatvalues); + XFree(xv_attr); +#ifdef HAVE_XSHM + delete (XShmSegmentInfo*)xv_shminfo; +#endif + destroyImage(); +#endif + if (xv_gc) + XFreeGC(qt_xdisplay(), xv_gc); + +#ifdef HAVE_LIBXV + if (xv_port != -1) + XvUngrabPort(qt_xdisplay(), xv_port, CurrentTime); +#endif +} + + +bool KXvDevice::init() +{ +#ifndef HAVE_LIBXV + return false; +#else + assert(xv_port != -1); // make sure we were prepped by KXv already. + + if (XvGrabPort(qt_xdisplay(), xv_port, CurrentTime)) { + kdWarning() << "KXvDevice::init(): Unable to grab Xv port." << endl; + return false; + } + + if (Success != XvQueryEncodings(qt_xdisplay(), + xv_port, + &xv_encodings, + (XvEncodingInfo **)&xv_encoding_info)) { + kdWarning() << "KXvDevice::init: Xv QueryEncodings failed. Dropping Xv support for this device." << endl; + return false; + } + + // Package the encodings up nicely + for (unsigned int i = 0; i < xv_encodings; i++) { + //kdDebug() << "Added encoding: " << ((XvEncodingInfo *)xv_encoding_info)[i].name << endl; + _encodingList << ((XvEncodingInfo *)xv_encoding_info)[i].name; + } + + xv_attr = XvQueryPortAttributes(qt_xdisplay(), + xv_port, + &xv_encoding_attributes); + XvAttribute *xvattr = (XvAttribute *)xv_attr; + kdDebug() << "Attributes for port " << xv_port << endl; + for (int i = 0; i < xv_encoding_attributes; i++) { + assert(xvattr); + kdDebug() << " -> " << xvattr[i].name + << ((xvattr[i].flags & XvGettable) ? " get" : "") + << ((xvattr[i].flags & XvSettable) ? " set" : "") + << " Range: " << xvattr[i].min_value + << " -> " << xvattr[i].max_value << endl; + + KXvDeviceAttribute *xvda = new KXvDeviceAttribute; + xvda->name = xvattr[i].name; + xvda->min = xvattr[i].min_value; + xvda->max = xvattr[i].max_value; + xvda->flags = xvattr[i].flags; + _attrs.append(xvda); + } + + XvImageFormatValues *fo; + fo = XvListImageFormats(qt_xdisplay(), xv_port, &xv_formats); + xv_formatvalues = (void *)fo; + kdDebug() << "Image formats for port " << xv_port << endl; + for (int i = 0; i < xv_formats; i++) { + assert(fo); + QString imout; + imout.sprintf(" 0x%x (%c%c%c%c) %s", + fo[i].id, + fo[i].id & 0xff, + (fo[i].id >> 8) & 0xff, + (fo[i].id >> 16) & 0xff, + (fo[i].id >> 24) & 0xff, + ((fo[i].format == XvPacked) ? + "Packed" : "Planar")); + kdDebug() << imout << endl; + } + + kdDebug() << "Disabling double buffering." << endl; + setAttribute("XV_DOUBLE_BUFFER", 0); + + return true; +#endif +} + + +bool KXvDevice::supportsWidget(QWidget *w) +{ +#ifndef HAVE_LIBXV + return false; +#else + for (int i = 0; i < xv_nvisualformats; i++) { + if (static_cast<XvFormat*>(xv_visualformats)[i].visual_id + == static_cast<Visual*>(w->x11Visual())->visualid) { + return true; + } + } + return false; +#endif +} + + +bool KXvDevice::isVideoSource() +{ +#ifndef HAVE_LIBXV + return false; +#else + if (xv_type & XvVideoMask && xv_type & XvInputMask) + return true; + return false; +#endif +} + + +bool KXvDevice::isImageBackend() +{ +#ifndef HAVE_LIBXV + return false; +#else + if (xv_type & XvImageMask && xv_type & XvInputMask) + return true; + return false; +#endif +} + + +const KXvDeviceAttributes& KXvDevice::attributes() +{ + return _attrs; +} + + +bool KXvDevice::getAttributeRange(const QString& attribute, int *min, int *max) +{ + for (KXvDeviceAttribute *at = _attrs.first(); at != NULL; at = _attrs.next()) { + if (at->name == attribute) { + if (min) *min = at->min; + if (max) *max = at->max; + return true; + } + } + return false; +} + + +bool KXvDevice::getAttribute(const QString& attribute, int *val) +{ +#ifndef HAVE_LIBXV + return false; +#else + for (KXvDeviceAttribute *at = _attrs.first(); at != NULL; at = _attrs.next()) { + if (at->name == attribute) { + if (val) + XvGetPortAttribute(qt_xdisplay(), xv_port, at->atom(), val); + return true; + } + } + return false; +#endif +} + + +bool KXvDevice::setAttribute(const QString& attribute, int val) +{ +#ifndef HAVE_LIBXV + return false; +#else + for (KXvDeviceAttribute *at = _attrs.first(); at != NULL; at = _attrs.next()) { + if (at->name == attribute) { + XvSetPortAttribute(qt_xdisplay(), xv_port, at->atom(), val); + XSync(qt_xdisplay(), False); + return true; + } + } + return false; +#endif +} + + +const QString& KXvDevice::name() const +{ + return xv_name; +} + + +int KXvDevice::port() const +{ + return xv_port; +} + +const QStringList& KXvDevice::encodings() const +{ + return _encodingList; +} + +bool KXvDevice::encoding(QString& encoding) +{ +#ifndef HAVE_LIBXV + return false; +#else + XvEncodingID enc; + + for (KXvDeviceAttribute *at = _attrs.first(); at != 0L; at = _attrs.next()) { + if (at->name == "XV_ENCODING") { + XvGetPortAttribute(qt_xdisplay(), xv_port, at->atom(), (int*)&enc); + kdDebug() << "KXvDevice: encoding: " << enc << endl; + encoding = enc; + return true; + } + } + return false; +#endif +} + +bool KXvDevice::setEncoding(const QString& e) +{ +#ifdef HAVE_LIBXV + for (unsigned int i = 0; i < xv_encodings; i++) { + if (e == ((XvEncodingInfo *)xv_encoding_info)[i].name) { + xv_encoding = i; + return setAttribute("XV_ENCODING", + ((XvEncodingInfo *)xv_encoding_info)[i].encoding_id); + } + } +#endif + return false; +} + +bool KXvDevice::videoPlaying() const +{ + return videoStarted; +} + + +bool KXvDevice::useShm(bool on) +{ +#ifndef HAVE_XSHM + if (on) { + return false; + } +#endif + if (!_haveShm) { + return false; + } + if (_shm != on) + rebuildImage(xv_image_w, xv_image_h, on); + if (_haveShm) // This can change in rebuildImage() + _shm = on; + return _shm; +} + + +bool KXvDevice::usingShm() const +{ + return _shm; +} + + +#include <unistd.h> +void KXvDevice::rebuildImage(int w, int h, bool shm) +{ + if (xv_image) { + destroyImage(); + } +#ifdef HAVE_LIBXV + if (!shm) { + xv_image = (void*)XvCreateImage(qt_xdisplay(), xv_port, xv_imageformat, + 0, w, h); + if (!xv_image) { + kdWarning() << "KXvDevice::rebuildImage: XvCreateImage failed." << endl; + } + } else { +#ifdef HAVE_XSHM + memset(xv_shminfo, 0, sizeof(XShmSegmentInfo)); + xv_image = (void*)XvShmCreateImage(qt_xdisplay(), xv_port, xv_imageformat, + 0, w, h, static_cast<XShmSegmentInfo*>(xv_shminfo)); + if (!xv_image) { + kdWarning() << "KXvDevice::rebuildImage: Error using SHM with Xv! Disabling SHM..." << endl; + _haveShm = false; + _shm = false; + xv_image = (void*)XvCreateImage(qt_xdisplay(), xv_port, xv_imageformat, + 0, w, h); + if (!xv_image) { + kdWarning() << "KXvDevice::rebuildImage: XvCreateImage failed." << endl; + } + } else { + static_cast<XShmSegmentInfo*>(xv_shminfo)->shmid = + shmget(IPC_PRIVATE, + static_cast<XvImage*>(xv_image)->data_size, + IPC_CREAT | 0600); + static_cast<XShmSegmentInfo*>(xv_shminfo)->shmaddr = + (char*)shmat(static_cast<XShmSegmentInfo*>(xv_shminfo)->shmid, 0, 0); + static_cast<XShmSegmentInfo*>(xv_shminfo)->readOnly = True; + static_cast<XvImage*>(xv_image)->data = + static_cast<XShmSegmentInfo*>(xv_shminfo)->shmaddr; + XShmAttach(qt_xdisplay(), static_cast<XShmSegmentInfo*>(xv_shminfo)); + XSync(qt_xdisplay(), False); + shmctl(static_cast<XShmSegmentInfo*>(xv_shminfo)->shmid, IPC_RMID, 0); + } +#endif + } + Q_ASSERT(xv_image != 0); + xv_image_w = w; + xv_image_h = h; +#endif +} + + +void KXvDevice::destroyImage() +{ +#ifdef HAVE_LIBXV + if (!_shm) { + if (xv_image) { + static_cast<XvImage*>(xv_image)->data = 0; + } + } else { + if (xv_image) { +#ifdef HAVE_XSHM + shmdt(static_cast<XShmSegmentInfo*>(xv_shminfo)->shmaddr); +#endif + } + } + XFree(xv_image); + xv_image = 0; +#endif +} + + +Atom KXvDeviceAttribute::atom() +{ + return XInternAtom(qt_xdisplay(), name.latin1(), False); +} diff --git a/kopete/libkopete/avdevice/kxv.h b/kopete/libkopete/avdevice/kxv.h new file mode 100644 index 00000000..d386cda9 --- /dev/null +++ b/kopete/libkopete/avdevice/kxv.h @@ -0,0 +1,260 @@ +/* + * KDE Xv interface + * + * Copyright (C) 2001 George Staikos (staikos@kde.org) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __KXV_H +#define __KXV_H + +#include <X11/X.h> +#include <X11/Xlib.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qptrlist.h> + +class QWidget; +class QImage; + +class KXvPrivate; +class KXvDevice; +class KXvDevicePrivate; + +typedef QPtrList<KXvDevice> KXvDeviceList; + + +class KXv +{ +public: + ~KXv(); + + /* + * To get access to the Xv extension, you call this method. It will return + * a KXv* object on success, or NULL if it can't connect. + * + * d is typically the Window ID + */ + static KXv *connect(Drawable d); + + /* + * True if we can connect to the Xv extension. + */ + static bool haveXv(); + + /* + * Return the list of Xv devices + */ + KXvDeviceList& devices(); + +protected: + KXv(); + bool init(Drawable d); + + /*** XV info ***/ + unsigned int xv_version, xv_release, xv_request, xv_event, xv_error; + unsigned int xv_adaptors; + void *xv_adaptor_info; + + KXvDeviceList _devs; + +private: + KXvPrivate *d; +}; + + + +class KXvDeviceAttribute +{ +public: + QString name; + int min; + int max; + int flags; + + Atom atom(); +}; + +typedef QPtrList<KXvDeviceAttribute> KXvDeviceAttributes; + + +class KXvDevice +{ + friend class KXv; +public: + + KXvDevice(); + ~KXvDevice(); + + /* + * return the list of known attributes + */ + const KXvDeviceAttributes& attributes(); + + /* + * return the range for a given attribute + */ + bool getAttributeRange(const QString& attribute, int *min, int *max); + + /* + * get the current value of a given attribute + */ + bool getAttribute(const QString& attribute, int *val); + + /* + * set the current value of a given attribute + */ + bool setAttribute(const QString& attribute, int val); + + bool grabStill(QImage *pix, int dw, int dh); + + /* + * True if this device can operate on the given widget + */ + bool supportsWidget(QWidget *w); + + /* + * Display the given image with Xv. + */ + int displayImage(QWidget *widget, const unsigned char *const data, int w, int h, int dw, int dh); + int displayImage(Window win, const unsigned char *const data, int w, int h, int dw, int dh); + + /* + * Display a portion of the given image with Xv. + */ + int displayImage(QWidget *widget, const unsigned char *const data, int w, int h, int x, int y, int sw, int sh, int dw, int dh); + int displayImage(Window win, const unsigned char *const data, int w, int h, int x, int y, int sw, int sh, int dw, int dh); + + /* + * Start a video stream in widget w, width dw, height dh + */ + bool startVideo(QWidget *w, int dw, int dh); + bool startVideo(Window w, int dw, int dh); + + /* + * Is the video playing + */ + bool videoPlaying() const; + + /* + * Stop video stream + */ + bool stopVideo(); + + /* + * True if this is an image output backend (video card) + */ + bool isImageBackend(); + + /* + * True if this is a video source + */ + bool isVideoSource(); + + /* + * Name of the device + */ + const QString& name() const; + + /* + * The Xv port for this device + */ + int port() const; + + /* + * The list of encodings/norms available + */ + const QStringList& encodings() const; + + /* + * get encoding + */ + bool encoding(QString& encoding); + + /* + * Set the encoding to the given one. This should be taken from the list. + */ + bool setEncoding(const QString& e); + + /* + * Set the image format. (ex YUV) + */ + int setImageFormat(int format); + + /* + * Get the current image format + */ + int imageFormat() const; + + /* + * Use SHM for PutImage if available + */ + bool useShm(bool on); + + /* + * Is SHM being used? + */ + bool usingShm() const; + + +protected: + bool init(); + + bool _shm; + KXvDeviceAttributes _attrs; + + int xv_type, xv_adaptor; + QString xv_name; + int xv_port; + unsigned int xv_encodings; + int xv_encoding; + void *xv_encoding_info; + int xv_encoding_attributes; + void *xv_attr; + GC xv_gc; + Window xv_last_win; + + QStringList _encodingList; + + int xv_formats; + void *xv_formatvalues; + + int xv_nvisualformats; + void *xv_visualformats; // XvFormat* + + bool videoStarted; + Window videoWindow; + + long xv_imageformat; + + void *xv_shminfo; + void *xv_image; + int xv_image_w; + int xv_image_h; + bool _haveShm; + + +private: + KXvDevicePrivate *d; + + void rebuildImage(int w, int h, bool shm); + void destroyImage(); +}; + + +#endif + diff --git a/kopete/libkopete/avdevice/qvideo.cpp b/kopete/libkopete/avdevice/qvideo.cpp new file mode 100644 index 00000000..ad6fb762 --- /dev/null +++ b/kopete/libkopete/avdevice/qvideo.cpp @@ -0,0 +1,154 @@ +/*************************************************************************** + qvideo.cpp + ---------- + begin : Sat Jun 12 2004 + copyright : (C) 2004 by Dirk Ziegelmeier + (C) 2002 by George Staikos + email : dziegel@gmx.de + ***************************************************************************/ + +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "qvideo.h" + +#include <kdebug.h> +#include <qpaintdevice.h> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +unsigned int QVideo::bytesppForFormat(ImageFormat fmt) +{ + switch (fmt) { + case FORMAT_RGB32: + case FORMAT_RGB24: + case FORMAT_BGR32: + case FORMAT_BGR24: + return 4; + + case FORMAT_RGB15_LE: + case FORMAT_RGB16_LE: + case FORMAT_RGB15_BE: + case FORMAT_RGB16_BE: + case FORMAT_YUYV: + case FORMAT_UYVY: + case FORMAT_YUV422P: + case FORMAT_YUV420P: + return 2; + + case FORMAT_GREY: + case FORMAT_HI240: + return 1; + + default: + // unknown format + return 0; + } +} + +bool QVideo::findDisplayProperties(ImageFormat& fmt, int& depth, unsigned int& bitsperpixel, int& bytesperpixel) +{ + XVisualInfo *vi_in, vi_out; + long mask = VisualScreenMask; + int nvis = 0; + + ImageFormat p = FORMAT_NONE; + int bpp = 0; + int d = 0; + + vi_out.screen = QPaintDevice::x11AppScreen(); + vi_in = XGetVisualInfo(qt_xdisplay(), mask, &vi_out, &nvis); + + if (vi_in) { + for (int i = 0; i < nvis; i++) { + bpp = 0; + int n; + XPixmapFormatValues *pf = XListPixmapFormats(qt_xdisplay(),&n); + d = vi_in[i].depth; + for (int j = 0; j < n; j++) { + if (pf[j].depth == d) { + bpp = pf[j].bits_per_pixel; + break; + } + } + XFree(pf); + + // FIXME: Endianess detection + + p = FORMAT_NONE; + switch (bpp) { + case 32: + if (vi_in[i].red_mask == 0xff0000 && + vi_in[i].green_mask == 0x00ff00 && + vi_in[i].blue_mask == 0x0000ff) { + p = FORMAT_BGR32; + kdDebug() << "QVideo: Found BGR32 display." << endl; + } + break; + case 24: + if (vi_in[i].red_mask == 0xff0000 && + vi_in[i].green_mask == 0x00ff00 && + vi_in[i].blue_mask == 0x0000ff) { + p = FORMAT_BGR24; + kdDebug() << "QVideo: Found BGR24 display." << endl; + } + break; + case 16: + if (vi_in[i].red_mask == 0x00f800 && + vi_in[i].green_mask == 0x0007e0 && + vi_in[i].blue_mask == 0x00001f) { + p = FORMAT_RGB15_LE; + kdDebug() << "QVideo: Found RGB16_LE display." << endl; + } else + if (vi_in[i].red_mask == 0x007c00 && + vi_in[i].green_mask == 0x0003e0 && + vi_in[i].blue_mask == 0x00001f) { + p = FORMAT_RGB15_LE; + kdDebug() << "QVideo: Found RGB15_LE display." << endl; + } + break; + case 8: + default: + continue; + } + + if (p != FORMAT_NONE) + break; + } + XFree(vi_in); + } + + if (p != FORMAT_NONE) { + int bytespp = bytesppForFormat(p); + kdDebug() << "QVideo: Display properties: depth: " << d + << ", bits/pixel: " << bpp + << ", bytes/pixel: " << bytespp << endl; + fmt = p; + bitsperpixel = bpp; + bytesperpixel = bytespp; + depth = d; + return true; + } else { + kdWarning() << "QVideo: Unable to find out palette. What display do you have????" << endl; + fmt = FORMAT_NONE; + bitsperpixel = 0; + bytesperpixel = 0; + depth = 0; + return false; + } +} diff --git a/kopete/libkopete/avdevice/qvideo.h b/kopete/libkopete/avdevice/qvideo.h new file mode 100644 index 00000000..20e999fc --- /dev/null +++ b/kopete/libkopete/avdevice/qvideo.h @@ -0,0 +1,68 @@ +// -*- c++ -*- +/*************************************************************************** + qvideo.h + -------- + begin : Sat Jun 12 2004 + copyright : (C) 2004 by Dirk Ziegelmeier + email : dziegel@gmx.de + ***************************************************************************/ + +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef QVIDEO_H +#define QVIDEO_H + +class QVideo +{ +public: + typedef enum { + FORMAT_NONE = 0, + FORMAT_GREY = (1<<0), + FORMAT_HI240 = (1<<1), + FORMAT_RGB15_LE = (1<<2), + FORMAT_RGB15_BE = (1<<3), + FORMAT_RGB16_LE = (1<<4), + FORMAT_RGB16_BE = (1<<5), + FORMAT_RGB32 = (1<<6), + FORMAT_BGR32 = (1<<7), + FORMAT_RGB24 = (1<<8), + FORMAT_BGR24 = (1<<9), + FORMAT_YUYV = (1<<10), + FORMAT_UYVY = (1<<11), + FORMAT_YUV422P = (1<<12), + FORMAT_YUV420P = (1<<13), + FORMAT_ALL = 0x00003FFF + } ImageFormat; + + typedef enum { + METHOD_NONE = 0, + METHOD_XSHM = 1, + METHOD_XV = 2, + METHOD_XVSHM = 4, + METHOD_X11 = 8, + METHOD_DGA = 16, /* unimplemented */ + METHOD_GL = 32, + METHOD_SDL = 64 /* unimplemented */ + } VideoMethod; + + static unsigned int bytesppForFormat(ImageFormat fmt); + static bool findDisplayProperties(ImageFormat& fmt, int& depth, unsigned int& bitsperpixel, int& bytesperpixel); +}; + +#endif //QVIDEO_H + diff --git a/kopete/libkopete/avdevice/qvideostream.cpp b/kopete/libkopete/avdevice/qvideostream.cpp new file mode 100644 index 00000000..cd7aafc2 --- /dev/null +++ b/kopete/libkopete/avdevice/qvideostream.cpp @@ -0,0 +1,731 @@ +/* + * + * Copyright (C) 2002 George Staikos <staikos@kde.org> + * 2004 Dirk Ziegelmeier <dziegel@gmx.de> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "qvideostream.h" +#include <qevent.h> +#include <qimage.h> +#include <qtimer.h> + +#include <kdebug.h> +#include "kxv.h" + +#include <sys/types.h> +#include <X11/Xutil.h> + +#ifdef HAVE_XSHM +extern "C" { +#include <sys/shm.h> +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/extensions/XShm.h> +} +#endif + +#ifdef HAVE_GL +class QVideoStreamGLWidget : public QGLWidget +{ +public: + QVideoStreamGLWidget(QWidget* parent = 0, const char* name = 0); + virtual ~QVideoStreamGLWidget(); + + void setInputSize(const QSize& sz); + void display(const unsigned char *const img, int x, int y, int sw, int sh); + +private: + virtual void resizeGL(int w, int h); + void initializeGL(); + + virtual bool eventFilter( QObject *o, QEvent *e ); + void calc(QPoint& p, QPoint& v); + + + QSize _inputSize; + GLuint _tex; + int _tw, _th; + QWidget* _w; + int _maxGL; + QSize _sz; + bool _glfun; + QPoint _ul, _ur, _ll, _lr; + QPoint _vul, _vur, _vll, _vlr; + QTimer* _glfunTimer; +}; +#endif + +class QVideoStreamPrivate +{ +public: + QVideoStreamPrivate(); + ~QVideoStreamPrivate(); + KXv *xvHandle; + KXvDevice *xvdev; + XImage *xim; + GC gc; +#ifdef HAVE_GL + QVideoStreamGLWidget* glwidget; +#endif +#ifdef HAVE_XSHM + XShmSegmentInfo shmh; +#endif +}; + +QVideoStreamPrivate::QVideoStreamPrivate() +{ + xvHandle = 0; + xim = 0; +} + +QVideoStreamPrivate::~QVideoStreamPrivate() +{ + delete xvHandle; +} + +QVideoStream::QVideoStream(QWidget *widget, const char* name) + : QObject(widget, name), + d(new QVideoStreamPrivate), + _w(widget), + _methods(METHOD_NONE), + _method(METHOD_NONE), + _format(FORMAT_NONE), + _init(false) +{ + int dummy; + unsigned int dummy2; + findDisplayProperties(_xFormat, dummy, dummy2, dummy); + + _methods = (VideoMethod)(_methods | METHOD_X11); + +#ifdef HAVE_XSHM + if (XShmQueryExtension(_w->x11Display())) { + _methods = (VideoMethod)(_methods | METHOD_XSHM); + } +#endif + + if (KXv::haveXv()) { + _methods = (VideoMethod)(_methods | METHOD_XV); +#ifdef HAVE_XSHM + _methods = (VideoMethod)(_methods | METHOD_XVSHM); +#endif + } + +#ifdef HAVE_GL + if (QGLFormat::hasOpenGL()) { + _methods = (VideoMethod)(_methods | METHOD_GL); + } +#endif + + d->gc = XCreateGC(_w->x11Display(), _w->winId(), 0, NULL); +} + +QVideoStream::~QVideoStream() +{ + deInit(); + XFreeGC(_w->x11Display(), d->gc); + delete d; +} + +void QVideoStream::deInit() +{ + if (!_init) + return; + + _init = false; + _format = FORMAT_NONE; + + Q_ASSERT(_methods & _method); + if (!(_methods & _method)) + return; + + switch (_method) { + case METHOD_XSHM: +#ifdef HAVE_XSHM + XShmDetach(_w->x11Display(), &(d->shmh)); + XDestroyImage(d->xim); + d->xim = 0; + shmdt(d->shmh.shmaddr); +#endif + break; + case METHOD_X11: + delete[] d->xim->data; + d->xim->data = 0; + XDestroyImage(d->xim); + d->xim = 0; + break; + case METHOD_XVSHM: + case METHOD_XV: + delete d->xvHandle; + d->xvHandle = 0; + break; + case METHOD_GL: +#ifdef HAVE_GL + delete d->glwidget; +#endif + break; + default: + Q_ASSERT(0); + return; + } +} + +void QVideoStream::init() +{ + Q_ASSERT(_methods & _method); + if (!(_methods & _method)) + return; + + switch (_method) { + case METHOD_XSHM: + { +#ifdef HAVE_XSHM + if ( !_inputSize.isValid() ) { + kdWarning() << "QVideoStream::init() (XSHM): Unable to initialize due to invalid input size." << endl; + return; + } + + memset(&(d->shmh), 0, sizeof(XShmSegmentInfo)); + d->xim = XShmCreateImage(_w->x11Display(), + (Visual*)_w->x11Visual(), + _w->x11Depth(), + ZPixmap, 0, &(d->shmh), + _inputSize.width(), + _inputSize.height()); + d->shmh.shmid = shmget(IPC_PRIVATE, + d->xim->bytes_per_line*d->xim->height, + IPC_CREAT|0600); + d->shmh.shmaddr = (char *)shmat(d->shmh.shmid, 0, 0); + d->xim->data = (char*)d->shmh.shmaddr; + d->shmh.readOnly = False; + Status s = XShmAttach(_w->x11Display(), &(d->shmh)); + if (s) { + XSync(_w->x11Display(), False); + shmctl(d->shmh.shmid, IPC_RMID, 0); + _format = _xFormat; + _init = true; + } else { + kdWarning() << "XShmAttach failed!" << endl; + XDestroyImage(d->xim); + d->xim = 0; + shmdt(d->shmh.shmaddr); + } +#endif + } + break; + case METHOD_X11: + if ( !_inputSize.isValid() ) { + kdWarning() << "QVideoStream::init() (X11): Unable to initialize due to invalid input size." << endl; + return; + } + + d->xim = XCreateImage(_w->x11Display(), + (Visual*)_w->x11Visual(), + _w->x11Depth(), + ZPixmap, 0, 0, + _inputSize.width(), + _inputSize.height(), + 32, 0); + + d->xim->data = new char[d->xim->bytes_per_line*_inputSize.height()]; + _format = _xFormat; + _init = true; + break; + case METHOD_XVSHM: + case METHOD_XV: + { + if (d->xvHandle) + delete d->xvHandle; + + d->xvHandle = KXv::connect(_w->winId()); + KXvDeviceList& xvdl(d->xvHandle->devices()); + KXvDevice *xvdev = NULL; + + for (xvdev = xvdl.first(); xvdev; xvdev = xvdl.next()) { + if (xvdev->isImageBackend() && + xvdev->supportsWidget(_w)) { + d->xvdev = xvdev; + d->xvdev->useShm(_method == METHOD_XVSHM); + _format = FORMAT_YUYV; + _init = true; + break; + } + } + + if (!_init) { + delete d->xvHandle; + d->xvHandle = 0; + } + } + break; + case METHOD_GL: +#ifdef HAVE_GL + d->glwidget = new QVideoStreamGLWidget(_w, "QVideoStreamGLWidget"); + d->glwidget->resize(_w->width(), _w->height()); + d->glwidget->show(); + _format = FORMAT_BGR24; + _init = true; +#endif + break; + default: + Q_ASSERT(0); + return; + } +} + +bool QVideoStream::haveMethod(VideoMethod method) const +{ + return _methods & method; +} + +QVideo::VideoMethod QVideoStream::method() const +{ + return _method; +} + +QVideo::VideoMethod QVideoStream::setMethod(VideoMethod method) +{ + if (_methods & method) { + deInit(); + _method = method; + init(); + } + + return _method; +} + +QSize QVideoStream::maxSize() const +{ + return _size; +} + +int QVideoStream::maxWidth() const +{ + return _size.width(); +} + +int QVideoStream::maxHeight() const +{ + return _size.height(); +} + +QSize QVideoStream::size() const +{ + return _size; +} + +int QVideoStream::width() const +{ + return _size.width(); +} + +int QVideoStream::height() const +{ + return _size.height(); +} + +QSize QVideoStream::setSize(const QSize& sz) +{ + _size = sz; + return _size; +} + +int QVideoStream::setWidth(int width) +{ + if (width < 0) + width = 0; + if (width > maxWidth()) + width = maxWidth(); + _size.setWidth(width); + return _size.width(); +} + +int QVideoStream::setHeight(int height) +{ + if (height < 0) + height = 0; + if (height > maxHeight()) + height = maxHeight(); + _size.setHeight(height); + return _size.height(); +} + +QSize QVideoStream::inputSize() const +{ + return _inputSize; +} + +int QVideoStream::inputWidth() const +{ + return _inputSize.width(); +} + +int QVideoStream::inputHeight() const +{ + return _inputSize.height(); +} + +QSize QVideoStream::setInputSize(const QSize& sz) +{ + if (sz == _inputSize) + return _inputSize; + _inputSize = sz; + if (_method & (METHOD_XSHM | METHOD_X11)) { + deInit(); + init(); + } +#ifdef HAVE_GL + if (_method & METHOD_GL) { + d->glwidget->setInputSize(_inputSize); + } +#endif + return _inputSize; +} + +int QVideoStream::setInputWidth(int width) +{ + if (width == _inputSize.width()) + return _inputSize.width(); + _inputSize.setWidth(width); + if (_method & (METHOD_XSHM | METHOD_X11)) { + deInit(); + init(); + } +#ifdef HAVE_GL + if (_method & METHOD_GL) { + d->glwidget->setInputSize(_inputSize); + } +#endif + return _inputSize.width(); +} + +int QVideoStream::setInputHeight(int height) +{ + if (height == _inputSize.height()) + return _inputSize.height(); + _inputSize.setHeight(height); + if (_method & (METHOD_XSHM | METHOD_X11)) { + deInit(); + init(); + } +#ifdef HAVE_GL + if (_method & METHOD_GL) { + d->glwidget->setInputSize(_inputSize); + } +#endif + return _inputSize.height(); +} + +bool QVideoStream::supportsFormat(VideoMethod method, ImageFormat format) +{ + return (bool)(formatsForMethod(method) & format); +} + +QVideo::ImageFormat QVideoStream::formatsForMethod(VideoMethod method) +{ + switch(method) { + case METHOD_XSHM: + case METHOD_X11: + return _xFormat; + case METHOD_XV: + case METHOD_XVSHM: + return FORMAT_YUYV; + case METHOD_GL: + return FORMAT_BGR24; + default: + return FORMAT_NONE; + } +} + +QVideo::ImageFormat QVideoStream::format() const +{ + return _format; +} + +bool QVideoStream::setFormat(ImageFormat format) +{ + if(supportsFormat(_method, format)) { + _format = format; + return true; + } else { + return false; + } +} + +int QVideoStream::displayFrame(const unsigned char *const img) +{ + return displayFrame(img, 0, 0, _inputSize.width(), _inputSize.height()); +} + +int QVideoStream::displayFrame(const unsigned char *const img, int x, int y, int sw, int sh) +{ + Q_ASSERT(_init); + if (!_init) + return -1; + + Q_ASSERT(_methods & _method); + if (!(_methods & _method)) + return -1; + + switch (_method) { + case METHOD_XV: + case METHOD_XVSHM: + return d->xvdev->displayImage(_w, img, + _inputSize.width(), _inputSize.height(), x, y, sw, sh, + _size.width(), _size.height()); + break; + case METHOD_XSHM: +#ifdef HAVE_XSHM + memcpy(d->xim->data,img,d->xim->bytes_per_line*d->xim->height); + XShmPutImage(_w->x11Display(), _w->winId(), d->gc, d->xim, + x, y, + 0, 0, + sw, sh, + 0); + XSync(_w->x11Display(), False); + break; +#else + return -1; +#endif + case METHOD_X11: + memcpy(d->xim->data, img, d->xim->bytes_per_line*d->xim->height); + XPutImage(_w->x11Display(), _w->winId(), d->gc, d->xim, + x, y, + 0, 0, + sw, sh); + XSync(_w->x11Display(), False); + break; + case METHOD_GL: +#ifdef HAVE_GL + d->glwidget->display(img, x, y, sw, sh); +#endif + break; + default: + Q_ASSERT(0); + return -1; + } + + return 0; +} + +QVideoStream& QVideoStream::operator<<(const unsigned char *const img) +{ + displayFrame(img); + return *this; +} + +// --------------------------------------------------------------------------------------- +#ifdef HAVE_GL + +QVideoStreamGLWidget::QVideoStreamGLWidget(QWidget* parent, const char* name) + : QGLWidget(QGLFormat(QGL::DoubleBuffer | QGL::Rgba | QGL::DirectRendering), parent, name), + _tex(0), + _w(parent), + _glfun(false) +{ + kdDebug() << "QVideoStreamGLWidget::QVideoStreamGLWidget()" << endl; + + connect(_w, SIGNAL(resized(int, int)), + this, SLOT(resize(int, int))); + + topLevelWidget()->installEventFilter(this); + _glfunTimer = new QTimer(); +} + +QVideoStreamGLWidget::~QVideoStreamGLWidget() +{ + kdDebug() << "QVideoStreamGLWidget::~QVideoStreamGLWidget()" << endl; + delete _glfunTimer; + + makeCurrent(); + if(_tex != 0) { + glDeleteTextures(1, &_tex); + } +} + +bool QVideoStreamGLWidget::eventFilter(QObject*, QEvent* e) +{ + // For some reason, KeyPress does not work (yields 2), QEvent::KeyPress is unknown... What the f...???? + // I am too lazy to scan the header files for the reason. + if(e->type() == 6) { + QKeyEvent* ke = static_cast<QKeyEvent*>(e); + if(ke->key() == Qt::Key_Pause) { + _glfunTimer->start(500, true); + } else if (_glfunTimer->isActive() && (ke->key() == Qt::Key_Escape)) { + _glfun = !_glfun; + } + } + return false; +} + +void QVideoStreamGLWidget::initializeGL() +{ + kdDebug() << "QVideoStreamGLWidget::initializeGL()" << endl; + setAutoBufferSwap(false); + + QGLFormat f = format(); + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &_maxGL); + kdDebug() << "OpenGL capabilities (* = required):" << endl; + kdDebug() << " Valid context*: " << isValid() << endl; + kdDebug() << " DoubleBuffer*: " << f.doubleBuffer() << endl; + kdDebug() << " Depth: " << f.depth() << endl; + kdDebug() << " RGBA*: " << f.rgba() << endl; + kdDebug() << " Alpha: " << f.alpha() << endl; + kdDebug() << " Accum: " << f.accum() << endl; + kdDebug() << " Stencil: " << f.stencil() << endl; + kdDebug() << " Stereo: " << f.stereo() << endl; + kdDebug() << " DirectRendering*: " << f.directRendering() << endl; + kdDebug() << " Overlay: " << f.hasOverlay() << endl; + kdDebug() << " Plane: " << f.plane() << endl; + kdDebug() << " MAX_TEXTURE_SIZE: " << _maxGL << endl; + + qglClearColor(Qt::black); + glShadeModel(GL_FLAT); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + _vul = QPoint( 4, 10); + _vur = QPoint(-8, 4); + _vll = QPoint(10, -4); + _vlr = QPoint(-8, -10); +} + +void QVideoStreamGLWidget::resizeGL(int w, int h) +{ + _sz = QSize(w, h); + + glViewport(0, 0, w, h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0, w, 0.0, h, -1, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + _ul = QPoint(0, 0); + _ur = QPoint(w, 0); + _ll = QPoint(0, h); + _lr = QPoint(w, h); +} + +void QVideoStreamGLWidget::setInputSize(const QSize& sz) +{ + makeCurrent(); + + _inputSize = sz; + int iw = _inputSize.width(); + int ih = _inputSize.height(); + + if ( (iw > _maxGL) || (ih > _maxGL) ) { + kdWarning() << "QVideoStreamGLWidget::setInputSize(): Texture too large! maxGL: " << _maxGL << endl; + return; + } + + // textures have power-of-two x,y dimensions + int i; + for (i = 0; iw >= (1 << i); i++) + ; + _tw = (1 << i); + for (i = 0; ih >= (1 << i); i++) + ; + _th = (1 << i); + + // Generate texture + if(_tex != 0) { + glDeleteTextures(1, &_tex); + } + glGenTextures(1, &_tex); + glBindTexture(GL_TEXTURE_2D, _tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + // Blank texture + char* dummy = new char[_tw*_th*4]; + memset(dummy, 128, _tw*_th*4); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _tw, _th, 0, + GL_RGB, GL_UNSIGNED_BYTE, dummy); + delete[] dummy; +} + +void QVideoStreamGLWidget::display(const unsigned char *const img, int x, int y, int sw, int sh) +{ + makeCurrent(); + + // FIXME: Endianess - also support GL_RGB + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _inputSize.width(), _inputSize.height(), + GL_BGR, GL_UNSIGNED_BYTE, img); + + // upper right coords + float ur_x = (float)(x + sw) / _tw; + float ur_y = (float)(y + sh) / _th; + + // lower left coords + float ll_x = (float)(x) / _tw; + float ll_y = (float)(y) / _th; + + glEnable(GL_TEXTURE_2D); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + glBindTexture(GL_TEXTURE_2D, _tex); + if (!_glfun) { + glBegin(GL_QUADS); + glTexCoord2f(ll_x, ur_y); glVertex2i(0, 0 ); + glTexCoord2f(ll_x, ll_y); glVertex2i(0, _sz.height()); + glTexCoord2f(ur_x, ll_y); glVertex2i(_sz.width(), _sz.height()); + glTexCoord2f(ur_x, ur_y); glVertex2i(_sz.width(), 0 ); + glEnd(); + } else { + calc(_ul, _vul); + calc(_ur, _vur); + calc(_ll, _vll); + calc(_lr, _vlr); + + glClear(GL_COLOR_BUFFER_BIT); + + glBegin(GL_QUADS); + glTexCoord2f(0, y); glVertex2i(_ul.x(), _ul.y()); + glTexCoord2f(0, 0); glVertex2i(_ll.x(), _ll.y()); + glTexCoord2f(x, 0); glVertex2i(_lr.x(), _lr.y()); + glTexCoord2f(x, y); glVertex2i(_ur.x(), _ur.y()); + glEnd(); + } + swapBuffers(); + glDisable(GL_TEXTURE_2D); +} + +void QVideoStreamGLWidget::calc(QPoint& p, QPoint& v) +{ + p += v; + + if(p.x() < 0) { + p.setX(-p.x()); + v.setX(-v.x()); + } + if(p.y() < 0) { + p.setY(-p.y()); + v.setY(-v.y()); + } + if(p.x() > _sz.width()) { + p.setX(_sz.width() - (p.x() - _sz.width())); + v.setX(-v.x()); + } + if(p.y() > _sz.height()) { + p.setY(_sz.height() - (p.y() - _sz.height())); + v.setY(-v.y()); + } +} +#endif + +#include "qvideostream.moc" diff --git a/kopete/libkopete/avdevice/qvideostream.h b/kopete/libkopete/avdevice/qvideostream.h new file mode 100644 index 00000000..801fa829 --- /dev/null +++ b/kopete/libkopete/avdevice/qvideostream.h @@ -0,0 +1,112 @@ +// -*- c++ -*- + +/* + * + * Copyright (C) 2002 George Staikos <staikos@kde.org> + * 2004 Dirk Ziegelmeier <dziegel@gmx.de> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _QVIDEOSTREAM_H +#define _QVIDEOSTREAM_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_GL +#include <qgl.h> +#include <GL/gl.h> +#endif + +#include <qwidget.h> +#include "qvideo.h" + +class QVideoStreamPrivate; + +/** + * QT-style video stream driver. + */ +class QVideoStream : public QObject, public QVideo +{ + Q_OBJECT + +public: + QVideoStream(QWidget *widget, const char* name = 0); + ~QVideoStream(); + + /* output method */ + bool haveMethod(VideoMethod method) const; + VideoMethod method() const; + VideoMethod setMethod(VideoMethod method); + + /* max output sizes */ + QSize maxSize() const; + int maxWidth() const; + int maxHeight() const; + + /* output sizes */ + QSize size() const; + int width() const; + int height() const; + + QSize setSize(const QSize& sz); + int setWidth(int width); + int setHeight(int height); + + /* input sizes */ + QSize inputSize() const; + int inputWidth() const; + int inputHeight() const; + + QSize setInputSize(const QSize& sz); + int setInputWidth(int width); + int setInputHeight(int height); + + /* input format */ + ImageFormat format() const; + bool setFormat(ImageFormat format); + + /* functions to find out about formats */ + ImageFormat formatsForMethod(VideoMethod method); + bool supportsFormat(VideoMethod method, ImageFormat format); + + /* Display image */ + QVideoStream& operator<<(const unsigned char *const img); + +public slots: + int displayFrame(const unsigned char *const img); + int displayFrame(const unsigned char *const img, int x, int y, int sw, int sh); + +private: + QVideoStreamPrivate* d; + + QWidget* _w; + VideoMethod _methods; // list of methods + VideoMethod _method; // the current method + ImageFormat _format; + QSize _size; + QSize _inputSize; + bool _init; + ImageFormat _xFormat; + + void deInit(); + void init(); +}; + +#endif + diff --git a/kopete/libkopete/avdevice/sonix_compress.cpp b/kopete/libkopete/avdevice/sonix_compress.cpp new file mode 100644 index 00000000..400635c4 --- /dev/null +++ b/kopete/libkopete/avdevice/sonix_compress.cpp @@ -0,0 +1,180 @@ +#include "sonix_compress.h" + +#define CLAMP(x) ((x)<0?0:((x)>255)?255:(x)) + +typedef struct { + int is_abs; + int len; + int val; + int unk; +} code_table_t; + + +/* local storage */ +static code_table_t table[256]; +static int init_done = 0; + +/* global variable */ +int sonix_unknown = 0; + +/* + sonix_decompress_init + ===================== + pre-calculates a locally stored table for efficient huffman-decoding. + + Each entry at index x in the table represents the codeword + present at the MSB of byte x. + +*/ +void sonix_decompress_init(void) +{ + int i; + int is_abs, val, len, unk; + + for (i = 0; i < 256; i++) { + is_abs = 0; + val = 0; + len = 0; + unk = 0; + if ((i & 0x80) == 0) { + /* code 0 */ + val = 0; + len = 1; + } + else if ((i & 0xE0) == 0x80) { + /* code 100 */ + val = +4; + len = 3; + } + else if ((i & 0xE0) == 0xA0) { + /* code 101 */ + val = -4; + len = 3; + } + else if ((i & 0xF0) == 0xD0) { + /* code 1101 */ + val = +11; + len = 4; + } + else if ((i & 0xF0) == 0xF0) { + /* code 1111 */ + val = -11; + len = 4; + } + else if ((i & 0xF8) == 0xC8) { + /* code 11001 */ + val = +20; + len = 5; + } + else if ((i & 0xFC) == 0xC0) { + /* code 110000 */ + val = -20; + len = 6; + } + else if ((i & 0xFC) == 0xC4) { + /* code 110001xx: unknown */ + val = 0; + len = 8; + unk = 1; + } + else if ((i & 0xF0) == 0xE0) { + /* code 1110xxxx */ + is_abs = 1; + val = (i & 0x0F) << 4; + len = 8; + } + table[i].is_abs = is_abs; + table[i].val = val; + table[i].len = len; + table[i].unk = unk; + } + + sonix_unknown = 0; + init_done = 1; +} + + +/* + sonix_decompress + ================ + decompresses an image encoded by a SN9C101 camera controller chip. + + IN width + height + inp pointer to compressed frame (with header already stripped) + OUT outp pointer to decompressed frame + + Returns 0 if the operation was successful. + Returns <0 if operation failed. + +*/ +int sonix_decompress(int width, int height, unsigned char *inp, unsigned char *outp) +{ + int row, col; + int val; + int bitpos; + unsigned char code; + unsigned char *addr; + + if (!init_done) { + /* do sonix_decompress_init first! */ + return -1; + } + + bitpos = 0; + for (row = 0; row < height; row++) { + + col = 0; + + /* first two pixels in first two rows are stored as raw 8-bit */ + if (row < 2) { + addr = inp + (bitpos >> 3); + code = (addr[0] << (bitpos & 7)) | (addr[1] >> (8 - (bitpos & 7))); + bitpos += 8; + *outp++ = code; + + addr = inp + (bitpos >> 3); + code = (addr[0] << (bitpos & 7)) | (addr[1] >> (8 - (bitpos & 7))); + bitpos += 8; + *outp++ = code; + + col += 2; + } + + while (col < width) { + /* get bitcode from bitstream */ + addr = inp + (bitpos >> 3); + code = (addr[0] << (bitpos & 7)) | (addr[1] >> (8 - (bitpos & 7))); + + /* update bit position */ + bitpos += table[code].len; + + /* update code statistics */ + sonix_unknown += table[code].unk; + + /* calculate pixel value */ + val = table[code].val; + if (!table[code].is_abs) { + /* value is relative to top and left pixel */ + if (col < 2) { + /* left column: relative to top pixel */ + val += outp[-2*width]; + } + else if (row < 2) { + /* top row: relative to left pixel */ + val += outp[-2]; + } + else { + /* main area: average of left pixel and top pixel */ + val += (outp[-2] + outp[-2*width]) / 2; + } + } + + /* store pixel */ + *outp++ = CLAMP(val); + col++; + } + } + + return 0; +} diff --git a/kopete/libkopete/avdevice/sonix_compress.h b/kopete/libkopete/avdevice/sonix_compress.h new file mode 100644 index 00000000..509bcb07 --- /dev/null +++ b/kopete/libkopete/avdevice/sonix_compress.h @@ -0,0 +1,8 @@ +// Call this function first (just once is needed), before calling sonix_decompress +void sonix_decompress_init(void); + +// decompresses data at inp until a full image of widthxheight has been written to outp +int sonix_decompress(int width, int height, unsigned char *inp, unsigned char *outp); + +// counter to detect presence of currently unknown huffman codes +extern int sonix_unknown; diff --git a/kopete/libkopete/avdevice/videocontrol.cpp b/kopete/libkopete/avdevice/videocontrol.cpp new file mode 100644 index 00000000..f4807c1c --- /dev/null +++ b/kopete/libkopete/avdevice/videocontrol.cpp @@ -0,0 +1,36 @@ +/* + videoinput.cpp - Kopete Video Input Class + + Copyright (c) 2007 by Cláudio da Silveira Pinheiro <taupter@gmail.com> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "videocontrol.h" + +namespace Kopete { + +namespace AV { + +VideoControl::VideoControl() +{ +} + + +VideoControl::~VideoControl() +{ +} + + +} + +} diff --git a/kopete/libkopete/avdevice/videocontrol.h b/kopete/libkopete/avdevice/videocontrol.h new file mode 100644 index 00000000..2675be6e --- /dev/null +++ b/kopete/libkopete/avdevice/videocontrol.h @@ -0,0 +1,82 @@ +/* + videoinput.cpp - Kopete Video Input Class + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro <taupter@gmail.com> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#define ENABLE_AV + +#ifndef KOPETE_AVVIDEOCONTROL_H +#define KOPETE_AVVIDEOCONTROL_H + +#include <asm/types.h> +#undef __STRICT_ANSI__ +#ifndef __u64 //required by videodev.h +#define __u64 unsigned long long +#endif // __u64 + +#ifndef __s64 //required by videodev.h +#define __s64 long long +#endif // __s64 + +#include <qstring.h> +#include <kdebug.h> +#include <qvaluevector.h> +#include "kopete_export.h" + +namespace Kopete { + +namespace AV { + +typedef enum +{ + CONTROLTYPE_INTEGER = 0, + CONTROLTYPE_BOOLEAN = 1, + CONTROLTYPE_MENU = 2, + CONTROLTYPE_BUTTON = 3 +} control_type; + +typedef enum +{ + CONTROLFLAG_DISABLED = (1 << 0), // This control is permanently disabled and should be ignored by the application. + CONTROLFLAG_GRABBED = (1 << 1), // This control is temporarily unchangeable, + CONTROLFLAG_READONLY = (1 << 2), // This control is permanently readable only. + CONTROLFLAG__UPDATE = (1 << 3), // Changing this control may affect the value of other controls within the same control class. + CONTROLFLAG_INACTIVE = (1 << 4), // This control is not applicable to the current configuration. + CONTROLFLAG_SLIDER = (1 << 5) // This control is best represented as a slider. +} control_flag; +/** + @author Kopete Developers <kopete-devel@kde.org> +*/ +class VideoControl{ +public: + VideoControl(); + ~VideoControl(); + +protected: + __u32 m_id; + control_type m_type; + QString m_name; + __s32 m_minimum; + __s32 m_maximum; + __s32 m_step; + __s32 m_default; + __u32 m_flags; +}; + +} + +} + +#endif diff --git a/kopete/libkopete/avdevice/videodevice.cpp b/kopete/libkopete/avdevice/videodevice.cpp new file mode 100644 index 00000000..ada02ae5 --- /dev/null +++ b/kopete/libkopete/avdevice/videodevice.cpp @@ -0,0 +1,2752 @@ +/* + videodevice.cpp - Kopete Video Device Low-level Support + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro <taupter@gmail.com> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#define ENABLE_AV + +#include <cstdlib> +#include <cerrno> +#include <cstring> + +#include <kdebug.h> + +#include "videoinput.h" +#include "videodevice.h" + +#include "bayer.h" +#include "sonix_compress.h" + +#define CLEAR(x) memset (&(x), 0, sizeof (x)) + +namespace Kopete { + +namespace AV { + +VideoDevice::VideoDevice() +{ +// kdDebug(14010) << "libkopete (avdevice): VideoDevice() called" << endl; + descriptor = -1; + m_streambuffers = 0; + m_current_input = 0; +// kdDebug(14010) << "libkopete (avdevice): VideoDevice() exited successfuly" << endl; + maxwidth = 32767; + maxheight = 32767; + minwidth = 1; + minheight = 1; +} + + +VideoDevice::~VideoDevice() +{ +} + + + + + + +#ifdef V4L2_CAP_VIDEO_CAPTURE + +void VideoDevice::enumerateMenu (void) +{ + kdDebug(14010) << k_funcinfo << " Menu items:" << endl; + + memset (&querymenu, 0, sizeof (querymenu)); + querymenu.id = queryctrl.id; + + for (querymenu.index = queryctrl.minimum; querymenu.index <= queryctrl.maximum; querymenu.index++) + { + if (0 == xioctl (VIDIOC_QUERYMENU, &querymenu)) + { + kdDebug(14010) << k_funcinfo << " " << QString::fromLocal8Bit((const char*)querymenu.name) << endl; + } + else + { + perror ("VIDIOC_QUERYMENU"); + exit (EXIT_FAILURE); + } + } +} + + +#endif + + + + + +/*! + \fn VideoDevice::xioctl(int fd, int request, void *arg) + */ +int VideoDevice::xioctl(int request, void *arg) +{ + int r; + + do r = ioctl (descriptor, request, arg); + while (-1 == r && EINTR == errno); + return r; +} + +/*! + \fn VideoDevice::errnoReturn(const char* s) + */ +int VideoDevice::errnoReturn(const char* s) +{ + /// @todo implement me + fprintf (stderr, "%s error %d, %s\n",s, errno, strerror (errno)); + return EXIT_FAILURE; +} + +/*! + \fn VideoDevice::setFileName(QString name) + */ +int VideoDevice::setFileName(QString filename) +{ + /// @todo implement me + full_filename=filename; + return EXIT_SUCCESS; +} + +/*! + \fn VideoDevice::open() + */ +int VideoDevice::open() +{ + /// @todo implement me + + kdDebug(14010) << k_funcinfo << "called" << endl; + if(-1 != descriptor) + { + kdDebug(14010) << k_funcinfo << "Device is already open" << endl; + return EXIT_SUCCESS; + } + descriptor = ::open (QFile::encodeName(full_filename), O_RDWR, 0); + if(isOpen()) + { + kdDebug(14010) << k_funcinfo << "File " << full_filename << " was opened successfuly" << endl; + if(EXIT_FAILURE==checkDevice()) + { + kdDebug(14010) << k_funcinfo << "File " << full_filename << " could not be opened" << endl; + close(); + return EXIT_FAILURE; + } + } + else + { + kdDebug(14010) << k_funcinfo << "Unable to open file " << full_filename << "Err: "<< errno << endl; + return EXIT_FAILURE; + } + + initDevice(); + selectInput(m_current_input); + kdDebug(14010) << k_funcinfo << "exited successfuly" << endl; + return EXIT_SUCCESS; +} + +bool VideoDevice::isOpen() +{ + if(-1 == descriptor) + { +// kdDebug(14010) << k_funcinfo << "VideoDevice::isOpen() File is not open" << endl; + return false; + } +// kdDebug(14010) << k_funcinfo << "VideoDevice::isOpen() File is open" << endl; + return true; +} + +int VideoDevice::checkDevice() +{ + kdDebug(14010) << k_funcinfo << "checkDevice() called." << endl; + if(isOpen()) + { + m_videocapture=false; + m_videochromakey=false; + m_videoscale=false; + m_videooverlay=false; + m_videoread=false; + m_videoasyncio=false; + m_videostream=false; + + m_driver=VIDEODEV_DRIVER_NONE; +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + +//if(!getWorkaroundBrokenDriver()) +{ + kdDebug(14010) << k_funcinfo << "checkDevice(): " << full_filename << " Trying V4L2 API." << endl; + CLEAR(V4L2_capabilities); + + if (-1 != xioctl (VIDIOC_QUERYCAP, &V4L2_capabilities)) + { + if (!(V4L2_capabilities.capabilities & V4L2_CAP_VIDEO_CAPTURE)) + { + kdDebug(14010) << k_funcinfo << "checkDevice(): " << full_filename << " is not a video capture device." << endl; + m_driver = VIDEODEV_DRIVER_NONE; + return EXIT_FAILURE; + } + m_videocapture=true; + kdDebug(14010) << k_funcinfo << "checkDevice(): " << full_filename << " is a V4L2 device." << endl; + m_driver = VIDEODEV_DRIVER_V4L2; + m_model=QString::fromLocal8Bit((const char*)V4L2_capabilities.card); + + +// Detect maximum and minimum resolution supported by the V4L2 device + CLEAR (fmt); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl (VIDIOC_G_FMT, &fmt)) + kdDebug(14010) << k_funcinfo << "VIDIOC_G_FMT failed (" << errno << ")." << endl; + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = 32767; + fmt.fmt.pix.height = 32767; + fmt.fmt.pix.field = V4L2_FIELD_ANY; + if (-1 == xioctl (VIDIOC_S_FMT, &fmt)) + { + kdDebug(14010) << k_funcinfo << "Detecting maximum size with VIDIOC_S_FMT failed (" << errno << ").Returned maxwidth: " << pixelFormatName(fmt.fmt.pix.pixelformat) << " " << fmt.fmt.pix.width << "x" << fmt.fmt.pix.height << endl; + // Note VIDIOC_S_FMT may change width and height. + } + else + { + maxwidth = fmt.fmt.pix.width; + maxheight = fmt.fmt.pix.height; + } + if (-1 == xioctl (VIDIOC_G_FMT, &fmt)) + kdDebug(14010) << k_funcinfo << "VIDIOC_G_FMT failed (" << errno << ")." << endl; + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = 1; + fmt.fmt.pix.height = 1; + fmt.fmt.pix.field = V4L2_FIELD_ANY; + if (-1 == xioctl (VIDIOC_S_FMT, &fmt)) + { + kdDebug(14010) << k_funcinfo << "Detecting minimum size with VIDIOC_S_FMT failed (" << errno << ").Returned maxwidth: " << fmt.fmt.pix.width << "x" << fmt.fmt.pix.height << endl; + // Note VIDIOC_S_FMT may change width and height. + } + else + { + minwidth = fmt.fmt.pix.width; + minheight = fmt.fmt.pix.height; + } + +// Buggy driver paranoia +/* min = fmt.fmt.pix.width * 2; + if (fmt.fmt.pix.bytesperline < min) + fmt.fmt.pix.bytesperline = min; + min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; + if (fmt.fmt.pix.sizeimage < min) + fmt.fmt.pix.sizeimage = min; + m_buffer_size=fmt.fmt.pix.sizeimage ;*/ + + int inputisok=EXIT_SUCCESS; + m_input.clear(); + for(unsigned int loop=0; inputisok==EXIT_SUCCESS; loop++) + { + struct v4l2_input videoinput; + CLEAR(videoinput); + videoinput.index = loop; + inputisok=xioctl(VIDIOC_ENUMINPUT, &videoinput); + if(inputisok==EXIT_SUCCESS) + { + VideoInput tempinput; + tempinput.name = QString::fromLocal8Bit((const char*)videoinput.name); + tempinput.hastuner = videoinput.type & V4L2_INPUT_TYPE_TUNER; + tempinput.m_standards = videoinput.std; + m_input.push_back(tempinput); + kdDebug(14010) << k_funcinfo << "Input " << loop << ": " << tempinput.name << " (tuner: " << ((videoinput.type & V4L2_INPUT_TYPE_TUNER) != 0) << ")" << endl; + if((videoinput.type & V4L2_INPUT_TYPE_TUNER) != 0) + { +// _tunerForInput[name] = desc.tuner; +// _isTuner = true; + } + else + { +// _tunerForInput[name] = -1; + } + } + } + + + + +// ----------------------------------------------------------------------------------------------------------------- +// This must turn up to be a proper method to check for controls' existence. +CLEAR (queryctrl); +// v4l2_queryctrl may zero the .id in some cases, even if the IOCTL returns EXIT_SUCCESS (tested with a bttv card, when testing for V4L2_CID_AUDIO_VOLUME). +// As of 6th Aug 2007, according to the V4L2 specification version 0.21, this behavior is undocumented, and the example 1-8 code found at +// http://www.linuxtv.org/downloads/video4linux/API/V4L2_API/spec/x519.htm fails because of this behavior with a bttv card. + +int currentid = V4L2_CID_BASE; + +kdDebug(14010) << k_funcinfo << "Checking CID controls" << endl; + +for (currentid = V4L2_CID_BASE; currentid < V4L2_CID_LASTP1; currentid++) +//for (queryctrl.id = 9963776; queryctrl.id < 9963800; queryctrl.id++) +{ + queryctrl.id = currentid; +//kdDebug(14010) << k_funcinfo << "Checking CID controls from " << V4L2_CID_BASE << " to " << V4L2_CID_LASTP1 << ". Current: " << queryctrl.id << ". IOCTL returns: " << resultado << endl; + if (0 == xioctl (VIDIOC_QUERYCTRL, &queryctrl)) + { + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) + continue; + +//kdDebug(14010) << k_funcinfo << " Control: " << QString::fromLocal8Bit((const char*)queryctrl.name) << endl; +kdDebug(14010) << k_funcinfo << " Control: " << QString::fromLocal8Bit((const char*)queryctrl.name) << " Values from " << queryctrl.minimum << " to " << queryctrl.maximum << " with steps of " << queryctrl.step << ". Default: " << queryctrl.default_value << endl; + +/* switch (queryctrl.type) + { + case V4L2_CTRL_TYPE_INTEGER : + }*/ + if (queryctrl.type == V4L2_CTRL_TYPE_MENU) + enumerateMenu (); + } + else + { + if (errno == EINVAL) + continue; + + perror ("VIDIOC_QUERYCTRL"); +// exit (EXIT_FAILURE); + } +} + +kdDebug(14010) << k_funcinfo << "Checking CID private controls" << endl; + +for (currentid = V4L2_CID_PRIVATE_BASE;; currentid++) +//for (queryctrl.id = 9963776; queryctrl.id < 9963800; queryctrl.id++) +{ + queryctrl.id = currentid; +//kdDebug(14010) << k_funcinfo << "Checking CID private controls from " << V4L2_CID_PRIVATE_BASE << ". Current: " << queryctrl.id << ". IOCTL returns: " << resultado << endl; + if ( 0 == xioctl (VIDIOC_QUERYCTRL, &queryctrl)) + { + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) + continue; + +kdDebug(14010) << k_funcinfo << " Control: " << QString::fromLocal8Bit((const char*)queryctrl.name) << " Values from " << queryctrl.minimum << " to " << queryctrl.maximum << " with steps of " << queryctrl.step << ". Default: " << queryctrl.default_value << endl; + + if (queryctrl.type == V4L2_CTRL_TYPE_MENU) + enumerateMenu (); + } + else + { + if (errno == EINVAL) + break; + + perror ("VIDIOC_QUERYCTRL"); +// exit (EXIT_FAILURE); + } +} + + + + + } + else + { +// V4L-only drivers should return an EINVAL in errno to indicate they cannot handle V4L2 calls. Not every driver is compliant, so +// it will try the V4L api even if the error code is different than expected. + kdDebug(14010) << k_funcinfo << "checkDevice(): " << full_filename << " is not a V4L2 device." << endl; + } + +} +#endif + + CLEAR(V4L_capabilities); + + if(m_driver==VIDEODEV_DRIVER_NONE) + { + kdDebug(14010) << k_funcinfo << "checkDevice(): " << full_filename << " Trying V4L API." << endl; + if (-1 == xioctl (VIDIOCGCAP, &V4L_capabilities)) + { + perror ("ioctl (VIDIOCGCAP)"); + m_driver = VIDEODEV_DRIVER_NONE; + return EXIT_FAILURE; + } + else + { + kdDebug(14010) << k_funcinfo << full_filename << " is a V4L device." << endl; + m_driver = VIDEODEV_DRIVER_V4L; + m_model=QString::fromLocal8Bit((const char*)V4L_capabilities.name); + if(V4L_capabilities.type & VID_TYPE_CAPTURE) + m_videocapture=true; + if(V4L_capabilities.type & VID_TYPE_CHROMAKEY) + m_videochromakey=true; + if(V4L_capabilities.type & VID_TYPE_SCALES) + m_videoscale=true; + if(V4L_capabilities.type & VID_TYPE_OVERLAY) + m_videooverlay=true; +// kdDebug(14010) << "libkopete (avdevice): Inputs : " << V4L_capabilities.channels << endl; +// kdDebug(14010) << "libkopete (avdevice): Audios : " << V4L_capabilities.audios << endl; + minwidth = V4L_capabilities.minwidth; + maxwidth = V4L_capabilities.maxwidth; + minheight = V4L_capabilities.minheight; + maxheight = V4L_capabilities.maxheight; + + + int inputisok=EXIT_SUCCESS; + m_input.clear(); + for(int loop=0; loop < V4L_capabilities.channels; loop++) + { + struct video_channel videoinput; + CLEAR(videoinput); + videoinput.channel = loop; + videoinput.norm = 1; + inputisok=xioctl(VIDIOCGCHAN, &videoinput); + if(inputisok==EXIT_SUCCESS) + { + VideoInput tempinput; + tempinput.name = QString::fromLocal8Bit((const char*)videoinput.name); + tempinput.hastuner=videoinput.flags & VIDEO_VC_TUNER; +// TODO: The routine to detect the appropriate video standards for V4L must be placed here + m_input.push_back(tempinput); +// kdDebug(14010) << "libkopete (avdevice): Input " << loop << ": " << tempinput.name << " (tuner: " << ((videoinput.flags & VIDEO_VC_TUNER) != 0) << ")" << endl; +/* if((input.type & V4L2_INPUT_TYPE_TUNER) != 0) + { +// _tunerForInput[name] = desc.tuner; +// _isTuner = true; + } + else + { +// _tunerForInput[name] = -1; + } +*/ } + } + + } + } +#endif + m_name=m_model; // Take care about changing the name to be different from the model itself... + + detectPixelFormats(); + +// TODO: Now we must execute the proper initialization according to the type of the driver. + kdDebug(14010) << k_funcinfo << "checkDevice() exited successfuly." << endl; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + + +/*! + \fn VideoDevice::showDeviceCapabilities() + */ +int VideoDevice::showDeviceCapabilities() +{ + kdDebug(14010) << k_funcinfo << "showDeviceCapabilities() called." << endl; + if(isOpen()) + { +/* kdDebug(14010) << "libkopete (avdevice): Driver: " << (const char*)V4L2_capabilities.driver << " " + << ((V4L2_capabilities.version>>16) & 0xFF) << "." + << ((V4L2_capabilities.version>> 8) & 0xFF) << "." + << ((V4L2_capabilities.version ) & 0xFF) << endl; + kdDebug(14010) << "libkopete (avdevice): Card: " << name << endl; + kdDebug(14010) << "libkopete (avdevice): Capabilities:" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_VIDEO_CAPTURE) + kdDebug(14010) << "libkopete (avdevice): Video capture" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_VIDEO_OUTPUT) + kdDebug(14010) << "libkopete (avdevice): Video output" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_VIDEO_OVERLAY) + kdDebug(14010) << "libkopete (avdevice): Video overlay" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_VBI_CAPTURE) + kdDebug(14010) << "libkopete (avdevice): VBI capture" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_VBI_OUTPUT) + kdDebug(14010) << "libkopete (avdevice): VBI output" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_RDS_CAPTURE) + kdDebug(14010) << "libkopete (avdevice): RDS capture" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_TUNER) + kdDebug(14010) << "libkopete (avdevice): Tuner IO" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_AUDIO) + kdDebug(14010) << "libkopete (avdevice): Audio IO" << endl; +;*/ + kdDebug(14010) << k_funcinfo << "Card model: " << m_model << endl; + kdDebug(14010) << k_funcinfo << "Card name : " << m_name << endl; + kdDebug(14010) << k_funcinfo << "Capabilities:" << endl; + if(canCapture()) + kdDebug(14010) << k_funcinfo << " Video capture" << endl; + if(canRead()) + kdDebug(14010) << k_funcinfo << " Read" << endl; + if(canAsyncIO()) + kdDebug(14010) << k_funcinfo << " Asynchronous input/output" << endl; + if(canStream()) + kdDebug(14010) << k_funcinfo << " Streaming" << endl; + if(canChromakey()) + kdDebug(14010) << k_funcinfo << " Video chromakey" << endl; + if(canScale()) + kdDebug(14010) << k_funcinfo << " Video scales" << endl; + if(canOverlay()) + kdDebug(14010) << k_funcinfo << " Video overlay" << endl; +// kdDebug(14010) << "libkopete (avdevice): Audios : " << V4L_capabilities.audios << endl; + kdDebug(14010) << k_funcinfo << " Max res: " << maxWidth() << " x " << maxHeight() << endl; + kdDebug(14010) << k_funcinfo << " Min res: " << minWidth() << " x " << minHeight() << endl; + kdDebug(14010) << k_funcinfo << " Inputs : " << inputs() << endl; + for (unsigned int loop=0; loop < inputs(); loop++) + kdDebug(14010) << k_funcinfo << "Input " << loop << ": " << m_input[loop].name << " (tuner: " << m_input[loop].hastuner << ")" << endl; + kdDebug(14010) << k_funcinfo << "showDeviceCapabilities() exited successfuly." << endl; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +/*! + \fn VideoDevicePool::initDevice() + */ +int VideoDevice::initDevice() +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "initDevice() started" << endl; + if(-1 == descriptor) + { + kdDebug(14010) << k_funcinfo << "initDevice() Device is not open" << endl; + return EXIT_FAILURE; + } + m_io_method = IO_METHOD_NONE; + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + if(V4L2_capabilities.capabilities & V4L2_CAP_READWRITE) + { + m_videoread=true; + m_io_method = IO_METHOD_READ; + kdDebug(14010) << k_funcinfo << " Read/Write interface" << endl; + } + if(V4L2_capabilities.capabilities & V4L2_CAP_ASYNCIO) + { + m_videoasyncio=true; + kdDebug(14010) << k_funcinfo << " Async IO interface" << endl; + } + if(V4L2_capabilities.capabilities & V4L2_CAP_STREAMING) + { + m_videostream=true; + m_io_method = IO_METHOD_MMAP; +// m_io_method = IO_METHOD_USERPTR; + kdDebug(14010) << k_funcinfo << " Streaming interface" << endl; + } + if(m_io_method==IO_METHOD_NONE) + { + kdDebug(14010) << k_funcinfo << "initDevice() Found no suitable input/output method for " << full_filename << endl; + return EXIT_FAILURE; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + m_videoread=true; + m_io_method=IO_METHOD_READ; + if(-1 != xioctl(VIDIOCGFBUF,&V4L_videobuffer)) + { +// m_videostream=true; +// m_io_method = IO_METHOD_MMAP; + kdDebug(14010) << k_funcinfo << " Streaming interface" << endl; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + + break; + } + +// Select video input, video standard and tune here. +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl (VIDIOC_CROPCAP, &cropcap)) + { // Errors ignored. + } + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c = cropcap.defrect; // reset to default + if (-1 == xioctl (VIDIOC_S_CROP, &crop)) + { + switch (errno) + { + case EINVAL: break; // Cropping not supported. + default: break; // Errors ignored. + } + } +#endif +#endif + + showDeviceCapabilities(); + kdDebug(14010) << k_funcinfo << "initDevice() exited successfuly" << endl; + return EXIT_SUCCESS; +} + +unsigned int VideoDevice::inputs() +{ + return m_input.size(); +} + + +int VideoDevice::width() +{ + return currentwidth; +} + +int VideoDevice::minWidth() +{ + return minwidth; +} + +int VideoDevice::maxWidth() +{ + return maxwidth; +} + +int VideoDevice::height() +{ + return currentheight; +} + +int VideoDevice::minHeight() +{ + return minheight; +} + +int VideoDevice::maxHeight() +{ + return maxheight; +} + +int VideoDevice::setSize( int newwidth, int newheight) +{ +kdDebug(14010) << k_funcinfo << "setSize(" << newwidth << ", " << newheight << ") called." << endl; + if(isOpen()) + { +// It should not be there. It must remain in a completely distict place, cause this method should not change the pixelformat. + kdDebug(14010) << k_funcinfo << "Trying YUY422P" << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_YUV422P)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support YUV422P format. Trying YUYV." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_YUYV)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support YUYV format. Trying UYVY." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_UYVY)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support UYVY format. Trying YUV420P." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_YUV420P)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support YUV420P format. Trying RGB24." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_RGB24)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support RGB24 format. Trying BGR24." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_BGR24)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support RGB24 format. Trying RGB32." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_RGB32)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support RGB32 format. Trying BGR32." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_BGR32)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support BGR32 format. Trying SN9C10X." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_SN9C10X)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support SN9C10X format. Trying Bayer RGB." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_SBGGR8)) + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support SBGGR8 format. Fallback from it is not yet implemented." << endl; + } + } + } + } + } + } + } + } + } + + if(newwidth > maxwidth ) newwidth = maxwidth; + if(newheight > maxheight) newheight = maxheight; + if(newwidth < minwidth ) newwidth = minwidth; + if(newheight < minheight) newheight = minheight; + + currentwidth = newwidth; + currentheight = newheight; + +//kdDebug(14010) << k_funcinfo << "width: " << pixelFormatName(fmt.fmt.pix.pixelformat) << " " << width() << "x" << height() << endl; +// Change resolution for the video device + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: +// CLEAR (fmt); + if (-1 == xioctl (VIDIOC_G_FMT, &fmt)) + kdDebug(14010) << k_funcinfo << "VIDIOC_G_FMT failed (" << errno << ").Returned width: " << pixelFormatName(fmt.fmt.pix.pixelformat) << " " << fmt.fmt.pix.width << "x" << fmt.fmt.pix.height << endl; + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = width(); + fmt.fmt.pix.height = height(); + fmt.fmt.pix.field = V4L2_FIELD_ANY; + if (-1 == xioctl (VIDIOC_S_FMT, &fmt)) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_S_FMT failed (" << errno << ").Returned width: " << pixelFormatName(fmt.fmt.pix.pixelformat) << " " << fmt.fmt.pix.width << "x" << fmt.fmt.pix.height << endl; + // Note VIDIOC_S_FMT may change width and height. + } + else + { +// Buggy driver paranoia. +kdDebug(14010) << k_funcinfo << "VIDIOC_S_FMT worked (" << errno << ").Returned width: " << pixelFormatName(fmt.fmt.pix.pixelformat) << " " << fmt.fmt.pix.width << "x" << fmt.fmt.pix.height << endl; + unsigned int min = fmt.fmt.pix.width * 2; + if (fmt.fmt.pix.bytesperline < min) + fmt.fmt.pix.bytesperline = min; + min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; + if (fmt.fmt.pix.sizeimage < min) + fmt.fmt.pix.sizeimage = min; + m_buffer_size=fmt.fmt.pix.sizeimage ; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + { + struct video_window V4L_videowindow; + +kdDebug(14010) << "------------- width: " << V4L_videowindow.width << " Height: " << V4L_videowindow.height << " Clipcount: " << V4L_videowindow.clipcount << " -----------------" << endl; + + if (xioctl (VIDIOCGWIN, &V4L_videowindow)== -1) + { + perror ("ioctl VIDIOCGWIN"); +// return (NULL); + } + V4L_videowindow.width = width(); + V4L_videowindow.height = height(); + V4L_videowindow.clipcount=0; + if (xioctl (VIDIOCSWIN, &V4L_videowindow)== -1) + { + perror ("ioctl VIDIOCSWIN"); +// return (NULL); + } +kdDebug(14010) << "------------- width: " << V4L_videowindow.width << " Height: " << V4L_videowindow.height << " Clipcount: " << V4L_videowindow.clipcount << " -----------------" << endl; + +// kdDebug(14010) << "libkopete (avdevice): V4L_picture.palette: " << V4L_picture.palette << " Depth: " << V4L_picture.depth << endl; + +/* if(-1 == xioctl(VIDIOCGFBUF,&V4L_videobuffer)) + kdDebug(14010) << "libkopete (avdevice): VIDIOCGFBUF failed (" << errno << "): Card cannot stream" << endl;*/ + + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + m_buffer_size = width() * height() * pixelFormatDepth(m_pixelformat) / 8; +kdDebug(14010) << "------------------------- ------- -- m_buffer_size: " << m_buffer_size << " !!! -- ------- -----------------------------------------" << endl; + + m_currentbuffer.pixelformat=m_pixelformat; + m_currentbuffer.data.resize(m_buffer_size); + + switch (m_io_method) + { + case IO_METHOD_NONE: break; + case IO_METHOD_READ: initRead (); break; + case IO_METHOD_MMAP: initMmap (); break; + case IO_METHOD_USERPTR: initUserptr (); break; + } + +kdDebug(14010) << k_funcinfo << "setSize(" << newwidth << ", " << newheight << ") exited successfuly." << endl; + return EXIT_SUCCESS; + } +kdDebug(14010) << k_funcinfo << "setSize(" << newwidth << ", " << newheight << ") Device is not open." << endl; + return EXIT_FAILURE; +} + + + + + + + + + + + + + +pixel_format VideoDevice::setPixelFormat(pixel_format newformat) +{ + pixel_format ret = PIXELFORMAT_NONE; +//kdDebug(14010) << k_funcinfo << "called." << endl; +// Change the pixel format for the video device + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: +// CLEAR (fmt); + if (-1 == xioctl (VIDIOC_G_FMT, &fmt)) + { +// return errnoReturn ("VIDIOC_S_FMT"); +// kdDebug(14010) << k_funcinfo << "VIDIOC_G_FMT failed (" << errno << ").Returned width: " << pixelFormatName(fmt.fmt.pix.pixelformat) << " " << fmt.fmt.pix.width << "x" << fmt.fmt.pix.height << endl; + } + else + m_pixelformat = pixelFormatForPalette(fmt.fmt.pix.pixelformat); + + fmt.fmt.pix.pixelformat = pixelFormatCode(newformat); + if (-1 == xioctl (VIDIOC_S_FMT, &fmt)) + { +// kdDebug(14010) << k_funcinfo << "VIDIOC_S_FMT failed (" << errno << ").Returned width: " << pixelFormatName(fmt.fmt.pix.pixelformat) << " " << fmt.fmt.pix.width << "x" << fmt.fmt.pix.height << endl; + } + else + { + if (fmt.fmt.pix.pixelformat == pixelFormatCode(newformat)) + { + m_pixelformat = newformat; + ret = m_pixelformat; + } + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + { + struct video_picture V4L_picture; + if(-1 == xioctl(VIDIOCGPICT, &V4L_picture)) + kdDebug(14010) << k_funcinfo << "VIDIOCGPICT failed (" << errno << ")." << endl; +// kdDebug(14010) << k_funcinfo << "V4L_picture.palette: " << V4L_picture.palette << " Depth: " << V4L_picture.depth << endl; + V4L_picture.palette = pixelFormatCode(newformat); + V4L_picture.depth = pixelFormatDepth(newformat); + if(-1 == xioctl(VIDIOCSPICT,&V4L_picture)) + { +// kdDebug(14010) << k_funcinfo << "Card seems to not support " << pixelFormatName(newformat) << " format. Fallback to it is not yet implemented." << endl; + } + + if(-1 == xioctl(VIDIOCGPICT, &V4L_picture)) + kdDebug(14010) << k_funcinfo << "VIDIOCGPICT failed (" << errno << ")." << endl; + +// kdDebug(14010) << k_funcinfo << "V4L_picture.palette: " << V4L_picture.palette << " Depth: " << V4L_picture.depth << endl; + m_pixelformat=pixelFormatForPalette(V4L_picture.palette); + if (m_pixelformat == newformat) + ret = newformat; + + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return ret; +} + + + + + + +/*! + \fn Kopete::AV::VideoDevice::currentInput() + */ +int VideoDevice::currentInput() +{ + /// @todo implement me + if(isOpen()) + { + return m_current_input; + } + return 0; +} + +/*! + \fn Kopete::AV::VideoDevice::selectInput(int input) + */ +int VideoDevice::selectInput(int newinput) +{ + /// @todo implement me + if(m_current_input >= inputs()) + return EXIT_FAILURE; + + if(isOpen()) + { + switch (m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + if (-1 == ioctl (descriptor, VIDIOC_S_INPUT, &newinput)) + { + perror ("VIDIOC_S_INPUT"); + return EXIT_FAILURE; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + struct video_channel V4L_input; + V4L_input.channel=newinput; + V4L_input.norm=4; // Hey, it's plain wrong! It should be input's signal standard! + if (-1 == ioctl (descriptor, VIDIOCSCHAN, &V4L_input)) + { + perror ("ioctl (VIDIOCSCHAN)"); + return EXIT_FAILURE; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + kdDebug(14010) << k_funcinfo << "Selected input " << newinput << " (" << m_input[newinput].name << ")" << endl; + m_current_input = newinput; + setInputParameters(); + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +/*! + \fn Kopete::AV::VideoDevice::setInputParameters() + */ +int VideoDevice::setInputParameters() +{ + /// @todo implement me + if( (isOpen()) && (m_current_input < inputs() ) ) + { + setBrightness( getBrightness() ); + setContrast( getContrast() ); + setSaturation( getSaturation() ); + setWhiteness( getWhiteness() ); + setHue( getHue() ); + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +/*! + \fn VideoDevice::startCapturing() + */ +int VideoDevice::startCapturing() +{ + + kdDebug(14010) << k_funcinfo << "called." << endl; + if(isOpen()) + { + switch (m_io_method) + { + case IO_METHOD_NONE: // Card cannot capture frames + return EXIT_FAILURE; + break; + case IO_METHOD_READ: // Nothing to do + break; + case IO_METHOD_MMAP: +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + { + unsigned int loop; + for (loop = 0; loop < m_streambuffers; ++loop) + { + struct v4l2_buffer buf; + CLEAR (buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = loop; + if (-1 == xioctl (VIDIOC_QBUF, &buf)) + return errnoReturn ("VIDIOC_QBUF"); + } + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl (VIDIOC_STREAMON, &type)) + return errnoReturn ("VIDIOC_STREAMON"); + } +#endif +#endif + break; + case IO_METHOD_USERPTR: +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + { + unsigned int loop; + for (loop = 0; loop < m_streambuffers; ++loop) + { + struct v4l2_buffer buf; + CLEAR (buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + buf.m.userptr = (unsigned long) m_rawbuffers[loop].start; + buf.length = m_rawbuffers[loop].length; + if (-1 == xioctl (VIDIOC_QBUF, &buf)) + return errnoReturn ("VIDIOC_QBUF"); + } + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl (VIDIOC_STREAMON, &type)) + return errnoReturn ("VIDIOC_STREAMON"); + } +#endif +#endif + break; + } + + kdDebug(14010) << k_funcinfo << "exited successfuly." << endl; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +/*! + \fn VideoDevice::getFrame() + */ +int VideoDevice::getFrame() +{ + /// @todo implement me + ssize_t bytesread; + +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + struct v4l2_buffer v4l2buffer; +#endif +#endif +// kdDebug(14010) << k_funcinfo << "getFrame() called." << endl; + if(isOpen()) + { + switch (m_io_method) + { + case IO_METHOD_NONE: // Card cannot capture frames + return EXIT_FAILURE; + break; + case IO_METHOD_READ: +// kdDebug(14010) << k_funcinfo << "Using IO_METHOD_READ.File descriptor: " << descriptor << " Buffer address: " << &m_currentbuffer.data[0] << " Size: " << m_currentbuffer.data.size() << endl; + bytesread = read (descriptor, &m_currentbuffer.data[0], m_currentbuffer.data.size()); + if (-1 == bytesread) // must verify this point with ov511 driver. + { + kdDebug(14010) << k_funcinfo << "IO_METHOD_READ failed." << endl; + switch (errno) + { + case EAGAIN: + return EXIT_FAILURE; + case EIO: /* Could ignore EIO, see spec. fall through */ + default: + return errnoReturn ("read"); + } + } + if((int)m_currentbuffer.data.size() < bytesread) + { + kdDebug(14010) << k_funcinfo << "IO_METHOD_READ returned less bytes (" << bytesread << ") than it was asked for (" << m_currentbuffer.data.size() <<")." << endl; + } + break; + case IO_METHOD_MMAP: +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + CLEAR (v4l2buffer); + v4l2buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2buffer.memory = V4L2_MEMORY_MMAP; + if (-1 == xioctl (VIDIOC_DQBUF, &v4l2buffer)) + { + kdDebug(14010) << k_funcinfo << full_filename << " MMAPed getFrame failed." << endl; + switch (errno) + { + case EAGAIN: + { + kdDebug(14010) << k_funcinfo << full_filename << " MMAPed getFrame failed: EAGAIN. Pointer: " << endl; + return EXIT_FAILURE; + } + case EIO: /* Could ignore EIO, see spec. fall through */ + default: + return errnoReturn ("VIDIOC_DQBUF"); + } + } +/* if (v4l2buffer.index < m_streambuffers) + return EXIT_FAILURE;*/ //it was an assert() +//kdDebug(14010) << k_funcinfo << "m_rawbuffers[" << v4l2buffer.index << "].start: " << (void *)m_rawbuffers[v4l2buffer.index].start << " Size: " << m_currentbuffer.data.size() << endl; + + + +/*{ + unsigned long long result=0; + unsigned long long R=0, G=0, B=0, A=0; + int Rmax=0, Gmax=0, Bmax=0, Amax=0; + int Rmin=255, Gmin=255, Bmin=255, Amin=0; + + for(unsigned int loop=0;loop < m_currentbuffer.data.size();loop+=4) + { + R+=m_rawbuffers[v4l2buffer.index].start[loop]; + G+=m_rawbuffers[v4l2buffer.index].start[loop+1]; + B+=m_rawbuffers[v4l2buffer.index].start[loop+2]; +// A+=currentbuffer.data[loop+3]; + if (m_currentbuffer.data[loop] < Rmin) Rmin = m_currentbuffer.data[loop]; + if (m_currentbuffer.data[loop+1] < Gmin) Gmin = m_currentbuffer.data[loop+1]; + if (m_currentbuffer.data[loop+2] < Bmin) Bmin = m_currentbuffer.data[loop+2]; +// if (m_currentbuffer.data[loop+3] < Amin) Amin = m_currentbuffer.data[loop+3]; + if (m_currentbuffer.data[loop] > Rmax) Rmax = m_currentbuffer.data[loop]; + if (m_currentbuffer.data[loop+1] > Gmax) Gmax = m_currentbuffer.data[loop+1]; + if (m_currentbuffer.data[loop+2] > Bmax) Bmax = m_currentbuffer.data[loop+2]; +// if (m_currentbuffer.data[loop+3] > Amax) Amax = m_currentbuffer.data[loop+3]; + } + kdDebug(14010) << " R: " << R << " G: " << G << " B: " << B << " A: " << A << + " Rmin: " << Rmin << " Gmin: " << Gmin << " Bmin: " << Bmin << " Amin: " << Amin << + " Rmax: " << Rmax << " Gmax: " << Gmax << " Bmax: " << Bmax << " Amax: " << Amax << endl; +}*/ + + +memcpy(&m_currentbuffer.data[0], m_rawbuffers[v4l2buffer.index].start, m_currentbuffer.data.size()); + if (-1 == xioctl (VIDIOC_QBUF, &v4l2buffer)) + return errnoReturn ("VIDIOC_QBUF"); +#endif +#endif + break; + case IO_METHOD_USERPTR: +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + { + unsigned int i; + CLEAR (v4l2buffer); + v4l2buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2buffer.memory = V4L2_MEMORY_USERPTR; + if (-1 == xioctl (VIDIOC_DQBUF, &v4l2buffer)) + { + switch (errno) + { + case EAGAIN: + return EXIT_FAILURE; + case EIO: /* Could ignore EIO, see spec. fall through */ + default: + return errnoReturn ("VIDIOC_DQBUF"); + } + } + for (i = 0; i < m_streambuffers; ++i) + if (v4l2buffer.m.userptr == (unsigned long) m_rawbuffers[i].start && v4l2buffer.length == m_rawbuffers[i].length) + break; + if (i < m_streambuffers) + return EXIT_FAILURE; + if (-1 == xioctl (VIDIOC_QBUF, &v4l2buffer)) + return errnoReturn ("VIDIOC_QBUF"); + } +#endif +#endif + break; + } + +/* Automatic color correction. Now it just swaps R and B channels in RGB24/BGR24 modes. + if(m_input[m_current_input].getAutoColorCorrection()) + { + switch(m_currentbuffer.pixelformat) + { + case PIXELFORMAT_NONE : break; + case PIXELFORMAT_GREY : break; + case PIXELFORMAT_RGB332 : break; + case PIXELFORMAT_RGB555 : break; + case PIXELFORMAT_RGB555X: break; + case PIXELFORMAT_RGB565 : break; + case PIXELFORMAT_RGB565X: break; + case PIXELFORMAT_RGB24 : + case PIXELFORMAT_BGR24 : + { + unsigned char temp; + for(unsigned int loop=0;loop < m_currentbuffer.data.size();loop+=3) + { + temp = m_currentbuffer.data[loop]; + m_currentbuffer.data[loop] = m_currentbuffer.data[loop+2]; + m_currentbuffer.data[loop+2] = temp; + } + } + break; + case PIXELFORMAT_RGB32 : + case PIXELFORMAT_BGR32 : + { + unsigned char temp; + for(unsigned int loop=0;loop < m_currentbuffer.data.size();loop+=4) + { + temp = m_currentbuffer.data[loop]; + m_currentbuffer.data[loop] = m_currentbuffer.data[loop+2]; + m_currentbuffer.data[loop+2] = temp; + } + } + break; + case PIXELFORMAT_YUYV : break; + case PIXELFORMAT_UYVY : break; + case PIXELFORMAT_YUV420P: break; + case PIXELFORMAT_YUV422P: break; + } + }*/ +//kdDebug(14010) << k_funcinfo << "10 Using IO_METHOD_READ.File descriptor: " << descriptor << " Buffer address: " << &m_currentbuffer.data[0] << " Size: " << m_currentbuffer.data.size() << endl; + + +// put frame copy operation here +// kdDebug(14010) << k_funcinfo << "exited successfuly." << endl; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +/*! + \fn VideoDevice::getFrame(imagebuffer *imgbuffer) + */ +int VideoDevice::getFrame(imagebuffer *imgbuffer) +{ + if(imgbuffer) + { + getFrame(); + imgbuffer->height = m_currentbuffer.height; + imgbuffer->width = m_currentbuffer.width; + imgbuffer->pixelformat = m_currentbuffer.pixelformat; + imgbuffer->data = m_currentbuffer.data; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +/*! + \fn Kopete::AV::VideoDevice::getImage(const QImage *qimage) + */ +int VideoDevice::getImage(QImage *qimage) +{ + /// @todo implement me + + // do NOT delete qimage here, as it is received as a parameter + if (qimage->width() != width() || qimage->height() != height()) + qimage->create(width(), height(),32, QImage::IgnoreEndian); + + uchar *bits=qimage->bits(); +// kDebug() << "Capturing in " << pixelFormatName(m_currentbuffer.pixelformat); + switch(m_currentbuffer.pixelformat) + { + case PIXELFORMAT_NONE : break; + +// Packed RGB formats + case PIXELFORMAT_RGB332 : break; + case PIXELFORMAT_RGB444 : break; + case PIXELFORMAT_RGB555 : break; + case PIXELFORMAT_RGB565 : + { + int step=0; + for(int loop=0;loop < qimage->numBytes();loop+=4) + { + bits[loop] = (m_currentbuffer.data[step]<<3)+(m_currentbuffer.data[step]<<3>>5); + bits[loop+1] = ((m_currentbuffer.data[step+1])<<5)|m_currentbuffer.data[step]>>5; + bits[loop+2] = ((m_currentbuffer.data[step+1])&248)+((m_currentbuffer.data[step+1])>>5); + bits[loop+3] = 255; + step+=2; + } + } + break; + case PIXELFORMAT_RGB555X: break; + case PIXELFORMAT_RGB565X: break; + case PIXELFORMAT_BGR24 : + { + int step=0; + for(int loop=0;loop < qimage->numBytes();loop+=4) + { + bits[loop] = m_currentbuffer.data[step+2]; + bits[loop+1] = m_currentbuffer.data[step+1]; + bits[loop+2] = m_currentbuffer.data[step]; + bits[loop+3] = 255; + step+=3; + } + } + break; + case PIXELFORMAT_RGB24 : + { + int step=0; + for(int loop=0;loop < qimage->numBytes();loop+=4) + { + bits[loop] = m_currentbuffer.data[step]; + bits[loop+1] = m_currentbuffer.data[step+1]; + bits[loop+2] = m_currentbuffer.data[step+2]; + bits[loop+3] = 255; + step+=3; + } + } + break; + case PIXELFORMAT_BGR32 : break; + case PIXELFORMAT_RGB32 : memcpy(bits,&m_currentbuffer.data[0], m_currentbuffer.data.size()); + break; + +// Bayer RGB format + case PIXELFORMAT_SBGGR8 : + { + unsigned char *d = (unsigned char *) malloc (width() * height() * 3); + bayer2rgb24(d, &m_currentbuffer.data.first(), width(), height()); + int step=0; + for(int loop=0;loop < qimage->numBytes();loop+=4) + { + bits[loop] = d[step+2]; + bits[loop+1] = d[step+1]; + bits[loop+2] = d[step]; + bits[loop+3] = 255; + step+=3; + } + free(d); + } + break; + +// YUV formats + case PIXELFORMAT_GREY : break; + case PIXELFORMAT_YUYV: + case PIXELFORMAT_UYVY: + case PIXELFORMAT_YUV420P: + case PIXELFORMAT_YUV422P: + { + uchar *yptr, *cbptr, *crptr; + bool halfheight=false; + bool packed=false; +// Adjust algorythm to specific YUV data arrangements. + if (m_currentbuffer.pixelformat == PIXELFORMAT_YUV420P) + halfheight=true; + if (m_currentbuffer.pixelformat == PIXELFORMAT_YUYV) + { + yptr = &m_currentbuffer.data[0]; + cbptr = yptr + 1; + crptr = yptr + 3; + packed=true; + } + else if (m_currentbuffer.pixelformat == PIXELFORMAT_UYVY) + { + cbptr = &m_currentbuffer.data[0]; + yptr = cbptr + 1; + crptr = cbptr + 2; + packed=true; + } + else + { + yptr = &m_currentbuffer.data[0]; + cbptr = yptr + (width()*height()); + crptr = cbptr + (width()*height()/(halfheight ? 4:2)); + } + + for(int y=0; y<height(); y++) + { +// Decode scanline + for(int x=0; x<width(); x++) + { + int c,d,e; + + if (packed) + { + c = (yptr[x<<1])-16; + d = (cbptr[x>>1<<2])-128; + e = (crptr[x>>1<<2])-128; + } + else + { + c = (yptr[x])-16; + d = (cbptr[x>>1])-128; + e = (crptr[x>>1])-128; + } + + int r = (298 * c + 409 * e + 128)>>8; + int g = (298 * c - 100 * d - 208 * e + 128)>>8; + int b = (298 * c + 516 * d + 128)>>8; + + if (r<0) r=0; if (r>255) r=255; + if (g<0) g=0; if (g>255) g=255; + if (b<0) b=0; if (b>255) b=255; + + uint *p = (uint*)qimage->scanLine(y)+x; + *p = qRgba(r,g,b,255); + + } +// Jump to next line + if (packed) + { + yptr+=width()*2; + cbptr+=width()*2; + crptr+=width()*2; + } + else + { + yptr+=width(); + if (!halfheight || y&1) + { + cbptr+=width()/2; + crptr+=width()/2; + } + } + } + } + break; + +// Compressed formats + case PIXELFORMAT_JPEG : break; + case PIXELFORMAT_MPEG : break; + +// Reserved formats + case PIXELFORMAT_DV : break; + case PIXELFORMAT_ET61X251:break; + case PIXELFORMAT_HI240 : break; + case PIXELFORMAT_HM12 : break; + case PIXELFORMAT_MJPEG : break; + case PIXELFORMAT_PWC1 : break; + case PIXELFORMAT_PWC2 : break; + case PIXELFORMAT_SN9C10X: + { + unsigned char *s = new unsigned char [width() * height()]; + unsigned char *d = new unsigned char [width() * height() * 3]; + sonix_decompress_init(); + sonix_decompress(width(), height(), &m_currentbuffer.data.first(), s); + bayer2rgb24(d, s, width(), height()); + int step=0; + for(int loop=0;loop < qimage->numBytes();loop+=4) + { + bits[loop] = d[step+2]; + bits[loop+1] = d[step+1]; + bits[loop+2] = d[step]; + bits[loop+3] = 255; + step+=3; + } + delete[] s; + delete[] d; + } + break; + case PIXELFORMAT_WNVA : break; + case PIXELFORMAT_YYUV : break; + } + return EXIT_SUCCESS; +} + +/*! + \fn VideoDevice::stopCapturing() + */ +int VideoDevice::stopCapturing() +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "called." << endl; + if(isOpen()) + { + switch (m_io_method) + { + case IO_METHOD_NONE: // Card cannot capture frames + return EXIT_FAILURE; + break; + case IO_METHOD_READ: // Nothing to do + break; + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: +#ifdef V4L2_CAP_VIDEO_CAPTURE + { + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl (VIDIOC_STREAMOFF, &type)) + return errnoReturn ("VIDIOC_STREAMOFF"); + + if (m_io_method == IO_METHOD_MMAP) + { + unsigned int loop; + for (loop = 0; loop < m_streambuffers; ++loop) + { + if (munmap(m_rawbuffers[loop].start,m_rawbuffers[loop].length) != 0) + { + kdDebug(14010) << k_funcinfo << "unable to munmap." << endl; + } + } + } + } +#endif + break; + } + kdDebug(14010) << k_funcinfo << "exited successfuly." << endl; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + + +/*! + \fn VideoDevice::close() + */ +int VideoDevice::close() +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << " called." << endl; + if(isOpen()) + { + kdDebug(14010) << k_funcinfo << " Device is open. Trying to properly shutdown the device." << endl; + stopCapturing(); + kdDebug(14010) << k_funcinfo << "::close() returns " << ::close(descriptor) << endl; + } + descriptor = -1; + return EXIT_SUCCESS; +} + +float VideoDevice::getBrightness() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getBrightness(); + else + return 0; +} + +float VideoDevice::setBrightness(float brightness) +{ + kdDebug(14010) << k_funcinfo << " called." << endl; + m_input[m_current_input].setBrightness(brightness); // Just to check bounds + + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + { + struct v4l2_queryctrl queryctrl; + struct v4l2_control control; + + CLEAR (queryctrl); + queryctrl.id = V4L2_CID_BRIGHTNESS; + + if (-1 == xioctl (VIDIOC_QUERYCTRL, &queryctrl)) + { + if (errno != EINVAL) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_QUERYCTRL failed (" << errno << ")." << endl; + } else + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Brightness control." << endl; + } + } else + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Brightness control." << endl; + } else + { + CLEAR (control); + control.id = V4L2_CID_BRIGHTNESS; + control.value = (__s32)((queryctrl.maximum - queryctrl.minimum)*getBrightness()); + + if (-1 == xioctl (VIDIOC_S_CTRL, &control)) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_S_CTRL failed (" << errno << ")." << endl; + } + } + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + { + struct video_picture V4L_picture; + if(-1 == xioctl(VIDIOCGPICT, &V4L_picture)) + kdDebug(14010) << k_funcinfo << "VIDIOCGPICT failed (" << errno << ")." << endl; + V4L_picture.brightness = uint(65535*getBrightness()); + if(-1 == xioctl(VIDIOCSPICT,&V4L_picture)) + kdDebug(14010) << k_funcinfo << "Card seems to not support adjusting image brightness. Fallback to it is not yet implemented." << endl; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return getBrightness(); +} + +float VideoDevice::getContrast() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getContrast(); + else + return 0; +} + +float VideoDevice::setContrast(float contrast) +{ + kdDebug(14010) << k_funcinfo << " called." << endl; + m_input[m_current_input].setContrast(contrast); // Just to check bounds + + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + { + struct v4l2_queryctrl queryctrl; + struct v4l2_control control; + + CLEAR (queryctrl); + queryctrl.id = V4L2_CID_CONTRAST; + + if (-1 == xioctl (VIDIOC_QUERYCTRL, &queryctrl)) + { + if (errno != EINVAL) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_QUERYCTRL failed (" << errno << ")." << endl; + } else + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Contrast control." << endl; + } + } else + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Contrast control." << endl; + } else + { + CLEAR (control); + control.id = V4L2_CID_CONTRAST; + control.value = (__s32)((queryctrl.maximum - queryctrl.minimum)*getContrast()); + + if (-1 == xioctl (VIDIOC_S_CTRL, &control)) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_S_CTRL failed (" << errno << ")." << endl; + } + } + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + { + struct video_picture V4L_picture; + if(-1 == xioctl(VIDIOCGPICT, &V4L_picture)) + kdDebug(14010) << k_funcinfo << "VIDIOCGPICT failed (" << errno << ")." << endl; + V4L_picture.contrast = uint(65535*getContrast()); + if(-1 == xioctl(VIDIOCSPICT,&V4L_picture)) + kdDebug(14010) << k_funcinfo << "Card seems to not support adjusting image contrast. Fallback to it is not yet implemented." << endl; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return getContrast(); +} + +float VideoDevice::getSaturation() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getSaturation(); + else + return 0; +} + +float VideoDevice::setSaturation(float saturation) +{ + kdDebug(14010) << k_funcinfo << " called." << endl; + m_input[m_current_input].setSaturation(saturation); // Just to check bounds + + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + { + struct v4l2_queryctrl queryctrl; + struct v4l2_control control; + + CLEAR (queryctrl); + queryctrl.id = V4L2_CID_SATURATION; + + if (-1 == xioctl (VIDIOC_QUERYCTRL, &queryctrl)) + { + if (errno != EINVAL) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_QUERYCTRL failed (" << errno << ")." << endl; + } else + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Saturation control." << endl; + } + } else + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Saturation control." << endl; + } else + { + CLEAR (control); + control.id = V4L2_CID_SATURATION; + control.value = (__s32)((queryctrl.maximum - queryctrl.minimum)*getSaturation()); + + if (-1 == xioctl (VIDIOC_S_CTRL, &control)) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_S_CTRL failed (" << errno << ")." << endl; + } + } + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + { + struct video_picture V4L_picture; + if(-1 == xioctl(VIDIOCGPICT, &V4L_picture)) + kdDebug(14010) << k_funcinfo << "VIDIOCGPICT failed (" << errno << ")." << endl; + V4L_picture.colour = uint(65535*getSaturation()); + if(-1 == xioctl(VIDIOCSPICT,&V4L_picture)) + kdDebug(14010) << k_funcinfo << "Card seems to not support adjusting image saturation. Fallback to it is not yet implemented." << endl; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return getSaturation(); +} + +float VideoDevice::getWhiteness() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getWhiteness(); + else + return 0; +} + +float VideoDevice::setWhiteness(float whiteness) +{ + kdDebug(14010) << k_funcinfo << " called." << endl; + m_input[m_current_input].setWhiteness(whiteness); // Just to check bounds + + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + { + struct v4l2_queryctrl queryctrl; + struct v4l2_control control; + + CLEAR (queryctrl); + queryctrl.id = V4L2_CID_WHITENESS; + + if (-1 == xioctl (VIDIOC_QUERYCTRL, &queryctrl)) + { + if (errno != EINVAL) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_QUERYCTRL failed (" << errno << ")." << endl; + } else + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Whiteness control." << endl; + } + } else + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Whiteness control." << endl; + } else + { + CLEAR (control); + control.id = V4L2_CID_WHITENESS; + control.value = (__s32)((queryctrl.maximum - queryctrl.minimum)*getWhiteness()); + + if (-1 == xioctl (VIDIOC_S_CTRL, &control)) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_S_CTRL failed (" << errno << ")." << endl; + } + } + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + { + struct video_picture V4L_picture; + if(-1 == xioctl(VIDIOCGPICT, &V4L_picture)) + kdDebug(14010) << k_funcinfo << "VIDIOCGPICT failed (" << errno << ")." << endl; + V4L_picture.whiteness = uint(65535*getWhiteness()); + if(-1 == xioctl(VIDIOCSPICT,&V4L_picture)) + kdDebug(14010) << k_funcinfo << "Card seems to not support adjusting white level. Fallback to it is not yet implemented." << endl; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return getWhiteness(); +} + +float VideoDevice::getHue() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getHue(); + else + return 0; +} + +float VideoDevice::setHue(float hue) +{ + kdDebug(14010) << k_funcinfo << " called." << endl; + m_input[m_current_input].setHue(hue); // Just to check bounds + + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + { + struct v4l2_queryctrl queryctrl; + struct v4l2_control control; + + CLEAR (queryctrl); + queryctrl.id = V4L2_CID_HUE; + + if (-1 == xioctl (VIDIOC_QUERYCTRL, &queryctrl)) + { + if (errno != EINVAL) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_QUERYCTRL failed (" << errno << ")." << endl; + } else + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Hue control." << endl; + } + } else + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Hue control." << endl; + } else + { + CLEAR (control); + control.id = V4L2_CID_HUE; + control.value = (__s32)((queryctrl.maximum - queryctrl.minimum)*getHue()); + + if (-1 == xioctl (VIDIOC_S_CTRL, &control)) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_S_CTRL failed (" << errno << ")." << endl; + } + } + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + { + struct video_picture V4L_picture; + if(-1 == xioctl(VIDIOCGPICT, &V4L_picture)) + kdDebug(14010) << k_funcinfo << "VIDIOCGPICT failed (" << errno << ")." << endl; + V4L_picture.hue = uint(65535*getHue()); + if(-1 == xioctl(VIDIOCSPICT,&V4L_picture)) + kdDebug(14010) << k_funcinfo << "Card seems to not support adjusting image hue. Fallback to it is not yet implemented." << endl; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return getHue(); +} + + +bool VideoDevice::getAutoBrightnessContrast() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getAutoBrightnessContrast(); + else + return false; +} + +bool VideoDevice::setAutoBrightnessContrast(bool brightnesscontrast) +{ + kdDebug(14010) << k_funcinfo << "VideoDevice::setAutoBrightnessContrast(" << brightnesscontrast << ") called." << endl; + if (m_current_input < m_input.size() ) + { + m_input[m_current_input].setAutoBrightnessContrast(brightnesscontrast); + return m_input[m_current_input].getAutoBrightnessContrast(); + } + else + return false; + +} + +bool VideoDevice::getAutoColorCorrection() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getAutoColorCorrection(); + else + return false; +} + +bool VideoDevice::setAutoColorCorrection(bool colorcorrection) +{ + kdDebug(14010) << k_funcinfo << "VideoDevice::setAutoColorCorrection(" << colorcorrection << ") called." << endl; + if (m_current_input < m_input.size() ) + { + m_input[m_current_input].setAutoColorCorrection(colorcorrection); + return m_input[m_current_input].getAutoColorCorrection(); + } + else + return false; +} + +bool VideoDevice::getImageAsMirror() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getImageAsMirror(); + else + return false; +} + +bool VideoDevice::setImageAsMirror(bool imageasmirror) +{ + kdDebug(14010) << k_funcinfo << "VideoDevice::setImageAsMirror(" << imageasmirror << ") called." << endl; + if (m_current_input < m_input.size() ) + { + m_input[m_current_input].setImageAsMirror(imageasmirror); + return m_input[m_current_input].getImageAsMirror(); + } + else + return false; +} + +pixel_format VideoDevice::pixelFormatForPalette( int palette ) +{ + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + switch(palette) + { + case 0 : return PIXELFORMAT_NONE; break; + +// Packed RGB formats + case V4L2_PIX_FMT_RGB332 : return PIXELFORMAT_RGB332; break; +#if defined( V4L2_PIX_FMT_RGB444 ) + case V4L2_PIX_FMT_RGB444 : return PIXELFORMAT_RGB444; break; +#endif + case V4L2_PIX_FMT_RGB555 : return PIXELFORMAT_RGB555; break; + case V4L2_PIX_FMT_RGB565 : return PIXELFORMAT_RGB565; break; + case V4L2_PIX_FMT_RGB555X : return PIXELFORMAT_RGB555X; break; + case V4L2_PIX_FMT_RGB565X : return PIXELFORMAT_RGB565X; break; + case V4L2_PIX_FMT_BGR24 : return PIXELFORMAT_BGR24; break; + case V4L2_PIX_FMT_RGB24 : return PIXELFORMAT_RGB24; break; + case V4L2_PIX_FMT_BGR32 : return PIXELFORMAT_BGR32; break; + case V4L2_PIX_FMT_RGB32 : return PIXELFORMAT_RGB32; break; + +// Bayer RGB format + case V4L2_PIX_FMT_SBGGR8 : return PIXELFORMAT_SBGGR8; break; + +// YUV formats + case V4L2_PIX_FMT_GREY : return PIXELFORMAT_GREY; break; + case V4L2_PIX_FMT_YUYV : return PIXELFORMAT_YUYV; break; + case V4L2_PIX_FMT_UYVY : return PIXELFORMAT_UYVY; break; + case V4L2_PIX_FMT_YUV420 : return PIXELFORMAT_YUV420P; break; + case V4L2_PIX_FMT_YUV422P : return PIXELFORMAT_YUV422P; break; + +// Compressed formats + case V4L2_PIX_FMT_JPEG : return PIXELFORMAT_JPEG; break; + case V4L2_PIX_FMT_MPEG : return PIXELFORMAT_MPEG; break; + +// Reserved formats + case V4L2_PIX_FMT_DV : return PIXELFORMAT_DV; break; + case V4L2_PIX_FMT_ET61X251 : return PIXELFORMAT_ET61X251; break; + case V4L2_PIX_FMT_HI240 : return PIXELFORMAT_HI240; break; +#if defined( V4L2_PIX_FMT_HM12 ) + case V4L2_PIX_FMT_HM12 : return PIXELFORMAT_HM12; break; +#endif + case V4L2_PIX_FMT_MJPEG : return PIXELFORMAT_MJPEG; break; + case V4L2_PIX_FMT_PWC1 : return PIXELFORMAT_PWC1; break; + case V4L2_PIX_FMT_PWC2 : return PIXELFORMAT_PWC2; break; + case V4L2_PIX_FMT_SN9C10X : return PIXELFORMAT_SN9C10X; break; + case V4L2_PIX_FMT_WNVA : return PIXELFORMAT_WNVA; break; + case V4L2_PIX_FMT_YYUV : return PIXELFORMAT_YYUV; break; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + switch(palette) + { + case 0 : return PIXELFORMAT_NONE; break; + case VIDEO_PALETTE_GREY : return PIXELFORMAT_GREY; break; + case VIDEO_PALETTE_HI240 : return PIXELFORMAT_RGB332; break; + case VIDEO_PALETTE_RGB555 : return PIXELFORMAT_RGB555; break; + case VIDEO_PALETTE_RGB565 : return PIXELFORMAT_RGB565; break; + case VIDEO_PALETTE_RGB24 : return PIXELFORMAT_RGB24; break; + case VIDEO_PALETTE_RGB32 : return PIXELFORMAT_RGB32; break; + case VIDEO_PALETTE_YUYV : return PIXELFORMAT_YUYV; break; + case VIDEO_PALETTE_UYVY : return PIXELFORMAT_UYVY; break; + case VIDEO_PALETTE_YUV420 : + case VIDEO_PALETTE_YUV420P : return PIXELFORMAT_YUV420P; break; + case VIDEO_PALETTE_YUV422P : return PIXELFORMAT_YUV422P; break; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + return PIXELFORMAT_NONE; break; + } + return PIXELFORMAT_NONE; +} + +int VideoDevice::pixelFormatCode(pixel_format pixelformat) +{ + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + switch(pixelformat) + { + case PIXELFORMAT_NONE : return 0; break; + +// Packed RGB formats + case PIXELFORMAT_RGB332 : return V4L2_PIX_FMT_RGB332; break; +#if defined( V4L2_PIX_FMT_RGB444 ) + case PIXELFORMAT_RGB444 : return V4L2_PIX_FMT_RGB444; break; +#endif + case PIXELFORMAT_RGB555 : return V4L2_PIX_FMT_RGB555; break; + case PIXELFORMAT_RGB565 : return V4L2_PIX_FMT_RGB565; break; + case PIXELFORMAT_RGB555X: return V4L2_PIX_FMT_RGB555X; break; + case PIXELFORMAT_RGB565X: return V4L2_PIX_FMT_RGB565X; break; + case PIXELFORMAT_BGR24 : return V4L2_PIX_FMT_BGR24; break; + case PIXELFORMAT_RGB24 : return V4L2_PIX_FMT_RGB24; break; + case PIXELFORMAT_BGR32 : return V4L2_PIX_FMT_BGR32; break; + case PIXELFORMAT_RGB32 : return V4L2_PIX_FMT_RGB32; break; + +// Bayer RGB format + case PIXELFORMAT_SBGGR8 : return V4L2_PIX_FMT_SBGGR8; break; + +// YUV formats + case PIXELFORMAT_GREY : return V4L2_PIX_FMT_GREY; break; + case PIXELFORMAT_YUYV : return V4L2_PIX_FMT_YUYV; break; + case PIXELFORMAT_UYVY : return V4L2_PIX_FMT_UYVY; break; + case PIXELFORMAT_YUV420P: return V4L2_PIX_FMT_YUV420; break; + case PIXELFORMAT_YUV422P: return V4L2_PIX_FMT_YUV422P; break; + +// Compressed formats + case PIXELFORMAT_JPEG : return V4L2_PIX_FMT_JPEG; break; + case PIXELFORMAT_MPEG : return V4L2_PIX_FMT_MPEG; break; + +// Reserved formats + case PIXELFORMAT_DV : return V4L2_PIX_FMT_DV; break; + case PIXELFORMAT_ET61X251:return V4L2_PIX_FMT_ET61X251;break; + case PIXELFORMAT_HI240 : return V4L2_PIX_FMT_HI240; break; +#if defined( V4L2_PIX_FMT_HM12 ) + case PIXELFORMAT_HM12 : return V4L2_PIX_FMT_HM12; break; +#endif + case PIXELFORMAT_MJPEG : return V4L2_PIX_FMT_MJPEG; break; + case PIXELFORMAT_PWC1 : return V4L2_PIX_FMT_PWC1; break; + case PIXELFORMAT_PWC2 : return V4L2_PIX_FMT_PWC2; break; + case PIXELFORMAT_SN9C10X: return V4L2_PIX_FMT_SN9C10X; break; + case PIXELFORMAT_WNVA : return V4L2_PIX_FMT_WNVA; break; + case PIXELFORMAT_YYUV : return V4L2_PIX_FMT_YYUV; break; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + switch(pixelformat) + { + case PIXELFORMAT_NONE : return 0; break; + +// Packed RGB formats + case PIXELFORMAT_RGB332 : return VIDEO_PALETTE_HI240; break; + case PIXELFORMAT_RGB444 : return 0; break; + case PIXELFORMAT_RGB555 : return VIDEO_PALETTE_RGB555; break; + case PIXELFORMAT_RGB565 : return VIDEO_PALETTE_RGB565; break; + case PIXELFORMAT_RGB555X: return 0; break; + case PIXELFORMAT_RGB565X: return 0; break; + case PIXELFORMAT_BGR24 : return 0; break; + case PIXELFORMAT_RGB24 : return VIDEO_PALETTE_RGB24; break; + case PIXELFORMAT_BGR32 : return 0; break; + case PIXELFORMAT_RGB32 : return VIDEO_PALETTE_RGB32; break; + +// Bayer RGB format + case PIXELFORMAT_SBGGR8 : return 0; break; + +// YUV formats + case PIXELFORMAT_GREY : return VIDEO_PALETTE_GREY; break; + case PIXELFORMAT_YUYV : return VIDEO_PALETTE_YUYV; break; + case PIXELFORMAT_UYVY : return VIDEO_PALETTE_UYVY; break; + case PIXELFORMAT_YUV420P: return VIDEO_PALETTE_YUV420; break; + case PIXELFORMAT_YUV422P: return VIDEO_PALETTE_YUV422P; break; + +// Compressed formats + case PIXELFORMAT_JPEG : return 0; break; + case PIXELFORMAT_MPEG : return 0; break; + +// Reserved formats + case PIXELFORMAT_DV : return 0; break; + case PIXELFORMAT_ET61X251:return 0; break; + case PIXELFORMAT_HI240 : return VIDEO_PALETTE_HI240; break; + case PIXELFORMAT_HM12 : return 0; break; + case PIXELFORMAT_MJPEG : return 0; break; + case PIXELFORMAT_PWC1 : return 0; break; + case PIXELFORMAT_PWC2 : return 0; break; + case PIXELFORMAT_SN9C10X: return 0; break; + case PIXELFORMAT_WNVA : return 0; break; + case PIXELFORMAT_YYUV : return 0; break; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + return PIXELFORMAT_NONE; break; + } + return PIXELFORMAT_NONE; +} + +int VideoDevice::pixelFormatDepth(pixel_format pixelformat) +{ + switch(pixelformat) + { + case PIXELFORMAT_NONE : return 0; break; + +// Packed RGB formats + case PIXELFORMAT_RGB332 : return 8; break; + case PIXELFORMAT_RGB444 : return 16; break; + case PIXELFORMAT_RGB555 : return 16; break; + case PIXELFORMAT_RGB565 : return 16; break; + case PIXELFORMAT_RGB555X: return 16; break; + case PIXELFORMAT_RGB565X: return 16; break; + case PIXELFORMAT_BGR24 : return 24; break; + case PIXELFORMAT_RGB24 : return 24; break; + case PIXELFORMAT_BGR32 : return 32; break; + case PIXELFORMAT_RGB32 : return 32; break; + +// Bayer RGB format + case PIXELFORMAT_SBGGR8 : return 0; break; + +// YUV formats + case PIXELFORMAT_GREY : return 8; break; + case PIXELFORMAT_YUYV : return 16; break; + case PIXELFORMAT_UYVY : return 16; break; + case PIXELFORMAT_YUV420P: return 16; break; + case PIXELFORMAT_YUV422P: return 16; break; + +// Compressed formats + case PIXELFORMAT_JPEG : return 0; break; + case PIXELFORMAT_MPEG : return 0; break; + +// Reserved formats + case PIXELFORMAT_DV : return 0; break; + case PIXELFORMAT_ET61X251:return 0; break; + case PIXELFORMAT_HI240 : return 8; break; + case PIXELFORMAT_HM12 : return 0; break; + case PIXELFORMAT_MJPEG : return 0; break; + case PIXELFORMAT_PWC1 : return 0; break; + case PIXELFORMAT_PWC2 : return 0; break; + case PIXELFORMAT_SN9C10X: return 0; break; + case PIXELFORMAT_WNVA : return 0; break; + case PIXELFORMAT_YYUV : return 0; break; + } + return 0; +} + +QString VideoDevice::pixelFormatName(pixel_format pixelformat) +{ + QString returnvalue; + returnvalue = "None"; + switch(pixelformat) + { + case PIXELFORMAT_NONE : returnvalue = "None"; break; + +// Packed RGB formats + case PIXELFORMAT_RGB332 : returnvalue = "8-bit RGB332"; break; + case PIXELFORMAT_RGB444 : returnvalue = "8-bit RGB444"; break; + case PIXELFORMAT_RGB555 : returnvalue = "16-bit RGB555"; break; + case PIXELFORMAT_RGB565 : returnvalue = "16-bit RGB565"; break; + case PIXELFORMAT_RGB555X: returnvalue = "16-bit RGB555X"; break; + case PIXELFORMAT_RGB565X: returnvalue = "16-bit RGB565X"; break; + case PIXELFORMAT_BGR24 : returnvalue = "24-bit BGR24"; break; + case PIXELFORMAT_RGB24 : returnvalue = "24-bit RGB24"; break; + case PIXELFORMAT_BGR32 : returnvalue = "32-bit BGR32"; break; + case PIXELFORMAT_RGB32 : returnvalue = "32-bit RGB32"; break; + +// Bayer RGB format + case PIXELFORMAT_SBGGR8 : returnvalue = "Bayer RGB format"; break; + +// YUV formats + case PIXELFORMAT_GREY : returnvalue = "8-bit Grayscale"; break; + case PIXELFORMAT_YUYV : returnvalue = "Packed YUV 4:2:2"; break; + case PIXELFORMAT_UYVY : returnvalue = "Packed YVU 4:2:2"; break; + case PIXELFORMAT_YUV420P: returnvalue = "Planar YUV 4:2:0"; break; + case PIXELFORMAT_YUV422P: returnvalue = "Planar YUV 4:2:2"; break; + + +// Compressed formats + case PIXELFORMAT_JPEG : returnvalue = "JPEG image"; break; + case PIXELFORMAT_MPEG : returnvalue = "MPEG stream"; break; + +// Reserved formats + case PIXELFORMAT_DV : returnvalue = "DV (unknown)"; break; + case PIXELFORMAT_ET61X251:returnvalue = "ET61X251"; break; + case PIXELFORMAT_HI240 : returnvalue = "8-bit HI240 (RGB332)"; break; + case PIXELFORMAT_HM12 : returnvalue = "Packed YUV 4:2:2"; break; + case PIXELFORMAT_MJPEG : returnvalue = "8-bit Grayscale"; break; + case PIXELFORMAT_PWC1 : returnvalue = "PWC1"; break; + case PIXELFORMAT_PWC2 : returnvalue = "PWC2"; break; + case PIXELFORMAT_SN9C10X: returnvalue = "SN9C102"; break; + case PIXELFORMAT_WNVA : returnvalue = "Winnov Videum"; break; + case PIXELFORMAT_YYUV : returnvalue = "YYUV (unknown)"; break; + } + return returnvalue; +} + +QString VideoDevice::pixelFormatName(int pixelformat) +{ + QString returnvalue; + returnvalue = "None"; + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + switch(pixelformat) + { + case 0 : returnvalue = pixelFormatName(PIXELFORMAT_NONE); break; + +// Packed RGB formats + case V4L2_PIX_FMT_RGB332 : returnvalue = pixelFormatName(PIXELFORMAT_RGB332); break; +#if defined( V4L2_PIX_FMT_RGB444 ) + case V4L2_PIX_FMT_RGB444 : returnvalue = pixelFormatName(PIXELFORMAT_RGB444); break; +#endif + case V4L2_PIX_FMT_RGB555 : returnvalue = pixelFormatName(PIXELFORMAT_RGB555); break; + case V4L2_PIX_FMT_RGB565 : returnvalue = pixelFormatName(PIXELFORMAT_RGB565); break; + case V4L2_PIX_FMT_RGB555X : returnvalue = pixelFormatName(PIXELFORMAT_RGB555X); break; + case V4L2_PIX_FMT_RGB565X : returnvalue = pixelFormatName(PIXELFORMAT_RGB565X); break; + case V4L2_PIX_FMT_BGR24 : returnvalue = pixelFormatName(PIXELFORMAT_BGR24); break; + case V4L2_PIX_FMT_RGB24 : returnvalue = pixelFormatName(PIXELFORMAT_RGB24); break; + case V4L2_PIX_FMT_BGR32 : returnvalue = pixelFormatName(PIXELFORMAT_BGR32); break; + case V4L2_PIX_FMT_RGB32 : returnvalue = pixelFormatName(PIXELFORMAT_RGB32); break; + +// Bayer RGB format + case V4L2_PIX_FMT_SBGGR8 : returnvalue = pixelFormatName(PIXELFORMAT_SBGGR8); break; + +// YUV formats + case V4L2_PIX_FMT_GREY : returnvalue = pixelFormatName(PIXELFORMAT_GREY); break; + case V4L2_PIX_FMT_YUYV : returnvalue = pixelFormatName(PIXELFORMAT_YUYV); break; + case V4L2_PIX_FMT_UYVY : returnvalue = pixelFormatName(PIXELFORMAT_UYVY); break; + case V4L2_PIX_FMT_YUV420 : returnvalue = pixelFormatName(PIXELFORMAT_YUV420P); break; + case V4L2_PIX_FMT_YUV422P : returnvalue = pixelFormatName(PIXELFORMAT_YUV422P); break; + +// Compressed formats + case V4L2_PIX_FMT_JPEG : returnvalue = pixelFormatName(PIXELFORMAT_JPEG); break; + case V4L2_PIX_FMT_MPEG : returnvalue = pixelFormatName(PIXELFORMAT_MPEG); break; + +// Reserved formats + case V4L2_PIX_FMT_DV : returnvalue = pixelFormatName(PIXELFORMAT_DV); break; + case V4L2_PIX_FMT_ET61X251 : returnvalue = pixelFormatName(PIXELFORMAT_ET61X251); break; + case V4L2_PIX_FMT_HI240 : returnvalue = pixelFormatName(PIXELFORMAT_HI240); break; +#if defined( V4L2_PIX_FMT_HM12 ) + case V4L2_PIX_FMT_HM12 : returnvalue = pixelFormatName(PIXELFORMAT_HM12); break; +#endif + case V4L2_PIX_FMT_MJPEG : returnvalue = pixelFormatName(PIXELFORMAT_MJPEG); break; + case V4L2_PIX_FMT_PWC1 : returnvalue = pixelFormatName(PIXELFORMAT_PWC1); break; + case V4L2_PIX_FMT_PWC2 : returnvalue = pixelFormatName(PIXELFORMAT_PWC2); break; + case V4L2_PIX_FMT_SN9C10X : returnvalue = pixelFormatName(PIXELFORMAT_SN9C10X); break; + case V4L2_PIX_FMT_WNVA : returnvalue = pixelFormatName(PIXELFORMAT_WNVA); break; + case V4L2_PIX_FMT_YYUV : returnvalue = pixelFormatName(PIXELFORMAT_YYUV); break; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + switch(pixelformat) + { + case VIDEO_PALETTE_GREY : returnvalue = pixelFormatName(PIXELFORMAT_GREY); break; + case VIDEO_PALETTE_HI240 : returnvalue = pixelFormatName(PIXELFORMAT_RGB332); break; + case VIDEO_PALETTE_RGB555 : returnvalue = pixelFormatName(PIXELFORMAT_RGB555); break; + case VIDEO_PALETTE_RGB565 : returnvalue = pixelFormatName(PIXELFORMAT_RGB565); break; + case VIDEO_PALETTE_RGB24 : returnvalue = pixelFormatName(PIXELFORMAT_RGB24); break; + case VIDEO_PALETTE_RGB32 : returnvalue = pixelFormatName(PIXELFORMAT_RGB32); break; + case VIDEO_PALETTE_YUYV : returnvalue = pixelFormatName(PIXELFORMAT_YUYV); break; + case VIDEO_PALETTE_UYVY : returnvalue = pixelFormatName(PIXELFORMAT_UYVY); break; + case VIDEO_PALETTE_YUV420 : + case VIDEO_PALETTE_YUV420P : returnvalue = pixelFormatName(PIXELFORMAT_YUV420P); break; + case VIDEO_PALETTE_YUV422P : returnvalue = pixelFormatName(PIXELFORMAT_YUV422P); break; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return returnvalue; +} + +int VideoDevice::detectPixelFormats() +{ + int err = 0; + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + fmtdesc.index = 0; + fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + while ( err == 0 ) + { + if (-1 == xioctl (VIDIOC_ENUM_FMT, &fmtdesc)) +// if (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) < 0 ) + { + perror("VIDIOC_ENUM_FMT"); + err = errno; + } + else + { + kdDebug(14010) << k_funcinfo << fmtdesc.pixelformat << " " << pixelFormatName(fmtdesc.pixelformat) << endl; // Need a cleanup. PixelFormatForPalette is a really bad name + fmtdesc.index++; + } + } +// break; +#endif + case VIDEODEV_DRIVER_V4L: +// TODO: THis thing can be used to detec what pixel formats are supported in a API-independent way, but V4L2 has VIDIOC_ENUM_PIXFMT. +// The correct thing to do is to isolate these calls and do a proper implementation for V4L and another for V4L2 when this thing will be migrated to a plugin architecture. + +// Packed RGB formats + kdDebug(14010) << k_funcinfo << "Supported pixel formats:" << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB332)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB332) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB444)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB444) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB555)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB555) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB565)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB565) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB555X)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB555X) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB565X)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB565X) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_BGR24)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_BGR24) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB24)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB24) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_BGR32)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_BGR32) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB32)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB32) << endl; + +// Bayer RGB format + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_SBGGR8)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_SBGGR8) << endl; + +// YUV formats + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_GREY)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_GREY) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_YUYV)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_YUYV) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_UYVY)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_UYVY) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_YUV420P)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_YUV420P) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_YUV422P)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_YUV422P) << endl; + +// Compressed formats + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_JPEG)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_JPEG) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_MPEG)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_MPEG) << endl; + +// Reserved formats + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_DV)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_DV) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_ET61X251)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_ET61X251) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_HI240)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_HI240) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_HM12)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_HM12) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_MJPEG)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_MJPEG) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_PWC1)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_PWC1) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_PWC2)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_PWC2) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_SN9C10X)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_SN9C10X) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_WNVA)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_WNVA) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_YYUV)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_YYUV) << endl; + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + return PIXELFORMAT_NONE; break; + } + return PIXELFORMAT_NONE; +} + +__u64 VideoDevice::signalStandardCode(signal_standard standard) +{ + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + switch(standard) + { + case STANDARD_NONE : return V4L2_STD_UNKNOWN; break; + case STANDARD_PAL_B : return V4L2_STD_PAL_B; break; + case STANDARD_PAL_B1 : return V4L2_STD_PAL_B1; break; + case STANDARD_PAL_G : return V4L2_STD_PAL_G; break; + case STANDARD_PAL_H : return V4L2_STD_PAL_H; break; + case STANDARD_PAL_I : return V4L2_STD_PAL_I; break; + case STANDARD_PAL_D : return V4L2_STD_PAL_D; break; + case STANDARD_PAL_D1 : return V4L2_STD_PAL_D1; break; + case STANDARD_PAL_K : return V4L2_STD_PAL_K; break; + case STANDARD_PAL_M : return V4L2_STD_PAL_M; break; + case STANDARD_PAL_N : return V4L2_STD_PAL_N; break; + case STANDARD_PAL_Nc : return V4L2_STD_PAL_Nc; break; + case STANDARD_PAL_60 : return V4L2_STD_PAL_60; break; + case STANDARD_NTSC_M : return V4L2_STD_NTSC_M; break; + case STANDARD_NTSC_M_JP : return V4L2_STD_NTSC_M_JP; break; + case STANDARD_NTSC_443 : return V4L2_STD_NTSC; break; // Using workaround value because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case STANDARD_SECAM_B : return V4L2_STD_SECAM_B; break; + case STANDARD_SECAM_D : return V4L2_STD_SECAM_D; break; + case STANDARD_SECAM_G : return V4L2_STD_SECAM_G; break; + case STANDARD_SECAM_H : return V4L2_STD_SECAM_H; break; + case STANDARD_SECAM_K : return V4L2_STD_SECAM_K; break; + case STANDARD_SECAM_K1 : return V4L2_STD_SECAM_K1; break; + case STANDARD_SECAM_L : return V4L2_STD_SECAM_L; break; + case STANDARD_SECAM_LC : return V4L2_STD_SECAM; break; // Using workaround value because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case STANDARD_ATSC_8_VSB : return V4L2_STD_ATSC_8_VSB; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_ATSC_16_VSB : return V4L2_STD_ATSC_16_VSB; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_PAL_BG : return V4L2_STD_PAL_BG; break; + case STANDARD_PAL_DK : return V4L2_STD_PAL_DK; break; + case STANDARD_PAL : return V4L2_STD_PAL; break; + case STANDARD_NTSC : return V4L2_STD_NTSC; break; + case STANDARD_SECAM_DK : return V4L2_STD_SECAM_DK; break; + case STANDARD_SECAM : return V4L2_STD_SECAM; break; + case STANDARD_525_60 : return V4L2_STD_525_60; break; + case STANDARD_625_50 : return V4L2_STD_625_50; break; + case STANDARD_ALL : return V4L2_STD_ALL; break; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + switch(standard) + { + case STANDARD_NONE : return VIDEO_MODE_AUTO; break; + case STANDARD_PAL_B : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_B1 : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_G : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_H : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_I : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_D : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_D1 : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_K : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_M : return 5; break; // Undocumented value found to be compatible with V4L bttv driver + case STANDARD_PAL_N : return 6; break; // Undocumented value found to be compatible with V4L bttv driver + case STANDARD_PAL_Nc : return 4; break; // Undocumented value found to be compatible with V4L bttv driver + case STANDARD_PAL_60 : return VIDEO_MODE_PAL; break; + case STANDARD_NTSC_M : return VIDEO_MODE_NTSC; break; + case STANDARD_NTSC_M_JP : return 7; break; // Undocumented value found to be compatible with V4L bttv driver + case STANDARD_NTSC_443 : return VIDEO_MODE_NTSC; break; // Using workaround value because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case STANDARD_SECAM_B : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM_D : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM_G : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM_H : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM_K : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM_K1 : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM_L : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM_LC : return VIDEO_MODE_SECAM; break; // Using workaround value because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case STANDARD_ATSC_8_VSB : return VIDEO_MODE_AUTO; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_ATSC_16_VSB : return VIDEO_MODE_AUTO; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_PAL_BG : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_DK : return VIDEO_MODE_PAL; break; + case STANDARD_PAL : return VIDEO_MODE_PAL; break; + case STANDARD_NTSC : return VIDEO_MODE_NTSC; break; + case STANDARD_SECAM_DK : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM : return VIDEO_MODE_SECAM; break; + case STANDARD_525_60 : return VIDEO_MODE_PAL; break; + case STANDARD_625_50 : return VIDEO_MODE_SECAM; break; + case STANDARD_ALL : return VIDEO_MODE_AUTO; break; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + return STANDARD_NONE; break; + } + return STANDARD_NONE; +} + +QString VideoDevice::signalStandardName(signal_standard standard) +{ + QString returnvalue; + returnvalue = "None"; + switch(standard) + { + case STANDARD_NONE : returnvalue = "None"; break; + case STANDARD_PAL_B : returnvalue = "PAL-B"; break; + case STANDARD_PAL_B1 : returnvalue = "PAL-B1"; break; + case STANDARD_PAL_G : returnvalue = "PAL-G"; break; + case STANDARD_PAL_H : returnvalue = "PAL-H"; break; + case STANDARD_PAL_I : returnvalue = "PAL-I"; break; + case STANDARD_PAL_D : returnvalue = "PAL-D"; break; + case STANDARD_PAL_D1 : returnvalue = "PAL-D1"; break; + case STANDARD_PAL_K : returnvalue = "PAL-K"; break; + case STANDARD_PAL_M : returnvalue = "PAL-M"; break; + case STANDARD_PAL_N : returnvalue = "PAL-N"; break; + case STANDARD_PAL_Nc : returnvalue = "PAL-Nc"; break; + case STANDARD_PAL_60 : returnvalue = "PAL-60"; break; + case STANDARD_NTSC_M : returnvalue = "NTSC-M"; break; + case STANDARD_NTSC_M_JP : returnvalue = "NTSC-M(JP)"; break; + case STANDARD_NTSC_443 : returnvalue = "NTSC-443"; break; + case STANDARD_SECAM_B : returnvalue = "SECAM-B"; break; + case STANDARD_SECAM_D : returnvalue = "SECAM-D"; break; + case STANDARD_SECAM_G : returnvalue = "SECAM-G"; break; + case STANDARD_SECAM_H : returnvalue = "SECAM-H"; break; + case STANDARD_SECAM_K : returnvalue = "SECAM-K"; break; + case STANDARD_SECAM_K1 : returnvalue = "SECAM-K1"; break; + case STANDARD_SECAM_L : returnvalue = "SECAM-L"; break; + case STANDARD_SECAM_LC : returnvalue = "SECAM-LC"; break; + case STANDARD_ATSC_8_VSB : returnvalue = "ATSC-8-VSB"; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_ATSC_16_VSB : returnvalue = "ATSC-16-VSB"; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_PAL_BG : returnvalue = "PAL-BG"; break; + case STANDARD_PAL_DK : returnvalue = "PAL-DK"; break; + case STANDARD_PAL : returnvalue = "PAL"; break; + case STANDARD_NTSC : returnvalue = "NTSC"; break; + case STANDARD_SECAM_DK : returnvalue = "SECAM-DK"; break; + case STANDARD_SECAM : returnvalue = "SECAM"; break; + case STANDARD_525_60 : returnvalue = "525 lines 60Hz"; break; + case STANDARD_625_50 : returnvalue = "625 lines 50Hz"; break; + case STANDARD_ALL : returnvalue = "All"; break; + } + return returnvalue; +} + +QString VideoDevice::signalStandardName(int standard) +{ + QString returnvalue; + returnvalue = "None"; + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + switch(standard) + { + case V4L2_STD_PAL_B : returnvalue = signalStandardName(STANDARD_PAL_B); break; + case V4L2_STD_PAL_B1 : returnvalue = signalStandardName(STANDARD_PAL_B1); break; + case V4L2_STD_PAL_G : returnvalue = signalStandardName(STANDARD_PAL_G); break; + case V4L2_STD_PAL_H : returnvalue = signalStandardName(STANDARD_PAL_H); break; + case V4L2_STD_PAL_I : returnvalue = signalStandardName(STANDARD_PAL_I); break; + case V4L2_STD_PAL_D : returnvalue = signalStandardName(STANDARD_PAL_D); break; + case V4L2_STD_PAL_D1 : returnvalue = signalStandardName(STANDARD_PAL_D1); break; + case V4L2_STD_PAL_K : returnvalue = signalStandardName(STANDARD_PAL_K); break; + case V4L2_STD_PAL_M : returnvalue = signalStandardName(STANDARD_PAL_M); break; + case V4L2_STD_PAL_N : returnvalue = signalStandardName(STANDARD_PAL_N); break; + case V4L2_STD_PAL_Nc : returnvalue = signalStandardName(STANDARD_PAL_Nc); break; + case V4L2_STD_PAL_60 : returnvalue = signalStandardName(STANDARD_PAL_60); break; + case V4L2_STD_NTSC_M : returnvalue = signalStandardName(STANDARD_NTSC_M); break; + case V4L2_STD_NTSC_M_JP : returnvalue = signalStandardName(STANDARD_NTSC_M_JP); break; +// case V4L2_STD_NTSC_443 : returnvalue = signalStandardName(STANDARD_NTSC_443); break; // Commented out because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case V4L2_STD_SECAM_B : returnvalue = signalStandardName(STANDARD_SECAM_B); break; + case V4L2_STD_SECAM_D : returnvalue = signalStandardName(STANDARD_SECAM_D); break; + case V4L2_STD_SECAM_G : returnvalue = signalStandardName(STANDARD_SECAM_G); break; + case V4L2_STD_SECAM_H : returnvalue = signalStandardName(STANDARD_SECAM_H); break; + case V4L2_STD_SECAM_K : returnvalue = signalStandardName(STANDARD_SECAM_K); break; + case V4L2_STD_SECAM_K1 : returnvalue = signalStandardName(STANDARD_SECAM_K1); break; + case V4L2_STD_SECAM_L : returnvalue = signalStandardName(STANDARD_SECAM_L); break; +// case V4L2_STD_SECAM_LC : returnvalue = signalStandardName(STANDARD_SECAM_LC); break; // Commented out because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case V4L2_STD_ATSC_8_VSB : returnvalue = signalStandardName(STANDARD_ATSC_8_VSB); break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case V4L2_STD_ATSC_16_VSB : returnvalue = signalStandardName(STANDARD_ATSC_16_VSB); break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case V4L2_STD_PAL_BG : returnvalue = signalStandardName(STANDARD_PAL_BG); break; + case V4L2_STD_PAL_DK : returnvalue = signalStandardName(STANDARD_PAL_DK); break; + case V4L2_STD_PAL : returnvalue = signalStandardName(STANDARD_PAL); break; + case V4L2_STD_NTSC : returnvalue = signalStandardName(STANDARD_NTSC); break; + case V4L2_STD_SECAM_DK : returnvalue = signalStandardName(STANDARD_SECAM_DK); break; + case V4L2_STD_SECAM : returnvalue = signalStandardName(STANDARD_SECAM); break; + case V4L2_STD_525_60 : returnvalue = signalStandardName(STANDARD_525_60); break; + case V4L2_STD_625_50 : returnvalue = signalStandardName(STANDARD_625_50); break; + case V4L2_STD_ALL : returnvalue = signalStandardName(STANDARD_ALL); break; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + switch(standard) + { + case VIDEO_MODE_PAL : returnvalue = signalStandardName(STANDARD_PAL); break; + case VIDEO_MODE_NTSC : returnvalue = signalStandardName(STANDARD_NTSC); break; + case VIDEO_MODE_SECAM : returnvalue = signalStandardName(STANDARD_SECAM); break; + case VIDEO_MODE_AUTO : returnvalue = signalStandardName(STANDARD_ALL); break; // It must be disabled until I find a correct way to handle those non-standard bttv modes +// case VIDEO_MODE_PAL_Nc : returnvalue = signalStandardName(STANDARD_PAL_Nc); break; // Undocumented value found to be compatible with V4L bttv driver + case VIDEO_MODE_PAL_M : returnvalue = signalStandardName(STANDARD_PAL_M); break; // Undocumented value found to be compatible with V4L bttv driver + case VIDEO_MODE_PAL_N : returnvalue = signalStandardName(STANDARD_PAL_N); break; // Undocumented value found to be compatible with V4L bttv driver + case VIDEO_MODE_NTSC_JP : returnvalue = signalStandardName(STANDARD_NTSC_M_JP); break; // Undocumented value found to be compatible with V4L bttv driver + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return returnvalue; +} + +/*! + \fn VideoDevice::detectSignalStandards() + */ +int VideoDevice::detectSignalStandards() +{ + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + break; +#endif + case VIDEODEV_DRIVER_V4L: + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + //FIXME: return a real value + return 0; +} + +/*! + \fn VideoDevice::initRead() + */ +int VideoDevice::initRead() +{ + /// @todo implement me + + kdDebug(14010) << k_funcinfo << "called." << endl; + if(isOpen()) + { + m_rawbuffers.resize(1); + if (m_rawbuffers.size()==0) + { + fprintf (stderr, "Out of memory\n"); + return EXIT_FAILURE; + } + kdDebug(14010) << k_funcinfo << "m_buffer_size: " << m_buffer_size << endl; + +// m_rawbuffers[0].pixelformat=m_pixelformat; + m_rawbuffers[0].length = m_buffer_size; + m_rawbuffers[0].start = (uchar *)malloc (m_buffer_size); + + if (!m_rawbuffers[0].start) + { + fprintf (stderr, "Out of memory\n"); + return EXIT_FAILURE; + } + kdDebug(14010) << k_funcinfo << "exited successfuly." << endl; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + + +/*! + \fn VideoDevice::initMmap() + */ +int VideoDevice::initMmap() +{ + /// @todo implement me +#define BUFFERS 2 + if(isOpen()) + { + kdDebug(14010) << k_funcinfo << full_filename << " Trying to MMAP" << endl; +#ifdef V4L2_CAP_VIDEO_CAPTURE + struct v4l2_requestbuffers req; + + CLEAR (req); + + req.count = BUFFERS; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl (VIDIOC_REQBUFS, &req)) + { + if (EINVAL == errno) + { + kdDebug(14010) << k_funcinfo << full_filename << " does not support memory mapping" << endl; + return EXIT_FAILURE; + } + else + { + return errnoReturn ("VIDIOC_REQBUFS"); + } + } + + if (req.count < BUFFERS) + { + kdDebug(14010) << k_funcinfo << "Insufficient buffer memory on " << full_filename << endl; + return EXIT_FAILURE; + } + + m_rawbuffers.resize(req.count); + + if (m_rawbuffers.size()==0) + { + kdDebug(14010) << k_funcinfo << "Out of memory" << endl; + return EXIT_FAILURE; + } + + for (m_streambuffers = 0; m_streambuffers < req.count; ++m_streambuffers) + { + struct v4l2_buffer v4l2buffer; + + CLEAR (v4l2buffer); + + v4l2buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2buffer.memory = V4L2_MEMORY_MMAP; + v4l2buffer.index = m_streambuffers; + + if (-1 == xioctl (VIDIOC_QUERYBUF, &v4l2buffer)) + return errnoReturn ("VIDIOC_QUERYBUF"); + + m_rawbuffers[m_streambuffers].length = v4l2buffer.length; + m_rawbuffers[m_streambuffers].start = (uchar *) mmap (NULL /* start anywhere */, v4l2buffer.length, PROT_READ | PROT_WRITE /* required */, MAP_SHARED /* recommended */, descriptor, v4l2buffer.m.offset); + + if (MAP_FAILED == m_rawbuffers[m_streambuffers].start) + return errnoReturn ("mmap"); + } +#endif + m_currentbuffer.data.resize(m_rawbuffers[0].length); // Makes the imagesize.data buffer size equal to the rawbuffer size + kdDebug(14010) << k_funcinfo << full_filename << " m_currentbuffer.data.size(): " << m_currentbuffer.data.size() << endl; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + + +/*! + \fn VideoDevice::initUserptr() + */ +int VideoDevice::initUserptr() +{ + /// @todo implement me + if(isOpen()) + { +#ifdef V4L2_CAP_VIDEO_CAPTURE + struct v4l2_requestbuffers req; + + CLEAR (req); + + req.count = 2; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_USERPTR; + + if (-1 == xioctl (VIDIOC_REQBUFS, &req)) + { + if (EINVAL == errno) + { + kdDebug(14010) << k_funcinfo << full_filename << " does not support memory mapping" << endl; + return EXIT_FAILURE; + } + else + { + return errnoReturn ("VIDIOC_REQBUFS"); + } + } + + m_rawbuffers.resize(4); + + if (m_rawbuffers.size()==0) + { + fprintf (stderr, "Out of memory\n"); + return EXIT_FAILURE; + } + + for (m_streambuffers = 0; m_streambuffers < 4; ++m_streambuffers) + { + m_rawbuffers[m_streambuffers].length = m_buffer_size; + m_rawbuffers[m_streambuffers].start = (uchar *) malloc (m_buffer_size); + + if (!m_rawbuffers[m_streambuffers].start) + { + kdDebug(14010) << k_funcinfo << "Out of memory" << endl; + return EXIT_FAILURE; + } + } +#endif + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +bool VideoDevice::canCapture() +{ + return m_videocapture; +} + +bool VideoDevice::canChromakey() +{ + return m_videochromakey; +} + +bool VideoDevice::canScale() +{ + return m_videoscale; +} + +bool VideoDevice::canOverlay() +{ + return m_videooverlay; +} + +bool VideoDevice::canRead() +{ + return m_videoread; +} + +bool VideoDevice::canAsyncIO() +{ + return m_videoasyncio; +} + +bool VideoDevice::canStream() +{ + return m_videostream; +} + + + +} + +} diff --git a/kopete/libkopete/avdevice/videodevice.h b/kopete/libkopete/avdevice/videodevice.h new file mode 100644 index 00000000..982ab5f3 --- /dev/null +++ b/kopete/libkopete/avdevice/videodevice.h @@ -0,0 +1,333 @@ +/* + videodevice.cpp - Kopete Video Device Low-level Support + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro <taupter@gmail.com> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#define ENABLE_AV + +#ifndef KOPETE_AVVIDEODEVICELISTITEM_H +#define KOPETE_AVVIDEODEVICELISTITEM_H + +#if defined HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/time.h> +#include <sys/mman.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <signal.h> + +#if defined(__linux__) && defined(ENABLE_AV) + +#include <asm/types.h> +#undef __STRICT_ANSI__ +#ifndef __u64 //required by videodev.h +#define __u64 unsigned long long +#endif // __u64 + +#ifndef __s64 //required by videodev.h +#define __s64 long long +#endif // __s64 + + +#ifndef pgoff_t +#define pgoff_t unsigned long +#endif + +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/videodev.h> +#define VIDEO_MODE_PAL_Nc 3 +#define VIDEO_MODE_PAL_M 4 +#define VIDEO_MODE_PAL_N 5 +#define VIDEO_MODE_NTSC_JP 6 +#define __STRICT_ANSI__ + +#endif // __linux__ + +#include <qstring.h> +#include <qfile.h> +#include <qimage.h> +#include <qvaluevector.h> +#include <kcombobox.h> + +#include "videoinput.h" +#include "videocontrol.h" + +namespace Kopete { + +namespace AV { + +/** +@author Kopete Developers +*/ +typedef enum +{ + VIDEODEV_DRIVER_NONE +#if defined( __linux__) && defined(ENABLE_AV) + , + VIDEODEV_DRIVER_V4L +#ifdef V4L2_CAP_VIDEO_CAPTURE + , + VIDEODEV_DRIVER_V4L2 +#endif +#endif +} videodev_driver; + +typedef enum +{ +// Packed RGB formats + PIXELFORMAT_NONE = 0, + PIXELFORMAT_GREY = (1 << 0), + PIXELFORMAT_RGB332 = (1 << 1), + PIXELFORMAT_RGB444 = (1 << 2), + PIXELFORMAT_RGB555 = (1 << 3), + PIXELFORMAT_RGB565 = (1 << 4), + PIXELFORMAT_RGB555X = (1 << 5), + PIXELFORMAT_RGB565X = (1 << 6), + PIXELFORMAT_BGR24 = (1 << 7), + PIXELFORMAT_RGB24 = (1 << 8), + PIXELFORMAT_BGR32 = (1 << 9), + PIXELFORMAT_RGB32 = (1 << 10), + +// Bayer RGB format + PIXELFORMAT_SBGGR8 = (1 << 11), + +// YUV formats + PIXELFORMAT_YUYV = (1 << 12), + PIXELFORMAT_UYVY = (1 << 13), + PIXELFORMAT_YUV420P = (1 << 14), + PIXELFORMAT_YUV422P = (1 << 15), + +// Compressed formats + PIXELFORMAT_JPEG = (1 << 16), + PIXELFORMAT_MPEG = (1 << 17), + +// Reserved formats + PIXELFORMAT_DV = (1 << 18), + PIXELFORMAT_ET61X251 = (1 << 19), + PIXELFORMAT_HI240 = (1 << 20), + PIXELFORMAT_HM12 = (1 << 21), + PIXELFORMAT_MJPEG = (1 << 22), + PIXELFORMAT_PWC1 = (1 << 23), + PIXELFORMAT_PWC2 = (1 << 24), + PIXELFORMAT_SN9C10X = (1 << 25), + PIXELFORMAT_WNVA = (1 << 26), + PIXELFORMAT_YYUV = (1 << 27) + +// PIXELFORMAT_ALL = 0x00003FFF +} pixel_format; + +typedef enum +{ + STANDARD_NONE = 0, + STANDARD_PAL_B = (1 << 0), + STANDARD_PAL_B1 = (1 << 1), + STANDARD_PAL_G = (1 << 2), + STANDARD_PAL_H = (1 << 3), + STANDARD_PAL_I = (1 << 4), + STANDARD_PAL_D = (1 << 5), + STANDARD_PAL_D1 = (1 << 6), + STANDARD_PAL_K = (1 << 7), + STANDARD_PAL_M = (1 << 8), + STANDARD_PAL_N = (1 << 9), + STANDARD_PAL_Nc = (1 << 10), + STANDARD_PAL_60 = (1 << 11), +// STANDARD_PAL_60 is a hybrid standard with 525 lines, 60 Hz refresh rate, and PAL color modulation with a 4.43 MHz color subcarrier. Some PAL video recorders can play back NTSC tapes in this mode for display on a 50/60 Hz agnostic PAL TV. + STANDARD_NTSC_M = (1 << 12), + STANDARD_NTSC_M_JP = (1 << 13), + STANDARD_NTSC_443 = (1 << 14), +// STANDARD_NTSC_443 is a hybrid standard with 525 lines, 60 Hz refresh rate, and NTSC color modulation with a 4.43 MHz color subcarrier. + STANDARD_SECAM_B = (1 << 16), + STANDARD_SECAM_D = (1 << 17), + STANDARD_SECAM_G = (1 << 18), + STANDARD_SECAM_H = (1 << 19), + STANDARD_SECAM_K = (1 << 20), + STANDARD_SECAM_K1 = (1 << 21), + STANDARD_SECAM_L = (1 << 22), + STANDARD_SECAM_LC = (1 << 23), +// ATSC/HDTV + STANDARD_ATSC_8_VSB = (1 << 24), + STANDARD_ATSC_16_VSB = (1 << 25), + + STANDARD_PAL_BG = ( STANDARD_PAL_B | STANDARD_PAL_B1 | STANDARD_PAL_G ), + STANDARD_PAL_DK = ( STANDARD_PAL_D | STANDARD_PAL_D1 | STANDARD_PAL_K ), + STANDARD_PAL = ( STANDARD_PAL_BG | STANDARD_PAL_DK | STANDARD_PAL_H | STANDARD_PAL_I ), + STANDARD_NTSC = ( STANDARD_NTSC_M | STANDARD_NTSC_M_JP ), + STANDARD_SECAM_DK = ( STANDARD_SECAM_D | STANDARD_SECAM_K | STANDARD_SECAM_K1 ), + STANDARD_SECAM = ( STANDARD_SECAM_B | STANDARD_SECAM_G | STANDARD_SECAM_H | STANDARD_SECAM_DK | STANDARD_SECAM_L), + STANDARD_525_60 = ( STANDARD_PAL_M | STANDARD_PAL_60 | STANDARD_NTSC | STANDARD_NTSC_443), + STANDARD_625_50 = ( STANDARD_PAL | STANDARD_PAL_N | STANDARD_PAL_Nc | STANDARD_SECAM), + STANDARD_ALL = ( STANDARD_525_60 | STANDARD_625_50) +} signal_standard; + + +typedef enum +{ + IO_METHOD_NONE, + IO_METHOD_READ, + IO_METHOD_MMAP, + IO_METHOD_USERPTR +} io_method; + +struct imagebuffer +{ + int height; + int width; + pixel_format pixelformat; + QValueVector <uchar> data; // maybe it should be a rawbuffer instead of it? It could make us avoid a memory copy +}; +struct rawbuffer // raw buffer +{ + uchar * start; + size_t length; +}; + + +class VideoDevice{ +public: + VideoDevice(); + ~VideoDevice(); + int setFileName(QString filename); + int open(); + bool isOpen(); + int checkDevice(); + int showDeviceCapabilities(); + int initDevice(); + unsigned int inputs(); + int width(); + int minWidth(); + int maxWidth(); + int height(); + int minHeight(); + int maxHeight(); + int setSize( int newwidth, int newheight); + + pixel_format setPixelFormat(pixel_format newformat); + int pixelFormatCode(pixel_format pixelformat); + pixel_format pixelFormatForPalette( int palette ); + int pixelFormatDepth(pixel_format pixelformat); + QString pixelFormatName(pixel_format pixelformat); + QString pixelFormatName(int pixelformat); + int detectPixelFormats(); + + __u64 signalStandardCode(signal_standard standard); + QString signalStandardName(signal_standard standard); + QString signalStandardName(int standard); + int detectSignalStandards(); + + int currentInput(); + int selectInput(int input); + int setInputParameters(); + int startCapturing(); + int getFrame(); + int getFrame(imagebuffer *imgbuffer); + int getImage(QImage *qimage); + int stopCapturing(); + int close(); + + float getBrightness(); + float setBrightness(float brightness); + float getContrast(); + float setContrast(float contrast); + float getSaturation(); + float setSaturation(float saturation); + float getWhiteness(); + float setWhiteness(float whiteness); + float getHue(); + float setHue(float Hue); + + bool getAutoBrightnessContrast(); + bool setAutoBrightnessContrast(bool brightnesscontrast); + bool getAutoColorCorrection(); + bool setAutoColorCorrection(bool colorcorrection); + bool getImageAsMirror(); + bool setImageAsMirror(bool imageasmirror); + + bool canCapture(); + bool canChromakey(); + bool canScale(); + bool canOverlay(); + bool canRead(); + bool canAsyncIO(); + bool canStream(); + + QString m_model; + QString m_name; + size_t m_modelindex; // Defines what's the number of a device when more than 1 device of a given model is present; + QString full_filename; + videodev_driver m_driver; + int descriptor; + +//protected: +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + struct v4l2_capability V4L2_capabilities; + struct v4l2_cropcap cropcap; + struct v4l2_crop crop; + struct v4l2_format fmt; + struct v4l2_fmtdesc fmtdesc; // Not sure if it must be here or inside detectPixelFormats(). Should inve +// struct v4l2_input m_input; + struct v4l2_queryctrl queryctrl; + struct v4l2_querymenu querymenu; +void enumerateMenu (void); + +#endif + struct video_capability V4L_capabilities; + struct video_buffer V4L_videobuffer; +#endif + QValueVector<Kopete::AV::VideoInput> m_input; + QValueVector<Kopete::AV::VideoControl> m_control; +// QFile file; +protected: + int currentwidth, minwidth, maxwidth, currentheight, minheight, maxheight; + + bool m_disablemmap; + bool m_workaroundbrokendriver; + + QValueVector<rawbuffer> m_rawbuffers; + unsigned int m_streambuffers; + imagebuffer m_currentbuffer; + int m_buffer_size; + + int m_current_input; + pixel_format m_pixelformat; + + io_method m_io_method; + bool m_videocapture; + bool m_videochromakey; + bool m_videoscale; + bool m_videooverlay; + bool m_videoread; + bool m_videoasyncio; + bool m_videostream; + + int xioctl(int request, void *arg); + int errnoReturn(const char* s); + int initRead(); + int initMmap(); + int initUserptr(); + +}; + +} + +} + +#endif diff --git a/kopete/libkopete/avdevice/videodevicemodelpool.cpp b/kopete/libkopete/avdevice/videodevicemodelpool.cpp new file mode 100644 index 00000000..c6fc533e --- /dev/null +++ b/kopete/libkopete/avdevice/videodevicemodelpool.cpp @@ -0,0 +1,68 @@ +/* + videodevicepool.h - Kopete Multiple Video Device handler Class + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro <taupter@gmail.com> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "videodevicemodelpool.h" + +namespace Kopete { + +namespace AV { + +VideoDeviceModelPool::VideoDeviceModelPool() +{ +} + + +VideoDeviceModelPool::~VideoDeviceModelPool() +{ +} + +void VideoDeviceModelPool::clear() +{ + m_devicemodel.clear(); +} + +size_t VideoDeviceModelPool::size() +{ + return m_devicemodel.size(); +} + +size_t VideoDeviceModelPool::addModel( QString newmodel ) +{ + VideoDeviceModel newdevicemodel; + newdevicemodel.model=newmodel; + newdevicemodel.count=0; + + if(m_devicemodel.size()) + { + for ( size_t loop = 0 ; loop < m_devicemodel.size(); loop++) + if (newmodel == m_devicemodel[loop].model) + { + kdDebug() << k_funcinfo << "Model " << newmodel << " already exists." << endl; + m_devicemodel[loop].count++; + return m_devicemodel[loop].count; + } + } + m_devicemodel.push_back(newdevicemodel); + m_devicemodel[m_devicemodel.size()-1].model = newmodel; + m_devicemodel[m_devicemodel.size()-1].count = 0; + return 0; +} + + +} + +} diff --git a/kopete/libkopete/avdevice/videodevicemodelpool.h b/kopete/libkopete/avdevice/videodevicemodelpool.h new file mode 100644 index 00000000..54d801c4 --- /dev/null +++ b/kopete/libkopete/avdevice/videodevicemodelpool.h @@ -0,0 +1,53 @@ +/* + videodevicepool.h - Kopete Multiple Video Device handler Class + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro <taupter@gmail.com> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_AVVIDEODEVICEMODELPOOL_H +#define KOPETE_AVVIDEODEVICEMODELPOOL_H + +#include <qstring.h> +#include <qvaluevector.h> +#include <kdebug.h> +#include "kopete_export.h" + +namespace Kopete { + +namespace AV { + +/** + @author Kopete Developers <kopete-devel@kde.org> +*/ +class VideoDeviceModelPool{ + + struct VideoDeviceModel + { + QString model; + size_t count; + }; + QValueVector<VideoDeviceModel> m_devicemodel; +public: + VideoDeviceModelPool(); + ~VideoDeviceModelPool(); + void clear(); + size_t size(); + size_t addModel(QString newmodel); +}; + +} + +} + +#endif diff --git a/kopete/libkopete/avdevice/videodevicepool.cpp b/kopete/libkopete/avdevice/videodevicepool.cpp new file mode 100644 index 00000000..2651addb --- /dev/null +++ b/kopete/libkopete/avdevice/videodevicepool.cpp @@ -0,0 +1,889 @@ +/* + videodevice.cpp - Kopete Video Device Low-level Support + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro <taupter@gmail.com> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#define ENABLE_AV + +#include <assert.h> +#include <cstdlib> +#include <cerrno> +#include <cstring> + +#include <kdebug.h> +#include <klocale.h> +#include <qdir.h> + +#include "videodevice.h" +#include "videodevicepool.h" + +#define CLEAR(x) memset (&(x), 0, sizeof (x)) + +namespace Kopete { + +namespace AV { + +VideoDevicePool *VideoDevicePool::s_self = NULL; +__u64 VideoDevicePool::m_clients = 0; + +VideoDevicePool* VideoDevicePool::self() +{ + kdDebug(14010) << "libkopete (avdevice): self() called" << endl; + if (s_self == NULL) + { + s_self = new VideoDevicePool; + if (s_self) + m_clients = 0; + } + kdDebug(14010) << "libkopete (avdevice): self() exited successfuly. m_clients = " << m_clients << endl; + return s_self; +} + +VideoDevicePool::VideoDevicePool() +{ +} + + +VideoDevicePool::~VideoDevicePool() +{ +} + + + + +/*! + \fn VideoDevicePool::open() + */ +int VideoDevicePool::open() +{ + /// @todo implement me + + m_ready.lock(); + if(!m_videodevice.size()) + { + kdDebug(14010) << k_funcinfo << "open(): No devices found. Must scan for available devices." << m_current_device << endl; + scanDevices(); + } + if(!m_videodevice.size()) + { + kdDebug(14010) << k_funcinfo << "open(): No devices found. bailing out." << m_current_device << endl; + m_ready.unlock(); + return EXIT_FAILURE; + } + if(m_current_device >= m_videodevice.size()) + { + kdDebug(14010) << k_funcinfo << "open(): Device out of scope (" << m_current_device << "). Defaulting to the first one." << endl; + m_current_device = 0; + } + int isopen = m_videodevice[currentDevice()].open(); + if ( isopen == EXIT_SUCCESS) + { + loadConfig(); // Temporary hack. The open() seems to clean the input parameters. Need to find a way to fix it. + + } + m_clients++; + kdDebug(14010) << k_funcinfo << "Number of clients: " << m_clients << endl; + m_ready.unlock(); + return isopen; +} + +/*! + \fn VideoDevicePool::open(int device) + */ +int VideoDevicePool::open(unsigned int device) +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "open(" << device << ") called." << endl; + if(device >= m_videodevice.size()) + { + kdDebug(14010) << k_funcinfo << "open(" << device <<"): Device does not exist." << endl; + return EXIT_FAILURE; + } + close(); + kdDebug(14010) << k_funcinfo << "open(" << device << ") Setting m_current_Device to " << device << endl; + m_current_device = device; + saveConfig(); + kdDebug(14010) << k_funcinfo << "open(" << device << ") Calling open()." << endl; + return open(); +} + +bool VideoDevicePool::isOpen() +{ + return m_videodevice[currentDevice()].isOpen(); +} + +/*! + \fn VideoDevicePool::showDeviceCapabilities(int device) + */ +int VideoDevicePool::showDeviceCapabilities(unsigned int device) +{ + return m_videodevice[device].showDeviceCapabilities(); +} + +int VideoDevicePool::width() +{ + return m_videodevice[currentDevice()].width(); +} + +int VideoDevicePool::minWidth() +{ + return m_videodevice[currentDevice()].minWidth(); +} + +int VideoDevicePool::maxWidth() +{ + return m_videodevice[currentDevice()].maxWidth(); +} + +int VideoDevicePool::height() +{ + return m_videodevice[currentDevice()].height(); +} + +int VideoDevicePool::minHeight() +{ + return m_videodevice[currentDevice()].minHeight(); +} + +int VideoDevicePool::maxHeight() +{ + return m_videodevice[currentDevice()].maxHeight(); +} + +int VideoDevicePool::setSize( int newwidth, int newheight) +{ + if(m_videodevice.size()) + return m_videodevice[currentDevice()].setSize(newwidth, newheight); + else + { + kdDebug(14010) << k_funcinfo << "VideoDevicePool::setSize() fallback for no device." << endl; + m_buffer.width=newwidth; + m_buffer.height=newheight; + m_buffer.pixelformat= PIXELFORMAT_RGB24; + m_buffer.data.resize(m_buffer.width*m_buffer.height*3); + kdDebug(14010) << k_funcinfo << "VideoDevicePool::setSize() buffer size: "<< m_buffer.data.size() << endl; + } + return EXIT_SUCCESS; +} + +/*! + \fn VideoDevicePool::close() + */ +int VideoDevicePool::close() +{ + /// @todo implement me + if(m_clients) + m_clients--; + if((currentDevice() < m_videodevice.size())&&(!m_clients)) + return m_videodevice[currentDevice()].close(); + if(m_clients) + kdDebug(14010) << k_funcinfo << "VideoDevicePool::close() The video device is still in use." << endl; + if(currentDevice() >= m_videodevice.size()) + kdDebug(14010) << k_funcinfo << "VideoDevicePool::close() Current device out of range." << endl; + return EXIT_FAILURE; +} + +/*! + \fn VideoDevicePool::startCapturing() + */ +int VideoDevicePool::startCapturing() +{ + kdDebug(14010) << k_funcinfo << "startCapturing() called." << endl; + if(m_videodevice.size()) + return m_videodevice[currentDevice()].startCapturing(); + return EXIT_FAILURE; +} + + +/*! + \fn VideoDevicePool::stopCapturing() + */ +int VideoDevicePool::stopCapturing() +{ + if(m_videodevice.size()) + return m_videodevice[currentDevice()].stopCapturing(); + return EXIT_FAILURE; +} + +// Implementation of the methods that get / set input's adjustment parameters +/*! + \fn VideoDevicePool::getBrightness() + */ +float VideoDevicePool::getBrightness() +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].getBrightness(); + else + return 0; +} + +/*! + \fn VideoDevicePool::setBrightness(float brightness) + */ +float VideoDevicePool::setBrightness(float brightness) +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].setBrightness(brightness); + else + return 0; +} + +/*! + \fn VideoDevicePool::getContrast() + */ +float VideoDevicePool::getContrast() +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].getContrast(); + else + return 0; +} + +/*! + \fn VideoDevicePool::setContrast(float contrast) + */ +float VideoDevicePool::setContrast(float contrast) +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].setContrast(contrast); + else + return 0; +} + +/*! + \fn VideoDevicePool::getSaturation() + */ +float VideoDevicePool::getSaturation() +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].getSaturation(); + else + return 0; +} + +/*! + \fn VideoDevicePool::setSaturation(float saturation) + */ +float VideoDevicePool::setSaturation(float saturation) +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].setSaturation(saturation); + else + return 0; +} + +/*! + \fn VideoDevicePool::getWhiteness() + */ +float VideoDevicePool::getWhiteness() +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].getWhiteness(); + else + return 0; +} + +/*! + \fn VideoDevicePool::setWhiteness(float whiteness) + */ +float VideoDevicePool::setWhiteness(float whiteness) +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].setWhiteness(whiteness); + else + return 0; +} + +/*! + \fn VideoDevicePool::getHue() + */ +float VideoDevicePool::getHue() +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].getHue(); + else + return 0; +} + +/*! + \fn VideoDevicePool::setHue(float hue) + */ +float VideoDevicePool::setHue(float hue) +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].setHue(hue); + else + return 0; +} + +/*! + \fn VideoDevicePool::getAutoBrightnessContrast() + */ +bool VideoDevicePool::getAutoBrightnessContrast() +{ + if(m_videodevice.size()) + return m_videodevice[currentDevice()].getAutoBrightnessContrast(); + return false; +} + +/*! + \fn VideoDevicePool::setAutoBrightnessContrast(bool brightnesscontrast) + */ +bool VideoDevicePool::setAutoBrightnessContrast(bool brightnesscontrast) +{ + kdDebug(14010) << k_funcinfo << "VideoDevicePool::setAutoBrightnessContrast(" << brightnesscontrast << ") called." << endl; + if(m_videodevice.size()) + return m_videodevice[currentDevice()].setAutoBrightnessContrast(brightnesscontrast); + return false; +} + +/*! + \fn VideoDevicePool::getAutoColorCorrection() + */ +bool VideoDevicePool::getAutoColorCorrection() +{ + if(m_videodevice.size()) + return m_videodevice[currentDevice()].getAutoColorCorrection(); + return false; +} + +/*! + \fn VideoDevicePool::setAutoColorCorrection(bool colorcorrection) + */ +bool VideoDevicePool::setAutoColorCorrection(bool colorcorrection) +{ + kdDebug(14010) << k_funcinfo << "VideoDevicePool::setAutoColorCorrection(" << colorcorrection << ") called." << endl; + if(m_videodevice.size()) + return m_videodevice[currentDevice()].setAutoColorCorrection(colorcorrection); + return false; +} + +/*! + \fn VideoDevicePool::getIMageAsMirror() + */ +bool VideoDevicePool::getImageAsMirror() +{ + if(m_videodevice.size()) + return m_videodevice[currentDevice()].getImageAsMirror(); + return false; +} + +/*! + \fn VideoDevicePool::setImageAsMirror(bool imageasmirror) + */ +bool VideoDevicePool::setImageAsMirror(bool imageasmirror) +{ + kdDebug(14010) << k_funcinfo << "VideoDevicePool::setImageAsMirror(" << imageasmirror << ") called." << endl; + if(m_videodevice.size()) + return m_videodevice[currentDevice()].setImageAsMirror(imageasmirror); + return false; +} + +/*! + \fn VideoDevicePool::getFrame() + */ +int VideoDevicePool::getFrame() +{ +// kdDebug(14010) << k_funcinfo << "VideoDevicePool::getFrame() called." << endl; + if(m_videodevice.size()) + return m_videodevice[currentDevice()].getFrame(); + else + { + kdDebug(14010) << k_funcinfo << "VideoDevicePool::getFrame() fallback for no device." << endl; + for(unsigned int loop=0; loop < m_buffer.data.size(); loop+=3) + { + m_buffer.data[loop] = 255; + m_buffer.data[loop+1] = 0; + m_buffer.data[loop+2] = 0; + } + } +// kdDebug(14010) << k_funcinfo << "VideoDevicePool::getFrame() exited successfuly." << endl; + return EXIT_SUCCESS; +} + +/*! + \fn VideoDevicePool::getQImage(QImage *qimage) + */ +int VideoDevicePool::getImage(QImage *qimage) +{ +// kdDebug(14010) << k_funcinfo << "VideoDevicePool::getImage() called." << endl; + if(m_videodevice.size()) + return m_videodevice[currentDevice()].getImage(qimage); + else + { + kdDebug(14010) << k_funcinfo << "VideoDevicePool::getImage() fallback for no device." << endl; + qimage->create(m_buffer.width, m_buffer.height,32, QImage::IgnoreEndian); + uchar *bits=qimage->bits(); + switch(m_buffer.pixelformat) + { + case PIXELFORMAT_NONE : break; + case PIXELFORMAT_GREY : break; + case PIXELFORMAT_RGB332 : break; + case PIXELFORMAT_RGB555 : break; + case PIXELFORMAT_RGB555X: break; + case PIXELFORMAT_RGB565 : break; + case PIXELFORMAT_RGB565X: break; + case PIXELFORMAT_RGB24 : + { + kdDebug(14010) << k_funcinfo << "VideoDevicePool::getImage() fallback for no device - RGB24." << endl; + int step=0; + for(int loop=0;loop < qimage->numBytes();loop+=4) + { + bits[loop] = m_buffer.data[step]; + bits[loop+1] = m_buffer.data[step+1]; + bits[loop+2] = m_buffer.data[step+2]; + bits[loop+3] = 255; + step+=3; + } + } + break; + case PIXELFORMAT_BGR24 : break; + { + int step=0; + for(int loop=0;loop < qimage->numBytes();loop+=4) + { + bits[loop] = m_buffer.data[step+2]; + bits[loop+1] = m_buffer.data[step+1]; + bits[loop+2] = m_buffer.data[step]; + bits[loop+3] = 255; + step+=3; + } + } + break; + case PIXELFORMAT_RGB32 : memcpy(bits,&m_buffer.data[0], m_buffer.data.size()); + break; + case PIXELFORMAT_BGR32 : break; + } + } + kdDebug(14010) << k_funcinfo << "VideoDevicePool::getImage() exited successfuly." << endl; + return EXIT_SUCCESS; +} + +/*! + \fn Kopete::AV::VideoDevicePool::selectInput(int input) + */ +int VideoDevicePool::selectInput(int newinput) +{ + kdDebug(14010) << k_funcinfo << "VideoDevicePool::selectInput(" << newinput << ") called." << endl; + if(m_videodevice.size()) + return m_videodevice[currentDevice()].selectInput(newinput); + else + return 0; +} + +/*! + \fn Kopete::AV::VideoDevicePool::setInputParameters() + */ +int VideoDevicePool::setInputParameters() +{ + if(m_videodevice.size()) + return m_videodevice[currentDevice()].setInputParameters(); + else + return EXIT_FAILURE; +} + +/*! + \fn Kopete::AV::VideoDevicePool::fillDeviceKComboBox(KComboBox *combobox) + */ +int VideoDevicePool::fillDeviceKComboBox(KComboBox *combobox) +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "fillInputKComboBox: Called." << endl; + combobox->clear(); + if(m_videodevice.size()) + { + for (unsigned int loop=0; loop < m_videodevice.size(); loop++) + { + combobox->insertItem(m_videodevice[loop].m_name); + kdDebug(14010) << k_funcinfo << "DeviceKCombobox: Added device " << loop << ": " << m_videodevice[loop].m_name << endl; + } + combobox->setCurrentItem(currentDevice()); + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +/*! + \fn Kopete::AV::VideoDevicePool::fillInputKComboBox(KComboBox *combobox) + */ +int VideoDevicePool::fillInputKComboBox(KComboBox *combobox) +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "fillInputKComboBox: Called." << endl; + combobox->clear(); + if(m_videodevice.size()) + { + if(m_videodevice[currentDevice()].inputs()>0) + { + for (unsigned int loop=0; loop < m_videodevice[currentDevice()].inputs(); loop++) + { + combobox->insertItem(m_videodevice[currentDevice()].m_input[loop].name); + kdDebug(14010) << k_funcinfo << "InputKCombobox: Added input " << loop << ": " << m_videodevice[currentDevice()].m_input[loop].name << " (tuner: " << m_videodevice[currentDevice()].m_input[loop].hastuner << ")" << endl; + } + combobox->setCurrentItem(currentInput()); + return EXIT_SUCCESS; + } + } + return EXIT_FAILURE; +} + +/*! + \fn Kopete::AV::VideoDevicePool::fillStandardKComboBox(KComboBox *combobox) + */ +int VideoDevicePool::fillStandardKComboBox(KComboBox *combobox) +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "fillInputKComboBox: Called." << endl; + combobox->clear(); + if(m_videodevice.size()) + { + if(m_videodevice[currentDevice()].inputs()>0) + { + for (unsigned int loop=0; loop < 25; loop++) + { + if ( (m_videodevice[currentDevice()].m_input[currentInput()].m_standards) & (1 << loop) ) + combobox->insertItem(m_videodevice[currentDevice()].signalStandardName( 1 << loop)); +/* + case STANDARD_PAL_B1 : return V4L2_STD_PAL_B1; break; + case STANDARD_PAL_G : return V4L2_STD_PAL_G; break; + case STANDARD_PAL_H : return V4L2_STD_PAL_H; break; + case STANDARD_PAL_I : return V4L2_STD_PAL_I; break; + case STANDARD_PAL_D : return V4L2_STD_PAL_D; break; + case STANDARD_PAL_D1 : return V4L2_STD_PAL_D1; break; + case STANDARD_PAL_K : return V4L2_STD_PAL_K; break; + case STANDARD_PAL_M : return V4L2_STD_PAL_M; break; + case STANDARD_PAL_N : return V4L2_STD_PAL_N; break; + case STANDARD_PAL_Nc : return V4L2_STD_PAL_Nc; break; + case STANDARD_PAL_60 : return V4L2_STD_PAL_60; break; + case STANDARD_NTSC_M : return V4L2_STD_NTSC_M; break; + case STANDARD_NTSC_M_JP : return V4L2_STD_NTSC_M_JP; break; + case STANDARD_NTSC_443 : return V4L2_STD_NTSC; break; // Using workaround value because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case STANDARD_SECAM_B : return V4L2_STD_SECAM_B; break; + case STANDARD_SECAM_D : return V4L2_STD_SECAM_D; break; + case STANDARD_SECAM_G : return V4L2_STD_SECAM_G; break; + case STANDARD_SECAM_H : return V4L2_STD_SECAM_H; break; + case STANDARD_SECAM_K : return V4L2_STD_SECAM_K; break; + case STANDARD_SECAM_K1 : return V4L2_STD_SECAM_K1; break; + case STANDARD_SECAM_L : return V4L2_STD_SECAM_L; break; + case STANDARD_SECAM_LC : return V4L2_STD_SECAM; break; // Using workaround value because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case STANDARD_ATSC_8_VSB : return V4L2_STD_ATSC_8_VSB; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_ATSC_16_VSB : return V4L2_STD_ATSC_16_VSB; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_PAL_BG : return V4L2_STD_PAL_BG; break; + case STANDARD_PAL_DK : return V4L2_STD_PAL_DK; break; + case STANDARD_PAL : return V4L2_STD_PAL; break; + case STANDARD_NTSC : return V4L2_STD_NTSC; break; + case STANDARD_SECAM_DK : return V4L2_STD_SECAM_DK; break; + case STANDARD_SECAM : return V4L2_STD_SECAM; break; + case STANDARD_525_60 : return V4L2_STD_525_60; break; + case STANDARD_625_50 : return V4L2_STD_625_50; break; + case STANDARD_ALL : return V4L2_STD_ALL; break; + + combobox->insertItem(m_videodevice[currentDevice()].m_input[loop].name); + kdDebug(14010) << k_funcinfo << "StandardKCombobox: Added input " << loop << ": " << m_videodevice[currentDevice()].m_input[loop].name << " (tuner: " << m_videodevice[currentDevice()].m_input[loop].hastuner << ")" << endl;*/ + } + combobox->setCurrentItem(currentInput()); + return EXIT_SUCCESS; + } + } + return EXIT_FAILURE; +} + +/*! + \fn Kopete::AV::VideoDevicePool::scanDevices() + */ +int VideoDevicePool::scanDevices() +{ + /// @todo implement me + + kdDebug(14010) << k_funcinfo << "called" << endl; +#if defined(__linux__) && defined(ENABLE_AV) + QDir videodevice_dir; + const QString videodevice_dir_path=QString::fromLocal8Bit("/dev/v4l/"); + const QString videodevice_dir_filter=QString::fromLocal8Bit("video*"); + VideoDevice videodevice; + + m_videodevice.clear(); + m_modelvector.clear(); + + videodevice_dir.setPath(videodevice_dir_path); + videodevice_dir.setNameFilter(videodevice_dir_filter); + videodevice_dir.setFilter( QDir::System | QDir::NoSymLinks | QDir::Readable | QDir::Writable ); + videodevice_dir.setSorting( QDir::Name ); + + kdDebug(14010) << k_funcinfo << "Looking for devices in " << videodevice_dir_path << endl; + const QFileInfoList *list = videodevice_dir.entryInfoList(); + + if (!list) + { + kdDebug(14010) << k_funcinfo << "Found no suitable devices in " << videodevice_dir_path << endl; + QDir videodevice_dir; + const QString videodevice_dir_path=QString::fromLocal8Bit("/dev/"); + const QString videodevice_dir_filter=QString::fromLocal8Bit("video*"); + VideoDevice videodevice; + + videodevice_dir.setPath(videodevice_dir_path); + videodevice_dir.setNameFilter(videodevice_dir_filter); + videodevice_dir.setFilter( QDir::System | QDir::NoSymLinks | QDir::Readable | QDir::Writable ); + videodevice_dir.setSorting( QDir::Name ); + + kdDebug(14010) << k_funcinfo << "Looking for devices in " << videodevice_dir_path << endl; + const QFileInfoList *list = videodevice_dir.entryInfoList(); + + if (!list) + { + kdDebug(14010) << k_funcinfo << "Found no suitable devices in " << videodevice_dir_path << endl; + return EXIT_FAILURE; + } + + QFileInfoListIterator fileiterator ( *list ); + QFileInfo *fileinfo; + + kdDebug(14010) << k_funcinfo << "scanning devices in " << videodevice_dir_path << "..." << endl; + while ( (fileinfo = fileiterator.current()) != 0 ) + { + videodevice.setFileName(fileinfo->absFilePath()); + kdDebug(14010) << k_funcinfo << "Found device " << videodevice.full_filename << endl; + videodevice.open(); // It should be opened with O_NONBLOCK (it's a FIFO) but I dunno how to do it using QFile + if(videodevice.isOpen()) + { + kdDebug(14010) << k_funcinfo << "File " << videodevice.full_filename << " was opened successfuly" << endl; + +// This must be changed to proper code to handle multiple devices of the same model. It currently simply add models without proper checking + videodevice.close(); + videodevice.m_modelindex=m_modelvector.addModel (videodevice.m_model); // Adds device to the device list and sets model number + m_videodevice.push_back(videodevice); + } + ++fileiterator; + } + + + m_current_device = 0; + loadConfig(); + kdDebug(14010) << k_funcinfo << "exited successfuly" << endl; + return EXIT_SUCCESS; + + } + QFileInfoListIterator fileiterator ( *list ); + QFileInfo *fileinfo; + + kdDebug(14010) << k_funcinfo << "scanning devices in " << videodevice_dir_path << "..." << endl; + while ( (fileinfo = fileiterator.current()) != 0 ) + { + videodevice.setFileName(fileinfo->absFilePath()); + kdDebug(14010) << k_funcinfo << "Found device " << videodevice.full_filename << endl; + videodevice.open(); // It should be opened with O_NONBLOCK (it's a FIFO) but I dunno how to do it using QFile + if(videodevice.isOpen()) + { + kdDebug(14010) << k_funcinfo << "File " << videodevice.full_filename << " was opened successfuly" << endl; + +// This must be changed to proper code to handle multiple devices of the same model. It currently simply add models without proper checking + videodevice.close(); + videodevice.m_modelindex=m_modelvector.addModel (videodevice.m_model); // Adds device to the device list and sets model number + m_videodevice.push_back(videodevice); + } + ++fileiterator; + } + m_current_device = 0; + loadConfig(); +#endif + kdDebug(14010) << k_funcinfo << "exited successfuly" << endl; + return EXIT_SUCCESS; +} + +/*! + \fn Kopete::AV::VideoDevicePool::hasDevices() + */ +bool VideoDevicePool::hasDevices() +{ + /// @todo implement me + if(m_videodevice.size()) + return true; + return false; +} + +/*! + \fn Kopete::AV::VideoDevicePool::size() + */ +size_t VideoDevicePool::size() +{ + /// @todo implement me + return m_videodevice.size(); +} + +/*! + \fn Kopete::AV::VideoDevicePool::currentDevice() + */ +unsigned int VideoDevicePool::currentDevice() +{ + /// @todo implement me + return m_current_device; +} + +/*! + \fn Kopete::AV::VideoDevicePool::currentInput() + */ +int VideoDevicePool::currentInput() +{ + /// @todo implement me + return m_videodevice[currentDevice()].currentInput(); +} + +/*! + \fn Kopete::AV::VideoDevicePool::currentInput() + */ +unsigned int VideoDevicePool::inputs() +{ + /// @todo implement me + return m_videodevice[currentDevice()].inputs(); +} + +/*! + \fn Kopete::AV::VideoDevicePool::loadConfig() + */ +void VideoDevicePool::loadConfig() +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "called" << endl; + if((hasDevices())&&(m_clients==0)) + { + KConfig *config = KGlobal::config(); + config->setGroup("Video Device Settings"); + const QString currentdevice = config->readEntry("Current Device", QString::null); + kdDebug(14010) << k_funcinfo << "Current device: " << currentdevice << endl; + +// m_current_device = 0; // Must check this thing because of the fact that multiple loadConfig in other methodas can do bad things. Watch out! + + VideoDeviceVector::iterator vditerator; + for( vditerator = m_videodevice.begin(); vditerator != m_videodevice.end(); ++vditerator ) + { + const QString modelindex = QString::fromLocal8Bit ( "Model %1 Device %2") .arg ((*vditerator).m_name ) .arg ((*vditerator).m_modelindex); + if(modelindex == currentdevice) + { + m_current_device = vditerator - m_videodevice.begin(); +// kdDebug(14010) << k_funcinfo << "This place will be used to set " << modelindex << " as the current device ( " << (vditerator - m_videodevice.begin()) << " )." << endl; + } + const QString name = config->readEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Name") .arg ((*vditerator).m_name ) .arg ((*vditerator).m_modelindex)), (*vditerator).m_model); + const int currentinput = config->readNumEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Current input") .arg ((*vditerator).m_name ) .arg ((*vditerator).m_modelindex)), 0); + kdDebug(14010) << k_funcinfo << "Device name: " << name << endl; + kdDebug(14010) << k_funcinfo << "Device current input: " << currentinput << endl; + (*vditerator).selectInput(currentinput); + + for (size_t input = 0 ; input < (*vditerator).m_input.size(); input++) + { + const float brightness = config->readDoubleNumEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Brightness").arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , 0.5 ); + const float contrast = config->readDoubleNumEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Contrast") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , 0.5 ); + const float saturation = config->readDoubleNumEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Saturation").arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , 0.5 ); + const float whiteness = config->readDoubleNumEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Whiteness") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , 0.5 ); + const float hue = config->readDoubleNumEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Hue") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , 0.5 ); + const bool autobrightnesscontrast = config->readBoolEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 AutoBrightnessContrast") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , false ); + const bool autocolorcorrection = config->readBoolEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 AutoColorCorrection") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , false ); + const bool imageasmirror = config->readBoolEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 mageAsMirror") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , false ); + (*vditerator).setBrightness(brightness); + (*vditerator).setContrast(contrast); + (*vditerator).setSaturation(saturation); + (*vditerator).setHue(hue); + (*vditerator).setAutoBrightnessContrast(autobrightnesscontrast); + (*vditerator).setAutoColorCorrection(autocolorcorrection); + (*vditerator).setImageAsMirror(imageasmirror); + kdDebug(14010) << k_funcinfo << "Brightness:" << brightness << endl; + kdDebug(14010) << k_funcinfo << "Contrast :" << contrast << endl; + kdDebug(14010) << k_funcinfo << "Saturation:" << saturation << endl; + kdDebug(14010) << k_funcinfo << "Whiteness :" << whiteness << endl; + kdDebug(14010) << k_funcinfo << "Hue :" << hue << endl; + kdDebug(14010) << k_funcinfo << "AutoBrightnessContrast:" << autobrightnesscontrast << endl; + kdDebug(14010) << k_funcinfo << "AutoColorCorrection :" << autocolorcorrection << endl; + kdDebug(14010) << k_funcinfo << "ImageAsMirror :" << imageasmirror << endl; + } + } + } +} + +/*! + \fn Kopete::AV::VideoDevicePool::saveConfig() + */ +void VideoDevicePool::saveConfig() +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "called" << endl; + if(hasDevices()) + { + KConfig *config = KGlobal::config(); + config->setGroup("Video Device Settings"); + +/* if(m_modelvector.size()) + { + VideoDeviceModelPool::m_devicemodel::iterator vmiterator; + for( vmiterator = m_modelvector.begin(); vmiterator != m_modelvector.end(); ++vmiterator ) + { + kdDebug(14010) << "Device Model: " << (*vmiterator).model << endl; + kdDebug(14010) << "Device Count: " << (*vmiterator).count << endl; + } + } +*/ +// Stores what is the current video device in use + const QString currentdevice = QString::fromLocal8Bit ( "Model %1 Device %2" ) .arg(m_videodevice[m_current_device].m_model) .arg(m_videodevice[m_current_device].m_modelindex); + config->writeEntry( "Current Device", currentdevice); + + VideoDeviceVector::iterator vditerator; + for( vditerator = m_videodevice.begin(); vditerator != m_videodevice.end(); ++vditerator ) + { + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Name:" << (*vditerator).m_name << endl; + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Current input:" << (*vditerator).currentInput() << endl; + +// Stores current input for the given video device + const QString name = QString::fromLocal8Bit ( "Model %1 Device %2 Name") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex); + const QString currentinput = QString::fromLocal8Bit ( "Model %1 Device %2 Current input") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex); + config->writeEntry( name, (*vditerator).m_name); + config->writeEntry( currentinput, (*vditerator).currentInput()); + + for (size_t input = 0 ; input < (*vditerator).m_input.size(); input++) + { + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Input:" << input << ":Brightness: " << (*vditerator).m_input[input].getBrightness() << endl; + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Input:" << input << ":Contrast : " << (*vditerator).m_input[input].getContrast() << endl; + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Input:" << input << ":Saturation: " << (*vditerator).m_input[input].getSaturation() << endl; + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Input:" << input << ":Whiteness : " << (*vditerator).m_input[input].getWhiteness() << endl; + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Input:" << input << ":Hue : " << (*vditerator).m_input[input].getHue() << endl; + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Input:" << input << ":Automatic brightness / contrast: " << (*vditerator).m_input[input].getAutoBrightnessContrast() << endl; + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Input:" << input << ":Automatic color correction : " << (*vditerator).m_input[input].getAutoColorCorrection() << endl; + +// Stores configuration about each channel + const QString brightness = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Brightness") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + const QString contrast = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Contrast") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + const QString saturation = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Saturation") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + const QString whiteness = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Whiteness") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + const QString hue = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Hue") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + const QString autobrightnesscontrast = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 AutoBrightnessContrast") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + const QString autocolorcorrection = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 AutoColorCorrection") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + const QString imageasmirror = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 ImageAsMirror") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + config->writeEntry( brightness, (*vditerator).m_input[input].getBrightness()); + config->writeEntry( contrast, (*vditerator).m_input[input].getContrast()); + config->writeEntry( saturation, (*vditerator).m_input[input].getSaturation()); + config->writeEntry( whiteness, (*vditerator).m_input[input].getWhiteness()); + config->writeEntry( hue, (*vditerator).m_input[input].getHue()); + config->writeEntry( autobrightnesscontrast, (*vditerator).m_input[input].getAutoBrightnessContrast()); + config->writeEntry( autocolorcorrection, (*vditerator).m_input[input].getAutoColorCorrection()); + config->writeEntry( imageasmirror, (*vditerator).m_input[input].getImageAsMirror()); + } + } + config->sync(); + kdDebug(14010) << endl; + } +} + + + +} + +} diff --git a/kopete/libkopete/avdevice/videodevicepool.h b/kopete/libkopete/avdevice/videodevicepool.h new file mode 100644 index 00000000..1fbdb3e1 --- /dev/null +++ b/kopete/libkopete/avdevice/videodevicepool.h @@ -0,0 +1,127 @@ +/* + videodevicepool.h - Kopete Multiple Video Device handler Class + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro <taupter@gmail.com> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_AVVIDEODEVICE_H +#define KOPETE_AVVIDEODEVICE_H + +#include <qvaluevector.h> +#include <iostream> + + +#include "videoinput.h" +#include "videodevicemodelpool.h" +#include <qstring.h> +#include <qimage.h> +#include <qvaluevector.h> +#include <qmutex.h> +#include <kcombobox.h> +#include "videodevice.h" +#include "kopete_export.h" +#include <kapplication.h> +#include <kconfig.h> +#include <kglobal.h> + +namespace Kopete { + +namespace AV { + +/** +This class allows kopete to check for the existence, open, configure, test, set parameters, grab frames from and close a given video capture card using the Video4Linux API. + +@author Cláudio da Silveira Pinheiro +*/ + +typedef QValueVector<Kopete::AV::VideoDevice> VideoDeviceVector; + +class VideoDevicePoolPrivate; + +class KOPETE_EXPORT VideoDevicePool +{ +public: + static VideoDevicePool* self(); + int open(); + int open(unsigned int device); + bool isOpen(); + int getFrame(); + int width(); + int minWidth(); + int maxWidth(); + int height(); + int minHeight(); + int maxHeight(); + int setSize( int newwidth, int newheight); + int close(); + int startCapturing(); + int stopCapturing(); + int readFrame(); + int getImage(QImage *qimage); + int selectInput(int newinput); + int setInputParameters(); + int scanDevices(); + bool hasDevices(); + size_t size(); + ~VideoDevicePool(); + VideoDeviceVector m_videodevice; // Vector to be filled with found devices + VideoDeviceModelPool m_modelvector; // Vector to be filled with unique device models + int fillDeviceKComboBox(KComboBox *combobox); + int fillInputKComboBox(KComboBox *combobox); + int fillStandardKComboBox(KComboBox *combobox); + unsigned int currentDevice(); + int currentInput(); + unsigned int inputs(); + + float getBrightness(); + float setBrightness(float brightness); + float getContrast(); + float setContrast(float contrast); + float getSaturation(); + float setSaturation(float saturation); + float getWhiteness(); + float setWhiteness(float whiteness); + float getHue(); + float setHue(float hue); + + bool getAutoBrightnessContrast(); + bool setAutoBrightnessContrast(bool brightnesscontrast); + bool getAutoColorCorrection(); + bool setAutoColorCorrection(bool colorcorrection); + bool getImageAsMirror(); + bool setImageAsMirror(bool imageasmirror); + + void loadConfig(); // Load configuration parameters; + void saveConfig(); // Save configuretion parameters; + +protected: + int xioctl(int request, void *arg); + int errnoReturn(const char* s); + int showDeviceCapabilities(unsigned int device); + void guessDriver(); + unsigned int m_current_device; + struct imagebuffer m_buffer; // only used when no devices were found + + QMutex m_ready; +private: + VideoDevicePool(); + static VideoDevicePool* s_self; + static __u64 m_clients; // Number of instances +}; + +} + +} + +#endif diff --git a/kopete/libkopete/avdevice/videoinput.cpp b/kopete/libkopete/avdevice/videoinput.cpp new file mode 100644 index 00000000..5f0f8e58 --- /dev/null +++ b/kopete/libkopete/avdevice/videoinput.cpp @@ -0,0 +1,172 @@ +/* + videoinput.cpp - Kopete Video Input Class + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro <taupter@gmail.com> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "videoinput.h" + +namespace Kopete { + +namespace AV { + +VideoInput::VideoInput() +{ + kdDebug() << k_funcinfo << "Executing Video Input's constructor!!!" << endl; + m_brightness = 0.5; + m_contrast = 0.5; + m_saturation = 0.5; + m_hue = 0.5; + m_autobrightnesscontrast = false; + m_autocolorcorrection = false; +} + + +VideoInput::~VideoInput() +{ +} + +float VideoInput::getBrightness() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_brightness; +} + +float VideoInput::setBrightness(float brightness) +{ +// kdDebug() << k_funcinfo << " called." << endl; + if ( brightness > 1 ) + brightness = 1; + else + if ( brightness < 0 ) + brightness = 0; + m_brightness = brightness; + return getBrightness(); +} + +float VideoInput::getContrast() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_contrast; +} + +float VideoInput::setContrast(float contrast) +{ +// kdDebug() << k_funcinfo << " called." << endl; + if ( contrast > 1 ) + contrast = 1; + else + if ( contrast < 0 ) + contrast = 0; + m_contrast = contrast; + return getContrast(); +} + +float VideoInput::getSaturation() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_saturation; +} + +float VideoInput::setSaturation(float saturation) +{ +// kdDebug() << k_funcinfo << " called." << endl; + if ( saturation > 1 ) + saturation = 1; + else + if ( saturation < 0 ) + saturation = 0; + m_saturation = saturation; + return getSaturation(); +} + +float VideoInput::getWhiteness() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_whiteness; +} + +float VideoInput::setWhiteness(float whiteness) +{ +// kdDebug() << k_funcinfo << " called." << endl; + if ( whiteness > 1 ) + whiteness = 1; + else + if ( whiteness < 0 ) + whiteness = 0; + m_whiteness = whiteness; + return getWhiteness(); +} + +float VideoInput::getHue() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_hue; +} + +float VideoInput::setHue(float hue) +{ +// kdDebug() << k_funcinfo << " called." << endl; + if ( hue > 1 ) + hue = 1; + else + if ( hue < 0 ) + hue = 0; + m_hue = hue; + return getHue(); +} + + +bool VideoInput::getAutoBrightnessContrast() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_autobrightnesscontrast; +} + +bool VideoInput::setAutoBrightnessContrast(bool brightnesscontrast) +{ +// kdDebug() << k_funcinfo << " called." << endl; + m_autobrightnesscontrast = brightnesscontrast; + return getAutoBrightnessContrast(); +} + +bool VideoInput::getAutoColorCorrection() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_autocolorcorrection; +} + +bool VideoInput::setAutoColorCorrection(bool colorcorrection) +{ +// kdDebug() << k_funcinfo << " called." << endl; + m_autocolorcorrection = colorcorrection; + return getAutoColorCorrection(); +} + +bool VideoInput::getImageAsMirror() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_imageasmirror; +} + +bool VideoInput::setImageAsMirror(bool imageasmirror) +{ +// kdDebug() << k_funcinfo << " called." << endl; + m_imageasmirror = imageasmirror; + return getImageAsMirror(); +} + +} + +} diff --git a/kopete/libkopete/avdevice/videoinput.h b/kopete/libkopete/avdevice/videoinput.h new file mode 100644 index 00000000..3381663e --- /dev/null +++ b/kopete/libkopete/avdevice/videoinput.h @@ -0,0 +1,89 @@ +/* + videodevice.h - Kopete Video Input Class + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro <taupter@gmail.com> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#define ENABLE_AV + +#ifndef KOPETE_AVVIDEOINPUT_H +#define KOPETE_AVVIDEOINPUT_H + +#ifdef __linux__ +#include <asm/types.h> +#undef __STRICT_ANSI__ +#endif // __linux__ +#ifndef __u64 //required by videodev.h +#define __u64 unsigned long long +#endif // __u64*/ + +#include <qstring.h> +#include <kdebug.h> +#include <qvaluevector.h> +#include "kopete_export.h" + +#include "videocontrol.h" + +namespace Kopete { + +namespace AV { + +/** +@author Kopete Developers +*/ +class KOPETE_EXPORT VideoInput{ +public: + VideoInput(); + ~VideoInput(); + QString name; + int hastuner; + __u64 m_standards; + + + float getBrightness(); + float setBrightness(float brightness); + float getContrast(); + float setContrast(float contrast); + float getSaturation(); + float setSaturation(float saturation); + float getWhiteness(); + float setWhiteness(float whiteness); + float getHue(); + float setHue(float Hue); + bool getAutoBrightnessContrast(); + bool setAutoBrightnessContrast(bool brightnesscontrast); + bool getAutoColorCorrection(); + bool setAutoColorCorrection(bool colorcorrection); + bool getImageAsMirror(); + bool setImageAsMirror(bool imageasmirror); + +protected: + QValueVector<VideoControl> m_control; + float m_brightness; + float m_contrast; + float m_saturation; + float m_whiteness; + float m_hue; + bool m_autobrightnesscontrast; + bool m_autocolorcorrection; + bool m_imageasmirror; + + +}; + +} + +} + +#endif diff --git a/kopete/libkopete/clientiface.h b/kopete/libkopete/clientiface.h new file mode 100644 index 00000000..02162189 --- /dev/null +++ b/kopete/libkopete/clientiface.h @@ -0,0 +1,56 @@ +#ifndef KDED_NETWORKSTATUS_CLIENTIFACE_H +#define KDED_NETWORKSTATUS_CLIENTIFACE_H + +#include "networkstatuscommon.h" + +#include <dcopobject.h> + +class ClientIface : virtual public DCOPObject +{ +K_DCOP +k_dcop: + /** Get the set of networks that the daemon is aware of. Mostly for debug */ + virtual QStringList networks() = 0; + /** + * Get the status of the connection to the given host. + * @param host + * @return a NetworkStatus::EnumStatus representing the state of the connection to the given host + */ + virtual int status( const QString & host = QString::null ) = 0; + /** + * Request a connection to the named host, registering the application's usage of this connection + * @param host The hostname the client wants to connect to. + * @param userInitiated Indicates whether the connection is a direct result of a user action or is a background task. Used by the daemon to decide whether to create an on-demand connection. + * @return An NetworkStatus::EnumRequestResult indicating whether the request was accepted + */ + virtual int request( const QString & host, bool userInitiated ) = 0; + /** + * Indicate that a previously registered connection to the given host is no longer needed by this client + * @param host The hostname being relinquished. + */ + virtual void relinquish( const QString & host ) = 0; + /** + * Indicate that a communication failure has occured for a given host + * @param host The hostname for which the failure occurred. + * @return True indicates the caller should try again to lookup the host, as the daemon has another IP address available. + */ + virtual bool reportFailure( const QString & host ) = 0; + /** + * Utility method to check the daemon's status + */ +k_dcop_signals: + /** + * A status change occurred for the network(s) used to connect to the given host. + * @param host The host which the application has indicated it is using + * @param status The new status of the network used to reach host. + */ + void statusChange( QString host, int status ); + /** + * The network would like to shut down - any clients using this host are to finish using it immediately and call + * relinquish() when done. + * @param host The host, registered as in use by applications, which is about to be disconnected. + */ + void shutdownRequested( QString host ); +}; + +#endif diff --git a/kopete/libkopete/compat/Makefile.am b/kopete/libkopete/compat/Makefile.am new file mode 100644 index 00000000..6723bcf5 --- /dev/null +++ b/kopete/libkopete/compat/Makefile.am @@ -0,0 +1,8 @@ +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) +noinst_LTLIBRARIES = libkopetecompat.la + +libkopetecompat_la_SOURCES = kpixmapregionselectordialog.cpp kpixmapregionselectorwidget.cpp +libkopetecompat_la_LDFLAGS = -no-undefined $(all_libraries) +libkopetecompat_la_LIBADD = $(LIB_KDEUI) $(LIB_KDECORE) + diff --git a/kopete/libkopete/compat/kpixmapregionselectordialog.cpp b/kopete/libkopete/compat/kpixmapregionselectordialog.cpp new file mode 100644 index 00000000..ee9d185e --- /dev/null +++ b/kopete/libkopete/compat/kpixmapregionselectordialog.cpp @@ -0,0 +1,127 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Antonio Larrosa <larrosa@kde.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kpixmapregionselectordialog.h" +#include <kdialogbase.h> +#include <qdialog.h> +#include <qdesktopwidget.h> +#include <klocale.h> +#include <kdialog.h> + +KPixmapRegionSelectorDialog::KPixmapRegionSelectorDialog(QWidget *parent, + const char *name, bool modal ) : KDialogBase(parent, name, modal, i18n("Select Region of Image"), Help|Ok|Cancel, Ok, true ) +{ + QVBox *vbox=new QVBox(this); + new QLabel(i18n("Please click and drag on the image to select the region of interest:"), vbox); + m_pixmapSelectorWidget= new KPixmapRegionSelectorWidget(vbox); + + vbox->setSpacing( KDialog::spacingHint() ); + + setMainWidget(vbox); +} + +KPixmapRegionSelectorDialog::~KPixmapRegionSelectorDialog() +{ +} + +QRect KPixmapRegionSelectorDialog::getSelectedRegion(const QPixmap &pixmap, QWidget *parent ) +{ + KPixmapRegionSelectorDialog dialog(parent); + + dialog.pixmapRegionSelectorWidget()->setPixmap(pixmap); + + QDesktopWidget desktopWidget; + QRect screen=desktopWidget.availableGeometry(); + dialog.pixmapRegionSelectorWidget()->setMaximumWidgetSize( + (int)(screen.width()*4.0/5), (int)(screen.height()*4.0/5)); + + int result = dialog.exec(); + + QRect rect; + + if ( result == QDialog::Accepted ) + rect = dialog.pixmapRegionSelectorWidget()->unzoomedSelectedRegion(); + + return rect; +} + +QRect KPixmapRegionSelectorDialog::getSelectedRegion(const QPixmap &pixmap, int aspectRatioWidth, int aspectRatioHeight, QWidget *parent ) +{ + KPixmapRegionSelectorDialog dialog(parent); + + dialog.pixmapRegionSelectorWidget()->setPixmap(pixmap); + dialog.pixmapRegionSelectorWidget()->setSelectionAspectRatio(aspectRatioWidth,aspectRatioHeight); + + QDesktopWidget desktopWidget; + QRect screen=desktopWidget.availableGeometry(); + dialog.pixmapRegionSelectorWidget()->setMaximumWidgetSize( + (int)(screen.width()*4.0/5), (int)(screen.height()*4.0/5)); + + int result = dialog.exec(); + + QRect rect; + + if ( result == QDialog::Accepted ) + rect = dialog.pixmapRegionSelectorWidget()->unzoomedSelectedRegion(); + + return rect; +} + +QImage KPixmapRegionSelectorDialog::getSelectedImage(const QPixmap &pixmap, QWidget *parent ) +{ + KPixmapRegionSelectorDialog dialog(parent); + + dialog.pixmapRegionSelectorWidget()->setPixmap(pixmap); + + QDesktopWidget desktopWidget; + QRect screen=desktopWidget.availableGeometry(); + dialog.pixmapRegionSelectorWidget()->setMaximumWidgetSize( + (int)(screen.width()*4.0/5), (int)(screen.height()*4.0/5)); + int result = dialog.exec(); + + QImage image; + + if ( result == QDialog::Accepted ) + image = dialog.pixmapRegionSelectorWidget()->selectedImage(); + + return image; +} + +QImage KPixmapRegionSelectorDialog::getSelectedImage(const QPixmap &pixmap, int aspectRatioWidth, int aspectRatioHeight, QWidget *parent ) +{ + KPixmapRegionSelectorDialog dialog(parent); + + dialog.pixmapRegionSelectorWidget()->setPixmap(pixmap); + dialog.pixmapRegionSelectorWidget()->setSelectionAspectRatio(aspectRatioWidth,aspectRatioHeight); + + QDesktopWidget desktopWidget; + QRect screen=desktopWidget.availableGeometry(); + dialog.pixmapRegionSelectorWidget()->setMaximumWidgetSize( + (int)(screen.width()*4.0/5), (int)(screen.height()*4.0/5)); + + int result = dialog.exec(); + + QImage image; + + if ( result == QDialog::Accepted ) + image = dialog.pixmapRegionSelectorWidget()->selectedImage(); + + return image; +} + diff --git a/kopete/libkopete/compat/kpixmapregionselectordialog.h b/kopete/libkopete/compat/kpixmapregionselectordialog.h new file mode 100644 index 00000000..1c15067e --- /dev/null +++ b/kopete/libkopete/compat/kpixmapregionselectordialog.h @@ -0,0 +1,107 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Antonio Larrosa <larrosa@kde.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KPIXMAPREGIONSELECTORDIALOG_H__ +#define __KPIXMAPREGIONSELECTORDIALOG_H__ + +#include <qimage.h> + +#include <kdialogbase.h> +#include <kpixmapregionselectorwidget.h> + +/** + * A dialog that uses a KPixmapRegionSelectorWidget to allow the user + * to select a region of an image. If you want to use special features + * like forcing the selected area to have a fixed aspect ratio, you can use + * @see pixmapRegionSelectorWidget() to get the pointer to the + * KPixmapRegionSelectorWidget object and set the desired options there. + * + * There are some convenience methods that allow to easily show a dialog + * for the user to select a region of an image, and just care about the selected + * image. + * + * @author Antonio Larrosa <larrosa@kde.org> + * @since 3.4 + */ +class KOPETE_EXPORT KPixmapRegionSelectorDialog : public KDialogBase +{ +public: + /** + * The constructor of an empty KPixmapRegionSelectorDialog, you have to call + * later the setPixmap method of the KPixmapRegionSelectorWidget widget of + * the new object. + */ + KPixmapRegionSelectorDialog(QWidget *parent=0L, const char *name=0L, + bool modal = false ); + /** + * The destructor of the dialog + */ + ~KPixmapRegionSelectorDialog(); + + /** + * @returns the KPixmapRegionSelectorWidget widget so that additional + * parameters can be set by using it. + */ + KPixmapRegionSelectorWidget *pixmapRegionSelectorWidget() const + { return m_pixmapSelectorWidget; }; + + /** + * Creates a modal dialog, lets the user to select a region of the @p pixmap + * and returns when the dialog is closed. + * + * @returns the selected rectangle, or an invalid rectangle if the user + * pressed the Cancel button. + */ + static QRect getSelectedRegion(const QPixmap &pixmap, QWidget *parent = 0L ); + + /** + * Creates a modal dialog, lets the user to select a region of the @p pixmap + * with the same aspect ratio than @p aspectRatioWidth x @p aspectRatioHeight + * and returns when the dialog is closed. + * + * @returns the selected rectangle, or an invalid rectangle if the user + * pressed the Cancel button. + */ + static QRect getSelectedRegion(const QPixmap &pixmap, int aspectRatioWidth, int aspectRatioHeight, QWidget *parent = 0L ); + + /** + * Creates a modal dialog, lets the user to select a region of the @p pixmap + * and returns when the dialog is closed. + * + * @returns the selected image, or an invalid image if the user + * pressed the Cancel button. + */ + static QImage getSelectedImage(const QPixmap &pixmap, QWidget *parent = 0L ); + + /** + * Creates a modal dialog, lets the user to select a region of the @p pixmap + * with the same aspect ratio than @p aspectRatioWidth x @p aspectRatioHeight + * and returns when the dialog is closed. + * + * @returns the selected image, or an invalid image if the user + * pressed the Cancel button. + */ + static QImage getSelectedImage(const QPixmap &pixmap, int aspectRatioWidth, int aspectRatioHeight, QWidget *parent = 0L ); + +protected: + KPixmapRegionSelectorWidget *m_pixmapSelectorWidget; +}; + + +#endif diff --git a/kopete/libkopete/compat/kpixmapregionselectorwidget.cpp b/kopete/libkopete/compat/kpixmapregionselectorwidget.cpp new file mode 100644 index 00000000..da2be5f9 --- /dev/null +++ b/kopete/libkopete/compat/kpixmapregionselectorwidget.cpp @@ -0,0 +1,450 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Antonio Larrosa <larrosa@kde.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/* NOTE: There are two copies of this .h and the .cpp file, with subtle differences. + * One copy is in kdelibs/kdeui, and the other copy is in kdepim/libkdepim + * This is because kdepim has to remain backwards compatible. Any changes + * to either file should be made to the other. + */ + +#include "kpixmapregionselectorwidget.h" +#include <qpainter.h> +#include <qcolor.h> +#include <qimage.h> +#include <qlayout.h> +#include <kimageeffect.h> +#include <kdebug.h> +#include <klocale.h> +#include <kpopupmenu.h> +#include <kaction.h> +#include <stdlib.h> +#include <qcursor.h> +#include <qapplication.h> + +KPixmapRegionSelectorWidget::KPixmapRegionSelectorWidget( QWidget *parent, + const char *name) : QWidget( parent, name) +{ + QHBoxLayout * hboxLayout=new QHBoxLayout( this ); + + hboxLayout->addStretch(); + QVBoxLayout * vboxLayout=new QVBoxLayout( hboxLayout ); + + vboxLayout->addStretch(); + m_label = new QLabel(this, "pixmapHolder"); + m_label->setBackgroundMode( Qt::NoBackground ); + m_label->installEventFilter( this ); + + vboxLayout->addWidget(m_label); + vboxLayout->addStretch(); + + hboxLayout->addStretch(); + + m_forcedAspectRatio=0; + + m_zoomFactor=1.0; +} + +KPixmapRegionSelectorWidget::~KPixmapRegionSelectorWidget() +{ +} + +void KPixmapRegionSelectorWidget::setPixmap( const QPixmap &pixmap ) +{ + Q_ASSERT(!pixmap.isNull()); //This class isn't designed to deal with null pixmaps. + m_originalPixmap = pixmap; + m_unzoomedPixmap = pixmap; + m_label->setPixmap( pixmap ); + resetSelection(); +} + +void KPixmapRegionSelectorWidget::resetSelection() +{ + m_selectedRegion = m_originalPixmap.rect(); + updatePixmap(); +} + +QRect KPixmapRegionSelectorWidget::selectedRegion() const +{ + return m_selectedRegion; +} + +void KPixmapRegionSelectorWidget::setSelectedRegion(const QRect &rect) +{ + if (!rect.isValid()) resetSelection(); + else + { + m_selectedRegion=rect; + updatePixmap(); + + QRect r=unzoomedSelectedRegion(); + } +} + +void KPixmapRegionSelectorWidget::updatePixmap() +{ + Q_ASSERT(!m_originalPixmap.isNull()); if(m_originalPixmap.isNull()) { m_label->setPixmap(m_originalPixmap); return; } + if (m_selectedRegion.width()>m_originalPixmap.width()) m_selectedRegion.setWidth( m_originalPixmap.width() ); + if (m_selectedRegion.height()>m_originalPixmap.height()) m_selectedRegion.setHeight( m_originalPixmap.height() ); + + QPainter painter; + if (m_linedPixmap.isNull()) + { + m_linedPixmap = m_originalPixmap; + + painter.begin(&m_linedPixmap); + painter.setRasterOp( Qt::XorROP ); + painter.fillRect(0,0,m_linedPixmap.width(), m_linedPixmap.height(), + QBrush( QColor(255,255,255), Qt::BDiagPattern) ); + painter.end(); + + QImage image=m_linedPixmap.convertToImage(); + image=KImageEffect::fade(image, (float)0.4, QColor(0,0,0)); + m_linedPixmap.convertFromImage(image); + } + + QPixmap pixmap = m_linedPixmap; + + painter.begin(&pixmap); + painter.drawPixmap( m_selectedRegion.topLeft(), + m_originalPixmap, m_selectedRegion ); + + painter.setPen( QColor(255,255,255) ); + painter.setRasterOp( Qt::XorROP ); + + painter.drawRect( m_selectedRegion ); + + painter.end(); + + m_label->setPixmap(pixmap); +} + + +KPopupMenu *KPixmapRegionSelectorWidget::createPopupMenu() +{ + KPopupMenu *popup=new KPopupMenu(this, "PixmapRegionSelectorPopup"); + popup->insertTitle(i18n("Image Operations")); + + KAction *action = new KAction(i18n("&Rotate Clockwise"), "rotate_cw", + 0, this, SLOT(rotateClockwise()), + popup, "rotateclockwise"); + action->plug(popup); + + action = new KAction(i18n("Rotate &Counterclockwise"), "rotate_ccw", + 0, this, SLOT(rotateCounterclockwise()), + popup, "rotatecounterclockwise"); + action->plug(popup); + +/* + I wonder if it would be appropiate to have here an "Open with..." option to + edit the image (antlarr) +*/ + return popup; +} + +void KPixmapRegionSelectorWidget::rotate(KImageEffect::RotateDirection direction) +{ + int w=m_originalPixmap.width(); + int h=m_originalPixmap.height(); + QImage img=m_unzoomedPixmap.convertToImage(); + img= KImageEffect::rotate(img, direction); + m_unzoomedPixmap.convertFromImage(img); + + img=m_originalPixmap.convertToImage(); + img= KImageEffect::rotate(img, direction); + m_originalPixmap.convertFromImage(img); + + m_linedPixmap=QPixmap(); + + if (m_forcedAspectRatio>0 && m_forcedAspectRatio!=1) + resetSelection(); + else + { + switch (direction) + { + case ( KImageEffect::Rotate90 ): + { + int x=h-m_selectedRegion.y()-m_selectedRegion.height(); + int y=m_selectedRegion.x(); + m_selectedRegion.setRect(x, y, m_selectedRegion.height(), m_selectedRegion.width() ); + updatePixmap(); + } break; + case ( KImageEffect::Rotate270 ): + { + int x=m_selectedRegion.y(); + int y=w-m_selectedRegion.x()-m_selectedRegion.width(); + m_selectedRegion.setRect(x, y, m_selectedRegion.height(), m_selectedRegion.width() ); + updatePixmap(); + } break; + default: resetSelection(); + } + } +} + +void KPixmapRegionSelectorWidget::rotateClockwise() +{ + rotate(KImageEffect::Rotate90); +} + +void KPixmapRegionSelectorWidget::rotateCounterclockwise() +{ + rotate(KImageEffect::Rotate270); +} + +bool KPixmapRegionSelectorWidget::eventFilter(QObject *obj, QEvent *ev) +{ + if ( ev->type() == QEvent::MouseButtonPress ) + { + QMouseEvent *mev= (QMouseEvent *)(ev); + //kdDebug() << QString("click at %1,%2").arg( mev->x() ).arg( mev->y() ) << endl; + + if ( mev->button() == RightButton ) + { + KPopupMenu *popup = createPopupMenu( ); + popup->exec( mev->globalPos() ); + delete popup; + return TRUE; + }; + + QCursor cursor; + + if ( m_selectedRegion.contains( mev->pos() ) + && m_selectedRegion!=m_originalPixmap.rect() ) + { + m_state=Moving; + cursor.setShape( Qt::SizeAllCursor ); + } + else + { + m_state=Resizing; + cursor.setShape( Qt::CrossCursor ); + } + QApplication::setOverrideCursor(cursor); + + m_tempFirstClick=mev->pos(); + + + return TRUE; + } + + if ( ev->type() == QEvent::MouseMove ) + { + QMouseEvent *mev= (QMouseEvent *)(ev); + + //kdDebug() << QString("move to %1,%2").arg( mev->x() ).arg( mev->y() ) << endl; + + if ( m_state == Resizing ) + { + setSelectedRegion ( + calcSelectionRectangle( m_tempFirstClick, mev->pos() ) ); + } + else if (m_state == Moving ) + { + int mevx = mev->x(); + int mevy = mev->y(); + bool mouseOutside=false; + if ( mevx < 0 ) + { + m_selectedRegion.moveBy(-m_selectedRegion.x(),0); + mouseOutside=true; + } + else if ( mevx > m_originalPixmap.width() ) + { + m_selectedRegion.moveBy(m_originalPixmap.width()-m_selectedRegion.width()-m_selectedRegion.x(),0); + mouseOutside=true; + } + if ( mevy < 0 ) + { + m_selectedRegion.moveBy(0,-m_selectedRegion.y()); + mouseOutside=true; + } + else if ( mevy > m_originalPixmap.height() ) + { + m_selectedRegion.moveBy(0,m_originalPixmap.height()-m_selectedRegion.height()-m_selectedRegion.y()); + mouseOutside=true; + } + if (mouseOutside) { updatePixmap(); return TRUE; }; + + m_selectedRegion.moveBy( mev->x()-m_tempFirstClick.x(), + mev->y()-m_tempFirstClick.y() ); + + // Check that the region has not fallen outside the image + if (m_selectedRegion.x() < 0) + m_selectedRegion.moveBy(-m_selectedRegion.x(),0); + else if (m_selectedRegion.right() > m_originalPixmap.width()) + m_selectedRegion.moveBy(-(m_selectedRegion.right()-m_originalPixmap.width()),0); + + if (m_selectedRegion.y() < 0) + m_selectedRegion.moveBy(0,-m_selectedRegion.y()); + else if (m_selectedRegion.bottom() > m_originalPixmap.height()) + m_selectedRegion.moveBy(0,-(m_selectedRegion.bottom()-m_originalPixmap.height())); + + m_tempFirstClick=mev->pos(); + updatePixmap(); + } + return TRUE; + } + + if ( ev->type() == QEvent::MouseButtonRelease ) + { + QMouseEvent *mev= (QMouseEvent *)(ev); + + if ( m_state == Resizing && mev->pos() == m_tempFirstClick) + resetSelection(); + + m_state=None; + QApplication::restoreOverrideCursor(); + + return TRUE; + } + + QWidget::eventFilter(obj, ev); + return FALSE; +} + +QRect KPixmapRegionSelectorWidget::calcSelectionRectangle( const QPoint & startPoint, const QPoint & _endPoint ) +{ + QPoint endPoint = _endPoint; + if ( endPoint.x() < 0 ) endPoint.setX(0); + else if ( endPoint.x() > m_originalPixmap.width() ) endPoint.setX(m_originalPixmap.width()); + if ( endPoint.y() < 0 ) endPoint.setY(0); + else if ( endPoint.y() > m_originalPixmap.height() ) endPoint.setY(m_originalPixmap.height()); + int w=abs(startPoint.x()-endPoint.x()); + int h=abs(startPoint.y()-endPoint.y()); + + if (m_forcedAspectRatio>0) + { + double aspectRatio=w/double(h); + + if (aspectRatio>m_forcedAspectRatio) + h=(int)(w/m_forcedAspectRatio); + else + w=(int)(h*m_forcedAspectRatio); + } + + int x,y; + if ( startPoint.x() < endPoint.x() ) + x=startPoint.x(); + else + x=startPoint.x()-w; + if ( startPoint.y() < endPoint.y() ) + y=startPoint.y(); + else + y=startPoint.y()-h; + + if (x<0) + { + w+=x; + x=0; + h=(int)(w/m_forcedAspectRatio); + + if ( startPoint.y() > endPoint.y() ) + y=startPoint.y()-h; + } + else if (x+w>m_originalPixmap.width()) + { + w=m_originalPixmap.width()-x; + h=(int)(w/m_forcedAspectRatio); + + if ( startPoint.y() > endPoint.y() ) + y=startPoint.y()-h; + } + if (y<0) + { + h+=y; + y=0; + w=(int)(h*m_forcedAspectRatio); + + if ( startPoint.x() > endPoint.x() ) + x=startPoint.x()-w; + } + else if (y+h>m_originalPixmap.height()) + { + h=m_originalPixmap.height()-y; + w=(int)(h*m_forcedAspectRatio); + + if ( startPoint.x() > endPoint.x() ) + x=startPoint.x()-w; + } + + return QRect(x,y,w,h); +} + +QRect KPixmapRegionSelectorWidget::unzoomedSelectedRegion() const +{ + return QRect((int)(m_selectedRegion.x()/m_zoomFactor), + (int)(m_selectedRegion.y()/m_zoomFactor), + (int)(m_selectedRegion.width()/m_zoomFactor), + (int)(m_selectedRegion.height()/m_zoomFactor)); +} + +QImage KPixmapRegionSelectorWidget::selectedImage() const +{ + QImage origImage=m_unzoomedPixmap.convertToImage(); + return origImage.copy(unzoomedSelectedRegion()); +} + +void KPixmapRegionSelectorWidget::setSelectionAspectRatio(int width, int height) +{ + m_forcedAspectRatio=width/double(height); +} + +void KPixmapRegionSelectorWidget::setFreeSelectionAspectRatio() +{ + m_forcedAspectRatio=0; +} + +void KPixmapRegionSelectorWidget::setMaximumWidgetSize(int width, int height) +{ + m_maxWidth=width; + m_maxHeight=height; + + m_originalPixmap=m_unzoomedPixmap; + if (m_selectedRegion == m_originalPixmap.rect()) m_selectedRegion=QRect(); + +// kdDebug() << QString(" original Pixmap :") << m_originalPixmap.rect() << endl; +// kdDebug() << QString(" unzoomed Pixmap : %1 x %2 ").arg(m_unzoomedPixmap.width()).arg(m_unzoomedPixmap.height()) << endl; + + if ( !m_originalPixmap.isNull() && + ( m_originalPixmap.width() > m_maxWidth || + m_originalPixmap.height() > m_maxHeight ) ) + { + /* We have to resize the pixmap to get it complete on the screen */ + QImage image=m_originalPixmap.convertToImage(); + m_originalPixmap.convertFromImage( image.smoothScale( width, height, QImage::ScaleMin ) ); + double oldZoomFactor = m_zoomFactor; + m_zoomFactor=m_originalPixmap.width()/(double)m_unzoomedPixmap.width(); + + if (m_selectedRegion.isValid()) + { + m_selectedRegion= + QRect((int)(m_selectedRegion.x()*m_zoomFactor/oldZoomFactor), + (int)(m_selectedRegion.y()*m_zoomFactor/oldZoomFactor), + (int)(m_selectedRegion.width()*m_zoomFactor/oldZoomFactor), + (int)(m_selectedRegion.height()*m_zoomFactor/oldZoomFactor) ); + } + } + + if (!m_selectedRegion.isValid()) m_selectedRegion = m_originalPixmap.rect(); + + m_linedPixmap=QPixmap(); + updatePixmap(); + resize(m_label->width(), m_label->height()); +} + +#include "kpixmapregionselectorwidget.moc" diff --git a/kopete/libkopete/compat/kpixmapregionselectorwidget.h b/kopete/libkopete/compat/kpixmapregionselectorwidget.h new file mode 100644 index 00000000..a4a9cfcf --- /dev/null +++ b/kopete/libkopete/compat/kpixmapregionselectorwidget.h @@ -0,0 +1,170 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Antonio Larrosa <larrosa@kde.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __KPIXMAPREGIONSELECTORWIDGET_H__ +#define __KPIXMAPREGIONSELECTORWIDGET_H__ +#include <qvbox.h> +#include <qpixmap.h> +#include <qrect.h> +#include <qlabel.h> +#include <kimageeffect.h> + +class KPopupMenu; + +#include "kopete_export.h" + +/** + * KPixmapRegionSelectorWidget is a widget that shows a picture and provides the + * user with a friendly way to select a rectangular subregion of the pixmap. + * + * NOTE: There are two copies of this .h and the .cpp file, with subtle differences. + * One copy is in kdelibs/kdeui, and the other copy is in kdepim/libkdepim + * This is because kdepim has to remain backwards compatible. Any changes + * to either file should be made to the other. + * + * @author Antonio Larrosa <larrosa@kde.org> + * @since 3.4 + */ +class KOPETE_EXPORT KPixmapRegionSelectorWidget : public QWidget +{ + Q_OBJECT +public: + /** + * Constructor for a KPixmapRegionSelectorWidget. + */ + KPixmapRegionSelectorWidget( QWidget *parent = 0L, const char *name=0L); + + /** + * Destructor for a KPixmapRegionSelectorWidget + */ + ~KPixmapRegionSelectorWidget(); + + /** + * Sets the pixmap which will be shown for the user to select a region from. + * @param pixmap The pixmap. Must be non-null. + * + */ + void setPixmap( const QPixmap &pixmap ); + + /** + * @return the original whole pixmap that we're using in this widget as the + * pixmap the user is selecting a region from. + */ + QPixmap pixmap() const { return m_unzoomedPixmap; }; + + /** + * Sets the selected region to be @p rect (in zoomed pixmap coordinates) + */ + void setSelectedRegion(const QRect &rect); + + /** + * Returns the selected region ( in zoomed pixmap coordinates ) + */ + QRect selectedRegion() const; + + /** + * Returns the selected region ( in unzoomed, original pixmap coordinates ) + */ + QRect unzoomedSelectedRegion() const; + + /** + * Resets the selection to use the whole image + */ + void resetSelection(); + + /** + * @returns a QImage object with just the region the user selected from the + * image + */ + QImage selectedImage() const; + + /** + * Sets the aspect ration that the selected subimage should have. The way to + * select it, is specifying an example valid @p width and @p height. + * @see setFreeSelectionAspectRatio() + */ + void setSelectionAspectRatio(int width, int height); + + /** + * Allows the user to do a selection which has any aspect ratio. This is + * the default. + * @see setSelectionAspectRatio() + */ + void setFreeSelectionAspectRatio(); + + /** + * Sets the maximum size for the widget. If the image is larger than this + * (either horizontally or vertically), it's scaled to adjust to the maximum + * size (preserving the aspect ratio) + */ + void setMaximumWidgetSize( int width, int height ); + + /** + * Rotates the image as specified by the @p direction parameter, also tries + * to rotate the selected region so that it doesn't change, as long as the + * forced aspect ratio setting is respected, in other case, the selected region + * is resetted. + */ + void rotate(KImageEffect::RotateDirection direction); + +public slots: + /** + * Rotates the current image 90º clockwise + */ + void rotateClockwise(); + /** + * Rotates the current image 90º counterclockwise + */ + void rotateCounterclockwise(); + +protected: + /** + * Creates a KPopupMenu with the menu that appears when clicking with the right button on the label + */ + virtual KPopupMenu *createPopupMenu(); + +private: + bool eventFilter(QObject *obj, QEvent *ev); + + /** + * Recalculates the pixmap that is shown based on the current selected area, + * the original image, etc. + */ + void updatePixmap(); + + QRect calcSelectionRectangle( const QPoint &startPoint, const QPoint & endPoint ); + + enum CursorState { None=0, Resizing, Moving }; + CursorState m_state; + + QPixmap m_unzoomedPixmap; + QPixmap m_originalPixmap; + QPixmap m_linedPixmap; + QRect m_selectedRegion; + QLabel *m_label; + + QPoint m_tempFirstClick; + double m_forcedAspectRatio; + + int m_maxWidth, m_maxHeight; + double m_zoomFactor; +}; + +#endif + diff --git a/kopete/libkopete/configure.in.in b/kopete/libkopete/configure.in.in new file mode 100644 index 00000000..f9d1fb76 --- /dev/null +++ b/kopete/libkopete/configure.in.in @@ -0,0 +1,32 @@ +# -- Check for XScreenSaver ----------------------------------------- +AC_CHECK_HEADERS(tgmath.h)xss_save_ldflags="$LDFLAGS" +LDFLAGS="$X_LDFLAGS" + +LIB_XSS= + +KDE_CHECK_HEADER(X11/extensions/scrnsaver.h, + [ + AC_CHECK_LIB(Xext,XScreenSaverQueryInfo, + [ + AC_DEFINE(HAVE_XSCREENSAVER, 1, [Define if you have the XScreenSaver extension]) + LIB_XSS="-lXext" + ], + [ + ld_shared_flag= + KDE_CHECK_COMPILER_FLAG(shared, [ld_shared_flag="-shared"]) + AC_CHECK_LIB(Xss,XScreenSaverQueryInfo, + [ + AC_DEFINE(HAVE_XSCREENSAVER, 1, [Define if you have the XScreenSaver extension]) + LIB_XSS="-lXss" + ], + [], + [ $ld_shared_flag $X_PRE_LIBS -lXext -lX11 $X_EXTRA_LIBS ]) + ], + [ $X_PRE_LIBS -lX11 $X_EXTRA_LIBS ]) + ], [], + [ + #include <X11/Xlib.h> + ] ) + +AC_SUBST(LIB_XSS) +LDFLAGS="$xss_save_ldflags" diff --git a/kopete/libkopete/connectionmanager.cpp b/kopete/libkopete/connectionmanager.cpp new file mode 100644 index 00000000..b2dd7825 --- /dev/null +++ b/kopete/libkopete/connectionmanager.cpp @@ -0,0 +1,153 @@ +#include <kapplication.h> +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kstaticdeleter.h> + +#include "clientiface_stub.h" +#include "networkstatuscommon.h" + +#include "connectionmanager.h" + +// ConnectionManager's private parts +class ConnectionManagerPrivate +{ + public: + // this holds the currently active state + ConnectionManager::State m_state; + ClientIface_stub * m_stub; + bool m_userInitiatedOnly; +}; + +// Connection manager itself +ConnectionManager::ConnectionManager( QObject * parent, const char * name ) : DCOPObject( "ConnectionManager" ),QObject( parent, name ) +{ + d = new ConnectionManagerPrivate; + + d->m_stub = new ClientIface_stub( kapp->dcopClient(), "kded", "networkstatus" ); + + connectDCOPSignal( "kded", "networkstatus", "statusChange(QString,int)", "slotStatusChanged(QString,int)", false ); + d->m_userInitiatedOnly = false; + initialise(); +} + +ConnectionManager *ConnectionManager::s_self = 0L; + +ConnectionManager *ConnectionManager::self() +{ + static KStaticDeleter<ConnectionManager> deleter; + if(!s_self) + deleter.setObject( s_self, new ConnectionManager( 0, "connection_manager" ) ); + return s_self; +} + +void ConnectionManager::initialise() +{ + // determine initial state and set the state object accordingly. + d->m_state = Inactive; + updateStatus(); +} + +void ConnectionManager::updateStatus() +{ + NetworkStatus::EnumStatus daemonStatus = (NetworkStatus::EnumStatus)d->m_stub->status( QString::null ); + kdDebug() << k_funcinfo << endl; + switch ( daemonStatus ) + { + case NetworkStatus::Offline: + case NetworkStatus::OfflineFailed: + case NetworkStatus::OfflineDisconnected: + case NetworkStatus::ShuttingDown: + if ( d->m_state == Online ) + { + kdDebug() << "STATE IS PENDING" << endl; + d->m_state = Pending; + } + else + { + kdDebug() << "STATE IS OFFLINE" << endl; + d->m_state = Offline; + } + break; + case NetworkStatus::Establishing: + case NetworkStatus::Online: + kdDebug() << "STATE IS ONLINE" << endl; + d->m_state = Online; + break; + case NetworkStatus::NoNetworks: + case NetworkStatus::Unreachable: + kdDebug() << "STATE IS INACTIVE" << endl; + d->m_state = Inactive; + break; + } +} + +ConnectionManager::~ConnectionManager() +{ + delete d; +} + +NetworkStatus::EnumStatus ConnectionManager::status( const QString & host ) +{ + // need also to check that the daemon hasn't died + updateStatus(); + if ( d->m_state == Pending ) + return NetworkStatus::Offline; + if ( d->m_state == Online ) + return NetworkStatus::Online; + if ( d->m_state == Offline ) + return NetworkStatus::Offline; + return NetworkStatus::NoNetworks; +} + +NetworkStatus::EnumRequestResult ConnectionManager::requestConnection( QWidget * mainWidget, const QString & host, bool userInitiated ) +{ + kdDebug() << k_funcinfo << endl; + NetworkStatus::EnumRequestResult result; + // if offline and the user has previously indicated they didn't want any new connections, suppress it + if ( d->m_state == Offline && !userInitiated && d->m_userInitiatedOnly ) + result = NetworkStatus::UserRefused; + // if offline, ask the user whether this connection should be allowed + if ( d->m_state == Offline ) + { + if ( askToConnect( mainWidget ) ) + //result = NetworkStatus::Connected; + result = (NetworkStatus::EnumRequestResult)d->m_stub->request( host, userInitiated ); + else + result = NetworkStatus::UserRefused; + } + // otherwise, just ask for the connection + else + result = (NetworkStatus::EnumRequestResult)d->m_stub->request( host, userInitiated ); + + return result; +} + +void ConnectionManager::relinquishConnection( const QString & host ) +{ + d->m_stub->relinquish( host ); +} + +void ConnectionManager::slotStatusChanged( QString host, int status ) +{ + kdDebug() << k_funcinfo << endl; + updateStatus(); + // reset user initiated only flag if we are now online + if ( d->m_state == Online ) + d->m_userInitiatedOnly = false; + + emit statusChanged( host, (NetworkStatus::EnumStatus)status ); +} + +bool ConnectionManager::askToConnect( QWidget * mainWidget ) +{ + i18n( "A network connection was disconnected. The application is now in offline mode. Do you want the application to resume network operations when the network is available again?" ); + i18n( "This application is currently in offline mode. Do you want to connect?" ); + return ( KMessageBox::questionYesNo( mainWidget, + i18n("This application is currently in offline mode. Do you want to connect in order to carry out this operation?"), + i18n("Leave Offline Mode?"), + i18n("Connect"), i18n("Stay Offline"), + QString::fromLatin1("OfflineModeAlwaysGoOnline") ) == KMessageBox::Yes ); +} + +#include "connectionmanager.moc" diff --git a/kopete/libkopete/connectionmanager.h b/kopete/libkopete/connectionmanager.h new file mode 100644 index 00000000..b78df8d4 --- /dev/null +++ b/kopete/libkopete/connectionmanager.h @@ -0,0 +1,58 @@ +/* + connectionmanager.h - Provides the client side interface to the kde networkstatus daemon + + Copyright (c) 2004 by Will Stephenson <lists@stevello.free-online.co.uk> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KDE_CONNECTION_MANAGER_H +#define KDE_CONNECTION_MANAGER_H + +#include <dcopobject.h> + +#include "kopete_export.h" +#include "networkstatuscommon.h" + +class ConnectionManagerPrivate; + +class KOPETE_EXPORT ConnectionManager : public QObject, virtual public DCOPObject +{ + Q_OBJECT + K_DCOP + public: + static ConnectionManager* self(); + enum State { Inactive, Online, Offline, Pending }; + virtual ~ConnectionManager(); + NetworkStatus::EnumStatus status( const QString & host ); + // check if a hostname is available. Ask user if offline. Request host + NetworkStatus::EnumRequestResult requestConnection( QWidget* mainWidget, const QString & host, bool userInitiated ); + // method to relinquish a connection + void relinquishConnection( const QString & host ); + signals: + // signal that the network for a hostname is up/down + void statusChanged( const QString & host, NetworkStatus::EnumStatus status ); + protected: + // sets up internal state + void initialise(); + // reread the desktop status from the daemon and update internal state + void updateStatus(); + // ask if the user would like to reconnect + bool askToConnect( QWidget * mainWidget ); + k_dcop: + void slotStatusChanged( QString host, int status ); + private: + ConnectionManager( QObject *parent, const char * name ); + ConnectionManagerPrivate *d; + static ConnectionManager * s_self; +}; + +#endif + diff --git a/kopete/libkopete/kabcpersistence.cpp b/kopete/libkopete/kabcpersistence.cpp new file mode 100644 index 00000000..527a99a4 --- /dev/null +++ b/kopete/libkopete/kabcpersistence.cpp @@ -0,0 +1,452 @@ +/* + addressbooklink.cpp - Manages operations involving the KDE Address Book + + Copyright (c) 2005 Will Stephenson <lists@stevello.free-online.co.uk> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <qstring.h> +#include <qtimer.h> + +#include <kabc/addressbook.h> +#include <kabc/addressee.h> +#include <kabc/resource.h> +#include <kabc/stdaddressbook.h> + +// UI related includes used for importing from KABC +#include <kdialogbase.h> +#include <klocale.h> +#include <kmessagebox.h> +#include "accountselector.h" +#include "kopeteuiglobal.h" + +#include <kstaticdeleter.h> + +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" +#include "kopetecontact.h" +#include "kopetemetacontact.h" +#include "kopetepluginmanager.h" +#include "kopeteprotocol.h" + +#include "kabcpersistence.h" + +namespace Kopete +{ + +/** + * utility function to merge two QStrings containing individual elements separated by 0xE000 + */ +static QString unionContents( QString arg1, QString arg2 ) +{ + QChar separator( 0xE000 ); + QStringList outList = QStringList::split( separator, arg1 ); + QStringList arg2List = QStringList::split( separator, arg2 ); + for ( QStringList::iterator it = arg2List.begin(); it != arg2List.end(); ++it ) + if ( !outList.contains( *it ) ) + outList.append( *it ); + QString out = outList.join( separator ); + return out; +} + +KABCPersistence::KABCPersistence( QObject * parent, const char * name ) : QObject( parent, name ) +{ + s_pendingResources.setAutoDelete( false ); +} + +KABCPersistence::~KABCPersistence() +{ +} + +KABCPersistence *KABCPersistence::s_self = 0L; + +bool KABCPersistence::s_addrBookWritePending = false; + +QPtrList<KABC::Resource> KABCPersistence::s_pendingResources; + +KABC::AddressBook* KABCPersistence::s_addressBook = 0; + +KABCPersistence *KABCPersistence::self() +{ + static KStaticDeleter<KABCPersistence> deleter; + if(!s_self) + deleter.setObject( s_self, new KABCPersistence() ); + return s_self; +} + +KABC::AddressBook* KABCPersistence::addressBook() +{ + if ( s_addressBook == 0L ) + { + s_addressBook = KABC::StdAddressBook::self(); + KABC::StdAddressBook::setAutomaticSave( false ); + } + return s_addressBook; +} + +void KABCPersistence::write( MetaContact * mc ) +{ + // Save any changes in each contact's addressBookFields to KABC + KABC::AddressBook* ab = addressBook(); + + kdDebug( 14010 ) << k_funcinfo << "looking up Addressee for " << mc->displayName() << "..." << endl; + // Look up the address book entry + KABC::Addressee theAddressee = ab->findByUid( mc->metaContactId() ); + // Check that if addressee is not deleted or if the link is spurious + // (inherited from Kopete < 0.8, where all metacontacts had random ids) + if ( theAddressee.isEmpty() ) + { + // not found in currently enabled addressbooks - may be in a disabled resource... + return; + } + else + { + // collate the instant messaging data to be inserted into the address book + QMap<QString, QStringList> addressMap; + QPtrList<Contact> contacts = mc->contacts(); + QPtrListIterator<Contact> cIt( contacts ); + while ( Contact * c = cIt.current() ) + { + QStringList addresses = addressMap[ c->protocol()->addressBookIndexField() ]; + addresses.append( c->contactId() ); + addressMap.insert( c->protocol()->addressBookIndexField(), addresses ); + ++cIt; + } + + // insert a custom field for each protocol + QMap<QString, QStringList>::ConstIterator it = addressMap.begin(); + for ( ; it != addressMap.end(); ++it ) + { + // read existing data for this key + QString currentCustomForProtocol = theAddressee.custom( it.key(), QString::fromLatin1( "All" ) ); + // merge without duplicating + QString toWrite = unionContents( currentCustomForProtocol, it.data().join( QChar( 0xE000 ) ) ); + // Note if nothing ends up in the KABC data, this is because insertCustom does nothing if any param is empty. + kdDebug( 14010 ) << k_funcinfo << "Writing: " << it.key() << ", " << "All" << ", " << toWrite << endl; + theAddressee.insertCustom( it.key(), QString::fromLatin1( "All" ), toWrite ); + QString check = theAddressee.custom( it.key(), QString::fromLatin1( "All" ) ); + } + ab->insertAddressee( theAddressee ); + //kdDebug( 14010 ) << k_funcinfo << "dumping addressbook before write " << endl; + //dumpAB(); + writeAddressBook( theAddressee.resource() ); + //theAddressee.dump(); + } + +/* // Wipe out the existing addressBook entries + d->addressBook.clear(); + // This causes each Kopete::Protocol subclass to serialise its contacts' data into the metacontact's plugin data and address book data + emit aboutToSave(this); + + kdDebug( 14010 ) << k_funcinfo << "...FOUND ONE!" << endl; + // Store address book fields + QMap<QString, QMap<QString, QString> >::ConstIterator appIt = d->addressBook.begin(); + for( ; appIt != d->addressBook.end(); ++appIt ) + { + QMap<QString, QString>::ConstIterator addrIt = appIt.data().begin(); + for( ; addrIt != appIt.data().end(); ++addrIt ) + { + // read existing data for this key + QString currentCustom = theAddressee.custom( appIt.key(), addrIt.key() ); + // merge without duplicating + QString toWrite = unionContents( currentCustom, addrIt.data() ); + // write the result + // Note if nothing ends up in the KABC data, this is because insertCustom does nothing if any param is empty. + kdDebug( 14010 ) << k_funcinfo << "Writing: " << appIt.key() << ", " << addrIt.key() << ", " << toWrite << endl; + theAddressee.insertCustom( appIt.key(), addrIt.key(), toWrite ); + } + } + ab->insertAddressee( theAddressee ); + writeAddressBook(); + }*/ +} + +void KABCPersistence::writeAddressBook( const KABC::Resource * res) +{ + if ( !s_pendingResources.containsRef( res ) ) + s_pendingResources.append( res ); + if ( !s_addrBookWritePending ) + { + s_addrBookWritePending = true; + QTimer::singleShot( 2000, this, SLOT( slotWriteAddressBook() ) ); + } +} + +void KABCPersistence::slotWriteAddressBook() +{ + //kdDebug( 14010 ) << k_funcinfo << endl; + KABC::AddressBook* ab = addressBook(); + QPtrListIterator<KABC::Resource> it( s_pendingResources ); + for ( ; it.current(); ++it ) + { + //kdDebug( 14010 ) << "Writing resource " << it.current()->resourceName() << endl; + KABC::Ticket *ticket = ab->requestSaveTicket( it.current() ); + if ( !ticket ) + kdWarning( 14010 ) << "WARNING: Resource is locked by other application!" << endl; + else + { + if ( !ab->save( ticket ) ) + { + kdWarning( 14010 ) << "ERROR: Saving failed!" << endl; + ab->releaseSaveTicket( ticket ); + } + } + //kdDebug( 14010 ) << "Finished writing KABC" << endl; + } + s_pendingResources.clear(); + s_addrBookWritePending = false; +} + +void KABCPersistence::removeKABC( MetaContact *) +{ +/* // remove any data this KMC has written to the KDE address book + // Save any changes in each contact's addressBookFields to KABC + KABC::AddressBook* ab = addressBook(); + + // Wipe out the existing addressBook entries + d->addressBook.clear(); + // This causes each Kopete::Protocol subclass to serialise its contacts' data into the metacontact's plugin data and address book data + emit aboutToSave(this); + + // If the metacontact is linked to a kabc entry + if ( !d->metaContactId.isEmpty() ) + { + //kdDebug( 14010 ) << k_funcinfo << "looking up Addressee for " << displayName() << "..." << endl; + // Look up the address book entry + KABC::Addressee theAddressee = ab->findByUid( metaContactId() ); + + if ( theAddressee.isEmpty() ) + { + // remove the link + //kdDebug( 14010 ) << k_funcinfo << "...not found." << endl; + d->metaContactId=QString::null; + } + else + { + //kdDebug( 14010 ) << k_funcinfo << "...FOUND ONE!" << endl; + // Remove address book fields + QMap<QString, QMap<QString, QString> >::ConstIterator appIt = d->addressBook.begin(); + for( ; appIt != d->addressBook.end(); ++appIt ) + { + QMap<QString, QString>::ConstIterator addrIt = appIt.data().begin(); + for( ; addrIt != appIt.data().end(); ++addrIt ) + { + // FIXME: This assumes Kopete is the only app writing these fields + kdDebug( 14010 ) << k_funcinfo << "Removing: " << appIt.key() << ", " << addrIt.key() << endl; + theAddressee.removeCustom( appIt.key(), addrIt.key() ); + } + } + ab->insertAddressee( theAddressee ); + + writeAddressBook(); + } + } +// kdDebug(14010) << k_funcinfo << kdBacktrace() <<endl;*/ +} + +bool KABCPersistence::syncWithKABC( MetaContact * mc ) +{ + kdDebug(14010) << k_funcinfo << endl; + bool contactAdded = false; + // check whether the dontShowAgain was checked + KABC::AddressBook* ab = addressBook(); + KABC::Addressee addr = ab->findByUid( mc->metaContactId() ); + + if ( !addr.isEmpty() ) // if we are associated with KABC + { +// load the set of addresses from KABC + QStringList customs = addr.customs(); + + QStringList::ConstIterator it; + for ( it = customs.begin(); it != customs.end(); ++it ) + { + QString app, name, value; + splitField( *it, app, name, value ); + kdDebug( 14010 ) << "app=" << app << " name=" << name << " value=" << value << endl; + + if ( app.startsWith( QString::fromLatin1( "messaging/" ) ) ) + { + if ( name == QString::fromLatin1( "All" ) ) + { + kdDebug( 14010 ) << " syncing \"" << app << ":" << name << " with contactlist " << endl; + // Get the protocol name from the custom field + // by chopping the 'messaging/' prefix from the custom field app name + QString protocolName = app.right( app.length() - 10 ); + // munge Jabber hack + if ( protocolName == QString::fromLatin1( "xmpp" ) ) + protocolName = QString::fromLatin1( "jabber" ); + + // Check Kopete supports it + Protocol * proto = dynamic_cast<Protocol*>( PluginManager::self()->loadPlugin( QString::fromLatin1( "kopete_" ) + protocolName ) ); + if ( !proto ) + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "<qt>\"%1\" is not supported by Kopete.</qt>" ).arg( protocolName ), + i18n( "Could Not Sync with KDE Address Book" ) ); + continue; + } + + // See if we need to add each contact in this protocol + QStringList addresses = QStringList::split( QChar( 0xE000 ), value ); + QStringList::iterator end = addresses.end(); + for ( QStringList::iterator it = addresses.begin(); it != end; ++it ) + { + // check whether each one is present in Kopete + // Is it in the contact list? + // First discard anything after an 0xE120, this is used by IRC to separate nick and server group name, but + // IRC doesn't support this properly yet, so the user will have to select an appropriate account manually + int separatorPos = (*it).find( QChar( 0xE120 ) ); + if ( separatorPos != -1 ) + *it = (*it).left( separatorPos ); + + QDict<Kopete::Account> accounts = Kopete::AccountManager::self()->accounts( proto ); + QDictIterator<Kopete::Account> acs(accounts); + Kopete::MetaContact *otherMc = 0; + for ( acs.toFirst(); acs.current(); ++acs ) + { + Kopete::Contact *c= acs.current()->contacts()[*it]; + if(c) + { + otherMc=c->metaContact(); + break; + } + } + + if ( otherMc ) // Is it in another metacontact? + { + // Is it already in this metacontact? If so, we needn't do anything + if ( otherMc == mc ) + { + kdDebug( 14010 ) << *it << " already a child of this metacontact." << endl; + continue; + } + kdDebug( 14010 ) << *it << " already exists in OTHER metacontact, move here?" << endl; + // find the Kopete::Contact and attempt to move it to this metacontact. + otherMc->findContact( proto->pluginId(), QString::null, *it )->setMetaContact( mc ); + } + else + { + // if not, prompt to add it + kdDebug( 14010 ) << proto->pluginId() << "://" << *it << " was not found in the contact list. Prompting to add..." << endl; + if ( KMessageBox::Yes == KMessageBox::questionYesNo( Kopete::UI::Global::mainWidget(), + i18n( "<qt>An address was added to this contact by another application.<br>Would you like to use it in Kopete?<br><b>Protocol:</b> %1<br><b>Address:</b> %2</qt>" ).arg( proto->displayName() ).arg( *it ), i18n( "Import Address From Address Book" ), i18n("Use"), i18n("Do Not Use"), QString::fromLatin1( "ImportFromKABC" ) ) ) + { + // Check the accounts for this protocol are all connected + // Most protocols do not allow you to add contacts while offline + // Would be better to have a virtual bool Kopete::Account::readyToAddContact() + bool allAccountsConnected = true; + for ( acs.toFirst(); acs.current(); ++acs ) + if ( !acs.current()->isConnected() ) + { allAccountsConnected = false; + break; + } + if ( !allAccountsConnected ) + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "<qt>One or more of your accounts using %1 are offline. Most systems have to be connected to add contacts. Please connect these accounts and try again.</qt>" ).arg( protocolName ), + i18n( "Not Connected" ) ); + continue; + } + + // we have got a contact to add, our accounts are connected, so add it. + // Do we need to choose an account + Kopete::Account *chosen = 0; + if ( accounts.count() > 1 ) + { // if we have >1 account in this protocol, prompt for the protocol. + KDialogBase *chooser = new KDialogBase(0, "chooser", true, + i18n("Choose Account"), KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Ok, false); + AccountSelector *accSelector = new AccountSelector(proto, chooser, + "accSelector"); + chooser->setMainWidget(accSelector); + if ( chooser->exec() == QDialog::Rejected ) + continue; + chosen = accSelector->selectedItem(); + + delete chooser; + } + else if ( accounts.isEmpty() ) + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "<qt>You do not have an account configured for <b>%1</b> yet. Please create an account, connect it, and try again.</qt>" ).arg( protocolName ), + i18n( "No Account Found" ) ); + continue; + } + else // if we have 1 account in this protocol, choose it + { + chosen = acs.toFirst(); + } + + // add the contact to the chosen account + if ( chosen ) + { + kdDebug( 14010 ) << "Adding " << *it << " to " << chosen->accountId() << endl; + if ( chosen->addContact( *it, mc ) ) + contactAdded = true; + else + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "<qt>It was not possible to add the contact.</qt>" ), + i18n( "Could Not Add Contact") ) ; + } + } + else + kdDebug( 14010 ) << " user declined to add " << *it << " to contactlist " << endl; + } + } + kdDebug( 14010 ) << " all " << addresses.count() << " contacts in " << proto->pluginId() << " checked " << endl; + } + else + kdDebug( 14010 ) << "not interested in name=" << name << endl; + + } + else + kdDebug( 14010 ) << "not interested in app=" << app << endl; + } + } + return contactAdded; + return false; +} + +// FIXME: Remove when IM address API is in KABC (KDE 4) +void KABCPersistence::splitField( const QString &str, QString &app, QString &name, QString &value ) +{ + int colon = str.find( ':' ); + if ( colon != -1 ) { + QString tmp = str.left( colon ); + value = str.mid( colon + 1 ); + + int dash = tmp.find( '-' ); + if ( dash != -1 ) { + app = tmp.left( dash ); + name = tmp.mid( dash + 1 ); + } + } +} + +void KABCPersistence::dumpAB() +{ + KABC::AddressBook * ab = addressBook(); + kdDebug( 14010 ) << k_funcinfo << " DUMPING ADDRESSBOOK" << endl; + KABC::AddressBook::ConstIterator dumpit = ab->begin(); + for ( ; dumpit != ab->end(); ++dumpit ) + { + (*dumpit).dump(); + } +} + + +} // end namespace Kopete + + // dump addressbook contents + +#include "kabcpersistence.moc" diff --git a/kopete/libkopete/kabcpersistence.h b/kopete/libkopete/kabcpersistence.h new file mode 100644 index 00000000..fa02fb64 --- /dev/null +++ b/kopete/libkopete/kabcpersistence.h @@ -0,0 +1,107 @@ +/* + addressbooklink.h - Manages operations involving the KDE Address Book + + Copyright (c) 2005 Will Stephenson <lists@stevello.free-online.co.uk> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEADDRESSBOOKLINK_H +#define KOPETEADDRESSBOOKLINK_H + +#include "kopete_export.h" + +// Goal is to have all the address book modifying code in one place +// Currently in +// *) Add Contact Wizard +// *) KopeteMetaContact +// *) KopeteAddrBookExport +// *) KABC Export Wizard - TODO - think about sequence of events when adding addressees AND writing their IM data. - Extra save should be unnecessary because we are sharing a kabc instance +// *) Select addressbook entry + +namespace KABC +{ + class AddressBook; + class Resource; +} + +namespace Kopete +{ + + class MetaContact; + +class KOPETE_EXPORT KABCPersistence : public QObject +{ + Q_OBJECT + public: + /** + * \brief Retrieve the instance of AccountManager. + * + * The account manager is a singleton class of which only a single + * instance will exist. If no manager exists yet this function will + * create one for you. + * + * \return the instance of the AccountManager + */ + static KABCPersistence* self(); + + KABCPersistence( QObject * parent = 0, const char * name = 0 ); + ~KABCPersistence(); + /** + * @brief Access Kopete's KDE address book instance + */ + static KABC::AddressBook* addressBook(); + /** + * @brief Change the KABC data associated with this metacontact + * + * The KABC exposed data changed, so change it in KABC. + * Replaces Kopete::MetaContact::updateKABC() + */ + void write( MetaContact * mc ); + + /** + * @brief Remove any KABC data for this meta contact + */ + void removeKABC( MetaContact * mc ); + + /** + * Check for any new addresses added to this contact's KABC entry + * and prompt if they should be added in Kopete too. + * @return whether any contacts were added from KABC. + */ + bool syncWithKABC( MetaContact * mc ); + + /** + * Request an address book write, will be delayed to bundle any others happening around the same time + */ + void writeAddressBook( const KABC::Resource * res ); + protected: + + static void splitField( const QString &str, QString &app, QString &name, QString &value ); + + void dumpAB(); + protected slots: + /** + * Perform a delayed address book write + */ + void slotWriteAddressBook(); + private: + static KABCPersistence * s_self; + static KABC::AddressBook* s_addressBook; + static bool s_addrBookWritePending; + static QPtrList<KABC::Resource> s_pendingResources; +}; + +} // end namespace Kopete + +#endif + diff --git a/kopete/libkopete/kautoconfig.cpp b/kopete/libkopete/kautoconfig.cpp new file mode 100644 index 00000000..497b6cd5 --- /dev/null +++ b/kopete/libkopete/kautoconfig.cpp @@ -0,0 +1,450 @@ +/* + * This file is part of the KDE libraries + * Copyright (C) 2003 Benjamin C Meyer (ben+kdelibs at meyerhome dot net) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "kautoconfig.h" + +#include <kglobal.h> +#include <qsqlpropertymap.h> +#include <qobjectlist.h> +#include <kconfig.h> +#include <kapplication.h> +#include <kdeversion.h> + +/** + * Macro function to warn developers when they are making calls + * that can never return anything of value + */ +#ifndef NDEBUG +#include "kdebug.h" +#define functionCallPreOrderCheck(functionName, returnValue) \ + if(!d->retrievedSettings){ \ + kdDebug(180) << "KAutoConfig::"functionName"() was called before " \ + "KAutoConfig::retrieveSettings(). This should NEVER happen because " \ + "it will do nothing. Please Fix." << endl; \ + return returnValue; \ + } + +#define functionCallPostOrderCheck(functionName, returnValue) \ + if(d->retrievedSettings){ \ + kdDebug(180) << "KAutoConfig::"functionName"() was called after " \ + "KAutoConfig::retrieveSettings(). This should NEVER happen because " \ + "it will do nothing. Please Fix." << endl; \ + return returnValue; \ + } +#else +#define functionCallPostOrderCheck(functionName, returnValue) +#define functionCallPreOrderCheck(functionName, returnValue) +#endif + +class KAutoConfig::KAutoConfigPrivate { + +public: + KAutoConfigPrivate() : changed(false) +#ifndef NDEBUG + , retrievedSettings(false) +#endif + { init(); } + + // Widgets to parse + QPtrList<QWidget> widgets; + // Name of the group that KConfig should be set to for each widget. + QMap<QWidget*, QString> groups; + + // Child widgets of widgets to ignore + QPtrList<QWidget> ignore; + + // Reset to false after saveSettings returns true. + bool changed; + +#ifndef NDEBUG + // Many functions require this to be true to be of any value. + bool retrievedSettings; +#endif + + // Known widgets that can be configured + QMap<QWidget*, QPtrList<QWidget> > autoWidgets; + // Default values for the widgets. + QMap<QWidget*, QVariant> defaultValues; + // Widgets to not get properties on (QLabel etc) + QAsciiDict<int> ignoreTheseWidgets; + + void init(){ + ignoreTheseWidgets.insert("QLabel", new int(1)); + ignoreTheseWidgets.insert("QFrame", new int(2)); + ignoreTheseWidgets.insert("QGroupBox", new int(3)); + ignoreTheseWidgets.insert("QButtonGroup", new int(4)); + ignoreTheseWidgets.insert("QWidget", new int(5)); + ignoreTheseWidgets.setAutoDelete(true); + + static bool defaultKDEPropertyMapInstalled = false; + if ( !defaultKDEPropertyMapInstalled && kapp ) { + kapp->installKDEPropertyMap(); + defaultKDEPropertyMapInstalled = true; + } + } +}; + +KAutoConfig::KAutoConfig(KConfig *kconfig, QObject *parent, + const char *name) : QObject(parent, name), config(kconfig) { + d = new KAutoConfigPrivate(); +} + +KAutoConfig::KAutoConfig(QObject *parent, const char *name) : + QObject(parent, name), config(KGlobal::config()) { + d = new KAutoConfigPrivate(); +} + +KAutoConfig::~KAutoConfig(){ + delete d; +} + +void KAutoConfig::addWidget(QWidget *widget, const QString &group){ + functionCallPostOrderCheck("addWidget",); + d->groups.insert(widget, group); + d->widgets.append(widget); + QPtrList<QWidget> newAutoConfigWidget; + d->autoWidgets.insert(widget, newAutoConfigWidget ); +} + +void KAutoConfig::ignoreSubWidget(QWidget *widget){ + functionCallPostOrderCheck("ignoreSubWidget",); + d->ignore.append(widget); +} + +bool KAutoConfig::retrieveSettings(bool trackChanges){ +#ifndef NDEBUG + if(d->retrievedSettings){ + kdDebug(180) << "This should not happen. Function " + "KAutoConfig::retrieveSettings() was called more then once, returning " + "false. Please fix." << endl; + return false; + } + d->retrievedSettings = true; +#endif + + if(trackChanges){ + // QT + changedMap.insert(QString::fromLatin1("QButton"), SIGNAL(stateChanged(int))); + changedMap.insert(QString::fromLatin1("QCheckBox"), SIGNAL(stateChanged(int))); + changedMap.insert(QString::fromLatin1("QPushButton"), SIGNAL(stateChanged(int))); + changedMap.insert(QString::fromLatin1("QRadioButton"), SIGNAL(stateChanged(int))); + changedMap.insert(QString::fromLatin1("QComboBox"), SIGNAL(activated (int))); + //qsqlproperty map doesn't store the text, but the value! + //changedMap.insert(QString::fromLatin1("QComboBox"), SIGNAL(textChanged(const QString &))); + changedMap.insert(QString::fromLatin1("QDateEdit"), SIGNAL(valueChanged(const QDate &))); + changedMap.insert(QString::fromLatin1("QDateTimeEdit"), SIGNAL(valueChanged(const QDateTime &))); + changedMap.insert(QString::fromLatin1("QDial"), SIGNAL(valueChanged (int))); + changedMap.insert(QString::fromLatin1("QLineEdit"), SIGNAL(textChanged(const QString &))); + changedMap.insert(QString::fromLatin1("QSlider"), SIGNAL(valueChanged(int))); + changedMap.insert(QString::fromLatin1("QSpinBox"), SIGNAL(valueChanged(int))); + changedMap.insert(QString::fromLatin1("QTimeEdit"), SIGNAL(valueChanged(const QTime &))); + changedMap.insert(QString::fromLatin1("QTextEdit"), SIGNAL(textChanged())); + changedMap.insert(QString::fromLatin1("QTextBrowser"), SIGNAL(sourceChanged(const QString &))); + changedMap.insert(QString::fromLatin1("QMultiLineEdit"), SIGNAL(textChanged())); + changedMap.insert(QString::fromLatin1("QListBox"), SIGNAL(selectionChanged())); + changedMap.insert(QString::fromLatin1("QTabWidget"), SIGNAL(currentChanged(QWidget *))); + + // KDE + changedMap.insert( QString::fromLatin1("KComboBox"), SIGNAL(activated (int))); + changedMap.insert( QString::fromLatin1("KFontCombo"), SIGNAL(activated (int))); + changedMap.insert( QString::fromLatin1("KFontRequester"), SIGNAL(fontSelected(const QFont &))); + changedMap.insert( QString::fromLatin1("KFontChooser"), SIGNAL(fontSelected(const QFont &))); + changedMap.insert( QString::fromLatin1("KHistoryCombo"), SIGNAL(activated (int))); + + changedMap.insert( QString::fromLatin1("KColorButton"), SIGNAL(changed(const QColor &))); + changedMap.insert( QString::fromLatin1("KDatePicker"), SIGNAL(dateSelected (QDate))); + changedMap.insert( QString::fromLatin1("KEditListBox"), SIGNAL(changed())); + changedMap.insert( QString::fromLatin1("KListBox"), SIGNAL(selectionChanged())); + changedMap.insert( QString::fromLatin1("KLineEdit"), SIGNAL(textChanged(const QString &))); + changedMap.insert( QString::fromLatin1("KPasswordEdit"), SIGNAL(textChanged(const QString &))); + changedMap.insert( QString::fromLatin1("KRestrictedLine"), SIGNAL(textChanged(const QString &))); + changedMap.insert( QString::fromLatin1("KTextBrowser"), SIGNAL(sourceChanged(const QString &))); + changedMap.insert( QString::fromLatin1("KTextEdit"), SIGNAL(textChanged())); + changedMap.insert( QString::fromLatin1("KURLRequester"), SIGNAL(textChanged (const QString& ))); + changedMap.insert( QString::fromLatin1("KIntNumInput"), SIGNAL(valueChanged (int))); + changedMap.insert( QString::fromLatin1("KIntSpinBox"), SIGNAL(valueChanged (int))); + changedMap.insert( QString::fromLatin1("KDoubleNumInput"), SIGNAL(valueChanged (double))); + } + + // Go through all of the children of the widgets and find all known widgets + QPtrListIterator<QWidget> it( d->widgets ); + QWidget *widget; + bool usingDefaultValues = false; + while ( (widget = it.current()) != 0 ) { + ++it; + config->setGroup(d->groups[widget]); + usingDefaultValues |= parseChildren(widget, d->autoWidgets[widget], trackChanges); + } + return usingDefaultValues; +} + +bool KAutoConfig::saveSettings() { + functionCallPreOrderCheck("saveSettings", false); + + QSqlPropertyMap *propertyMap = QSqlPropertyMap::defaultMap(); + // Go through all of the widgets + QPtrListIterator<QWidget> it( d->widgets ); + QWidget *widget; + while ( (widget = it.current()) != 0 ) { + ++it; + config->setGroup(d->groups[widget]); + + // Go through the known autowidgets of this widget and save + QPtrListIterator<QWidget> it( d->autoWidgets[widget] ); + QWidget *groupWidget; + bool widgetChanged = false; + while ( (groupWidget = it.current()) != 0 ){ + ++it; + QVariant defaultValue = d->defaultValues[groupWidget]; + QVariant currentValue = propertyMap->property(groupWidget); +#if KDE_IS_VERSION( 3, 1, 90 ) + if(!config->hasDefault(QString::fromLatin1(groupWidget->name())) && currentValue == defaultValue){ + config->revertToDefault(QString::fromLatin1(groupWidget->name())); + widgetChanged = true; + } + else{ +#endif + QVariant savedValue = config->readPropertyEntry(groupWidget->name(), + defaultValue); + if(savedValue != currentValue){ + config->writeEntry(groupWidget->name(), currentValue); + widgetChanged = true; + } +#if KDE_IS_VERSION( 3, 1, 90 ) + } +#endif + } + d->changed |= widgetChanged; + if(widgetChanged) + emit( settingsChanged(widget) ); + } + + if(d->changed){ + emit( settingsChanged() ); + d->changed = false; + config->sync(); + return true; + } + return false; +} + +bool KAutoConfig::hasChanged() const { + functionCallPreOrderCheck("hasChanged", false); + + QSqlPropertyMap *propertyMap = QSqlPropertyMap::defaultMap(); + // Go through all of the widgets + QPtrListIterator<QWidget> it( d->widgets ); + QWidget *widget; + while ( (widget = it.current()) != 0 ) { + ++it; + config->setGroup(d->groups[widget]); + // Go through the known autowidgets of this widget and save + QPtrListIterator<QWidget> it( d->autoWidgets[widget] ); + QWidget *groupWidget; + while ( (groupWidget = it.current()) != 0 ){ + ++it; + QVariant defaultValue = d->defaultValues[groupWidget]; + QVariant currentValue = propertyMap->property(groupWidget); + QVariant savedValue = config->readPropertyEntry(groupWidget->name(), + defaultValue); + + // Return once just one item is found to have changed. + if((currentValue == defaultValue && savedValue != currentValue) || + (savedValue != currentValue)) + return true; + } + } + return false; +} + +bool KAutoConfig::isDefault() const { + functionCallPreOrderCheck("isDefault", false); + + QSqlPropertyMap *propertyMap = QSqlPropertyMap::defaultMap(); + // Go through all of the widgets + QPtrListIterator<QWidget> it( d->widgets ); + QWidget *widget; + while ( (widget = it.current()) != 0 ) { + ++it; + config->setGroup(d->groups[widget]); + // Go through the known autowidgets of this widget and save + QPtrListIterator<QWidget> it( d->autoWidgets[widget] ); + QWidget *groupWidget; + while ( (groupWidget = it.current()) != 0 ){ + ++it; + QVariant defaultValue = d->defaultValues[groupWidget]; + QVariant currentValue = propertyMap->property(groupWidget); + if(currentValue != defaultValue){ + //qDebug("groupWidget %s, has changed: default: %s new: %s", groupWidget->name(), defaultValue.toString().latin1(), currentValue.toString().latin1()); + return false; + } + } + } + return true; +} + +void KAutoConfig::resetSettings() const { + functionCallPreOrderCheck("resetSettings",); + + QSqlPropertyMap *propertyMap = QSqlPropertyMap::defaultMap(); + // Go through all of the widgets + QPtrListIterator<QWidget> it( d->widgets ); + QWidget *widget; + while ( (widget = it.current()) != 0 ) { + ++it; + config->setGroup(d->groups[widget]); + + // Go through the known autowidgets of this widget and save + QPtrListIterator<QWidget> it( d->autoWidgets[widget] ); + QWidget *groupWidget; + while ( (groupWidget = it.current()) != 0 ){ + ++it; + QVariant defaultValue = d->defaultValues[groupWidget]; + if(defaultValue != propertyMap->property(groupWidget)){ + propertyMap->setProperty(groupWidget, defaultValue); + d->changed = true; + } + } + } +} + +void KAutoConfig::reloadSettings() const { + functionCallPreOrderCheck("reloadSettings", ); + + QSqlPropertyMap *propertyMap = QSqlPropertyMap::defaultMap(); + // Go through all of the widgets + QPtrListIterator<QWidget> it( d->widgets ); + QWidget *pageWidget; + while ( (pageWidget = it.current()) != 0 ) { + ++it; + config->setGroup(d->groups[pageWidget]); + + // Go through the known widgets of this page and reload + QPtrListIterator<QWidget> it( d->autoWidgets[pageWidget] ); + QWidget *widget; + while ( (widget = it.current()) != 0 ){ + ++it; + QVariant defaultSetting = d->defaultValues[widget]; + QVariant setting = + config->readPropertyEntry(widget->name(), defaultSetting); + propertyMap->setProperty(widget, setting); + } + } + d->changed = false; +} + +bool KAutoConfig::parseChildren(const QWidget *widget, + QPtrList<QWidget>& currentGroup, bool trackChanges){ + bool valueChanged = false; + const QPtrList<QObject> *listOfChildren = widget->children(); + if(!listOfChildren) + return valueChanged; + + QSqlPropertyMap *propertyMap = QSqlPropertyMap::defaultMap(); + QPtrListIterator<QObject> it( *listOfChildren ); + QObject *object; + while ( (object = it.current()) != 0 ) + { + ++it; + if(!object->isWidgetType()){ + continue; + } + QWidget *childWidget = (QWidget *)object; + if(d->ignore.containsRef(childWidget)){ + continue; + } + + bool parseTheChildren = true; +#ifndef NDEBUG + if(d->ignoreTheseWidgets[childWidget->className()] == 0 && + childWidget->name(0) == NULL){ + // Without a name the widget is just skipped over. + kdDebug(180) << "KAutoConfig::retrieveSettings, widget with " + "NULL name. className: " << childWidget->className() << endl; + } +#endif + + + if( d->ignoreTheseWidgets[childWidget->className()] == 0 && + childWidget->name(0) != NULL ) + { + QVariant defaultSetting = propertyMap->property(childWidget); + if(defaultSetting.isValid()) + { + parseTheChildren = false; + // Disable the widget if it is immutable? + if(config->entryIsImmutable( QString::fromLatin1(childWidget->name()))) + childWidget->setEnabled(false); + else + { + // FOR THOSE WHO ARE LOOKING + // Here is the code were the widget is actually marked to watch. + //qDebug("KAutoConfig: Adding widget(%s)",childWidget->name()); + currentGroup.append(childWidget); + d->defaultValues.insert(childWidget, defaultSetting); + } + // Get/Set settings and connect up the changed signal + QVariant setting = + config->readPropertyEntry(childWidget->name(), defaultSetting); + if(setting != defaultSetting) + { + propertyMap->setProperty(childWidget, setting); + valueChanged = true; + } + if(trackChanges && changedMap.find(QString::fromLatin1(childWidget->className())) != + changedMap.end()) + { + connect(childWidget, changedMap[QString::fromLatin1(childWidget->className())], + this, SIGNAL(widgetModified())); + } +#ifndef NDEBUG + else if(trackChanges && + changedMap.find(QString::fromLatin1(childWidget->className())) == changedMap.end()) + { + // Without a signal kautoconfigdialog could incorectly + // enable/disable the buttons + kdDebug(180) << "KAutoConfig::retrieveSettings, Unknown changed " + "signal for widget:" << childWidget->className() << endl; + } +#endif + + } +#ifndef NDEBUG + else + { + // If kautoconfig doesn't know how to get/set the widget's value + // nothing can be done to it and it is skipped. + kdDebug(180) << "KAutoConfig::retrieveSettings, Unknown widget:" + << childWidget->className() << endl; + } +#endif + } + if(parseTheChildren) + { + // this widget is not known as something we can store. + // Maybe we can store one of its children. + valueChanged |= parseChildren(childWidget, currentGroup, trackChanges); + } + } + return valueChanged; +} + +#include "kautoconfig.moc" + diff --git a/kopete/libkopete/kautoconfig.h b/kopete/libkopete/kautoconfig.h new file mode 100644 index 00000000..de0df143 --- /dev/null +++ b/kopete/libkopete/kautoconfig.h @@ -0,0 +1,278 @@ +/* + * This file is part of the KDE libraries + * Copyright (C) 2003 Benjamin C Meyer (ben+kdelibs at meyerhome dot net) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef KAUTOCONFIG_H +#define KAUTOCONFIG_H + +#include <qobject.h> +#include <qptrlist.h> + +#include "kopete_export.h" + +class KConfig; +class QWidget; + +/** + * @short Provides a means of automatically retrieving, saving and resetting basic settings + * + * The KAutoConfig class provides a means of automatically retrieving, + * saving and resetting basic settings. It also can emit signals when + * settings have been changed (settings were saved) or modified (the + * user changes a checkbox from on to off). + * + * When told to retrieve settings ( retrieveSettings()) KAutoConfig + * will traverse the specified widgets building a list of all known widgets + * that have a name and havn't been marked to be ignored. + * If a setting is marked immutable the value is loaded and the widget is + * disabled. + * + * The name of the widget determines the name of the setting. The initial + * value of the widget also is the default value when the widget is reset. + * If the widget does not have a name then it is ignored. + * + * When saveSettings() or resetSettings() is called KAutoConfig + * goes through the list of known widgets and performs the operation on each + * of them. + * + * If one of the widgets needs special treatment it can be specified to be + * ignored using the ignoreSubWidget() function. + * + * KAutoConfig uses the QSqlPropertyMap class to determine if it can do + * anything to a widget. Note that KAutoConfig doesn't require a database, + * it simply uses the functionality that is built into the QSqlPropertyMap + * class. New widgets can be added to the map using + * QSqlPropertyMap::installDefaultMap(). Note that you can't just add any + * class. The class must have a matching Q_PROPERTY(...) macro defined. + * + * For example (note that KColorButton is already added and it doesn't need to + * manually added): + * + * kcolorbutton.h defines the following property: + * \code + * Q_PROPERTY( QColor color READ color WRITE setColor ) + * \endcode + * + * To add KColorButton the following code would be inserted in the main. + * + * \code + * QSqlPropertyMap *map = QSqlPropertyMap::defaultMap(); + * map.insert("KColorButton", "color"); + * QSqlPropertyMap::installDefaultMap(map); + * \endcode + * + * If you add a new widget to the QSqlPropertyMap and wish to be notified when + * it is modified you should add its signal using addWidgetChangedSignal(). + * If the Apply and Default buttons and enabled/disabled by KAutoConfigDialog + * automatically then this must be done. + * + * @see KAutoConfigDialog + * @since 3.2 + * @author Benjamin C Meyer <ben+kdelibs at meyerhome dot net> + */ +class KOPETE_EXPORT KAutoConfig : public QObject { + +Q_OBJECT + +signals: + /** + * One or more of the settings have been saved (such as when the user + * clicks on the Apply button). This is only emitted by saveSettings() + * whenever one or more setting were changed and consequently saved. + */ + void settingsChanged(); + + /** + * One or more of the settings have been changed. + * @param widget - The widget group (pass in via addWidget()) that + * contains the one or more modified setting. + * @see settingsChanged() + */ + void settingsChanged( QWidget *widget ); + + /** + * If retrieveSettings() was told to track changes then if + * any known setting was changed this signal will be emitted. Note + * that a settings can be modified several times and might go back to the + * original saved state. hasChanged() will tell you if anything has + * actually changed from the saved values. + */ + void widgetModified(); + + +public: + /** + * Constructor. + * @param kconfig - KConfig to use when retrieving/saving the widgets + * that KAutoConfig knows about. + * @param parent - Parent object. + * @param name - Object name. + */ + KAutoConfig( KConfig *kconfig, QObject *parent=0, const char *name=0 ); + + /** + * Constructor. + * Uses KGlobal::config() when retrieving/saving the widgets that + * KAutoConfig knows about. + * @param parent - Parent object. + * @param name - Object name. + */ + KAutoConfig( QObject *parent=0, const char *name=0 ); + + /** + * Destructor. Deletes private class. + */ + ~KAutoConfig(); + + /** + * Adds a widget to the list of widgets that should be parsed for any + * children that KAutoConfig might know when retrieveSettings() is + * called. All calls to this function should be made before calling + * retrieveSettings(). + * @param widget - Pointer to the widget to add. + * @param group - Name of the group from which all of the settings for this + * widget will be located. If a child of 'widget' needs to be in a separate + * group it should be added separately and also ignored. + * @see ignoreSubWidget() + */ + void addWidget( QWidget *widget, const QString &group ); + + /** + * Ignore the specified child widget when performing an action. Doesn't + * effect widgets that were added with addWidget() only their children. All + * calls to this function should be made before calling retrieveSettings(). + * @param widget - Pointer to the widget that should be ignored. + * Note: Widgets that don't have a name are ignored automatically. + **/ + void ignoreSubWidget( QWidget *widget ); + + /** + * Traverse the specified widgets to see if anything is different then the + * current settings. retrieveSettings() must be called before this + * function to build the list of known widgets and default values. + * @return bool - True if any settings are different then the stored values. + */ + bool hasChanged() const; + + /** + * Traverse the specified widgets to see if anything is different then the + * default. retrieveSettings() must be called before this function to + * build the list of known widgets and default values. + * @return bool - True if all of the settings are their default values. + */ + bool isDefault() const; + + /** + * Adds a widget and its signal to the internal list so that when + * KAutoConfig finds widgetName in retrieveSettings() it will know + * how to connect its signal that it has changed to KAutoConfig's signal + * widgetModified(). This function should be called before + * + * Example: + * \code + * addWidgetChangedSignal( "QCheckbox", SIGNAL(stateChanged(int)) ); + * \endcode + * + * This is generally used in conjunction with the addition of a class + * to QSqlPropertyMap so KAutoConfig can get/set its values. + * + * @param widgetName - The class name of the widget (className()). + * @param signal - The signal (with "SIGNAL()" wrapper) that should be called. + */ + inline void addWidgetChangedSignal( const QString &widgetName, + const QCString &signal ){ + changedMap.insert( widgetName, signal ); + } + + /** + * Traverse the specified widgets, retrieving the settings for all known + * widgets that aren't being ignored and storing the default values. + * @param trackChanges - If any changes by the widgets should be tracked + * set true. This causes the emitting the modified() signal when + * something changes. + * @return bool - True if any setting was changed from the default. + */ + bool retrieveSettings( bool trackChanges=false ); + +public slots: + /** + * Traverse the specified widgets, saving the settings for all known + * widgets that aren't being ignored. retrieveSettings() must be called + * before this function to build the list of known widgets and default values. + * @return bool - True if any settings were changed. + * + * Example use: User clicks Ok or Apply button in a configure dialog. + */ + bool saveSettings(); + + /** + * Traverse the specified widgets, reseting the widgets to their default + * values for all known widgets that aren't being ignored. + * retrieveSettings() must be called before this function to build + * the list of known widgets and default values. + * + * Example use: User clicks Default button in a configure dialog. + */ + void resetSettings() const; + + /** + * Traverse the specified widgets, reloading the settings for all known + * widgets that aren't being ignored. + * retrieveSettings() must be called before this function to build + * the list of known widgets and default values. + * + * Example use: User clicks Reset button in a configure dialog. + */ + void reloadSettings() const; + +protected: + /** + * KConfigBase object used to get/save values. + */ + KConfig *config; + /** + * Map of the classes and the signals that they emit when changed. + */ + QMap<QString, QCString> changedMap; + + /** + * Recursive function that finds all known children. + * Goes through the children of widget and if any are known and not being + * ignored, stores them in currentGroup. Also checks if the widget + * should be disabled because it is set immutable. + * @param widget - Parent of the children to look at. + * @param currentGroup - Place to store known children of widget. + * @param trackChanges - If true then tracks any changes to the children of + * widget that are known. + * @return bool - If a widget was set to something other then its default. + * @see retrieveSettings() + */ + bool parseChildren( const QWidget *widget, + QPtrList<QWidget>¤tGroup, bool trackChanges ); + +private: + class KAutoConfigPrivate; + /** + * KAutoConfig Private class. + */ + KAutoConfigPrivate *d; + +}; + +#endif // KAUTOCONFIG_H + diff --git a/kopete/libkopete/kcautoconfigmodule.cpp b/kopete/libkopete/kcautoconfigmodule.cpp new file mode 100644 index 00000000..8bbe87c0 --- /dev/null +++ b/kopete/libkopete/kcautoconfigmodule.cpp @@ -0,0 +1,114 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Olivier Goffart <ogoffart @ kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + +*/ + +#include "kcautoconfigmodule.h" + +#include <qlayout.h> + +#include <kautoconfig.h> + +class KCAutoConfigModule::KCAutoConfigModulePrivate +{ + public: + KAutoConfig *kautoconfig; +}; + + +KCAutoConfigModule::KCAutoConfigModule( QWidget * parent, const char * name, const QStringList & args ) + : KCModule( parent, name, args ) + , d( new KCAutoConfigModulePrivate ) +{ + d->kautoconfig = new KAutoConfig( this ); + connect(d->kautoconfig, SIGNAL(widgetModified()), SLOT(slotWidgetModified())); + connect(d->kautoconfig, SIGNAL(settingsChanged()), SLOT(widgetModified())); +} + +KCAutoConfigModule::KCAutoConfigModule( KInstance * instance, QWidget * parent, const QStringList & args ) + : KCModule( instance, parent, args ) + , d( new KCAutoConfigModulePrivate ) +{ + d->kautoconfig = new KAutoConfig( this ); + connect(d->kautoconfig, SIGNAL(widgetModified()), SLOT(slotWidgetModified())); + connect(d->kautoconfig, SIGNAL(settingsChanged()), SLOT(slotWidgetModified())); +} + + + +KCAutoConfigModule::KCAutoConfigModule( KConfig *config,QWidget * parent, const char * name, const QStringList & args ) + : KCModule( parent, name, args ) , d( new KCAutoConfigModulePrivate ) +{ + d->kautoconfig = new KAutoConfig( config, this ); + connect(d->kautoconfig, SIGNAL(widgetModified()), SLOT(slotWidgetModified())); + connect(d->kautoconfig, SIGNAL(settingsChanged()), SLOT(slotWidgetModified())); +} + +KCAutoConfigModule::KCAutoConfigModule( KConfig *config , KInstance * instance, QWidget * parent, const QStringList & args ) + : KCModule( instance, parent, args ) + , d( new KCAutoConfigModulePrivate ) +{ + d->kautoconfig = new KAutoConfig( config, this ); + connect(d->kautoconfig, SIGNAL(widgetModified()), SLOT(slotWidgetModified())); + connect(d->kautoconfig, SIGNAL(settingsChanged()), SLOT(slotWidgetModified())); +} + + +KCAutoConfigModule::~KCAutoConfigModule() +{ + delete d; +} + + +void KCAutoConfigModule::load() +{ + d->kautoconfig->reloadSettings(); +} + +void KCAutoConfigModule::save() +{ + d->kautoconfig->saveSettings(); +} + +void KCAutoConfigModule::defaults() +{ + d->kautoconfig->resetSettings(); +} + +void KCAutoConfigModule::slotWidgetModified() +{ + emit changed(d->kautoconfig->hasChanged()); +} + +KAutoConfig *KCAutoConfigModule::autoConfig() +{ + return d->kautoconfig; +} + +void KCAutoConfigModule::setMainWidget(QWidget *widget, const QString& group ) +{ + QBoxLayout * l = new QVBoxLayout( this ); + l->addWidget( widget ); + + d->kautoconfig->addWidget(widget,group); + d->kautoconfig->retrieveSettings(true); +} + +#include "kcautoconfigmodule.moc" + +// vim: sw=4 sts=4 et + diff --git a/kopete/libkopete/kcautoconfigmodule.h b/kopete/libkopete/kcautoconfigmodule.h new file mode 100644 index 00000000..e3737ca5 --- /dev/null +++ b/kopete/libkopete/kcautoconfigmodule.h @@ -0,0 +1,150 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Olivier Goffart <ogoffart @ kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + +*/ + +#ifndef KCAUTOCONFIGMODULE_H +#define KCAUTOCONFIGMODULE_H + +#include <kcmodule.h> + +#include "kopete_export.h" + +class KAutoConfig; +class KConfig; + + +/** + * @short Convenience KCModule for creating config page handled with KAutoConfig + * + * This class makes it very easy to create a configuration page using KAutoConfig. + * All you need to do is create a class that is derived from KCAutoConfigModule, create your + * config page with QDesigner, and add it to the module + * This can be done using the setMainWidget() method: + * \code + * typedef KGenericFactory<MyPageConfig, QWidget> MyPageConfigFactory; + * K_EXPORT_COMPONENT_FACTORY( kcm_mypageconfig, MyPageConfigFactory( "kcm_mypageconfig" ) ) + * + * MyPageConfig( QWidget * parent, const char *, const QStringList & args ) + * : KCAutoConfigModule( MyPageConfigFactory::instance(), parent, args ) + * { + * setMainWidget( new MyPageConfigBase(this) , "MyGroup" ); + * } + * \endcode + * + * + * @author Olivier Goffart <ogoffart(@)tisclinet.be> + * @since 3.2 + */ +class KOPETE_EXPORT KCAutoConfigModule : public KCModule +{ + Q_OBJECT + public: + /** + * Standard KCModule constructor. Use KGlobal::config() + */ + KCAutoConfigModule( QWidget * parent = 0, const char * name = 0, const QStringList & args = QStringList() ); + + /** + * Standard KCModule constructor. Use KGlobal::config() + */ + KCAutoConfigModule( KInstance * instance, QWidget * parent = 0, const QStringList & args = QStringList() ); + + /** + * Constructor. + * @param config the KConfig to use + * @param instance KInstance object for this KCM + * @param parent parent widget + * @param args special arguments for this KCM + * + * @todo document what the args mean (inherited from KCModule?) + */ + KCAutoConfigModule(KConfig* config, KInstance * instance, QWidget * parent = 0, const QStringList & args = QStringList() ); + + /** + * Constructor, much like the one above, except with + * no instance and with a name. + * @param config the KConfig to use + * @param parent parent widget + * @param name name of the object + * @param args special arguments for this KCM + */ + KCAutoConfigModule(KConfig* config, QWidget * parent = 0, const char * name=0 , const QStringList & args = QStringList() ); + + + ~KCAutoConfigModule(); + + /** + * Set the main widget. @p widget will be lay out to take all available place in the module. + * @p widget must have this module as parent. + * + * This method automatically call KAutoConfig::addWidget() and KAutoConfig::retrieveSettings() + * + * @param widget the widget to place on the page and to add in the KAutoConfig + * @param group the name of the group where settings are stored in the config file + */ + void setMainWidget(QWidget *widget, const QString& group); + + /** + * @brief a reference to the KAutoConfig + * + * You can add or remove manually some widget from the KAutoWidget. + * If you choose to don't add the main widget with setMainWidget() , you need + * to call KAutoConfig::retrieveSettings(true) yourself + * + * @return a reference to the KAutoConfig + */ + KAutoConfig *autoConfig(); + + /** + * Reload the config from the configfile. + * + * You can also reimplement this method, but you should always call the parent KCModule::load() + * be sure you know what you are doing + */ + virtual void load(); + + /** + * Save the config to the configfile. + * + * You can also reimplement this method, but you should always call the parent KCModule::save() + * be sure you know what you are doing + */ + virtual void save(); + + /** + * Reload the default config + * + * You can also reimplement this method, but you should always call the parent KCModule::defaults() + * be sure you know what you are doing + */ + virtual void defaults(); + + + protected slots: + /** + * Some setting was modified, updates buttons + */ + virtual void slotWidgetModified(); + + private: + class KCAutoConfigModulePrivate; + KCAutoConfigModulePrivate * d; +}; + + +#endif diff --git a/kopete/libkopete/knotification.cpp b/kopete/libkopete/knotification.cpp new file mode 100644 index 00000000..3749c21c --- /dev/null +++ b/kopete/libkopete/knotification.cpp @@ -0,0 +1,533 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005 Olivier Goffart <ogoffart @ kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include "knotification.h" + +#include <kdebug.h> +#include <kapplication.h> +#include <knotifyclient.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kconfig.h> +#include <kpassivepopup.h> +#include <kactivelabel.h> +#include <kprocess.h> +#include <kdialog.h> +#include <kmacroexpander.h> +#include <kwin.h> + + +#include <qvbox.h> +#include <dcopclient.h> +#include <qcstring.h> +#include <qguardedptr.h> +#include <qstylesheet.h> +#include <qlabel.h> +#include <qtimer.h> +#include <qtabwidget.h> + + + +//TODO, make the KNotification aware of the systemtray. +#include "kopeteuiglobal.h" +static WId checkWinId( const QString &/*appName*/, WId senderWinId ) +{ + if(senderWinId==0) + senderWinId=Kopete::UI::Global::sysTrayWId(); + + return senderWinId; +} + + +struct KNotification::Private +{ + QWidget *widget; + QString text; + QStringList actions; + int level; +}; + +KNotification::KNotification(QObject *parent) : + QObject(parent) , d(new Private) +{ + m_linkClicked = false; +} + +KNotification::~KNotification() +{ + delete d; +} + + +void KNotification::notifyByExecute(const QString &command, const QString& event, + const QString& fromApp, const QString& text, + int winId, int eventId) +{ + if (!command.isEmpty()) + { + // kdDebug() << "executing command '" << command << "'" << endl; + QMap<QChar,QString> subst; + subst.insert( 'e', event ); + subst.insert( 'a', fromApp ); + subst.insert( 's', text ); + subst.insert( 'w', QString::number( winId )); + subst.insert( 'i', QString::number( eventId )); + QString execLine = KMacroExpander::expandMacrosShellQuote( command, subst ); + if ( execLine.isEmpty() ) + execLine = command; // fallback + + KProcess p; + p.setUseShell(true); + p << execLine; + p.start(KProcess::DontCare); +// return true; + } + //return false; +} + + +void KNotification::notifyByMessagebox() +{ + // ignore empty messages + if ( d->text.isEmpty() ) + return; + + QString action=d->actions[0]; + WId winId=d->widget ? d->widget->topLevelWidget()->winId() : 0; + + if( action.isEmpty()) + { + // display message box for specified event level + switch( d->level ) + { + default: + case KNotifyClient::Notification: + KMessageBox::informationWId( winId, d->text, i18n( "Notification" ) ); + break; + case KNotifyClient::Warning: + KMessageBox::sorryWId( winId, d->text, i18n( "Warning" ) ); + break; + case KNotifyClient::Error: + KMessageBox::errorWId( winId, d->text, i18n( "Error" ) ); + break; + case KNotifyClient::Catastrophe: + KMessageBox::errorWId( winId, d->text, i18n( "Fatal" ) ); + break; + } + } + else + { //we may show the specific action button + int result=0; + QGuardedPtr<KNotification> _this=this; //this can be deleted + switch( d->level ) + { + default: + case KNotifyClient::Notification: + result = KMessageBox::questionYesNo(d->widget, d->text, i18n( "Notification" ), action, KStdGuiItem::cancel(), QString::null, false ); + break; + case KNotifyClient::Warning: + result = KMessageBox::warningYesNo( d->widget, d->text, i18n( "Warning" ), action, KStdGuiItem::cancel(), QString::null, false ); + break; + case KNotifyClient::Error: + result = KMessageBox::warningYesNo( d->widget, d->text, i18n( "Error" ), action, KStdGuiItem::cancel(), QString::null, false ); + break; + case KNotifyClient::Catastrophe: + result = KMessageBox::warningYesNo( d->widget, d->text, i18n( "Fatal" ), action, KStdGuiItem::cancel(), QString::null, false ); + break; + } + if(result==KMessageBox::Yes && _this) + { + activate(0); + } + } +} + + + +void KNotification::notifyByPassivePopup(const QPixmap &pix ) +{ + QString appName = QString::fromAscii( KNotifyClient::instance()->instanceName() ); + KIconLoader iconLoader( appName ); + KConfig eventsFile( QString::fromAscii( KNotifyClient::instance()->instanceName()+"/eventsrc" ), true, false, "data"); + KConfigGroup config( &eventsFile, "!Global!" ); + QString iconName = config.readEntry( "IconName", appName ); + QPixmap icon = iconLoader.loadIcon( iconName, KIcon::Small ); + QString title = config.readEntry( "Comment", appName ); + //KPassivePopup::message(title, text, icon, senderWinId); + + WId winId=d->widget ? d->widget->topLevelWidget()->winId() : 0; + + KPassivePopup *pop = new KPassivePopup( checkWinId(appName, winId) ); + QObject::connect(this, SIGNAL(closed()), pop, SLOT(deleteLater())); + + QVBox *vb = pop->standardView( title, pix.isNull() ? d->text: QString::null , icon ); + QVBox *vb2=vb; + + if(!pix.isNull()) + { + QHBox *hb = new QHBox(vb); + hb->setSpacing(KDialog::spacingHint()); + QLabel *pil=new QLabel(hb); + pil->setPixmap(pix); + pil->setScaledContents(true); + if(pix.height() > 80 && pix.height() > pix.width() ) + { + pil->setMaximumHeight(80); + pil->setMaximumWidth(80*pix.width()/pix.height()); + } + else if(pix.width() > 80 && pix.height() <= pix.width()) + { + pil->setMaximumWidth(80); + pil->setMaximumHeight(80*pix.height()/pix.width()); + } + vb=new QVBox(hb); + QLabel *msg = new QLabel( d->text, vb, "msg_label" ); + msg->setAlignment( AlignLeft ); + } + + + if ( !d->actions.isEmpty() ) + { + QString linkCode=QString::fromLatin1("<p align=\"right\">"); + int i=0; + for ( QStringList::ConstIterator it = d->actions.begin() ; it != d->actions.end(); ++it ) + { + i++; + linkCode+=QString::fromLatin1(" <a href=\"%1\">%2</a> ").arg( QString::number(i) , QStyleSheet::escape(*it) ); + } + linkCode+=QString::fromLatin1("</p>"); + KActiveLabel *link = new KActiveLabel(linkCode , vb ); + //link->setAlignment( AlignRight ); + QObject::disconnect(link, SIGNAL(linkClicked(const QString &)), link, SLOT(openLink(const QString &))); + QObject::connect(link, SIGNAL(linkClicked(const QString &)), this, SLOT(slotPopupLinkClicked(const QString &))); + QObject::connect(link, SIGNAL(linkClicked(const QString &)), pop, SLOT(hide())); + } + + pop->setAutoDelete( true ); + //pop->setTimeout(-1); + + pop->setView( vb2 ); + pop->show(); + +} + +void KNotification::slotPopupLinkClicked(const QString &adr) +{ + m_linkClicked = true; + unsigned int action=adr.toUInt(); + if(action==0) + return; + + activate(action); + + // since we've hidden the message (KNotification::notifyByPassivePopup(const QPixmap &pix )) + // we must now schedule overselves for deletion + close(); +} + +void KNotification::activate(unsigned int action) +{ + if(action==0) + emit activated(); + + emit activated(action); + deleteLater(); +} + + +void KNotification::close() +{ + // if the user hasn't clicked the link, and if we got here, it means the dialog closed + // and we were ignored + if (!m_linkClicked) + { + emit ignored(); + } + + emit closed(); + deleteLater(); +} + + +void KNotification::raiseWidget() +{ + if(!d->widget) + return; + + raiseWidget(d->widget); +} + + +void KNotification::raiseWidget(QWidget *w) +{ + //TODO this funciton is far from finished. + if(w->isTopLevel()) + { + w->raise(); + KWin::activateWindow( w->winId() ); + } + else + { + QWidget *pw=w->parentWidget(); + raiseWidget(pw); + + if( QTabWidget *tab_widget=dynamic_cast<QTabWidget*>(pw)) + { + tab_widget->showPage(w); + } + } +} + + + + + +KNotification *KNotification::event( const QString& message , const QString& text, + const QPixmap& pixmap, QWidget *widget, + const QStringList &actions, unsigned int flags) +{ + /* NOTE: this function still use the KNotifyClient, + * in the future (KDE4) all the function of the knotifyclient will be moved there. + * Some code here is derived from the old KNotify deamon + */ + + int level=KNotifyClient::Default; + QString sound; + QString file; + QString commandline; + + // get config file + KConfig eventsFile( QString::fromAscii( KNotifyClient::instance()->instanceName()+"/eventsrc" ), true, false, "data"); + eventsFile.setGroup(message); + + KConfig configFile( QString::fromAscii( KNotifyClient::instance()->instanceName()+".eventsrc" ), true, false); + configFile.setGroup(message); + + int present=KNotifyClient::getPresentation(message); + if(present==-1) + present=KNotifyClient::getDefaultPresentation(message); + if(present==-1) + present=0; + + // get sound file name + if( present & KNotifyClient::Sound ) { + QString theSound = configFile.readPathEntry( "soundfile" ); + if ( theSound.isEmpty() ) + theSound = eventsFile.readPathEntry( "default_sound" ); + if ( !theSound.isEmpty() ) + sound = theSound; + } + + // get log file name + if( present & KNotifyClient::Logfile ) { + QString theFile = configFile.readPathEntry( "logfile" ); + if ( theFile.isEmpty() ) + theFile = eventsFile.readPathEntry( "default_logfile" ); + if ( !theFile.isEmpty() ) + file = theFile; + } + + // get default event level + if( present & KNotifyClient::Messagebox ) + level = eventsFile.readNumEntry( "level", 0 ); + + // get command line + if (present & KNotifyClient::Execute ) { + commandline = configFile.readPathEntry( "commandline" ); + if ( commandline.isEmpty() ) + commandline = eventsFile.readPathEntry( "default_commandline" ); + } + + return userEvent( text, pixmap, widget, actions, present , level, sound, file, commandline, flags ); +} + +KNotification *KNotification::userEvent( const QString& text, const QPixmap& pixmap, QWidget *widget, + QStringList actions,int present, int level, const QString &sound, const QString &file, + const QString &commandline, unsigned int flags) +{ + + /* NOTE: this function still use the KNotifyClient, + * in the futur (KDE4) all the function of the knotifyclient will be moved there. + * Some code of this function fome from the old KNotify deamon + */ + + + KNotification *notify=new KNotification(widget); + notify->d->widget=widget; + notify->d->text=text; + notify->d->actions=actions; + notify->d->level=level; + WId winId=widget ? widget->topLevelWidget()->winId() : 0; + + + //we will catch some event that will not be fired by the old deamon + + + //we remove presentation that has been already be played, and we fire the event in the old way + + + KNotifyClient::userEvent(winId,text,present & ~( KNotifyClient::PassivePopup|KNotifyClient::Messagebox|KNotifyClient::Execute),level,sound,file); + + + if ( present & KNotifyClient::PassivePopup ) + { + notify->notifyByPassivePopup( pixmap ); + } + if ( present & KNotifyClient::Messagebox ) + { + QTimer::singleShot(0,notify,SLOT(notifyByMessagebox())); + } + else //not a message box (because closing the event when a message box is there is suicide) + if(flags & CloseOnTimeout) + { + QTimer::singleShot(6*1000, notify, SLOT(close())); + } + if ( present & KNotifyClient::Execute ) + { + QString appname = QString::fromAscii( KNotifyClient::instance()->instanceName() ); + notify->notifyByExecute(commandline, QString::null,appname,text, winId, 0 ); + } + + return notify; + +} + + + +/* This code is there before i find a great way to perform context-dependent notifications + * in a way independent of kopete. + * i'm in fact still using the Will's old code. + */ + + +#include "kopeteeventpresentation.h" +#include "kopetegroup.h" +#include "kopetenotifydataobject.h" +#include "kopetenotifyevent.h" +#include "kopetemetacontact.h" +#include "kopeteuiglobal.h" +#include <qimage.h> + + +static KNotification *performCustomNotifications( QWidget *widget, Kopete::MetaContact * mc, const QString &message, bool& suppress) +{ + KNotification *n=0L; + //kdDebug( 14010 ) << k_funcinfo << endl; + if ( suppress ) + return n; + + // Anything, including the MC itself, may set suppress and prevent further notifications + + /* This is a really ugly piece of logic now. The idea is to check for notifications + * first on the metacontact, then on each of its groups, until something suppresses + * any further notifications. + * So on the first run round this loop, dataObj points to the metacontact, and on subsequent + * iterations it points to one of the contact's groups. The metacontact pointer is maintained + * so that if a group has a chat notification set for this event, we can call execute() on the MC. + */ + + bool checkingMetaContact = true; + Kopete::NotifyDataObject * dataObj = mc; + do { + QString sound; + QString text; + + if ( dataObj ) + { + Kopete::NotifyEvent *evt = dataObj->notifyEvent( message ); + if ( evt ) + { + suppress = evt->suppressCommon(); + int present = 0; + // sound + Kopete::EventPresentation *pres = evt->presentation( Kopete::EventPresentation::Sound ); + if ( pres && pres->enabled() ) + { + present = present | KNotifyClient::Sound; + sound = pres->content(); + evt->firePresentation( Kopete::EventPresentation::Sound ); + } + // message + if ( ( pres = evt->presentation( Kopete::EventPresentation::Message ) ) + && pres->enabled() ) + { + present = present | KNotifyClient::PassivePopup; + text = pres->content(); + evt->firePresentation( Kopete::EventPresentation::Message ); + } + // chat + if ( ( pres = evt->presentation( Kopete::EventPresentation::Chat ) ) + && pres->enabled() ) + { + mc->execute(); + evt->firePresentation( Kopete::EventPresentation::Chat ); + } + // fire the event + n=KNotification::userEvent( text, mc->photo(), widget, QStringList() , present, 0, sound, QString::null, QString::null , KNotification::CloseOnTimeout); + } + } + + if ( mc ) + { + if ( checkingMetaContact ) + { + // only executed on first iteration + checkingMetaContact = false; + dataObj = mc->groups().first(); + } + else + dataObj = mc->groups().next(); + } + } + while ( dataObj && !suppress ); + return n; +} + + + + +KNotification *KNotification::event( Kopete::MetaContact *mc, const QString& message , + const QString& text, const QPixmap& pixmap, QWidget *widget, + const QStringList &actions, unsigned int flags) +{ + if (message.isEmpty()) return 0; + + bool suppress = false; + KNotification *n=performCustomNotifications( widget, mc, message, suppress); + + if ( suppress ) + { + //kdDebug( 14000 ) << "suppressing common notifications" << endl; + return n; // custom notifications don't create a single unique id + } + else + { + //kdDebug( 14000 ) << "carrying out common notifications" << endl; + return event( message, text, pixmap, widget , actions, flags); + } +} + + + + + +#include "knotification.moc" + + + diff --git a/kopete/libkopete/knotification.h b/kopete/libkopete/knotification.h new file mode 100644 index 00000000..b017b7c0 --- /dev/null +++ b/kopete/libkopete/knotification.h @@ -0,0 +1,217 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005 Olivier Goffart <ogoffart @ kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + + +#ifndef KNOTIFICATION_H +#define KNOTIFICATION_H + + +#include <qpixmap.h> +#include <qobject.h> +#include <qstringlist.h> +#include "kopete_export.h" + +class QWidget; +namespace Kopete { class MetaContact; } + +/** + * KNotification is used to notify some event to the user. + * + * It covers severals kind of notifications + * + * @li Interface feedback events: + * For notifying the user that he/she just performed an operation, like maximizing a + * window. This allows us to play sounds when a dialog appears. + * This is an instant notification. It ends automatically after a small timeout + * + * @li complex notifications: + * Notify when one received a new message, or when something important happened + * the user has to know. This notification has a start and a end. It start when + * the event actually occurs, and finish when the message is acknowledged. + * + * + * use the static function event() to fire an event + * + * the returned KNotification pointer may be used to connect signals or slots + * + * @author Olivier Goffart <ogoffart @ kde.org> + */ +class KOPETE_EXPORT KNotification : public QObject +{ + Q_OBJECT +public: + + enum NotificationFlags + { + /** + * When the notification is activated, raise the notification's widget. + * + * This will change the desktop, raise the window, and switch to the tab. + */ + RaiseWidgetOnActivation=0x01, + + /** + * The notification will be automatically closed after a timeout. + */ + CloseOnTimeout=0x02, + /** + * The notification will be automatically closed if the widget() becomes + * activated. + * + * If the widget is already activated when the notification occurs, the + * notification will be closed after a small timeout. + */ + CloseWhenWidgetActivated=0x03 + }; + + + ~KNotification(); + + /** + * @brief the widget associated to the notification + * + * If the widget is destroyed, the notification will be automatically canceled. + * If the widget is activated, the notificaiton will be automatically closed if the flags said that + * + * When the notification is activated, the widget might be raised. + * Depending of the configuration, the taskbar entry of the window containing the widget may blink. + */ + QWidget *widget(); + + signals: + /** + * Emit only when the default activation has occured + */ + void activated(); + /** + * Emit when an action has been activated. + * @param action will be 0 is the default aciton was activated, or any actiton id + */ + void activated(unsigned int action); + + /** + * Emit when the notification is closed. Both if it's activated or just ignored + */ + void closed(); + + /** + * The notification has been ignored + */ + void ignored(); + +public slots: + /** + * @brief Active the action specified action + * If the action is zero, then the default action is activated + */ + void activate(unsigned int action=0); + + /** + * close the notification without activate it. + * + * This will delete the notification + */ + void close(); + + /** + * @brief Raise the widget. + * This will change the desktop, activate the window, and the tab if needed. + */ + void raiseWidget(); + + + +private: + struct Private; + Private *d; + KNotification(QObject *parent=0L); + /** + * recursive function that raise the widget. @p w + * + * @see raiseWidget() + */ + static void raiseWidget(QWidget *w); + + bool m_linkClicked; + +private slots: + void notifyByMessagebox(); + void notifyByPassivePopup(const QPixmap &pix); + void notifyByExecute(const QString &command, const QString& event,const QString& fromApp, const QString& text, int winId, int eventId); + void slotPopupLinkClicked(const QString &); + + +public: + /** + * @brief emit an event + * + * A popup may be showed, a sound may be played, depending the config. + * + * return a KNotification . You may use that pointer to connect some signals or slot. + * the pointer is automatically deleted when the event is closed. + * + * Make sure you use one of the CloseOnTimeOut or CloseWhenWidgetActivated, if not, + * you have to close yourself the notification. + * + * @note the text is shown in a QLabel, you should make sure to escape the html is needed. + * + * @param eventId is the name of the event + * @param text is the text of the notification to show in the popup. + * @param pixmap is a picture which may be shown in the popup. + * @param widget is a widget where the notification reports to + * @param actions is a list of action texts. + * @param flags is a bitmask of NotificationsFlags + */ + static KNotification *event( const QString& eventId , const QString& text=QString::null, + const QPixmap& pixmap=QPixmap(), QWidget *widget=0L, + const QStringList &actions=QStringList(), unsigned int flags=CloseOnTimeout); + + + /** + * @brief emit a custom event + * + * @param text is the text of the notification to show in the popup. + * @param pixmap is a picture which may be shown in the popup + * @param widget is a widget where the notification raports to + * @param actions is a list of actions text. + * @param present The presentation method of the event + * @param level The error message level + * @param sound The sound to play if selected with @p present + * @param file The log file to append the message to if selected with @p parent + * @param commandLine the command line to run if selected with @p parent + * @param flags Indicates the way in which the notification should be handled + */ + static KNotification *userEvent( const QString& text, const QPixmap& pixmap, + QWidget *widget, QStringList actions,int present, int level, + const QString &sound, const QString &file, + const QString &commandLine, unsigned int flags); + + + + /** + * @todo find a proper way to do context-dependent notifications + */ + static KNotification *event( Kopete::MetaContact *mc, const QString& eventId , const QString& text=QString::null, + const QPixmap& pixmap=QPixmap(), QWidget *widget=0L, + const QStringList &actions=QStringList(),unsigned int flags=CloseOnTimeout); + +}; + + + +#endif diff --git a/kopete/libkopete/kopete.kcfg b/kopete/libkopete/kopete.kcfg new file mode 100644 index 00000000..59573689 --- /dev/null +++ b/kopete/libkopete/kopete.kcfg @@ -0,0 +1,12 @@ +<!DOCTYPE kcfg SYSTEM "http://www.kde.org/standards/kcfg/1.0/kcfg.dtd"> +<kcfg> + <kcfgfile name="kopete" /> + <group name="GlobalIdentity" > + <entry key="enableGlobalIdentity" type="Bool" name="EnableGlobalIdentity" > + <label>Enable the global identity feature</label> + <whatsthis>When enabled, this allows you to set your data in a central place. All your IM accounts will use this global data. +</whatsthis> + <default>false</default> + </entry> + </group> +</kcfg> diff --git a/kopete/libkopete/kopete_export.h b/kopete/libkopete/kopete_export.h new file mode 100644 index 00000000..df184b57 --- /dev/null +++ b/kopete/libkopete/kopete_export.h @@ -0,0 +1,30 @@ +/* + Kopete Export macors + + Copyright (c) 2004 by Dirk Mueller <mueller@kde.org> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_EXPORT_H +#define KOPETE_EXPORT_H + +#include <kdemacros.h> +#include <kdeversion.h> + +#if KDE_IS_VERSION(3,3,2) +#define KOPETE_EXPORT KDE_EXPORT +#else +#define KOPETE_EXPORT +#endif + +#endif diff --git a/kopete/libkopete/kopeteaccount.cpp b/kopete/libkopete/kopeteaccount.cpp new file mode 100644 index 00000000..52bb26bc --- /dev/null +++ b/kopete/libkopete/kopeteaccount.cpp @@ -0,0 +1,581 @@ +/* + kopeteaccount.cpp - Kopete Account + + Copyright (c) 2003-2005 by Olivier Goffart <ogoffart@ kde.org> + Copyright (c) 2003-2004 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <qapplication.h> +#include <qtimer.h> + +#include <kconfig.h> +#include <kdebug.h> +#include <kdeversion.h> +#include <kdialogbase.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kiconeffect.h> +#include <kaction.h> +#include <kpopupmenu.h> +#include <kmessagebox.h> +#include <knotifyclient.h> + +#include "kabcpersistence.h" +#include "kopetecontactlist.h" +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" +#include "kopetecontact.h" +#include "kopetemetacontact.h" +#include "kopeteprotocol.h" +#include "kopetepluginmanager.h" +#include "kopetegroup.h" +#include "kopeteprefs.h" +#include "kopeteutils.h" +#include "kopeteuiglobal.h" +#include "kopeteblacklister.h" +#include "kopeteonlinestatusmanager.h" +#include "editaccountwidget.h" + +namespace Kopete +{ + + +class Account::Private +{ +public: + Private( Protocol *protocol, const QString &accountId ) + : protocol( protocol ), id( accountId ) + , excludeconnect( true ), priority( 0 ), myself( 0 ) + , suppressStatusTimer( 0 ), suppressStatusNotification( false ) + , blackList( new Kopete::BlackLister( protocol->pluginId(), accountId ) ) + , connectionTry(0) + { } + + + ~Private() { delete blackList; } + + Protocol *protocol; + QString id; + QString accountLabel; + bool excludeconnect; + uint priority; + QDict<Contact> contacts; + QColor color; + Contact *myself; + QTimer suppressStatusTimer; + bool suppressStatusNotification; + Kopete::BlackLister *blackList; + KConfigGroup *configGroup; + uint connectionTry; + QString customIcon; + Kopete::OnlineStatus restoreStatus; + QString restoreMessage; +}; + +Account::Account( Protocol *parent, const QString &accountId, const char *name ) + : QObject( parent, name ), d( new Private( parent, accountId ) ) +{ + d->configGroup=new KConfigGroup(KGlobal::config(), QString::fromLatin1( "Account_%1_%2" ).arg( d->protocol->pluginId(), d->id )); + + d->excludeconnect = d->configGroup->readBoolEntry( "ExcludeConnect", false ); + d->color = d->configGroup->readColorEntry( "Color", &d->color ); + d->customIcon = d->configGroup->readEntry( "Icon", QString() ); + d->priority = d->configGroup->readNumEntry( "Priority", 0 ); + + d->restoreStatus = Kopete::OnlineStatus::Online; + d->restoreMessage = ""; + + QObject::connect( &d->suppressStatusTimer, SIGNAL( timeout() ), + this, SLOT( slotStopSuppression() ) ); +} + +Account::~Account() +{ + d->contacts.remove( d->myself->contactId() ); + + // Delete all registered child contacts first + while ( !d->contacts.isEmpty() ) + delete *QDictIterator<Contact>( d->contacts ); + + kdDebug( 14010 ) << k_funcinfo << " account '" << d->id << "' about to emit accountDestroyed " << endl; + emit accountDestroyed(this); + + delete d->myself; + delete d->configGroup; + delete d; +} + +void Account::reconnect() +{ + kdDebug( 14010 ) << k_funcinfo << "account " << d->id << " restoreStatus " << d->restoreStatus.status() << " restoreMessage " << d->restoreMessage << endl; + setOnlineStatus( d->restoreStatus, d->restoreMessage ); +} + +void Account::disconnected( DisconnectReason reason ) +{ + kdDebug( 14010 ) << k_funcinfo << reason << endl; + //reconnect if needed + if(reason == BadPassword ) + { + QTimer::singleShot(0, this, SLOT(reconnect())); + } + else if ( KopetePrefs::prefs()->reconnectOnDisconnect() == true && reason > Manual ) + { + d->connectionTry++; + //use a timer to allow the plugins to clean up after return + if(d->connectionTry < 3) + QTimer::singleShot(10000, this, SLOT(reconnect())); // wait 10 seconds before reconnect + } + if(reason== OtherClient) + { + Kopete::Utils::notifyConnectionLost(this, i18n("You have been disconnected"), i18n( "You have connected from another client or computer to the account '%1'" ).arg(d->id), i18n("Most proprietary Instant Messaging services do not allow you to connect from more than one location. Check that nobody is using your account without your permission. If you need a service that supports connection from various locations at the same time, use the Jabber protocol.")); + } +} + +Protocol *Account::protocol() const +{ + return d->protocol; +} + +QString Account::accountId() const +{ + return d->id; +} + +const QColor Account::color() const +{ + return d->color; +} + +void Account::setColor( const QColor &color ) +{ + d->color = color; + if ( d->color.isValid() ) + d->configGroup->writeEntry( "Color", d->color ); + else + d->configGroup->deleteEntry( "Color" ); + emit colorChanged( color ); +} + +void Account::setPriority( uint priority ) +{ + d->priority = priority; + d->configGroup->writeEntry( "Priority", d->priority ); +} + +uint Account::priority() const +{ + return d->priority; +} + + +QPixmap Account::accountIcon(const int size) const +{ + QString icon= d->customIcon.isEmpty() ? d->protocol->pluginIcon() : d->customIcon; + + // FIXME: this code is duplicated with OnlineStatus, can we merge it somehow? + QPixmap base = KGlobal::instance()->iconLoader()->loadIcon( + icon, KIcon::Small, size ); + + if ( d->color.isValid() ) + { + KIconEffect effect; + base = effect.apply( base, KIconEffect::Colorize, 1, d->color, 0); + } + + if ( size > 0 && base.width() != size ) + { + base = QPixmap( base.convertToImage().smoothScale( size, size ) ); + } + + return base; +} + +KConfigGroup* Kopete::Account::configGroup() const +{ + return d->configGroup; +} + +void Account::setAccountLabel( const QString &label ) +{ + d->accountLabel = label; +} + +QString Account::accountLabel() const +{ + if( d->accountLabel.isNull() ) + return d->id; + return d->accountLabel; +} + +void Account::setExcludeConnect( bool b ) +{ + d->excludeconnect = b; + d->configGroup->writeEntry( "ExcludeConnect", d->excludeconnect ); +} + +bool Account::excludeConnect() const +{ + return d->excludeconnect; +} + +void Account::registerContact( Contact *c ) +{ + d->contacts.insert( c->contactId(), c ); + QObject::connect( c, SIGNAL( contactDestroyed( Kopete::Contact * ) ), + SLOT( contactDestroyed( Kopete::Contact * ) ) ); +} + +void Account::contactDestroyed( Contact *c ) +{ + d->contacts.remove( c->contactId() ); +} + + +const QDict<Contact>& Account::contacts() +{ + return d->contacts; +} + + +Kopete::MetaContact* Account::addContact( const QString &contactId, const QString &displayName , Group *group, AddMode mode ) +{ + + if ( contactId == d->myself->contactId() ) + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Error, + i18n("You are not allowed to add yourself to the contact list. The addition of \"%1\" to account \"%2\" will not take place.").arg(contactId,accountId()), i18n("Error Creating Contact") + ); + return false; + } + + bool isTemporary = mode == Temporary; + + Contact *c = d->contacts[ contactId ]; + + if(!group) + group=Group::topLevel(); + + if ( c && c->metaContact() ) + { + if ( c->metaContact()->isTemporary() && !isTemporary ) + { + kdDebug( 14010 ) << k_funcinfo << " You are trying to add an existing temporary contact. Just add it on the list" << endl; + + c->metaContact()->setTemporary(false, group ); + ContactList::self()->addMetaContact(c->metaContact()); + } + else + { + // should we here add the contact to the parentContact if any? + kdDebug( 14010 ) << k_funcinfo << "Contact already exists" << endl; + } + return c->metaContact(); + } + + MetaContact *parentContact = new MetaContact(); + if(!displayName.isEmpty()) + parentContact->setDisplayName( displayName ); + + //Set it as a temporary contact if requested + if ( isTemporary ) + parentContact->setTemporary( true ); + else + parentContact->addToGroup( group ); + + if ( c ) + { + c->setMetaContact( parentContact ); + if ( mode == ChangeKABC ) + { + kdDebug( 14010 ) << k_funcinfo << " changing KABC" << endl; + KABCPersistence::self()->write( parentContact ); + } + } + else + { + if ( !createContact( contactId, parentContact ) ) + { + delete parentContact; + return 0L; + } + } + + ContactList::self()->addMetaContact( parentContact ); + return parentContact; +} + +bool Account::addContact(const QString &contactId , MetaContact *parent, AddMode mode ) +{ + if ( contactId == myself()->contactId() ) + { + KMessageBox::error( Kopete::UI::Global::mainWidget(), + i18n("You are not allowed to add yourself to the contact list. The addition of \"%1\" to account \"%2\" will not take place.").arg(contactId,accountId()), i18n("Error Creating Contact") + ); + return 0L; + } + + bool isTemporary= parent->isTemporary(); + Contact *c = d->contacts[ contactId ]; + if ( c && c->metaContact() ) + { + if ( c->metaContact()->isTemporary() && !isTemporary ) + { + kdDebug( 14010 ) << + "Account::addContact: You are trying to add an existing temporary contact. Just add it on the list" << endl; + + //setMetaContact ill take care about the deletion of the old contact + c->setMetaContact(parent); + return true; + } + else + { + // should we here add the contact to the parentContact if any? + kdDebug( 14010 ) << "Account::addContact: Contact already exists" << endl; + } + return false; //(the contact is not in the correct metacontact, so false) + } + + bool success = createContact(contactId, parent); + + if ( success && mode == ChangeKABC ) + { + kdDebug( 14010 ) << k_funcinfo << " changing KABC" << endl; + KABCPersistence::self()->write( parent ); + } + + return success; +} + +KActionMenu * Account::actionMenu() +{ + //default implementation + KActionMenu *menu = new KActionMenu( accountId(), myself()->onlineStatus().iconFor( this ), this ); + QString nick = myself()->property( Kopete::Global::Properties::self()->nickName()).value().toString(); + + menu->popupMenu()->insertTitle( myself()->onlineStatus().iconFor( myself() ), + nick.isNull() ? accountLabel() : i18n( "%2 <%1>" ).arg( accountLabel(), nick ) + ); + + OnlineStatusManager::self()->createAccountStatusActions(this, menu); + menu->popupMenu()->insertSeparator(); + menu->insert( new KAction ( i18n( "Properties" ), 0, this, SLOT( editAccount() ), menu, "actionAccountProperties" ) ); + + return menu; +} + + +bool Account::isConnected() const +{ + return myself() && myself()->isOnline(); +} + +bool Account::isAway() const +{ + return d->myself && ( d->myself->onlineStatus().status() == Kopete::OnlineStatus::Away ); +} + +Contact * Account::myself() const +{ + return d->myself; +} + +void Account::setMyself( Contact *myself ) +{ + bool wasConnected = isConnected(); + + if ( d->myself ) + { + QObject::disconnect( d->myself, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT( slotOnlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) ); + QObject::disconnect( d->myself, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), + this, SLOT( slotContactPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ) ); + } + + d->myself = myself; + +// d->contacts.remove( myself->contactId() ); + + QObject::connect( d->myself, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT( slotOnlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) ); + QObject::connect( d->myself, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), + this, SLOT( slotContactPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ) ); + + if ( isConnected() != wasConnected ) + emit isConnectedChanged(); +} + +void Account::slotOnlineStatusChanged( Contact * /* contact */, + const OnlineStatus &newStatus, const OnlineStatus &oldStatus ) +{ + bool wasOffline = !oldStatus.isDefinitelyOnline(); + bool isOffline = !newStatus.isDefinitelyOnline(); + + if ( wasOffline || newStatus.status() == OnlineStatus::Offline ) + { + // Wait for five seconds until we treat status notifications for contacts + // as unrelated to our own status change. + // Five seconds may seem like a long time, but just after your own + // connection it's basically neglectible, and depending on your own + // contact list's size, the protocol you are using, your internet + // connection's speed and your computer's speed you *will* need it. + d->suppressStatusNotification = true; + d->suppressStatusTimer.start( 5000, true ); + //the timer is also used to reset the d->connectionTry + } + + if ( !isOffline ) + { + d->restoreStatus = newStatus; + d->restoreMessage = myself()->property( Kopete::Global::Properties::self()->awayMessage() ).value().toString(); +// kdDebug( 14010 ) << k_funcinfo << "account " << d->id << " restoreStatus " << d->restoreStatus.status() << " restoreMessage " << d->restoreMessage << endl; + } + +/* kdDebug(14010) << k_funcinfo << "account " << d->id << " changed status. was " + << Kopete::OnlineStatus::statusTypeToString(oldStatus.status()) << ", is " + << Kopete::OnlineStatus::statusTypeToString(newStatus.status()) << endl;*/ + if ( wasOffline != isOffline ) + emit isConnectedChanged(); +} + +void Account::setAllContactsStatus( const Kopete::OnlineStatus &status ) +{ + d->suppressStatusNotification = true; + d->suppressStatusTimer.start( 5000, true ); + + for ( QDictIterator<Contact> it( d->contacts ); it.current(); ++it ) + if ( it.current() != d->myself ) + it.current()->setOnlineStatus( status ); +} + +void Account::slotContactPropertyChanged( Contact * /* contact */, + const QString &key, const QVariant &old, const QVariant &newVal ) +{ + if ( key == QString::fromLatin1("awayMessage") && old != newVal && isConnected() ) + { + d->restoreMessage = newVal.toString(); +// kdDebug( 14010 ) << k_funcinfo << "account " << d->id << " restoreMessage " << d->restoreMessage << endl; + } +} + +void Account::slotStopSuppression() +{ + d->suppressStatusNotification = false; + if(isConnected()) + d->connectionTry=0; +} + +bool Account::suppressStatusNotification() const +{ + return d->suppressStatusNotification; +} + +bool Account::removeAccount() +{ + //default implementation + return true; +} + + +BlackLister* Account::blackLister() +{ + return d->blackList; +} + +void Account::block( const QString &contactId ) +{ + d->blackList->addContact( contactId ); +} + +void Account::unblock( const QString &contactId ) +{ + d->blackList->removeContact( contactId ); +} + +bool Account::isBlocked( const QString &contactId ) +{ + return d->blackList->isBlocked( contactId ); +} + +void Account::editAccount(QWidget *parent) +{ + KDialogBase *editDialog = new KDialogBase( parent, "KopeteAccountConfig::editDialog", true, + i18n( "Edit Account" ), KDialogBase::Ok | KDialogBase::Cancel, + KDialogBase::Ok, true ); + + KopeteEditAccountWidget *m_accountWidget = protocol()->createEditAccountWidget( this, editDialog ); + if ( !m_accountWidget ) + return; + + // FIXME: Why the #### is EditAccountWidget not a QWidget?!? This sideways casting + // is braindead and error-prone. Looking at MSN the only reason I can see is + // because it allows direct subclassing of designer widgets. But what is + // wrong with embedding the designer widget in an empty QWidget instead? + // Also, if this REALLY has to be a pure class and not a widget, then the + // class should at least be renamed to EditAccountIface instead - Martijn + QWidget *w = dynamic_cast<QWidget *>( m_accountWidget ); + if ( !w ) + return; + + editDialog->setMainWidget( w ); + if ( editDialog->exec() == QDialog::Accepted ) + { + if( m_accountWidget->validateData() ) + m_accountWidget->apply(); + } + + editDialog->deleteLater(); +} + +void Account::setPluginData( Plugin* /*plugin*/, const QString &key, const QString &value ) +{ + configGroup()->writeEntry(key,value); +} + +QString Account::pluginData( Plugin* /*plugin*/, const QString &key ) const +{ + return configGroup()->readEntry(key); +} + +void Account::setAway(bool away, const QString& reason) +{ + setOnlineStatus( OnlineStatusManager::self()->onlineStatus(protocol() , away ? OnlineStatusManager::Away : OnlineStatusManager::Online) , reason ); +} + +void Account::setCustomIcon( const QString & i) +{ + d->customIcon = i; + if(!i.isEmpty()) + d->configGroup->writeEntry( "Icon", i ); + else + d->configGroup->deleteEntry( "Icon" ); + emit colorChanged( color() ); +} + +QString Account::customIcon() const +{ + return d->customIcon; +} + +void Account::virtual_hook( uint /*id*/, void* /*data*/) +{ +} + + + +} + + //END namespace Kopete + +#include "kopeteaccount.moc" diff --git a/kopete/libkopete/kopeteaccount.h b/kopete/libkopete/kopeteaccount.h new file mode 100644 index 00000000..f3c2d338 --- /dev/null +++ b/kopete/libkopete/kopeteaccount.h @@ -0,0 +1,554 @@ +/* + kopeteaccount.h - Kopete Account + + Copyright (c) 2003-2004 by Olivier Goffart <ogoffart@ tiscalinet.be> + Copyright (c) 2003-2004 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEACCOUNT_H +#define KOPETEACCOUNT_H + +#include "kopeteonlinestatus.h" + +#include "kopete_export.h" + +#include <qobject.h> +#include <qdict.h> + +class QDomNode; +class KActionMenu; +class KConfigGroup; + +namespace Kopete +{ +class Contact; +class Plugin; +class Protocol; +class MetaContact; +class Group; +class OnlineStatus; +class BlackLister; + +/** + * The Kopete::Account class handles one account. + * Each protocol should subclass this class in its own custom accounts class. + * There are few pure virtual method that the protocol must implement. Examples are: + * \li \ref connect() + * \li \ref disconnect() + * \li \ref createContact() + * + * If your account requires a password, derive from @ref PasswordedAccount instead of this class. + * + * The accountId is an @em constant unique id, which represents the login. + * The @ref myself() contact is one of the most important contacts, which represents + * the user tied to this account. You must create this contact in the contructor of your + * account and pass it to @ref setMyself(). + * + * All account data is saved to @ref KConfig. This includes the accountId, the autoconnect flag and + * the color. You can save more data using @ref configGroup() + * + * When you create a new account, you have to register it with the account manager by calling + * @ref AccountManager::registerAccount. + * + * @author Olivier Goffart <ogoffart@tiscalinet.be> + */ +class KOPETE_EXPORT Account : public QObject +{ + Q_OBJECT + + Q_ENUMS( AddMode ) + Q_PROPERTY( QString accountId READ accountId ) + Q_PROPERTY( bool excludeConnect READ excludeConnect WRITE setExcludeConnect ) + Q_PROPERTY( QColor color READ color WRITE setColor ) + Q_PROPERTY( QPixmap accountIcon READ accountIcon ) + Q_PROPERTY( bool isConnected READ isConnected ) + Q_PROPERTY( bool isAway READ isAway ) + Q_PROPERTY( bool suppressStatusNotification READ suppressStatusNotification ) + Q_PROPERTY( uint priority READ priority WRITE setPriority ) + +public: + /** + * \brief Describes how the account was disconnected + * + * Manual means that the disconnection was done by the user and no reconnection + * will take place. Any other value will reconnect the account on disconnection. + * The case where the password is wrong will be handled differently. + * @see @ref disconnected + */ + enum DisconnectReason { + OtherClient = -4, ///< connection went down because another client connected the same account + BadPassword = -3, ///< connection failed because password was incorrect + BadUserName = -2, ///< connection failed because user name was invalid / unknown + InvalidHost = -1, ///< connection failed because host is unreachable + Manual = 0, ///< the user disconnected normally + ConnectionReset = 1, ///< the connection was lost + Unknown = 99 ///< the reason for disconnection is unknown + }; + + /** + * @param parent the protocol for this account. The account is a child object of the + * protocol, so it will be automatically deleted when the protocol is. + * @param accountID the unique ID of this account. + * @param name the name of this QObject. + */ + Account(Protocol *parent, const QString &accountID, const char *name=0L); + ~Account(); + + /** + * \return the Protocol for this account + */ + Protocol *protocol() const ; + + /** + * \return the unique ID of this account used as the login + */ + QString accountId() const; + + /** + * \return The label of this account, for the GUI + */ + QString accountLabel() const; + + /** + * \brief Get the priority of this account. + * + * Used for sorting and determining the preferred account to message a contact. + */ + uint priority() const; + + /** + * \brief Set the priority of this account. + * + * @note This method is called by the UI, and should not be called elsewhere. + */ + void setPriority( uint priority ); + + /** + * \brief Set if the account should not log in automatically. + * + * This function can be used by the EditAccountPage. Kopete handles connection automatically. + * @sa @ref excludeConnect + */ + void setExcludeConnect(bool); + + /** + * \brief Get if the account should not log in. + * + * @return @c true if the account should not be connected when connectAll at startup, @c false otherwise. + */ + bool excludeConnect() const; + + /** + * \brief Get the color for this account. + * + * The color will be used to visually differentiate this account from other accounts on the + * same protocol. + * + * \return the user color for this account + */ + const QColor color() const; + + /** + * \brief Set the color for this account. + * + * This is called by Kopete's account config page; you don't have to set the color yourself. + * + * @sa @ref color() + */ + void setColor( const QColor &color); + + /** + * \brief Get the icon for this account. + * + * Generates an image of size @p size representing this account. The result is not cached. + * + * @param size the size of the icon. If the size is 0, the default size is used. + * @return the icon for this account, colored if needed + */ + QPixmap accountIcon( const int size = 0 ) const; + + /** + * \brief change the account icon. + * by default the icon of an account is the protocol one, but it may be overide it. + * Set QString::null to go back to the default (the protocol icon) + * + * this call will emit colorChanged() + */ + void setCustomIcon( const QString& ); + + /** + * \brief return the icon base + * This is the custom account icon set with setIcon. if this icon is null, then the protocol icon is used + * don't use this funciton to get the icon that need to be displayed, use accountIcon + */ + QString customIcon() const; + + + + + /** + * \brief Retrieve the 'myself' contact. + * + * \return a pointer to the Contact object for this account + * + * \see setMyself(). + */ + Contact * myself() const; + + /** + * @brief Return the menu for this account + * + * You have to reimplement this method to return the custom action menu which will + * be shown in the statusbar. It is the caller's responsibility to ensure the menu is deleted. + * + * The default implementation provides a generic menu, with actions generated from the protocol's + * registered statuses, and an action to show the account's settings dialog. + * + * You should call the default implementation from your reimplementation, and add more actions + * you require to the resulting action menu. + * + * @see OnlineStatusManager::registerOnlineStatus + */ + virtual KActionMenu* actionMenu() ; + + /** + * @brief Retrieve the list of contacts for this account + * + * The list is guaranteed to contain only contacts for this account, + * so you can safely use static_cast to your own derived contact class + * if needed. + */ + const QDict<Contact>& contacts(); + + /** + * Indicates whether or not we should suppress status notifications + * for contacts belonging to this account. + * + * This is used when we just connected or disconnected, and every contact has their initial + * status set. + * + * @return @c true if notifications should not be used, @c false otherwise + */ + bool suppressStatusNotification() const; + + /** + * \brief Describes what should be done when the contact is added to a metacontact + * @sa @ref addContact() + */ + enum AddMode { + ChangeKABC = 0, ///< The KDE Address book may be updated + DontChangeKABC = 1, ///< The KDE Address book will not be changed + Temporary = 2 ///< The contact will not be added on the contactlist + }; + + /** + * \brief Create a contact (creating a new metacontact if necessary) + * + * If a contact for this account with ID @p contactId is not already on the contact list, + * a new contact with that ID is created, and added to a new metacontact. + * + * If @p mode is @c ChangeKABC, MetaContact::updateKABC will be called on the resulting metacontact. + * If @p mode is @c Temporary, MetaContact::setTemporary will be called on the resulting metacontact, + * and the metacontact will not be added to @p group. + * If @p mode is @c DontChangeKABC, no additional action is carried out. + * + * @param contactId the @ref Contact::contactId of the contact to create + * @param displayName the displayname (alias) of the new metacontact. Leave as QString::null if + * no alias is known, then by default, the nick will be taken as alias and tracked if changed. + * @param group the group to add the created metacontact to, or 0 for the top-level group. + * @param mode the mode used to add the contact. Use DontChangeKABC when deserializing. + * @return the new created metacontact or 0L if the operation failed + */ + MetaContact* addContact( const QString &contactId, const QString &displayName = QString::null, Group *group = 0, AddMode mode = DontChangeKABC ) ; + + /** + * @brief Create a new contact, adding it to an existing metacontact + * + * If a contact for this account with ID @p contactId is not already on the contact list, + * a new contact with that ID is created, and added to the metacontact @p parent. + * + * @param contactId the @ref Contact::contactId of the contact to create + * @param parent the parent metacontact (must not be 0) + * @param mode the mode used to add the contact. See addContact(const QString&,const QString&,Group*,AddMode) for details. + * + * @return @c true if creation of the contact succeeded or the contact was already in the list, + * @c false otherwise. + */ + bool addContact( const QString &contactId, MetaContact *parent, AddMode mode = DontChangeKABC ); + + /** + * @brief Indicate whether the account is connected at all. + * + * This is a convenience method that calls @ref Contact::isOnline() on @ref myself(). + * This function is safe to call if @ref setMyself() has not been called yet. + * + * @see @ref isConnectedChanged() + */ + bool isConnected() const; + + /** + * @brief Indicate whether the account is away. + * + * This is a convenience method that queries @ref Contact::onlineStatus() on @ref myself(). + * This function is safe to call if @ref setMyself() has not been called yet. + */ + bool isAway() const; + + /** + * Return the @ref KConfigGroup used to write and read special properties + * + * "Protocol", "AccountId" , "Color", "AutoConnect", "Priority", "Enabled" , "Icon" are reserved keyword + * already in use in that group. + * + * for compatibility, try to not use key that start with a uppercase + */ + KConfigGroup *configGroup() const; + + /** + * @brief Remove the account from the server. + * + * Reimplement this if your protocol supports removing the accounts from the server. + * This function is called by @ref AccountManager::removeAccount typically when you remove the + * account on the account config page. + * + * You should add a confirmation message box before removing the account. The default + * implementation does nothing. + * + * @return @c false only if the user requested for the account to be deleted, and deleting the + * account failed. Returns @c true in all other cases. + */ + virtual bool removeAccount(); + + /** + * \return a pointer to the blacklist of the account + */ + BlackLister* blackLister(); + + /** + * \return @c true if the contact with ID @p contactId is in the blacklist, @c false otherwise. + */ + virtual bool isBlocked( const QString &contactId ); + +protected: + /** + * \brief Set the 'myself' contact. + * + * This contact must be defined for every account, because it holds the online status + * of the account. You must call this function in the constructor of your account. + * + * The myself contact can't be deleted as long as the account still exists. The myself + * contact is used as a member of every ChatSession involving this account. myself's + * contactId should be the accountID. The online status of the myself contact represents + * the account's status. + * + * The myself should have the @ref ContactList::myself() as parent metacontact + * + */ + void setMyself( Contact *myself ); + + /** + * \brief Create a new contact in the specified metacontact + * + * You shouldn't ever call this method yourself. To add contacts, use @ref addContact(). + * + * This method is called by @ref addContact(). In this method, you should create the + * new custom @ref Contact, using @p parentContact as the parent. + * + * If the metacontact is not temporary and the protocol supports it, you can add the + * contact to the server. + * + * @param contactId the ID of the contact to create + * @param parentContact the metacontact to add this contact to + * @return @c true if creating the contact succeeded, @c false on failure. + */ + virtual bool createContact( const QString &contactId, MetaContact *parentContact ) =0; + + + /** + * \brief Sets the account label + * + * @param label The label to set + */ + void setAccountLabel( const QString &label ); + +protected slots: + /** + * \brief The service has been disconnected + * + * You have to call this method when you are disconnected. Depending on the value of + * @p reason, this function may attempt to reconnect to the server. + * + * - BadPassword will ask again for the password + * - OtherClient will show a message box + * + * @param reason the reason for the disconnection. + */ + virtual void disconnected( Kopete::Account::DisconnectReason reason ); + + /** + * @brief Sets the online status of all contacts in this account to the same value + * + * Some protocols do not provide status-changed events for all contacts when an account + * becomes connected or disconnected. For such protocols, this function may be useful + * to set all contacts offline. + * + * Calls @ref Kopete::Contact::setOnlineStatus on all contacts of this account (except the + * @ref myself() contact), passing @p status as the status. + * + * @param status the status to set all contacts of this account except @ref myself() to. + */ + void setAllContactsStatus( const Kopete::OnlineStatus &status ); + +signals: + /** + * The color of the account has been changed + * + * also emited when the icon change + * @todo probably rename to accountIconChanged + */ + void colorChanged( const QColor & ); + + /** + * Emitted when the account is deleted. + * @warning emitted in the Account destructor. It is not safe to call any functions on @p account. + */ + void accountDestroyed( const Kopete::Account *account ); + + /** + * Emitted whenever @ref isConnected() changes. + */ + void isConnectedChanged(); + +private: + /** + * @internal + * Reads the configuration information of the account from KConfig. + */ + void readConfig(); + +public: + /** + * @internal + * Register a new Contact with the account. This should be called @em only from the + * @ref Contact constructor, not from anywhere else (not even a derived class). + */ + void registerContact( Contact *c ); + +public slots: + /** + * @brief Go online for this service. + * + * @param initialStatus is the status to connect with. If it is an invalid status for this + * account, the default online for the account should be used. + */ + virtual void connect( const Kopete::OnlineStatus& initialStatus = OnlineStatus() ) = 0; + + /** + * @brief Go offline for this service. + * + * If the service is connecting, you should abort the connection. + * + * You should call the @ref disconnected function from this function. + */ + virtual void disconnect( ) = 0 ; + +public slots: + /** + * If @p away is @c true, set the account away with away message @p reason. Otherwise, + * set the account to not be away. + * + * @todo change ; make use of setOnlineStatus + */ + virtual void setAway( bool away, const QString &reason = QString::null ); + + /** + * Reimplement this function to set the online status + * @param status is the new status + * @param reason is the away message to set. + * @note If needed, you need to connect. if the offline status is given, you should disconnect + */ + virtual void setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason = QString::null ) = 0; + + /** + * Display the edit account widget for the account + */ + void editAccount( QWidget* parent = 0L ); + + /** + * Add a user to the blacklist. The default implementation calls + * blackList()->addContact( contactId ) + * + * @param contactId the contact to be added to the blacklist + */ + virtual void block( const QString &contactId ); + + /** + * Remove a user from the blacklist. The default implementation calls + * blackList()->removeContact( contactId ) + * + * @param contactId the contact to be removed from the blacklist + */ + virtual void unblock( const QString &contactId ); + +private slots: + /** + * Restore online status and status message on reconnect. + */ + virtual void reconnect(); + + /** + * Track the deletion of a Contact and clean up + */ + void contactDestroyed( Kopete::Contact * ); + + /** + * The @ref myself() contact's online status changed. + */ + void slotOnlineStatusChanged( Kopete::Contact *contact, const Kopete::OnlineStatus &newStatus, const Kopete::OnlineStatus &oldStatus ); + + /** + * The @ref myself() contact's property changed. + */ + void slotContactPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ); + + /** + * Stop the suppression of status notification (connected to a timer) + */ + void slotStopSuppression(); + +private: + class Private; + Private *d; + +protected: + virtual void virtual_hook( uint id, void* data); + +public: + /** + * @todo remove + * @deprecated use configGroup + */ + void setPluginData( Plugin* /*plugin*/, const QString &key, const QString &value ) KDE_DEPRECATED; + + /** + * @todo remove + * @deprecated use configGroup + */ + QString pluginData( Plugin* /*plugin*/, const QString &key ) const KDE_DEPRECATED; +}; + +} //END namespace Kopete + +#endif + diff --git a/kopete/libkopete/kopeteaccountmanager.cpp b/kopete/libkopete/kopeteaccountmanager.cpp new file mode 100644 index 00000000..b00f080e --- /dev/null +++ b/kopete/libkopete/kopeteaccountmanager.cpp @@ -0,0 +1,440 @@ +/* + kopeteaccountmanager.cpp - Kopete Account Manager + + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2003-2004 by Olivier Goffart <ogoffart @ kde.org> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteaccountmanager.h" + +#include <qapplication.h> +#include <qregexp.h> +#include <qtimer.h> + +#include <kconfig.h> +#include <kdebug.h> +#include <kglobal.h> +#include <kplugininfo.h> + +#include "kopeteaccount.h" +#include "kopeteaway.h" +#include "kopeteprotocol.h" +#include "kopetecontact.h" +#include "kopetecontactlist.h" +#include "kopetepluginmanager.h" +#include "kopeteonlinestatus.h" +#include "kopeteonlinestatusmanager.h" +#include "kopetemetacontact.h" +#include "kopetegroup.h" + +namespace Kopete { + +class AccountManager::Private +{ +public: + + class AccountPtrList : public QPtrList<Account> + { + protected: + int compareItems( AccountPtrList::Item a, AccountPtrList::Item b ) + { + uint priority1 = static_cast<Account*>(a)->priority(); + uint priority2 = static_cast<Account*>(b)->priority(); + + if( a==b ) //two account are equal only if they are equal :-) + return 0; // remember than an account can be only once on the list, but two account may have the same priority when loading + else if( priority1 > priority2 ) + return 1; + else + return -1; + } + } accounts; + +}; + +AccountManager * AccountManager::s_self = 0L; + +AccountManager * AccountManager::self() +{ + if ( !s_self ) + s_self = new AccountManager; + + return s_self; +} + + +AccountManager::AccountManager() +: QObject( qApp, "KopeteAccountManager" ) +{ + d = new Private; +} + + +AccountManager::~AccountManager() +{ + s_self = 0L; + + delete d; +} + +bool AccountManager::isAnyAccountConnected() +{ + for ( QPtrListIterator<Account> it( d->accounts ); it.current(); ++it ) + { + if(it.current()->isConnected()) + return true; + } + return false; +} + +void AccountManager::connectAll() +{ + for ( QPtrListIterator<Account> it( d->accounts ); it.current(); ++it ) + if(!it.current()->excludeConnect()) + it.current()->connect(); +} + +void AccountManager::setAvailableAll( const QString &awayReason ) +{ + Away::setGlobalAway( false ); + bool anyConnected = isAnyAccountConnected(); + + for ( QPtrListIterator<Account> it( d->accounts ); it.current(); ++it ) + { + if ( anyConnected ) + { + if ( it.current()->isConnected() ) + it.current()->setAway( false, awayReason ); + } + else + if(!it.current()->excludeConnect()) + it.current()->connect(); + } +} + +void AccountManager::disconnectAll() +{ + for ( QPtrListIterator<Account> it( d->accounts ); it.current(); ++it ) + it.current()->disconnect(); +} + +void AccountManager::setAwayAll( const QString &awayReason, bool away ) +{ + Away::setGlobalAway( true ); + bool anyConnected = isAnyAccountConnected(); + + for ( QPtrListIterator<Account> it( d->accounts ); it.current(); ++it ) + { + // FIXME: ICQ's invisible online should be set to invisible away + Contact *self = it.current()->myself(); + bool isInvisible = self && self->onlineStatus().status() == OnlineStatus::Invisible; + if ( anyConnected ) + { + if ( it.current()->isConnected() && !isInvisible ) + it.current()->setAway( away, awayReason ); + } + else + { + if ( !it.current()->excludeConnect() && !isInvisible ) + it.current()->setAway( away, awayReason ); + } + } +} + +void AccountManager::setOnlineStatus( uint category , const QString& awayMessage, uint flags ) +{ + OnlineStatusManager::Categories katgor=(OnlineStatusManager::Categories)category; + bool anyConnected = isAnyAccountConnected(); + + for ( QPtrListIterator<Account> it( d->accounts ); it.current(); ++it ) + { + Account *account = it.current(); + Kopete::OnlineStatus status = OnlineStatusManager::self()->onlineStatus(account->protocol() , katgor); + if ( anyConnected ) + { + if ( account->isConnected() || ( (flags & ConnectIfOffline) && !account->excludeConnect() ) ) + account->setOnlineStatus( status , awayMessage ); + } + else + { + if ( !account->excludeConnect() ) + account->setOnlineStatus( status , awayMessage ); + } + } +} + + +QColor AccountManager::guessColor( Protocol *protocol ) const +{ + // In a perfect wold, we should check if the color is actually not used by the account. + // Anyway, this is not really required, It would be a difficult job for about nothing more. + // -- Olivier + int protocolCount = 0; + + for ( QPtrListIterator<Account> it( d->accounts ); it.current(); ++it ) + { + if ( it.current()->protocol()->pluginId() == protocol->pluginId() ) + protocolCount++; + } + + // let's figure a color + QColor color; + switch ( protocolCount % 7 ) + { + case 0: + color = QColor(); + break; + case 1: + color = Qt::red; + break; + case 2: + color = Qt::green; + break; + case 3: + color = Qt::blue; + break; + case 4: + color = Qt::yellow; + break; + case 5: + color = Qt::magenta; + break; + case 6: + color = Qt::cyan; + break; + } + + return color; +} + +Account* AccountManager::registerAccount( Account *account ) +{ + if( !account || d->accounts.contains( account ) ) + return account; + + if( account->accountId().isEmpty() ) + { + account->deleteLater(); + return 0L; + } + + // If this account already exists, do nothing + for ( QPtrListIterator<Account> it( d->accounts ); it.current(); ++it ) + { + if ( ( account->protocol() == it.current()->protocol() ) && ( account->accountId() == it.current()->accountId() ) ) + { + account->deleteLater(); + return 0L; + } + } + + d->accounts.append( account ); + d->accounts.sort(); + + // Connect to the account's status changed signal + connect(account->myself(), SIGNAL(onlineStatusChanged(Kopete::Contact *, + const Kopete::OnlineStatus &, const Kopete::OnlineStatus &)), + this, SLOT(slotAccountOnlineStatusChanged(Kopete::Contact *, + const Kopete::OnlineStatus &, const Kopete::OnlineStatus &))); + + connect(account, SIGNAL(accountDestroyed(const Kopete::Account *)) , this, SLOT( unregisterAccount(const Kopete::Account *) )); + + emit accountRegistered( account ); + return account; +} + +void AccountManager::unregisterAccount( const Account *account ) +{ + kdDebug( 14010 ) << k_funcinfo << "Unregistering account " << account->accountId() << endl; + d->accounts.remove( account ); + emit accountUnregistered( account ); +} + +const QPtrList<Account>& AccountManager::accounts() const +{ + return d->accounts; +} + +QDict<Account> AccountManager::accounts( const Protocol *protocol ) const +{ + QDict<Account> dict; + for ( QPtrListIterator<Account> it( d->accounts ); it.current(); ++it ) + { + if ( it.current()->protocol() == protocol && !it.current()->accountId().isNull() ) + dict.insert( it.current()->accountId(), it.current() ); + } + + return dict; +} + +Account * AccountManager::findAccount( const QString &protocolId, const QString &accountId ) +{ + for ( QPtrListIterator<Account> it( d->accounts ); it.current(); ++it ) + { + if ( it.current()->protocol()->pluginId() == protocolId && it.current()->accountId() == accountId ) + return it.current(); + } + return 0L; +} + +void AccountManager::removeAccount( Account *account ) +{ + if(!account->removeAccount()) + return; + + Protocol *protocol = account->protocol(); + + + KConfigGroup *configgroup = account->configGroup(); + + // Clean up the contact list + QDictIterator<Kopete::Contact> it( account->contacts() ); + for ( ; it.current(); ++it ) + { + Contact* c = it.current(); + MetaContact* mc = c->metaContact(); + if ( mc == ContactList::self()->myself() ) + continue; + mc->removeContact( c ); + c->deleteLater(); + if ( mc->contacts().count() == 0 ) //we can delete the metacontact + { + //get the first group and it's members + Group* group = mc->groups().first(); + QPtrList<MetaContact> groupMembers = group->members(); + ContactList::self()->removeMetaContact( mc ); + if ( groupMembers.count() == 1 && groupMembers.findRef( mc ) != -1 ) + ContactList::self()->removeGroup( group ); + } + } + + // Clean up the account list + d->accounts.remove( account ); + + // Clean up configuration + configgroup->deleteGroup(); + configgroup->sync(); + + delete account; + + if ( accounts( protocol ).isEmpty() ) + { + // FIXME: pluginId() should return the internal name and not the class name, so + // we can get rid of this hack - Olivier/Martijn + QString protocolName = protocol->pluginId().remove( QString::fromLatin1( "Protocol" ) ).lower(); + + PluginManager::self()->setPluginEnabled( protocolName, false ); + PluginManager::self()->unloadPlugin( protocolName ); + } +} + +void AccountManager::save() +{ + //kdDebug( 14010 ) << k_funcinfo << endl; + d->accounts.sort(); + + for ( QPtrListIterator<Account> it( d->accounts ); it.current(); ++it ) + { + KConfigBase *config = it.current()->configGroup(); + + config->writeEntry( "Protocol", it.current()->protocol()->pluginId() ); + config->writeEntry( "AccountId", it.current()->accountId() ); + } + + KGlobal::config()->sync(); +} + +void AccountManager::load() +{ + connect( PluginManager::self(), SIGNAL( pluginLoaded( Kopete::Plugin * ) ), + this, SLOT( slotPluginLoaded( Kopete::Plugin * ) ) ); + + // Iterate over all groups that start with "Account_" as those are accounts + // and load the required protocols if the account is enabled. + // Don't try to optimize duplicate calls out, the plugin queue is smart enough + // (and fast enough) to handle that without adding complexity here + KConfig *config = KGlobal::config(); + QStringList accountGroups = config->groupList().grep( QRegExp( QString::fromLatin1( "^Account_" ) ) ); + for ( QStringList::Iterator it = accountGroups.begin(); it != accountGroups.end(); ++it ) + { + config->setGroup( *it ); + + QString protocol = config->readEntry( "Protocol" ); + if ( protocol.endsWith( QString::fromLatin1( "Protocol" ) ) ) + protocol = QString::fromLatin1( "kopete_" ) + protocol.lower().remove( QString::fromLatin1( "protocol" ) ); + + if ( config->readBoolEntry( "Enabled", true ) ) + PluginManager::self()->loadPlugin( protocol, PluginManager::LoadAsync ); + } +} + +void AccountManager::slotPluginLoaded( Plugin *plugin ) +{ + Protocol* protocol = dynamic_cast<Protocol*>( plugin ); + if ( !protocol ) + return; + + // Iterate over all groups that start with "Account_" as those are accounts + // and parse them if they are from this protocol + KConfig *config = KGlobal::config(); + QStringList accountGroups = config->groupList().grep( QRegExp( QString::fromLatin1( "^Account_" ) ) ); + for ( QStringList::Iterator it = accountGroups.begin(); it != accountGroups.end(); ++it ) + { + config->setGroup( *it ); + + if ( config->readEntry( "Protocol" ) != protocol->pluginId() ) + continue; + + // There's no GUI for this, but developers may want to disable an account. + if ( !config->readBoolEntry( "Enabled", true ) ) + continue; + + QString accountId = config->readEntry( "AccountId" ); + if ( accountId.isEmpty() ) + { + kdWarning( 14010 ) << k_funcinfo << + "Not creating account for empty accountId." << endl; + continue; + } + + kdDebug( 14010 ) << k_funcinfo << + "Creating account for '" << accountId << "'" << endl; + + Account *account = 0L; + account = registerAccount( protocol->createNewAccount( accountId ) ); + if ( !account ) + { + kdWarning( 14010 ) << k_funcinfo << + "Failed to create account for '" << accountId << "'" << endl; + continue; + } + } +} + +void AccountManager::slotAccountOnlineStatusChanged(Contact *c, + const OnlineStatus &oldStatus, const OnlineStatus &newStatus) +{ + Account *account = c->account(); + if (!account) + return; + + //kdDebug(14010) << k_funcinfo << endl; + emit accountOnlineStatusChanged(account, oldStatus, newStatus); +} + +} //END namespace Kopete + +#include "kopeteaccountmanager.moc" +// vim: set noet ts=4 sts=4 sw=4: +// kate: tab-width 4; indent-mode csands; diff --git a/kopete/libkopete/kopeteaccountmanager.h b/kopete/libkopete/kopeteaccountmanager.h new file mode 100644 index 00000000..ed0c939a --- /dev/null +++ b/kopete/libkopete/kopeteaccountmanager.h @@ -0,0 +1,233 @@ +/* + kopeteaccountmanager.h - Kopete Account Manager + + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2003-2004 by Olivier Goffart <ogoffart@ tiscalinet.be> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef __kopeteaccountmanager_h__ +#define __kopeteaccountmanager_h__ + +#include <qobject.h> +#include <qptrlist.h> +#include <qdict.h> + +#include "kopete_export.h" + + +namespace Kopete { + +class Account; +class Plugin; +class Protocol; +class Contact; +class OnlineStatus; + +/** + * AccountManager manages all defined accounts in Kopete. You can + * query them and globally set them all online or offline from here. + * + * AccountManager is a singleton, you may uses it with @ref AccountManager::self() + * + * @author Martijn Klingens <klingens@kde.org> + * @author Olivier Goffart <ogoffart@ tiscalinet.be> + */ +class KOPETE_EXPORT AccountManager : public QObject +{ + Q_OBJECT + +public: + /** + * \brief Retrieve the instance of AccountManager. + * + * The account manager is a singleton class of which only a single + * instance will exist. If no manager exists yet this function will + * create one for you. + * + * \return the instance of the AccountManager + */ + static AccountManager* self(); + + ~AccountManager(); + + /** + * \brief Retrieve the list of accounts + * \return a list of all the accounts + */ + const QPtrList<Account> & accounts() const; + + /** + * \brief Retrieve a QDict of accounts for the given protocol + * + * The list is guaranteed to contain only accounts for the specified + * protocol + * \param p is the Protocol object you want accounts for + */ + QDict<Account> accounts( const Protocol *p ) const; + + /** + * \brief Return the account asked + * \param protocolId is the ID for the protocol + * \param accountId is the ID for the account you want + * \return the Account object found or NULL if no account was found + */ + Account* findAccount( const QString &protocolId, const QString &accountId ); + + /** + * \brief Delete the account and clean the config data + * + * This is praticaly called by the account config page when you remove the account. + */ + void removeAccount( Account *account ); + + /** + * \brief Guess the color for a new account + * + * Guesses a color for the next account of a given protocol based on the already registered colors + * \return the color guessed for the account + */ + QColor guessColor( Protocol *protocol ) const ; + + /** + * @brief Register the account. + * + * This adds the account in the manager's account list. + * It will check no accounts already exist with the same ID, if any, the account is deleted. and not added + * + * @return @p account, or 0L if the account was deleted because id collision + */ + Account *registerAccount( Account *account ); + + + /** + * Flag to be used in setOnlineStatus + * + * @c ConnectIfOffline : if set, this will connect offlines account with the status. + */ + enum SetOnlineStatusFlag { ConnectIfOffline=0x01 }; + + +public slots: + /** + * \brief Connect all accounts at once. + * + * Connect every account if the flag excludeConnect is false + * @see @ref Account::excludeConnect() + */ + void connectAll(); + + /** + * \brief Disconnect all accounts at once. + */ + void disconnectAll(); + + /** + * @brief Set all accounts a status in the specified category + * + * Account that are offline will not be connected, unless the ConnectIfOffline flag is set. + * + * @param category is one of the Kopete::OnlineStatusManager::Categories + * @param awayMessage is the new away message + * @param flags is a bitmask of SetOnlineStatusFlag + */ + void setOnlineStatus( /*Kopete::OnlineStatusManager::Categories*/ uint category, + const QString& awayMessage = QString::null, uint flags=0); + + /** + * \brief Set all accounts to away at once. + * + * All account that are connected, but not invisible will be set to away + * @see Account::setAway + * @param awayReason is the away message that will be set. + * @param away decides whether the message is away/non-away + */ + void setAwayAll( const QString &awayReason = QString::null, bool away=true ); + + /** + * \brief Connect or make available every account. + * Make all accounts Available, by setting status, and connecting if necessary. + * Accounts are connected based on their excludeConnect() setting. + * Accounts which are already connected are controlled regardless of their excludeConnect() setting. + * This is a slot, so you can connect directly to it from e.g. a KAction. + * @param awayReason is the away(status) message that will be set. + */ + void setAvailableAll( const QString &awayReason = QString::null ); + + /** + * \internal + * Save the account data to KConfig + */ + void save(); + + /** + * \internal + * Load the account data from KConfig + */ + void load(); + + + +signals: + /** + * \brief Signals when an account is ready for use + */ + void accountRegistered( Kopete::Account *account ); + + /** + * \brief Signals when an account has been unregistered + * + * At this state, we are already in the Account destructor. + */ + void accountUnregistered( const Kopete::Account *account ); + + /** + * \brief An account has changed its onlinestatus + * Technically this monitors Account::myself() onlinestatus changes + * \param account Account which changed its onlinestatus + * \param oldStatus The online status before the change + * \param newStatus The new online status + */ + void accountOnlineStatusChanged(Kopete::Account *account, + const Kopete::OnlineStatus &oldStatus, const Kopete::OnlineStatus &newStatus); + +private: + /** + * Private constructor, because we're a singleton + */ + AccountManager(); + +private slots: + void slotPluginLoaded( Kopete::Plugin *plugin ); + void slotAccountOnlineStatusChanged(Kopete::Contact *c, + const Kopete::OnlineStatus &oldStatus, const Kopete::OnlineStatus &newStatus); + + /** + * \internal + * Unregister the account. + */ + void unregisterAccount( const Kopete::Account *account ); + +private: + bool isAnyAccountConnected(); + static AccountManager *s_self; + class Private; + Private *d; +}; + +} //END namespace Kopete + + +#endif + + diff --git a/kopete/libkopete/kopeteaway.cpp b/kopete/libkopete/kopeteaway.cpp new file mode 100644 index 00000000..fa500e0c --- /dev/null +++ b/kopete/libkopete/kopeteaway.cpp @@ -0,0 +1,525 @@ +/* + kopeteaway.cpp - Kopete Away + + Copyright (c) 2002 by Hendrik vom Lehn <hvl@linux-4-ever.de> + Copyright (c) 2003 Olivier Goffart <ogoffart @ kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "kopeteaway.h" + +#include "kopeteaccountmanager.h" +#include "kopeteaccount.h" +#include "kopeteonlinestatus.h" +#include "kopeteonlinestatusmanager.h" +#include "kopetecontact.h" +#include "kopeteprefs.h" + +#include <kconfig.h> +#include <qtimer.h> +#include <kapplication.h> +#include <dcopref.h> + +#include <klocale.h> +#include <kglobal.h> +#include <kdebug.h> +#include <ksettings/dispatcher.h> + +#ifdef Q_WS_X11 +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Xresource.h> +// The following include is to make --enable-final work +#include <X11/Xutil.h> + +#ifdef HAVE_XSCREENSAVER +#define HasScreenSaver +#include <X11/extensions/scrnsaver.h> +#endif +#endif // Q_WS_X11 + +// As this is an untested X extension we better leave it off +#undef HAVE_XIDLE +#undef HasXidle + + +struct KopeteAwayPrivate +{ + QString awayMessage; + QString autoAwayMessage; + bool useAutoAwayMessage; + bool globalAway; + QStringList awayMessageList; + QTime idleTime; + QTimer *timer; + bool autoaway; + bool goAvailable; + int awayTimeout; + bool useAutoAway; + QPtrList<Kopete::Account> autoAwayAccounts; + + int mouse_x; + int mouse_y; + unsigned int mouse_mask; +#ifdef Q_WS_X11 + Window root; /* root window the pointer is on */ + Screen* screen; /* screen the pointer is on */ + + Time xIdleTime; +#endif + bool useXidle; + bool useMit; +}; + +Kopete::Away *Kopete::Away::instance = 0L; + +Kopete::Away::Away() : QObject( kapp , "Kopete::Away") +{ + int dummy = 0; + dummy = dummy; // shut up + + d = new KopeteAwayPrivate; + + // Set up the away messages + d->awayMessage = QString::null; + d->autoAwayMessage = QString::null; + d->useAutoAwayMessage = false; + d->globalAway = false; + d->autoaway = false; + d->useAutoAway = true; + + // Empty the list + d->awayMessageList.clear(); + + // set the XAutoLock info +#ifdef Q_WS_X11 + Display *dsp = qt_xdisplay(); +#endif + d->mouse_x = d->mouse_y=0; + d->mouse_mask = 0; +#ifdef Q_WS_X11 + d->root = DefaultRootWindow (dsp); + d->screen = ScreenOfDisplay (dsp, DefaultScreen (dsp)); +#endif + d->useXidle = false; + d->useMit = false; +#ifdef HasXidle + d->useXidle = XidleQueryExtension(qt_xdisplay(), &dummy, &dummy); +#endif +#ifdef HasScreenSaver + if(!d->useXidle) + d->useMit = XScreenSaverQueryExtension(qt_xdisplay(), &dummy, &dummy); +#endif +#ifdef Q_WS_X11 + d->xIdleTime = 0; +#endif + kdDebug(14010) << k_funcinfo << "Idle detection methods:" << endl; + kdDebug(14010) << k_funcinfo << "\tKScreensaverIface::isBlanked()" << endl; +#ifdef Q_WS_X11 + kdDebug(14010) << k_funcinfo << "\tX11 XQueryPointer()" << endl; +#endif + if (d->useXidle) + { + kdDebug(14010) << k_funcinfo << "\tX11 Xidle extension" << endl; + } + if (d->useMit) + { + kdDebug(14010) << k_funcinfo << "\tX11 MIT Screensaver extension" << endl; + } + + + load(); + KSettings::Dispatcher::self()->registerInstance( KGlobal::instance(), this, SLOT( load() ) ); + // Set up the config object + KConfig *config = KGlobal::config(); + /* Load the saved away messages */ + config->setGroup("Away Messages"); + + // Away Messages + if(config->hasKey("Messages")) + { + d->awayMessageList = config->readListEntry("Messages"); + } + else if(config->hasKey("Titles")) // Old config format + { + QStringList titles = config->readListEntry("Titles"); // Get the titles + for(QStringList::iterator i = titles.begin(); i != titles.end(); ++i) + { + d->awayMessageList.append( config->readEntry(*i) ); // And add it to the list + } + + /* Save this list to disk */ + save(); + } + else + { + d->awayMessageList.append( i18n( "Sorry, I am busy right now" ) ); + d->awayMessageList.append( i18n( "I am gone right now, but I will be back later" ) ); + + /* Save this list to disk */ + save(); + } + + // Auto away message + if(config->hasKey("AutoAwayMessage")) + { + d->autoAwayMessage = config->readEntry("AutoAwayMessage"); + } + else + { + d->autoAwayMessage = i18n( "I am gone right now, but I will be back later" ); + + // Save the default auto away message to disk + save(); + } + + // init the timer + d->timer = new QTimer(this, "AwayTimer"); + connect(d->timer, SIGNAL(timeout()), this, SLOT(slotTimerTimeout())); + d->timer->start(4000); + + //init the time and other + setActive(); +} + +Kopete::Away::~Away() +{ + if(this == instance) + instance = 0L; + delete d; +} + +QString Kopete::Away::message() +{ + return getInstance()->d->awayMessage; +} + +QString Kopete::Away::autoAwayMessage() +{ + return getInstance()->d->autoAwayMessage; +} + +void Kopete::Away::setGlobalAwayMessage(const QString &message) +{ + if( !message.isEmpty() ) + { + kdDebug(14010) << k_funcinfo << + "Setting global away message: " << message << endl; + d->awayMessage = message; + } +} + +void Kopete::Away::setAutoAwayMessage(const QString &message) +{ + if( !message.isEmpty() ) + { + kdDebug(14010) << k_funcinfo << + "Setting auto away message: " << message << endl; + d->autoAwayMessage = message; + + // Save the new auto away message to disk + save(); + } +} + +Kopete::Away *Kopete::Away::getInstance() +{ + if (!instance) + instance = new Kopete::Away; + return instance; +} + +bool Kopete::Away::globalAway() +{ + return getInstance()->d->globalAway; +} + +void Kopete::Away::setGlobalAway(bool status) +{ + getInstance()->d->globalAway = status; +} + +void Kopete::Away::save() +{ + KConfig *config = KGlobal::config(); + /* Set the away message settings in the Away Messages config group */ + config->setGroup("Away Messages"); + config->writeEntry("Messages", d->awayMessageList); + config->writeEntry("AutoAwayMessage", d->autoAwayMessage); + config->sync(); + + emit( messagesChanged() ); +} + +void Kopete::Away::load() +{ + KConfig *config = KGlobal::config(); + config->setGroup("AutoAway"); + d->awayTimeout=config->readNumEntry("Timeout", 600); + d->goAvailable=config->readBoolEntry("GoAvailable", true); + d->useAutoAway=config->readBoolEntry("UseAutoAway", true); + d->useAutoAwayMessage=config->readBoolEntry("UseAutoAwayMessage", false); +} + +QStringList Kopete::Away::getMessages() +{ + return d->awayMessageList; +} + +QString Kopete::Away::getMessage( uint messageNumber ) +{ + QStringList::iterator it = d->awayMessageList.at( messageNumber ); + if( it != d->awayMessageList.end() ) + { + QString str = *it; + d->awayMessageList.prepend( str ); + d->awayMessageList.remove( it ); + save(); + return str; + } + else + { + return QString::null; + } +} + +void Kopete::Away::addMessage(const QString &message) +{ + d->awayMessageList.prepend( message ); + if( (int)d->awayMessageList.count() > KopetePrefs::prefs()->rememberedMessages() ) + d->awayMessageList.pop_back(); + save(); +} + +long int Kopete::Away::idleTime() +{ + //FIXME: the time is reset to zero if more than 24 hours are elapsed + // we can imagine someone who leave his PC for several weeks + return (d->idleTime.elapsed() / 1000); +} + +void Kopete::Away::slotTimerTimeout() +{ + // Time to check whether we're active or autoaway. We basically have two + // bits of info to go on - KDE's screensaver status + // (KScreenSaverIface::isBlanked()) and the X11 activity detection. + // + // Note that isBlanked() is a slight of a misnomer. It returns true if we're: + // - using a non-locking screensaver, which is running, or + // - using a locking screensaver which is still locked, regardless of + // whether the user is trying to unlock it right now + // Either way, it's only worth checking for activity if the screensaver + // isn't blanked/locked, because activity while blanked is impossible and + // activity while locked never matters (if there is any, it's probably just + // the cleaner wiping the keyboard :). + + + /* we should be able to respond to KDesktop queries to avoid a deadlock, so we allow the event loop to be called */ + static bool rentrency_protection=false; + if(rentrency_protection) + return; + rentrency_protection=true; + DCOPRef screenSaver("kdesktop", "KScreensaverIface"); + DCOPReply isBlanked = screenSaver.callExt("isBlanked" , DCOPRef::UseEventLoop, 10); + rentrency_protection=false; + if(!instance) //this may have been deleted in the event loop + return; + if (!(isBlanked.isValid() && isBlanked.type == "bool" && ((bool)isBlanked))) + { + // DCOP failed, or returned something odd, or the screensaver is + // inactive, so check for activity the X11 way. It's only worth + // checking for autoaway if there's no activity, and because + // Screensaver blanking/locking implies autoAway activation (see + // KopeteIface::KopeteIface()), only worth checking autoAway when the + // screensaver isn't running. + if (isActivity()) + { + setActive(); + } + else if (!d->autoaway && d->useAutoAway && idleTime() > d->awayTimeout) + { + setAutoAway(); + } + } +} + +bool Kopete::Away::isActivity() +{ + // Copyright (c) 1999 Martin R. Jones <mjones@kde.org> + // + // KDE screensaver engine + // + // This module is a heavily modified xautolock. + // In fact as of KDE 2.0 this code is practically unrecognisable as xautolock. + + bool activity = false; + +#ifdef Q_WS_X11 + Display *dsp = qt_xdisplay(); + Window dummy_w; + int dummy_c; + unsigned int mask; /* modifier mask */ + int root_x; + int root_y; + + /* + * Find out whether the pointer has moved. Using XQueryPointer for this + * is gross, but it also is the only way never to mess up propagation + * of pointer events. + * + * Remark : Unlike XNextEvent(), XPending () doesn't notice if the + * connection to the server is lost. For this reason, earlier + * versions of xautolock periodically called XNoOp (). But + * why not let XQueryPointer () do the job for us, since + * we now call that periodically anyway? + */ + if (!XQueryPointer (dsp, d->root, &(d->root), &dummy_w, &root_x, &root_y, + &dummy_c, &dummy_c, &mask)) + { + /* + * Pointer has moved to another screen, so let's find out which one. + */ + for (int i = 0; i < ScreenCount(dsp); i++) + { + if (d->root == RootWindow(dsp, i)) + { + d->screen = ScreenOfDisplay (dsp, i); + break; + } + } + } + + // ================================================================================= + + Time xIdleTime = 0; // millisecs since last input event + + #ifdef HasXidle + if (d->useXidle) + { + XGetIdleTime(dsp, &xIdleTime); + } + else + #endif /* HasXIdle */ + + { + #ifdef HasScreenSaver + if(d->useMit) + { + static XScreenSaverInfo* mitInfo = 0; + if (!mitInfo) mitInfo = XScreenSaverAllocInfo(); + XScreenSaverQueryInfo (dsp, d->root, mitInfo); + xIdleTime = mitInfo->idle; + } + #endif /* HasScreenSaver */ + } + + // ================================================================================= + + // Only check idle time if we have some way of measuring it, otherwise if + // we've neither Mit nor Xidle it'll still be zero and we'll always appear active. + // FIXME: what problem does the 2000ms fudge solve? + if (root_x != d->mouse_x || root_y != d->mouse_y || mask != d->mouse_mask + || ((d->useXidle || d->useMit) && xIdleTime < d->xIdleTime + 2000)) + { + // -1 => just gone autoaway, ignore apparent activity this time round + // anything else => genuine activity + // See setAutoAway(). + if (d->mouse_x != -1) + { + activity = true; + } + d->mouse_x = root_x; + d->mouse_y = root_y; + d->mouse_mask = mask; + d->xIdleTime = xIdleTime; + } +#endif // Q_WS_X11 + // ================================================================================= + + return activity; +} + +void Kopete::Away::setActive() +{ +// kdDebug(14010) << k_funcinfo << "Found activity on desktop, resetting away timer" << endl; + d->idleTime.start(); + + if(d->autoaway) + { + d->autoaway = false; + emit activity(); + if (d->goAvailable) + { + d->autoAwayAccounts.setAutoDelete(false); + for(Kopete::Account *i=d->autoAwayAccounts.first() ; i; i=d->autoAwayAccounts.current() ) + { + if(i->isConnected() && i->isAway()) + { + i->setOnlineStatus( Kopete::OnlineStatusManager::self()->onlineStatus( i->protocol() , + Kopete::OnlineStatusManager::Online ) ); + } + + // remove() makes the next entry in the list the current one, + // that's why we use current() above + d->autoAwayAccounts.remove(); + } + } + } +} + +void Kopete::Away::setAutoAway() +{ + // A value of -1 in mouse_x indicates to checkActivity() that next time it + // fires it should ignore any apparent idle/mouse/keyboard changes. + // I think the point of this is that if you manually start the screensaver + // then there'll unavoidably be some residual mouse/keyboard activity + // that should be ignored. + d->mouse_x = -1; + +// kdDebug(14010) << k_funcinfo << "Going AutoAway!" << endl; + d->autoaway = true; + + // Set all accounts that are not away already to away. + // We remember them so later we only set the accounts to + // available that we set to away (and not the user). + QPtrList<Kopete::Account> accounts = Kopete::AccountManager::self()->accounts(); + for(Kopete::Account *i=accounts.first() ; i; i=accounts.next() ) + { + if(i->myself()->onlineStatus().status() == Kopete::OnlineStatus::Online) + { + d->autoAwayAccounts.append(i); + + if(d->useAutoAwayMessage) + { + // Display a specific away message + i->setOnlineStatus( Kopete::OnlineStatusManager::self()->onlineStatus( i->protocol() , + Kopete::OnlineStatusManager::Idle ) , + getInstance()->d->autoAwayMessage); + } + else + { + // Display the last away message used + i->setOnlineStatus( Kopete::OnlineStatusManager::self()->onlineStatus( i->protocol() , + Kopete::OnlineStatusManager::Idle ) , + getInstance()->d->awayMessage); + } + } + } +} + +#include "kopeteaway.moc" +// vim: set et ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopeteaway.h b/kopete/libkopete/kopeteaway.h new file mode 100644 index 00000000..544dff75 --- /dev/null +++ b/kopete/libkopete/kopeteaway.h @@ -0,0 +1,217 @@ +/* + kopeteaway.h - Kopete Away + + Copyright (c) 2002 by Hendrik vom Lehn <hvl@linux-4-ever.de> + Copyright (c) 2003 Olivier Goffart <ogoffart @ kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEAWAY_HI +#define KOPETEAWAY_HI + +#include <qstring.h> +#include <qobject.h> +#include <qvaluelist.h> + +#include "kopeteawaydialog.h" +#include "kopete_export.h" + +class QStringList; + +struct KopeteAwayPrivate; + +class KopeteGlobalAwayDialog; +class KopeteAwayDialog; + +namespace Kopete +{ + +/** + * @class Kopete::Away kopeteaway.h + * + * Kopete::Away is a singleton class that manages away messages + * for Kopete. It stores a global away message, as well as + * a list of user defined away messages. + * This class is used by KopeteAwayDialog, which gets it's + * list of user-defined away messages from this. Protocol + * plugins' individual away dialogs should also get away + * messages from this object. + * + * It also handle global Idle Time, and all auto away stuff + * + * @author Hendrik vom Lehn <hvl@linux-4-ever.de> + * @author Chris TenHarmsel <tenharmsel@users.sourceforge.net> + * @author Olivier Goffart <ogoffart @ kde.org> + + */ +class KOPETE_EXPORT Away : public QObject +{ +Q_OBJECT + +friend class ::KopeteAwayDialog; + +public: + + /** + * @brief Method to get the single instance of Kopete::Away + * @return Kopete::Away instance pointer + */ + static Away *getInstance(); + + /** + * @brief Gets the current global away message + * @return The global away message + */ + static QString message(); + + /** + * @brief Gets the current global auto away message + * @return The global auto away message + */ + static QString autoAwayMessage(); + + /** + * This method sets the global away message, + * it does not set you away, just sets the message. + * @brief Sets the global away message + * @param message The message you want to set + */ + void setGlobalAwayMessage(const QString &message); + + /** + * This method sets the global auto away message, + * it does not set you away, just sets the message. + * @brief Sets the global auto away message + * @param message The message you want to set + */ + void setAutoAwayMessage(const QString &message); + + /** + * @brief Sets global away for all protocols + */ + static void setGlobalAway(bool status); + + /** + * @brief Indicates global away status + * @return Bool indicating global away status + */ + static bool globalAway(); + + /** + * @brief Function to get the titles of user defined away messages + * @return List of away message titles + * + * This function can be used to retrieve a QStringList of the away message titles, + * these titles can be passed to getMessage(QString title) to retrieve the + * corresponding message. + */ + QStringList getMessages(); + + /** + * @brief Function to get an away message + * @return The away message corresponding to the title + * @param messageNumber Number of the away message to retrieve + * + * This function retrieves the away message that corresponds to the ringbuffer index + * passed in. + */ + QString getMessage( uint messageNumber ); + + /** + * @brief Adds an away message to the ringbuffer + * @param message The away message + * + * This function will add an away message to the ringbuffer of user defined + * away messages. + */ + void addMessage(const QString &message); + + /** + * time in seconds the user has been idle + */ + long int idleTime(); + +private: + Away(); + ~Away(); + + /** + * @brief Saves the away messages to disk + * + * This function will save the current list of away messages to the disk + * using KConfig. It is called automatically. + */ + void save(); + + /** + * @brief Check for activity using X11 methods + * @return true if activity was detected, otherwise false + * + * Attempt to detect activity using a variety of X11 methods. + */ + bool isActivity(); + + //Away( const Away &rhs ); + //Away &operator=( const Away &rhs ); + static Away *instance; + KopeteAwayPrivate *d; + +private slots: + void slotTimerTimeout(); + void load(); + +public slots: + /** + * @brief Mark the user active + * + * Plugins can mark the user active if they discover activity by another way than the mouse or the keyboard + * (example, the motion auto away plugin) + * this will reset the @ref idleTime to 0, and set all protocols to available (online) if the state was + * set automatically to away because of idleness, and if they was previously online + */ + void setActive(); + + /** + * Use this method if you want to go in the autoaway mode. + * This will go autoaway even if the idle time is not yet reached. (and even if the user + * did not selected to go autoaway automaticaly) + * But that will go unaway again when activity will be detected + */ + void setAutoAway(); + +signals: + /** + * @brief Activity was detected + * + * this signal is emit when activity has been discover after being autoAway. + */ + void activity(); + + /** + * @brief Default messages were changed + */ + void messagesChanged(); +}; + +} + +#endif +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopeteawayaction.cpp b/kopete/libkopete/kopeteawayaction.cpp new file mode 100644 index 00000000..84622c7e --- /dev/null +++ b/kopete/libkopete/kopeteawayaction.cpp @@ -0,0 +1,134 @@ +/* + kopeteaway.cpp - Kopete Away Action + + Copyright (c) 2003 Jason Keirstead <jason@keirstead.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <klocale.h> +#include <kdeversion.h> +#include <kinputdialog.h> +#include <kstringhandler.h> + +#include "kopeteawayaction.h" +#include "kopeteaway.h" +#include "kopeteonlinestatus.h" + + +namespace Kopete { + +class AwayAction::Private +{ +public: + Private(const OnlineStatus& s) : reasonCount(0) , status(s) {}; + int reasonCount; + OnlineStatus status; +}; + + +AwayAction::AwayAction(const QString &text, const QIconSet &pix, const KShortcut &cut, + const QObject *receiver, const char *slot, QObject *parent, const char *name ) + : KSelectAction(text, pix, cut, parent, name ) , d(new Private( OnlineStatus() ) ) +{ + QObject::connect( Kopete::Away::getInstance(), SIGNAL( messagesChanged() ), + this, SLOT( slotAwayChanged() ) ); + + QObject::connect( this, SIGNAL( awayMessageSelected( const QString & ) ), + receiver, slot ); + + QObject::connect( this, SIGNAL( activated( int ) ), + this, SLOT( slotSelectAway( int ) ) ); + + slotAwayChanged(); +} + +AwayAction::AwayAction( const OnlineStatus& status, const QString &text, const QIconSet &pix, const KShortcut &cut, + const QObject *receiver, const char *slot, QObject *parent, const char *name ) + : KSelectAction(text, pix, cut, parent, name ) , d(new Private( status ) ) +{ + QObject::connect( Kopete::Away::getInstance(), SIGNAL( messagesChanged() ), + this, SLOT( slotAwayChanged() ) ); + + QObject::connect( this, SIGNAL( awayMessageSelected( const Kopete::OnlineStatus &, const QString & ) ), + receiver, slot ); + + QObject::connect( this, SIGNAL( activated( int ) ), + this, SLOT( slotSelectAway( int ) ) ); + + slotAwayChanged(); +} + +AwayAction::~AwayAction() +{ + delete d; +} + +void AwayAction::slotAwayChanged() +{ + QStringList awayMessages = Kopete::Away::getInstance()->getMessages(); + for( QStringList::iterator it = awayMessages.begin(); it != awayMessages.end(); ++it ) + { + (*it) = KStringHandler::rsqueeze( *it ); + } + d->reasonCount = awayMessages.count(); + QStringList menu; + menu << i18n( "No Message" ); + menu << i18n( "New Message..." ); + menu << QString::null ; //separator + menu += awayMessages ; + setItems( menu ); + setCurrentItem( -1 ); +} + +void AwayAction::slotSelectAway( int index ) +{ + //remove that crappy check mark cf bug 119862 + setCurrentItem( -1 ); + + Kopete::Away *mAway = Kopete::Away::getInstance(); + QString awayReason; + + // Index == -1 means this is a result of Global Away all. + // Use the last entered message (0) + if( index == -1 ) + index = 0; + + switch(index) + { + case 0: + awayReason = QString::null; + break; + case 1: + bool ok; + awayReason = KInputDialog::getText( i18n( "New Away Message" ), i18n( "Please enter your away reason:" ) , QString::null , &ok ); + if(!ok) //the user canceled + return; + if( !awayReason.isEmpty() ) + Kopete::Away::getInstance()->addMessage( awayReason ); + break; + case 2: + //not possible case, that's a separator + break; + default: + if( index-3 < d->reasonCount ) + awayReason = mAway->getMessage( index-3 ); + } + + emit awayMessageSelected( awayReason ) ; + emit awayMessageSelected( d->status, awayReason ); +} + +} //END namespace Kopete + +#include "kopeteawayaction.moc" + diff --git a/kopete/libkopete/kopeteawayaction.h b/kopete/libkopete/kopeteawayaction.h new file mode 100644 index 00000000..f8ab9d64 --- /dev/null +++ b/kopete/libkopete/kopeteawayaction.h @@ -0,0 +1,91 @@ +/* + kopetehistorydialog.h - Kopete Away Action + + Copyright (c) 2003 Jason Keirstead <jason@keirstead.org> + + Kopete (c) 2002 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEAWAYACTION_H +#define KOPETEAWAYACTION_H + +#include <kdeversion.h> +#include <kactionclasses.h> +#include <kaction.h> + +#include "kopete_export.h" + +namespace Kopete +{ + +class OnlineStatus; + +/** + * @class Kopete::AwayAction + * + * Kopete::AwayAction is a KAction that lets you select an away message + * from the list of predefined away messages, or enter a custom one. + * + * @author Jason Keirstead <jason@keirstead.org> + */ +class KOPETE_EXPORT AwayAction : public KSelectAction +{ + Q_OBJECT + public: + /** + * Constructor + * @p text, @p pix, @p cut, @p receiver, @p slot, @p parent and + * @p name are all handled by KSelectAction. + **/ + AwayAction(const QString &text, const QIconSet &pix, + const KShortcut &cut, const QObject *receiver, const char *slot, + QObject *parent, const char *name = 0); + + /** + * Constructor + * @param status the OnlineStatus that appears in the signal + * @param slot must have the following signature: ( const OnlineStatus &, const QString & ) + * @p text, @p pix, @p cut, @p receiver, @p slot, @p parent and + * @p name are all handled by KSelectAction. + **/ + AwayAction(const OnlineStatus &status, const QString &text, const QIconSet &pix, + const KShortcut &cut, const QObject *receiver, const char *slot, + QObject *parent, const char *name = 0); + + /** + * Destructor. + */ + ~AwayAction(); + + signals: + /** + * @brief Emits when the user selects an away message + */ + void awayMessageSelected( const QString & ); + + /** + * same as above, but with the saved status + */ + void awayMessageSelected( const Kopete::OnlineStatus& , const QString & ); + + private slots: + void slotAwayChanged(); + void slotSelectAway( int index ); + + private: + class Private; + Private *d; +}; + +} + +#endif diff --git a/kopete/libkopete/kopeteawaydialog.cpp b/kopete/libkopete/kopeteawaydialog.cpp new file mode 100644 index 00000000..0dbb7023 --- /dev/null +++ b/kopete/libkopete/kopeteawaydialog.cpp @@ -0,0 +1,143 @@ +/* + kopeteawaydialog.cpp - Kopete Away Dialog + + Copyright (c) 2002 by Hendrik vom Lehn <hvl@linux-4-ever.de> + Copyright (c) 2003 by Martijn Klingens <klingens@kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteawaydialog.h" + +#include <kcombobox.h> +#include <kdebug.h> +#include <klineedit.h> +#include <klocale.h> +#include <kstringhandler.h> + +#include "kopeteaway.h" +#include "kopeteawaydialogbase.h" + +class KopeteAwayDialogPrivate +{ +public: + KopeteAwayDialog_Base *base; +}; + +KopeteAwayDialog::KopeteAwayDialog( QWidget *parent, const char *name ) +: KDialogBase( parent, name, true, i18n( "Global Away Message" ), + KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok, true ) +{ + //kdDebug( 14010 ) << k_funcinfo << "Building KopeteAwayDialog..." << endl; + + d = new KopeteAwayDialogPrivate; + + d->base = new KopeteAwayDialog_Base( this ); + setMainWidget( d->base ); + + QObject::connect( d->base->cmbHistory, SIGNAL( activated( int ) ), this, SLOT( slotComboBoxSelection( int ) ) ); + + awayInstance = Kopete::Away::getInstance(); + mExtendedAwayType = 0; + init(); + + //kdDebug( 14010 ) << k_funcinfo << "KopeteAwayDialog created." << endl; +} + +KopeteAwayDialog::~KopeteAwayDialog() +{ + delete d; +} + +void KopeteAwayDialog::slotComboBoxSelection( int index ) +{ + // If they selected something out of the combo box + // They probably want to use it + d->base->txtOneShot->setText( awayInstance->getMessage(index) ); + d->base->txtOneShot->setCursorPosition( 0 ); +} + +void KopeteAwayDialog::show() +{ + // When this show is called, set the + // mExtendedAwayType to the empty string + mExtendedAwayType = 0; + + // Reinit the GUI + init(); + + //kdDebug( 14010 ) << k_funcinfo << "Showing Dialog with no extended away type" << endl; + + KDialogBase::show(); +} + +void KopeteAwayDialog::show( int awayType ) +{ + mExtendedAwayType = awayType; + + // Reinit the GUI to set it up correctly + init(); + + kdDebug( 14010 ) << k_funcinfo << "Showing Dialog with extended away type " << awayType << endl; + + KDialogBase::show(); +} + +void KopeteAwayDialog::cancelAway( int /* awayType */ ) +{ + /* Empty default implementation */ +} + +void KopeteAwayDialog::init() +{ + QStringList awayMessages = awayInstance->getMessages(); + for( QStringList::iterator it = awayMessages.begin(); it != awayMessages.end(); ++it ) + { + *it = KStringHandler::rsqueeze( *it ); + } + + d->base->cmbHistory->clear(); + d->base->cmbHistory->insertStringList( awayMessages ); + d->base->txtOneShot->setText( awayMessages[0] ); + + d->base->txtOneShot->setFocus(); + d->base->txtOneShot->setCursorPosition( 0 ); +} + +QString KopeteAwayDialog::getSelectedAwayMessage() +{ + mLastUserAwayMessage = d->base->txtOneShot->text(); + return mLastUserAwayMessage; +} + +void KopeteAwayDialog::slotOk() +{ + // Save the text the user typed + mLastUserTypedMessage = d->base->txtOneShot->text(); + + setAway( mExtendedAwayType ); + + KDialogBase::slotOk(); +} + +void KopeteAwayDialog::slotCancel() +{ + // Call the virtual function with the type of away + cancelAway( mExtendedAwayType ); + + KDialogBase::slotCancel(); +} + +#include "kopeteawaydialog.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopeteawaydialog.h b/kopete/libkopete/kopeteawaydialog.h new file mode 100644 index 00000000..313cafe2 --- /dev/null +++ b/kopete/libkopete/kopeteawaydialog.h @@ -0,0 +1,201 @@ +/* + kopeteawaydialog.h - Kopete Away Dialog + + Copyright (c) 2002 by Hendrik vom Lehn <hvl@linux-4-ever.de> + Copyright (c) 2003 by Martijn Klingens <klingens@kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEAWAYDIALOG_H +#define KOPETEAWAYDIALOG_H + +#include <kdialogbase.h> +#include "kopete_export.h" + +namespace Kopete +{ +class Away; +} + +class KopeteAwayDialogPrivate; + +/** + * KopeteAwayDialog is a base class used for implementing + * Away Message selection dialogs in Kopete. It presents + * the user with a list of pre-written away messages and + * a line edit for them to type a "single shot" away message, + * one that is not saved and will be lost the next time + * they restart the application. + * + * Individual protocols should subclass this class for protocol + * specific Away Message choosers (in the case that the user + * wants to set only one protocol away). There are methods for + * getting the message that the user selected, as well as a + * virtual method that should be implemented that is called + * when the user selects "OK", and should be used to do + * protocol specific actions needed to set the user as + * "Away" (or whatever the protocol calls it). + * + * @author Hendrik vom Lehn <hvl@linux-4-ever.de> + * @author Christopher TenHarmsel <tenharmsel@users.sourceforge.net> + */ + +class KOPETE_EXPORT KopeteAwayDialog : public KDialogBase +{ + Q_OBJECT + +public: + /** + * Constructor for the Away Dialog + * @param parent The object that owns this + * @param name Name for this object + */ + KopeteAwayDialog( QWidget *parent = 0, const char *name = 0 ); + + /** + * Destructor + */ + virtual ~KopeteAwayDialog(); + +protected: + /** + * Do not delete this, this instance will + * deleted when the application closes + */ + Kopete::Away *awayInstance; + + /** + * \brief Gets the last selected away message + * @return An away message + */ + QString getSelectedAwayMessage(); + + /** + * \brief Sets the user away + * + * This method is called when the user clicks + * OK in the GUI, signalling that they wish + * to set the away message that they have chosen. + * Please reimplement this method to do protocol + * specific things, and use getSelectedAwayMessage() + * to get the text of the message that the user + * selected. + * + * @param awayType This is the away type specified + * if show was called with a parameter. If show() was called + * instead, this parameter will be the empty string. You + * will need to compare it to an enum that you declare + * in your subclass. + */ + virtual void setAway( int awayType ) = 0; + + /** + * \brief Called when "Cancel" is clicked + * + * This method is called when the user clicks + * Cancel in the GUI, signalling that they + * canceled their request to mark themselves as + * away. If your implementation finds this + * information useful, implement this method + * to handle this info. By default it does nothing + * + * @param awayType This is the away type specified + * if show was called with a parameter, if show() was called + * instead, this parameter will be the empty string. + */ + virtual void cancelAway( int awayType ); + +public slots: + /** + * \brief Shows the dialog + */ + virtual void show(); + + /** + * \brief Shows the dialog + * + * Shows the away dialog, but maintains a "state" + * so you can specify if you're setting away, + * do not disturb, gone, etc for protocols that + * support this like ICQ and MSN. + * + * This string does not have any special internal + * meaning, but rather will get passed to setAway() + * when it is called so that you can decide what + * kind of "away" you really want to do. + * + * @param awayType The type of "away" you want to set. + */ + void show( int awayType ); + +protected slots: + /** + * This slot is called when the user click on "OK" + * it will call setAway(), which is pure virtual and + * should be implemented for specific needs + */ + virtual void slotOk(); + + /** + * This slot is called when the user clicks on + * "Cancel". It calls cancelAway(), which is + * pure virtual and should be implemented to + * fit your specific needs if the user selects + * "Cancel". This method will close the + * dialog, but if you require any specific actions + * please implement them in cancelAway(). + */ + virtual void slotCancel(); + +private slots: + /** + * \brief An entry was selected from the combo box + */ + void slotComboBoxSelection( int index ); + +private: + /** + * Initializes the GUI elements every time the + * dialog is show. Basically used for remembering + * the singleshot message that the user may have + * typed in. + */ + void init(); + + /** + * The last user-entered away text + * or the title of the last selected + * saved away message, whichever was + * last chosen + */ + QString mLastUserAwayMessage; + + /** + * The last message that the user typed in the + * line edit + */ + QString mLastUserTypedMessage; + + /** + * This is used to store the type of away that we're + * going to go. + */ + int mExtendedAwayType; + + KopeteAwayDialogPrivate *d; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopeteblacklister.cpp b/kopete/libkopete/kopeteblacklister.cpp new file mode 100644 index 00000000..8ec5c54b --- /dev/null +++ b/kopete/libkopete/kopeteblacklister.cpp @@ -0,0 +1,109 @@ +/* + kopeteblacklister.cpp - Kopete BlackLister + + Copyright (c) 2004 by Roie Kerstein <sf_kersteinroie@bezeqint.net> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteblacklister.h" + +#include "kopetecontact.h" + +#include <kconfig.h> +#include <kglobal.h> + +#include <qstringlist.h> + +namespace Kopete +{ + +class BlackLister::Private +{ +public: + QStringList blacklist; + QString owner; + QString protocol; +}; + + +BlackLister::BlackLister(const QString &protocolId, const QString &accountId, QObject *parent, const char *name) + : QObject(parent, name), d( new Private ) +{ + KConfig *config = KGlobal::config(); + + d->owner = accountId; + d->protocol = protocolId; + config->setGroup("BlackLister"); + d->blacklist = config->readListEntry( d->protocol + QString::fromLatin1("_") + d->owner ); +} + +BlackLister::~BlackLister() +{ + delete d; +} + + +bool BlackLister::isBlocked(const QString &contactId) +{ + return (d->blacklist.find( contactId ) != d->blacklist.end() ); +} + +bool BlackLister::isBlocked(Contact *contact) +{ + return isBlocked(contact->contactId()); +} + +void BlackLister::addContact(const QString &contactId) +{ + if( !isBlocked(contactId) ) + { + d->blacklist += contactId; + saveToDisk(); + emit contactAdded( contactId ); + } +} + +void BlackLister::addContact(Contact *contact) +{ + QString temp = contact->contactId(); + + addContact( temp ); +} + +void BlackLister::removeContact(Contact *contact) +{ + QString temp = contact->contactId(); + + removeContact( temp ); +} + +void BlackLister::saveToDisk() +{ + KConfig *config = KGlobal::config(); + + config->setGroup("BlackLister"); + config->writeEntry( d->protocol + QString::fromLatin1("_") + d->owner, d->blacklist ); + config->sync(); +} + +void BlackLister::removeContact(const QString &contactId) +{ + if( isBlocked(contactId) ) + { + d->blacklist.remove( contactId ); + saveToDisk(); + emit contactRemoved( contactId ); + } +} + +} + +#include "kopeteblacklister.moc" diff --git a/kopete/libkopete/kopeteblacklister.h b/kopete/libkopete/kopeteblacklister.h new file mode 100644 index 00000000..ed3e5566 --- /dev/null +++ b/kopete/libkopete/kopeteblacklister.h @@ -0,0 +1,124 @@ +/* + kopeteblacklister.h - Kopete BlackLister + + Copyright (c) 2004 by Roie Kerstein <sf_kersteinroie@bezeqint.net> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEBLACKLISTER_H +#define KOPETEBLACKLISTER_H + +#include <qobject.h> + +namespace Kopete +{ + +class Contact; + +/** + * @brief Manages the list of blacklisted contacts for an account + * + * This class manages the list of contacts the user wishes + * to ignore permanently. In order to use the this class, there is no need to + * create an instance. Use the @ref Kopete::Account::blackLister() instead. + * + * Keep in mind that this class does not discard messages from blocked + * users - It only manages the list. It is the up to the protocol to + * check whether a user is blocked, and act accordingly. A protocol may + * re-implement @ref Kopete::Account::block() and @ref Kopete::Account::unblock() + * and use @ref Kopete::Account::blackLister() as a persistent list manager + * only, or connect the signals @ref contactAdded() and @ref contactRemoved() + * to its slots. + * + * @sa Kopete::Account::block() Kopete::Account::unblock() + * + * @author Roie Kerstein <sf_kersteinroie@bezeqint.net> + */ +class BlackLister : public QObject +{ + Q_OBJECT + +public: + /** + * Create an instance, and read the blacklist from disk if it exists. + * @param protocolId is the ID of the protocol owning accountId + * @param accountId is the ID of the owning Account. + * @param parent The QObject parent for this class. + * @param name The QObject name for this class. + */ + BlackLister( const QString &protocolId, const QString &accountId, QObject *parent = 0, const char *name = 0 ); + ~BlackLister(); + + /** + * \return @c true if @p contact is blocked, @c false otherwise. + */ + bool isBlocked( Contact *contact ); + + /** + * \return @c true if the contact with ID @p contactId is blocked, @c false otherwise. + */ + bool isBlocked( const QString &contactId ); + +public slots: + /** + * Add a contact to the blacklist. + * + * This function emits the @ref contactAdded() signal. + * @param contactId is the ID of the contact to be added to the list. + */ + void addContact( const QString &contactId ); + + /** + * @overload + */ + void addContact( Contact *contact ); + + /** + * \brief Remove a contact from the blacklist. + * + * Removes the contact from the blacklist. + * This function emits the @ref contactRemoved() signal. + * @param contact is the contact to be removed from the list. + */ + void removeContact( Contact *contact ); + + /** + * @overload + */ + void removeContact( const QString &contactId ); + +signals: + /** + * \brief A new contact has been added to the list + * + * Connect to this signal if you want to perform additional actions, + * and you prefer not to derive from this class. + */ + void contactAdded( const QString &contactId ); + + /** + * \brief A contact has been removed from the list + * + * Connect to this signal if you want to perform additional actions, + * and you prefer not to derive from this class. + */ + void contactRemoved( const QString &contactId ); + +private: + void saveToDisk(); + + class Private; + Private *d; +}; + +} + +#endif diff --git a/kopete/libkopete/kopetechatsession.cpp b/kopete/libkopete/kopetechatsession.cpp new file mode 100644 index 00000000..9ebf1d07 --- /dev/null +++ b/kopete/libkopete/kopetechatsession.cpp @@ -0,0 +1,515 @@ +/* + kopetechatsession.cpp - Manages all chats + + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002 by Daniel Stone <dstone@kde.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart @ kde.org> + Copyright (c) 2003 by Jason Keirstead <jason@keirstead.org> + Copyright (c) 2005 by Michaël Larouche <michael.larouche@kdemail.net> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetechatsession.h" + +#include <qapplication.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <kdeversion.h> +#include <kglobal.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <knotification.h> + +#include "kopeteaccount.h" +#include "kopetecommandhandler.h" +#include "kopetechatsessionmanager.h" +#include "kopetemessagehandlerchain.h" +#include "kopetemetacontact.h" +#include "knotification.h" +#include "kopeteprefs.h" +#include "kopeteuiglobal.h" +#include "kopeteglobal.h" +#include "kopeteview.h" +#include "kopetecontact.h" + +class KMMPrivate +{ +public: + Kopete::ContactPtrList mContactList; + const Kopete::Contact *mUser; + QMap<const Kopete::Contact *, Kopete::OnlineStatus> contactStatus; + Kopete::Protocol *mProtocol; + bool isEmpty; + bool mCanBeDeleted; + unsigned int refcount; + bool customDisplayName; + QDateTime awayTime; + QString displayName; + KopeteView *view; + bool mayInvite; + Kopete::MessageHandlerChain::Ptr chains[3]; +}; + +Kopete::ChatSession::ChatSession( const Kopete::Contact *user, + Kopete::ContactPtrList others, Kopete::Protocol *protocol, const char *name ) +: QObject( user->account(), name ) +{ + d = new KMMPrivate; + d->mUser = user; + d->mProtocol = protocol; + d->isEmpty = others.isEmpty(); + d->mCanBeDeleted = true; + d->refcount = 0; + d->view = 0L; + d->customDisplayName = false; + d->mayInvite = false; + + for ( Kopete::Contact *c = others.first(); c; c = others.next() ) + addContact( c, true ); + + connect( user, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), this, + SLOT( slotOnlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) ); + + if( user->metaContact() ) + connect( user->metaContact(), SIGNAL( photoChanged() ), this, SIGNAL( photoChanged() ) ); + + slotUpdateDisplayName(); +} + +Kopete::ChatSession::~ChatSession() +{ + //for ( Kopete::Contact *c = d->mContactList.first(); c; c = d->mContactList.next() ) + // c->setConversations( c->conversations() - 1 ); + + if ( !d ) + return; + d->mCanBeDeleted = false; //prevent double deletion + Kopete::ChatSessionManager::self()->removeSession( this ); + emit closing( this ); + delete d; +} + +void Kopete::ChatSession::slotOnlineStatusChanged( Kopete::Contact *c, const Kopete::OnlineStatus &status, const Kopete::OnlineStatus &oldStatus ) +{ + slotUpdateDisplayName(); + emit onlineStatusChanged((Kopete::Contact*)c, status, oldStatus); +} + +void Kopete::ChatSession::setContactOnlineStatus( const Kopete::Contact *contact, const Kopete::OnlineStatus &status ) +{ + Kopete::OnlineStatus oldStatus = d->contactStatus[ contact ]; + d->contactStatus[ contact ] = status; + disconnect( contact, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus &) ) ); + emit onlineStatusChanged( (Kopete::Contact*)contact, status, oldStatus ); +} + +const Kopete::OnlineStatus Kopete::ChatSession::contactOnlineStatus( const Kopete::Contact *contact ) const +{ + if ( d->contactStatus.contains( contact ) ) + return d->contactStatus[ contact ]; + + return contact->onlineStatus(); +} + +const QString Kopete::ChatSession::displayName() +{ + if ( d->displayName.isNull() ) + { + slotUpdateDisplayName(); + } + + return d->displayName; +} + +void Kopete::ChatSession::setDisplayName( const QString &newName ) +{ + d->displayName = newName; + d->customDisplayName = true; + emit displayNameChanged(); +} + +void Kopete::ChatSession::slotUpdateDisplayName() +{ + if( d->customDisplayName ) + return; + + Kopete::Contact *c = d->mContactList.first(); + + //If there is no member yet, don't try to update the display name + if ( !c ) + return; + + d->displayName=QString::null; + do + { + if(! d->displayName.isNull() ) + d->displayName.append( QString::fromLatin1( ", " ) ) ; + + if ( c->metaContact() ) + d->displayName.append( c->metaContact()->displayName() ); + else + { + QString nick=c->property(Kopete::Global::Properties::self()->nickName()).value().toString(); + d->displayName.append( nick.isEmpty() ? c->contactId() : nick ); + } + c=d->mContactList.next(); + } while (c); + + //If we have only 1 contact, add the status of him + if ( d->mContactList.count() == 1 ) + { + d->displayName.append( QString::fromLatin1( " (%1)" ).arg( d->mContactList.first()->onlineStatus().description() ) ); + } + + emit displayNameChanged(); +} + +const Kopete::ContactPtrList& Kopete::ChatSession::members() const +{ + return d->mContactList; +} + +const Kopete::Contact* Kopete::ChatSession::myself() const +{ + return d->mUser; +} + +Kopete::Protocol* Kopete::ChatSession::protocol() const +{ + return d->mProtocol; +} + + +#include "kopetemessagehandler.h" +#include "kopetemessageevent.h" + +// FIXME: remove this and the friend decl in KMM +class Kopete::TemporaryKMMCallbackAppendMessageHandler : public Kopete::MessageHandler +{ + Kopete::ChatSession *manager; +public: + TemporaryKMMCallbackAppendMessageHandler( Kopete::ChatSession *manager ) + : manager(manager) + { + } + void handleMessage( Kopete::MessageEvent *event ) + { + Kopete::Message message = event->message(); + emit manager->messageAppended( message, manager ); + delete event; + } +}; + +class TempFactory : public Kopete::MessageHandlerFactory +{ +public: + Kopete::MessageHandler *create( Kopete::ChatSession *manager, Kopete::Message::MessageDirection ) + { + return new Kopete::TemporaryKMMCallbackAppendMessageHandler( manager ); + } + int filterPosition( Kopete::ChatSession *, Kopete::Message::MessageDirection ) + { + // FIXME: somewhere after everyone else. + return 100000; + } +}; + +Kopete::MessageHandlerChain::Ptr Kopete::ChatSession::chainForDirection( Kopete::Message::MessageDirection dir ) +{ + if( dir < 0 || dir > 2) + kdFatal(14000) << k_funcinfo << "invalid message direction " << dir << endl; + if( !d->chains[dir] ) + { + TempFactory theTempFactory; + d->chains[dir] = Kopete::MessageHandlerChain::create( this, dir ); + } + return d->chains[dir]; +} + +void Kopete::ChatSession::sendMessage( Kopete::Message &message ) +{ + message.setManager( this ); + Kopete::Message sentMessage = message; + if ( !Kopete::CommandHandler::commandHandler()->processMessage( message, this ) ) + { + emit messageSent( sentMessage, this ); + if ( !account()->isAway() || KopetePrefs::prefs()->soundIfAway() ) + { + KNotification::event(QString::fromLatin1( "kopete_outgoing" ), i18n( "Outgoing Message Sent" ) ); + } + } + else + { + messageSucceeded(); + } +} + +void Kopete::ChatSession::messageSucceeded() +{ + emit messageSuccess(); +} + +void Kopete::ChatSession::emitNudgeNotification() +{ + KNotification::event( QString::fromLatin1("buzz_nudge"), i18n("A contact sent you a buzz/nudge.") ); +} + +void Kopete::ChatSession::appendMessage( Kopete::Message &msg ) +{ + msg.setManager( this ); + + if ( msg.direction() == Kopete::Message::Inbound ) + { + QString nick=myself()->property(Kopete::Global::Properties::self()->nickName()).value().toString(); + if ( KopetePrefs::prefs()->highlightEnabled() && !nick.isEmpty() && + msg.plainBody().contains( QRegExp( QString::fromLatin1( "\\b(%1)\\b" ).arg( nick ), false ) ) ) + { + msg.setImportance( Kopete::Message::Highlight ); + } + + emit messageReceived( msg, this ); + } + + // outbound messages here are ones the user has sent that are now + // getting reflected back to the chatwindow. they should go down + // the incoming chain. + Kopete::Message::MessageDirection chainDirection = msg.direction(); + if( chainDirection == Kopete::Message::Outbound ) + chainDirection = Kopete::Message::Inbound; + + chainForDirection( chainDirection )->processMessage( msg ); +// emit messageAppended( msg, this ); +} + +void Kopete::ChatSession::addContact( const Kopete::Contact *c, const Kopete::OnlineStatus &initialStatus, bool suppress ) +{ + if( !d->contactStatus.contains(c) ) + d->contactStatus[ c ] = initialStatus; + addContact( c, suppress ); +} + +void Kopete::ChatSession::addContact( const Kopete::Contact *c, bool suppress ) +{ + //kdDebug( 14010 ) << k_funcinfo << endl; + if ( d->mContactList.contains( c ) ) + { + kdDebug( 14010 ) << k_funcinfo << "Contact already exists" <<endl; + emit contactAdded( c, suppress ); + } + else + { + if ( d->mContactList.count() == 1 && d->isEmpty ) + { + kdDebug( 14010 ) << k_funcinfo << " FUCKER ZONE " << endl; + /* We have only 1 contact before, so the status of the + message manager was given from that contact status */ + Kopete::Contact *old = d->mContactList.first(); + d->mContactList.remove( old ); + d->mContactList.append( c ); + + disconnect( old, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT( slotOnlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus &) ) ); + + if ( old->metaContact() ) + { + disconnect( old->metaContact(), SIGNAL( displayNameChanged( const QString &, const QString & ) ), this, SLOT( slotUpdateDisplayName() ) ); + disconnect( old->metaContact(), SIGNAL( photoChanged() ), this, SIGNAL( photoChanged() ) ); + } + else + disconnect( old, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), this, SLOT( slotUpdateDisplayName() ) ); + emit contactAdded( c, suppress ); + emit contactRemoved( old, QString::null ); + } + else + { + d->mContactList.append( c ); + emit contactAdded( c, suppress ); + } + + connect( c, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT( slotOnlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus &) ) ); +; + if ( c->metaContact() ) + { + connect( c->metaContact(), SIGNAL( displayNameChanged( const QString &, const QString & ) ), this, SLOT( slotUpdateDisplayName() ) ); + connect( c->metaContact(), SIGNAL( photoChanged() ), this, SIGNAL( photoChanged() ) ); + } + else + connect( c, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), this, SLOT( slotUpdateDisplayName() ) ); + connect( c, SIGNAL( contactDestroyed( Kopete::Contact * ) ), this, SLOT( slotContactDestroyed( Kopete::Contact * ) ) ); + + slotUpdateDisplayName(); + } + d->isEmpty = false; +} + +void Kopete::ChatSession::removeContact( const Kopete::Contact *c, const QString& reason, Kopete::Message::MessageFormat format, bool suppressNotification ) +{ + kdDebug( 14010 ) << k_funcinfo << endl; + if ( !c || !d->mContactList.contains( c ) ) + return; + + if ( d->mContactList.count() == 1 ) + { + kdDebug( 14010 ) << k_funcinfo << "Contact not removed. Keep always one contact" << endl; + d->isEmpty = true; + } + else + { + d->mContactList.remove( c ); + + disconnect( c, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT( slotOnlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus &) ) ); + + if ( c->metaContact() ) + { + disconnect( c->metaContact(), SIGNAL( displayNameChanged( const QString &, const QString & ) ), this, SLOT( slotUpdateDisplayName() ) ); + disconnect( c->metaContact(), SIGNAL( photoChanged() ), this, SIGNAL( photoChanged() ) ); + } + else + disconnect( c, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), this, SLOT( slotUpdateDisplayName() ) ); + disconnect( c, SIGNAL( contactDestroyed( Kopete::Contact * ) ), this, SLOT( slotContactDestroyed( Kopete::Contact * ) ) ); + + slotUpdateDisplayName(); + } + + d->contactStatus.remove( c ); + + emit contactRemoved( c, reason, format, suppressNotification ); +} + +void Kopete::ChatSession::receivedTypingMsg( const Kopete::Contact *c, bool t ) +{ + emit remoteTyping( c, t ); +} + +void Kopete::ChatSession::receivedTypingMsg( const QString &contactId, bool t ) +{ + for ( Kopete::Contact *it = d->mContactList.first(); it; it = d->mContactList.next() ) + { + if ( it->contactId() == contactId ) + { + receivedTypingMsg( it, t ); + return; + } + } +} + +void Kopete::ChatSession::typing( bool t ) +{ + emit myselfTyping( t ); +} + +void Kopete::ChatSession::receivedEventNotification( const QString& notificationText) +{ + emit eventNotification( notificationText ); +} + +void Kopete::ChatSession::setCanBeDeleted ( bool b ) +{ + d->mCanBeDeleted = b; + if (d->refcount < (b?1:0) && !d->view ) + deleteLater(); +} + +void Kopete::ChatSession::ref () +{ + d->refcount++; +} +void Kopete::ChatSession::deref () +{ + d->refcount--; + if ( d->refcount < 1 && d->mCanBeDeleted && !d->view ) + deleteLater(); +} + +KopeteView* Kopete::ChatSession::view( bool canCreate, const QString &requestedPlugin ) +{ + if ( !d->view && canCreate ) + { + d->view = Kopete::ChatSessionManager::self()->createView( this, requestedPlugin ); + if ( d->view ) + { + connect( d->view->mainWidget(), SIGNAL( closing( KopeteView * ) ), this, SLOT( slotViewDestroyed( ) ) ); + } + else + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Error, + i18n( "<qt>An error has occurred while creating a new chat window. The chat window has not been created.</qt>" ), + i18n( "Error While Creating Chat Window" ) ); + } + } + return d->view; +} + +void Kopete::ChatSession::slotViewDestroyed() +{ + d->view = 0L; + if ( d->mCanBeDeleted && d->refcount < 1) + deleteLater(); +} + +Kopete::Account *Kopete::ChatSession::account() const +{ + return myself()->account(); +} + +void Kopete::ChatSession::slotContactDestroyed( Kopete::Contact *contact ) +{ + if(contact == myself()) + deleteLater(); + + if( !contact || !d->mContactList.contains( contact ) ) + return; + + //This is a workaround to prevent crash if the contact get deleted. + // in the best case, we should ask the protocol to recreate a temporary contact. + // (remember: the contact may be deleted when the users removes it from the contactlist, or when closing kopete ) + d->mContactList.remove( contact ); + emit contactRemoved( contact, QString::null ); + + if ( d->mContactList.isEmpty() ) + deleteLater(); +} + +bool Kopete::ChatSession::mayInvite() const +{ + return d->mayInvite; +} + +void Kopete::ChatSession::inviteContact(const QString& ) +{ + //default implementation do nothing +} + +void Kopete::ChatSession::setMayInvite( bool b ) +{ + d->mayInvite=b; +} + +void Kopete::ChatSession::raiseView() +{ + KopeteView *v=view(true, KopetePrefs::prefs()->interfacePreference() ); + if(v) + v->raise(true); +} + +#include "kopetechatsession.moc" + + + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetechatsession.h b/kopete/libkopete/kopetechatsession.h new file mode 100644 index 00000000..86d5fa64 --- /dev/null +++ b/kopete/libkopete/kopetechatsession.h @@ -0,0 +1,405 @@ +/* + kopetechatsession.h - Manages all chats + + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002 by Daniel Stone <dstone@kde.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart @ kde.org> + Copyright (c) 2003 by Jason Keirstead <jason@keirstead.org> + Copyright (c) 2005 by Michaël Larouche <michael.larouche@kdemail.net> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef __KOPETECHATSESSION_H__ +#define __KOPETECHATSESSION_H__ + +#include <qobject.h> +#include <qptrlist.h> +#include <qvaluelist.h> + +#include <kxmlguiclient.h> + +#include "kopete_export.h" + +// FIXME: get rid of these includes +#include "kopetemessage.h" +#include "kopetemessagehandlerchain.h" + +class KMMPrivate; + +class KopeteView; + +namespace Kopete +{ + +class Contact; +class Message; +class Protocol; +class OnlineStatus; +class Account; +class ChatSessionManager; +class MessageHandlerChain; +class TemporaryKMMCallbackAppendMessageHandler; + +typedef QPtrList<Contact> ContactPtrList; +typedef QValueList<Message> MessageList; + + +/** + * @author Duncan Mac-Vicar Prett <duncan@kde.org> + * @author Daniel Stone <dstone@kde.org> + * @author Martijn Klingens <klingens@kde.org> + * @author Olivier Goffart <ogoffart @ kde.org> + * @author Jason Keirstead <jason@keirstead.org> + * + * The Kopete::ChatSession manages a single chat. + * It is an interface between the protocol, and the chatwindow. + * The protocol can connect to @ref messageSent() signals to send the message, and can + * append received message with @ref messageReceived() + * + * The KMM inherits from KXMLGUIClient, this client is merged with the chatwindow's ui + * so plugins can add childClients of this client to add their own actions in the + * chatwindow. + */ +class KOPETE_EXPORT ChatSession : public QObject , public KXMLGUIClient +{ + // friend class so the object factory can access the protected constructor + friend class ChatSessionManager; + + Q_OBJECT + +public: + /** + * Delete a chat manager instance + * You shouldn't delete the KMM yourself. it will be deleted when the chatwindow is closed + * see also @ref setCanBeDeleted() , @ref deref() + */ + ~ChatSession(); + + /** + * @brief Get a list of all contacts in the session + */ + const ContactPtrList& members() const; + + /** + * @brief Get the local user in the session + * @return the local user in the session, same as account()->myself() + */ + const Contact* myself() const; + + /** + * @brief Get the protocol being used. + * @return the protocol + */ + Protocol* protocol() const; + + /** + * @brief get the account + * @return the account + */ + Account *account() const ; + + /** + * @brief The caption of the chat + * + * Used for named chats + */ + const QString displayName(); + + /** + * @brief change the displayname + * + * change the display name of the chat + */ + void setDisplayName( const QString &displayName ); + + /** + * @brief set a specified KOS for specified contact in this KMM + * + * Set a special icon for a contact in this kmm only. + * by default, all contact have their own status + */ + void setContactOnlineStatus( const Contact *contact, const OnlineStatus &newStatus ); + + /** + * @brief get the status of a contact. + * + * see @ref setContactOnlineStatus() + */ + const OnlineStatus contactOnlineStatus( const Contact *contact ) const; + + /** + * @brief the manager's view + * + * Return the view for the supplied Kopete::ChatSession. If it already + * exists, it will be returned, otherwise, 0L will be returned or a new one + * if canCreate=true + * @param canCreate create a new one if it does not exist + * @param requestedPlugin Specifies the view plugin to use if we have to create one. + */ + // FIXME: canCreate should definitely be an enum and not a bool - Martijn + KopeteView* view( bool canCreate = false, const QString &requestedPlugin = QString::null ); + + /** + * says if you may invite contact from the same account to this chat with @ref inviteContact + * @see setMayInvite + * @return true if it is possible to invite contact to this chat. + */ + bool mayInvite() const ; + + /** + * this method is called when a contact is dragged to the contactlist. + * @p contactId is the id of the contact. the contact is supposed to be of the same account as + * the @ref account() but we can't be sure the Kopete::Contact is realy on the contactlist + * + * It is possible to drag contact only if @ref mayInvite return true + * + * the default implementaiton do nothing + */ + virtual void inviteContact(const QString &contactId); + + /** + * Returns the message handler chain for the message direction @p dir. + */ + MessageHandlerChain::Ptr chainForDirection( Message::MessageDirection dir ); + +signals: + /** + * @brief the KMM will be deleted + * Used by a Kopete::ChatSession to signal that it is closing. + */ + void closing( Kopete::ChatSession *kmm ); + + /** + * a message will be soon shown in the chatwindow. + * See @ref Kopete::ChatSessionManager::aboutToDisplay() signal + */ + void messageAppended( Kopete::Message &msg, Kopete::ChatSession *kmm = 0L ); + + /** + * a message will be soon received + * See @ref Kopete::ChatSessionManager::aboutToReceive() signal + */ + void messageReceived( Kopete::Message &msg, Kopete::ChatSession *kmm = 0L ); + + /** + * @brief a message is going to be sent + * + * The message is going to be sent. + * protocols can connect to this signal to send the message ro the network. + * the protocol have also to call @ref appendMessage() and @ref messageSucceeded() + * See also @ref Kopete::ChatSessionManager::aboutToSend() signal + */ + void messageSent( Kopete::Message &msg, Kopete::ChatSession *kmm = 0L ); + + /** + * The last message has finaly successfully been sent + */ + void messageSuccess(); + + /** + * @brief a new contact is now in the chat + */ + // FIXME: What's 'suppress'? Shouldn't this be an enum? - Martijn + void contactAdded( const Kopete::Contact *contact, bool suppress ); + + /** + * @brief a contact is no longer in this chat + */ + void contactRemoved( const Kopete::Contact *contact, const QString &reason, Kopete::Message::MessageFormat format = Message::PlainText, bool contactRemoved = false ); + + /** + * @brief a contact in this chat has changed his status + */ + void onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ); + + /** + * @brief The name of the chat is changed + */ + void displayNameChanged(); + + /** + * @brief emitting a typing notification + * + * The user is typing a message, or just stopped typing + * the protocol should connect to this signal to signal to others + * that the user is typing if the protocol supports this + * @param isTyping say if the user is typing or not + */ + void myselfTyping( bool isTyping ); + + /** + * Signals that a remote user is typing a message. + * the chatwindow connects to this signal to update the statusbar + */ + void remoteTyping( const Kopete::Contact *contact, bool isTyping ); + + /** + * Signals that a an event has to be displayed in the statusbar. + * The chatwindow connects to this signal to update the statusbar. + */ + void eventNotification( const QString& notificationText); + + /** + * @brief A contact within the chat session changed his photo. + * Used to update the contacts photo in chat window. + */ + void photoChanged(); + +public slots: + /** + * @brief Got a typing notification from a user + */ + void receivedTypingMsg( const Kopete::Contact *contact , bool isTyping = true ); + + /** + * Got a typing notification from a user. This is a convenience version + * of the above method that takes a QString contactId instead of a full + * Kopete::Contact + */ + void receivedTypingMsg( const QString &contactId, bool isTyping = true ); + + /** + * @brief Got an event notification from a user. + * It will emit the signal eventNotification(). Use this slot in your protocols + * and plugins to change chatwindow statusBar text. + */ + void receivedEventNotification( const QString& notificationText ); + + /** + * Show a message to the chatwindow, or append it to the queue. + * This is the function protocols HAVE TO call for both incoming and outgoing messages + * if the message must be showed in the chatwindow + */ + void appendMessage( Kopete::Message &msg ); + + /** + * Add a contact to the session + * @param c is the contact + * @param suppress mean the there will be no automatic notifications in the chatwindow. + * (note that i don't like the param suppress at all. it is used in irc to show a different notification (with an info text) + * a QStringinfo would be more interesting, but it is also used to don't show the notification when entering in a channel) + */ + void addContact( const Kopete::Contact *c, bool suppress = false ); + + /** + * Add a contact to the session with a pre-set initial status + * @param c is the contact + * @param initialStatus The initial contactOnlineStatus of the contact + * @param suppress mean the there will be no automatic notifications in the chatwindow. + * (note that i don't like the param suppress at all. it is used in irc to show a different notification (with an info text) + * a QStringinfo would be more interesting, but it is also used to don't show the notification when entering in a channel) + * @see contactOnlineStatus + */ + void addContact( const Kopete::Contact *c, const Kopete::OnlineStatus &initialStatus, bool suppress = false ); + + /** + * Remove a contact from the session + * @param contact is the contact + * @param reason is the optional raison message showed in the chatwindow + * @param format The format of the message + * @param suppressNotification prevents a notification of the removal in the chat view. See note in @ref addContact + */ + void removeContact( const Kopete::Contact *contact, const QString& reason = QString::null, Kopete::Message::MessageFormat format = Message::PlainText, bool suppressNotification = false ); + + /** + * Set if the KMM will be deleted when the chatwindow is deleted. It is useful if you want + * to keep the KMM alive even if the chatwindow is closed. + * Warning: if you set it to false, please keep in mind that you have to reset it to true + * later to delete it. In many case, you should never delete yourself the KMM, just call this + * this method. + * default is true. + * If there are no chatwindow when setting it to true, the kmm will be deleted. + * + * @deprecated use ref and deref + */ + void setCanBeDeleted ( bool canBeDeleted ); + + /** + * reference count the chat session. + * the chat session may be deleted only if the count reach 0 + * if you ref, don't forget to deref + * @see deref() + */ + void ref(); + /** + * dereference count the chat session + * if the reference counter reach 0 and there is no chat window open, the chat session will be deleted. + */ + void deref(); + + + /** + * Send a message to the user + */ + void sendMessage( Kopete::Message &message ); + + /** + * Tell the KMM that the user is typing + * This method should be called only by a chatwindow. It emits @ref myselfTyping signal + */ + void typing( bool t ); + + /** + * Protocols have to call this method when the last message sent has been correctly sent + * This will emit @ref messageSuccess signal. and allow the email window to get closed + */ + void messageSucceeded(); + + /** + * Protcols have to call this method if they want to emit a notification when a nudge/buzz is received. + */ + void emitNudgeNotification(); + + /** + * Raise the chat window and give him the focus + * It's used when the user wanted to activated (by clicking on the "view" button of a popup) + */ + void raiseView(); + +private slots: + void slotUpdateDisplayName(); + void slotViewDestroyed(); + void slotOnlineStatusChanged( Kopete::Contact *c, const Kopete::OnlineStatus &status, const Kopete::OnlineStatus &oldStatus ); + void slotContactDestroyed( Kopete::Contact *contact ); + +protected: + /** + * Create a message manager. This constructor is private, because the + * static factory method createSession() creates the object. You may + * not create instances yourself directly! + */ + ChatSession( const Contact *user, ContactPtrList others, + Protocol *protocol, const char *name = 0 ); + + /** + * Set wether or not contact from this account may be invited in this chat. + * By default, it is set to false + * @see inviteContact() + * @see mayInvite() + */ + void setMayInvite(bool); + +private: + KMMPrivate *d; + + // FIXME: remove + friend class TemporaryKMMCallbackAppendMessageHandler; +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetechatsessionmanager.cpp b/kopete/libkopete/kopetechatsessionmanager.cpp new file mode 100644 index 00000000..9b7dd489 --- /dev/null +++ b/kopete/libkopete/kopetechatsessionmanager.cpp @@ -0,0 +1,197 @@ +/* + kopetechatsessionmanager.cpp - Creates chat sessions + + Copyright (c) 2002-2003 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetechatsessionmanager.h" +#include "kopeteviewmanager.h" + +#include <kapplication.h> +#include <kdebug.h> + +#include "ui/kopeteview.h" +#include "kopetecontact.h" + +namespace Kopete { + +class ChatSessionManager::Private +{ + public: + QValueList <ChatSession*> sessions; +// UI::ChatView *activeView; +}; + +ChatSessionManager* ChatSessionManager::s_self = 0L; + +ChatSessionManager* ChatSessionManager::self() +{ + if( !s_self ) + s_self = new ChatSessionManager( kapp ); + + return s_self; +} + +ChatSessionManager::ChatSessionManager( QObject* parent, + const char* name ) + : QObject( parent, name ) +{ + d=new Private; + s_self = this; +} + +ChatSessionManager::~ChatSessionManager() +{ + s_self = 0L; + QValueListIterator<ChatSession*> it; + for ( it=d->sessions.begin() ; it!=d->sessions.end() ; ++it ) + { + kdDebug( 14010 ) << k_funcinfo << "Unloading KMM: Why this KMM isn't yet unloaded?" << endl; + (*it)->deleteLater(); + } + delete d; +} + +ChatSession* ChatSessionManager::findChatSession(const Contact *user, + ContactPtrList chatContacts, Protocol *protocol) +{ + ChatSession *result = 0L; + QValueList<ChatSession*>::Iterator it; + for ( it= d->sessions.begin(); it!=d->sessions.end() && !result ; ++it ) + { + ChatSession* cs=(*it); + if ( cs->protocol() == protocol && user == cs->myself() ) + { + QPtrList<Contact> contactlist = cs->members(); + + // set this to false if chatContacts doesn't contain current cs's contactlist + bool halfMatch = true; + + Contact *tmpContact; + for (tmpContact = contactlist.first(); tmpContact && halfMatch; tmpContact = contactlist.next()) + { + if ( !chatContacts.containsRef( tmpContact ) ) + halfMatch = false; + } + + // If chatContacts contains current cs's contactlist, try the other way around + if (halfMatch) + { + bool fullMatch = true; + for (tmpContact = chatContacts.first(); tmpContact && fullMatch; tmpContact = chatContacts.next()) + { + if ( !contactlist.containsRef( tmpContact ) ) + fullMatch = false; + } + // We have a winner + if (fullMatch) + result = cs; + } + } + } + return result; +} + +ChatSession *ChatSessionManager::create( + const Contact *user, ContactPtrList chatContacts, Protocol *protocol) +{ + ChatSession *result=findChatSession( user, chatContacts, protocol); + if (!result) + { + result = new ChatSession(user, chatContacts, protocol ); + registerChatSession(result); + } + return (result); +} + +void ChatSessionManager::slotReadMessage() +{ + emit readMessage(); +} + +void ChatSessionManager::registerChatSession(ChatSession * result) +{ + d->sessions.append( result ); + + /* + * There's no need for a slot here... just add a public remove() + * method and call from KMM's destructor + */ + connect( result, SIGNAL( messageAppended( Kopete::Message &, Kopete::ChatSession * ) ), + SIGNAL( aboutToDisplay( Kopete::Message & ) ) ); + connect( result, SIGNAL( messageSent( Kopete::Message &, Kopete::ChatSession * ) ), + SIGNAL( aboutToSend(Kopete::Message & ) ) ); + connect( result, SIGNAL( messageReceived( Kopete::Message &, Kopete::ChatSession * ) ), + SIGNAL( aboutToReceive(Kopete::Message & ) ) ); + + connect( result, SIGNAL(messageAppended( Kopete::Message &, Kopete::ChatSession *) ), + SIGNAL( display( Kopete::Message &, Kopete::ChatSession *) ) ); + + emit chatSessionCreated(result); +} + + +void ChatSessionManager::removeSession( ChatSession *session) +{ + kdDebug(14010) << k_funcinfo << endl; + d->sessions.remove( session ); +} + +QValueList<ChatSession*> ChatSessionManager::sessions( ) +{ + return d->sessions; +} + +KopeteView * ChatSessionManager::createView( ChatSession *kmm , const QString &requestedPlugin ) +{ + KopeteView *newView = KopeteViewManager::viewManager()->view(kmm,requestedPlugin); + if(!newView) + { + kdDebug(14010) << k_funcinfo << "View not successfuly created" << endl; + return 0L; + } + + QObject *viewObject = dynamic_cast<QObject *>(newView); + if(viewObject) + { + connect(viewObject, SIGNAL(activated(KopeteView *)), + this, SIGNAL(viewActivated(KopeteView *))); + connect(viewObject, SIGNAL(closing(KopeteView *)), + this, SIGNAL(viewClosing(KopeteView *))); + } + else + { + kdWarning(14010) << "Failed to cast view to QObject *" << endl; + } + + emit viewCreated( newView ) ; + return newView; +} + +void ChatSessionManager::postNewEvent(MessageEvent *e) +{ + emit newEvent(e); +} + +KopeteView *ChatSessionManager::activeView() +{ + return KopeteViewManager::viewManager()->activeView(); +} + +} //END namespace Kopete + +#include "kopetechatsessionmanager.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetechatsessionmanager.h b/kopete/libkopete/kopetechatsessionmanager.h new file mode 100644 index 00000000..e41eb14d --- /dev/null +++ b/kopete/libkopete/kopetechatsessionmanager.h @@ -0,0 +1,192 @@ +/* + kopetechatsessionmanager.h - Creates chat sessions + + Copyright (c) 2002-2003 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEMESSAGEMANAGERFACTORY_H +#define KOPETEMESSAGEMANAGERFACTORY_H + +#include <qobject.h> +#include <qptrlist.h> +#include <qintdict.h> +#include <qvaluelist.h> + +#include "kopetechatsession.h" +#include "kopetemessage.h" + +#include "kopete_export.h" + +class KopeteView; + +namespace Kopete +{ + +class Contact; +class Protocol; +class MessageEvent; + +typedef QPtrList<Contact> ContactPtrList; +typedef QValueList<Message> MessageList; + +/** + * @author Duncan Mac-Vicar Prett <duncan@kde.org> + * + * Kopete::ChatSessionManager is responsible for creating and tracking Kopete::ChatSession + * instances for each chat. + */ +class KOPETE_EXPORT ChatSessionManager : public QObject +{ + Q_OBJECT + +public: + static ChatSessionManager* self(); + + ~ChatSessionManager(); + + /** + * Create a new chat session. Provided is the initial list of contacts in + * the session. If a session with exactly these contacts already exists, + * it will be reused. Otherwise a new session is created. + * @param user The local user in the session. + * @param chatContacts The list of contacts taking part in the chat. + * @param protocol The protocol that the chat is using. + * @return A pointer to a new or reused Kopete::ChatSession. + */ + Kopete::ChatSession* create( const Kopete::Contact *user, + Kopete::ContactPtrList chatContacts, Kopete::Protocol *protocol); + + /** + * Find a chat session, if one exists, that matches the given list of contacts. + * @param user The local user in the session. + * @param chatContacts The list of contacts taking part in the chat. + * @param protocol The protocol that the chat is using. + * @return A pointer to an existing Kopete::ChatSession, or 0L if none was found. + */ + Kopete::ChatSession* findChatSession( const Kopete::Contact *user, + Kopete::ContactPtrList chatContacts, Kopete::Protocol *protocol); + + /** + * Registers a Kopete::ChatSession (or subclass thereof) with the Kopete::ChatSessionManager + */ + void registerChatSession(Kopete::ChatSession *); + + /** + * Get a list of all open sessions. + */ + QValueList<ChatSession*> sessions(); + + /** + * @internal + * called by the kmm itself when it gets deleted + */ + void removeSession( Kopete::ChatSession *session ); + + /** + * create a new view for the manager. + * only the manager should call this function + */ + KopeteView *createView( Kopete::ChatSession * , const QString &requestedPlugin = QString::null ); + + /** + * Post a new event. this will emit the @ref newEvent signal + */ + void postNewEvent(Kopete::MessageEvent*); + + /** + * Returns the current active Kopete view + */ + KopeteView *activeView(); + +signals: + /** + * This signal is emitted whenever a message + * is about to be displayed by the KopeteChatWindow. + * Please remember that both messages sent and + * messages received will emit this signal! + * Plugins may connect to this signal to change + * the message contents before it's going to be displayed. + */ + void aboutToDisplay( Kopete::Message& message ); + + /** + * Plugins may connect to this signal + * to manipulate the contents of the + * message that is being sent. + */ + void aboutToSend( Kopete::Message& message ); + + /** + * Plugins may connect to this signal + * to manipulate the contents of the + * message that is being received. + * + * This signal is emitted before @ref aboutToDisplay() + */ + void aboutToReceive( Kopete::Message& message ); + + /** + * A new view has been created + */ + void viewCreated( KopeteView * ); + + /** + * A view as been activated(manually only?). + */ + void viewActivated( KopeteView *view ); + + /* + * A view is about to close. + */ + void viewClosing( KopeteView *view ); + + /** + * a new KMM has been created + */ + void chatSessionCreated( Kopete::ChatSession *); + + /** + * the message is ready to be displayed + */ + void display( Kopete::Message& message, Kopete::ChatSession * ); + + /** + * A new event has been posted. + */ + void newEvent(Kopete::MessageEvent *); + + /** + * The global shortcut for sending message has been used + */ + void readMessage(); + +public slots: + void slotReadMessage(); + +private: + ChatSessionManager( QObject* parent = 0, const char* name = 0 ); + + class Private; + Private *d; + + static ChatSessionManager *s_self; + +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetecommandhandler.cpp b/kopete/libkopete/kopetecommandhandler.cpp new file mode 100644 index 00000000..b761ec08 --- /dev/null +++ b/kopete/libkopete/kopetecommandhandler.cpp @@ -0,0 +1,490 @@ +/* + kopetecommandhandler.cpp - Command Handler + + Copyright (c) 2003 by Jason Keirstead <jason@keirstead.org> + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <kapplication.h> +#include <qregexp.h> +#include <kdebug.h> +#include <klocale.h> +#include <kprocess.h> +#include <kdeversion.h> +#include <kxmlguiclient.h> +#include <kaction.h> +#include <qdom.h> + +#include "kopetechatsessionmanager.h" +#include "kopeteprotocol.h" +#include "kopetepluginmanager.h" +#include "kopeteview.h" +#include "kopeteaccountmanager.h" +#include "kopeteaccount.h" +#include "kopetecommandhandler.h" +#include "kopetecontact.h" +#include "kopetecommand.h" + +using Kopete::CommandList; + +typedef QMap<QObject*, CommandList> PluginCommandMap; +typedef QMap<QString,QString> CommandMap; +typedef QPair<Kopete::ChatSession*, Kopete::Message::MessageDirection> ManagerPair; + +class KopeteCommandGUIClient : public QObject, public KXMLGUIClient +{ + public: + KopeteCommandGUIClient( Kopete::ChatSession *manager ) : QObject(manager), KXMLGUIClient(manager) + { + setXMLFile( QString::fromLatin1("kopetecommandui.rc") ); + + QDomDocument doc = domDocument(); + QDomNode menu = doc.documentElement().firstChild().firstChild().firstChild(); + CommandList mCommands = Kopete::CommandHandler::commandHandler()->commands( + manager->protocol() + ); + + for( QDictIterator<Kopete::Command> it( mCommands ); it.current(); ++it ) + { + KAction *a = static_cast<KAction*>( it.current() ); + actionCollection()->insert( a ); + QDomElement newNode = doc.createElement( QString::fromLatin1("Action") ); + newNode.setAttribute( QString::fromLatin1("name"), + QString::fromLatin1( a->name() ) ); + + bool added = false; + for( QDomElement n = menu.firstChild().toElement(); + !n.isNull(); n = n.nextSibling().toElement() ) + { + if( QString::fromLatin1(a->name()) < n.attribute(QString::fromLatin1("name"))) + { + menu.insertBefore( newNode, n ); + added = true; + break; + } + } + + if( !added ) + { + menu.appendChild( newNode ); + } + } + + setDOMDocument( doc ); + } +}; + +struct CommandHandlerPrivate +{ + PluginCommandMap pluginCommands; + Kopete::CommandHandler *s_handler; + QMap<KProcess*,ManagerPair> processMap; + bool inCommand; + QPtrList<KAction> m_commands; +}; + +CommandHandlerPrivate *Kopete::CommandHandler::p = 0L; + +Kopete::CommandHandler::CommandHandler() : QObject( qApp ) +{ + p->s_handler = this; + p->inCommand = false; + + CommandList mCommands(31, false); + mCommands.setAutoDelete( true ); + p->pluginCommands.insert( this, mCommands ); + + registerCommand( this, QString::fromLatin1("help"), SLOT( slotHelpCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /help [<command>] - Used to list available commands, or show help for a specified command." ), 0, 1 ); + + registerCommand( this, QString::fromLatin1("close"), SLOT( slotCloseCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /close - Closes the current view." ) ); + + // FIXME: What's the difference with /close? The help doesn't explain it - Martijn + registerCommand( this, QString::fromLatin1("part"), SLOT( slotPartCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /part - Closes the current view." ) ); + + registerCommand( this, QString::fromLatin1("clear"), SLOT( slotClearCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /clear - Clears the active view's chat buffer." ) ); + + //registerCommand( this, QString::fromLatin1("me"), SLOT( slotMeCommand( const QString &, Kopete::ChatSession * ) ), + // i18n( "USAGE: /me <text> - Formats message as in '<nickname> went to the store'." ) ); + + registerCommand( this, QString::fromLatin1("away"), SLOT( slotAwayCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /away [<reason>] - Marks you as away/back for the current account only." ) ); + + registerCommand( this, QString::fromLatin1("awayall"), SLOT( slotAwayAllCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /awayall [<reason>] - Marks you as away/back for all accounts." ) ); + + registerCommand( this, QString::fromLatin1("say"), SLOT( slotSayCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /say <text> - Say text in this chat. This is the same as just typing a message, but is very " + "useful for scripts." ), 1 ); + + registerCommand( this, QString::fromLatin1("exec"), SLOT( slotExecCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /exec [-o] <command> - Executes the specified command and displays the output in the chat buffer. " + "If -o is specified, the output is sent to all members of the chat."), 1 ); + + connect( Kopete::PluginManager::self(), SIGNAL( pluginLoaded( Kopete::Plugin*) ), + this, SLOT(slotPluginLoaded(Kopete::Plugin*) ) ); + + connect( Kopete::ChatSessionManager::self(), SIGNAL( viewCreated( KopeteView * ) ), + this, SLOT( slotViewCreated( KopeteView* ) ) ); +} + +Kopete::CommandHandler::~CommandHandler() +{ + delete p; +} + +Kopete::CommandHandler *Kopete::CommandHandler::commandHandler() +{ + if( !p ) + { + p = new CommandHandlerPrivate; + p->s_handler = new Kopete::CommandHandler(); + } + + return p->s_handler; +} + +void Kopete::CommandHandler::registerCommand( QObject *parent, const QString &command, const char* handlerSlot, + const QString &help, uint minArgs, int maxArgs, const KShortcut &cut, const QString &pix ) +{ + QString lowerCommand = command.lower(); + + Kopete::Command *mCommand = new Kopete::Command( parent, lowerCommand, handlerSlot, help, + Normal, QString::null, minArgs, maxArgs, cut, pix); + p->pluginCommands[ parent ].insert( lowerCommand, mCommand ); +} + +void Kopete::CommandHandler::unregisterCommand( QObject *parent, const QString &command ) +{ + if( p->pluginCommands[ parent ].find(command) ) + p->pluginCommands[ parent ].remove( command ); +} + +void Kopete::CommandHandler::registerAlias( QObject *parent, const QString &alias, const QString &formatString, + const QString &help, CommandType type, uint minArgs, int maxArgs, const KShortcut &cut, const QString &pix ) +{ + QString lowerAlias = alias.lower(); + + Kopete::Command *mCommand = new Kopete::Command( parent, lowerAlias, 0L, help, type, + formatString, minArgs, maxArgs, cut, pix ); + p->pluginCommands[ parent ].insert( lowerAlias, mCommand ); +} + +void Kopete::CommandHandler::unregisterAlias( QObject *parent, const QString &alias ) +{ + if( p->pluginCommands[ parent ].find(alias) ) + p->pluginCommands[ parent ].remove( alias ); +} + +bool Kopete::CommandHandler::processMessage( const QString &msg, Kopete::ChatSession *manager ) +{ + if( p->inCommand ) + return false; + QRegExp splitRx( QString::fromLatin1("^/([\\S]+)(.*)") ); + QString command; + QString args; + if(splitRx.search(msg) != -1) + { + command = splitRx.cap(1); + args = splitRx.cap(2).mid(1); + } + else + return false; + + CommandList mCommands = commands( manager->protocol() ); + Kopete::Command *c = mCommands[ command ]; + if(c) + { + kdDebug(14010) << k_funcinfo << "Handled Command" << endl; + if( c->type() != SystemAlias && c->type() != UserAlias ) + p->inCommand = true; + + c->processCommand( args, manager ); + p->inCommand = false; + + return true; + } + + return false; +} + +bool Kopete::CommandHandler::processMessage( Kopete::Message &msg, Kopete::ChatSession *manager ) +{ + QString messageBody = msg.plainBody(); + + return processMessage( messageBody, manager ); +} + +void Kopete::CommandHandler::slotHelpCommand( const QString &args, Kopete::ChatSession *manager ) +{ + QString output; + if( args.isEmpty() ) + { + int commandCount = 0; + output = i18n( "Available Commands:\n" ); + + CommandList mCommands = commands( manager->myself()->protocol() ); + QDictIterator<Kopete::Command> it( mCommands ); + for( ; it.current(); ++it ) + { + output.append( it.current()->command().upper() + '\t' ); + if( commandCount++ == 5 ) + { + commandCount = 0; + output.append( '\n' ); + } + } + output.append( i18n( "\nType /help <command> for more information." ) ); + } + else + { + QString command = parseArguments( args ).front().lower(); + Kopete::Command *c = commands( manager->myself()->protocol() )[ command ]; + if( c && !c->help().isNull() ) + output = c->help(); + else + output = i18n("There is no help available for '%1'.").arg( command ); + } + + Kopete::Message msg(manager->myself(), manager->members(), output, Kopete::Message::Internal, Kopete::Message::PlainText); + manager->appendMessage(msg); +} + +void Kopete::CommandHandler::slotSayCommand( const QString &args, Kopete::ChatSession *manager ) +{ + //Just say whatever is passed + Kopete::Message msg(manager->myself(), manager->members(), args, + Kopete::Message::Outbound, Kopete::Message::PlainText); + manager->sendMessage(msg); +} + +void Kopete::CommandHandler::slotExecCommand( const QString &args, Kopete::ChatSession *manager ) +{ + if( !args.isEmpty() ) + { + KProcess *proc = 0L; + if ( kapp->authorize( QString::fromLatin1( "shell_access" ) ) ) + proc = new KProcess(manager); + + if( proc ) + { + *proc << QString::fromLatin1("sh") << QString::fromLatin1("-c"); + + QStringList argsList = parseArguments( args ); + if( argsList.front() == QString::fromLatin1("-o") ) + { + p->processMap.insert( proc, ManagerPair(manager, Kopete::Message::Outbound) ); + *proc << args.section(QRegExp(QString::fromLatin1("\\s+")), 1); + } + else + { + p->processMap.insert( proc, ManagerPair(manager, Kopete::Message::Internal) ); + *proc << args; + } + + connect(proc, SIGNAL(receivedStdout(KProcess *, char *, int)), this, SLOT(slotExecReturnedData(KProcess *, char *, int))); + connect(proc, SIGNAL(receivedStderr(KProcess *, char *, int)), this, SLOT(slotExecReturnedData(KProcess *, char *, int))); + proc->start( KProcess::NotifyOnExit, KProcess::AllOutput ); + } + else + { + Kopete::Message msg(manager->myself(), manager->members(), + i18n( "ERROR: Shell access has been restricted on your system. The /exec command will not function." ), + Kopete::Message::Internal, Kopete::Message::PlainText ); + manager->sendMessage( msg ); + } + } +} + +void Kopete::CommandHandler::slotClearCommand( const QString &, Kopete::ChatSession *manager ) +{ + if( manager->view() ) + manager->view()->clear(); +} + +void Kopete::CommandHandler::slotPartCommand( const QString &, Kopete::ChatSession *manager ) +{ + if( manager->view() ) + manager->view()->closeView(); +} + +void Kopete::CommandHandler::slotAwayCommand( const QString &args, Kopete::ChatSession *manager ) +{ + bool goAway = !manager->account()->isAway(); + + if( args.isEmpty() ) + manager->account()->setAway( goAway ); + else + manager->account()->setAway( goAway, args ); +} + +void Kopete::CommandHandler::slotAwayAllCommand( const QString &args, Kopete::ChatSession *manager ) +{ + if( manager->account()->isAway() ) + Kopete::AccountManager::self()->setAvailableAll(); + + else + { + if( args.isEmpty() ) + Kopete::AccountManager::self()->setAwayAll(); + else + Kopete::AccountManager::self()->setAwayAll( args ); + } +} + +void Kopete::CommandHandler::slotCloseCommand( const QString &, Kopete::ChatSession *manager ) +{ + if( manager->view() ) + manager->view()->closeView(); +} + +void Kopete::CommandHandler::slotExecReturnedData(KProcess *proc, char *buff, int bufflen ) +{ + kdDebug(14010) << k_funcinfo << endl; + QString buffer = QString::fromLocal8Bit( buff, bufflen ); + ManagerPair mgrPair = p->processMap[ proc ]; + Kopete::Message msg( mgrPair.first->myself(), mgrPair.first->members(), buffer, mgrPair.second, Kopete::Message::PlainText ); + if( mgrPair.second == Kopete::Message::Outbound ) + mgrPair.first->sendMessage( msg ); + else + mgrPair.first->appendMessage( msg ); +} + +void Kopete::CommandHandler::slotExecFinished(KProcess *proc) +{ + delete proc; + p->processMap.remove( proc ); +} + +QStringList Kopete::CommandHandler::parseArguments( const QString &args ) +{ + QStringList arguments; + QRegExp quotedArgs( QString::fromLatin1("\"(.*)\"") ); + quotedArgs.setMinimal( true ); + + if ( quotedArgs.search( args ) != -1 ) + { + for( int i = 0; i< quotedArgs.numCaptures(); i++ ) + arguments.append( quotedArgs.cap(i) ); + } + + QStringList otherArgs = QStringList::split( QRegExp(QString::fromLatin1("\\s+")), args.section( quotedArgs, 0 ) ); + for( QStringList::Iterator it = otherArgs.begin(); it != otherArgs.end(); ++it ) + arguments.append( *it ); + + return arguments; +} + +bool Kopete::CommandHandler::commandHandled( const QString &command ) +{ + for( PluginCommandMap::Iterator it = p->pluginCommands.begin(); it != p->pluginCommands.end(); ++it ) + { + if( it.data()[ command ] ) + return true; + } + + return false; +} + +bool Kopete::CommandHandler::commandHandledByProtocol( const QString &command, Kopete::Protocol *protocol ) +{ + // Make sure the protocol is not NULL + if(!protocol) + return false; + + // Fetch the commands for the protocol + CommandList commandList = commands( protocol ); + QDictIterator<Kopete::Command> it ( commandList ); + + // Loop through commands and check if they match the supplied command + for( ; it.current(); ++it ) + { + if( it.current()->command().lower() == command ) + return true; + } + + // No commands found + return false; +} + +CommandList Kopete::CommandHandler::commands( Kopete::Protocol *protocol ) +{ + CommandList commandList(63, false); + + //Add plugin user aliases first + addCommands( p->pluginCommands[protocol], commandList, UserAlias ); + + //Add plugin system aliases next + addCommands( p->pluginCommands[protocol], commandList, SystemAlias ); + + //Add the commands for this protocol next + addCommands( p->pluginCommands[protocol], commandList ); + + //Add plugin commands + for( PluginCommandMap::Iterator it = p->pluginCommands.begin(); it != p->pluginCommands.end(); ++it ) + { + if( !it.key()->inherits("Kopete::Protocol") && it.key()->inherits("Kopete::Plugin") ) + addCommands( it.data(), commandList ); + } + + //Add global user aliases first + addCommands( p->pluginCommands[this], commandList, UserAlias ); + + //Add global system aliases next + addCommands( p->pluginCommands[this], commandList, SystemAlias ); + + //Add the internal commands *last* + addCommands( p->pluginCommands[this], commandList ); + + return commandList; +} + +void Kopete::CommandHandler::addCommands( CommandList &from, CommandList &to, CommandType type ) +{ + QDictIterator<Kopete::Command> itDict( from ); + for( ; itDict.current(); ++itDict ) + { + if( !to[ itDict.currentKey() ] && + ( type == Undefined || itDict.current()->type() == type ) ) + to.insert( itDict.currentKey(), itDict.current() ); + } +} + +void Kopete::CommandHandler::slotViewCreated( KopeteView *view ) +{ + new KopeteCommandGUIClient( view->msgManager() ); +} + +void Kopete::CommandHandler::slotPluginLoaded( Kopete::Plugin *plugin ) +{ + connect( plugin, SIGNAL( destroyed( QObject * ) ), this, SLOT( slotPluginDestroyed( QObject * ) ) ); + if( !p->pluginCommands.contains( plugin ) ) + { + //Create a QDict optomized for a larger # of commands, and case insensitive + CommandList mCommands(31, false); + mCommands.setAutoDelete( true ); + p->pluginCommands.insert( plugin, mCommands ); + } +} + +void Kopete::CommandHandler::slotPluginDestroyed( QObject *plugin ) +{ + p->pluginCommands.remove( static_cast<Kopete::Plugin*>(plugin) ); +} + +#include "kopetecommandhandler.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetecommandhandler.h b/kopete/libkopete/kopetecommandhandler.h new file mode 100644 index 00000000..f763ace6 --- /dev/null +++ b/kopete/libkopete/kopetecommandhandler.h @@ -0,0 +1,219 @@ +/* + kopetecommandhandler.h - Command Handler + + Copyright (c) 2003 by Jason Keirstead <jason@keirstead.org> + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETECOMMANDHANDLER_H_ +#define _KOPETECOMMANDHANDLER_H_ + +#include <qdict.h> +#include <kshortcut.h> +#include "kopetemessage.h" + +#include "kopete_export.h" + +class KProcess; + +struct CommandHandlerPrivate; + +class KopeteView; +class KopeteCommandGUIClient; + +namespace Kopete +{ + +class ChatSession; +class Plugin; +class Protocol; +class Command; + +typedef QDict<Command> CommandList; + +/** + * @author Jason Keirstead <jason@keirstead.org> + * + * The Kopete::CommandHandler can handle /action like messages + */ +class KOPETE_EXPORT CommandHandler : public QObject +{ + friend class ::KopeteCommandGUIClient; + + Q_OBJECT + + public: + /** + * an enum defining the type of a command + */ + enum CommandType { Normal, SystemAlias, UserAlias, Undefined }; + + /** + * Returns a pointer to the command handler + */ + static CommandHandler *commandHandler(); + + /** + * \brief Register a command with the command handler. + * + * Command matching is case insensitive. All commands are registered, + * regardless of whether or not they are already handled by another + * handler. This is so that if the first plugin is unloaded, the next + * handler in the sequence will handle the command. However, there are + * certain commands which are reserved (internally handled by the + * Kopete::CommandHandler). These commands can also be overridden by + * registering a new duplicate command. + * + * @param parent The plugin who owns this command + * @param command The command we want to handle, not including the '/' + * @param handlerSlot The slot used to handle the command. This slot must + * accept two parameters, a QString of arguments, and a Kopete::ChatSession + * pointer to the manager under which the command was sent. + * @param help An optional help string to be shown when the user uses + * /help \<command\> + * @param minArgs the minimum number of arguments for this command + * @param maxArgs the maximum number of arguments this command takes + * @param cut a default keyboard shortcut + * @param pix icon name, the icon will be shown in menus + */ + void registerCommand( QObject *parent, const QString &command, const char* handlerSlot, + const QString &help = QString::null, uint minArgs = 0, int maxArgs = -1, + const KShortcut &cut = 0, const QString &pix = QString::null ); + + /** + * \brief Register a command alias. + * + * @param parent The plugin who owns this alias + * @param alias The command for the alias + * @param formatString This is the string that will be transformed into another + * command. The formatString should begin with an already existing command, + * followed by any other arguments. The variables %1, %2... %9 will be substituted + * with the arguments passed into the alias. The variable %s will be substituted with + * the entire argument string + * @param help An optional help string to be shown when the user uses + * /help \<command\> + * @param minArgs the minimum number of arguments for this command + * @param maxArgs the maximum number of arguments this command takes + * @param cut a default keyboard shortcut + * @param pix icon name, the icon will be shown in menus + */ + void registerAlias( QObject *parent, + const QString &alias, + const QString &formatString, + const QString &help = QString::null, + CommandType = SystemAlias, + uint minArgs = 0, + int maxArgs = -1, + const KShortcut &cut = 0, + const QString &pix = QString::null ); + + /** + * \brief Unregister a command. + * + * When a plugin unloads, all commands are automaticlly unregistered and deleted. + * This function should only be called in the case of a plugin which loads and + * unloads commands dynamically. + * + * @param parent The plugin who owns this command + * @param command The command to unload + */ + void unregisterCommand( QObject *parent, const QString &command ); + + /** + * \brief Unregister an alias. + * + * \see unregisterCommand( QObject *parent, const QString &command ) + * @param parent The plugin who owns this alias + * @param alias The alais to unload + */ + void unregisterAlias( QObject *parent, const QString &alias ); + + /** + * \brief Process a message to see if any commands should be handled + * + * @param msg The message to process + * @param manager The manager who owns this message + * @return True if the command was handled, false if not + */ + bool processMessage( Message &msg, ChatSession *manager ); + + /** + * \brief Process a message to see if any commands should be handled + * + * \see processMessage( Kopete::Message &msg, Kopete::ChatSession *manager) + * \param msg A QString contain the message + * \param manager the Kopete::ChatSession who will own the message + * \return true if the command was handled, false if the command was not handled. + */ + bool processMessage( const QString &msg, ChatSession *manager ); + + /** + * Parses a string of command arguments into a QStringList. Quoted + * blocks within the arguments string are treated as one argument. + */ + static QStringList parseArguments( const QString &args ); + + /** + * \brief Check if a command is already handled + * + * @param command The command to check + * @return True if the command is already being handled, False if not + */ + bool commandHandled( const QString &command ); + + /** + * \brief Check if a command is already handled by a spesific protocol + * + * @param command The command to check + * @param protocol The protocol to check + * @return True if the command is already being handled, False if not + */ + bool commandHandledByProtocol( const QString &command, Protocol *protocol); + + private slots: + void slotPluginLoaded( Kopete::Plugin * ); + void slotPluginDestroyed( QObject * ); + void slotExecReturnedData(KProcess *proc, char *buff, int bufflen ); + void slotExecFinished(KProcess *proc); + void slotViewCreated( KopeteView *view ); + + void slotHelpCommand( const QString & args, Kopete::ChatSession *manager ); + void slotClearCommand( const QString & args, Kopete::ChatSession *manager ); + void slotPartCommand( const QString & args, Kopete::ChatSession *manager ); + void slotCloseCommand( const QString & args, Kopete::ChatSession *manager ); + //void slotMeCommand( const QString & args, Kopete::ChatSession *manager ); + void slotExecCommand( const QString & args, Kopete::ChatSession *manager ); + void slotAwayCommand( const QString & args, Kopete::ChatSession *manager ); + void slotAwayAllCommand( const QString & args, Kopete::ChatSession *manager ); + void slotSayCommand( const QString & args, Kopete::ChatSession *manager ); + + private: + /** + * Helper function. Returns all the commands that can be used by a KMM of this protocol + * (all non-protocol commands, plus this protocols commands) + */ + CommandList commands( Protocol * ); + + /** + * Helper function for commands() + */ + void addCommands( CommandList &from, CommandList &to, CommandType type = Undefined ); + + CommandHandler(); + ~CommandHandler(); + + static CommandHandlerPrivate *p; +}; + +} + +#endif diff --git a/kopete/libkopete/kopetecommandui.rc b/kopete/libkopete/kopetecommandui.rc new file mode 100644 index 00000000..8cd10680 --- /dev/null +++ b/kopete/libkopete/kopetecommandui.rc @@ -0,0 +1,12 @@ +<!DOCTYPE kpartgui> +<kpartgui version="2" name="kopetechatwindow"> + <MenuBar> + <Menu noMerge="1" name="file"> + <Menu name="commandmenu"> + <text>Commands</text> + <ActionList name="commandactionlist" /> + </Menu> + </Menu> + </MenuBar> + +</kpartgui> diff --git a/kopete/libkopete/kopeteconfig.kcfgc b/kopete/libkopete/kopeteconfig.kcfgc new file mode 100644 index 00000000..86dbae8e --- /dev/null +++ b/kopete/libkopete/kopeteconfig.kcfgc @@ -0,0 +1,13 @@ +ClassName=Config +File=kopete.kcfg +GlobalEnums=false +Inherits=KConfigSkeleton +ItemAccessors=true +MemberVariables=private +Mutators=true +NameSpace=Kopete +SetUserTexts=false +Singleton=true +Visibility=KOPETE_EXPORT +IncludeFiles=kopete_export.h + diff --git a/kopete/libkopete/kopetecontact.cpp b/kopete/libkopete/kopetecontact.cpp new file mode 100644 index 00000000..15cb27df --- /dev/null +++ b/kopete/libkopete/kopetecontact.cpp @@ -0,0 +1,863 @@ +/* + kopetecontact.cpp - Kopete Contact + + Copyright (c) 2002-2004 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart @tiscalinet.be> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetecontact.h" + +#include <qapplication.h> + +#include <kdebug.h> + +#include <kdeversion.h> +#include <kinputdialog.h> + +#include <kabcpersistence.h> +#include <kdialogbase.h> +#include <klocale.h> +#include <kpopupmenu.h> +#include <kmessagebox.h> +#include <klistviewsearchline.h> + +#include "kopetecontactlist.h" +#include "kopeteglobal.h" +#include "kopeteuiglobal.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopetestdaction.h" +#include "kopetechatsession.h" +#include "kopeteview.h" +#include "kopetemetacontact.h" +#include "kopeteprefs.h" +#include "metacontactselectorwidget.h" +#include "kopeteemoticons.h" + +//For the moving to another metacontact dialog +#include <qlabel.h> +#include <qimage.h> +#include <qmime.h> +#include <qvbox.h> +#include <klistview.h> +#include <qcheckbox.h> +#include <qwhatsthis.h> +#include <qstylesheet.h> + +namespace Kopete { + +struct Contact::Private +{ +public: + bool fileCapable; + + OnlineStatus onlineStatus; + Account *account; + + MetaContact *metaContact; + + QString contactId; + QString icon; + + QTime idleTimer; + unsigned long int idleTime; + + Kopete::ContactProperty::Map properties; + +}; + +Contact::Contact( Account *account, const QString &contactId, + MetaContact *parent, const QString &icon ) + : QObject( parent ) +{ + d = new Private; + + //kdDebug( 14010 ) << k_funcinfo << "Creating contact with id " << contactId << endl; + + d->contactId = contactId; + d->metaContact = parent; + d->fileCapable = false; + d->account = account; + d->idleTime = 0; + d->icon = icon; + + // If can happend that a MetaContact may be used without a account + // (ex: for unit tests or chat window style preview) + if ( account ) + { + account->registerContact( this ); + connect( account, SIGNAL( isConnectedChanged() ), SLOT( slotAccountIsConnectedChanged() ) ); + } + + // Need to check this because myself() may have no parent + // Maybe too the metaContact doesn't have a valid protocol() + // (ex: for unit tests or chat window style preview) + if( parent && protocol() ) + { + connect( parent, SIGNAL( aboutToSave( Kopete::MetaContact * ) ), + protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact * ) ) ); + + parent->addContact( this ); + } + + +} + +Contact::~Contact() +{ + //kdDebug(14010) << k_funcinfo << endl; + emit( contactDestroyed( this ) ); + delete d; +} + + + +OnlineStatus Contact::onlineStatus() const +{ + if ( this == account()->myself() || account()->isConnected() ) + return d->onlineStatus; + else + return protocol()->accountOfflineStatus(); +} + +void Contact::setOnlineStatus( const OnlineStatus &status ) +{ + if( status == d->onlineStatus ) + return; + + OnlineStatus oldStatus = d->onlineStatus; + d->onlineStatus = status; + + Kopete::Global::Properties *globalProps = Kopete::Global::Properties::self(); + + // Contact changed from Offline to another status + if( oldStatus.status() == OnlineStatus::Offline && + status.status() != OnlineStatus::Offline ) + { + setProperty( globalProps->onlineSince(), QDateTime::currentDateTime() ); + /*kdDebug(14010) << k_funcinfo << "REMOVING lastSeen property for " << + d->displayName << endl;*/ + removeProperty( globalProps->lastSeen() ); + } + else if( oldStatus.status() != OnlineStatus::Offline && + oldStatus.status() != OnlineStatus::Unknown && + status.status() == OnlineStatus::Offline ) // Contact went back offline + { + removeProperty( globalProps->onlineSince() ); + /*kdDebug(14010) << k_funcinfo << "SETTING lastSeen property for " << + d->displayName << endl;*/ + setProperty( globalProps->lastSeen(), QDateTime::currentDateTime() ); + } + + if ( this == account()->myself() || account()->isConnected() ) + emit onlineStatusChanged( this, status, oldStatus ); +} + +void Contact::slotAccountIsConnectedChanged() +{ + if ( this == account()->myself() ) + return; + + if ( account()->isConnected() ) + emit onlineStatusChanged( this, d->onlineStatus, protocol()->accountOfflineStatus() ); + else + emit onlineStatusChanged( this, protocol()->accountOfflineStatus(), d->onlineStatus ); +} + + +void Contact::sendFile( const KURL &, const QString &, uint ) +{ + kdWarning( 14010 ) << k_funcinfo << "Plugin " + << protocol()->pluginId() << " has enabled file sending, " + << "but didn't implement it!" << endl; +} + +void Contact::slotAddContact() +{ + if( metaContact() ) + { + metaContact()->setTemporary( false ); + ContactList::self()->addMetaContact( metaContact() ); + } +} + +KPopupMenu* Contact::popupMenu( ChatSession *manager ) +{ + // Build the menu + KPopupMenu *menu = new KPopupMenu(); + + // insert title + QString titleText; + QString nick = property( Kopete::Global::Properties::self()->nickName() ).value().toString(); + if( nick.isEmpty() ) + titleText = QString::fromLatin1( "%1 (%2)" ).arg( contactId(), onlineStatus().description() ); + else + titleText = QString::fromLatin1( "%1 <%2> (%3)" ).arg( nick, contactId(), onlineStatus().description() ); + menu->insertTitle( titleText ); + + if( metaContact() && metaContact()->isTemporary() && contactId() != account()->myself()->contactId() ) + { + KAction *actionAddContact = new KAction( i18n( "&Add to Your Contact List" ), QString::fromLatin1( "add_user" ), + 0, this, SLOT( slotAddContact() ), menu, "actionAddContact" ); + actionAddContact->plug( menu ); + menu->insertSeparator(); + } + + // FIXME: After KDE 3.2 we should make isReachable do the isConnected call so it can be removed here - Martijn + bool reach = account()->isConnected() && isReachable(); + bool myself = (this == account()->myself()); + + KAction *actionSendMessage = KopeteStdAction::sendMessage( this, SLOT( sendMessage() ), menu, "actionSendMessage" ); + actionSendMessage->setEnabled( reach && !myself ); + actionSendMessage->plug( menu ); + + KAction *actionChat = KopeteStdAction::chat( this, SLOT( startChat() ), menu, "actionChat" ); + actionChat->setEnabled( reach && !myself ); + actionChat->plug( menu ); + + KAction *actionSendFile = KopeteStdAction::sendFile( this, SLOT( sendFile() ), menu, "actionSendFile" ); + actionSendFile->setEnabled( reach && d->fileCapable && !myself ); + actionSendFile->plug( menu ); + + // Protocol specific options will go below this separator + // through the use of the customContextMenuActions() function + + // Get the custom actions from the protocols ( pure virtual function ) + QPtrList<KAction> *customActions = customContextMenuActions( manager ); + if( customActions && !customActions->isEmpty() ) + { + menu->insertSeparator(); + + for( KAction *a = customActions->first(); a; a = customActions->next() ) + a->plug( menu ); + } + delete customActions; + + menu->insertSeparator(); + + if( metaContact() && !metaContact()->isTemporary() ) + KopeteStdAction::changeMetaContact( this, SLOT( changeMetaContact() ), menu, "actionChangeMetaContact" )->plug( menu ); + + KopeteStdAction::contactInfo( this, SLOT( slotUserInfo() ), menu, "actionUserInfo" )->plug( menu ); + +#if 0 //this is not fully implemented yet (and doesn't work). disable for now - Olivier 2005-01-11 + if ( account()->isBlocked( d->contactId ) ) + KopeteStdAction::unblockContact( this, SLOT( slotUnblock() ), menu, "actionUnblockContact" )->plug( menu ); + else + KopeteStdAction::blockContact( this, SLOT( slotBlock() ), menu, "actionBlockContact" )->plug( menu ); +#endif + + if( metaContact() && !metaContact()->isTemporary() ) + KopeteStdAction::deleteContact( this, SLOT( slotDelete() ), menu, "actionDeleteContact" )->plug( menu ); + + return menu; +} + +void Contact::changeMetaContact() +{ + KDialogBase *moveDialog = new KDialogBase( Kopete::UI::Global::mainWidget(), "moveDialog", true, i18n( "Move Contact" ), + KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok, true ); + + QVBox *w = new QVBox( moveDialog ); + w->setSpacing( KDialog::spacingHint() ); + Kopete::UI::MetaContactSelectorWidget *selector = new Kopete::UI::MetaContactSelectorWidget(w); + selector->setLabelMessage(i18n( "Select the meta contact to which you want to move this contact:" )); + // exclude this metacontact as a target metacontact for the move + selector->excludeMetaContact( metaContact() ); + QCheckBox *chkCreateNew = new QCheckBox( i18n( "Create a new metacontact for this contact" ), w ); + QWhatsThis::add( chkCreateNew , i18n( "If you select this option, a new metacontact will be created in the top-level group " + "with the name of this contact and the contact will be moved to it." ) ); + QObject::connect( chkCreateNew , SIGNAL( toggled(bool) ) , selector , SLOT ( setDisabled(bool) ) ) ; + + moveDialog->setMainWidget(w); + if( moveDialog->exec() == QDialog::Accepted ) + { + Kopete::MetaContact *mc = selector->metaContact(); + if(chkCreateNew->isChecked()) + { + mc=new Kopete::MetaContact(); + Kopete::ContactList::self()->addMetaContact(mc); + } + if( mc ) + { + setMetaContact( mc ); + } + } + + moveDialog->deleteLater(); +} + +void Contact::setMetaContact( MetaContact *m ) +{ + MetaContact *old = d->metaContact; + if(old==m) //that make no sens + return; + + if( old ) + { + int result=KMessageBox::No; + if( old->isTemporary() ) + result=KMessageBox::Yes; + else if( old->contacts().count()==1 ) + { //only one contact, including this one, that mean the contact will be empty efter the move + result = KMessageBox::questionYesNoCancel( Kopete::UI::Global::mainWidget(), i18n( "You are moving the contact `%1' to the meta contact `%2'.\n" + "`%3' will be empty afterwards. Do you want to delete this contact?" ) + .arg(contactId(), m ? m->displayName() : QString::null, old->displayName()) + , i18n( "Move Contact" ), KStdGuiItem::del(), i18n( "&Keep" ) , QString::fromLatin1("delete_old_contact_when_move") ); + if(result==KMessageBox::Cancel) + return; + } + old->removeContact( this ); + disconnect( old, SIGNAL( aboutToSave( Kopete::MetaContact * ) ), + protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact * ) ) ); + + if(result==KMessageBox::Yes) + { + //remove the old metacontact. (this delete the MC) + ContactList::self()->removeMetaContact(old); + } + else + { + d->metaContact = m; //i am forced to do that now if i want the next line works + //remove cached data for this protocol which will not be removed since we disconnected + protocol()->slotMetaContactAboutToSave( old ); + } + } + + d->metaContact = m; + + if( m ) + { + m->addContact( this ); + m->insertChild( this ); + // it is necessary to call this write here, because MetaContact::addContact() does not differentiate + // between adding completely new contacts (which should be written to kabc) and restoring upon restart + // (where no write is needed). + KABCPersistence::self()->write( m ); + connect( d->metaContact, SIGNAL( aboutToSave( Kopete::MetaContact * ) ), + protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact * ) ) ); + } + sync(); +} + +void Contact::serialize( QMap<QString, QString> &/*serializedData*/, + QMap<QString, QString> & /* addressBookData */ ) +{ +} + + +void Contact::serializeProperties(QMap<QString, QString> &serializedData) +{ + + Kopete::ContactProperty::Map::ConstIterator it;// = d->properties.ConstIterator; + for (it=d->properties.begin(); it != d->properties.end(); ++it) + { + if (!it.data().tmpl().persistent()) + continue; + + QVariant val = it.data().value(); + QString key = QString::fromLatin1("prop_%1_%2").arg(QString::fromLatin1(val.typeName()), it.key()); + + serializedData[key] = val.toString(); + + } // end for() +} // end serializeProperties() + +void Contact::deserializeProperties( + QMap<QString, QString> &serializedData ) +{ + QMap<QString, QString>::ConstIterator it; + for ( it=serializedData.begin(); it != serializedData.end(); ++it ) + { + QString key = it.key(); + + if ( !key.startsWith( QString::fromLatin1("prop_") ) ) // avoid parsing other serialized data + continue; + + QStringList keyList = QStringList::split( QChar('_'), key, false ); + if( keyList.count() < 3 ) // invalid key, not enough parts in string "prop_X_Y" + continue; + + key = keyList[2]; // overwrite key var with the real key name this property has + QString type( keyList[1] ); // needed for QVariant casting + + QVariant variant( it.data() ); + if( !variant.cast(QVariant::nameToType(type.latin1())) ) + { + kdDebug(14010) << k_funcinfo << + "Casting QVariant to needed type FAILED" << + "key=" << key << ", type=" << type << endl; + continue; + } + + Kopete::ContactPropertyTmpl tmpl = Kopete::Global::Properties::self()->tmpl(key); + if( tmpl.isNull() ) + { + kdDebug( 14010 ) << k_funcinfo << "no ContactPropertyTmpl defined for" \ + " key " << key << ", cannot restore persistent property" << endl; + continue; + } + + setProperty(tmpl, variant); + } // end for() +} + + +bool Contact::isReachable() +{ + // The default implementation returns false when offline and true + // otherwise. Subclass if you need more control over the process. + return onlineStatus().status() != OnlineStatus::Offline; +} + + +void Contact::startChat() +{ + KopeteView *v=manager( CanCreate )->view(true, QString::fromLatin1("kopete_chatwindow") ); + if(v) + v->raise(true); +} + +void Contact::sendMessage() +{ + KopeteView *v=manager( CanCreate )->view(true, QString::fromLatin1("kopete_emailwindow") ); + if(v) + v->raise(true); +} + +void Contact::execute() +{ + // FIXME: After KDE 3.2 remove the isConnected check and move it to isReachable - Martijn + if ( account()->isConnected() && isReachable() ) + { + KopeteView *v=manager( CanCreate )->view(true, KopetePrefs::prefs()->interfacePreference() ); + if(v) + v->raise(true); + } + else + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "This user is not reachable at the moment. Please try a protocol that supports offline sending, or wait " + "until this user comes online." ), i18n( "User is Not Reachable" ) ); + } +} + +void Contact::slotDelete() +{ + if ( KMessageBox::warningContinueCancel( Kopete::UI::Global::mainWidget(), + i18n( "Are you sure you want to remove the contact '%1' from your contact list?" ). + arg( d->contactId ), i18n( "Remove Contact" ), KGuiItem(i18n("Remove"), QString::fromLatin1("delete_user") ), + QString::fromLatin1("askRemoveContact"), KMessageBox::Notify | KMessageBox::Dangerous ) + == KMessageBox::Continue ) + { + deleteContact(); + } +} + +void Contact::deleteContact() +{ + // Default implementation simply deletes the contact + deleteLater(); +} + + +MetaContact * Contact::metaContact() const +{ + return d->metaContact; +} + +QString Contact::contactId() const +{ + return d->contactId; +} + +Protocol * Contact::protocol() const +{ + return d->account ? d->account->protocol() : 0L; +} + +Account * Contact::account() const +{ + return d->account; +} + + + +void Contact::sync(unsigned int) +{ + /* Default implementation does nothing */ +} + +QString& Contact::icon() const +{ + return d->icon; +} + +void Contact::setIcon( const QString& icon ) +{ + d->icon = icon; + return; +} + +QPtrList<KAction> *Contact::customContextMenuActions() +{ + return 0L; +} + +QPtrList<KAction> *Contact::customContextMenuActions( ChatSession * /* manager */ ) +{ + return customContextMenuActions(); +} + + +bool Contact::isOnline() const +{ + return onlineStatus().isDefinitelyOnline(); +} + + +bool Contact::isFileCapable() const +{ + return d->fileCapable; +} + +void Contact::setFileCapable( bool filecap ) +{ + d->fileCapable = filecap; +} + + +bool Contact::canAcceptFiles() const +{ + return isOnline() && d->fileCapable; +} + +unsigned long int Contact::idleTime() const +{ + if(d->idleTime==0) + return 0; + + return d->idleTime+(d->idleTimer.elapsed()/1000); +} + +void Contact::setIdleTime( unsigned long int t ) +{ + bool idleChanged = false; + if(d->idleTime != t) + idleChanged = true; + d->idleTime=t; + if(t > 0) + d->idleTimer.start(); +//FIXME: if t == 0, idleTime() will now return garbage +// else +// d->idleTimer.stop(); + if(idleChanged) + emit idleStateChanged(this); +} + + +QStringList Contact::properties() const +{ + return d->properties.keys(); +} + +bool Contact::hasProperty(const QString &key) const +{ + return d->properties.contains(key); +} + +const ContactProperty &Contact::property(const QString &key) const +{ + if(hasProperty(key)) + return d->properties[key]; + else + return Kopete::ContactProperty::null; +} + +const Kopete::ContactProperty &Contact::property( + const Kopete::ContactPropertyTmpl &tmpl) const +{ + if(hasProperty(tmpl.key())) + return d->properties[tmpl.key()]; + else + return Kopete::ContactProperty::null; +} + + +void Contact::setProperty(const Kopete::ContactPropertyTmpl &tmpl, + const QVariant &value) +{ + if(tmpl.isNull() || tmpl.key().isEmpty()) + { + kdDebug(14000) << k_funcinfo << + "No valid template for property passed!" << endl; + return; + } + + if(value.isNull() || value.canCast(QVariant::String) && value.toString().isEmpty()) + { + removeProperty(tmpl); + } + else + { + QVariant oldValue = property(tmpl.key()).value(); + + if(oldValue != value) + { + Kopete::ContactProperty prop(tmpl, value); + d->properties.insert(tmpl.key(), prop, true); + + emit propertyChanged(this, tmpl.key(), oldValue, value); + } + } +} + +void Contact::removeProperty(const Kopete::ContactPropertyTmpl &tmpl) +{ + if(!tmpl.isNull() && !tmpl.key().isEmpty()) + { + + QVariant oldValue = property(tmpl.key()).value(); + d->properties.remove(tmpl.key()); + emit propertyChanged(this, tmpl.key(), oldValue, QVariant()); + } +} + + +QString Contact::toolTip() const +{ + Kopete::ContactProperty p; + QString tip; + QStringList shownProps = KopetePrefs::prefs()->toolTipContents(); + + // -------------------------------------------------------------------------- + // Fixed part of tooltip + + QString iconName = QString::fromLatin1("kopete-contact-icon:%1:%2:%3") + .arg( KURL::encode_string( protocol()->pluginId() ), + KURL::encode_string( account()->accountId() ), + KURL::encode_string( contactId() ) ); + + // TODO: the nickname should be a configurable properties, like others. -Olivier + QString nick = property( Kopete::Global::Properties::self()->nickName() ).value().toString(); + if ( nick.isEmpty() ) + { + tip = i18n( "<b>DISPLAY NAME</b><br><img src=\"%2\"> CONTACT STATUS", + "<b><nobr>%3</nobr></b><br><img src=\"%2\"> %1" ). + arg( Kopete::Message::escape( onlineStatus().description() ), iconName, + Kopete::Message::escape( d->contactId ) ); + } + else + { + tip = i18n( "<b>DISPLAY NAME</b> (CONTACT ID)<br><img src=\"%2\"> CONTACT STATUS", + "<nobr><b>%4</b> (%3)</nobr><br><img src=\"%2\"> %1" ). + arg( Kopete::Message::escape( onlineStatus().description() ), iconName, + Kopete::Message::escape( contactId() ), + Kopete::Emoticons::parseEmoticons( Kopete::Message::escape( nick ) ) ); + } + + // -------------------------------------------------------------------------- + // Configurable part of tooltip + + for(QStringList::Iterator it=shownProps.begin(); it!=shownProps.end(); ++it) + { + if((*it) == QString::fromLatin1("FormattedName")) + { + QString name = formattedName(); + if(!name.isEmpty()) + { + tip += i18n("<br><b>Full Name:</b> FORMATTED NAME", + "<br><b>Full Name:</b> <nobr>%1</nobr>").arg(QStyleSheet::escape(name)); + } + } + else if ((*it) == QString::fromLatin1("FormattedIdleTime")) + { + QString time = formattedIdleTime(); + if(!time.isEmpty()) + { + tip += i18n("<br><b>Idle:</b> FORMATTED IDLE TIME", + "<br><b>Idle:</b> <nobr>%1</nobr>").arg(time); + } + } + else if ((*it) == QString::fromLatin1("homePage")) + { + QString url = property(*it).value().toString(); + if(!url.isEmpty()) + { + tip += i18n("<br><b>Home Page:</b> FORMATTED URL", + "<br><b>Home Page:</b> <a href=\"%1\"><nobr>%2</nobr></a>"). + arg( KURL::encode_string( url ), Kopete::Message::escape( QStyleSheet::escape(url) ) ); + } + } + else if ((*it) == QString::fromLatin1("awayMessage")) + { + QString awaymsg = property(*it).value().toString(); + if(!awaymsg.isEmpty()) + { + tip += i18n("<br><b>Away Message:</b> FORMATTED AWAY MESSAGE", + "<br><b>Away Message:</b> %1").arg ( Kopete::Emoticons::parseEmoticons( Kopete::Message::escape(awaymsg) ) ); + } + } + else + { + p = property(*it); + if(!p.isNull()) + { + QVariant val = p.value(); + QString valueText; + + switch(val.type()) + { + case QVariant::DateTime: + valueText = KGlobal::locale()->formatDateTime(val.toDateTime()); + valueText = Kopete::Message::escape( valueText ); + break; + case QVariant::Date: + valueText = KGlobal::locale()->formatDate(val.toDate()); + valueText = Kopete::Message::escape( valueText ); + break; + case QVariant::Time: + valueText = KGlobal::locale()->formatTime(val.toTime()); + valueText = Kopete::Message::escape( valueText ); + break; + default: + if( p.isRichText() ) + { + valueText = val.toString(); + } + else + { + valueText = Kopete::Message::escape( val.toString() ); + } + } + + tip += i18n("<br><b>PROPERTY LABEL:</b> PROPERTY VALUE", + "<br><nobr><b>%2:</b></nobr> %1"). + arg( valueText, QStyleSheet::escape(p.tmpl().label()) ); + } + } + } + + return tip; +} + +QString Kopete::Contact::formattedName() const +{ + if( hasProperty(QString::fromLatin1("FormattedName")) ) + return property(QString::fromLatin1("FormattedName")).value().toString(); + + QString ret; + Kopete::ContactProperty first, last; + + first = property(QString::fromLatin1("firstName")); + last = property(QString::fromLatin1("lastName")); + if(!first.isNull()) + { + if(!last.isNull()) // contact has both first and last name + { + ret = i18n("firstName lastName", "%2 %1") + .arg(last.value().toString()) + .arg(first.value().toString()); + } + else // only first name set + { + ret = first.value().toString(); + } + } + else if(!last.isNull()) // only last name set + { + ret = last.value().toString(); + } + + return ret; +} + +QString Kopete::Contact::formattedIdleTime() const +{ + QString ret; + unsigned long int leftTime = idleTime(); + + if ( leftTime > 0 ) + { // FIXME: duplicated from code in kopetecontactlistview.cpp + unsigned long int days, hours, mins, secs; + + days = leftTime / ( 60*60*24 ); + leftTime = leftTime % ( 60*60*24 ); + hours = leftTime / ( 60*60 ); + leftTime = leftTime % ( 60*60 ); + mins = leftTime / 60; + secs = leftTime % 60; + + if ( days != 0 ) + { + ret = i18n( "<days>d <hours>h <minutes>m <seconds>s", + "%4d %3h %2m %1s" ) + .arg( secs ) + .arg( mins ) + .arg( hours ) + .arg( days ); + } + else if ( hours != 0 ) + { + ret = i18n( "<hours>h <minutes>m <seconds>s", "%3h %2m %1s" ) + .arg( secs ) + .arg( mins ) + .arg( hours ); + } + else + { + ret = i18n( "<minutes>m <seconds>s", "%2m %1s" ) + .arg( secs ) + .arg( mins ); + } + } + return ret; +} + + +void Kopete::Contact::slotBlock() +{ + account()->block( d->contactId ); +} + +void Kopete::Contact::slotUnblock() +{ + account()->unblock( d->contactId ); +} + +void Kopete::Contact::setNickName( const QString &name ) +{ + setProperty( Kopete::Global::Properties::self()->nickName(), name ); +} + +QString Kopete::Contact::nickName() const +{ + QString nick = property( Kopete::Global::Properties::self()->nickName() ).value().toString(); + if( !nick.isEmpty() ) + return nick; + + return contactId(); +} + +void Contact::virtual_hook( uint , void * ) +{ } + + +} //END namespace Kopete + + +#include "kopetecontact.moc" + + diff --git a/kopete/libkopete/kopetecontact.h b/kopete/libkopete/kopetecontact.h new file mode 100644 index 00000000..8f02bfc2 --- /dev/null +++ b/kopete/libkopete/kopetecontact.h @@ -0,0 +1,557 @@ +/* + kopetecontact.h - Kopete Contact + + Copyright (c) 2002-2004 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart @ tiscalinet.be> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef __KOPETECONTACT_H__ +#define __KOPETECONTACT_H__ + +#include <qobject.h> +#include <kurl.h> +#include <kdemacros.h> +#include "kopeteglobal.h" + +#include "kopete_export.h" + +class QImage; +class KPopupMenu; +class KAction; + +namespace Kopete +{ + +class Group; +class MetaContact; +class ChatSession; +class OnlineStatus; +class Plugin; +class Protocol; +class Account; +typedef QPtrList<Group> GroupList; + +/** + * @author Duncan Mac-Vicar P. <duncan@kde.org> + * @author Martijn Klingens <klingens@kde.org> + * @author Olivier Goffart <ogoffart@ tiscalinet.be> + * + * This class abstracts a generic contact + * Use it for inserting contacts in the contact list for example. + */ +class KOPETE_EXPORT Contact : public QObject +{ + Q_OBJECT + + Q_ENUMS( CanCreateFlags ) + Q_PROPERTY( QString formattedName READ formattedName ) + Q_PROPERTY( QString formattedIdleTime READ formattedIdleTime ) + Q_PROPERTY( bool isOnline READ isOnline ) + Q_PROPERTY( bool fileCapable READ isFileCapable WRITE setFileCapable ) + Q_PROPERTY( bool canAcceptFiles READ canAcceptFiles ) + //Q_PROPERTY( bool isReachable READ isReachable ) + Q_PROPERTY( QString contactId READ contactId ) + Q_PROPERTY( QString icon READ icon WRITE setIcon ) + Q_PROPERTY( QString toolTip READ toolTip ) + Q_PROPERTY( QString nickName READ nickName WRITE setNickName ) + //Q_PROPERTY( unsigned long idleTime READ idleTime WRITE setIdleTime ) + +public: + /** + * \brief Create new contact. + * + * <b>The parent MetaContact must not be NULL</b> + * + * \note id is required to be unique per protocol and per account. + * Across those boundaries ids may occur multiple times. + * The id is solely for comparing items safely (using pointers is + * more crash-prone). DO NOT assume anything regarding the id's + * value! Even if it may look like an ICQ UIN or an MSN passport, + * this is undefined and may change at any time! + * + * @param account is the parent account. this constructor automatically register the contact to the account + * @param id is the Contact's unique Id (mostly the user's login) + * @param parent is the parent @ref MetaContact this Contact is part of + * @param icon is an optional icon + */ + Contact( Account *account, const QString &id, MetaContact *parent, + const QString &icon = QString::null ); + + ~Contact(); + + /** + * \brief Get the metacontact for this contact + * @return The MetaContact object for this contact + */ + MetaContact *metaContact() const; + + + /** + * \brief Get the unique id that identifies a contact. + * + * \note Id is required to be unique per protocol and per account. + * Across those boundaries ids may occur multiple times. + * The id is solely for comparing items safely (using pointers is + * more crash-prone). DO NOT assume anything regarding the id's + * value! Even if it may look like an ICQ UIN or an MSN passport, + * this is undefined and may change at any time! + * + * @return The unique id of the contact + */ + QString contactId() const; + + /** + * \brief Get the protocol that the contact belongs to. + * + * simply return account()->protocol() + * + * @return the contact's protocol + */ + Protocol* protocol() const; + + /** + * \brief Get the account that this contact belongs to + * + * @return the Account object for this contact + */ + Account* account() const; + + /** + * \brief Move this contact to a new MetaContact. + * This basically reparents the contact and updates the internal + * data structures. + * If the old contact is going to be empty, a question may ask to the user if it wants to delete the old contact. + * + * @param m The new MetaContact to move this contact to + */ + void setMetaContact(MetaContact *m); + + + /** + * @brief Get whether this contact is online. + * @return @c true if the contact is online, @c false otherwise. + */ + bool isOnline() const; + + /** + * \brief Get whether this contact can receive messages + * + * Used in determining if the contact is able to + * receive messages. This function must be defined by child classes + * + * @return true if the contact can be reached + * @return false if the contact can not be reached + */ + // FIXME: After KDE 3.2 we should split this into a public, NON-virtual + // isReachable() accessor that checks for account->isConnected() + // and then calls a new virtual method that does the + // protocol-specific work, like 'doIsUnreachable' or so - Martijn + // + //FIXME: Can this be made const please? - JK + virtual bool isReachable(); + + /** + * @brief Serialize the contact for storage in the contact list. + * + * The provided serializedData contain the contact id in the field + * "contactId". If you don't like this, or don't want to + * store these fields at all, + * you are free to remove them from the list. + * + * Most plugins don't need more than these fields, so they only need + * to set the address book fields themselves. If you have nothing to + * save at all you can clear the QMap, an empty map is treated as + * 'nothing to save'. + * + * The provided addressBookFields QMap contains the index field as + * marked with @ref Plugin::addAddressBookField() with the + * contact id as value. If no index field is available the QMap is + * simply passed as an empty map. + * + * @sa Protocol::deserializeContact + */ + virtual void serialize( QMap<QString, QString> &serializedData, QMap<QString, QString> &addressBookData ); + + /** + * @brief Serialize the contacts persistent properties for storage in the contact list. + * + * Does the same as @ref serialize() does but for KopeteContactProperties + * set in this contact with their persistency flag turned on. + * In contrary to @ref serialize() this does not need to be reimplemented. + * + */ + void serializeProperties(QMap<QString, QString> &serializedData); + + /** + * @brief Deserialize the contacts persistent properties + */ + void deserializeProperties(QMap<QString, QString> &serializedData); + + /** + * @brief Get the online status of the contact + * @return the online status of the contact + */ + OnlineStatus onlineStatus() const; + + /** + * \brief Set the contact's online status + */ + void setOnlineStatus(const OnlineStatus &status); + + /** + * \brief Get the set of custom menu items for this contact + * + * Returns a set of custom menu items for the context menu + * which is displayed in showContextMenu (private). Protocols + * should use this to add protocol-specific actions to the + * popup menu. Kopete take care of the deletion of the action collection. + * Actions should have the collection as parent. + * + * @return Collection of menu items to be show on the context menu + * @todo if possible, try to use KXMLGUI + */ + virtual QPtrList<KAction> *customContextMenuActions(); + + /** + * @todo What is this function for ? + */ + virtual QPtrList<KAction> *customContextMenuActions( ChatSession *manager ); + + /** + * @brief Get the Context Menu for this contact + * + * This menu includes generic actions common to each protocol, and action defined in + * @ref customContextMenuActions() + */ + KPopupMenu *popupMenu( ChatSession *manager = 0L ); + + /** + * \brief Get whether or not this contact is capable of file transfers + * + * + * \see setFileCapable() + * \return true if the protocol for this contact is capable of file transfers + * \return false if the protocol for this contact is not capable of file transfers + * + * @todo have a capabilioties. or move to protocol capabilities + */ + bool isFileCapable() const; + + /** + * \brief Set the file transfer capability of this contact + * + * \param filecap The new file transfer capability setting + * @todo have a capabilioties. or move to protocol capabilities + */ + void setFileCapable( bool filecap ); + + /** + * \brief Get whether or not this contact can accept file transfers + * + * This function checks to make sure that the contact is online as well as + * capable of sending files. + * \see isReachable() + * @return true if this contact is online and is capable of receiving files + * @todo have a capabilioties. or move to protocol capabilities + */ + bool canAcceptFiles() const; + + enum CanCreateFlags { CannotCreate=false , CanCreate=true }; + + /** + * Returns the primary message manager affiliated with this contact + * Although a contact can have more than one active message manager + * (as is the case with MSN at least), only one message manager will + * ever be the contacts "primary" message manager.. aka the 1 on 1 chat. + * This function should always return that instance. + * + * @param canCreate If a new message manager can be created in addition + * to any existing managers. Currently, this is only set to true when + * a chat is initiated by the user by clicking the contact list. + */ + virtual ChatSession * manager( CanCreateFlags canCreate = CannotCreate ) =0; + + /** + * Returns the name of the icon to use for this contact + * If null, the protocol icon need to be used. + * The icon is not colored, nor has the status icon overloaded + */ + QString& icon() const; + + /** + * @brief Change the icon to use for this contact + * If you don't want to have the protocol icon as icon for this contact, you may set + * another icon. The icon doesn't need to be colored with the account icon as this operation + * will be performed later. + * + * if you want to go back to the protocol icon, set a null string. + */ + void setIcon( const QString& icon ); + + /** + * \brief Get the time (in seconds) this contact has been idle + * It will return the time set in @ref setIdleTime() with an addition of the time + * since you set this last time + * @return time this contact has been idle for, in seconds + // + // FIXME: Can we make this just 'unsigned long' ? QT Properties can't handle + // 'unsigned long int' + */ + virtual unsigned long int idleTime() const; + + /** + * \brief Set the current idle time in seconds. + * Kopete will automatically calculate the time in @ref idleTime + * except if you set 0. + // + // FIXME: Can we make this just 'unsigned long' ? QT Properties can't handle + // 'unsigned long int' + */ + void setIdleTime(unsigned long int); + + /** + * @return A QStringList containing all property keys + **/ + QStringList properties() const; + + /** + * Check for existance of a certain property stored + * using "key". + * \param key the property to check for + **/ + bool hasProperty(const QString &key) const; + + /** + * \brief Get the value of a property with key "key". + * + * If you don't know the type of the returned QVariant, you will need + * to check for it. + * \return the value of the property + **/ + const Kopete::ContactProperty &property(const QString &key) const; + const Kopete::ContactProperty &property(const Kopete::ContactPropertyTmpl &tmpl) const; + + /** + * \brief Add or Set a property for this contact. + * + * @param tmpl The template this property is based on, key, label etc. are + * taken from this one + * @param value The value to store + * + * \note Setting a NULL value or an empty QString castable value + * removes the property if it already existed. + * <b>Don't</b> abuse this for property-removal, instead use + * @ref removeProperty() if you want to remove on purpose. + * The Removal is done to clean up the list of properties and to purge them + * from UI. + **/ + void setProperty(const Kopete::ContactPropertyTmpl &tmpl, const QVariant &value); + + /** + * \brief Convenience method to set the nickName property to the specified value + * @param name The nickname to set + */ + void setNickName( const QString &name ); + + /** + * \brief Convenience method to retrieve the nickName property. + * + * This method will return the contactId if there has been no nickName property set + */ + QString nickName() const; + + /** + * \brief Remove a property if it exists + * + * @param tmpl the template this property is based on + **/ + void removeProperty(const Kopete::ContactPropertyTmpl &tmpl); + + /** + * \brief Get the tooltip for this contact + * Makes use of formattedName() and formattedIdleTime(). + * \return an RTF tooltip depending on KopetePrefs settings + **/ + QString toolTip() const; + + /** + * Returns a formatted string of "firstName" and/or "lastName" properties + * if present. + * Suitable for GUI display + **/ + QString formattedName() const; + + /** + * Returns a formatted string of idleTime(). + * Suitable for GUI display + **/ + QString formattedIdleTime() const; + + /** + * used in @ref sync() + */ + enum Changed{ MovedBetweenGroup = 0x01, ///< the contact has been moved between groups + DisplayNameChanged = 0x02 ///< the displayname of the contact changed + }; + + +public slots: + /** + * This should typically pop up a KopeteChatWindow + */ + void startChat(); + + /** + * Pops up an email type window + */ + void sendMessage(); + + /** + * The user clicked on the contact, do the default action + */ + void execute(); + + /** + * Changes the MetaContact that this contact is a part of. This function + * is called by the KAction changeMetaContact that is part of the context + * menu. + */ + void changeMetaContact(); + + /** + * Method to retrieve user information. Should be implemented by + * the protocols, and popup some sort of dialog box + * + * reimplement it to show the informlation + * @todo rename and make it pure virtual + */ + virtual void slotUserInfo() {}; + + /** + * @brief Syncronise the server and the metacontact. + * Protocols with server-side contact lists can implement this to + * sync the server groups with the metaContact groups. Or the server alias if any. + * + * This method is called every time the metacontact has been moved or renamed. + * + * default implementation does nothing + * + * @param changed is a bitmask of the @ref Changed enum which say why the call to this funtion is done. + */ + virtual void sync(unsigned int changed = 0xFF); + + /** + * Method to delete a contact from the contact list, + * should be implemented by protocol plugin to handle + * protocol-specific actions required to delete a contact + * (ie. messages to the server, etc) + * the default implementation simply call deleteLater() + */ + virtual void deleteContact(); + + /** + * This is the Contact level slot for sending files. It should be + * implemented by all contacts which have the setFileCapable() flag set to + * true. If the function is called through the GUI, no parameters are sent + * and they take on default values (the file is chosen with a file open dialog) + * + * @param sourceURL The actual KURL of the file you are sending + * @param fileName (Optional) An alternate name for the file - what the + * receiver will see + * @param fileSize (Optional) Size of the file being sent. Used when sending + * a nondeterminate + * file size (such as over asocket + */ + virtual void sendFile( const KURL &sourceURL = KURL(), + const QString &fileName = QString::null, uint fileSize = 0L ); + +private slots: + + /** + * This add the contact totally in the list if it was a temporary contact + */ + void slotAddContact(); + + /** + * slot called when the action "delete" is called. + */ + void slotDelete(); + + /** + * slot called when the action "block" is called. + */ + void slotBlock(); + + /** + * slot called when the action "unblock" is called. + */ + void slotUnblock(); + + /** + * The account's isConnected has changed. + */ + void slotAccountIsConnectedChanged(); + +signals: + /** + * The contact's online status changed + */ + void onlineStatusChanged( Kopete::Contact *contact, + const Kopete::OnlineStatus &status, const Kopete::OnlineStatus &oldStatus ); + + /** + * The contact is about to be destroyed. + * Called when entering the destructor. Useful for cleanup, since + * metaContact() is still accessible at this point. + * + * @warning this signal is emit in the Contact destructor, so all + * virtual method are not available + */ + void contactDestroyed( Kopete::Contact *contact ); + + /** + * The contact's idle state changed. + * You need to emit this signal to update the view. + * That mean when activity has been noticed + */ + void idleStateChanged( Kopete::Contact *contact ); + + /** + * One of the contact's properties has changed. + * @param contact this contact, useful for listening to signals from more than one contact + * @param key the key whose value has changed + * @param oldValue the value before the change, or an invalid QVariant if the property is new + * @param newValue the value after the change, or an invalid QVariant if the property was removed + */ + void propertyChanged( Kopete::Contact *contact, const QString &key, + const QVariant &oldValue, const QVariant &newValue ); + +protected: + virtual void virtual_hook(uint id, void *data); + +private: + class Private; + Private *d; + + +}; + + +} //END namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetecontactlist.cpp b/kopete/libkopete/kopetecontactlist.cpp new file mode 100644 index 00000000..9aab9f2f --- /dev/null +++ b/kopete/libkopete/kopetecontactlist.cpp @@ -0,0 +1,1112 @@ +/* + kopetecontactlist.cpp - Kopete's Contact List backend + + Copyright (c) 2005 by Michael Larouche <michael.larouche@kdemail.net> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart @ kde.org> + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Copyright (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetecontactlist.h" + +#include <qdir.h> +#include <qregexp.h> +#include <qtimer.h> + +#include <kapplication.h> +#include <kabc/stdaddressbook.h> +#include <kdebug.h> +#include <ksavefile.h> +#include <kstandarddirs.h> +#include <kopeteconfig.h> +#include <kglobal.h> +#include "kopetemetacontact.h" +#include "kopetecontact.h" +#include "kopetechatsession.h" +//#include "kopetemessage.h" +#include "kopetepluginmanager.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" +#include "kopetegroup.h" +#include "kopetepicture.h" + + +namespace Kopete +{ + +class ContactList::Private +{public: + /** Flag: do not save the contactlist until she is completely loaded */ + bool loaded ; + + QPtrList<MetaContact> contacts; + QPtrList<Group> groups; + QPtrList<MetaContact> selectedMetaContacts; + QPtrList<Group> selectedGroups; + + QTimer *saveTimer; + + MetaContact *myself; + + /** Flag: does the user uses the global identity */ + bool useGlobalIdentity; + + /** + * Current contact list version * 10 ( i.e. '10' is version '1.0' ) + */ + static const uint ContactListVersion = 10; +}; + +ContactList *ContactList::s_self = 0L; + +ContactList *ContactList::self() +{ + if( !s_self ) + s_self = new ContactList; + + return s_self; +} + +ContactList::ContactList() + : QObject( kapp, "KopeteContactList" ) +{ + d=new Private; + + //the myself metacontact can't be created now, because it will use + //ContactList::self() as parent which will call this constructor -> infinite loop + d->myself=0L; + + //no contactlist loaded yet, don't save them + d->loaded=false; + + // automatically save on changes to the list + d->saveTimer = new QTimer( this, "saveTimer" ); + connect( d->saveTimer, SIGNAL( timeout() ), SLOT ( save() ) ); + + connect( this, SIGNAL( metaContactAdded( Kopete::MetaContact * ) ), SLOT( slotSaveLater() ) ); + connect( this, SIGNAL( metaContactRemoved( Kopete::MetaContact * ) ), SLOT( slotSaveLater() ) ); + connect( this, SIGNAL( groupAdded( Kopete::Group * ) ), SLOT( slotSaveLater() ) ); + connect( this, SIGNAL( groupRemoved( Kopete::Group * ) ), SLOT( slotSaveLater() ) ); + connect( this, SIGNAL( groupRenamed( Kopete::Group *, const QString & ) ), SLOT( slotSaveLater() ) ); +} + +ContactList::~ContactList() +{ + delete d->myself; + delete d; +} + +QPtrList<MetaContact> ContactList::metaContacts() const +{ + return d->contacts; +} + + +QPtrList<Group> ContactList::groups() const +{ + return d->groups; +} + + +MetaContact *ContactList::metaContact( const QString &metaContactId ) const +{ + QPtrListIterator<MetaContact> it( d->contacts ); + + for( ; it.current(); ++it ) + { + if( it.current()->metaContactId() == metaContactId ) + return it.current(); + } + + return 0L; +} + + +Group * ContactList::group(unsigned int groupId) const +{ + Group *groupIterator; + for ( groupIterator = d->groups.first(); groupIterator; groupIterator = d->groups.next() ) + { + if( groupIterator->groupId()==groupId ) + return groupIterator; + } + return 0L; +} + + +Contact *ContactList::findContact( const QString &protocolId, + const QString &accountId, const QString &contactId ) const +{ + //Browsing metacontacts is too slow, better to uses the Dict of the account. + Account *i=AccountManager::self()->findAccount(protocolId,accountId); + if(!i) + { + kdDebug( 14010 ) << k_funcinfo << "Account not found" << endl; + return 0L; + } + return i->contacts()[contactId]; +} + + +MetaContact *ContactList::findMetaContactByDisplayName( const QString &displayName ) const +{ + QPtrListIterator<MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { +// kdDebug(14010) << "Display Name: " << it.current()->displayName() << "\n"; + if( it.current()->displayName() == displayName ) { + return it.current(); + } + } + + return 0L; +} + +MetaContact* ContactList::findMetaContactByContactId( const QString &contactId ) const +{ + QPtrList<Account> acts=AccountManager::self()->accounts(); + QPtrListIterator<Account> it( acts ); + for ( ; it.current(); ++it ) + { + Contact *c=(*it)->contacts()[contactId]; + if(c && c->metaContact()) + return c->metaContact(); + } + return 0L; +} + +Group * ContactList::findGroup(const QString& displayName, int type) +{ + if( type == Group::Temporary ) + return Group::temporary(); + + Group *groupIterator; + for ( groupIterator = d->groups.first(); groupIterator; groupIterator = d->groups.next() ) + { + if( groupIterator->type() == type && groupIterator->displayName() == displayName ) + return groupIterator; + } + + Group *newGroup = new Group( displayName, (Group::GroupType)type ); + addGroup( newGroup ); + return newGroup; +} + + +QPtrList<MetaContact> ContactList::selectedMetaContacts() const +{ + return d->selectedMetaContacts; +} + +QPtrList<Group> ContactList::selectedGroups() const +{ + return d->selectedGroups; +} + + +void ContactList::addMetaContact( MetaContact *mc ) +{ + if ( d->contacts.contains( mc ) ) + return; + + d->contacts.append( mc ); + + emit metaContactAdded( mc ); + connect( mc, SIGNAL( persistentDataChanged( ) ), SLOT( slotSaveLater() ) ); + connect( mc, SIGNAL( addedToGroup( Kopete::MetaContact *, Kopete::Group * ) ), SIGNAL( metaContactAddedToGroup( Kopete::MetaContact *, Kopete::Group * ) ) ); + connect( mc, SIGNAL( removedFromGroup( Kopete::MetaContact *, Kopete::Group * ) ), SIGNAL( metaContactRemovedFromGroup( Kopete::MetaContact *, Kopete::Group * ) ) ); +} + + +void ContactList::removeMetaContact(MetaContact *m) +{ + if ( !d->contacts.contains(m) ) + { + kdDebug(14010) << k_funcinfo << "Trying to remove a not listed MetaContact." << endl; + return; + } + + if ( d->selectedMetaContacts.contains( m ) ) + { + d->selectedMetaContacts.remove( m ); + setSelectedItems( d->selectedMetaContacts, d->selectedGroups ); + } + + //removes subcontact from server here and now. + QPtrList<Contact> cts=m->contacts(); + for( Contact *c = cts.first(); c; c = cts.next() ) + { + c->deleteContact(); + } + + d->contacts.remove( m ); + emit metaContactRemoved( m ); + m->deleteLater(); +} + + +void ContactList::addGroup( Group * g ) +{ + if(!d->groups.contains(g) ) + { + d->groups.append( g ); + emit groupAdded( g ); + connect( g , SIGNAL ( displayNameChanged(Kopete::Group* , const QString & )) , this , SIGNAL ( groupRenamed(Kopete::Group* , const QString & )) ) ; + } +} + +void ContactList::removeGroup( Group *g ) +{ + if ( d->selectedGroups.contains( g ) ) + { + d->selectedGroups.remove( g ); + setSelectedItems( d->selectedMetaContacts, d->selectedGroups ); + } + + d->groups.remove( g ); + emit groupRemoved( g ); + g->deleteLater(); +} + + +void ContactList::setSelectedItems(QPtrList<MetaContact> metaContacts , QPtrList<Group> groups) +{ + kdDebug( 14010 ) << k_funcinfo << metaContacts.count() << " metacontacts, " << groups.count() << " groups selected" << endl; + d->selectedMetaContacts=metaContacts; + d->selectedGroups=groups; + + emit metaContactSelected( groups.isEmpty() && metaContacts.count()==1 ); + emit selectionChanged(); +} + +MetaContact* ContactList::myself() +{ + if(!d->myself) + d->myself=new MetaContact(); + return d->myself; +} + +void ContactList::loadGlobalIdentity() +{ + // Apply the global identity + if(Kopete::Config::enableGlobalIdentity()) + { + // Disconnect to make sure it will not cause duplicate calls. + disconnect(myself(), SIGNAL(displayNameChanged(const QString&, const QString&)), this, SLOT(slotDisplayNameChanged())); + disconnect(myself(), SIGNAL(photoChanged()), this, SLOT(slotPhotoChanged())); + + connect(myself(), SIGNAL(displayNameChanged(const QString&, const QString&)), this, SLOT(slotDisplayNameChanged())); + connect(myself(), SIGNAL(photoChanged()), this, SLOT(slotPhotoChanged())); + + // Ensure that the myself metaContactId is always the KABC whoAmI + KABC::Addressee a = KABC::StdAddressBook::self()->whoAmI(); + if(!a.isEmpty() && a.uid() != myself()->metaContactId()) + { + myself()->setMetaContactId(a.uid()); + } + + // Apply the global identity + // Maybe one of the myself contact from a account has a different displayName/photo at startup. + slotDisplayNameChanged(); + slotPhotoChanged(); + } + else + { + disconnect(myself(), SIGNAL(displayNameChanged(const QString&, const QString&)), this, SLOT(slotDisplayNameChanged())); + disconnect(myself(), SIGNAL(photoChanged()), this, SLOT(slotPhotoChanged())); + } +} + +void ContactList::slotDisplayNameChanged() +{ + static bool mutex=false; + if(mutex) + { + kdDebug (14010) << k_funcinfo << " mutex blocked" << endl ; + return; + } + mutex=true; + + kdDebug( 14010 ) << k_funcinfo << myself()->displayName() << endl; + + emit globalIdentityChanged(Kopete::Global::Properties::self()->nickName().key(), myself()->displayName()); + mutex=false; +} + +void ContactList::slotPhotoChanged() +{ + static bool mutex=false; + if(mutex) + { + kdDebug (14010) << k_funcinfo << " mutex blocked" << endl ; + return; + } + mutex=true; + kdDebug( 14010 ) << k_funcinfo << myself()->picture().path() << endl; + + emit globalIdentityChanged(Kopete::Global::Properties::self()->photo().key(), myself()->picture().path()); + mutex=false; + /* The mutex is usefull to don't have such as stack overflow + Kopete::ContactList::slotPhotoChanged -> Kopete::ContactList::globalIdentityChanged + MSNAccount::slotGlobalIdentityChanged -> Kopete::Contact::propertyChanged + Kopete::MetaContact::slotPropertyChanged -> Kopete::MetaContact::photoChanged -> Kopete::ContactList::slotPhotoChanged + */ +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +void ContactList::load() +{ + loadXML(); + // Apply the global identity when all the protocols plugins are loaded. + connect(PluginManager::self(), SIGNAL(allPluginsLoaded()), this, SLOT(loadGlobalIdentity())); +} + +void ContactList::loadXML() +{ + // don't save when we're in the middle of this... + d->loaded = false; + + QString filename = locateLocal( "appdata", QString::fromLatin1( "contactlist.xml" ) ); + if( filename.isEmpty() ) + { + d->loaded=true; + return ; + } + + QDomDocument contactList( QString::fromLatin1( "kopete-contact-list" ) ); + + QFile contactListFile( filename ); + contactListFile.open( IO_ReadOnly ); + contactList.setContent( &contactListFile ); + + QDomElement list = contactList.documentElement(); + + QString versionString = list.attribute( QString::fromLatin1( "version" ), QString::null ); + uint version = 0; + if( QRegExp( QString::fromLatin1( "[0-9]+\\.[0-9]" ) ).exactMatch( versionString ) ) + version = versionString.replace( QString::fromLatin1( "." ), QString::null ).toUInt(); + + if( version < Private::ContactListVersion ) + { + // The version string is invalid, or we're using an older version. + // Convert first and reparse the file afterwards + kdDebug( 14010 ) << k_funcinfo << "Contact list version " << version + << " is older than current version " << Private::ContactListVersion + << ". Converting first." << endl; + + contactListFile.close(); + + convertContactList( filename, version, Private::ContactListVersion ); + + contactList = QDomDocument ( QString::fromLatin1( "kopete-contact-list" ) ); + + contactListFile.open( IO_ReadOnly ); + contactList.setContent( &contactListFile ); + + list = contactList.documentElement(); + } + + addGroup( Kopete::Group::topLevel() ); + + QDomElement element = list.firstChild().toElement(); + while( !element.isNull() ) + { + if( element.tagName() == QString::fromLatin1("meta-contact") ) + { + //TODO: id isn't used + //QString id = element.attribute( "id", QString::null ); + Kopete::MetaContact *metaContact = new Kopete::MetaContact(); + if ( !metaContact->fromXML( element ) ) + { + delete metaContact; + metaContact = 0; + } + else + { + Kopete::ContactList::self()->addMetaContact( + metaContact ); + } + } + else if( element.tagName() == QString::fromLatin1("kopete-group") ) + { + Kopete::Group *group = new Kopete::Group(); + if( !group->fromXML( element ) ) + { + delete group; + group = 0; + } + else + { + Kopete::ContactList::self()->addGroup( group ); + } + } + // Only load myself metacontact information when Global Identity is enabled. + else if( element.tagName() == QString::fromLatin1("myself-meta-contact") && Kopete::Config::enableGlobalIdentity() ) + { + if( !myself()->fromXML( element ) ) + { + delete d->myself; + d->myself = 0; + } + } + else + { + kdWarning(14010) << "Kopete::ContactList::loadXML: " + << "Unknown element '" << element.tagName() + << "' in contact list!" << endl; + } + element = element.nextSibling().toElement(); + } + contactListFile.close(); + d->loaded=true; +} + +void ContactList::convertContactList( const QString &fileName, uint /* fromVersion */, uint /* toVersion */ ) +{ + // For now, ignore fromVersion and toVersion. These are meant for future + // changes to allow incremental (multi-pass) conversion so we don't have + // to rewrite the whole conversion code for each change. + + QDomDocument contactList( QString::fromLatin1( "messaging-contact-list" ) ); + QFile contactListFile( fileName ); + contactListFile.open( IO_ReadOnly ); + contactList.setContent( &contactListFile ); + + QDomElement oldList = contactList.documentElement(); + + QDomDocument newList( QString::fromLatin1( "kopete-contact-list" ) ); + newList.appendChild( newList.createProcessingInstruction( QString::fromLatin1( "xml" ), QString::fromLatin1( "version=\"1.0\"" ) ) ); + + QDomElement newRoot = newList.createElement( QString::fromLatin1( "kopete-contact-list" ) ); + newList.appendChild( newRoot ); + newRoot.setAttribute( QString::fromLatin1( "version" ), QString::fromLatin1( "1.0" ) ); + + QDomNode oldNode = oldList.firstChild(); + while( !oldNode.isNull() ) + { + QDomElement oldElement = oldNode.toElement(); + if( !oldElement.isNull() ) + { + if( oldElement.tagName() == QString::fromLatin1("meta-contact") ) + { + // Ignore ID, it is not used in the current list anyway + QDomElement newMetaContact = newList.createElement( QString::fromLatin1( "meta-contact" ) ); + newRoot.appendChild( newMetaContact ); + + // Plugin data is stored completely different, and requires + // some bookkeeping to convert properly + QMap<QString, QDomElement> pluginData; + QStringList icqData; + QStringList gaduData; + + // ICQ and Gadu can only be converted properly if the address book fields + // are already parsed. Therefore, scan for those first and add the rest + // afterwards + QDomNode oldContactNode = oldNode.firstChild(); + while( !oldContactNode.isNull() ) + { + QDomElement oldContactElement = oldContactNode.toElement(); + if( !oldContactElement.isNull() && oldContactElement.tagName() == QString::fromLatin1("address-book-field") ) + { + // Convert address book fields. + // Jabber will be called "xmpp", Aim/Toc and Aim/Oscar both will + // be called "aim". MSN, AIM, IRC, Oscar and SMS don't use address + // book fields yet; Gadu and ICQ can be converted as-is. + // As Yahoo is unfinished we won't try to convert at all. + QString id = oldContactElement.attribute( QString::fromLatin1( "id" ), QString::null ); + QString data = oldContactElement.text(); + + QString app, key, val; + QString separator = QString::fromLatin1( "," ); + if( id == QString::fromLatin1( "messaging/gadu" ) ) + separator = QString::fromLatin1( "\n" ); + else if( id == QString::fromLatin1( "messaging/icq" ) ) + separator = QString::fromLatin1( ";" ); + else if( id == QString::fromLatin1( "messaging/jabber" ) ) + id = QString::fromLatin1( "messaging/xmpp" ); + + if( id == QString::fromLatin1( "messaging/gadu" ) || id == QString::fromLatin1( "messaging/icq" ) || + id == QString::fromLatin1( "messaging/winpopup" ) || id == QString::fromLatin1( "messaging/xmpp" ) ) + { + app = id; + key = QString::fromLatin1( "All" ); + val = data.replace( separator, QChar( 0xE000 ) ); + } + + if( !app.isEmpty() ) + { + QDomElement addressBookField = newList.createElement( QString::fromLatin1( "address-book-field" ) ); + newMetaContact.appendChild( addressBookField ); + + addressBookField.setAttribute( QString::fromLatin1( "app" ), app ); + addressBookField.setAttribute( QString::fromLatin1( "key" ), key ); + + addressBookField.appendChild( newList.createTextNode( val ) ); + + // ICQ didn't store the contactId locally, only in the address + // book fields, so we need to be able to access it later + if( id == QString::fromLatin1( "messaging/icq" ) ) + icqData = QStringList::split( QChar( 0xE000 ), val ); + else if( id == QString::fromLatin1("messaging/gadu") ) + gaduData = QStringList::split( QChar( 0xE000 ), val ); + } + } + oldContactNode = oldContactNode.nextSibling(); + } + + // Now, convert the other elements + oldContactNode = oldNode.firstChild(); + while( !oldContactNode.isNull() ) + { + QDomElement oldContactElement = oldContactNode.toElement(); + if( !oldContactElement.isNull() ) + { + if( oldContactElement.tagName() == QString::fromLatin1("display-name") ) + { + QDomElement displayName = newList.createElement( QString::fromLatin1( "display-name" ) ); + displayName.appendChild( newList.createTextNode( oldContactElement.text() ) ); + newMetaContact.appendChild( displayName ); + } + else if( oldContactElement.tagName() == QString::fromLatin1("groups") ) + { + QDomElement groups = newList.createElement( QString::fromLatin1( "groups" ) ); + newMetaContact.appendChild( groups ); + + QDomNode oldGroup = oldContactElement.firstChild(); + while( !oldGroup.isNull() ) + { + QDomElement oldGroupElement = oldGroup.toElement(); + if ( oldGroupElement.tagName() == QString::fromLatin1("group") ) + { + QDomElement group = newList.createElement( QString::fromLatin1( "group" ) ); + group.appendChild( newList.createTextNode( oldGroupElement.text() ) ); + groups.appendChild( group ); + } + else if ( oldGroupElement.tagName() == QString::fromLatin1("top-level") ) + { + QDomElement group = newList.createElement( QString::fromLatin1( "top-level" ) ); + groups.appendChild( group ); + } + + oldGroup = oldGroup.nextSibling(); + } + } + else if( oldContactElement.tagName() == QString::fromLatin1( "plugin-data" ) ) + { + // Convert the plugin data + QString id = oldContactElement.attribute( QString::fromLatin1( "plugin-id" ), QString::null ); + QString data = oldContactElement.text(); + + bool convertOldAim = false; + uint fieldCount = 1; + QString addressBookLabel; + if( id == QString::fromLatin1("MSNProtocol") ) + { + fieldCount = 3; + addressBookLabel = QString::fromLatin1("msn"); + } + else if( id == QString::fromLatin1("IRCProtocol") ) + { + fieldCount = 3; + addressBookLabel = QString::fromLatin1("irc"); + } + else if( id == QString::fromLatin1("OscarProtocol") ) + { + fieldCount = 2; + addressBookLabel = QString::fromLatin1("aim"); + } + else if( id == QString::fromLatin1("AIMProtocol") ) + { + id = QString::fromLatin1("OscarProtocol"); + convertOldAim = true; + addressBookLabel = QString::fromLatin1("aim"); + } + else if( id == QString::fromLatin1("ICQProtocol") || id == QString::fromLatin1("WPProtocol") || id == QString::fromLatin1("GaduProtocol") ) + { + fieldCount = 1; + } + else if( id == QString::fromLatin1("JabberProtocol") ) + { + fieldCount = 4; + } + else if( id == QString::fromLatin1("SMSProtocol") ) + { + // SMS used a variable serializing using a dot as delimiter. + // The minimal count is three though (id, name, delimiter). + fieldCount = 2; + addressBookLabel = QString::fromLatin1("sms"); + } + + if( pluginData[ id ].isNull() ) + { + pluginData[ id ] = newList.createElement( QString::fromLatin1( "plugin-data" ) ); + pluginData[ id ].setAttribute( QString::fromLatin1( "plugin-id" ), id ); + newMetaContact.appendChild( pluginData[ id ] ); + } + + // Do the actual conversion + if( id == QString::fromLatin1( "MSNProtocol" ) || id == QString::fromLatin1( "OscarProtocol" ) || + id == QString::fromLatin1( "AIMProtocol" ) || id == QString::fromLatin1( "IRCProtocol" ) || + id == QString::fromLatin1( "ICQProtocol" ) || id == QString::fromLatin1( "JabberProtocol" ) || + id == QString::fromLatin1( "SMSProtocol" ) || id == QString::fromLatin1( "WPProtocol" ) || + id == QString::fromLatin1( "GaduProtocol" ) ) + { + QStringList strList = QStringList::split( QString::fromLatin1( "||" ), data ); + + // Unescape '||' + for( QStringList::iterator it = strList.begin(); it != strList.end(); ++it ) + { + ( *it ).replace( QString::fromLatin1( "\\|;" ), QString::fromLatin1( "|" ) ). + replace( QString::fromLatin1( "\\\\" ), QString::fromLatin1( "\\" ) ); + } + + uint idx = 0; + while( idx < strList.size() ) + { + QDomElement dataField; + + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "contactId" ) ); + if( id == QString::fromLatin1("ICQProtocol") ) + dataField.appendChild( newList.createTextNode( icqData[ idx ] ) ); + else if( id == QString::fromLatin1("GaduProtocol") ) + dataField.appendChild( newList.createTextNode( gaduData[ idx ] ) ); + else if( id == QString::fromLatin1("JabberProtocol") ) + dataField.appendChild( newList.createTextNode( strList[ idx + 1 ] ) ); + else + dataField.appendChild( newList.createTextNode( strList[ idx ] ) ); + + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "displayName" ) ); + if( convertOldAim || id == QString::fromLatin1("ICQProtocol") || id == QString::fromLatin1("WPProtocol") || id == QString::fromLatin1("GaduProtocol") ) + dataField.appendChild( newList.createTextNode( strList[ idx ] ) ); + else if( id == QString::fromLatin1("JabberProtocol") ) + dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) ); + else + dataField.appendChild( newList.createTextNode( strList[ idx + 1 ] ) ); + + if( id == QString::fromLatin1("MSNProtocol") ) + { + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "groups" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) ); + } + else if( id == QString::fromLatin1("IRCProtocol") ) + { + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "serverName" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) ); + } + else if( id == QString::fromLatin1("JabberProtocol") ) + { + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "accountId" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx ] ) ); + + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "groups" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx + 3 ] ) ); + } + else if( id == QString::fromLatin1( "SMSProtocol" ) && + ( idx + 2 < strList.size() ) && strList[ idx + 2 ] != QString::fromLatin1( "." ) ) + { + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "serviceName" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) ); + + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "servicePrefs" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx + 3 ] ) ); + + // Add extra fields + idx += 2; + } + + // MSN, AIM, IRC, Oscar and SMS didn't store address book fields up + // to now, so create one + if( id != QString::fromLatin1("ICQProtocol") && id != QString::fromLatin1("JabberProtocol") && id != QString::fromLatin1("WPProtocol") && id != QString::fromLatin1("GaduProtocol") ) + { + QDomElement addressBookField = newList.createElement( QString::fromLatin1( "address-book-field" ) ); + newMetaContact.appendChild( addressBookField ); + + addressBookField.setAttribute( QString::fromLatin1( "app" ), + QString::fromLatin1( "messaging/" ) + addressBookLabel ); + addressBookField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "All" ) ); + addressBookField.appendChild( newList.createTextNode( strList[ idx ] ) ); + } + + idx += fieldCount; + } + } + else if( id == QString::fromLatin1("ContactNotesPlugin") || id == QString::fromLatin1("CryptographyPlugin") || id == QString::fromLatin1("TranslatorPlugin") ) + { + QDomElement dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + if( id == QString::fromLatin1("ContactNotesPlugin") ) + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "notes" ) ); + else if( id == QString::fromLatin1("CryptographyPlugin") ) + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "gpgKey" ) ); + else if( id == QString::fromLatin1("TranslatorPlugin") ) + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "languageKey" ) ); + + dataField.appendChild( newList.createTextNode( data ) ); + } + } + } + oldContactNode = oldContactNode.nextSibling(); + } + } + else if( oldElement.tagName() == QString::fromLatin1("kopete-group") ) + { + QDomElement newGroup = newList.createElement( QString::fromLatin1( "kopete-group" ) ); + newRoot.appendChild( newGroup ); + + QDomNode oldGroupNode = oldNode.firstChild(); + while( !oldGroupNode.isNull() ) + { + QDomElement oldGroupElement = oldGroupNode.toElement(); + + if( oldGroupElement.tagName() == QString::fromLatin1("display-name") ) + { + QDomElement displayName = newList.createElement( QString::fromLatin1( "display-name" ) ); + displayName.appendChild( newList.createTextNode( oldGroupElement.text() ) ); + newGroup.appendChild( displayName ); + } + if( oldGroupElement.tagName() == QString::fromLatin1("type") ) + { + if( oldGroupElement.text() == QString::fromLatin1("Temporary") ) + newGroup.setAttribute( QString::fromLatin1( "type" ), QString::fromLatin1( "temporary" ) ); + else if( oldGroupElement.text() == QString::fromLatin1( "TopLevel" ) ) + newGroup.setAttribute( QString::fromLatin1( "type" ), QString::fromLatin1( "top-level" ) ); + else + newGroup.setAttribute( QString::fromLatin1( "type" ), QString::fromLatin1( "standard" ) ); + } + if( oldGroupElement.tagName() == QString::fromLatin1("view") ) + { + if( oldGroupElement.text() == QString::fromLatin1("collapsed") ) + newGroup.setAttribute( QString::fromLatin1( "view" ), QString::fromLatin1( "collapsed" ) ); + else + newGroup.setAttribute( QString::fromLatin1( "view" ), QString::fromLatin1( "expanded" ) ); + } + else if( oldGroupElement.tagName() == QString::fromLatin1("plugin-data") ) + { + // Per-group plugin data + // FIXME: This needs updating too, ideally, convert this in a later + // contactlist.xml version + QDomElement groupPluginData = newList.createElement( QString::fromLatin1( "plugin-data" ) ); + newGroup.appendChild( groupPluginData ); + + groupPluginData.setAttribute( QString::fromLatin1( "plugin-id" ), + oldGroupElement.attribute( QString::fromLatin1( "plugin-id" ), QString::null ) ); + groupPluginData.appendChild( newList.createTextNode( oldGroupElement.text() ) ); + } + + oldGroupNode = oldGroupNode.nextSibling(); + } + } + else + { + kdWarning( 14010 ) << k_funcinfo << "Unknown element '" << oldElement.tagName() + << "' in contact list!" << endl; + } + } + oldNode = oldNode.nextSibling(); + } + + // Close the file, and save the new file + contactListFile.close(); + + QDir().rename( fileName, fileName + QString::fromLatin1( ".bak" ) ); + + // kdDebug( 14010 ) << k_funcinfo << "XML output:\n" << newList.toString( 2 ) << endl; + + contactListFile.open( IO_WriteOnly ); + QTextStream stream( &contactListFile ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + stream << newList.toString( 2 ); + + contactListFile.flush(); + contactListFile.close(); +} + +void Kopete::ContactList::save() +{ + saveXML(); +} + +void Kopete::ContactList::saveXML() +{ + if(!d->loaded) + { + kdDebug(14010) << "Kopete::ContactList::saveXML: contactlist not loaded, abort saving" << endl; + return; + } + + QString contactListFileName = locateLocal( "appdata", QString::fromLatin1( "contactlist.xml" ) ); + KSaveFile contactListFile( contactListFileName ); + if( contactListFile.status() == 0 ) + { + QTextStream *stream = contactListFile.textStream(); + stream->setEncoding( QTextStream::UnicodeUTF8 ); + toXML().save( *stream, 4 ); + + if ( contactListFile.close() ) + { + // cancel any scheduled saves + d->saveTimer->stop(); + return; + } + else + { + kdDebug(14010) << "Kopete::ContactList::saveXML: failed to write contactlist, error code is: " << contactListFile.status() << endl; + } + } + else + { + kdWarning(14010) << "Kopete::ContactList::saveXML: Couldn't open contact list file " + << contactListFileName << ". Contact list not saved." << endl; + } + + // if we got here, saving the contact list failed. retry every minute until it works. + d->saveTimer->start( 60000, true /* single-shot: will get restarted by us next time if it's still failing */ ); +} + +const QDomDocument ContactList::toXML() +{ + QDomDocument doc; + doc.appendChild( doc.createElement( QString::fromLatin1("kopete-contact-list") ) ); + doc.documentElement().setAttribute( QString::fromLatin1("version"), QString::fromLatin1("1.0")); + + // Save group information. ie: Open/Closed, pehaps later icons? Who knows. + for( Kopete::Group *g = d->groups.first(); g; g = d->groups.next() ) + doc.documentElement().appendChild( doc.importNode( g->toXML(), true ) ); + + // Save metacontact information. + for( Kopete::MetaContact *m = d->contacts.first(); m; m = d->contacts.next() ) + if( !m->isTemporary() ) + doc.documentElement().appendChild( doc.importNode( m->toXML(), true ) ); + + // Save myself metacontact information + if( Kopete::Config::enableGlobalIdentity() ) + { + QDomElement myselfElement = myself()->toXML(true); // Save minimal information. + myselfElement.setTagName( QString::fromLatin1("myself-meta-contact") ); + doc.documentElement().appendChild( doc.importNode( myselfElement, true ) ); + } + + return doc; +} + +QStringList ContactList::contacts() const +{ + QStringList contacts; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + contacts.append( it.current()->displayName() ); + } + return contacts; +} + +QStringList ContactList::contactStatuses() const +{ + QStringList meta_contacts; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + meta_contacts.append( QString::fromLatin1( "%1 (%2)" ). + arg( it.current()->displayName(), it.current()->statusString() )); + } + return meta_contacts; +} + +QStringList ContactList::reachableContacts() const +{ + QStringList contacts; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + if ( it.current()->isReachable() ) + contacts.append( it.current()->displayName() ); + } + return contacts; +} + +QPtrList<Contact> ContactList::onlineContacts() const +{ + QPtrList<Kopete::Contact> result; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + if ( it.current()->isOnline() ) + { + QPtrList<Kopete::Contact> contacts = it.current()->contacts(); + QPtrListIterator<Kopete::Contact> cit( contacts ); + for( ; cit.current(); ++cit ) + { + if ( cit.current()->isOnline() ) + result.append( cit.current() ); + } + } + } + return result; +} + +QPtrList<Kopete::MetaContact> Kopete::ContactList::onlineMetaContacts() const +{ + QPtrList<Kopete::MetaContact> result; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + if ( it.current()->isOnline() ) + result.append( it.current() ); + } + return result; +} + +QPtrList<Kopete::MetaContact> Kopete::ContactList::onlineMetaContacts( const QString &protocolId ) const +{ + QPtrList<Kopete::MetaContact> result; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + // FIXME: This loop is not very efficient :( + if ( it.current()->isOnline() ) + { + QPtrList<Kopete::Contact> contacts = it.current()->contacts(); + QPtrListIterator<Kopete::Contact> cit( contacts ); + for( ; cit.current(); ++cit ) + { + if( cit.current()->isOnline() && cit.current()->protocol()->pluginId() == protocolId ) + result.append( it.current() ); + } + } + } + return result; +} + +QPtrList<Kopete::Contact> Kopete::ContactList::onlineContacts( const QString &protocolId ) const +{ + QPtrList<Kopete::Contact> result; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + // FIXME: This loop is not very efficient :( + if ( it.current()->isOnline() ) + { + QPtrList<Kopete::Contact> contacts = it.current()->contacts(); + QPtrListIterator<Kopete::Contact> cit( contacts ); + for( ; cit.current(); ++cit ) + { + if( cit.current()->isOnline() && cit.current()->protocol()->pluginId() == protocolId ) + result.append( cit.current() ); + } + } + } + return result; +} + +QStringList Kopete::ContactList::fileTransferContacts() const +{ + QStringList contacts; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + if ( it.current()->canAcceptFiles() ) + contacts.append( it.current()->displayName() ); + } + return contacts; +} + +void Kopete::ContactList::sendFile( const QString &displayName, const KURL &sourceURL, + const QString &altFileName, const long unsigned int fileSize) +{ +// kdDebug(14010) << "Send To Display Name: " << displayName << "\n"; + + Kopete::MetaContact *c = findMetaContactByDisplayName( displayName ); + if( c ) + c->sendFile( sourceURL, altFileName, fileSize ); +} + +void Kopete::ContactList::messageContact( const QString &contactId, const QString &messageText ) +{ + Kopete::MetaContact *mc = findMetaContactByContactId( contactId ); + if (!mc) return; + + Kopete::Contact *c = mc->execute(); //We need to know which contact was chosen as the preferred in order to message it + if (!c) return; + + Kopete::Message msg(c->account()->myself(), c, messageText, Kopete::Message::Outbound); + c->manager(Contact::CanCreate)->sendMessage(msg); + +} + + +QStringList Kopete::ContactList::contactFileProtocols(const QString &displayName) +{ +// kdDebug(14010) << "Get contacts for: " << displayName << "\n"; + QStringList protocols; + + Kopete::MetaContact *c = findMetaContactByDisplayName( displayName ); + if( c ) + { + QPtrList<Kopete::Contact> mContacts = c->contacts(); + kdDebug(14010) << mContacts.count() << endl; + QPtrListIterator<Kopete::Contact> jt( mContacts ); + for ( ; jt.current(); ++jt ) + { + kdDebug(14010) << "1" << jt.current()->protocol()->pluginId() << endl; + if( jt.current()->canAcceptFiles() ) { + kdDebug(14010) << jt.current()->protocol()->pluginId() << endl; + protocols.append ( jt.current()->protocol()->pluginId() ); + } + } + return protocols; + } + return QStringList(); +} + + +void ContactList::slotSaveLater() +{ + // if we already have a save scheduled, it will be cancelled. either way, + // start a timer to save the contact list a bit later. + d->saveTimer->start( 17100 /* 17,1 seconds */, true /* single-shot */ ); +} + +void ContactList::slotKABCChanged() +{ + // TODO: react to changes in KABC, replacing this function, post 3.4 (Will) + // call syncWithKABC on each metacontact to check if its associated kabc entry has changed. +/* for ( MetaContact * mc = d->contacts.first(); mc; mc = d->contacts.next() ) + + mc->syncWithKABC();*/ +} + + +} //END namespace Kopete + +#include "kopetecontactlist.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetecontactlist.h b/kopete/libkopete/kopetecontactlist.h new file mode 100644 index 00000000..fc6dd5f9 --- /dev/null +++ b/kopete/libkopete/kopetecontactlist.h @@ -0,0 +1,405 @@ +/* + kopetecontactlist.h - Kopete's Contact List backend + + Copyright (c) 2002 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart @ kde.org> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETECONTACTLIST_H__ +#define KOPETECONTACTLIST_H__ + +#include <qobject.h> +#include <qptrlist.h> + +#include "kopete_export.h" + +class KURL; +class QDomDocument; + + +namespace Kopete +{ + +class MetaContact; +class Group; +class Contact; + + +/** + * @brief manage contacts and metacontact + * + * The contactList is a singleton you can uses with @ref ContactList::self() + * + * it let you get a list of metacontact with metaContacts() + * Only metacontact which are on the contactlist are returned. + * + * @author Martijn Klingens <klingens@kde.org> + * @author Olivier Goffart <ogoffart@tiscalinet.be> + */ +class KOPETE_EXPORT ContactList : public QObject +{ + Q_OBJECT + +public: + /** + * The contact list is a singleton object. Use this method to retrieve + * the instance. + */ + static ContactList *self(); + ~ContactList(); + + /** + * @brief return a list of all metacontact of the contactlist + * Retrieve the list of all available meta contacts. + * The returned QPtrList is not the internally used variable, so changes + * to it won't propagate into the actual contact list. This can be + * useful if you need a subset of the contact list, because you can + * simply filter the result set as you wish without worrying about + * side effects. + * The contained MetaContacts are obviously _not_ duplicates, so + * changing those *will* have the expected result :-) + */ + QPtrList<MetaContact> metaContacts() const; + + /** + * @return all groups + */ + QPtrList<Group> groups() const; + + /** + * Return the metacontact referenced by the given id. is none is found, return 0L + * @sa MetaContact::metaContactId() + */ + MetaContact *metaContact( const QString &metaContactId ) const; + + /** + * return the group with the given unique id. if none is found return 0L + */ + Group * group(unsigned int groupId) const; + + + /** + * @brief find a contact in the contactlist. + * Browse in each metacontact of the list to find the contact with the given ID. + * @param protocolId the @ref Plugin::pluginId() of the protocol ("MSNProtocol") + * @param accountId the @ref Account::accountId() + * @param contactId the @ref Contact::contactId() + * @return the contact with the parameters, or 0L if not found. + */ + Contact *findContact( const QString &protocolId, const QString &accountId, const QString &contactId ) const; + + /** + * Find a contact by display name. Returns the first match. + */ + MetaContact *findMetaContactByDisplayName( const QString &displayName ) const; + + /** + * Find a meta contact by its contact id. Returns the first match. + */ + MetaContact *findMetaContactByContactId( const QString &contactId ) const; + + /** + * @brief find a group with his displayName + * If a group already exists with the given name and the given type, the existing group will be returned. + * Otherwise, a new group will be created. + * @param displayName is the display name to search + * @param type is the Group::GroupType to search, the default value is group::Normal + * @return always a valid Group + */ + Group * findGroup( const QString &displayName, int type = 0/*Group::Normal*/ ); + + /** + * return the list of metacontact actually selected in the contactlist UI + */ + QPtrList<MetaContact> selectedMetaContacts() const; + + /** + * return the list of groups actualy selected in the contactlist UI + */ + QPtrList<Group> selectedGroups() const ; + + /** + * return the metacontact that represent the user itself. + * This metacontact should be the parent of every Kopete::Account::myself() contacts. + * + * This metacontact is not in the contactlist. + */ + MetaContact* myself(); + + +public slots: + + /** + * Add the metacontact into the contact list + * When calling this method, the contact has to be already placed in the correct group. + * If the contact is not in a group, it will be added to the top-level group. + * It is also better if the MetaContact could also be completely created, i.e: all contacts already in it + */ + void addMetaContact( Kopete::MetaContact *c ); + + /** + * Remove a metacontact from the contactlist. + * This method delete itself the metacontact. + */ + void removeMetaContact( Kopete::MetaContact *contact ); + + /** + * Add a group + * each group must be added on the list after his creation. + */ + void addGroup(Kopete::Group *); + + /** + * Remove a group + * this method delete the group + */ + void removeGroup(Kopete::Group *); + + /** + * Set which items are selected in the ContactList GUI. + * This method has to be called by the contactlist UI side. + * it stores the selected items, and emits signals + */ + void setSelectedItems(QPtrList<MetaContact> metaContacts , QPtrList<Group> groups); + + /** + * Apply the global identity. + */ + void loadGlobalIdentity(); + +signals: + /** + * A meta contact was added to the contact list. Interested classes + * ( like the listview widgets ) can connect to this signal to receive + * the newly added contacts. + */ + void metaContactAdded( Kopete::MetaContact *mc ); + + /** + * A metacontact has just been removed. and will be soon deleted + */ + void metaContactRemoved( Kopete::MetaContact *mc ); + + /** + * A group has just been added + */ + void groupAdded( Kopete::Group * ); + + /** + * A group has just been removed + */ + void groupRemoved( Kopete::Group * ); + + /** + * A group has just been renamed + */ + void groupRenamed(Kopete::Group *, const QString & oldname); + + /** + * A contact has been added to a group + */ + void metaContactAddedToGroup( Kopete::MetaContact *mc, Kopete::Group *to ); + /** + * A contact has been removed from a group + */ + void metaContactRemovedFromGroup( Kopete::MetaContact *mc, Kopete::Group *from ); + + /** + * This signal is emit when the selection has changed, it is emitted after the following slot + * Warning: Do not delete any contacts in slots connected to this signal. (it is the warning in the QListView::selectionChanged() doc) + */ + void selectionChanged(); + /** + * This signal is emitted each time the selection has changed. the bool is set to true if only one meta contact has been selected, + * and set to false if none, or several contacts are selected + * you can connect this signal to KAction::setEnabled if you have an action which is applied to only one contact + */ + void metaContactSelected(bool); + + /** + * This signal is emitted each time a global identity field change. + * HOWTO use: + * + * - Connect signal globalIdentityChanged(const QString &key, const QVariant + * &value) to a slot in your derivate Account class (the best + * place to put it). + * - In the slot: + * - Check the key you want to be sync with global identity. + * - Update the myself contact and/or update on server. + * + * For now, when photo is changed, it always send the photo file path. + * + * Connect signal in your Account constructor: + * @code + * connect(Kopete::ContactList::self(), SIGNAL(globalIdentityChanged(const QString&, const QVariant&)), SLOT(slotglobalIdentityChanged(const QString&, const QVariant&))); + * @endcode + * + * Example of a typical implemented slot: + * @code + * void slotGlobalIdentityChanged(const QString &key, const QVariant &value) + * { + * if(key == Kopete::Global::Properties::self()->nickName().key()) + * { + * myself()->setProperty(protocol()->propNickname, value.toString()); + * this->slotUpdateUserInfo(); + * } + * else if(key == Kopete::Global::Properties::self()->photo().key()) + * { + * myself()->setProperty(protocol()->propPhotoUrl, value.toString()); + * this->slotUpdateDisplayPicture(); + * } + * } + * @endcode + */ + void globalIdentityChanged( const QString &key, const QVariant &value ); + +private slots: + /** + * Called when the contact list changes. Flags the list dirty and schedules a save for a little while later. + */ + void slotSaveLater(); + /** + * Called on contactlist load or when KABC has changed, to check if we need to update our contactlist from there. + */ + void slotKABCChanged(); + + /** + * Called when the myself displayName changed. + */ + void slotDisplayNameChanged(); + + /** + * Called when the myself photo changed. + */ + void slotPhotoChanged(); + +private: + + /** + * Convert the contact list from an older version + */ + void convertContactList( const QString &fileName, uint fromVersion, uint toVersion ); + + + /** + * Private constructor: we are a singleton + */ + ContactList(); + + static ContactList *s_self; + class Private; + Private *d; + +public: //TODO I think all theses method should be moved to the decop interface. + /** + * Return all meta contacts + */ + QStringList contacts() const; + + /** + * Return all meta contacts that are reachable + */ + QStringList reachableContacts() const; + + /** + * Return all contacts that are online + */ + QPtrList<Contact> onlineContacts() const; + + /** + * Overloaded method of @ref onlineContacts() that only returns + * the online contacts for a single protocol + */ + QPtrList<Contact> onlineContacts( const QString &protocolId ) const; + + /** + * Return all meta contacts that are online + */ + QPtrList<MetaContact> onlineMetaContacts() const; + + /** + * Overloaded method of @ref onlineMetaContacts() that only returns + * the online meta contacts for a single protocol + */ + QPtrList<MetaContact> onlineMetaContacts( const QString &protocolId ) const; + + /** + * Returns all contacts which can accept file transfers + */ + QStringList fileTransferContacts() const; + + QStringList contactFileProtocols( const QString &displayName); + + /** + * Return all meta contacts with their current status + * + * FIXME: Do we *need* this one? Sounds error prone to me, because + * nicknames can contain parentheses too. - Martijn + */ + QStringList contactStatuses() const; + + + /** + * Exposed via DCOP in kopeteiface + * Used to send a file to a MetaContact using the highest ranked protocol + * + * FIXME: We need to change this to use a unique ID instead of the displayName + * + * @param displayName Metacontact to send file to + * @param sourceURL The file we are sending + * @param altFileName (Optional) An alternate filename for the file we are sending + * @param fileSize (Optional) The size of the file + */ + void sendFile(const QString &displayName, const KURL &sourceURL, + const QString &altFileName = QString::null, const long unsigned int fileSize = 0L); + + /** + * Open a chat to a contact, and optionally set some initial text + */ + void messageContact( const QString &displayName, const QString &messageText = QString::null ); + +public slots: + /** + * @internal + * Load the contact list + * + * FIXME: Use a better way, without exposing the XML backend, though. + */ + void load(); + + void save(); + +private: + /** + * Return a XML representation of the contact list + */ + const QDomDocument toXML(); + + /** + * Load the contact list from XML file + */ + void loadXML(); + + /** + * Save the contact list to XML file + */ + void saveXML(); +}; + +} //END namespace Kopete + + +#endif + + diff --git a/kopete/libkopete/kopetecontactlistelement.cpp b/kopete/libkopete/kopetecontactlistelement.cpp new file mode 100644 index 00000000..2474d1af --- /dev/null +++ b/kopete/libkopete/kopetecontactlistelement.cpp @@ -0,0 +1,261 @@ +/* + kopeteplugindataobject.cpp - Kopete Plugin Data Object + + Copyright (c) 2003-2004 by Olivier Goffart <ogoffart @tiscalinet.be> + Copyright (c) 2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetecontactlistelement.h" + +#include <kconfig.h> +#include <kdebug.h> +#include <kglobal.h> + +#include "kopeteplugin.h" + +namespace Kopete { + +class ContactListElement::Private +{ +public: + QMap<QString, QMap<QString, QString> > pluginData; + QMap<ContactListElement::IconState, QString> icons; + bool useCustomIcon; +}; + +ContactListElement::ContactListElement( QObject *parent, const char *name ) +: QObject( parent, name ) +{ + d = new Private; + + d->useCustomIcon = false; +#if 0 //TODO + connect( Kopete::Global::onlineStatusIconCache(), SIGNAL( iconsChanged() ), SIGNAL( iconAppearanceChanged() ) ); +#endif +} + +ContactListElement::~ContactListElement() +{ + delete d; +} + +void ContactListElement::setPluginData( Plugin *plugin, const QMap<QString, QString> &pluginData ) +{ + if ( pluginData.isEmpty() ) + { + d->pluginData.remove( plugin->pluginId() ); + return; + } + + d->pluginData[ plugin->pluginId() ] = pluginData; + + emit pluginDataChanged(); +} + +void ContactListElement::setPluginData( Plugin *p, const QString &key, const QString &value ) +{ + d->pluginData[ p->pluginId() ][ key ] = value; + + emit pluginDataChanged(); +} + +QMap<QString, QString> ContactListElement::pluginData( Plugin *plugin ) const +{ + if ( !d->pluginData.contains( plugin->pluginId() ) ) + return QMap<QString, QString>(); + + return d->pluginData[ plugin->pluginId() ]; +} + +QString ContactListElement::pluginData( Plugin *plugin, const QString &key ) const +{ + if ( !d->pluginData.contains( plugin->pluginId() ) || !d->pluginData[ plugin->pluginId() ].contains( key ) ) + return QString::null; + + return d->pluginData[ plugin->pluginId() ][ key ]; +} + +const QValueList<QDomElement> ContactListElement::toXML() +{ + QDomDocument pluginData; + QValueList<QDomElement> pluginNodes; + pluginData.appendChild( pluginData.createElement( QString::fromLatin1( "plugin-data" ) ) ); + + if ( !d->pluginData.isEmpty() ) + { + QMap<QString, QMap<QString, QString> >::ConstIterator pluginIt; + for ( pluginIt = d->pluginData.begin(); pluginIt != d->pluginData.end(); ++pluginIt ) + { + QDomElement pluginElement = pluginData.createElement( QString::fromLatin1( "plugin-data" ) ); + pluginElement.setAttribute( QString::fromLatin1( "plugin-id" ), pluginIt.key() ); + + QMap<QString, QString>::ConstIterator it; + for ( it = pluginIt.data().begin(); it != pluginIt.data().end(); ++it ) + { + QDomElement pluginDataField = pluginData.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginDataField.setAttribute( QString::fromLatin1( "key" ), it.key() ); + pluginDataField.appendChild( pluginData.createTextNode( it.data() ) ); + pluginElement.appendChild( pluginDataField ); + } + + pluginData.documentElement().appendChild( pluginElement ); + pluginNodes.append( pluginElement ); + } + } + if ( !d->icons.isEmpty() ) + { + QDomElement iconsElement = pluginData.createElement( QString::fromLatin1( "custom-icons" ) ); + iconsElement.setAttribute( QString::fromLatin1( "use" ), d->useCustomIcon ? QString::fromLatin1( "1" ) : QString::fromLatin1( "0" ) ); + + for ( QMap<IconState, QString >::ConstIterator it = d->icons.begin(); it != d->icons.end(); ++it ) + { + QDomElement iconElement = pluginData.createElement( QString::fromLatin1( "icon" ) ); + QString stateStr; + switch ( it.key() ) + { + case Open: + stateStr = QString::fromLatin1( "open" ); + break; + case Closed: + stateStr = QString::fromLatin1( "closed" ); + break; + case Online: + stateStr = QString::fromLatin1( "online" ); + break; + case Away: + stateStr = QString::fromLatin1( "away" ); + break; + case Offline: + stateStr = QString::fromLatin1( "offline" ); + break; + case Unknown: + stateStr = QString::fromLatin1( "unknown" ); + break; + case None: + default: + stateStr = QString::fromLatin1( "none" ); + break; + } + iconElement.setAttribute( QString::fromLatin1( "state" ), stateStr ); + iconElement.appendChild( pluginData.createTextNode( it.data() ) ); + iconsElement.appendChild( iconElement ); + } + pluginData.documentElement().appendChild( iconsElement ); + pluginNodes.append( iconsElement ); + } + return pluginNodes; +} + +bool ContactListElement::fromXML( const QDomElement& element ) +{ + if ( element.tagName() == QString::fromLatin1( "plugin-data" ) ) + { + QMap<QString, QString> pluginData; + QString pluginId = element.attribute( QString::fromLatin1( "plugin-id" ), QString::null ); + + //in kopete 0.6 the AIM protocol was called OSCAR + if ( pluginId == QString::fromLatin1( "OscarProtocol" ) ) + pluginId = QString::fromLatin1( "AIMProtocol" ); + + QDomNode field = element.firstChild(); + while( !field.isNull() ) + { + QDomElement fieldElement = field.toElement(); + if ( fieldElement.tagName() == QString::fromLatin1( "plugin-data-field" ) ) + { + pluginData.insert( fieldElement.attribute( QString::fromLatin1( "key" ), + QString::fromLatin1( "undefined-key" ) ), fieldElement.text() ); + } + field = field.nextSibling(); + } + d->pluginData.insert( pluginId, pluginData ); + } + else if ( element.tagName() == QString::fromLatin1( "custom-icons" ) ) + { + d->useCustomIcon= element.attribute( QString::fromLatin1( "use" ), QString::fromLatin1( "1" ) ) == QString::fromLatin1( "1" ); + QDomNode ic = element.firstChild(); + while( !ic.isNull() ) + { + QDomElement iconElement = ic.toElement(); + if ( iconElement.tagName() == QString::fromLatin1( "icon" ) ) + { + QString stateStr = iconElement.attribute( QString::fromLatin1( "state" ), QString::null ); + QString icon = iconElement.text(); + IconState state = None; + + if ( stateStr == QString::fromLatin1( "open" ) ) + state = Open; + if ( stateStr == QString::fromLatin1( "closed" ) ) + state = Closed; + if ( stateStr == QString::fromLatin1( "online" ) ) + state = Online; + if ( stateStr == QString::fromLatin1( "offline" ) ) + state = Offline; + if ( stateStr == QString::fromLatin1( "away" ) ) + state = Away; + if ( stateStr == QString::fromLatin1( "unknown" ) ) + state = Unknown; + + d->icons[ state ] = icon; + } + ic = ic.nextSibling(); + } + } + else + { + return false; + } + + return true; +} + +QString ContactListElement::icon( ContactListElement::IconState state ) const +{ + if ( d->icons.contains( state ) ) + return d->icons[state]; + + return d->icons[ None ]; +} + +void ContactListElement::setIcon( const QString& icon , ContactListElement::IconState state ) +{ + if ( icon.isNull() ) + d->icons.remove( state ); + else + d->icons[ state ] = icon; + + emit iconChanged( state, icon ); + emit iconAppearanceChanged(); +} + +bool ContactListElement::useCustomIcon() const +{ + return d->useCustomIcon; +} + +void ContactListElement::setUseCustomIcon( bool useCustomIcon ) +{ + if ( d->useCustomIcon != useCustomIcon ) + { + d->useCustomIcon = useCustomIcon; + emit useCustomIconChanged( useCustomIcon ); + } +} + +} //END namespace Kopete + +#include "kopetecontactlistelement.moc" + + + diff --git a/kopete/libkopete/kopetecontactlistelement.h b/kopete/libkopete/kopetecontactlistelement.h new file mode 100644 index 00000000..b0f2eb69 --- /dev/null +++ b/kopete/libkopete/kopetecontactlistelement.h @@ -0,0 +1,172 @@ +/* + kopeteplugindataobject.h - Kopete Plugin Data Object + + Copyright (c) 2003-2004 by Olivier Goffart <ogoffart@ tiscalinet.be> + Copyright (c) 2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPLUGINDATAOBJECT_H +#define KOPETEPLUGINDATAOBJECT_H + +#include <qobject.h> +#include <qdom.h> + +#include "kopete_export.h" + +namespace Kopete { + +class Plugin; + + +/** + * @author Olivier Goffart <ogoffart@ tiscalinet.be> + * + * This is the base class for base elements of the contactlist. + * His purpose is to share the code between @ref Group and @ref MetaContact + * + * It handle the saving and loading of plugin data from the contactlist. + * Plugins may set custom datas to metaocntacts or groups by calling @ref setPluginData + * and may retreive them with @ref pluginData + * + * It also allow to store an icon for this element. + */ +class KOPETE_EXPORT ContactListElement : public QObject /* public KopeteNotifyDataObject */ +{ + Q_OBJECT + +protected: + ContactListElement( QObject *parent = 0L, const char *name = 0L ); + ~ContactListElement(); + + +public: + + /** + * Set the plugin-specific data. + * The data in the provided QMap is a set of key/value pairs. + * Note that protocol plugins usually shouldn't use this method, but + * reimplement @ref Contact::serialize() instead. This method + * is called by @ref Protocol for those classes. + * + * WARNING: This erases all old data stored for this object! + * You may want to consider the @ref setPluginData() overload + * that takes a single field as parameter. + */ + void setPluginData( Plugin *plugin, const QMap<QString, QString> &value ); + + /** + * Convenience method to store or change only a single field of the + * plugin data. As with the other @ref setPluginData() method, protocols + * are advised not to use this method and reimplement + * @ref Contact::serialize() instead. + * + * Note that you should save the file after adding data or it will get lost. + */ + void setPluginData( Plugin *plugin, const QString &key, const QString &value ); + + /** + * Get the settings as stored previously by calls to @ref setPluginData() + * + * Note that calling this method for protocol plugins that use the + * @ref Contact::serialize() API may yield unexpected results. + */ + QMap<QString, QString> pluginData( Plugin *plugin ) const; + + /** + * Convenience method to retrieve only a single field from the plugin + * data. See @ref setPluginData(). + * + * Note that plugin data is accessible only after it has been loaded + * from the XML file. Don't call this method before then (e.g. in + * constructors). + */ + QString pluginData( Plugin *plugin, const QString &key ) const; + + /** + * The various icon states. Some state are reserved for Groups, + * other for metacontact. + * 'None' is the default icon. + */ + enum IconState { None, Open, Closed, Online, Away, Offline, Unknown }; + + /** + * return the icon for this object, in the given state. + * if there is no icon registered for this state, the None icon is used + * if available + */ + QString icon( IconState state = None ) const; + + /** + * Set the icon in the given state + * To clear an entry, set a QString::null + */ + void setIcon( const QString &icon, IconState = None ); + + /** + * return if yes or no the user wants to display some custom icon. + * you can use @ref icon() to know the icons to uses + */ + bool useCustomIcon() const; + + /** + * set if the user want to show custom icon he set with @ref setIcon + * this does not clear icons string if you set false + */ + void setUseCustomIcon( bool useCustomIcon ); + +signals: + /** + * The plugin data was changed (by a plugin) + */ + void pluginDataChanged(); + + /** + * The icon to use for some state has changed + */ + void iconChanged( Kopete::ContactListElement::IconState, const QString & ); + + /** + * The visual appearance of some of our icons has changed + */ + void iconAppearanceChanged(); + + /** + * The useCustomIcon property has changed + */ + void useCustomIconChanged( bool useCustomIcon ); + +protected: + /** + * Return a XML representation of plugin data + */ + const QValueList<QDomElement> toXML(); + + /** + * Load plugin data from one Dom Element: + * It should be a <plugin-data> element or a <custom-icons> element. if not, nothing will happen + * @return true if something has ben loaded. false if the element was not a fine + */ + bool fromXML( const QDomElement &element ); + +private: + class Private; + Private *d; +}; + +} //END namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetecontactproperty.cpp b/kopete/libkopete/kopetecontactproperty.cpp new file mode 100644 index 00000000..87e176af --- /dev/null +++ b/kopete/libkopete/kopetecontactproperty.cpp @@ -0,0 +1,204 @@ +/* + kopetecontactproperty.cpp + + Kopete::Contact Property class + + Copyright (c) 2004 by Stefan Gehn <metz AT gehn.net> + Kopete (c) 2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetecontactproperty.h" +#include <kdebug.h> +#include "kopeteglobal.h" + +namespace Kopete +{ + +struct ContactPropertyTmplPrivate +{ + QString key; + QString label; + QString icon; + bool persistent; + bool richText; + bool privateProp; + unsigned int refCount; +}; + +ContactPropertyTmpl ContactPropertyTmpl::null; + + +ContactPropertyTmpl::ContactPropertyTmpl() +{ + d = new ContactPropertyTmplPrivate; + d->refCount = 1; + d->persistent = false; + // Don't register empty template +} + +ContactPropertyTmpl::ContactPropertyTmpl(const QString &key, + const QString &label, const QString &icon, bool persistent, bool richText, bool privateProp) +{ + ContactPropertyTmpl other = Kopete::Global::Properties::self()->tmpl(key); + if(other.isNull()) + { +// kdDebug(14000) << k_funcinfo << "Creating new template for key = '" << key << "'" << endl; + + d = new ContactPropertyTmplPrivate; + d->refCount = 1; + d->key = key; + d->label = label; + d->icon = icon; + d->persistent = persistent; + d->richText = richText; + d->privateProp = privateProp; + Kopete::Global::Properties::self()->registerTemplate(key, (*this)); + } + else + { +// kdDebug(14000) << k_funcinfo << "Using existing template for key = '" << key << "'" << endl; + d = other.d; + d->refCount++; + } +} + +ContactPropertyTmpl::ContactPropertyTmpl(const ContactPropertyTmpl &other) +{ + d = other.d; + d->refCount++; +} + +ContactPropertyTmpl &ContactPropertyTmpl::operator=( + const ContactPropertyTmpl &other) +{ + d->refCount--; + if(d->refCount == 0) + { + if (!d->key.isEmpty()) // null property + Kopete::Global::Properties::self()->unregisterTemplate(d->key); + delete d; + } + + d = other.d; + d->refCount++; + + return *this; +} + +ContactPropertyTmpl::~ContactPropertyTmpl() +{ + d->refCount--; + if(d->refCount == 0) + { + if (!d->key.isEmpty()) // null property + Kopete::Global::Properties::self()->unregisterTemplate(d->key); + delete d; + } +} + +bool ContactPropertyTmpl::operator==(const ContactPropertyTmpl &other) const +{ + return (d && other.d && + d->key == other.d->key && + d->label == other.d->label && + d->icon == other.d->key && + d->persistent == other.d->persistent); +} + +bool ContactPropertyTmpl::operator!=(const ContactPropertyTmpl &other) const +{ + return (!d || !other.d || + d->key != other.d->key || + d->label != other.d->label || + d->icon != other.d->key || + d->persistent != other.d->persistent); +} + + +const QString &ContactPropertyTmpl::key() const +{ + return d->key; +} + +const QString &ContactPropertyTmpl::label() const +{ + return d->label; +} + +const QString &ContactPropertyTmpl::icon() const +{ + return d->icon; +} + +bool ContactPropertyTmpl::persistent() const +{ + return d->persistent; +} + +bool ContactPropertyTmpl::isRichText() const +{ + return d->richText; +} + +bool ContactPropertyTmpl::isPrivate() const +{ + return d->privateProp; +} + +bool ContactPropertyTmpl::isNull() const +{ + return (!d || d->key.isNull()); +} + + +// ----------------------------------------------------------------------------- + + +ContactProperty ContactProperty::null; + +ContactProperty::ContactProperty() +{ +} + +ContactProperty::ContactProperty(const ContactPropertyTmpl &tmpl, + const QVariant &val) +{ + mTemplate = tmpl; + mValue = val; +} + +ContactProperty::~ContactProperty() +{ + //kdDebug(14000) << k_funcinfo << "this = " << (void *)this << endl; +} + +const QVariant &ContactProperty::value() const +{ + return mValue; +} + +const ContactPropertyTmpl &ContactProperty::tmpl() const +{ + return mTemplate; +} + +bool ContactProperty::isNull() const +{ + return mValue.isNull(); +} + +bool ContactProperty::isRichText() const +{ + return mTemplate.isRichText(); +} + +} // END namespace Kopete diff --git a/kopete/libkopete/kopetecontactproperty.h b/kopete/libkopete/kopetecontactproperty.h new file mode 100644 index 00000000..b5c8f060 --- /dev/null +++ b/kopete/libkopete/kopetecontactproperty.h @@ -0,0 +1,195 @@ +/* + kopetecontactproperty.h + + Kopete::Contact Property class + + Copyright (c) 2004 by Stefan Gehn <metz AT gehn.net> + Kopete (c) 2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETECONTACTPROPERTY_H_ +#define _KOPETECONTACTPROPERTY_H_ + +#include <qvariant.h> + +#include "kopete_export.h" + +namespace Kopete +{ + +struct ContactPropertyTmplPrivate; + +/** + * @author Stefan Gehn <metz AT gehn.net> + * + * The template class for registering properties in Kopete + * You need to use this if you want to set properties for a + * Kopete::Contact + **/ +class KOPETE_EXPORT ContactPropertyTmpl +{ + public: + /** + * Constructor only used for empty ContactPropertyTmpl objects + * + * Note: Only useful for the null object + **/ + ContactPropertyTmpl(); + + /** + * Constructor + * @param key internal unique key for this template + * @param label a label to show for properties based on this template + * @param icon name of the icon to show for properties based on this template + * @param persistent if true, properties based on this template will be + * saved to the contactlist. + * @param richText Indicate that this property should be able to handle rich text + * @param privateProp if true, properties based on this template won't be + * visible to the user + **/ + ContactPropertyTmpl( const QString &key, + const QString &label, + const QString &icon = QString::null, + bool persistent = false, + bool richText = false, + bool privateProp = false ); + + /** + * Copy constructor + **/ + ContactPropertyTmpl(const ContactPropertyTmpl &other); + + /** Destructor */ + ~ContactPropertyTmpl(); + + ContactPropertyTmpl &operator=(const ContactPropertyTmpl &other); + + bool operator==(const ContactPropertyTmpl &other) const; + bool operator!=(const ContactPropertyTmpl &other) const; + + /** + * Getter for the unique key. Properties based on this template will be + * stored with this key + **/ + const QString &key() const; + + /** + * Getter for i18ned label + **/ + const QString &label() const; + + /** + * Getter for icon to show aside or instead of @p label() + **/ + const QString &icon() const; + + /** + * Returns true if properties based on this template should + * be saved across Kopete sessions, false otherwise. + **/ + bool persistent() const; + + /** + * Returns true if properties based on this template are HTML formatted + **/ + bool isRichText() const; + + /** + * Returns true if properties based on this template are invisible to the user + **/ + bool isPrivate() const; + + /** + * An empty template, check for it using isNull() + */ + static ContactPropertyTmpl null; + + /** + * Returns true if this object is an empty template + **/ + bool isNull() const; + + /** + * A Map of QString and ContactPropertyTmpl objects, based on QMap + **/ + typedef QMap<QString, ContactPropertyTmpl> Map; + + private: + ContactPropertyTmplPrivate *d; +}; + + +/** + * @author Stefan Gehn <metz AT gehn.net> + * + * A data container for whatever information Kopete or any of its + * plugins want to store for a Kopete::Contact + **/ +class KOPETE_EXPORT ContactProperty +{ + // TODO: Add d-pointer ! + public: + /** + * Constructor only used for empty ContactProperty objects + * + * Note: you cannot set a label or value later on! + **/ + ContactProperty(); + + /** + * @param tmpl The contact property template this property is based on + * @param value The value this Property holds + **/ + ContactProperty(const ContactPropertyTmpl &tmpl, const QVariant &value); + + /** Destructor **/ + ~ContactProperty(); + + /** + * Getter for this properties template + **/ + const ContactPropertyTmpl &tmpl() const; + + /** + * Getter for this properties value + **/ + const QVariant &value() const; + + /** + * The null, i.e. empty, ContactProperty + */ + static ContactProperty null; + + /** + * Returns true if this object is an empty Property (i.e. it holds no + * value), false otherwise. + **/ + bool isNull() const; + + /** + * Returns true if this property is HTML formatted + **/ + bool isRichText() const; + + /** + * A map of key,ContactProperty items + **/ + typedef QMap<QString, ContactProperty> Map; + + private: + QVariant mValue; + ContactPropertyTmpl mTemplate; +}; + +} // END namespace Kopete + +#endif //_KOPETECONTACTPROPERTY_H_ diff --git a/kopete/libkopete/kopeteeventpresentation.cpp b/kopete/libkopete/kopeteeventpresentation.cpp new file mode 100644 index 00000000..f90a19e5 --- /dev/null +++ b/kopete/libkopete/kopeteeventpresentation.cpp @@ -0,0 +1,90 @@ +/* + kopeteeventpresentation.cpp - Kopete Custom Notify Data Object + + Copyright (c) 2004 by Will Stephenson <lists@stevello.free-online.co.uk> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteeventpresentation.h" + +Kopete::EventPresentation::EventPresentation( const PresentationType type ) +{ + m_type = type; +} + +Kopete::EventPresentation::EventPresentation( const PresentationType type, + const QString &content, const bool singleShot, const bool enabled ) +{ + m_type = type; + m_content = content; + m_singleShot = singleShot; + m_enabled = enabled; +} + +Kopete::EventPresentation::~EventPresentation() +{ +} + +Kopete::EventPresentation::PresentationType Kopete::EventPresentation::type() +{ + return m_type; +} + +QString Kopete::EventPresentation::content() +{ + return m_content; +} + +bool Kopete::EventPresentation::enabled() +{ + return m_enabled; +} + +bool Kopete::EventPresentation::singleShot() +{ + return m_singleShot; +} + +void Kopete::EventPresentation::setContent( const QString &content ) +{ + m_content = content; +} + +void Kopete::EventPresentation::setEnabled( const bool enabled ) +{ + m_enabled = enabled; +} + +void Kopete::EventPresentation::setSingleShot( const bool singleShot ) +{ + m_singleShot = singleShot; +} + +QString Kopete::EventPresentation::toString() +{ + QString type; + switch ( m_type ) + { + case Sound: + type= QString::fromLatin1("sound"); + break; + case Message: + type= QString::fromLatin1("message"); + break; + case Chat: + type= QString::fromLatin1("chat"); + break; + } + QString stringRep = QString::fromLatin1( "Presentation; type=%1; content=%2; enabled=%3; single shot=%4\n" ).arg(type).arg(m_content).arg(m_enabled).arg(m_singleShot); + return stringRep; +} diff --git a/kopete/libkopete/kopeteeventpresentation.h b/kopete/libkopete/kopeteeventpresentation.h new file mode 100644 index 00000000..ea30cb5d --- /dev/null +++ b/kopete/libkopete/kopeteeventpresentation.h @@ -0,0 +1,56 @@ +/* + kopeteeventpresentation.h - Kopete Custom Notify Data Object + + Copyright (c) 2004 by Will Stephenson <lists@stevello.free-online.co.uk> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEEVENTPRESENTATION_H +#define KOPETEEVENTPRESENTATION_H + +#include <qstring.h> + +#include "kopete_export.h" + +namespace Kopete +{ + +class KOPETE_EXPORT EventPresentation +{ +public: + enum PresentationType { Sound, Message, Chat }; + EventPresentation( const PresentationType type ); + EventPresentation( const PresentationType type, + const QString &content = QString::null, + const bool singleShot = false, const bool enabled = false ); + ~EventPresentation(); + + PresentationType type(); + QString content(); + bool enabled(); + bool singleShot(); + + void setContent( const QString &content ); + void setEnabled( const bool enabled ); + void setSingleShot( const bool singleShot ); + QString toString(); +private: + PresentationType m_type; + QString m_content; + bool m_enabled; + bool m_singleShot; +}; + +} + +#endif diff --git a/kopete/libkopete/kopeteglobal.cpp b/kopete/libkopete/kopeteglobal.cpp new file mode 100644 index 00000000..a11dafdd --- /dev/null +++ b/kopete/libkopete/kopeteglobal.cpp @@ -0,0 +1,346 @@ +/* + kopeteglobal.cpp - Kopete Globals + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteglobal.h" +#include "kopeteuiglobal.h" + +#include <kapplication.h> +#include <kdebug.h> +#include <klocale.h> +#include <kio/netaccess.h> +#include <kmimetype.h> +#include <kmessagebox.h> +#include <kprogress.h> +#include <kstandarddirs.h> +#include <ktar.h> +#include <kzip.h> +#include <kmimetype.h> + + +namespace Kopete +{ + +namespace Global +{ + +class PropertiesPrivate +{ + public: + ContactPropertyTmpl::Map mTemplates; +}; + +Properties *Properties::mSelf = 0L; + +Properties *Properties::self() +{ + if(!mSelf) + { + //kdDebug(14000) << k_funcinfo << endl; + mSelf = new Properties(); + } + return mSelf; +} + +Properties::Properties() +{ + kdDebug(14000) << k_funcinfo << endl; + d = new PropertiesPrivate(); +} + +Properties::~Properties() +{ + kdDebug(14000) << k_funcinfo << endl; + delete d; +} + +const ContactPropertyTmpl &Properties::tmpl(const QString &key) const +{ + if(d->mTemplates.contains(key)) + { + /*kdDebug(14000) << k_funcinfo << + "Found template for key = '" << key << "'" << endl;*/ + return d->mTemplates[key]; + } + else + return ContactPropertyTmpl::null; +} + +bool Properties::registerTemplate(const QString &key, + const ContactPropertyTmpl &tmpl) +{ + if(d->mTemplates.contains(key)) + { + kdDebug(14000) << k_funcinfo << + "Called for EXISTING key = '" << key << "'" << endl; + return false; + } + else + { + d->mTemplates.insert(key, tmpl); + return true; + } +} + +void Properties::unregisterTemplate(const QString &key) +{ + kdDebug(14000) << k_funcinfo << "called for key: '" << key << "'" << endl; + d->mTemplates.remove(key); +} + +bool Properties::isRegistered(const QString &key) +{ + return d->mTemplates.contains(key); +} + +const ContactPropertyTmpl &Properties::fullName() const +{ + return createProp(QString::fromLatin1("FormattedName"), + i18n("Full Name")); +} + +const ContactPropertyTmpl &Properties::idleTime() const +{ + return createProp(QString::fromLatin1("idleTime"), + i18n("Idle Time")); +} + +const ContactPropertyTmpl &Properties::onlineSince() const +{ + return createProp(QString::fromLatin1("onlineSince"), + i18n("Online Since")); +} + +const ContactPropertyTmpl &Properties::lastSeen() const +{ + return createProp(QString::fromLatin1("lastSeen"), + i18n("Last Seen"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::awayMessage() const +{ + return createProp(QString::fromLatin1("awayMessage"), + i18n("Away Message")); +} + +const ContactPropertyTmpl &Properties::firstName() const +{ + return createProp(QString::fromLatin1("firstName"), + i18n("First Name"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::lastName() const +{ + return createProp(QString::fromLatin1("lastName"), + i18n("Last Name"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::privatePhone() const +{ + return createProp(QString::fromLatin1("privatePhoneNumber"), + i18n("Private Phone"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::privateMobilePhone() const +{ + return createProp(QString::fromLatin1("privateMobilePhoneNumber"), + i18n("Private Mobile Phone"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::workPhone() const +{ + return createProp(QString::fromLatin1("workPhoneNumber"), + i18n("Work Phone"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::workMobilePhone() const +{ + return createProp(QString::fromLatin1("workMobilePhoneNumber"), + i18n("Work Mobile Phone"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::emailAddress() const +{ + return createProp(QString::fromLatin1("emailAddress"), + i18n("Email Address"), QString::fromLatin1("mail_generic"), true); +} + +const ContactPropertyTmpl &Properties::nickName() const +{ + return createProp(QString::fromLatin1("nickName"), + i18n("Nick Name"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::photo() const +{ + return createProp(QString::fromLatin1("photo"), + i18n("Photo"), QString::null, true); +} + + +const ContactPropertyTmpl &Properties::createProp(const QString &key, + const QString &label, const QString &icon, bool persistent) const +{ + /*kdDebug(14000) << k_funcinfo << + "key = " << key << ", label = " << label << endl;*/ + + if(!d->mTemplates.contains(key)) + { +/* kdDebug(14000) << k_funcinfo << + "CREATING NEW ContactPropertyTmpl WITH key = " << key << + ", label = " << label << ", persisten = " << persistent << endl;*/ + d->mTemplates.insert(key, ContactPropertyTmpl(key, label, icon, persistent)); + } + return tmpl(key); +} + +const ContactPropertyTmpl::Map &Properties::templateMap() const +{ + return d->mTemplates; +} + + +// ----------------------------------------------------------------------------- + + +void installEmoticonTheme(const QString &archiveName) +{ + QStringList foundThemes; + KArchiveEntry *currentEntry = 0L; + KArchiveDirectory* currentDir = 0L; + KProgressDialog *progressDlg = 0L; + KArchive *archive = 0L; + + QString localThemesDir(locateLocal("emoticons", QString::null) ); + + if(localThemesDir.isEmpty()) + { + KMessageBox::queuedMessageBox(Kopete::UI::Global::mainWidget(), + KMessageBox::Error, i18n("Could not find suitable place " \ + "to install emoticon themes into.")); + return; + } + + progressDlg = new KProgressDialog(0 , "emoticonInstProgress", + i18n("Installing Emoticon Themes..."), QString::null, true); + progressDlg->progressBar()->setTotalSteps(foundThemes.count()); + progressDlg->show(); + kapp->processEvents(); + + QString currentBundleMimeType = KMimeType::findByPath(archiveName, 0, false)->name(); + if( currentBundleMimeType == QString::fromLatin1("application/x-zip") ) + archive = new KZip(archiveName); + else if( currentBundleMimeType == QString::fromLatin1("application/x-tgz") || + currentBundleMimeType == QString::fromLatin1("application/x-tbz") || + currentBundleMimeType == QString::fromLatin1("application/x-gzip") || + currentBundleMimeType == QString::fromLatin1("application/x-bzip2") ) + archive = new KTar(archiveName); + else if(archiveName.endsWith(QString::fromLatin1("jisp")) || archiveName.endsWith(QString::fromLatin1("zip")) ) + archive = new KZip(archiveName); + else + archive = new KTar(archiveName); + + if ( !archive || !archive->open(IO_ReadOnly) ) + { + KMessageBox::queuedMessageBox(Kopete::UI::Global::mainWidget(), + KMessageBox::Error, + i18n("Could not open \"%1\" for unpacking.").arg(archiveName)); + delete archive; + delete progressDlg; + return; + } + + const KArchiveDirectory* rootDir = archive->directory(); + + // iterate all the dirs looking for an emoticons.xml file + QStringList entries = rootDir->entries(); + for (QStringList::Iterator it = entries.begin(); it != entries.end(); ++it) + { + currentEntry = const_cast<KArchiveEntry*>(rootDir->entry(*it)); + if (currentEntry->isDirectory()) + { + currentDir = dynamic_cast<KArchiveDirectory*>( currentEntry ); + if (currentDir && ( currentDir->entry(QString::fromLatin1("emoticons.xml")) != NULL || + currentDir->entry(QString::fromLatin1("icondef.xml")) != NULL ) ) + foundThemes.append(currentDir->name()); + } + } + + if (foundThemes.isEmpty()) + { + KMessageBox::queuedMessageBox(Kopete::UI::Global::mainWidget(), + KMessageBox::Error, i18n("<qt>The file \"%1\" is not a valid" \ + " emoticon theme archive.</qt>").arg(archiveName)); + archive->close(); + delete archive; + delete progressDlg; + return; + } + + for (QStringList::ConstIterator it = foundThemes.begin(); it != foundThemes.end(); ++it) + { + progressDlg->setLabel( + i18n("<qt>Installing <strong>%1</strong> emoticon theme</qt>") + .arg(*it)); + progressDlg->resize(progressDlg->sizeHint()); + kapp->processEvents(); + + if (progressDlg->wasCancelled()) + break; + + currentEntry = const_cast<KArchiveEntry *>(rootDir->entry(*it)); + if (currentEntry == 0) + { + kdDebug(14010) << k_funcinfo << "couldn't get next archive entry" << endl; + continue; + } + + if(currentEntry->isDirectory()) + { + currentDir = dynamic_cast<KArchiveDirectory*>(currentEntry); + if (currentDir == 0) + { + kdDebug(14010) << k_funcinfo << + "couldn't cast archive entry to KArchiveDirectory" << endl; + continue; + } + currentDir->copyTo(localThemesDir + *it); + progressDlg->progressBar()->advance(1); + } + } + + archive->close(); + delete archive; + + // check if all steps were done, if there are skipped ones then we didn't + // succeed copying all dirs from the tarball + if (progressDlg->progressBar()->totalSteps() > progressDlg->progressBar()->progress()) + { + KMessageBox::queuedMessageBox(Kopete::UI::Global::mainWidget(), + KMessageBox::Error, + i18n("<qt>A problem occurred during the installation process. " + "However, some of the emoticon themes in the archive may have been " + "installed.</qt>")); + } + + delete progressDlg; +} + +} // END namespace Global + +} // END namespace Kopete + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopeteglobal.h b/kopete/libkopete/kopeteglobal.h new file mode 100644 index 00000000..aa6456f4 --- /dev/null +++ b/kopete/libkopete/kopeteglobal.h @@ -0,0 +1,176 @@ +/* + kopeteglobal.h - Kopete Globals + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEGLOBAL_H +#define KOPETEGLOBAL_H + +#include "kopetecontactproperty.h" + +#include "kopete_export.h" + +/** + * This namespace contains all of Kopete's core classes and functions. + */ +namespace Kopete +{ + +/** + * This namespace contains Kopete's global settings and functions + */ +namespace Global +{ + class PropertiesPrivate; + + /** + * \brief Installs one or more kopete emoticon themes from a tarball + * (either .kopete-emoticons or .tar.gz or .tar.bz2) + * + * @p localPath Full path to a local emoticon archive, use KIO to download + * files in case their are non-local. + * + * @return true in case install was successful, false otherwise. Errors are + * displayed by either KIO or by using KMessagebox directly. + * + * TODO: If possible, port it to KIO instead of using ugly blocking KTar + **/ + KOPETE_EXPORT void installEmoticonTheme(const QString &localPath); + + /** + * \brief Global facility to query/store templates that are needed by KopeteContactProperty + * + * Basically all a plugin author needs to worry about is creating ContactPropertyTmpl + * objects for all the properties he wants to set for a Kopete::Contact, + * everything else is handled behind the scenes. + **/ + class KOPETE_EXPORT Properties + { + friend class Kopete::ContactPropertyTmpl; + public: + /** + * \brief Singleton accessor for this class. + * + * Use it to access the global list of property-templates or to get + * a reference to one of the common ContactPropertyTmpl objects + */ + static Properties *self(); + + /** + * Return a template with defined by @p key, if no such template has + * been registered ContactPropertyTmpl::null will be returned + */ + const ContactPropertyTmpl &tmpl(const QString &key) const; + + /** + * @return a ready-to-use template for a contact's full name. + * + * This is actually no real property, it makes use of + * firstName() and lastName() to assemble an name that consists of + * both name parts + */ + const ContactPropertyTmpl &fullName() const; + + /** + * Return default template for a contact's idle-time + */ + const ContactPropertyTmpl &idleTime() const; + /** + * Return default template for a contact's online-since time + * (i.e. time since he went from offline to online) + */ + const ContactPropertyTmpl &onlineSince() const; + /** + * @return default template for a contact's last-seen time + */ + const ContactPropertyTmpl &lastSeen() const; + /** + * @return default template for a contact's away-message + */ + const ContactPropertyTmpl &awayMessage() const; + /** + * @return default template for a contact's first name + */ + const ContactPropertyTmpl &firstName() const; + /** + * @return default template for a contact's last name + */ + const ContactPropertyTmpl &lastName() const; + /** + * @return default template for a contact's email-address + */ + const ContactPropertyTmpl &emailAddress() const; + /** + * @return default template for a contact's private phone number + */ + const ContactPropertyTmpl &privatePhone() const; + /** + * @return default template for a contact's private mobile number + */ + const ContactPropertyTmpl &privateMobilePhone() const; + /** + * @return default template for a contact's work phone number + */ + const ContactPropertyTmpl &workPhone() const; + /** + * @return default template for a contact's work mobile number + */ + const ContactPropertyTmpl &workMobilePhone() const; + /** + * @return default template for a contact's nickname (set by the contact) + */ + const ContactPropertyTmpl &nickName() const; + /** + * default template for a contact's photo. + * + * It could be either a QString or a QImage. + * If it's a QString, it should points to the path the image is stored. + */ + const ContactPropertyTmpl &photo() const; + + /** + * @return a map of all registered ContactPropertyTmpl object + */ + const ContactPropertyTmpl::Map &templateMap() const; + + /** + * return true if a template with key @p key is already registered, + * false otherwise + */ + bool isRegistered(const QString &key); + + private: + Properties(); + ~Properties(); + + bool registerTemplate(const QString &key, + const ContactPropertyTmpl &tmpl); + void unregisterTemplate(const QString &key); + + const ContactPropertyTmpl &createProp(const QString &key, + const QString &label, const QString &icon=QString::null, + bool persistent = false) const; + + private: + static Properties *mSelf; + PropertiesPrivate *d; + }; // end class Properties + +} // Global + +} // Kopete + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetegroup.cpp b/kopete/libkopete/kopetegroup.cpp new file mode 100644 index 00000000..f50eb08b --- /dev/null +++ b/kopete/libkopete/kopetegroup.cpp @@ -0,0 +1,335 @@ +/* + kopetegroup.cpp - Kopete (Meta)Contact Group + + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart@ tiscalinet.be> + Copyright (c) 2003 by Martijn Klingens <klingens@kde.org> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetegroup.h" + +#include "kopetecontactlist.h" +#include "kopetemetacontact.h" +#include "kopetecontact.h" +#include "kopetechatsession.h" + +#include <klocale.h> + +namespace Kopete { + +class Group::Private +{ +public: + QString displayName; + Group::GroupType type; + bool expanded; + uint groupId; + + //Unique contact id per metacontact + static uint uniqueGroupId; +}; + +Group *Group::s_topLevel = 0L; +Group *Group::s_temporary = 0L; +Group * Group::topLevel() +{ + if ( !s_topLevel ) + s_topLevel = new Group( i18n( "Top Level" ), Group::TopLevel ); + + return s_topLevel; +} + +Group * Group::temporary() +{ + if ( !s_temporary ) + s_temporary = new Group( i18n( "Not in your contact list" ), Group::Temporary ); + + return s_temporary; +} + +uint Group::Private::uniqueGroupId = 0; + +Group::Group( const QString &_name, GroupType _type ) + : ContactListElement( ContactList::self() ) +{ + d = new Private; + d->displayName = _name; + d->type = _type; + d->expanded = true; + d->groupId = 0; +} + +Group::Group() + : ContactListElement( ContactList::self() ) +{ + d = new Private; + d->expanded = true; + d->type = Normal; + d->groupId = 0; +} + +Group::~Group() +{ + if(d->type == TopLevel) + s_topLevel=0L; + if(d->type == Temporary) + s_temporary=0L; + delete d; +} + +QPtrList<MetaContact> Group::members() const +{ + QPtrList<MetaContact> members = ContactList::self()->metaContacts(); + // members is a *copy* of the meta contacts, so using first(), next() and remove() is fine. + for( members.first(); members.current(); ) + { + if ( members.current()->groups().contains( this ) ) + members.next(); + else + members.remove(); + } + return members; +} + +const QDomElement Group::toXML() +{ + QDomDocument group; + group.appendChild( group.createElement( QString::fromLatin1( "kopete-group" ) ) ); + group.documentElement().setAttribute( QString::fromLatin1( "groupId" ), QString::number( groupId() ) ); + + QString type; + switch ( d->type ) + { + case Temporary: + type = QString::fromLatin1( "temporary" ); + break; + case TopLevel: + type = QString::fromLatin1( "top-level" ); + break; + default: + type = QString::fromLatin1( "standard" ); // == Normal + break; + } + + group.documentElement().setAttribute( QString::fromLatin1( "type" ), type ); + group.documentElement().setAttribute( QString::fromLatin1( "view" ), QString::fromLatin1( d->expanded ? "expanded" : "collapsed" ) ); + + QDomElement displayName = group.createElement( QString::fromLatin1( "display-name" ) ); + displayName.appendChild( group.createTextNode( d->displayName ) ); + group.documentElement().appendChild( displayName ); + + // Store other plugin data + QValueList<QDomElement> pluginData = ContactListElement::toXML(); + for ( QValueList<QDomElement>::Iterator it = pluginData.begin(); it != pluginData.end(); ++it ) + group.documentElement().appendChild( group.importNode( *it, true ) ); + + // Store custom notification data + QDomElement notifyData = Kopete::NotifyDataObject::notifyDataToXML(); + if ( notifyData.hasChildNodes() ) + group.documentElement().appendChild( group.importNode( notifyData, true ) ); + + return group.documentElement(); +} + +bool Group::fromXML( const QDomElement &data ) +{ + QString strGroupId = data.attribute( QString::fromLatin1( "groupId" ) ); + if ( !strGroupId.isEmpty() ) + { + d->groupId = strGroupId.toUInt(); + if ( d->groupId > d->uniqueGroupId ) + d->uniqueGroupId = d->groupId; + } + + // Don't overwrite type for Temporary and TopLevel groups + if ( d->type != Temporary && d->type != TopLevel ) + { + QString type = data.attribute( QString::fromLatin1( "type" ), QString::fromLatin1( "standard" ) ); + if ( type == QString::fromLatin1( "temporary" ) ) + { + if ( d->type != Temporary ) + { + s_temporary->fromXML( data ); + return false; + } + } + else if ( type == QString::fromLatin1( "top-level" ) ) + { + if ( d->type != TopLevel ) + { + s_topLevel->fromXML( data ); + return false; + } + } + else + { + d->type = Normal; + } + } + + QString view = data.attribute( QString::fromLatin1( "view" ), QString::fromLatin1( "expanded" ) ); + d->expanded = ( view != QString::fromLatin1( "collapsed" ) ); + + QDomNode groupData = data.firstChild(); + while ( !groupData.isNull() ) + { + QDomElement groupElement = groupData.toElement(); + if ( groupElement.tagName() == QString::fromLatin1( "display-name" ) ) + { + // Don't set display name for temporary or top-level items + if ( d->type == Normal ) + d->displayName = groupElement.text(); + } + else if( groupElement.tagName() == QString::fromLatin1( "custom-notifications" ) ) + { + Kopete::NotifyDataObject::notifyDataFromXML( groupElement ); + } + else + { + Kopete::ContactListElement::fromXML( groupElement ); + } + + groupData = groupData.nextSibling(); + } + + // Sanity checks. We must not have groups without a displayname. + if ( d->displayName.isEmpty() ) + { + switch ( d->type ) + { + case Temporary: + d->displayName = QString::fromLatin1( "Temporary" ); + break; + case TopLevel: + d->displayName = QString::fromLatin1( "Top-Level" ); + break; + default: + d->displayName = i18n( "(Unnamed Group)" ); + break; + } + } + + //this allows to save data for the top-level group in the top-level group + return ( d->type == Normal ); +} + +void Group::setDisplayName( const QString &s ) +{ + if ( d->displayName != s ) + { + QString oldname = d->displayName; + d->displayName = s; + emit displayNameChanged( this, oldname ); + } +} + +QString Group::displayName() const +{ + return d->displayName; +} + +Group::GroupType Group::type() const +{ + return d->type; +} + +void Group::setType( GroupType t ) +{ + d->type = t; +} + +void Group::setExpanded( bool isExpanded ) +{ + d->expanded = isExpanded; +} + +bool Group::isExpanded() const +{ + return d->expanded; +} + +uint Group::groupId() const +{ + if ( d->groupId == 0 ) + d->groupId = ++d->uniqueGroupId; + + return d->groupId; +} + + +void Group::sendMessage() +{ + QPtrList<Kopete::MetaContact> list = onlineMembers(); + Kopete::MetaContact *mc = list.first(); + Kopete::Contact *c; + + if(!mc) + return; + c = mc->preferredContact(); + c->sendMessage(); + if( c->manager( Contact::CanCreate ) ) + { + connect( c->manager(), SIGNAL( messageSent( Kopete::Message&, Kopete::ChatSession* ) ), this, SLOT( sendMessage( Kopete::Message& ) )); + } +} + +void Group::sendMessage( Message& msg ) +{ + QPtrList<MetaContact> list = onlineMembers(); + Kopete::MetaContact *mc = list.first(); + ChatSession *cs=msg.manager(); + if( cs ) + { + disconnect( cs, SIGNAL( messageSent( Kopete::Message&, Kopete::ChatSession* ) ), this, SLOT( sendMessage( Kopete::Message& ) ) ); + } + else + return; + + if(!mc) + return; + list.remove( msg.to().first()->metaContact() ); + for( mc = list.first(); mc; mc = list.next() ) + { + if(mc->isReachable()) + { + Contact *kcontact=mc->preferredContact(); + if( kcontact->manager( Contact::CanCreate ) ) + { + //This is hack and stupid. send message to group should never exist anyway - Olivier 2005-09-11 + // changing the "to" is require, because jabber use it to send the messgae. Cf BUG 111514 + Message msg2(cs->myself() , kcontact , msg.plainBody() , msg.direction() , Message::PlainText , msg.requestedPlugin() ); + kcontact->manager( Contact::CanCreate )->sendMessage( msg2 ); + } + } + } +} + +QPtrList<MetaContact> Group::onlineMembers() const +{ + QPtrList<MetaContact> list = members(); + + for( list.first(); list.current(); ) + if( list.current()->isReachable() && list.current()->isOnline() ) + list.next(); + else + list.remove(); + return list; +} + +} //END namespace Kopete + + +#include "kopetegroup.moc" + + + diff --git a/kopete/libkopete/kopetegroup.h b/kopete/libkopete/kopetegroup.h new file mode 100644 index 00000000..37b8572d --- /dev/null +++ b/kopete/libkopete/kopetegroup.h @@ -0,0 +1,187 @@ +/* + kopetegroup.h - Kopete (Meta)Contact Group + + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart @ kde.org> + Copyright (c) 2003 by Martijn Klingens <klingens@kde.org> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEGROUP_H +#define KOPETEGROUP_H + +#include "kopetenotifydataobject.h" +#include "kopetecontactlistelement.h" + +#include "kopete_export.h" + +#include <qptrlist.h> + +class QDomElement; + + +namespace Kopete { + + +class MetaContact; +class Message; + +/** + * Class which represents the Group. + * + * A Group is a ConstacListElement which means plugin can save datas. + * + * some static group are availavle from this class: topLevel and temporary + * + * @author Olivier Goffart + */ +class KOPETE_EXPORT Group : public ContactListElement, public NotifyDataObject +{ + Q_PROPERTY( QString displayName READ displayName WRITE setDisplayName ) + Q_PROPERTY( uint groupId READ groupId ) + Q_PROPERTY( bool expanded READ isExpanded WRITE setExpanded ) + + Q_OBJECT + +public: + /** Kinds of groups. */ + enum GroupType { Normal=0, Temporary, TopLevel }; + + /** + * \brief Create an empty group + * + * Note that the constructor will not add the group automatically to the contact list. + * Use @ref ContactList::addGroup() to add it + */ + Group(); + + /** + * \brief Create a group of the specified type + * + * Overloaded constructor to create a group with a display name of the specified type. + */ + Group( const QString &displayName, GroupType type = Normal ); + + ~Group(); + + /** + * \brief Return the group's display name + * + * \return the display name of the group + */ + QString displayName() const; + + /** + * \brief Rename the group + */ + void setDisplayName( const QString &newName ); + + /** + * \return the group type + */ + GroupType type() const; + + /** + * \brief Set the group type + */ + void setType( GroupType newType ); + + /** + * \return the unique id for this group + */ + uint groupId() const; + + /** + * @brief child metacontact + * This function is not very efficient - it searches through all the metacontacts in the contact list + * \return the members of this group + */ + QPtrList<MetaContact> members() const; + + /** + * \brief Set if the group is expanded. + * + * This is saved to the xml contactlist file + */ + void setExpanded( bool expanded ); + + /** + * + * \return true if the group is expanded. + * \return false otherwise + */ + bool isExpanded() const; + + /** + * \return a Group pointer to the toplevel group + */ + static Group *topLevel(); + + /** + * \return a Group pointer to the temporary group + */ + static Group *temporary(); + + + + /** + * @internal + * Outputs the group data in XML + */ + const QDomElement toXML(); + + + /** + * @internal + * Loads the group data from XML + */ + bool fromXML( const QDomElement &data ); + + + + +public slots: + /** + * Send a message to all contacts in the group + */ + void sendMessage(); + + +signals: + /** + * \brief Emitted when the group has been renamed + */ + void displayNameChanged( Kopete::Group *group , const QString &oldName ); + + +private slots: + void sendMessage( Kopete::Message& ); + +private: + static Group *s_topLevel; + static Group *s_temporary; + + class Private; + Private *d; + + + /** + * @internal used to get reachabe contact to send message to thom. + */ + QPtrList<MetaContact> onlineMembers() const; +}; + +} //END namespace Kopete + +#endif + + diff --git a/kopete/libkopete/kopetemessage.cpp b/kopete/libkopete/kopetemessage.cpp new file mode 100644 index 00000000..54761799 --- /dev/null +++ b/kopete/libkopete/kopetemessage.cpp @@ -0,0 +1,641 @@ +/* + kopetemessage.cpp - Base class for Kopete messages + + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2006 by Olivier Goffart <ogoffart @ kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <stdlib.h> + +#include <qcolor.h> +#include <qbuffer.h> +#include <qimage.h> +#include <qstylesheet.h> +#include <qregexp.h> +#include <qtextcodec.h> +#include <kdebug.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kstringhandler.h> +#include <kmdcodec.h> +#include <qguardedptr.h> + +#include "kopetemessage.h" +#include "kopetemetacontact.h" +#include "kopeteprotocol.h" +#include "kopetechatsession.h" +#include "kopeteprefs.h" +#include "kopetecontact.h" +#include "kopeteemoticons.h" + + +using namespace Kopete; + +class Message::Private + : public KShared +{ +public: + Private( const QDateTime &timeStamp, const Contact *from, const ContactPtrList &to, + const QString &subject, MessageDirection direction, + const QString &requestedPlugin, MessageType type ); + + QGuardedPtr<const Contact> from; + ContactPtrList to; + ChatSession *manager; + + MessageDirection direction; + MessageFormat format; + MessageType type; + QString requestedPlugin; + MessageImportance importance; + bool bgOverride; + bool fgOverride; + bool rtfOverride; + bool isRightToLeft; + QDateTime timeStamp; + QFont font; + + QColor fgColor; + QColor bgColor; + QString body; + QString subject; +}; + +Message::Private::Private( const QDateTime &timeStamp, const Contact *from, + const ContactPtrList &to, const QString &subject, + MessageDirection direction, const QString &requestedPlugin, MessageType type ) +: from( from ), to( to ), manager( 0 ), direction( direction ), format( PlainText ), type( type ), + requestedPlugin( requestedPlugin ), importance( (to.count() <= 1) ? Normal : Low ), + bgOverride( false ), fgOverride( false ), rtfOverride( false ), isRightToLeft( false ), + timeStamp( timeStamp ), body( QString::null ), subject( subject ) +{ +} + +Message::Message() +: d( new Private( QDateTime::currentDateTime(), 0L, QPtrList<Contact>(), QString::null, Internal, + QString::null, TypeNormal ) ) +{ +} + +Message::Message( const Contact *fromKC, const QPtrList<Contact> &toKC, const QString &body, + MessageDirection direction, MessageFormat f, const QString &requestedPlugin, MessageType type ) +: d( new Private( QDateTime::currentDateTime(), fromKC, toKC, QString::null, direction, requestedPlugin, type ) ) +{ + doSetBody( body, f ); +} + +Message::Message( const Contact *fromKC, const Contact *toKC, const QString &body, + MessageDirection direction, MessageFormat f, const QString &requestedPlugin, MessageType type ) +{ + QPtrList<Contact> to; + to.append(toKC); + d = new Private( QDateTime::currentDateTime(), fromKC, to, QString::null, direction, requestedPlugin, type ); + doSetBody( body, f ); +} + +Message::Message( const Contact *fromKC, const QPtrList<Contact> &toKC, const QString &body, + const QString &subject, MessageDirection direction, MessageFormat f, const QString &requestedPlugin, MessageType type ) + : d( new Private( QDateTime::currentDateTime(), fromKC, toKC, subject, direction, requestedPlugin, type ) ) +{ + doSetBody( body, f ); +} + +Message::Message( const QDateTime &timeStamp, const Contact *fromKC, const QPtrList<Contact> &toKC, + const QString &body, MessageDirection direction, MessageFormat f, const QString &requestedPlugin, MessageType type ) + : d( new Private( timeStamp, fromKC, toKC, QString::null, direction, requestedPlugin, type ) ) +{ + doSetBody( body, f ); +} + + +Message::Message( const QDateTime &timeStamp, const Contact *fromKC, const QPtrList<Contact> &toKC, + const QString &body, const QString &subject, MessageDirection direction, MessageFormat f, const QString &requestedPlugin, MessageType type ) + : d( new Private( timeStamp, fromKC, toKC, subject, direction, requestedPlugin, type ) ) +{ + doSetBody( body, f ); +} + +Kopete::Message::Message( const Message &other ) + : d(other.d) +{ +} + + + +Message& Message::operator=( const Message &other ) +{ + d = other.d; + return *this; +} + +Message::~Message() +{ +} + +void Message::detach() +{ + // there is no detach in KSharedPtr :( + if( d.count() == 1 ) + return; + + // Warning: this only works as long as the private object doesn't contain pointers to allocated objects. + // The from contact for example is fine, but it's a shallow copy this way. + d = new Private(*d); +} + +void Message::setBgOverride( bool enabled ) +{ + detach(); + d->bgOverride = enabled; +} + +void Message::setFgOverride( bool enabled ) +{ + detach(); + d->fgOverride = enabled; +} + +void Message::setRtfOverride( bool enabled ) +{ + detach(); + d->rtfOverride = enabled; +} + +void Message::setFg( const QColor &color ) +{ + detach(); + d->fgColor=color; +} + +void Message::setBg( const QColor &color ) +{ + detach(); + d->bgColor=color; +} + +void Message::setFont( const QFont &font ) +{ + detach(); + d->font = font; +} + +void Message::doSetBody( const QString &_body, Message::MessageFormat f ) +{ + QString body = _body; + + //TODO: move that in ChatTextEditPart::contents + if( f == RichText ) + { + //This is coming from the RichTextEditor component. + //Strip off the containing HTML document + body.replace( QRegExp( QString::fromLatin1(".*<body[^>]*>(.*)</body>.*") ), QString::fromLatin1("\\1") ); + + //Strip <p> tags + body.replace( QString::fromLatin1("<p>"), QString::null ); + + //Replace </p> with a <br/> + body.replace( QString::fromLatin1("</p>"), QString::fromLatin1("<br/>") ); + + //Remove trailing </br> + if ( body.endsWith( QString::fromLatin1("<br/>") ) ) + body.truncate( body.length() - 5 ); + + body.remove( QString::fromLatin1("\n") ); + body.replace( QRegExp( QString::fromLatin1( "\\s\\s" ) ), QString::fromLatin1( " " ) ); + } + /* + else if( f == ParsedHTML ) + { + kdWarning( 14000 ) << k_funcinfo << "using ParsedHTML which is internal! Message: '" << + body << "', Backtrace: " << kdBacktrace() << endl; + } + */ + + d->body = body; + d->format = f; + + // unescaping is very expensive, do it only once and cache the result + d->isRightToLeft = ( f & RichText ? unescape( d->body ).isRightToLeft() : d->body.isRightToLeft() ); +} + +void Message::setBody( const QString &body, MessageFormat f ) +{ + detach(); + + doSetBody( body, f ); +} + +bool Message::isRightToLeft() const +{ + return d->isRightToLeft; +} + +void Message::setImportance(Message::MessageImportance i) +{ + detach(); + d->importance = i; +} + +QString Message::unescape( const QString &xml ) +{ + QString data = xml; + + // Remove linebreak and multiple spaces. First return nbsp's to normal spaces :) + data.simplifyWhiteSpace(); + + int pos; + while ( ( pos = data.find( '<' ) ) != -1 ) + { + int endPos = data.find( '>', pos + 1 ); + if( endPos == -1 ) + break; // No more complete elements left + + // Take the part between < and >, and extract the element name from that + int matchWidth = endPos - pos + 1; + QString match = data.mid( pos + 1, matchWidth - 2 ).simplifyWhiteSpace(); + int elemEndPos = match.find( ' ' ); + QString elem = ( elemEndPos == -1 ? match.lower() : match.left( elemEndPos ).lower() ); + if ( elem == QString::fromLatin1( "img" ) ) + { + // Replace smileys with their original text' + const QString attrTitle = QString::fromLatin1( "title=\"" ); + int titlePos = match.find( attrTitle, elemEndPos ); + int titleEndPos = match.find( '"', titlePos + attrTitle.length() ); + if( titlePos == -1 || titleEndPos == -1 ) + { + // Not a smiley but a normal <img> + // Don't update pos, we restart at this position :) + data.remove( pos, matchWidth ); + } + else + { + QString orig = match.mid( titlePos + attrTitle.length(), + titleEndPos - titlePos - attrTitle.length() ); + data.replace( pos, matchWidth, orig ); + pos += orig.length(); + } + } + else if ( elem == QString::fromLatin1( "/p" ) || elem == QString::fromLatin1( "/div" ) || + elem == QString::fromLatin1( "br" ) ) + { + // Replace paragraph, div and line breaks with a newline + data.replace( pos, matchWidth, '\n' ); + pos++; + } + else + { + // Remove all other elements entirely + // Don't update pos, we restart at this position :) + data.remove( pos, matchWidth ); + } + } + + // Replace stuff starting with '&' + data.replace( QString::fromLatin1( ">" ), QString::fromLatin1( ">" ) ); + data.replace( QString::fromLatin1( "<" ), QString::fromLatin1( "<" ) ); + data.replace( QString::fromLatin1( """ ), QString::fromLatin1( "\"" ) ); + data.replace( QString::fromLatin1( " " ), QString::fromLatin1( " " ) ); + data.replace( QString::fromLatin1( "&" ), QString::fromLatin1( "&" ) ); + data.replace( QString::fromLatin1( " " ), QString::fromLatin1( " " ) ); //this one is used in jabber: note, we should escape all &#xx; + + return data; +} + +QString Message::escape( const QString &text ) +{ + QString html = QStyleSheet::escape( text ); + //Replace carriage returns inside the text + html.replace( QString::fromLatin1( "\n" ), QString::fromLatin1( "<br />" ) ); + //Replace a tab with 4 spaces + html.replace( QString::fromLatin1( "\t" ), QString::fromLatin1( " " ) ); + + //Replace multiple spaces with + //do not replace every space so we break the linebreak + html.replace( QRegExp( QString::fromLatin1( "\\s\\s" ) ), QString::fromLatin1( " " ) ); + + return html; +} + + + +QString Message::plainBody() const +{ + QString body=d->body; + if( d->format & RichText ) + { + body = unescape( body ); + } + return body; +} + +QString Message::escapedBody() const +{ + QString escapedBody=d->body; +// kdDebug(14000) << k_funcinfo << escapedBody << " " << d->rtfOverride << endl; + + if( d->format & PlainText ) + { + escapedBody=escape( escapedBody ); + } + else if( d->format & RichText && d->rtfOverride) + { + //remove the rich text + escapedBody = escape (unescape( escapedBody ) ); + } + + return escapedBody; +} + +QString Message::parsedBody() const +{ + //kdDebug(14000) << k_funcinfo << "messageformat: " << d->format << endl; + + if( d->format == ParsedHTML ) + { + return d->body; + } + else + { + return Kopete::Emoticons::parseEmoticons(parseLinks(escapedBody(), RichText)); + } +} + +static QString makeRegExp( const char *pattern ) +{ + const QString urlChar = QString::fromLatin1( "\\+\\-\\w\\./#@&;:=\\?~%_,\\!\\$\\*\\(\\)" ); + const QString boundaryStart = QString::fromLatin1( "(^|[^%1])(" ).arg( urlChar ); + const QString boundaryEnd = QString::fromLatin1( ")([^%1]|$)" ).arg( urlChar ); + + return boundaryStart + QString::fromLatin1(pattern) + boundaryEnd; +} + +QString Message::parseLinks( const QString &message, MessageFormat format ) +{ + if ( format == ParsedHTML ) + return message; + + if ( format & RichText ) + { + // < in HTML *always* means start-of-tag + QStringList entries = QStringList::split( QChar('<'), message, true ); + + QStringList::Iterator it = entries.begin(); + + // first one is different: it doesn't start with an HTML tag. + if ( it != entries.end() ) + { + *it = parseLinks( *it, PlainText ); + ++it; + } + + for ( ; it != entries.end(); ++it ) + { + QString curr = *it; + // > in HTML means start-of-tag if and only if it's the first one after a < + int tagclose = curr.find( QChar('>') ); + // no >: the HTML is broken, but we can cope + if ( tagclose == -1 ) + continue; + QString tag = curr.left( tagclose + 1 ); + QString body = curr.mid( tagclose + 1 ); + *it = tag + parseLinks( body, PlainText ); + } + return entries.join(QString::fromLatin1("<")); + } + + QString result = message; + + // common subpatterns - may not contain matching parens! + const QString name = QString::fromLatin1( "[\\w\\+\\-=_\\.]+" ); + const QString userAndPassword = QString::fromLatin1( "(?:%1(?::%1)?\\@)" ).arg( name ); + const QString urlChar = QString::fromLatin1( "\\+\\-\\w\\./#@&;:=\\?~%_,\\!\\$\\*\\(\\)" ); + const QString urlSection = QString::fromLatin1( "[%1]+" ).arg( urlChar ); + const QString domain = QString::fromLatin1( "[\\-\\w_]+(?:\\.[\\-\\w_]+)+" ); + + //Replace http/https/ftp links: + // Replace (stuff)://[user:password@](linkstuff) with a link + result.replace( + QRegExp( makeRegExp("\\w+://%1?\\w%2").arg( userAndPassword, urlSection ) ), + QString::fromLatin1("\\1<a href=\"\\2\" title=\"\\2\">\\2</a>\\3" ) ); + + // Replace www.X.Y(linkstuff) with a http: link + result.replace( + QRegExp( makeRegExp("%1?www\\.%2%3").arg( userAndPassword, domain, urlSection ) ), + QString::fromLatin1("\\1<a href=\"http://\\2\" title=\"http://\\2\">\\2</a>\\3" ) ); + + //Replace Email Links + // Replace user@domain with a mailto: link + result.replace( + QRegExp( makeRegExp("%1@%2").arg( name, domain ) ), + QString::fromLatin1("\\1<a href=\"mailto:\\2\" title=\"mailto:\\2\">\\2</a>\\3") ); + + //Workaround for Bug 85061: Highlighted URLs adds a ' ' after the URL itself + // the trailing is included in the url. + result.replace( QRegExp( QString::fromLatin1("(<a href=\"[^\"]+)( )(\")") ) , QString::fromLatin1("\\1\\3") ); + + return result; +} + + + +QDateTime Message::timestamp() const +{ + return d->timeStamp; +} + +const Contact *Message::from() const +{ + return d->from; +} + +QPtrList<Contact> Message::to() const +{ + return d->to; +} + +Message::MessageType Message::type() const +{ + return d->type; +} + +QString Message::requestedPlugin() const +{ + return d->requestedPlugin; +} + +QColor Message::fg() const +{ + return d->fgColor; +} + +QColor Message::bg() const +{ + return d->bgColor; +} + +QFont Message::font() const +{ + //QDomElement bodyNode = d->xmlDoc.elementsByTagName( QString::fromLatin1("body") ).item(0).toElement(); + //return QFont( bodyNode.attribute( QString::fromLatin1("font") ), bodyNode.attribute( QString::fromLatin1("fontsize") ).toInt() ); + return d->font; +} + +QString Message::subject() const +{ + return d->subject; +} + +Message::MessageFormat Message::format() const +{ + return d->format; +} + +Message::MessageDirection Message::direction() const +{ + return d->direction; +} + +Message::MessageImportance Message::importance() const +{ + return d->importance; +} + +ChatSession *Message::manager() const +{ + return d->manager; +} + +void Message::setManager(ChatSession *kmm) +{ + detach(); + d->manager=kmm; +} + +QString Message::getHtmlStyleAttribute() const +{ + QString styleAttribute; + + styleAttribute = QString::fromUtf8("style=\""); + + // Affect foreground(color) and background color to message. + if( !d->fgOverride && d->fgColor.isValid() ) + { + styleAttribute += QString::fromUtf8("color: %1; ").arg(d->fgColor.name()); + } + if( !d->bgOverride && d->bgColor.isValid() ) + { + styleAttribute += QString::fromUtf8("background-color: %1; ").arg(d->bgColor.name()); + } + + // Affect font parameters. + if( !d->rtfOverride && d->font!=QFont() ) + { + QString fontstr; + if(!d->font.family().isNull()) + fontstr+=QString::fromLatin1("font-family: ")+d->font.family()+QString::fromLatin1("; "); + if(d->font.italic()) + fontstr+=QString::fromLatin1("font-style: italic; "); + if(d->font.strikeOut()) + fontstr+=QString::fromLatin1("text-decoration: line-through; "); + if(d->font.underline()) + fontstr+=QString::fromLatin1("text-decoration: underline; "); + if(d->font.bold()) + fontstr+=QString::fromLatin1("font-weight: bold;"); + + styleAttribute += fontstr; + } + + styleAttribute += QString::fromUtf8("\""); + + return styleAttribute; +} + +// KDE4: Move that to a utils class/namespace +QString Message::decodeString( const QCString &message, const QTextCodec *providedCodec, bool *success ) +{ + /* + Note to everyone. This function is not the most efficient, that is for sure. + However, it *is* the only way we can be guarenteed that a given string is + decoded properly. + */ + + if( success ) + *success = true; + + // Avoid heavy codec tests on empty message. + if( message.isEmpty() ) + return QString::fromAscii( message ); + + //Check first 128 chars + int charsToCheck = message.length(); + charsToCheck = 128 > charsToCheck ? charsToCheck : 128; + + //They are providing a possible codec. Check if it is valid + if( providedCodec && providedCodec->heuristicContentMatch( message, charsToCheck ) >= 0 ) + { + //All chars decodable. + return providedCodec->toUnicode( message ); + } + + //Check if it is UTF + if( KStringHandler::isUtf8(message) ) + { + //We have a UTF string almost for sure. At least we know it will be decoded. + return QString::fromUtf8( message ); + } + + //Try codecForContent - exact match + QTextCodec *testCodec = QTextCodec::codecForContent(message, charsToCheck); + if( testCodec && testCodec->heuristicContentMatch( message, charsToCheck ) >= 0 ) + { + //All chars decodable. + return testCodec->toUnicode( message ); + } + + kdWarning(14000) << k_funcinfo << "Unable to decode string using provided codec(s), taking best guesses!" << endl; + if( success ) + *success = false; + + //We don't have any clues here. + + //Try local codec + testCodec = QTextCodec::codecForLocale(); + if( testCodec && testCodec->heuristicContentMatch( message, charsToCheck ) >= 0 ) + { + //All chars decodable. + kdDebug(14000) << k_funcinfo << "Using locale's codec" << endl; + return testCodec->toUnicode( message ); + } + + //Try latin1 codec + testCodec = QTextCodec::codecForMib(4); + if( testCodec && testCodec->heuristicContentMatch( message, charsToCheck ) >= 0 ) + { + //All chars decodable. + kdDebug(14000) << k_funcinfo << "Using latin1" << endl; + return testCodec->toUnicode( message ); + } + + kdDebug(14000) << k_funcinfo << "Using latin1 and cleaning string" << endl; + //No codec decoded. Just decode latin1, and clean out any junk. + QString result = QString::fromLatin1( message ); + const uint length = message.length(); + for( uint i = 0; i < length; ++i ) + { + if( !result[i].isPrint() ) + result[i] = '?'; + } + + return result; +} diff --git a/kopete/libkopete/kopetemessage.h b/kopete/libkopete/kopetemessage.h new file mode 100644 index 00000000..0737d2ae --- /dev/null +++ b/kopete/libkopete/kopetemessage.h @@ -0,0 +1,424 @@ +/* + kopetemessage.h - Base class for Kopete messages + + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart @ kde.org> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef __KOPETE_MESSAGE_H__ +#define __KOPETE_MESSAGE_H__ + +#include "kopetecontact.h" + +#include <ksharedptr.h> + +#include <qptrlist.h> +#include <qstring.h> +#include <qdom.h> +#include <qcolor.h> +#include <qfont.h> +#include <qdatetime.h> +#include <qvaluelist.h> + +#include "kopete_export.h" + + +class QDateTime; + +namespace Kopete { + + + class ChatSession; +class Contact; + + +/** + * @author Martijn Klingens <klingens@kde.org> + * @author Olivier Goffart <ogoffart @ kde.org> + * + * Message represents any kind of messages shown on a chat view. + * + * The message may be a simple plaintext string, or a Richtext HTML like message, + * this is indicated by the @ref format() flag. + * PlainText message can however have a color, or specific fonts with the flag + * @ref bg(), @ref fg(), @ref font() + * It is recommended to use these flags, even for RichText messages, so the user can disable + * custom colors in the chat window style. + */ +class KOPETE_EXPORT Message +{ +public: + /** + * Direction of a message. + * - Inbound: Message is from the chat partner + * - Outbound: Message sent by the user. + * - Internal: Messages which are not sent via the network. This is just a notification a plugin can show in a chat view + * - Action: For the /me command , like on irc + */ + enum MessageDirection { Inbound = 0, Outbound = 1, Internal= 2 }; + + /** + * Format of body + * - PlainText: Just a simple text, without any formatting. If it contains HTML tags then they will be simply shown in the chatview. + * - RichText: Text already HTML escaped and which can contains some tags. the string + * should be a valid (X)HTML string. + * Any HTML specific characters (\<, \>, \&, ...) are escaped to the equivalent HTML + * entity (\>, \<, ...) newlines are \<br /\> and any other HTML tags will be interpreted. + * - ParsedHTML: only used by the chatview, this text is parsed and ready to + * show into the chatview, with Emoticons, and URLs + * - Crypted is used only by Jabber and the Cryptography plugin + */ + enum MessageFormat{ PlainText = 0x01 , RichText =0x02 , ParsedHTML = 0x04|RichText , Crypted = 0x08|PlainText}; + + /** + * Specifies the type of the message. + * Currently supported types are: + * - Normal: a message + * - Action: an IRC-style DESCRIBE action. + */ + enum MessageType { TypeNormal, TypeAction }; + + /** + * Specifies the type of notification that will be sent with this message + * - Low: almost no notifications. automatically used in groupChat + * - Normal: Default notification, for normal message + * - Highlight: Highlight notification, for most important messages, which require particular attentions. + */ + enum MessageImportance { Low = 0, Normal = 1, Highlight = 2 }; + + /** + * Constructs a new empty message + */ + Message(); + + /** + * Deref and clean private object if refcount == 0 + */ + ~Message(); + + /** + * Constructs a new message. See @ref setBody() to more information about the format + * @param fromKC The Contact that the message is coming from + * @param toKC List of Contacts the message is going to + * @param body Message body + * @param direction The direction of the message, Message::Inbound, Message::Outbound, Message::Internal + * @param format Format of the message + * @param requestedPlugin Requested view plugin for the message + * @param type Type of the message, see @ref MessageType + */ + Message( const Contact *fromKC, const QPtrList<Contact> &toKC, const QString &body, + MessageDirection direction, MessageFormat format = PlainText, + const QString &requestedPlugin = QString::null, MessageType type = TypeNormal ); + + /** + * Constructs a new message. See @ref setBody() to more information about the format + * @param fromKC The Contact that the message is coming from + * @param toKC List of Contacts the message is going to + * @param body Message body + * @param direction The direction of the message, Message::Inbound, Message::Outbound, Message::Internal + * @param format Format of the message + * @param requestedPlugin Requested view plugin for the message + * @param type Type of the message, see @ref MessageType + */ + Message( const Contact *fromKC, const Contact *toKC, const QString &body, + MessageDirection direction, MessageFormat format = PlainText, + const QString &requestedPlugin = QString::null, MessageType type = TypeNormal ); + + /** + * Constructs a new message. See @ref setBody() to more information about the format + * @param fromKC The Contact that the message is coming from + * @param toKC List of Contacts the message is going to + * @param body Message body + * @param subject The subject of the message + * @param direction The direction of the message, Message::Inbound, Message::Outbound, Message::Internal + * @param format Format of the message + * @param requestedPlugin Requested view plugin for the message + * @param type Type of the message, see @ref MessageType + */ + Message( const Contact *fromKC, const QPtrList<Contact> &toKC, const QString &body, + const QString &subject, MessageDirection direction, MessageFormat format = PlainText, + const QString &requestedPlugin = QString::null, MessageType type = TypeNormal ); + + /** + * Constructs a new message. See @ref setBody() to more information about the format + * @param timeStamp Timestamp for the message + * @param fromKC The Contact that the message is coming from + * @param toKC List of Contacts the message is going to + * @param body Message body + * @param direction The direction of the message, Message::Inbound, Message::Outbound, Message::Internal + * @param format Format of the message + * @param requestedPlugin Requested view plugin for the message + * @param type Type of the message, see @ref MessageType + */ + Message( const QDateTime &timeStamp, const Contact *fromKC, const QPtrList<Contact> &toKC, + const QString &body, MessageDirection direction, MessageFormat format = PlainText, + const QString &requestedPlugin = QString::null, MessageType type = TypeNormal ); + + /** + * Constructs a new message. See @ref setBody() to more information about the format + * @param timeStamp Timestamp for the message + * @param fromKC The Contact that the message is coming from + * @param toKC List of Contacts the message is going to + * @param body Message body + * @param subject The subject of the message + * @param direction The direction of the message, Message::Inbound, Message::Outbound, Message::Internal + * @param format Format of the message + * @param requestedPlugin Requested view plugin for the message + * @param type Type of the message, see @ref MessageType + */ + Message( const QDateTime &timeStamp, const Contact *fromKC, const QPtrList<Contact> &toKC, + const QString &body, const QString &subject, MessageDirection direction, + MessageFormat format = PlainText, const QString &requestedPlugin = QString::null, + MessageType type = TypeNormal ); + + /** + * Copy constructor. + * Just adds a reference, doesn't actually copy. + */ + Message( const Message &other ); + + /** + * Assignment operator + * Just like the copy constructor it just refs and doesn't copy. + */ + Message & operator=( const Message &other ); + + /** + * Accessor method for the timestamp of the message + * @return The message's timestamp + */ + QDateTime timestamp() const; + + /** + * Accessor method for the Contact that sent this message + * @return The Contact who sent this message + */ + const Contact * from() const; + + /** + * Accessor method for the Contacts that this message was sent to + * @return Pointer list of the Contacts this message was sent to + */ + QPtrList<Contact> to() const; + + /** + * @return the @ref MessageType of this message + */ + MessageType type() const; + + /** + * @return the view plugin you would prefer to use to read this message. If + * null, Kopete will use the user's preferred plugin. + */ + QString requestedPlugin() const; + + /** + * Accessor method for the foreground color + * @return The message's foreground color + */ + QColor fg() const; + + /** + * Accessor method for the background color of the message + * @return The message's background color + */ + QColor bg() const; + + /** + * Accessor method for the font of the message + * @return The message's font + */ + QFont font() const; + + /** + * Accessor method for the subject of the message + * @return The message subject + */ + QString subject() const; + + /** + * Accessor method for the format of the message + * @return The message format + */ + MessageFormat format() const; + + /** + * Accessor method for the direction of the message + * @return The message direction + */ + MessageDirection direction() const; + + /** + * @brief Accessor method for the importance + * @return The message importance (low/normal/highlight) + */ + MessageImportance importance() const; + + /** + * @brief Set the importance. + * @see importance + * @param importance The message importance to set + */ + void setImportance(MessageImportance importance); + + /** + * Sets the foreground color for the message + * @param color The color + */ + void setFg( const QColor &color ); + + /** + * Sets the background color for the message + * @param color The color + */ + void setBg( const QColor &color ); + + /** + * Sets the font for the message + * @param font The font + */ + void setFont( const QFont &font ); + + /** + * @brief Sets the body of the message + * + * @param body The body + * @param format The format of the message, @see MessageFormat + */ + void setBody( const QString &body, MessageFormat format = PlainText ); + + /** + * Get the message body back as plain text + * @return The message body as plain text + */ + QString plainBody() const; + + /** + * Get the message body as escaped (X)HTML format. + * That means every HTML special char (\>, \<, \&, ...) is escaped to the HTML entity (\<, \>, ...) + * and newlines (\\n) are converted to \<br /\> + * @return The message body as escaped text + */ + QString escapedBody() const; + + /** + * Get the message body as parsed HTML with Emoticons, and URL parsed + * this should be ready to be shown in the chatwindow. + * @return The HTML and Emoticon parsed message body + */ + QString parsedBody() const; + + /** + * Get the related message manager. + * If it is not set, returns 0L. + * + * The @ref ChatSession is only set if the message is already passed by the manager. + * We should trust this only in aboutToSend/aboutToReceive signals + */ + ChatSession *manager() const ; + + /** + * set the messagemanager for this message. + * should be only used by the manager itself + */ + void setManager(ChatSession *); + + /** + * Enables the use of a background for a message + * @param enable A flag to indicate if the background should be enabled or disabled. + */ + void setBgOverride( bool enable ); + + /** + * Enables the use of a foreground for a message + * @param enable A flag to indicate if the foreground should be enabled or disabled. + */ + void setFgOverride( bool enable ); + + /** + * Enables the use of a RTF formatting for a message + * @param enable A flag to indicate if the RTF formatting should be enabled or disabled. + */ + void setRtfOverride( bool enable ); + + /** + * Return HTML style attribute for this message. + * @return A string formatted like this: "style=attr" + */ + QString getHtmlStyleAttribute() const; + +public: /* static helpers */ + + /** + * Unescapes a string, removing XML entity references and returns a plain text. + * + * Note that this method is *VERY* expensive when called on rich text bodies, + * use with care! + * + * @param xml The string you want to unescape + */ + static QString unescape( const QString &xml ); + + /** + * Indicate whether the string is right-to-left (Arabic or Hebrew are bidi locales) + * or "normal" left-to-right. Calculating RTL on rich text is expensive, and + * isRightToLeft() therefore uses a cached value. + */ + bool isRightToLeft() const; + + /** + * @brief Transform a pleintext message to an html. + * it escape main entity like > < add some <br /> or &nbsp; + */ + static QString escape( const QString & ); + + + /** + * Helper function to decode a string. Whatever returned here is *nearly guarenteed* to + * be parseable by the XML engine. + * + * @param message The string you are trying to decode + * @param providedCodec A codec you want to try to decode with + * @param success Optional pointer to a bool you want updated on success. "Success" + * is defined as a successfull decoding using either UTF8 or the codec you + * provided. If a guess has to be taken, success will be false. + */ + static QString decodeString( const QCString &message, + const QTextCodec *providedCodec = 0L, bool *success = 0L ); + +private: + /** + * Message is implicitly shared. + * Detach the instance when modifying data. + */ + void detach(); + + /** + * Called internally by @ref setBody() and the constructor + * Basically @ref setBody() without detach + */ + void doSetBody( const QString &body, MessageFormat format = PlainText ); + + class Private; + KSharedPtr<Private> d; + + static QString parseLinks( const QString &message, MessageFormat format ); +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetemessageevent.cpp b/kopete/libkopete/kopetemessageevent.cpp new file mode 100644 index 00000000..fb129837 --- /dev/null +++ b/kopete/libkopete/kopetemessageevent.cpp @@ -0,0 +1,105 @@ +/* + kopetemessageevent.cpp - Kopete Message Event + + Copyright (c) 2003 by Olivier Goffart <ogoffart @ kde.org> + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002 by Hendrik vom Lehn <hvl@linux-4-ever.de> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2004 by Richard Smith <richard@metafoo.co.uk> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <kdebug.h> + +#include "kopetemessageevent.h" +#include "kopetemetacontact.h" +#include "kopetecontactlist.h" +#include "kopetecontact.h" +#include "kopeteprefs.h" + +namespace Kopete +{ + +class MessageEvent::Private +{ +public: + Kopete::Message message; + EventState state; +}; + +MessageEvent::MessageEvent( const Message& m, QObject *parent, const char *name ) + : QObject(parent,name), d( new Private ) +{ + d->message = m; + d->state = Nothing; + const Contact *c=m.from(); + if(c) + connect(c,SIGNAL(contactDestroyed( Kopete::Contact* )),this,SLOT(discard())); +} + +MessageEvent::~MessageEvent() +{ + kdDebug(14010) << k_funcinfo << endl; + emit done(this); + delete d; +} + +Kopete::Message MessageEvent::message() +{ + return d->message; +} + +void MessageEvent::setMessage( const Kopete::Message &message ) +{ + d->message = message; +} + +MessageEvent::EventState MessageEvent::state() +{ + return d->state; +} + +void MessageEvent::apply() +{ + kdDebug(14010) << k_funcinfo << endl; + d->state = Applied; + deleteLater(); +} + +void MessageEvent::ignore() +{ + // FIXME: this should be done by the contact list for itself. + if( d->message.from()->metaContact() && d->message.from()->metaContact()->isTemporary() && + KopetePrefs::prefs()->balloonNotifyIgnoreClosesChatView() ) + ContactList::self()->removeMetaContact( d->message.from()->metaContact() ); + d->state = Ignored; + deleteLater(); +} + +void MessageEvent::accept() +{ + emit accepted(this); +} + +void MessageEvent::discard() +{ + emit discarded(this); + delete this; +} + +} + +#include "kopetemessageevent.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetemessageevent.h b/kopete/libkopete/kopetemessageevent.h new file mode 100644 index 00000000..7beb1aa2 --- /dev/null +++ b/kopete/libkopete/kopetemessageevent.h @@ -0,0 +1,129 @@ +/* + kopetemessageevent.h - Kopete Message Event + + Copyright (c) 2003 by Olivier Goffart <ogoffart @ kde.org> + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002 by Hendrik vom Lehn <hvl@linux-4-ever.de> + Copyright (c) 2004 by Richard Smith <richard@metafoo.co.uk> + + Kopete (c) 2002 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEMESSAGEEVENT_H +#define KOPETEMESSAGEEVENT_H + +#include <qobject.h> + +#include "kopetemessage.h" + +#include "kopete_export.h" + +namespace Kopete +{ + +/** + * @author Olivier Goffart <ogoffart @ kde.org> + * @author Richard Smith <richard@metafoo.co.uk> + * + * Kopete::MessageEvent is used when a new messages arrives, it is + * caught by the UI. It contains just informations about + * the message, and a signal when it is terminated (i.e. + * the message is read + **/ +class KOPETE_EXPORT MessageEvent : public QObject +{ + Q_OBJECT + +public: + MessageEvent(const Kopete::Message& , QObject* parent=0L, const char *name=0L); + ~MessageEvent(); + + /** + * @return A copy of the message + */ + Kopete::Message message(); + + /** + * Sets the message contained in this event. + * @param message The new value for the message + */ + void setMessage( const Kopete::Message &message ); + + /** + * The state of the event. + * - @c Nothing means that the event has not been accepted or ignored + * - @c Applied if the event has been applied + * - @c Ignored if the event has been ignored + */ + enum EventState { Nothing , Applied , Ignored }; + + EventState state(); + +public slots: + /** + * @deprecated Use accept() instead to continue the processing of this event once the caller has moved to using MessageHandlers + * + * execute the event + */ + void apply(); + + /** + * @deprecated Use discard() instead to destroy this event once the caller has moved to using MessageHandlers + * + * ignore the event + */ + void ignore(); + + /** + * @brief Passes the event to the next handler + * + * Call this when you've finished processing this event + */ + void accept(); + + /** + * @brief Discards the event + * + * If this event should not be processed any further, this function + * should be called to discard it. + */ + void discard(); + +signals: + /** + * The event has been processed + */ + void done(Kopete::MessageEvent *); + + /** + * The event has been discarded. + * @param event The event sending the signal. + */ + void discarded(Kopete::MessageEvent *event); + + /** + * The event has been accepted by its current handler. + * @param event The event sending the signal. + */ + void accepted(Kopete::MessageEvent *event); + +private: + class Private; + Private *d; +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetemessagehandler.cpp b/kopete/libkopete/kopetemessagehandler.cpp new file mode 100644 index 00000000..89628d4f --- /dev/null +++ b/kopete/libkopete/kopetemessagehandler.cpp @@ -0,0 +1,111 @@ +/* + kopetemessagefilter.cpp - Kopete Message Filtering + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetemessagehandler.h" +#include "kopetemessageevent.h" + +#include <kstaticdeleter.h> + +namespace Kopete +{ + +class MessageHandler::Private +{ +public: + Private() : next(0) {} + MessageHandler *next; +}; + +MessageHandler::MessageHandler() + : QObject( 0 ), d( new Private ) +{ +} + +MessageHandler::~MessageHandler() +{ + delete d; +} + +MessageHandler *MessageHandler::next() +{ + return d->next; +} + +void MessageHandler::setNext( MessageHandler *next ) +{ + d->next = next; +} + +int MessageHandler::capabilities() +{ + return d->next->capabilities(); +} + +void MessageHandler::handleMessageInternal( MessageEvent *event ) +{ + connect( event, SIGNAL( accepted(Kopete::MessageEvent*) ), this, SLOT( messageAccepted(Kopete::MessageEvent*) ) ); + handleMessage( event ); +} + +void MessageHandler::handleMessage( MessageEvent *event ) +{ + messageAccepted( event ); +} + +void MessageHandler::messageAccepted( MessageEvent *event ) +{ + disconnect( event, SIGNAL( accepted(Kopete::MessageEvent*) ), this, SLOT( messageAccepted(Kopete::MessageEvent*) ) ); + d->next->handleMessageInternal( event ); +} + + +class MessageHandlerFactory::Private +{ +public: + static FactoryList &factories(); +}; + +MessageHandlerFactory::FactoryList &MessageHandlerFactory::Private::factories() +{ + static KStaticDeleter<FactoryList> deleter; + static FactoryList *list = 0; + if( !list ) + deleter.setObject( list, new FactoryList ); + return *list; +} + +MessageHandlerFactory::MessageHandlerFactory() + : d( new Private ) +{ + Private::factories().append(this); +} + +MessageHandlerFactory::~MessageHandlerFactory() +{ + Private::factories().remove( this ); + delete d; +} + +MessageHandlerFactory::FactoryList MessageHandlerFactory::messageHandlerFactories() +{ + return Private::factories(); +} + +} + +#include "kopetemessagehandler.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemessagehandler.h b/kopete/libkopete/kopetemessagehandler.h new file mode 100644 index 00000000..ba16184c --- /dev/null +++ b/kopete/libkopete/kopetemessagehandler.h @@ -0,0 +1,225 @@ +/* + kopetemessagehandler.h - Kopete Message Filtering + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEMESSAGEHANDLER_H +#define KOPETEMESSAGEHANDLER_H + +#include <qobject.h> +//#include <kdemacros.h> +#include "kopete_export.h" + +//FIXME: Message::MessageDirection could be moved into namespace Kopete +// to avoid this being included everywhere +#include "kopetemessage.h" + +#include <qvaluelist.h> + +namespace Kopete +{ + +class MessageEvent; +class ChatSession; + +/** + * @author Richard Smith <kde@metafoo.co.uk> + * + * An object which sits between the protocol and the chat window which + * intercepts and processes messages on their way through. + * + * This class implements Handler role in the Chain of Responsibility pattern. + * The Client role will be filled by the Kopete::MessageHandlerChain class. + */ +class KOPETE_EXPORT MessageHandler : public QObject +{ + Q_OBJECT +public: + MessageHandler(); + virtual ~MessageHandler() = 0; + + /** + * @return the next handler in the chain + */ + MessageHandler *next(); + // FIXME: remove? + void setNext( MessageHandler *next ); + + /** + * @brief Gets the rich-text capabilities of this message handling object + * + * The default implementation returns next()->capabilities(). + */ + virtual int capabilities(); + + /** + * @brief Performs any processing necessary on the message + * + * @param event The message event to process. Should not be null. + * + * Overriders of this handler @em must cause (possibly asynchronously) + * one of the following to happen: + * - @p event->discard() to be called + * - @p event->continue() to be called + * - this base class implementation to be called (equivalent to event->continue() but faster) + * + * The base class implementation passes the event on to the next + * handler in the chain. + * + * @note If you store @p event, be aware that it could be deleted at any time, and either + * connect to the its discarded(Kopete::MessageEvent*) signal or store it in a QGuardedPtr. + */ + virtual void handleMessage( MessageEvent *event ); + + /** @internal */ + void handleMessageInternal( MessageEvent *event ); +private slots: + /** + * @internal The message has been accepted. Pass it on to the next handler. + */ + void messageAccepted( Kopete::MessageEvent *event ); + +private: + class Private; + Private *d; +}; + +/** + * @author Richard Smith <kde@metafoo.co.uk> + * + * A factory for creating MessageHandlers. Instantiate a class derived from MessageHandlerFactory + * in order to make your MessageHandler be automatically added to the list of handlers used + * when constructing handler chains. + * + * @note If you construct a handler for an Inbound chain, it may still be asked to process Outbound + * messages. This is because when a message is being sent it first passes through the Outbound + * chain to the protocol, then (when it has been delivered) it passes back through the Inbound + * chain to the chat window to be displayed. + */ +class KOPETE_EXPORT MessageHandlerFactory +{ +public: + /** + * Constructs a MessageHandlerFactory, and adds it to the list of factories considered when + * creating a MessageHandlerChain for a ChatSession. + * + * @note Since the factory is added to the list of possible factories before the object is + * finished being constructed, it is not safe to call any function from a derived class's + * constructor which may cause a MessageHandlerChain to be created. + */ + MessageHandlerFactory(); + /** + * Destroys the MessageHandlerFactory and removes it from the list of factories. + */ + virtual ~MessageHandlerFactory(); + + typedef QValueList<MessageHandlerFactory*> FactoryList; + /** + * @return the list of registered message handler factories + */ + static FactoryList messageHandlerFactories(); + + /** + * @brief Creates a message handler for a given manager in a given direction. + * @param manager The manager whose message handler chain the message handler is for + * @param direction The direction of the chain that is being created. + * @return the @ref MessageHandler object to put in the chain, or 0 if none is needed. + */ + virtual MessageHandler *create( ChatSession *manager, Message::MessageDirection direction ) = 0; + + /** + * Special stages usable with any message direction + */ + enum SpecialStage + { + StageDoNotCreate = -10000, ///< do not create a filter for this stage + StageStart = 0, ///< start of processing + StageEnd = 10000 ///< end of processing + }; + + /** + * Processing stages for handlers in inbound message handler chains + */ + enum InboundStage + { + InStageStart = 0, ///< message was just received + InStageToSent = 2000, ///< convert from received format to sent format + InStageToDesired = 5000, ///< convert to how the user wants the message + InStageFormat = 7000, ///< decorate the message without changing the content + InStageEnd = 10000 ///< message ready for display + }; + + /** + * Processing stages for handlers in outbound message handler chains + */ + enum OutboundStage + { + OutStageStart = 0, ///< user just hit Send + OutStageParse = 2000, ///< process commands + OutStageToDesired = 4000, ///< convert to how the user wanted to send + OutStageFormat = 6000, ///< decorate the message without changing the content + OutStageToSent = 8000, ///< convert to the format to send in + OutStageEnd = 10000 ///< message ready for sending + }; + + /** + * Processing stages for handlers in internal message handler chains + */ + enum InternalStage + { + IntStageStart = 0, ///< some component just created the message + IntStageEnd = 10000 ///< message ready for display + }; + + /** + * Offsets within a processing stage. Using these values allows finer + * control over where in a chain a message handler will be added. Add + * one of these values to values from the various Stage enumerations + * to form a filter position. + */ + enum Offset + { + OffsetBefore = -90, + OffsetVeryEarly = -60, + OffsetEarly = -30, + OffsetNormal = 0, + OffsetLate = 30, + OffsetVeryLate = 60, + OffsetAfter = 90 + }; + + /** + * @brief Returns the position in the message handler chain to put this factory's handlers + * @param manager The manager whose message handler chain the message handler is for + * @param direction The direction of the chain that is being created. + * @return a member of the InboundStage, OutboundStage or InternalStage enumeration, as + * appropriate, optionally combined with a member of the Offset enumeration. + * @retval StageDoNotCreate No filter should be created for this chain. + */ + virtual int filterPosition( ChatSession *manager, Message::MessageDirection direction ) = 0; + +private: + // noncopyable + MessageHandlerFactory(const MessageHandlerFactory &); + void operator=(const MessageHandlerFactory &); + + class Private; + Private *d; +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemessagehandlerchain.cpp b/kopete/libkopete/kopetemessagehandlerchain.cpp new file mode 100644 index 00000000..fe1e96ab --- /dev/null +++ b/kopete/libkopete/kopetemessagehandlerchain.cpp @@ -0,0 +1,186 @@ +/* + kopetemessagehandlerchain.h - Kopete Message Handler Chain + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetemessagehandlerchain.h" +#include "kopetemessagehandler.h" +#include "kopetemessageevent.h" +#include "kopetechatsession.h" + +#include <kdebug.h> + +#include <qmap.h> +#include <qtimer.h> +#include <qvaluelist.h> + +namespace Kopete +{ + +class MessageHandlerChainTerminator : public MessageHandler +{ +public: + void handleMessage( MessageEvent *event ) + { + kdError( 14010 ) << k_funcinfo << "message got to end of chain!" << endl; + event->discard(); + } + int capabilities() + { + kdError( 14010 ) << k_funcinfo << "request got to end of chain!" << endl; + return 0; + } +}; + +// BEGIN MessageHandlerChain + +class MessageHandlerChain::Private +{ +public: + Private() : first(0) {} + MessageHandler *first; +}; + +MessageHandlerChain::Ptr MessageHandlerChain::create( ChatSession *manager, Message::MessageDirection direction ) +{ + // create the handler chain + MessageHandlerChain *chain = new MessageHandlerChain; + + // grab the list of handler factories + typedef MessageHandlerFactory::FactoryList FactoryList; + FactoryList factories = MessageHandlerFactory::messageHandlerFactories(); + + // create a sorted list of handlers + typedef QValueList<MessageHandler*> HandlerList; + typedef QMap<int,HandlerList> HandlerMap; + HandlerMap handlers; + uint count = 0; + for( FactoryList::Iterator it = factories.begin(); it != factories.end(); ++it ) + { + int position = (*it)->filterPosition( manager, direction ); + if ( position == MessageHandlerFactory::StageDoNotCreate ) + continue; + MessageHandler *handler = (*it)->create( manager, direction ); + if ( handler ) + { + ++count; + handlers[ position ].append( handler ); + } + } + + kdDebug(14010) << k_funcinfo << "got " << count << " handlers for chain" << endl; + + // add the handlers to the chain + MessageHandler *curr = 0; + for( HandlerMap::Iterator it = handlers.begin(); it != handlers.end(); ++it ) + { + for ( HandlerList::Iterator handlerIt = (*it).begin(); handlerIt != (*it).end(); ++handlerIt ) + { + if ( curr ) + curr->setNext( *handlerIt ); + else + chain->d->first = *handlerIt; + curr = *handlerIt; + } + } + + // add a terminator to avoid crashes if the message somehow manages to get to the + // end of the chain. maybe we should use a MessageHandlerFactory for this too? + MessageHandler *terminator = new MessageHandlerChainTerminator; + if ( curr ) + curr->setNext( terminator ); + else // empty chain: might happen for dir == Internal + chain->d->first = terminator; + + return chain; +} + +MessageHandlerChain::MessageHandlerChain() + : QObject( 0 ), d( new Private ) +{ +} + +MessageHandlerChain::~MessageHandlerChain() +{ + kdDebug(14010) << k_funcinfo << endl; + MessageHandler *handler = d->first; + while( handler ) + { + MessageHandler *next = handler->next(); + delete handler; + handler = next; + } + delete d; +} + + +ProcessMessageTask *MessageHandlerChain::processMessage( const Message &message ) +{ + MessageEvent *event = new MessageEvent( message ); + return new ProcessMessageTask( this, event ); +} + +int MessageHandlerChain::capabilities() +{ + return d->first->capabilities(); +} + +// END MessageHandlerChain + +// BEGIN ProcessMessageTask + +class ProcessMessageTask::Private +{ +public: + Private( MessageHandlerChain::Ptr chain, MessageEvent *event ) : chain(chain), event(event) {} + MessageHandlerChain::Ptr chain; + MessageEvent *event; +}; + +ProcessMessageTask::ProcessMessageTask( MessageHandlerChain::Ptr chain, MessageEvent *event ) + : d( new Private(chain, event) ) +{ + QTimer::singleShot( 0, this, SLOT( slotStart() ) ); + connect( event, SIGNAL( done( Kopete::MessageEvent* ) ), this, SLOT( slotDone() ) ); + event->message().manager()->ref(); +} + +ProcessMessageTask::~ProcessMessageTask() +{ + delete d; +} + +void ProcessMessageTask::slotStart() +{ + d->chain->d->first->handleMessageInternal( d->event ); +} + +void ProcessMessageTask::slotDone() +{ + d->event->message().manager()->deref(); + emitResult(); +} + +MessageEvent *ProcessMessageTask::event() +{ + return d->event; +} + +//END ProcessMessageTask + +} + +#include "kopetemessagehandlerchain.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemessagehandlerchain.h b/kopete/libkopete/kopetemessagehandlerchain.h new file mode 100644 index 00000000..5852c2da --- /dev/null +++ b/kopete/libkopete/kopetemessagehandlerchain.h @@ -0,0 +1,96 @@ +/* + kopetefilterchain.h - Kopete Message Filter Chain + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEFILTERCHAIN_H +#define KOPETEFILTERCHAIN_H + +#include <qobject.h> +#include <kdemacros.h> +#include <ksharedptr.h> +#include "kopetemessage.h" +#include "kopetetask.h" + +namespace Kopete +{ + +class MessageEvent; +class MessageHandler; +class ProcessMessageTask; + +/** + * @brief A chain of message handlers; the processing layer between protocol and chat view + * + * This class represents a chain of connected message handlers. + * + * This class is the client of the chain of responsibility formed by the + * MessageHandlers, and acts as a facade for that chain, presenting a + * more convenient interface. + * + * @author Richard Smith <kde@metafoo.co.uk> + */ +class MessageHandlerChain : public QObject, private KShared +{ + Q_OBJECT +public: + friend class KSharedPtr<MessageHandlerChain>; + typedef KSharedPtr<MessageHandlerChain> Ptr; + + /** + * Create a new MessageHandlerChain object with the appropriate handlers for + * processing messages entering @p manager in direction @p direction. + */ + static Ptr create( ChatSession *manager, Message::MessageDirection direction ); + + ProcessMessageTask *processMessage( const Message &message ); + int capabilities(); + +private: + MessageHandlerChain(); + ~MessageHandlerChain(); + + friend class ProcessMessageTask; + class Private; + Private *d; +}; + +/** + * @brief A task for processing a message + * @author Richard Smith <kde@metafoo.co.uk> + */ +class ProcessMessageTask : public Task +{ + Q_OBJECT +public: + MessageEvent *event(); + +private slots: + void slotStart(); + void slotDone(); + +private: + ProcessMessageTask(MessageHandlerChain::Ptr, MessageEvent *event); + ~ProcessMessageTask(); + + friend class MessageHandlerChain; + class Private; + Private *d; +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemessagemanager.h b/kopete/libkopete/kopetemessagemanager.h new file mode 100644 index 00000000..a07fb6f9 --- /dev/null +++ b/kopete/libkopete/kopetemessagemanager.h @@ -0,0 +1,3 @@ +#warning kopetemessagemanager.h has been renamed to kopetechatsession.h +#include "kopetechatsession.h" + diff --git a/kopete/libkopete/kopetemessagemanagerfactory.h b/kopete/libkopete/kopetemessagemanagerfactory.h new file mode 100644 index 00000000..9ebdaa98 --- /dev/null +++ b/kopete/libkopete/kopetemessagemanagerfactory.h @@ -0,0 +1,3 @@ +#warning kopetemessagemanagerfactory.h has been renamed to kopetechatsessionmanager.h +#include "kopetechatsessionmanager.h" + diff --git a/kopete/libkopete/kopetemetacontact.cpp b/kopete/libkopete/kopetemetacontact.cpp new file mode 100644 index 00000000..e181f52e --- /dev/null +++ b/kopete/libkopete/kopetemetacontact.cpp @@ -0,0 +1,1442 @@ +/* + kopetemetacontact.cpp - Kopete Meta Contact + + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2005 by Olivier Goffart <ogoffart@ kde.org> + Copyright (c) 2002-2004 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2005 by Michaël Larouche <michael.larouche@kdemail.net> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetemetacontact.h" + +#include <kapplication.h> + +#include <kabc/addressbook.h> +#include <kabc/addressee.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kdeversion.h> + +#include "kabcpersistence.h" +#include "kopetecontactlist.h" +#include "kopetecontact.h" +#include "kopeteaccountmanager.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopetepluginmanager.h" +#include "kopetegroup.h" +#include "kopeteglobal.h" +#include "kopeteprefs.h" +#include "kopeteuiglobal.h" +#include "kopetepicture.h" + +namespace Kopete { + +// this is just to save typing +const QString NSCID_ELEM = QString::fromUtf8("nameSourceContactId" ); +const QString NSPID_ELEM = QString::fromUtf8( "nameSourcePluginId" ); +const QString NSAID_ELEM = QString::fromUtf8( "nameSourceAccountId" ); +const QString PSCID_ELEM = QString::fromUtf8( "photoSourceContactId" ); +const QString PSPID_ELEM = QString::fromUtf8( "photoSourcePluginId" ); +const QString PSAID_ELEM = QString::fromUtf8( "photoSourceAccountId" ); + +class MetaContact::Private +{ public: + Private() : + photoSource(MetaContact::SourceCustom), displayNameSource(MetaContact::SourceCustom), + displayNameSourceContact(0L), photoSourceContact(0L), temporary(false), + onlineStatus(Kopete::OnlineStatus::Offline), photoSyncedWithKABC(false) + {} + + ~Private() + {} + + QPtrList<Contact> contacts; + + // property sources + PropertySource photoSource; + PropertySource displayNameSource; + + // when source is contact + Contact *displayNameSourceContact; + Contact *photoSourceContact; + + // used when source is kabc + QString metaContactId; + + // used when source is custom + QString displayName; + KURL photoUrl; + + QPtrList<Group> groups; + QMap<QString, QMap<QString, QString> > addressBook; + bool temporary; + + OnlineStatus::StatusType onlineStatus; + bool photoSyncedWithKABC; + + // Used to set contact source at load. + QString nameSourcePID, nameSourceAID, nameSourceCID; + QString photoSourcePID, photoSourceAID, photoSourceCID; + + // The photo cache. Reduce disk access and CPU usage. + Picture customPicture, contactPicture, kabcPicture; +}; + +MetaContact::MetaContact() + : ContactListElement( ContactList::self() ) +{ + d = new Private; + + connect( this, SIGNAL( pluginDataChanged() ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( iconChanged( Kopete::ContactListElement::IconState, const QString & ) ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( useCustomIconChanged( bool ) ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( displayNameChanged( const QString &, const QString & ) ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( movedToGroup( Kopete::MetaContact *, Kopete::Group *, Kopete::Group * ) ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( removedFromGroup( Kopete::MetaContact *, Kopete::Group * ) ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( addedToGroup( Kopete::MetaContact *, Kopete::Group * ) ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( contactAdded( Kopete::Contact * ) ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( contactRemoved( Kopete::Contact * ) ), SIGNAL( persistentDataChanged() ) ); + + // Update the KABC picture when the KDE Address book change. + connect(KABCPersistence::self()->addressBook(), SIGNAL(addressBookChanged(AddressBook *)), this, SLOT(slotUpdateAddressBookPicture())); + + // make sure MetaContact is at least in one group + addToGroup( Group::topLevel() ); + //i'm not sure this is correct -Olivier + // we probably should do the check in groups() instead +} + +MetaContact::~MetaContact() +{ + delete d; +} + +void MetaContact::addContact( Contact *c ) +{ + if( d->contacts.contains( c ) ) + { + kdWarning(14010) << "Ignoring attempt to add duplicate contact " << c->contactId() << "!" << endl; + } + else + { + d->contacts.append( c ); + + connect( c, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + SLOT( slotContactStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) ); + + connect( c, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), + this, SLOT( slotPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ) ) ; + + connect( c, SIGNAL( contactDestroyed( Kopete::Contact * ) ), + this, SLOT( slotContactDestroyed( Kopete::Contact * ) ) ); + + connect( c, SIGNAL( idleStateChanged( Kopete::Contact * ) ), + this, SIGNAL( contactIdleStateChanged( Kopete::Contact * ) ) ); + + emit contactAdded(c); + + updateOnlineStatus(); + + // if this is the first contact, probbaly was created by a protocol + // so it has empty custom properties, then set sources to the contact + if ( d->contacts.count() == 1 ) + { + if ( displayName().isEmpty() ) + { + setDisplayNameSourceContact(c); + setDisplayNameSource(SourceContact); + } + if ( photo().isNull() ) + { + setPhotoSourceContact(c); + setPhotoSource(SourceContact); + } + } + } +} + +void MetaContact::updateOnlineStatus() +{ + Kopete::OnlineStatus::StatusType newStatus = Kopete::OnlineStatus::Unknown; + Kopete::OnlineStatus mostSignificantStatus; + + for ( QPtrListIterator<Contact> it( d->contacts ); it.current(); ++it ) + { + // find most significant status + if ( it.current()->onlineStatus() > mostSignificantStatus ) + mostSignificantStatus = it.current()->onlineStatus(); + } + + newStatus = mostSignificantStatus.status(); + + if( newStatus != d->onlineStatus ) + { + d->onlineStatus = newStatus; + emit onlineStatusChanged( this, d->onlineStatus ); + } +} + + +void MetaContact::removeContact(Contact *c, bool deleted) +{ + if( !d->contacts.contains( c ) ) + { + kdDebug(14010) << k_funcinfo << " Contact is not in this metaContact " << endl; + } + else + { + // must check before removing, or will always be false + bool wasTrackingName = ( !displayNameSourceContact() && (displayNameSource() == SourceContact) ); + bool wasTrackingPhoto = ( !photoSourceContact() && (photoSource() == SourceContact) ); + // save for later use + QString currDisplayName = displayName(); + + d->contacts.remove( c ); + + // if the contact was a source of property data, clean + if (displayNameSourceContact() == c) + setDisplayNameSourceContact(0L); + if (photoSourceContact() == c) + setPhotoSourceContact(0L); + + + if ( wasTrackingName ) + { + // Oh! this contact was the source for the metacontact's name + // lets do something + // is this the only contact? + if ( d->contacts.isEmpty() ) + { + // fallback to a custom name as we don't have + // more contacts to chose as source. + setDisplayNameSource(SourceCustom); + // perhaps the custom display name was empty + // no problems baby, I saved the old one. + setDisplayName(currDisplayName); + } + else + { + // we didn't fallback to SourceCustom above so lets use the next + // contact as source + setDisplayNameSourceContact( d->contacts.first() ); + } + } + + if ( wasTrackingPhoto ) + { + // Oh! this contact was the source for the metacontact's photo + // lets do something + // is this the only contact? + if ( d->contacts.isEmpty() ) + { + // fallback to a custom photo as we don't have + // more contacts to chose as source. + setPhotoSource(SourceCustom); + // FIXME set the custom photo + } + else + { + // we didn't fallback to SourceCustom above so lets use the next + // contact as source + setPhotoSourceContact( d->contacts.first() ); + } + } + + if(!deleted) + { //If this function is tell by slotContactRemoved, c is maybe just a QObject + disconnect( c, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT( slotContactStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) ); + disconnect( c, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), + this, SLOT( slotPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ) ) ; + disconnect( c, SIGNAL( contactDestroyed( Kopete::Contact * ) ), + this, SLOT( slotContactDestroyed( Kopete::Contact * ) ) ); + disconnect( c, SIGNAL( idleStateChanged( Kopete::Contact * ) ), + this, SIGNAL( contactIdleStateChanged( Kopete::Contact *) ) ); + + kdDebug( 14010 ) << k_funcinfo << "Contact disconnected" << endl; + + KABCPersistence::self()->write( this ); + } + + // Reparent the contact + removeChild( c ); + + emit contactRemoved( c ); + } + updateOnlineStatus(); +} + +Contact *MetaContact::findContact( const QString &protocolId, const QString &accountId, const QString &contactId ) +{ + //kdDebug( 14010 ) << k_funcinfo << "Num contacts: " << d->contacts.count() << endl; + QPtrListIterator<Contact> it( d->contacts ); + for( ; it.current(); ++it ) + { + //kdDebug( 14010 ) << k_funcinfo << "Trying " << it.current()->contactId() << ", proto " + //<< it.current()->protocol()->pluginId() << ", account " << it.current()->accountId() << endl; + if( ( it.current()->contactId() == contactId ) && ( it.current()->protocol()->pluginId() == protocolId || protocolId.isNull() ) ) + { + if ( accountId.isNull() ) + return it.current(); + + if(it.current()->account()) + { + if(it.current()->account()->accountId() == accountId) + return it.current(); + } + } + } + + // Contact not found + return 0L; +} + +void MetaContact::setDisplayNameSource(PropertySource source) +{ + QString oldName = displayName(); + d->displayNameSource = source; + QString newName = displayName(); + if ( oldName != newName) + emit displayNameChanged( oldName, newName ); +} + +MetaContact::PropertySource MetaContact::displayNameSource() const +{ + return d->displayNameSource; +} + +void MetaContact::setPhotoSource(PropertySource source) +{ + PropertySource oldSource = photoSource(); + d->photoSource = source; + if ( source != oldSource ) + { + emit photoChanged(); + } +} + +MetaContact::PropertySource MetaContact::photoSource() const +{ + return d->photoSource; +} + + +Contact *MetaContact::sendMessage() +{ + Contact *c = preferredContact(); + + if( !c ) + { + KMessageBox::queuedMessageBox( UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "This user is not reachable at the moment. Please make sure you are connected and using a protocol that supports offline sending, or wait " + "until this user comes online." ), i18n( "User is Not Reachable" ) ); + } + else + { + c->sendMessage(); + return c; + } + return 0L; +} + +Contact *MetaContact::startChat() +{ + Contact *c = preferredContact(); + + if( !c ) + { + KMessageBox::queuedMessageBox( UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "This user is not reachable at the moment. Please make sure you are connected and using a protocol that supports offline sending, or wait " + "until this user comes online." ), i18n( "User is Not Reachable" ) ); + } + else + { + c->startChat(); + return c; + } + return 0L; +} + +Contact *MetaContact::preferredContact() +{ + /* + This function will determine what contact will be used to reach the contact. + + The prefered contact is choose with the following criterias: (in that order) + 1) If a contact was an open chatwindow already, we will use that one. + 2) The contact with the better online status is used. But if that + contact is not reachable, we prefer return no contact. + 3) If all the criterias aboxe still gives ex-eaquo, we use the preffered + account as selected in the account preferances (with the arrows) + */ + + Contact *contact = 0; + bool hasOpenView=false; //has the selected contact already an open chatwindow + for ( QPtrListIterator<Contact> it( d->contacts ); it.current(); ++it ) + { + Contact *c=it.current(); + + //Does the contact an open chatwindow? + if( c->manager( Contact::CannotCreate ) ) + { //no need to check the view. having a manager is enough + if( !hasOpenView ) + { + contact=c; + hasOpenView=true; + if( c->isReachable() ) + continue; + } //else, several contact might have an open view, uses following criterias + } + else if( hasOpenView && contact->isReachable() ) + continue; //This contact has not open view, but the selected contact has, and is reachable + + // FIXME: The isConnected call should be handled in Contact::isReachable + // after KDE 3.2 - Martijn + if ( !c->account() || !c->account()->isConnected() || !c->isReachable() ) + continue; //if this contact is not reachable, we ignore it. + + if ( !contact ) + { //this is the first contact. + contact= c; + continue; + } + + if( c->onlineStatus().status() > contact->onlineStatus().status() ) + contact=c; //this contact has a better status + else if ( c->onlineStatus().status() == contact->onlineStatus().status() ) + { + if( c->account()->priority() > contact->account()->priority() ) + contact=c; + else if( c->account()->priority() == contact->account()->priority() + && c->onlineStatus().weight() > contact->onlineStatus().weight() ) + contact = c; //the weight is not supposed to follow the same scale for each protocol + } + } + return contact; +} + +Contact *MetaContact::execute() +{ + Contact *c = preferredContact(); + + if( !c ) + { + KMessageBox::queuedMessageBox( UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "This user is not reachable at the moment. Please make sure you are connected and using a protocol that supports offline sending, or wait " + "until this user comes online." ), i18n( "User is Not Reachable" ) ); + } + else + { + c->execute(); + return c; + } + + return 0L; +} + +unsigned long int MetaContact::idleTime() const +{ + unsigned long int time = 0; + QPtrListIterator<Contact> it( d->contacts ); + for( ; it.current(); ++it ) + { + unsigned long int i = it.current()->idleTime(); + if( it.current()->isOnline() && i < time || time == 0 ) + { + time = i; + } + } + return time; +} + +QString MetaContact::statusIcon() const +{ + switch( status() ) + { + case OnlineStatus::Online: + if( useCustomIcon() ) + return icon( ContactListElement::Online ); + else + return QString::fromUtf8( "metacontact_online" ); + case OnlineStatus::Away: + if( useCustomIcon() ) + return icon( ContactListElement::Away ); + else + return QString::fromUtf8( "metacontact_away" ); + + case OnlineStatus::Unknown: + if( useCustomIcon() ) + return icon( ContactListElement::Unknown ); + if ( d->contacts.isEmpty() ) + return QString::fromUtf8( "metacontact_unknown" ); + else + return QString::fromUtf8( "metacontact_offline" ); + + case OnlineStatus::Offline: + default: + if( useCustomIcon() ) + return icon( ContactListElement::Offline ); + else + return QString::fromUtf8( "metacontact_offline" ); + } +} + +QString MetaContact::statusString() const +{ + switch( status() ) + { + case OnlineStatus::Online: + return i18n( "Online" ); + case OnlineStatus::Away: + return i18n( "Away" ); + case OnlineStatus::Offline: + return i18n( "Offline" ); + case OnlineStatus::Unknown: + default: + return i18n( "Status not available" ); + } +} + +OnlineStatus::StatusType MetaContact::status() const +{ + return d->onlineStatus; +} + +bool MetaContact::isOnline() const +{ + QPtrListIterator<Contact> it( d->contacts ); + for( ; it.current(); ++it ) + { + if( it.current()->isOnline() ) + return true; + } + return false; +} + +bool MetaContact::isReachable() const +{ + if ( isOnline() ) + return true; + + for ( QPtrListIterator<Contact> it( d->contacts ); it.current(); ++it ) + { + if ( it.current()->account()->isConnected() && it.current()->isReachable() ) + return true; + } + return false; +} + +//Determine if we are capable of accepting file transfers +bool MetaContact::canAcceptFiles() const +{ + if( !isOnline() ) + return false; + + QPtrListIterator<Contact> it( d->contacts ); + for( ; it.current(); ++it ) + { + if( it.current()->canAcceptFiles() ) + return true; + } + return false; +} + +//Slot for sending files +void MetaContact::sendFile( const KURL &sourceURL, const QString &altFileName, unsigned long fileSize ) +{ + //If we can't send any files then exit + if( d->contacts.isEmpty() || !canAcceptFiles() ) + return; + + //Find the highest ranked protocol that can accept files + Contact *contact = d->contacts.first(); + for( QPtrListIterator<Contact> it( d->contacts ) ; it.current(); ++it ) + { + if( ( *it )->onlineStatus() > contact->onlineStatus() && ( *it )->canAcceptFiles() ) + contact = *it; + } + + //Call the sendFile slot of this protocol + contact->sendFile( sourceURL, altFileName, fileSize ); +} + + +void MetaContact::slotContactStatusChanged( Contact * c, const OnlineStatus &status, const OnlineStatus &/*oldstatus*/ ) +{ + updateOnlineStatus(); + emit contactStatusChanged( c, status ); +} + +void MetaContact::setDisplayName( const QString &name ) +{ + /*kdDebug( 14010 ) << k_funcinfo << "Change displayName from " << d->displayName << + " to " << name << ", d->trackChildNameChanges=" << d->trackChildNameChanges << endl; + kdDebug(14010) << kdBacktrace(6) << endl;*/ + + if( name == d->displayName ) + return; + + const QString old = d->displayName; + d->displayName = name; + + emit displayNameChanged( old , name ); + + for( QPtrListIterator<Kopete::Contact> it( d->contacts ) ; it.current(); ++it ) + ( *it )->sync(Contact::DisplayNameChanged); + +} + +QString MetaContact::customDisplayName() const +{ + return d->displayName; +} + +QString MetaContact::displayName() const +{ + PropertySource source = displayNameSource(); + if ( source == SourceKABC ) + { + // kabc source, try to get from addressbook + // if the metacontact has a kabc association + if ( !metaContactId().isEmpty() ) + return nameFromKABC(metaContactId()); + } + else if ( source == SourceContact ) + { + if ( d->displayNameSourceContact==0 ) + { + if( d->contacts.count() >= 1 ) + {// don't call setDisplayNameSource , or there will probably be an infinite loop + d->displayNameSourceContact=d->contacts.first(); +// kdDebug( 14010 ) << k_funcinfo << " setting displayname source for " << metaContactId() << endl; + } + } + if ( displayNameSourceContact() != 0L ) + { + return nameFromContact(displayNameSourceContact()); + } + else + { +// kdDebug( 14010 ) << k_funcinfo << " source == SourceContact , but there is no displayNameSourceContact for contact " << metaContactId() << endl; + } + } + return d->displayName; +} + +QString nameFromKABC( const QString &id ) /*const*/ +{ + KABC::AddressBook* ab = KABCPersistence::self()->addressBook(); + if ( ! id.isEmpty() && !id.contains(':') ) + { + KABC::Addressee theAddressee = ab->findByUid(id); + if ( theAddressee.isEmpty() ) + { + kdDebug( 14010 ) << k_funcinfo << "no KABC::Addressee found for ( " << id << " ) " << " in current address book" << endl; + } + else + { + return theAddressee.formattedName(); + } + } + // no kabc association, return null image + return QString::null; +} + +QString nameFromContact( Kopete::Contact *c) /*const*/ +{ + if ( !c ) + return QString::null; + + QString contactName; + if ( c->hasProperty( Kopete::Global::Properties::self()->nickName().key() ) ) + contactName = c->property( Global::Properties::self()->nickName()).value().toString(); + + //the replace is there to workaround the Bug 95444 + return contactName.isEmpty() ? c->contactId() : contactName.replace('\n',QString::fromUtf8("")); +} + +KURL MetaContact::customPhoto() const +{ + return d->photoUrl; +} + +void MetaContact::setPhoto( const KURL &url ) +{ + d->photoUrl = url; + d->customPicture.setPicture(url.path()); + + if ( photoSource() == SourceCustom ) + { + emit photoChanged(); + } +} + +QImage MetaContact::photo() const +{ + if( picture().image().width() > 96 && picture().image().height() > 96 ) + { + kdDebug( 14010 ) << k_funcinfo << "Resizing image from " << picture().image().width() << " x " << picture().image().height() << endl; + return picture().image().smoothScale(96,96,QImage::ScaleMin); + } + else + return picture().image(); +} + +Picture &MetaContact::picture() const +{ + if ( photoSource() == SourceKABC ) + { + return d->kabcPicture; + } + else if ( photoSource() == SourceContact ) + { + return d->contactPicture; + } + + return d->customPicture; +} + +QImage MetaContact::photoFromCustom() const +{ + return d->customPicture.image(); +} + +QImage photoFromContact( Kopete::Contact *contact) /*const*/ +{ + if ( contact == 0L ) + return QImage(); + + QVariant photoProp; + if ( contact->hasProperty( Kopete::Global::Properties::self()->photo().key() ) ) + photoProp = contact->property( Kopete::Global::Properties::self()->photo().key() ).value(); + + QImage img; + if(photoProp.canCast( QVariant::Image )) + img=photoProp.toImage(); + else if(photoProp.canCast( QVariant::Pixmap )) + img=photoProp.toPixmap().convertToImage(); + else if(!photoProp.asString().isEmpty()) + { + img=QPixmap( photoProp.toString() ).convertToImage(); + } + return img; +} + +QImage photoFromKABC( const QString &id ) /*const*/ +{ + KABC::AddressBook* ab = KABCPersistence::self()->addressBook(); + if ( ! id.isEmpty() && !id.contains(':') ) + { + KABC::Addressee theAddressee = ab->findByUid(id); + if ( theAddressee.isEmpty() ) + { + kdDebug( 14010 ) << k_funcinfo << "no KABC::Addressee found for ( " << id << " ) " << " in current address book" << endl; + } + else + { + KABC::Picture pic = theAddressee.photo(); + if ( pic.data().isNull() && pic.url().isEmpty() ) + pic = theAddressee.logo(); + + if ( pic.isIntern()) + { + return pic.data(); + } + else + { + return QPixmap( pic.url() ).convertToImage(); + } + } + } + // no kabc association, return null image + return QImage(); +} + +Contact *MetaContact::displayNameSourceContact() const +{ + return d->displayNameSourceContact; +} + +Contact *MetaContact::photoSourceContact() const +{ + return d->photoSourceContact; +} + +void MetaContact::setDisplayNameSourceContact( Contact *contact ) +{ + Contact *old = d->displayNameSourceContact; + d->displayNameSourceContact = contact; + if ( displayNameSource() == SourceContact ) + { + emit displayNameChanged( nameFromContact(old), nameFromContact(contact)); + } +} + +void MetaContact::setPhotoSourceContact( Contact *contact ) +{ + d->photoSourceContact = contact; + + // Create a cache for the contact photo. + if(d->photoSourceContact != 0L) + { + QVariant photoProp; + if ( contact->hasProperty( Kopete::Global::Properties::self()->photo().key() ) ) + photoProp = contact->property( Kopete::Global::Properties::self()->photo().key() ).value(); + + if(photoProp.canCast( QVariant::Image )) + d->contactPicture.setPicture(photoProp.toImage()); + else if(photoProp.canCast( QVariant::Pixmap )) + d->contactPicture.setPicture(photoProp.toPixmap().convertToImage()); + else if(!photoProp.asString().isEmpty()) + { + d->contactPicture.setPicture(photoProp.toString()); + } + } + + if ( photoSource() == SourceContact ) + { + emit photoChanged(); + } +} + +void MetaContact::slotPropertyChanged( Contact* subcontact, const QString &key, + const QVariant &oldValue, const QVariant &newValue ) +{ + if ( displayNameSource() == SourceContact ) + { + if( key == Global::Properties::self()->nickName().key() ) + { + if (displayNameSourceContact() == subcontact) + { + emit displayNameChanged( oldValue.toString(), newValue.toString()); + } + else + { + // HACK the displayName that changed is not from the contact we are tracking, but + // as the current one is null, lets use this new one + if (displayName().isEmpty()) + setDisplayNameSourceContact(subcontact); + } + } + } + + if (photoSource() == SourceContact) + { + if ( key == Global::Properties::self()->photo().key() ) + { + if (photoSourceContact() != subcontact) + { + // HACK the displayName that changed is not from the contact we are tracking, but + // as the current one is null, lets use this new one + if (photo().isNull()) + setPhotoSourceContact(subcontact); + + } + else if(photoSourceContact() == subcontact) + { + if(d->photoSyncedWithKABC) + setPhotoSyncedWithKABC(true); + + setPhotoSourceContact(subcontact); + } + } + } +} + +void MetaContact::moveToGroup( Group *from, Group *to ) +{ + if ( !from || !groups().contains( from ) ) + { + // We're adding, not moving, because 'from' is illegal + addToGroup( to ); + return; + } + + if ( !to || groups().contains( to ) ) + { + // We're removing, not moving, because 'to' is illegal + removeFromGroup( from ); + return; + } + + if ( isTemporary() && to->type() != Group::Temporary ) + return; + + + //kdDebug( 14010 ) << k_funcinfo << from->displayName() << " => " << to->displayName() << endl; + + d->groups.remove( from ); + d->groups.append( to ); + + for( Contact *c = d->contacts.first(); c ; c = d->contacts.next() ) + c->sync(Contact::MovedBetweenGroup); + + emit movedToGroup( this, from, to ); +} + +void MetaContact::removeFromGroup( Group *group ) +{ + if ( !group || !groups().contains( group ) || ( isTemporary() && group->type() == Group::Temporary ) ) + { + return; + } + + d->groups.remove( group ); + + // make sure MetaContact is at least in one group + if ( d->groups.isEmpty() ) + { + d->groups.append( Group::topLevel() ); + emit addedToGroup( this, Group::topLevel() ); + } + + for( Contact *c = d->contacts.first(); c ; c = d->contacts.next() ) + c->sync(Contact::MovedBetweenGroup); + + emit removedFromGroup( this, group ); +} + +void MetaContact::addToGroup( Group *to ) +{ + if ( !to || groups().contains( to ) ) + return; + + if ( d->temporary && to->type() != Group::Temporary ) + return; + + if ( d->groups.contains( Group::topLevel() ) ) + { + d->groups.remove( Group::topLevel() ); + emit removedFromGroup( this, Group::topLevel() ); + } + + d->groups.append( to ); + + for( Contact *c = d->contacts.first(); c ; c = d->contacts.next() ) + c->sync(Contact::MovedBetweenGroup); + + emit addedToGroup( this, to ); +} + +QPtrList<Group> MetaContact::groups() const +{ + return d->groups; +} + +void MetaContact::slotContactDestroyed( Contact *contact ) +{ + removeContact(contact,true); +} + +const QDomElement MetaContact::toXML(bool minimal) +{ + // This causes each Kopete::Protocol subclass to serialise its contacts' data into the metacontact's plugin data and address book data + emit aboutToSave(this); + + QDomDocument metaContact; + metaContact.appendChild( metaContact.createElement( QString::fromUtf8( "meta-contact" ) ) ); + metaContact.documentElement().setAttribute( QString::fromUtf8( "contactId" ), metaContactId() ); + + // the custom display name, used for the custom name source + QDomElement displayName = metaContact.createElement( QString::fromUtf8("display-name" ) ); + displayName.appendChild( metaContact.createTextNode( d->displayName ) ); + metaContact.documentElement().appendChild( displayName ); + QDomElement photo = metaContact.createElement( QString::fromUtf8("photo" ) ); + KURL photoUrl = d->photoUrl; + photo.appendChild( metaContact.createTextNode( photoUrl.url() ) ); + metaContact.documentElement().appendChild( photo ); + + // Property sources + QDomElement propertySources = metaContact.createElement( QString::fromUtf8("property-sources" ) ); + QDomElement _nameSource = metaContact.createElement( QString::fromUtf8("name") ); + QDomElement _photoSource = metaContact.createElement( QString::fromUtf8("photo") ); + + // set the contact source for display name + _nameSource.setAttribute(QString::fromUtf8("source"), sourceToString(displayNameSource())); + + // set contact source metadata + if (displayNameSourceContact()) + { + QDomElement contactNameSource = metaContact.createElement( QString::fromUtf8("contact-source") ); + contactNameSource.setAttribute( NSCID_ELEM, displayNameSourceContact()->contactId() ); + contactNameSource.setAttribute( NSPID_ELEM, displayNameSourceContact()->protocol()->pluginId() ); + contactNameSource.setAttribute( NSAID_ELEM, displayNameSourceContact()->account()->accountId() ); + _nameSource.appendChild( contactNameSource ); + } + + // set the contact source for photo + _photoSource.setAttribute(QString::fromUtf8("source"), sourceToString(photoSource())); + + if( !d->metaContactId.isEmpty() ) + photo.setAttribute( QString::fromUtf8("syncWithKABC") , QString::fromUtf8( d->photoSyncedWithKABC ? "true" : "false" ) ); + + if (photoSourceContact()) + { + //kdDebug(14010) << k_funcinfo << "serializing photo source " << nameFromContact(photoSourceContact()) << endl; + // set contact source metadata for photo + QDomElement contactPhotoSource = metaContact.createElement( QString::fromUtf8("contact-source") ); + contactPhotoSource.setAttribute( NSCID_ELEM, photoSourceContact()->contactId() ); + contactPhotoSource.setAttribute( NSPID_ELEM, photoSourceContact()->protocol()->pluginId() ); + contactPhotoSource.setAttribute( NSAID_ELEM, photoSourceContact()->account()->accountId() ); + _photoSource.appendChild( contactPhotoSource ); + } + // apend name and photo sources to property sources + propertySources.appendChild(_nameSource); + propertySources.appendChild(_photoSource); + + metaContact.documentElement().appendChild(propertySources); + + // Don't store these information in minimal mode. + if(!minimal) + { + // Store groups + if ( !d->groups.isEmpty() ) + { + QDomElement groups = metaContact.createElement( QString::fromUtf8("groups") ); + Group *g; + for ( g = d->groups.first(); g; g = d->groups.next() ) + { + QDomElement group = metaContact.createElement( QString::fromUtf8("group") ); + group.setAttribute( QString::fromUtf8("id"), g->groupId() ); + groups.appendChild( group ); + } + metaContact.documentElement().appendChild( groups ); + } + + // Store other plugin data + QValueList<QDomElement> pluginData = Kopete::ContactListElement::toXML(); + for( QValueList<QDomElement>::Iterator it = pluginData.begin(); it != pluginData.end(); ++it ) + metaContact.documentElement().appendChild( metaContact.importNode( *it, true ) ); + + // Store custom notification data + QDomElement notifyData = NotifyDataObject::notifyDataToXML(); + if ( notifyData.hasChildNodes() ) + metaContact.documentElement().appendChild( metaContact.importNode( notifyData, true ) ); + } + return metaContact.documentElement(); +} + +bool MetaContact::fromXML( const QDomElement& element ) +{ + if( !element.hasChildNodes() ) + return false; + + bool oldPhotoTracking = false; + bool oldNameTracking = false; + + QString strContactId = element.attribute( QString::fromUtf8("contactId") ); + if( !strContactId.isEmpty() ) + { + d->metaContactId = strContactId; + // Set the KABC Picture + slotUpdateAddressBookPicture(); + } + + QDomElement contactElement = element.firstChild().toElement(); + while( !contactElement.isNull() ) + { + + if( contactElement.tagName() == QString::fromUtf8( "display-name" ) ) + { // custom display name, used for the custom name source + + // WTF, why were we not loading the metacontact if nickname was empty. + //if ( contactElement.text().isEmpty() ) + // return false; + + //the replace is there to workaround the Bug 95444 + d->displayName = contactElement.text().replace('\n',QString::fromUtf8("")); + + if ( contactElement.hasAttribute(NSCID_ELEM) && contactElement.hasAttribute(NSPID_ELEM) && contactElement.hasAttribute(NSAID_ELEM)) + { + oldNameTracking = true; + //kdDebug(14010) << k_funcinfo << "old name tracking" << endl; + // retrieve deprecated data (now stored in property-sources) + // save temporarely, we will find a Contact* with this later + d->nameSourceCID = contactElement.attribute( NSCID_ELEM ); + d->nameSourcePID = contactElement.attribute( NSPID_ELEM ); + d->nameSourceAID = contactElement.attribute( NSAID_ELEM ); + } +// else +// kdDebug(14010) << k_funcinfo << "no old name tracking" << endl; + } + else if( contactElement.tagName() == QString::fromUtf8( "photo" ) ) + { + // custom photo, used for custom photo source + setPhoto( KURL(contactElement.text()) ); + + d->photoSyncedWithKABC = (contactElement.attribute(QString::fromUtf8("syncWithKABC")) == QString::fromUtf8("1")) || (contactElement.attribute(QString::fromUtf8("syncWithKABC")) == QString::fromUtf8("true")); + + // retrieve deprecated data (now stored in property-sources) + // save temporarely, we will find a Contact* with this later + if ( contactElement.hasAttribute(PSCID_ELEM) && contactElement.hasAttribute(PSPID_ELEM) && contactElement.hasAttribute(PSAID_ELEM)) + { + oldPhotoTracking = true; +// kdDebug(14010) << k_funcinfo << "old photo tracking" << endl; + d->photoSourceCID = contactElement.attribute( PSCID_ELEM ); + d->photoSourcePID = contactElement.attribute( PSPID_ELEM ); + d->photoSourceAID = contactElement.attribute( PSAID_ELEM ); + } +// else +// kdDebug(14010) << k_funcinfo << "no old photo tracking" << endl; + } + else if( contactElement.tagName() == QString::fromUtf8( "property-sources" ) ) + { + QDomNode property = contactElement.firstChild(); + while( !property.isNull() ) + { + QDomElement propertyElement = property.toElement(); + + if( propertyElement.tagName() == QString::fromUtf8( "name" ) ) + { + QString source = propertyElement.attribute( QString::fromUtf8("source") ); + setDisplayNameSource(stringToSource(source)); + // find contact sources now. + QDomNode propertyParam = propertyElement.firstChild(); + while( !propertyParam.isNull() ) + { + QDomElement propertyParamElement = propertyParam.toElement(); + if( propertyParamElement.tagName() == QString::fromUtf8( "contact-source" ) ) + { + d->nameSourceCID = propertyParamElement.attribute( NSCID_ELEM ); + d->nameSourcePID = propertyParamElement.attribute( NSPID_ELEM ); + d->nameSourceAID = propertyParamElement.attribute( NSAID_ELEM ); + } + propertyParam = propertyParam.nextSibling(); + } + } + if( propertyElement.tagName() == QString::fromUtf8( "photo" ) ) + { + QString source = propertyElement.attribute( QString::fromUtf8("source") ); + setPhotoSource(stringToSource(source)); + // find contact sources now. + QDomNode propertyParam = propertyElement.firstChild(); + while( !propertyParam.isNull() ) + { + QDomElement propertyParamElement = propertyParam.toElement(); + if( propertyParamElement.tagName() == QString::fromUtf8( "contact-source" ) ) + { + d->photoSourceCID = propertyParamElement.attribute( NSCID_ELEM ); + d->photoSourcePID = propertyParamElement.attribute( NSPID_ELEM ); + d->photoSourceAID = propertyParamElement.attribute( NSAID_ELEM ); + } + propertyParam = propertyParam.nextSibling(); + } + } + property = property.nextSibling(); + } + } + else if( contactElement.tagName() == QString::fromUtf8( "groups" ) ) + { + QDomNode group = contactElement.firstChild(); + while( !group.isNull() ) + { + QDomElement groupElement = group.toElement(); + + if( groupElement.tagName() == QString::fromUtf8( "group" ) ) + { + QString strGroupId = groupElement.attribute( QString::fromUtf8("id") ); + if( !strGroupId.isEmpty() ) + addToGroup( Kopete::ContactList::self()->group( strGroupId.toUInt() ) ); + else //kopete 0.6 contactlist + addToGroup( Kopete::ContactList::self()->findGroup( groupElement.text() ) ); + } + else if( groupElement.tagName() == QString::fromUtf8( "top-level" ) ) //kopete 0.6 contactlist + addToGroup( Kopete::Group::topLevel() ); + + group = group.nextSibling(); + } + } + else if( contactElement.tagName() == QString::fromUtf8( "address-book-field" ) ) + { + QString app = contactElement.attribute( QString::fromUtf8( "app" ), QString::null ); + QString key = contactElement.attribute( QString::fromUtf8( "key" ), QString::null ); + QString val = contactElement.text(); + d->addressBook[ app ][ key ] = val; + } + else if( contactElement.tagName() == QString::fromUtf8( "custom-notifications" ) ) + { + Kopete::NotifyDataObject::notifyDataFromXML( contactElement ); + } + else //if( groupElement.tagName() == QString::fromUtf8( "plugin-data" ) || groupElement.tagName() == QString::fromUtf8("custom-icons" )) + { + Kopete::ContactListElement::fromXML(contactElement); + } + contactElement = contactElement.nextSibling().toElement(); + } + + if( oldNameTracking ) + { + /* if (displayNameSourceContact() ) <- doesn't work because the contact is only set up when all plugin are loaded (BUG 111956) */ + if ( !d->nameSourceCID.isEmpty() ) + { +// kdDebug(14010) << k_funcinfo << "Converting old name source" << endl; + // even if the old tracking attributes exists, they could have been null, that means custom + setDisplayNameSource(SourceContact); + } + else + { + // lets do the best conversion for the old name tracking + // if the custom display name is the same as kabc name, set the source to kabc + if ( !d->metaContactId.isEmpty() && ( d->displayName == nameFromKABC(d->metaContactId)) ) + setDisplayNameSource(SourceKABC); + else + setDisplayNameSource(SourceCustom); + } + } + + if ( oldPhotoTracking ) + { +// kdDebug(14010) << k_funcinfo << "Converting old photo source" << endl; + if ( !d->photoSourceCID.isEmpty() ) + { + setPhotoSource(SourceContact); + } + else + { + if ( !d->metaContactId.isEmpty() && !photoFromKABC(d->metaContactId).isNull()) + setPhotoSource(SourceKABC); + else + setPhotoSource(SourceCustom); + } + } + + // If a plugin is loaded, load data cached + connect( Kopete::PluginManager::self(), SIGNAL( pluginLoaded(Kopete::Plugin*) ), + this, SLOT( slotPluginLoaded(Kopete::Plugin*) ) ); + + // All plugins are already loaded, call manually the contact setting slot. + if( Kopete::PluginManager::self()->isAllPluginsLoaded() ) + slotAllPluginsLoaded(); + else + // When all plugins are loaded, set the source contact. + connect( Kopete::PluginManager::self(), SIGNAL( allPluginsLoaded() ), + this, SLOT( slotAllPluginsLoaded() ) ); + + // track changes only works if ONE Contact is inside the MetaContact +// if (d->contacts.count() > 1) // Does NOT work as intended +// d->trackChildNameChanges=false; + +// kdDebug(14010) << k_funcinfo << "END" << endl; + return true; +} + +QString MetaContact::sourceToString(PropertySource source) const +{ + if ( source == SourceCustom ) + return QString::fromUtf8("custom"); + else if ( source == SourceKABC ) + return QString::fromUtf8("addressbook"); + else if ( source == SourceContact ) + return QString::fromUtf8("contact"); + else // recovery + return sourceToString(SourceCustom); +} + +MetaContact::PropertySource MetaContact::stringToSource(const QString &name) const +{ + if ( name == QString::fromUtf8("custom") ) + return SourceCustom; + else if ( name == QString::fromUtf8("addressbook") ) + return SourceKABC; + else if ( name == QString::fromUtf8("contact") ) + return SourceContact; + else // recovery + return SourceCustom; +} + +QString MetaContact::addressBookField( Kopete::Plugin * /* p */, const QString &app, const QString & key ) const +{ + return d->addressBook[ app ][ key ]; +} + +void Kopete::MetaContact::setAddressBookField( Kopete::Plugin * /* p */, const QString &app, const QString &key, const QString &value ) +{ + d->addressBook[ app ][ key ] = value; +} + +void MetaContact::slotPluginLoaded( Plugin *p ) +{ + if( !p ) + return; + + QMap<QString, QString> map= pluginData( p ); + if(!map.isEmpty()) + { + p->deserialize(this,map); + } +} + +void MetaContact::slotAllPluginsLoaded() +{ + // Now that the plugins and subcontacts are loaded, set the source contact. + setDisplayNameSourceContact( findContact( d->nameSourcePID, d->nameSourceAID, d->nameSourceCID) ); + setPhotoSourceContact( findContact( d->photoSourcePID, d->photoSourceAID, d->photoSourceCID) ); +} + +void MetaContact::slotUpdateAddressBookPicture() +{ + KABC::AddressBook* ab = KABCPersistence::self()->addressBook(); + QString id = metaContactId(); + if ( !id.isEmpty() && !id.contains(':') ) + { + KABC::Addressee theAddressee = ab->findByUid(id); + if ( theAddressee.isEmpty() ) + { + kdDebug( 14010 ) << k_funcinfo << "no KABC::Addressee found for ( " << id << " ) " << " in current address book" << endl; + } + else + { + KABC::Picture pic = theAddressee.photo(); + if ( pic.data().isNull() && pic.url().isEmpty() ) + pic = theAddressee.logo(); + + d->kabcPicture.setPicture(pic); + } + } +} + +bool MetaContact::isTemporary() const +{ + return d->temporary; +} + +void MetaContact::setTemporary( bool isTemporary, Group *group ) +{ + d->temporary = isTemporary; + Group *temporaryGroup = Group::temporary(); + if ( d->temporary ) + { + addToGroup (temporaryGroup); + Group *g; + for( g = d->groups.first(); g; g = d->groups.next() ) + { + if(g != temporaryGroup) + removeFromGroup(g); + } + } + else + moveToGroup(temporaryGroup, group ? group : Group::topLevel()); +} + +QString MetaContact::metaContactId() const +{ + if(d->metaContactId.isEmpty()) + { + Contact *c=d->contacts.first(); + if(!c) + return QString::null; + return c->protocol()->pluginId()+QString::fromUtf8(":")+c->account()->accountId()+QString::fromUtf8(":") + c->contactId() ; + } + return d->metaContactId; +} + +void MetaContact::setMetaContactId( const QString& newMetaContactId ) +{ + if(newMetaContactId == d->metaContactId) + return; + + // 1) Check the Id is not already used by another contact + // 2) cause a kabc write ( only in response to metacontactLVIProps calling this, or will + // write be called twice when creating a brand new MC? ) + // 3) What about changing from one valid kabc to another, are kabc fields removed? + // 4) May be called with Null to remove an invalid kabc uid by KMC::toKABC() + // 5) Is called when reading the saved contact list + + // Don't remove IM addresses from kabc if we are changing contacts; + // other programs may have written that data and depend on it + d->metaContactId = newMetaContactId; + KABCPersistence::self()->write( this ); + emit onlineStatusChanged( this, d->onlineStatus ); + emit persistentDataChanged(); +} + +bool MetaContact::isPhotoSyncedWithKABC() const +{ + return d->photoSyncedWithKABC; +} + +void MetaContact::setPhotoSyncedWithKABC(bool b) +{ + d->photoSyncedWithKABC=b; + if(b) + { + QVariant newValue; + + switch( photoSource() ) + { + case SourceContact: + { + Contact *source = photoSourceContact(); + if(source != 0L) + newValue = source->property( Kopete::Global::Properties::self()->photo() ).value(); + break; + } + case SourceCustom: + { + if( !d->customPicture.isNull() ) + newValue = d->customPicture.path(); + break; + } + // Don't sync the photo with KABC if the source is KABC ! + default: + return; + } + + if ( !d->metaContactId.isEmpty() && !newValue.isNull()) + { + KABC::Addressee theAddressee = KABCPersistence::self()->addressBook()->findByUid( metaContactId() ); + + if ( !theAddressee.isEmpty() ) + { + QImage img; + if(newValue.canCast( QVariant::Image )) + img=newValue.toImage(); + else if(newValue.canCast( QVariant::Pixmap )) + img=newValue.toPixmap().convertToImage(); + + if(img.isNull()) + { + // Some protocols like MSN save the photo as a url in + // contact properties, we should not use this url + // to sync with kabc but try first to embed the + // photo data in the kabc addressee, because it could + // be remote resource and the local url makes no sense + QImage fallBackImage = QImage(newValue.toString()); + if(fallBackImage.isNull()) + theAddressee.setPhoto(newValue.toString()); + else + theAddressee.setPhoto(fallBackImage); + } + else + theAddressee.setPhoto(img); + + KABCPersistence::self()->addressBook()->insertAddressee(theAddressee); + KABCPersistence::self()->writeAddressBook( theAddressee.resource() ); + } + } + } +} + +QPtrList<Contact> MetaContact::contacts() const +{ + return d->contacts; +} +} //END namespace Kopete + +#include "kopetemetacontact.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemetacontact.h b/kopete/libkopete/kopetemetacontact.h new file mode 100644 index 00000000..3bdaa33a --- /dev/null +++ b/kopete/libkopete/kopetemetacontact.h @@ -0,0 +1,615 @@ +/* + kopetemetacontact.h - Kopete Meta Contact + + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002-2005 by Olivier Goffart <ogoffart @ kde.org> + Copyright (c) 2003 by Will Stephenson <will@stevello.free-online.co.uk> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef kopetemetacontact_h__ +#define kopetemetacontact_h__ + +#include "kopetecontactlistelement.h" +#include <qptrlist.h> +#include <qstring.h> + +#include <kdemacros.h> +#include "kopete_export.h" + +#include "kopetenotifydataobject.h" +#include "kopetecontactlistelement.h" +#include "kopeteonlinestatus.h" + +class QDomNode; + +class KURL; + +namespace Kopete { + + +class Plugin; +class Group; +class Picture; + +/** + * @author Will Stephenson <will@stevello.free-online.co.uk> + * @author Martijn Klingens <klingens@kde.org> + * @author Duncan Mac-Vicar Prett <duncan@kde.org> + * @author Olivier Goffart <ogoffart@tiscalinet.be> + * + * A metacontact represent a person. This is a kind of entry to + * the contactlist. All information of a contact is contained in + * the metacontact. Plugins can store data in it with all + * @ref ContactListElement methods + */ +class KOPETE_EXPORT MetaContact : public ContactListElement, public NotifyDataObject +{ + Q_OBJECT + + Q_PROPERTY( QString displayName READ displayName WRITE setDisplayName ) + Q_PROPERTY( QString statusString READ statusString ) + Q_PROPERTY( QString statusIcon READ statusIcon ) + Q_PROPERTY( bool isOnline READ isOnline ) + Q_PROPERTY( bool isReachable READ isReachable ) + Q_PROPERTY( bool isTemporary READ isTemporary ) + Q_PROPERTY( bool canAcceptFiles READ canAcceptFiles ) + //Q_PROPERTY( ulong idleTime READ idleTime ) + Q_PROPERTY( QString metaContactId READ metaContactId WRITE setMetaContactId ) + Q_PROPERTY( bool photoSyncedWithKABC READ isPhotoSyncedWithKABC WRITE setPhotoSyncedWithKABC ) + +public: + /** + * Enumeration of possible sources for a property (which may be + * photos, see setPhotoSource() for instance). + */ + enum PropertySource { + SourceContact /**< Data comes from the contact itself. */, + SourceKABC /**< Data comes from KABC (addressbook). */, + SourceCustom /**< Data comes from somewhere else. */ + }; + + /** + * constructor + */ + MetaContact(); + /** + * destructor + */ + ~MetaContact(); + + /** + * @brief Returns this metacontact's ID. + * + * Every metacontact has a unique id, set by when creating the contact, or reading the contactlist + * TODO: make it real + */ + QString metaContactId() const; + + /** + * @brief Add or change the link to a KDE addressbook (KABC) Addressee. + * FIXME: Use with care. You could create 1 to many relationships with the current implementation + */ + void setMetaContactId( const QString& newMetaContactId ); + + /** + * @brief Retrieve the list of contacts that are part of the meta contact + */ + QPtrList<Contact> contacts() const; + + /** + * @brief The groups the contact is stored in + */ + QPtrList<Group> groups() const; + + /** + * Find the Contact to a given contact. If contact + * is not found, a null pointer is returned. + * if @p protocolId or @p accountId are null, it is searched over all protocols/accounts + */ + Contact *findContact( const QString &protocolId, const QString &accountId, const QString &contactId ); + + /** + * @brief Set the source of metacontact displayName + * + * This method selects the display name source for one + * of the sources defined in @ref PropertySource + * + * @see PropertySource + */ + void setDisplayNameSource(PropertySource source); + + /** + * @brief get the source of metacontact display name + * + * This method obtains the current name source for one + * of the sources defined in @ref PropertySource + * + * @see PropertySource + */ + PropertySource displayNameSource() const; + + /** + * @brief Set the source of metacontact photo + * + * This method selects the photo source for one + * of the sources defined in @ref PropertySource + * + * @see PropertySource + */ + void setPhotoSource(PropertySource source); + + /** + * @brief get the source of metacontact photo + * + * This method obtains the current photo source for one + * of the sources defined in @ref PropertySource + * + * @see PropertySource + */ + PropertySource photoSource() const; + + /** + * @brief the display name showed in the contactlist window + * + * The displayname is the name which should be shown almost everywere to + * represent the metacontact. (in the contactlist, in the chatwindow, ....) + * + * This is a kind of alias, set by the kopete user, as opposed to a nickname + * set by the contact itself. + * + * If the protocol support alias serverside, the metacontact displayname + * should probably be syncronized with the alias on the server. + * + * This displayName is obtained from the source set with @ref setDisplayNameSource + */ + QString displayName() const; + + /** + * @brief the photo showed in the contactlist window + * + * Returns a image for the metacontact. If the metacontact photo source is + * the KDE addressbook. it will return the picture stored in the addressbook + * It can also use a subcontact as the photo source. + * + * This photo is obtained from the source set with @ref setPhotoSource + */ + QImage photo() const; + + /** + * Return the correct Kopete::Picture object depending of the metacontact photo source. + * + * This photo is obtained from the source set with @ref setPhotoSource + * + * KDE4 TODO: Rename this to photo() and use the new object. + */ + Picture &picture() const; + + /** + * @brief Set the custom displayName. + * + * This display name is used when name source is Custom + * this metohd may emit @ref displayNameChanged signal. + * And will call @ref Kopete::Contact::sync + * + * @see displayName() + * @see displayNameSource() + */ + void setDisplayName( const QString &name ); + + /** + * @brief Returns the custom display name + * + * @see displayName() + * @see displayNameSource() + */ + QString customDisplayName() const; + + /** + * @brief Returns the custom display photo + * + * @see photo() + * @see photoSource() + */ + KURL customPhoto() const; + + + /** + * @brief Set the custom photo. + * + * This photo is used when photo source is set toCustom + * this metohd may emit @ref photoChanged signal. + * + * @see photo() + * @see photoSource() + */ + void setPhoto( const KURL &url ); + + /** + * @brief get the subcontact being tracked for its displayname (null if not set) + * + * The MetaContact will adjust its displayName() every time the + * "nameSource" changes its nickname property. + */ + Contact *displayNameSourceContact() const; + + /** + * @brief set the subcontact whose name is to be tracked (set to null to disable tracking) + * @see nameSource + */ + void setDisplayNameSourceContact( Contact* contact ); + + /** + * @brief get the subcontact being tracked for its photo + */ + Contact *photoSourceContact() const; + + /** + * @brief set the subcontact to use for SourceContact source + */ + void setPhotoSourceContact( Contact* contact ); + + /** + * @return true if when a subcontact change his photo, the photo will be set to the kabc contact. + */ + bool isPhotoSyncedWithKABC() const; + + /** + * Set if the photo should be synced with the adressbook when the photosource change his photo + * + * If \p b is true, the photo will be synced immediatly if possible + */ + void setPhotoSyncedWithKABC(bool b); + + + /** + * Temporary contacts will not be serialized. + * If they are added to the contactlist, they appears in a special "Not in your contactlist" group. + * (the @ref Group::temporary group) + */ + bool isTemporary() const; + + /** + * @brief Add a contact which has just been deserialised to the meta contact + * @param c The Contact being added + */ + void addContact( Contact *c ); + + /** + * @brief remove the contact from this metacontact + * + * set 'deleted' to true if the Contact is already deleted + * + * @param c is the contact to remove + * @param deleted : if it is false, it will disconnect the old contact, and call some method. + */ + void removeContact( Contact *c , bool deleted = false ); + + /** + * @return the preferred child Contact for communication, or 0 if none is suitable (all unreachable). + */ + Contact *preferredContact(); + + /** + * @brief The name of the icon associated with the contact's status + * @todo improve with OnlineStatus + */ + QString statusIcon() const; + + /** + * @brief The status string of the contact + * + * @see @ref status() + * @todo improve with OnlineStatus + */ + QString statusString() const; + + /** + * Returns whether this contact can be reached online for at least one + * FIXME: Make that an enum, because status can be unknown for certain + * protocols + */ + bool isOnline() const; + + /** + * Returns whether this contact can accept files + * @return True if the user is online with a file capable protocol, false otherwise + */ + bool canAcceptFiles() const; + + /** + * Return a more fine-grained status. + * Online means at least one sub-contact is online, away means at least + * one is away, but nobody is online and offline speaks for itself + */ + OnlineStatus::StatusType status() const; + + /** + * Like isOnline, but returns true even if the contact is not online, but + * can be reached trough offline-messages. + * it it return false, you are unable to open a chatwindow + * @todo : Here too, use preference order, not append order! + * @todo : Here too an enum. + */ + bool isReachable() const; + + /** + * return the time in second the contact is idle. + */ + unsigned long int idleTime() const; + + /** + * Return a XML representation of the metacontact + * @internal + * @param minimal When true, it doesn't save the + * plugins, groups and notification data. False by default. + */ + const QDomElement toXML(bool minimal = false); + + /** + * Creates a metacontact from XML + * Return value of false indicated that + * creation failed and this contact should be + * discarded. + * @internal + */ + bool fromXML( const QDomElement& cnode ); + + /** + * Get or set a field for the KDE address book backend. Fields not + * registered during the call to Plugin::addressBookFields() + * cannot be altered! + * + * @param p The Plugin by which uses this field + * @param app refers to the application id in the libkabc database. + * This should be a standardized format to make sense in the address + * book in the first place - if you could use "" as application + * then probably you should use the plugin data API instead of the + * address book fields. + * @param key The name of the address book field to get or set + * + * @todo: In the code the requirement that fields are registered first + * is already lifted, but the API needs some review before we + * can remove it here too. + * Probably it requires once more some rewrites to get it working + * properly :( - Martijn + */ + QString addressBookField( Plugin *p, const QString &app, const QString &key ) const; + + /** + * @brief set an address book field + * + * @see also @ref addressBookField() + * @param p The Plugin by which uses this field + * @param app The application ID in the KABC database + * @param key The name of the address book field to set + * @param value The value of the address book field to set + */ + void setAddressBookField( Plugin *p, const QString &app, const QString &key, const QString &value ); + +public slots: + + /** + * @brief Send a file to this metacontact + * + * This is the MetaContact level slot for sending files. It may be called through the + * "Send File" entry in the GUI, or over DCOP. If the function is called through the GUI, + * no parameters are sent and they assume default values. This slot calls the slotSendFile + * with identical params of the highest ranked contact capable of sending files (if any) + * + * @param sourceURL The actual KURL of the file you are sending + * @param altFileName (Optional) An alternate name for the file - what the receiver will see + * @param fileSize (Optional) Size of the file being sent. Used when sending a nondeterminate + * file size (such as over a socket) + * + */ + void sendFile( const KURL &sourceURL, const QString &altFileName = QString::null, + unsigned long fileSize = 0L ); +signals: + /** + * This metaContact is going to be saved to the contactlist. Plugins should + * connect to this signal to update data with setPluginData() + */ + void aboutToSave( Kopete::MetaContact *metaContact ); + + /** + * One of the subcontacts' idle status has changed. As with online status, + * this can occur without the metacontact changing idle state + */ + void contactIdleStateChanged( Kopete::Contact *contact ); + + +public slots: + + /** + * @brief Move a contact from one group to another. + */ + void moveToGroup( Kopete::Group *from, Kopete::Group *to ); + + /** + * @brief Remove a contact from one group + */ + void removeFromGroup( Kopete::Group *from ); + + /** + * @brief Add a contact to another group. + */ + void addToGroup( Kopete::Group *to ); + + /** + * @brief Set if this is a temporary contact. (see @ref isTemporary) + * + * @param b if the contact is or not temporary + * @param group if the contact was temporary and b is false, then the contact will be moved to this group. + * if group is null, it will be moved to top-level + */ + void setTemporary( bool b = true, Kopete::Group *group = 0L ); + + /** + * @brief Contact another user. + * + * Depending on the config settings, call sendMessage() or + * startChat() + * + * returns the Contact that was chosen as the preferred + */ + Contact *execute(); + + /** + * @brief Send a single message, classic ICQ style. + * + * The actual sending is done by the Contact, but the meta contact + * does the GUI side of things. + * This is a slot to allow being called easily from e.g. a GUI. + * + * returns the Contact that was chosen as the preferred + */ + Contact *sendMessage(); + + /** + * @brief Start a chat in a persistent chat window + * + * Like sendMessage, but this time a full-blown chat will be opened. + * Most protocols can't distinguish between the two and are either + * completely session based like MSN or completely message based like + * ICQ the only true difference is the GUI shown to the user. + * + * returns the Contact that was chosen as the preferred + */ + Contact *startChat(); + +signals: + /** + * @brief The MetaContact online status changed + */ + void onlineStatusChanged( Kopete::MetaContact *contact, Kopete::OnlineStatus::StatusType status ); + + /** + * @brief A contact's online status changed + * + * this signal differs from @ref onlineStatusChanged because a contact can + * change his status without changing MetaContact status. It is mainly used to update the small icons + * in the contactlist + */ + void contactStatusChanged( Kopete::Contact *contact, const Kopete::OnlineStatus &status ); + + /** + * @brief The meta contact's display name changed + */ + void displayNameChanged( const QString &oldName, const QString &newName ); + + /** + * @brief The meta contact's photo changed + */ + void photoChanged(); + + /** + * @brief The contact was moved + */ + void movedToGroup( Kopete::MetaContact *contact, Kopete::Group *from, Kopete::Group *to ); + + /** + * @brief The contact was removed from group + */ + void removedFromGroup( Kopete::MetaContact *contact, Kopete::Group *group ); + + /** + * @brief The contact was added to another group + */ + void addedToGroup( Kopete::MetaContact *contact, Kopete::Group *to ); + + /** + * @brief a contact has been added into this metacontact + * + * This signal is emitted when a contact is added to this metacontact + */ + void contactAdded( Kopete::Contact *c ); + + /** + * @brief a contact has been removed from this metacontact + * + * This signal is emitted when a contact is removed from this metacontact + */ + void contactRemoved( Kopete::Contact *c ); + + /** + * Some part of this object's persistent data (as returned by toXML) has changed. + */ + void persistentDataChanged( ); + +private slots: + /** + * Update the contact's online status and emit onlineStatusChanged + * when appropriate + */ + void updateOnlineStatus(); + + /** + * One of the child contact's online status changed + */ + void slotContactStatusChanged( Kopete::Contact *c, const Kopete::OnlineStatus &status, const Kopete::OnlineStatus &oldStatus ); + + /** + * One of the child contact's property changed + */ + void slotPropertyChanged( Kopete::Contact *contact, const QString &key, const QVariant &oldValue, const QVariant &newValue ); + + /** + * A child contact was deleted, remove it from the list, if it's still + * there + */ + void slotContactDestroyed( Kopete::Contact* ); + + /** + * If a plugin is loaded, maybe data about this plugin are already cached in the metacontact + */ + void slotPluginLoaded( Kopete::Plugin *plugin ); + + /** + * When all the plugins are loaded, set the Contact Source. + */ + void slotAllPluginsLoaded(); + + /** + * Update the KABC Picture when the addressbook is changed. + */ + void slotUpdateAddressBookPicture(); + +protected: + //QImage photoFromContact( Kopete::Contact *c) const; + //QImage photoFromKABC( const QString &id ) const; + QImage photoFromCustom() const; + //QString nameFromContact( Kopete::Contact *c) const; + //QString nameFromKABC( const QString &id ) const; + + QString sourceToString(PropertySource source) const; + PropertySource stringToSource(const QString &name) const; +private: + class Private; + Private *d; +}; + +// util functions shared with metacontact property dialog +KOPETE_EXPORT QImage photoFromContact( Kopete::Contact *c) /*const*/; +KOPETE_EXPORT QImage photoFromKABC( const QString &id ) /*const*/; +KOPETE_EXPORT QString nameFromContact( Kopete::Contact *c) /*const*/; +KOPETE_EXPORT QString nameFromKABC( const QString &id ) /*const*/; + +} //END namespace Kopete + + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetemimesourcefactory.cpp b/kopete/libkopete/kopetemimesourcefactory.cpp new file mode 100644 index 00000000..a34d8aee --- /dev/null +++ b/kopete/libkopete/kopetemimesourcefactory.cpp @@ -0,0 +1,175 @@ +/* + kopetemimesourcefactory.cpp - Kopete mime source factory + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetemimesourcefactory.h" + +#include "kopeteaccountmanager.h" +#include "kopetecontactlist.h" +#include "kopeteaccount.h" +#include "kopetecontact.h" +#include "kopetemetacontact.h" + +#include <kdebug.h> +#include <kiconloader.h> + +#include <qdragobject.h> +#include <qstring.h> +#include <qstringlist.h> + +namespace Kopete +{ + +class MimeSourceFactory::Private +{ +public: + Private() : lastMimeSource( 0 ) {} + ~Private() { delete lastMimeSource; } + mutable QMimeSource *lastMimeSource; +}; + +MimeSourceFactory::MimeSourceFactory() + : d( new Private ) +{ +} + +MimeSourceFactory::~MimeSourceFactory() +{ + delete d; +} + +const QMimeSource *MimeSourceFactory::data( const QString &abs_name ) const +{ + // flag used to signal something went wrong when creating a mimesource + bool completed = false; + // extract and decode arguments + QStringList parts = QStringList::split( QChar(':'), abs_name ); + for ( QStringList::Iterator it = parts.begin(); it != parts.end(); ++it ) + *it = KURL::decode_string( *it ); + + QPixmap img; + if ( parts[0] == QString::fromLatin1("kopete-contact-icon") ) + { + if ( parts.size() >= 4 ) + { + Account *account = AccountManager::self()->findAccount( parts[1], parts[2] ); + if ( account ) + { + Contact *contact = account->contacts()[ parts[3] ]; + if ( contact ) + { + img = contact->onlineStatus().iconFor( contact ); + completed = true; + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-contact-icon: contact not found" << endl; + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-contact-icon: account not found" << endl; + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-contact-icon: insufficient information in abs_name: " << parts << endl; + } + + if ( parts[0] == QString::fromLatin1("kopete-account-icon") ) + { + if ( parts.size() >= 3 ) + { + Account *account = AccountManager::self()->findAccount( parts[1], parts[2] ); + if ( account ) + { + img = account->myself()->onlineStatus().iconFor( account->myself() ); + completed = true; + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-account-icon: account not found" << endl; + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-account-icon: insufficient information in abs_name: " << parts << endl; + } + + if ( parts[0] == QString::fromLatin1("kopete-metacontact-icon") ) + { + if ( parts.size() >= 2 ) + { + MetaContact *mc = ContactList::self()->metaContact( parts[1] ); + if ( mc ) + { + img = SmallIcon( mc->statusIcon() ); + completed = true; + } + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-metacontact-icon: insufficient information in abs_name: " << parts << endl; + } + + if ( parts[0] == QString::fromLatin1("kopete-metacontact-photo") ) + { + if ( parts.size() >= 2 ) + { + MetaContact *mc = ContactList::self()->metaContact( parts[1] ); + if ( mc ) + { + QImage photo = mc->photo(); + delete d->lastMimeSource; + d->lastMimeSource = new QImageDrag( photo ); + return d->lastMimeSource; + } + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-metacontact-photo: insufficient information in abs_name: " << parts << endl; + } + + if ( parts[0] == QString::fromLatin1("kopete-onlinestatus-icon") ) + { + if ( parts.size() >= 2 ) + { + /* + * We are using a dirty trick here: this mime source is supposed to return the + * icon for an arbitrary KOS instance. To do this, the caller needs to ask + * the KOS for the mime source key first, which also ensures the icon is + * currently in the cache. The cache is global, so we just need to find any + * existing KOS instance to return us the rendered icon from the cache. + * To find a valid KOS, we ask Kopete's account manager to locate an existing + * account. We'll use the myself() instance of that account to reference its + * current KOS object, which in turn has access to the global KOS icon cache. + * Note that if the cache has been invalidated in the meantime, we'll just + * get an empty pixmap back. + */ + Account *account = AccountManager::self()->accounts().getFirst(); + if ( account ) + { + img = account->myself()->onlineStatus().iconFor( parts[1] ); + completed = true; + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-onlinestatus-icon: no active account found" << endl; + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-onlinestatus-icon: insufficient information in abs_name: " << parts << endl; + } + + delete d->lastMimeSource; + if ( completed ) + d->lastMimeSource = new QImageDrag( img.convertToImage() ); + else + d->lastMimeSource = 0; + return d->lastMimeSource; +} + +} // END namespace Kopete + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemimesourcefactory.h b/kopete/libkopete/kopetemimesourcefactory.h new file mode 100644 index 00000000..76c2f188 --- /dev/null +++ b/kopete/libkopete/kopetemimesourcefactory.h @@ -0,0 +1,55 @@ +/* + kopetemimesourcefactory.h - Kopete mime source factory + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEMIMESOURCEFACTORY_H +#define KOPETEMIMESOURCEFACTORY_H + +#include <qmime.h> + +#include "kopete_export.h" + +namespace Kopete +{ + +/** + * @brief A mime source factory for providing kopete's various icons for labels and tooltips + * + * The following 'protocols' are supported, and provide appropriate icons for + * various situations: + * kopete-contact-icon:\<protocolId\>:\<accountId\>:\<contactId\> + * kopete-account-icon:\<protocolId\>:\<accountId\> + * kopete-metacontact-icon:\<metaContactId\> + * Note that the various id strings should be URL-encoded (with, for instance, + * KURL::encode_string) if they might contain colons. + */ +class KOPETE_EXPORT MimeSourceFactory : public QMimeSourceFactory +{ +public: + MimeSourceFactory(); + ~MimeSourceFactory(); + + const QMimeSource *data( const QString &abs_name ) const; + +private: + class Private; + Private *d; +}; + +} // Kopete + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemimetypehandler.cpp b/kopete/libkopete/kopetemimetypehandler.cpp new file mode 100644 index 00000000..04c4939b --- /dev/null +++ b/kopete/libkopete/kopetemimetypehandler.cpp @@ -0,0 +1,215 @@ +/* + kopetemimetypehandler.cpp - Kopete mime type handlers + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetemimetypehandler.h" +#include "kopeteglobal.h" +#include "kopeteuiglobal.h" + +#include <qwidget.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <klocale.h> +#include <kio/netaccess.h> +#include <kmimetype.h> +#include <kmessagebox.h> +#include <kprogress.h> +#include <kstandarddirs.h> +#include <ktar.h> + +namespace Kopete +{ + +namespace +{ + static QDict<Kopete::MimeTypeHandler> g_mimeHandlers; + static QDict<Kopete::MimeTypeHandler> g_protocolHandlers; +} + +class MimeTypeHandler::Private +{ +public: + Private( bool carf ) : canAcceptRemoteFiles( carf ) {} + bool canAcceptRemoteFiles; + QStringList mimeTypes; + QStringList protocols; +}; + +MimeTypeHandler::MimeTypeHandler( bool canAcceptRemoteFiles ) + : d( new Private( canAcceptRemoteFiles ) ) +{ +} + +MimeTypeHandler::~MimeTypeHandler() +{ + for( QStringList::iterator it = d->mimeTypes.begin(); it != d->mimeTypes.end(); ++it ) + g_mimeHandlers.remove( *it ); + + for( QStringList::iterator it = d->protocols.begin(); it != d->protocols.end(); ++it ) + g_protocolHandlers.remove( *it ); + + delete d; +} + +bool MimeTypeHandler::registerAsMimeHandler( const QString &mimeType ) +{ + if( g_mimeHandlers[ mimeType ] ) + { + kdWarning(14010) << k_funcinfo << "Warning: Two mime type handlers attempting" + " to handle " << mimeType << endl; + return false; + } + + g_mimeHandlers.insert( mimeType, this ); + d->mimeTypes.append( mimeType ); +// kdDebug(14010) << k_funcinfo << "Mime type " << mimeType << " registered" << endl; + return true; +} + +bool MimeTypeHandler::registerAsProtocolHandler( const QString &protocol ) +{ + if( g_protocolHandlers[ protocol ] ) + { + kdWarning(14010) << k_funcinfo << "Warning: Two protocol handlers attempting" + " to handle " << protocol << endl; + return false; + } + + g_protocolHandlers.insert( protocol, this ); + d->protocols.append( protocol ); + kdDebug(14010) << k_funcinfo << "Mime type " << protocol << " registered" << endl; + return true; +} + +const QStringList MimeTypeHandler::mimeTypes() const +{ + return d->mimeTypes; +} + +const QStringList MimeTypeHandler::protocols() const +{ + return d->protocols; +} + +bool MimeTypeHandler::canAcceptRemoteFiles() const +{ + return d->canAcceptRemoteFiles; +} + +bool MimeTypeHandler::dispatchURL( const KURL &url ) +{ + if( url.isEmpty() ) + return false; + + QString type = KMimeType::findByURL( url )->name(); + + MimeTypeHandler *mimeHandler = g_mimeHandlers[ type ]; + + if( mimeHandler ) + { + return dispatchToHandler( url, type, mimeHandler ); + } + else + { + mimeHandler = g_protocolHandlers[ url.protocol() ]; + + if( mimeHandler ) + { + mimeHandler->handleURL( url ); + return true; + } + else + { + kdDebug(14010) << "No mime type handler can handle this URL: " << url.prettyURL() << endl; + return false; + } + } +} + +bool MimeTypeHandler::dispatchToHandler( const KURL &url, const QString &mimeType, MimeTypeHandler *handler ) +{ + if( !handler->canAcceptRemoteFiles() ) + { + QString file; + if( !KIO::NetAccess::download( url, file, Kopete::UI::Global::mainWidget() ) ) + { + QString sorryText; + if ( url.isLocalFile() ) + { + sorryText = i18n( "Unable to find the file %1." ); + } + else + { + sorryText = i18n( "<qt>Unable to download the requested file;<br>" + "please check that address %1 is correct.</qt>" ); + } + + KMessageBox::sorry( Kopete::UI::Global::mainWidget(), + sorryText.arg( url.prettyURL() ) ); + return false; + } + + KURL dest; + dest.setPath( file ); + + if( !mimeType.isNull() ) + handler->handleURL( mimeType, dest ); + else + handler->handleURL( dest ); + + // for now, local-only handlers have to be synchronous + KIO::NetAccess::removeTempFile( file ); + } + else + { + if( !mimeType.isNull() ) + handler->handleURL( mimeType, url ); + else + handler->handleURL( url ); + } + + return true; +} + +void MimeTypeHandler::handleURL( const KURL &url ) const +{ + Q_UNUSED( url ); +} + +void MimeTypeHandler::handleURL( const QString &mimeType, const KURL &url ) const +{ + Q_UNUSED( mimeType ); + Q_UNUSED( url ); +} + + +EmoticonMimeTypeHandler::EmoticonMimeTypeHandler() + : MimeTypeHandler( false ) +{ + registerAsMimeHandler( QString::fromLatin1("application/x-kopete-emoticons") ); + registerAsMimeHandler( QString::fromLatin1("application/x-tgz") ); + registerAsMimeHandler( QString::fromLatin1("application/x-tbz") ); +} + +void EmoticonMimeTypeHandler::handleURL( const QString &, const KURL &url ) const +{ + Global::installEmoticonTheme( url.path() ); +} + +} // END namespace Kopete + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemimetypehandler.h b/kopete/libkopete/kopetemimetypehandler.h new file mode 100644 index 00000000..8f3235f0 --- /dev/null +++ b/kopete/libkopete/kopetemimetypehandler.h @@ -0,0 +1,133 @@ +/* + kopetemimetypehandler.h - Kopete Mime-type Handlers + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEMIMETYPEHANDLER_H +#define KOPETEMIMETYPEHANDLER_H + +class KURL; +class QString; +class QStringList; + +#include "kopete_export.h" + +namespace Kopete +{ + +/** + * @brief A handler for some set of mime-types + * A mime type handler is responsible for handling requests to open files of + * certain mime types presented to the main application. + */ +class KOPETE_EXPORT MimeTypeHandler +{ +protected: + MimeTypeHandler( bool canAcceptRemoteFiles = false ); +public: + virtual ~MimeTypeHandler(); + + /** + * Finds a MimeTypeHandler for a given URL, and tells that handler to handle it + * + * @param url the url to dispatch + * + * @return true if a handler was registered for the mime type, false otherwise + */ + static bool dispatchURL( const KURL &url ); + + /** + * Returns a list of mime types this object is registered to handle + */ + const QStringList mimeTypes() const; + + /** + * Returns a list of protocols this object is registered to handle + */ + const QStringList protocols() const; + + /** + * Returns true if this handler can accept remote files direcltly; + * If false, remote files are downloaded via KIO::NetAccess before + * being passed to handleURL + */ + bool canAcceptRemoteFiles() const; + + /** + * Handles the URL @p url + * + * @param url The url to handle + */ + virtual void handleURL( const KURL &url ) const; + + /** + * Handles the URL @p url, which has the mime type @p mimeType + * + * @param mimeType The mime type of the URL + * @param url The url to handle + */ + virtual void handleURL( const QString &mimeType, const KURL &url ) const; + +protected: + /** + * Register this object as the handler of type @p mimeType. + * @param mimeType the mime type to handle + * @return true if registration succeeded, false if another handler is + * already set for this mime type. + */ + bool registerAsMimeHandler( const QString &mimeType ); + + /** + * Register this object as the handler of type @p protocol. + * @param protocol the protocol to handle + * @return true if registration succeeded, false if another handler is + * already set for this protocol. + */ + bool registerAsProtocolHandler( const QString &protocol ); + +private: + /** + * Helper function. + * Attempts to dispatch a given URL to a given handler + * + * @param url The url to dispatch + * @param mimeType The mime type of the url + * @param handler The handler to attempt + * + * @return true if a handler was able to process the URL, false otherwise + */ + static bool dispatchToHandler( const KURL &url, const QString &mimeType, MimeTypeHandler *handler ); + + class Private; + Private *d; +}; + +/** + * Mime-type handler class for Kopete emoticon files + */ +class KOPETE_EXPORT EmoticonMimeTypeHandler : public MimeTypeHandler +{ +public: + EmoticonMimeTypeHandler(); + + const QStringList mimeTypes() const; + + void handleURL( const QString &mimeType, const KURL &url ) const; +}; + +} // Kopete + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetenotifydataobject.cpp b/kopete/libkopete/kopetenotifydataobject.cpp new file mode 100644 index 00000000..9a0de544 --- /dev/null +++ b/kopete/libkopete/kopetenotifydataobject.cpp @@ -0,0 +1,152 @@ +/* + kopetenotifydataobject.cpp - Container for notification events + + Copyright (c) 2004 by Will Stephenson <lists@stevello.free-online.co.uk> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <qdom.h> +#include <kdebug.h> +#include "kopetenotifydataobject.h" +#include "kopetenotifyevent.h" + +class Kopete::NotifyDataObject::Private +{ +public: + QDict<Kopete::NotifyEvent> events; +}; + +Kopete::NotifyDataObject::NotifyDataObject() +{ + d = new Private(); + d->events.setAutoDelete( true ); +} + +Kopete::NotifyDataObject::~NotifyDataObject() +{ + delete d; +} + +Kopete::NotifyEvent * Kopete::NotifyDataObject::notifyEvent( const QString &event ) const +{ + Kopete::NotifyEvent *evt = d->events.find( event ); + return evt; +} + +void Kopete::NotifyDataObject::setNotifyEvent( const QString& event, Kopete::NotifyEvent *notifyEvent ) +{ + d->events.replace( event, notifyEvent ); +} + +bool Kopete::NotifyDataObject::removeNotifyEvent( const QString &event ) +{ + return d->events.remove( event ); +} + +QDomElement Kopete::NotifyDataObject::notifyDataToXML() +{ + QDomDocument notify; + QDomElement notifications; + if ( !d->events.isEmpty() ) + { + //<custom-notifications> + notifications = notify.createElement( QString::fromLatin1( "custom-notifications" ) ); + QDictIterator<Kopete::NotifyEvent> it( d->events ); + for ( ; it.current(); ++it ) + { + //<event name="..." suppress-common="true|false"> + QDomElement event = notify.createElement( QString::fromLatin1( "event" ) ); + event.setAttribute( QString::fromLatin1( "name" ), it.currentKey() ); + event.setAttribute( QString::fromLatin1( "suppress-common" ), QString::fromLatin1( it.current()->suppressCommon() ? "true" : "false" ) ); + QValueList<QDomElement> presentations = it.current()->toXML(); + //<sound-notification enabled="true|false" src="..." single-shot=""> + for ( QValueList<QDomElement>::Iterator it = presentations.begin(); it != presentations.end(); ++it ) + event.appendChild( notify.importNode( *it, true ) ); + notifications.appendChild( event ); + } + } + return notifications; +} + +bool Kopete::NotifyDataObject::notifyDataFromXML( const QDomElement& element ) +{ + if ( element.tagName() == QString::fromLatin1( "custom-notifications" ) ) + { + QDomNode field = element.firstChild(); + while( !field.isNull() ) + { + //read an event + QDomElement fieldElement = field.toElement(); + if ( fieldElement.tagName() == QString::fromLatin1( "event" ) ) + { + // get its attributes + QString name = fieldElement.attribute( QString::fromLatin1( "name" ), QString::null ); + QString suppress = fieldElement.attribute( QString::fromLatin1( "suppress-common" ), QString::null ); + Kopete::NotifyEvent *evt = new Kopete::NotifyEvent( suppress == QString::fromLatin1( "true" ) ); + + // get its children + QDomNode child = fieldElement.firstChild(); + while( !child.isNull() ) + { + QDomElement childElement = child.toElement(); + if ( childElement.tagName() == QString::fromLatin1( "sound-presentation" ) ) + { +// kdDebug(14010) << k_funcinfo << "read: sound" << endl; + QString src = childElement.attribute( QString::fromLatin1( "src" ) ); + QString enabled = childElement.attribute( QString::fromLatin1( "enabled" ) ); + QString singleShot = childElement.attribute( QString::fromLatin1( "single-shot" ) ); + Kopete::EventPresentation *pres = new Kopete::EventPresentation( Kopete::EventPresentation::Sound, src, + ( singleShot == QString::fromLatin1( "true" ) ), + ( enabled == QString::fromLatin1( "true" ) ) ); + evt->setPresentation( Kopete::EventPresentation::Sound, pres ); +// kdDebug(14010) << k_funcinfo << "after sound: " << evt->toString() << endl; + } + if ( childElement.tagName() == QString::fromLatin1( "message-presentation" ) ) + { +// kdDebug(14010) << k_funcinfo << "read: msg" << endl; + QString src = childElement.attribute( QString::fromLatin1( "src" ) ); + QString enabled = childElement.attribute( QString::fromLatin1( "enabled" ) ); + QString singleShot = childElement.attribute( QString::fromLatin1( "single-shot" ) ); + Kopete::EventPresentation *pres = new Kopete::EventPresentation( Kopete::EventPresentation::Message, src, + ( singleShot == QString::fromLatin1( "true" ) ), + ( enabled == QString::fromLatin1( "true" ) ) ); + evt->setPresentation( Kopete::EventPresentation::Message, pres ); +// kdDebug(14010) << k_funcinfo << "after message: " << evt->toString() << endl; + } + if ( childElement.tagName() == QString::fromLatin1( "chat-presentation" ) ) + { +// kdDebug(14010) << k_funcinfo << "read: chat" << endl; + QString enabled = childElement.attribute( QString::fromLatin1( "enabled" ) ); + QString singleShot = childElement.attribute( QString::fromLatin1( "single-shot" ) ); + Kopete::EventPresentation *pres = new Kopete::EventPresentation( Kopete::EventPresentation::Chat, QString::null, + ( singleShot == QString::fromLatin1( "true" ) ), + ( enabled == QString::fromLatin1( "true" ) ) ); + evt->setPresentation( Kopete::EventPresentation::Chat, pres ); +// kdDebug(14010) << k_funcinfo << "after chat: " << evt->toString() << endl; + } + child = child.nextSibling(); + } +// kdDebug(14010) << k_funcinfo << "read: " << evt->toString() << endl; + setNotifyEvent( name, evt ); + } + field = field.nextSibling(); + } + return true; + } + else + { + kdDebug( 14010 ) << "element wasn't custom-notifications" << endl; + return false; + } +} + diff --git a/kopete/libkopete/kopetenotifydataobject.h b/kopete/libkopete/kopetenotifydataobject.h new file mode 100644 index 00000000..db253c60 --- /dev/null +++ b/kopete/libkopete/kopetenotifydataobject.h @@ -0,0 +1,58 @@ +/* + kopetenotifydataobject.h - Kopete Custom Notify Data Object + + Copyright (c) 2004 by Will Stephenson <lists@stevello.free-online.co.uk> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ +#ifndef KOPETENOTIFYDATAOBJECT_H +#define KOPETENOTIFYDATAOBJECT_H + +#include <qdict.h> +#include <qstring.h> +#include <qvaluelist.h> + +#include "kopete_export.h" + +class QDomElement; + +namespace Kopete +{ + +class NotifyEvent; + +/** + * Contains custom notification control and storage functionality + */ + +class KOPETE_EXPORT NotifyDataObject +{ + public: + NotifyDataObject(); + ~NotifyDataObject(); + // Notify events + NotifyEvent *notifyEvent( const QString &event ) const; + void setNotifyEvent( const QString &event, + NotifyEvent *notifyEvent ); + bool removeNotifyEvent( const QString &event ); + // Serialization + protected: + QDomElement notifyDataToXML(); + bool notifyDataFromXML( const QDomElement& element ); + private: + class Private; + NotifyDataObject::Private* d; +}; + +} + +#endif diff --git a/kopete/libkopete/kopetenotifyevent.cpp b/kopete/libkopete/kopetenotifyevent.cpp new file mode 100644 index 00000000..28c4ab15 --- /dev/null +++ b/kopete/libkopete/kopetenotifyevent.cpp @@ -0,0 +1,181 @@ +/* + kopetenotifyevent.h - Kopete Notifications for a given event + + Copyright (c) 2004 by Will Stephenson <lists@stevello.free-online.co.uk> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <qdom.h> +#include <kdebug.h> +#include "kopetenotifyevent.h" +#include "kopeteeventpresentation.h" + +Kopete::NotifyEvent::NotifyEvent( const bool suppressCommon ) +{ + m_suppressCommon = suppressCommon; + m_message = 0; + m_chat = 0; + m_sound = 0; +} + +Kopete::NotifyEvent::~NotifyEvent() +{ + delete m_sound; + delete m_message; + delete m_chat; +} + +bool Kopete::NotifyEvent::suppressCommon() const +{ + return m_suppressCommon; +} + +Kopete::EventPresentation *Kopete::NotifyEvent::presentation( const Kopete::EventPresentation::PresentationType type ) const +{ + switch ( type ) + { + case Kopete::EventPresentation::Sound: + return m_sound; + case Kopete::EventPresentation::Message: + return m_message; + case Kopete::EventPresentation::Chat: + return m_chat; + default: + return 0; + } +} + +void Kopete::NotifyEvent::removePresentation( const Kopete::EventPresentation::PresentationType type ) +{ + Kopete::EventPresentation **presToChange; + switch ( type ) + { + case Kopete::EventPresentation::Sound: + presToChange = &m_sound; + break; + case Kopete::EventPresentation::Message: + presToChange = &m_message; + break; + case Kopete::EventPresentation::Chat: + presToChange = &m_chat; + break; + default: + kdDebug( 14010 ) << k_funcinfo << " Someone tried to set an unrecognised type of presentation!" << endl; + return; + } + if ( *presToChange ) + { + delete *presToChange; + *presToChange = 0; + } +} + +void Kopete::NotifyEvent::setPresentation( const Kopete::EventPresentation::PresentationType type, Kopete::EventPresentation * notification ) +{ + Kopete::EventPresentation **presToChange; + switch ( type ) + { + case Kopete::EventPresentation::Sound: + presToChange = &m_sound; + break; + case Kopete::EventPresentation::Message: + presToChange = &m_message; + break; + case Kopete::EventPresentation::Chat: + presToChange = &m_chat; + break; + default: + kdDebug( 14010 ) << k_funcinfo << " Someone tried to set an unrecognised type of presentation!" << endl; + return; + } + if ( *presToChange ) + delete *presToChange; + *presToChange = notification; +} + +bool Kopete::NotifyEvent::firePresentation( const Kopete::EventPresentation::PresentationType type ) +{ + kdDebug( 14010 ) << k_funcinfo << endl; + Kopete::EventPresentation **presToChange; + switch ( type ) + { + case Kopete::EventPresentation::Sound: + presToChange = &m_sound; + break; + case Kopete::EventPresentation::Message: + presToChange = &m_message; + break; + case Kopete::EventPresentation::Chat: + presToChange = &m_chat; + break; + default: + return false; + } + kdDebug( 14010 ) << toString() << endl; + if ( *presToChange && (*presToChange)->singleShot() ) + { + kdDebug( 14010 ) << " removing singleshot!" << endl; + delete *presToChange; + *presToChange = 0; + kdDebug( 14010 ) << toString() << endl; + return true; + } + return false; +} + +void Kopete::NotifyEvent::setSuppressCommon( const bool suppress ) +{ + m_suppressCommon = suppress; +} + +const QValueList<QDomElement> Kopete::NotifyEvent::toXML() const +{ + QDomDocument eventData; + QValueList<QDomElement> eventNodes; + if ( m_sound && !m_sound->content().isEmpty() ) + { + QDomElement soundElmt = eventData.createElement( QString::fromLatin1( "sound-presentation" ) ); + soundElmt.setAttribute( QString::fromLatin1( "enabled" ), QString::fromLatin1( m_sound->enabled() ? "true" : "false" ) ); + soundElmt.setAttribute( QString::fromLatin1( "single-shot" ), QString::fromLatin1( m_sound->singleShot() ? "true" : "false" ) ); + soundElmt.setAttribute( QString::fromLatin1( "src" ), m_sound->content() ); + eventNodes.append( soundElmt ); + } + if ( m_message && !m_message->content().isEmpty() ) + { + QDomElement msgElmt = eventData.createElement( QString::fromLatin1( "message-presentation" ) ); + msgElmt.setAttribute( QString::fromLatin1( "enabled" ), QString::fromLatin1( m_message->enabled() ? "true" : "false" ) ); + msgElmt.setAttribute( QString::fromLatin1( "single-shot" ), QString::fromLatin1( m_message->singleShot() ? "true" : "false" ) ); + msgElmt.setAttribute( QString::fromLatin1( "src" ), m_message->content() ); + eventNodes.append( msgElmt ); + } + if ( m_chat && m_chat->enabled() ) + { + QDomElement chatElmt = eventData.createElement( QString::fromLatin1( "chat-presentation" ) ); + chatElmt.setAttribute( QString::fromLatin1( "enabled" ), QString::fromLatin1( "true" ) ); + chatElmt.setAttribute( QString::fromLatin1( "single-shot" ), QString::fromLatin1( m_chat->singleShot() ? "true" : "false" ) ); + eventNodes.append( chatElmt ); + } + return eventNodes; +} + +QString Kopete::NotifyEvent::toString() +{ + QString stringRep = QString::fromLatin1("Event; Suppress common=%1").arg( QString::fromLatin1( suppressCommon() ? "true" : "false" ) ); + if ( m_sound) + stringRep += m_sound->toString(); + if ( m_message) + stringRep += m_message->toString(); + if ( m_chat) + stringRep += m_chat->toString(); + return stringRep; +} diff --git a/kopete/libkopete/kopetenotifyevent.h b/kopete/libkopete/kopetenotifyevent.h new file mode 100644 index 00000000..b7acd3c1 --- /dev/null +++ b/kopete/libkopete/kopetenotifyevent.h @@ -0,0 +1,60 @@ +/* + kopetenotifyevent.h - Container for presentations of an event + + Copyright (c) 2004 by Will Stephenson <lists@stevello.free-online.co.uk> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETENOTIFYEVENT_H +#define KOPETENOTIFYEVENT_H + +#include <qstring.h> +#include <qvaluelist.h> +#include "kopeteeventpresentation.h" + +#include "kopete_export.h" + +class QDomElement; + +namespace Kopete +{ + +class KOPETE_EXPORT NotifyEvent +{ +public: + NotifyEvent( const bool suppressCommon = false ); + ~NotifyEvent(); + + bool suppressCommon() const; + EventPresentation *presentation( const EventPresentation::PresentationType type ) const; + void setPresentation( const EventPresentation::PresentationType type, EventPresentation * ); + void removePresentation( const EventPresentation::PresentationType type ); + /** + * @return true if the presentation was single shot + */ + bool firePresentation( const EventPresentation::PresentationType type ); + + void setSuppressCommon( bool suppress ); + const QValueList<QDomElement> toXML() const; + QString toString(); +private: + QString m_event; + EventPresentation *m_sound; + EventPresentation *m_message; + EventPresentation *m_chat; + bool m_suppressCommon; +}; + +} + +#endif diff --git a/kopete/libkopete/kopeteonlinestatus.cpp b/kopete/libkopete/kopeteonlinestatus.cpp new file mode 100644 index 00000000..8872f28b --- /dev/null +++ b/kopete/libkopete/kopeteonlinestatus.cpp @@ -0,0 +1,302 @@ +/* + kopeteonlinestatus.cpp - Kopete Online Status + + Copyright (c) 2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2003 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2003 by Will Stephenson <lists@stevello.free-online.co.uk> + Copyright (c) 2004 by Olivier Goffart <ogoffart @ tiscalinet.be> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteonlinestatus.h" +#include "kopeteonlinestatusmanager.h" + +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopetecontact.h" +#include <kiconloader.h> +#include <kiconeffect.h> +#include <kdebug.h> +#include <klocale.h> +#include <kstaticdeleter.h> +#include <kapplication.h> + + + +using namespace Kopete; + +class OnlineStatus::Private + : public KShared +{ +public: + StatusType status; + unsigned weight; + Protocol *protocol; + unsigned internalStatus; + QStringList overlayIcons; + QString description; + unsigned refCount; + + QString protocolIcon() const + { + return protocol ? protocol->pluginIcon() : QString::fromLatin1( "unknown" ); + } + +}; + +/** + * This is required by some plugins, when a status need to be stored on + * the disk, to avoid problems. + */ +static struct +{ + OnlineStatus::StatusType status; + const char *name; +} statusNames[] = { + { OnlineStatus::Unknown, "Unknown" }, + { OnlineStatus::Offline, "Offline" }, + { OnlineStatus::Connecting, "Connecting" }, + { OnlineStatus::Invisible, "Invisible" }, + { OnlineStatus::Online, "Online"}, + { OnlineStatus::Away, "Away" } }; + +OnlineStatus::OnlineStatus( StatusType status, unsigned weight, Protocol *protocol, + unsigned internalStatus, const QStringList &overlayIcons, const QString &description ) + : d( new Private ) +{ + d->status = status; + d->internalStatus = internalStatus; + d->weight = weight; + d->overlayIcons = overlayIcons; + d->protocol = protocol; + d->description = description; +} + +OnlineStatus::OnlineStatus( StatusType status, unsigned weight, Protocol *protocol, unsigned internalStatus, + const QStringList &overlayIcons, const QString &description, const QString &caption, unsigned int categories , unsigned int options ) + : d( new Private ) +{ + d->status = status; + d->internalStatus = internalStatus; + d->weight = weight; + d->overlayIcons = overlayIcons; + d->protocol = protocol; + d->description = description; + + OnlineStatusManager::self()->registerOnlineStatus(*this, caption, categories, options ); +} + +OnlineStatus::OnlineStatus( StatusType status ) + : d( new Private ) +{ + d->status = status; + d->internalStatus = 0; + d->weight = 0; + d->protocol = 0L; + + switch( status ) + { + case Online: + d->description = i18n( "Online" ); + break; + case Away: + d->description = i18n( "Away" ); + break; + case Connecting: + d->description = i18n( "Connecting" ); + break; + case Invisible: + d->description = i18n( "Invisible" ); + break; + case Offline: + d->description = i18n( "Offline" ); + break; + case Unknown: + default: + d->description = i18n( "Unknown" ); + d->overlayIcons = QString::fromLatin1("status_unknown"); + break; + + } +} + +OnlineStatus::OnlineStatus() + : d( new Private ) +{ + d->status = Unknown; + d->internalStatus = 0; + d->weight = 0; + d->protocol = 0L; + d->overlayIcons = QString::fromLatin1( "status_unknown" ); +} + +OnlineStatus::OnlineStatus( const OnlineStatus &other ) + : d( other.d ) +{ +} + +bool OnlineStatus::operator==( const OnlineStatus &other ) const +{ + if ( d->internalStatus == other.d->internalStatus && d->protocol == other.d->protocol && + d->weight == other.d->weight && d->overlayIcons == other.d->overlayIcons && + d->description == other.d->description ) + { + return true; + } + + return false; +} + +bool OnlineStatus::operator!=( const OnlineStatus &other ) const +{ + return !(*this == other); +} + + +bool OnlineStatus::operator>( const OnlineStatus &other ) const +{ + if( d->status == other.d->status ) + return d->weight > other.d->weight; + else + return d->status > other.d->status; +} + +bool OnlineStatus::operator<( const OnlineStatus &other ) const +{ + if( d->status == other.d->status ) + return d->weight < other.d->weight; + else + return d->status < other.d->status; +} + +OnlineStatus & OnlineStatus::operator=( const OnlineStatus &other ) +{ + d = other.d; + return *this; +} + +OnlineStatus::~OnlineStatus() +{ +} + +OnlineStatus::StatusType OnlineStatus::status() const +{ + return d->status; +} + +unsigned OnlineStatus::internalStatus() const +{ + return d->internalStatus; +} + +unsigned OnlineStatus::weight() const +{ + return d->weight; +} + +QStringList OnlineStatus::overlayIcons() const +{ + return d->overlayIcons; +} + +QString OnlineStatus::description() const +{ + return d->description; +} + +Protocol* OnlineStatus::protocol() const +{ + return d->protocol; +} + +bool OnlineStatus::isDefinitelyOnline() const +{ + if ( status() == Offline || status() == Connecting || status() == Unknown ) + return false; + return true; +} + +QPixmap OnlineStatus::iconFor( const Contact *contact, int size ) const +{ + return OnlineStatusManager::self()->cacheLookupByMimeSource( mimeSourceFor( contact, size ) ); +} + + +QString OnlineStatus::mimeSourceFor( const Contact *contact, int size ) const +{ + // figure out what icon we should use for this contact + QString iconName = contact->icon(); + if ( iconName.isNull() ) + iconName = contact->account()->customIcon(); + if ( iconName.isNull() ) + iconName = d->protocolIcon(); + + + return mimeSource( iconName, size, contact->account()->color(),contact->idleTime() >= 10*60 ); +} + +QPixmap OnlineStatus::iconFor( const Account *account, int size ) const +{ + return OnlineStatusManager::self()->cacheLookupByMimeSource( mimeSourceFor( account, size ) ); +} + +QString OnlineStatus::mimeSourceFor( const Account *account, int size ) const +{ + QString iconName = account->customIcon(); + if ( iconName.isNull() ) + iconName = d->protocolIcon(); + + return mimeSource( iconName, size, account->color(), false ); +} + +QPixmap OnlineStatus::iconFor( const QString &mimeSource ) const +{ + return OnlineStatusManager::self()->cacheLookupByMimeSource( mimeSource ); +} + +QPixmap OnlineStatus::protocolIcon() const +{ + return OnlineStatusManager::self()->cacheLookupByObject( *this, d->protocolIcon() , 16, QColor() ); +} + +QString OnlineStatus::mimeSource( const QString& icon, int size, QColor color, bool idle) const +{ + // make sure the item is in the cache + OnlineStatusManager::self()->cacheLookupByObject( *this, icon, size, color, idle ); + // now return the fingerprint instead + return OnlineStatusManager::self()->fingerprint( *this, icon, size, color, idle ); +} + +QString OnlineStatus::statusTypeToString(OnlineStatus::StatusType statusType) +{ + const int size = sizeof(statusNames) / sizeof(statusNames[0]); + + for (int i=0; i< size; i++) + if (statusNames[i].status == statusType) + return QString::fromLatin1(statusNames[i].name); + + return QString::fromLatin1(statusNames[0].name); // Unknown +} + +OnlineStatus::StatusType OnlineStatus::statusStringToType(QString& string) +{ + int size = sizeof(statusNames) / sizeof(statusNames[0]); + + for (int i=0; i< size; i++) + if (QString::fromLatin1(statusNames[i].name) == string) + return statusNames[i].status; + + return OnlineStatus::Unknown; +} + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopeteonlinestatus.h b/kopete/libkopete/kopeteonlinestatus.h new file mode 100644 index 00000000..2eed5164 --- /dev/null +++ b/kopete/libkopete/kopeteonlinestatus.h @@ -0,0 +1,415 @@ +/* + kopeteonlinestatus.h - Kopete Online Status + + Copyright (c) 2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2003 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2003 by Will Stephenson <lists@stevello.free-online.co.uk> + Copyright (c) 2004 by Olivier Goffart <ogoffart @ tiscalinet.be> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef kopeteonlinestatus_h +#define kopeteonlinestatus_h + +#include "kopete_export.h" + +#include <kdemacros.h> +#include <ksharedptr.h> + +#include <qobject.h> + +class QString; +class QPixmap; +class QColor; + +namespace Kopete +{ + + class OnlineStatusManager; + class Protocol; + class Account; + class Contact; + +/** + * @author Martijn Klingens <klingens@kde.org> + * @author Will Stephenson (icon generating code) + * + * OnlineStatus is a class that encapsulates all information about the + * various online states that a protocol can be in in a single class. The + * online status consists of both a 'global' status as it's known to libkopete + * and used for going online or away, which non-protocol plugins can use, + * and the 'private' status, which is simply an unsigned int and is only + * useful for the actual protocol plugin that uses the status. + * + * This class is passed around by value, but is refcounted to cut down on the + * amount of overhead. All in all it should be more than fast enough for + * general use. + * + * Note that ONLY the constructor can set the data, the object is considered + * to be const after creation as there really shouldn't be a need to change + * a status' characteristics during runtime! + */ +class KOPETE_EXPORT OnlineStatus +{ +public: + /** + * The available global states. It is possible that multiple internal + * states map to the same global states. For example ICQ's 'Do not disturb' + * is handled just like 'Away' by libkopete. Only ICQ itself makes (and + * should make) a distinction. + * The order is important and is used in the < or > operator + */ + enum StatusType + { + /** + * Refers to protocols where state cannot be determined. This + * applies to SMS contacts (text messages via mobile phones), + * since there's no presence information over SMS, but also + * to e.g. MSN contacts that are not on your contact list, + * since MSN only allows a user to query online state for + * users that are formally on the contact list. Lastly, libkopete + * itself uses the Unknown state in @ref MetaContact for + * meta contacts that have no child contacts at all. + */ + Unknown=0, + /** + * State where you really cannot be contacted. Although + * Kopete doesn't oppose any technical limitations it really + * doesn't make sense to have more than one status per protocol + * that maps to 'Offline', since you're supposed to be + * disconnected from the network in this state. + */ + Offline=10, + /** + * State where the user is not available on the network yet + * but trying to get onto. Most useful to yourself contact, because + * this state means not visible but with network access + */ + Connecting=20, + /** + * State where you are online but none of your contacts can + * see that you're online. Useful for all the protocols that support + * being invisible. + */ + Invisible=30, + /** + * Refers to a state where you can be technically reached, but + * for one reason or another it is often not useful to do so. + * This can be because you really aren't behind the computer + * ('Away' or 'Idle') or because you have other things to do + * and don't want to get involved in messaging ('Busy' or 'Do + * not Disturb' for example). + */ + Away=40, + /** + * Refers to a true online state, i.e. you can be contacted by + * others both technically and practically. This also applies + * to e.g. ICQ's 'Free for Chat' status. + */ + Online=50 + }; + // note than Unknown is first, because the metacontact algorithm to detect + // the metacontact status from the contact status starts from Unknown, and + // takes a contact only if its status is greater + + /** + * Reserved internal status values + * + * Any internal status value > 0x80000000 is reserved for internal + * libkopete use. This enumeration lists the currently known values. + */ + enum ReservedInternalStatus + { + /** + * The account this contact belongs to is offline. Used with + * the Unknown StatusType. + */ + AccountOffline = 0x80000001 + }; + + + /** + * Constructor. + * + * Creates an empty OnlineStatus object. Since you cannot change + * OnlineStatus objects that are already created other than by their + * assignment operator, this constructor is only a convenience method + * for use in e.g. class members and local variables. + */ + OnlineStatus(); + + + /** + * Constructor. + * + * Creates a new OnlineStatus object. All fields are mandatory; there + * are no default values. Also, you cannot change the object after creation. + * + * @param status is the global online status as used by libkopete + * @param weight is the 'weight' of this status. The contact list is + * sorted by status, and by weight within a status. It's not possible to + * 'promote' an Away item to a level above Online, since the status field + * always takes precedence. Weight is used when the same status is used + * more than once. Weight is also used for picking the most important + * 'Away' status for a protocol when going Away. + * @param protocol is a pointer to the protocol used. This is used when + * comparing two online status objects. + * @param internalStatus is the status as used internally by the protocol. + * This status is usually a lot more fine-grained than the status as used + * by libkopete and should be unique per protocol. + * @param overlayIcons is a list of QStrings which are the name of status + * icons to be used by the KDE icon loader. (Statuses which don't have icons + * to overlay like Online and Offline should use QString::null as icon + * name ). NOTE if the string is a movie ( *.mng ) it must be the first string in the list. + * TODO: KDE4 sort out movies and overlay icons. + * @param description is a description in e.g. tooltips. + */ + OnlineStatus( StatusType status, unsigned weight, Protocol *protocol, + unsigned internalStatus, const QStringList &overlayIcons, const QString &description ); + + /** + * Constructor. + * + * @p Creates a new OnlineStatus object and registers it with the @ref Kopete::OnlineStatusManager. + * Registration allows you to generate a KActionMenu filled with KActions for changing to this OnlineStatus, + * using Kopete::Account::accountMenu(). + * + * @p Note that weight has an additional significance for registered protocols when used for menu generation. + * + * All fields are mandatory; there + * are no default values. Also, you cannot change the object after creation. + * + * @param status is the global online status as used by libkopete + * @param weight is the 'weight' of this status. The contact list is + * sorted by status, and by weight within a status. It's not possible to + * 'promote' an Away item to a level above Online, since the status field + * always takes precedence. Weight is used when the same status is used + * more than once. Weight is also used for picking the most important + * 'Away' status for a protocol when going Away. Additionally, Weight determinesis also + * @param protocol is a pointer to the protocol used. This is used when + * comparing two online status objects. + * @param internalStatus is the status as used internally by the protocol. + * This status is usually a lot more fine-grained than the status as used + * by libkopete and should be unique per protocol. + * @param overlayIcon is a string returning the name of the status icon to be + * used by the KDE icon loader. (Status whiwh doesn't have icon to overlay like + * Online and Offline should use QString::null as icon string) + * @param description is a description in e.g. tooltips. + * @param caption is the text of the action in the menu + * @param categories the categories this online status is in + * @param options the options of this online status + * @see Kopete::OnlineStatusManager::registerOnlineStatus for more info about the categories and options parameters + */ + OnlineStatus( StatusType status, unsigned weight, Protocol *protocol, unsigned internalStatus, const QStringList &overlayIcon, + const QString &description, const QString& caption, unsigned int categories=0x0 , unsigned int options=0x0 ); + + + /** + * Constructor. + * + * Creates a libkopete builtin status object. Weight, protocol and internal + * status are set to zero, the strings and icons are set to the meta contact + * strings. + */ + OnlineStatus( StatusType status ); + + /** + * Copy constructor. + * + * Just adds a reference to the refcount. Used to copy around the status + * objects with very little overhead. + */ + OnlineStatus( const OnlineStatus &other ); + + /** + * Destructor. + */ + ~OnlineStatus(); + + /** + * \brief Return the status + */ + StatusType status() const; + + /** + * \brief Return the internal status + */ + unsigned internalStatus() const; + + /** + * \brief Return the weight + */ + unsigned weight() const; + + /** + * \brief Return the list of overlay icons + */ + QStringList overlayIcons() const; + + /** + * \brief Return the description + */ + QString description() const; + + /** + * \brief Return the protocol this applies to + */ + Protocol* protocol() const; + + /** + * @return @c true if this a contact with this status is definitely online, + * @c false if the contact is Offline, Connecting or Unknown. + */ + bool isDefinitelyOnline() const; + + + /** + * \brief Return a status icon generated for the given Contact + * + * This will draw an overlay representing the online status + * of the contact the OnlineStatus applies to + * over the base icon. + * A cache is employed to reduce CPU and memory usage. + * @param contact is the contact the icon should apply to. + * @param size is the size we the icon should be scaled to - 16 is default and so costs nothing + */ + QPixmap iconFor( const Contact *contact, int size = 16 ) const; + + /** + * \brief Return the mime source for a status icon generated for the given Contact + * + * This behaves essentially like the method above, except for that + * it returns a mime source string that can be used to render the + * image in richtext components and the like. The returned key + * is only valid until the cache is cleared for the next time, + * so no assumptions should be made about long-time availability + * of the referenced data. + * @param contact is the contact the icon should apply to. + * @param size is the size we the icon should be scaled to - 16 is default and so costs nothing + */ + QString mimeSourceFor( const Contact *contact, int size = 16 ) const; + + /** + * \brief Return a status icon generated for the given Account + * + * This will draw an overlay representing the online status + * of the account the OnlineStatus applies to + * over the base icon. + * A cache is employed to reduce CPU and memory usage. + * @param account is the account the icon should apply to. + * The account's color causes tinting, if it's plain QColor(), no tinting takes place. + * @param size is the size we the icon should be scaled to - 16 is default and so costs nothing + */ + QPixmap iconFor( const Account *account, int size = 16 ) const; + + /** + * \brief Return the mime source for a status icon generated for the given Account + * + * This behaves essentially like the method above, except for that + * it returns a mime source string that can be used to render the + * image in richtext components and the like. The returned key + * is only valid until the cache is cleared for the next time, + * so no assumptions should be made about long-time availability + * of the referenced data. + * @param account is the account the icon should apply to. + * The account's color causes tinting, if it's plain QColor(), no tinting takes place. + * @param size is the size we the icon should be scaled to - 16 is default and so costs nothing + */ + QString mimeSourceFor( const Account *account, int size = 16 ) const; + + /** + * \brief Return a previously rendered status icon for a mime source key + * + * You can access icons with this method that have previously been rendered + * using mimeSourceFor(). Note that only a cache lookup will be done, so + * if the cache has been invalidated due to a change of icon sets between + * requesting the key (thus rendering the icon) and trying to access the + * icon by key, an invalid pixmap will be returned. + */ + QPixmap iconFor( const QString &mimeSource ) const; + + /** + * \brief Returns the status icon for the protocol. + * + * A cache is employed to reduce CPU and memory usage. + */ + QPixmap protocolIcon() const; + + /** + * Assignment operator + */ + OnlineStatus & operator=( const OnlineStatus &other ); + + /** + * Comparison operator + * + * Returns true if both the protocol and the internal status are + * identical. + */ + bool operator==( const OnlineStatus &other ) const; + + /** + * Comparison operator + * + * This operator works exactly opposite of @ref operator==() + */ + bool operator!=( const OnlineStatus &other ) const; + + /** + * Comparison operator + * + * Returns true if the status() of this contact is of higher value than the other + * contact or if both statuses are equal and weight() is higher for this contact. + */ + bool operator>( const OnlineStatus &other ) const; + + /** + * Comparison operator + * + * This operator works exactly opposite of @ref operator>() + */ + bool operator<( const OnlineStatus &other ) const; + + /** + * \brief returns a QString from a StatusType + * + * Static method to convert a Kopete::OnlineStatus::StatusType to a string to avoid + * many issues when saving StatusType to disk + */ + static QString statusTypeToString(OnlineStatus::StatusType status); + + /** + * \brief returns a StatusType from a QString + * + * Static method to convert a QString representing a StatusType to a StatusType to avoid + * many issues when saving StatusType to disk + */ + static OnlineStatus::StatusType statusStringToType(QString& string); + + + +private: + + class Private; + KSharedPtr<Private> d; + + QString mimeSource( const QString& icon, int size, QColor color, bool idle) const; + + +}; + +} //END namespace Kopete + +#endif + + diff --git a/kopete/libkopete/kopeteonlinestatusmanager.cpp b/kopete/libkopete/kopeteonlinestatusmanager.cpp new file mode 100644 index 00000000..61c41b83 --- /dev/null +++ b/kopete/libkopete/kopeteonlinestatusmanager.cpp @@ -0,0 +1,436 @@ +/* + kopeteonlinestatusmanager.cpp + + Copyright (c) 2004 by Olivier Goffart <ogoffart @ tiscalinet . be> + Copyright (c) 2003 by Will Stephenson <lists@stevello.free-online.co.uk> + + Kopete (c) 2003-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteonlinestatusmanager.h" + +#include "kopeteawayaction.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopetecontact.h" + +#include <kiconloader.h> +#include <kiconeffect.h> +#include <kdebug.h> +#include <klocale.h> +#include <kstaticdeleter.h> +#include <kapplication.h> +#include <kcpuinfo.h> // for WORDS_BIGENDIAN + +#include <algorithm> // for min + +namespace Kopete { + + +class OnlineStatusManager::Private +{public: + + struct RegisteredStatusStruct + { + QString caption; + unsigned int categories; + unsigned int options; + }; + + typedef QMap< OnlineStatus , RegisteredStatusStruct > ProtocolMap ; + + QPixmap *nullPixmap; + QMap<Protocol* , ProtocolMap > registeredStatus; + QDict< QPixmap > iconCache; +}; + +OnlineStatusManager *OnlineStatusManager::s_self=0L; + +OnlineStatusManager *OnlineStatusManager::self() +{ + static KStaticDeleter<OnlineStatusManager> deleter; + if(!s_self) + deleter.setObject( s_self, new OnlineStatusManager() ); + return s_self; +} + +OnlineStatusManager::OnlineStatusManager() + : d( new Private ) +{ + d->iconCache.setAutoDelete( true ); + d->nullPixmap = new QPixmap; + connect( kapp, SIGNAL( iconChanged(int) ), this, SLOT( slotIconsChanged() ) ); +} + +OnlineStatusManager::~OnlineStatusManager() +{ + delete d->nullPixmap; + delete d; +} + +void OnlineStatusManager::slotIconsChanged() +{ + d->iconCache.clear(); + emit iconsChanged(); +} + +void OnlineStatusManager::registerOnlineStatus( const OnlineStatus &status, const QString & caption, unsigned int categories, unsigned int options) +{ + Private::RegisteredStatusStruct s; + s.caption=caption; + s.categories=categories; + s.options=options; + d->registeredStatus[status.protocol()].insert(status, s ); +} + +OnlineStatus OnlineStatusManager::onlineStatus(Protocol * protocol, Categories category) const +{ + /* Each category has a number which is a power of two, so it is possible to have several categories per online status + * the logaritm in base two if this number, which represent the bit which is equal to 1 in the number is chosen to be in a tree + * 1 (0 is reserved for Offline) + * / \ + * 2 3 + * / \ / \ + * 4 5 6 7 + * /\ / \ / \ / \ + * 8 9 10 11 12 13 14 15 + * To get the parent of a key, one just divide per two the number + */ + + Private::ProtocolMap protocolMap=d->registeredStatus[protocol]; + + int categ_nb=-1; //the logaritm of category + uint category_=category; + while(category_) + { + category_ >>= 1; + categ_nb++; + } //that code will give the log +1 + + do + { + Private::ProtocolMap::Iterator it; + for ( it = protocolMap.begin(); it != protocolMap.end(); it++ ) + { + unsigned int catgs=it.data().categories; + if(catgs & (1<<(categ_nb))) + return it.key(); + } + //no status found in this category, try the previous one. + categ_nb=(int)(categ_nb/2); + } while (categ_nb > 0); + + kdWarning() << "No status in the category " << category << " for the protocol " << protocol->displayName() <<endl; + return OnlineStatus(); +} + +QString OnlineStatusManager::fingerprint( const OnlineStatus &statusFor, const QString& icon, int size, QColor color, bool idle) +{ + // create a 'fingerprint' to use as a hash key + // fingerprint consists of description/icon name/color/overlay name/size/idle state + return QString::fromLatin1("%1/%2/%3/%4/%5/%6") + .arg( statusFor.description() ) + .arg( icon ) + .arg( color.name() ) + .arg( statusFor.overlayIcons().join( QString::fromLatin1( "," ) ) ) + .arg( size ) + .arg( idle ? 'i' : 'a' ); +} + +QPixmap OnlineStatusManager::cacheLookupByObject( const OnlineStatus &statusFor, const QString& icon, int size, QColor color, bool idle) +{ + QString fp = fingerprint( statusFor, icon, size, color, idle ); + + // look it up in the cache + QPixmap *theIcon= d->iconCache.find( fp ); + if ( !theIcon ) + { + // cache miss +// kdDebug(14010) << k_funcinfo << "Missed " << fingerprint << " in icon cache!" << endl; + theIcon = renderIcon( statusFor, icon, size, color, idle); + d->iconCache.insert( fp, theIcon ); + } + return *theIcon; +} + +QPixmap OnlineStatusManager::cacheLookupByMimeSource( const QString &mimeSource ) +{ + // look it up in the cache + const QPixmap *theIcon= d->iconCache.find( mimeSource ); + if ( !theIcon ) + { + // need to return an invalid pixmap + theIcon = d->nullPixmap; + } + return *theIcon; +} + +// This code was forked from the broken KImageEffect::blendOnLower, but it's +// been so heavily fixed and rearranged it's hard to recognise that now. +static void blendOnLower( const QImage &upper_, QImage &lower, const QPoint &offset ) +{ + if ( upper_.width() <= 0 || upper_.height() <= 0 ) + return; + if ( lower.width() <= 0 || lower.height() <= 0 ) + return; + if ( offset.x() < 0 || offset.x() >= lower.width() ) + return; + if ( offset.y() < 0 || offset.y() >= lower.height() ) + return; + + QImage upper = upper_; + if ( upper.depth() != 32 ) + upper = upper.convertDepth( 32 ); + if ( lower.depth() != 32 ) + lower = lower.convertDepth( 32 ); + + const int cx = offset.x(); + const int cy = offset.y(); + const int cw = std::min( upper.width() + cx, lower.width() ); + const int ch = std::min( upper.height() + cy, lower.height() ); + const int m = 255; + + for ( int j = cy; j < ch; ++j ) + { + QRgb *u = (QRgb*)upper.scanLine(j - cy); + QRgb *l = (QRgb*)lower.scanLine(j) + cx; + + for( int k = cx; k < cw; ++u, ++l, ++k ) + { + int ua = qAlpha(*u); + if ( !ua ) + continue; + + int la = qAlpha(*l); + + int d = ua * m + la * (m - ua); + uchar r = uchar( ( qRed(*u) * ua * m + qRed(*l) * la * (m - ua) ) / d ); + uchar g = uchar( ( qGreen(*u) * ua * m + qGreen(*l) * la * (m - ua) ) / d ); + uchar b = uchar( ( qBlue(*u) * ua * m + qBlue(*l) * la * (m - ua) ) / d ); + uchar a = uchar( ( ua * ua * m + la * la * (m - ua) ) / d ); + *l = qRgba( r, g, b, a ); + } + } +} + +// Get bounding box of image via alpha channel +static QRect getBoundingBox( const QImage& image ) +{ + const int width = image.width(); + const int height = image.height(); + if ( width <= 0 || height <= 0 ) + return QRect(); + + // scan image from left to right and top to bottom + // to get upper left corner of bounding box + int x1 = width - 1; + int y1 = height - 1; + for ( int j = 0; j < height; ++j ) + { + QRgb *i = (QRgb*)image.scanLine(j); + + for( int k = 0; k < width; ++i, ++k ) + { + if ( qAlpha(*i) ) + { + x1 = std::min( x1, k ); + y1 = std::min( y1, j ); + break; + } + } + } + + // scan image from right to left and bottom to top + // to get lower right corner of bounding box + int x2 = 0; + int y2 = 0; + for ( int j = height-1; j >= 0; --j ) + { + QRgb *i = (QRgb*)image.scanLine(j) + width-1; + + for( int k = width-1; k >= 0; --i, --k ) + { + if ( qAlpha(*i) ) + { + x2 = std::max( x2, k ); + y2 = std::max( y2, j ); + break; + } + } + } + return QRect( x1, y1, std::max( 0, x2-x1+1 ), std::max( 0, y2-y1+1 ) ); +} + +// Get offset for upperImage to blend it in the i%4-th corner of lowerImage: +// bottom right, bottom left, top left, top right +static QPoint getOffsetForCorner( const QImage& upperImage, const QImage& lowerImage, const int i ) +{ + const int dX = lowerImage.width() - upperImage.width(); + const int dY = lowerImage.height() - upperImage.height(); + const int corner = i % 4; + QPoint offset; + switch( corner ) { + case 0: + // bottom right + offset = QPoint( dX, dY ); + break; + case 1: + // bottom left + offset = QPoint( 0, dY ); + break; + case 2: + // top left + offset = QPoint( 0, 0 ); + break; + case 3: + // top right + offset = QPoint( dX, 0 ); + break; + } + return offset; +} + +QPixmap* OnlineStatusManager::renderIcon( const OnlineStatus &statusFor, const QString& baseIcon, int size, QColor color, bool idle) const +{ + // create an icon suiting the status from the base icon + // use reasonable defaults if not provided or protocol not set + + if ( baseIcon == statusFor.overlayIcons().first() ) + kdWarning( 14010 ) << "Base and overlay icons are the same - icon effects will not be visible." << endl; + + QPixmap* basis = new QPixmap( SmallIcon( baseIcon ) ); + + // Colorize + if ( color.isValid() ) + *basis = KIconEffect().apply( *basis, KIconEffect::Colorize, 1, color, 0); + + // Note that we do this before compositing the overlay, since we want + // that to be colored in this case. + if ( statusFor.internalStatus() == Kopete::OnlineStatus::AccountOffline || statusFor.status() == Kopete::OnlineStatus::Offline ) + { + *basis = KIconEffect().apply( *basis, KIconEffect::ToGray , 0.85, QColor() , false ); + } + + //composite the iconOverlay for this status and the supplied baseIcon + QStringList overlays = statusFor.overlayIcons(); + if ( !( overlays.isEmpty() ) ) // otherwise leave the basis as-is + { + KIconLoader *loader = KGlobal::instance()->iconLoader(); + + int i = 0; + for( QStringList::iterator it = overlays.begin(), end = overlays.end(); it != end; ++it ) + { + QPixmap overlay = loader->loadIcon(*it, KIcon::Small, 0 , + KIcon::DefaultState, 0L, /*canReturnNull=*/ true ); + + if ( !overlay.isNull() ) + { + // we want to preserve the alpha channels of both basis and overlay. + // there's no way to do this in Qt. In fact, there's no way to do this + // in KDE since KImageEffect is so badly broken. + QImage basisImage = basis->convertToImage(); + QImage overlayImage = overlay.convertToImage(); + QPoint offset; + if ( (*it).endsWith( QString::fromLatin1( "_overlay" ) ) ) + { + // it is possible to have more than one overlay icon + // to avoid overlapping we place them in different corners + overlayImage = overlayImage.copy( getBoundingBox( overlayImage ) ); + offset = getOffsetForCorner( overlayImage, basisImage, i ); + ++i; + } + blendOnLower( overlayImage, basisImage, offset ); + basis->convertFromImage( basisImage ); + } + } + } + + // no need to scale if the icon is already of the required size (assuming height == width!) + if ( basis->width() != size ) + { + QImage scaledImg = basis->convertToImage().smoothScale( size, size ); + *basis = QPixmap( scaledImg ); + } + + // if idle, apply effects + if ( idle ) + KIconEffect::semiTransparent( *basis ); + + return basis; +} + +void OnlineStatusManager::createAccountStatusActions( Account *account , KActionMenu *parent) +{ + Private::ProtocolMap protocolMap=d->registeredStatus[account->protocol()]; + Private::ProtocolMap::Iterator it; + for ( it = --protocolMap.end(); it != protocolMap.end(); --it ) + { + unsigned int options=it.data().options; + if(options & OnlineStatusManager::HideFromMenu) + continue; + + OnlineStatus status=it.key(); + QString caption=it.data().caption; + KAction *action; + + // Any existing actions owned by the account are reused by recovering them + // from the parent's child list. + // The description of the onlinestatus is used as the qobject name + // This is safe as long as OnlineStatus are immutable + QCString actionName = status.description().ascii(); + if ( !( action = static_cast<KAction*>( account->child( actionName ) ) ) ) + { + if(options & OnlineStatusManager::HasAwayMessage) + { + action = new AwayAction( status, caption, status.iconFor(account), 0, account, + SLOT( setOnlineStatus( const Kopete::OnlineStatus&, const QString& ) ), + account, actionName ); + } + else + { + action=new OnlineStatusAction( status, caption, status.iconFor(account) , account, actionName ); + connect(action,SIGNAL(activated(const Kopete::OnlineStatus&)) , + account, SLOT(setOnlineStatus(const Kopete::OnlineStatus&))); + } + } + +#if 0 + //disabled because since action are reused, they are not enabled back if the account is online. + if(options & OnlineStatusManager::DisabledIfOffline && !account->isConnected()) + action->setEnabled(false); +#endif + + if(parent) + parent->insert(action); + + } +} + + +OnlineStatusAction::OnlineStatusAction( const OnlineStatus& status, const QString &text, const QIconSet &pix, QObject *parent, const char *name) + : KAction( text, pix, KShortcut() , parent, name) , m_status(status) +{ + connect(this,SIGNAL(activated()),this,SLOT(slotActivated())); +} + +void OnlineStatusAction::slotActivated() +{ + emit activated(m_status); +} + + +} //END namespace Kopete + +#include "kopeteonlinestatusmanager.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopeteonlinestatusmanager.h b/kopete/libkopete/kopeteonlinestatusmanager.h new file mode 100644 index 00000000..d3369403 --- /dev/null +++ b/kopete/libkopete/kopeteonlinestatusmanager.h @@ -0,0 +1,169 @@ +/* + kopeteonlinestatusmanager.h + + Copyright (c) 2004-2005 by Olivier Goffart <ogoffart @ kde.org> + + Kopete (c) 2004-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef kopeteonlinestatusmanager_h__ +#define kopeteonlinestatusmanager_h__ + +#include <qobject.h> + +#include "kopeteonlinestatus.h" +#include "kaction.h" + +class QString; +class QPixmap; +class QColor; +class KActionMenu; + +namespace Kopete +{ + class OnlineStatus; + class Account; + + +/** + * OnlineStatusManager is a singleton which manage OnlineStatus + * + * @author Olivier Goffart + */ +class KOPETE_EXPORT OnlineStatusManager : public QObject +{ + Q_OBJECT +public: + static OnlineStatusManager* self(); + ~OnlineStatusManager(); + + /** + * Kopete will uses categories to have a more general system than siply globaly away. + * + * Idealy, in each protocol, there should be one status per categories (status may be in several or in none categories + * + * Idle is the status used for auto-away + * + * Status number are organised so that make a tree. + */ + //please be carrefull when modifying values of status. read comment in onlineStatus() + enum Categories + { + Idle=1<<8, ExtendedAway=1<<9 , Invisible=1<<10, + // \ / __________/ + /*1<<4*/ Busy=1<<5, FreeForChat=1<<6, /* 1<<7*/ + // \ / / + Away=1<<2, /* 1<<3 */ + // \ / + Online=1<<1, + Offline=1 + }; + + + /** + * @see registerOnlineStatus + */ + enum Options + { + /// The user may set away messages for this online status + HasAwayMessage = 0x01, + /// The action of the status will be disabled if the account is offline. + /// use it if your protocol doesn't support connecting with the status as initial status. + /// You praticaly shouldn't abuse of that, and automaticaly set status after connecting if possible + DisabledIfOffline = 0x02, + /// The status will not appears in the action menu. Used if you want to register the status for e.g. autoaway, + /// without letting the user set itself that status + HideFromMenu = 0x04 + }; + + /** + * You need to register each status an account can be. + * Registered statuses will appear in the account menu. + * + * The Protocol constructor is a good place to call this function. + * But if you want, you may use a special OnlineStatus constructor that call this function automaticaly + * + * You can set the status to be in the predefined categories. + * Ideally, each category should own one status. + * A status may be in several categories, or in none. + * There shouldn't be more than one status per protocol per categories. + * + * @param status The status to register + * @param caption The caption that will appear in menus (e.g. "Set &Away") + * @param categories A bitflag of @ref Categories + * @param options is a bitflag of @ref Options + */ + void registerOnlineStatus(const OnlineStatus& status, const QString &caption, unsigned int categories=0x00 , unsigned int options=0x0); + + /** + * insert "setStatus" actions from the given account to the specified actionMenu. + * (actions have that menu as parent QObject) + * they are connected to the Account::setOnlineStatus signal + * + * Items are stored by status height. + * + * @param account the account + * @param parent the ActionMenu where action are inserted + */ + void createAccountStatusActions( Account *account , KActionMenu *parent); + + /** + * return the status of the @p protocol which is in the category @p category + * + * If no status has been registered in this category, return the one in the category which is the most similair + */ + OnlineStatus onlineStatus(Protocol *protocol, Categories category) const; + +private: + friend class OnlineStatus; + QPixmap cacheLookupByObject( const OnlineStatus &statusFor, const QString& icon, int size, QColor color, bool idle = false); + QPixmap cacheLookupByMimeSource( const QString &mimeSource ); + QString fingerprint( const OnlineStatus &statusFor, const QString& icon, int size, QColor color, bool idle = false); + QPixmap* renderIcon( const OnlineStatus &statusFor, const QString& baseicon, int size, QColor color, bool idle = false) const; + +signals: + void iconsChanged(); + +private slots: + void slotIconsChanged(); + +private: + + static OnlineStatusManager *s_self; + OnlineStatusManager(); + class Private; + Private *d; +}; + + +/** + * @internal + */ +class OnlineStatusAction : public KAction +{ + Q_OBJECT + public: + OnlineStatusAction ( const OnlineStatus& status, const QString &text, const QIconSet &pix, QObject *parent=0, const char *name=0); + signals: + void activated( const Kopete::OnlineStatus& status ); + private slots: + void slotActivated(); + private: + OnlineStatus m_status; +}; + +} //END namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetepassword.cpp b/kopete/libkopete/kopetepassword.cpp new file mode 100644 index 00000000..f0b788a9 --- /dev/null +++ b/kopete/libkopete/kopetepassword.cpp @@ -0,0 +1,502 @@ +/* + kopetepassword.cpp - Kopete Password + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteuiglobal.h" +#include "kopetepassword.h" +#include "kopetepassworddialog.h" +#include "kopetewalletmanager.h" + +#include <kwallet.h> + +#include <qapplication.h> +#include <qlabel.h> +#include <qlineedit.h> +#include <qcheckbox.h> + +#include <kactivelabel.h> +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kdialogbase.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kiconloader.h> +#include <kpassdlg.h> +#include <kstringhandler.h> + +class Kopete::Password::Private +{ +public: + Private( const QString &group, uint maxLen, bool blanksAllowed ) + : refCount( 1 ), configGroup( group ), remembered( false ), maximumLength( maxLen ), + isWrong( false ), allowBlankPassword( blanksAllowed ) + { + } + Private *incRef() + { + ++refCount; + return this; + } + void decRef() + { + if( --refCount == 0 ) + delete this; + } + /** Reference count */ + int refCount; + /** Group to use for KConfig and KWallet */ + const QString configGroup; + /** Is the password being remembered? */ + bool remembered; + /** The current password in the KConfig file, or QString::null if no password there */ + QString passwordFromKConfig; + /** The maximum length allowed for this password, or -1 if there is no limit */ + uint maximumLength; + /** Is the current password known to be wrong? */ + bool isWrong; + /** Are we allowed to have blank passwords? */ + bool allowBlankPassword; + /** The cached password */ + QString cachedValue; +}; + +/** + * Implementation detail of Kopete::Password: manages a single password request + * @internal + * @author Richard Smith <kde@metafoo.co.uk> + */ +class KopetePasswordRequest : public KopetePasswordRequestBase +{ +public: + KopetePasswordRequest( QObject *owner, Kopete::Password &pass ) + : QObject( owner ), mPassword( pass ), mWallet( 0 ) + { + } + + /** + * Start the request - ask for the wallet + */ + void begin() + { + kdDebug( 14010 ) << k_funcinfo << endl; + Kopete::WalletManager::self()->openWallet( this, SLOT( walletReceived( KWallet::Wallet* ) ) ); + } + + void walletReceived( KWallet::Wallet *wallet ) + { + kdDebug( 14010 ) << k_funcinfo << endl; + mWallet = wallet; + processRequest(); + } + + /** + * Got wallet; now carry out whatever action this request represents + */ + virtual void processRequest() = 0; + + void slotOkPressed() {} + void slotCancelPressed() {} + +protected: + Kopete::Password mPassword; + KWallet::Wallet *mWallet; +}; + +/** + * Implementation detail of Kopete::Password: manages a single password retrieval request + * @internal + * @author Richard Smith <kde@metafoo.co.uk> + */ +class KopetePasswordGetRequest : public KopetePasswordRequest +{ +public: + KopetePasswordGetRequest( QObject *owner, Kopete::Password &pass ) + : KopetePasswordRequest( owner, pass ) + { + } + + QString grabPassword() + { + // Before trying to read from the wallet, check if the config file holds a password. + // If so, remove it from the config and set it through KWallet instead. + QString pwd; + if ( mPassword.d->remembered && !mPassword.d->passwordFromKConfig.isNull() ) + { + pwd = mPassword.d->passwordFromKConfig; + mPassword.set( pwd ); + return pwd; + } + + if ( mWallet && mWallet->readPassword( mPassword.d->configGroup, pwd ) == 0 && !pwd.isNull() ) + return pwd; + + if ( mPassword.d->remembered && !mPassword.d->passwordFromKConfig.isNull() ) + return mPassword.d->passwordFromKConfig; + + return QString::null; + } + + void finished( const QString &result ) + { + mPassword.d->cachedValue = result; + emit requestFinished( result ); + delete this; + } +}; + +class KopetePasswordGetRequestPrompt : public KopetePasswordGetRequest +{ +public: + KopetePasswordGetRequestPrompt( QObject *owner, Kopete::Password &pass, const QPixmap &image, const QString &prompt, Kopete::Password::PasswordSource source ) + : KopetePasswordGetRequest( owner, pass ), mImage( image ), mPrompt( prompt ), mSource( source ), mView( 0 ) + { + } + + void processRequest() + { + QString result = grabPassword(); + if ( mSource == Kopete::Password::FromUser || result.isNull() ) + doPasswordDialog(); + else + finished( result ); + } + + void doPasswordDialog() + { + kdDebug( 14010 ) << k_funcinfo << endl; + + KDialogBase *passwdDialog = new KDialogBase( Kopete::UI::Global::mainWidget(), "passwdDialog", true, i18n( "Password Required" ), + KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok, true ); + + mView = new KopetePasswordDialog( passwdDialog ); + passwdDialog->setMainWidget( mView ); + + mView->m_text->setText( mPrompt ); + mView->m_image->setPixmap( mImage ); + /* Do not put the default password, or it will confuse those which doesn't echo anything for the password + mView->m_password->insert( password ); + */ + int maxLength = mPassword.maximumLength(); + if ( maxLength != 0 ) + mView->m_password->setMaxLength( maxLength ); + mView->m_password->setFocus(); + + // FIXME: either document what these are for or remove them - lilac + mView->adjustSize(); + passwdDialog->adjustSize(); + + connect( passwdDialog, SIGNAL( okClicked() ), SLOT( slotOkPressed() ) ); + connect( passwdDialog, SIGNAL( cancelClicked() ), SLOT( slotCancelPressed() ) ); + connect( this, SIGNAL( destroyed() ), passwdDialog, SLOT( deleteLater() ) ); + passwdDialog->show(); + } + + void slotOkPressed() + { + QString result = QString::fromLocal8Bit( mView->m_password->password() ); + if ( mView->m_save_passwd->isChecked() ) + mPassword.set( result ); + + finished( result ); + } + + void slotCancelPressed() + { + finished( QString::null ); + } + +private: + QPixmap mImage; + QString mPrompt; + Kopete::Password::PasswordSource mSource; + unsigned int mMaxLength; + KopetePasswordDialog *mView; +}; + +class KopetePasswordGetRequestNoPrompt : public KopetePasswordGetRequest +{ +public: + KopetePasswordGetRequestNoPrompt( QObject *owner, Kopete::Password &pass ) + : KopetePasswordGetRequest( owner, pass ) + { + } + + void processRequest() + { + finished( grabPassword() ); + } +}; + +/** + * Implementation detail of Kopete::Password: manages a single password change request + * @internal + * @author Richard Smith <kde@metafoo.co.uk> + */ +class KopetePasswordSetRequest : public KopetePasswordRequest +{ +public: + KopetePasswordSetRequest( Kopete::Password &pass, const QString &newPass ) + : KopetePasswordRequest( 0, pass ), mNewPass( newPass ) + { + if ( KApplication *app = KApplication::kApplication() ) + app->ref(); + } + ~KopetePasswordSetRequest() + { + if ( KApplication *app = KApplication::kApplication() ) + app->deref(); + kdDebug( 14010 ) << k_funcinfo << "job complete" << endl; + } + void processRequest() + { + if ( setPassword() ) + { + mPassword.setWrong( false ); + mPassword.d->cachedValue = mNewPass; + } + delete this; + } + bool setPassword() + { + kdDebug( 14010 ) << k_funcinfo << " setting password for " << mPassword.d->configGroup << endl; + + if ( mWallet && mWallet->writePassword( mPassword.d->configGroup, mNewPass ) == 0 ) + { + mPassword.d->remembered = true; + mPassword.d->passwordFromKConfig = QString::null; + mPassword.writeConfig(); + return true; + } + + if ( KWallet::Wallet::isEnabled() ) + { + // If we end up here, the wallet is enabled, but failed somehow. + // Ask the user what to do now. + + //NOTE: This will start a nested event loop. However, this is fine; the only code we + // call after this point is in Kopete::Password, so as long as we've not been deleted + // everything should work out OK. We have no parent QObject, so we should survive. + if ( KMessageBox::warningContinueCancel( Kopete::UI::Global::mainWidget(), + i18n( "<qt>Kopete is unable to save your password securely in your wallet;<br>" + "do you want to save the password in the <b>unsafe</b> configuration file instead?</qt>" ), + i18n( "Unable to Store Secure Password" ), + KGuiItem( i18n( "Store &Unsafe" ), QString::fromLatin1( "unlock" ) ), + QString::fromLatin1( "KWalletFallbackToKConfig" ) ) != KMessageBox::Continue ) + { + return false; + } + } + mPassword.d->remembered = true; + mPassword.d->passwordFromKConfig = mNewPass; + mPassword.writeConfig(); + return true; + } + +private: + QString mNewPass; +}; + +class KopetePasswordClearRequest : public KopetePasswordRequest +{ +public: + KopetePasswordClearRequest( Kopete::Password &pass ) + : KopetePasswordRequest( 0, pass ) + { + if ( KApplication *app = KApplication::kApplication() ) + app->ref(); + } + ~KopetePasswordClearRequest() + { + if ( KApplication *app = KApplication::kApplication() ) + app->deref(); + kdDebug( 14010 ) << k_funcinfo << "job complete" << endl; + } + void processRequest() + { + if ( clearPassword() ) + { + mPassword.setWrong( true ); + mPassword.d->cachedValue = QString::null; + } + + delete this; + } + bool clearPassword() + { + kdDebug( 14010 ) << k_funcinfo << " clearing password" << endl; + + mPassword.d->remembered = false; + mPassword.d->passwordFromKConfig = QString::null; + mPassword.writeConfig(); + if ( mWallet ) + mWallet->removeEntry( mPassword.d->configGroup ); + return true; + } +}; + +Kopete::Password::Password( const QString &configGroup, uint maximumLength, const char *name ) + : QObject( 0, name ), d( new Private( configGroup, maximumLength, false ) ) +{ + readConfig(); +} + +Kopete::Password::Password( const QString &configGroup, uint maximumLength, + bool allowBlankPassword, const char *name ) + : QObject( 0, name ), d( new Private( configGroup, maximumLength, allowBlankPassword ) ) +{ + readConfig(); +} + +Kopete::Password::Password( Password &other, const char *name ) + : QObject( 0, name ), d( other.d->incRef() ) +{ +} + +Kopete::Password::~Password() +{ + d->decRef(); +} + +Kopete::Password &Kopete::Password::operator=( Password &other ) +{ + if ( d == other.d ) return *this; + d->decRef(); + d = other.d->incRef(); + return *this; +} + +void Kopete::Password::readConfig() +{ + KConfig *config = KGlobal::config(); + config->setGroup( d->configGroup ); + + QString passwordCrypted = config->readEntry( "Password" ); + if ( passwordCrypted.isNull() ) + d->passwordFromKConfig = QString::null; + else + d->passwordFromKConfig = KStringHandler::obscure( passwordCrypted ); + + d->remembered = config->readBoolEntry( "RememberPassword", false ); + d->isWrong = config->readBoolEntry( "PasswordIsWrong", false ); +} + +void Kopete::Password::writeConfig() +{ + KConfig *config = KGlobal::config(); + if(!config->hasGroup(d->configGroup)) + { + //### (KOPETE) + // if the kopete account has been removed, we have no way to know it. + // but we don't want in any case to recreate the group. + // see Bug 106460 + // (the problem is that when we remove the account, we remove the password + // also, which cause a call to this function ) + return; + } + + config->setGroup( d->configGroup ); + + if ( d->remembered && !d->passwordFromKConfig.isNull() ) + config->writeEntry( "Password", KStringHandler::obscure( d->passwordFromKConfig ) ); + else + config->deleteEntry( "Password" ); + + config->writeEntry( "RememberPassword", d->remembered ); + config->writeEntry( "PasswordIsWrong", d->isWrong ); +} + +int Kopete::Password::preferredImageSize() +{ + return IconSize(KIcon::Toolbar); +} + +bool Kopete::Password::allowBlankPassword() +{ + return d->allowBlankPassword; +} + +uint Kopete::Password::maximumLength() +{ + return d->maximumLength; +} + +void Kopete::Password::setMaximumLength( uint max ) +{ + d->maximumLength = max; +} + +bool Kopete::Password::isWrong() +{ + return d->isWrong; +} + +void Kopete::Password::setWrong( bool bWrong ) +{ + d->isWrong = bWrong; + writeConfig(); + + if ( bWrong ) d->cachedValue = QString::null; +} + +void Kopete::Password::requestWithoutPrompt( QObject *returnObj, const char *slot ) +{ + KopetePasswordRequest *request = new KopetePasswordGetRequestNoPrompt( returnObj, *this ); + // call connect on returnObj so we can still connect if 'slot' is protected/private + returnObj->connect( request, SIGNAL( requestFinished( const QString & ) ), slot ); + request->begin(); +} + +void Kopete::Password::request( QObject *returnObj, const char *slot, const QPixmap &image, const QString &prompt, Kopete::Password::PasswordSource source ) +{ + KopetePasswordRequest *request = new KopetePasswordGetRequestPrompt( returnObj, *this, image, prompt, source ); + returnObj->connect( request, SIGNAL( requestFinished( const QString & ) ), slot ); + request->begin(); +} + +QString Kopete::Password::cachedValue() +{ + return d->cachedValue; +} + +void Kopete::Password::set( const QString &pass ) +{ + // if we're being told to forget the password, and we aren't remembering one, + // don't try to open the wallet. fixes bug #71804. + if( pass.isNull() && !d->allowBlankPassword ) + { + if( remembered() ) + clear(); + return; + } + + KopetePasswordRequest *request = new KopetePasswordSetRequest( *this, pass ); + request->begin(); +} + +void Kopete::Password::clear() +{ + KopetePasswordClearRequest *request = new KopetePasswordClearRequest( *this ); + request->begin(); +} + +bool Kopete::Password::remembered() +{ + return d->remembered; +} + +#include "kopetepassword.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetepassword.h b/kopete/libkopete/kopetepassword.h new file mode 100644 index 00000000..149db6f6 --- /dev/null +++ b/kopete/libkopete/kopetepassword.h @@ -0,0 +1,220 @@ +/* + kopetepassword.h - Kopete Password + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPASSWORD_H +#define KOPETEPASSWORD_H + +#include <qobject.h> +#include "kopete_export.h" + +namespace KWallet { class Wallet; } + +class QPixmap; + +/** @internal */ +class KopetePasswordGetRequest; +/** @internal */ +class KopetePasswordSetRequest; +/** @internal */ +class KopetePasswordClearRequest; + +namespace Kopete +{ + +/** + * @author Richard Smith <kde@metafoo.co.uk> + * + * The Kopete::Password object is responsible for storing and retrieving a + * password for a plugin or account object. + * + * If the KWallet is active, passwords will be stored in it, otherwise, they + * will be stored in the KConfig, in a slightly mangled form. + */ +class KOPETE_EXPORT Password : public QObject +{ + Q_OBJECT + +public: + /** + * Create a new Kopete::Password object. + * + * @param configGroup The configuration group to save passwords in. + * @param maxLength The maximum length of the password, or 0 if no maximum exists. + * @param name The name for this object + * + * @deprecated Use the constructor that specifies if a blank password is allowed + */ + explicit Password( const QString &configGroup, uint maxLength = 0, const char *name = 0 ); + + /** + * Create a new Kopete::Password object. + * + * @param configGroup The configuration group to save passwords in. + * @param maxLength The maximum length of the password, or 0 if no maximum exists. + * @param allowBlankPassword If this password is allowed to be blank + * @param name The name for this object + */ + explicit Password( const QString &configGroup, uint maxLength = 0, + bool allowBlankPassword = false, const char *name = 0 ); + + /** + * Create a shallow copy of this object + */ + Password( Password &other, const char *name = 0 ); + ~Password(); + + /** + * Assignment operator for passwords: make this object represent a different password + */ + Password &operator=( Password &other ); + + /** + * Returns the preferred size for images passed to the retrieve and request functions. + */ + static int preferredImageSize(); + + /** + * @brief Returns the maximum allowed length of the password, or 0 if there is no maximum. + */ + uint maximumLength(); + /** + * Sets the maximum allowed length of the password. + * @param max The new maximum allowed length, or 0 if there is no maximum. + */ + void setMaximumLength( uint max ); + + /** + * @brief Returns whether the password currently stored by this object is known to be incorrect. + * This flag gets reset whenever the user enters a new password, and is + * expected to be set by the user of this class if it is detected that the + * password the user entered is wrong. + */ + bool isWrong(); + /** + * Flag the password as being incorrect. + * @see isWrong + */ + void setWrong( bool bWrong = true ); + + /** + * Type of password request to perform: + * FromConfigOrUser : get the password from the config file, or from the user + * if no password in config. + * FromUser : always ask the user for a password (ie, if last password was + * wrong or you know the password has changed). + */ + enum PasswordSource { FromConfigOrUser, FromUser }; + + /** + * @brief Start an asynchronous call to get the password. + * Causes a password entry dialog to appear if the password is not set. Triggers + * a provided slot when done, but not until after this function has returned (you + * don't need to worry about reentrancy or nested event loops). + * + * @param receiver The object to notify when the password request finishes + * @param slot The slot on receiver to call at the end of the request. The signature + * of this function should be slot( const QString &password ). password will + * be the password if successful, or QString::null if failed. + * @param image The icon to display in the dialog when asking for the password + * @param prompt The message to display to the user, asking for a + * password. Can be any Qt RichText string. + * @param source The source the password is taken from if a wrong or + * invalid password is entered or the password could not be found in the wallet + */ + void request( QObject *receiver, const char *slot, const QPixmap &image, + const QString &prompt, PasswordSource source = FromConfigOrUser ); + + /** + * @brief Start an asynchronous password request without a prompt + * + * Starts an asynchronous password request. Does not pop up a password entry dialog + * if there is no password. + * @see request(QObject*,const char*,const QPixmap&,const QString&,bool,unsigned int) + * The password given to the provided slot will be NULL if no password could be retrieved for + * some reason, such as the user declining to open the wallet, or no password being found. + */ + void requestWithoutPrompt( QObject *receiver, const char *slot ); + + /** + * @return true if the password is remembered, false otherwise. + * + * If it returns false, calling @ref request() will + * pop up an Enter Password window. + */ + bool remembered(); + + /** + * @return true if you are allowed to have a blank password + */ + bool allowBlankPassword(); + + /** + * When a password request succeeds, the password is cached. This function + * returns the cached password, if there is one, or QString::null if there + * is not. + */ + QString cachedValue(); + +public slots: + /** + * Set the password for this account. + * @param pass If set to QString::null, the password is forgotten unless you + * specified to allow blank passwords. Otherwise, sets the password to + * this value. + * + * Note: this function is asynchronous; changes will not be instant. + */ + void set( const QString &pass = QString::null ); + + /** + * Unconditionally clears the stored password + */ + void clear(); + +private: + void readConfig(); + void writeConfig(); + + class Private; + Private *d; + + //TODO: can we rearrange things so these aren't friends? + friend class ::KopetePasswordGetRequest; + friend class ::KopetePasswordSetRequest; + friend class ::KopetePasswordClearRequest; +}; + +} + +/** + * This class is an implementation detail of KopetePassword. + * @internal + * @see KopetePassword + */ +class KopetePasswordRequestBase : public virtual QObject +{ + Q_OBJECT +signals: + void requestFinished( const QString &password ); +public slots: + virtual void walletReceived( KWallet::Wallet *wallet ) = 0; + virtual void slotOkPressed() = 0; + virtual void slotCancelPressed() = 0; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetepasswordedaccount.cpp b/kopete/libkopete/kopetepasswordedaccount.cpp new file mode 100644 index 00000000..9fea5c66 --- /dev/null +++ b/kopete/libkopete/kopetepasswordedaccount.cpp @@ -0,0 +1,111 @@ +/* + kopetepasswordedaccount.cpp - Kopete Account with a password + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetepasswordedaccount.h" +#include "kopetepassword.h" +#include "kopeteprotocol.h" +#include "kopeteonlinestatus.h" + +#include <klocale.h> + +#include <qpixmap.h> + +struct Kopete::PasswordedAccount::Private +{ + Private( const QString &group, uint maxLen, bool allowBlankPassword ) : + password( group, maxLen, allowBlankPassword, "mPassword" ) {} + Kopete::Password password; + Kopete::OnlineStatus initialStatus; +}; + +Kopete::PasswordedAccount::PasswordedAccount( Kopete::Protocol *parent, const QString &acctId, uint maxLen, const char *name ) + : Kopete::Account( parent, acctId, name ), d( new Private( QString::fromLatin1("Account_")+ parent->pluginId() + QString::fromLatin1("_") + acctId , maxLen, false ) ) +{ +} + +Kopete::PasswordedAccount::PasswordedAccount( Kopete::Protocol *parent, const QString &acctId, uint maxLen, + bool allowBlankPassword, const char *name ) + : Kopete::Account( parent, acctId, name ), d( new Private( QString::fromLatin1("Account_")+ parent->pluginId() + QString::fromLatin1("_") + acctId , maxLen, allowBlankPassword ) ) +{ +} + +Kopete::PasswordedAccount::~PasswordedAccount() +{ + delete d; +} + +Kopete::Password &Kopete::PasswordedAccount::password() +{ + return d->password; +} + +void Kopete::PasswordedAccount::connect( ) +{ + Kopete::OnlineStatus s(Kopete::OnlineStatus::Online); + connect( s ); +} + +void Kopete::PasswordedAccount::connect( const Kopete::OnlineStatus& initialStatus ) +{ + // check that the networkstatus is up + + // warn user somewhere + d->initialStatus = initialStatus; + QString cached = password().cachedValue(); + if( !cached.isNull() || d->password.allowBlankPassword() ) + { + connectWithPassword( cached ); + return; + } + + QString prompt = passwordPrompt(); + Kopete::Password::PasswordSource src = password().isWrong() ? Kopete::Password::FromUser : Kopete::Password::FromConfigOrUser; + + password().request( this, SLOT( connectWithPassword( const QString & ) ), accountIcon( Kopete::Password::preferredImageSize() ), prompt, src ); +} + +QString Kopete::PasswordedAccount::passwordPrompt() +{ + if ( password().isWrong() ) + return i18n( "<b>The password was wrong;</b> please re-enter your password for %1 account <b>%2</b>" ).arg( protocol()->displayName(), accountId() ); + else + return i18n( "Please enter your password for %1 account <b>%2</b>" ).arg( protocol()->displayName(), accountId() ); +} + +Kopete::OnlineStatus Kopete::PasswordedAccount::initialStatus() +{ + return d->initialStatus; +} + +bool Kopete::PasswordedAccount::removeAccount() +{ + password().set(QString::null); + return Kopete::Account::removeAccount(); +} + +void Kopete::PasswordedAccount::disconnected( Kopete::Account::DisconnectReason reason ) +{ + if(reason==Kopete::Account::BadPassword || reason==Kopete::Account::BadUserName) + { + password().setWrong(true); + } + Kopete::Account::disconnected(reason); +} + + +#include "kopetepasswordedaccount.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetepasswordedaccount.h b/kopete/libkopete/kopetepasswordedaccount.h new file mode 100644 index 00000000..1534025d --- /dev/null +++ b/kopete/libkopete/kopetepasswordedaccount.h @@ -0,0 +1,132 @@ +/* + kopetepasswordedaccount.h - Kopete Account with a password + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPASSWORDEDACCOUNT_H +#define KOPETEPASSWORDEDACCOUNT_H + +#include "kopeteaccount.h" + +#include "kopete_export.h" + +class Kopete::OnlineStatus; + +namespace Kopete +{ + +class Password; + +/** + * An account requiring a password to connect. Instead of reimplementing connect() + * in your subclass, reimplement connectWithPassword. + * + * @author Richard Smith <kde@metafoo.co.uk> + */ +class KOPETE_EXPORT PasswordedAccount : public Account +{ + Q_OBJECT + +public: + + /** + * KopetePasswordedAccount constructor + * @param parent The protocol this account connects via + * @param acctId The ID of this account - should be unique within this protocol + * @param maxPasswordLength The maximum length for passwords for this account, or 0 for no limit + * @param name The name for this QObject + * + * @deprecated Use the constructor that specifies if a blank password is allowed + */ + PasswordedAccount( Protocol *parent, const QString &acctId, uint maxPasswordLength = 0, const char *name = 0 ); + + /** + * KopetePasswordedAccount constructor + * @param parent The protocol this account connects via + * @param acctId The ID of this account - should be unique within this protocol + * @param maxPasswordLength The maximum length for passwords for this account, or 0 for no limit + * @param allowBlankPassword If this protocol allows blank passwords. Note that this will mean that + * + * @param name The name for this QObject + */ + PasswordedAccount( Protocol *parent, const QString &acctId, uint maxPasswordLength = 0, + bool allowBlankPassword = false, const char *name = 0 ); + + virtual ~PasswordedAccount(); + + /** + * Returns a reference to the password object stored in this account. + */ + Password &password(); + + void connect(); + + /** + * @brief Go online for this service. + * + * @param initialStatus is the status to connect with. If it is an invalid status for this + * account, the default online for the account should be used. + */ + void connect( const OnlineStatus& initialStatus ); + + /** + * \brief Get the initial status + */ + OnlineStatus initialStatus(); + + /** + * @brief Remove the account from the server. + * + * Reimplementation of Account::removeAccount() to remove the password from the wallet. + * if your protocol reimplements this function, this function should still be called. + * + * @return Always true + */ + virtual bool removeAccount(); + + +public slots: + /** + * Called when your account should attempt to connect. + * @param password The password to connect with, or QString::null + * if the user wished to cancel the connection attempt. + */ + virtual void connectWithPassword( const QString &password ) = 0; + +protected: + /** + * Returns the prompt shown to the user when requesting their password. + * The default implementation should be adequate in most cases; override + * if you have a custom message to show the user. + */ + virtual QString passwordPrompt(); + +protected slots: + /** + * @internal + * Reimplemented to set the password wrong if the reason is BadPassword + */ + virtual void disconnected( Kopete::Account::DisconnectReason reason ); + + +private: + class Private; + Private *d; +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetepicture.cpp b/kopete/libkopete/kopetepicture.cpp new file mode 100644 index 00000000..1c586b40 --- /dev/null +++ b/kopete/libkopete/kopetepicture.cpp @@ -0,0 +1,197 @@ +/* + kopetepicture.cpp - Kopete Picture + + Copyright (c) 2005 by Michaël Larouche <michael.larouche@kdemail.net> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ +#include "kopetepicture.h" + +#include <qbuffer.h> + +#include <kabc/picture.h> + +#include <kmdcodec.h> +#include <kstandarddirs.h> +#include <kdebug.h> + +namespace Kopete +{ + +class Picture::Private : public KShared +{ +public: + Private() + {} + + QString pictureBase64; + QImage pictureImage; + QString picturePath; +}; + +Picture::Picture() + : d(new Private) +{ +} + +Picture::Picture(const QString &path) + : d(new Private) +{ + setPicture(path); +} + +Picture::Picture(const QImage &image) + : d(new Private) +{ + setPicture(image); +} + +Picture::Picture(const KABC::Picture &picture) + : d(new Private) +{ + setPicture(picture); +} + +Picture::Picture(const Picture &other) + : d(other.d) +{} + +Picture::~Picture() +{} + +Picture &Picture::operator=(const Picture &other) +{ + d = other.d; + return *this; +} + +QImage Picture::image() +{ + // Do the conversion if only needed. + // If the image is null, the path is not empty then. + if( d->pictureImage.isNull() ) + { + d->pictureImage = QImage(d->picturePath); + } + + return d->pictureImage; +} + +QString Picture::base64() +{ + if( d->pictureBase64.isEmpty() ) + { + // Generate base64 cache for the picture. + QByteArray tempArray; + QBuffer tempBuffer( tempArray ); + tempBuffer.open( IO_WriteOnly ); + // Make sure it create a image cache. + if( image().save( &tempBuffer, "PNG" ) ) + { + d->pictureBase64 = KCodecs::base64Encode(tempArray); + } + } + + return d->pictureBase64; +} + +QString Picture::path() +{ + if( d->picturePath.isEmpty() ) + { + // For a image source, finding a filename is tricky. + // I decided to use MD5 Hash as the filename. + QString localPhotoPath; + + // Generate MD5 Hash for the image. + QByteArray tempArray; + QBuffer tempBuffer(tempArray); + tempBuffer.open( IO_WriteOnly ); + image().save(&tempBuffer, "PNG"); + KMD5 context(tempArray); + // Save the image to a file. + localPhotoPath = context.hexDigest() + ".png"; + localPhotoPath = locateLocal( "appdata", QString::fromUtf8("metacontactpicturecache/%1").arg( localPhotoPath) ); + if( image().save(localPhotoPath, "PNG") ) + { + d->picturePath = localPhotoPath; + } + } + + return d->picturePath; +} + +bool Picture::isNull() +{ + if( d->pictureBase64.isEmpty() && d->picturePath.isEmpty() && d->pictureImage.isNull() ) + { + return true; + } + else + { + return false; + } +} + +void Picture::clear() +{ + detach(); + d->pictureBase64 = QString::null; + d->picturePath = QString::null; + d->pictureImage = QImage(); +} + +void Picture::setPicture(const QImage &image) +{ + detach(); + + d->pictureImage = image; + + // Clear the path and base64, it will call the update of then when "getted" + d->picturePath= QString::null; + d->pictureBase64 = QString::null; +} + +void Picture::setPicture(const QString &path) +{ + detach(); + d->picturePath = path; + + // Clear the image and base64, it will call the update of then when "getted" + d->pictureImage = QImage(); + d->pictureBase64 = QString::null; +} + +void Picture::setPicture(const KABC::Picture &picture) +{ + // No need to call detach() here because setPicture will do it. + if ( picture.isIntern()) + { + setPicture( picture.data() ); + } + else + { + setPicture( picture.url() ); + } +} + +void Picture::detach() +{ + // there is no detach in KSharedPtr. + if( d.count() == 1 ) + return; + + // Warning: this only works as long as the private object doesn't contain pointers to allocated objects. + d = new Private(*d); +} + +} // END namespace Kopete diff --git a/kopete/libkopete/kopetepicture.h b/kopete/libkopete/kopetepicture.h new file mode 100644 index 00000000..5631afc1 --- /dev/null +++ b/kopete/libkopete/kopetepicture.h @@ -0,0 +1,149 @@ +/* + kopetepicture.h - Kopete Picture + + Copyright (c) 2005 by Michaël Larouche <michael.larouche@kdemail.net> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ +#ifndef KOPETEPICTURE_H +#define KOPETEPICTURE_H + +#include <kdemacros.h> +#include <ksharedptr.h> +#include "kopete_export.h" + +#include <qimage.h> + +namespace KABC +{ + class Picture; +} + +namespace Kopete +{ +/** + * @brief Represent a picture in Kopete context + * + * It kept a cache of a QImage object, a base64 string and + * a path to a image file. It ensure that all source are synced. + * Interally, the image is stored in PNG format when possible. + * It can happen that the image path do not return a PNG file. + * + * You can only use an QImage and a image path to create/update + * the picture. + * If the picture doesn't exist as a file, it generate a local + * copy into ~/.kde/share/apps/kopete/metacontactpicturecache + * + * This class is implicitly shared, so don't use it as a pointer. + * + * How to use this class: + * @code + * Kopete::Picture picture; + * picture.setPicture(QImage()); + * picture.setPicture(QString("/tmp/image.png")); + * + * QString base64 = picture.base64(); + * QString path = picture.path(); + * QImage image = picture.image(); + * @endcode + * + * @author Michaël Larouche <michael.larouche@kdemail.net> + */ +class KOPETE_EXPORT Picture +{ +public: + /** + * Create a empty Kopete::Picture + */ + Picture(); + /** + * Create a picture from a local path. + */ + Picture(const QString &path); + /** + * Create a picture from a QImage. + */ + Picture(const QImage &image); + /** + * Create a picture from a KABC::Picture. + */ + Picture(const KABC::Picture &picture); + /** + * Copy a picture. It doesn't create a full copy, it just make a reference. + */ + Picture(const Picture &other); + /** + * Delete the Kopete::Picture + */ + ~Picture(); + /** + * Assignment operator. + * Like the copy constructor, it just make a reference. + */ + Picture &operator=(const Picture &other); + + /** + * Return the current picture as QImage. + * QImage can used to draw the image on a context. + * + * @return the QImage cache of current picture. + */ + QImage image(); + /** + * Return the current picture as a base64 string. + * The base64 is used to include the picture into a XML/XHTML context. + */ + QString base64(); + /** + * Return the local path of the current picture. + */ + QString path(); + + /** + * Check if the picture is null. + */ + bool isNull(); + /** + * Reset the picture. + */ + void clear(); + + /** + * Set the picture content. + * @param image the picture as a QImage. + */ + void setPicture(const QImage &image); + /** + * Set the picture content. + * @param path the path to the picture. + */ + void setPicture(const QString &path); + /** + * Set the picture content. + * @param picture a KABC Picture. + */ + void setPicture(const KABC::Picture &picture); + +private: + /** + * Kopete::Picture is implicitly shared. + * Detach the instance when modifying data. + */ + void detach(); + + class Private; + KSharedPtr<Private> d; +}; + +}//END namespace Kopete + +#endif diff --git a/kopete/libkopete/kopeteplugin.cpp b/kopete/libkopete/kopeteplugin.cpp new file mode 100644 index 00000000..cec99179 --- /dev/null +++ b/kopete/libkopete/kopeteplugin.cpp @@ -0,0 +1,110 @@ +/* + kopeteplugin.cpp - Kopete Plugin API + + Copyright (c) 2001-2002 by Duncan Mac-Vicar P. <duncan@kde.org> + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart @tiscalinet.be> + + Copyright (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteplugin.h" +#include "kopetepluginmanager.h" + +#include <kplugininfo.h> +#include <ksettings/dispatcher.h> +#include <kplugininfo.h> + +namespace Kopete { + +class Plugin::Private +{ +public: + QStringList addressBookFields; + QString indexField; +}; + +Plugin::Plugin( KInstance *instance, QObject *parent, const char *name ) +: QObject( parent, name ), KXMLGUIClient(), d(new Private) +{ + setInstance( instance ); + KSettings::Dispatcher::self()->registerInstance( instance, this, SIGNAL( settingsChanged() ) ); +} + +Plugin::~Plugin() +{ + delete d; +} + +QString Plugin::pluginId() const +{ + return QString::fromLatin1( className() ); +} + + +QString Plugin::displayName() const +{ + return pluginInfo() ? pluginInfo()->name() : QString::null; +} + +QString Plugin::pluginIcon() const +{ + return pluginInfo() ? pluginInfo()->icon() : QString::null; +} + + +KPluginInfo *Plugin::pluginInfo() const +{ + return PluginManager::self()->pluginInfo( this ); +} + +void Plugin::aboutToUnload() +{ + // Just make the unload synchronous by default + emit readyForUnload(); +} + + +void Plugin::deserialize( MetaContact * /* metaContact */, + const QMap<QString, QString> & /* stream */ ) +{ + // Do nothing in default implementation +} + + + +void Kopete::Plugin::addAddressBookField( const QString &field, AddressBookFieldAddMode mode ) +{ + d->addressBookFields.append( field ); + if( mode == MakeIndexField ) + d->indexField = field; +} + +QStringList Kopete::Plugin::addressBookFields() const +{ + return d->addressBookFields; +} + +QString Kopete::Plugin::addressBookIndexField() const +{ + return d->indexField; + +} + + +void Plugin::virtual_hook( uint, void * ) { } + +} //END namespace Kopete + + +#include "kopeteplugin.moc" + + diff --git a/kopete/libkopete/kopeteplugin.desktop b/kopete/libkopete/kopeteplugin.desktop new file mode 100644 index 00000000..f5c29fd4 --- /dev/null +++ b/kopete/libkopete/kopeteplugin.desktop @@ -0,0 +1,69 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Kopete/Plugin +X-KDE-Derived=KPluginInfo +Comment=Kopete Plugin +Comment[ar]=توصيلة Kopete +Comment[be]=Модуль Kopete +Comment[bg]=Приставки на Kopete +Comment[bn]=কপেট প্লাগিন +Comment[br]=Lugent Kopete +Comment[bs]=Kopete dodatak +Comment[ca]=Connector de Kopete +Comment[cs]=Modul aplikace Kopete +Comment[cy]=Ategyn Kopete +Comment[da]=Kopete-plugin +Comment[de]=Kopete-Modul +Comment[el]=Πρόσθετο Kopete +Comment[eo]=Kopete-kromaĵo +Comment[es]=Complemento de Kopete +Comment[et]=Kopete plugin +Comment[eu]=Kopete plugin-a +Comment[fa]=Kopete وصلۀ +Comment[fi]=Kopete-liitännäinen +Comment[fr]=Module de Kopete +Comment[ga]=Breiseán Kopete +Comment[gl]=Plugin de Kopete +Comment[he]=תוסף Kopete +Comment[hi]=के-ऑप्टी प्लगइन +Comment[hr]=Umetak za Kopete +Comment[hu]=Kopete bővítőmodul +Comment[is]=Kopete íforrit +Comment[it]=Plugin di Kopete +Comment[ja]=Kopete プラグイン +Comment[ka]=Kopeteს მოდული +Comment[kk]=Kopete плагин модулі +Comment[km]=កម្មវិធីជំនួយ Kopete +Comment[lt]=Kopete įskiepis +Comment[mk]=Приклучок за Kopete +Comment[nb]=Programtillegg for Kopete +Comment[nds]=Kopete-Moduul +Comment[ne]=कोपेट प्लगइन +Comment[nl]=Kopete-plugin +Comment[nn]=Kopete-programtillegg +Comment[pl]=Wtyczka Kopete +Comment[pt]='Plugin' do Kopete +Comment[pt_BR]=Plug-in do Kopete +Comment[ro]=Modul Kopete +Comment[ru]=Модуль Kopete +Comment[se]=Kopete lassemoduvla +Comment[sk]=Modul Kopete +Comment[sl]=Vstavek za Kopete +Comment[sr]=Прикључак за Kopete +Comment[sr@Latn]=Priključak za Kopete +Comment[sv]=Insticksprogram för Kopete +Comment[ta]=Kopete செருகல் +Comment[tg]=Модули Kopete +Comment[tr]=Kopete Eklentisi +Comment[uk]=Втулок Kopete +Comment[uz]=Kopete plagini +Comment[uz@cyrillic]=Kopete плагини +Comment[wa]=Tchôke-divins po Kopete +Comment[zh_CN]=Kopete 插件 +Comment[zh_HK]=Kopete 插件 +Comment[zh_TW]=Kopete 外掛程式 + +# The Kopete version for which the plugin is written +[PropertyDef::X-Kopete-Version] +Type=int + diff --git a/kopete/libkopete/kopeteplugin.h b/kopete/libkopete/kopeteplugin.h new file mode 100644 index 00000000..43a80849 --- /dev/null +++ b/kopete/libkopete/kopeteplugin.h @@ -0,0 +1,217 @@ +/* + kopeteplugin.h - Kopete Plugin API + + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart@ tiscalinet.be> + + Copyright (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPLUGIN_H +#define KOPETEPLUGIN_H + +#include <kxmlguiclient.h> +#include <qobject.h> +#include <kdemacros.h> + +#include "kopete_export.h" + +#include <kopetemessage.h> //TODO: remove +namespace DOM { class Node; } //TODO: remove +class KAction; //TODO: remove + + +class KPluginInfo; + + +namespace Kopete +{ + +class MetaContact; + +/** + * @brief Base class for all plugins or protocols. + * + * To create a plugin, you need to create a .desktop file which looks like that: + * \verbatim +[Desktop Entry] +Encoding=UTF-8 +Type=Service +X-Kopete-Version=1000900 +Icon=icon +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_myplugin +X-KDE-PluginInfo-Author=Your Name +X-KDE-PluginInfo-Email=your@mail.com +X-KDE-PluginInfo-Name=kopete_myplugin +X-KDE-PluginInfo-Version=0.0.1 +X-KDE-PluginInfo-Website=http://yoursite.com +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=MyPlugin +Comment=Plugin that do some nice stuff + \endverbatim + * + * The constructor of your plugin should looks like this: + * + * \code + typedef KGenericFactory<MyPlugin> MyPluginFactory; + static const KAboutData aboutdata("kopete_myplugin", I18N_NOOP("MyPlugin") , "1.0" ); + K_EXPORT_COMPONENT_FACTORY( kopete_myplugin, MyPluginFactory( &aboutdata ) ) + + MyPlugin::MyPlugin( QObject *parent, const char *name, const QStringList & args ) + : Kopete::Plugin( MyPluginFactory::instance(), parent, name ) + { + //... + } + \endcode + * + * Kopete::Plugin inherits from KXMLGUIClient. That client is added + * to the Kopete's mainwindow KXMLGUIFactory. So you may add actions + * on the main window (for hinstance in the meta contact popup menu). + * Please note the the client is added right after the plugin is created. + * so you have to create every actions in the constructor + * + * @author Duncan Mac-Vicar P. <duncan@kde.org> + * @author Olivier Goffart <ogoffart @ tiscalinet.be> + */ +class KOPETE_EXPORT Plugin : public QObject, public KXMLGUIClient +{ + Q_OBJECT + +public: + Plugin( KInstance *instance, QObject *parent, const char *name ); + virtual ~Plugin(); + + /** + * Returns the KPluginInfo object associated with this plugin + */ + KPluginInfo *pluginInfo() const; + + /** + * Get the name of the icon for this plugin. The icon name is taken from the + * .desktop file. + * + * May return an empty string if the .desktop file for this plugin specifies + * no icon name to use. + * + * This is a convenience method that simply calls @ref pluginInfo()->icon(). + */ + QString pluginIcon() const; + + /** + * Returns the display name of this plugin. + * + * This is a convenience method that simply calls @ref pluginInfo()->name(). + */ + QString displayName() const; + + /** + * @brief Get the plugin id + * @return the plugin's id which is gotten by calling QObject::className(). + */ + QString pluginId() const; + + /** + * Return the list of all keys from the address book in which the plugin + * is interested. Those keys are monitored for changes upon load and + * during runtime. When the key actually changes, the plugin's + * addressBookKeyChanged( Kopete::MetaContact *mc, const QString &key ) + * is called. + * You can add fields to the list using @ref addAddressBookField() + */ + QStringList addressBookFields() const; + + /** + * Return the index field as set by @ref addAddressBookField() + */ + QString addressBookIndexField() const; + + /** + * Mode for an address book field as used by @ref addAddressBookField() + */ + enum AddressBookFieldAddMode { AddOnly, MakeIndexField }; + + /** + * Add a field to the list of address book fields. See also @ref addressBookFields() + * for a description of the fields. + * + * Set mode to MakeIndexField to make this the index field. Index fields + * are currently used by Kopete::Contact::serialize to autoset the index + * when possible. + * + * Only one field can be index field. Calling this method multiple times + * as index field will reset the value of index field! + */ + void addAddressBookField( const QString &field, AddressBookFieldAddMode mode = AddOnly ); + + /** + * @brief Prepare for unloading a plugin + * + * When unloading a plugin the plugin manager first calls aboutToUnload() + * to indicate the pending unload. Some plugins need time to shutdown + * asynchronously and thus can't be simply deleted in the destructor. + * + * The default implementation immediately emits the @ref readyForUnload() signal, + * which basically makes the shutdown immediate and synchronous. If you need + * more time you can reimplement this method and fire the signal whenever + * you're ready. (you have 3 seconds) + * + * @ref Kopete::Protocol reimplement it. + */ + virtual void aboutToUnload(); + +signals: + /** + * Notify that the settings of a plugin were changed. + * These changes are passed on from the new KCDialog code in kdelibs/kutils. + */ + void settingsChanged(); + + /** + * Indicate when we're ready for unload. + * @see aboutToUnload() + */ + void readyForUnload(); + +public slots: + + /** + * deserialize() and tell the plugin + * to apply the previously stored data again. + * This method is also responsible for retrieving the settings from the + * address book. Settings that were registered can be retrieved with + * @ref Kopete::MetaContact::addressBookField(). + * + * The default implementation does nothing. + * + * @todo we probably should think to another way to save the contacltist. + */ + virtual void deserialize( MetaContact *metaContact, const QMap<QString, QString> &data ); + + +protected: + virtual void virtual_hook( uint id, void *data ); + +private: + class Private; + Private *d; +}; + + +} //END namespace Kopete + + +#endif diff --git a/kopete/libkopete/kopetepluginmanager.cpp b/kopete/libkopete/kopetepluginmanager.cpp new file mode 100644 index 00000000..8f613a86 --- /dev/null +++ b/kopete/libkopete/kopetepluginmanager.cpp @@ -0,0 +1,534 @@ +/* + kopetepluginmanager.cpp - Kopete Plugin Loader + + Copyright (c) 2002-2003 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart @tiscalinet.be> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "config.h" + +#include "kopetepluginmanager.h" + +#if defined(HAVE_VALGRIND_H) && !defined(NDEBUG) && defined(__i386__) +// We don't want the per-skin includes, so pretend we have a skin header already +#define __VALGRIND_SOMESKIN_H +#include <valgrind/valgrind.h> +#endif + +#include <qapplication.h> +#include <qfile.h> +#include <qregexp.h> +#include <qtimer.h> +#include <qvaluestack.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kparts/componentfactory.h> +#include <kplugininfo.h> +#include <ksimpleconfig.h> +#include <kstandarddirs.h> +#include <kstaticdeleter.h> +#include <kurl.h> + +#include "kopeteplugin.h" +#include "kopetecontactlist.h" +#include "kopeteaccountmanager.h" + +namespace Kopete +{ + + +class PluginManager::Private +{ +public: + Private() : shutdownMode( StartingUp ), isAllPluginsLoaded(false) {} + + // All available plugins, regardless of category, and loaded or not + QValueList<KPluginInfo *> plugins; + + // Dict of all currently loaded plugins, mapping the KPluginInfo to + // a plugin + typedef QMap<KPluginInfo *, Plugin *> InfoToPluginMap; + InfoToPluginMap loadedPlugins; + + // The plugin manager's mode. The mode is StartingUp until loadAllPlugins() + // has finished loading the plugins, after which it is set to Running. + // ShuttingDown and DoneShutdown are used during Kopete shutdown by the + // async unloading of plugins. + enum ShutdownMode { StartingUp, Running, ShuttingDown, DoneShutdown }; + ShutdownMode shutdownMode; + + // Plugins pending for loading + QValueStack<QString> pluginsToLoad; + + static KStaticDeleter<PluginManager> deleter; + + bool isAllPluginsLoaded; +}; + +KStaticDeleter<PluginManager> PluginManager::Private::deleter; +PluginManager* PluginManager::s_self = 0L; + +PluginManager* PluginManager::self() +{ + if ( !s_self ) + Private::deleter.setObject( s_self, new PluginManager() ); + + return s_self; +} + +PluginManager::PluginManager() : QObject( qApp ), d( new Private ) +{ + d->plugins = KPluginInfo::fromServices( KTrader::self()->query( QString::fromLatin1( "Kopete/Plugin" ), + QString::fromLatin1( "[X-Kopete-Version] == 1000900" ) ) ); + + // We want to add a reference to the application's event loop so we + // can remain in control when all windows are removed. + // This way we can unload plugins asynchronously, which is more + // robust if they are still doing processing. + kapp->ref(); +} + +PluginManager::~PluginManager() +{ + if ( d->shutdownMode != Private::DoneShutdown ) + kdWarning( 14010 ) << k_funcinfo << "Destructing plugin manager without going through the shutdown process! Backtrace is: " << endl << kdBacktrace() << endl; + + // Quick cleanup of the remaining plugins, hope it helps + // Note that deleting it.data() causes slotPluginDestroyed to be called, which + // removes the plugin from the list of loaded plugins. + while ( !d->loadedPlugins.empty() ) + { + Private::InfoToPluginMap::ConstIterator it = d->loadedPlugins.begin(); + kdWarning( 14010 ) << k_funcinfo << "Deleting stale plugin '" << it.data()->name() << "'" << endl; + delete it.data(); + } + + delete d; +} + +QValueList<KPluginInfo *> PluginManager::availablePlugins( const QString &category ) const +{ + if ( category.isEmpty() ) + return d->plugins; + + QValueList<KPluginInfo *> result; + QValueList<KPluginInfo *>::ConstIterator it; + for ( it = d->plugins.begin(); it != d->plugins.end(); ++it ) + { + if ( ( *it )->category() == category ) + result.append( *it ); + } + + return result; +} + +PluginList PluginManager::loadedPlugins( const QString &category ) const +{ + PluginList result; + + for ( Private::InfoToPluginMap::ConstIterator it = d->loadedPlugins.begin(); + it != d->loadedPlugins.end(); ++it ) + { + if ( category.isEmpty() || it.key()->category() == category ) + result.append( it.data() ); + } + + return result; +} + + +KPluginInfo *PluginManager::pluginInfo( const Plugin *plugin ) const +{ + for ( Private::InfoToPluginMap::ConstIterator it = d->loadedPlugins.begin(); + it != d->loadedPlugins.end(); ++it ) + { + if ( it.data() == plugin ) + return it.key(); + } + return 0; +} + +void PluginManager::shutdown() +{ + if(d->shutdownMode != Private::Running) + { + kdDebug( 14010 ) << k_funcinfo << "called when not running. / state = " << d->shutdownMode << endl; + return; + } + + d->shutdownMode = Private::ShuttingDown; + + + /* save the contact list now, just in case a change was made very recently + and it hasn't autosaved yet + from a OO point of view, theses lines should not be there, but i don't + see better place -Olivier + */ + Kopete::ContactList::self()->save(); + Kopete::AccountManager::self()->save(); + + // Remove any pending plugins to load, we're shutting down now :) + d->pluginsToLoad.clear(); + + // Ask all plugins to unload + for ( Private::InfoToPluginMap::ConstIterator it = d->loadedPlugins.begin(); + it != d->loadedPlugins.end(); /* EMPTY */ ) + { + // Plugins could emit their ready for unload signal directly in response to this, + // which would invalidate the current iterator. Therefore, we copy the iterator + // and increment it beforehand. + Private::InfoToPluginMap::ConstIterator current( it ); + ++it; + // FIXME: a much cleaner approach would be to just delete the plugin now. if it needs + // to do some async processing, it can grab a reference to the app itself and create + // another object to do it. + current.data()->aboutToUnload(); + } + + // When running under valgrind, don't enable the timer because it will almost + // certainly fire due to valgrind's much slower processing +#if defined(HAVE_VALGRIND_H) && !defined(NDEBUG) && defined(__i386__) + if ( RUNNING_ON_VALGRIND ) + kdDebug(14010) << k_funcinfo << "Running under valgrind, disabling plugin unload timeout guard" << endl; + else +#endif + QTimer::singleShot( 3000, this, SLOT( slotShutdownTimeout() ) ); +} + +void PluginManager::slotPluginReadyForUnload() +{ + // Using QObject::sender() is on purpose here, because otherwise all + // plugins would have to pass 'this' as parameter, which makes the API + // less clean for plugin authors + // FIXME: I don't buy the above argument. Add a Kopete::Plugin::emitReadyForUnload(void), + // and make readyForUnload be passed a plugin. - Richard + Plugin *plugin = dynamic_cast<Plugin *>( const_cast<QObject *>( sender() ) ); + kdDebug( 14010 ) << k_funcinfo << plugin->pluginId() << "ready for unload" << endl; + if ( !plugin ) + { + kdWarning( 14010 ) << k_funcinfo << "Calling object is not a plugin!" << endl; + return; + } + + plugin->deleteLater(); +} + + +void PluginManager::slotShutdownTimeout() +{ + // When we were already done the timer might still fire. + // Do nothing in that case. + if ( d->shutdownMode == Private::DoneShutdown ) + return; + + QStringList remaining; + for ( Private::InfoToPluginMap::ConstIterator it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it ) + remaining.append( it.data()->pluginId() ); + + kdWarning( 14010 ) << k_funcinfo << "Some plugins didn't shutdown in time!" << endl + << "Remaining plugins: " << remaining.join( QString::fromLatin1( ", " ) ) << endl + << "Forcing Kopete shutdown now." << endl; + + slotShutdownDone(); +} + +void PluginManager::slotShutdownDone() +{ + kdDebug( 14010 ) << k_funcinfo << endl; + + d->shutdownMode = Private::DoneShutdown; + + kapp->deref(); +} + +void PluginManager::loadAllPlugins() +{ + // FIXME: We need session management here - Martijn + + KConfig *config = KGlobal::config(); + if ( config->hasGroup( QString::fromLatin1( "Plugins" ) ) ) + { + QMap<QString, bool> pluginsMap; + + QMap<QString, QString> entries = config->entryMap( QString::fromLatin1( "Plugins" ) ); + QMap<QString, QString>::Iterator it; + for ( it = entries.begin(); it != entries.end(); ++it ) + { + QString key = it.key(); + if ( key.endsWith( QString::fromLatin1( "Enabled" ) ) ) + pluginsMap.insert( key.left( key.length() - 7 ), (it.data() == QString::fromLatin1( "true" )) ); + } + + QValueList<KPluginInfo *> plugins = availablePlugins( QString::null ); + QValueList<KPluginInfo *>::ConstIterator it2 = plugins.begin(); + QValueList<KPluginInfo *>::ConstIterator end = plugins.end(); + for ( ; it2 != end; ++it2 ) + { + // Protocols are loaded automatically so they aren't always in Plugins group. (fixes bug 167113) + if ( (*it2)->category() == QString::fromLatin1( "Protocols" ) ) + continue; + + QString pluginName = (*it2)->pluginName(); + bool inMap = pluginsMap.contains( pluginName ); + if ( (inMap && pluginsMap[pluginName]) || (!inMap && (*it2)->isPluginEnabledByDefault()) ) + { + if ( !plugin( pluginName ) ) + d->pluginsToLoad.push( pluginName ); + } + else + { + //This happens if the user unloaded plugins with the config plugin page. + // No real need to be assync because the user usualy unload few plugins + // compared tto the number of plugin to load in a cold start. - Olivier + if ( plugin( pluginName ) ) + unloadPlugin( pluginName ); + } + } + } + else + { + // we had no config, so we load any plugins that should be loaded by default. + QValueList<KPluginInfo *> plugins = availablePlugins( QString::null ); + QValueList<KPluginInfo *>::ConstIterator it = plugins.begin(); + QValueList<KPluginInfo *>::ConstIterator end = plugins.end(); + for ( ; it != end; ++it ) + { + if ( (*it)->isPluginEnabledByDefault() ) + d->pluginsToLoad.push( (*it)->pluginName() ); + } + } + // Schedule the plugins to load + QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) ); +} + +void PluginManager::slotLoadNextPlugin() +{ + if ( d->pluginsToLoad.isEmpty() ) + { + if ( d->shutdownMode == Private::StartingUp ) + { + d->shutdownMode = Private::Running; + d->isAllPluginsLoaded = true; + emit allPluginsLoaded(); + } + return; + } + + QString key = d->pluginsToLoad.pop(); + loadPluginInternal( key ); + + // Schedule the next run unconditionally to avoid code duplication on the + // allPluginsLoaded() signal's handling. This has the added benefit that + // the signal is delayed one event loop, so the accounts are more likely + // to be instantiated. + QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) ); +} + +Plugin * PluginManager::loadPlugin( const QString &_pluginId, PluginLoadMode mode /* = LoadSync */ ) +{ + QString pluginId = _pluginId; + + // Try to find legacy code + // FIXME: Find any cases causing this, remove them, and remove this too - Richard + if ( pluginId.endsWith( QString::fromLatin1( ".desktop" ) ) ) + { + kdWarning( 14010 ) << "Trying to use old-style API!" << endl << kdBacktrace() << endl; + pluginId = pluginId.remove( QRegExp( QString::fromLatin1( ".desktop$" ) ) ); + } + + if ( mode == LoadSync ) + { + return loadPluginInternal( pluginId ); + } + else + { + d->pluginsToLoad.push( pluginId ); + QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) ); + return 0L; + } +} + +Plugin *PluginManager::loadPluginInternal( const QString &pluginId ) +{ + //kdDebug( 14010 ) << k_funcinfo << pluginId << endl; + + KPluginInfo *info = infoForPluginId( pluginId ); + if ( !info ) + { + kdWarning( 14010 ) << k_funcinfo << "Unable to find a plugin named '" << pluginId << "'!" << endl; + return 0L; + } + + if ( d->loadedPlugins.contains( info ) ) + return d->loadedPlugins[ info ]; + + int error = 0; + Plugin *plugin = KParts::ComponentFactory::createInstanceFromQuery<Plugin>( QString::fromLatin1( "Kopete/Plugin" ), + QString::fromLatin1( "[X-KDE-PluginInfo-Name]=='%1'" ).arg( pluginId ), this, 0, QStringList(), &error ); + + if ( plugin ) + { + d->loadedPlugins.insert( info, plugin ); + info->setPluginEnabled( true ); + + connect( plugin, SIGNAL( destroyed( QObject * ) ), this, SLOT( slotPluginDestroyed( QObject * ) ) ); + connect( plugin, SIGNAL( readyForUnload() ), this, SLOT( slotPluginReadyForUnload() ) ); + + kdDebug( 14010 ) << k_funcinfo << "Successfully loaded plugin '" << pluginId << "'" << endl; + + emit pluginLoaded( plugin ); + } + else + { + switch( error ) + { + case KParts::ComponentFactory::ErrNoServiceFound: + kdDebug( 14010 ) << k_funcinfo << "No service implementing the given mimetype " + << "and fullfilling the given constraint expression can be found." << endl; + break; + + case KParts::ComponentFactory::ErrServiceProvidesNoLibrary: + kdDebug( 14010 ) << "the specified service provides no shared library." << endl; + break; + + case KParts::ComponentFactory::ErrNoLibrary: + kdDebug( 14010 ) << "the specified library could not be loaded." << endl; + break; + + case KParts::ComponentFactory::ErrNoFactory: + kdDebug( 14010 ) << "the library does not export a factory for creating components." << endl; + break; + + case KParts::ComponentFactory::ErrNoComponent: + kdDebug( 14010 ) << "the factory does not support creating components of the specified type." << endl; + break; + } + + kdDebug( 14010 ) << k_funcinfo << "Loading plugin '" << pluginId << "' failed, KLibLoader reported error: '" << endl << + KLibLoader::self()->lastErrorMessage() << "'" << endl; + } + + return plugin; +} + +bool PluginManager::unloadPlugin( const QString &spec ) +{ + //kdDebug(14010) << k_funcinfo << spec << endl; + if( Plugin *thePlugin = plugin( spec ) ) + { + thePlugin->aboutToUnload(); + return true; + } + else + return false; +} + + + +void PluginManager::slotPluginDestroyed( QObject *plugin ) +{ + for ( Private::InfoToPluginMap::Iterator it = d->loadedPlugins.begin(); + it != d->loadedPlugins.end(); ++it ) + { + if ( it.data() == plugin ) + { + d->loadedPlugins.erase( it ); + break; + } + } + + if ( d->shutdownMode == Private::ShuttingDown && d->loadedPlugins.isEmpty() ) + { + // Use a timer to make sure any pending deleteLater() calls have + // been handled first + QTimer::singleShot( 0, this, SLOT( slotShutdownDone() ) ); + } +} + + + + +Plugin* PluginManager::plugin( const QString &_pluginId ) const +{ + // Hack for compatibility with Plugin::pluginId(), which returns + // classname() instead of the internal name. Changing that is not easy + // as it invalidates the config file, the contact list, and most likely + // other code as well. + // For now, just transform FooProtocol to kopete_foo. + // FIXME: In the future we'll need to change this nevertheless to unify + // the handling - Martijn + QString pluginId = _pluginId; + if ( pluginId.endsWith( QString::fromLatin1( "Protocol" ) ) ) + pluginId = QString::fromLatin1( "kopete_" ) + _pluginId.lower().remove( QString::fromLatin1( "protocol" ) ); + // End hack + + KPluginInfo *info = infoForPluginId( pluginId ); + if ( !info ) + return 0L; + + if ( d->loadedPlugins.contains( info ) ) + return d->loadedPlugins[ info ]; + else + return 0L; +} + +KPluginInfo * PluginManager::infoForPluginId( const QString &pluginId ) const +{ + QValueList<KPluginInfo *>::ConstIterator it; + for ( it = d->plugins.begin(); it != d->plugins.end(); ++it ) + { + if ( ( *it )->pluginName() == pluginId ) + return *it; + } + + return 0L; +} + + +bool PluginManager::setPluginEnabled( const QString &_pluginId, bool enabled /* = true */ ) +{ + QString pluginId = _pluginId; + + KConfig *config = KGlobal::config(); + config->setGroup( "Plugins" ); + + // FIXME: What is this for? This sort of thing is kconf_update's job - Richard + if ( !pluginId.startsWith( QString::fromLatin1( "kopete_" ) ) ) + pluginId.prepend( QString::fromLatin1( "kopete_" ) ); + + if ( !infoForPluginId( pluginId ) ) + return false; + + config->writeEntry( pluginId + QString::fromLatin1( "Enabled" ), enabled ); + config->sync(); + + return true; +} + +bool PluginManager::isAllPluginsLoaded() const +{ + return d->isAllPluginsLoaded; +} + +} //END namespace Kopete + + +#include "kopetepluginmanager.moc" + + + + + diff --git a/kopete/libkopete/kopetepluginmanager.h b/kopete/libkopete/kopetepluginmanager.h new file mode 100644 index 00000000..815cf422 --- /dev/null +++ b/kopete/libkopete/kopetepluginmanager.h @@ -0,0 +1,246 @@ +/* + kopetepluginmanager.h - Kopete Plugin Loader + + Copyright (c) 2002-2003 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPLUGINMANAGER_H +#define KOPETEPLUGINMANAGER_H + +#include <qmap.h> +#include <qobject.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qvaluelist.h> + +#include "kopete_export.h" + +class KPluginInfo; + +namespace Kopete +{ + +class Plugin; + +typedef QValueList<Plugin*> PluginList; + +/** + * @author Duncan Mac-Vicar Prett <duncan@kde.org> + * @author Martijn Klingens <klingens@kde.org> + */ +class KOPETE_EXPORT PluginManager : public QObject +{ + Q_OBJECT + Q_ENUMS( PluginLoadMode ) + +public: + /** + * Retrieve the plugin loader instance. + */ + static PluginManager* self(); + + ~PluginManager(); + + /** + * Returns a list of all available plugins for the given category. + * Currently there are two categories, "Plugins" and "Protocols", but + * you can add your own categories if you want. + * + * If you pass an empty string you get the complete list of ALL plugins. + * + * You can query all information on the plugins through the KPluginInfo + * interface. + */ + QValueList<KPluginInfo *> availablePlugins( const QString &category = QString::null ) const; + + /** + * Returns a list of all plugins that are actually loaded. + * If you omit the category you get all, otherwise it's a filtered list. + * See also @ref availablePlugins(). + */ + PluginList loadedPlugins( const QString &category = QString::null ) const; + + /** + * @brief Search by plugin name. This is the key used as X-KDE-PluginInfo-Name in + * the .desktop file, e.g. "kopete_jabber" + * + * @return The @ref Kopete::Plugin object found by the search, or a null + * pointer if the plugin is not loaded. + * + * If you want to also load the plugin you can better use @ref loadPlugin, which returns + * the pointer to the plugin if it's already loaded. + */ + Plugin *plugin( const QString &pluginName ) const; + + /** + * @return the KPluginInfo for the specified plugin + */ + KPluginInfo *pluginInfo( const Kopete::Plugin *plugin ) const; + + + /** + * Shuts down the plugin manager on Kopete shutdown, but first + * unloads all plugins asynchronously. + * + * After 3 seconds all plugins should be removed; what's still left + * by then is unloaded through a hard delete instead. + * + * Note that this call also derefs the plugin manager from the event + * loop, so do NOT call this method when not terminating Kopete! + */ + void shutdown(); + + /** + * Enable a plugin. + * + * This marks a plugin as enabled in the config file, so loadAll() + * can pick it up later. + * + * This method does not actually load a plugin, it only edits the + * config file. + * + * @param name is the name of the plugin as it is listed in the .desktop + * file in the X-KDE-Library field. + * @param enabled sets whether or not the plugin is enabled + * + * Returns false when no appropriate plugin can be found. + */ + bool setPluginEnabled( const QString &name, bool enabled = true ); + + /** + * This method check if all the plugins are loaded. + * @return true if all the plugins are loaded. + */ + bool isAllPluginsLoaded() const; + + /** + * Plugin loading mode. Used by @ref loadPlugin(). Code that doesn't want to block + * the GUI and/or lot a lot of plugins at once should use asynchronous loading (@c LoadAsync). + * The default is synchronous loading (@c LoadSync). + */ + enum PluginLoadMode { LoadSync, LoadAsync }; + +public slots: + /** + * @brief Load a single plugin by plugin name. Returns an existing plugin + * if one is already loaded in memory. + * + * If mode is set to Async, the plugin will be queued and loaded in + * the background. This method will return a null pointer. To get + * the loaded plugin you can track the @ref pluginLoaded() signal. + * + * See also @ref plugin(). + */ + Plugin *loadPlugin( const QString &pluginId, PluginLoadMode mode = LoadSync ); + + /** + * @brief Unload the plugin specified by @p pluginName + */ + bool unloadPlugin( const QString &pluginName ); + + /** + * @brief Loads all the enabled plugins. Also used to reread the + * config file when the configuration has changed. + */ + void loadAllPlugins(); + +signals: + /** + * @brief Signals a new plugin has just been loaded. + */ + void pluginLoaded( Kopete::Plugin *plugin ); + + /** + * @brief All plugins have been loaded by the plugin manager. + * + * This signal is emitted exactly ONCE, when the plugin manager has emptied + * its plugin queue for the first time. This means that if you call an async + * loadPlugin() before loadAllPlugins() this signal is probably emitted after + * the initial call completes, unless you are quick enough to fill the queue + * before it completes, which is a dangerous race you shouldn't count upon :) + * + * The signal is delayed one event loop iteration through a singleShot timer, + * but that is not guaranteed to be enough for account instantiation. You may + * need an additional timer for it in the code if you want to programmatically + * act on it. + * + * If you use the signal for enabling/disabling GUI objects there is little + * chance a user is able to activate them in the short while that's remaining, + * the slow part of the code is over now and the remaining processing time + * is neglectable for the user. + */ + void allPluginsLoaded(); + +private slots: + /** + * @brief Cleans up some references if the plugin is destroyed + */ + void slotPluginDestroyed( QObject *plugin ); + + /** + * shutdown() starts a timer, when it fires we force all plugins + * to be unloaded here by deref()-ing the event loop to trigger the plugin + * manager's destruction + */ + void slotShutdownTimeout(); + + /** + * Common entry point to deref() the KApplication. Used both by the clean + * shutdown and the timeout condition of slotShutdownTimeout() + */ + void slotShutdownDone(); + + /** + * Emitted by a Kopete::Plugin when it's ready for unload + */ + void slotPluginReadyForUnload(); + + /** + * Load a plugin from our queue. Does nothing if the queue is empty. + * Schedules itself again if more plugins are pending. + */ + void slotLoadNextPlugin(); + +private: + /** + * @internal + * + * The internal method for loading plugins. + * Called by @ref loadPlugin directly or through the queue for async plugin + * loading. + */ + Plugin * loadPluginInternal( const QString &pluginId ); + + /** + * @internal + * + * Find the KPluginInfo structure by key. Reduces some code duplication. + * + * Returns a null pointer when no plugin info is found. + */ + KPluginInfo * infoForPluginId( const QString &pluginId ) const; + + PluginManager(); + + class Private; + Private *d; + static PluginManager *s_self; +}; + +} + +#endif // KOPETEPLUGINMANAGER_H + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopeteprefs.cpp b/kopete/libkopete/kopeteprefs.cpp new file mode 100644 index 00000000..e1148260 --- /dev/null +++ b/kopete/libkopete/kopeteprefs.cpp @@ -0,0 +1,672 @@ +/* + kopeteprefs.cpp - Kopete Preferences Container-Class + + Copyright (c) 2002 by Stefan Gehn <metz AT gehn.net> + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteprefs.h" + +#include <qfile.h> +#include <qfont.h> +#include <qmetaobject.h> + +#include <kapplication.h> +#include <kglobalsettings.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kstandarddirs.h> + +#define KOPETE_DEFAULT_CHATSTYLE "Kopete" + +KopetePrefs *KopetePrefs::s_prefs = 0L; + +KopetePrefs *KopetePrefs::prefs() +{ + if( !s_prefs ) + s_prefs = new KopetePrefs; + return s_prefs; +} + +KopetePrefs::KopetePrefs() : QObject( kapp, "KopetePrefs" ) +{ + config = KGlobal::config(); + load(); +} + +void KopetePrefs::load() +{ +// kdDebug( 14010 ) << k_funcinfo << endl; + config->setGroup("Appearance"); + + mIconTheme = config->readEntry("EmoticonTheme", defaultTheme()); + mUseEmoticons = config->readBoolEntry("Use Emoticons", true); + mEmoticonsRequireSpaces = config->readBoolEntry("EmoticonsRequireSpaces" , true ); + mShowOffline = config->readBoolEntry("ShowOfflineUsers", true); + mShowEmptyGroups = config->readBoolEntry("ShowEmptyGroups", true); + mGreyIdle = config->readBoolEntry("GreyIdleMetaContacts", true); + mSortByGroup = config->readBoolEntry("SortByGroup" , true); + mTreeView = config->readBoolEntry("TreeView", true); + mStartDocked = config->readBoolEntry("StartDocked", false); + mUseQueue = config->readBoolEntry("Use Queue", true); + mUseStack = config->readBoolEntry("Use Stack", false); + mRaiseMsgWindow = config->readBoolEntry("Raise Msg Window", false); + mShowEvents = config->readBoolEntry("Show Events in Chat Window", true); + mSpellCheck = config->readBoolEntry("SpellCheck", true); + mQueueUnreadMessages = config->readBoolEntry("Queue Unread Messages", false); + mQueueOnlyHighlightedMessagesInGroupChats = config->readBoolEntry("Queue Only Highlighted Messages In Group Chats", false); + mQueueOnlyMessagesOnAnotherDesktop = config->readBoolEntry("Queue Only Messages On Another Desktop", false); + mBalloonNotify = config->readBoolEntry("Balloon Notification", true); + mBalloonNotifyIgnoreClosesChatView = config->readBoolEntry("Balloon Notification Ignore Closes Chat View", false); + mBalloonCloseDelay = config->readNumEntry("Balloon Autoclose Delay", 30); + mBalloonClose = config->readBoolEntry("Balloon Autoclose", false); + mTrayflashNotify = config->readBoolEntry("Trayflash Notification", true); + mTrayflashNotifyLeftClickOpensMessage = config->readBoolEntry("Trayflash Notification Left Click Opens Message", true); + mTrayflashNotifySetCurrentDesktopToChatView = config->readBoolEntry("Trayflash Notification Set Current Desktop To Chat View", false); + mSoundIfAway = config->readBoolEntry("Sound Notification If Away", true); + mChatWindowPolicy = config->readNumEntry("Chatwindow Policy", 0); + mRichText = config->readBoolEntry("RichText editor", false); + mChatWShowSend = config->readBoolEntry("Show Chatwindow Send Button", true); + mRememberedMessages = config->readNumEntry("Remembered Messages", 5); + mTruncateContactNames = config->readBoolEntry("TruncateContactNames", false); + mMaxContactNameLength = config->readNumEntry("MaxContactNameLength", 20); + + mChatViewBufferSize = config->readNumEntry("ChatView BufferSize", 250); + + QColor tmpColor = KGlobalSettings::highlightColor(); + mHighlightBackground = config->readColorEntry("Highlight Background Color", &tmpColor); + tmpColor = KGlobalSettings::highlightedTextColor(); + mHighlightForeground = config->readColorEntry("Highlight Foreground Color", &tmpColor); + mHighlightEnabled = config->readBoolEntry("Highlighting Enabled", true); + mBgOverride = config->readBoolEntry("ChatView Override Background", false); + mFgOverride = config->readBoolEntry("ChatView Override Foreground", false); + mRtfOverride = config->readBoolEntry("ChatView Override RTF", false); + mInterfacePreference = config->readEntry("View Plugin", QString::fromLatin1("kopete_chatwindow") ); + tmpColor = KGlobalSettings::textColor(); + mTextColor = config->readColorEntry("Text Color", &tmpColor ); + tmpColor = KGlobalSettings::baseColor(); + mBgColor = config->readColorEntry("Bg Color", &tmpColor ); + tmpColor = KGlobalSettings::linkColor(); + mLinkColor = config->readColorEntry("Link Color", &tmpColor ); + mFontFace = config->readFontEntry("Font Face"); + tmpColor = darkGray; + mIdleContactColor = config->readColorEntry("Idle Contact Color", &tmpColor); + + mShowTray = config->readBoolEntry("Show Systemtray", true); + + _setStylePath(config->readEntry("StylePath")); + mStyleVariant = config->readEntry("StyleVariant"); + // Read Chat Window Style display + mGroupConsecutiveMessages = config->readBoolEntry("GroupConsecutiveMessages", true); + + mToolTipContents = config->readListEntry("ToolTipContents"); + if(mToolTipContents.empty()) + { + mToolTipContents + << QString::fromLatin1("FormattedName") + << QString::fromLatin1("userInfo") + << QString::fromLatin1("server") + << QString::fromLatin1("channels") + << QString::fromLatin1("FormattedIdleTime") + << QString::fromLatin1("channelMembers") + << QString::fromLatin1("channelTopic") + << QString::fromLatin1("emailAddress") + << QString::fromLatin1("homePage") + << QString::fromLatin1("onlineSince") + << QString::fromLatin1("lastOnline") + << QString::fromLatin1("awayMessage"); + } + + config->setGroup("ContactList"); + int n = metaObject()->findProperty( "contactListDisplayMode" ); + QString value = config->readEntry("DisplayMode",QString::fromLatin1("Default")); + mContactListDisplayMode = (ContactDisplayMode)metaObject()->property( n )->keyToValue( value.latin1() ); + n = metaObject()->findProperty( "contactListIconMode" ); + value = config->readEntry("IconMode", + QString::fromLatin1("IconDefault")); + mContactListIconMode = (IconDisplayMode) metaObject()->property( n )->keyToValue( value.latin1() ); + mContactListIndentContacts = config->readBoolEntry("IndentContacts", false); + mContactListUseCustomFonts = config->readBoolEntry("UseCustomFonts", false); + QFont font = KGlobalSettings::generalFont(); + mContactListNormalFont = config->readFontEntry("NormalFont", &font); + if ( font.pixelSize() != -1 ) + font.setPixelSize( (font.pixelSize() * 3) / 4 ); + else + font.setPointSizeFloat( font.pointSizeFloat() * 0.75 ); + mContactListSmallFont = config->readFontEntry("SmallFont", &font); + mContactListGroupNameColor = config->readColorEntry("GroupNameColor", &darkRed); + mContactListAnimation = config->readBoolEntry("AnimateChanges", true); + mContactListFading = config->readBoolEntry("FadeItems", true); + mContactListFolding = config->readBoolEntry("FoldItems", true); + mContactListAutoHide = config->readBoolEntry("AutoHide", false); + mContactListAutoHideTimeout = config->readUnsignedNumEntry("AutoHideTimeout", 30); + + // Load the reconnection setting + config->setGroup("General"); + mReconnectOnDisconnect = config->readBoolEntry("ReconnectOnDisconnect", true); + mAutoConnect = config->readBoolEntry("AutoConnect", false); + + // Nothing has changed yet + mWindowAppearanceChanged = false; + mContactListAppearanceChanged = false; + mMessageAppearanceChanged = false; + mStylePathChanged = false; + mStyleVariantChanged = false; +} + +void KopetePrefs::save() +{ +// kdDebug(14010) << "KopetePrefs::save()" << endl; + config->setGroup("Appearance"); + + config->writeEntry("EmoticonTheme", mIconTheme); + config->writeEntry("Use Emoticons", mUseEmoticons); + config->writeEntry("EmoticonsRequireSpaces", mEmoticonsRequireSpaces); + config->writeEntry("ShowOfflineUsers", mShowOffline); + config->writeEntry("ShowEmptyGroups", mShowEmptyGroups); + config->writeEntry("GreyIdleMetaContacts", mGreyIdle); + config->writeEntry("TreeView", mTreeView); + config->writeEntry("SortByGroup", mSortByGroup); + config->writeEntry("StartDocked", mStartDocked); + config->writeEntry("Use Queue", mUseQueue); + config->writeEntry("Use Stack", mUseStack); + config->writeEntry("Raise Msg Window", mRaiseMsgWindow); + config->writeEntry("Show Events in Chat Window", mShowEvents); + config->writeEntry("SpellCheck", mSpellCheck); + config->writeEntry("Queue Unread Messages", mQueueUnreadMessages); + config->writeEntry("Queue Only Highlighted Messages In Group Chats", mQueueOnlyHighlightedMessagesInGroupChats); + config->writeEntry("Queue Only Messages On Another Desktop", mQueueOnlyMessagesOnAnotherDesktop); + config->writeEntry("Balloon Notification", mBalloonNotify); + config->writeEntry("Balloon Notification Ignore Closes Chat View", mBalloonNotifyIgnoreClosesChatView); + config->writeEntry("Balloon Autoclose Delay", mBalloonCloseDelay); + config->writeEntry("Balloon Autoclose", mBalloonClose); + config->writeEntry("Trayflash Notification", mTrayflashNotify); + config->writeEntry("Trayflash Notification Left Click Opens Message", mTrayflashNotifyLeftClickOpensMessage); + config->writeEntry("Trayflash Notification Set Current Desktop To Chat View", mTrayflashNotifySetCurrentDesktopToChatView); + config->writeEntry("Sound Notification If Away", mSoundIfAway); + config->writeEntry("Chatwindow Policy", mChatWindowPolicy); + config->writeEntry("ChatView Override Background", mBgOverride); + config->writeEntry("ChatView Override Foreground", mFgOverride); + config->writeEntry("ChatView Override RTF", mRtfOverride); + config->writeEntry("ChatView BufferSize", mChatViewBufferSize); + config->writeEntry("Highlight Background Color", mHighlightBackground); + config->writeEntry("Highlight Foreground Color", mHighlightForeground); + config->writeEntry("Highlighting Enabled", mHighlightEnabled ); + config->writeEntry("Font Face", mFontFace); + config->writeEntry("Text Color",mTextColor); + config->writeEntry("Remembered Messages",mRememberedMessages); + config->writeEntry("Bg Color", mBgColor); + config->writeEntry("Link Color", mLinkColor); + config->writeEntry("Idle Contact Color", mIdleContactColor); + config->writeEntry("RichText editor", mRichText); + config->writeEntry("Show Chatwindow Send Button", mChatWShowSend); + config->writeEntry("TruncateContactNames", mTruncateContactNames); + config->writeEntry("MaxContactNameLength", mMaxContactNameLength); + + config->writeEntry("View Plugin", mInterfacePreference); + + config->writeEntry("Show Systemtray", mShowTray); + + //Style + //for xhtml+css + config->writeEntry("StylePath", mStylePath); + config->writeEntry("StyleVariant", mStyleVariant); + // Chat Window Display + config->writeEntry("GroupConsecutiveMessages", mGroupConsecutiveMessages); + + config->writeEntry("ToolTipContents", mToolTipContents); + + config->setGroup("ContactList"); + int n = metaObject()->findProperty( "contactListDisplayMode" ); + config->writeEntry("DisplayMode", metaObject()->property( n )->valueToKey( mContactListDisplayMode )); + n = metaObject()->findProperty( "contactListIconMode" ); + config->writeEntry("IconMode", metaObject()->property( n )->valueToKey( mContactListIconMode )); + config->writeEntry("IndentContacts", mContactListIndentContacts); + config->writeEntry("UseCustomFonts", mContactListUseCustomFonts); + config->writeEntry("NormalFont", mContactListNormalFont); + config->writeEntry("SmallFont", mContactListSmallFont); + config->writeEntry("GroupNameColor", mContactListGroupNameColor); + config->writeEntry("AnimateChanges", mContactListAnimation); + config->writeEntry("FadeItems", mContactListFading); + config->writeEntry("FoldItems", mContactListFolding); + config->writeEntry("AutoHide", mContactListAutoHide); + config->writeEntry("AutoHideTimeout", mContactListAutoHideTimeout); + + //Save the reconnection setting + config->setGroup("General"); + config->writeEntry("ReconnectOnDisconnect", mReconnectOnDisconnect); + config->writeEntry("AutoConnect", mAutoConnect); + + config->sync(); + emit saved(); + + if(mWindowAppearanceChanged) + emit windowAppearanceChanged(); + + if(mContactListAppearanceChanged) + emit contactListAppearanceChanged(); + + if(mMessageAppearanceChanged) + emit messageAppearanceChanged(); + + if(mStylePathChanged) + emit styleChanged(mStylePath); + + if(mStyleVariantChanged) + emit styleVariantChanged(mStyleVariant); + + // Clear all *Changed flags. This will cause breakage if someone makes some + // changes but doesn't save them in a slot connected to a *Changed signal. + mWindowAppearanceChanged = false; + mContactListAppearanceChanged = false; + mMessageAppearanceChanged = false; + mStylePathChanged = false; + mStyleVariantChanged = false; +} + +void KopetePrefs::setIconTheme(const QString &value) +{ + if( mIconTheme != value ) + { + mMessageAppearanceChanged = true; + mContactListAppearanceChanged = true; + } + mIconTheme = value; +} + +void KopetePrefs::setUseEmoticons(bool value) +{ + if( mUseEmoticons != value ) + { + mMessageAppearanceChanged = true; + mContactListAppearanceChanged = true; + } + mUseEmoticons = value; +} + +void KopetePrefs::setShowOffline(bool value) +{ + if( value != mShowOffline ) mContactListAppearanceChanged = true; + mShowOffline = value; +} + +void KopetePrefs::setShowEmptyGroups(bool value) +{ + if( value != mShowEmptyGroups ) mContactListAppearanceChanged = true; + mShowEmptyGroups = value; +} + +void KopetePrefs::setTreeView(bool value) +{ + if( value != mTreeView ) mContactListAppearanceChanged = true; + mTreeView = value; +} + +void KopetePrefs::setSortByGroup(bool value) +{ + if( value != mSortByGroup ) mContactListAppearanceChanged = true; + mSortByGroup = value; +} + +void KopetePrefs::setGreyIdleMetaContacts(bool value) +{ + if( value != mGreyIdle ) mContactListAppearanceChanged = true; + mGreyIdle = value; +} + +void KopetePrefs::setStartDocked(bool value) +{ + mStartDocked = value; +} + +void KopetePrefs::setUseQueue(bool value) +{ + mUseQueue = value; +} + +void KopetePrefs::setUseStack(bool value) +{ + mUseStack = value; +} + + +void KopetePrefs::setRaiseMsgWindow(bool value) +{ + mRaiseMsgWindow = value; +} + +void KopetePrefs::setRememberedMessages(int value) +{ + mRememberedMessages = value; +} + +void KopetePrefs::setShowEvents(bool value) +{ + mShowEvents = value; +} + +void KopetePrefs::setTrayflashNotify(bool value) +{ + mTrayflashNotify = value; +} + +void KopetePrefs::setSpellCheck(bool value) +{ + mSpellCheck = value; +} + +void KopetePrefs::setQueueUnreadMessages(bool value) +{ + mQueueUnreadMessages = value; +} + +void KopetePrefs::setQueueOnlyHighlightedMessagesInGroupChats(bool value) +{ + mQueueOnlyHighlightedMessagesInGroupChats = value; +} + +void KopetePrefs::setQueueOnlyMessagesOnAnotherDesktop(bool value) +{ + mQueueOnlyMessagesOnAnotherDesktop = value; +} + +void KopetePrefs::setTrayflashNotifyLeftClickOpensMessage(bool value) +{ + mTrayflashNotifyLeftClickOpensMessage = value; +} + +void KopetePrefs::setTrayflashNotifySetCurrentDesktopToChatView(bool value) +{ + mTrayflashNotifySetCurrentDesktopToChatView = value; +} + +void KopetePrefs::setBalloonNotify(bool value) +{ + mBalloonNotify = value; +} + +void KopetePrefs::setBalloonNotifyIgnoreClosesChatView(bool value) +{ + mBalloonNotifyIgnoreClosesChatView = value; +} + +void KopetePrefs::setBalloonClose( bool value ) +{ + mBalloonClose = value; +} + +void KopetePrefs::setBalloonDelay( int value ) +{ + mBalloonCloseDelay = value; +} + +void KopetePrefs::setSoundIfAway(bool value) +{ + mSoundIfAway = value; +} + +void KopetePrefs::setStylePath(const QString &stylePath) +{ + if(mStylePath != stylePath) mStylePathChanged = true; + _setStylePath(stylePath); +} + +void KopetePrefs::_setStylePath(const QString &stylePath) +{ + mStylePath = stylePath; + + // Fallback to default style if the directory doesn't exist + // or the value is empty. + if( !QFile::exists(stylePath) || stylePath.isEmpty() ) + { + QString fallback; + fallback = QString(QString::fromLatin1("styles/%1/")).arg(QString::fromLatin1(KOPETE_DEFAULT_CHATSTYLE)); + mStylePath = locate("appdata", fallback); + } +} + +void KopetePrefs::setStyleVariant(const QString &variantPath) +{ + if(mStyleVariant != variantPath) mStyleVariantChanged = true; + mStyleVariant = variantPath; +} + +void KopetePrefs::setFontFace( const QFont &value ) +{ + if( value != mFontFace ) mWindowAppearanceChanged = true; + mFontFace = value; +} + +void KopetePrefs::setTextColor( const QColor &value ) +{ + if( value != mTextColor ) mWindowAppearanceChanged = true; + mTextColor = value; +} + +void KopetePrefs::setBgColor( const QColor &value ) +{ + if( value != mBgColor ) mWindowAppearanceChanged = true; + mBgColor = value; +} + +void KopetePrefs::setLinkColor( const QColor &value ) +{ + if( value != mLinkColor ) mWindowAppearanceChanged = true; + mLinkColor = value; +} + +void KopetePrefs::setChatWindowPolicy(int value) +{ + mChatWindowPolicy = value; +} + +void KopetePrefs::setTruncateContactNames( bool value ) +{ + mTruncateContactNames = value; +} + +void KopetePrefs::setMaxContactNameLength( int value ) +{ + mMaxContactNameLength = value; +} + +void KopetePrefs::setInterfacePreference(const QString &value) +{ + mInterfacePreference = value; +} + +void KopetePrefs::setChatViewBufferSize( int value ) +{ + mChatViewBufferSize = value; +} + +void KopetePrefs::setHighlightBackground(const QColor &value) +{ + if( value != mHighlightBackground ) mWindowAppearanceChanged = true; + mHighlightBackground = value; +} + +void KopetePrefs::setHighlightForeground(const QColor &value) +{ + if( value != mHighlightForeground ) mWindowAppearanceChanged = true; + mHighlightForeground = value; +} + +void KopetePrefs::setHighlightEnabled(bool value) +{ + if( value != mHighlightEnabled ) mWindowAppearanceChanged = true; + mHighlightEnabled = value; +} + +void KopetePrefs::setBgOverride(bool value) +{ + if( value != mBgOverride ) mMessageAppearanceChanged = true; + mBgOverride = value; +} + +void KopetePrefs::setFgOverride(bool value) +{ + if( value != mFgOverride ) mMessageAppearanceChanged = true; + mFgOverride = value; +} + +void KopetePrefs::setRtfOverride(bool value) +{ + if( value != mRtfOverride ) mMessageAppearanceChanged = true; + mRtfOverride = value; +} + +void KopetePrefs::setShowTray(bool value) +{ + mShowTray = value; +} + + +QString KopetePrefs::fileContents(const QString &path) +{ + QString contents; + QFile file( path ); + if ( file.open( IO_ReadOnly ) ) + { + QTextStream stream( &file ); + contents = stream.read(); + file.close(); + } + return contents; +} + +void KopetePrefs::setIdleContactColor(const QColor &value) +{ + if( value != mIdleContactColor ) mContactListAppearanceChanged = true; + mIdleContactColor = value; +} + +void KopetePrefs::setRichText(bool value) +{ + mRichText=value; +} + +void KopetePrefs::setToolTipContents(const QStringList &value) +{ + mToolTipContents=value; +} + +void KopetePrefs::setContactListIndentContacts( bool v ) +{ + if( v != mContactListIndentContacts ) mContactListAppearanceChanged = true; + mContactListIndentContacts = v; +} + +void KopetePrefs::setContactListDisplayMode( ContactDisplayMode v ) +{ + if( v != mContactListDisplayMode ) mContactListAppearanceChanged = true; + mContactListDisplayMode = v; +} + +void KopetePrefs::setContactListIconMode( IconDisplayMode v ) +{ + if( v != mContactListIconMode ) mContactListAppearanceChanged = true; + mContactListIconMode = v; +} + +void KopetePrefs::setContactListUseCustomFonts( bool v ) +{ + if( v != mContactListUseCustomFonts ) mContactListAppearanceChanged = true; + mContactListUseCustomFonts = v; +} + +void KopetePrefs::setContactListCustomNormalFont( const QFont & v ) +{ + if( v != mContactListNormalFont ) mContactListAppearanceChanged = true; + mContactListNormalFont = v; +} + +void KopetePrefs::setContactListCustomSmallFont( const QFont & v ) +{ + if( v != mContactListSmallFont ) mContactListAppearanceChanged = true; + mContactListSmallFont = v; +} + +QFont KopetePrefs::contactListSmallFont() const +{ + if ( mContactListUseCustomFonts ) + return contactListCustomSmallFont(); + QFont smallFont = KGlobalSettings::generalFont(); + if ( smallFont.pixelSize() != -1 ) + smallFont.setPixelSize( (smallFont.pixelSize() * 3) / 4 ); + else + smallFont.setPointSizeFloat( smallFont.pointSizeFloat() * 0.75 ); + return smallFont; +} + +void KopetePrefs::setContactListGroupNameColor( const QColor & v ) +{ + if( v != mContactListGroupNameColor ) mContactListAppearanceChanged = true; + mContactListGroupNameColor = v; +} + +void KopetePrefs::setContactListAnimation( bool n ) +{ + if( n != mContactListAnimation ) mContactListAppearanceChanged = true; + mContactListAnimation = n; +} + +void KopetePrefs::setContactListFading( bool n ) +{ + if( n != mContactListFading ) mContactListAppearanceChanged = true; + mContactListFading = n; +} + +void KopetePrefs::setContactListFolding( bool n ) +{ + if( n != mContactListFolding ) mContactListAppearanceChanged = true; + mContactListFolding = n; +} + +void KopetePrefs::setContactListAutoHide( bool n ) +{ + if( n != mContactListAutoHide ) mContactListAppearanceChanged = true; + mContactListAutoHide = n; +} + +void KopetePrefs::setContactListAutoHideTimeout( unsigned int n ) +{ + if( n != mContactListAutoHideTimeout ) mContactListAppearanceChanged = true; + mContactListAutoHideTimeout = n; +} + +void KopetePrefs::setReconnectOnDisconnect( bool newSetting ) +{ + mReconnectOnDisconnect = newSetting; +} + +void KopetePrefs::setAutoConnect(bool b) +{ + mAutoConnect=b; +} + +void KopetePrefs::setEmoticonsRequireSpaces( bool b ) +{ + if( mEmoticonsRequireSpaces != b ) + { + mMessageAppearanceChanged = true; + mContactListAppearanceChanged = true; + } + mEmoticonsRequireSpaces=b; +} + +void KopetePrefs::setGroupConsecutiveMessages( bool value ) +{ + mGroupConsecutiveMessages = value; +} +#include "kopeteprefs.moc" +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopeteprefs.h b/kopete/libkopete/kopeteprefs.h new file mode 100644 index 00000000..4a5162ff --- /dev/null +++ b/kopete/libkopete/kopeteprefs.h @@ -0,0 +1,318 @@ +/* + kopeteprefs.cpp - Kopete Preferences Container-Class + + Copyright (c) 2002 by Stefan Gehn <metz AT gehn.net> + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef __KOPETEPREFS_H__ +#define __KOPETEPREFS_H__ + +#include <qobject.h> +#include <kdeversion.h> +#include <qcolor.h> +#include <qfont.h> + +#include "kopete_export.h" + +class KConfig; + +class KOPETE_EXPORT KopetePrefs : public QObject +{ + Q_OBJECT + // here so we can use Qt to translate enums<-->strings + Q_PROPERTY( ContactDisplayMode contactListDisplayMode READ contactListDisplayMode WRITE setContactListDisplayMode ) + Q_PROPERTY( IconDisplayMode contactListIconMode READ contactListIconMode WRITE setContactListIconMode ) + Q_ENUMS( ContactDisplayMode IconDisplayMode ) + +public: + /** + * The prefs container-class is a singleton object. Use this method to retrieve + * the instance. + */ + static KopetePrefs *prefs(); + + /** + * Reads all pref-variables from KConfig + * usually you don't need this as KopetePrefs loads settings + * when an instance is created + */ + void load(); + + /** + * Stores all pref-variables into KConfig + */ + void save(); + + QString iconTheme() const { return mIconTheme; } + bool useEmoticons() const { return mUseEmoticons; } + bool showOffline() const { return mShowOffline; } + bool showEmptyGroups() const { return mShowEmptyGroups; } + bool treeView() const { return mTreeView; } + bool sortByGroup() const { return mSortByGroup; } + bool greyIdleMetaContacts() const { return mGreyIdle; } + bool startDocked() const { return mStartDocked; } + bool useQueue() const { return mUseQueue; } + bool useStack() const { return mUseStack; } + bool raiseMsgWindow() const{ return mRaiseMsgWindow; } + bool showEvents() const{ return mShowEvents; } + bool trayflashNotify() const { return mTrayflashNotify; } + bool spellCheck() const { return mSpellCheck; } + bool queueUnreadMessages() const { return mQueueUnreadMessages; } + bool queueOnlyHighlightedMessagesInGroupChats() const { return mQueueOnlyHighlightedMessagesInGroupChats; } + bool queueOnlyMessagesOnAnotherDesktop() const { return mQueueOnlyMessagesOnAnotherDesktop; } + bool trayflashNotifyLeftClickOpensMessage() const { return mTrayflashNotifyLeftClickOpensMessage; } + bool trayflashNotifySetCurrentDesktopToChatView() const { return mTrayflashNotifySetCurrentDesktopToChatView; } + bool balloonNotify() const { return mBalloonNotify; } + bool balloonNotifyIgnoreClosesChatView() const { return mBalloonNotifyIgnoreClosesChatView; } + bool balloonClose() const { return mBalloonClose; } + int balloonCloseDelay() const { return mBalloonCloseDelay; } + bool soundIfAway() const { return mSoundIfAway; } + int chatViewBufferSize() const { return mChatViewBufferSize; } + int rememberedMessages() const { return mRememberedMessages; } + const QColor &highlightBackground() const { return mHighlightBackground; } + const QColor &highlightForeground() const { return mHighlightForeground; } + const QColor &textColor() const { return mTextColor; } + const QColor &bgColor() const { return mBgColor; } + const QColor &linkColor() const { return mLinkColor; } + const QFont &fontFace() const { return mFontFace; } + const QColor &idleContactColor() const { return mIdleContactColor; } + bool highlightEnabled() const { return mHighlightEnabled; } + bool bgOverride() const { return mBgOverride; } + bool fgOverride() const { return mFgOverride; } + bool rtfOverride() const { return mRtfOverride; } + + QString interfacePreference() const { return mInterfacePreference; } + bool showTray() const { return mShowTray; } + bool richText() const { return mRichText; } + bool chatWShowSend() const { return mChatWShowSend; } + bool autoConnect() const { return mAutoConnect; } + + int chatWindowPolicy() const { return mChatWindowPolicy; } + + //Styles + QString defaultTheme() const { return QString::fromLatin1("Default"); } + //for Adium (xhtml+css) + QString stylePath() const { return mStylePath; } + QString styleVariant() const { return mStyleVariant; } + + QStringList toolTipContents() const { return mToolTipContents; } + + /// + enum ContactDisplayMode { Classic, RightAligned, Detailed, Yagami, Default = Classic }; + /// + enum IconDisplayMode { IconPic, PhotoPic, IconDefault = IconPic }; + bool contactListIndentContacts() const { return mContactListIndentContacts; } + ContactDisplayMode contactListDisplayMode() const { return mContactListDisplayMode; } + IconDisplayMode contactListIconMode() const { return mContactListIconMode; } + bool contactListUseCustomFonts() const { return mContactListUseCustomFonts; } + QFont contactListCustomNormalFont() const { return mContactListNormalFont; } + QFont contactListCustomSmallFont() const { return mContactListSmallFont; } + QFont contactListSmallFont() const; + QColor contactListGroupNameColor() const { return mContactListGroupNameColor; } + bool contactListAnimation() const { return mContactListAnimation; } + bool contactListFading() const { return mContactListFading; } + bool contactListFolding() const { return mContactListFolding; } + bool contactListAutoHide() const { return mContactListAutoHide; } + unsigned int contactListAutoHideTimeout() const { return mContactListAutoHideTimeout; } + + bool reconnectOnDisconnect() const { return mReconnectOnDisconnect; } + + bool truncateContactNames() const { return mTruncateContactNames; } + int maxConactNameLength() const { return mMaxContactNameLength; } + bool emoticonsRequireSpaces() const { return mEmoticonsRequireSpaces; } + bool groupConsecutiveMessages() const { return mGroupConsecutiveMessages; } + + void setIconTheme(const QString &value); + void setUseEmoticons(bool value); + void setShowOffline(bool value); + void setShowEmptyGroups(bool value); + void setTreeView(bool); + void setSortByGroup(bool); + void setGreyIdleMetaContacts(bool); + void setStartDocked(bool); + void setUseQueue(bool); + void setUseStack(bool); + void setRaiseMsgWindow(bool); + void setShowEvents(bool); + void setTrayflashNotify(bool); + void setSpellCheck(bool); + void setQueueUnreadMessages(bool); + void setQueueOnlyHighlightedMessagesInGroupChats(bool); + void setQueueOnlyMessagesOnAnotherDesktop(bool); + void setTrayflashNotifyLeftClickOpensMessage(bool); + void setTrayflashNotifySetCurrentDesktopToChatView(bool); + void setBalloonNotify(bool); + void setBalloonNotifyIgnoreClosesChatView(bool); + void setSoundIfAway(bool); + void setBeepNotify(bool); + void setChatWindowPolicy(int); + void setStylePath(const QString &); + void setStyleVariant(const QString &); + void setChatViewBufferSize(int); + void setHighlightBackground(const QColor &); + void setHighlightForeground(const QColor &); + void setHighlightEnabled(bool); + void setBgOverride(bool); + void setFgOverride(bool); + void setRtfOverride(bool); + void setInterfacePreference(const QString &viewPlugin); + void setTextColor(const QColor &); + void setBgColor(const QColor &); + void setLinkColor(const QColor &); + void setFontFace(const QFont &); + void setIdleContactColor(const QColor &); + void setShowTray(bool); + void setRichText(bool); + void setRememberedMessages(int); + void setToolTipContents(const QStringList &); + void setContactListIndentContacts( bool v ); + void setContactListDisplayMode( ContactDisplayMode v ); + void setContactListIconMode( IconDisplayMode v ); + void setContactListUseCustomFonts( bool v ); + void setContactListCustomNormalFont( const QFont & v ); + void setContactListCustomSmallFont( const QFont & v ); + void setContactListGroupNameColor( const QColor & v ); + void setContactListAnimation( bool ); + void setContactListFading( bool ); + void setContactListFolding( bool ); + void setContactListAutoHide( bool ); + void setContactListAutoHideTimeout( unsigned int ); + void setReconnectOnDisconnect( bool newSetting ); + void setTruncateContactNames( bool ); + void setMaxContactNameLength( int ); + void setAutoConnect( bool ); + void setEmoticonsRequireSpaces( bool ); + void setBalloonClose( bool ); + void setBalloonDelay( int ); + void setGroupConsecutiveMessages( bool ); + +signals: + /** + * Emitted when config gets saved by save() + */ + void saved(); + /** + * Emitted when config gets saved by save() and a certain + * setting has changed. + * Naming scheme is the same as with the config vars. + */ + void windowAppearanceChanged(); + void messageAppearanceChanged(); + void contactListAppearanceChanged(); + /** + * Emitted when chat Window Style changed. + * @param stylePath New stylePath + */ + void styleChanged(const QString &stylePath); + /** + * Emitted when ChatWindowStyle variant changed. + * @param variantPath New variant Path. + */ + void styleVariantChanged(const QString &variantPath); + +private: + /** + * Private constructor: we are a singleton + */ + KopetePrefs(); + + /** + * Our instance + */ + static KopetePrefs *s_prefs; + + KConfig *config; + + QString mIconTheme; + bool mUseEmoticons; + bool mShowOffline; + bool mShowEmptyGroups; + bool mGreyIdle; + bool mTreeView; + bool mSortByGroup; + bool mStartDocked; + bool mUseQueue; + bool mUseStack; + bool mRaiseMsgWindow; + bool mShowEvents; + bool mTrayflashNotify; + bool mSpellCheck; + bool mQueueUnreadMessages; + bool mQueueOnlyHighlightedMessagesInGroupChats; + bool mQueueOnlyMessagesOnAnotherDesktop; + bool mTrayflashNotifyLeftClickOpensMessage; + bool mTrayflashNotifySetCurrentDesktopToChatView; + bool mBalloonNotify; + bool mBalloonNotifyIgnoreClosesChatView; + bool mBalloonClose; + int mBalloonCloseDelay; + bool mSoundIfAway; + int mRememberedMessages; + QString mInterfacePreference; + int mChatViewBufferSize; + QColor mHighlightBackground; + QColor mHighlightForeground; + QColor mTextColor; + QColor mBgColor; + QColor mLinkColor; + QFont mFontFace; + QColor mIdleContactColor; + bool mHighlightEnabled; + bool mBgOverride; + bool mFgOverride; + bool mRtfOverride; + bool mShowTray; + bool mWindowAppearanceChanged; + bool mMessageAppearanceChanged; + bool mContactListAppearanceChanged; + bool mChatWShowSend; + bool mAutoConnect; + + int mChatWindowPolicy; + + bool mTruncateContactNames; + int mMaxContactNameLength; + + bool mRichText; + + // xhtml+css + //for Adium (xhtml+css) + QString mStylePath; + QString mStyleVariant; + bool mStylePathChanged; + bool mStyleVariantChanged; + + QStringList mToolTipContents; + + bool mContactListIndentContacts; + ContactDisplayMode mContactListDisplayMode; + IconDisplayMode mContactListIconMode; + bool mContactListUseCustomFonts; + QFont mContactListNormalFont; + QFont mContactListSmallFont; + QColor mContactListGroupNameColor; + bool mContactListAnimation; + bool mContactListFading; + bool mContactListFolding; + bool mContactListAutoHide; + unsigned int mContactListAutoHideTimeout; + + bool mReconnectOnDisconnect; + bool mEmoticonsRequireSpaces; + bool mGroupConsecutiveMessages; + + QString fileContents(const QString &path); + void _setStylePath (const QString &); +}; +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopeteproperties.cpp b/kopete/libkopete/kopeteproperties.cpp new file mode 100644 index 00000000..9009cd07 --- /dev/null +++ b/kopete/libkopete/kopeteproperties.cpp @@ -0,0 +1,50 @@ +/* + kopetetproperties.cpp - Kopete Properties + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteproperties.h" + +#include <kdebug.h> + +#include <qdom.h> +#include <qvariant.h> +#include <typeinfo> + +namespace Kopete { +namespace Properties { + +// Keep as much type-independent code out of the templated stuff as we can +// FIXME: shouldn't be inline +void customPropertyDataIncorrectType( const char *name, const std::type_info &found, const std::type_info &expected ) +{ + kdWarning(14010) << "data time mismatch for property data name " << name + << ". found: " << found.name() << ", expected: " << expected.name() << endl; +} + +template<> +int variantTo<int>(QVariant) { return 0; } +//... + +QVariant variantFromXML(const QDomElement&) +{ + return QVariant(); +} + +void variantToXML(QVariant v, QDomElement &) +{ +} + +} // namespace Properties +} // namespace Kopete diff --git a/kopete/libkopete/kopeteproperties.h b/kopete/libkopete/kopeteproperties.h new file mode 100644 index 00000000..bfeaedea --- /dev/null +++ b/kopete/libkopete/kopeteproperties.h @@ -0,0 +1,350 @@ +/* + kopeteproperties.h - Kopete Properties + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPROPERTIES_H +#define KOPETEPROPERTIES_H + +#include <qasciidict.h> + +#include <typeinfo> + +class QString; +class QVariant; +class QDomElement; + +namespace Kopete +{ + +/** + * Contains the classes forming Kopete's Properties system. + * + * @todo Explain more, give examples. + * + * @author Richard Smith <kde@metafoo.co.uk> + */ +namespace Properties +{ + +//BEGIN core functionality + +/** + * @brief Property-type-independent base class for properties + * + * The base class for all properties of any type which can be set or got for @p Parent + * objects. It is rare to need to use this class directly. Usually you will want to use + * the @ref Property derived class, or dynamic_cast the PropertyBase object to another interface. + * + * @see Property UserVisible XMLSerializable StringSerializable + * + * @author Richard Smith <kde@metafoo.co.uk> + */ +template<class Parent> +class PropertyBase +{ +public: + /** + * Returns the name of the property. This name should uniquely identify this property + * within the type Parent, and will be used for persistently identifying this property. + * + * For core properties, the chosen name should not contain any slash characters. For + * properties defined in plugins kept in Kopete's CVS, the name should be of the form + * pluginName/propertyName. For third-party plugins, please use a URL with a host which + * you own, such as "http://my-host.com/kopete/properties/groupId". + * + * @return the name of this property. + */ + virtual const char *name() const = 0; +}; + +/** + * @brief Property-type-dependent base class for properties + * + * This class represents a property of type @p Type applicable to @p Parent objects. Usage + * of this class is usually as simple as: + * + * \code + * SomeParent *propertyContainer = ... + * Property<SomeParent,QString> &myProperty = ... + * QString value = propertyContainer->property(myProperty); + * propertyContainer->setProperty(myProperty, "hello"); + * \endcode + * + * You should never need to call functions in this class directly. + */ +template<class Parent, typename Type> +class Property : public PropertyBase<Parent> +{ +public: + /** + * Returns the value of this property in the object @p parent. + */ + virtual Type get( const Parent *parent ) const = 0; + /** + * Sets the value of this property in the object @p parent. + */ + virtual void set( Parent *, const Type & ) const = 0; +}; + +/** + * @brief Base class for property data objects + * + * Some property objects want to store property-specific data in their parent objects. + * To support that, subclasses of this class are permitted to be stored. Once passed + * to the @ref PropertyStorage object via @ref PropertyStorage::setCustomPropertyData, + * the @ref PropertyStorage object owns the PropertyData, and will delete it when it + * is no longer needed. + */ +struct PropertyData +{ + virtual ~PropertyData() {} +}; + +/** + * @brief Storage object for PropertyData objects + * + * This class is responsible for storing PropertyData-derived data objects for properties. + * This is the non-templated part of the @ref WithProperties class, split out into its own + * class to eliminate the template bloat. + */ +class PropertyStorage +{ + typedef QAsciiDict<PropertyData> PropertyDict; + // setCustomPropertyData can be called on a const object, allowing the + // guarantee that DataProperty::data() never returns 0. + mutable PropertyDict _storage; + +public: + PropertyStorage() { _storage.setAutoDelete( true ); } + + /** + * Sets the stored property data with name @p name to be @p data. + * + * @note The @p name argument should usually be the name of the property which the data + * is being stored for. However, if properties wish to share data, they may choose to + * name their custom data differently. Names are bound by the same rules as are laid out + * for naming properties in PropertyBase<Parent>::name. + */ + void setCustomPropertyData( const char *name, PropertyData *data ) const { _storage.replace( name, data ); } + + /** + * Gets the stored property data with name @p name. Returns a null + * pointer if no data has been stored for that property. + */ + PropertyData *getCustomPropertyData( const char *name ) const { return _storage[name]; } +}; + +/** + * @brief Base class for classes to which properties can be applied + * + * This class provides support for properties to another class. If you want your class + * to support properties, derive from this passing your class as the Parent parameter: + * + * \code + * class YourClass : public WithProperties<YourClass> { ... }; + * \endcode + * + * You will also need to explicitly specialise the propertyCreated() member function to + * load property data upon creation of a new property object. + */ +template<class Parent> +class WithProperties : public PropertyStorage +{ +public: + /** + * Get the value of property @p prop in this object. + * @param prop the Property object representing the property to get + */ + template<typename T> + T property( Property<Parent,T> const &prop ) { return prop.get( static_cast<Parent*>(this) ); } + /** + * Set the value of property @p prop in this object. + * @param prop the Property object representing the property to get + * @param value the value to set the property to + */ + template<typename T> + void setProperty( Property<Parent,T> const &prop, const T &value ) { prop.set( static_cast<Parent*>(this), value ); } + + /** + * Called when a property is created; loads the Parent object's data into the property. + * + * @note Derived classes must explicitly specialize this to load the property's data into + * every object of this type. + */ + static void propertyCreated( const PropertyBase<Parent> &property ); +}; + +//END core functionality + +//BEGIN interfaces + +/** + * @brief An interface for user-visible properties + * @todo document + */ +template<class Parent> +struct UserVisible +{ + virtual QString userText( Parent * ) = 0; + virtual QString label() = 0; + virtual QString icon() = 0; +}; + +/** + * @brief An interface for properties which can be serialized as XML + * @todo document + */ +template<class Parent> +struct XMLSerializable +{ + virtual void fromXML( Parent *, const QDomElement & ) = 0; + virtual void toXML( const Parent *, QDomElement & ) = 0; +}; + +/** + * @brief An interface for properties which can be serialized as strings + * @todo document + */ +template<class Parent> +struct StringSerializable +{ + virtual void fromString( Parent *, const QString & ) = 0; + virtual QString toString( const Parent * ) = 0; +}; + +//END interfaces + +//BEGIN convenience classes + +/** + * @internal Display a warning message when the wrong type of property data is found + */ +void customPropertyDataIncorrectType( const char *name, const std::type_info &found, const std::type_info &expected ); + +/** + * @brief Convenience implementation of a Property that stores PropertyData + * + * A property for objects of type @p Parent, that stores data in the class @p Data. + * @p Data must be derived from @ref PropertyBase, or your code will not compile. + */ +template<class Parent, typename Type, class Data> +class DataProperty : public Property<Parent,Type> +{ +public: + Data *data( const Parent *c ) const + { + PropertyData *pd = c->getCustomPropertyData( this->name() ); + Data *data = dynamic_cast<Data*>(pd); + if ( !data ) + { + if ( pd ) + customPropertyDataIncorrectType( this->name(), typeid(*pd), typeid(Data) ); + data = new Data; + c->setCustomPropertyData( this->name(), data ); + } + return data; + } +}; + +/** + * @brief Convenience implementation of a PropertyData subclass which stores a single datum + * + * If a @ref Property needs to store only a single value in an object, using this + * class is simpler than deriving from @ref PropertyData yourself. The value will + * be default-constructed (which means for numeric types and pointers it will be + * set to 0). + */ +template<typename T> +struct SimplePropertyData : public PropertyData +{ + SimplePropertyData() : value() {} + T value; +}; + +/** + * @brief Convenience implementation of a Property which stores a single datum as PropertyData + * + * This convenience class implements the @ref Property interface by simply storing and + * retrieving the datum from PropertyData. This class does not provide any serialization + * of the data. + * + * @note You will need to derive from this class to use it; the @ref name function is + * still pure virtual. + */ +template<class Parent, typename Type> +class SimpleDataProperty : public DataProperty<Parent,Type,SimplePropertyData<Type> > +{ +public: + Type get( const Parent *p ) const { return data(p)->value; } + void set( Parent *p, const Type &v ) const { data(p)->value = v; } +}; + +/** + * Move somewhere else + * @{ + */ + +/** + * Explicitly specialised for all types QVariant supports + */ +template<class T> T variantTo(QVariant); + +QVariant variantFromXML(const QDomElement&); +void variantToXML(QVariant v, QDomElement &); + +/** + * @} + */ + +/** + * @brief Convenience implementation of XMLSerializable in terms of QVariants + * + * This class provides XML serialization for data that can be stored in a QVariant. You + * will need to multiply-inherit from this class and (usually indirectly) from @ref Property. + * + * You can combine this class with other convenience classes such as SimpleDataProperty + * like this: + * + * \code + * class ContactNickNameProperty + * : public SimpleDataProperty<Contact,QString> + * , XMLProperty<ContactNickNameProperty,Contact,QString> + * { + * public: + * const char *name() const { return "nickName"; } + * }; + * \endcode + */ +template<class Derived, class Parent, typename Type> +class XMLProperty : public XMLSerializable<Parent> +{ +public: + void fromXML( Parent *t, const QDomElement &e ) + { + static_cast<Derived*>(this)->set(t, variantTo<Type>(variantFromXML(e))); + } + void toXML( const Parent *t, QDomElement &e ) + { + variantToXML(QVariant(static_cast<Derived*>(this)->get(t)),e); + } +}; + +//END convenience classes + +} // namespace Properties + +} // namespace Kopete + +#endif diff --git a/kopete/libkopete/kopeteprotocol.cpp b/kopete/libkopete/kopeteprotocol.cpp new file mode 100644 index 00000000..7854a1a3 --- /dev/null +++ b/kopete/libkopete/kopeteprotocol.cpp @@ -0,0 +1,340 @@ +/* + kopeteprotocol.cpp - Kopete Protocol + + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart @tiscalinet.be> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteprotocol.h" + +#include <kdebug.h> +#include <kaction.h> +#include <klocale.h> + +#include <qdict.h> + +#include "kopeteaccountmanager.h" +#include "kopeteaccount.h" +#include "kopetecontact.h" +#include "kopeteglobal.h" +#include "kopetecontactproperty.h" +#include "kopetemetacontact.h" + +namespace Kopete +{ + +class Protocol::Private +{ +public: + bool unloading; + int capabilities; + /* + * Make sure we always have a lastSeen and a fullname property as long as + * a protocol is loaded + */ + ContactPropertyTmpl mStickLastSeen; + ContactPropertyTmpl mStickFullName; + + Kopete::OnlineStatus accountNotConnectedStatus; +}; + +Protocol::Protocol( KInstance *instance, QObject *parent, const char *name ) +: Plugin( instance, parent, name ) +{ + d = new Private; + d->mStickLastSeen = Global::Properties::self()->lastSeen(); + d->mStickFullName = Global::Properties::self()->fullName(); + d->unloading = false; + d->capabilities = 0; + d->accountNotConnectedStatus = Kopete::OnlineStatus( Kopete::OnlineStatus::Unknown, 0, this, Kopete::OnlineStatus::AccountOffline, QString::fromLatin1( "account_offline_overlay" ), i18n( "Account Offline" ) ); +} + +Protocol::~Protocol() +{ + // Remove all active accounts + QDict<Account> accounts = AccountManager::self()->accounts( this ); + if ( !accounts.isEmpty() ) + { + kdWarning( 14010 ) << k_funcinfo << "Deleting protocol with existing accounts! Did the account unloading go wrong?" << endl; + + for( QDictIterator<Account> it( accounts ); it.current() ; ++it ) + delete *it; + } + + delete d; +} + +unsigned int Protocol::capabilities() const +{ + return d->capabilities; +} + +void Protocol::setCapabilities( unsigned int capabilities ) +{ + d->capabilities = capabilities; +} + + +Kopete::OnlineStatus Protocol::accountOfflineStatus() const +{ + return d->accountNotConnectedStatus; +} + +void Protocol::slotAccountOnlineStatusChanged( Contact *self ) +{//slot connected in aboutToUnload + if ( !self || !self->account() || self->account()->isConnected()) + return; + // some protocols change status several times during shutdown. We should only call deleteLater() once + disconnect( self, 0, this, 0 ); + + connect( self->account(), SIGNAL(accountDestroyed(const Kopete::Account* )), + this, SLOT( slotAccountDestroyed( ) ) ); + + self->account()->deleteLater(); +} + +void Protocol::slotAccountDestroyed( ) +{ + QDict<Account> dict = AccountManager::self()->accounts( this ); + if ( dict.isEmpty() ) + { + // While at this point we are still in a stack trace from the destroyed + // account it's safe to emit readyForUnload already, because it uses a + // deleteLater rather than a delete for exactly this reason, to keep the + // API managable + emit( readyForUnload() ); + } +} + +void Protocol::aboutToUnload() +{ + + d->unloading = true; + + // Disconnect all accounts + QDict<Account> accounts = AccountManager::self()->accounts( this ); + + if ( accounts.isEmpty() ) + emit readyForUnload(); + else for ( QDictIterator<Account> it( accounts ); it.current() ; ++it ) + { + if ( it.current()->myself() && it.current()->myself()->isOnline() ) + { + kdDebug( 14010 ) << k_funcinfo << it.current()->accountId() << + " is still connected, disconnecting..." << endl; + + QObject::connect( it.current()->myself(), + SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT( slotAccountOnlineStatusChanged( Kopete::Contact * ) ) ); + it.current()->disconnect(); + } + else + { + // Remove account, it's already disconnected + kdDebug( 14010 ) << k_funcinfo << it.current()->accountId() << + " is already disconnected, deleting..." << endl; + + QObject::connect( it.current(), SIGNAL( accountDestroyed( const Kopete::Account* ) ), + this, SLOT( slotAccountDestroyed( ) ) ); + it.current()->deleteLater(); + } + } +} + + + +void Protocol::slotMetaContactAboutToSave( MetaContact *metaContact ) +{ + QMap<QString, QString> serializedData, sd; + QMap<QString, QString> addressBookData, ad; + QMap<QString, QString>::Iterator it; + + //kdDebug( 14010 ) << "Protocol::metaContactAboutToSave: protocol " << pluginId() << ": serializing " << metaContact->displayName() << endl; + + QPtrList<Contact> contacts=metaContact->contacts(); + for (Contact *c=contacts.first() ; c ; c=contacts.next() ) + { + if( c->protocol()->pluginId() != pluginId() ) + continue; + + sd.clear(); + ad.clear(); + + // Preset the contactId and displayName, if the plugin doesn't want to save + // them, or use its own format, it can call clear() on the provided list + sd[ QString::fromLatin1( "contactId" ) ] = c->contactId(); + //TODO(nick) remove + sd[ QString::fromLatin1( "displayName" ) ] = c->property(Global::Properties::self()->nickName()).value().toString(); + if(c->account()) + sd[ QString::fromLatin1( "accountId" ) ] = c->account()->accountId(); + + // If there's an index field preset it too + QString index = c->protocol()->addressBookIndexField(); + if( !index.isEmpty() ) + ad[ index ] = c->contactId(); + + c->serializeProperties( sd ); + c->serialize( sd, ad ); + + // Merge the returned fields with what we already (may) have + for( it = sd.begin(); it != sd.end(); ++it ) + { + // The Unicode chars E000-F800 are non-printable and reserved for + // private use in applications. For more details, see also + // http://www.unicode.org/charts/PDF/UE000.pdf. + // Inside libkabc the use of QChar( 0xE000 ) has been standardized + // as separator for the string lists, use this also for the 'normal' + // serialized data. + if( serializedData.contains( it.key() ) ) + serializedData[ it.key() ] = serializedData[ it.key() ] + QChar( 0xE000 ) + it.data(); + else + serializedData[ it.key() ] = it.data(); + } + + for( it = ad.begin(); it != ad.end(); ++it ) + { + if( addressBookData.contains( it.key() ) ) + addressBookData[ it.key() ] = addressBookData[ it.key() ] + QChar( 0xE000 ) + it.data(); + else + addressBookData[ it.key() ] = it.data(); + } + } + + // Pass all returned fields to the contact list + //if( !serializedData.isEmpty() ) //even if we are empty, that mean there are no contact, so remove old value + metaContact->setPluginData( this, serializedData ); + + for( it = addressBookData.begin(); it != addressBookData.end(); ++it ) + { + //kdDebug( 14010 ) << "Protocol::metaContactAboutToSave: addressBookData: key: " << it.key() << ", data: " << it.data() << endl; + // FIXME: This is a terrible hack to check the key name for the phrase "messaging/" + // to indicate what app name to use, but for now it's by far the easiest + // way to get this working. + // Once all this is in CVS and the actual storage in libkabc is working + // we can devise a better API, but with the constantly changing + // requirements every time I learn more about kabc I'd better no touch + // the API yet - Martijn + if( it.key().startsWith( QString::fromLatin1( "messaging/" ) ) ) + { + metaContact->setAddressBookField( this, it.key(), QString::fromLatin1( "All" ), it.data() ); +// kdDebug(14010) << k_funcinfo << "metaContact->setAddressBookField( " << this << ", " << it.key() << ", \"All\", " << it.data() << " );" << endl; + } + else + metaContact->setAddressBookField( this, QString::fromLatin1( "kopete" ), it.key(), it.data() ); + } +} + +void Protocol::deserialize( MetaContact *metaContact, const QMap<QString, QString> &data ) +{ + /*kdDebug( 14010 ) << "Protocol::deserialize: protocol " << + pluginId() << ": deserializing " << metaContact->displayName() << endl;*/ + + QMap<QString, QStringList> serializedData; + QMap<QString, QStringList::Iterator> serializedDataIterators; + QMap<QString, QString>::ConstIterator it; + for( it = data.begin(); it != data.end(); ++it ) + { + serializedData[ it.key() ] = QStringList::split( QChar( 0xE000 ), it.data(), true ); + serializedDataIterators[ it.key() ] = serializedData[ it.key() ].begin(); + } + + uint count = serializedData[QString::fromLatin1("contactId")].count(); + + // Prepare the independent entries to pass to the plugin's implementation + for( uint i = 0; i < count ; i++ ) + { + QMap<QString, QString> sd; + QMap<QString, QStringList::Iterator>::Iterator serializedDataIt; + for( serializedDataIt = serializedDataIterators.begin(); serializedDataIt != serializedDataIterators.end(); ++serializedDataIt ) + { + sd[ serializedDataIt.key() ] = *( serializedDataIt.data() ); + ++( serializedDataIt.data() ); + } + + const QString& accountId=sd[ QString::fromLatin1( "accountId" ) ]; + // myself was allowed in the contactlist in old version of kopete. + // But if one keep it on the contactlist now, it may conflict witht he myself metacontact. + // So ignore it + if(accountId == sd[ QString::fromLatin1( "contactId" ) ] ) + { + kdDebug( 14010 ) << k_funcinfo << "Myself contact was on the contactlist.xml for account " << accountId << ". Ignore it" << endl; + continue; + } + + // FIXME: This code almost certainly breaks when having more than + // one contact in a meta contact. There are solutions, but + // they are all hacky and the API needs revision anyway (see + // FIXME a few lines below :), so I'm not going to add that + // for now. + // Note that even though it breaks, the current code will + // never notice, since none of the plugins use the address + // book data in the deserializer yet, only when serializing. + // - Martijn + QMap<QString, QString> ad; + QStringList kabcFields = addressBookFields(); + for( QStringList::Iterator fieldIt = kabcFields.begin(); fieldIt != kabcFields.end(); ++fieldIt ) + { + // FIXME: This hack is even more ugly, and has the same reasons as the similar + // hack in the serialize code. + // Once this code is actually capable of talking to kabc this hack + // should be removed ASAP! - Martijn + if( ( *fieldIt ).startsWith( QString::fromLatin1( "messaging/" ) ) ) + ad[ *fieldIt ] = metaContact->addressBookField( this, *fieldIt, QString::fromLatin1( "All" ) ); + else + ad[ *fieldIt ] = metaContact->addressBookField( this, QString::fromLatin1( "kopete" ), *fieldIt ); + } + + // Check if we have an account id. If not we're deserializing a Kopete 0.6 contact + // (our our config is corrupted). Pick the first available account there. This + // might not be what you want for corrupted accounts, but it's correct for people + // who migrate from 0.6, as there's only one account in that case + if( accountId.isNull() ) + { + QDict<Account> accounts = AccountManager::self()->accounts( this ); + if ( accounts.count() > 0 ) + { + sd[ QString::fromLatin1( "accountId" ) ] = QDictIterator<Account>( accounts ).currentKey(); + } + else + { + kdWarning( 14010 ) << k_funcinfo << + "No account available and account not set in " \ + "contactlist.xml either!" << endl + << "Not deserializing this contact." << endl; + return; + } + } + + + Contact *c = deserializeContact( metaContact, sd, ad ); + if (c) // should never be null but I do not like crashes + c->deserializeProperties( sd ); + } +} + +Contact *Protocol::deserializeContact( + MetaContact */*metaContact */, + const QMap<QString, QString> & /* serializedData */, + const QMap<QString, QString> & /* addressBookData */ ) +{ + /* Default implementation does nothing */ + return 0; +} + + +} //END namespace Kopete + +#include "kopeteprotocol.moc" + diff --git a/kopete/libkopete/kopeteprotocol.desktop b/kopete/libkopete/kopeteprotocol.desktop new file mode 100644 index 00000000..3b3762a4 --- /dev/null +++ b/kopete/libkopete/kopeteprotocol.desktop @@ -0,0 +1,66 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Kopete/Protocol +X-KDE-Derived=Kopete/Plugin +Comment=Kopete Protocol Plugin +Comment[ar]=توصيلة بروتوكول KDE +Comment[be]=Пратакол Kopete +Comment[bg]=Приставка за протоколите на Kopete +Comment[bn]=কপেট প্রোটোকল প্লাগিন +Comment[br]=Lugent komenad Kopete +Comment[bs]=Kopete dodatak za protokol +Comment[ca]=Connector de protocol per a Kopete +Comment[cs]=Modul protokolu aplikace Kopete +Comment[cy]=Ategyn Protocol Kopete +Comment[da]=Kopete-protokol-plugin +Comment[de]=Kopete Protokoll-Modul +Comment[el]=Πρόσθετο πρωτοκόλλου Kopete +Comment[eo]=Kopete-Protokolkromaĵo +Comment[es]=Complemento de protocolo de Kopete +Comment[et]=Kopete protokolliplugin +Comment[eu]=Kopete protocolo plugin-a +Comment[fa]=وصلۀ قرارداد Kopete +Comment[fi]=Kopeten yhteyskäytäntöliitännäinen +Comment[fr]=Module de protocole pour Kopete +Comment[ga]=Breiseán Phrótacal Kopete +Comment[gl]=Plugin de protocolo para Kopete +Comment[he]=תוסף פרוטוקול של Kopete +Comment[hi]=के-ऑप्टी प्रोटोकॉल प्लगइन +Comment[hr]=Umetak za Kopete protokol +Comment[hu]=Kopete protokollkezelő bővítőmodul +Comment[is]=Íforrit fyrir Kopete samskiptamátann +Comment[it]=Plugin del protocollo di Kopete +Comment[ja]=Kopete プロトコルプラグイン +Comment[ka]=Kopete ოქმის მოდული +Comment[kk]=Kopete протоколының плагин модулі +Comment[km]=កម្មវិធីជំនួយពិធីការ Kopete +Comment[lt]=Kopete protokolo įskiepis +Comment[mk]=Приклучок за протокол во Kopete +Comment[nb]=Programtillegg for Kopete-protokoll +Comment[nds]=Kopete-Protokollmoduul +Comment[ne]=कोपेट प्रोटोकल प्लगइन +Comment[nl]=Kopete protocol-plugin +Comment[nn]=Kopete-programtillegg for protokoll +Comment[pl]=Wtyczka protokołu Kopete +Comment[pt]='Plugin' de Protocolo do Kopete +Comment[pt_BR]=Plugin do Protocolo do Kopete +Comment[ro]=Modul de protocol Kopete +Comment[ru]=Модуль протокола Kopete +Comment[se]=Kopete protokollalassemoduvla +Comment[sk]=Modul protokolu Kopete +Comment[sl]=Vstavek za protokol za Kopete +Comment[sr]=Kopete-ов прикључак за протокол +Comment[sr@Latn]=Kopete-ov priključak za protokol +Comment[sv]=Protokollinsticksprogram för Kopete +Comment[ta]=Kopete விதிமுறை செருகல் +Comment[tg]=Модули Қарордоди Kopete +Comment[tr]=Kopete İletişim Kuralı Eklentisi +Comment[uk]=Втулок протоколу для Kopete +Comment[wa]=Tchôke-divins di protocole po Kopete +Comment[zh_CN]=Kopete 协议插件 +Comment[zh_HK]=Kopete 通訊協定插件 +Comment[zh_TW]=Kopete 協定外掛程式 + +[PropertyDef::X-Kopete-Messaging-Protocol] +Type=QString + diff --git a/kopete/libkopete/kopeteprotocol.h b/kopete/libkopete/kopeteprotocol.h new file mode 100644 index 00000000..805e00c2 --- /dev/null +++ b/kopete/libkopete/kopeteprotocol.h @@ -0,0 +1,269 @@ +/* + kopeteprotocol.h - Kopete Protocol + + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart@ tiscalinet.be> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPROTOCOL_H +#define KOPETEPROTOCOL_H + +#include "kopeteplugin.h" +#include "kopeteonlinestatus.h" + +class KopeteEditAccountWidget; +class AddContactPage; + +#include "kopete_export.h" + +namespace Kopete +{ + +class Contact; +class MetaContact; +class Account; + +/*namespace UI +{ + class EditAccountWidget; + class AddContactPage; +}*/ + + +/** + * @brief base class of every protocols. + * + * A protocol is just a particular case of Plugin + * + * Protocol is an abstract class, you need to reimplement createNewAccount, + * createAddContactPage, createEditAccountWidget + * + * + * @author Duncan Mac-Vicar Prett <duncan@kde.org> + * @author Martijn Klingens <klingens@kde.org> + * @author Olivier Goffart <ogoffart @ tiscalinet.be> + */ +class KOPETE_EXPORT Protocol : public Plugin +{ + Q_OBJECT + +public: + + /** + * @todo Ideally, the destructor should be protected. but we need it public to allow QPtrList<Protocol> + */ + virtual ~Protocol(); + + /** + * @brief Create an empty Account + * + * This method is called during the loading of the config file. + * @param accountId - the account ID to create the account with. This is usually + * the login name of the account + * + * you don't need to register the account to the AccountManager in this function. + * But if you want to use this function don't forget to call @ref AccountManager::registerAccount + * + * @return The new @ref Account object created by this function + */ + virtual Account *createNewAccount( const QString &accountId ) = 0; + + /** + * @brief Create a new AddContactPage widget to be shown in the Add Contact Wizard. + * + * @return A new AddContactPage to be shown in the Add Contact Wizard + */ + virtual AddContactPage *createAddContactWidget( QWidget *parent, Account *account ) = 0; + + /** + * @brief Create a new KopeteEditAccountWidget + * + * @return A new KopeteEditAccountWidget to be shown in the account part of the configurations. + * + * @param account is the KopeteAccount to edit. If it's 0L, then we create a new account + * @param parent The parent of the 'to be returned' widget + */ + virtual KopeteEditAccountWidget * createEditAccountWidget( Account *account, QWidget *parent ) = 0; + + + /** + * @brief a bitmask of the capabilities of this protocol + * @sa @ref setCapabilities + */ + unsigned int capabilities() const ; + + + /** + * @brief Available capabilities + * + * @ref capabilities() returns an ORed list of these, which + * the edit widget interperts to determine what buttons to show + */ + enum Capabilities + { + BaseFgColor = 0x1, ///< Setting the bg color of the whole edit widget / message + BaseBgColor = 0x2, ///< Setting the fg color of the whole edit widget / message + RichFgColor = 0x4, ///< Setting the fg/bg color of text portions individually + RichBgColor = 0x8, ///< Setting the fg/bg color of text portions individually + + BaseFont = 0x10, ///< Setting the font of the whole edit widget / message + RichFont = 0x20, ///< Setting the font of text portions individually + + /// Setting the formatting of the whole edit widget / message + BaseUFormatting = 0x40, + BaseIFormatting = 0x80, + BaseBFormatting = 0x100, + + /// Setting the formatting of text portions individually + RichUFormatting = 0x200, + RichIFormatting = 0x400, + RichBFormatting = 0x800, + + Alignment = 0x1000, ///< Setting the alignment of text portions + + /// Setting the formatting of the whole edit widget / message + BaseFormatting = BaseIFormatting | BaseUFormatting | BaseBFormatting, + + /// Setting the formatting of text portions individually + RichFormatting = RichIFormatting | RichUFormatting | RichBFormatting, + + RichColor = RichBgColor | RichFgColor, + BaseColor = BaseBgColor | BaseFgColor, + + //Shortcut for All of the above - full HTML + FullRTF = RichFormatting | Alignment | RichFont | RichFgColor | RichBgColor , + + + CanSendOffline = 0x10000 ///< If it's possible to send offline messages + }; + + /** + * @brief Returns the status used for contacts when accounts of this protocol are offline + */ + Kopete::OnlineStatus accountOfflineStatus() const; + + +protected: + /** + * @brief Constructor for Protocol + * + * @param instance The protocol's instance, every plugin needs to have a KInstance of its own + * @param parent The protocol's parent object + * @param name The protocol's name + */ + Protocol( KInstance *instance, QObject *parent, const char *name ); + + /** + * @brief Sets the capabilities of this protcol. + * + * The subclass contructor is a good place for calling it. + * @sa @ref capabilities() + */ + void setCapabilities( unsigned int ); + +public: + + /** + * Reimplemented from Kopete::Plugin. + * + * This method disconnects all accounts and deletes them, after which it + * will emit readyForUnload. + * + * Note that this is an asynchronous operation that may take some time + * with active chats. It's no longer immediate as it used to be in + * Kopete 0.7.x and before. This also means that you can do a clean + * shutdown. + * @note The method is not private to allow subclasses to reimplement + * it even more, but if you need to do this please explain why + * on the list first. It might make more sense to add another + * virtual for protocols that's called instead, but for now I + * actually think protocols don't need their own implementation + * at all, so I left out the necessary hooks on purpose. + * - Martijn + */ + virtual void aboutToUnload(); + +private slots: + /** + * @internal + * The account changed online status. Used while unloading the protocol. + */ + void slotAccountOnlineStatusChanged( Kopete::Contact *self ); + + /** + * @internal + * The account is destroyed. When it's the last account we emit the + * readyForUnload signal. Used while unloading the protocol. + */ + void slotAccountDestroyed( ); + + +public: + + /** + * @brief Deserialize the plugin data for a meta contact. + * + * This method splits up the data into the independent Kopete::Contact objects + * and calls @ref deserializeContact() for each contact. + * + * Note that you can still reimplement this method if you prefer, but you are + * strongly recommended to use this version of the method instead, unless you + * want to do _VERY_ special things with the data... + * + * @todo we probably should think to another way to save the contacltist. + */ + virtual void deserialize( MetaContact *metaContact, const QMap<QString, QString> &serializedData ); + + /** + * @brief Deserialize a single contact. + * + * This method is called by @ref deserialize() for each separate contact, + * so you don't need to add your own hooks for multiple contacts in a single + * meta contact yourself. @p serializedData and @p addressBookData will be + * the data the contact provided in Kopete::Contact::serialize. + * + * The default implementation does nothing. + * + * @return The contact created from the data + * @sa Contact::serialize + * + * @todo we probably should think to another way to save the contacltist. + */ + virtual Contact *deserializeContact( MetaContact *metaContact, + const QMap<QString, QString> &serializedData, + const QMap<QString, QString> &addressBookData ); + + + +public slots: + /** + * A meta contact is about to save. + * Call serialize() for all contained contacts for this protocol. + * @internal + * it's public because for example, Contact::setMetaContact uses it. + * @todo we probably should think to another way to save the contacltist. + */ + void slotMetaContactAboutToSave( Kopete::MetaContact *metaContact ); + + +private: + class Private; + Private *d; +}; + +} //END namespace kopete + +#endif + diff --git a/kopete/libkopete/kopetesimplemessagehandler.cpp b/kopete/libkopete/kopetesimplemessagehandler.cpp new file mode 100644 index 00000000..3e44520c --- /dev/null +++ b/kopete/libkopete/kopetesimplemessagehandler.cpp @@ -0,0 +1,101 @@ +/* + kopetemessagefilter.cpp - Kopete Message Filtering + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetesimplemessagehandler.h" +#include "kopetemessageevent.h" + +#include <kstaticdeleter.h> + +#include <qguardedptr.h> + +namespace Kopete +{ + +//BEGIN SimpleMessageHandlerFactory + +class SimpleMessageHandlerFactory::Private +{ +public: + Message::MessageDirection direction; + int position; + QGuardedPtr<QObject> target; + const char *slot; +}; + +SimpleMessageHandlerFactory::SimpleMessageHandlerFactory( Message::MessageDirection direction, + int position, QObject *target, const char *slot ) + : d( new Private ) +{ + d->direction = direction; + d->position = position; + d->target = target; + d->slot = slot; +} + +SimpleMessageHandlerFactory::~SimpleMessageHandlerFactory() +{ + delete d; +} + +MessageHandler *SimpleMessageHandlerFactory::create( ChatSession */*manager*/, Message::MessageDirection direction ) +{ + if ( direction != d->direction ) + return 0; + MessageHandler *handler = new SimpleMessageHandler; + QObject::connect( handler, SIGNAL( handle( Kopete::Message & ) ), d->target, d->slot ); + return handler; +} + +int SimpleMessageHandlerFactory::filterPosition( ChatSession */*manager*/, Message::MessageDirection direction ) +{ + if ( direction != d->direction ) + return StageDoNotCreate; + return d->position; +} + +//END SimpleMessageHandlerFactory + +//BEGIN SimpleMessageHandler + +class SimpleMessageHandler::Private +{ +}; + +SimpleMessageHandler::SimpleMessageHandler() + : d(0) +{ +} + +SimpleMessageHandler::~SimpleMessageHandler() +{ + delete d; +} + +void SimpleMessageHandler::handleMessage( MessageEvent *event ) +{ + Message message = event->message(); + emit handle( message ); + event->setMessage( message ); + MessageHandler::handleMessage( event ); +} + +//END SimpleMessageHandler + +} + +#include "kopetesimplemessagehandler.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetesimplemessagehandler.h b/kopete/libkopete/kopetesimplemessagehandler.h new file mode 100644 index 00000000..af6de4ab --- /dev/null +++ b/kopete/libkopete/kopetesimplemessagehandler.h @@ -0,0 +1,90 @@ +/* + kopetesimplemessagehandler.h - Kopete Message Filtering - simple interface + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETESIMPLEMESSAGEHANDLER_H +#define KOPETESIMPLEMESSAGEHANDLER_H + +#include "kopete_export.h" +#include "kopetemessagehandler.h" + +namespace Kopete +{ + +/** + * @brief A MessageHandlerFactory that creates synchronous MessageHandlers that just call a slot + * + * A concrete MessageHandlerFactory. This class is intended to make writing MessageHandlers simpler; + * all that is required is to implement a message processing function and place an instance of this + * class in your Plugin-derived class. + * + * Whenever a message passes through a handler created by this factory, the slot passed to the + * constructor will be called. The slot should take a single argument of type (non-@p const) + * <tt>Message &</tt>. + */ +class KOPETE_EXPORT SimpleMessageHandlerFactory : public MessageHandlerFactory +{ +public: + /** + * @param direction The direction this factory should create message handlers for + * @param position Where in the chain the handler should be installed + * @param target The object to call back to when handling a message + * @param slot The slot on @p target to call when handling a message + * @see Kopete::MessageHandlerFactory::filterPosition + */ + SimpleMessageHandlerFactory( Message::MessageDirection direction, int position, + QObject *target, const char *slot ); + ~SimpleMessageHandlerFactory(); + + /** + * Creates and returns a SimpleMessageHandler object. + */ + MessageHandler *create( ChatSession *manager, Message::MessageDirection direction ); + /** + * Returns the filter position passed to the constructor if @p direction matches the + * direction passed to the constructor, otherwise returns @c StageDoNotCreate. + */ + int filterPosition( ChatSession *manager, Message::MessageDirection direction ); + +private: + class Private; + Private *d; +}; + +/** + * @internal This class is used to implement SimpleMessageHandlerFactory. + */ +class SimpleMessageHandler : public MessageHandler +{ + Q_OBJECT +public: + SimpleMessageHandler(); + ~SimpleMessageHandler(); + + void handleMessage( MessageEvent *event ); + +signals: + void handle( Kopete::Message &message ); + +private: + class Private; + Private *d; +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetetask.cpp b/kopete/libkopete/kopetetask.cpp new file mode 100644 index 00000000..b7484116 --- /dev/null +++ b/kopete/libkopete/kopetetask.cpp @@ -0,0 +1,108 @@ +/* + kopetetask.cpp - Kopete Task + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetetask.h" + +#include <klocale.h> + +#include <qptrlist.h> + +namespace Kopete +{ + +class Task::Private +{ +public: + Private() + : result( ResultFailed ) + { + errorMessage = i18n( "The operation has not finished yet" ); + } + + Task::Result result; + QString errorMessage; + QPtrList<Task> subtasks; +}; + +Task::Task() + : d( new Private ) +{ +} + +Task::~Task() +{ + delete d; +} + +bool Task::succeeded() const +{ + return d->result == ResultSucceeded; +} + +const QString &Task::errorString() const +{ + return d->errorMessage; +} + +void Task::abort( int flags ) +{ + int childFlags = flags & ~AbortEmitResult; + for ( Task *task = d->subtasks.first(); task; task = d->subtasks.next() ) + task->abort( childFlags ); + + if ( flags & AbortEmitResult ) + emitResult( ResultFailed, i18n( "Aborted" ) ); + else + delete this; +} + +void Task::addSubtask( Task *task ) +{ + d->subtasks.append( task ); + connect( task, SIGNAL( result( Kopete::Task* ) ), + this, SLOT( slotResult( Kopete::Task* ) ) ); + connect( task, SIGNAL( statusMessage( Kopete::Task*, const QString & ) ), + this, SIGNAL( statusMessage( Kopete::Task*, const QString & ) ) ); +} + +void Task::removeSubtask( Task *task, RemoveSubtaskIfLast actionIfLast ) +{ + disconnect( task, SIGNAL( result( Kopete::Task* ) ), + this, SLOT( slotResult( Kopete::Task* ) ) ); + disconnect( task, SIGNAL( statusMessage( Kopete::Task*, const QString & ) ), + this, SIGNAL( statusMessage( Kopete::Task*, const QString & ) ) ); + d->subtasks.remove( task ); + if ( d->subtasks.isEmpty() && actionIfLast == IfLastEmitResult ) + emitResult( task->succeeded() ? ResultSucceeded : ResultFailed, task->errorString() ); +} + +void Task::emitResult( Result res, const QString &errorMessage ) +{ + d->result = res; + d->errorMessage = errorMessage; + emit result( this ); + delete this; +} + +void Task::slotResult( Kopete::Task *task ) +{ + removeSubtask( task ); +} + +} + +#include "kopetetask.moc" +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetetask.h b/kopete/libkopete/kopetetask.h new file mode 100644 index 00000000..115e1ebe --- /dev/null +++ b/kopete/libkopete/kopetetask.h @@ -0,0 +1,162 @@ +/* + kopetetask.h - Kopete Task + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETETASK_H +#define KOPETETASK_H + +#include <qobject.h> +#include <kdemacros.h> + +namespace Kopete +{ + +/** + * The base class for all tasks. + * For most tasks created in Kopete, the code looks like + * + * \code + * Kopete::Task *task = someobject->someoperation( some parameters ); + * connect( task, SIGNAL( result( Kopete::Task * ) ), + * this, SLOT( slotResult( Kopete::Task * ) ) ); + * \endcode + * (other connects, specific to the job) + * + * And slotResult is usually at least: + * + * \code + * if ( !task->succeeded() ) + * Kopete::UI::Global::showTaskError( task ); + * \endcode + * + * Much of the ideas (and some of the documentation and function names) for this + * class come from KIO::Job. + * @author Richard Smith <kde@metafoo.co.uk> + */ +class Task : public QObject +{ + Q_OBJECT + +protected: + Task(); +public: + ~Task(); + + /** + * Returns whether the task completed successfully. + * Only call this method from the slot connected to result(). + * @return if the task succeeded, returns true, otherwise returns false. + */ + bool succeeded() const; + /** + * Converts an error code and a non-i18n error message into an + * error message in the current language. The low level (non-i18n) + * error message (usually a url) is put into the translated error + * message using %%1. + * + * Use this to display the error yourself, but for a dialog box + * use Kopete::UI::Global::showTaskError. Do not call it if succeeded() + * returns true. + * @return the error message and if there is no error, a message + * telling the user that the app is broken, so check with + * succeeded() whether there is an error. + */ + const QString &errorString() const; + + /** Flags for the abort() function */ + enum AbortFlags { AbortNormal = 0, AbortEmitResult = 1 }; +public slots: + /** + * Abort this task. + * This aborts all subtasks and deletes the task. + * + * @param flags a combination of flags from AbortFlags. If AbortEmitResult is + * set, Job will emit the result signal. AbortEmitResult is removed + * from the flags passed to the abort function of subtasks. + */ + virtual void abort( int flags = AbortNormal ); + +signals: + /** + * Emitted when the task is finished, in any case (completed, canceled, + * failed...). Use error() to find the result. + * @param task the task that emitted this signal + */ + void result( Kopete::Task *task ); + /** + * Emitted to display status information about this task. + * Examples of messages are: + * "Removing ICQ contact Joe from server-side list", + * "Loading account plugin", etc. + * @param task the task that emitted this signal + * @param message the info message + */ + void statusMessage( Kopete::Task *task, const QString &message ); + +protected: + /** + * Add a task that has to be completed before a result is emitted. This + * obviously should not be called after the finish signal is emitted by + * the subtask. + * + * @param task the subtask to add + */ + virtual void addSubtask( Task *task ); + + enum RemoveSubtaskIfLast { IfLastDoNothing, IfLastEmitResult }; + /** + * Mark a sub job as being done. If it's the last to + * wait on the job will emit a result - jobs with + * two steps might want to override slotResult + * in order to avoid calling this method. + * + * @param task the subjob to add + * @param actionIfLast the action to take if this is the last subtask. + * If set to IfLastEmitResult, the error information from @p task + * will be copied to this object, and emitResult() will be called. + */ + virtual void removeSubtask( Task *task, RemoveSubtaskIfLast actionIfLast = IfLastEmitResult ); + + enum Result { ResultFailed = 0, ResultSucceeded = 1 }; + /** + * Utility function to emit the result signal, and suicide this job. + * Sets the stored result and error message to @p result and @p errorMessage. + * You should call this instead of emitting the result() signal yourself. + */ + void emitResult( Result result = ResultSucceeded, const QString &errorMessage = QString::null ); + +protected slots: + /** + * Called whenever a subtask finishes. + * The default implementation checks for errors and propagates + * them to this task, then calls removeSubtask(). + * Override if you want to provide a different @p actionIfLast to + * removeSubtask, or want to perform some other processing in response + * to a subtask finishing + * @param task the subtask that finished + * @see result() + */ + virtual void slotResult( Kopete::Task *task ); + +private: + class Private; + Private *d; +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetetransfermanager.cpp b/kopete/libkopete/kopetetransfermanager.cpp new file mode 100644 index 00000000..1131cd90 --- /dev/null +++ b/kopete/libkopete/kopetetransfermanager.cpp @@ -0,0 +1,271 @@ +/* + kopetetransfermanager.cpp + + Copyright (c) 2002-2003 by Nick Betcher <nbetcher@kde.org> + Copyright (c) 2002-2003 by Richard Smith <kopete@metafoo.co.uk> + + Kopete (c) 2002 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <klocale.h> +#include <kstaticdeleter.h> +#include <kfiledialog.h> +#include <kfileitem.h> +#include <kmessagebox.h> +#include <kio/observer.h> + +#include "kopetemetacontact.h" +#include "kopetecontact.h" +#include "kopeteuiglobal.h" + +#include "kopetetransfermanager.h" +#include "kopetefileconfirmdialog.h" + +/*************************** + * Kopete::FileTransferInfo * + ***************************/ + +Kopete::FileTransferInfo::FileTransferInfo( Kopete::Contact *contact, const QString& file, const unsigned long size, const QString &recipient, KopeteTransferDirection di, const unsigned int id, QString internalId) +{ + mContact = contact; + mFile = file; + mId = id; + mSize = size; + mRecipient = recipient; + m_intId= internalId; + mDirection= di; +} + +/*************************** + * Kopete::Transfer * + ***************************/ + + +Kopete::Transfer::Transfer( const Kopete::FileTransferInfo &kfti, const QString &localFile, bool showProgressInfo) + : KIO::Job(showProgressInfo), mInfo(kfti) +{ + KURL targ; targ.setPath( localFile ); + init( targ, showProgressInfo ); +} + +Kopete::Transfer::Transfer( const Kopete::FileTransferInfo &kfti, const Kopete::Contact *contact, bool showProgressInfo) + : KIO::Job(showProgressInfo), mInfo(kfti) +{ + // TODO: use mInfo.url().fileName() after move to protocol-aware filetransfers + KURL targ; targ.setPath( mInfo.file() ); + init( displayURL( contact, targ.fileName() ), showProgressInfo ); +} + +void Kopete::Transfer::init( const KURL &target, bool showProgressInfo ) +{ + mTarget = target; + + if( showProgressInfo ) + Observer::self()->slotCopying( this, sourceURL(), destinationURL() ); + + connect( this, SIGNAL( result( KIO::Job* ) ), SLOT( slotResultEmitted() ) ); + + setAutoErrorHandlingEnabled( true, 0 ); +} + +Kopete::Transfer::~Transfer() +{ +} + +KURL Kopete::Transfer::displayURL( const Kopete::Contact *contact, const QString &file ) +{ + KURL url; + url.setProtocol( QString::fromLatin1("kopete") ); + + QString host; + if( !contact ) + host = QString::fromLatin1("unknown origin"); + else if( contact->metaContact() ) + host = contact->metaContact()->displayName(); + else + host = contact->contactId(); + url.setHost(host); + + // url.setPath( contact->protocol()->displayName() ); + + url.setFileName( file ); + return url; +} + +// TODO: add possibility of network file transfers; +// call mInfo->url() not file() +KURL Kopete::Transfer::sourceURL() +{ + if( mInfo.direction() == Kopete::FileTransferInfo::Incoming ) + return displayURL( mInfo.contact(), mInfo.file() ); + else + { + KURL url; url.setPath( mInfo.file() ); + return url; + } +} + +KURL Kopete::Transfer::destinationURL() +{ + return mTarget; +} + +void Kopete::Transfer::slotProcessed(unsigned int bytes) +{ + emitPercent( bytes, mInfo.size() ); +} + +void Kopete::Transfer::slotComplete() +{ + emitResult(); +} + +void Kopete::Transfer::slotError( int error, const QString &errorText ) +{ + m_error = error; + m_errorText = errorText; + + emitResult(); +} + +void Kopete::Transfer::slotResultEmitted() +{ + if( error() == KIO::ERR_USER_CANCELED ) + emit transferCanceled(); +} + +/*************************** + * Kopete::TransferManager * + ***************************/ + +static KStaticDeleter<Kopete::TransferManager> deleteManager; +Kopete::TransferManager *Kopete::TransferManager::s_transferManager = 0; + +Kopete::TransferManager* Kopete::TransferManager::transferManager() +{ + if(!s_transferManager) + deleteManager.setObject(s_transferManager, new Kopete::TransferManager(0)); + + return s_transferManager; +} + +Kopete::TransferManager::TransferManager( QObject *parent ) : QObject( parent ) +{ + nextID = 0; +} + +Kopete::Transfer* Kopete::TransferManager::addTransfer( Kopete::Contact *contact, const QString& file, const unsigned long size, const QString &recipient , Kopete::FileTransferInfo::KopeteTransferDirection di) +{ +// if (nextID != 0) + nextID++; + Kopete::FileTransferInfo info(contact, file, size, recipient,di, nextID); + Kopete::Transfer *trans = new Kopete::Transfer(info, contact); + connect(trans, SIGNAL(result(KIO::Job *)), this, SLOT(slotComplete(KIO::Job *))); + mTransfersMap.insert(nextID, trans); + return trans; +} + +void Kopete::TransferManager::slotAccepted(const Kopete::FileTransferInfo& info, const QString& filename) +{ + Kopete::Transfer *trans = new Kopete::Transfer(info, filename); + connect(trans, SIGNAL(result(KIO::Job *)), this, SLOT(slotComplete(KIO::Job *))); + mTransfersMap.insert(info.transferId(), trans); + emit accepted(trans,filename); +} + +int Kopete::TransferManager::askIncomingTransfer( Kopete::Contact *contact, const QString& file, const unsigned long size, const QString& description, QString internalId) +{ +// if (nextID != 0) + nextID++; + + QString dn= contact ? (contact->metaContact() ? contact->metaContact()->displayName() : contact->contactId()) : i18n("<unknown>"); + + Kopete::FileTransferInfo info(contact, file, size, dn, Kopete::FileTransferInfo::Incoming , nextID , internalId); + + //FIXME!!! this will not be deleted if it's still open when kopete exits + KopeteFileConfirmDialog *diag= new KopeteFileConfirmDialog(info, description , 0 ) ; + + connect( diag, SIGNAL( accepted(const Kopete::FileTransferInfo&, const QString&)) , this, SLOT( slotAccepted(const Kopete::FileTransferInfo&, const QString&) ) ); + connect( diag, SIGNAL( refused(const Kopete::FileTransferInfo&)) , this, SIGNAL( refused(const Kopete::FileTransferInfo&) ) ); + diag->show(); + return nextID; +} + +void Kopete::TransferManager::removeTransfer( unsigned int id ) +{ + mTransfersMap.remove(id); + //we don't need to delete the job, the job get deleted itself +} + +void Kopete::TransferManager::slotComplete(KIO::Job *job) +{ + Kopete::Transfer *transfer=dynamic_cast<Kopete::Transfer*>(job); + if(!transfer) + return; + + emit done(transfer); + + for( QMap<unsigned, Kopete::Transfer*>::Iterator it = mTransfersMap.begin(); + it != mTransfersMap.end(); ++it ) + { + if( it.data() == transfer ) + { + removeTransfer(it.key()); + break; + } + } +} + +void Kopete::TransferManager::sendFile( const KURL &file, const QString &fname, unsigned long sz, + bool mustBeLocal, QObject *sendTo, const char *slot ) +{ + KURL url(file); + QString filename; + unsigned int size = 0; + + //If the file location is null, then get it from a file open dialog + if( !url.isValid() ) + url = KFileDialog::getOpenURL( QString::null, QString::fromLatin1("*"), 0l, i18n( "Kopete File Transfer" )); + else + { + filename = fname; + size = sz; + } + + if( filename.isEmpty() ) + filename = url.fileName(); + + if( size == 0 ) + { + KFileItem finfo(KFileItem::Unknown, KFileItem::Unknown, url); + size = (unsigned long)finfo.size(); + } + + if( !url.isEmpty() ) + { + if( mustBeLocal && !url.isLocalFile() ) + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "Sorry, sending files which are not stored locally is not yet supported by this protocol.\n" + "Please copy this file to your computer and try again." ) ); + } + else + { + connect( this, SIGNAL(sendFile(const KURL&, const QString&, unsigned int)), sendTo, slot ); + emit sendFile( url, filename, size ); + disconnect( this, SIGNAL(sendFile(const KURL&, const QString&, unsigned int)), sendTo, slot ); + } + } +} + +#include "kopetetransfermanager.moc" + diff --git a/kopete/libkopete/kopetetransfermanager.h b/kopete/libkopete/kopetetransfermanager.h new file mode 100644 index 00000000..f4e7416f --- /dev/null +++ b/kopete/libkopete/kopetetransfermanager.h @@ -0,0 +1,212 @@ +/* + kopetetransfermanager.h + + Copyright (c) 2002-2003 by Nick Betcher <nbetcher@kde.org> + Copyright (c) 2002-2003 by Richard Smith <kopete@metafoo.co.uk> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEFILETRANSFER_H +#define KOPETEFILETRANSFER_H + +#include <qobject.h> +#include <qstring.h> +#include <qmap.h> +#include "kopete_export.h" + +#include <kio/job.h> + +namespace Kopete +{ + +class Transfer; +class Contact; + +/** + * @author Nick Betcher. <nbetcher@kde.org> + */ +class KOPETE_EXPORT FileTransferInfo +{ +public: + enum KopeteTransferDirection { Incoming, Outgoing }; + + FileTransferInfo( Contact *, const QString&, const unsigned long size, const QString &, KopeteTransferDirection di, const unsigned int id, QString internalId=QString::null); + ~FileTransferInfo() {} + unsigned int transferId() const { return mId; } + const Contact* contact() const { return mContact; } + QString file() const { return mFile; } + QString recipient() const { return mRecipient; } + unsigned long size() const { return mSize; } + QString internalId() const { return m_intId; } + KopeteTransferDirection direction() const { return mDirection; } + +private: + unsigned long mSize; + QString mRecipient; + unsigned int mId; + Contact *mContact; + QString mFile; + QString m_intId; + KopeteTransferDirection mDirection; +}; + +/** + * Creates and manages kopete file transfers + */ +class KOPETE_EXPORT TransferManager : public QObject +{ + Q_OBJECT + +public: + /** + * Retrieve the transfer manager instance + */ + static TransferManager* transferManager(); + virtual ~TransferManager() {}; + + /** + * @brief Adds a file transfer to the Kopete::TransferManager + */ + Transfer *addTransfer( Contact *contact, const QString& file, const unsigned long size, const QString &recipient , FileTransferInfo::KopeteTransferDirection di); + int askIncomingTransfer( Contact *contact, const QString& file, const unsigned long size, const QString& description=QString::null, QString internalId=QString::null); + void removeTransfer( unsigned int id ); + + /** + * @brief Ask the user which file to send when they click Send File. + * + * Possibly ask the user which file to send when they click Send File. Sends a signal indicating KURL to + * send when the local user accepts the transfer. + * @param file If valid, the user will not be prompted for a URL, and this one will be used instead. + * If it refers to a remote file and mustBeLocal is true, the file will be transferred to the local + * filesystem. + * @param localFile file name to display if file is a valid URL + * @param fileSize file size to send if file is a valid URL + * @param mustBeLocal If the protocol can only send files on the local filesystem, this flag + * allows you to ensure the filename will be local. + * @param sendTo The object to send the signal to + * @param slot The slot to send the signal to. Signature: sendFile(const KURL &file) + */ + void sendFile( const KURL &file, const QString &localFile, unsigned long fileSize, + bool mustBeLocal, QObject *sendTo, const char *slot ); + +signals: + /** @brief Signals the transfer is done. */ + void done( Kopete::Transfer* ); + + /** @brief Signals the transfer has been canceled. */ + void canceled( Kopete::Transfer* ); + + /** @brief Signals the transfer has been accepted */ + void accepted(Kopete::Transfer*, const QString &fileName); + + /** @brief Signals the transfer has been rejected */ + void refused(const Kopete::FileTransferInfo& ); + + /** @brief Send a file */ + void sendFile(const KURL &file, const QString &localFile, unsigned int fileSize); + +private slots: + void slotAccepted(const Kopete::FileTransferInfo&, const QString&); + void slotComplete(KIO::Job*); + +private: + TransferManager( QObject *parent ); + static TransferManager *s_transferManager; + + int nextID; + QMap<unsigned int, Transfer *> mTransfersMap; +}; + +/** + * A KIO job for a kopete file transfer. + * @author Richard Smith <kopete@metafoo.co.uk> + */ +class KOPETE_EXPORT Transfer : public KIO::Job +{ + Q_OBJECT + +public: + /** + * Constructor + */ + Transfer( const FileTransferInfo &, const QString &localFile, bool showProgressInfo = true); + + /** + * Constructor + */ + Transfer( const FileTransferInfo &, const Contact *toUser, bool showProgressInfo = true); + + /** + * Destructor + */ + ~Transfer(); + + /** @brief Get the info for this file transfer */ + const FileTransferInfo &info() const { return mInfo; } + + /** + * Retrieve a URL indicating where the file is being copied from. + * For display purposes only! There's no guarantee that this URL + * refers to a real file being transferred. + */ + KURL sourceURL(); + + /** + * Retrieve a URL indicating where the file is being copied to. + * See @ref sourceURL + */ + KURL destinationURL(); + +public slots: + + /** + * @brief Set the file size processed so far + */ + void slotProcessed(unsigned int); + + /** + * @brief Indicate that the transfer is complete + */ + void slotComplete(); + + /** + * @brief Inform the job that an error has occurred while transferring the file. + * + * @param error A member of the KIO::Error enumeration indicating what error occurred. + * @param errorText A string to aid understanding of the error, often the offending URL. + */ + void slotError( int error, const QString &errorText ); + +signals: + /** + * @deprecated Use result() and check error() for ERR_USER_CANCELED + */ + void transferCanceled(); + +private: + void init( const KURL &, bool ); + + static KURL displayURL( const Contact *contact, const QString &file ); + + FileTransferInfo mInfo; + KURL mTarget; + int mPercent; + +private slots: + void slotResultEmitted(); +}; + +} + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopeteui.desktop b/kopete/libkopete/kopeteui.desktop new file mode 100644 index 00000000..6818fc35 --- /dev/null +++ b/kopete/libkopete/kopeteui.desktop @@ -0,0 +1,60 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Kopete/UI +X-KDE-Derived=Kopete/Plugin +Comment=A Kopete UI Plugin +Comment[ar]=توصيلة واجهة استخدام Kopete +Comment[be]=Модуль інтэрфэйсу Kopete +Comment[bg]=Приставка за графичния интерфейс на Kopete +Comment[bn]=একটি কপেট ইউ-আই প্লাগিন +Comment[bs]=Kopete dodatak za UI +Comment[ca]=Un connector de IU per a Kopete +Comment[cs]=Modul rozhraní aplikace Kopete +Comment[cy]=Ategyn UI Kopete +Comment[da]=En Kopete UI-plugin +Comment[de]=Ein Kopete Benutzeroberflächenmodul +Comment[el]=Ένα πρόσθετο γραφικού περιβάλλοντος του Kopete +Comment[es]=Complemento de UI de Kopete +Comment[et]=Kopete kasutajaliidese plugin +Comment[eu]=Kopete UI plugin bat +Comment[fa]=یک وصلۀ شناسۀ کاربر Kopete +Comment[fi]=Kopeten käyttöliittymäliitännäinen +Comment[fr]=Un module d'interface utilisateur pour Kopete +Comment[ga]=Breiseán Chomhéadan Úsáideora Kopete +Comment[gl]=Un protocolo de interfaz gráfica para Kopete +Comment[he]=תוסף ממשק משתמש של Kopete +Comment[hi]=एक के-ऑप्टी यूआई प्लगइन +Comment[hr]=Umetak za Kopeteovo korisničko sučelje +Comment[hu]=Kopete bővítőmodul a grafikus felülethez +Comment[is]=Viðmótsíforrit fyrir Kopete +Comment[it]=Plugin per UI di Kopete +Comment[ja]=Kopete UI プラグイン +Comment[ka]=Kopete UI მოდული +Comment[kk]=Kopete интерфейсінің плагин модулі +Comment[km]=កម្មវិធីជំនួយចំណុចប្រទាក់ក្រាហ្វិករបស់ Kopete +Comment[lt]=Kopete sąsajos įskiepis +Comment[mk]=UI-приклучок за Kopete +Comment[nb]=Et programtillegg for Kopete brukergrensesnitt +Comment[nds]=Kopete-Böversietmoduul +Comment[ne]=कोपेट यू आई प्लगइन +Comment[nl]=Een Kopete gebruikersinterface-plugin +Comment[nn]=Kopete-programtillegg for brukargrensesnitt +Comment[pl]=Wtyczka interfejsu użytkownika Kopete +Comment[pt]=Um 'Plugin' de Interface do Kopete +Comment[pt_BR]=Um plug-in de UI do Kopete +Comment[ro]=Un modul interfaţă grafică Kopete +Comment[ru]=Модуль интерфейса пользователя Kopete +Comment[se]=Kopete geavaheaddjelaktalassemoduvla +Comment[sk]=Modul rozhrania Kopete +Comment[sl]=Vstavek za uporabniški vmesnik za Kopete +Comment[sr]=Прикључак за Kopete-ов кориснички интерфејс +Comment[sr@Latn]=Priključak za Kopete-ov korisnički interfejs +Comment[sv]=Gränssnittsinsticksprogram för Kopete +Comment[ta]=ஒரு Kopete UI செருகல் +Comment[tg]=Модули Интерфейси Корвандии Kopete +Comment[tr]=Bir Kopete UI Eklentisi +Comment[uk]=Втулок інтерфейсу для Kopete +Comment[wa]=On tchôke-divins d' eterface grafike po Kopete +Comment[zh_CN]=Kopete 界面插件 +Comment[zh_HK]=Kopete 用戶界面插件 +Comment[zh_TW]=Kopete 使用者介面外掛程式 diff --git a/kopete/libkopete/kopeteuiglobal.cpp b/kopete/libkopete/kopeteuiglobal.cpp new file mode 100644 index 00000000..06c0dfa3 --- /dev/null +++ b/kopete/libkopete/kopeteuiglobal.cpp @@ -0,0 +1,60 @@ +/* + kopeteuiglobal.cpp - Kopete UI Globals + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteuiglobal.h" + +#include <qguardedptr.h> + + +namespace Kopete +{ + + +namespace +{ + QGuardedPtr<QWidget> g_mainWidget; + int g_sysTrayWId; +} + +void UI::Global::setMainWidget( QWidget *widget ) +{ + g_mainWidget = widget; +} + +QWidget *UI::Global::mainWidget() +{ + return g_mainWidget; +} + +void UI::Global::setSysTrayWId( int newWinId ) +{ + g_sysTrayWId = newWinId; +} + +int UI::Global::sysTrayWId() +{ + if ( g_sysTrayWId == 0 ) + return g_mainWidget->winId(); + else + return g_sysTrayWId; +} + + +} + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopeteuiglobal.h b/kopete/libkopete/kopeteuiglobal.h new file mode 100644 index 00000000..4a79eb87 --- /dev/null +++ b/kopete/libkopete/kopeteuiglobal.h @@ -0,0 +1,72 @@ +/* + kopeteuiglobal.h - Kopete UI Globals + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEUIGLOBAL_H +#define KOPETEUIGLOBAL_H + +#include <qwidget.h> + +#include "kopete_export.h" + +namespace Kopete +{ + +namespace UI +{ + +/** + * This namespace contains the Kopete user interface's global settings + */ +namespace Global +{ + /** + * Set the main widget to widget + */ + KOPETE_EXPORT void setMainWidget( QWidget *widget ); + /** + * Returns the main widget - this is the widget that message boxes + * and KNotify stuff should use as a parent. + */ + KOPETE_EXPORT QWidget *mainWidget(); + + /** + * \brief Returns the WId of the system tray. + * + * Allows developers easy access to the WId of the system tray so + * that it can be used for passive popups in the protocols + * \return the WId of the system tray. Returns the WId of the main + * widget if there's no system tray. + */ + KOPETE_EXPORT int sysTrayWId(); + + /** + * \brief Set the WId of the system tray. + * + * Called by the KopeteSystemTray constructor and destructor to + * set the WId for the system tray appropriately + */ + KOPETE_EXPORT void setSysTrayWId( int newWinId ); +} //Global::UI + +} //UI + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopeteutils.cpp b/kopete/libkopete/kopeteutils.cpp new file mode 100644 index 00000000..d7d8eb0f --- /dev/null +++ b/kopete/libkopete/kopeteutils.cpp @@ -0,0 +1,124 @@ +/* + Kopete Utils. + Copyright (c) 2005 Duncan Mac-Vicar Prett <duncan@kde.org> + + isHostReachable function code derived from KDE's HTTP kioslave + Copyright (c) 2005 Waldo Bastian <bastian@kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <qmap.h> + +#include <kmessagebox.h> +#include <knotifyclient.h> + +#include <kapplication.h> +#include <klocale.h> +#include <dcopclient.h> +#include <kdatastream.h> +#include <kdebug.h> +#include <kiconloader.h> + +#include "kopeteaccount.h" +#include "knotification.h" +#include "kopeteutils_private.h" +#include "kopeteutils.h" +#include "kopeteuiglobal.h" + +static const QString notifyConnectionLost_DefaultMessage = i18n("You have been disconnected."); +static const QString notifyConnectionLost_DefaultCaption = i18n("Connection Lost."); +static const QString notifyConnectionLost_DefaultExplanation = i18n("Kopete lost the channel used to talk to the instant messaging system.\nThis can be because either your internet access went down, the service is experiencing problems, or the service disconnected you because you tried to connect with the same account from another location. Try connecting again later."); + +static const QString notifyCannotConnect_DefaultMessage = i18n("Can't connect with the instant messaging server or peers."); +static const QString notifyCannotConnect_DefaultCaption = i18n("Can't connect."); +static const QString notifyCannotConnect_DefaultExplanation = i18n("This means Kopete can't reach the instant messaging server or peers.\nThis can be because either your internet access is down or the server is experiencing problems. Try connecting again later."); + +namespace Kopete +{ +namespace Utils +{ + +void notify( QPixmap pic, const QString &eventid, const QString &caption, const QString &message, const QString explanation, const QString debugInfo) +{ + QString action; + if ( !explanation.isEmpty() ) + action = i18n( "More Information..." ); + kdDebug( 14010 ) << k_funcinfo << endl; + KNotification *n = KNotification::event( eventid, message, pic , 0L , action ); + ErrorNotificationInfo info; + info.explanation = explanation; + info.debugInfo = debugInfo; + + NotifyHelper::self()->registerNotification(n, info); + QObject::connect( n, SIGNAL(activated(unsigned int )) , NotifyHelper::self() , SLOT( slotEventActivated(unsigned int) ) ); + QObject::connect( n, SIGNAL(closed()) , NotifyHelper::self() , SLOT( slotEventClosed() ) ); +} + +void notifyConnectionLost( const Account *account, const QString &caption, const QString &message, const QString &explanation, const QString &debugInfo ) +{ + if (!account) + return; + + notify( account->accountIcon(32), QString::fromLatin1("connection_lost"), caption.isEmpty() ? notifyConnectionLost_DefaultCaption : caption, message.isEmpty() ? notifyConnectionLost_DefaultMessage : message, explanation.isEmpty() ? notifyConnectionLost_DefaultExplanation : explanation, debugInfo); +} + +bool isHostReachable(const QString &host) +{ + const int NetWorkStatusUnknown = 1; + const int NetWorkStatusOnline = 8; + QCString replyType; + QByteArray params; + QByteArray reply; + + QDataStream stream(params, IO_WriteOnly); + stream << host; + + if ( KApplication::kApplication()->dcopClient()->call( "kded", "networkstatus", "status(QString)", params, replyType, reply ) && (replyType == "int") ) + { + int result; + QDataStream stream2( reply, IO_ReadOnly ); + stream2 >> result; + return (result != NetWorkStatusUnknown) && (result != NetWorkStatusOnline); + } + return false; // On error, assume we are online +} + +void notifyCannotConnect( const Account *account, const QString &explanation, const QString &debugInfo) +{ + if (!account) + return; + + notify( account->accountIcon(), QString::fromLatin1("cannot_connect"), notifyCannotConnect_DefaultCaption, notifyCannotConnect_DefaultMessage, notifyCannotConnect_DefaultExplanation, debugInfo); +} + +void notifyConnectionError( const Account *account, const QString &caption, const QString &message, const QString &explanation, const QString &debugInfo ) +{ + if (!account) + return; + + // TODO: Display a specific default connection error message, I don't want to introducte too many new strings + notify( account->accountIcon(32), QString::fromLatin1("connection_error"), caption, message, explanation, debugInfo); +} + +void notifyServerError( const Account *account, const QString &caption, const QString &message, const QString &explanation, const QString &debugInfo ) +{ + if (!account) + return; + + // TODO: Display a specific default server error message, I don't want to introducte too many new strings + notify( account->accountIcon(32), QString::fromLatin1("server_error"), caption, message, explanation, debugInfo); +} + +} // end ns ErrorNotifier +} // end ns Kopete + diff --git a/kopete/libkopete/kopeteutils.h b/kopete/libkopete/kopeteutils.h new file mode 100644 index 00000000..1cbcb4c3 --- /dev/null +++ b/kopete/libkopete/kopeteutils.h @@ -0,0 +1,114 @@ +/* + Kopete Utils. + + Copyright (c) 2005 Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_UTILS_H +#define KOPETE_UTILS_H + +#include "qobject.h" +#include "qstring.h" +#include "qpixmap.h" +#include "kopete_export.h" + +class KNotification; + +namespace Kopete +{ + +class Account; + +namespace Utils +{ + +/** + * Checks if host is accesible. Useful for plugins to check for disconnected events. + * + * @param host The host to be cheked + */ +bool isHostReachable( const QString &host ); + +/** + * Notifies the user connection has been lost without coupling plugins with GUI code. + * + * @param account The account that lost the connection and wants to notify the user. + * @param caption A brief subject line, used where possible if the presentation allows it. + * @param message A short description of the error. + * @param explanation A long description on how the error occured and what the user can do about it. + * @param debugInfo Debug info that can be sent to the developers or to the network service owners. + * + * You can not provide debugInfo without an user explanation. If you don't provide a caption, message, or + * explanation, Kopete will use a default explanation. + */ +void KOPETE_EXPORT notifyConnectionLost( const Account *account, + const QString &caption = QString::null, + const QString &message = QString::null, + const QString &explanation = QString::null, + const QString &debugInfo = QString::null ); + + +/** + * Notifies the user the server is not reachable without coupling plugins with GUI code. + * + * @param account The account that cannot establish a connection and want to notify the user about that. + * @param explanation A long description on how the error occured and what the user can do about it. + * @param debugInfo Debug info that can be sent to the developers or to the network service owners. + * + * You can not provide debugInfo without an user explanation. If you don't provide a caption, message, or + * explanation, Kopete will use a default explanation. + */ +void KOPETE_EXPORT notifyCannotConnect( const Account *account, + const QString &explanation = QString::null, + const QString &debugInfo = QString::null); + +/** + * Notifies the user that an error on a connection occcured without coupling plugins with GUI code. + * + * @param account The account where the connection error occured and wants to notify the user. + * @param caption A brief subject line, used where possible if the presentation allows it. + * @param message A short description of the error. + * @param explanation A long description on how the error occured and what the user can do about it. + * @param debugInfo Debug info that can be sent to the developers or to the network service owners. + * + * You can not provide debugInfo without an user explanation. If you don't provide a caption, message, or + * explanation, Kopete will use a default explanation. + */ +void KOPETE_EXPORT notifyConnectionError( const Account *account, + const QString &caption = QString::null, + const QString &message = QString::null, + const QString &explanation = QString::null, + const QString &debugInfo = QString::null ); + +/** + * Notifies the user that an error on the server occcured without coupling plugins with GUI code. + * + * @param account The account where the server error occured and wants to notify the user. + * @param caption A brief subject line, used where possible if the presentation allows it. + * @param message A short description of the error. + * @param explanation A long description on how the error occured and what the user can do about it. + * @param debugInfo Debug info that can be sent to the developers or to the network service owners. + * + * You can not provide debugInfo without an user explanation. If you don't provide a caption, message, or + * explanation, Kopete will use a default explanation. + */ +void KOPETE_EXPORT notifyServerError( const Account *account, + const QString &caption = QString::null, + const QString &message = QString::null, + const QString &explanation = QString::null, + const QString &debugInfo = QString::null ); +} // end ns Utils +} // end ns Kopete + +#endif diff --git a/kopete/libkopete/kopeteversion.h b/kopete/libkopete/kopeteversion.h new file mode 100644 index 00000000..9775347c --- /dev/null +++ b/kopete/libkopete/kopeteversion.h @@ -0,0 +1,29 @@ +/* + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETE_VERSION_H_ +#define _KOPETE_VERSION_H_ + +#define KOPETE_VERSION_STRING "0.12.7" +#define KOPETE_VERSION_MAJOR 0 +#define KOPETE_VERSION_MINOR 12 +#define KOPETE_VERSION_RELEASE 7 +#define KOPETE_MAKE_VERSION( a,b,c ) (((a) << 16) | ((b) << 8) | (c)) + +#define KOPETE_VERSION \ + KOPETE_MAKE_VERSION(KOPETE_VERSION_MAJOR,KOPETE_VERSION_MINOR,KOPETE_VERSION_RELEASE) + +#define KOPETE_IS_VERSION(a,b,c) ( KOPETE_VERSION >= KOPETE_MAKE_VERSION(a,b,c) ) + + +#endif // _KOPETE_VERSION_H_ diff --git a/kopete/libkopete/kopetewalletmanager.cpp b/kopete/libkopete/kopetewalletmanager.cpp new file mode 100644 index 00000000..e1d198fc --- /dev/null +++ b/kopete/libkopete/kopetewalletmanager.cpp @@ -0,0 +1,190 @@ +/* + kopetewalletmanager.cpp - Kopete Wallet Manager + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetewalletmanager.h" + +#include "kopeteuiglobal.h" + +#include <kdebug.h> +#include <kstaticdeleter.h> +#include <kwallet.h> + +#include <qtimer.h> +#include <qwidget.h> +#include <qapplication.h> + +static WId mainWindowID() +{ + if ( QWidget *w = Kopete::UI::Global::mainWidget() ) + return w->winId(); + return 0; +} + +class Kopete::WalletManager::Private +{ +public: + Private() : wallet(0), signal(0) {} + ~Private() { delete wallet; delete signal; } + + KWallet::Wallet *wallet; + + // we can't just connect every slot that wants the wallet to the + // walletOpened signal - since we disconnect all the slots immediately + // after emitting the signal, this would result in everyone who asked + // for the wallet again in response to a walletOpened signal to fail + // to receive it. + // instead, we store a KopeteWalletSignal which we connect to, and create + // a new one for each set of requests. + KopeteWalletSignal *signal; +}; + +Kopete::WalletManager::WalletManager() + : d( new Private ) +{ +} + +Kopete::WalletManager::~WalletManager() +{ + closeWallet(); + delete d; +} + +Kopete::WalletManager *Kopete::WalletManager::self() +{ + static KStaticDeleter<Kopete::WalletManager> s_deleter; + static Kopete::WalletManager *s_self = 0; + + if ( !s_self ) + s_deleter.setObject( s_self, new Kopete::WalletManager() ); + return s_self; +} + +void Kopete::WalletManager::openWallet( QObject *object, const char *slot ) +{ + if ( !d->signal ) + d->signal = new KopeteWalletSignal; + // allow connecting to protected slots by calling object->connect + connect( d->signal, SIGNAL( walletOpened( KWallet::Wallet* ) ), object, slot ); + //object->connect( d->signal, SIGNAL( walletOpened( KWallet::Wallet* ) ), slot ); + openWalletInner(); +} + +void Kopete::WalletManager::openWalletInner() +{ + // do we already have a wallet? + if ( d->wallet ) + { + // if the wallet isn't open yet, we're pending a slotWalletChangedStatus + // anyway, so we don't set up a single shot. + if ( d->wallet->isOpen() ) + { + kdDebug(14010) << k_funcinfo << " wallet already open" << endl; + QTimer::singleShot( 0, this, SLOT( slotGiveExistingWallet() ) ); + } + else + { + kdDebug(14010) << k_funcinfo << " still waiting for earlier request" << endl; + } + return; + } + + kdDebug(14010) << k_funcinfo << " about to open wallet async" << endl; + + // we have no wallet: ask for one. + d->wallet = KWallet::Wallet::openWallet( KWallet::Wallet::NetworkWallet(), + mainWindowID(), KWallet::Wallet::Asynchronous ); + + connect( d->wallet, SIGNAL( walletOpened(bool) ), SLOT( slotWalletChangedStatus() ) ); +} + +void Kopete::WalletManager::slotWalletChangedStatus() +{ + kdDebug(14010) << k_funcinfo << " isOpen: " << d->wallet->isOpen() << endl; + + if( d->wallet->isOpen() ) + { + if ( !d->wallet->hasFolder( QString::fromLatin1( "Kopete" ) ) ) + d->wallet->createFolder( QString::fromLatin1( "Kopete" ) ); + + if ( d->wallet->setFolder( QString::fromLatin1( "Kopete" ) ) ) + { + // success! + QObject::connect( d->wallet, SIGNAL( walletClosed() ), this, SLOT( closeWallet() ) ); + } + else + { + // opened OK, but we can't use it + delete d->wallet; + d->wallet = 0; + } + } + else + { + // failed to open + delete d->wallet; + d->wallet = 0; + } + + emitWalletOpened( d->wallet ); +} + +void Kopete::WalletManager::slotGiveExistingWallet() +{ + kdDebug(14010) << k_funcinfo << " with d->wallet " << d->wallet << endl; + + if ( d->wallet ) + { + // the wallet was already open + if ( d->wallet->isOpen() ) + emitWalletOpened( d->wallet ); + // if the wallet was not open, but d->wallet is not 0, + // then we're waiting for it to open, and will be told + // when it's done: do nothing. + else + kdDebug(14010) << k_funcinfo << " wallet gone, waiting for another wallet" << endl; + } + else + { + // the wallet was lost between us trying to open it and + // getting called back. try to reopen it. + openWalletInner(); + } +} + +void Kopete::WalletManager::closeWallet() +{ + if ( !d->wallet ) return; + + delete d->wallet; + d->wallet = 0L; + + emit walletLost(); +} + +void Kopete::WalletManager::emitWalletOpened( KWallet::Wallet *wallet ) +{ + KopeteWalletSignal *signal = d->signal; + d->signal = 0; + if ( signal ) + emit signal->walletOpened( wallet ); + delete signal; +} + + +#include "kopetewalletmanager.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetewalletmanager.h b/kopete/libkopete/kopetewalletmanager.h new file mode 100644 index 00000000..fdd3a154 --- /dev/null +++ b/kopete/libkopete/kopetewalletmanager.h @@ -0,0 +1,116 @@ +/* + kopetewalletmanager.h - Kopete Wallet Manager + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEWALLETMANAGER_H +#define KOPETEWALLETMANAGER_H + +#include <qobject.h> + +#include <kdemacros.h> + +#include "kopete_export.h" + +namespace KWallet { class Wallet; } + +namespace Kopete +{ + +/** + * @author Richard Smith <kde@metafoo.co.uk> + * + * The Kopete::WalletManager class is a singleton, which looks after Kopete's + * KWallet connection. + */ +class KOPETE_EXPORT WalletManager : public QObject +{ + Q_OBJECT + +public: + /** + * Retrieve the wallet manager instance + */ + static WalletManager *self(); + ~WalletManager(); + + /** + * @brief Attempt to open the KWallet asyncronously, then signal an + * object to indicate the task is complete. + * + * @param object The object to call back to + * @param slot The slot on object to call; must have signature slot( KWallet::Wallet* ) + * The parameter to the slot will be the wallet that was opened if the call + * succeeded, or NULL if the wallet failed to open or the Kopete folder was + * inaccessible. + * + * For simplicity of client code, it is guaranteed that your slot + * will not be called during a call to this function. + */ + void openWallet( QObject *object, const char *slot ); + +public slots: + /** + * Close the connection to the wallet. Will cause walletLost() to be emitted. + */ + void closeWallet(); + +signals: + /** + * Emitted when the connection to the wallet is lost. + */ + void walletLost(); + +private slots: + /** + * Called by the stored wallet pointer when it is successfully opened or + * when it fails. + * + * Causes walletOpened to be emitted. + */ + void slotWalletChangedStatus(); + + /** + * Called by a singleShot timer in the event that we are asked for a + * wallet when we already have one open and ready. + */ + void slotGiveExistingWallet(); + +private: + void openWalletInner(); + void emitWalletOpened( KWallet::Wallet *wallet ); + + class Private; + Private *d; + + WalletManager(); +}; + +} + +/** + * @internal + */ +class KopeteWalletSignal : public QObject +{ + Q_OBJECT + friend class Kopete::WalletManager; +signals: + void walletOpened( KWallet::Wallet *wallet ); +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/managedconnectionaccount.cpp b/kopete/libkopete/managedconnectionaccount.cpp new file mode 100644 index 00000000..0f1625b2 --- /dev/null +++ b/kopete/libkopete/managedconnectionaccount.cpp @@ -0,0 +1,73 @@ +/* + managedconnectionaccount.h - Kopete Account that uses a manager to + control its connection and respond to connection events + + Copyright (c) 2005 by Will Stephenson <lists@stevello.free-online.co.uk> + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "connectionmanager.h" +#include "kopeteuiglobal.h" + +#include "managedconnectionaccount.h" + + +namespace Kopete +{ + +ManagedConnectionAccount::ManagedConnectionAccount( Protocol *parent, const QString &acctId, uint maxPasswordLength, const char *name ) + : PasswordedAccount( parent, acctId, maxPasswordLength, name ), m_waitingForConnection( false ) +{ + QObject::connect( ConnectionManager::self(), SIGNAL(statusChanged(const QString&, NetworkStatus::EnumStatus ) ), + SLOT(slotConnectionStatusChanged(const QString&, NetworkStatus::EnumStatus ) ) ); +} + +void ManagedConnectionAccount::connectWithPassword( const QString &password ) +{ + m_password = password; + NetworkStatus::EnumStatus status = ConnectionManager::self()->status( QString::null ); + if ( status == NetworkStatus::NoNetworks ) + performConnectWithPassword( password ); + else + { + m_waitingForConnection = true; + // need to adapt libkopete so we know the hostname in this class and whether the connection was user initiated + // for now, these are the default parameters to always bring up a connection to "the internet". + NetworkStatus::EnumRequestResult response = ConnectionManager::self()->requestConnection( Kopete::UI::Global::mainWidget(), QString::null, true ); + if ( response == NetworkStatus::Connected ) + { + m_waitingForConnection = false; + performConnectWithPassword( password ); + } + else if ( response == NetworkStatus::UserRefused || response == NetworkStatus::Unavailable ) + disconnect(); + } +} + +void ManagedConnectionAccount::slotConnectionStatusChanged( const QString & host, NetworkStatus::EnumStatus status ) +{ + Q_UNUSED(host); // as above, we didn't register a hostname, so treat any connection as our own. + + if ( m_waitingForConnection && ( status == NetworkStatus::Online || status == NetworkStatus::NoNetworks ) ) + { + m_waitingForConnection = false; + performConnectWithPassword( m_password ); + } + else if ( isConnected() && ( status == NetworkStatus::Offline + || status == NetworkStatus::ShuttingDown + || status == NetworkStatus::OfflineDisconnected + || status == NetworkStatus::OfflineFailed ) ) + disconnect(); +} + +} // end namespace Kopete +#include "managedconnectionaccount.moc" diff --git a/kopete/libkopete/managedconnectionaccount.h b/kopete/libkopete/managedconnectionaccount.h new file mode 100644 index 00000000..ad29feed --- /dev/null +++ b/kopete/libkopete/managedconnectionaccount.h @@ -0,0 +1,79 @@ +/* + managedconnectionaccount.h - Kopete Account that uses a manager to + control its connection and respond to connection events + + Copyright (c) 2005 by Will Stephenson <lists@stevello.free-online.co.uk> + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef MANAGEDCONNECTIONACCOUNT_H +#define MANAGEDCONNECTIONACCOUNT_H + +#include "networkstatuscommon.h" + +#include "kopetepasswordedaccount.h" + +namespace Kopete +{ +class Protocol; + +/** + * A ManagedConnectionAccount queries the NetworkStatus KDED Module before trying to connect using + * connectwithPassword, starting a network connection if needed. If the network is not available, + * it delays calling performConnectWithPassword until it receives notification from the daemon + * that the network is up. The account receiveds notifications from the daemon of network failures + * and calls disconnect to set the account offline in a timely manner. + */ +class KOPETE_EXPORT ManagedConnectionAccount : public PasswordedAccount +{ + Q_OBJECT + public: + /** + * @brief ManagedConnectionAccount constructor. + * @param parent The protocol this account connects via + * @param acctId The ID of this account - should be unique within this protocol + * @param maxPasswordLength The maximum length for passwords for this account, or 0 for no limit + * @param name The name for this QObject + */ + ManagedConnectionAccount( Protocol *parent, const QString &acctId, uint maxPasswordLength = 0, const char *name = 0 ); + public slots: + /** + * @brief Begin the connection process, by checking if the connection is available with the ConnectionManager. + * This method is called by PasswordedAccount::connect() + * @param password the password to connect with. + */ + void connectWithPassword( const QString &password ); + protected: + /** + * @brief Connect to the server, once the network is available. + * This method is called by the ManagedConnectionAccount once the network is available. In this method you should set up your + * network connection and connect to the server. + */ + virtual void performConnectWithPassword( const QString & password ) = 0; + protected slots: + /** + * @brief Handle a change in the network connection + * Called by the ConnectionManager when the network comes up or fails. + * The default implementation calls performConnectWithPassword when the network goes online and connectWithPassword() was + * previously called, and calls disconnect() when the connection goes down. + * @param host For future expansion. + * @param status the new status of the network + */ + virtual void slotConnectionStatusChanged( const QString & host, NetworkStatus::EnumStatus status ); + private: + QString m_password; + bool m_waitingForConnection; +}; + +} + +#endif diff --git a/kopete/libkopete/networkstatuscommon.cpp b/kopete/libkopete/networkstatuscommon.cpp new file mode 100644 index 00000000..216752bd --- /dev/null +++ b/kopete/libkopete/networkstatuscommon.cpp @@ -0,0 +1,32 @@ +#include "networkstatuscommon.h" +#include <kdebug.h> + +QDataStream & operator<< ( QDataStream & s, const NetworkStatus::Properties p ) +{ + kdDebug() << k_funcinfo << "status is: " << (int)p.status << endl; + s << (int)p.status; + s << (int)p.onDemandPolicy; + s << p.service; + s << ( p.internet ? 1 : 0 ); + s << p.netmasks; + return s; +} + +QDataStream & operator>> ( QDataStream & s, NetworkStatus::Properties &p ) +{ + int status, onDemandPolicy, internet; + s >> status; + kdDebug() << k_funcinfo << "status is: " << status << endl; + p.status = ( NetworkStatus::EnumStatus )status; + s >> onDemandPolicy; + p.onDemandPolicy = ( NetworkStatus::EnumOnDemandPolicy )onDemandPolicy; + s >> p.service; + s >> internet; + if ( internet ) + p.internet = true; + else + p.internet = false; + s >> p.netmasks; + kdDebug() << k_funcinfo << "enum converted status is: " << p.status << endl; + return s; +} diff --git a/kopete/libkopete/networkstatuscommon.h b/kopete/libkopete/networkstatuscommon.h new file mode 100644 index 00000000..e6906445 --- /dev/null +++ b/kopete/libkopete/networkstatuscommon.h @@ -0,0 +1,33 @@ +#ifndef NETWORKSTATUS_COMMON_H +#define NETWORKSTATUS_COMMON_H + +#include <qstringlist.h> + +namespace NetworkStatus +{ + enum EnumStatus { NoNetworks = 1, Unreachable, OfflineDisconnected, OfflineFailed, ShuttingDown, Offline, Establishing, Online }; + enum EnumRequestResult { RequestAccepted = 1, Connected, UserRefused, Unavailable }; + enum EnumOnDemandPolicy { All, User, None, Permanent }; + struct Properties + { + QString name; + // status of the network + EnumStatus status; + // policy for on-demand usage as defined by the service + EnumOnDemandPolicy onDemandPolicy; + // identifier for the service + QCString service; + // indicate that the connection is to 'the internet' - similar to default gateway in routing + bool internet; + // list of netmasks that the network connects to - overridden by above internet + QStringList netmasks; + // for future expansion consider + // EnumChargingModel - FlatRate, TimeCharge, VolumeCharged + // EnumLinkStatus - for WLANs - VPOOR, POOR, AVERAGE, GOOD, EXCELLENT + }; +} + +QDataStream & operator>> ( QDataStream & s, NetworkStatus::Properties &p ); +QDataStream & operator<< ( QDataStream & s, const NetworkStatus::Properties p ); + +#endif diff --git a/kopete/libkopete/private/Makefile.am b/kopete/libkopete/private/Makefile.am new file mode 100644 index 00000000..15e930df --- /dev/null +++ b/kopete/libkopete/private/Makefile.am @@ -0,0 +1,13 @@ +METASOURCES = AUTO + +AM_CPPFLAGS = -DKDE_NO_COMPAT -DQT_NO_COMPAT -DQT_NO_CAST_ASCII -DQT_NO_ASCII_CAST \ + $(KOPETE_INCLUDES) $(all_includes) + +noinst_LTLIBRARIES = libkopeteprivate.la + +libkopeteprivate_la_SOURCES = kopeteemoticons.cpp \ + kopetecommand.cpp kopeteviewmanager.cpp kopeteutils_private.cpp +libkopeteprivate_la_LDFLAGS = $(all_libraries) +libkopeteprivate_la_LIBADD = $(LIB_KDEUI) +# vim: set noet: + diff --git a/kopete/libkopete/private/kopetecommand.cpp b/kopete/libkopete/private/kopetecommand.cpp new file mode 100644 index 00000000..52588f2e --- /dev/null +++ b/kopete/libkopete/private/kopetecommand.cpp @@ -0,0 +1,142 @@ +/* + kopetecommand.cpp - Command + + Copyright (c) 2003 by Jason Keirstead <jason@keirstead.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <qstringlist.h> +#include <kapplication.h> +#include <kdebug.h> +#include <kinputdialog.h> +#include <klocale.h> +#include <kmessagebox.h> + +#include "kopetechatsessionmanager.h" +#include "kopeteview.h" +#include "kopetecommand.h" +#include "kopeteuiglobal.h" + +Kopete::Command::Command( QObject *parent, const QString &command, const char* handlerSlot, + const QString &help, Kopete::CommandHandler::CommandType type, const QString &formatString, + uint minArgs, int maxArgs, const KShortcut &cut, const QString &pix ) + : KAction( command[0].upper() + command.right( command.length() - 1).lower(), pix, cut, parent, + ( command.lower() + QString::fromLatin1("_command") ).latin1() ) +{ + init( command, handlerSlot, help, type, formatString, minArgs, maxArgs ); +} + +void Kopete::Command::init( const QString &command, const char* slot, const QString &help, + Kopete::CommandHandler::CommandType type, const QString &formatString, uint minArgs, int maxArgs ) +{ + m_command = command; + m_help = help; + m_type = type; + m_formatString = formatString; + m_minArgs = minArgs; + m_maxArgs = maxArgs; + m_processing = false; + + if( m_type == Kopete::CommandHandler::Normal ) + { + QObject::connect( this, SIGNAL( handleCommand( const QString &, Kopete::ChatSession *) ), + parent(), slot ); + } + + QObject::connect( this, SIGNAL( activated() ), this, SLOT( slotAction() ) ); +} + +void Kopete::Command::slotAction() +{ + Kopete::ChatSession *manager = Kopete::ChatSessionManager::self()->activeView()->msgManager(); + + QString args; + if( m_minArgs > 0 ) + { + args = KInputDialog::getText( i18n("Enter Arguments"), i18n("Enter the arguments to %1:").arg(m_command) ); + if( args.isNull() ) + return; + } + + processCommand( args, manager, true ); +} + +void Kopete::Command::processCommand( const QString &args, Kopete::ChatSession *manager, bool gui ) +{ + QStringList mArgs = Kopete::CommandHandler::parseArguments( args ); + if( m_processing ) + { + printError( i18n("Alias \"%1\" expands to itself.").arg( text() ), manager, gui ); + } + else if( mArgs.count() < m_minArgs ) + { + printError( i18n("\"%1\" requires at least %n argument.", + "\"%1\" requires at least %n arguments.", m_minArgs) + .arg( text() ), manager, gui ); + } + else if( m_maxArgs > -1 && (int)mArgs.count() > m_maxArgs ) + { + printError( i18n("\"%1\" has a maximum of %n argument.", + "\"%1\" has a maximum of %n arguments.", m_minArgs) + .arg( text() ), manager, gui ); + } + else if( !KApplication::kApplication()->authorizeKAction( name() ) ) + { + printError( i18n("You are not authorized to perform the command \"%1\".").arg(text()), manager, gui ); + } + else + { + m_processing = true; + if( m_type == Kopete::CommandHandler::UserAlias || + m_type == Kopete::CommandHandler::SystemAlias ) + { + QString formatString = m_formatString; + + // Translate %s to the whole string and %n to current nickname + + formatString.replace( QString::fromLatin1("%n"), manager->myself()->nickName() ); + formatString.replace( QString::fromLatin1("%s"), args ); + + // Translate %1..%N to word1..wordN + + while( mArgs.count() > 0 ) + { + formatString = formatString.arg( mArgs.front() ); + mArgs.pop_front(); + } + + kdDebug(14010) << "New Command after processing alias: " << formatString << endl; + + Kopete::CommandHandler::commandHandler()->processMessage( QString::fromLatin1("/") + formatString, manager ); + } + else + { + emit( handleCommand( args, manager ) ); + } + m_processing = false; + } +} + +void Kopete::Command::printError( const QString &error, Kopete::ChatSession *manager, bool gui ) const +{ + if( gui ) + { + KMessageBox::error( Kopete::UI::Global::mainWidget(), error, i18n("Command Error") ); + } + else + { + Kopete::Message msg( manager->myself(), manager->members(), error, + Kopete::Message::Internal, Kopete::Message::PlainText ); + manager->appendMessage( msg ); + } +} + +#include "kopetecommand.moc" diff --git a/kopete/libkopete/private/kopetecommand.h b/kopete/libkopete/private/kopetecommand.h new file mode 100644 index 00000000..298872db --- /dev/null +++ b/kopete/libkopete/private/kopetecommand.h @@ -0,0 +1,109 @@ + +/* + kopetecommand.h - Command + + Copyright (c) 2003 by Jason Keirstead <jason@keirstead.org> + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef __KOPETECOMMAND_H__ +#define __KOPETECOMMAND_H__ + +#include <qobject.h> +#include <kaction.h> +#include "kopetecommandhandler.h" + +namespace Kopete +{ + +class ChatSession; + +class Command : public KAction +{ + Q_OBJECT + + public: + /** + * Creates a Kopete::Command object + * + * @param parent The plugin who owns this command + * @param command The command we want to handle, not including the '/' + * @param handlerSlot The slot used to handle the command. This slot must + * accept two parameters, a QString of arguments, and a Kopete::ChatSession + * pointer to the Manager under which the command was sent. + * @param help An optional help string to be shown when the user uses + * /help <i>command</i> + * @param type If this command is an alias, and what type + * @param formatString The formatString of the alias if any + * @param minArgs Minimum number of arguments + * @param maxArgs Maximum number of arguments + * @param cut The shortcut for the command + * @param pix The icon to use for the command + */ + Command( QObject *parent, const QString &command, const char* handlerSlot, + const QString &help = QString::null, CommandHandler::CommandType type = CommandHandler::Normal, const QString &formatString = QString::null, + uint minArgs = 0, int maxArgs = -1, const KShortcut &cut = 0, + const QString &pix = QString::null ); + + /** + * Process this command + */ + void processCommand( const QString &args, ChatSession *manager, bool gui = false ); + + /** + * Returns the command this object handles + */ + const QString &command() const { return m_command; }; + + /** + * Returns the help string for this command + */ + const QString &help() const { return m_help; }; + + /** + * Returns the type of the command + */ + const CommandHandler::CommandType type() const { return m_type; }; + + signals: + /** + * Emitted whenever a command is handled by this object. When a command + * has been handled, all processing on it stops by the command handler + * (a command cannot be handled twice) + */ + void handleCommand( const QString &args, Kopete::ChatSession *manager ); + + private slots: + /** + * Connected to our activated() signal + */ + void slotAction(); + + private: + void init( const QString &command, const char* slot, const QString &help, + CommandHandler::CommandType type, const QString &formatString, + uint minArgs, int maxArgs ); + + void printError( const QString &error, ChatSession *manager, bool gui = false ) const; + + QString m_command; + QString m_help; + QString m_formatString; + uint m_minArgs; + int m_maxArgs; + bool m_processing; + CommandHandler::CommandType m_type; +}; + +} + +#endif diff --git a/kopete/libkopete/private/kopeteemoticons.cpp b/kopete/libkopete/private/kopeteemoticons.cpp new file mode 100644 index 00000000..87da4cf7 --- /dev/null +++ b/kopete/libkopete/private/kopeteemoticons.cpp @@ -0,0 +1,559 @@ +/* + kopeteemoticons.cpp - Kopete Preferences Container-Class + + Copyright (c) 2002 by Stefan Gehn <metz AT gehn.net> + Copyright (c) 2002-2006 by Olivier Goffart <ogoffart @ kde.org> + Copyright (c) 2005 by Engin AYDOGAN <engin@bzzzt.biz> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteemoticons.h" + +#include "kopeteprefs.h" + +#include <qdom.h> +#include <qfile.h> +#include <qstylesheet.h> +#include <qimage.h> +#include <qdatetime.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kstandarddirs.h> +#include <kdeversion.h> + +#include <set> +#include <algorithm> +#include <iterator> + + +/* + * Testcases can be found in the kopeteemoticontest app in the tests/ directory. + */ + + +namespace Kopete { + + +struct Emoticons::Emoticon +{ + Emoticon(){} + /* sort by longest to shortest matchText */ + bool operator< (const Emoticon &e){ return matchText.length() > e.matchText.length(); } + QString matchText; + QString matchTextEscaped; + QString picPath; + QString picHTMLCode; +}; + +/* This is the object we will store each emoticon match in */ +struct Emoticons::EmoticonNode { + const Emoticon emoticon; + int pos; + EmoticonNode() : emoticon(), pos( -1 ) {} + EmoticonNode( const Emoticon e, int p ) : emoticon( e ), pos( p ) {} +}; + +class Emoticons::Private +{ +public: + QMap<QChar, QValueList<Emoticon> > emoticonMap; + QMap<QString, QStringList> emoticonAndPicList; + + /** + * The current icon theme from KopetePrefs + */ + QString theme; + + +}; + + +Emoticons *Emoticons::s_self = 0L; + +Emoticons *Emoticons::self() +{ + if( !s_self ) + s_self = new Emoticons; + return s_self; +} + + +QString Emoticons::parseEmoticons(const QString& message, ParseMode mode ) //static +{ + return self()->parse( message, mode ); +} + +QValueList<Emoticons::Token> Emoticons::tokenizeEmoticons( const QString& message, ParseMode mode ) // static +{ + return self()->tokenize( message, mode ); +} + +QValueList<Emoticons::Token> Emoticons::tokenize( const QString& message, uint mode ) +{ + QValueList<Token> result; + if ( !KopetePrefs::prefs()->useEmoticons() ) + { + result.append( Token( Text, message ) ); + return result; + } + + if( ! ( mode & (StrictParse|RelaxedParse) ) ) + { + //if none of theses two mode are selected, use the mode from the config + mode |= KopetePrefs::prefs()->emoticonsRequireSpaces() ? StrictParse : RelaxedParse ; + } + + /* previous char, in the firs iteration assume that it is space since we want + * to let emoticons at the beginning, the very first previous QChar must be a space. */ + QChar p = ' '; + QChar c; /* current char */ + QChar n; /* next character after a match candidate, if strict this should be QChar::null or space */ + + /* This is the EmoticonNode container, it will represent each matched emoticon */ + QValueList<EmoticonNode> foundEmoticons; + QValueList<EmoticonNode>::const_iterator found; + /* First-pass, store the matched emoticon locations in foundEmoticons */ + QValueList<Emoticon> emoticonList; + QValueList<Emoticon>::const_iterator it; + size_t pos; + + bool inHTMLTag = false; + bool inHTMLLink = false; + bool inHTMLEntity = false; + QString needle; // search for this + for ( pos = 0; pos < message.length(); pos++ ) + { + c = message[ pos ]; + + if ( mode & SkipHTML ) // Shall we skip HTML ? + { + if ( !inHTMLTag ) // Are we already in an HTML tag ? + { + if ( c == '<' ) { // If not check if are going into one + inHTMLTag = true; // If we are, change the state to inHTML + p = c; + continue; + } + } + else // We are already in a HTML tag + { + if ( c == '>' ) { // Check if it ends + inHTMLTag = false; // If so, change the state + if ( p == 'a' ) + { + inHTMLLink = false; + } + } + else if ( c == 'a' && p == '<' ) // check if we just entered an achor tag + { + inHTMLLink = true; // don't put smileys in urls + } + p = c; + continue; + } + + if( !inHTMLEntity ) + { // are we + if( c == '&' ) + { + inHTMLEntity = true; + } + } + } + + if ( inHTMLLink ) // i can't think of any situation where a link adress might need emoticons + { + p = c; + continue; + } + + if ( (mode & StrictParse) && !p.isSpace() && p != '>') + { // '>' may mark the end of an html tag + p = c; + continue; + } /* strict requires space before the emoticon */ + if ( d->emoticonMap.contains( c ) ) + { + emoticonList = d->emoticonMap[ c ]; + bool found = false; + for ( it = emoticonList.begin(); it != emoticonList.end(); ++it ) + { + // If this is an HTML, then search for the HTML form of the emoticon. + // For instance <o) => >o) + needle = ( mode & SkipHTML ) ? (*it).matchTextEscaped : (*it).matchText; + if ( ( pos == (size_t)message.find( needle, pos ) ) ) + { + if( mode & StrictParse ) + { + /* check if the character after this match is space or end of string*/ + n = message[ pos + needle.length() ]; + //<br/> marks the end of a line + if( n != '<' && !n.isSpace() && !n.isNull() && n!= '&') + break; + } + /* Perfect match */ + foundEmoticons.append( EmoticonNode( (*it), pos ) ); + found = true; + /* Skip the matched emoticon's matchText */ + pos += needle.length() - 1; + break; + } + } + if( !found ) + { + if( inHTMLEntity ){ + // If we are in an HTML entitiy such as > + int htmlEnd = message.find( ';', pos ); + // Search for where it ends + if( htmlEnd == -1 ) + { + // Apparently this HTML entity isn't ended, something is wrong, try skip the '&' + // and continue + kdDebug( 14000 ) << k_funcinfo << "Broken HTML entity, trying to recover." << endl; + inHTMLEntity = false; + pos++; + } + else + { + pos = htmlEnd; + inHTMLEntity = false; + } + } + } + } /* else no emoticons begin with this character, so don't do anything */ + p = c; + } + + /* if no emoticons found just return the text */ + if ( foundEmoticons.isEmpty() ) + { + result.append( Token( Text, message ) ); + return result; + } + + /* Second-pass, generate tokens based on the matches */ + + pos = 0; + int length; + + for ( found = foundEmoticons.begin(); found != foundEmoticons.end(); ++found ) + { + needle = ( mode & SkipHTML ) ? (*found).emoticon.matchTextEscaped : (*found).emoticon.matchText; + if ( ( length = ( (*found).pos - pos ) ) ) + { + result.append( Token( Text, message.mid( pos, length ) ) ); + result.append( Token( Image, (*found).emoticon.matchTextEscaped, (*found).emoticon.picPath, (*found).emoticon.picHTMLCode ) ); + pos += length + needle.length(); + } + else + { + result.append( Token( Image, (*found).emoticon.matchTextEscaped, (*found).emoticon.picPath, (*found).emoticon.picHTMLCode ) ); + pos += needle.length(); + } + } + + if ( message.length() - pos ) // if there is remaining regular text + { + result.append( Token( Text, message.mid( pos ) ) ); + } + + return result; +} + +Emoticons::Emoticons( const QString &theme ) : QObject( kapp, "KopeteEmoticons" ) +{ +// kdDebug(14010) << "KopeteEmoticons::KopeteEmoticons" << endl; + d=new Private; + if(theme.isNull()) + { + initEmoticons(); + connect( KopetePrefs::prefs(), SIGNAL(saved()), this, SLOT(initEmoticons()) ); + } + else + { + initEmoticons( theme ); + } +} + + +Emoticons::~Emoticons( ) +{ + delete d; +} + + + +void Emoticons::addIfPossible( const QString& filenameNoExt, const QStringList &emoticons ) +{ + KStandardDirs *dir = KGlobal::dirs(); + QString pic; + + //maybe an extension was given, so try to find the exact file + pic = dir->findResource( "emoticons", d->theme + QString::fromLatin1( "/" ) + filenameNoExt ); + + if( pic.isNull() ) + pic = dir->findResource( "emoticons", d->theme + QString::fromLatin1( "/" ) + filenameNoExt + QString::fromLatin1( ".mng" ) ); + if ( pic.isNull() ) + pic = dir->findResource( "emoticons", d->theme + QString::fromLatin1( "/" ) + filenameNoExt + QString::fromLatin1( ".png" ) ); + if ( pic.isNull() ) + pic = dir->findResource( "emoticons", d->theme + QString::fromLatin1( "/" ) + filenameNoExt + QString::fromLatin1( ".gif" ) ); + + if( !pic.isNull() ) // only add if we found one file + { + QPixmap p; + QString result; + + d->emoticonAndPicList.insert( pic, emoticons ); + + for ( QStringList::const_iterator it = emoticons.constBegin(), end = emoticons.constEnd(); + it != end; ++it ) + { + QString matchEscaped=QStyleSheet::escape(*it); + + Emoticon e; + e.picPath = pic; + + // We need to include size (width, height attributes) hints in the emoticon HTML code + // Unless we do so, ChatMessagePart::slotScrollView does not work properly and causing + // HTMLPart not to be scrolled to the very last message. + p.load( e.picPath ); + result = QString::fromLatin1( "<img align=\"center\" src=\"" ) + + e.picPath + + QString::fromLatin1( "\" title=\"" ) + + matchEscaped + + QString::fromLatin1( "\" width=\"" ) + + QString::number( p.width() ) + + QString::fromLatin1( "\" height=\"" ) + + QString::number( p.height() ) + + QString::fromLatin1( "\" />" ); + + e.picHTMLCode = result; + e.matchTextEscaped = matchEscaped; + e.matchText = *it; + d->emoticonMap[ matchEscaped[0] ].append( e ); + d->emoticonMap[ (*it)[0] ].append( e ); + } + } +} + +void Emoticons::initEmoticons( const QString &theme ) +{ + if(theme.isNull()) + { + if ( d->theme == KopetePrefs::prefs()->iconTheme() ) + return; + + d->theme = KopetePrefs::prefs()->iconTheme(); + } + else + { + d->theme = theme; + } + +// kdDebug(14010) << k_funcinfo << "Called" << endl; + d->emoticonAndPicList.clear(); + d->emoticonMap.clear(); + + QString filename= KGlobal::dirs()->findResource( "emoticons", d->theme + QString::fromLatin1( "/emoticons.xml" ) ); + if(!filename.isEmpty()) + return initEmoticon_emoticonsxml( filename ); + filename= KGlobal::dirs()->findResource( "emoticons", d->theme + QString::fromLatin1( "/icondef.xml" ) ); + if(!filename.isEmpty()) + return initEmoticon_JEP0038( filename ); + kdWarning(14010) << k_funcinfo << "emotiucon XML theme description not found" <<endl; +} + +void Emoticons::initEmoticon_emoticonsxml( const QString & filename) +{ + QDomDocument emoticonMap( QString::fromLatin1( "messaging-emoticon-map" ) ); + + QFile mapFile( filename ); + mapFile.open( IO_ReadOnly ); + emoticonMap.setContent( &mapFile ); + + QDomElement list = emoticonMap.documentElement(); + QDomNode node = list.firstChild(); + while( !node.isNull() ) + { + QDomElement element = node.toElement(); + if( !element.isNull() ) + { + if( element.tagName() == QString::fromLatin1( "emoticon" ) ) + { + QString emoticon_file = element.attribute( + QString::fromLatin1( "file" ), QString::null ); + QStringList items; + + QDomNode emoticonNode = node.firstChild(); + while( !emoticonNode.isNull() ) + { + QDomElement emoticonElement = emoticonNode.toElement(); + if( !emoticonElement.isNull() ) + { + if( emoticonElement.tagName() == QString::fromLatin1( "string" ) ) + { + items << emoticonElement.text(); + } + else + { + kdDebug(14010) << k_funcinfo << + "Warning: Unknown element '" << element.tagName() << + "' in emoticon data" << endl; + } + } + emoticonNode = emoticonNode.nextSibling(); + } + + addIfPossible ( emoticon_file, items ); + } + else + { + kdDebug(14010) << k_funcinfo << "Warning: Unknown element '" << + element.tagName() << "' in map file" << endl; + } + } + node = node.nextSibling(); + } + mapFile.close(); + sortEmoticons(); +} + + +void Emoticons::initEmoticon_JEP0038( const QString & filename) +{ + QDomDocument emoticonMap( QString::fromLatin1( "icondef" ) ); + + QFile mapFile( filename ); + mapFile.open( IO_ReadOnly ); + emoticonMap.setContent( &mapFile ); + + QDomElement list = emoticonMap.documentElement(); + QDomNode node = list.firstChild(); + while( !node.isNull() ) + { + QDomElement element = node.toElement(); + if( !element.isNull() ) + { + if( element.tagName() == QString::fromLatin1( "icon" ) ) + { + QStringList items; + QString emoticon_file; + + QDomNode emoticonNode = node.firstChild(); + while( !emoticonNode.isNull() ) + { + QDomElement emoticonElement = emoticonNode.toElement(); + if( !emoticonElement.isNull() ) + { + if( emoticonElement.tagName() == QString::fromLatin1( "text" ) ) + { + //TODO xml:lang + items << emoticonElement.text(); + } + else if( emoticonElement.tagName() == QString::fromLatin1( "object" ) && emoticon_file.isEmpty() ) + { + QString mime= emoticonElement.attribute( + QString::fromLatin1( "mime" ), QString::fromLatin1("image/*") ); + if(mime.startsWith(QString::fromLatin1("image/")) && !mime.endsWith(QString::fromLatin1("/svg+xml"))) + { + emoticon_file = emoticonElement.text(); + } + else + { + kdDebug(14010) << k_funcinfo << "Warning: Unsupported format '" << mime << endl; + } + } + /*else + { + kdDebug(14010) << k_funcinfo << + "Warning: Unknown element '" << element.tagName() << + "' in emoticon data" << endl; + }*/ + } + emoticonNode = emoticonNode.nextSibling(); + } + if( !items.isEmpty() && !emoticon_file.isEmpty() ) + addIfPossible ( emoticon_file, items ); + } + else + { + kdDebug(14010) << k_funcinfo << "Warning: Unknown element '" << + element.tagName() << "' in map file" << endl; + } + } + node = node.nextSibling(); + } + mapFile.close(); + sortEmoticons(); +} + + +void Emoticons::sortEmoticons() +{ + /* sort strings in order of longest to shortest to provide convenient input for + greedy matching in the tokenizer */ + QValueList<QChar> keys = d->emoticonMap.keys(); + for ( QValueList<QChar>::const_iterator it = keys.begin(); it != keys.end(); ++it ) + { + QChar key = (*it); + QValueList<Emoticon> keyValues = d->emoticonMap[key]; + qHeapSort(keyValues.begin(), keyValues.end()); + d->emoticonMap[key] = keyValues; + } +} + + + + +QMap<QString, QStringList> Emoticons::emoticonAndPicList() +{ + return d->emoticonAndPicList; +} + + +QString Emoticons::parse( const QString &message, ParseMode mode ) +{ + if ( !KopetePrefs::prefs()->useEmoticons() ) + return message; + + QValueList<Token> tokens = tokenize( message, mode ); + QValueList<Token>::const_iterator token; + QString result; + QPixmap p; + for ( token = tokens.begin(); token != tokens.end(); ++token ) + { + switch ( (*token).type ) + { + case Text: + result += (*token).text; + break; + case Image: + result += (*token).picHTMLCode; + kdDebug( 14010 ) << k_funcinfo << "Emoticon html code: " << result << endl; + break; + default: + kdDebug( 14010 ) << k_funcinfo << "Unknown token type. Something's broken." << endl; + } + } + return result; +} + +} //END namesapce Kopete + +#include "kopeteemoticons.moc" + + + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/private/kopeteemoticons.h b/kopete/libkopete/private/kopeteemoticons.h new file mode 100644 index 00000000..848185e6 --- /dev/null +++ b/kopete/libkopete/private/kopeteemoticons.h @@ -0,0 +1,184 @@ +/* + kopeteemoticons.cpp - Kopete Preferences Container-Class + + Copyright (c) 2002-2003 by Stefan Gehn <metz AT gehn.net> + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + Copyright (c) 2005 by Engin AYDOGAN <engin @ bzzzt.biz> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef kopeteemoticons_h__ +#define kopeteemoticons_h__ + +#include <qobject.h> +#include <qvaluelist.h> +#include <qregexp.h> + +#include "kopete_export.h" + +namespace Kopete { + +class KOPETE_EXPORT Emoticons : public QObject +{ + Q_OBJECT +public: + /** + * Constructor: DON'T use it if you want to use the emoticon theme + * chosen by the user. + * Instead, use @ref Kopete::Emoticons::self() + **/ + Emoticons( const QString &theme = QString::null ); + + ~Emoticons( ); + + /** + * The emoticons container-class by default is a singleton object. + * Use this method to retrieve the instance. + */ + static Emoticons *self(); + + /** + * The possible parse modes + */ + enum ParseMode { DefaultParseMode = 0x0 , /** Use strict or relaxed according the config */ + StrictParse = 0x1, /** Strict parsing requires a space between each emoticon */ + RelaxedParse = 0x4, /** Parse mode where all possible emoticon matches are allowed */ + SkipHTML = 0x2 /** Skip emoticons within HTML */ + }; + + /** + * Use it to parse emoticons in a text. + * You don't need to use this for chat windows, + * There is a special class that abstract a chat view + * and uses emoticons parser. + * This function will use the selected emoticon theme. + * If nicks is provided, they will not be parsed if they + * exist in message. + */ + static QString parseEmoticons( const QString &message, ParseMode = SkipHTML ) ; + + + QString parse( const QString &message, ParseMode = SkipHTML ); + + /** + * TokenType, a token might be an image ( emoticon ) or text. + */ + enum TokenType { Undefined, /** Undefined, for completeness only */ + Image, /** Token contains a path to an image */ + Text /** Token contains test */ + }; + + /** + * A token consists of a QString text which is either a regular text + * or a path to image depending on the type. + * If type is Image the text refers to an image path. + * If type is Text the text refers to a regular text. + */ + struct Token { + Token() : type( Undefined ) {} + Token( TokenType t, const QString &m ) : type( t ), text(m) {} + Token( TokenType t, const QString &m, const QString &p, const QString &html ) + : type( t ), text( m ), picPath( p ), picHTMLCode( html ) {} + TokenType type; + QString text; + QString picPath; + QString picHTMLCode; + }; + + + /** + * Static function which will call tokenize + * @see tokenize( const QString& ) + */ + static QValueList<Token> tokenizeEmoticons( const QString &message, ParseMode mode = DefaultParseMode ); + + /** + * Tokenizes an message. + * For example; + * Assume :], (H), :-x are three emoticons. + * A text "(H)(H) foo bar john :] :-x" would be tokenized as follows (not strict): + * 1- /path/to/shades.png + * 2- /path/to/shades.png + * 3- " foo bar john " + * 4- /path/to/bat.png + * 5- " " + * 6- /path/to/kiss.png + * + * Strict tokenization (require spaces around emoticons): + * 1- "(H)(H) foo bar john " + * 2- /path/to/bat.png + * 3- " " + * 4- /path/to/kiss.png + * Note: quotation marks are used to emphasize white spaces. + * @param message is the message to tokenize + * @param mode is a bitmask of ParseMode enum + * @return a QValueList which consiste of ordered tokens of the text. + * @author Engin AYDOGAN < engin@bzzzt.biz > + * @since 23-03-05 + */ + QValueList<Token> tokenize( const QString &message, uint mode = DefaultParseMode ); + + /** + * Return all emoticons and the corresponding icon. + * (only one emoticon per image) + */ + QMap<QString, QStringList> emoticonAndPicList(); + + +private: + /** + * Our instance + **/ + static Emoticons *s_self; + + /** + * add an emoticon to our mapping if + * an animation/pixmap has been found for it + **/ + void addIfPossible( const QString& filenameNoExt, const QStringList &emoticons ); + + /** + * uses the kopete's emoticons.xml for the theme + * @see initEmoticons + */ + void initEmoticon_emoticonsxml( const QString & filename); + + /** + * uses the JEP-0038 xml description for the theme + * @see initEmoticons + */ + void initEmoticon_JEP0038( const QString & filename); + + /** + * sorts emoticons for convenient parsing, which yields greedy matching on + * matchText + */ + void sortEmoticons(); + + + struct Emoticon; + struct EmoticonNode; + class Private; + Private *d; +private slots: + + /** + * Fills the map with paths and emoticons + * This needs to be done on every emoticon-theme change + **/ + void initEmoticons ( const QString &theme = QString::null ); +}; + + +} //END namespace Kopete + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/private/kopeteutils_private.cpp b/kopete/libkopete/private/kopeteutils_private.cpp new file mode 100644 index 00000000..3746bcd3 --- /dev/null +++ b/kopete/libkopete/private/kopeteutils_private.cpp @@ -0,0 +1,85 @@ +/* + Kopete Utils. + Copyright (c) 2005 Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <qmap.h> + +#include <kmessagebox.h> + +#include <kdebug.h> + +#include "knotification.h" +#include "kopeteutils_private.h" +#include "kopeteuiglobal.h" + +namespace Kopete +{ +namespace Utils +{ + +NotifyHelper* NotifyHelper::s_self = 0L; + +NotifyHelper::NotifyHelper() +{ +} + +NotifyHelper::~NotifyHelper() +{ +} + +NotifyHelper* NotifyHelper::self() +{ + if (!s_self) + s_self = new NotifyHelper(); + + return s_self; +} + +void NotifyHelper::slotEventActivated(unsigned int action) +{ + const KNotification *n = dynamic_cast<const KNotification *>(QObject::sender()); + if (n) + { + ErrorNotificationInfo info = m_events[n]; + if ( info.debugInfo.isEmpty() ) + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Information, info.explanation, info.caption); + else + KMessageBox::queuedDetailedError( Kopete::UI::Global::mainWidget(), info.explanation, info.debugInfo, info.caption); + + unregisterNotification(n); + } +} + +void NotifyHelper::slotEventClosed() +{ + const KNotification *n = dynamic_cast<const KNotification *>(QObject::sender()); + if (n) + unregisterNotification(n); +} + +void NotifyHelper::registerNotification(const KNotification* event, ErrorNotificationInfo error) +{ + m_events.insert( event, error); +} + +void NotifyHelper::unregisterNotification(const KNotification* event) +{ + m_events.remove(event); +} + +} // end ns ErrorNotifier +} // end ns Kopete + +#include "kopeteutils_private.moc" diff --git a/kopete/libkopete/private/kopeteutils_private.h b/kopete/libkopete/private/kopeteutils_private.h new file mode 100644 index 00000000..a684c965 --- /dev/null +++ b/kopete/libkopete/private/kopeteutils_private.h @@ -0,0 +1,60 @@ +/* + Kopete Utils. + + Copyright (c) 2005 Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_UTILS_PRIVATE_H +#define KOPETE_UTILS_PRIVATE_H + +#include "qobject.h" +#include "qstring.h" +#include "qpixmap.h" + +class KNotification; + +namespace Kopete +{ + +namespace Utils +{ + +typedef struct +{ + QString caption; + QString explanation; + QString debugInfo; +} ErrorNotificationInfo; + +class NotifyHelper : public QObject +{ +Q_OBJECT +public: + static NotifyHelper* self(); + void registerNotification(const KNotification* event, ErrorNotificationInfo error); + void unregisterNotification(const KNotification* event); +public slots: + void slotEventActivated(unsigned int action); + void slotEventClosed(); +private: + NotifyHelper(); + ~NotifyHelper(); + QMap<const KNotification*, ErrorNotificationInfo> m_events; + static NotifyHelper *s_self; +}; + +} // end ns Utils +} // end ns Kopete + +#endif diff --git a/kopete/libkopete/private/kopeteviewmanager.cpp b/kopete/libkopete/private/kopeteviewmanager.cpp new file mode 100644 index 00000000..c6d295fd --- /dev/null +++ b/kopete/libkopete/private/kopeteviewmanager.cpp @@ -0,0 +1,364 @@ +/* + kopeteviewmanager.cpp - View Manager + + Copyright (c) 2003 by Jason Keirstead <jason@keirstead.org> + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + + +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <klocale.h> +#include <qptrlist.h> +#include <qstylesheet.h> +#include <kplugininfo.h> +#include <knotification.h> +#include <kglobal.h> +#include <kwin.h> + +#include "kopeteprefs.h" +#include "kopeteaccount.h" +#include "kopetepluginmanager.h" +#include "kopeteviewplugin.h" +#include "kopetechatsessionmanager.h" +#include "kopetemetacontact.h" +#include "kopetenotifyevent.h" +#include "kopetemessageevent.h" +#include "kopeteview.h" +//#include "systemtray.h" + +#include "kopeteviewmanager.h" + +typedef QMap<Kopete::ChatSession*,KopeteView*> ManagerMap; +typedef QPtrList<Kopete::MessageEvent> EventList; + +struct KopeteViewManagerPrivate +{ + ManagerMap managerMap; + EventList eventList; + KopeteView *activeView; + + bool useQueueOrStack; + bool raiseWindow; + bool queueUnreadMessages; + bool queueOnlyHighlightedMessagesInGroupChats; + bool queueOnlyMessagesOnAnotherDesktop; + bool balloonNotifyIgnoreClosesChatView; + bool foreignMessage; +}; + +KopeteViewManager *KopeteViewManager::s_viewManager = 0L; + +KopeteViewManager *KopeteViewManager::viewManager() +{ + if( !s_viewManager ) + s_viewManager = new KopeteViewManager(); + return s_viewManager; +} + +KopeteViewManager::KopeteViewManager() +{ + s_viewManager=this; + d = new KopeteViewManagerPrivate; + d->activeView = 0L; + d->foreignMessage=false; + + connect( KopetePrefs::prefs(), SIGNAL( saved() ), this, SLOT( slotPrefsChanged() ) ); + + connect( Kopete::ChatSessionManager::self() , SIGNAL( display( Kopete::Message &, Kopete::ChatSession *) ), + this, SLOT ( messageAppended( Kopete::Message &, Kopete::ChatSession *) ) ); + + connect( Kopete::ChatSessionManager::self() , SIGNAL( readMessage() ), + this, SLOT ( nextEvent() ) ); + + slotPrefsChanged(); +} + +KopeteViewManager::~KopeteViewManager() +{ +// kdDebug(14000) << k_funcinfo << endl; + + //delete all open chatwindow. + ManagerMap::Iterator it; + for ( it = d->managerMap.begin(); it != d->managerMap.end(); ++it ) + it.data()->closeView( true ); //this does not clean the map, but we don't care + + delete d; +} + +void KopeteViewManager::slotPrefsChanged() +{ + d->useQueueOrStack = KopetePrefs::prefs()->useQueue() || KopetePrefs::prefs()->useStack(); + d->raiseWindow = KopetePrefs::prefs()->raiseMsgWindow(); + d->queueUnreadMessages = KopetePrefs::prefs()->queueUnreadMessages(); + d->queueOnlyHighlightedMessagesInGroupChats = KopetePrefs::prefs()->queueOnlyHighlightedMessagesInGroupChats(); + d->queueOnlyMessagesOnAnotherDesktop = KopetePrefs::prefs()->queueOnlyMessagesOnAnotherDesktop(); + d->balloonNotifyIgnoreClosesChatView = KopetePrefs::prefs()->balloonNotifyIgnoreClosesChatView(); +} + +KopeteView *KopeteViewManager::view( Kopete::ChatSession* session, const QString &requestedPlugin ) +{ +// kdDebug(14000) << k_funcinfo << endl; + + if( d->managerMap.contains( session ) && d->managerMap[ session ] ) + { + return d->managerMap[ session ]; + } + else + { + Kopete::PluginManager *pluginManager = Kopete::PluginManager::self(); + Kopete::ViewPlugin *viewPlugin = 0L; + + QString pluginName = requestedPlugin.isEmpty() ? KopetePrefs::prefs()->interfacePreference() : requestedPlugin; + if( !pluginName.isEmpty() ) + { + viewPlugin = (Kopete::ViewPlugin*)pluginManager->loadPlugin( pluginName ); + + if( !viewPlugin ) + { + kdWarning(14000) << "Requested view plugin, " << pluginName << + ", was not found. Falling back to chat window plugin" << endl; + } + } + + if( !viewPlugin ) + viewPlugin = (Kopete::ViewPlugin*)pluginManager->loadPlugin( QString::fromLatin1("kopete_chatwindow") ); + + if( viewPlugin ) + { + KopeteView *newView = viewPlugin->createView(session); + + d->foreignMessage = false; + d->managerMap.insert( session, newView ); + + connect( session, SIGNAL( closing(Kopete::ChatSession *) ), + this, SLOT(slotChatSessionDestroyed(Kopete::ChatSession*)) ); + + return newView; + } + else + { + kdError(14000) << "Could not create a view, no plugins available!" << endl; + return 0L; + } + } +} + + +void KopeteViewManager::messageAppended( Kopete::Message &msg, Kopete::ChatSession *manager) +{ +// kdDebug(14000) << k_funcinfo << endl; + + bool outgoingMessage = ( msg.direction() == Kopete::Message::Outbound ); + + if( !outgoingMessage || d->managerMap.contains( manager ) ) + { + d->foreignMessage=!outgoingMessage; //let know for the view we are about to create + manager->view(true,msg.requestedPlugin())->appendMessage( msg ); + d->foreignMessage=false; //the view is created, reset the flag + + bool appendMessageEvent = d->useQueueOrStack; + + QWidget *w; + if( d->queueUnreadMessages && ( w = dynamic_cast<QWidget*>(view( manager )) ) ) + { + // append msg event to queue if chat window is active but not the chat view in it... + appendMessageEvent = appendMessageEvent && !(w->isActiveWindow() && manager->view() == d->activeView); + // ...and chat window is on another desktop + appendMessageEvent = appendMessageEvent && (!d->queueOnlyMessagesOnAnotherDesktop || !KWin::windowInfo( w->topLevelWidget()->winId(), NET::WMDesktop ).isOnCurrentDesktop()); + } + else + { + // append if no chat window exists already + appendMessageEvent = appendMessageEvent && !view( manager )->isVisible(); + } + + // in group chats always append highlighted messages to queue + appendMessageEvent = appendMessageEvent && (!d->queueOnlyHighlightedMessagesInGroupChats || manager->members().count() == 1 || msg.importance() == Kopete::Message::Highlight); + + if( appendMessageEvent ) + { + if ( !outgoingMessage ) + { + Kopete::MessageEvent *event=new Kopete::MessageEvent(msg,manager); + d->eventList.append( event ); + connect(event, SIGNAL(done(Kopete::MessageEvent *)), this, SLOT(slotEventDeleted(Kopete::MessageEvent *))); + Kopete::ChatSessionManager::self()->postNewEvent(event); + } + } + else if( d->eventList.isEmpty() ) + { + readMessages( manager, outgoingMessage ); + } + + if ( !outgoingMessage && ( !manager->account()->isAway() || KopetePrefs::prefs()->soundIfAway() ) + && msg.direction() != Kopete::Message::Internal ) + { + QWidget *w=dynamic_cast<QWidget*>(manager->view(false)); + KConfig *config = KGlobal::config(); + config->setGroup("General"); + if( (!manager->view(false) || !w || manager->view() != d->activeView || + config->readBoolEntry("EventIfActive", true) || !w->isActiveWindow()) + && msg.from()) + { + QString msgFrom = QString::null; + if( msg.from()->metaContact() ) + msgFrom = msg.from()->metaContact()->displayName(); + else + msgFrom = msg.from()->contactId(); + + QString msgText = msg.plainBody(); + if( msgText.length() > 90 ) + msgText = msgText.left(88) + QString::fromLatin1("..."); + + QString event; + QString body =i18n( "<qt>Incoming message from %1<br>\"%2\"</qt>" ); + + switch( msg.importance() ) + { + case Kopete::Message::Low: + event = QString::fromLatin1( "kopete_contact_lowpriority" ); + break; + case Kopete::Message::Highlight: + event = QString::fromLatin1( "kopete_contact_highlight" ); + body = i18n( "<qt>A highlighted message arrived from %1<br>\"%2\"</qt>" ); + break; + default: + event = QString::fromLatin1( "kopete_contact_incoming" ); + } + KNotification *notify=KNotification::event(msg.from()->metaContact() , event, body.arg( QStyleSheet::escape(msgFrom), QStyleSheet::escape(msgText) ), 0, /*msg.from()->metaContact(),*/ + w , i18n("View") ); + + connect(notify,SIGNAL(activated(unsigned int )), manager , SLOT(raiseView()) ); + } + } + } +} + +void KopeteViewManager::readMessages( Kopete::ChatSession *manager, bool outgoingMessage, bool activate ) +{ +// kdDebug( 14000 ) << k_funcinfo << endl; + d->foreignMessage=!outgoingMessage; //let know for the view we are about to create + KopeteView *thisView = manager->view( true ); + d->foreignMessage=false; //the view is created, reset the flag + if( ( outgoingMessage && !thisView->isVisible() ) || d->raiseWindow || activate ) + thisView->raise( activate ); + else if( !thisView->isVisible() ) + thisView->makeVisible(); + + QPtrListIterator<Kopete::MessageEvent> it( d->eventList ); + Kopete::MessageEvent* event; + while ( ( event = it.current() ) != 0 ) + { + ++it; + if ( event->message().manager() == manager ) + { + event->apply(); + d->eventList.remove( event ); + } + } +} + +void KopeteViewManager::slotEventDeleted( Kopete::MessageEvent *event ) +{ +// kdDebug(14000) << k_funcinfo << endl; + Kopete::ChatSession *kmm=event->message().manager(); + if(!kmm) + return; + + d->eventList.remove( event ); + + if ( event->state() == Kopete::MessageEvent::Applied ) + { + readMessages( kmm, false, true ); + } + else if ( event->state() == Kopete::MessageEvent::Ignored && d->balloonNotifyIgnoreClosesChatView ) + { + bool bAnotherWithThisManager = false; + for( QPtrListIterator<Kopete::MessageEvent> it( d->eventList ); it; ++it ) + { + Kopete::MessageEvent *event = it.current(); + if ( event->message().manager() == kmm ) + bAnotherWithThisManager = true; + } + if ( !bAnotherWithThisManager && kmm->view( false ) ) + kmm->view()->closeView( true ); + } +} + +void KopeteViewManager::nextEvent() +{ +// kdDebug( 14000 ) << k_funcinfo << endl; + + if( d->eventList.isEmpty() ) + return; + + Kopete::MessageEvent* event = d->eventList.first(); + + if ( event ) + event->apply(); +} + +void KopeteViewManager::slotViewActivated( KopeteView *view ) +{ +// kdDebug( 14000 ) << k_funcinfo << endl; + d->activeView = view; + + QPtrListIterator<Kopete::MessageEvent> it ( d->eventList ); + Kopete::MessageEvent* event; + while ( ( event = it.current() ) != 0 ) + { + ++it; + if ( event->message().manager() == view->msgManager() ) + event->deleteLater(); + } + +} + +void KopeteViewManager::slotViewDestroyed( KopeteView *closingView ) +{ +// kdDebug( 14000 ) << k_funcinfo << endl; + + if( d->managerMap.contains( closingView->msgManager() ) ) + { + d->managerMap.remove( closingView->msgManager() ); +// closingView->msgManager()->setCanBeDeleted( true ); + } + + if( closingView == d->activeView ) + d->activeView = 0L; +} + +void KopeteViewManager::slotChatSessionDestroyed( Kopete::ChatSession *manager ) +{ +// kdDebug( 14000 ) << k_funcinfo << endl; + + if( d->managerMap.contains( manager ) ) + { + KopeteView *v=d->managerMap[ manager ]; + v->closeView( true ); + delete v; //closeView call deleteLater, but in this case this is not enough, because some signal are called that case crash + d->managerMap.remove( manager ); + } +} + +KopeteView* KopeteViewManager::activeView() const +{ + return d->activeView; +} + + +#include "kopeteviewmanager.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/private/kopeteviewmanager.h b/kopete/libkopete/private/kopeteviewmanager.h new file mode 100644 index 00000000..b1706906 --- /dev/null +++ b/kopete/libkopete/private/kopeteviewmanager.h @@ -0,0 +1,103 @@ +/* + kopeteviewmanager.h - View Manager + + Copyright (c) 2003 by Jason Keirstead + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEVIEWMANAGER_H +#define KOPETEVIEWMANAGER_H + +#include "kopetemessage.h" +#include "kopete_export.h" + +namespace Kopete +{ + class ChatSession; + class Protocol; + class Contact; + class MessageEvent; +} + +class KopeteView; +class QTextEdit; + +struct KopeteViewManagerPrivate; + +/** + * Relates an actual chat to the means used to view it. + */ +class KOPETE_EXPORT KopeteViewManager : public QObject +{ + Q_OBJECT + public: + /** This is a singleton class. Call this method to get a pointer to + * a KopeteViewManager. + */ + static KopeteViewManager *viewManager(); + + KopeteViewManager(); + ~KopeteViewManager(); + + /** + * Return a view for the supplied Kopete::ChatSession. If one already + * exists, it will be returned, otherwise, a new view is created. + * @param session The Kopete::ChatSession we are viewing. + * @param requestedPlugin Specifies the view plugin to use. + */ + KopeteView *view( Kopete::ChatSession *session, const QString &requestedPlugin = QString::null ); + + /** + * Provide access to the list of KopeteChatWindow the class maintains. + */ + KopeteView *activeView() const; + + private: + + + KopeteViewManagerPrivate *d; + static KopeteViewManager *s_viewManager; + + public slots: + /** + * Make a view visible and on top. + * @param manager The originating Kopete::ChatSession. + * @param outgoingMessage Whether the message is inbound or outbound. + * @param activate Indicate whether the view should be activated + * @todo Document @p activate + */ + void readMessages( Kopete::ChatSession* manager, bool outgoingMessage, bool activate = false ); + + /** + * Called when a new message has been appended to the given + * Kopete::ChatSession. Procures a view for the message, and generates any notification events or displays messages, as appropriate. + * @param msg The new message + * @param manager The originating Kopete::ChatSession + */ + void messageAppended( Kopete::Message &msg, Kopete::ChatSession *manager); + + void nextEvent(); + + private slots: + void slotViewDestroyed( KopeteView *); + void slotChatSessionDestroyed( Kopete::ChatSession * ); + + /** + * An event has been deleted. + */ + void slotEventDeleted( Kopete::MessageEvent * ); + + void slotPrefsChanged(); + void slotViewActivated( KopeteView * ); +}; + +#endif diff --git a/kopete/libkopete/tests/Makefile.am b/kopete/libkopete/tests/Makefile.am new file mode 100644 index 00000000..417ce2a8 --- /dev/null +++ b/kopete/libkopete/tests/Makefile.am @@ -0,0 +1,41 @@ +SUBDIRS = mock . +AM_CPPFLAGS = -DKDE_NO_COMPAT -DQT_NO_ASCII_CAST -DQT_NO_COMPAT \ + $(KOPETE_INCLUDES) -I$(top_srcdir)/kopete/libkopete/private -I$(top_srcdir)/kopete/libkopete/private -I$(top_srcdir)/kopete/libkopete/tests/mock $(all_includes) -DSRCDIR=\"$(top_srcdir)/kopete/libkopete/tests\" +METASOURCES = AUTO + +check_LTLIBRARIES = kunittest_kopetemessage_test.la kunittest_kopetepropertiestest.la kunittest_kopetecontactlist_test.la +noinst_LTLIBRARIES = kunittest_kopeteemoticontest.la + +check_PROGRAMS = kopetewallettest_program kopetepasswordtest_program + +kunittest_kopetepropertiestest_la_SOURCES = kopetepropertiestest.cpp ../kopeteproperties.cpp +kunittest_kopetepropertiestest_la_LIBADD = -lkunittest ../libkopete.la +kunittest_kopetepropertiestest_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) + +kunittest_kopeteemoticontest_la_SOURCES = kopeteemoticontest.cpp +kunittest_kopeteemoticontest_la_LIBADD = -lkunittest ../libkopete.la +kunittest_kopeteemoticontest_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) + +kunittest_kopetemessage_test_la_SOURCES = kopetemessage_test.cpp +kunittest_kopetemessage_test_la_LIBADD = -lkunittest mock/libkopete_mock.la +kunittest_kopetemessage_test_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) + +kopetewallettest_program_SOURCES = kopetewallettest_program.cpp +kopetewallettest_program_LDFLAGS = -no-undefined $(all_libraries) $(KDE_RPATH) +kopetewallettest_program_LDADD = ../libkopete.la + +kopetepasswordtest_program_SOURCES = kopetepasswordtest_program.cpp +kopetepasswordtest_program_LDFLAGS = -no-undefined $(all_libraries) $(KDE_RPATH) +kopetepasswordtest_program_LDADD = ../libkopete.la + +kunittest_kopetecontactlist_test_la_SOURCES = kopetecontactlist_test.cpp +kunittest_kopetecontactlist_test_la_LIBADD = -lkunittest mock/libkopete_mock.la +kunittest_kopetecontactlist_test_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) + +noinst_HEADERS = kopetepropertiestest.h kopeteemoticontest.h + +check-local: + kunittestmodrunner +guicheck: + kunittestmod $(PWD) + diff --git a/kopete/libkopete/tests/README b/kopete/libkopete/tests/README new file mode 100644 index 00000000..ead9fbdc --- /dev/null +++ b/kopete/libkopete/tests/README @@ -0,0 +1,47 @@ +LibKopete Unit Tests +==================== + +KopeteSuite: +-------------- +Emoticon Test +Link Test +Property Test + +Test Programs: +-------------- +Password Test Program +Wallet Test Program + + +HOWTO Run +========= + +You can use the console or the GUI version: + + $ make guicheck + $ make check + +The 'silent' switch in make is useful to reduce output: + + $ make check -s + + +Tricks +====== + +Accessing private data?, you should not. We will kill you. +If it is really required, do something like: + + #define private public + #include "kopetemessage.h" + #undef private + +Add a new test quickly: + + $ ./create_test.rb Kopete::ContactList + Creating test for class Kopete::ContactList + kopetecontactlist_test.h and kopetecontactlist_test.cpp writen. + Please add the following to Makefile.am: + kunittest_kopetecontactlist_test_la_SOURCES = kopetecontactlist_test.cpp + kunittest_kopetecontactlist_test_la_LIBADD = -lkunittest ../mock/libkopete_mock.la + kunittest_kopetecontactlist_test_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) diff --git a/kopete/libkopete/tests/create_test.rb b/kopete/libkopete/tests/create_test.rb new file mode 100755 index 00000000..7951bf35 --- /dev/null +++ b/kopete/libkopete/tests/create_test.rb @@ -0,0 +1,56 @@ +#!/usr/bin/ruby +# +# Copyright (c) 2005 by Duncan Mac-Vicar <duncan@kde.org> +# +# Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> +# +# ************************************************************************* +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU General Public License as published by * +# * the Free Software Foundation; either version 2 of the License, or * +# * (at your option) any later version. * +# * * +# ************************************************************************* + +className = ARGV[0] + +if className.nil? + puts "Need a class name" + exit +end + +puts "Creating test for class #{className}" + +hBase = "template_test.h" +cppBase = "template_test.cpp" + +fileH = File.new(hBase).read +fileCpp = File.new(cppBase).read + +fileH.gsub!(/TEMPLATE/, className.upcase.gsub(/::/,"")) +fileH.gsub!(/Template/, className.gsub(/::/,"")) +fileH.gsub!(/some requirement/, className + " class.") + +fileCpp.gsub!(/TEMPLATE/, className.upcase.gsub(/::/,"")) +fileCpp.gsub!(/template/, className.downcase.gsub(/::/,"")) +fileCpp.gsub!(/Template/, className.gsub(/::/,"")) +fileCpp.gsub!(/some requirement/, className + " class.") + +makefileAm = "kunittest_template_test_la_SOURCES = template_test.cpp\nkunittest_template_test_la_LIBADD = -lkunittest ../mock/libkopete_mock.la\nkunittest_template_test_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries)\n" +makefileAm.gsub!(/template/, className.downcase.gsub(/::/,"")) + +hNew = hBase.gsub(/template/, className.downcase.gsub(/::/,"")) +cppNew = cppBase.gsub(/template/, className.downcase.gsub(/::/,"")) + +hOut = File.new(hNew, "w") +cppOut = File.new(cppNew, "w") + +hOut.write(fileH) +cppOut.write(fileCpp) + +puts "#{hNew} and #{cppNew} writen." + +puts "Please add the following to Makefile.am:" +puts makefileAm + diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-1.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-1.input new file mode 100644 index 00000000..795d3c7b --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-1.input @@ -0,0 +1 @@ +:))
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-1.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-1.output new file mode 100644 index 00000000..795d3c7b --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-1.output @@ -0,0 +1 @@ +:))
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-10.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-10.input new file mode 100644 index 00000000..6ddd0c7f --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-10.input @@ -0,0 +1 @@ +:Ptesting:P
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-10.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-10.output new file mode 100644 index 00000000..6ddd0c7f --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-10.output @@ -0,0 +1 @@ +:Ptesting:P
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-2.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-2.input new file mode 100644 index 00000000..2571b163 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-2.input @@ -0,0 +1 @@ +In a sentence:practical example
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-2.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-2.output new file mode 100644 index 00000000..2571b163 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-2.output @@ -0,0 +1 @@ +In a sentence:practical example
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-3.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-3.input new file mode 100644 index 00000000..2319ced9 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-3.input @@ -0,0 +1 @@ +Bla ( )
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-3.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-3.output new file mode 100644 index 00000000..2319ced9 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-3.output @@ -0,0 +1 @@ +Bla ( )
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-4.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-4.input new file mode 100644 index 00000000..f5d88878 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-4.input @@ -0,0 +1 @@ +:D and :-D are not the same as :d and :-d
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-4.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-4.output new file mode 100644 index 00000000..0d94eb97 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-4.output @@ -0,0 +1 @@ +<img align="center" width="20" height="20" src="teeth.png" title=":D"/> and <img align="center" width="20" height="20" src="teeth.png" title=":-D"/> are not the same as :d and :-d
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-5.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-5.input new file mode 100644 index 00000000..5b39691b --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-5.input @@ -0,0 +1 @@ +4d:D>:)F:/>:-(:Pu:d9
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-5.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-5.output new file mode 100644 index 00000000..5b39691b --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-5.output @@ -0,0 +1 @@ +4d:D>:)F:/>:-(:Pu:d9
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-6.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-6.input new file mode 100644 index 00000000..379e01a1 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-6.input @@ -0,0 +1 @@ +<::pvar:: test=1>
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-6.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-6.output new file mode 100644 index 00000000..379e01a1 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-6.output @@ -0,0 +1 @@ +<::pvar:: test=1>
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-7.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-7.input new file mode 100644 index 00000000..d6e7e6c0 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-7.input @@ -0,0 +1 @@ +a non-breaking space ( ) character
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-7.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-7.output new file mode 100644 index 00000000..d6e7e6c0 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-7.output @@ -0,0 +1 @@ +a non-breaking space ( ) character
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-8.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-8.input new file mode 100644 index 00000000..a3734027 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-8.input @@ -0,0 +1 @@ +-+-[-:-(-:-)-:-]-+-
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-8.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-8.output new file mode 100644 index 00000000..a3734027 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-8.output @@ -0,0 +1 @@ +-+-[-:-(-:-)-:-]-+-
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-9.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-9.input new file mode 100644 index 00000000..538c5b0b --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-9.input @@ -0,0 +1 @@ +::shrugs::
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-9.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-9.output new file mode 100644 index 00000000..538c5b0b --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-9.output @@ -0,0 +1 @@ +::shrugs::
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-1.input b/kopete/libkopete/tests/emoticon-parser-testcases/working-1.input new file mode 100644 index 00000000..a5440d64 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-1.input @@ -0,0 +1 @@ +:):)
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-1.output b/kopete/libkopete/tests/emoticon-parser-testcases/working-1.output new file mode 100644 index 00000000..a7c018d4 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-1.output @@ -0,0 +1 @@ +<img align="center" width="20" height="20" src="smile.png" title=":)"/><img align="center" width="20" height="20" src="smile.png" title=":)"/>
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-2.input b/kopete/libkopete/tests/emoticon-parser-testcases/working-2.input new file mode 100644 index 00000000..223ce5be --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-2.input @@ -0,0 +1 @@ +<img src="..." title=":-)" />
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-2.output b/kopete/libkopete/tests/emoticon-parser-testcases/working-2.output new file mode 100644 index 00000000..223ce5be --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-2.output @@ -0,0 +1 @@ +<img src="..." title=":-)" />
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-3.input b/kopete/libkopete/tests/emoticon-parser-testcases/working-3.input new file mode 100644 index 00000000..d685c091 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-3.input @@ -0,0 +1 @@ +End of sentence:p
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-3.output b/kopete/libkopete/tests/emoticon-parser-testcases/working-3.output new file mode 100644 index 00000000..013515be --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-3.output @@ -0,0 +1 @@ +End of sentence<img align="center" width="20" height="20" src="tongue.png" title=":p"/>
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-4.input b/kopete/libkopete/tests/emoticon-parser-testcases/working-4.input new file mode 100644 index 00000000..093690c4 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-4.input @@ -0,0 +1 @@ +http://www.kde.org
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-4.output b/kopete/libkopete/tests/emoticon-parser-testcases/working-4.output new file mode 100644 index 00000000..093690c4 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-4.output @@ -0,0 +1 @@ +http://www.kde.org
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-5.input b/kopete/libkopete/tests/emoticon-parser-testcases/working-5.input new file mode 100644 index 00000000..1e3caf28 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-5.input @@ -0,0 +1 @@ +>:-)
\ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-5.output b/kopete/libkopete/tests/emoticon-parser-testcases/working-5.output new file mode 100644 index 00000000..3b1d4c31 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-5.output @@ -0,0 +1 @@ +<img align="center" width="20" height="20" src="devil.png" title=">:-)"/>
\ No newline at end of file diff --git a/kopete/libkopete/tests/kopetecontactlist_test.cpp b/kopete/libkopete/tests/kopetecontactlist_test.cpp new file mode 100644 index 00000000..001f3f0d --- /dev/null +++ b/kopete/libkopete/tests/kopetecontactlist_test.cpp @@ -0,0 +1,55 @@ +/* + Tests for Kopete::ContactList class. + + Copyright (c) 2005 by Duncan Mac-Vicar <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <qfile.h> +#include <qdir.h> +#include <kstandarddirs.h> +#include <kunittest/module.h> +#include "kopetecontactlist_test.h" + +using namespace KUnitTest; + +KUNITTEST_MODULE( kunittest_kopetecontactlist_test, "KopeteSuite"); +KUNITTEST_MODULE_REGISTER_TESTER( KopeteContactList_Test ); + +void KopeteContactList_Test::allTests() +{ + testSomething(); +} + +void KopeteContactList_Test::testSomething() +{ + // change user data dir to avoid messing with user's .kde dir + setenv( "KDEHOME", QFile::encodeName( QDir::homeDirPath() + "/.kopete-unittest" ), true ); + + QString filename = locateLocal( "appdata", QString::fromLatin1( "contactlist.xml" ) ); + if( ! filename.isEmpty() ) + { + // previous test run, delete the previous contact list + bool removed = QFile::remove(filename); + // if we cant remove the file, abort test + if (!removed) + return; + } + + int result = 1; + int expected = 1; + // result should be the expected one + CHECK(result, expected); +} + + diff --git a/kopete/libkopete/tests/kopetecontactlist_test.h b/kopete/libkopete/tests/kopetecontactlist_test.h new file mode 100644 index 00000000..faab1e48 --- /dev/null +++ b/kopete/libkopete/tests/kopetecontactlist_test.h @@ -0,0 +1,35 @@ +/* + Tests for Kopete::ContactList class. + + Copyright (c) 2005 by Duncan Mac-Vicar <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETECONTACTLIST_TEST_H +#define KOPETECONTACTLIST_TEST_H + +#include <kunittest/tester.h> + +// change to SlotTester when it works +class KopeteContactList_Test : public KUnitTest::Tester +{ +public: + void allTests(); +public slots: + void testSomething(); +private: + +}; + +#endif + diff --git a/kopete/libkopete/tests/kopeteemoticontest.cpp b/kopete/libkopete/tests/kopeteemoticontest.cpp new file mode 100644 index 00000000..e9a81c1d --- /dev/null +++ b/kopete/libkopete/tests/kopeteemoticontest.cpp @@ -0,0 +1,132 @@ +/* + Tests for Kopete::Message::parseEmoticons + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Copyright (c) 2005 by Duncan Mac-Vicar <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <stdlib.h> + +#include <qstring.h> +#include <qdir.h> +#include <qfile.h> + +#include <kapplication.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <kdebug.h> + +#include <kunittest/module.h> +#include "kopeteemoticontest.h" +#include "kopetemessage.h" +#include "kopeteemoticons.h" + +using namespace KUnitTest; + +KUNITTEST_MODULE( kunittest_kopeteemoticontest, "KopeteSuite"); +KUNITTEST_MODULE_REGISTER_TESTER( KopeteEmoticonTest ); + +/* + There are three sets of tests, the Kopete 0.7 baseline with tests that were + working properly in Kopete 0.7.x. When these fail it's a real regression. + + The second set are those known to work in the current codebase. + The last set is the set with tests that are known to fail right now. + + the name convention is working|broken-number.input|output +*/ + + +void KopeteEmoticonTest::allTests() +{ + // change user data dir to avoid messing with user's .kde dir + setenv( "KDEHOME", QFile::encodeName( QDir::homeDirPath() + "/.kopete-unittest" ), true ); + + //KApplication::disableAutoDcopRegistration(); + //KApplication app; + + testEmoticonParser(); +} + +void KopeteEmoticonTest::testEmoticonParser() +{ + Kopete::Emoticons emo("Default"); + QString basePath = QString::fromLatin1( SRCDIR ) + QString::fromLatin1("/emoticon-parser-testcases"); + QDir testCasesDir(basePath); + + QStringList inputFileNames = testCasesDir.entryList("*.input"); + for ( QStringList::ConstIterator it = inputFileNames.begin(); it != inputFileNames.end(); ++it) + { + QString fileName = *it; + kdDebug() << "testcase: " << fileName << endl; + QString outputFileName = fileName; + outputFileName.replace("input","output"); + // open the input file + QFile inputFile(basePath + QString::fromLatin1("/") + fileName); + QFile expectedFile(basePath + QString::fromLatin1("/") + outputFileName); + // check if the expected output file exists + // if it doesn't, skip the testcase + if ( ! expectedFile.exists() ) + { + SKIP("Warning! expected output for testcase "+ *it + " not found. Skiping testcase"); + continue; + } + if ( inputFile.open( IO_ReadOnly ) && expectedFile.open( IO_ReadOnly )) + { + QTextStream inputStream(&inputFile); + QTextStream expectedStream(&expectedFile); + QString inputData; + QString expectedData; + inputData = inputStream.read(); + expectedData = expectedStream.read(); + + inputFile.close(); + expectedFile.close(); + + QString path = KGlobal::dirs()->findResource( "emoticons", "Default/smile.png" ).replace( "smile.png", QString::null ); + + Kopete::Emoticons::self(); + QString result = emo.parse( inputData ).replace( path, QString::null ); + + // HACK to know the test case we applied, concatenate testcase name to both + // input and expected string. WIll remove when I can add some sort of metadata + // to a CHECK so debug its origin testcase + result = fileName + QString::fromLatin1(": ") + result; + expectedData = fileName + QString::fromLatin1(": ") + expectedData; + // if the test case begins with broken, we expect it to fail, then use XFAIL + // otherwise use CHECK + if ( fileName.section("-", 0, 0) == QString::fromLatin1("broken") ) + { + kdDebug() << "checking known-broken testcase: " << fileName << endl; + XFAIL(result, expectedData); + } + else + { + kdDebug() << "checking known-working testcase: " << fileName << endl; + CHECK(result, expectedData); + } + } + else + { + SKIP("Warning! can't open testcase files for "+ *it + ". Skiping testcase"); + continue; + } + } + +} + + + + +
\ No newline at end of file diff --git a/kopete/libkopete/tests/kopeteemoticontest.h b/kopete/libkopete/tests/kopeteemoticontest.h new file mode 100644 index 00000000..a885b2c4 --- /dev/null +++ b/kopete/libkopete/tests/kopeteemoticontest.h @@ -0,0 +1,39 @@ +/* + Tests for Kopete::Message::parseEmoticons + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Copyright (c) 2005 by Duncan Mac-Vicar <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_EMOTICON_TEST_H +#define KOPETE_EMOTICON_TEST_H + +#include <kunittest/tester.h> + +// change to SlotTester when it works +class KopeteEmoticonTest : public KUnitTest::Tester +{ +public: + //KopeteLinkTest(); + //~KopeteLinkTest(); + void allTests(); +public slots: + void testEmoticonParser(); +private: + +}; + +#endif + + diff --git a/kopete/libkopete/tests/kopetemessage.xsd b/kopete/libkopete/tests/kopetemessage.xsd new file mode 100644 index 00000000..69f99d20 --- /dev/null +++ b/kopete/libkopete/tests/kopetemessage.xsd @@ -0,0 +1,180 @@ +<?xml version="1.0"?> +<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + + <xsd:annotation> + <xsd:documentation xml:lang="en"> + <![CDATA[ + This is the XSD schema of a Kopete message in XML form. This is both the + format that the XSL stylesheets will expect, as well as the format that + results from saving the chatwindow contents. This is *not* the same as + the format of the history plugin. + + The XML format has one other little quirk - you can pass flags into the + engine as XML processing instructions. For example, if you add this + instruction to your document: + + <?Kopete Flag:TransformAllMessages> + + ... it will instruct the Kopete XSL engine that you want the entire contents + of the chat window to be re-drawn each time a new message is appended. This + is not the normal procedure, and is only required for special situations + (see the Adium style for an example). + + TransformAllMessages is the only flag currently defined. + ]]> + </xsd:documentation> + </xsd:annotation> + + <!-- This is defined if we save a chat with multiple messages --> + <xsd:element name="document"> + <xsd:complexType> + <xsd:sequence> + <xsd:element ref="message" maxOccurs="unbounded"/> + </xsd:sequence> + </xsd:complexType> + </xsd:element> + + <!-- The main message element --> + <xsd:element name="message"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="from" type="metaContact" minOccurs="0" maxOccurs="1"/> + <xsd:element name="to" type="metaContact" minOccurs="0" maxOccurs="1"/> + <xsd:element name="body" type="messageBody" minOccurs="1" maxOccurs="1"/> + </xsd:sequence> + + <!-- The time only. eg 12:00 pm --> + <xsd:attribute name="time" type="xsd:string" use="required"/> + + <!-- Full timestamp. eg Tue Feb 8 19:04:49 AST 2005 --> + <xsd:attribute name="timestamp" type="xsd:string" use="required"/> + + <!-- Formatted timestamp. eg 12:00:57 pm --> + <xsd:attribute name="formattedTimestamp" type="xsd:string" use="required"/> + + <!-- Message subject. Used by Jabber Email. --> + <xsd:attribute name="subject" type="xsd:string" use="required"/> + + <!-- Message direction (Inbound, Outbound, Internal). + This is deprecated. Use @type and @route --> + <xsd:attribute name="direction" type="direction" use="required"/> + + <!-- Message route (inbound, outbound, internal).--> + <xsd:attribute name="route" type="route" use="required"/> + + <!-- Message type (normal, action).--> + <xsd:attribute name="type" type="type" use="required"/> + + <!-- Message importance.--> + <xsd:attribute name="importance" type="importance" use="required"/> + + <!-- This is the main contact Id - the other person in the + converation besides you. If it is a group chat, it is the first + person who was being spoken to, or the group chat name. --> + <xsd:attribute name="mainContactId" type="xsd:string" use="optional"/> + </xsd:complexType> + </xsd:element> + + <!-- Enumeration for message direction + (this is deprecated - use the route/type) --> + <xsd:simpleType name="direction"> + <xsd:restriction base="xsd:integer"> + <xsd:enumeration value="0"/> <!-- Inbound --> + <xsd:enumeration value="1"/> <!-- Outbound --> + <xsd:enumeration value="2"/> <!-- Internal --> + <xsd:enumeration value="3"/> <!-- Action --> + </xsd:restriction> + </xsd:simpleType> + + <!-- Enumeration for message route --> + <xsd:simpleType name="route"> + <xsd:restriction base="xsd:string"> + <xsd:enumeration value="inbound"/> + <xsd:enumeration value="outbound"/> + <xsd:enumeration value="internal"/> + </xsd:restriction> + </xsd:simpleType> + + <!-- Enumeration for message type --> + <xsd:simpleType name="type"> + <xsd:restriction base="xsd:string"> + <xsd:enumeration value="normal"/> + <xsd:enumeration value="action"/> + </xsd:restriction> + </xsd:simpleType> + + <!-- Enumeration for message importance --> + <xsd:simpleType name="importance"> + <xsd:restriction base="xsd:integer"> + <xsd:enumeration value="0"/> <!-- Low --> + <xsd:enumeration value="1"/> <!-- Normal --> + <xsd:enumeration value="2"/> <!-- Highlight --> + </xsd:restriction> + </xsd:simpleType> + + <!-- Enumeration for bidi direction --> + <xsd:simpleType name="bidiDirection"> + <xsd:restriction base="xsd:string"> + <xsd:enumeration value="ltr"/> + <xsd:enumeration value="rtl"/> + </xsd:restriction> + </xsd:simpleType> + + <!-- Element for display names --> + <xsd:complexType name="displayName"> + <!-- The direction of the name, for Bidi suport. --> + <xsd:attribute name="dir" type="bidiDirection"/> + + <!-- The actual name text --> + <xsd:attribute name="text" type="xsd:string"/> + </xsd:complexType> + + <!-- The contact element --> + <xsd:complexType name="metaContact"> + <xsd:sequence> + <xsd:element name="contact"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="contactDisplayName" type="displayName" minOccurs="1" maxOccurs="1"/> + <xsd:element name="metaContactDisplayName" type="displayName" minOccurs="1" maxOccurs="1"/> + </xsd:sequence> + + <!-- The contact's id --> + <xsd:attribute name="contactId" type="xsd:string" use="required"/> + + <!-- The contact's custom color --> + <xsd:attribute name="color" type="xsd:string" use="required"/> + + <!-- The contact's photo. This file name only remains valid + while the message is in transit --> + <xsd:attribute name="userPhoto" type="xsd:string" use="optional"/> + + <!-- The contact's protocol icon --> + <xsd:attribute name="protocolIcon" type="xsd:string" use="required"/> + </xsd:complexType> + </xsd:element> + </xsd:sequence> + </xsd:complexType> + + <!-- The message body element --> + <xsd:complexType name="messageBody"> + <xsd:simpleContent> + <xsd:extension base="xsd:string"> + <!-- The foreground color of the message --> + <xsd:attribute name="color" type="xsd:string" use="optional"/> + + <!-- The background color of the message --> + <xsd:attribute name="bgcolor" type="xsd:string" use="optional"/> + + <!-- The font of the message. This is a CSS string + describing the font-family, font-size, text-decoration, + and font-weight --> + <xsd:attribute name="font" type="xsd:string" use="optional"/> + + <!-- The direction of the message, for Bidi suport. --> + <xsd:attribute name="dir" type="bidiDirection" use="required"/> + </xsd:extension> + </xsd:simpleContent> + </xsd:complexType> + +</xsd:schema>
\ No newline at end of file diff --git a/kopete/libkopete/tests/kopetemessage_test.cpp b/kopete/libkopete/tests/kopetemessage_test.cpp new file mode 100644 index 00000000..1ca57123 --- /dev/null +++ b/kopete/libkopete/tests/kopetemessage_test.cpp @@ -0,0 +1,324 @@ +/* + Tests for Kopete::Message + + Copyright (c) 2005 by Tommi Rantala <tommi.rantala@cs.helsinki.fi> + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <stdlib.h> + +#include <qdir.h> +#include <qfile.h> +#include <kapplication.h> +#include <kstandarddirs.h> +#include <kinstance.h> +#include <kprocess.h> +#include <kunittest/module.h> +#include <kdebug.h> + +#include "kopetemessage_test.h" +#include "kopeteaccount_mock.h" +#include "kopeteprotocol_mock.h" +#include "kopetecontact_mock.h" +#include "kopetemetacontact_mock.h" +#include "kopeteaccount_mock.h" + +using namespace KUnitTest; + +KUNITTEST_MODULE( kunittest_kopetemessage_test, "KopeteSuite"); +KUNITTEST_MODULE_REGISTER_TESTER( KopeteMessage_Test ); + +/* + There are four sets of tests: for each of plain text and html, we have those + known to work in the current codebase, and those known to fail right now. + + the name convention is working|broken-plaintext|html-number.input|output +*/ + +KopeteMessage_Test::KopeteMessage_Test() +{ + // change user data dir to avoid messing with user's .kde dir + setenv( "KDEHOME", QFile::encodeName( QDir::homeDirPath() + "/.kopete-unittest" ), true ); + + // create fake objects needed to build a reasonable testeable message + m_protocol = new Kopete::Test::Mock::Protocol( new KInstance(QCString("test-kopete-message")), 0L, "test-kopete-message"); + m_account = new Kopete::Test::Mock::Account(m_protocol, "testaccount"); + m_metaContactMyself = new Kopete::Test::Mock::MetaContact(); + m_metaContactOther = new Kopete::Test::Mock::MetaContact(); + m_contactFrom = new Kopete::Test::Mock::Contact(m_account, QString::fromLatin1("test-myself"), m_metaContactMyself, QString::null); + m_contactTo = new Kopete::Test::Mock::Contact(m_account, QString::fromLatin1("test-dest"), m_metaContactOther, QString::null); + m_message = new Kopete::Message( m_contactFrom, m_contactTo, QString::null, Kopete::Message::Outbound, Kopete::Message::PlainText); +} + +void KopeteMessage_Test::allTests() +{ + KApplication::disableAutoDcopRegistration(); + //KCmdLineArgs::init(argc,argv,"testkopetemessage", 0, 0, 0, 0); + + // At least Kopete::Message::asXML() seems to require that a QApplication + // is created. Running the console version doesn't create it, but the GUI + // version does. + + if (!kapp) + new KApplication(); + + testPrimitives(); + testLinkParser(); +} + +void KopeteMessage_Test::testPrimitives() +{ + /********************************************** + * from(), to() + *********************************************/ + + { + Kopete::Message msg( m_contactFrom, m_contactTo, "foobar", Kopete::Message::Inbound, Kopete::Message::PlainText); + Q_ASSERT(msg.from()); + Q_ASSERT(!msg.to().isEmpty()); + } + + /********************************************** + * Direction + *********************************************/ + + { + Kopete::Message msg( m_contactFrom, m_contactTo, "foobar", Kopete::Message::Inbound, Kopete::Message::PlainText); + CHECK(Kopete::Message::Inbound, msg.direction()); + } + { + Kopete::Message msg( m_contactFrom, m_contactTo, "foobar", Kopete::Message::Outbound, Kopete::Message::RichText); + CHECK(Kopete::Message::Outbound, msg.direction()); + } + { + Kopete::Message msg( m_contactFrom, m_contactTo, "foobar", Kopete::Message::Internal, Kopete::Message::RichText); + CHECK(Kopete::Message::Internal, msg.direction()); + } + + /********************************************** + * Message Format + *********************************************/ + + { + Kopete::Message msg( m_contactFrom, m_contactTo, "foobar", Kopete::Message::Inbound, Kopete::Message::PlainText); + CHECK(Kopete::Message::PlainText, msg.format()); + } + { + Kopete::Message msg( m_contactFrom, m_contactTo, "foobar", Kopete::Message::Inbound, Kopete::Message::RichText); + CHECK(Kopete::Message::RichText, msg.format()); + } + { + QString m = "foobar"; + Kopete::Message msg( m_contactFrom, m_contactTo, m, Kopete::Message::Inbound, Kopete::Message::RichText); + + msg.setBody(m, Kopete::Message::PlainText); + CHECK(Kopete::Message::PlainText, msg.format()); + + msg.setBody(m, Kopete::Message::RichText); + CHECK(Kopete::Message::RichText, msg.format()); + + msg.setBody(m, Kopete::Message::ParsedHTML); + CHECK(Kopete::Message::ParsedHTML, msg.format()); + + msg.setBody(m, Kopete::Message::Crypted); + CHECK(Kopete::Message::Crypted, msg.format()); + } + + + /********************************************** + * setBody() + *********************************************/ + + { + QString m = "foobar"; + Kopete::Message msg( m_contactFrom, m_contactTo, m, Kopete::Message::Inbound, Kopete::Message::RichText); + + msg.setBody("NEW", Kopete::Message::PlainText); + CHECK(QString("NEW"), msg.plainBody()); + + msg.setBody("NEW_NEW", Kopete::Message::RichText); + CHECK(QString("NEW_NEW"), msg.plainBody()); + } + { + QString m = "foobar"; + Kopete::Message msg( m_contactFrom, m_contactTo, m, Kopete::Message::Inbound, Kopete::Message::PlainText); + + msg.setBody("NEW", Kopete::Message::PlainText); + CHECK(QString("NEW"), msg.plainBody()); + + msg.setBody("NEW_NEW", Kopete::Message::RichText); + CHECK(QString("NEW_NEW"), msg.plainBody()); + } + { + QString m = "<html><head></head><body foo=\"bar\"> <b>HELLO WORLD</b> </body></html>"; + Kopete::Message msg( m_contactFrom, m_contactTo, m, Kopete::Message::Inbound, Kopete::Message::PlainText); + CHECK(m, msg.plainBody()); + + msg.setBody("<simple> SIMPLE", Kopete::Message::PlainText); + CHECK(msg.plainBody(), QString("<simple> SIMPLE") ); + CHECK(msg.escapedBody(), QString("<simple> SIMPLE") ); + + msg.setBody("<simple>SIMPLE</simple>", Kopete::Message::RichText); + CHECK(msg.plainBody(), QString("SIMPLE") ); + CHECK(msg.escapedBody(), QString("<simple>SIMPLE</simple>") ); + + CHECK(Kopete::Message::unescape( QString( "<simple>SIMPLE</simple>" ) ), QString("SIMPLE") ); + CHECK(Kopete::Message::unescape( QString( "Foo <img src=\"foo.png\" />" ) ), QString("Foo ") ); + CHECK(Kopete::Message::unescape( QString( "Foo <img src=\"foo.png\" title=\"Bar\" />" ) ), QString("Foo Bar") ); + + msg.setBody(m, Kopete::Message::RichText); + + // FIXME: Should setBody() also strip extra white space? + //CHECK(msg.plainBody(), QString("HELLO WORLD")); + //CHECK(msg.escapedBody(), QString("<b>HELLO WORLD</b>")); + + CHECK(msg.escapedBody(), QString(" <b>HELLO WORLD</b> ")); + CHECK(msg.plainBody(), QString(" HELLO WORLD ")); + CHECK(msg.plainBody().stripWhiteSpace(), QString("HELLO WORLD")); + CHECK(msg.escapedBody().stripWhiteSpace(), QString(" <b>HELLO WORLD</b> ")); + } + { + Kopete::Message msg( m_contactFrom, m_contactTo, "foo", Kopete::Message::Inbound, Kopete::Message::PlainText); + + msg.setBody("<p>foo", Kopete::Message::RichText); + CHECK(msg.escapedBody(), QString("foo")); + + msg.setBody("<p>foo</p>", Kopete::Message::RichText); + CHECK(msg.escapedBody(), QString("foo")); + + msg.setBody("\n<p>foo</p>\n<br/>", Kopete::Message::RichText); + CHECK(msg.escapedBody(), QString("foo<br/>")); + } + + /********************************************** + * Copy constructor + *********************************************/ + + { + Kopete::Message msg1(m_contactFrom, m_contactTo, "foo", Kopete::Message::Inbound, Kopete::Message::RichText); + Kopete::Message msg2(msg1); + + CHECK(msg1.plainBody(), msg2.plainBody()); + CHECK(msg1.escapedBody(), msg2.escapedBody()); + + msg1.setBody("NEW", Kopete::Message::PlainText); + CHECK(msg1.plainBody(), QString("NEW")); + CHECK(msg2.plainBody(), QString("foo")); + } + + /********************************************** + * operator= + *********************************************/ + + { + Kopete::Message msg1(m_contactFrom, m_contactTo, "foo", Kopete::Message::Inbound, Kopete::Message::RichText); + { + Kopete::Message msg2; + + CHECK(msg2.plainBody(), QString::null); + + msg2 = msg1; + + CHECK(msg1.plainBody(), msg2.plainBody()); + CHECK(msg1.escapedBody(), msg2.escapedBody()); + + msg1.setBody("NEW", Kopete::Message::PlainText); + CHECK(msg1.plainBody(), QString("NEW")); + CHECK(msg2.plainBody(), QString("foo")); + } + CHECK(msg1.plainBody(), QString("NEW")); + + msg1 = msg1; + CHECK(msg1.plainBody(), QString("NEW")); + } +} + +void KopeteMessage_Test::setup() +{ +} + +void KopeteMessage_Test::testLinkParser() +{ + QString basePath = QString::fromLatin1( SRCDIR ) + QString::fromLatin1("/link-parser-testcases"); + QDir testCasesDir(basePath); + + QStringList inputFileNames = testCasesDir.entryList("*.input"); + for ( QStringList::ConstIterator it = inputFileNames.begin(); it != inputFileNames.end(); ++it) + { + QString fileName = *it; + QString outputFileName = fileName; + outputFileName.replace("input","output"); + // open the input file + QFile inputFile(basePath + QString::fromLatin1("/") + fileName); + QFile expectedFile(basePath + QString::fromLatin1("/") + outputFileName); + // check if the expected output file exists + // if it doesn't, skip the testcase + if ( ! expectedFile.exists() ) + { + SKIP("Warning! expected output for testcase "+ *it + " not found. Skiping testcase"); + continue; + } + if ( inputFile.open( IO_ReadOnly ) && expectedFile.open( IO_ReadOnly )) + { + QTextStream inputStream(&inputFile); + QTextStream expectedStream(&expectedFile); + QString inputData; + QString expectedData; + inputData = inputStream.read(); + expectedData = expectedStream.read(); + + inputFile.close(); + expectedFile.close(); + + // use a concrete url + inputData.replace( "$URL","http://www.kde.org" ); + expectedData.replace( "$URL","http://www.kde.org" ); + + // set message format for parsing according to textcase filename convention + Kopete::Message::MessageFormat format; + if ( fileName.section("-", 1, 1) == QString::fromLatin1("plaintext") ) + format = Kopete::Message::PlainText; + else + format = Kopete::Message::RichText; + + QString result = Kopete::Message::parseLinks( inputData, format ); + + // HACK to know the test case we applied, concatenate testcase name to both + // input and expected string. WIll remove when I can add some sort of metadata + // to a CHECK so debug its origin testcase + result = fileName + QString::fromLatin1(": ") + result; + expectedData = fileName + QString::fromLatin1(": ") + expectedData; + // if the test case begins with broken, we expect it to fail, then use XFAIL + // otherwise use CHECK + if ( fileName.section("-", 0, 0) == QString::fromLatin1("broken") ) + { + //kdDebug() << "checking known-broken testcase: " << fileName << endl; + XFAIL(result, expectedData); + } + else + { + //kdDebug() << "checking known-working testcase: " << fileName << endl; + CHECK(result, expectedData); + } + } + else + { + SKIP("Warning! can't open testcase files for "+ *it + ". Skiping testcase"); + continue; + } + } +} + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/tests/kopetemessage_test.h b/kopete/libkopete/tests/kopetemessage_test.h new file mode 100644 index 00000000..52d09fb8 --- /dev/null +++ b/kopete/libkopete/tests/kopetemessage_test.h @@ -0,0 +1,56 @@ +/* + Tests for Kopete::Message + + Copyright (c) 2005 by Duncan Mac-Vicar <duncan@kde.org> + Copyright (c) 2005 by Tommi Rantala <tommi.rantala@cs.helsinki.fi> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEMESSAGE_TEST_H +#define KOPETEMESSAGE_TEST_H + +#include <kunittest/tester.h> + +#define private public +#include "kopetemessage.h" +#undef private + +class Kopete::Protocol; +class Kopete::Account; +class Kopete::MetaContact; +class Kopete::Contact; + +// change to SlotTester when it works +class KopeteMessage_Test : public KUnitTest::Tester +{ +public: + KopeteMessage_Test(); + void allTests(); + +public slots: + void testPrimitives(); + void testLinkParser(); + +private: + void setup(); + Kopete::Message *m_message; + Kopete::Protocol *m_protocol; + Kopete::Account *m_account; + Kopete::MetaContact *m_metaContactMyself; + Kopete::MetaContact *m_metaContactOther; + Kopete::Contact *m_contactFrom; + Kopete::Contact *m_contactTo; +}; + +#endif + diff --git a/kopete/libkopete/tests/kopetepasswordtest_program.cpp b/kopete/libkopete/tests/kopetepasswordtest_program.cpp new file mode 100644 index 00000000..a1f3a50e --- /dev/null +++ b/kopete/libkopete/tests/kopetepasswordtest_program.cpp @@ -0,0 +1,132 @@ +/* + Tests for the Kopete::Password class + + Copyright (c) 2003 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetepasswordtest_program.h" +#include "kopetepassword.h" + +#include <qtextstream.h> +#include <qpixmap.h> +#include <qtimer.h> + +#include <kaboutdata.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <kdebug.h> +#include <kglobal.h> +#include <kstandarddirs.h> + +static QTextStream _out( stdout, IO_WriteOnly ); + +static KCmdLineOptions opts[] = +{ + { "id <id>", I18N_NOOP("Config group to store password in"), "TestAccount" }, + { "set <new>", I18N_NOOP("Set password to new"), 0 }, + { "error", I18N_NOOP("Claim password was erroneous"), 0 }, + { "prompt <prompt>", I18N_NOOP("Password prompt"), "Enter a password" }, + { "image <filename>", I18N_NOOP("Image to display in password dialog"), 0 }, + KCmdLineLastOption +}; + +using namespace Kopete; + +QString retrieve( Password &pwd, const QPixmap &image, const QString &prompt ) +{ + PasswordRetriever r; + pwd.request( &r, SLOT( gotPassword( const QString & ) ), image, prompt ); + QTimer tmr; + r.connect( &tmr, SIGNAL( timeout() ), SLOT( timer() ) ); + tmr.start( 1000 ); + qApp->exec(); + return r.password; +} + +void PasswordRetriever::gotPassword( const QString &pass ) +{ + password = pass; + qApp->quit(); +} + +void PasswordRetriever::timer() +{ + _out << "." << flush; +} + +int main( int argc, char *argv[] ) +{ + KAboutData aboutData( "kopetepasswordtest", "kopetepasswordtest", "version" ); + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineArgs::addCmdLineOptions( opts ); + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + KApplication app( "kopetepasswordtest" ); + + bool setPassword = args->isSet("set"); + QString newPwd = args->getOption("set"); + QString passwordId = args->getOption("id"); + bool error = args->isSet("error"); + QString prompt = args->getOption("prompt"); + QPixmap image = QString(args->getOption("image")); + + _out << (image.isNull() ? "image is null" : "image is valid") << endl; + + Password pwd( passwordId, 0, false ); + pwd.setWrong( error ); + + _out << "Cached value is null: " << pwd.cachedValue().isNull() << endl; + + QString pass = retrieve( pwd, image, prompt ); + + if ( !pass.isNull() ) + _out << "Read password: " << pass << endl; + else + _out << "Could not read a password" << endl; + + _out << "Cached value: " << (pwd.cachedValue().isNull() ? "null" : pwd.cachedValue()) << endl; + + if ( setPassword ) + { + if ( newPwd.isEmpty() ) + { + _out << "Clearing password" << endl; + newPwd = QString::null; + } + else + { + _out << "Setting password to " << newPwd << endl; + } + pwd.set( newPwd ); + } + + // without this, setting passwords will fail since they're + // set asynchronously. + QTimer::singleShot( 0, &app, SLOT( deref() ) ); + app.exec(); + + if ( setPassword ) + { + pass = retrieve( pwd, image, i18n("Hopefully this popped up because you set the password to the empty string.") ); + if( pass == newPwd ) + _out << "Password successfully set." << endl; + else + _out << "Failed: password ended up as " << pass << endl; + } + + return 0; +} + +#include "kopetepasswordtest_program.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/tests/kopetepasswordtest_program.h b/kopete/libkopete/tests/kopetepasswordtest_program.h new file mode 100644 index 00000000..507da2a1 --- /dev/null +++ b/kopete/libkopete/tests/kopetepasswordtest_program.h @@ -0,0 +1,16 @@ +#ifndef KOPETEPASSWORDTEST_H +#define KOPETEPASSWORDTEST_H + +#include <qobject.h> + +class PasswordRetriever : public QObject +{ + Q_OBJECT +public: + QString password; +public slots: + void timer(); + void gotPassword( const QString & ); +}; + +#endif diff --git a/kopete/libkopete/tests/kopetepropertiestest.cpp b/kopete/libkopete/tests/kopetepropertiestest.cpp new file mode 100644 index 00000000..1e60c77c --- /dev/null +++ b/kopete/libkopete/tests/kopetepropertiestest.cpp @@ -0,0 +1,59 @@ +/* + Tests for Kopete Properties + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Copyright (c) 2005 by Duncan Mac-Vicar <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <kunittest/module.h> + +#include "kopeteproperties.h" + +#include <qstring.h> +#include <qtextstream.h> + +#include <kaboutdata.h> +#include <kapplication.h> +#include <kglobal.h> +#include <kstandarddirs.h> + +#include "kopetepropertiestest.h" + +using namespace KUnitTest; + +KUNITTEST_MODULE( kunittest_kopetepropertiestest, "KopeteSuite"); +KUNITTEST_MODULE_REGISTER_TESTER( KopetePropertiesTest ); + +using namespace Kopete::Properties; + +static QTextStream _out( stdout, IO_WriteOnly ); + +class PropertyHost : public WithProperties<PropertyHost> {}; + +class FooProperty : public SimpleDataProperty<PropertyHost, QString> +{ +public: + const char *name() const { return "foo"; } +} fooProperty; + +void KopetePropertiesTest::allTests() +{ + PropertyHost myPropertyHost; + CHECK( myPropertyHost.property(fooProperty).isNull(), true); + myPropertyHost.setProperty( fooProperty, QString::fromLatin1("Foo!") ); + CHECK( myPropertyHost.property(fooProperty), QString::fromLatin1("Foo!") ); +} + + +
\ No newline at end of file diff --git a/kopete/libkopete/tests/kopetepropertiestest.h b/kopete/libkopete/tests/kopetepropertiestest.h new file mode 100644 index 00000000..c997dd80 --- /dev/null +++ b/kopete/libkopete/tests/kopetepropertiestest.h @@ -0,0 +1,36 @@ +/* + Tests for Kopete Properties + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + Copyright (c) 2005 by Duncan Mac-Vicar <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_PROPERTIES_TEST_H +#define KOPETE_PROPERTIES_TEST_H + +#include <kunittest/tester.h> + +// change to SlotTester when it works +class KopetePropertiesTest : public KUnitTest::Tester +{ +public: + void allTests(); +public slots: +private: + +}; + +#endif + + diff --git a/kopete/libkopete/tests/kopetewallettest_program.cpp b/kopete/libkopete/tests/kopetewallettest_program.cpp new file mode 100644 index 00000000..29de1edc --- /dev/null +++ b/kopete/libkopete/tests/kopetewallettest_program.cpp @@ -0,0 +1,98 @@ +/* + Tests for the wallet manager + + Copyright (c) 2003 by Richard Smith <kde@metafoo.co.uk> + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <qtextstream.h> +#include <qtimer.h> + +#include <kaboutdata.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <kdebug.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <dcopclient.h> +#include <kwallet.h> + +#include "kopetewalletmanager.h" +#include "kopetewallettest_program.h" + +static QTextStream _out( stdout, IO_WriteOnly ); + +void closeWallet() +{ + Kopete::WalletManager::self()->closeWallet(); +} + +void delay() +{ + QTimer::singleShot( 3000, qApp, SLOT( quit() ) ); + qApp->exec(); +} + +void openWalletAsync() +{ + WalletReciever *r = new WalletReciever; + _out << "[ASYNC] About to open wallet, receiver: " << r << endl; + Kopete::WalletManager::self()->openWallet( r, SLOT( gotWallet( KWallet::Wallet* ) ) ); +} + +void WalletReciever::gotWallet( KWallet::Wallet *w ) +{ + _out << "[ASYNC] Received wallet pointer: " << w << " for receiver: " << this << endl; +} + +void WalletReciever::timer() +{ + _out << "Timer..." << endl; +} + +int main( int argc, char *argv[] ) +{ + KAboutData aboutData( "kopetewallettest", "kopetewallettest", "version" ); + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineOptions opts[] = { {"+action",0,0}, KCmdLineLastOption }; + KCmdLineArgs::addCmdLineOptions( opts ); + KApplication app( "kopetewallettest" ); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + // must register with DCOP or async callbacks will fail + _out << "DCOP registration returned " << app.dcopClient()->registerAs(app.name()) << endl; + + for( int i = 0; i < args->count(); ++i ) + { + QString arg = args->arg( i ); + _out << "Processing " << arg << endl; + if( arg == QString::fromLatin1( "open" ) ) openWalletAsync(); + if( arg == QString::fromLatin1( "close" ) ) closeWallet(); + if( arg == QString::fromLatin1( "delay" ) ) delay(); + _out << "Done." << endl; + } + + WalletReciever *r = new WalletReciever; + + QTimer timer; + r->connect( &timer, SIGNAL( timeout() ), SLOT( timer() ) ); + timer.start( 1000 ); + + _out << "About to start 30 second event loop" << endl; + QTimer::singleShot( 30000, qApp, SLOT( quit() ) ); + return qApp->exec(); +} + +#include "kopetewallettest_program.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/tests/kopetewallettest_program.h b/kopete/libkopete/tests/kopetewallettest_program.h new file mode 100644 index 00000000..58bdbb6e --- /dev/null +++ b/kopete/libkopete/tests/kopetewallettest_program.h @@ -0,0 +1,17 @@ +#ifndef KOPETEWALLETTEST_H +#define KOPETEWALLETTEST_H + +#include <qobject.h> + +namespace KWallet { class Wallet; } + +class WalletReciever : public QObject +{ + Q_OBJECT +public slots: + void timer(); +private slots: + void gotWallet( KWallet::Wallet *w ); +}; + +#endif diff --git a/kopete/libkopete/tests/link-parser-testcases/broken-html-1.input b/kopete/libkopete/tests/link-parser-testcases/broken-html-1.input new file mode 100644 index 00000000..ecaf4b7e --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/broken-html-1.input @@ -0,0 +1 @@ +<a href="$URL" title="$URL">$URL</a>
\ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/broken-html-1.output b/kopete/libkopete/tests/link-parser-testcases/broken-html-1.output new file mode 100644 index 00000000..5bf3f88a --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/broken-html-1.output @@ -0,0 +1 @@ +<a href="$URL" title="$URL">$URL</a> diff --git a/kopete/libkopete/tests/link-parser-testcases/working-html-1.input b/kopete/libkopete/tests/link-parser-testcases/working-html-1.input new file mode 100644 index 00000000..306ab458 --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-html-1.input @@ -0,0 +1 @@ +$URL
\ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-html-1.output b/kopete/libkopete/tests/link-parser-testcases/working-html-1.output new file mode 100644 index 00000000..ecaf4b7e --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-html-1.output @@ -0,0 +1 @@ +<a href="$URL" title="$URL">$URL</a>
\ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-html-2.input b/kopete/libkopete/tests/link-parser-testcases/working-html-2.input new file mode 100644 index 00000000..4480dee7 --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-html-2.input @@ -0,0 +1 @@ +<a href="$URL">KDE</a>
\ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-html-2.output b/kopete/libkopete/tests/link-parser-testcases/working-html-2.output new file mode 100644 index 00000000..4480dee7 --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-html-2.output @@ -0,0 +1 @@ +<a href="$URL">KDE</a>
\ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-plaintext-1.input b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-1.input new file mode 100644 index 00000000..306ab458 --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-1.input @@ -0,0 +1 @@ +$URL
\ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-plaintext-1.output b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-1.output new file mode 100644 index 00000000..ecaf4b7e --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-1.output @@ -0,0 +1 @@ +<a href="$URL" title="$URL">$URL</a>
\ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-plaintext-2.input b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-2.input new file mode 100644 index 00000000..14e0e606 --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-2.input @@ -0,0 +1 @@ +$URL/
\ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-plaintext-2.output b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-2.output new file mode 100644 index 00000000..109c616b --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-2.output @@ -0,0 +1 @@ +<a href="$URL/" title="$URL/">$URL/</a>
\ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-plaintext-3.input b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-3.input new file mode 100644 index 00000000..828cd483 --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-3.input @@ -0,0 +1 @@ +www.kde.org/
\ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-plaintext-3.output b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-3.output new file mode 100644 index 00000000..9e898eb0 --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-3.output @@ -0,0 +1 @@ +<a href="$URL/" title="$URL/">www.kde.org/</a>
\ No newline at end of file diff --git a/kopete/libkopete/tests/mock/Makefile.am b/kopete/libkopete/tests/mock/Makefile.am new file mode 100644 index 00000000..b132a2a5 --- /dev/null +++ b/kopete/libkopete/tests/mock/Makefile.am @@ -0,0 +1,14 @@ +METASOURCES = AUTO +AM_CPPFLAGS = -DKDE_NO_COMPAT -DQT_NO_COMPAT -DQT_NO_CAST_ASCII -DQT_NO_ASCII_CAST \ + $(KOPETE_INCLUDES) -I$(top_srcdir)/kopete/libkopete/private $(all_includes) + +noinst_LTLIBRARIES = libkopete_mock.la + +libkopete_mock_la_SOURCES = kopetemessage_mock.cpp kopeteaccount_mock.cpp kopetecontact_mock.cpp kopetemetacontact_mock.cpp kopeteprotocol_mock.cpp + +libkopete_mock_la_LDFLAGS = $(all_libraries) -lkabc +libkopete_mock_la_LIBADD = ../../libkopete.la ../../private/libkopeteprivate.la $(LIB_KHTML) + +noinst_HEADERS = kopetemessage_mock.h kopetecontact_mock.h kopetemetacontact_mock.h kopeteaccount_mock.h kopeteprotocol_mock.h + + diff --git a/kopete/libkopete/tests/mock/kopeteaccount_mock.cpp b/kopete/libkopete/tests/mock/kopeteaccount_mock.cpp new file mode 100644 index 00000000..8a8425bc --- /dev/null +++ b/kopete/libkopete/tests/mock/kopeteaccount_mock.cpp @@ -0,0 +1,61 @@ +/* + Account mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteaccount_mock.h" +#include "kopetemetacontact.h" +#include "kopeteaccount_mock.h" + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +Account::Account(Kopete::Protocol *parent, const QString &accountID, const char *name) : Kopete::Account(parent, accountID, name) +{ + +} + +Account::~Account() +{ + +} + +bool Account::createContact( const QString &contactId, Kopete::MetaContact *parentContact ) +{ + return true; +} + +void Account::connect( const Kopete::OnlineStatus& initialStatus) +{ + // do nothing +} + +void Account::disconnect() +{ + // do nothing +} + +void Account::setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason) +{ + // do nothing +} + +} // end ns Kopete::Test::Mock +} // end ns Kopete::Test +} // end ns Kopete diff --git a/kopete/libkopete/tests/mock/kopeteaccount_mock.h b/kopete/libkopete/tests/mock/kopeteaccount_mock.h new file mode 100644 index 00000000..55ba15cc --- /dev/null +++ b/kopete/libkopete/tests/mock/kopeteaccount_mock.h @@ -0,0 +1,54 @@ +/* + Account mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETEACCOUNT_MOCK_H_ +#define _KOPETEACCOUNT_MOCK_H_ + +#include "kopeteaccount.h" + +class Kopete::Protocol; +class Kopete::OnlineStatus; +class Kopete::MetaContact; + +class QString; + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +class Account : public Kopete::Account +{ +public: + Account(Kopete::Protocol *parent, const QString &accountID, const char *name=0L); + ~Account(); + // pure virtual functions implementation + virtual bool createContact( const QString &contactId, MetaContact *parentContact ); + virtual void connect( const Kopete::OnlineStatus& initialStatus = OnlineStatus() ); + virtual void disconnect(); + virtual void setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason = QString::null ); +}; + +} // end ns Kopete::Test::Mock +} // end ns Kopete::Test +} // end ns Kopete + + +#endif + diff --git a/kopete/libkopete/tests/mock/kopetecontact_mock.cpp b/kopete/libkopete/tests/mock/kopetecontact_mock.cpp new file mode 100644 index 00000000..19cfa7b0 --- /dev/null +++ b/kopete/libkopete/tests/mock/kopetecontact_mock.cpp @@ -0,0 +1,44 @@ +/* + Contact mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetecontact_mock.h" + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +Contact::Contact( Kopete::Account *account, const QString &id, Kopete::MetaContact *parent, const QString &icon) : Kopete::Contact( account, id, parent, icon) +{ + +} + +Contact::~Contact() +{ + +} + +Kopete::ChatSession* Contact::manager( CanCreateFlags canCreate) +{ + return 0L; +} + +} // end ns Kopete::Test::Mock +} // end ns Kopete::Test +} // end ns Kopete
\ No newline at end of file diff --git a/kopete/libkopete/tests/mock/kopetecontact_mock.h b/kopete/libkopete/tests/mock/kopetecontact_mock.h new file mode 100644 index 00000000..e445a571 --- /dev/null +++ b/kopete/libkopete/tests/mock/kopetecontact_mock.h @@ -0,0 +1,49 @@ +/* + Contact mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETECONTACT_MOCK_H_ +#define _KOPETECONTACT_MOCK_H_ + +#include "kopetecontact.h" + +class Kopete::MetaContact; +class Kopete::Account; +class Kopete::ChatSession; +class QString; + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +class Contact : public Kopete::Contact +{ +public: + Contact( Kopete::Account *account, const QString &id, Kopete::MetaContact *parent, const QString &icon = QString::null ); + ~Contact(); + virtual Kopete::ChatSession* manager( CanCreateFlags canCreate = CannotCreate ); +}; + +} // end ns Kopete::Test::Mock +} // end ns Kopete::Test +} // end ns Kopete + + +#endif + diff --git a/kopete/libkopete/tests/mock/kopetemessage_mock.cpp b/kopete/libkopete/tests/mock/kopetemessage_mock.cpp new file mode 100644 index 00000000..a3e543e3 --- /dev/null +++ b/kopete/libkopete/tests/mock/kopetemessage_mock.cpp @@ -0,0 +1,20 @@ +/* + Message mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetemessage_mock.h" + + diff --git a/kopete/libkopete/tests/mock/kopetemessage_mock.h b/kopete/libkopete/tests/mock/kopetemessage_mock.h new file mode 100644 index 00000000..13c92574 --- /dev/null +++ b/kopete/libkopete/tests/mock/kopetemessage_mock.h @@ -0,0 +1,39 @@ +/* + Message mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETEMESSAGE_MOCK_H_ +#define _KOPETEMESSAGE_MOCK_H_ + +#include "kopetemessage.h" + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +class Message : public Kopete::Message +{ + +}; + +} // end ns Kopete::Test::Mock +} // end ns Kopete::Test +} // end ns Kopete + +#endif
\ No newline at end of file diff --git a/kopete/libkopete/tests/mock/kopetemetacontact_mock.cpp b/kopete/libkopete/tests/mock/kopetemetacontact_mock.cpp new file mode 100644 index 00000000..32f0fe1c --- /dev/null +++ b/kopete/libkopete/tests/mock/kopetemetacontact_mock.cpp @@ -0,0 +1,20 @@ +/* + MetaContact Mock Object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetemetacontact_mock.h" + + diff --git a/kopete/libkopete/tests/mock/kopetemetacontact_mock.h b/kopete/libkopete/tests/mock/kopetemetacontact_mock.h new file mode 100644 index 00000000..f3311713 --- /dev/null +++ b/kopete/libkopete/tests/mock/kopetemetacontact_mock.h @@ -0,0 +1,41 @@ +/* + MetaContact Mock Object + + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETEMETACONTACT_MOCK_H_ +#define _KOPETEMETACONTACT_MOCK_H_ + +#include "kopetemetacontact.h" + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +class MetaContact : public Kopete::MetaContact +{ + +}; + +} // end ns Kopete::Test::Mock +} // end ns Kopete::Test +} // end ns Kopete + + +#endif + diff --git a/kopete/libkopete/tests/mock/kopeteprotocol_mock.cpp b/kopete/libkopete/tests/mock/kopeteprotocol_mock.cpp new file mode 100644 index 00000000..d3bbd0e2 --- /dev/null +++ b/kopete/libkopete/tests/mock/kopeteprotocol_mock.cpp @@ -0,0 +1,49 @@ +/* + Protocol mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteprotocol_mock.h" + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +Protocol::Protocol( KInstance *instance, QObject *parent, const char *name ) : Kopete::Protocol(instance, parent, name) +{ + +} + +Account* Protocol::createNewAccount( const QString &accountId ) +{ + return 0L; +} + +AddContactPage* Protocol::createAddContactWidget( QWidget *parent, Kopete::Account *account ) +{ + return 0L; +} + +KopeteEditAccountWidget* Protocol::createEditAccountWidget( Kopete::Account *account, QWidget *parent ) +{ + return 0L; +} + +} // end ns mock +} // end ns test +} // end ns kopete diff --git a/kopete/libkopete/tests/mock/kopeteprotocol_mock.h b/kopete/libkopete/tests/mock/kopeteprotocol_mock.h new file mode 100644 index 00000000..189f7d79 --- /dev/null +++ b/kopete/libkopete/tests/mock/kopeteprotocol_mock.h @@ -0,0 +1,53 @@ +/* + Protocol mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETEPROTOCOL_MOCK_H_ +#define _KOPETEPROTOCOL_MOCK_H_ + +#include "kopeteprotocol.h" + +class KInstance; +class QObject; + +class KopeteEditAccountWidget; +class AddContactPage; +class KopeteEditAccountWidget; + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +class Protocol : public Kopete::Protocol +{ +public: + Protocol( KInstance *instance, QObject *parent, const char *name ); + // pure virtual functions implemented + virtual Account *createNewAccount( const QString &accountId ); + virtual AddContactPage *createAddContactWidget( QWidget *parent, Kopete::Account *account ); + virtual KopeteEditAccountWidget * createEditAccountWidget( Kopete::Account *account, QWidget *parent ); +}; + +} // end ns Kopete::Test::Mock +} // end ns Kopete::Test +} // end ns Kopete + + +#endif + diff --git a/kopete/libkopete/tests/template_test.cpp b/kopete/libkopete/tests/template_test.cpp new file mode 100644 index 00000000..8598f79f --- /dev/null +++ b/kopete/libkopete/tests/template_test.cpp @@ -0,0 +1,37 @@ +/* + Tests for some requirement + + Copyright (c) 2005 by Duncan Mac-Vicar <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <kunittest/module.h> +#include "template_test.h" + +using namespace KUnitTest; + +KUNITTEST_MODULE( kunittest_template_test, "KopeteSuite"); +KUNITTEST_MODULE_REGISTER_TESTER( Template_Test ); + +void Template_Test::allTests() +{ + testSomething(); +} + +void Template_Test::testSomething() +{ + int result = 1; + int expected = 1; + // result should be the expected one + CHECK(result, expected); +} diff --git a/kopete/libkopete/tests/template_test.h b/kopete/libkopete/tests/template_test.h new file mode 100644 index 00000000..4d0f1617 --- /dev/null +++ b/kopete/libkopete/tests/template_test.h @@ -0,0 +1,35 @@ +/* + Tests for some requirement + + Copyright (c) 2005 by Duncan Mac-Vicar <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef TEMPLATE_TEST_H +#define TEMPLATE_TEST_H + +#include <kunittest/tester.h> + +// change to SlotTester when it works +class Template_Test : public KUnitTest::Tester +{ +public: + void allTests(); +public slots: + void testSomething(); +private: + +}; + +#endif + diff --git a/kopete/libkopete/ui/Makefile.am b/kopete/libkopete/ui/Makefile.am new file mode 100644 index 00000000..211e0b48 --- /dev/null +++ b/kopete/libkopete/ui/Makefile.am @@ -0,0 +1,31 @@ +METASOURCES = AUTO +AM_CPPFLAGS = -DKDE_NO_COMPAT -DQT_NO_COMPAT -DQT_NO_CAST_ASCII -DQT_NO_ASCII_CAST \ + $(KOPETE_INCLUDES) -I$(top_srcdir)/kopete/libkopete/private $(all_includes) + +noinst_LTLIBRARIES = libkopeteui.la + +libkopeteui_la_SOURCES = kopetecontactaction.cpp addcontactpage.cpp \ + editaccountwidget.cpp kopetepassworddialog.ui kopetestdaction.cpp kopeteawaydialogbase.ui \ + kopetefileconfirmdialog.cpp fileconfirmbase.ui userinfodialog.cpp kopeteview.cpp \ + kopetepasswordwidgetbase.ui kopetepasswordwidget.cpp accountselector.cpp kopeteviewplugin.cpp \ + addresseeitem.cpp addressbookselectorwidget_base.ui addressbookselectordialog.cpp \ + addressbookselectorwidget.cpp metacontactselectorwidget_base.ui metacontactselectorwidget.cpp \ + kopetelistview.cpp kopetelistviewitem.cpp kopetelistviewsearchline.cpp \ + contactaddednotifywidget.ui contactaddednotifydialog.cpp addressbooklinkwidget_base.ui \ + addressbooklinkwidget.cpp + +libkopeteui_la_LDFLAGS = $(all_libraries) -lkabc +libkopeteui_la_LIBADD = ../private/libkopeteprivate.la $(LIB_KHTML) + +kopeteincludedir = $(includedir)/kopete/ui +kopeteinclude_HEADERS = accountselector.h fileconfirmbase.h \ + kopetefileconfirmdialog.h kopetepasswordwidget.h kopeteview.h addcontactpage.h \ + kopeteawaydialogbase.h kopetepasswordwidgetbase.h kopeteviewplugin.h editaccountwidget.h \ + kopetecontactaction.h kopetepassworddialog.h kopetestdaction.h userinfodialog.h \ + addressbookselectordialog.h addressbookselectorwidget.h kopetelistview.h kopetelistviewitem.h \ + kopetelistviewsearchline.h addressbooklinkwidget.h + +noinst_HEADERS = addresseeitem.h contactaddednotifywidget.h + +# vim: set noet: + diff --git a/kopete/libkopete/ui/accountselector.cpp b/kopete/libkopete/ui/accountselector.cpp new file mode 100644 index 00000000..2ea8e719 --- /dev/null +++ b/kopete/libkopete/ui/accountselector.cpp @@ -0,0 +1,186 @@ +/* + accountselector.cpp - An Accountselector + + Copyright (c) 2004 by Stefan Gehn <metz AT gehn.net> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "accountselector.h" +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" + +#include <qheader.h> +#include <qlayout.h> +#include <qpixmap.h> + +#include <kdebug.h> +#include <klistview.h> + +class AccountListViewItem : public KListViewItem +{ + private: + Kopete::Account *mAccount; + + public: + AccountListViewItem(QListView *parent, Kopete::Account *acc) + : KListViewItem(parent) + { + if (acc==0) + return; + + /*kdDebug(14010) << k_funcinfo << + "account name = " << acc->accountId() << endl;*/ + mAccount = acc; + setText(0, mAccount->accountId()); + setPixmap(0, mAccount->accountIcon()); + } + + Kopete::Account *account() + { + return mAccount; + } +}; + + +// ---------------------------------------------------------------------------- + +class AccountSelectorPrivate +{ + public: + KListView *lv; + Kopete::Protocol *proto; +}; + + +AccountSelector::AccountSelector(QWidget *parent, const char *name) + : QWidget(parent, name) +{ + //kdDebug(14010) << k_funcinfo << "for no special protocol" << endl; + d = new AccountSelectorPrivate; + d->proto = 0; + initUI(); +} + + +AccountSelector::AccountSelector(Kopete::Protocol *proto, QWidget *parent, + const char *name) : QWidget(parent, name) +{ + //kdDebug(14010) << k_funcinfo << " for protocol " << proto->pluginId() << endl; + d = new AccountSelectorPrivate; + d->proto = proto; + initUI(); +} + + +AccountSelector::~AccountSelector() +{ + kdDebug(14010) << k_funcinfo << endl; + delete d; +} + + +void AccountSelector::initUI() +{ + kdDebug(14010) << k_funcinfo << endl; + (new QVBoxLayout(this))->setAutoAdd(true); + d->lv = new KListView(this); + d->lv->setFullWidth(true); + d->lv->addColumn(QString::fromLatin1("")); + d->lv->header()->hide(); + + if(d->proto != 0) + { + kdDebug(14010) << k_funcinfo << "creating list for a certain protocol" << endl; + QDict<Kopete::Account> accounts = Kopete::AccountManager::self()->accounts(d->proto); + QDictIterator<Kopete::Account> it(accounts); + for(; Kopete::Account *account = it.current(); ++it) + { + new AccountListViewItem(d->lv, account); + } + } + else + { + kdDebug(14010) << k_funcinfo << "creating list of all accounts" << endl; + QPtrList<Kopete::Account> accounts = Kopete::AccountManager::self()->accounts(); + Kopete::Account *account = 0; + for(account = accounts.first(); account; account = accounts.next()) + { + new AccountListViewItem(d->lv, account); + } + } + + connect(d->lv, SIGNAL(selectionChanged(QListViewItem *)), + this, SLOT(slotSelectionChanged(QListViewItem *))); +} + + +void AccountSelector::setSelected(Kopete::Account *account) +{ + if (account==0) + return; + + QListViewItemIterator it(d->lv); + while (it.current()) + { + if(static_cast<AccountListViewItem *>(it.current())->account() == account) + { + it.current()->setSelected(true); + return; + } + } +} + + +bool AccountSelector::isSelected(Kopete::Account *account) +{ + if (account==0) + return false; + + QListViewItemIterator it(d->lv); + while (it.current()) + { + if(static_cast<AccountListViewItem *>(it.current())->account() == account) + return true; + } + return false; +} + + +Kopete::Account *AccountSelector::selectedItem() +{ + //kdDebug(14010) << k_funcinfo << endl; + + if (d->lv->selectedItem() != 0) + return static_cast<AccountListViewItem *>(d->lv->selectedItem())->account(); + return 0; +} + + +void AccountSelector::slotSelectionChanged(QListViewItem *item) +{ + //kdDebug(14010) << k_funcinfo << endl; + if (item != 0) + { + Kopete::Account *account = static_cast<AccountListViewItem *>(item)->account(); + if (account != 0) + { + emit selectionChanged(account); + return; + } + } + + emit selectionChanged(0); +} + +#include "accountselector.moc" +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/accountselector.h b/kopete/libkopete/ui/accountselector.h new file mode 100644 index 00000000..4f5d50ac --- /dev/null +++ b/kopete/libkopete/ui/accountselector.h @@ -0,0 +1,92 @@ +/* + accountselector.cpp - An Accountselector + + Copyright (c) 2004 by Stefan Gehn <metz AT gehn.net> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef ACCOUNTSELECTOR_H +#define ACCOUNTSELECTOR_H + +#include <qwidget.h> +#include <kopeteprotocol.h> +#include "kopete_export.h" + +class AccountSelectorPrivate; +class QListViewItem; +/** + * \brief widget to select an account, based on KListView + * @author Stefan Gehn <metz AT gehn.net> + */ +class KOPETE_EXPORT AccountSelector : public QWidget +{ +Q_OBJECT + + public: + /** + * Constructor. + * + * The parameters @p parent and @p name are handled by + * KListView. + */ + AccountSelector(QWidget *parent=0, const char *name=0); + + /** + * Constructor for a list of accounts for one protocol only + * + * The parameters @p parent and @p name are handled by + * KListView. @p proto defines the protocol whose accounts are + * shown in the list + */ + AccountSelector(Kopete::Protocol *proto, QWidget *parent=0, const char *name=0); + + /** + * Destructor. + */ + ~AccountSelector(); + + /** + * Select @p account in the list, in case it's part of the list + */ + void setSelected(Kopete::Account *account); + + /** + * Returns true in case @p account is in the list and + * the currently selected item, false otherwise + */ + bool isSelected(Kopete::Account *account); + + /** + * @return the currently selected account. + */ + Kopete::Account *selectedItem(); + + signals: + /** + * Emitted whenever the selection changed, @p acc is a pointer to the + * newly selected account + */ + void selectionChanged(Kopete::Account *acc); + + private slots: + void slotSelectionChanged(QListViewItem *item); + + private: + void initUI(); + + private: + AccountSelectorPrivate *d; +}; + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/addcontactpage.cpp b/kopete/libkopete/ui/addcontactpage.cpp new file mode 100644 index 00000000..f308a7d4 --- /dev/null +++ b/kopete/libkopete/ui/addcontactpage.cpp @@ -0,0 +1,29 @@ +/* + addcontactpage.cpp - Kopete's Add Contact GUI + + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "addcontactpage.h" + +AddContactPage::AddContactPage(QWidget *parent, const char *name ) : QWidget(parent,name) +{ +} + +AddContactPage::~AddContactPage() +{ +} + +#include "addcontactpage.moc" +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/addcontactpage.h b/kopete/libkopete/ui/addcontactpage.h new file mode 100644 index 00000000..506c5bcc --- /dev/null +++ b/kopete/libkopete/ui/addcontactpage.h @@ -0,0 +1,65 @@ +/* + addcontactpage.h - Kopete's Add Contact GUI + + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef ADDCONTACTPAGE_H +#define ADDCONTACTPAGE_H + +#include <qwidget.h> +#include <kopeteprotocol.h> + +#include "kopete_export.h" + +/** + * @author Duncan Mac-Vicar P. <duncan@kde.org> + * @todo i want to be able to have a assync apply. + * (in the case of jabber, i need to translate the legacy id to a JID) + * this could also be usefull in the case of MLSN to check if no error (and also jabber) + */ +class KOPETE_EXPORT AddContactPage : public QWidget +{ +Q_OBJECT + +public: + AddContactPage(QWidget *parent=0, const char *name=0); + virtual ~AddContactPage(); + //Kopete::Protocol *protocol; + + /** + * Plugin should reimplement this methode. + * return true if the content of the page are valid + * + * This method is called in the add account wizzard when the user press the next button + * and this page is showed. when it return false, it does not go to the nextpage. + * You should popup a dialog to explain WHY the page has not been validate + */ + virtual bool validateData()=0; + + /** + * add the contact the the specified meta contact, with the given account + * return false if the contact has not been added + */ + virtual bool apply(Kopete::Account * , Kopete::MetaContact *) = 0; + +signals: + /** + * New incarnation of validateData, emit it everytime you think the current data is valid/invalid + */ + void dataValid( AddContactPage *, bool); +}; + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/addressbooklinkwidget.cpp b/kopete/libkopete/ui/addressbooklinkwidget.cpp new file mode 100644 index 00000000..a6aff32b --- /dev/null +++ b/kopete/libkopete/ui/addressbooklinkwidget.cpp @@ -0,0 +1,99 @@ +/* + AddressBookLinkWidget + + A compact widget for showing and changing which address book item a + particular Kopete::MetaContact is related to. + + Comprises a label showing the contact's name, a Clear button, and a Change + button that usually invokes the AddressBookSelectorWidget. + + Copyright (c) 2006 by Will Stephenson <wstephenson@kde.org> + + Kopete (c) 2002-2006 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <qapplication.h> +#include <klineedit.h> +#include <klocale.h> +#include <kpushbutton.h> + +#include <kiconloader.h> + +#include <kopetemetacontact.h> + +#include "addressbooklinkwidget.h" +#include "addressbookselectordialog.h" +#include "addressbookselectorwidget.h" + +namespace Kopete { +namespace UI { + + +AddressBookLinkWidget::AddressBookLinkWidget( QWidget * parent, const char * name ) : AddressBookLinkWidgetBase( parent, name ), mMetaContact( 0 ) +{ + btnClear->setIconSet( SmallIconSet( QApplication::reverseLayout() ? QString::fromLatin1( "locationbar_erase" ) : QString::fromLatin1( "clear_left") ) ); + connect( btnClear, SIGNAL( clicked() ), this, SLOT( slotClearAddressee() ) ); + connect( btnSelectAddressee, SIGNAL( clicked() ), SLOT( slotSelectAddressee() ) ); +} + +void AddressBookLinkWidget::setAddressee( const KABC::Addressee& addr ) +{ + edtAddressee->setText( addr.realName() ); + btnClear->setEnabled( !addr.isEmpty() ); +} + +void AddressBookLinkWidget::setMetaContact( const Kopete::MetaContact * mc ) +{ + mMetaContact = mc; +} + +QString AddressBookLinkWidget::uid() const +{ + return mSelectedUid; +} + +void AddressBookLinkWidget::slotClearAddressee() +{ + edtAddressee->clear(); + btnClear->setEnabled( false ); + KABC::Addressee mrEmpty; + mSelectedUid = QString::null; + emit addresseeChanged( mrEmpty ); +} + +void AddressBookLinkWidget::slotSelectAddressee() +{ + QString message; + if ( mMetaContact ) + message = i18n("Choose the corresponding entry for '%1'" ).arg( mMetaContact->displayName() ); + else + message = i18n("Choose the corresponding entry in the address book" ); + + Kopete::UI::AddressBookSelectorDialog dialog( i18n("Addressbook Association"), message, ( mMetaContact ? mMetaContact->metaContactId() : QString::null ), this ); + int result = dialog.exec(); + + KABC::Addressee addr; + if ( result == QDialog::Accepted ) + { + addr = dialog.addressBookSelectorWidget()->addressee(); + + edtAddressee->setText( addr.realName() ); + btnClear->setEnabled( !addr.isEmpty() ); + mSelectedUid = ( addr.isEmpty() ? QString::null : addr.uid() ); + emit addresseeChanged( addr ); + } +} + +} // end namespace UI +} // end namespace Kopete + +#include "addressbooklinkwidget.moc" diff --git a/kopete/libkopete/ui/addressbooklinkwidget.h b/kopete/libkopete/ui/addressbooklinkwidget.h new file mode 100644 index 00000000..dff23c58 --- /dev/null +++ b/kopete/libkopete/ui/addressbooklinkwidget.h @@ -0,0 +1,81 @@ +/* + AddressBookLinkWidget + + A compact widget for showing and changing which address book item a + particular Kopete::MetaContact is related to. + + Comprises a label showing the contact's name, a Clear button, and a Change + button that usually invokes the AddressBookSelectorWidget. + + Copyright (c) 2006 by Will Stephenson <wstephenson@kde.org> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef ADDRESSBOOKLINKWIDGET_H +#define ADDRESSBOOKLINKWIDGET_H + +#include <kabc/addressee.h> + +#include "addressbooklinkwidget_base.h" + +namespace Kopete { +class MetaContact; + +namespace UI { + +/** + * A compact widget for showing and changing which address book item a + * particular Kopete::MetaContact is related to. + * + * Comprises a label showing the contact's name, a Clear button, and a Change + * button that usually invokes the AddressBookSelectorWidget. + */ +class AddressBookLinkWidget : public AddressBookLinkWidgetBase +{ +Q_OBJECT +public: + AddressBookLinkWidget( QWidget * parent, const char * name ); + ~AddressBookLinkWidget() {} + /** + * Set the currently selected addressee + */ + void setAddressee( const KABC::Addressee& addr ); + /** + * Set the current metacontact so that the selector dialog may be preselected + */ + void setMetaContact( const Kopete::MetaContact * ); + /** + * Return the selected addressbook UID. + */ + QString uid() const; +signals: + /** + * Emitted when the selected addressee changed. addr is the KABC::Addressee that was selected. If addr.isEmpty() is empty, the clear button was clicked. + */ + void addresseeChanged( const KABC::Addressee& addr ); + + /** + * Provided so you can perform your own actions instead of opening the AddressBookSelectorWidget. + * To do so, QObject::disconnect() btnSelectAddressee and connect your own slot to this signal + */ + void selectAddresseeClicked(); +protected slots: + void slotClearAddressee(); + void slotSelectAddressee(); +private: + const Kopete::MetaContact * mMetaContact; + QString mSelectedUid; +}; +} // end namespace UI +} // end namespace Kopete +#endif diff --git a/kopete/libkopete/ui/addressbooklinkwidget_base.ui b/kopete/libkopete/ui/addressbooklinkwidget_base.ui new file mode 100644 index 00000000..4656459c --- /dev/null +++ b/kopete/libkopete/ui/addressbooklinkwidget_base.ui @@ -0,0 +1,78 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>AddressBookLinkWidgetBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>AddressBookLinkWidgetBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>350</width> + <height>31</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout9</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KLineEdit"> + <property name="name"> + <cstring>edtAddressee</cstring> + </property> + <property name="text"> + <string></string> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="toolTip" stdset="0"> + <string>The KDE Address Book entry associated with this Kopete Contact</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>btnClear</cstring> + </property> + <property name="text"> + <string></string> + </property> + <property name="toolTip" stdset="0"> + <string>Clear</string> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>btnSelectAddressee</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>C&hange...</string> + </property> + <property name="toolTip" stdset="0"> + <string>Select an address book entry</string> + </property> + </widget> + </hbox> + </widget> + </vbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/kopete/libkopete/ui/addressbookselectordialog.cpp b/kopete/libkopete/ui/addressbookselectordialog.cpp new file mode 100644 index 00000000..7d2e17ff --- /dev/null +++ b/kopete/libkopete/ui/addressbookselectordialog.cpp @@ -0,0 +1,89 @@ +/* + AddressBookSelectorDialog + Nice Dialog to select a KDE AddressBook contact + + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "addressbookselectordialog.h" +#include "addressbookselectorwidget.h" +#include <kdialogbase.h> +#include <qdialog.h> +#include <qlistview.h> +#include <qvbox.h> +#include <klocale.h> +#include <kdialog.h> + +namespace Kopete +{ +namespace UI +{ + +AddressBookSelectorDialog::AddressBookSelectorDialog(const QString &title, const QString &message, const QString &preSelectUid, QWidget *parent, const char *name, bool modal ) : KDialogBase(parent, name, modal, title, Help|Ok|Cancel, Ok, true ) +{ + QVBox *vbox=new QVBox(this); + m_addressBookSelectorWidget= new AddressBookSelectorWidget(vbox); + m_addressBookSelectorWidget->setLabelMessage(message); + + vbox->setSpacing( KDialog::spacingHint() ); + + setMainWidget(vbox); + enableButtonOK(false); + //setHelp("linkaddressbook"); + + connect(m_addressBookSelectorWidget, SIGNAL(addresseeListClicked( QListViewItem * )), SLOT(slotWidgetAddresseeListClicked( QListViewItem * ))); + + if ( !preSelectUid.isEmpty() ) + m_addressBookSelectorWidget->selectAddressee(preSelectUid); +} + +AddressBookSelectorDialog::~AddressBookSelectorDialog() +{ +} + +KABC::Addressee AddressBookSelectorDialog::getAddressee( const QString &title, const QString &message, const QString &preSelectUid, QWidget *parent) +{ + AddressBookSelectorDialog dialog(title, message, preSelectUid, parent); + int result = dialog.exec(); + + KABC::Addressee adr; + if ( result == QDialog::Accepted ) + adr = dialog.addressBookSelectorWidget()->addressee(); + + return adr; +} + +void AddressBookSelectorDialog::slotWidgetAddresseeListClicked( QListViewItem *addressee ) +{ + // enable ok if a valid addressee is selected + enableButtonOK( addressee ? addressee->isSelected() : false); +} + +void AddressBookSelectorDialog::accept() +{ + QDialog::accept(); +} + +void AddressBookSelectorDialog::reject() +{ + QDialog::reject(); +} + +} // namespace UI +} // namespace Kopete + +#include "addressbookselectordialog.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/addressbookselectordialog.h b/kopete/libkopete/ui/addressbookselectordialog.h new file mode 100644 index 00000000..f391aa3a --- /dev/null +++ b/kopete/libkopete/ui/addressbookselectordialog.h @@ -0,0 +1,90 @@ +/* + AddressBookSelectorDialog + Nice Dialog to select a KDE AddressBook contact + + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef ADDRESSBOOKSELECTORDIALOG_H +#define ADDRESSBOOKSELECTORDIALOG_H + +#include <kdemacros.h> +#include "kopete_export.h" +#include <kdialogbase.h> + +namespace KABC +{ + class AddressBook; + class Addressee; +} + +namespace Kopete +{ +namespace UI +{ + +class AddressBookSelectorWidget; + +/** + * A dialog that uses AddressBookSelectorWidget to allow the user + * to select a KDE addressbook contact. If you want to use special features + * you can use @see addressBookSelectorWidget() to get the pointer to the + * AddressBookSelectorWidget object and set the desired options there. + * + * @author Duncan Mac-Vicar Prett <duncan@kde.org> + */ +class KOPETE_EXPORT AddressBookSelectorDialog : public KDialogBase +{ + Q_OBJECT +public: + /** + * The constructor of an empty AddressBookSelectorWidget + */ + AddressBookSelectorDialog( const QString &title, const QString &message, const QString &preSelectUid, QWidget *parent=0L, const char *name=0L, bool modal = false ); + /** + * The destructor of the dialog + */ + ~AddressBookSelectorDialog(); + + /** + * @returns the AddressBookSelectorWidget widget so that additional + * parameters can be set by using it. + */ + AddressBookSelectorWidget *addressBookSelectorWidget() const + { return m_addressBookSelectorWidget; }; + + /** + * Creates a modal dialog, lets the user to select a addressbook contact + * and returns when the dialog is closed. + * + * @returns the selected contact, or a null addressee if the user + * pressed the Cancel button. Optionally + */ + static KABC::Addressee getAddressee( const QString &title, const QString &message, const QString &preSelectUid, QWidget *parent = 0L ); + +protected slots: + virtual void accept(); + virtual void reject(); + void slotWidgetAddresseeListClicked( QListViewItem *addressee ); +protected: + AddressBookSelectorWidget *m_addressBookSelectorWidget; +}; + +} // namespace UI +} // namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/addressbookselectorwidget.cpp b/kopete/libkopete/ui/addressbookselectorwidget.cpp new file mode 100644 index 00000000..50c4a885 --- /dev/null +++ b/kopete/libkopete/ui/addressbookselectorwidget.cpp @@ -0,0 +1,171 @@ +/* + AddressBookSelectorWidget + + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Based on LinkAddressBookUI whose code was shamelessly stolen from + kopete's add new contact wizard, used in Konversation, and then + reappropriated by Kopete. + + LinkAddressBookUI: + Copyright (c) 2004 by John Tapsell <john@geola.co.uk> + Copyright (c) 2003-2005 by Will Stephenson <will@stevello.free-online.co.uk> + Copyright (c) 2002 by Nick Betcher <nbetcher@kde.org> + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <qcheckbox.h> +#include <kapplication.h> +#include <kconfig.h> +#include <klocale.h> +#include <kiconloader.h> + +#include <kdeversion.h> +#include <kinputdialog.h> + +#include <kpushbutton.h> +#include <kactivelabel.h> +#include <kdebug.h> +#include <klistview.h> +#include <klistviewsearchline.h> +#include <qlabel.h> +#include <qtooltip.h> +#include <qwhatsthis.h> + +#include "addressbookselectorwidget.h" +#include <addresseeitem.h> +#include "kabcpersistence.h" + +using namespace Kopete::UI; + +namespace Kopete +{ +namespace UI +{ + +AddressBookSelectorWidget::AddressBookSelectorWidget( QWidget *parent, const char *name ) + : AddressBookSelectorWidget_Base( parent, name ) +{ + m_addressBook = Kopete::KABCPersistence::self()->addressBook(); + + // Addressee validation connections + connect( addAddresseeButton, SIGNAL( clicked() ), SLOT( slotAddAddresseeClicked() ) ); + connect( addAddresseeButton, SIGNAL( clicked() ), SIGNAL( addAddresseeClicked() ) ); + + connect( addresseeListView, SIGNAL( clicked(QListViewItem * ) ), + SIGNAL( addresseeListClicked( QListViewItem * ) ) ); + connect( addresseeListView, SIGNAL( selectionChanged( QListViewItem * ) ), + SIGNAL( addresseeListClicked( QListViewItem * ) ) ); + connect( addresseeListView, SIGNAL( spacePressed( QListViewItem * ) ), + SIGNAL( addresseeListClicked( QListViewItem * ) ) ); + + connect( m_addressBook, SIGNAL( addressBookChanged( AddressBook * ) ), this, SLOT( slotLoadAddressees() ) ); + + //We should add a clear KAction here. But we can't really do that with a designer file :\ this sucks + + addresseeListView->setColumnText(2, SmallIconSet(QString::fromLatin1("email")), i18n("Email")); + + kListViewSearchLine->setListView(addresseeListView); + slotLoadAddressees(); + + addresseeListView->setColumnWidthMode(0, QListView::Manual); + addresseeListView->setColumnWidth(0, 63); //Photo is 60, and it's nice to have a small gap, imho +} + + +AddressBookSelectorWidget::~AddressBookSelectorWidget() +{ + disconnect( m_addressBook, SIGNAL( addressBookChanged( AddressBook * ) ), this, SLOT( slotLoadAddressees() ) ); +} + + +KABC::Addressee AddressBookSelectorWidget::addressee() +{ + AddresseeItem *item = 0L; + item = static_cast<AddresseeItem *>( addresseeListView->selectedItem() ); + + if ( item ) + m_addressee = item->addressee(); + + return m_addressee; +} + +void AddressBookSelectorWidget::selectAddressee( const QString &uid ) +{ + // iterate trough list view + QListViewItemIterator it( addresseeListView ); + while( it.current() ) + { + AddresseeItem *addrItem = (AddresseeItem *) it.current(); + if ( addrItem->addressee().uid() == uid ) + { + // select the contact item + addresseeListView->setSelected( addrItem, true ); + addresseeListView->ensureItemVisible( addrItem ); + } + ++it; + } +} + +bool AddressBookSelectorWidget::addresseeSelected() +{ + return addresseeListView->selectedItem() ? true : false; +} + +/** Read in contacts from addressbook, and select the contact that is for our nick. */ +void AddressBookSelectorWidget::slotLoadAddressees() +{ + addresseeListView->clear(); + KABC::AddressBook::Iterator it; + AddresseeItem *addr; + for( it = m_addressBook->begin(); it != m_addressBook->end(); ++it ) + { + addr = new AddresseeItem( addresseeListView, (*it)); + } + +} + +void AddressBookSelectorWidget::setLabelMessage( const QString &msg ) +{ + lblHeader->setText(msg); +} + +void AddressBookSelectorWidget::slotAddAddresseeClicked() +{ + // Pop up add addressee dialog + QString addresseeName = KInputDialog::getText( i18n( "New Address Book Entry" ), i18n( "Name the new entry:" ), QString::null, 0, this ); + + if ( !addresseeName.isEmpty() ) + { + KABC::Addressee addr; + addr.setNameFromString( addresseeName ); + m_addressBook->insertAddressee(addr); + Kopete::KABCPersistence::self()->writeAddressBook( 0 ); + slotLoadAddressees(); + // select the addressee we just added + QListViewItem * added = addresseeListView->findItem( addresseeName, 1 ); + kListViewSearchLine->clear(); + kListViewSearchLine->updateSearch(); + addresseeListView->setSelected( added, true ); + addresseeListView->ensureItemVisible( added ); + } +} + +} // namespace UI +} // namespace Kopete + +#include "addressbookselectorwidget.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/addressbookselectorwidget.h b/kopete/libkopete/ui/addressbookselectorwidget.h new file mode 100644 index 00000000..3141f726 --- /dev/null +++ b/kopete/libkopete/ui/addressbookselectorwidget.h @@ -0,0 +1,90 @@ +/* + AddressBookSelectorWidget + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Based on LinkAddressBookUI whose code was shamelessly stolen from + kopete's add new contact wizard, used in Konversation, and then + reappropriated by Kopete. + + LinkAddressBookUI: + Copyright (c) 2004 by John Tapsell <john@geola.co.uk> + Copyright (c) 2003-2005 by Will Stephenson <will@stevello.free-online.co.uk> + Copyright (c) 2002 by Nick Betcher <nbetcher@kde.org> + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef AddressBookSelectorWidget_H +#define AddressBookSelectorWidget_H + +#include <kdialogbase.h> +#include <kabc/addressbook.h> + +#include <kdemacros.h> +#include "kopete_export.h" + +#include "addressbookselectorwidget_base.h" + +namespace KABC { + class AddressBook; + class Addressee; +} + +namespace Kopete +{ +namespace UI +{ + +class KOPETE_EXPORT AddressBookSelectorWidget : public AddressBookSelectorWidget_Base +{ + Q_OBJECT +public: + AddressBookSelectorWidget( QWidget *parent = 0, const char *name = 0 ); + ~AddressBookSelectorWidget(); + KABC::Addressee addressee(); + /** + * sets the widget label message + * example: Please select a contact + * or, Choose a contact to delete + */ + void setLabelMessage( const QString &msg ); + /** + * pre-selects a contact + */ + void selectAddressee( const QString &uid ); + /** + * @return true if there is a contact selected + */ + bool addresseeSelected(); + +private: + KABC::AddressBook * m_addressBook; + KABC::Addressee m_addressee; + +protected slots: + void slotAddAddresseeClicked(); + /** + * Utility function, populates the addressee list + */ + void slotLoadAddressees(); +signals: + void addresseeListClicked( QListViewItem *addressee ); + void addAddresseeClicked(); +}; + +} // namespace UI +} // namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/addressbookselectorwidget_base.ui b/kopete/libkopete/ui/addressbookselectorwidget_base.ui new file mode 100644 index 00000000..d5e2e6f2 --- /dev/null +++ b/kopete/libkopete/ui/addressbookselectorwidget_base.ui @@ -0,0 +1,175 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>AddressBookSelectorWidget_Base</class> +<widget class="QWidget"> + <property name="name"> + <cstring>AddressBookSelectorWidget_Base</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>596</width> + <height>572</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="caption"> + <string>Select Contact</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <spacer row="3" column="1"> + <property name="name"> + <cstring>spacer11</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>405</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QPushButton" row="3" column="0"> + <property name="name"> + <cstring>addAddresseeButton</cstring> + </property> + <property name="text"> + <string>Create New Entr&y...</string> + </property> + <property name="toolTip" stdset="0"> + <string>Create a new entry in your address book</string> + </property> + </widget> + <widget class="KActiveLabel" row="0" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>lblHeader</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>4</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + <widget class="KListView" row="2" column="0" rowspan="1" colspan="2"> + <column> + <property name="text"> + <string>Photo</string> + </property> + <property name="clickable"> + <bool>false</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Name</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Email</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>addresseeListView</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>10</verstretch> + </sizepolicy> + </property> + <property name="allColumnsShowFocus"> + <bool>true</bool> + </property> + <property name="resizeMode"> + <enum>LastColumn</enum> + </property> + <property name="toolTip" stdset="0"> + <string>Select the contact you want to communicate with via Instant Messaging</string> + </property> + </widget> + <widget class="QLayoutWidget" row="1" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>layout1</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>lblSearch</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>S&earch:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>kListViewSearchLine</cstring> + </property> + </widget> + <widget class="KListViewSearchLine"> + <property name="name"> + <cstring>kListViewSearchLine</cstring> + </property> + </widget> + </hbox> + </widget> + </grid> +</widget> +<includes> + <include location="global" impldecl="in declaration">klistviewsearchline.h</include> +</includes> +<layoutdefaults spacing="6" margin="11"/> +<layoutfunctions spacing="KDialog::spacingHint" margin="KDialog::marginHint"/> +<includehints> + <includehint>kactivelabel.h</includehint> + <includehint>klistview.h</includehint> + <includehint>klistviewsearchline.h</includehint> +</includehints> +</UI> diff --git a/kopete/libkopete/ui/addresseeitem.cpp b/kopete/libkopete/ui/addresseeitem.cpp new file mode 100644 index 00000000..3888ee27 --- /dev/null +++ b/kopete/libkopete/ui/addresseeitem.cpp @@ -0,0 +1,63 @@ +/* + Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include <qlayout.h> +#include <qpushbutton.h> +#include <qgroupbox.h> +#include <qregexp.h> + +#include <klocale.h> +#include <kdebug.h> + +#include "addresseeitem.h" + +AddresseeItem::AddresseeItem( QListView *parent, const KABC::Addressee &addressee) : + KListViewItem( parent ), + mAddressee( addressee ) +{ + //We can't save showphoto because we don't have a d pointer + KABC::Picture pic = mAddressee.photo(); + if(!pic.isIntern()) + pic = mAddressee.logo(); + if(pic.isIntern()) + { + QPixmap qpixmap( pic.data().scaleWidth(60) ); //60 pixels seems okay.. kmail uses 60 btw + setPixmap( Photo,qpixmap ); + } + + setText( Name, addressee.realName() ); + setText( Email, addressee.preferredEmail() ); +} + +QString AddresseeItem::key( int column, bool ) const +{ + if (column == Email) { + QString value = text(Email); + QRegExp emailRe(QString::fromLatin1("<\\S*>")); + int match = emailRe.search(value); + if (match > -1) + value = value.mid(match + 1, emailRe.matchedLength() - 2); + + return value.lower(); + } + + return text(column).lower(); +} + + diff --git a/kopete/libkopete/ui/addresseeitem.h b/kopete/libkopete/ui/addresseeitem.h new file mode 100644 index 00000000..b190fea6 --- /dev/null +++ b/kopete/libkopete/ui/addresseeitem.h @@ -0,0 +1,68 @@ +/* + This file is part of libkabc. + Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef KABC_ADDRESSEEDIALOG_H +#define KABC_ADDRESSEEDIALOG_H + +#include <qdict.h> + +#include <kdialogbase.h> +#include <klineedit.h> +#include <klistview.h> + +#include <kabc/addressbook.h> + +/** + @short Special ListViewItem +*/ +class AddresseeItem : public KListViewItem +{ + public: + + /** + Type of column + @li @p Name - Name in Addressee + @li @p Email - Email in Addressee + */ + enum columns { Photo =0, Name = 1, Email = 2 }; + + /** + Constructor. + + @param parent The parent listview. + @param addressee The associated addressee. + */ + AddresseeItem( QListView *parent, const KABC::Addressee &addressee ); + + /** + Returns the addressee. + */ + KABC::Addressee addressee() const { return mAddressee; } + + /** + Method used by QListView to sort the items. + */ + virtual QString key( int column, bool ascending ) const; + + private: + KABC::Addressee mAddressee; +}; + +#endif diff --git a/kopete/libkopete/ui/contactaddednotifydialog.cpp b/kopete/libkopete/ui/contactaddednotifydialog.cpp new file mode 100644 index 00000000..abcd4c7e --- /dev/null +++ b/kopete/libkopete/ui/contactaddednotifydialog.cpp @@ -0,0 +1,178 @@ +/* + Copyright (c) 2005 Olivier Goffart <ogoffart@ kde.org> + + Kopete (c) 2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "contactaddednotifydialog.h" + + +#include <qvbox.h> +#include <qlabel.h> +#include <qcheckbox.h> +#include <qgroupbox.h> +#include <qstylesheet.h> +#include <qapplication.h> + +#include <klocale.h> +#include <kcombobox.h> +#include <klineedit.h> +#include <kpushbutton.h> +#include <kiconloader.h> + +#include <kabc/addressee.h> + +#include "kopetegroup.h" +#include "kopeteaccount.h" +#include "kopeteuiglobal.h" +#include "kopeteprotocol.h" +#include "kopetecontactlist.h" +#include "kopetemetacontact.h" +#include "addressbooklinkwidget.h" +#include "addressbookselectordialog.h" + + +#include "contactaddednotifywidget.h" + +namespace Kopete { + +namespace UI { + +struct ContactAddedNotifyDialog::Private +{ + ContactAddedNotifyWidget *widget; + Account *account; + QString contactId; + QString addressbookId; +}; + + +ContactAddedNotifyDialog::ContactAddedNotifyDialog(const QString& contactId, + const QString& contactNick, Kopete::Account *account, uint hide) + : KDialogBase( Global::mainWidget(), "ContactAddedNotify", /*modal=*/false, + i18n("Someone Has Added You"), Ok|Cancel ) +{ + + setWFlags(WDestructiveClose | getWFlags() ); + + d=new Private; + d->widget=new ContactAddedNotifyWidget(this); + setMainWidget(d->widget); + + d->account=account; + d->contactId=contactId; + d->widget->m_label->setText(i18n("<qt><img src=\"kopete-account-icon:%1\" /> The contact <b>%2</b> has added you to his/her contactlist. (Account %3)</qt>") + .arg( KURL::encode_string( account->protocol()->pluginId() ) + QString::fromLatin1(":") + + KURL::encode_string( account->accountId() ) , + contactNick.isEmpty() ? contactId : contactNick + QString::fromLatin1(" < ") + contactId + QString::fromLatin1(" >") , + account->accountLabel() ) ); + if( hide & InfoButton) + d->widget->m_infoButton->hide() ; + if( hide & AuthorizeCheckBox ) + { + d->widget->m_authorizeCb->hide(); + d->widget->m_authorizeCb->setChecked(false); + } + if( hide & AddCheckBox ) + { + d->widget->m_addCb->hide(); + d->widget->m_addCb->setChecked(false); + } + if( hide & AddGroupBox ) + d->widget->m_contactInfoBox->hide(); + + // Populate the groups list + Kopete::GroupList groups=Kopete::ContactList::self()->groups(); + for( Kopete::Group *it = groups.first(); it; it = groups.next() ) + { + QString groupname = it->displayName(); + if ( it->type() == Group::Normal && !groupname.isEmpty() ) + { + d->widget->m_groupList->insertItem(groupname); + } + } + d->widget->m_groupList->setCurrentText(QString::null); //default to top-level + + connect( d->widget->widAddresseeLink, SIGNAL( addresseeChanged( const KABC::Addressee& ) ), this, SLOT( slotAddresseeSelected( const KABC::Addressee& ) ) ); + connect( d->widget->m_infoButton, SIGNAL( clicked() ), this, SLOT( slotInfoClicked() ) ); + + connect( this, SIGNAL(okClicked()) , this , SLOT(slotFinished())); + +} + + +ContactAddedNotifyDialog::~ContactAddedNotifyDialog() +{ + delete d; +} + +bool ContactAddedNotifyDialog::added() const +{ + return d->widget->m_addCb->isChecked(); +} + +bool ContactAddedNotifyDialog::authorized() const +{ + return d->widget->m_authorizeCb->isChecked(); +} + +QString ContactAddedNotifyDialog::displayName() const +{ + return d->widget->m_displayNameEdit->text(); +} + +Group *ContactAddedNotifyDialog::group() const +{ + QString grpName=d->widget->m_groupList->currentText(); + if(grpName.isEmpty()) + return Group::topLevel(); + + return ContactList::self()->findGroup( grpName ); +} + +MetaContact *ContactAddedNotifyDialog::addContact() const +{ + if(!added() || !d->account) + return 0L; + + MetaContact *metacontact=d->account->addContact(d->contactId, displayName(), group()); + if(!metacontact) + return 0L; + + metacontact->setMetaContactId(d->addressbookId); + + return metacontact; +} + +void ContactAddedNotifyDialog::slotAddresseeSelected( const KABC::Addressee & addr ) +{ + if ( !addr.isEmpty() ) + { + d->addressbookId = addr.uid(); + } +} + +void ContactAddedNotifyDialog::slotInfoClicked() +{ + emit infoClicked(d->contactId); +} + +void ContactAddedNotifyDialog::slotFinished() +{ + emit applyClicked(d->contactId); +} + + + +} // namespace UI +} // namespace Kopete +#include "contactaddednotifydialog.moc" diff --git a/kopete/libkopete/ui/contactaddednotifydialog.h b/kopete/libkopete/ui/contactaddednotifydialog.h new file mode 100644 index 00000000..96f8844c --- /dev/null +++ b/kopete/libkopete/ui/contactaddednotifydialog.h @@ -0,0 +1,175 @@ +/* + Copyright (c) 2005 Olivier Goffart <ogoffart@ kde.org> + + Kopete (c) 2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + + +#ifndef KOPETE_UICONTACTADDEDNOTIFYDIALOG_H +#define KOPETE_UICONTACTADDEDNOTIFYDIALOG_H + +#include <kdialogbase.h> +#include "kopete_export.h" + +namespace KABC { + class Addressee; +} + +namespace Kopete { + +class Group; +class Account; +class MetaContact; + +namespace UI { + +/** + * @brief Dialog which is shown when a contact added you in the contactlist. + * + * This dialog asks the user to give authorization for the addition to the + * person who added the user and also asks the user if the contact who you've + * received the notification for should be added to the user's contact list + * + * example of usage + * @code + + Kopete::UI::ContactAddedNotifyDialog *dialog = + new ContactAddedNotifyDialog(contactId, QString::null,account); + QObject::connect(dialog,SIGNAL(applyClicked(const QString&)),this,SLOT(contactAddedDialogApplied())); + QObject::connect(dialog,SIGNAL(infoClicked(const QString&)),this,SLOT(contactAddedDialogInfo())); + dialog->show(); + + * @endcode + * + * and in your contactAddedDialogApplied slot + * @code + const Kopete::UI::ContactAddedNotifyDialog *dialog = + dynamic_cast<const Kopete::UI::ContactAddedNotifyDialog *>(sender()); + if(!dialog) + return; + if(dialog->authorized()) + socket->authorize(contactId); + if(dialog->added()) + dialog->addContact(); + * @endcode + * + * Note that you can also use exec() but this is not recommended + * + * @author Olivier Goffart + * @since 0.11 + */ +class KOPETE_EXPORT ContactAddedNotifyDialog : public KDialogBase +{ +Q_OBJECT +public: + /** + * All widget in the dialog that may be hidden. + */ + enum HideWidget + { + InfoButton = 0x01, /**< the button which ask for more info about the contact */ + AuthorizeCheckBox = 0x02, /**< the checkbox which ask for authorize the contact */ + AddCheckBox = 0x04, /**< the checkbox which ask if the contact should be added */ + AddGroupBox = 0x08 /**< all the widget about metacontact properties */ + }; + + /** + * @brief Constructor + * + * The dialog is by default not modal, and will delete itself when closed + * + * @param contactId the contactId of the contact which just added the user + * @param contactNick the nickname of the contact if available. + * @param account is used to display the account icon and informaiton about the account + * @param hide a bitmask of HideWidget used to hide some widget. By default, everything is shown. + * + */ + ContactAddedNotifyDialog(const QString& contactId, const QString& contactNick=QString::null, + Kopete::Account *account=0L, uint hide=0x00); + + /** + * @brief Destructor + */ + ~ContactAddedNotifyDialog(); + + /** + * @brief return if the user has checked the "authorize" checkbox + * @return true if the authorize checkbox is checked, false otherwise + */ + bool authorized() const; + + /** + * @brief return if the user has checked the "add" checkbox + * @return true if the add checkbox is checked, false otherwise + */ + bool added() const; + + /** + * @brief return the display name the user has entered + */ + QString displayName() const; + + /** + * @brief return the group the user has selected + * + * If the user has entered a group which doesn't exist yet, it will be created now + */ + Group* group() const; + +public slots: + + /** + * @brief create a metacontact. + * + * This function only works if the add checkbox is checked, otherwise, + * it will return 0L. + * + * it uses the Account::addContact function to add the contact + * + * @return the new metacontact created, or 0L if the operation failed. + */ + MetaContact *addContact() const; + +signals: + /** + * @brief the dialog has been applied + * @param contactId is the id of the contact passed in the constructor. + */ + void applyClicked(const QString &contactId); + + /** + * @brief the button "info" has been pressed + * If you haven't hidden the more info button, you should connect this + * signal to a slot which show a dialog with more info about the + * contact. + * + * hint: you can use sender() as parent of the new dialog + * @param contactId is the id of the contact passed in the constructor. + */ + void infoClicked(const QString &contactId); + + +private slots: + void slotAddresseeSelected( const KABC::Addressee &); + void slotInfoClicked(); + void slotFinished(); + +private: + struct Private; + Private *d; +}; + + + +} // namespace UI +} // namespace Kopete +#endif diff --git a/kopete/libkopete/ui/contactaddednotifywidget.ui b/kopete/libkopete/ui/contactaddednotifywidget.ui new file mode 100644 index 00000000..47d3f070 --- /dev/null +++ b/kopete/libkopete/ui/contactaddednotifywidget.ui @@ -0,0 +1,260 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>ContactAddedNotifyWidget</class> +<author>Olivier Goffart</author> +<widget class="QWidget"> + <property name="name"> + <cstring>Form2</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>466</width> + <height>342</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>m_label</cstring> + </property> + <property name="text"> + <string>The contact XXX added you in his contactlist</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter</set> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>151</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_infoButton</cstring> + </property> + <property name="text"> + <string>Read More Info About This Contact</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>m_authorizeCb</cstring> + </property> + <property name="text"> + <string>Authorize this contact to see my status</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>m_addCb</cstring> + </property> + <property name="text"> + <string>Add this contact in my contactlist</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <widget class="QGroupBox"> + <property name="name"> + <cstring>m_contactInfoBox</cstring> + </property> + <property name="title"> + <string></string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout19</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel7</cstring> + </property> + <property name="text"> + <string>Display name:</string> + </property> + <property name="toolTip" stdset="0"> + <string>The display name of the contact. Leave it empty to use the contact nickname</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter the contact display name. This is how the contact will appears in the contactlist. +Leave it empty if you want to see the contact nickname as display name.</string> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>m_displayNameEdit</cstring> + </property> + <property name="toolTip" stdset="0"> + <string>The display name of the contact. Leave it empty to use the contact nickname</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter the contact display name. This is how the contact will appears in the contactlist. +Leave it empty if you want to see the contact nickname as display name.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel5</cstring> + </property> + <property name="text"> + <string>In the group:</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter the group where the contact should be added. Leave it empty to add it in the top level group.</string> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>m_groupList</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="editable"> + <bool>true</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter the group where the contact should be added. Leave it empty to add it in the top level group.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel6</cstring> + </property> + <property name="text"> + <string>Addressbook link:</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout11</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer11</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>51</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="Kopete::UI::AddressBookLinkWidget"> + <property name="name"> + <cstring>widAddresseeLink</cstring> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + <spacer> + <property name="name"> + <cstring>spacer21</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>40</height> + </size> + </property> + </spacer> + </vbox> +</widget> +<connections> + <connection> + <sender>m_addCb</sender> + <signal>toggled(bool)</signal> + <receiver>m_contactInfoBox</receiver> + <slot>setEnabled(bool)</slot> + </connection> +</connections> +<customwidgets> + <customwidget> + <class>Kopete::UI::AddressBookLinkWidget</class> + <header>addressbooklinkwidget.h</header> + </customwidget> +</customwidgets> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kpushbutton.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kcombobox.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/kopete/libkopete/ui/editaccountwidget.cpp b/kopete/libkopete/ui/editaccountwidget.cpp new file mode 100644 index 00000000..7428a8ad --- /dev/null +++ b/kopete/libkopete/ui/editaccountwidget.cpp @@ -0,0 +1,49 @@ +/* + editaccountwidget.cpp - Kopete Account Widget + + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2003 by Olivier Goffart <ogoffart @ kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "editaccountwidget.h" + +class KopeteEditAccountWidgetPrivate +{ +public: + Kopete::Account *account; +}; + +KopeteEditAccountWidget::KopeteEditAccountWidget( Kopete::Account *account ) +{ + d = new KopeteEditAccountWidgetPrivate; + d->account = account; +} + +KopeteEditAccountWidget::~KopeteEditAccountWidget() +{ + delete d; +} + +Kopete::Account * KopeteEditAccountWidget::account() const +{ + return d->account; +} + +void KopeteEditAccountWidget::setAccount( Kopete::Account *account ) +{ + d->account = account; +} + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/editaccountwidget.h b/kopete/libkopete/ui/editaccountwidget.h new file mode 100644 index 00000000..533c90ff --- /dev/null +++ b/kopete/libkopete/ui/editaccountwidget.h @@ -0,0 +1,104 @@ +/* + editaccountwidget.h - Kopete Account Widget + + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2003 by Olivier Goffart <ogoffart @ kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef EDITACCOUNTWIDGET_H +#define EDITACCOUNTWIDGET_H + +#include "kopete_export.h" + +namespace Kopete +{ +class Account; +} + +class KopeteEditAccountWidgetPrivate; + +/** + * @author Olivier Goffart <ogoffart @ kde.org> + * + * This class is used by the protocol plugins to add specific protocol fields in the add account wizard, + * or in the account preferences. If the given account is 0L, then you will have to create a new account + * in @ref apply(). + * + * Each protocol has to subclass this class, and the protocol's edit account page MUST inherits from + * QWidget too. + * + * We suggest to put at least these fields in the page: + * + * - The User login, or the accountId. you can retrieve it from @ref Kopete::Account::accountId(). This + * field has to be marked as ReadOnly or shown as a label if the account already exists. Remember + * that accountId should be constant after account creation! + * + * - The password, and the remember password checkboxes. + * + * - The auto connect checkbox: use @ref Kopete::Account::excludeConnect() and + * @ref Kopete::Account::setExcludeConnect() to get/set this flag. + * + * You may add other custom fields, e.g. the nickname. To save or retrieve these settings use + * @ref Kopete::ContactListElement::pluginData() with your protocol as plugin. + */ +class KOPETE_EXPORT KopeteEditAccountWidget +{ +public: + /** + * Constructor. + * + * If 'account' is 0L we are in the 'add account wizard', otherwise + * we are editing an existing account. + */ + KopeteEditAccountWidget( Kopete::Account *account ); + + /** + * Destructor + */ + virtual ~KopeteEditAccountWidget(); + + /** + * This method must be reimplemented. + * It does the same as @ref AddContactPage::validateData() + */ + virtual bool validateData() = 0; + + /** + * Create a new account if we are in the 'add account wizard', + * otherwise update the existing account. + */ + virtual Kopete::Account *apply() = 0; + +protected: + /** + * Get a pointer to the Kopete::Account passed to the constructor. + * You can modify it any way you like, just don't delete the object. + */ + Kopete::Account * account() const; + + /** + * Set the account + */ + // FIXME: Is it possible to make the API not require this? A const account + // in this widget seems a lot cleaner to me - Martijn + void setAccount( Kopete::Account *account ); + +private: + KopeteEditAccountWidgetPrivate *d; +}; + +// vim: set noet ts=4 sts=4 sw=4: + +#endif + diff --git a/kopete/libkopete/ui/fileconfirmbase.ui b/kopete/libkopete/ui/fileconfirmbase.ui new file mode 100644 index 00000000..3d697b0f --- /dev/null +++ b/kopete/libkopete/ui/fileconfirmbase.ui @@ -0,0 +1,151 @@ +<!DOCTYPE UI><UI version="3.1" stdsetdef="1"> +<class>FileConfirmBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>FileConfirmBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>410</width> + <height>307</height> + </rect> + </property> + <property name="caption"> + <string>A User Would Like to Send You a File</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>3</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QLabel" row="0" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>TextLabel1</cstring> + </property> + <property name="text"> + <string>A user is trying to send you a file. The file will only be downloaded if you accept this dialog. If you do not wish to receive it, please click 'Refuse'. This file will never be executed by Kopete at any point during or after the transfer.</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter</set> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>TextLabel1_2</cstring> + </property> + <property name="text"> + <string>From:</string> + </property> + </widget> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>TextLabel7</cstring> + </property> + <property name="text"> + <string>File name:</string> + </property> + </widget> + <widget class="KLineEdit" row="6" column="1"> + <property name="name"> + <cstring>m_saveto</cstring> + </property> + </widget> + <widget class="KPushButton" row="6" column="2"> + <property name="name"> + <cstring>cmdBrowse</cstring> + </property> + <property name="text"> + <string>&Browse...</string> + </property> + </widget> + <widget class="QLabel" row="5" column="0"> + <property name="name"> + <cstring>TextLabel11</cstring> + </property> + <property name="text"> + <string>Size:</string> + </property> + </widget> + <widget class="QLabel" row="3" column="0"> + <property name="name"> + <cstring>TextLabel8</cstring> + </property> + <property name="text"> + <string>Description:</string> + </property> + </widget> + <widget class="QTextEdit" row="3" column="1" rowspan="2" colspan="2"> + <property name="name"> + <cstring>m_description</cstring> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + <spacer row="4" column="0"> + <property name="name"> + <cstring>Spacer6</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QLabel" row="6" column="0"> + <property name="name"> + <cstring>TextLabel13</cstring> + </property> + <property name="text"> + <string>Save to:</string> + </property> + </widget> + <widget class="KLineEdit" row="1" column="1" rowspan="1" colspan="2"> + <property name="name"> + <cstring>m_from</cstring> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + <widget class="KLineEdit" row="2" column="1" rowspan="1" colspan="2"> + <property name="name"> + <cstring>m_filename</cstring> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + <widget class="KLineEdit" row="5" column="1" rowspan="1" colspan="2"> + <property name="name"> + <cstring>m_size</cstring> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </grid> +</widget> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> +</includehints> +</UI> diff --git a/kopete/libkopete/ui/kopete.widgets b/kopete/libkopete/ui/kopete.widgets new file mode 100644 index 00000000..7c441d0f --- /dev/null +++ b/kopete/libkopete/ui/kopete.widgets @@ -0,0 +1,24 @@ +[Global] +PluginName=KopeteWidgets +Includes=kinstance.h +Init=new KInstance("kopetewidgets"); + +[Kopete::UI::ListView::ListView] +ToolTip=List View (Kopete) +WhatsThis=A component capable list view widget. +IncludeFile=kopetelistview.h +Group=Views (Kopete) + +[Kopete::UI::ListView::SearchLine] +ToolTip=List View Search Line (Kopete) +WhatsThis=Search line able to use Kopete custom list View. +IncludeFile=kopetelistviewsearchline.h +ConstructorArgs=(parent, 0, name) +Group=Input (Kopete) + +[Kopete::UI::AddressBookLinkWidget] +ToolTip=Address Book Link Widget (Kopete) +WhatsThis=KABC::Addressee display/selector +IncludeFile=addressbooklinkwidget.h +ConstructorArgs=(parent, name) +Group=Input (Kopete) diff --git a/kopete/libkopete/ui/kopeteawaydialogbase.ui b/kopete/libkopete/ui/kopeteawaydialogbase.ui new file mode 100644 index 00000000..783fe4da --- /dev/null +++ b/kopete/libkopete/ui/kopeteawaydialogbase.ui @@ -0,0 +1,85 @@ +<!DOCTYPE UI><UI version="3.1" stdsetdef="1"> +<class>KopeteAwayDialog_Base</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KopeteAwayDialog_Base</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>322</width> + <height>192</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>TextLabel1</cstring> + </property> + <property name="text"> + <string>Please specify an away message, or choose a predefined one.</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter|AlignLeft</set> + </property> + <property name="wordwrap" stdset="0"> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>txtOneShot</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>300</width> + <height>0</height> + </size> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>cmbHistory</cstring> + </property> + <property name="editable"> + <bool>false</bool> + </property> + <property name="insertionPolicy"> + <enum>AtCurrent</enum> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </vbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kopete/libkopete/ui/kopetecontactaction.cpp b/kopete/libkopete/ui/kopetecontactaction.cpp new file mode 100644 index 00000000..d02c2ff2 --- /dev/null +++ b/kopete/libkopete/ui/kopetecontactaction.cpp @@ -0,0 +1,54 @@ +/* + kopetecontactaction.cpp - KAction for selecting a Kopete::Contact + + Copyright (c) 2003 by Martijn Klingens <klingens@kde.org> + + Kopete (c) 2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetecontactaction.h" + +#include "kopetemetacontact.h" +#include "kopetecontact.h" +#include "kopeteonlinestatus.h" + +KopeteContactAction::KopeteContactAction( Kopete::Contact *contact, const QObject *receiver, + const char *slot, KAction *parent ) +: KAction( contact->metaContact()->displayName(), QIconSet( contact->onlineStatus().iconFor( contact ) ), KShortcut(), + parent, contact->contactId().latin1() ) +{ + m_contact = contact; + + connect( this, SIGNAL( activated() ), SLOT( slotContactActionActivated() ) ); + connect( this, SIGNAL( activated( Kopete::Contact * ) ), receiver, slot ); +} + +KopeteContactAction::~KopeteContactAction() +{ +} + +void KopeteContactAction::slotContactActionActivated() +{ + emit activated( m_contact ); +} + +Kopete::Contact * KopeteContactAction::contact() const +{ + return m_contact; +} + + +#include "kopetecontactaction.moc" + +// vim: set noet ts=4 sts=4 sw=4: + + diff --git a/kopete/libkopete/ui/kopetecontactaction.h b/kopete/libkopete/ui/kopetecontactaction.h new file mode 100644 index 00000000..bb9d9f76 --- /dev/null +++ b/kopete/libkopete/ui/kopetecontactaction.h @@ -0,0 +1,61 @@ +/* + kopetecontactaction.cpp - KAction for selecting a Kopete::Contact + + Copyright (c) 2003 by Martijn Klingens <klingens@kde.org> + + Kopete (c) 2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef __kopetecontactaction_h__ +#define __kopetecontactaction_h__ + +#include <kaction.h> +#include "kopete_export.h" + +namespace Kopete +{ +class Contact; +} + +/** + * @author Martijn Klingens <klingens@kde.org> + */ +class KOPETE_EXPORT KopeteContactAction : public KAction +{ + Q_OBJECT + +public: + /** + * Create a new KopeteContactAction + */ + KopeteContactAction( Kopete::Contact *contact, const QObject* receiver, const char* slot, KAction* parent ); + ~KopeteContactAction(); + + Kopete::Contact * contact() const; + +signals: + /** + * Overloaded signal to get the selected contact + */ + void activated( Kopete::Contact *action ); + +private slots: + void slotContactActionActivated(); + +private: + Kopete::Contact *m_contact; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/kopetefileconfirmdialog.cpp b/kopete/libkopete/ui/kopetefileconfirmdialog.cpp new file mode 100644 index 00000000..01036a05 --- /dev/null +++ b/kopete/libkopete/ui/kopetefileconfirmdialog.cpp @@ -0,0 +1,117 @@ +/* + kopetefileconfirmdialog.cpp + + Copyright (c) 2003-2004 by Olivier Goffart <ogoffart @ kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <qtextedit.h> + +#include <klineedit.h> +#include <kconfig.h> +#include <klocale.h> +#include <kfiledialog.h> +#include <kpushbutton.h> +#include <kmessagebox.h> + +//#include "kopetetransfermanager.h" +#include "fileconfirmbase.h" +#include "kopetefileconfirmdialog.h" + +#include "kopetemetacontact.h" +#include "kopetecontact.h" + +KopeteFileConfirmDialog::KopeteFileConfirmDialog(const Kopete::FileTransferInfo &info,const QString& description,QWidget *parent, const char *name ) +: KDialogBase( parent, name, false, i18n( "A User Would Like to Send You a File" ), + KDialogBase::User1 | KDialogBase::User2, KDialogBase::User1, true, i18n( "&Refuse" ), i18n( "&Accept" ) ), + m_info( info ) +{ + setWFlags( WDestructiveClose ); + m_emited=false; + + m_view=new FileConfirmBase(this, "FileConfirmView"); + m_view->m_from->setText( info.contact()->metaContact()->displayName() + QString::fromLatin1( " <" ) + + info.contact()->contactId() + QString::fromLatin1( "> " ) ); + m_view->m_size->setText( KGlobal::locale()->formatNumber( long( info.size() ), 0 ) ); + m_view->m_description->setText( description ); + m_view->m_filename->setText( info.file() ); + + KGlobal::config()->setGroup("File Transfer"); + const QString defaultPath=KGlobal::config()->readEntry("defaultPath" , QDir::homeDirPath() ); + m_view->m_saveto->setText(defaultPath + QString::fromLatin1( "/" ) + info.file() ); + + setMainWidget(m_view); + + connect(m_view->cmdBrowse, SIGNAL(clicked()), this, SLOT(slotBrowsePressed())); +} + +KopeteFileConfirmDialog::~KopeteFileConfirmDialog() +{ +} + +void KopeteFileConfirmDialog::slotBrowsePressed() +{ + QString saveFileName = KFileDialog::getSaveFileName( m_view->m_saveto->text(), QString::fromLatin1( "*" ), 0L , i18n( "File Transfer" ) ); + if ( !saveFileName.isNull()) + { + m_view->m_saveto->setText(saveFileName); + } +} + +void KopeteFileConfirmDialog::slotUser2() +{ + m_emited=true; + KURL url(m_view->m_saveto->text()); + if(url.isValid() && url.isLocalFile() ) + { + const QString directory=url.directory(); + if(!directory.isEmpty()) + { + KGlobal::config()->setGroup("File Transfer"); + KGlobal::config()->writeEntry("defaultPath" , directory ); + } + + if(QFile(m_view->m_saveto->text()).exists()) + { + int ret=KMessageBox::warningContinueCancel(this, i18n("The file '%1' already exists.\nDo you want to overwrite it ?").arg(m_view->m_saveto->text()) , + i18n("Overwrite File") , KStdGuiItem::save()); + if(ret==KMessageBox::Cancel) + return; + } + + emit accepted(m_info,m_view->m_saveto->text()); + close(); + } + else + KMessageBox::queuedMessageBox (this, KMessageBox::Sorry, i18n("You must provide a valid local filename") ); +} + +void KopeteFileConfirmDialog::slotUser1() +{ + m_emited=true; + emit refused(m_info); + close(); +} + +void KopeteFileConfirmDialog::closeEvent( QCloseEvent *e) +{ + if(!m_emited) + { + m_emited=true; + emit refused(m_info); + } + KDialogBase::closeEvent(e); +} + +#include "kopetefileconfirmdialog.moc" + diff --git a/kopete/libkopete/ui/kopetefileconfirmdialog.h b/kopete/libkopete/ui/kopetefileconfirmdialog.h new file mode 100644 index 00000000..20d58d51 --- /dev/null +++ b/kopete/libkopete/ui/kopetefileconfirmdialog.h @@ -0,0 +1,57 @@ +/* + kopetefileconfirmdialog.h + + Copyright (c) 2003 by Olivier Goffart <ogoffart @ kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEFILECONFIRMDIALOG_H +#define KOPETEFILECONFIRMDIALOG_H + +#include <qwidget.h> +#include <kdialogbase.h> +#include "kopetetransfermanager.h" + +class FileConfirmBase; + +/** + *@author Olivier Goffart + */ + +class KopeteFileConfirmDialog : public KDialogBase +{ +Q_OBJECT + +public: + KopeteFileConfirmDialog(const Kopete::FileTransferInfo &info,const QString& description=QString::null, QWidget *parent=0, const char* name=0); + ~KopeteFileConfirmDialog(); + +private: + FileConfirmBase* m_view; + Kopete::FileTransferInfo m_info; + bool m_emited; + +public slots: + void slotBrowsePressed(); + +protected slots: + virtual void slotUser2(); + virtual void slotUser1(); + virtual void closeEvent( QCloseEvent *e); + +signals: + void accepted(const Kopete::FileTransferInfo &info, const QString &filename); + void refused(const Kopete::FileTransferInfo &info); +}; + +#endif diff --git a/kopete/libkopete/ui/kopetelistview.cpp b/kopete/libkopete/ui/kopetelistview.cpp new file mode 100644 index 00000000..594f0920 --- /dev/null +++ b/kopete/libkopete/ui/kopetelistview.cpp @@ -0,0 +1,215 @@ +/* + kopetelistview.cpp - List View providing support for ListView::Items + + Copyright (c) 2004 by Engin AYDOGAN <engin@bzzzt.biz> + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetelistview.h" +#include "kopetelistviewitem.h" +#include "kopeteuiglobal.h" +#include "kopeteglobal.h" +#include "kopeteprefs.h" + +#include <qapplication.h> +#include <kglobal.h> +#include <kconfig.h> +#include <kdebug.h> + +#include <qtimer.h> +#include <qtooltip.h> +#include <qstyle.h> + +#include <utility> +#include <memory> + +namespace Kopete { +namespace UI { +namespace ListView { + +/* + Custom QToolTip for the list view. + The decision whether or not to show tooltips is taken in + maybeTip(). See also the QListView sources from Qt itself. + Delegates to the list view items. +*/ +class ToolTip : public QToolTip +{ +public: + ToolTip( QWidget *parent, ListView *lv ); + virtual ~ToolTip(); + + void maybeTip( const QPoint &pos ); + +private: + ListView *m_listView; +}; + +ToolTip::ToolTip( QWidget *parent, ListView *lv ) + : QToolTip( parent ) +{ + m_listView = lv; +} + +ToolTip::~ToolTip() +{ +} + +void ToolTip::maybeTip( const QPoint &pos ) +{ + if( !parentWidget() || !m_listView ) + return; + + if( Item *item = dynamic_cast<Item*>( m_listView->itemAt( pos ) ) ) + { + QRect itemRect = m_listView->itemRect( item ); + + uint leftMargin = m_listView->treeStepSize() * + ( item->depth() + ( m_listView->rootIsDecorated() ? 1 : 0 ) ) + + m_listView->itemMargin(); + + uint xAdjust = itemRect.left() + leftMargin; + uint yAdjust = itemRect.top(); + QPoint relativePos( pos.x() - xAdjust, pos.y() - yAdjust ); + + std::pair<QString,QRect> toolTip = item->toolTip( relativePos ); + if ( toolTip.first.isEmpty() ) + return; + + toolTip.second.moveBy( xAdjust, yAdjust ); +// kdDebug( 14000 ) << k_funcinfo << "Adding tooltip: itemRect: " +// << toolTip.second << ", tooltip: " << toolTip.first << endl; + tip( toolTip.second, toolTip.first ); + } +} + +struct ListView::Private +{ + QTimer sortTimer; + std::auto_ptr<ToolTip> toolTip; + //! C-tor + Private() {} +}; + +ListView::ListView( QWidget *parent, const char *name ) + : KListView( parent, name ), d( new Private ) +{ + connect( &d->sortTimer, SIGNAL( timeout() ), this, SLOT( slotSort() ) ); + + // We have our own tooltips, don't use the default QListView ones + setShowToolTips( false ); + d->toolTip.reset( new ToolTip( viewport(), this ) ); + + connect( this, SIGNAL( contextMenu( KListView *, QListViewItem *, const QPoint & ) ), + SLOT( slotContextMenu( KListView *, QListViewItem *, const QPoint & ) ) ); + connect( this, SIGNAL( doubleClicked( QListViewItem * ) ), + SLOT( slotDoubleClicked( QListViewItem * ) ) ); + + // set up flags for nicer painting + clearWFlags( WStaticContents ); + setWFlags( WNoAutoErase ); + + // clear the appropriate flags from the viewport - qt docs say we have to mask + // these flags out of the QListView to make weirdly painted list items work, but + // that doesn't do the job. masking them out of the viewport does. +// class MyWidget : public QWidget { public: using QWidget::clearWFlags; }; +// static_cast<MyWidget*>( viewport() )->clearWFlags( WStaticContents ); +// static_cast<MyWidget*>( viewport() )->setWFlags( WNoAutoErase ); + + // The above causes compiler errors with the (broken) native TRU64 and IRIX compilers. + // This should make it compile for both platforms and still seems to work. + // This is, of course, a nasty hack, but it works, so... + static_cast<ListView*>(viewport())->clearWFlags( WStaticContents ); + static_cast<ListView*>(viewport())->setWFlags( WNoAutoErase ); +} + +ListView::~ListView() +{ + delete d; +} + +void ListView::slotDoubleClicked( QListViewItem *item ) +{ + kdDebug( 14000 ) << k_funcinfo << endl; + + if ( item ) + setOpen( item, !isOpen( item ) ); +} + +void ListView::slotContextMenu( KListView * /*listview*/, + QListViewItem *item, const QPoint &/*point*/ ) +{ + if ( item && !item->isSelected() ) + { + clearSelection(); + item->setSelected( true ); + } + if ( !item ) + clearSelection(); + +// if( Item *myItem = dynamic_cast<Item*>( item ) ) + ;// TODO: myItem->contextMenu( point ); +} + +void ListView::setShowTreeLines( bool bShowAsTree ) +{ + if ( bShowAsTree ) + { + setRootIsDecorated( true ); + setTreeStepSize( 20 ); + } + else + { + setRootIsDecorated( false ); + setTreeStepSize( 0 ); + } + // TODO: relayout all items. their width may have changed, but they won't know about it. +} + +/* This is a small hack ensuring that only F2 triggers inline + * renaming. Won't win a beauty award, but whoever wrote it thinks + * relying on the fact that QListView intercepts and processes the + * F2 event through this event filter is sorta safe. + * + * Also use enter to execute the item since executed is not usually + * called when enter is pressed. + */ +void ListView::keyPressEvent( QKeyEvent *e ) +{ + QListViewItem *item = currentItem(); + if ( (e->key() == Qt::Key_F2) && item && item->isVisible() ) + rename( item, 0 ); + else if ( (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) && item && item->isVisible() ) + { + // must provide a point within the item; emitExecute checks for this + QPoint p = viewport()->mapToGlobal(itemRect(item).center()); + emitExecute( currentItem(), p, 0 ); + } + else + KListView::keyPressEvent(e); +} + +void ListView::delayedSort() +{ + if ( !d->sortTimer.isActive() ) + d->sortTimer.start( 500, true ); +} + +} // END namespace ListView +} // END namespace UI +} // END namespace Kopete + +#include "kopetelistview.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/kopetelistview.h b/kopete/libkopete/ui/kopetelistview.h new file mode 100644 index 00000000..8b2c579b --- /dev/null +++ b/kopete/libkopete/ui/kopetelistview.h @@ -0,0 +1,74 @@ +/* + kopetelistview.h - List View providing extra support for ListView::Items + + Copyright (c) 2005 by Engin AYDOGAN <engin@bzzzt.biz> + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_LISTVIEW_H +#define KOPETE_LISTVIEW_H + +#include <klistview.h> + +namespace Kopete { +namespace UI { +namespace ListView { + +/** + * @author Engin AYDOGAN <engin@bzzzt.biz> + * @author Richard Smith <kde@metafoo.co.uk> + */ +class ListView : public KListView +{ + Q_OBJECT + +public: + ListView( QWidget *parent = 0, const char *name = 0 ); + ~ListView(); + + /** + * Schedule a delayed sort operation. Sorts will be withheld for at most + * half a second, after which they will be performed. This way multiple + * sort calls can be safely bundled without writing complex code to avoid + * the sorts entirely. + */ + void delayedSort(); + + /** + * Set whether to show the lines and +/- boxes in the tree + */ + void setShowTreeLines( bool bShowAsTree ); + +public slots: + /** + * Calls QListView::sort() + */ + void slotSort() { sort(); } +protected: + virtual void keyPressEvent( QKeyEvent *e ); +private slots: + void slotContextMenu(KListView*,QListViewItem *item, const QPoint &point ); + void slotDoubleClicked( QListViewItem *item ); +private: + struct Private; + Private *d; +}; + +} // END namespace ListView +} // END namespace UI +} // END namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/kopetelistviewitem.cpp b/kopete/libkopete/ui/kopetelistviewitem.cpp new file mode 100644 index 00000000..fda2ff4c --- /dev/null +++ b/kopete/libkopete/ui/kopetelistviewitem.cpp @@ -0,0 +1,1314 @@ +/* + kopetelistviewitem.cpp - Kopete's modular QListViewItems + + Copyright (c) 2005 by Engin AYDOGAN <engin@bzzzt.biz> + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "kopetecontact.h" +#include "kopetelistviewitem.h" +#include "kopeteemoticons.h" +#include "kopeteonlinestatus.h" + +#include <kdebug.h> +#include <kiconloader.h> +#include <kstringhandler.h> + +#include <qapplication.h> +#include <qpixmap.h> +#include <qpainter.h> +#include <qptrlist.h> +#include <qrect.h> +#include <qtimer.h> +#include <qheader.h> +#include <qstyle.h> + +#ifdef HAVE_XRENDER +# include <X11/Xlib.h> +# include <X11/extensions/Xrender.h> +#endif + +#include <limits.h> + +namespace Kopete { +namespace UI { +namespace ListView { + +// ComponentBase -------- + +class ComponentBase::Private +{ +public: + QPtrList<Component> components; +}; + +ComponentBase::ComponentBase() + : d( new Private ) +{ +} + +ComponentBase::~ComponentBase() +{ + d->components.setAutoDelete( true ); + delete d; +} + +uint ComponentBase::components() { return d->components.count(); } +Component *ComponentBase::component( uint n ) { return d->components.at( n ); } + +Component *ComponentBase::componentAt( const QPoint &pt ) +{ + for ( uint n = 0; n < components(); ++n ) + { + if ( component( n )->rect().contains( pt ) ) + { + if ( Component *comp = component( n )->componentAt( pt ) ) + return comp; + return component( n ); + } + } + return 0; +} + +void ComponentBase::componentAdded( Component *component ) +{ + d->components.append( component ); +} + +void ComponentBase::componentRemoved( Component *component ) +{ + //TODO: make sure the component is in d->components once and only once. + // if not, the situation is best referred to as 'very very broken indeed'. + d->components.remove( component ); +} + +void ComponentBase::clear() +{ + /* I'm switching setAutoDelete back and forth instead of turning it + * on permenantly, because original author of this class set it to + * auto delete in the dtor, that might have a reason that I can't + * imagine right now */ + bool tmp = d->components.autoDelete(); + d->components.setAutoDelete( true ); + d->components.clear(); + d->components.setAutoDelete( tmp ); +} + +void ComponentBase::componentResized( Component * ) +{ +} + +std::pair<QString,QRect> ComponentBase::toolTip( const QPoint &relativePos ) +{ + for ( uint n = 0; n < components(); ++n ) + if ( component( n )->rect().contains( relativePos ) ) + return component( n )->toolTip( relativePos ); + + return std::make_pair( QString::null, QRect() ); +} + +void ComponentBase::updateAnimationPosition( int p, int s ) +{ + for ( uint n = 0; n < components(); ++n ) + { + Component *comp = component( n ); + QRect start = comp->startRect(); + QRect target = comp->targetRect(); + QRect rc( start.left() + ((target.left() - start.left()) * p) / s, + start.top() + ((target.top() - start.top()) * p) / s, + start.width() + ((target.width() - start.width()) * p) / s, + start.height() + ((target.height() - start.height()) * p) / s ); + comp->setRect( rc ); + comp->updateAnimationPosition( p, s ); + } +} + +// Component -------- + +class Component::Private +{ +public: + Private( ComponentBase *parent ) + : parent( parent ), minWidth( 0 ), minHeight( 0 ) + , growHoriz( false ), growVert( false ) + , tipSource( 0 ) + { + } + ComponentBase *parent; + QRect rect; + QRect startRect, targetRect; + int minWidth, minHeight; + bool growHoriz, growVert; + bool show; /** @since 23-03-2005 */ + ToolTipSource *tipSource; +}; + +Component::Component( ComponentBase *parent ) + : d( new Private( parent ) ) +{ + d->parent->componentAdded( this ); + d->show = true; +} + +int Component::RTTI = Rtti_Component; + +Component::~Component() +{ + d->parent->componentRemoved( this ); + delete d; +} + + +void Component::hide() +{ + d->show = false; +} + +void Component::show() +{ + d->show = true; +} + +bool Component::isShown() +{ + return d->show; +} + +bool Component::isHidden() +{ + return !d->show; +} + +void Component::setToolTipSource( ToolTipSource *source ) +{ + d->tipSource = source; +} + +std::pair<QString,QRect> Component::toolTip( const QPoint &relativePos ) +{ + if ( !d->tipSource ) + return ComponentBase::toolTip( relativePos ); + + QRect rc = rect(); + QString result = (*d->tipSource)( this, relativePos, rc ); + return std::make_pair(result, rc); +} + +QRect Component::rect() { return d->rect; } +QRect Component::startRect() { return d->startRect; } +QRect Component::targetRect() { return d->targetRect; } + +int Component::minWidth() { return d->minWidth; } +int Component::minHeight() { return d->minHeight; } +int Component::widthForHeight( int ) { return minWidth(); } +int Component::heightForWidth( int ) { return minHeight(); } + +bool Component::setMinWidth( int width ) +{ + if ( d->minWidth == width ) return false; + d->minWidth = width; + + d->parent->componentResized( this ); + return true; +} +bool Component::setMinHeight( int height ) +{ + if ( d->minHeight == height ) return false; + d->minHeight = height; + + d->parent->componentResized( this ); + return true; +} + +void Component::layout( const QRect &newRect ) +{ + if ( rect().isNull() ) + d->startRect = QRect( newRect.topLeft(), newRect.topLeft() ); + else + d->startRect = rect(); + d->targetRect = newRect; + //kdDebug(14000) << k_funcinfo << "At " << rect << endl; +} + +void Component::setRect( const QRect &rect ) +{ + d->rect = rect; +} + +void Component::paint( QPainter *painter, const QColorGroup &cg ) +{ + /*painter->setPen( Qt::red ); + painter->drawRect( rect() );*/ + for ( uint n = 0; n < components(); ++n ) + { + if( component( n )->isShown() ) + component( n )->paint( painter, cg ); + } +} + +void Component::repaint() +{ + d->parent->repaint(); +} + +void Component::relayout() +{ + d->parent->relayout(); +} + +void Component::componentAdded( Component *component ) +{ + ComponentBase::componentAdded( component ); + //update( Relayout ); +} + +void Component::componentRemoved( Component *component ) +{ + ComponentBase::componentRemoved( component ); + //update( Relayout ); +} + +// BoxComponent -------- + +class BoxComponent::Private +{ +public: + Private( BoxComponent::Direction dir ) : direction( dir ) {} + BoxComponent::Direction direction; + + static const int padding = 2; +}; + +BoxComponent::BoxComponent( ComponentBase *parent, Direction dir ) + : Component( parent ), d( new Private( dir ) ) +{ +} + +int BoxComponent::RTTI = Rtti_BoxComponent; + +BoxComponent::~BoxComponent() +{ + delete d; +} + +int BoxComponent::widthForHeight( int height ) +{ + if ( d->direction != Horizontal ) + { + int width = 0; + for ( uint n = 0; n < components(); ++n ) + width = QMAX( width, component( n )->widthForHeight( height ) ); + return width; + } + else + { + int width = (components() - 1) * Private::padding; + for ( uint n = 0; n < components(); ++n ) + width += component( n )->widthForHeight( height ); + return width; + } +} + +int BoxComponent::heightForWidth( int width ) +{ + if ( d->direction == Horizontal ) + { + int height = 0; + for ( uint n = 0; n < components(); ++n ) + height = QMAX( height, component( n )->heightForWidth( width ) ); + return height; + } + else + { + int height = (components() - 1) * Private::padding; + for ( uint n = 0; n < components(); ++n ) + height += component( n )->heightForWidth( width ); + return height; + } +} + +void BoxComponent::calcMinSize() +{ + int sum = (components() - 1) * Private::padding, max = 0; + for ( uint n = 0; n < components(); ++n ) + { + Component *comp = component( n ); + if ( d->direction == Horizontal ) + { + max = QMAX( max, comp->minHeight() ); + sum += comp->minWidth(); + } + else + { + max = QMAX( max, comp->minWidth() ); + sum += comp->minHeight(); + } + } + + bool sizeChanged = false; + if ( d->direction == Horizontal ) + { + if ( setMinWidth( sum ) ) sizeChanged = true; + if ( setMinHeight( max ) ) sizeChanged = true; + } + else + { + if ( setMinWidth( max ) ) sizeChanged = true; + if ( setMinHeight( sum ) ) sizeChanged = true; + } + + if ( sizeChanged ) + repaint(); + else + relayout(); +} + +void BoxComponent::layout( const QRect &rect ) +{ + Component::layout( rect ); + + bool horiz = (d->direction == Horizontal); + int fixedSize = 0; + for ( uint n = 0; n < components(); ++n ) + { + Component *comp = component( n ); + if ( horiz ) + fixedSize += comp->minWidth(); + else + fixedSize += comp->minHeight(); + } + + // remaining space after all fixed items have been allocated + int padding = Private::padding; + + // ensure total is at least minXXX. the only time the rect + // will be smaller than that is when we don't fit, and in + // that cases we should pretend that we're wide/high enough. + int total; + if ( horiz ) + total = QMAX( rect.width(), minWidth() ); + else + total = QMAX( rect.height(), minHeight() ); + + int remaining = total - fixedSize - padding * (components() - 1); + + // finally, lay everything out + int pos = 0; + for ( uint n = 0; n < components(); ++n ) + { + Component *comp = component( n ); + + QRect rc; + if ( horiz ) + { + rc.setLeft( rect.left() + pos ); + rc.setTop( rect.top() ); + rc.setHeight( rect.height() ); + int minWidth = comp->minWidth(); + int desiredWidth = comp->widthForHeight( rect.height() ); + rc.setWidth( QMIN( remaining + minWidth, desiredWidth ) ); + pos += rc.width(); + remaining -= rc.width() - minWidth; + } + else + { + rc.setLeft( rect.left() ); + rc.setTop( rect.top() + pos ); + rc.setWidth( rect.width() ); + int minHeight = comp->minHeight(); + int desiredHeight = comp->heightForWidth( rect.width() ); + rc.setHeight( QMIN( remaining + minHeight, desiredHeight ) ); + pos += rc.height(); + remaining -= rc.height() - minHeight; + } + comp->layout( rc & rect ); + pos += padding; + } +} + +void BoxComponent::componentAdded( Component *component ) +{ + Component::componentAdded( component ); + calcMinSize(); +} + +void BoxComponent::componentRemoved( Component *component ) +{ + Component::componentRemoved( component ); + calcMinSize(); +} + +void BoxComponent::componentResized( Component *component ) +{ + Component::componentResized( component ); + calcMinSize(); +} + +// ImageComponent -------- + +class ImageComponent::Private +{ +public: + QPixmap image; +}; + +ImageComponent::ImageComponent( ComponentBase *parent ) + : Component( parent ), d( new Private ) +{ +} + +int ImageComponent::RTTI = Rtti_ImageComponent; + +ImageComponent::ImageComponent( ComponentBase *parent, int minW, int minH ) + : Component( parent ), d( new Private ) +{ + setMinWidth( minW ); + setMinHeight( minH ); + repaint(); +} + +ImageComponent::~ImageComponent() +{ + delete d; +} + +QPixmap ImageComponent::pixmap() +{ + return d->image; +} + +void ImageComponent::setPixmap( const QPixmap &img, bool adjustSize) +{ + d->image = img; + if ( adjustSize ) + { + setMinWidth( img.width() ); + setMinHeight( img.height() ); + } + repaint(); +} + +static QPoint operator+( const QPoint &pt, const QSize &sz ) +{ + return QPoint( pt.x() + sz.width(), pt.y() + sz.height() ); +} + +/*static QPoint operator+( const QSize &sz, const QPoint &pt ) +{ + return pt + sz; +}*/ + +void ImageComponent::paint( QPainter *painter, const QColorGroup & ) +{ + QRect ourRc = rect(); + QRect rc = d->image.rect(); + // center rc within our rect + rc.moveTopLeft( ourRc.topLeft() + (ourRc.size() - rc.size()) / 2 ); + // paint, shrunk to be within our rect + painter->drawPixmap( rc & ourRc, d->image ); +} + +void ImageComponent::scale( int w, int h, QImage::ScaleMode mode ) +{ + QImage im = d->image.convertToImage(); + setPixmap( QPixmap( im.smoothScale( w, h, mode ) ) ); +} +// TextComponent + +class TextComponent::Private +{ +public: + Private() : customColor( false ) {} + QString text; + bool customColor; + QColor color; + QFont font; +}; + +TextComponent::TextComponent( ComponentBase *parent, const QFont &font, const QString &text ) + : Component( parent ), d( new Private ) +{ + setFont( font ); + setText( text ); +} + +int TextComponent::RTTI = Rtti_TextComponent; + +TextComponent::~TextComponent() +{ + delete d; +} + +QString TextComponent::text() +{ + return d->text; +} + +void TextComponent::setText( const QString &text ) +{ + if ( text == d->text ) return; + d->text = text; + relayout(); + calcMinSize(); +} + +QFont TextComponent::font() +{ + return d->font; +} + +void TextComponent::setFont( const QFont &font ) +{ + if ( font == d->font ) return; + d->font = font; + calcMinSize(); +} + +void TextComponent::calcMinSize() +{ + setMinWidth( 0 ); + + if ( !d->text.isEmpty() ) + setMinHeight( QFontMetrics( font() ).height() ); + else + setMinHeight( 0 ); + + repaint(); +} + +int TextComponent::widthForHeight( int ) +{ + // add 2 to place an extra gap between the text and things to its right. + // allegedly if this is not done the protocol icons overlap the text. + // i however have never seen this problem (which would almost certainly + // be a bug somewhere else). + return QFontMetrics( font() ).width( d->text ) + 2; +} + +QColor TextComponent::color() +{ + return d->customColor ? d->color : QColor(); +} + +void TextComponent::setColor( const QColor &color ) +{ + d->color = color; + d->customColor = true; + repaint(); +} + +void TextComponent::setDefaultColor() +{ + d->customColor = false; + repaint(); +} + +void TextComponent::paint( QPainter *painter, const QColorGroup &cg ) +{ + if ( d->customColor ) + painter->setPen( d->color ); + else + painter->setPen( cg.text() ); + QString dispStr = KStringHandler::rPixelSqueeze( d->text, QFontMetrics( font() ), rect().width() ); + painter->setFont( font() ); + painter->drawText( rect(), Qt::SingleLine, dispStr ); +} + +// DisplayNameComponent + +class DisplayNameComponent::Private +{ +public: + QString text; + QFont font; +}; + +DisplayNameComponent::DisplayNameComponent( ComponentBase *parent ) + : BoxComponent( parent ), d( new Private ) +{ +} + +int DisplayNameComponent::RTTI = Rtti_DisplayNameComponent; + +DisplayNameComponent::~DisplayNameComponent() +{ + delete d; +} + +void DisplayNameComponent::layout( const QRect &rect ) +{ + Component::layout( rect ); + + // finally, lay everything out + QRect rc; + int totalWidth = rect.width(); + int usedWidth = 0; + bool exceeded = false; + for ( uint n = 0; n < components(); ++n ) + { + Component *comp = component( n ); + if ( !exceeded ) + { + if ( ( usedWidth + comp->widthForHeight( rect.height() ) ) > totalWidth ) + { + exceeded = true; + // TextComponents can squeeze themselves + if ( comp->rtti() == Rtti_TextComponent ) + { + comp->show(); + comp->layout( QRect( usedWidth+ rect.left(), rect.top(), + totalWidth - usedWidth, + comp->heightForWidth( totalWidth - usedWidth ) ) ); + } else { + comp->hide(); + } + } + else + { + comp->show(); + comp->layout( QRect( usedWidth+ rect.left(), rect.top(), + comp->widthForHeight( rect.height() ), + comp->heightForWidth( rect.width() ) ) ); + } + usedWidth+= comp->widthForHeight( rect.height() ); + } + else + { + // Shall we implement a hide()/show() in Component class ? + comp->hide(); + } + } +} + +void DisplayNameComponent::setText( const QString& text ) +{ + if ( d->text == text ) + return; + d->text = text; + + redraw(); +} + +void DisplayNameComponent::redraw() +{ + QColor color; + for ( uint n = 0; n < components(); ++n ) + if( component( n )->rtti() == Rtti_TextComponent ) + { + ((TextComponent*)component(n))->color(); + } + + QValueList<Kopete::Emoticons::Token> tokens; + QValueList<Kopete::Emoticons::Token>::const_iterator token; + + clear(); // clear childs + + tokens = Kopete::Emoticons::tokenizeEmoticons( d->text ); + ImageComponent *ic; + TextComponent *t; + + QFontMetrics fontMetrics( d->font ); + int fontHeight = fontMetrics.height(); + for ( token = tokens.begin(); token != tokens.end(); ++token ) + { + switch ( (*token).type ) + { + case Kopete::Emoticons::Text: + t = new TextComponent( this, d->font, (*token).text ); + break; + case Kopete::Emoticons::Image: + ic = new ImageComponent( this ); + ic->setPixmap( QPixmap( (*token).picPath ) ); + ic->scale( INT_MAX, fontHeight, QImage::ScaleMin ); + break; + default: + kdDebug( 14010 ) << k_funcinfo << "This should have not happened!" << endl; + } + } + + if(color.isValid()) + setColor( color ); +} + +void DisplayNameComponent::setFont( const QFont& font ) +{ + for ( uint n = 0; n < components(); ++n ) + if( component( n )->rtti() == Rtti_TextComponent ) + ((TextComponent*)component(n))->setFont( font ); + d->font = font; +} + +void DisplayNameComponent::setColor( const QColor& color ) +{ + for ( uint n = 0; n < components(); ++n ) + if( component( n )->rtti() == Rtti_TextComponent ) + ((TextComponent*)component(n))->setColor( color ); +} + +void DisplayNameComponent::setDefaultColor() +{ + for ( uint n = 0; n < components(); ++n ) + if( component( n )->rtti() == Rtti_TextComponent ) + ((TextComponent*)component(n))->setDefaultColor(); +} + +QString DisplayNameComponent::text() +{ + return d->text; +} + +// HSpacerComponent -------- + +HSpacerComponent::HSpacerComponent( ComponentBase *parent ) + : Component( parent ) +{ + setMinWidth( 0 ); + setMinHeight( 0 ); +} + +int HSpacerComponent::RTTI = Rtti_HSpacerComponent; + +int HSpacerComponent::widthForHeight( int ) +{ + return INT_MAX; +} + +// VSpacerComponent -------- + +VSpacerComponent::VSpacerComponent( ComponentBase *parent ) + : Component( parent ) +{ + setMinWidth( 0 ); + setMinHeight( 0 ); +} + +int VSpacerComponent::RTTI = Rtti_VSpacerComponent; + +int VSpacerComponent::heightForWidth( int ) +{ + return INT_MAX; +} + +////////////////// ContactComponent ///////////////////////// + +class ContactComponent::Private +{ +public: + Kopete::Contact *contact; + int iconSize; +}; + +ContactComponent::ContactComponent( ComponentBase *parent, Kopete::Contact *contact, int iconSize) : ImageComponent( parent ) , d( new Private ) +{ + d->contact = contact; + d->iconSize = iconSize; + updatePixmap(); +} + +ContactComponent::~ContactComponent() +{ + delete d; +} + +void ContactComponent::updatePixmap() +{ + setPixmap( contact()->onlineStatus().iconFor( contact(), d->iconSize ) ); +} +Kopete::Contact *ContactComponent::contact() +{ + return d->contact; +} + +// we don't need to use a tooltip source here - this way is simpler +std::pair<QString,QRect> ContactComponent::toolTip( const QPoint &/*relativePos*/ ) +{ + return std::make_pair(d->contact->toolTip(),rect()); +} + +////////////////// SpacerComponent ///////////////////////// + +SpacerComponent::SpacerComponent( ComponentBase *parent, int w, int h ) : Component( parent ) +{ + setMinWidth(w); + setMinHeight(h); +} + +// Item -------- + +/** + * A periodic timer intended to be shared amongst multiple objects. Will run only + * if an object is attached to it. + */ +class SharedTimer : private QTimer +{ + int period; + int users; +public: + SharedTimer( int period ) : period(period), users(0) {} + void attach( QObject *target, const char *slot ) + { + connect( this, SIGNAL(timeout()), target, slot ); + if( users++ == 0 ) + start( period ); + //kdDebug(14000) << "SharedTimer::attach: users is now " << users << "\n"; + } + void detach( QObject *target, const char *slot ) + { + disconnect( this, SIGNAL(timeout()), target, slot ); + if( --users == 0 ) + stop(); + //kdDebug(14000) << "SharedTimer::detach: users is now " << users << "\n"; + } +}; + +class SharedTimerRef +{ + SharedTimer &timer; + QObject * const object; + const char * const slot; + bool attached; +public: + SharedTimerRef( SharedTimer &timer, QObject *obj, const char *slot ) + : timer(timer), object(obj), slot(slot), attached(false) + { + } + void start() + { + if( attached ) return; + timer.attach( object, slot ); + attached = true; + } + void stop() + { + if( !attached ) return; + timer.detach( object, slot ); + attached = false; + } + bool isActive() + { + return attached; + } +}; + +class Item::Private +{ +public: + Private( Item *item ) + : layoutAnimateTimer( theLayoutAnimateTimer(), item, SLOT( slotLayoutAnimateItems() ) ) + , animateLayout( true ), opacity( 1.0 ) + , visibilityTimer( theVisibilityTimer(), item, SLOT( slotUpdateVisibility() ) ) + , visibilityLevel( 0 ), visibilityTarget( false ), searchMatch( true ) + { + } + + QTimer layoutTimer; + + //QTimer layoutAnimateTimer; + SharedTimerRef layoutAnimateTimer; + SharedTimer &theLayoutAnimateTimer() + { + static SharedTimer timer( 10 ); + return timer; + } + + bool animateLayout; + int layoutAnimateSteps; + static const int layoutAnimateStepsTotal = 10; + + float opacity; + + //QTimer visibilityTimer; + SharedTimerRef visibilityTimer; + SharedTimer &theVisibilityTimer() + { + static SharedTimer timer( 40 ); + return timer; + } + + int visibilityLevel; + bool visibilityTarget; + static const int visibilityFoldSteps = 7; +#ifdef HAVE_XRENDER + static const int visibilityFadeSteps = 7; +#else + static const int visibilityFadeSteps = 0; +#endif + static const int visibilityStepsTotal = visibilityFoldSteps + visibilityFadeSteps; + + bool searchMatch; + + static bool animateChanges; + static bool fadeVisibility; + static bool foldVisibility; +}; + +bool Item::Private::animateChanges = true; +bool Item::Private::fadeVisibility = true; +bool Item::Private::foldVisibility = true; + +Item::Item( QListViewItem *parent, QObject *owner, const char *name ) + : QObject( owner, name ), KListViewItem( parent ), d( new Private(this) ) +{ + initLVI(); +} + +Item::Item( QListView *parent, QObject *owner, const char *name ) + : QObject( owner, name ), KListViewItem( parent ), d( new Private(this) ) +{ + initLVI(); +} + +Item::~Item() +{ + delete d; +} + +void Item::setEffects( bool animation, bool fading, bool folding ) +{ + Private::animateChanges = animation; + Private::fadeVisibility = fading; + Private::foldVisibility = folding; +} + +void Item::initLVI() +{ + connect( listView()->header(), SIGNAL( sizeChange( int, int, int ) ), SLOT( slotColumnResized() ) ); + connect( &d->layoutTimer, SIGNAL( timeout() ), SLOT( slotLayoutItems() ) ); + //connect( &d->layoutAnimateTimer, SIGNAL( timeout() ), SLOT( slotLayoutAnimateItems() ) ); + //connect( &d->visibilityTimer, SIGNAL( timeout() ), SLOT( slotUpdateVisibility() ) ); + setVisible( false ); + setTargetVisibility( true ); +} + +void Item::slotColumnResized() +{ + scheduleLayout(); + // if we've been resized, don't animate the layout + d->animateLayout = false; +} + +void Item::scheduleLayout() +{ + // perform a delayed layout in order to speed it all up + if ( ! d->layoutTimer.isActive() ) + d->layoutTimer.start( 30, true ); +} + +void Item::slotLayoutItems() +{ + d->layoutTimer.stop(); + + for ( uint n = 0; n < components(); ++n ) + { + int width = listView()->columnWidth(n); + if ( n == 0 ) + { + int d = depth() + (listView()->rootIsDecorated() ? 1 : 0); + width -= d * listView()->treeStepSize(); + } + + int height = component( n )->heightForWidth( width ); + component( n )->layout( QRect( 0, 0, width, height ) ); + //kdDebug(14000) << k_funcinfo << "Component " << n << " is " << width << " x " << height << endl; + } + + if ( Private::animateChanges && d->animateLayout && !d->visibilityTimer.isActive() ) + { + d->layoutAnimateTimer.start(); + //if ( !d->layoutAnimateTimer.isActive() ) + // d->layoutAnimateTimer.start( 10 ); + d->layoutAnimateSteps = 0; + } + else + { + d->layoutAnimateSteps = Private::layoutAnimateStepsTotal; + d->animateLayout = true; + } + slotLayoutAnimateItems(); +} + +void Item::slotLayoutAnimateItems() +{ + if ( ++d->layoutAnimateSteps >= Private::layoutAnimateStepsTotal ) + d->layoutAnimateTimer.stop(); + + const int s = Private::layoutAnimateStepsTotal; + const int p = QMIN( d->layoutAnimateSteps, s ); + + updateAnimationPosition( p, s ); + setHeight(0); + repaint(); +} + +float Item::opacity() +{ + return d->opacity; +} + +void Item::setOpacity( float opacity ) +{ + if ( d->opacity == opacity ) return; + d->opacity = opacity; + repaint(); +} + +void Item::setSearchMatch( bool match ) +{ + d->searchMatch = match; + + if ( !match ) + setVisible( false ); + else + { + kdDebug(14000) << k_funcinfo << " match: " << match << ", vis timer active: " << d->visibilityTimer.isActive() + << ", target visibility: " << targetVisibility() << endl; + if ( d->visibilityTimer.isActive() ) + setVisible( true ); + else + setVisible( targetVisibility() ); + } +} + +bool Item::targetVisibility() +{ + return d->visibilityTarget; +} + +void Item::setTargetVisibility( bool vis ) +{ + if ( d->visibilityTarget == vis ) + { + // in case we're getting called because our parent was shown and + // we need to be rehidden + if ( !d->visibilityTimer.isActive() ) + setVisible( vis && d->searchMatch ); + return; + } + d->visibilityTarget = vis; + d->visibilityTimer.start(); + //d->visibilityTimer.start( 40 ); + if ( targetVisibility() ) + setVisible( d->searchMatch ); + slotUpdateVisibility(); +} + +void Item::slotUpdateVisibility() +{ + if ( targetVisibility() ) + ++d->visibilityLevel; + else + --d->visibilityLevel; + + if ( !Private::foldVisibility && !Private::fadeVisibility ) + d->visibilityLevel = targetVisibility() ? Private::visibilityStepsTotal : 0; + else if ( !Private::fadeVisibility && d->visibilityLevel >= Private::visibilityFoldSteps ) + d->visibilityLevel = targetVisibility() ? Private::visibilityStepsTotal : Private::visibilityFoldSteps - 1; + else if ( !Private::foldVisibility && d->visibilityLevel <= Private::visibilityFoldSteps ) + d->visibilityLevel = targetVisibility() ? Private::visibilityFoldSteps + 1 : 0; + + if ( d->visibilityLevel >= Private::visibilityStepsTotal ) + { + d->visibilityLevel = Private::visibilityStepsTotal; + d->visibilityTimer.stop(); + } + else if ( d->visibilityLevel <= 0 ) + { + d->visibilityLevel = 0; + d->visibilityTimer.stop(); + setVisible( false ); + } + setHeight( 0 ); + repaint(); +} + +void Item::repaint() +{ + // if we're about to relayout, don't bother painting yet. + if ( d->layoutTimer.isActive() ) + return; + listView()->repaintItem( this ); +} + +void Item::relayout() +{ + scheduleLayout(); +} + +void Item::setup() +{ + KListViewItem::setup(); + slotLayoutItems(); +} + +void Item::setHeight( int ) +{ + int minHeight = 0; + for ( uint n = 0; n < components(); ++n ) + minHeight = QMAX( minHeight, component( n )->rect().height() ); + //kdDebug(14000) << k_funcinfo << "Height is " << minHeight << endl; + if ( Private::foldVisibility && d->visibilityTimer.isActive() ) + { + int vis = QMIN( d->visibilityLevel, Private::visibilityFoldSteps ); + minHeight = (minHeight * vis) / Private::visibilityFoldSteps; + } + KListViewItem::setHeight( minHeight ); +} + +int Item::width( const QFontMetrics &, const QListView *lv, int c ) const +{ + // Qt computes the itemRect from this. we want the whole item to be + // clickable, so we return the widest we could possibly be. + return lv->header()->sectionSize( c ); +} + +void Item::paintCell( QPainter *p, const QColorGroup &cg, int column, int width, int align ) +{ + QPixmap back( width, height() ); + QPainter paint( &back ); + //KListViewItem::paintCell( &paint, cg, column, width, align ); + // PASTED FROM KLISTVIEWITEM: + // set the alternate cell background colour if necessary + QColorGroup _cg = cg; + if (isAlternate()) + if (listView()->viewport()->backgroundMode()==Qt::FixedColor) + _cg.setColor(QColorGroup::Background, static_cast< KListView* >(listView())->alternateBackground()); + else + _cg.setColor(QColorGroup::Base, static_cast< KListView* >(listView())->alternateBackground()); + // PASTED FROM QLISTVIEWITEM + { + QPainter *p = &paint; + + QListView *lv = listView(); + if ( !lv ) + return; + QFontMetrics fm( p->fontMetrics() ); + + // any text we render is done by the Components, not by this class, so make sure we've nothing to write + QString t; + + // removed text truncating code from Qt - we do that differently, further on + + int marg = lv->itemMargin(); + int r = marg; + // const QPixmap * icon = pixmap( column ); + + const BackgroundMode bgmode = lv->viewport()->backgroundMode(); + const QColorGroup::ColorRole crole = QPalette::backgroundRoleFromMode( bgmode ); + + if ( _cg.brush( crole ) != lv->colorGroup().brush( crole ) ) + p->fillRect( 0, 0, width, height(), _cg.brush( crole ) ); + else + { + // all copied from QListView::paintEmptyArea + + //lv->paintEmptyArea( p, QRect( 0, 0, width, height() ) ); + QStyleOption opt( lv->sortColumn(), 0 ); // ### hack; in 3.1, add a property in QListView and QHeader + QStyle::SFlags how = QStyle::Style_Default; + if ( lv->isEnabled() ) + how |= QStyle::Style_Enabled; + + lv->style().drawComplexControl( QStyle::CC_ListView, + p, lv, QRect( 0, 0, width, height() ), lv->colorGroup(), + how, QStyle::SC_ListView, QStyle::SC_None, + opt ); + } + + + + if ( isSelected() && + (column == 0 || lv->allColumnsShowFocus()) ) { + p->fillRect( r - marg, 0, width - r + marg, height(), + _cg.brush( QColorGroup::Highlight ) ); + // removed text pen setting code from Qt + } + + // removed icon drawing code from Qt + + // draw the tree gubbins + if ( multiLinesEnabled() && column == 0 && isOpen() && childCount() ) { + int textheight = fm.size( align, t ).height() + 2 * lv->itemMargin(); + textheight = QMAX( textheight, QApplication::globalStrut().height() ); + if ( textheight % 2 > 0 ) + textheight++; + if ( textheight < height() ) { + int w = lv->treeStepSize() / 2; + lv->style().drawComplexControl( QStyle::CC_ListView, p, lv, + QRect( 0, textheight, w + 1, height() - textheight + 1 ), _cg, + lv->isEnabled() ? QStyle::Style_Enabled : QStyle::Style_Default, + QStyle::SC_ListViewExpand, + (uint)QStyle::SC_All, QStyleOption( this ) ); + } + } + } + // END OF PASTE + + + //do you see a better way to tell the TextComponent we are selected ? - Olivier 2004-09-02 + if ( isSelected() ) + _cg.setColor(QColorGroup::Text , _cg.highlightedText() ); + + if ( Component *comp = component( column ) ) + comp->paint( &paint, _cg ); + paint.end(); + +#ifdef HAVE_XRENDER + QColor rgb = cg.base();//backgroundColor(); + float opac = 1.0; + if ( d->visibilityTimer.isActive() && Private::fadeVisibility ) + { + int vis = QMAX( d->visibilityLevel - Private::visibilityFoldSteps, 0 ); + opac = float(vis) / Private::visibilityFadeSteps; + } + opac *= opacity(); + const int alpha = 257 - int(opac * 257); + if ( alpha != 0 ) + { + XRenderColor clr = { alpha * rgb.red(), alpha * rgb.green(), alpha * rgb.blue(), alpha * 0xff }; + XRenderFillRectangle( back.x11Display(), PictOpOver, back.x11RenderHandle(), + &clr, 0, 0, width, height() ); + } +#endif + + p->drawPixmap( 0, 0, back ); +} + +void Item::componentAdded( Component *component ) +{ + ComponentBase::componentAdded( component ); + scheduleLayout(); +} + +void Item::componentRemoved( Component *component ) +{ + ComponentBase::componentRemoved( component ); + scheduleLayout(); +} + +void Item::componentResized( Component *component ) +{ + ComponentBase::componentResized( component ); + scheduleLayout(); +} + +} // END namespace ListView +} // END namespace UI +} // END namespace Kopete + +#include "kopetelistviewitem.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/kopetelistviewitem.h b/kopete/libkopete/ui/kopetelistviewitem.h new file mode 100644 index 00000000..5952c569 --- /dev/null +++ b/kopete/libkopete/ui/kopetelistviewitem.h @@ -0,0 +1,462 @@ +/* + kopetelistviewitem.h - Kopete's modular QListViewItems + + Copyright (c) 2005 by Engin AYDOGAN <engin@bzzzt.biz> + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_LISTVIEWITEM_H +#define KOPETE_LISTVIEWITEM_H + +#include <klistview.h> +#include <kopetecontact.h> + +#include <utility> +#include <qimage.h> + +class QPixmap; + +namespace Kopete { +namespace UI { +namespace ListView { + +class Component; + +class ComponentBase +{ +public: + ComponentBase(); + virtual ~ComponentBase() = 0; + + uint components(); + Component *component( uint n ); + Component *componentAt( const QPoint &pt ); + + /** Repaint this item */ + virtual void repaint() = 0; + /** Relayout this item */ + virtual void relayout() = 0; + + /** + * Get the tool tip string and rectangle for a tip request at position + * relativePos relative to this item. Queries the appropriate child component. + * + * @return a pair where the first element is the tooltip, and the second is + * the rectangle within the item for which the tip should be displayed. + */ + virtual std::pair<QString,QRect> toolTip( const QPoint &relativePos ); + +protected: + /** A child item has been added to this item */ + virtual void componentAdded( Component *component ); + /** A child item has been removed from this item */ + virtual void componentRemoved( Component *component ); + /** A child item has been resized */ + virtual void componentResized( Component *component ); + /** Remove all children */ + virtual void clear(); + + /** @internal animate items */ + void updateAnimationPosition( int p, int s ); +private: + class Private; + Private *d; + + // calls componentAdded and componentRemoved + friend class Component; +}; + +/** + * @author Richard Smith <kde@metafoo.co.uk> + */ +class ToolTipSource +{ +public: + /** + * Get the tooltip string and rect for a component + * + * @param component The component to get a tip for + * @param pt The point (relative to the list item) the mouse is at + * @param rect The tip will be removed when the mouse leaves this rect. + * Will initially be set to \p component's rect(). + */ + virtual QString operator() ( ComponentBase *component, const QPoint &pt, QRect &rect ) = 0; +}; + +/** + * This class represents a rectangular subsection of a ListItem. + * + * @author Richard Smith <kde@metafoo.co.uk> + */ +class Component : public ComponentBase +{ +protected: + Component( ComponentBase *parent ); +public: + virtual ~Component() = 0; + + /** + * Set the size and position of this item relative to the list view item. Should + * only be called by the containing item. + * @param rect the new rectangle this component will paint in, relative to the painter + * passed to the paint() function by the parent item. + */ + virtual void layout( const QRect &rect ); + + /** + * Paint this item, inside the rectangle returned by rect(). + * The default implementation calls paint on all children. + */ + virtual void paint( QPainter *painter, const QColorGroup &cg ); + + void repaint(); + void relayout(); + + /** + * @return the rect this component was allocated last time it was laid out + */ + QRect rect(); + /** + * Prevents this component to be drawn + */ + void hide(); + + /** + * Makes this component to be drawn + */ + void show(); + + bool isShown(); + bool isHidden(); + + /** + * Returns the smallest this component can become horizontally while still + * being useful. + */ + int minWidth(); + /** + * Returns the smallest this component can become vertically while still + * being useful. + */ + int minHeight(); + + /** + * Returns the width this component desires for a given @a height. By default + * this function returns minWidth(). + */ + virtual int widthForHeight( int height ); + /** + * Returns the height this component desires for a given @a width. By default + * this function returns minHeight(). + */ + virtual int heightForWidth( int width ); + + /** + * Set a tool tip source for this item. The tool tip source object is + * still owned by the caller, and must live for at least as long as + * this component. + */ + void setToolTipSource( ToolTipSource *source = 0 ); + + /** + * Get the tool tip string and rectangle for a tip request at position + * relativePos relative to this item. If a tooltip source is set, it will + * be used. Otherwise calls the base class. + * + * @return a pair where the first element is the tooltip, and the second is + * the rectangle within the item for which the tip should be displayed. + */ + std::pair<QString,QRect> toolTip( const QPoint &relativePos ); + + /** + * RTTI: Runtime Type Information + * Exactly the same as Qt's approach to identify types of + * QCanvasItems. + */ + + enum RttiValues { + Rtti_Component, Rtti_BoxComponent, Rtti_TextComponent, + Rtti_ImageComponent, Rtti_DisplayNameComponent, + Rtti_HSpacerComponent, Rtti_VSpacerComponent + }; + + static int RTTI; + virtual int rtti() const { return RTTI; } +protected: + /** + * Change the minimum width, in pixels, this component requires in order + * to be at all useful. Note: do not call this from your layout() function. + * @param width the minimum width + * @return true if the size has actually changed, false if it's been set to + * the existing values. if it returns true, you do not need to relayout, + * since the parent component will do that for you. + */ + bool setMinWidth( int width ); + /** + * Change the minimum height, in pixels, this component requires in order + * to be at all useful. Note: do not call this from your layout() function. + * @param height the minimum height + * @return true if the size has actually changed, false if it's been set to + * the existing values. If it returns true, you do not need to relayout, + * since the parent component will do that for you. + */ + bool setMinHeight( int height ); + + void componentAdded( Component *component ); + void componentRemoved( Component *component ); + +private: + // calls the three functions below + friend void ComponentBase::updateAnimationPosition( int p, int s ); + + // used for animation + void setRect( const QRect &rect ); + QRect startRect(); + QRect targetRect(); + + class Private; + Private *d; +}; + +class BoxComponent : public Component +{ +public: + enum Direction { Horizontal, Vertical }; + BoxComponent( ComponentBase *parent, Direction dir = Horizontal ); + ~BoxComponent(); + + void layout( const QRect &rect ); + + virtual int widthForHeight( int height ); + virtual int heightForWidth( int width ); + + static int RTTI; + virtual int rtti() const { return RTTI; } + +protected: + void componentAdded( Component *component ); + void componentRemoved( Component *component ); + void componentResized( Component *component ); + +private: + void calcMinSize(); + + class Private; + Private *d; +}; + +class TextComponent : public Component +{ +public: + TextComponent( ComponentBase *parent, const QFont &font = QFont(), const QString &text = QString::null ); + ~TextComponent(); + + QString text(); + void setText( const QString &text ); + + QFont font(); + void setFont( const QFont &font ); + + QColor color(); + void setColor( const QColor &color ); + void setDefaultColor(); + + int widthForHeight( int ); + + void paint( QPainter *painter, const QColorGroup &cg ); + + static int RTTI; + virtual int rtti() const { return RTTI; } + +private: + void calcMinSize(); + + class Private; + Private *d; +}; + +class ImageComponent : public Component +{ +public: + ImageComponent( ComponentBase *parent ); + ImageComponent( ComponentBase *parent, int minW, int minH ); + ~ImageComponent(); + + void setPixmap( const QPixmap &img, bool adjustSize = true); + QPixmap pixmap( void ); + + void paint( QPainter *painter, const QColorGroup &cg ); + + void scale( int w, int h, QImage::ScaleMode ); + static int RTTI; + virtual int rtti() const { return RTTI; } +private: + class Private; + Private *d; +}; + +/** + * ContactComponent + */ +class ContactComponent : public ImageComponent +{ +public: + ContactComponent( ComponentBase *parent, Kopete::Contact *contact, int iconSize); + ~ContactComponent(); + void updatePixmap(); + Kopete::Contact *contact(); + std::pair<QString,QRect> toolTip( const QPoint &relativePos ); +protected: + class Private; + Private *d; +}; + +/** + * SpacerComponent + */ +class SpacerComponent : public Component +{ +public: + SpacerComponent( ComponentBase *parent, int w, int h ); +}; + +/** + * DisplayNameComponent + */ + +class DisplayNameComponent : public BoxComponent +{ +public: + /** + * Constructor + */ + DisplayNameComponent( ComponentBase *parent ); + + /** + * Dtor + */ + ~DisplayNameComponent(); + void layout( const QRect& rect ); + + QString text(); + void setText( const QString& text ); + void setFont( const QFont& font ); + void setColor( const QColor& color ); + void setDefaultColor(); + static int RTTI; + virtual int rtti() const { return RTTI; } + /** + * reparse again for emoticon (call this when emoticon theme change) + */ + void redraw(); + +private: + class Private; + Private *d; +}; + +class HSpacerComponent : public Component +{ +public: + HSpacerComponent( ComponentBase *parent ); + int widthForHeight( int ); + + static int RTTI; + virtual int rtti() const { return RTTI; } +}; + +class VSpacerComponent : public Component +{ +public: + VSpacerComponent( ComponentBase *parent ); + int heightForWidth( int ); + + static int RTTI; + virtual int rtti() const { return RTTI; } +}; + +/** + * List-view item composed of Component items. Supports height-for-width, tooltips and + * some animation effects. + * + * @author Richard Smith <kde@metafoo.co.uk> + */ +class Item : public QObject, public KListViewItem, public ComponentBase +{ + Q_OBJECT +public: + Item( QListView *parent, QObject *owner = 0, const char *name = 0 ); + Item( QListViewItem *parent, QObject *owner = 0, const char *name = 0 ); + ~Item(); + + void repaint(); + void relayout(); + + void setup(); + virtual void paintCell( QPainter *p, const QColorGroup &cg, int column, int width, int align ); + //TODO: startRename(...) + + float opacity(); + void setOpacity( float alpha ); + + bool targetVisibility(); + void setTargetVisibility( bool vis ); + + /** + * Turn on and off certain visual effects for all Items. + * @param animation whether changes to items should be animated. + * @param fading whether requests to setTargetVisibility should cause fading of items. + * @param folding whether requests to setTargetVisibility should cause folding of items. + */ + static void setEffects( bool animation, bool fading, bool folding ); + + int width( const QFontMetrics & fm, const QListView * lv, int c ) const; + + /** + * Show or hide this item in a clean way depending on whether it matches + * the current quick search + * @param match If true, show or hide the item as normal. If false, hide the item immediately. + */ + virtual void setSearchMatch( bool match ); + +protected: + void componentAdded( Component *component ); + void componentRemoved( Component *component ); + void componentResized( Component *component ); + + void setHeight( int ); + +private: + void initLVI(); + void recalcHeight(); + void scheduleLayout(); + +private slots: + void slotColumnResized(); + void slotLayoutItems(); + void slotLayoutAnimateItems(); + void slotUpdateVisibility(); + +private: + class Private; + Private *d; +}; + +} // END namespace ListView +} // END namespace UI +} // END namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/kopetelistviewsearchline.cpp b/kopete/libkopete/ui/kopetelistviewsearchline.cpp new file mode 100644 index 00000000..a71d86c0 --- /dev/null +++ b/kopete/libkopete/ui/kopetelistviewsearchline.cpp @@ -0,0 +1,138 @@ +/* + kopetelistviewsearchline.cpp - a widget for performing quick searches on Kopete::ListViews + Based on code from KMail, copyright (c) 2004 Till Adam <adam@kde.org> + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetelistviewsearchline.h" +#include "kopetelistviewitem.h" +#include "kopetelistview.h" + +namespace Kopete { +namespace UI { +namespace ListView { + +SearchLine::SearchLine( QWidget *parent, ListView *listView, const char *name ) + : KListViewSearchLine( parent, listView, name ) +{ +} + +SearchLine::SearchLine(QWidget *parent, const char *name) + : KListViewSearchLine( parent, 0, name ) +{ +} + +SearchLine::~SearchLine() +{ +} + + +void SearchLine::updateSearch( const QString &s ) +{ + // we copy a huge chunk of code here simply in order to override + // the way items are shown/hidden. KSearchLine rudely + // calls setVisible() on items with no way to customise this behaviour. + + //BEGIN code from KSearchLine::updateSearch + if( !listView() ) + return; + + search = s.isNull() ? text() : s; + + // If there's a selected item that is visible, make sure that it's visible + // when the search changes too (assuming that it still matches). + + QListViewItem *currentItem = 0; + + switch( listView()->selectionMode() ) + { + case KListView::NoSelection: + break; + case KListView::Single: + currentItem = listView()->selectedItem(); + break; + default: + for( QListViewItemIterator it(listView(), QListViewItemIterator::Selected | QListViewItemIterator::Visible); + it.current() && !currentItem; ++it ) + { + if( listView()->itemRect( it.current() ).isValid() ) + currentItem = it.current(); + } + } + + if( keepParentsVisible() ) + checkItemParentsVisible( listView()->firstChild() ); + else + checkItemParentsNotVisible(); + + if( currentItem ) + listView()->ensureItemVisible( currentItem ); + //END code from KSearchLine::updateSearch +} + +void SearchLine::checkItemParentsNotVisible() +{ + //BEGIN code from KSearchLine::checkItemParentsNotVisible + QListViewItemIterator it( listView() ); + for( ; it.current(); ++it ) + { + QListViewItem *item = it.current(); + if( itemMatches( item, search ) ) + setItemVisible( item, true ); + else + setItemVisible( item, false ); + } + //END code from KSearchLine::checkItemParentsNotVisible +} + +bool SearchLine::checkItemParentsVisible( QListViewItem *item ) +{ + //BEGIN code from KSearchLine::checkItemParentsVisible + bool visible = false; + for( ; item; item = item->nextSibling() ) { + if( ( item->firstChild() && checkItemParentsVisible( item->firstChild() ) ) || + itemMatches( item, search ) ) + { + setItemVisible( item, true ); + // OUCH! this operation just became exponential-time. + // however, setting an item visible sets all its descendents + // visible too, which we definitely don't want. + // plus, in Kopete the nesting is never more than 2 deep, + // so this really just doubles the runtime, if that. + // this still can be done in O(n) time by a mark-set process, + // but that's overkill in our case. + checkItemParentsVisible( item->firstChild() ); + visible = true; + } + else + setItemVisible( item, false ); + } + return visible; + //END code from KSearchLine::checkItemParentsVisible +} + +void SearchLine::setItemVisible( QListViewItem *it, bool b ) +{ + if( Item *item = dynamic_cast<Item*>( it ) ) + item->setSearchMatch( b ); + else + it->setVisible( b ); +} + +} // namespace ListView +} // namespace UI +} // namespace Kopete + +#include "kopetelistviewsearchline.moc" diff --git a/kopete/libkopete/ui/kopetelistviewsearchline.h b/kopete/libkopete/ui/kopetelistviewsearchline.h new file mode 100644 index 00000000..a453b844 --- /dev/null +++ b/kopete/libkopete/ui/kopetelistviewsearchline.h @@ -0,0 +1,66 @@ +/* + kopetelistviewsearchline.h - a widget for performing quick searches of Kopete::ListViews + + Copyright (c) 2004 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2004 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETELISTVIEWSEARCHLINE_H +#define KOPETELISTVIEWSEARCHLINE_H + +#include <klistviewsearchline.h> + +namespace Kopete { +namespace UI { +namespace ListView { + +class ListView; + +class SearchLine : public KListViewSearchLine +{ + Q_OBJECT +public: + /** + * Constructs a SearchLine with \a listView being the + * ListView to be filtered. + * + * If \a listView is null then the widget will be disabled until a listview + * is set with setListView(). + */ + SearchLine( QWidget *parent, ListView *listView = 0, const char *name = 0 ); + /** + * Constructs a SearchLine without any ListView to filter. The + * KListView object has to be set later with setListView(). + */ + SearchLine(QWidget *parent, const char *name); + /** + * Destroys the SearchLine. + */ + ~SearchLine(); + + void updateSearch( const QString &s ); + +protected: + virtual void checkItemParentsNotVisible(); + virtual bool checkItemParentsVisible( QListViewItem *it ); + virtual void setItemVisible( QListViewItem *it, bool visible ); + +private: + QString search; +}; + +} // end namespace ListView +} // end namespace UI +} // end namespace Kopete + +#endif diff --git a/kopete/libkopete/ui/kopetepassworddialog.ui b/kopete/libkopete/ui/kopetepassworddialog.ui new file mode 100644 index 00000000..7ba4dff9 --- /dev/null +++ b/kopete/libkopete/ui/kopetepassworddialog.ui @@ -0,0 +1,109 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>KopetePasswordDialog</class> +<author>Olivier Goffart</author> +<widget class="QWidget"> + <property name="name"> + <cstring>KopetePasswordDialog</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>472</width> + <height>117</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout3</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>m_image</cstring> + </property> + </widget> + <widget class="KActiveLabel"> + <property name="name"> + <cstring>m_text</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="textFormat"> + <enum>RichText</enum> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel3</cstring> + </property> + <property name="text"> + <string>&Password:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>m_password</cstring> + </property> + </widget> + <widget class="KPasswordEdit"> + <property name="name"> + <cstring>m_password</cstring> + </property> + </widget> + </hbox> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>m_save_passwd</cstring> + </property> + <property name="text"> + <string>&Remember password</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>MinimumExpanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </vbox> +</widget> +<tabstops> + <tabstop>m_password</tabstop> + <tabstop>m_save_passwd</tabstop> +</tabstops> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kopete/libkopete/ui/kopetepasswordwidget.cpp b/kopete/libkopete/ui/kopetepasswordwidget.cpp new file mode 100644 index 00000000..2345f103 --- /dev/null +++ b/kopete/libkopete/ui/kopetepasswordwidget.cpp @@ -0,0 +1,132 @@ +/* + kopetepasswordwidget.cpp - widget for modifying a Kopete::Password + + Copyright (c) 2003 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetepasswordwidget.h" +#include "kopetepassword.h" + +#include <kpassdlg.h> + +#include <qcheckbox.h> + +class Kopete::UI::PasswordWidget::Private +{ +public: + uint maxLength; +}; + +Kopete::UI::PasswordWidget::PasswordWidget( QWidget *parent, const char *name, Kopete::Password *from ) + : KopetePasswordWidgetBase( parent, name ), d( new Private ) +{ + load( from ); +} + +Kopete::UI::PasswordWidget::~PasswordWidget() +{ + delete d; +} + +void Kopete::UI::PasswordWidget::load( Kopete::Password *source ) +{ + disconnect( mRemembered, SIGNAL( stateChanged( int ) ), this, SLOT( slotRememberChanged() ) ); + disconnect( mPassword, SIGNAL( textChanged( const QString & ) ), this, SIGNAL( changed() ) ); + disconnect( mRemembered, SIGNAL( stateChanged( int ) ), this, SIGNAL( changed() ) ); + + if ( source && source->remembered() ) + { + mRemembered->setTristate(); + mRemembered->setNoChange(); + source->requestWithoutPrompt( this, SLOT( receivePassword( const QString & ) ) ); + } + else + { + mRemembered->setTristate( false ); + mRemembered->setChecked( false ); + } + + if ( source ) + d->maxLength = source->maximumLength(); + else + d->maxLength = 0; + + mPassword->setEnabled( false ); + + connect( mRemembered, SIGNAL( stateChanged( int ) ), this, SLOT( slotRememberChanged() ) ); + connect( mPassword, SIGNAL( textChanged( const QString & ) ), this, SIGNAL( changed() ) ); + connect( mRemembered, SIGNAL( stateChanged( int ) ), this, SIGNAL( changed() ) ); + + emit changed(); +} + +void Kopete::UI::PasswordWidget::slotRememberChanged() +{ + mRemembered->setTristate( false ); + mPassword->setEnabled( mRemembered->isChecked() ); +} + +void Kopete::UI::PasswordWidget::receivePassword( const QString &pwd ) +{ + // pwd == null could mean user declined to open wallet + // don't uncheck the remembered field in this case. + if ( !pwd.isNull() && mRemembered->state() == QButton::NoChange ) + { + mRemembered->setChecked( true ); + setPassword( pwd ); + } +} + +void Kopete::UI::PasswordWidget::save( Kopete::Password *target ) +{ + if ( !target || mRemembered->state() == QButton::NoChange ) + return; + + if ( mRemembered->isChecked() ) + target->set( password() ); + else + target->set(); +} + +bool Kopete::UI::PasswordWidget::validate() +{ + if ( !mRemembered->isChecked() ) return true; + if ( d->maxLength == 0 ) return true; + return password().length() <= d->maxLength; +} + +QString Kopete::UI::PasswordWidget::password() const +{ + return QString::fromLocal8Bit( mPassword->password() ); +} + +bool Kopete::UI::PasswordWidget::remember() const +{ + return mRemembered->state() == QButton::On; +} + +void Kopete::UI::PasswordWidget::setPassword( const QString &pass ) +{ + // switch out of 'waiting for wallet' mode if we're in it + mRemembered->setTristate( false ); + + // fill in the password text + mPassword->erase(); + mPassword->insert( pass ); + mPassword->setEnabled( remember() ); +} + +#include "kopetepasswordwidget.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/kopetepasswordwidget.h b/kopete/libkopete/ui/kopetepasswordwidget.h new file mode 100644 index 00000000..b1c51a39 --- /dev/null +++ b/kopete/libkopete/ui/kopetepasswordwidget.h @@ -0,0 +1,109 @@ +/* + kopetepasswordwidget.cpp - widget for editing a Kopete::Password + + Copyright (c) 2003 by Richard Smith <kde@metafoo.co.uk> + + Kopete (c) 2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPASSWORDWIDGET_H +#define KOPETEPASSWORDWIDGET_H + +#include "kopetepasswordwidgetbase.h" +#include "kopete_export.h" + +namespace Kopete +{ + +class Password; + +namespace UI +{ + +/** + * @author Richard Smith <kde@metafoo.co.uk> + * This widget displays an editable password, including the Remember password checkbox. + * @todo This is NOT BC yet : it derives from a uic-generated class + */ +class KOPETE_EXPORT PasswordWidget : public KopetePasswordWidgetBase +{ + Q_OBJECT + +public: + /** + * Creates a Kopete::PasswordWidget. + * @param parent The widget to nest this one inside + * @param name The name of this QObject + * @param from The password to load the data for this widget from, or 0 if none + */ + PasswordWidget( QWidget *parent, const char *name = 0, Kopete::Password *from = 0 ); + ~PasswordWidget(); + + /** + * Loads the information stored in source into the widget + */ + void load( Kopete::Password *source ); + /** + * Saves the information in the widget into target + */ + void save( Kopete::Password *target ); + + /** + * Returns true if the information in the widget is valid, false if it is not. + * Currently the only way this can fail is if the password is too long. + * @todo this should return an enum of failures. + */ + bool validate(); + + /** + * Returns the string currently in the input box in the widget + */ + QString password() const; + /** + * Returns a boolean indicating whether the Remember Password checkbox is checked. + * Result is undefined if the Remember Password field is in the 'no change' state + * because the user has not (yet) opened the wallet. + */ + bool remember() const; + + /** + * Set the password stored in the widget. + * @param pass The text to place in the password field. + */ + void setPassword( const QString &pass ); + +signals: + /** + * Emitted when the information stored in this widget changes + */ + void changed(); + +public slots: + /** @internal */ + void receivePassword( const QString & ); + +private slots: + void slotRememberChanged(); + +private: + class Private; + Private *d; +}; + +} + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/kopetepasswordwidgetbase.ui b/kopete/libkopete/ui/kopetepasswordwidgetbase.ui new file mode 100644 index 00000000..5f6b665a --- /dev/null +++ b/kopete/libkopete/ui/kopetepasswordwidgetbase.ui @@ -0,0 +1,98 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>KopetePasswordWidgetBase</class> +<author>Richard Smith <kde@metafoo.co.uk></author> +<widget class="QWidget"> + <property name="name"> + <cstring>KopetePasswordWidgetBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>497</width> + <height>50</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QCheckBox" row="0" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>mRemembered</cstring> + </property> + <property name="text"> + <string>Remember password</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Check this and enter your password below if you would like your password to be stored in your wallet, so Kopete does not have to ask you for it each time it is needed.</string> + </property> + </widget> + <spacer row="1" column="0"> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>16</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QLabel" row="1" column="1"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>Password:</string> + </property> + </widget> + <widget class="KPasswordEdit" row="1" column="2"> + <property name="name"> + <cstring>mPassword</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip" stdset="0"> + <string>Enter your password here.</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter your password here. If you would rather not save your password, uncheck the Remember password checkbox above; you will then be prompted for your password whenever it is needed.</string> + </property> + </widget> + </grid> +</widget> +<tabstops> + <tabstop>mRemembered</tabstop> +</tabstops> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kpassdlg.h</includehint> +</includehints> +</UI> diff --git a/kopete/libkopete/ui/kopetestdaction.cpp b/kopete/libkopete/ui/kopetestdaction.cpp new file mode 100644 index 00000000..e6731485 --- /dev/null +++ b/kopete/libkopete/ui/kopetestdaction.cpp @@ -0,0 +1,129 @@ +/* + kopetestdaction.cpp - Kopete Standard Actionds + + Copyright (c) 2001-2002 by Ryan Cumming <ryan@kde.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + + Kopete (c) 2001-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetestdaction.h" + +#include <qapplication.h> + +#include <kdebug.h> +#include <kdeversion.h> +#include <kguiitem.h> +#include <klocale.h> +#include <ksettings/dialog.h> +#include <kstdaction.h> +#include <kstdguiitem.h> +#include <kwin.h> +#include <kcmultidialog.h> + +#include "kopetecontactlist.h" +#include "kopetegroup.h" +#include "kopeteuiglobal.h" + +KSettings::Dialog *KopetePreferencesAction::s_settingsDialog = 0L; + +KopetePreferencesAction::KopetePreferencesAction( KActionCollection *parent, const char *name ) +#if KDE_IS_VERSION( 3, 3, 90 ) +: KAction( KStdGuiItem::configure(), 0, 0, 0, parent, name ) +#else +: KAction( KGuiItem( i18n( "&Configure Kopete..." ), + QString::fromLatin1( "configure" ) ), 0, 0, 0, parent, name ) +#endif +{ + connect( this, SIGNAL( activated() ), this, SLOT( slotShowPreferences() ) ); +} + +KopetePreferencesAction::~KopetePreferencesAction() +{ +} + +void KopetePreferencesAction::slotShowPreferences() +{ + // FIXME: Use static deleter - Martijn + if ( !s_settingsDialog ) + s_settingsDialog = new KSettings::Dialog( KSettings::Dialog::Static, Kopete::UI::Global::mainWidget() ); + s_settingsDialog->show(); + + s_settingsDialog->dialog()->raise(); + + KWin::activateWindow( s_settingsDialog->dialog()->winId() ); +} + +KAction * KopeteStdAction::preferences( KActionCollection *parent, const char *name ) +{ + return new KopetePreferencesAction( parent, name ); +} + +KAction * KopeteStdAction::chat( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "Start &Chat..." ), QString::fromLatin1( "mail_generic" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::sendMessage( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "&Send Single Message..." ), QString::fromLatin1( "mail_generic" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::contactInfo( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "User &Info" ), QString::fromLatin1( "messagebox_info" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::sendFile( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "Send &File..." ), QString::fromLatin1( "attach" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::viewHistory( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "View &History..." ), QString::fromLatin1( "history" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::addGroup( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "&Create Group..." ), QString::fromLatin1( "folder" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::changeMetaContact( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "Cha&nge Meta Contact..." ), QString::fromLatin1( "move" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::deleteContact( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "&Delete Contact" ), QString::fromLatin1( "delete_user" ), Qt::Key_Delete, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::changeAlias( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "Change A&lias..." ), QString::fromLatin1( "signature" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::blockContact( const QObject *recvr, const char *slot, QObject* parent, const char *name ) +{ + return new KAction( i18n( "&Block Contact" ), QString::fromLatin1( "player_pause" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::unblockContact( const QObject *recvr, const char *slot, QObject* parent, const char *name ) +{ + return new KAction( i18n( "Un&block Contact" ), QString::fromLatin1( "player_play" ), 0, recvr, slot, parent, name ); +} + +#include "kopetestdaction.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/kopetestdaction.h b/kopete/libkopete/ui/kopetestdaction.h new file mode 100644 index 00000000..8f06d296 --- /dev/null +++ b/kopete/libkopete/ui/kopetestdaction.h @@ -0,0 +1,119 @@ +/* + kopetestdaction.h - Kopete Standard Actionds + + Copyright (c) 2001-2002 by Ryan Cumming <bodnar42@phalynx.dhs.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETESTDACTION_H +#define KOPETESTDACTION_H + +#undef KDE_NO_COMPAT +#include <kaction.h> +#include <qobject.h> + +#include "kopete_export.h" + +/** + * @author Ryan Cumming <bodnar42@phalynx.dhs.org> + */ +class KOPETE_EXPORT KopeteStdAction +{ +public: + /** + * Standard action to start a chat + */ + static KAction *chat( const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0 ); + /** + * Standard action to send a single message + */ + static KAction *sendMessage(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to open a user info dialog + */ + static KAction *contactInfo(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to open a history dialog or something similar + */ + static KAction *viewHistory(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to initiate sending a file to a contact + */ + static KAction *sendFile(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to change a contacts @ref Kopete::MetaContact + */ + static KAction *changeMetaContact(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to add a group + */ + static KAction *addGroup(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to delete a contact + */ + static KAction *deleteContact(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to change a contact alias/nickname in your contactlist + */ + static KAction *changeAlias(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to block a contact + */ + static KAction *blockContact(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to unblock a contact + */ + static KAction *unblockContact(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + + /** + * Return an action to change the Kopete preferences. + * + * The object has no signal/slot, the prefs are automatically shown + */ + static KAction *preferences(KActionCollection *parent, const char *name = 0); +}; + + +namespace KSettings +{ + class Dialog; +} + +class KOPETE_EXPORT KopetePreferencesAction : public KAction +{ + Q_OBJECT + + public: + KopetePreferencesAction( KActionCollection *parent, const char *name = 0 ); + ~KopetePreferencesAction(); + + protected slots: + void slotShowPreferences(); + private: + static KSettings::Dialog *s_settingsDialog; +}; + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/kopeteview.cpp b/kopete/libkopete/ui/kopeteview.cpp new file mode 100644 index 00000000..91f1fa9c --- /dev/null +++ b/kopete/libkopete/ui/kopeteview.cpp @@ -0,0 +1,52 @@ +/* + kopeteview.cpp - View Abstract Class + + Copyright (c) 2003 by Jason Keirstead + Copyright (c) 2003 by Olivier Goffart <ogoffart @ kde.org> + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteview.h" + +KopeteView::KopeteView( Kopete::ChatSession *manager, Kopete::ViewPlugin *plugin ) + : m_manager(manager), m_plugin(plugin) +{ +} + +Kopete::ChatSession *KopeteView::msgManager() const +{ + return m_manager; +} + +void KopeteView::clear() +{ + //Do nothing +} + +void KopeteView::appendMessages(QValueList<Kopete::Message> msgs) +{ + QValueList<Kopete::Message>::iterator it; + for ( it = msgs.begin(); it != msgs.end(); ++it ) + { + appendMessage(*it); + } + +} + +Kopete::ViewPlugin *KopeteView::plugin() +{ + return m_plugin; +} + +KopeteView::~ KopeteView( ) +{ +} diff --git a/kopete/libkopete/ui/kopeteview.h b/kopete/libkopete/ui/kopeteview.h new file mode 100644 index 00000000..47320546 --- /dev/null +++ b/kopete/libkopete/ui/kopeteview.h @@ -0,0 +1,176 @@ +/* + kopeteview.h - View Manager + + Copyright (c) 2003 by Jason Keirstead <jason@keirstead.org> + Copyright (c) 2004 by Matt Rogers <matt.rogers@kdemail.net> + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + + +#ifndef KOPETEVIEW_H +#define KOPETEVIEW_H + +#include "kopetemessage.h" +#include <qvaluelist.h> +#include "kopete_export.h" + +namespace Kopete +{ + class ViewPlugin; +} + +/** + * @author Jason Keirstead + * + * Abstract parent class for all types of views used for messaging.These view objects + * are provided by a @ref Kopete::ViewPlugin + * + * @see Kopete::ViewPlugin + */ +class KOPETE_EXPORT KopeteView +{ + public: + /** + * constructor + */ + KopeteView( Kopete::ChatSession *manager, Kopete::ViewPlugin *parent ); + virtual ~KopeteView(); + + /** + * @brief Returns the message currently in the edit area + * @return The Kopete::Message object containing the message + */ + virtual Kopete::Message currentMessage() = 0; + /** + * Set the message that the view is currently editing. + * @param newMessage The Kopete::Message object containing the message to be edited. + */ + virtual void setCurrentMessage( const Kopete::Message &newMessage ) = 0; + + /** + * @brief Get the message manager + * @return The Kopete::ChatSession that the view is in communication with. + */ + Kopete::ChatSession *msgManager() const; + + /** + * @brief add a message to the view + * + * The message gets added at the end of the view and is automatically + * displayed. Classes that inherit from KopeteView should make this a slot. + */ + virtual void appendMessage( Kopete::Message & ) = 0; + + /** + * @brief append multiple messages to the view + * + * This function does the same thing as the above function but + * can be reimplemented if it is faster to apend several messages + * in the same time. + * + * The default implementation just call @ref appendMessage() X times + */ + virtual void appendMessages( QValueList<Kopete::Message> ); + + /** + * @brief Raises the view above other windows + * @param activate change the focus to the window + */ + virtual void raise(bool activate = false) = 0; + + /** + * @brief Clear the buffer + */ + virtual void clear(); + + /** + * @brief Make the view visible + * + * Makes the view visible if it is currently hidden. + */ + virtual void makeVisible() = 0; + + /** + * @brief Close this view + */ + virtual bool closeView( bool force = false ) = 0; + + /** + * @brief Get the current visibility of the view + * @return Whether the view is visible or not. + */ + virtual bool isVisible() = 0; + + /** + * @brief Get the view widget + * + * Can be reimplemented to return this if derived object is a widget + */ + virtual QWidget *mainWidget() = 0; + + /** + * @brief Inform the view the message was sent successfully + * + * This should be reimplemented as a SLOT in any derived objects + */ + virtual void messageSentSuccessfully() = 0; + + /** + * @brief Register a handler for the context menu + * + * Plugins should call this slot at view creation to register + * themselves as handlers for the context menu of this view. Plugins + * can attach to the viewCreated signal of KopeteMessageManagerFactory + * to know when views are created. + * + * A view does not need to implement this method unless they have context + * menus that can be extended + * + * @param target A target QObject for the contextMenuEvent signal of the view + * @param slot A slot that matches the signature ( QString&, KPopupMenu *) + */ + virtual void registerContextMenuHandler( QObject *target, const char*slot ){ Q_UNUSED(target); Q_UNUSED(slot); }; + + /** + * @brief Register a handler for the tooltip + * + * Plugins should call this slot at view creation to register + * themselves as handlers for the tooltip of this view. Plugins + * can attach to the viewCreated signal of KopeteMessageManagerFactory + * to know when views are created. + * + * A view does not need to impliment this method unless it has the ability + * to show tooltips + * + * @param target A target QObject for the contextMenuEvent signal of the view + * @param slot A slot that matches the signature ( QString&, KPopupMenu *) + */ + virtual void registerTooltipHandler( QObject *target, const char*slot ){ Q_UNUSED(target); Q_UNUSED(slot); }; + + /** + * @brief Returns the Kopete::ViewPlugin responsible for this view + * + * KopeteView objects are created by plugins. This returns a pointer to the plugin + * that created this view. You can use this to infer other information on this view + * and it's capabilities. + */ + Kopete::ViewPlugin *plugin(); + + protected: + /** + * a pointer to the Kopete::ChatSession given in the constructor + */ + Kopete::ChatSession *m_manager; + Kopete::ViewPlugin *m_plugin; +}; + +#endif diff --git a/kopete/libkopete/ui/kopeteviewplugin.cpp b/kopete/libkopete/ui/kopeteviewplugin.cpp new file mode 100644 index 00000000..b358e547 --- /dev/null +++ b/kopete/libkopete/ui/kopeteviewplugin.cpp @@ -0,0 +1,28 @@ +/* + kopeteviewplugin.cpp - View Manager + + Copyright (c) 2005 by Jason Keirstead <jason@keirstead.org> + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteviewplugin.h" + +Kopete::ViewPlugin::ViewPlugin( KInstance *instance, QObject *parent, const char *name ) : + Kopete::Plugin( instance, parent, name ) +{ + +} + +void Kopete::ViewPlugin::aboutToUnload() +{ + emit readyForUnload(); +} diff --git a/kopete/libkopete/ui/kopeteviewplugin.h b/kopete/libkopete/ui/kopeteviewplugin.h new file mode 100644 index 00000000..e7797d56 --- /dev/null +++ b/kopete/libkopete/ui/kopeteviewplugin.h @@ -0,0 +1,59 @@ +/* + kopeteviewplugin.h - View Manager + + Copyright (c) 2005 by Jason Keirstead <jason@keirstead.org> + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEVIEWPLUGIN_H +#define KOPETEVIEWPLUGIN_H + +#include "kopeteplugin.h" + +class KopeteView; + +namespace Kopete +{ + +class ChatSession; + +/** + * @author Jason Keirstead + * + * @brief Factory plugin for creating KopeteView objects. + * + * Kopete ships with two of these currently, a Chat Window view plugin, and + * an Email Window view plugin. + * + */ +class KOPETE_EXPORT ViewPlugin : public Plugin +{ + public: + /** + * @brief Create and initialize the plugin + */ + ViewPlugin( KInstance *instance, QObject *parent = 0L, const char *name = 0L ); + + /** + * @brief Creates a view to be associated with the passed in session + */ + virtual KopeteView *createView( ChatSession * /*session*/ ){ return 0L; }; + + /** + * @brief Reimplemented from Kopete::Plugin + */ + virtual void aboutToUnload(); +}; + +} + +#endif diff --git a/kopete/libkopete/ui/kopetewidgets.cpp b/kopete/libkopete/ui/kopetewidgets.cpp new file mode 100644 index 00000000..093ee48e --- /dev/null +++ b/kopete/libkopete/ui/kopetewidgets.cpp @@ -0,0 +1,131 @@ +/** +* This file was autogenerated by makekdewidgets. Any changes will be lost! +* The generated code in this file is licensed under the same license that the +* input file. +*/ +#include <qwidgetplugin.h> + +#include <kinstance.h> +#include <addressbooklinkwidget.h> +#include <kopetelistview.h> +#include <kopetelistviewsearchline.h> +#ifndef EMBED_IMAGES +#include <kstandarddirs.h> +#endif + +class KopeteWidgets : public QWidgetPlugin +{ +public: + KopeteWidgets(); + + virtual ~KopeteWidgets(); + + virtual QStringList keys() const + { + QStringList result; + for (WidgetInfos::ConstIterator it = m_widgets.begin(); it != m_widgets.end(); ++it) + result << it.key(); + return result; + } + + virtual QWidget *create(const QString &key, QWidget *parent = 0, const char *name = 0); + + virtual QIconSet iconSet(const QString &key) const + { +#ifdef EMBED_IMAGES + QPixmap pix(m_widgets[key].iconSet); +#else + QPixmap pix(locate( "data", + QString::fromLatin1("kopetewidgets/pics/") + m_widgets[key].iconSet)); +#endif + return QIconSet(pix); + } + + virtual bool isContainer(const QString &key) const { return m_widgets[key].isContainer; } + + virtual QString group(const QString &key) const { return m_widgets[key].group; } + + virtual QString includeFile(const QString &key) const { return m_widgets[key].includeFile; } + + virtual QString toolTip(const QString &key) const { return m_widgets[key].toolTip; } + + virtual QString whatsThis(const QString &key) const { return m_widgets[key].whatsThis; } +private: + struct WidgetInfo + { + QString group; +#ifdef EMBED_IMAGES + QPixmap iconSet; +#else + QString iconSet; +#endif + QString includeFile; + QString toolTip; + QString whatsThis; + bool isContainer; + }; + typedef QMap<QString, WidgetInfo> WidgetInfos; + WidgetInfos m_widgets; +}; +KopeteWidgets::KopeteWidgets() +{ + WidgetInfo widget; + + widget.group = QString::fromLatin1("Input (Kopete)"); +#ifdef EMBED_IMAGES + widget.iconSet = QPixmap(kopete__ui__addressbooklinkwidget_xpm); +#else + widget.iconSet = QString::fromLatin1("kopete__ui__addressbooklinkwidget.png"); +#endif + widget.includeFile = QString::fromLatin1("addressbooklinkwidget.h"); + widget.toolTip = QString::fromLatin1("Address Book Link Widget (Kopete)"); + widget.whatsThis = QString::fromLatin1("KABC::Addressee display/selector"); + widget.isContainer = false; + m_widgets.insert(QString::fromLatin1("Kopete::UI::AddressBookLinkWidget"), widget); + + widget.group = QString::fromLatin1("Views (Kopete)"); +#ifdef EMBED_IMAGES + widget.iconSet = QPixmap(kopete__ui__listview__listview_xpm); +#else + widget.iconSet = QString::fromLatin1("kopete__ui__listview__listview.png"); +#endif + widget.includeFile = QString::fromLatin1("kopetelistview.h"); + widget.toolTip = QString::fromLatin1("List View (Kopete)"); + widget.whatsThis = QString::fromLatin1("A component capable list view widget."); + widget.isContainer = false; + m_widgets.insert(QString::fromLatin1("Kopete::UI::ListView::ListView"), widget); + + widget.group = QString::fromLatin1("Input (Kopete)"); +#ifdef EMBED_IMAGES + widget.iconSet = QPixmap(kopete__ui__listview__searchline_xpm); +#else + widget.iconSet = QString::fromLatin1("kopete__ui__listview__searchline.png"); +#endif + widget.includeFile = QString::fromLatin1("kopetelistviewsearchline.h"); + widget.toolTip = QString::fromLatin1("List View Search Line (Kopete)"); + widget.whatsThis = QString::fromLatin1("Search line able to use Kopete custom list View."); + widget.isContainer = false; + m_widgets.insert(QString::fromLatin1("Kopete::UI::ListView::SearchLine"), widget); + + new KInstance("kopetewidgets"); +} +KopeteWidgets::~KopeteWidgets() +{ + +} +QWidget *KopeteWidgets::create(const QString &key, QWidget *parent, const char *name) +{ + + if (key == QString::fromLatin1("Kopete::UI::AddressBookLinkWidget")) + return new Kopete::UI::AddressBookLinkWidget(parent, name); + + if (key == QString::fromLatin1("Kopete::UI::ListView::ListView")) + return new Kopete::UI::ListView::ListView(parent, name); + + if (key == QString::fromLatin1("Kopete::UI::ListView::SearchLine")) + return new Kopete::UI::ListView::SearchLine(parent, 0, name); + + return 0; +} +KDE_Q_EXPORT_PLUGIN(KopeteWidgets) + diff --git a/kopete/libkopete/ui/metacontactselectorwidget.cpp b/kopete/libkopete/ui/metacontactselectorwidget.cpp new file mode 100644 index 00000000..d9c75308 --- /dev/null +++ b/kopete/libkopete/ui/metacontactselectorwidget.cpp @@ -0,0 +1,287 @@ +/* + MetaContactSelectorWidget + + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <qcheckbox.h> +#include <qlabel.h> +#include <qtooltip.h> +#include <qwhatsthis.h> +#include <qvbox.h> +#include <qimage.h> +#include <qpixmap.h> +#include <qpainter.h> +#include <qlayout.h> +#include <qvaluelist.h> + +#include <kapplication.h> +#include <kconfig.h> +#include <klocale.h> +#include <kiconloader.h> + +#include <kdeversion.h> +#include <kinputdialog.h> +#include <kpushbutton.h> +#include <kactivelabel.h> +#include <kdebug.h> +#include <klistview.h> +#include <klistviewsearchline.h> + +#include "kopetelistview.h" +#include "kopetelistviewsearchline.h" +#include "kopetecontactlist.h" +#include "kopetemetacontact.h" +#include "metacontactselectorwidget_base.h" +#include "metacontactselectorwidget.h" + +using namespace Kopete::UI::ListView; + +namespace Kopete +{ +namespace UI +{ + +class MetaContactSelectorWidgetLVI::Private +{ +public: + Kopete::MetaContact *metaContact; + ImageComponent *metaContactPhoto; + ImageComponent *metaContactIcon; + DisplayNameComponent *nameText; + TextComponent *extraText; + BoxComponent *contactIconBox; + BoxComponent *spacerBox; + int photoSize; + int contactIconSize; +}; + + +MetaContactSelectorWidgetLVI::MetaContactSelectorWidgetLVI(Kopete::MetaContact *mc, QListView *parent, QObject *owner, const char *name) : Kopete::UI::ListView::Item(parent, owner, name) , d( new Private() ) +{ + d->metaContact = mc; + d->photoSize = 60; + + connect( d->metaContact, SIGNAL( photoChanged() ), + SLOT( slotPhotoChanged() ) ); + connect( d->metaContact, SIGNAL( displayNameChanged(const QString&, const QString&) ), + SLOT( slotDisplayNameChanged() ) ); + buildVisualComponents(); +} + +Kopete::MetaContact* MetaContactSelectorWidgetLVI::metaContact() +{ + return d->metaContact; +} + +void MetaContactSelectorWidgetLVI::slotDisplayNameChanged() +{ + if ( d->nameText ) + { + d->nameText->setText( d->metaContact->displayName() ); + + // delay the sort if we can + if ( ListView::ListView *lv = dynamic_cast<ListView::ListView *>( listView() ) ) + lv->delayedSort(); + else + listView()->sort(); + } +} + +QString MetaContactSelectorWidgetLVI::text ( int /* column */ ) const +{ + return d->metaContact->displayName(); +} + +void MetaContactSelectorWidgetLVI::slotPhotoChanged() +{ + QPixmap photoPixmap; + QImage photoImg = d->metaContact->photo(); + if ( !photoImg.isNull() && (photoImg.width() > 0) && (photoImg.height() > 0) ) + { + int photoSize = d->photoSize; + + photoImg = photoImg.smoothScale( photoSize, photoSize, QImage::ScaleMin ) ; + + // draw a 1 pixel black border + photoPixmap = photoImg; + QPainter p(&photoPixmap); + p.setPen(Qt::black); + p.drawLine(0, 0, photoPixmap.width()-1, 0); + p.drawLine(0, photoPixmap.height()-1, photoPixmap.width()-1, photoPixmap.height()-1); + p.drawLine(0, 0, 0, photoPixmap.height()-1); + p.drawLine(photoPixmap.width()-1, 0, photoPixmap.width()-1, photoPixmap.height()-1); + } + else + { + // if no photo use the smilie icon + photoPixmap=SmallIcon(d->metaContact->statusIcon(), d->photoSize); + } + d->metaContactPhoto->setPixmap( photoPixmap, false); +} + +void MetaContactSelectorWidgetLVI::buildVisualComponents() +{ + // empty... + while ( component( 0 ) ) + delete component( 0 ); + + d->nameText = 0L; + d->metaContactPhoto = 0L; + d->extraText = 0L; + d->contactIconSize = 16; + d->photoSize = 48; + + Component *hbox = new BoxComponent( this, BoxComponent::Horizontal ); + d->spacerBox = new BoxComponent( hbox, BoxComponent::Horizontal ); + + d->contactIconSize = IconSize( KIcon::Small ); + Component *imageBox = new BoxComponent( hbox, BoxComponent::Vertical ); + new VSpacerComponent( imageBox ); + // include borders in size + d->metaContactPhoto = new ImageComponent( imageBox, d->photoSize + 2 , d->photoSize + 2 ); + new VSpacerComponent( imageBox ); + Component *vbox = new BoxComponent( hbox, BoxComponent::Vertical ); + d->nameText = new DisplayNameComponent( vbox ); + d->extraText = new TextComponent( vbox ); + + Component *box = new BoxComponent( vbox, BoxComponent::Horizontal ); + d->contactIconBox = new BoxComponent( box, BoxComponent::Horizontal ); + + slotUpdateContactBox(); + slotDisplayNameChanged(); + slotPhotoChanged(); +} + +void MetaContactSelectorWidgetLVI::slotUpdateContactBox() +{ + QPtrList<Kopete::Contact> contacts = d->metaContact->contacts(); + for(Kopete::Contact *c = contacts.first(); c; c = contacts.next()) + { + new ContactComponent(d->contactIconBox, c, IconSize( KIcon::Small )); + } +} + +class MetaContactSelectorWidget::Private +{ +public: + MetaContactSelectorWidget_Base *widget; + QValueList<Kopete::MetaContact *> excludedMetaContacts; +}; + + +MetaContactSelectorWidget::MetaContactSelectorWidget( QWidget *parent, const char *name ) + : QWidget( parent, name ), d( new Private() ) +{ + QBoxLayout *l = new QVBoxLayout(this); + d->widget = new MetaContactSelectorWidget_Base(this); + l->addWidget(d->widget); + + connect( d->widget->metaContactListView, SIGNAL( clicked(QListViewItem * ) ), + SIGNAL( metaContactListClicked( QListViewItem * ) ) ); + connect( d->widget->metaContactListView, SIGNAL( selectionChanged( QListViewItem * ) ), + SIGNAL( metaContactListClicked( QListViewItem * ) ) ); + connect( d->widget->metaContactListView, SIGNAL( spacePressed( QListViewItem * ) ), + SIGNAL( metaContactListClicked( QListViewItem * ) ) ); + + connect( Kopete::ContactList::self(), SIGNAL( metaContactAdded( Kopete::MetaContact * ) ), this, SLOT( slotLoadMetaContacts() ) ); + + d->widget->kListViewSearchLine->setListView(d->widget->metaContactListView); + d->widget->metaContactListView->setFullWidth( true ); + d->widget->metaContactListView->header()->hide(); + d->widget->metaContactListView->setColumnWidthMode(0, QListView::Maximum); + slotLoadMetaContacts(); +} + + +MetaContactSelectorWidget::~MetaContactSelectorWidget() +{ + disconnect( Kopete::ContactList::self(), SIGNAL( metaContactAdded( Kopete::MetaContact * ) ), this, SLOT( slotLoadMetaContacts() ) ); +} + + +Kopete::MetaContact* MetaContactSelectorWidget::metaContact() +{ + MetaContactSelectorWidgetLVI *item = 0L; + item = static_cast<MetaContactSelectorWidgetLVI *>( d->widget->metaContactListView->selectedItem() ); + + if ( item ) + return item->metaContact(); + + return 0L; +} + +void MetaContactSelectorWidget::selectMetaContact( Kopete::MetaContact *mc ) +{ + // iterate trough list view + QListViewItemIterator it( d->widget->metaContactListView ); + while( it.current() ) + { + MetaContactSelectorWidgetLVI *item = (MetaContactSelectorWidgetLVI *) it.current(); + if (!item) + continue; + + if ( mc == item->metaContact() ) + { + // select the contact item + d->widget->metaContactListView->setSelected( item, true ); + d->widget->metaContactListView->ensureItemVisible( item ); + } + ++it; + } +} + +void MetaContactSelectorWidget::excludeMetaContact( Kopete::MetaContact *mc ) +{ + if( d->excludedMetaContacts.findIndex(mc) == -1 ) + { + d->excludedMetaContacts.append(mc); + } + slotLoadMetaContacts(); +} + +bool MetaContactSelectorWidget::metaContactSelected() +{ + return d->widget->metaContactListView->selectedItem() ? true : false; +} + +/** Read in metacontacts from contactlist */ +void MetaContactSelectorWidget::slotLoadMetaContacts() +{ + d->widget->metaContactListView->clear(); + + QPtrList<Kopete::MetaContact> metaContacts = Kopete::ContactList::self()->metaContacts(); + for( Kopete::MetaContact *mc = metaContacts.first(); mc ; mc = metaContacts.next() ) + { + if( !mc->isTemporary() && (d->excludedMetaContacts.findIndex(mc) == -1) ) + { + new MetaContactSelectorWidgetLVI(mc, d->widget->metaContactListView); + } + } + + d->widget->metaContactListView->sort(); +} + +void MetaContactSelectorWidget::setLabelMessage( const QString &msg ) +{ + d->widget->lblHeader->setText(msg); +} + +} // namespace UI +} // namespace Kopete + +#include "metacontactselectorwidget.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/metacontactselectorwidget.h b/kopete/libkopete/ui/metacontactselectorwidget.h new file mode 100644 index 00000000..1c0a21ff --- /dev/null +++ b/kopete/libkopete/ui/metacontactselectorwidget.h @@ -0,0 +1,103 @@ +/* + MetaContactSelectorWidget + + Copyright (c) 2005 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef MetaContactSelectorWidget_H +#define MetaContactSelectorWidget_H + +#include <kdemacros.h> +#include <qwidget.h> +#include "kopetelistviewitem.h" +#include "kopete_export.h" + +class Kopete::MetaContact; + +namespace Kopete +{ +namespace UI +{ + +/** + * @author Duncan Mac-Vicar Prett <duncan@kde.org> + * This class provides a widget which allows easy selection + * of available Kopete metacontacts. + */ +class KOPETE_EXPORT MetaContactSelectorWidget : public QWidget +{ + Q_OBJECT +public: + MetaContactSelectorWidget( QWidget *parent = 0, const char *name = 0 ); + ~MetaContactSelectorWidget(); + Kopete::MetaContact* metaContact(); + /** + * sets the widget label message + * example: Please select a contact + * or, Choose a contact to delete + */ + void setLabelMessage( const QString &msg ); + /** + * pre-selects a contact + */ + void selectMetaContact( Kopete::MetaContact *mc ); + /** + * excludes a metacontact from being shown in the list + * if the metacontact is already excluded, do nothing + */ + void excludeMetaContact( Kopete::MetaContact *mc ); + /** + * @return true if there is a contact selected + */ + bool metaContactSelected(); +protected slots: + /** + * Utility function, populates the metacontact list + */ + void slotLoadMetaContacts(); +signals: + void metaContactListClicked( QListViewItem *mc ); +private: + class Private; + Private *d; +}; + +/** + * @author Duncan Mac-Vicar Prett <duncan@kde.org> + */ + +class MetaContactSelectorWidgetLVI : public Kopete::UI::ListView::Item +{ + Q_OBJECT +public: + MetaContactSelectorWidgetLVI(Kopete::MetaContact *mc, QListView *parent, QObject *owner = 0, const char *name = 0 ); + Kopete::MetaContact* metaContact(); + virtual QString text ( int column ) const; +protected slots: + void slotPhotoChanged(); + void slotDisplayNameChanged(); + void buildVisualComponents(); + void slotUpdateContactBox(); +private: + class Private; + Private *d; +}; + +} // namespace UI +} // namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/metacontactselectorwidget_base.ui b/kopete/libkopete/ui/metacontactselectorwidget_base.ui new file mode 100644 index 00000000..bc1a38eb --- /dev/null +++ b/kopete/libkopete/ui/metacontactselectorwidget_base.ui @@ -0,0 +1,107 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>MetaContactSelectorWidget_Base</class> +<widget class="QWidget"> + <property name="name"> + <cstring>MetaContactSelectorWidget_Base</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>427</width> + <height>306</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="caption"> + <string>Select Contact</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KActiveLabel"> + <property name="name"> + <cstring>lblHeader</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>4</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>lblSearch</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>S&earch:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>kListViewSearchLine</cstring> + </property> + </widget> + <widget class="Kopete::UI::ListView::SearchLine"> + <property name="name"> + <cstring>kListViewSearchLine</cstring> + </property> + </widget> + </hbox> + </widget> + <widget class="Kopete::UI::ListView::ListView"> + <column> + <property name="text"> + <string>Meta Contact</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>metaContactListView</cstring> + </property> + </widget> + </vbox> +</widget> +<includes> + <include location="local" impldecl="in declaration">kopetelistviewsearchline.h</include> + <include location="local" impldecl="in declaration">kopetelistview.h</include> + <include location="global" impldecl="in declaration">qheader.h</include> +</includes> +<layoutdefaults spacing="6" margin="11"/> +<layoutfunctions spacing="KDialog::spacingHint" margin="KDialog::marginHint"/> +<includehints> + <includehint>kactivelabel.h</includehint> + <includehint>kopetelistviewsearchline.h</includehint> + <includehint>kopetelistview.h</includehint> +</includehints> +</UI> diff --git a/kopete/libkopete/ui/userinfodialog.cpp b/kopete/libkopete/ui/userinfodialog.cpp new file mode 100644 index 00000000..a25454a9 --- /dev/null +++ b/kopete/libkopete/ui/userinfodialog.cpp @@ -0,0 +1,277 @@ +/* + userinfodialog.h + + Copyright (c) 2003 by Zack Rusin <zack@kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "userinfodialog.h" +#include "kopeteuiglobal.h" + +#include <khtml_part.h> +#include <ktextbrowser.h> +#include <kapplication.h> +#include <klineedit.h> +#include <klocale.h> +#include <kdebug.h> + +#include <qlabel.h> +#include <qvbox.h> +#include <qlayout.h> + +namespace Kopete { + +struct UserInfoDialog::UserInfoDialogPrivate { + QString name; + QString id; + QString awayMessage; + QString status; + QString warningLevel; + QString onlineSince; + QString info; + QString address; + QString phone; + QMap<QString,QString> customFields; + QVBoxLayout *topLayout; + QWidget *page; + DialogStyle style; + KHTMLPart *htmlPart; + + KLineEdit *nameEdit; + KLineEdit *idEdit; + KLineEdit *statusEdit; + KLineEdit *warningEdit; + KLineEdit *onlineEdit; + KLineEdit *addressEdit; + KLineEdit *phoneEdit; + KTextBrowser *awayBrowser; + KTextBrowser *infoBrowser; +}; + +UserInfoDialog::UserInfoDialog( const QString& descr ) +: KDialogBase( Kopete::UI::Global::mainWidget(), "userinfodialog", true, i18n( "User Info for %1" ).arg( descr ), KDialogBase::Ok ) +{ + d = new UserInfoDialogPrivate; + d->page = new QWidget( this ); + setMainWidget( d->page ); + d->topLayout = new QVBoxLayout( d->page, 0, spacingHint() ); + d->style = Widget; +} + +UserInfoDialog::~UserInfoDialog() +{ + delete d; d=0; +} + +void UserInfoDialog::setStyle( DialogStyle style ) +{ + d->style = style; +} + +void UserInfoDialog::setName( const QString& name ) +{ + d->name = name; +} + +void UserInfoDialog::setId( const QString& id ) +{ + d->id = id; +} + +void UserInfoDialog::setAwayMessage( const QString& msg ) +{ + d->awayMessage = msg; +} + +void UserInfoDialog::setStatus( const QString& status ) +{ + d->status = status; +} + +void UserInfoDialog::setWarningLevel(const QString& level ) +{ + d->warningLevel = level; +} + +void UserInfoDialog::setOnlineSince( const QString& since ) +{ + d->onlineSince = since; +} + +void UserInfoDialog::setInfo( const QString& info ) +{ + d->info = info; +} + +void UserInfoDialog::setAddress( const QString& addr ) +{ + d->address = addr; +} + +void UserInfoDialog::setPhone( const QString& phone ) +{ + d->phone = phone; +} + +void UserInfoDialog::addCustomField( const QString& /*name*/, const QString& /*txt*/ ) +{ + +} + +void UserInfoDialog::addHTMLText( const QString& /*str*/ ) +{ + +} + +QHBox* UserInfoDialog::addLabelEdit( const QString& label, const QString& text, KLineEdit*& edit ) +{ + QHBox *box = new QHBox( d->page ); + new QLabel( label, box ); + edit = new KLineEdit( box ); + edit->setAlignment( Qt::AlignHCenter ); + edit->setText( text ); + edit->setReadOnly( true ); + return box; +} + +void UserInfoDialog::fillHTML() +{ + d->htmlPart = new KHTMLPart( this ); + + QString text; + /* + if ( d->name.isEmpty() ) { + text.append( QString("<div id=\"name\"><b>") + i18n("Name : ") + + QString("</b>") ); + text.append( d->name + QString("</div><br>") ); + } + + if ( d->id.isEmpty() ) { + text.append( "<div id=\"id\"><b>" + i18n("Id : ") + "</b>" ); + text.append( d->id + "</div><br>" ); + } + + if ( d->warningLevel.isEmpty() ) { + text.append( "<div id=\"warningLevel\"><b>" + i18n("Warning Level : ") + "</b>" ); + text.append( d->warningLevel + "</div><br>" ); + } + + if ( d->onlineSince.isEmpty() ) { + text.append( "<div id=\"onlineSince\"><b>" + i18n("Online Since : ") + "</b>" ); + text.append( d->onlineSince + "</div><br>" ); + } + + if ( d->address.isEmpty() ) { + text.append( "<div id=\"address\"><b>" + i18n("Address : ") + "</b>" ); + text.append( d->address + "</div><br>" ); + } + + if ( d->phone.isEmpty() ) { + text.append( "<div id=\"phone\"><b>" + i18n("Phone : ") + "</b>" ); + text.append( d->phone + "</div><br>" ); + } + + if ( d->status.isEmpty() ) { + text.append( "<div id=\"status\"><b>" + i18n("Status : ") + "</b>" ); + text.append( d->status + "</div><br>" ); + } + + if ( d->awayMessage.isEmpty() ) { + text.append( "<div id=\"awayMessage\"><b>" + i18n("Away Message : ") + "</b>" ); + text.append( d->awayMessage + "</div><br>" ); + } + + if ( d->info.isEmpty() ) { + text.append( "<div id=\"info\"><b>" + i18n("Info : ") + "</b>" ); + text.append( d->info + "</div><br>" ); + } +*/ + d->htmlPart->setOnlyLocalReferences( true ); + d->htmlPart->begin(); + d->htmlPart->write( text ); + d->htmlPart->end(); +} + +void UserInfoDialog::fillWidgets() +{ + kdDebug(14010)<<"Creating widgets"<<endl; + if ( !d->name.isEmpty() ) { + d->topLayout->addWidget( addLabelEdit( i18n("Name:"), d->name, d->nameEdit ) ); + } + + if ( !d->id.isEmpty() ) { + d->topLayout->addWidget( addLabelEdit( i18n("Contact ID:"), d->id, d->idEdit ) ); + } + + if ( !d->status.isEmpty() ) { + d->topLayout->addWidget( addLabelEdit( i18n("Status:"), d->status, d->statusEdit ) ); + } + + if ( !d->warningLevel.isEmpty() ) { + d->topLayout->addWidget( addLabelEdit( i18n("Warning level:"), d->warningLevel, d->warningEdit ) ); + } + + if ( !d->onlineSince.isEmpty() ) { + d->topLayout->addWidget( addLabelEdit( i18n("Online since:"), d->onlineSince, d->onlineEdit ) ); + } + + if ( !d->address.isEmpty() ) { + d->topLayout->addWidget( addLabelEdit( i18n("Address:"), d->address, d->addressEdit ) ); + } + + if ( !d->phone.isEmpty() ) { + d->topLayout->addWidget( addLabelEdit( i18n("Phone:"), d->phone, d->phoneEdit ) ); + } + + if ( !d->awayMessage.isEmpty() ) { + QVBox *awayBox = new QVBox( d->page ); + new QLabel( i18n("Away message:"), awayBox ); + d->awayBrowser = new KTextBrowser( awayBox ); + d->awayBrowser->setText( d->awayMessage ); + d->topLayout->addWidget( awayBox ); + } + + if ( !d->info.isEmpty() ) { + QVBox *infoBox = new QVBox( d->page ); + new QLabel( i18n("User info:"), infoBox ); + d->infoBrowser = new KTextBrowser( infoBox ); + d->infoBrowser->setText( d->info ); + d->topLayout->addWidget( infoBox ); + } +} + +void UserInfoDialog::setStyleSheet( const QString& /*css*/ ) +{ +} + +void UserInfoDialog::create() +{ + if ( d->style == HTML ) { + fillHTML(); + } else { + fillWidgets(); + } +} + +void UserInfoDialog::show() +{ + create(); + KDialogBase::show(); +} + +} + +#include "userinfodialog.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/userinfodialog.h b/kopete/libkopete/ui/userinfodialog.h new file mode 100644 index 00000000..7df19f4f --- /dev/null +++ b/kopete/libkopete/ui/userinfodialog.h @@ -0,0 +1,91 @@ +/* + userinfodialog.h + + Copyright (c) 2003 by Zack Rusin <zack@kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef USERINFODIALOG_H +#define USERINFODIALOG_H + +#include <kdialogbase.h> +#include <qstring.h> + +#include "kopete_export.h" + +class KLineEdit; + +namespace Kopete { + + class KOPETE_EXPORT UserInfoDialog : public KDialogBase + { + Q_OBJECT + public: + UserInfoDialog( const QString& descr ); + virtual ~UserInfoDialog(); + + + /** + * Specifies the look of this dialog. If set to HTML only + * KHTMLPart will be in the dialog and it's look can be customized + * through setStyleSheet + * @see setStyleSheet + */ + enum DialogStyle { HTML, Widget }; + void setStyle( DialogStyle style ); + + // The functions below set elements as specified in the name. + // If an element is not set it won't be displayed. + void setName( const QString& name ); + void setId( const QString& id ); + void setAwayMessage( const QString& msg ); + void setStatus( const QString& status ); + void setWarningLevel(const QString& level ); + void setOnlineSince( const QString& since ); + void setInfo( const QString& info ); + void setAddress( const QString& addr ); + void setPhone( const QString& phone ); + + void addCustomField( const QString& name, const QString& txt ); + void addHTMLText( const QString& str ); + + ///Shows the dialog + virtual void show(); + protected: + /** + * This function has to be called after setting all the fields. + * It builds the GUI for the dialog. By default show() calls it. + */ + virtual void create(); + //Fills the dialog HTML if DialogStyle is HTML + virtual void fillHTML(); + //Fills the dialog with widgets if DialogStyle is Widget + virtual void fillWidgets(); + + /** + * If the DialogStyle is set to HTML one can customize the look of this + * dialog by setting the right stylesheet. The CSS id elements that can be + * customized include : "name", "id", "warningLevel", "onlineSince", + * "address", "phone", "status", "awayMessage" and "info". + */ + void setStyleSheet( const QString& css ); + + QHBox* addLabelEdit( const QString& label, const QString& text, KLineEdit*& edit ); + + private: + struct UserInfoDialogPrivate; + UserInfoDialogPrivate *d; + }; + +} +#endif diff --git a/kopete/libkopete/ui/widgets.cw b/kopete/libkopete/ui/widgets.cw new file mode 100644 index 00000000..7f3b38dc --- /dev/null +++ b/kopete/libkopete/ui/widgets.cw @@ -0,0 +1,21 @@ +<!DOCTYPE CW><CW> +<customwidgets> + <customwidget> + <class>Kopete::UI::PasswordWidget</class> + <header location="local">kopetepasswordwidget.h</header> + <sizehint> + <width>50</width> + <height>50</height> + </sizehint> + <container>0</container> + <sizepolicy> + <hordata>1</hordata> + <verdata>0</verdata> + </sizepolicy> + <pixmap> + <data format="XPM.GZ" length="4462">789c9d97c76e24490e86effd1442f3d65870d2451a0ce6206f5adeb4cc620f8c34f2553225b5a4c1befb46927fe6a1d4c0ccac4287fa8a0c26834193f5dbb785b3fd9d856fbf7d799ec9ecba5ea8afe469e15bf3727ffffeeffffcf1e797af49b2d0ffc7d142f2f55f5fbe1ecc16ea85dde9a4ed81290045faa77ca49cf4ab67ba1e3953969173651ab9d4fdf1c8a27c3872ad7c3c72d3b32c2a67c3f3444636fbef23eb7e590267e68fcc4656395d8dacf6d938ef97eaef289781cddebdb2846fccff44b989ba58e3411f3dc75158e6df1d3889539517ca49bf54fe43398d1dec4f46567f685fd9c539f43fc025f80c1c3ce8d93f28e77185f8bf0c6cfae4c0b5f9c3c6e5c0542a4b5876fe13708df39d2bd7716372aa8c93c4e4723bb2f977aadc26ceec4bdb7310e6b0bfab9c2445ecd49f1370694cebca65d260ffa6711a41aef14d24e94cee6be3348e0ae5e9c8765f07ca3e8db17f0f9c825794eb3489351fe9bb7217e4969f29388bd51ee9fda5715a19730696b852fea95c6471647ca95cf64bcf43e0cef4657b647bfe6acf213d6bb32f9a9f5992b5a64f9a8f990b6cf9ecc15d6cf9acf6b2da55a8a75cb973dee4b2d1b38b5c05de02434e87e01aacfb351df5bebddea74b5c67f9c795711ea15e357e2ecde358fb87efc0a867df8cac728a074e62e527706afb59f3cf6583be5c28bb3c35f693812d1fbdc6dbe57966fec932d899ffa4f7e38adca17e34beaeca4b9c271a18f1d5fc739257b0d70d9c68be7bed7fcee7827ab91cd8e4740cf6b19d4ffb87ab07b9bf516e72d4a3ac821b9cef6e60d84f47367ded2fae1d9f7704f6163f79000ffde27660f387ed3c5dbf54dfeebb0bf6acbe6b706bfa5ef32f8f8b18f5bf0f463f20edb77956a4e68fbc80b3c4facd163847be6bfde72ec86dbeac82715fbc0286be683cf3bc081d44f7df80d344fb0b6b7de64581fb916765297263d6fccd9b7ee97e566e8b06cfdb1f59cf2bda2f8bbc2c11df0c5c41aefdb328ca22b1feb0012eedbce24736f91b18fb796d64eb87cb60817dcdafb07d906bfd14d22f659d8785ef97b2f6d7b2df6ef1d6fb2babaac6f92e8c25b27ecc1f239b7f3a5fca5a8678ea7c2d9bc0e6ef1238b3fca2c7814d5f4cbf9334b5f833384bed3c7a5f5514f4adbe2ec07962e7590457f06f6f649b977afe2a16f42f5e070bfc591e18f7a5f1a812a9e0df39d827da4fbd8c6ccfd7785569bf945f953371a9d5a3de67950bfa2d75c63eb2fb15bdcfaaf011e6c7263846bf8f4636f91618f3c7d3c0f047fb69550efab2074e12bd6fd27957553ec33c5b043bcc7f9d0795f818fdb501a3df8ae673e507ff69021eea37063bc45ffb73550736ff36c0a84f3e0317a86f8b5f139e6ffe1f821de6d9127898ff3be00a7c3ab2cd03e3d697560ff40016e47b3bb2e96bbd559d8f32bbff4b7065f193042c98873acf240af6adbf3c83c5f2954bb0c7bcd77c107d81d2fdebc63e33ff640aaecc9e1c8151df5c803dfaadc64b52dfa03ed64636ffb4ff4b2867f453ede7227586fcd3f9264ded91fffafe236d5da37fe83c91ceb7b9e5b7f6731ff92ed7f747d6fbf7e185cf58347e3e6932e47b3430fa893edf87d795c2fc3f003b67f5f902cec17abfde0dfa3c053b3c5ffb832fc2eb8fddcf233887fc195c801f46b6f3cdc02558fba72f7d2d1a7f7e321ee58fe0cad86bbff24dd3e2fe74fef836b0c6eb60d62fa6bf5e07b3419f853dd7dc70fb8bd5f1255fd90ed30f9f3c5ff30ddff21ddff384a7fcc08ffcc4cf61cdf8855ff9e79c7e1db4dff89d3f7891977899577895d7789d377893b7f83b6fcfe937bc13b477798ff7f9800ff928ac633ee11f7cca6761d7f99c7e1b3cb908da11c7413be1943376e153cc39175c72f549ff9e17c31744429e6a6aa8a58e2ee98aaee9866e7f617fc24b7417a4f734a1293dd0233dd133cd8285177aa5f9f3b63ca5377aa78f607b919668995682e62aadd17ab0b1419b9ff41f682b48bed336edd02eed05ed7d5ea3033a0cdf1ed1f127fd273ae123fa41a774a6b685cee982228a837e42e927fd47caf8985c38651eb40b2ac38e4a5842658a97fab33fd248cb87d2c9a55cc9b5dcc82d1fc99ddccb44a6f230af2f8ff224cf417f262ff22a3fe54ddee543166549966545567f617f4dd66523dc6b2c9bb225df655b7682f692ecca9eeccfe97772c09b722847722c27c1f3eb70f66bf9116c9fca999ccbc59cfe25bf4a143a5ef8992561324a78bd92522acf9ebc78efe7cf7b153276db37bef59dbff457fedadff85b7f27abfede4ffcd4cf9ff76faeff4fffefeff8c7f5fedfdfbffc0fa355c495</data> + </pixmap> + <signal>changed()</signal> + </customwidget> +</customwidgets> +</CW> diff --git a/kopete/libkopete/webcamwidget.cpp b/kopete/libkopete/webcamwidget.cpp new file mode 100644 index 00000000..ae617ad5 --- /dev/null +++ b/kopete/libkopete/webcamwidget.cpp @@ -0,0 +1,107 @@ +/* + webcamwidget.h - A simple widget for displaying webcam frames + + Copyright (c) 2006 by Gustavo Pichorim Boiko <gustavo.boiko@kdemail.net> + Kopete (c) 2002-2006 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "webcamwidget.h" + +#include <qcolor.h> +#include <qpainter.h> + +#include <kdebug.h> +namespace Kopete +{ + +WebcamWidget::WebcamWidget( QWidget* parent, const char* name ) +: QWidget( parent, name ) +{ + clear(); +} + +WebcamWidget::~WebcamWidget() +{ + // don't do anything either +} + +void WebcamWidget::updatePixmap(const QPixmap& pixmap) +{ + mPixmap = pixmap; + mText = ""; + + QPaintEvent *ev = new QPaintEvent( rect(), true ); + paintEvent( ev ); + delete ev; +} + +void WebcamWidget::clear() +{ + mText = ""; + if (!mPixmap.isNull()) + mPixmap.resize(0,0); + + QPaintEvent *ev = new QPaintEvent( rect(), true ); + paintEvent( ev ); + delete ev; +} + +void WebcamWidget::setText(const QString& text) +{ + mText = text; + + // for now redraw everything + QPaintEvent *ev = new QPaintEvent( rect(), true ); + paintEvent( ev ); + delete ev; +} + +void WebcamWidget::paintEvent( QPaintEvent* event ) +{ + QMemArray<QRect> rects = event->region().rects(); + + if (!mPixmap.isNull()) + { + for (unsigned int i = 0; i < rects.count(); ++i) + { + bitBlt(this, rects[i].topLeft(), &mPixmap, rects[i], Qt::CopyROP, true); + } + } + else + { + for (unsigned int i = 0; i < rects.count(); ++i) + { + QColor bgColor = paletteBackgroundColor(); + QPainter p(this); + p.fillRect(rects[i], bgColor); + } + } + + // TODO: draw the text + QPainter p(this); + QRect r = p.boundingRect(rect(), Qt::AlignCenter | Qt::WordBreak, mText ); + if ( !mText.isEmpty() && event->rect().intersects(r)) + { + p.setPen(Qt::black); + QRect rec = rect(); + rec.moveTopLeft(QPoint(1,1)); + p.drawText(rec, Qt::AlignCenter | Qt::WordBreak, mText, -1); + + rec.moveTopLeft(QPoint(-1,-1)); + p.setPen(Qt::white); + p.drawText(rec, Qt::AlignCenter | Qt::WordBreak, mText, -1); + } +} + +} // end namespace Kopete + +#include "webcamwidget.moc" diff --git a/kopete/libkopete/webcamwidget.h b/kopete/libkopete/webcamwidget.h new file mode 100644 index 00000000..6458f60a --- /dev/null +++ b/kopete/libkopete/webcamwidget.h @@ -0,0 +1,66 @@ +/* + webcamwidget.h - A simple widget for displaying webcam frames + + Copyright (c) 2006 by Gustavo Pichorim Boiko <gustavo.boiko@kdemail.net> + Kopete (c) 2002-2006 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef WEBCAMWIDGET_H +#define WEBCAMWIDGET_H + +#include <qwidget.h> +#include <qpixmap.h> +#include <qstring.h> + +#include "kopete_export.h" + +namespace Kopete +{ +/** + * A simple widget to display webcam frames. + */ +class KOPETE_EXPORT WebcamWidget : public QWidget +{ +Q_OBJECT +public: + /** + * @brief WebcamWidget constructor. + * @param parent The parent widget of this widget + * @param name The name for this QObject + */ + WebcamWidget( QWidget* parent = 0, const char* name = 0 ); + ~WebcamWidget(); + + /** + * @brief Updates the frame being displayed in the widget + * @param pixmap The frame to be displayed + */ + void updatePixmap(const QPixmap& pixmap); + + /** + * @brief Clear the widget + */ + void clear(); + + /** + * @brief Set a text to be displayed in the widget + * @param text The text to be displayed + */ + void setText(const QString& text); +protected slots: + void paintEvent( QPaintEvent* event ); + QPixmap mPixmap; + QString mText; +}; + +} // end namespace Kopete +#endif |