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