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.
552 lines
17 KiB
552 lines
17 KiB
/*
|
|
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.
|
|
*/
|
|
|
|
/*
|
|
The line input widget with chat enhanced functions
|
|
begin: Tue Mar 5 2002
|
|
copyright: (C) 2002 by Dario Abatianni
|
|
email: eisfuchs@tigress.com
|
|
*/
|
|
|
|
#include "ircinput.h"
|
|
#include "konversationapplication.h"
|
|
#include "multilineedit.h"
|
|
#include "chatwindow.h"
|
|
#include "ircview.h"
|
|
|
|
#include <tqrichtext_p.h>
|
|
#include <tqclipboard.h>
|
|
#include <tqregexp.h>
|
|
#include <tqdom.h>
|
|
#include <tqevent.h>
|
|
#include <tqobject.h>
|
|
#include <tqwhatsthis.h>
|
|
#include <tqpopupmenu.h>
|
|
|
|
#include <tdeapplication.h>
|
|
#include <tdemessagebox.h>
|
|
#include <tdelocale.h>
|
|
#include <tdecompletionbox.h>
|
|
|
|
#define MAXHISTORY 100
|
|
#define RICHTEXT 0
|
|
|
|
|
|
IRCInput::IRCInput(TQWidget* parent) : KTextEdit(parent)
|
|
{
|
|
m_lastHeight=document()->height();
|
|
|
|
//I am not terribly interested in finding out where this value comes from
|
|
//nor in compensating for it if my guess is incorrect. so, cache it.
|
|
m_qtBoxPadding=m_lastHeight-fontMetrics().lineSpacing();
|
|
|
|
connect(TDEApplication::kApplication(), TQT_SIGNAL(appearanceChanged()), this, TQT_SLOT(updateAppearance()));
|
|
m_multiRow = Preferences::useMultiRowInputBox();
|
|
|
|
m_useSelection = false;
|
|
|
|
// connect history signal
|
|
connect(this,TQT_SIGNAL (history(bool)) ,this,TQT_SLOT (getHistory(bool)) );
|
|
// add one empty line to the history (will be overwritten with newest entry)
|
|
historyList.prepend(TQString());
|
|
// reset history line counter
|
|
lineNum=0;
|
|
// reset completion mode
|
|
setCompletionMode('\0');
|
|
completionBox = new TDECompletionBox(this);
|
|
connect(completionBox, TQT_SIGNAL(activated(const TQString&)), this, TQT_SLOT(insertCompletion(const TQString&)));
|
|
|
|
// widget may not be resized vertically
|
|
setSizePolicy(TQSizePolicy(TQSizePolicy::MinimumExpanding,TQSizePolicy::Fixed));
|
|
|
|
//NoWrap coupled with the size policy constrains the line edit to be one row high
|
|
setWordWrap(m_multiRow ? WidgetWidth : NoWrap);
|
|
|
|
setHScrollBarMode(AlwaysOff);
|
|
setVScrollBarMode(AlwaysOff);
|
|
#if RICHTEXT == 1
|
|
setAutoFormatting(TQTextEdit::AutoNone);
|
|
setTextFormat(RichText);
|
|
#else
|
|
setTextFormat(PlainText);
|
|
#endif
|
|
|
|
TQWhatsThis::add(this, i18n("<qt>The input line is where you type messages to be sent the channel, query, or server. A message sent to a channel is seen by everyone on the channel, whereas a message in a query is sent only to the person in the query with you.<p>To automatically complete the nickname you began typing, press Tab. If you have not begun typing, the last successfully completed nickname will be used.<p>You can also send special commands:<br><table><tr><th>/me <i>action</i></th><td>shows up as an action in the channel or query. For example: <em>/me sings a song</em> will show up in the channel as 'Nick sings a song'.</td></tr><tr><th>/whois <i>nickname</i></th><td>shows information about this person, including what channels they are in.</td></tr></table><p>For more commands, see the Konversation Handbook.<p>A message cannot contain multiple lines.</qt>"));
|
|
|
|
m_disableSpellCheckTimer = new TQTimer(this);
|
|
connect(m_disableSpellCheckTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(disableSpellChecking()));
|
|
}
|
|
|
|
IRCInput::~IRCInput()
|
|
{
|
|
}
|
|
|
|
void IRCInput::showEvent(TQShowEvent* /* e */)
|
|
{
|
|
m_disableSpellCheckTimer->stop();
|
|
setCheckSpellingEnabled(Preferences::spellChecking());
|
|
}
|
|
|
|
void IRCInput::hideEvent(TQHideEvent* /* event */)
|
|
{
|
|
Preferences::setSpellChecking(checkSpellingEnabled());
|
|
|
|
// If we disable spell-checking here immediately, tab switching will
|
|
// be very slow. If we delay it by five seconds, a user would have to
|
|
// need more than five seconds to switch between all his tabs before
|
|
// the slowdown starts to occur (show event stops the timer, i.e. wrap-
|
|
// around is not an issue). Unless he has unlikely amounts of channels,
|
|
// needing more than five seconds indicates very slow switching speed,
|
|
// which makes the delay a non-issue to begin with. Hence this fixes
|
|
// the problem on the surface. In the KDE 4 version, we want to look
|
|
// into having only one spell-checker instance instead of starting and
|
|
// stopping at all.
|
|
m_disableSpellCheckTimer->start(5000, true);
|
|
}
|
|
|
|
void IRCInput::disableSpellChecking()
|
|
{
|
|
setCheckSpellingEnabled(false);
|
|
}
|
|
void IRCInput::slotSpellCheckDone(const TQString& s)
|
|
{
|
|
// NOTE: tdelibs 3.5's KSpell stupidly adds newlines to its
|
|
// buffer at some point for god-knows-what-reason, and for-
|
|
// gets to remove them again before handing the result back.
|
|
// There's a FIXME to the effect in KSpell::check. This is
|
|
// a workaround.
|
|
|
|
if (s == text() || s == (text() + '\n'+'\n'))
|
|
return;
|
|
|
|
setText(s.simplifyWhiteSpace());
|
|
}
|
|
|
|
void IRCInput::updateAppearance()
|
|
{
|
|
m_multiRow = Preferences::useMultiRowInputBox();
|
|
setWordWrap(m_multiRow ? WidgetWidth : NoWrap);
|
|
m_lastHeight=heightForWidth(sizeHint().width());
|
|
ensureCursorVisible(); //appears to trigger updateGeometry
|
|
}
|
|
|
|
void IRCInput::resizeContents( int w, int h )
|
|
{
|
|
if (document()->height() != m_lastHeight) {
|
|
m_lastHeight=document()->height();
|
|
updateGeometry();
|
|
}
|
|
KTextEdit::resizeContents(w,h);
|
|
}
|
|
|
|
// widget must be only one line high - luckily QT will enforce this via wrappping policy
|
|
TQSize IRCInput::sizeHint() const
|
|
{
|
|
constPolish();
|
|
|
|
int ObscurePadding = 4;
|
|
int f=2*frameWidth();
|
|
int w=12 * (kMax(fontMetrics().lineSpacing(),14) + f + ObscurePadding);
|
|
int h=m_lastHeight - m_qtBoxPadding + f + ObscurePadding;
|
|
return TQSize(w,h);
|
|
}
|
|
|
|
TQPopupMenu *IRCInput::createPopupMenu( const TQPoint &pos )
|
|
{
|
|
TQPopupMenu *menu=KTextEdit::createPopupMenu(pos);
|
|
menu->removeItemAt(menu->count()-1);
|
|
menu->removeItemAt(menu->count()-1);
|
|
return menu;
|
|
}
|
|
|
|
TQString IRCInput::text() const
|
|
{
|
|
#if RICHTEXT == 1
|
|
TQString content=KTextEdit::text();
|
|
|
|
TQDomDocument document;
|
|
|
|
document.setContent(content,false);
|
|
TQDomNodeList nodes=document.elementsByTagName("p");
|
|
if(nodes.count())
|
|
{
|
|
TQDomElement node=nodes.item(0).toElement();
|
|
return node.text();
|
|
}
|
|
return TQString();
|
|
|
|
#else
|
|
return KTextEdit::text();
|
|
#endif
|
|
}
|
|
|
|
void IRCInput::setText(const TQString& text)
|
|
{
|
|
// reimplemented to set cursor at the end of the new text
|
|
KTextEdit::setText(text);
|
|
setCursorPosition(0,text.length()+1);
|
|
}
|
|
|
|
// FIXME - find a better way to do this. eventfilters introduce nebulous behaviour
|
|
//take text events from IRCView and TopicLabel
|
|
bool IRCInput::eventFilter(TQObject *object,TQEvent *event)
|
|
{
|
|
if (object->isA("IRCView") || object->isA("Konversation::TopicLabel"))
|
|
{
|
|
if (event->type() == TQEvent::KeyPress)
|
|
{
|
|
TQKeyEvent* ke = TQT_TQKEYEVENT(event);
|
|
|
|
// Allow tab to be handled naturally by the widget.
|
|
// Once it runs out of links it goes to the next control.
|
|
if (ke->key() == Key_Tab && (ke->state() == 0 || ke->state() == TQt::ShiftButton))
|
|
return false;
|
|
|
|
if (!ke->text().isEmpty() && ((ke->state() & (TQt::ShiftButton|TQt::Keypad)) || ke->state() == 0))
|
|
{
|
|
setFocus();
|
|
KonversationApplication::sendEvent(this,event);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return KTextEdit::eventFilter(object,event);
|
|
}
|
|
|
|
// Take care of Tab, Cursor and so on
|
|
void IRCInput::keyPressEvent(TQKeyEvent* e)
|
|
{
|
|
switch(e->key())
|
|
{
|
|
case Key_Tab:
|
|
emit nickCompletion();
|
|
return;
|
|
break;
|
|
|
|
case Key_Up:
|
|
if (m_multiRow && (e->state() != (TQt::ShiftButton|TQt::ControlButton)))
|
|
break;
|
|
emit history(true);
|
|
return;
|
|
break;
|
|
|
|
case Key_Down:
|
|
if (m_multiRow && (e->state() != (TQt::ShiftButton|TQt::ControlButton)))
|
|
break;
|
|
emit history(false);
|
|
return;
|
|
break;
|
|
|
|
case Key_Enter:
|
|
case Key_Return:
|
|
{
|
|
if(text().length()) addHistory(text());
|
|
if(completionBox->isHidden())
|
|
{
|
|
// Reset completion mode
|
|
setCompletionMode('\0');
|
|
|
|
// Ctrl+Enter is a special case in which commands should be send as normal messages
|
|
if ( e->state() & ControlButton )
|
|
{
|
|
emit envelopeCommand();
|
|
}
|
|
else
|
|
{
|
|
setText(static_cast<KonversationApplication*>(kapp)->doAutoreplace(text(),true));
|
|
emit submit();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
insertCompletion(completionBox->currentText());
|
|
completionBox->hide();
|
|
}
|
|
// prevent widget from adding lines
|
|
return;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Check if the keystroke actually produced text. If not it was just a qualifier.
|
|
if(!e->text().isEmpty() || ((e->key() >= TQt::Key_Home) && (e->key() <= TQt::Key_Down)))
|
|
{
|
|
if(getCompletionMode()!='\0')
|
|
{
|
|
setCompletionMode('\0');
|
|
emit endCompletion();
|
|
}
|
|
|
|
completionBox->hide();
|
|
}
|
|
|
|
// support ASCII BEL
|
|
if(e->ascii() == 7)
|
|
insert("%G");
|
|
// support ^U (delete text in input box)
|
|
else if(e->ascii() == 21)
|
|
setText("");
|
|
}
|
|
|
|
KTextEdit::keyPressEvent(e);
|
|
}
|
|
|
|
void IRCInput::addHistory(const TQString& line)
|
|
{
|
|
// Only add line if it's not the same as the last was
|
|
if(historyList[1]!=line)
|
|
{
|
|
// Replace empty first entry with line
|
|
historyList[0]=line;
|
|
// Add new empty entry to history
|
|
historyList.prepend(TQString());
|
|
// Remove oldest line in history, if the list grows beyond MAXHISTORY
|
|
if(historyList.count()>MAXHISTORY) historyList.remove(historyList.last());
|
|
}
|
|
// Reset history counter
|
|
lineNum=0;
|
|
}
|
|
|
|
void IRCInput::getHistory(bool up)
|
|
{
|
|
// preserve text
|
|
historyList[lineNum]=text();
|
|
// Did the user press cursor up?
|
|
if(up)
|
|
{
|
|
// increment the line counter
|
|
lineNum++;
|
|
// if we are past the end of the list, go to the last entry
|
|
if(lineNum==historyList.count()) lineNum--;
|
|
}
|
|
// no, it was cursor down
|
|
else
|
|
{
|
|
// If we are at the top of the lest, arrow-down shall add the text to the history and clear the field for new input
|
|
if(lineNum==0)
|
|
{
|
|
if(text().length()) addHistory(text());
|
|
setText("");
|
|
}
|
|
// If we aren't at the top of the list, decrement the line counter
|
|
else
|
|
{
|
|
lineNum--;
|
|
}
|
|
}
|
|
// replace the text in the input field with history
|
|
setText(historyList[lineNum]);
|
|
}
|
|
|
|
/**
|
|
* Work around the fact that while TQTextEdit::paste() is virtual, whether we are
|
|
* pasting from middle button or control-V is PRIVATE and NO ACCESSOR is given.
|
|
*/
|
|
void IRCInput::contentsMouseReleaseEvent( TQMouseEvent *ev)
|
|
{
|
|
if (ev->button() == TQt::MidButton)
|
|
{
|
|
m_useSelection=true;
|
|
}
|
|
|
|
// Reset completion
|
|
setCompletionMode('\0');
|
|
emit endCompletion();
|
|
|
|
KTextEdit::contentsMouseReleaseEvent(ev);
|
|
m_useSelection=false;
|
|
}
|
|
|
|
void IRCInput::paste(bool useSelection)
|
|
{
|
|
m_useSelection = useSelection;
|
|
paste();
|
|
m_useSelection = false;
|
|
}
|
|
|
|
void IRCInput::paste()
|
|
{
|
|
TQClipboard *cb = TDEApplication::kApplication()->clipboard();
|
|
setFocus();
|
|
|
|
// Copy text from the clipboard (paste)
|
|
TQString pasteText;
|
|
if(m_useSelection)
|
|
{
|
|
pasteText = cb->text( TQClipboard::Selection);
|
|
}
|
|
else
|
|
{
|
|
pasteText = cb->text( TQClipboard::Clipboard);
|
|
}
|
|
|
|
// is there any text in the clipboard?
|
|
if(!pasteText.isEmpty())
|
|
{
|
|
//End completion on paste
|
|
setCompletionMode('\0');
|
|
emit endCompletion();
|
|
|
|
bool signal=false;
|
|
|
|
// replace \r with \n to make xterm pastes happy
|
|
pasteText.replace("\r","\n");
|
|
// remove blank lines
|
|
while(pasteText.contains("\n\n"))
|
|
pasteText.replace("\n\n","\n");
|
|
|
|
TQRegExp reTopSpace("^ *\n");
|
|
while(pasteText.contains(reTopSpace))
|
|
pasteText.remove(reTopSpace);
|
|
|
|
TQRegExp reBottomSpace("\n *$");
|
|
while(pasteText.contains(reBottomSpace))
|
|
pasteText.remove(reBottomSpace);
|
|
|
|
// Escape % when var expansion is enabled
|
|
if (!Preferences::disableExpansion())
|
|
{
|
|
pasteText.replace ('%', "%%");
|
|
}
|
|
|
|
// does the text contain at least one newline character?
|
|
if(pasteText.find('\n')!=-1)
|
|
{
|
|
// make comparisons easier (avoid signed / unsigned warnings)
|
|
unsigned int pos=pasteText.find('\n');
|
|
unsigned int rpos=pasteText.findRev('\n');
|
|
|
|
// emit the signal if there's a line break in the middle of the text
|
|
if(pos>0 && pos!=(pasteText.length()-1))
|
|
signal=true;
|
|
// emit the signal if there's more than one line break in the text
|
|
if(pos!=rpos)
|
|
signal=true;
|
|
|
|
// Remove the \n from end of the line if there's only one \n
|
|
if(!signal)
|
|
pasteText.remove('\n');
|
|
}
|
|
else
|
|
{
|
|
insert(pasteText);
|
|
return;
|
|
}
|
|
|
|
// should we signal the application due to newlines in the paste?
|
|
if(signal)
|
|
{
|
|
// if there is text in the input line
|
|
if(!text().isEmpty())
|
|
{
|
|
// prepend text to the paste
|
|
pasteText=text()+'\n'+pasteText;
|
|
}
|
|
// ask the user on long pastes
|
|
if(checkPaste(pasteText))
|
|
{
|
|
// signal pasted text
|
|
emit textPasted(pasteText);
|
|
// remember old line, in case the user does not paste eventually
|
|
addHistory(pasteText);
|
|
// delete input text
|
|
setText("");
|
|
}
|
|
}
|
|
// otherwise let the KLineEdit handle the pasting
|
|
else KTextEdit::paste();
|
|
}
|
|
}
|
|
|
|
bool IRCInput::checkPaste(TQString& text)
|
|
{
|
|
int doPaste=KMessageBox::Yes;
|
|
|
|
//text is now preconditioned when you get here
|
|
int lines=text.contains('\n');
|
|
|
|
if(text.length()>256 || lines)
|
|
{
|
|
doPaste=KMessageBox::warningYesNoCancel
|
|
(this,
|
|
i18n("<qt>You are attempting to paste a large portion of text (%1 bytes or %2 lines) into "
|
|
"the chat. This can cause connection resets or flood kills. "
|
|
"Do you really want to continue?</qt>").arg(text.length()).arg(lines+1),
|
|
i18n("Large Paste Warning"),
|
|
i18n("Paste"),
|
|
i18n("&Edit..."),
|
|
"LargePaste",
|
|
KMessageBox::Dangerous);
|
|
}
|
|
|
|
if (doPaste==KMessageBox::No)
|
|
{
|
|
TQString ret(MultilineEdit::edit(this,text));
|
|
if (ret.isEmpty())
|
|
return false;
|
|
text=ret;
|
|
return true;
|
|
}
|
|
|
|
return (doPaste==KMessageBox::Yes);
|
|
}
|
|
|
|
void IRCInput::showCompletionList(const TQStringList& nicks)
|
|
{
|
|
completionBox->setItems(nicks);
|
|
completionBox->popup();
|
|
}
|
|
|
|
void IRCInput::insertCompletion(const TQString& nick)
|
|
{
|
|
int pos; // = cursorPosition();
|
|
int oldPos; // = cursorPosition();
|
|
|
|
getCursorPosition(&oldPos,&pos);
|
|
oldPos=pos;
|
|
|
|
TQString line = text();
|
|
|
|
while(pos && line[pos-1] != ' ') pos--;
|
|
|
|
line.remove(pos, oldPos - pos);
|
|
|
|
// did we find the nick in the middle of the line?
|
|
if(pos)
|
|
{
|
|
TQString addMiddle(Preferences::nickCompleteSuffixMiddle());
|
|
line.insert(pos, nick + addMiddle);
|
|
pos += nick.length() + addMiddle.length();
|
|
}
|
|
// no, it was at the beginning
|
|
else
|
|
{
|
|
setLastCompletion(nick);
|
|
TQString addStart(Preferences::nickCompleteSuffixStart());
|
|
line.insert(pos, nick + addStart);
|
|
pos += nick.length() + addStart.length();
|
|
}
|
|
|
|
setText(line);
|
|
setCursorPosition(0,pos);
|
|
}
|
|
|
|
void IRCInput::setLastCompletion(const TQString& completion)
|
|
{
|
|
m_lastCompletion = completion;
|
|
}
|
|
|
|
// Accessor methods
|
|
|
|
void IRCInput::setCompletionMode(char mode) { completionMode=mode; }
|
|
char IRCInput::getCompletionMode() { return completionMode; }
|
|
void IRCInput::setOldCursorPosition(int pos) { oldPos=pos; }
|
|
int IRCInput::getOldCursorPosition() { return oldPos; }
|
|
|
|
#include "ircinput.moc"
|