You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
421 lines
12 KiB
421 lines
12 KiB
/*
|
|
chattexteditpart.cpp - Chat Text Edit Part
|
|
|
|
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. *
|
|
* *
|
|
*************************************************************************
|
|
*/
|
|
|
|
#include "chattexteditpart.h"
|
|
|
|
#include "kopetechatsession.h"
|
|
#include "kopeteonlinestatus.h"
|
|
#include "kopeteprotocol.h"
|
|
#include "kopeteglobal.h"
|
|
#include "kopeteprefs.h"
|
|
|
|
#include <kcompletion.h>
|
|
#include <kdebug.h>
|
|
#include <ktextedit.h>
|
|
#include <ksyntaxhighlighter.h>
|
|
|
|
#include <tqtimer.h>
|
|
#include <tqregexp.h>
|
|
|
|
ChatTextEditPart::ChatTextEditPart( Kopete::ChatSession *session, TQWidget *parent, const char *name )
|
|
: KopeteRichTextEditPart( parent, name, session->protocol()->capabilities() ), m_session(session)
|
|
{
|
|
historyPos = -1;
|
|
|
|
toggleAutoSpellCheck(KopetePrefs::prefs()->spellCheck());
|
|
|
|
mComplete = new TDECompletion();
|
|
mComplete->setIgnoreCase( true );
|
|
mComplete->setOrder( TDECompletion::Weighted );
|
|
|
|
// set params on the edit widget
|
|
edit()->setMinimumSize( TQSize( 75, 20 ) );
|
|
edit()->setWordWrap( TQTextEdit::WidgetWidth );
|
|
edit()->setWrapPolicy( TQTextEdit::AtWhiteSpace );
|
|
edit()->setAutoFormatting( TQTextEdit::AutoNone );
|
|
|
|
// some signals and slots connections
|
|
connect( edit(), TQT_SIGNAL( textChanged()), this, TQT_SLOT( slotTextChanged() ) );
|
|
|
|
// timers for typing notifications
|
|
m_typingRepeatTimer = new TQTimer(this, "m_typingRepeatTimer");
|
|
m_typingStopTimer = new TQTimer(this, "m_typingStopTimer");
|
|
|
|
connect( m_typingRepeatTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( slotRepeatTypingTimer() ) );
|
|
connect( m_typingStopTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( slotStoppedTypingTimer() ) );
|
|
|
|
connect( session, TQT_SIGNAL( contactAdded(const Kopete::Contact*, bool) ),
|
|
this, TQT_SLOT( slotContactAdded(const Kopete::Contact*) ) );
|
|
connect( session, TQT_SIGNAL( contactRemoved(const Kopete::Contact*, const TQString&, Kopete::Message::MessageFormat, bool) ),
|
|
this, TQT_SLOT( slotContactRemoved(const Kopete::Contact*) ) );
|
|
connect( session, TQT_SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus & , const Kopete::OnlineStatus &) ),
|
|
this, TQT_SLOT( slotContactStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) );
|
|
|
|
slotContactAdded( session->myself() );
|
|
for ( TQPtrListIterator<Kopete::Contact> it( session->members() ); it.current(); ++it )
|
|
slotContactAdded( *it );
|
|
}
|
|
|
|
ChatTextEditPart::~ChatTextEditPart()
|
|
{
|
|
delete mComplete;
|
|
}
|
|
|
|
KTextEdit *ChatTextEditPart::edit()
|
|
{
|
|
return static_cast<KTextEdit*>(widget());
|
|
}
|
|
|
|
void ChatTextEditPart::toggleAutoSpellCheck( bool enabled )
|
|
{
|
|
if ( richTextEnabled() )
|
|
enabled = false;
|
|
|
|
m_autoSpellCheckEnabled = enabled;
|
|
if ( spellHighlighter() )
|
|
{
|
|
spellHighlighter()->setAutomatic( enabled );
|
|
spellHighlighter()->setActive( enabled );
|
|
}
|
|
edit()->setCheckSpellingEnabled( enabled );
|
|
}
|
|
|
|
bool ChatTextEditPart::autoSpellCheckEnabled() const
|
|
{
|
|
return m_autoSpellCheckEnabled;
|
|
}
|
|
|
|
KDictSpellingHighlighter* ChatTextEditPart::spellHighlighter()
|
|
{
|
|
TQSyntaxHighlighter *qsh = edit()->syntaxHighlighter();
|
|
KDictSpellingHighlighter* kdsh = dynamic_cast<KDictSpellingHighlighter*>( qsh );
|
|
return kdsh;
|
|
}
|
|
|
|
// NAUGHTY, BAD AND WRONG! (but needed to fix nick complete bugs)
|
|
#include <tqrichtext_p.h>
|
|
class EvilTextEdit : public KTextEdit
|
|
{
|
|
public:
|
|
// grab the paragraph as plain text - very very evil.
|
|
TQString plainText( int para )
|
|
{
|
|
TQString str = document()->paragAt( para )->string()->toString();
|
|
// str includes an extra space on the end (from the newline character?) - remove it
|
|
return str.left( str.length() - 1 );
|
|
}
|
|
};
|
|
|
|
void ChatTextEditPart::complete()
|
|
{
|
|
int para = 1, parIdx = 1;
|
|
edit()->getCursorPosition( ¶, &parIdx);
|
|
|
|
// FIXME: strips out all formatting
|
|
TQString txt = static_cast<EvilTextEdit*>(edit())->plainText( para );
|
|
|
|
if ( parIdx > 0 )
|
|
{
|
|
int firstSpace = txt.findRev( TQRegExp( TQString::fromLatin1("\\s\\S+") ), parIdx - 1 ) + 1;
|
|
int lastSpace = txt.find( TQRegExp( TQString::fromLatin1("[\\s\\:]") ), firstSpace );
|
|
if( lastSpace == -1 )
|
|
lastSpace = txt.length();
|
|
|
|
TQString word = txt.mid( firstSpace, lastSpace - firstSpace );
|
|
TQString match;
|
|
|
|
kdDebug(14000) << k_funcinfo << word << " from '" << txt << "'" << endl;
|
|
|
|
if ( word != m_lastMatch )
|
|
{
|
|
match = mComplete->makeCompletion( word );
|
|
m_lastMatch = TQString();
|
|
parIdx -= word.length();
|
|
}
|
|
else
|
|
{
|
|
match = mComplete->nextMatch();
|
|
parIdx -= m_lastMatch.length();
|
|
}
|
|
|
|
if ( !match.isNull() && !match.isEmpty() )
|
|
{
|
|
TQString rightText = txt.right( txt.length() - lastSpace );
|
|
|
|
if ( para == 0 && firstSpace == 0 && rightText[0] != TQChar(':') )
|
|
{
|
|
rightText = match + TQString::fromLatin1(": ") + rightText;
|
|
parIdx += 2;
|
|
}
|
|
else
|
|
rightText = match + rightText;
|
|
|
|
// insert *before* remove. this is becase TQt adds an extra blank line
|
|
// if the rich text control becomes empty (if you remove the only para).
|
|
// disable updates while we change the contents to eliminate flicker.
|
|
edit()->setUpdatesEnabled( false );
|
|
edit()->insertParagraph( txt.left(firstSpace) + rightText, para );
|
|
edit()->removeParagraph( para + 1 );
|
|
edit()->setCursorPosition( para, parIdx + match.length() );
|
|
edit()->setUpdatesEnabled( true );
|
|
// must call this rather than update because TQTextEdit is broken :(
|
|
edit()->updateContents();
|
|
m_lastMatch = match;
|
|
}
|
|
else
|
|
{
|
|
kdDebug(14000) << k_funcinfo << "No completions! Tried " << mComplete->items() << endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ChatTextEditPart::slotPropertyChanged( Kopete::Contact*, const TQString &key,
|
|
const TQVariant& oldValue, const TQVariant &newValue )
|
|
{
|
|
if ( key == Kopete::Global::Properties::self()->nickName().key() )
|
|
{
|
|
mComplete->removeItem( oldValue.toString() );
|
|
mComplete->addItem( newValue.toString() );
|
|
}
|
|
}
|
|
|
|
void ChatTextEditPart::slotContactAdded( const Kopete::Contact *contact )
|
|
{
|
|
connect( contact, TQT_SIGNAL( propertyChanged( Kopete::Contact *, const TQString &, const TQVariant &, const TQVariant & ) ),
|
|
this, TQT_SLOT( slotPropertyChanged( Kopete::Contact *, const TQString &, const TQVariant &, const TQVariant & ) ) ) ;
|
|
|
|
TQString contactName = contact->property(Kopete::Global::Properties::self()->nickName()).value().toString();
|
|
mComplete->addItem( contactName );
|
|
}
|
|
|
|
void ChatTextEditPart::slotContactRemoved( const Kopete::Contact *contact )
|
|
{
|
|
disconnect( contact, TQT_SIGNAL( propertyChanged( Kopete::Contact *, const TQString &, const TQVariant &, const TQVariant & ) ),
|
|
this, TQT_SLOT( slotPropertyChanged( Kopete::Contact *, const TQString &, const TQVariant &, const TQVariant & ) ) ) ;
|
|
|
|
TQString contactName = contact->property(Kopete::Global::Properties::self()->nickName()).value().toString();
|
|
mComplete->removeItem( contactName );
|
|
}
|
|
|
|
bool ChatTextEditPart::canSend()
|
|
{
|
|
if ( !m_session ) return false;
|
|
|
|
// can't send if there's nothing *to* send...
|
|
if ( edit()->text().isEmpty() )
|
|
return false;
|
|
|
|
Kopete::ContactPtrList members = m_session->members();
|
|
|
|
// if we can't send offline, make sure we have a reachable contact...
|
|
if ( !( m_session->protocol()->capabilities() & Kopete::Protocol::CanSendOffline ) )
|
|
{
|
|
bool reachableContactFound = false;
|
|
|
|
//TODO: does this perform badly in large / busy IRC channels? - no, doesn't seem to
|
|
TQPtrListIterator<Kopete::Contact> it ( members );
|
|
for( ; it.current(); ++it )
|
|
{
|
|
if ( (*it)->isReachable() )
|
|
{
|
|
reachableContactFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// no online contact found and can't send offline? can't send.
|
|
if ( !reachableContactFound )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ChatTextEditPart::slotContactStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &newStatus, const Kopete::OnlineStatus &oldStatus )
|
|
{
|
|
//FIXME: should use signal contact->isReachableChanged, but it doesn't exist ;(
|
|
if ( ( oldStatus.status() == Kopete::OnlineStatus::Offline )
|
|
!= ( newStatus.status() == Kopete::OnlineStatus::Offline ) )
|
|
{
|
|
emit canSendChanged( canSend() );
|
|
}
|
|
}
|
|
|
|
void ChatTextEditPart::sendMessage()
|
|
{
|
|
TQString txt = text( TQt::PlainText );
|
|
// avoid sending emtpy messages or enter keys (see bug 100334)
|
|
if ( txt.isEmpty() || txt == "\n" )
|
|
return;
|
|
|
|
if ( m_lastMatch.isNull() && ( txt.find( TQRegExp( TQString::fromLatin1("^\\w+:\\s") ) ) > -1 ) )
|
|
{ //no last match and it finds something of the form of "word:" at the start of a line
|
|
TQString search = txt.left( txt.find(':') );
|
|
if( !search.isEmpty() )
|
|
{
|
|
TQString match = mComplete->makeCompletion( search );
|
|
if( !match.isNull() )
|
|
edit()->setText( txt.replace(0,search.length(),match) );
|
|
}
|
|
}
|
|
|
|
if ( !m_lastMatch.isNull() )
|
|
{
|
|
//FIXME: what is the next line for?
|
|
mComplete->addItem( m_lastMatch );
|
|
m_lastMatch = TQString();
|
|
}
|
|
|
|
slotStoppedTypingTimer();
|
|
Kopete::Message sentMessage = contents();
|
|
emit messageSent( sentMessage );
|
|
historyList.prepend( edit()->text() );
|
|
historyPos = -1;
|
|
clear();
|
|
emit canSendChanged( false );
|
|
}
|
|
|
|
bool ChatTextEditPart::isTyping()
|
|
{
|
|
TQString txt = text( TQt::PlainText );
|
|
|
|
//Make sure the message is empty. TQString::isEmpty()
|
|
//returns false if a message contains just whitespace
|
|
//which is the reason why we strip the whitespace
|
|
return !txt.stripWhiteSpace().isEmpty();
|
|
}
|
|
|
|
void ChatTextEditPart::slotTextChanged()
|
|
{
|
|
if ( isTyping() )
|
|
{
|
|
// And they were previously typing
|
|
if( !m_typingRepeatTimer->isActive() )
|
|
{
|
|
m_typingRepeatTimer->start( 4000, false );
|
|
slotRepeatTypingTimer();
|
|
}
|
|
|
|
// Reset the stop timer again, regardless of status
|
|
m_typingStopTimer->start( 4500, true );
|
|
}
|
|
|
|
emit canSendChanged( canSend() );
|
|
}
|
|
|
|
void ChatTextEditPart::historyUp()
|
|
{
|
|
if ( historyList.empty() || historyPos == historyList.count() - 1 )
|
|
return;
|
|
|
|
TQString text = edit()->text();
|
|
bool empty = text.stripWhiteSpace().isEmpty();
|
|
|
|
// got text? save it
|
|
if ( !empty )
|
|
{
|
|
if ( historyPos == -1 )
|
|
{
|
|
historyList.prepend( text );
|
|
historyPos = 0;
|
|
}
|
|
else
|
|
{
|
|
historyList[historyPos] = text;
|
|
}
|
|
}
|
|
|
|
historyPos++;
|
|
|
|
TQString newText = historyList[historyPos];
|
|
TextFormat format=edit()->textFormat();
|
|
edit()->setTextFormat(AutoText); //workaround bug 115690
|
|
edit()->setText( newText );
|
|
edit()->setTextFormat(format);
|
|
edit()->moveCursor( TQTextEdit::MoveEnd, false );
|
|
}
|
|
|
|
void ChatTextEditPart::historyDown()
|
|
{
|
|
if ( historyList.empty() || historyPos == -1 )
|
|
return;
|
|
|
|
TQString text = edit()->text();
|
|
bool empty = text.stripWhiteSpace().isEmpty();
|
|
|
|
// got text? save it
|
|
if ( !empty )
|
|
{
|
|
historyList[historyPos] = text;
|
|
}
|
|
|
|
historyPos--;
|
|
|
|
TQString newText = ( historyPos >= 0 ? historyList[historyPos] : TQString() );
|
|
|
|
|
|
TextFormat format=edit()->textFormat();
|
|
edit()->setTextFormat(AutoText); //workaround bug 115690
|
|
edit()->setText( newText );
|
|
edit()->setTextFormat(format);
|
|
|
|
|
|
|
|
edit()->moveCursor( TQTextEdit::MoveEnd, false );
|
|
}
|
|
|
|
void ChatTextEditPart::addText( const TQString &text )
|
|
{
|
|
edit()->insert( text );
|
|
}
|
|
|
|
void ChatTextEditPart::setContents( const Kopete::Message &message )
|
|
{
|
|
edit()->setText( richTextEnabled() ? message.escapedBody() : message.plainBody() );
|
|
|
|
setFont( message.font() );
|
|
setFgColor( message.fg() );
|
|
setBgColor( message.bg() );
|
|
}
|
|
|
|
Kopete::Message ChatTextEditPart::contents()
|
|
{
|
|
Kopete::Message currentMsg( m_session->myself(), m_session->members(), edit()->text(),
|
|
Kopete::Message::Outbound, richTextEnabled() ?
|
|
Kopete::Message::RichText : Kopete::Message::PlainText );
|
|
|
|
currentMsg.setBg( bgColor() );
|
|
currentMsg.setFg( fgColor() );
|
|
currentMsg.setFont( font() );
|
|
|
|
return currentMsg;
|
|
}
|
|
|
|
void ChatTextEditPart::slotRepeatTypingTimer()
|
|
{
|
|
emit typing( true );
|
|
}
|
|
|
|
void ChatTextEditPart::slotStoppedTypingTimer()
|
|
{
|
|
m_typingRepeatTimer->stop();
|
|
m_typingStopTimer->stop();
|
|
emit typing( false );
|
|
}
|
|
|
|
#include "chattexteditpart.moc"
|