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.
tdenetwork/kopete/kopete/chatwindow/chattexteditpart.cpp

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( &para, &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"