/***************************************************************************

  CTextBox.cpp

  (c) 2000-2012 Benoît Minisini <gambas@users.sourceforge.net>

  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, 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 program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  MA 02110-1301, USA.

***************************************************************************/

#define __CTEXTBOX_CPP

#include <qapplication.h>
#include <qcombobox.h>
#include <qcursor.h>
#include <qsizepolicy.h>
#include <QLineEdit>
#include <QListView>

#include "gambas.h"

#include "CConst.h"
#include "CTextBox.h"

DECLARE_EVENT(EVENT_Change);
DECLARE_EVENT(EVENT_Activate);
DECLARE_EVENT(EVENT_Click);

#define MAX_LEN 32767


/***************************************************************************

	TextBox

***************************************************************************/

static bool get(void *_object, QLineEdit **wid, bool error = true)
{
	QComboBox *combo;

	if (qobject_cast<QLineEdit *>(TEXTBOX))
	{
		*wid = TEXTBOX;
		return false;
	}

	combo = COMBOBOX;
	if (!combo->isEditable())
	{
		if (error)
			GB.Error("ComboBox is read-only");
		return true;
	}

	*wid = combo->lineEdit();
	return false;
}

#define GET_TEXT_BOX() \
	QLineEdit *textbox; \
	if (get(_object, &textbox)) \
		return;



BEGIN_METHOD(CTEXTBOX_new, GB_OBJECT parent)

	QLineEdit *wid = new QLineEdit(QCONTAINER(VARG(parent)));

	QObject::connect(wid, SIGNAL(textChanged(const QString &)), &CTextBox::manager, SLOT(onChange()));
	QObject::connect(wid, SIGNAL(returnPressed()), &CTextBox::manager, SLOT(onActivate()));

	wid->setAlignment(Qt::AlignLeft);

	CWIDGET_new(wid, (void *)_object);
	
END_METHOD


BEGIN_METHOD_VOID(CTEXTBOX_clear)

	TEXTBOX->clear();

END_METHOD

BEGIN_METHOD(CTEXTBOX_insert, GB_STRING text)

	GET_TEXT_BOX();

	//textbox->insert(QString(GB.ToZeroString(ARG(text))));
	textbox->insert(QSTRING_ARG(text));

END_METHOD


BEGIN_PROPERTY(CTEXTBOX_text)

	if (READ_PROPERTY)
		GB.ReturnNewZeroString(TO_UTF8(TEXTBOX->text()));
	else
		TEXTBOX->setText(QSTRING_PROP());

END_PROPERTY


BEGIN_PROPERTY(CTEXTBOX_length)

	GB.ReturnInteger(TEXTBOX->text().length());

END_PROPERTY


BEGIN_PROPERTY(CTEXTBOX_alignment)

	if (READ_PROPERTY)
		GB.ReturnInteger(CCONST_alignment(TEXTBOX->alignment() + Qt::AlignVCenter, ALIGN_NORMAL, false));
	else
		TEXTBOX->setAlignment((Qt::Alignment)CCONST_alignment(VPROP(GB_INTEGER), ALIGN_NORMAL, true) & Qt::AlignHorizontal_Mask);

END_PROPERTY


BEGIN_PROPERTY(CTEXTBOX_pos)

	GET_TEXT_BOX();

	if (READ_PROPERTY)
		GB.ReturnInteger(textbox->cursorPosition());
	else
		textbox->setCursorPosition(VPROP(GB_INTEGER));

END_PROPERTY


BEGIN_PROPERTY(CTEXTBOX_read_only)

	if (READ_PROPERTY)
		GB.ReturnBoolean(TEXTBOX->isReadOnly());
	else
		TEXTBOX->setReadOnly(VPROP(GB_BOOLEAN));

END_PROPERTY


BEGIN_PROPERTY(CTEXTBOX_border)

	GET_TEXT_BOX();

	if (READ_PROPERTY)
		GB.ReturnBoolean(textbox->hasFrame());
	else
		textbox->setFrame(VPROP(GB_BOOLEAN));

END_PROPERTY


BEGIN_PROPERTY(CTEXTBOX_password)

	GET_TEXT_BOX();

	if (READ_PROPERTY)
		GB.ReturnBoolean(textbox->echoMode() != QLineEdit::Normal);
	else
		textbox->setEchoMode(VPROP(GB_BOOLEAN) ? QLineEdit::Password : QLineEdit::Normal);

END_PROPERTY


BEGIN_PROPERTY(CTEXTBOX_max_length)

	int max;

	GET_TEXT_BOX();

	if (READ_PROPERTY)
	{
		max = textbox->maxLength();
		GB.ReturnInteger(max >= MAX_LEN ? 0 : max);
	}
	else
	{
		max = VPROP(GB_INTEGER);
		if (max < 1 || max > MAX_LEN)
			max = MAX_LEN;

		textbox->setMaxLength(max);
	}

END_PROPERTY


/***************************************************************************

	.TextBox.Selection

***************************************************************************/

BEGIN_PROPERTY(CTEXTBOX_sel_text)

	GET_TEXT_BOX();

	if (READ_PROPERTY)
		GB.ReturnNewZeroString(TO_UTF8(textbox->selectedText()));
	else
		textbox->insert(QSTRING_PROP());

END_PROPERTY


static void set_selection(QLineEdit *textbox, int start, int length)
{
	int len = (int)textbox->text().length();

	if (start < 0 || start >= len)
	{
		start = textbox->cursorPosition();
		length = 0;
	}

	textbox->setCursorPosition(start);

	if (length <= 0)
		textbox->deselect();
	else
	{
		if ((start + length) >= len)
			length = len - start;
		textbox->setSelection(start, length);
	}
}

static void get_selection(QLineEdit *textbox, int *start, int *length)
{
	*start = textbox->selectionStart();
	if (*start < 0)
		*start = textbox->cursorPosition();
	if (!textbox->hasSelectedText())
		*length = 0;
	else
		*length = textbox->selectedText().length();
}


BEGIN_PROPERTY(CTEXTBOX_sel_length)

	int start, length;

	GET_TEXT_BOX();

	get_selection(textbox, &start, &length);

	GB.ReturnInteger(length);

END_PROPERTY


BEGIN_PROPERTY(CTEXTBOX_sel_start)

	int start, length;

	GET_TEXT_BOX();

	get_selection(textbox, &start, &length);
	GB.ReturnInteger(start);

END_PROPERTY


BEGIN_METHOD_VOID(CTEXTBOX_sel_clear)

	GET_TEXT_BOX();

	textbox->deselect();

END_METHOD

BEGIN_METHOD_VOID(CTEXTBOX_selected)

	GET_TEXT_BOX();

	GB.ReturnBoolean(textbox->hasSelectedText());

END_METHOD


BEGIN_METHOD(CTEXTBOX_sel_select, GB_INTEGER start; GB_INTEGER length)

	GET_TEXT_BOX();

	if (MISSING(start) && MISSING(length))
		textbox->selectAll();
	else if (!MISSING(start) && !MISSING(length))
		set_selection(textbox, VARG(start), VARG(length));

END_METHOD

BEGIN_METHOD_VOID(CTEXTBOX_sel_all)

	GET_TEXT_BOX();

	textbox->selectAll();

END_METHOD


/***************************************************************************

	ComboBox

***************************************************************************/

#undef THIS
#define THIS OBJECT(CCOMBOBOX)

static void raise_click_event(void *_object)
{
	if (THIS->click)
		return;
	THIS->click = true;
	GB.Raise(THIS, EVENT_Click, 0);
	THIS->click = false;
	
}

static int combo_get_current_item(void *_object)
{
	COMBOBOX->sort();
	return COMBOBOX->count() == 0 ? -1 : COMBOBOX->currentItem();
}

static void combo_set_current_item(void *_object, int item)
{
	COMBOBOX->sort();
	
	if (item != combo_get_current_item(THIS))
	{
		if (item < COMBOBOX->count())
			COMBOBOX->setCurrentItem(item);
	}
	
	if (item >= 0 && !COMBOBOX->signalsBlocked())
		raise_click_event(THIS);
}

static int combo_find_item(void *_object, const QString& s)
{
	COMBOBOX->sort();
	for (int i = 0; i < (int)COMBOBOX->count(); i++)
	{
		if (COMBOBOX->text(i) == s)
			return i;
	}

	return (-1);
}

static void combo_set_text(CCOMBOBOX *_object, QString &text)
{
	int pos;

	pos = combo_find_item(THIS, text);
	if (!COMBOBOX->isEditable() || pos >= 0)
		combo_set_current_item(_object, pos);
	if (COMBOBOX->isEditable())
		COMBOBOX->lineEdit()->setText(text);
}

static void combo_set_editable(void *_object, bool ed)
{
	QLineEdit *textbox;
	QString text;
	bool hasFocus = COMBOBOX->hasFocus();
	
	COMBOBOX->blockSignals(true);
	text = COMBOBOX->currentText();

	if (ed)
	{
		if (!COMBOBOX->isEditable())
		{
			//CWidget::removeFilter(COMBOBOX);
			COMBOBOX->setEditable(true);
			COMBOBOX->setCompleter(0);
			//CWidget::installFilter(COMBOBOX);
			QObject::connect(COMBOBOX->lineEdit(), SIGNAL(returnPressed()), &CTextBox::manager, SLOT(onActivate()));

			if (CWIDGET_test_flag(THIS, WF_DESIGN))
			{
				get(_object, &textbox);
				//textbox->removeEventFilter(COMBOBOX);
				COMBOBOX->setFocusProxy(0);
			}
		}
	}
	else
	{
		if (COMBOBOX->isEditable())
		{
			get(THIS, &textbox);
			textbox->setFocusProxy(0);
			COMBOBOX->setEditable(false);
			COMBOBOX->update();
		}
	}
	
	combo_set_text(THIS, text);

	if (hasFocus)
		COMBOBOX->setFocus();
	
	if (CWIDGET_test_flag(THIS, WF_DESIGN))
		COMBOBOX->setFocusPolicy(Qt::NoFocus);

	COMBOBOX->blockSignals(false);
}

static void combo_get_list(void *_object, GB_ARRAY array)
{
	int i;
	
	COMBOBOX->sort();
	for (i = 0; i < COMBOBOX->count(); i++)
	{
		*((char **)GB.Array.Get(array, i)) = GB.NewZeroString(TO_UTF8(COMBOBOX->text(i)));
	}
}


static void combo_set_list(void *_object, GB_ARRAY array)
{
	int i;
	QString text = COMBOBOX->currentText();
	
	COMBOBOX->blockSignals(true);
	COMBOBOX->clear();

	if (array)
	{
		for (i = 0; i < GB.Array.Count(array); i++)
		{
			COMBOBOX->insertItem(TO_QSTRING(*((char **)GB.Array.Get(array, i))));
		}
	}

	COMBOBOX->setDirty();
	combo_set_text(THIS, text);
	
	if (!COMBOBOX->isEditable())
	{
		if (combo_get_current_item(THIS) < 0)
			combo_set_current_item(THIS, 0);
	}
	
	COMBOBOX->blockSignals(false);
}



BEGIN_METHOD(CCOMBOBOX_new, GB_OBJECT parent)

	MyComboBox *wid = new MyComboBox(QCONTAINER(VARG(parent)));

	QObject::connect(wid, SIGNAL(editTextChanged(const QString &)), &CTextBox::manager, SLOT(onChange()));
	QObject::connect(wid, SIGNAL(activated(int)), &CTextBox::manager, SLOT(onClick()));

	CWIDGET_new(wid, (void *)_object);

	combo_set_editable(_object, true);

END_METHOD


BEGIN_METHOD_VOID(CCOMBOBOX_clear)

	COMBOBOX->clear();
	COMBOBOX->clearEditText();

END_METHOD


BEGIN_METHOD_VOID(CCOMBOBOX_popup)

	COMBOBOX->showPopup();

END_METHOD


BEGIN_PROPERTY(CCOMBOBOX_text)

	if (READ_PROPERTY)
		GB.ReturnNewZeroString(TO_UTF8(COMBOBOX->currentText()));
	else
	{
		QString text = QSTRING_PROP();
		combo_set_text(THIS, text);
	}

END_PROPERTY


BEGIN_PROPERTY(CCOMBOBOX_length)

	GB.ReturnInteger(COMBOBOX->currentText().length());

END_PROPERTY



BEGIN_PROPERTY(CCOMBOBOX_read_only)

	if (READ_PROPERTY)
		GB.ReturnBoolean(!COMBOBOX->isEditable());
	else
		combo_set_editable(_object, !VPROP(GB_BOOLEAN));

END_PROPERTY


BEGIN_METHOD(CCOMBOBOX_get, GB_INTEGER index)

	int index = VARG(index);

	if (index < 0 || index >= (int)COMBOBOX->count())
	{
		GB.Error("Bad index");
		return;
	}

	THIS->index = index;

	RETURN_SELF();

END_METHOD


BEGIN_PROPERTY(CCOMBOBOX_item_text)

	if (READ_PROPERTY)
		GB.ReturnNewZeroString(TO_UTF8(COMBOBOX->itemText(THIS->index)));
	else
	{
		COMBOBOX->blockSignals(true);
		COMBOBOX->setItemText(THIS->index, QSTRING_PROP());
		COMBOBOX->setDirty();
		COMBOBOX->blockSignals(false);
	}

END_PROPERTY


BEGIN_METHOD(CCOMBOBOX_add, GB_STRING item; GB_INTEGER pos)

	int index;
	int pos = VARGOPT(pos, -1);

	COMBOBOX->blockSignals(true);
	index = combo_get_current_item(THIS);
	
	if (pos < 0 || pos >= COMBOBOX->count())
		pos = -1;
	
	if (pos < 0)
		COMBOBOX->addItem(QSTRING_ARG(item));
	else
		COMBOBOX->insertItem(pos, QSTRING_ARG(item));

	COMBOBOX->setDirty();

	if (index >= 0)
		combo_set_current_item(THIS, index);
	else
		combo_set_current_item(THIS, 0);
	
	COMBOBOX->blockSignals(false);
	
END_METHOD


BEGIN_METHOD(CCOMBOBOX_remove, GB_INTEGER pos)

	COMBOBOX->blockSignals(true);
	COMBOBOX->removeItem(VARG(pos));
	COMBOBOX->setDirty();
	COMBOBOX->blockSignals(false);

END_METHOD


BEGIN_PROPERTY(CCOMBOBOX_sorted)

	if (READ_PROPERTY)
		GB.ReturnBoolean(COMBOBOX->isSortingEnabled());
	else
		COMBOBOX->setSortingEnabled(VPROP(GB_BOOLEAN));

END_METHOD


BEGIN_PROPERTY(CCOMBOBOX_count)

	GB.ReturnInteger(COMBOBOX->count());

END_PROPERTY


BEGIN_PROPERTY(CCOMBOBOX_index)

	if (READ_PROPERTY)
		GB.ReturnInteger(combo_get_current_item(THIS));
	else
		combo_set_current_item(THIS, VPROP(GB_INTEGER));

END_PROPERTY


BEGIN_PROPERTY(CCOMBOBOX_current)

	THIS->index = combo_get_current_item(THIS);

	if (THIS->index < 0)
		GB.ReturnNull();
	else
		RETURN_SELF();

END_PROPERTY


/*
BEGIN_PROPERTY(CCOMBOBOX_mouse)

	if (READ_PROPERTY)
		GB.ReturnInteger(COMBOBOX->cursor().shape());
	else
	{
		if (COMBOBOX->editable())
			COMBOBOX->lineEdit()->setCursor(PROPERTY(int));

		COMBOBOX->setCursor(PROPERTY(int));
	}

END_METHOD
*/

BEGIN_METHOD(CCOMBOBOX_find, GB_STRING item)

	GB.ReturnInteger(combo_find_item(THIS, QSTRING_ARG(item)));

END_METHOD


BEGIN_PROPERTY(CCOMBOBOX_list)

	GB_ARRAY array;
	
	if (READ_PROPERTY)
	{
		GB.Array.New(&array, GB_T_STRING, COMBOBOX->count());
		combo_get_list(THIS, array);
		GB.ReturnObject(array);
	}
	else
	{
		combo_set_list(THIS, (GB_ARRAY)VPROP(GB_OBJECT));
	}

END_PROPERTY



/***************************************************************************

	class MyComboBox

***************************************************************************/

MyComboBox::MyComboBox(QWidget *parent) :
	QComboBox(parent)
{
	_sorted = _dirty = false;
	setCompleter(0);
	setInsertPolicy(NoInsert);
	calcMinimumHeight();
}


void MyComboBox::changeEvent(QEvent *e)
{
	QComboBox::changeEvent(e);
	if (e->type() == QEvent::FontChange || e->type() == QEvent::StyleChange)
		calcMinimumHeight();
}

void MyComboBox::calcMinimumHeight()
{
	QFontMetrics fm = fontMetrics();

	if (isEditable())
		setMinimumHeight(fm.lineSpacing() + height() - lineEdit()->height());
	else
		setMinimumHeight(fm.lineSpacing() + 2);
}

void MyComboBox::sort()
{
	if (_sorted && _dirty)
	{
		model()->sort(0);
		_dirty = false;
	}
}

void MyComboBox::showPopup()
{
	sort();
	QComboBox::showPopup();
}

/***************************************************************************

	class CTextBox

***************************************************************************/

CTextBox CTextBox::manager;

void CTextBox::onChange(void)
{
	RAISE_EVENT(EVENT_Change);
}


void CTextBox::onActivate(void)
{
	RAISE_EVENT(EVENT_Activate);
}


void CTextBox::onClick()
{
	GET_SENDER();
	raise_click_event(THIS);
}


/***************************************************************************

	Descriptions

***************************************************************************/

GB_DESC CTextBoxSelectionDesc[] =
{
	GB_DECLARE(".TextBox.Selection", 0), GB_VIRTUAL_CLASS(),

	GB_PROPERTY("Text", "s", CTEXTBOX_sel_text),
	GB_PROPERTY_READ("Length", "i", CTEXTBOX_sel_length),
	GB_PROPERTY_READ("Start", "i", CTEXTBOX_sel_start),
	GB_PROPERTY_READ("Pos", "i", CTEXTBOX_sel_start),

	GB_METHOD("Hide", NULL, CTEXTBOX_sel_clear, NULL),

	GB_END_DECLARE
};


GB_DESC CTextBoxDesc[] =
{
	GB_DECLARE("TextBox", sizeof(CTEXTBOX)), GB_INHERITS("Control"),

	GB_METHOD("_new", NULL, CTEXTBOX_new, "(Parent)Container;"),

	GB_PROPERTY("Text", "s", CTEXTBOX_text),
	GB_PROPERTY("Alignment", "i", CTEXTBOX_alignment),
	GB_PROPERTY_READ("Length", "i", CTEXTBOX_length),
	GB_PROPERTY("Pos", "i", CTEXTBOX_pos),
	GB_PROPERTY("ReadOnly", "b", CTEXTBOX_read_only),
	GB_PROPERTY("Border", "b", CTEXTBOX_border),
	GB_PROPERTY("Password", "b", CTEXTBOX_password),
	GB_PROPERTY("MaxLength", "i", CTEXTBOX_max_length),

	GB_PROPERTY_SELF("Selection", ".TextBox.Selection"),
	GB_METHOD("Select", NULL, CTEXTBOX_sel_select, "[(Start)i(Length)i]"),
	GB_METHOD("SelectAll", NULL, CTEXTBOX_sel_all, NULL),
	GB_METHOD("Unselect", NULL, CTEXTBOX_sel_clear, NULL),
	GB_PROPERTY_READ("Selected", "b", CTEXTBOX_selected),

	GB_METHOD("Clear", NULL, CTEXTBOX_clear, NULL),
	GB_METHOD("Insert", NULL, CTEXTBOX_insert, "(Text)s"),

	GB_EVENT("Change", NULL, NULL, &EVENT_Change),
	GB_EVENT("Activate", NULL, NULL, &EVENT_Activate),

	TEXTBOX_DESCRIPTION,

	GB_END_DECLARE
};


GB_DESC CComboBoxItemDesc[] =
{
	GB_DECLARE(".ComboBox.Item", 0), GB_VIRTUAL_CLASS(),

	GB_PROPERTY("Text", "s", CCOMBOBOX_item_text),

	GB_END_DECLARE
};


GB_DESC CComboBoxDesc[] =
{
	GB_DECLARE("ComboBox", sizeof(CCOMBOBOX)), GB_INHERITS("Control"),

	GB_METHOD("_new", NULL, CCOMBOBOX_new, "(Parent)Container;"),
	GB_METHOD("_get", ".ComboBox.Item", CCOMBOBOX_get, "(Index)i"),

	GB_PROPERTY("Text", "s", CCOMBOBOX_text),
	GB_PROPERTY_READ("Length", "i", CCOMBOBOX_length),
	GB_PROPERTY("Pos", "i", CTEXTBOX_pos),
	GB_PROPERTY("ReadOnly", "b", CCOMBOBOX_read_only),
	GB_PROPERTY("Password", "b", CTEXTBOX_password),
	GB_PROPERTY("MaxLength", "i", CTEXTBOX_max_length),

	GB_PROPERTY_SELF("Selection", ".TextBox.Selection"),
	GB_METHOD("Select", NULL, CTEXTBOX_sel_select, "[(Start)i(Length)i]"),
	GB_METHOD("SelectAll", NULL, CTEXTBOX_sel_all, NULL),
	GB_METHOD("Unselect", NULL, CTEXTBOX_sel_clear, NULL),
	GB_PROPERTY_READ("Selected", "b", CTEXTBOX_selected),

	GB_METHOD("Popup", NULL, CCOMBOBOX_popup, NULL),
	GB_METHOD("Clear", NULL, CCOMBOBOX_clear, NULL),
	GB_METHOD("Insert", NULL, CTEXTBOX_insert, "(Text)s"),

	GB_METHOD("Add", NULL, CCOMBOBOX_add, "(Item)s[(Index)i]"),
	GB_METHOD("Remove", NULL, CCOMBOBOX_remove, "(Index)i"),

	GB_METHOD("Find", "i", CCOMBOBOX_find, "(Item)s"),

	GB_PROPERTY("Sorted", "b", CCOMBOBOX_sorted),

	GB_PROPERTY("List", "String[]", CCOMBOBOX_list),
	//GB_PROPERTY("Contents", "s", CCOMBOBOX_list),

	GB_PROPERTY_READ("Count", "i", CCOMBOBOX_count),
	GB_PROPERTY_READ("Current", ".ComboBox.Item", CCOMBOBOX_current),
	GB_PROPERTY("Index", "i", CCOMBOBOX_index),

	GB_EVENT("Change", NULL, NULL, &EVENT_Change),
	GB_EVENT("Activate", NULL, NULL, &EVENT_Activate),
	GB_EVENT("Click", NULL, NULL, &EVENT_Click),

	COMBOBOX_DESCRIPTION,

	GB_END_DECLARE
};


