/* 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 #include #include #include #include #include #include #include #include #include #include #include #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("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.

To automatically complete the nickname you began typing, press Tab. If you have not begun typing, the last successfully completed nickname will be used.

You can also send special commands:
/me actionshows up as an action in the channel or query. For example: /me sings a song will show up in the channel as 'Nick sings a song'.
/whois nicknameshows information about this person, including what channels they are in.

For more commands, see the Konversation Handbook.

A message cannot contain multiple lines.")); 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(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() == Qt::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("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?").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"