00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018 #include "chattexteditpart.h"
00019
00020 #include "kopetecontact.h"
00021 #include "kopetechatsession.h"
00022 #include "kopeteonlinestatus.h"
00023 #include "kopeteprotocol.h"
00024 #include "kopeteglobal.h"
00025 #include <kopeteappearancesettings.h>
00026
00027 #include <kcompletion.h>
00028 #include <kdebug.h>
00029 #include <ktextedit.h>
00030 #include <sonnet/highlighter.h>
00031
00032 #include <QtCore/QTimer>
00033 #include <QtCore/QRegExp>
00034
00035 ChatTextEditPart::ChatTextEditPart( Kopete::ChatSession *session, QWidget *parent )
00036 : KRichTextEditPart(parent, 0, QStringList()), m_session(session)
00037 {
00038
00039 setProtocolRichTextSupport();
00040
00041 m_autoSpellCheckEnabled = true;
00042 historyPos = -1;
00043
00044 mComplete = new KCompletion();
00045 mComplete->setIgnoreCase( true );
00046 mComplete->setOrder( KCompletion::Weighted );
00047
00048
00049 textEdit()->setMinimumSize( QSize( 75, 20 ) );
00050
00051
00052
00053
00054 m_highlighter = new Sonnet::Highlighter( textEdit() );
00055
00056 connect( textEdit(), SIGNAL( textChanged()), this, SLOT( slotTextChanged() ) );
00057
00058
00059 m_typingRepeatTimer = new QTimer(this);
00060 m_typingRepeatTimer->setObjectName("m_typingRepeatTimer");
00061 m_typingStopTimer = new QTimer(this);
00062 m_typingStopTimer->setObjectName("m_typingStopTimer");
00063
00064 connect( m_typingRepeatTimer, SIGNAL( timeout() ), this, SLOT( slotRepeatTypingTimer() ) );
00065 connect( m_typingStopTimer, SIGNAL( timeout() ), this, SLOT( slotStoppedTypingTimer() ) );
00066
00067 connect( session, SIGNAL( contactAdded(const Kopete::Contact*, bool) ),
00068 this, SLOT( slotContactAdded(const Kopete::Contact*) ) );
00069 connect( session, SIGNAL( contactRemoved(const Kopete::Contact*, const QString&, Qt::TextFormat, bool) ),
00070 this, SLOT( slotContactRemoved(const Kopete::Contact*) ) );
00071 connect( session, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus & , const Kopete::OnlineStatus &) ),
00072 this, SLOT( slotContactStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) );
00073
00074 setFont( Kopete::AppearanceSettings::self()->chatFont() );
00075
00076 slotContactAdded( session->myself() );
00077
00078 foreach( Kopete::Contact *contact, session->members() )
00079 slotContactAdded( contact );
00080 }
00081
00082 ChatTextEditPart::~ChatTextEditPart()
00083 {
00084 delete mComplete;
00085 }
00086
00087 void ChatTextEditPart::toggleAutoSpellCheck( bool enabled )
00088 {
00089 if ( useRichText() )
00090 enabled = false;
00091
00092 m_autoSpellCheckEnabled = enabled;
00093 if ( spellHighlighter() )
00094 {
00095 spellHighlighter()->setAutomatic( enabled );
00096 spellHighlighter()->setActive( enabled );
00097 }
00098 textEdit()->setCheckSpellingEnabled( enabled );
00099 }
00100
00101 bool ChatTextEditPart::autoSpellCheckEnabled() const
00102 {
00103 return m_autoSpellCheckEnabled;
00104 }
00105
00106 Sonnet::Highlighter* ChatTextEditPart::spellHighlighter()
00107 {
00108 return m_highlighter;
00109 }
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126 void ChatTextEditPart::complete()
00127 {
00128 #ifdef __GNUC__
00129 #warning disabled nick completion to make it compile
00130 #endif
00131 #if 0
00132 int para = 1, parIdx = 1;
00133 textEdit()->getCursorPosition( ¶, &parIdx);
00134
00135
00136
00137 QString txt = textEdit()->text(para);
00138
00139 if ( parIdx > 0 )
00140 {
00141 int firstSpace = txt.lastIndexOf( QRegExp( QLatin1String("\\s\\S+") ), parIdx - 1 ) + 1;
00142 int lastSpace = txt.find( QRegExp( QLatin1String("[\\s\\:]") ), firstSpace );
00143 if( lastSpace == -1 )
00144 lastSpace = txt.length();
00145
00146 QString word = txt.mid( firstSpace, lastSpace - firstSpace );
00147 QString match;
00148
00149 kDebug(14000) << word << " from '" << txt << "'";
00150
00151 if ( word != m_lastMatch )
00152 {
00153 match = mComplete->makeCompletion( word );
00154 m_lastMatch.clear();
00155 parIdx -= word.length();
00156 }
00157 else
00158 {
00159 match = mComplete->nextMatch();
00160 parIdx -= m_lastMatch.length();
00161 }
00162
00163 if ( !match.isNull() && !match.isEmpty() )
00164 {
00165 QString rightText = txt.right( txt.length() - lastSpace );
00166
00167 if ( para == 0 && firstSpace == 0 && rightText[0] != QChar(':') )
00168 {
00169 rightText = match + QLatin1String(": ") + rightText;
00170 parIdx += 2;
00171 }
00172 else
00173 rightText = match + rightText;
00174
00175
00176
00177
00178 textEdit()->setUpdatesEnabled( false );
00179 textEdit()->insertParagraph( txt.left(firstSpace) + rightText, para );
00180 textEdit()->removeParagraph( para + 1 );
00181 textEdit()->setCursorPosition( para, parIdx + match.length() );
00182 textEdit()->setUpdatesEnabled( true );
00183
00184 textEdit()->updateContents();
00185 m_lastMatch = match;
00186 }
00187 else
00188 {
00189 kDebug(14000) << "No completions! Tried " << mComplete->items();
00190 }
00191 }
00192 #endif
00193 }
00194
00195 void ChatTextEditPart::slotPropertyChanged( Kopete::PropertyContainer*, const QString &key,
00196 const QVariant& oldValue, const QVariant &newValue )
00197 {
00198 if ( key == Kopete::Global::Properties::self()->nickName().key() )
00199 {
00200 mComplete->removeItem( oldValue.toString() );
00201 mComplete->addItem( newValue.toString() );
00202 }
00203 }
00204
00205 void ChatTextEditPart::slotContactAdded( const Kopete::Contact *contact )
00206 {
00207 connect( contact, SIGNAL( propertyChanged( Kopete::PropertyContainer *, const QString &, const QVariant &, const QVariant & ) ),
00208 this, SLOT( slotPropertyChanged( Kopete::PropertyContainer *, const QString &, const QVariant &, const QVariant & ) ) ) ;
00209
00210 QString contactName = contact->property(Kopete::Global::Properties::self()->nickName()).value().toString();
00211 mComplete->addItem( contactName );
00212 }
00213
00214 void ChatTextEditPart::slotContactRemoved( const Kopete::Contact *contact )
00215 {
00216 disconnect( contact, SIGNAL( propertyChanged( Kopete::PropertyContainer *, const QString &, const QVariant &, const QVariant & ) ),
00217 this, SLOT( slotPropertyChanged( Kopete::PropertyContainer *, const QString &, const QVariant &, const QVariant & ) ) ) ;
00218
00219 QString contactName = contact->property(Kopete::Global::Properties::self()->nickName()).value().toString();
00220 mComplete->removeItem( contactName );
00221 }
00222
00223 bool ChatTextEditPart::canSend()
00224 {
00225 int i;
00226
00227 if ( !m_session ) return false;
00228
00229
00230 if ( text(Qt::PlainText).isEmpty() )
00231 return false;
00232
00233 Kopete::ContactPtrList members = m_session->members();
00234
00235
00236 if ( !( m_session->protocol()->capabilities() & Kopete::Protocol::CanSendOffline ) )
00237 {
00238 bool reachableContactFound = false;
00239
00240
00241 for( i = 0; i != members.size(); i++ )
00242 {
00243 if ( members[i]->isReachable() )
00244 {
00245 reachableContactFound = true;
00246 break;
00247 }
00248 }
00249
00250
00251 if ( !reachableContactFound )
00252 return false;
00253 }
00254
00255 return true;
00256 }
00257
00258 void ChatTextEditPart::slotContactStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &newStatus, const Kopete::OnlineStatus &oldStatus )
00259 {
00260
00261 if ( ( oldStatus.status() == Kopete::OnlineStatus::Offline )
00262 != ( newStatus.status() == Kopete::OnlineStatus::Offline ) )
00263 {
00264 emit canSendChanged( canSend() );
00265 }
00266 }
00267
00268 void ChatTextEditPart::sendMessage()
00269 {
00270 QString txt = this->text( Qt::PlainText );
00271
00272 if ( txt.isEmpty() || txt == "\n" )
00273 return;
00274
00275 if ( m_lastMatch.isNull() && ( txt.indexOf( QRegExp( QLatin1String("^\\w+:\\s") ) ) > -1 ) )
00276 {
00277 QString search = txt.left( txt.indexOf(':') );
00278 if( !search.isEmpty() )
00279 {
00280 QString match = mComplete->makeCompletion( search );
00281 if( !match.isNull() )
00282 textEdit()->setText( txt.replace(0,search.length(),match) );
00283 }
00284 }
00285
00286 if ( !m_lastMatch.isNull() )
00287 {
00288
00289 mComplete->addItem( m_lastMatch );
00290 m_lastMatch.clear();
00291 }
00292
00293 slotStoppedTypingTimer();
00294 Kopete::Message sentMessage = contents();
00295 emit messageSent( sentMessage );
00296 historyList.prepend( this->text( Qt::PlainText) );
00297 historyPos = -1;
00298 clear();
00299 emit canSendChanged( false );
00300 }
00301
00302 bool ChatTextEditPart::isTyping()
00303 {
00304 QString txt = text( Qt::PlainText );
00305
00306
00307
00308
00309 return !txt.trimmed().isEmpty();
00310 }
00311
00312 void ChatTextEditPart::slotTextChanged()
00313 {
00314 if ( isTyping() )
00315 {
00316
00317 if( !m_typingRepeatTimer->isActive() )
00318 {
00319 m_typingRepeatTimer->setSingleShot( false );
00320 m_typingRepeatTimer->start( 4000 );
00321 slotRepeatTypingTimer();
00322 }
00323
00324
00325 m_typingStopTimer->setSingleShot( true );
00326 m_typingStopTimer->start( 4500 );
00327 }
00328
00329 emit canSendChanged( canSend() );
00330 }
00331
00332 void ChatTextEditPart::historyUp()
00333 {
00334 if ( historyList.empty() || historyPos == historyList.count() - 1 )
00335 return;
00336
00337 QString text = this->text(Qt::PlainText);
00338 bool empty = text.trimmed().isEmpty();
00339
00340
00341 if ( !empty )
00342 {
00343 if ( historyPos == -1 )
00344 {
00345 historyList.prepend( text );
00346 historyPos = 0;
00347 }
00348 else
00349 {
00350 historyList[historyPos] = text;
00351 }
00352 }
00353
00354 historyPos++;
00355
00356 QString newText = historyList[historyPos];
00357
00358
00359 textEdit()->setText( newText );
00360
00361 textEdit()->moveCursor( QTextCursor::End );
00362 }
00363
00364 void ChatTextEditPart::historyDown()
00365 {
00366 if ( historyList.empty() || historyPos == -1 )
00367 return;
00368
00369 QString text = this->text(Qt::PlainText);
00370 bool empty = text.trimmed().isEmpty();
00371
00372
00373 if ( !empty )
00374 {
00375 historyList[historyPos] = text;
00376 }
00377
00378 historyPos--;
00379
00380 QString newText = ( historyPos >= 0 ? historyList[historyPos] : QString() );
00381
00382
00383
00384
00385 textEdit()->setText( newText );
00386
00387 textEdit()->moveCursor( QTextCursor::End );
00388 }
00389
00390 void ChatTextEditPart::addText( const QString &text )
00391 {
00392 if( Qt::mightBeRichText(text) )
00393 {
00394 textEdit()->insertHtml( text );
00395 }
00396 else
00397 {
00398 textEdit()->insertPlainText( text );
00399 }
00400 }
00401
00402 void ChatTextEditPart::setContents( const Kopete::Message &message )
00403 {
00404 if ( useRichText() )
00405 textEdit()->setHtml ( message.escapedBody() );
00406 else
00407 textEdit()->setPlainText ( message.plainBody() );
00408
00409 setFont( message.font() );
00410 setTextColor( message.foregroundColor() );
00411
00412 }
00413
00414 Kopete::Message ChatTextEditPart::contents()
00415 {
00416 Kopete::Message currentMsg( m_session->myself(), m_session->members() );
00417 currentMsg.setDirection( Kopete::Message::Outbound );
00418 useRichText() ? currentMsg.setHtmlBody( text() ) : currentMsg.setPlainBody( text() );
00419
00420
00421 currentMsg.setForegroundColor( textColor() );
00422 currentMsg.setFont( font() );
00423
00424 return currentMsg;
00425 }
00426
00427 void ChatTextEditPart::slotRepeatTypingTimer()
00428 {
00429 emit typing( true );
00430 }
00431
00432 void ChatTextEditPart::slotStoppedTypingTimer()
00433 {
00434 m_typingRepeatTimer->stop();
00435 m_typingStopTimer->stop();
00436 emit typing( false );
00437 }
00438
00439 void ChatTextEditPart::setProtocolRichTextSupport()
00440 {
00441 KRichTextEditPart::RichTextSupport richText;
00442 Kopete::Protocol::Capabilities protocolCaps = m_session->protocol()->capabilities();
00443
00444
00445 if( (protocolCaps & Kopete::Protocol::BaseBFormatting) || (protocolCaps & Kopete::Protocol::RichBFormatting) )
00446 {
00447 richText |= KRichTextEditPart::SupportBold;
00448 }
00449
00450 if( (protocolCaps & Kopete::Protocol::BaseIFormatting) || (protocolCaps & Kopete::Protocol::RichIFormatting) )
00451 {
00452 richText |= KRichTextEditPart::SupportItalic;
00453 }
00454
00455 if( (protocolCaps & Kopete::Protocol::BaseUFormatting) || (protocolCaps & Kopete::Protocol::RichUFormatting) )
00456 {
00457 richText |= KRichTextEditPart::SupportUnderline;
00458 }
00459
00460 if( (protocolCaps & Kopete::Protocol::BaseFont) || (protocolCaps & Kopete::Protocol::RichFont) )
00461 {
00462 richText |= KRichTextEditPart::SupportFont;
00463 }
00464
00465 if( (protocolCaps & Kopete::Protocol::BaseFgColor) || (protocolCaps & Kopete::Protocol::RichFgColor) )
00466 {
00467 richText |= KRichTextEditPart::SupportTextColor;
00468 }
00469
00470 if( protocolCaps & Kopete::Protocol::Alignment )
00471 {
00472 richText |= KRichTextEditPart::SupportAlignment;
00473 }
00474
00475
00476 setRichTextSupport( richText );
00477 }
00478
00479 #include "chattexteditpart.moc"
00480
00481