2
kopeteviewmanager.cpp - View Manager
4
Copyright (c) 2003 by Jason Keirstead <jason@keirstead.org>
5
Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org>
7
*************************************************************************
9
* This program is free software; you can redistribute it and/or modify *
10
* it under the terms of the GNU General Public License as published by *
11
* the Free Software Foundation; either version 2 of the License, or *
12
* (at your option) any later version. *
14
*************************************************************************
17
#include "kopeteviewmanager.h"
20
#include <QTextDocument>
21
#include <QtAlgorithms>
26
#include <kplugininfo.h>
27
#include <knotification.h>
29
#include <kwindowsystem.h>
31
#include "kopetebehaviorsettings.h"
32
#include "kopeteaccount.h"
33
#include "kopetepluginmanager.h"
34
#include "kopeteviewplugin.h"
35
#include "kopetechatsessionmanager.h"
36
#include "kopetemetacontact.h"
37
#include "kopetecontact.h"
38
#include "kopetemessageevent.h"
39
#include "kopeteview.h"
40
#include "kopetechatsession.h"
41
#include "kopetegroup.h"
42
#include "kopetepicture.h"
43
#include "kopeteemoticons.h"
44
#include "kopeteactivenotification.h"
47
* Used to exrtract the message that will be shown in the notification popup.
49
* Was in KopeteSystemTray::squashMessage in KDE3
52
static QString squashMessage( const Kopete::Message& msg )
54
QString msgText = msg.parsedBody();
56
QRegExp rx( "(<a.*>((http://)?(.+))</a>)" );
57
rx.setMinimal( true );
58
if ( rx.indexIn( msgText ) == -1 )
60
// no URLs in text, just pick the first 30 chars of
61
// the parsed text if necessary. We used parsed text
62
// so that things like "<knuff>" show correctly
63
// Escape it after snipping it to not snip entities
64
msgText = msg.plainBody();
65
if( msgText.length() > 30 )
66
msgText = msgText.left( 30 ) + QString::fromLatin1( " ..." );
67
msgText=Kopete::Emoticons::parseEmoticons(Qt::escape(msgText));
71
QString plainText = msg.plainBody();
72
if ( plainText.length() > 30 )
74
QString fullUrl = rx.cap( 2 );
76
if ( fullUrl.length() > 30 )
78
QString urlWithoutProtocol = rx.cap( 4 );
79
shorterUrl = urlWithoutProtocol.left( 27 )
80
+ QString::fromLatin1( "... " );
84
shorterUrl = fullUrl.left( 27 )
85
+ QString::fromLatin1( "... " );
87
// remove message text
88
msgText = QString::fromLatin1( "... " ) +
90
QString::fromLatin1( " ..." );
91
// find last occurrence of URL (the one inside the <a> tag)
92
int revUrlOffset = msgText.lastIndexOf( fullUrl );
93
msgText.replace( revUrlOffset,
94
fullUrl.length(), shorterUrl );
97
kDebug(14000) << msgText;
101
typedef QMap<Kopete::ChatSession*, KopeteView*> SessionMap;
102
typedef QList<Kopete::MessageEvent*> EventList;
104
struct KopeteViewManagerPrivate
106
~KopeteViewManagerPrivate()
108
qDeleteAll(sessionMap);
110
qDeleteAll(eventList);
114
SessionMap sessionMap;
116
KopeteView *activeView;
120
bool queueUnreadMessages;
121
bool queueOnlyHighlightedMessagesInGroupChats;
122
bool queueOnlyMessagesOnAnotherDesktop;
123
bool balloonNotifyIgnoreClosesChatView;
124
bool balloonGroupMessageNotificationsPerSender;
126
bool animateOnMessageWithOpenChat;
127
bool enableEventsWhileAway;
128
Kopete::ActiveNotifications activeNotifications;
131
KopeteViewManager *KopeteViewManager::s_viewManager = 0L;
133
KopeteViewManager *KopeteViewManager::viewManager()
136
s_viewManager = new KopeteViewManager();
137
return s_viewManager;
140
KopeteViewManager::KopeteViewManager()
143
d = new KopeteViewManagerPrivate;
145
d->foreignMessage=false;
147
connect( Kopete::BehaviorSettings::self(), SIGNAL( configChanged() ), this, SLOT( slotPrefsChanged() ) );
149
connect( Kopete::ChatSessionManager::self() , SIGNAL( display( Kopete::Message &, Kopete::ChatSession *) ),
150
this, SLOT ( messageAppended( Kopete::Message &, Kopete::ChatSession *) ) );
152
connect( Kopete::ChatSessionManager::self() , SIGNAL( readMessage() ),
153
this, SLOT ( nextEvent() ) );
158
KopeteViewManager::~KopeteViewManager()
162
//delete all open chatwindow.
163
SessionMap::Iterator it;
164
for ( it = d->sessionMap.begin(); it != d->sessionMap.end(); ++it )
166
it.value()->closeView( true ); //this does not clean the map, but we don't care
172
void KopeteViewManager::slotPrefsChanged()
174
Kopete::BehaviorSettings *const bs = Kopete::BehaviorSettings::self();
176
d->useQueue = bs->useMessageQueue();
177
d->raiseWindow = bs->raiseMessageWindow();
178
d->queueUnreadMessages = bs->queueUnreadMessages();
179
d->queueOnlyHighlightedMessagesInGroupChats = bs->queueOnlyHighlightedMessagesInGroupChats();
180
d->queueOnlyMessagesOnAnotherDesktop = bs->queueOnlyMessagesOnAnotherDesktop();
181
d->balloonNotifyIgnoreClosesChatView = bs->balloonNotifyIgnoreClosesChatView();
182
d->balloonGroupMessageNotificationsPerSender = bs->balloonGroupMessageNotificationsPerSender();
183
d->animateOnMessageWithOpenChat = bs->animateOnMessageWithOpenChat();
184
d->enableEventsWhileAway = bs->enableEventsWhileAway();
187
KopeteView *KopeteViewManager::view( Kopete::ChatSession* session, const QString &requestedPlugin )
191
if( d->sessionMap.contains( session ) && d->sessionMap[ session ] )
193
return d->sessionMap[ session ];
197
Kopete::PluginManager *pluginManager = Kopete::PluginManager::self();
198
Kopete::ViewPlugin *viewPlugin = 0L;
200
QString pluginName = requestedPlugin.isEmpty() ? Kopete::BehaviorSettings::self()->viewPlugin() : requestedPlugin;
201
if( !pluginName.isEmpty() )
203
viewPlugin = (Kopete::ViewPlugin*)pluginManager->loadPlugin( pluginName );
207
kWarning(14000) << "Requested view plugin, " << pluginName
208
<< ", was not found. Falling back to chat window plugin" << endl;
214
viewPlugin = (Kopete::ViewPlugin*)pluginManager->loadPlugin( QLatin1String("kopete_chatwindow") );
219
KopeteView *newView = viewPlugin->createView(session);
221
d->foreignMessage = false;
222
d->sessionMap.insert( session, newView );
224
connect( session, SIGNAL( closing(Kopete::ChatSession *) ),
225
this, SLOT(slotChatSessionDestroyed(Kopete::ChatSession*)) );
231
kError(14000) << "Could not create a view, no plugins available!" << endl;
238
void KopeteViewManager::messageAppended( Kopete::Message &msg, Kopete::ChatSession *session)
240
const bool isOutboundMessage = msg.direction() == Kopete::Message::Outbound;
242
if ( isOutboundMessage && !d->sessionMap.contains( session ) )
245
// Get an early copy of the plain message body before the chat view works on it
246
// Otherwise toPlainBody() will ignore smileys if they were turned into images during
247
// the html conversion. See bug 161651.
248
const QString squashedMessage = squashMessage( msg );
250
d->foreignMessage = !isOutboundMessage; // for the view we are about to create
251
session->view( true, msg.requestedPlugin() )->appendMessage( msg );
252
d->foreignMessage = false; // the view has been created, reset the flag
254
bool appendMessageEvent = d->useQueue;
255
bool isViewOnCurrentDesktop = true;
257
QWidget *w = dynamic_cast< QWidget * >( view( session ) );
261
isViewOnCurrentDesktop = KWindowSystem::windowInfo( w->topLevelWidget()->winId(),
262
NET::WMDesktop ).isOnCurrentDesktop();
266
if ( w && d->queueUnreadMessages )
268
// append msg event to queue if chat window is active but not the chat view in it...
269
appendMessageEvent = appendMessageEvent && !( w->isActiveWindow()
270
&& session->view() == d->activeView );
271
// ...and chat window is on another desktop
272
appendMessageEvent = appendMessageEvent && !( d->queueOnlyMessagesOnAnotherDesktop
273
&& isViewOnCurrentDesktop );
277
// append if no chat window exists already
278
appendMessageEvent = appendMessageEvent && !view( session )->isVisible();
281
// in groupchats, don't notify on non-highlighted messages if configured that way.
282
const bool isIgnoredGroupChatMessage = session->members().count() > 1
283
&& d->queueOnlyHighlightedMessagesInGroupChats
284
&& msg.importance() != Kopete::Message::Highlight;
286
appendMessageEvent = appendMessageEvent && !isIgnoredGroupChatMessage;
288
QWidget *viewWidget = 0;
289
bool showNotification = false;
290
bool isActiveWindow = false;
291
Kopete::MessageEvent *event = 0;
293
if ( !isOutboundMessage )
295
if ( ( !session->account()->isAway() || d->enableEventsWhileAway )
296
&& msg.direction() != Kopete::Message::Internal )
298
viewWidget = dynamic_cast< QWidget * >( session->view( false ) );
299
// note that session->view( true [default value] ) can create a view, so watch the
301
isActiveWindow = session->view( false ) && viewWidget
302
&& session->view() == d->activeView && viewWidget->isActiveWindow();
303
showNotification = msg.from() != 0;
306
if ( appendMessageEvent || showNotification )
308
if ( msg.from() && d->eventList.isEmpty() ) // may happen for internal messages
309
showNotification = true;
311
event = new Kopete::MessageEvent( msg, session );
312
d->eventList.append( event );
314
// Don't call readMessages twice. We call it later in this method. Fixes bug 168978.
316
connect( event, SIGNAL(done(Kopete::MessageEvent *)),
317
this, SLOT(slotEventDeleted(Kopete::MessageEvent *)) );
322
showNotification = false;
324
if ( showNotification )
325
createNotification( msg, squashedMessage, session, event, viewWidget,
326
isActiveWindow, isViewOnCurrentDesktop );
330
// "Open messages instantly" setting
331
readMessages( session, isOutboundMessage );
334
KopeteView *view = session->view( false );
335
if ( d->raiseWindow && view && view->isVisible() )
337
// "Raise window on incoming message" setting
341
if ( event && ( appendMessageEvent || ( d->animateOnMessageWithOpenChat && !isActiveWindow ) )
342
&& !isIgnoredGroupChatMessage )
343
Kopete::ChatSessionManager::self()->postNewEvent(event);
346
void KopeteViewManager::createNotification( Kopete::Message &msg, const QString &squashedMessage,
347
Kopete::ChatSession *session, Kopete::MessageEvent *event,
349
bool isActiveWindow, bool isViewOnCurrentDesktop )
351
if ( d->balloonGroupMessageNotificationsPerSender )
353
Kopete::ActiveNotifications::iterator notifyIt =
354
d->activeNotifications.find( msg.from()->account()->accountLabel() + msg.from()->contactId() );
355
if (notifyIt != d->activeNotifications.end())
357
( *notifyIt )->incrementMessages();
362
const QString msgFrom = msg.from()->metaContact() ? msg.from()->metaContact()->displayName()
363
: msg.from()->contactId();
366
KLocalizedString body = ki18n( "Incoming message from %1<br />\"%2\"" );
367
switch( msg.importance() )
369
case Kopete::Message::Low:
370
eventId = QLatin1String( "kopete_contact_lowpriority" );
372
case Kopete::Message::Highlight:
373
eventId = QLatin1String( "kopete_contact_highlight" );
374
body = ki18n( "A highlighted message arrived from %1<br />\"%2\"" );
377
if ( isActiveWindow || (d->queueOnlyMessagesOnAnotherDesktop
378
&& isViewOnCurrentDesktop ) )
380
eventId = QLatin1String( "kopete_contact_incoming_active_window" );
384
eventId = QLatin1String( "kopete_contact_incoming" );
388
KNotification *notify = new KNotification(eventId, viewWidget, isActiveWindow
389
? KNotification::CloseOnTimeout
390
: KNotification::Persistent);
391
notify->setPixmap( QPixmap::fromImage( msg.from()->metaContact()->picture().image() ) );
392
notify->setActions( QStringList() << i18nc( "@action", "View" )
393
<< i18nc( "@action", "Ignore" ) );
395
QString bodyString = body.subs( Qt::escape(msgFrom) ).subs( squashedMessage ).toString();
396
if ( d->balloonGroupMessageNotificationsPerSender )
398
// notify is parent, will die with it
399
new Kopete::ActiveNotification( notify,
400
msg.from()->account()->accountLabel() + msg.from()->contactId(),
401
d->activeNotifications,
406
notify->setText( "<qt>" + bodyString + "</qt>" );
409
foreach ( const QString& cl , msg.classes() )
410
notify->addContext( qMakePair( QString::fromLatin1("class"), cl ) );
412
Kopete::MetaContact *mc= msg.from()->metaContact();
415
notify->addContext( qMakePair( QString::fromLatin1("contact"),
416
mc->metaContactId().toString()) );
417
foreach( Kopete::Group *g , mc->groups() )
419
notify->addContext( qMakePair( QString::fromLatin1("group"),
420
QString::number(g->groupId())) );
423
connect( notify, SIGNAL(activated()), session, SLOT(raiseView()) );
424
connect( notify, SIGNAL(action1Activated()), session, SLOT(raiseView()) );
425
connect( notify, SIGNAL(action2Activated()), event, SLOT(discard()) );
426
connect( event, SIGNAL(done(Kopete::MessageEvent*)), notify, SLOT(close()) );
430
void KopeteViewManager::readMessages( Kopete::ChatSession *session, bool isOutboundMessage, bool activate )
433
d->foreignMessage = !isOutboundMessage; //for the view we are about to create
434
KopeteView *thisView = session->view( true );
435
d->foreignMessage = false; //the view is created, reset the flag
436
if ( ( isOutboundMessage && !thisView->isVisible() ) || d->raiseWindow || activate )
437
thisView->raise( activate );
438
else if ( !thisView->isVisible() )
439
thisView->makeVisible();
441
foreach ( Kopete::MessageEvent *event, d->eventList )
443
if ( event->message().manager() == session )
446
d->eventList.removeAll( event );
451
void KopeteViewManager::slotEventDeleted( Kopete::MessageEvent *event )
453
// d->eventList.remove( event );
454
d->eventList.removeAll(event);
457
Kopete::ChatSession *kmm=event->message().manager();
460
// this can be NULL for example if KopeteViewManager::slotViewActivated()
461
// got called which does deleteLater() the event what may result in the
462
// case, that the QPointer<ChatSession> in Message::Private got removed
463
// aka set to NULL already.
467
if ( event->state() == Kopete::MessageEvent::Applied )
469
readMessages( kmm, false, true );
471
else if ( event->state() == Kopete::MessageEvent::Ignored && d->balloonNotifyIgnoreClosesChatView )
473
bool bAnotherWithThisManager = false;
474
foreach (Kopete::MessageEvent *event, d->eventList)
476
if ( event->message().manager() == kmm )
478
bAnotherWithThisManager = true;
481
if ( !bAnotherWithThisManager && kmm->view( false ) )
482
kmm->view()->closeView( true );
486
void KopeteViewManager::nextEvent()
490
if( d->eventList.isEmpty() )
493
Kopete::MessageEvent* event = d->eventList.first();
499
void KopeteViewManager::slotViewActivated( KopeteView *view )
502
d->activeView = view;
504
foreach (Kopete::MessageEvent *event, d->eventList)
506
if ( event->message().manager() == view->msgManager() )
508
event->deleteLater();
513
void KopeteViewManager::slotViewDestroyed( KopeteView *closingView )
517
if ( d->sessionMap.contains( closingView->msgManager() ) )
519
d->sessionMap.remove( closingView->msgManager() );
520
// closingView->msgManager()->setCanBeDeleted( true );
523
if( closingView == d->activeView )
527
void KopeteViewManager::slotChatSessionDestroyed( Kopete::ChatSession *session )
531
if ( d->sessionMap.contains( session ) )
533
KopeteView *v = d->sessionMap[ session ];
534
v->closeView( true );
535
delete v; //closeView call deleteLater, but in this case this is not enough, because some signal are called that case crash
536
d->sessionMap.remove( session );
540
KopeteView* KopeteViewManager::activeView() const
542
return d->activeView;
546
QList<Kopete::MessageEvent*> KopeteViewManager::pendingMessages( Kopete::Contact *contact )
548
QList<Kopete::MessageEvent*> pending;
549
foreach (Kopete::MessageEvent *event, d->eventList)
551
const Kopete::Message &message = event->message();
552
if ( event->state() == Kopete::MessageEvent::Nothing
553
&& message.direction() == Kopete::Message::Inbound
554
&& message.from() == contact )
563
#include "kopeteviewmanager.moc"
565
// vim: set noet ts=4 sts=4 sw=4: