// The implementation of the TQt specific subclass of ScintillaBase.
//
// Copyright (c) 2006
// 	Riverbank Computing Limited <info@riverbankcomputing.co.uk>
// 
// This file is part of TQScintilla.
// 
// This copy of TQScintilla 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, or (at your option) any
// later version.
// 
// TQScintilla is supplied 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
// TQScintilla; see the file LICENSE.  If not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <tqapplication.h>
#include <tqscrollbar.h>
#include <tqpopupmenu.h>
#include <tqstring.h>
#include <tqtimer.h>
#include <tqclipboard.h>
#include <tqdragobject.h>
#include <tqpainter.h>

#include "tqextscintillabase.h"
#include "ScintillaTQt.h"


// We want to use the Scintilla notification names as TQt signal names.
#undef	SCEN_CHANGE
#undef	SCN_AUTOCSELECTION
#undef	SCN_CALLTIPCLICK
#undef	SCN_CHARADDED
#undef	SCN_DOUBLECLICK
#undef	SCN_DWELLEND
#undef	SCN_DWELLSTART
#undef	SCN_HOTSPOTCLICK
#undef	SCN_HOTSPOTDOUBLECLICK
#undef	SCN_MACRORECORD
#undef	SCN_MARGINCLICK
#undef	SCN_MODIFIED
#undef	SCN_MODIFYATTEMPTRO
#undef	SCN_NEEDSHOWN
#undef	SCN_PAINTED
#undef	SCN_SAVEPOINTLEFT
#undef	SCN_SAVEPOINTREACHED
#undef	SCN_STYLENEEDED
#undef	SCN_UPDATEUI
#undef	SCN_USERLISTSELECTION
#undef	SCN_ZOOM

enum
{
	SCEN_CHANGE = 768,
	SCN_AUTOCSELECTION = 2022,
	SCN_CALLTIPCLICK = 2021,
	SCN_CHARADDED = 2001,
	SCN_DOUBLECLICK = 2006,
	SCN_DWELLEND = 2017,
	SCN_DWELLSTART = 2016,
	SCN_HOTSPOTCLICK = 2019,
	SCN_HOTSPOTDOUBLECLICK = 2020,
	SCN_MACRORECORD = 2009,
	SCN_MARGINCLICK = 2010,
	SCN_MODIFIED = 2008,
	SCN_MODIFYATTEMPTRO = 2004,
	SCN_NEEDSHOWN = 2011,
	SCN_PAINTED = 2013,
	SCN_SAVEPOINTLEFT = 2003,
	SCN_SAVEPOINTREACHED = 2002,
	SCN_STYLENEEDED = 2000,
	SCN_UPDATEUI = 2007,
	SCN_USERLISTSELECTION = 2014,
	SCN_ZOOM = 2018
};


// The ctor.
ScintillaTQt::ScintillaTQt(TQextScintillaBase *tqsb_) :
				capturedMouse(false), tqsb(tqsb_)
{
	wMain = tqsb -> txtarea;

	// We aren't a TQObject so we use the API class to do TQObject related
	// things for us.
	tqsb -> connect(&qtimer,TQ_SIGNAL(timeout()),TQ_SLOT(handleTimer()));
	
	Initialise();
}


// The dtor.
ScintillaTQt::~ScintillaTQt()
{ 
	Finalise();
}


// Initialise the instance.
void ScintillaTQt::Initialise()
{
	SetTicking(true);
}


// Tidy up the instance.
void ScintillaTQt::Finalise()
{
	SetTicking(false);
	ScintillaBase::Finalise();
}


// Start a drag.
void ScintillaTQt::StartDrag()
{
	// Allow applications to re-implement the drag start.
	tqsb -> startDrag();
}


// Do the real drag start.
void ScintillaTQt::StartDragImpl()
{
	TQDragObject *dobj = new TQTextDrag(textRange(&drag),tqsb -> txtarea);

	// Remove the dragged text if it was a move to another widget or
	// application.
	if (dobj -> drag() && dobj -> target() != tqsb -> txtarea)
		ClearSelection();

	inDragDrop = false;
	SetDragPosition(invalidPosition);
}


// Handle a drag enter event.
void ScintillaTQt::dragEnterEvent(TQDragEnterEvent *dee)
{
	dragMoveEvent(dee);
}


// Handle a drag move event.
void ScintillaTQt::dragMoveEvent(TQDragMoveEvent *dme)
{
	dme -> acceptAction(TQTextDrag::canDecode(dme));
	SetDragPosition(PositionFromLocation(Point(dme -> pos().x(),dme -> pos().y())));
}


// Handle a drag leave event.
void ScintillaTQt::dragLeaveEvent(TQDragLeaveEvent *dle)
{
	SetDragPosition(invalidPosition);
}


// Handle a drop event.
void ScintillaTQt::dropEvent(TQDropEvent *de)
{
	TQString text;

	if (TQTextDrag::decode(de,text))
	{
		bool moving = (de -> source() == tqsb -> txtarea && de -> action() == TQDropEvent::Move);

		de -> acceptAction();

		const char *s;
		TQCString us;

		if (IsUnicodeMode())
		{
			us = text.utf8();
			s = us.data();
		}
		else
			s = text.latin1();

		DropAt(posDrop,s,moving,false);
		SetDragPosition(invalidPosition);
		Redraw();
	}
}


// Re-implement to trap certain messages.
sptr_t ScintillaTQt::WndProc(unsigned int iMessage,uptr_t wParam,sptr_t lParam)
{
	switch (iMessage)
	{
	case SCI_GRABFOCUS:
		PWindow(wMain) -> setFocus();
		return 0;

	case SCI_GETDIRECTFUNCTION:
		return reinterpret_cast<sptr_t>(DirectFunction);
	
	case SCI_GETDIRECTPOINTER:
		return reinterpret_cast<sptr_t>(this);
	}

	return ScintillaBase::WndProc(iMessage,wParam,lParam);
}


// Windows nonsense.
sptr_t ScintillaTQt::DefWndProc(unsigned int,uptr_t,sptr_t)
{
	return 0;
}


// Manage the timer.
void ScintillaTQt::SetTicking(bool on)
{
	if (timer.ticking != on)
	{
		timer.ticking = on;

		if (timer.ticking)
			qtimer.start(timer.tickSize);
		else
			qtimer.stop();
	}

	timer.ticksToWait = caret.period;
}


// Grab or release the mouse (and keyboard).
void ScintillaTQt::SetMouseCapture(bool on)
{
	if (mouseDownCaptures)
		if (on)
			PWindow(wMain) -> grabMouse();
		else
			PWindow(wMain) -> releaseMouse();

	capturedMouse = on;
}


// Return true if the mouse/keyboard are currently grabbed.
bool ScintillaTQt::HaveMouseCapture()
{
	return capturedMouse;
}


// Set the position of the vertical scrollbar.
void ScintillaTQt::SetVerticalScrollPos()
{
	tqsb -> vsb -> setValue(topLine);
}


// Set the position of the horizontal scrollbar.
void ScintillaTQt::SetHorizontalScrollPos()
{
	tqsb -> hsb -> setValue(xOffset);
}


// Set the extent of the vertical and horizontal scrollbars and return true if
// the view needs re-drawing.
bool ScintillaTQt::ModifyScrollBars(int nMax,int nPage)
{
	tqsb -> vsb -> setMinValue(0);
	tqsb -> vsb -> setMaxValue(nMax - nPage + 1);
	tqsb -> vsb -> setLineStep(1);
	tqsb -> vsb -> setPageStep(nPage);

	tqsb -> hsb -> setMinValue(0);
	tqsb -> hsb -> setMaxValue(scrollWidth);
	tqsb -> hsb -> setPageStep(scrollWidth / 10);

	return true;
}


// Called after SCI_SETWRAPMODE and SCI_SETHSCROLLBAR.
void ScintillaTQt::ReconfigureScrollBars()
{
	// Hide or show the scrollbars if needed.
	if (horizontalScrollBarVisible && wrapState == eWrapNone)
		tqsb->hsb->show();
	else
		tqsb->hsb->hide();

	if (verticalScrollBarVisible)
		tqsb->vsb->show();
	else
		tqsb->vsb->hide();
}


// Notify interested parties of any change in the document.
void ScintillaTQt::NotifyChange()
{
	emit tqsb -> SCEN_CHANGE();
}


// Notify interested parties of various events.  This is the main mapping
// between Scintilla notifications and TQt signals.
void ScintillaTQt::NotifyParent(SCNotification scn)
{
	switch (scn.nmhdr.code)
	{
	case SCN_CALLTIPCLICK:
		emit tqsb -> SCN_CALLTIPCLICK(scn.position);
		break;

	case SCN_AUTOCSELECTION:
		emit tqsb -> SCN_AUTOCSELECTION(scn.text,scn.lParam);
		break;

	case SCN_CHARADDED:
		emit tqsb -> SCN_CHARADDED(scn.ch);
		break;

	case SCN_DOUBLECLICK:
		emit tqsb -> SCN_DOUBLECLICK();
		break;

	case SCN_DWELLEND:
		emit tqsb -> SCN_DWELLEND(scn.position,scn.x,scn.y);
		break;

	case SCN_DWELLSTART:
		emit tqsb -> SCN_DWELLSTART(scn.position,scn.x,scn.y);
		break;

	case SCN_HOTSPOTCLICK:
		emit tqsb -> SCN_HOTSPOTCLICK(scn.position,scn.modifiers);
		break;

	case SCN_HOTSPOTDOUBLECLICK:
		emit tqsb -> SCN_HOTSPOTDOUBLECLICK(scn.position,scn.modifiers);
		break;

	case SCN_MACRORECORD:
		emit tqsb -> SCN_MACRORECORD(scn.message,scn.wParam,scn.lParam);
		break;

	case SCN_MARGINCLICK:
		emit tqsb -> SCN_MARGINCLICK(scn.position,scn.modifiers,
					    scn.margin);
		break;

	case SCN_MODIFIED:
		emit tqsb -> SCN_MODIFIED(scn.position,scn.modificationType,
					 scn.text,scn.length,scn.linesAdded,
					 scn.line,scn.foldLevelNow,
					 scn.foldLevelPrev);
		break;

	case SCN_MODIFYATTEMPTRO:
		emit tqsb -> SCN_MODIFYATTEMPTRO();
		break;

	case SCN_NEEDSHOWN:
		emit tqsb -> SCN_NEEDSHOWN(scn.position,scn.length);
		break;

	case SCN_PAINTED:
		emit tqsb -> SCN_PAINTED();
		break;

	case SCN_SAVEPOINTLEFT:
		emit tqsb -> SCN_SAVEPOINTLEFT();
		break;

	case SCN_SAVEPOINTREACHED:
		emit tqsb -> SCN_SAVEPOINTREACHED();
		break;

	case SCN_STYLENEEDED:
		emit tqsb -> SCN_STYLENEEDED(scn.position);
		break;

	case SCN_UPDATEUI:
		emit tqsb -> SCN_UPDATEUI();
		break;

	case SCN_USERLISTSELECTION:
		emit tqsb -> SCN_USERLISTSELECTION(scn.text,scn.wParam);
		break;

	case SCN_ZOOM:
		emit tqsb -> SCN_ZOOM();
		break;

	default:
		tqWarning("Unknown notification: %u",scn.nmhdr.code);
	}
}


// Handle a key that hasn't been filtered out as a command key.  Return 0 if we
// haven't handled it.
int ScintillaTQt::KeyDefault(int key,int modifiers)
{
	// On Windows Alt Gr is returned as Ctrl-Alt (on X11 it seems to be the
	// Meta key).  We therefore ignore that combination.
#if defined(Q_OS_WIN)
	modifiers &= (SCI_CTRL | SCI_ALT);

	if (modifiers == SCI_CTRL || modifiers == SCI_ALT)
		return 0;
#else
	if (modifiers & (SCI_CTRL | SCI_ALT))
		return 0;
#endif

	AddChar(key);

	return 1;
}


// Convert a text range to a TQString.
TQString ScintillaTQt::textRange(const SelectionText *text)
{
	TQString qs;

	if (text -> s)
		if (IsUnicodeMode())
			qs = TQString::fromUtf8(text -> s);
		else
			qs.setLatin1(text -> s);

	return qs;
}


// Copy the selected text to the clipboard.
void ScintillaTQt::CopyToClipboard(const SelectionText &selectedText)
{
	TQApplication::clipboard() -> setText(textRange(&selectedText));
}


// Implement copy.
void ScintillaTQt::Copy()
{
	if (currentPos != anchor)
	{
		SelectionText text;

		CopySelectionRange(&text);
		CopyToClipboard(text);
	}
}


// Implement paste.
void ScintillaTQt::Paste()
{
	TQString str = TQApplication::clipboard() -> text();

	if (str.isEmpty())
		return;

	pdoc -> BeginUndoAction();

	ClearSelection();

	int len;

	if (IsUnicodeMode())
	{
		TQCString s = str.utf8();

		len = s.length();

		if (len)
			pdoc -> InsertString(currentPos,s.data(),len);
	}
	else
	{
		const char *s = str.latin1();

		len = (s ? strlen(s) : 0);

		if (len)
			pdoc -> InsertString(currentPos,s,len);
	}

	SetEmptySelection(currentPos + len);

	pdoc -> EndUndoAction();

	NotifyChange();
	Redraw();
}


// A simple TQWidget sub-class to implement a call tip.  No need to bother with
// all the moc stuff.
class TQtCallTip : public TQWidget
{
public:
	TQtCallTip(TQWidget *parent,ScintillaTQt *sci_);
	~TQtCallTip();

protected:
	void paintEvent(TQPaintEvent *);
	void mousePressEvent(TQMouseEvent *me);

private:
	ScintillaTQt *sci;
};


// Create a call tip.
TQtCallTip::TQtCallTip(TQWidget *parent,ScintillaTQt *sci_) :
		TQWidget(parent,0,WType_Popup|WStyle_Customize|WStyle_NoBorder), sci(sci_)
{
	// Ensure that the main window keeps the focus (and the caret flashing)
	// when this is displayed.
	setFocusProxy(parent);
}


// Destroy a call tip.
TQtCallTip::~TQtCallTip()
{
	// Ensure that the main window doesn't receive a focus out event when
	// this is destroyed.
	setFocusProxy(0);
}


// Paint a call tip.
void TQtCallTip::paintEvent(TQPaintEvent *)
{
	Surface *surfaceWindow = Surface::Allocate();

	if (surfaceWindow)
	{
		TQPainter p(this);

		surfaceWindow -> Init(&p,0);
		sci -> ct.PaintCT(surfaceWindow);
		surfaceWindow -> Release();

		delete surfaceWindow;
	}
}


// Handle a mouse press in a call tip.
void TQtCallTip::mousePressEvent(TQMouseEvent *me)
{
	Point pt;

	pt.x = me -> x();
	pt.y = me -> y();

	sci -> ct.MouseClick(pt);
	sci -> CallTipClick();
}


// Create a call tip window.
void ScintillaTQt::CreateCallTipWindow(PRectangle rc)
{
	if (!ct.wCallTip.Created())
		ct.wCallTip = ct.wDraw = new TQtCallTip(tqsb,this);

	PWindow(ct.wCallTip) -> resize(rc.right - rc.left,rc.bottom - rc.top);
	ct.wCallTip.Show();
}


// Add an item to the right button menu.
void ScintillaTQt::AddToPopUp(const char *label,int cmd,bool enabled)
{
	TQPopupMenu *pm = static_cast<TQPopupMenu *>(popup.GetID());

	if (label[0] != '\0')
	{
		TQString tr_label = tqApp -> translate("ContextMenu",label);

		pm -> insertItem(tr_label,tqsb,TQ_SLOT(handlePopUp(int)),0,cmd);
		pm -> setItemEnabled(cmd,enabled);
	}
	else
		pm -> insertSeparator();
}


// Claim the selection.
void ScintillaTQt::ClaimSelection()
{
	bool isSel = (currentPos != anchor);

	if (isSel)
	{
		TQClipboard *cb = TQApplication::clipboard();

		// If we support X11 style selection then make it available
		// now.
		if (cb -> supportsSelection())
		{
			SelectionText text;

			CopySelectionRange(&text);

			if (text.s)
			{
				cb -> setSelectionMode(true);
				cb -> setText(text.s);
				cb -> setSelectionMode(false);
			}
		}

		primarySelection = true;
	}
	else
		primarySelection = false;

	emit tqsb -> TQSCN_SELCHANGED(isSel);
}


// Unclaim the selection.
void ScintillaTQt::UnclaimSelection()
{
	if (primarySelection)
	{
		primarySelection = false;
		tqsb -> txtarea -> update();
	}
}


// Implemented to provide compatibility with the Windows version.
sptr_t ScintillaTQt::DirectFunction(ScintillaTQt *sciThis,unsigned int iMessage,
				   uptr_t wParam,sptr_t lParam)
{
	return sciThis -> WndProc(iMessage,wParam,lParam);
}


// Draw the contents of the widget.
void ScintillaTQt::paintEvent(TQPaintEvent *pe)
{
	bool isUnicodeMode = (pdoc && pdoc -> dbcsCodePage == SC_CP_UTF8);

	paintState = painting;

	const TQRect &qr = pe -> rect();

	rcPaint.left = qr.left();
	rcPaint.top = qr.top();
	rcPaint.right = qr.right() + 1;
	rcPaint.bottom = qr.bottom() + 1;

	PRectangle rcText = GetTextRectangle();
	paintingAllText = rcPaint.Contains(rcText);

	Surface *sw = Surface::Allocate();

	if (sw)
	{
		TQPainter painter(PWindow(wMain));

		sw -> Init(&painter,0);
		sw -> SetUnicodeMode(isUnicodeMode);
		Paint(sw,rcPaint);
		sw -> Release();
		delete sw;

		// If the painting area was insufficient to cover the new style
		// or brace highlight positions then repaint the whole thing.
		if (paintState == paintAbandoned)
			PWindow(wMain) -> update();
	}

	paintState = notPainting;
}
