/*
 * 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<qapplication.h>
#include<qlayout.h>
#include<qtimer.h>

#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()
{
}

bool ChatEdit::focusNextPrevChild(bool next)
{
	return QWidget::focusNextPrevChild(next);
}

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_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();
}

