/* * msgmle.cpp - subclass of PsiTextView to handle various hotkeys * Copyright (C) 2001-2003 Justin Karneges, Michail Pishchagin * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include"common.h" #include"msgmle.h" //---------------------------------------------------------------------------- // ChatView //---------------------------------------------------------------------------- ChatView::ChatView(QWidget *parent, const char *name) : PsiTextView(parent, name) { setWordWrap(WidgetWidth); setWrapPolicy(AtWordOrDocumentBoundary); setTextFormat(RichText); setReadOnly(true); setUndoRedoEnabled(false); setHScrollBarMode(QScrollView::AlwaysOff); #ifndef Q_WS_X11 // linux has this feature built-in connect(this, SIGNAL(copyAvailable(bool)), SLOT(autoCopy(bool))); #endif } ChatView::~ChatView() { } bool ChatView::focusNextPrevChild(bool next) { return QWidget::focusNextPrevChild(next); } void ChatView::keyPressEvent(QKeyEvent *e) { if(e->key() == Key_Escape) e->ignore(); #ifdef Q_WS_MAC else if(e->key() == Key_W && e->state() & ControlButton) e->ignore(); #endif else if(e->key() == Key_Return && ((e->state() & ControlButton) || (e->state() & AltButton)) ) e->ignore(); else if(e->key() == Key_H && (e->state() & ControlButton)) e->ignore(); else if(e->key() == Key_I && (e->state() & ControlButton)) e->ignore(); else if(e->key() == Key_M && (e->state() & ControlButton)) // newline insert("\n"); else if(e->key() == Key_U && (e->state() & ControlButton)) setText(""); else QTextEdit::keyPressEvent(e); } void ChatView::resizeEvent(QResizeEvent *e) { // This fixes flyspray #45 if(contentsY() == contentsHeight() - visibleHeight()) scrollToBottom(); QTextEdit::resizeEvent(e); } /*! Copies any selected text (from selection 0) to the clipboard if option.autoCopy is TRUE, \a copyAvailable is TRUE and ChatView is in read-only mode. In any other case it does nothing. This slot is connected with copyAvailable(bool) signal in ChatView's constructor. \sa copyAvailable() */ void ChatView::autoCopy(bool copyAvailable) { if ( isReadOnly() && copyAvailable && option.autoCopy ) { copy(); } } //---------------------------------------------------------------------------- // ChatEdit //---------------------------------------------------------------------------- ChatEdit::ChatEdit(QWidget *parent, const char *name) : PsiTextView(parent, name) { setWordWrap(QTextEdit::WidgetWidth); setReadOnly(false); setUndoRedoEnabled(true); setTextFormat(PlainText); setMinimumHeight(48); } ChatEdit::~ChatEdit() { } void ChatEdit::keyPressEvent(QKeyEvent *e) { if(e->key() == Key_Escape || (e->key() == Key_W && e->state() & ControlButton)) e->ignore(); else if(e->key() == Key_Return && ((e->state() & ControlButton) || (e->state() & AltButton)) ) e->ignore(); else if(e->key() == Key_M && (e->state() & ControlButton)) // newline insert("\n"); else if(e->key() == Key_H && (e->state() & ControlButton)) // history e->ignore(); else if(e->key() == Key_S && (e->state() & AltButton)) e->ignore(); else if(e->key() == Key_Backtab && (e->state() & ControlButton)) e->ignore(); else if(e->key() == Key_Tab && (e->state() & ControlButton)) e->ignore(); else if(e->key() == Key_U && (e->state() & ControlButton)) setText(""); else if((e->key() == Key_Return || e->key() == Key_Enter) && !(e->state() & ShiftButton) && option.chatSoftReturn) e->ignore(); else if((e->key() == Key_PageUp || e->key() == Key_PageDown) && (e->state() & ShiftButton)) e->ignore(); else if((e->key() == Key_PageUp || e->key() == Key_PageDown) && (e->state() & ControlButton)) e->ignore(); else QTextEdit::keyPressEvent(e); } //---------------------------------------------------------------------------- // LineEdit //---------------------------------------------------------------------------- LineEdit::LineEdit( QWidget *parent, const char *name ) : ChatEdit( parent, name ) { lastSize = QSize( 0, 0 ); initialWindowGeometry = QRect( 0, 0, 0, 0 ); QWidget *topParent = topLevelWidget(); topParent->installEventFilter( this ); moveTo = QPoint(topParent->x(), topParent->y()); moveTimer = new QTimer( this ); connect( moveTimer, SIGNAL(timeout()), SLOT(checkMoved()) ); // LineEdit's size hint is to be vertically as small as possible setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Maximum ); setWrapPolicy( AtWordOrDocumentBoundary ); // no need for horizontal scrollbar with this setHScrollBarMode(QScrollView::AlwaysOff); setMinimumHeight(-1); connect( this, SIGNAL( textChanged() ), SLOT( recalculateSize() ) ); } LineEdit::~LineEdit() { } /*! * Returns true if the dialog could be automatically resized by LineEdit. */ bool LineEdit::allowResize() const { QWidget *topParent = topLevelWidget(); QRect desktop = qApp->desktop()->availableGeometry( (QWidget *)topParent ); float desktopArea = desktop.width() * desktop.height(); float dialogArea = topParent->frameGeometry().width() * topParent->frameGeometry().height(); // maximized and large chat windows shoulnd't resize the dialog if ( (dialogArea / desktopArea) > 0.9 ) return false; return true; } /*! * In this implementation, it is quivalent to sizeHint(). */ QSize LineEdit::minimumSizeHint() const { return sizeHint(); } /*! * All magic is contained within this function. It determines the possible maximum * height, and controls the appearance of vertical scrollbar. */ QSize LineEdit::sizeHint() const { if ( lastSize.width() != 0 && lastSize.height() != 0 ) return lastSize; lastSize.setWidth( QTextEdit::sizeHint().width() ); int h = 7; if ( paragraphs() > 0 ) { for ( int i = 0; i < paragraphs(); i++ ) { h += paragraphRect( i ).height(); } } QWidget *topParent = topLevelWidget(); QRect desktop = qApp->desktop()->availableGeometry( (QWidget *)topParent ); int dh = h - height(); bool showScrollBar = false; // check that our dialog's height doesn't exceed the desktop's if ( allowResize() && (topParent->frameGeometry().height() + dh) >= desktop.height() ) { // handles the case when the dialog could be resized, // but lineedit wants to occupy too much space, so we should limit it h = desktop.height() - ( topParent->frameGeometry().height() - height() ); showScrollBar = true; } else if ( !allowResize() && (h > topParent->geometry().height()/2) ) { // handles the case when the dialog could not be resized(i.e. it's maximized). // in this case we limit maximum height of lineedit to the half of dialog's // full height h = topParent->geometry().height() / 2; showScrollBar = true; } // enable vertical scrollbar only when we're surely in need for it QTextEdit *textEdit = (QTextEdit *)this; if ( showScrollBar ) textEdit->setVScrollBarMode( AlwaysOn ); else textEdit->setVScrollBarMode( AlwaysOff ); lastSize.setHeight( h ); return lastSize; } /*! * Handles automatic dialog resize. */ void LineEdit::recalculateSize() { if ( !isUpdatesEnabled() ) return; QSize oldSize = lastSize; lastSize = QSize( 0, 0 ); // force sizeHint() to update QSize newSize = sizeHint(); if ( QABS(newSize.height() - oldSize.height()) > 1 ) { if ( allowResize() ) { parentWidget()->layout()->setEnabled( false ); // try to reduce some flicker QWidget *topParent = topLevelWidget(); int dh = newSize.height() - oldSize.height(); // if we're going to shrink dialog considerably, minimum // size will prevent us from doing it. Activating main // layout after resize will reset minimum sizes to sensible values topParent->setMinimumSize( 10, 10 ); topParent->resize( topParent->width(), topParent->height() + dh ); bool canMove = dh > 0; int newy = topParent->y(); // try to move window to its old position if ( movedWindow() && dh < 0 ) { newy = initialWindowGeometry.y(); canMove = true; } // check, if we need to move dialog upper QRect desktop = qApp->desktop()->availableGeometry( (QWidget *)topParent ); if ( canMove && ( newy + topParent->frameGeometry().height() >= desktop.bottom() ) ) { // initialize default window position if ( !movedWindow() ) { initialWindowGeometry = topParent->frameGeometry(); } newy = QMAX(0, desktop.bottom() - topParent->frameGeometry().height()); } if ( canMove && newy != topParent->y() ) { topParent->move( topParent->x(), newy ); moveTo = topParent->pos(); } parentWidget()->layout()->setEnabled( true ); } // issue a layout update parentWidget()->layout()->activate(); } } void LineEdit::resizeEvent( QResizeEvent *e ) { // issue a re-layout, just in case lastSize = QSize( 0, 0 ); // force sizeHint() to update sizeHint(); // update the size hint, and cache the value topLevelWidget()->layout()->activate(); PsiTextView::resizeEvent( e ); } void LineEdit::setUpdatesEnabled( bool enable ) { bool ue = isUpdatesEnabled(); ChatEdit::setUpdatesEnabled( enable ); if ( !ue && enable ) recalculateSize(); } bool LineEdit::eventFilter(QObject *watched, QEvent *e) { if ( !parentWidget()->layout()->isEnabled() ) return ChatEdit::eventFilter( watched, e ); if ( e->type() == QEvent::Reparent ) { // In case of tabbed chats, dialog could be reparented to a higher-level dialog // we need to get move events from it too. And unnecessary event filters // are automatically cleaned up by Qt. topLevelWidget()->installEventFilter( this ); } else if ( e->type() == QEvent::Move ) { QWidget *topParent = topLevelWidget(); if ( watched == topParent ) { moveTimer->start( 100, true ); } } return ChatEdit::eventFilter( watched, e ); } //! This function serves as a workaround for multiple move events, some of which //! have incorrect coordinates (at least on KDE) void LineEdit::checkMoved() { QWidget *topParent = topLevelWidget(); if ( QABS(moveTo.x() - topParent->x()) > 1 || QABS(moveTo.y() - topParent->y()) > 1 ) { moveTo = topParent->pos(); initialWindowGeometry = QRect( 0, 0, 0, 0 ); } } bool LineEdit::movedWindow() const { return initialWindowGeometry.left() || initialWindowGeometry.top() || initialWindowGeometry.width() || initialWindowGeometry.height(); }