// -*- C++ -*-

/* 
 * GChemPaint selection plugin
 * selectiontool.cc
 *
 * Copyright (C) 2001-2007 Jean Bréfort <jean.brefort@normalesup.org>
 *
 * 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 program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 */

#include "gchempaint-config.h"
#include "selectiontool.h"
#include "group.h"
#include "groupdlg.h"
#include "lib/molecule.h"
#include "lib/settings.h"
#include "lib/document.h"
#include "lib/application.h"
#include "lib/theme.h"
#include "lib/window.h"
#include <glib/gi18n-lib.h>
#include <cmath>
#include <stdexcept>

static void on_flip(GtkWidget *btn, gcpApplication* App)
{
	gcpSelectionTool *tool = (gcpSelectionTool*) App->GetTool ("Select");
	if (GTK_IS_WIDGET (btn))
		tool->OnFlip (strcmp (gtk_widget_get_name (btn), "VertFlip"));
	else
		tool->OnFlip (strcmp (gtk_action_get_name (GTK_ACTION (btn)), "VertFlip"));
}

static void on_rotate(GtkWidget *btn, gcpApplication* App)
{
	gcpSelectionTool *tool = (gcpSelectionTool*) App->GetTool ("Select");
	if (GTK_IS_WIDGET (btn))
		tool->Rotate (gtk_toggle_tool_button_get_active (GTK_TOGGLE_TOOL_BUTTON (btn)));
	else
		tool->Rotate (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (btn)));
}

static void on_merge(GtkWidget *btn, gcpApplication* App)
{
	gcpSelectionTool *tool = (gcpSelectionTool*) App->GetTool ("Select");
	tool->Merge ();
}

gcpSelectionTool::gcpSelectionTool(gcpApplication *App): gcpTool(App, "Select")
{
	m_bRotate = false;
	m_UIManager = NULL;
}

gcpSelectionTool::~gcpSelectionTool()
{
	if (m_UIManager)
		g_object_unref (m_UIManager);
}

bool gcpSelectionTool::OnClicked()
{
	gcpWindow *win = m_pView->GetDoc ()->GetWindow ();
	if (m_pObject)
	{
		Object* pObj = m_pObject->GetGroup();
		if (pObj) m_pObject = pObj;
		if (!m_pData->IsSelected(m_pObject))
		{
			m_pData->UnselectAll();
			m_pData->SetSelected(m_pObject);
			win->ActivateActionWidget ("/MainMenu/EditMenu/Copy", true);
			win->ActivateActionWidget ("/MainMenu/EditMenu/Cut", true);
			win->ActivateActionWidget ("/MainMenu/EditMenu/Erase", true);
		}
	}
	else
	{
		m_pData->UnselectAll();
		win->ActivateActionWidget ("/MainMenu/EditMenu/Copy", false);
		win->ActivateActionWidget ("/MainMenu/EditMenu/Cut", false);
		win->ActivateActionWidget ("/MainMenu/EditMenu/Erase", false);
	}
	if (m_bRotate) {
		// Calculate center of selection
		ArtDRect rect;
		m_pData->GetSelectionBounds(rect);
		m_cx = (rect.x0 + rect.x1) / 2.;
		m_cy = (rect.y0 + rect.y1) / 2.;
		m_dAngle = 0.;
		m_x0 -= m_cx;
		m_y0 -= m_cy;
		if (m_x0 == 0)
			m_dAngleInit = (m_y0 <= 0) ? 90 : 270;
		else
			m_dAngleInit = atan(-m_y0/m_x0) * 180 / M_PI;
		if (m_x0 < 0) m_dAngleInit += 180.;
		std::list<Object*>::iterator i;
		gcpDocument* pDoc = m_pView->GetDoc();
		m_pOp = pDoc-> GetNewOperation(GCP_MODIFY_OPERATION);
		for (i = m_pData->SelectedObjects.begin(); i != m_pData->SelectedObjects.end(); i++)
			m_pOp->AddObject(*i,0);
	}
	return true;
}

void gcpSelectionTool::OnDrag()
{
	double dx = m_x - m_x1, dy = m_y - m_y1, x1, y1, x2, y2;
	m_x1 = m_x;
	m_y1 = m_y;
	if (m_pObject)
	{
		if (m_bRotate) {
			double dAngle;
			m_x-= m_cx;
			m_y -= m_cy;
			if (m_x == 0)
			{
				if (m_y == 0) return;
				dAngle = (m_y < 0) ? 90 : 270;
			}
			else
			{
				dAngle = atan(-m_y/m_x) * 180. / M_PI;
				if (m_x < 0) dAngle += 180.;
				dAngle -= m_dAngleInit;
				if (!(m_nState & GDK_CONTROL_MASK)) dAngle = rint(dAngle / 5) * 5;
			}
			if (dAngle < -180.) dAngle += 360.;
			if (dAngle > 180.) dAngle -= 360.;
			if (dAngle != m_dAngle) {
				m_pData->RotateSelection (m_cx, m_cy, dAngle - m_dAngle);
				m_dAngle = dAngle;
			}
			char tmp[32];
			snprintf(tmp, sizeof(tmp) - 1, _("Orientation: %g"), dAngle);
			m_pApp->SetStatusText(tmp);
		} else
			m_pData->MoveSelectedItems(dx, dy);
	}
	else
	{
		if (m_pItem)
		{
			gnome_canvas_item_get_bounds(m_pItem, &x1, &y1, &x2, &y2);
			g_object_set(G_OBJECT(m_pItem), "x2", m_x, "y2", m_y, NULL);
			gnome_canvas_request_redraw(GNOME_CANVAS(m_pWidget), (int)x1, (int)y1, (int)x2, (int)y2);
		}
		else
		{
			gcpTheme *pTheme = m_pView->GetDoc ()->GetTheme ();
			m_pItem = gnome_canvas_item_new(
									m_pData->Group,
									gnome_canvas_rect_get_type(),
									"x1", m_x0,
									"y1", m_y0,
									"x2", m_x,
									"y2", m_y,
									"outline_color", SelectColor,
									"width_units", pTheme->GetBondWidth (),
									NULL);
		}
	}
}

void gcpSelectionTool::OnRelease()
{
	m_pApp->ClearStatus();
	if (m_pObject)
	{
		if (m_bRotate) {
			std::list<Object*>::iterator i;
			gcpDocument* pDoc = m_pView->GetDoc();
			for (i = m_pData->SelectedObjects.begin(); i != m_pData->SelectedObjects.end(); i++)
				m_pOp->AddObject(*i,1);
			pDoc->FinishOperation();
		} else {
			double dx = m_x1 - m_x0, dy = m_y1 - m_y0;
			if (dx != 0.0 && dy != 0.0)
			{
				m_pData->MoveSelectedItems(-dx, -dy);
				m_pData->MoveSelection(dx, dy);
			}
		}
	}
	else
	{
		if (m_x < m_x0)
		{
			m_x1 = m_x0;
			m_x0 = m_x;
		}
		else m_x1 = m_x;
		if (m_y < m_y0)
		{
			m_y1 = m_y0;
			m_y0 = m_y;
		}
		else m_y1 = m_y;
		double x0, y0, x1, y1;
		std::map<Object*, GnomeCanvasGroup*>::iterator j;
		for (j = m_pData->Items.begin (); j != m_pData->Items.end (); j++) {
			if (!m_pData->IsSelected ((*j).first)) {
				gnome_canvas_item_get_bounds(GNOME_CANVAS_ITEM((*j).second), &x0, &y0,&x1, &y1);
				if ((x0 < m_x1) && (y0 < m_y1) && (x1 > m_x0) && (y1 > m_y0)) {
					m_pObject = (*j).first->GetGroup ();	//GetMolecule();
					if (m_pObject) {
						if (!m_pData->IsSelected(m_pObject))
							m_pData->SetSelected(m_pObject);
					}
					else
						m_pData->SetSelected((*j).first);
				}
			}
		}
	}
	AddSelection(m_pData);
}

void gcpSelectionTool::Activate()
{
	if (m_UIManager)
		gtk_widget_set_sensitive (m_MergeBtn, false);
	gcpDocument *pDoc = m_pApp->GetActiveDocument ();
	if (pDoc) {
		m_pView = m_pApp->GetActiveDocument ()->GetView ();
		GtkWidget *w = m_pView->GetWidget ();
		m_pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (w), "data");
	}
}

bool gcpSelectionTool::Deactivate()
{
	while (!SelectedWidgets.empty())
	{
		SelectedWidgets.front()->UnselectAll();
		SelectedWidgets.pop_front();
	}
	return true;
}

void gcpSelectionTool::AddSelection(gcpWidgetData* data)
{
	gcpWidgetData *d = m_pData;
	m_pData = data;
	m_pView = data->View;
	gcpWindow *win = m_pView->GetDoc ()->GetWindow ();
	if (m_pData->HasSelection())
	{
		GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
		m_pView->OnCopySelection (m_pData->Canvas, clipboard);
		win->ActivateActionWidget ("/MainMenu/EditMenu/Copy", true);
		win->ActivateActionWidget ("/MainMenu/EditMenu/Cut", true);
		win->ActivateActionWidget ("/MainMenu/EditMenu/Erase", true);
	}
	SelectedWidgets.remove(m_pData);
	SelectedWidgets.push_front(m_pData);
	if (d) {
		m_pView = d->View;
		m_pData = d;
	}
	// If the selection is made of two molecules, activate the merge tool
	if (m_UIManager)
		gtk_widget_set_sensitive (m_MergeBtn, ((m_pData->SelectedObjects.size () == 2) &&
			(m_pData->SelectedObjects.front()->GetType() == MoleculeType) &&
			(m_pData->SelectedObjects.back()->GetType() == MoleculeType)));
}

void gcpSelectionTool::OnFlip (bool horizontal)
{
	if (!m_pData) {
		m_pView = m_pApp->GetActiveDocument ()->GetView ();
		GtkWidget *w = m_pView->GetWidget ();
		m_pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (w), "data");
	}
	if (!m_pData->SelectedObjects.size ())
		return;
	ArtDRect rect;
	m_pData->GetSelectionBounds(rect);
	m_cx = (rect.x0 + rect.x1) / 2.;
	m_cy = (rect.y0 + rect.y1) / 2.;
	m_x = (horizontal)? -1.: 1.;
	Matrix2D m(m_x, 0., 0., -m_x);
	std::list<Object*>::iterator i;
	gcpDocument* pDoc = m_pView->GetDoc();
	gcpTheme *pTheme = pDoc->GetTheme ();
	m_pOp = pDoc-> GetNewOperation(GCP_MODIFY_OPERATION);
	for (i = m_pData->SelectedObjects.begin(); i != m_pData->SelectedObjects.end(); i++) {
		m_pOp->AddObject(*i,0);
		(*i)->Transform2D (m, m_cx / pTheme->GetZoomFactor (), m_cy / pTheme->GetZoomFactor ());
		m_pView->Update(*i);
		m_pOp->AddObject(*i,1);
	}
	pDoc->FinishOperation();
}

void gcpSelectionTool::Rotate (bool rotate)
{
	m_bRotate = rotate;
}

void gcpSelectionTool::Merge ()
{
	gcpMolecule *pMol0, *pMol1;
	gcpDocument* pDoc = m_pApp->GetActiveDocument ();
	if (!m_pData) {
		m_pView = pDoc->GetView ();
		GtkWidget *w = m_pView->GetWidget ();
		m_pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (w), "data");
	}
	pMol0 = (gcpMolecule*) m_pData->SelectedObjects.front();
	pMol1 = (gcpMolecule*) m_pData->SelectedObjects.back();
	m_pOp = pDoc-> GetNewOperation(GCP_MODIFY_OPERATION);
	m_pOp->AddObject (pMol0, 0);
	m_pOp->AddObject (pMol1, 0);
	m_pData->UnselectAll ();
	if (pMol0->Merge (pMol1, true)) {
		m_pOp->AddObject (pMol0, 1);
		m_pData->SetSelected (pMol0);
		AddSelection(m_pData);
		m_pView->Update (pMol0);
		pDoc->FinishOperation();
	} else {
		pDoc->AbortOperation ();
	}
}

static void on_create_group (gcpSelectionTool* tool)
{
	tool->CreateGroup ();
}

static void on_group (gcpSelectionTool* tool)
{
	tool->Group ();
}

void gcpSelectionTool::Group ()
{
	gcpDocument *pDoc = m_pView->GetDoc ();
	new gcpGroupDlg (pDoc, NULL);
}

void gcpSelectionTool::CreateGroup ()
{
	gcpDocument *pDoc = m_pView->GetDoc ();
	Object *pObj = Object::CreateObject (Object::GetTypeName (m_Type), pDoc);
	try {
		m_pOp = pDoc-> GetNewOperation(GCP_MODIFY_OPERATION);
		std::list<Object*>::iterator i;
		for (i = m_pData->SelectedObjects.begin(); i != m_pData->SelectedObjects.end(); i++)
			m_pOp->AddObject(*i,0);
		if (!pObj->Build (m_pData->SelectedObjects))
			throw logic_error (_("Creation failed!"));
		m_pView->Update (pObj);
		m_pData->UnselectAll ();
		m_pData->SetSelected (pObj);
		AddSelection(m_pData);
		m_pOp->AddObject (pObj, 1);
		pDoc->FinishOperation ();
	}
	catch (invalid_argument& e) {
			pDoc->AbortOperation ();
			delete pObj;
			GtkWidget* message = gtk_message_dialog_new (NULL, (GtkDialogFlags) 0,
								GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, e.what ());
			gtk_window_set_icon_name (GTK_WINDOW (message), "gchempaint");
			g_signal_connect_swapped (G_OBJECT (message), "response", G_CALLBACK (gtk_widget_destroy), G_OBJECT (message));
			gtk_widget_show(message);
	}
}

bool gcpSelectionTool::OnRightButtonClicked (GtkUIManager *UIManager)
{
	// first destroy the GtkUIManager
	if (m_pData->SelectedObjects.size () > 1) {
		GtkActionGroup *group = gtk_action_group_new ("selection");
		GtkAction *action = gtk_action_new ("group", _("Group and/or align objects"), NULL, NULL);
		gtk_action_group_add_action (group, action);
		m_uiIds.push_front (gtk_ui_manager_add_ui_from_string (UIManager, "<ui><popup><menuitem action='group'/></popup></ui>", -1, NULL));
		g_signal_connect_swapped (action, "activate", G_CALLBACK (on_group), this);
		set<TypeId> possible_types, types, wrong_types;
		list<Object*>::iterator  i = m_pData->SelectedObjects.begin (),
												end = m_pData->SelectedObjects.end ();
		(*i)->GetPossibleAncestorTypes (possible_types);
		set<TypeId>::iterator type;
		for (i++; i != end; i++) {
			(*i)->GetPossibleAncestorTypes (types);
			for (type = possible_types.begin(); type != possible_types.end (); type++)
				if (types.find (*type) == types.end ())
					wrong_types.insert (*type);
			for (type = wrong_types.begin(); type != wrong_types.end (); type++)
				possible_types.erase (*type);
			wrong_types.clear ();
			types.clear ();
		}
		if (possible_types.size () == 1) {
			// Add a new action.
			m_Type = *possible_types.begin();
			const string &label = Object::GetCreationLabel (m_Type);
			if (label.size ()) {
				action = gtk_action_new ("create_group", label.c_str (), NULL, NULL);
				gtk_action_group_add_action (group, action);
				char buf[] = "<ui><popup><menuitem action='create_group'/></popup></ui>";
				m_uiIds.push_front (gtk_ui_manager_add_ui_from_string (UIManager, buf, -1, NULL));
				g_signal_connect_swapped (action, "activate", G_CALLBACK (on_create_group), this);
			}
		}
		gtk_ui_manager_insert_action_group (UIManager, group, 0);
		return true;
	}
	return false;
}

static GtkActionEntry entries[] = {
	{ "HorizFlip", "gcp_Horiz", N_("Horizontal flip"), NULL,
		N_("Flip the selection horizontally"), G_CALLBACK (on_flip) },
	{ "VertFlip", "gcp_Vert", N_("Vertical flip"), NULL,
		N_("Flip the selection vertically"), G_CALLBACK (on_flip) },
	{ "Merge", "gcp_Merge", N_("Merge"), NULL,
		N_("Merge two molecules"), G_CALLBACK (on_merge) }
};

static GtkToggleActionEntry toggles[] = {
	  { "Rotate", "gcp_Rotate", N_("_Rotate"), NULL,
		  N_("Rotate the selection"), G_CALLBACK (on_rotate), false }
};

static const char *ui_description =
"<ui>"
"  <toolbar name='Selection'>"
"    <toolitem action='HorizFlip'/>"
"    <toolitem action='VertFlip'/>"
"    <toolitem action='Rotate'/>"
"    <toolitem action='Merge'/>"
"  </toolbar>"
"</ui>";

GtkWidget *gcpSelectionTool::GetPropertyPage ()
{
	GtkWidget *box, *w;
	GtkActionGroup *action_group;
	GError *error;

	box = gtk_vbox_new (FALSE, 0);
	action_group = gtk_action_group_new ("SelectionToolActions");
	gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
	gtk_action_group_add_actions (action_group, entries, G_N_ELEMENTS (entries), m_pApp);
	gtk_action_group_add_toggle_actions (action_group, toggles, G_N_ELEMENTS (toggles), m_pApp);

	m_UIManager = gtk_ui_manager_new ();
	if (!gtk_ui_manager_add_ui_from_string (m_UIManager, ui_description, -1, &error))
	  {
		g_message ("building property page failed: %s", error->message);
		g_error_free (error);
		gtk_widget_destroy (box);
		g_object_unref (m_UIManager);
		m_UIManager = NULL;
		return NULL;;
	  }
	gtk_ui_manager_insert_action_group (m_UIManager, action_group, 0);
	w = gtk_ui_manager_get_widget (m_UIManager, "/Selection");
	gtk_toolbar_set_style (GTK_TOOLBAR (w), GTK_TOOLBAR_ICONS);
	gtk_toolbar_set_show_arrow (GTK_TOOLBAR (w), false);
	gtk_toolbar_set_tooltips (GTK_TOOLBAR (w), true);
	gtk_box_pack_start (GTK_BOX (box), w, false, false, 0);
	gtk_widget_show_all (box);
	m_MergeBtn = gtk_ui_manager_get_widget (m_UIManager, "/Selection/Merge");
	gtk_widget_set_sensitive (m_MergeBtn, false);
	return box;
}

char const *gcpSelectionTool::GetHelpTag ()
{
	if (m_bRotate)
		return "rotate";
	return "selection";
}


syntax highlighted by Code2HTML, v. 0.9.1