// -*- C++ -*-
/*
* GChemPaint bonds plugin
* chaintool.cc
*
* Copyright (C) 2006-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 "chaintool.h"
#include "lib/settings.h"
#include "lib/document.h"
#include "lib/application.h"
#include "lib/atom.h"
#include "lib/bond.h"
#include "lib/theme.h"
#include <glib/gi18n-lib.h>
#include <cmath>
gcpChainTool::gcpChainTool(gcpApplication *App): gcpTool (App, "Chain")
{
m_Length = 0; // < 2 is auto.
m_Points = gnome_canvas_points_new (3);
m_Atoms.resize (3);
m_CurPoints = 3;
m_AutoNb = true;
}
gcpChainTool::~gcpChainTool()
{
gnome_canvas_points_free (m_Points);
}
bool gcpChainTool::OnClicked()
{
if (Element::GetMaxBonds (m_pApp->GetCurZ()) < 2)
return false;
m_dAngle = 0.;
unsigned nb = (m_Length > 2)? m_Length + 1: 3;
double a1, x, y;
gcpDocument* pDoc = m_pView->GetDoc();
gcpTheme *pTheme = m_pView->GetDoc ()->GetTheme ();
m_BondLength = pDoc->GetBondLength ();
if (nb != m_CurPoints) {
m_CurPoints = nb;
gnome_canvas_points_free (m_Points);
m_Points = gnome_canvas_points_new (m_CurPoints);
if (m_CurPoints > m_Atoms.size ());
m_Atoms.resize (m_CurPoints);
}
m_Positive = m_nState & GDK_LOCK_MASK;
if (m_pObject) {
if (m_pObject->GetType () != AtomType)
return false;
m_Atoms[0] = dynamic_cast<gcpAtom*> (m_pObject);
nb = ((gcpAtom*) m_pObject)->GetBondsNumber ();
m_Atoms[0]->GetCoords(&m_x0, &m_y0, NULL);
x = m_x0 *= m_dZoomFactor;
y = m_y0 *= m_dZoomFactor;
m_Points->coords[0] = m_x0;
m_Points->coords[1] = m_y0;
switch (nb) {
case 1: {
map<Atom*, Bond*>::iterator i;
gcpBond* bond = (gcpBond*) ((Atom*) m_pObject)->GetFirstBond (i);
m_dAngle = bond->GetAngle2D ((gcpAtom*) m_pObject);
m_dAngle += (m_Positive)? +150: -150;
break;
}
case 2: {
double a2;
map<Atom*, Bond*>::iterator i;
gcpBond* bond = (gcpBond*) ((Atom*) m_pObject)->GetFirstBond (i);
a1 = bond->GetAngle2D ((gcpAtom*) m_pObject);
bond = (gcpBond*) ((Atom*) m_pObject)->GetNextBond (i);
a2 = bond->GetAngle2D ((gcpAtom*) m_pObject);
m_dAngle = (a1 + a2) / 2.;
a2 = fabs (a2 - m_dAngle);
if (a2 < 90.)
m_dAngle += 180.;
if (m_dAngle > 360.)
m_dAngle -= 360.;
m_dAngle += (m_Positive)?
90. - pDoc->GetBondAngle () / 2.:
pDoc->GetBondAngle () / 2. - 90;
break;
}
default:
break;
}
} else {
m_Atoms[0] = NULL;
x = m_Points->coords[0] = m_x0;
y = m_Points->coords[1] = m_y0;
}
FindAtoms ();
if (!(m_Allowed = CheckIfAllowed ()))
return true; // true, since dragging the mouse might make things OK.
char tmp[32];
snprintf(tmp, sizeof(tmp) - 1, _("Bonds: %d, Orientation: %g"), m_CurPoints - 1, m_dAngle);
m_pApp->SetStatusText(tmp);
m_pItem = gnome_canvas_item_new(
m_pGroup,
gnome_canvas_line_get_type(),
"points", m_Points,
"fill_color", AddColor,
"width_units", pTheme->GetBondWidth (),
NULL);
m_dMeanLength = pDoc->GetBondLength () * sin (pDoc->GetBondAngle () / 360. * M_PI) * m_dZoomFactor;
m_Allowed = true; // FIXME: if MergeAtoms is true, ensure that atoms accept necessary bonds
return true;
}
void gcpChainTool::OnDrag()
{
double x1, y1, x2, y2;
unsigned nb;
gcpDocument* pDoc = m_pView->GetDoc ();
gcpTheme *pTheme = pDoc->GetTheme ();
if (m_pItem) {
gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (m_pItem), &x1, &y1, &x2, &y2);
gtk_object_destroy (GTK_OBJECT(GNOME_CANVAS_ITEM (m_pItem)));
gnome_canvas_request_redraw (GNOME_CANVAS (m_pWidget), (int) x1, (int) y1, (int) x2, (int) y2);
m_pItem = NULL;
}
m_BondLength = pDoc->GetBondLength ();
GnomeCanvasItem* pItem = gnome_canvas_get_item_at (GNOME_CANVAS (m_pWidget), m_x, m_y);
if (pItem == (GnomeCanvasItem*) m_pBackground)
pItem = NULL;
Object* pObject = NULL;
if (pItem)
pObject = (Object*)g_object_get_data(G_OBJECT(pItem), "object");
double dAngle;
gcpAtom *pAtom = NULL;
if (pObject) {
if (pObject->GetType () == BondType)
pAtom = (gcpAtom*) pObject->GetAtomAt (m_x / m_dZoomFactor, m_y / m_dZoomFactor);
else if (pObject->GetType () == FragmentType)
pAtom = (gcpAtom*) pObject->GetAtomAt (m_x1 / m_dZoomFactor, m_y1 / m_dZoomFactor);
else if (pObject->GetType () == AtomType)
pAtom = (gcpAtom*) pObject;
}
if (m_pObject && pAtom == m_pObject)
return;
if (pAtom && MergeAtoms) {
// in that case, end the chain there with the current number of bonds
pAtom->GetCoords(&m_x, &m_y, NULL);
m_x *= m_dZoomFactor;
m_y *= m_dZoomFactor;
m_x-= m_x0;
m_y -= m_y0;
x2 = sqrt (m_x * m_x + m_y * m_y);
if (m_CurPoints % 2 == 0) {
x1 = m_dMeanLength * (m_CurPoints - 1);
y1 = pDoc->GetBondLength () * cos (pDoc->GetBondAngle () / 360. * M_PI) * m_dZoomFactor;
m_dAngle = (atan2 (-m_y, m_x) - atan2((m_Positive)? -y1: y1, x1)) / M_PI * 180.;
m_BondLength = pDoc->GetBondLength () * x2 / x1;
} else {
m_dAngle = atan2 (-m_y, m_x) / M_PI * 180.;
m_BondLength = x2 / (m_CurPoints - 1) / sin (pDoc->GetBondAngle () / 360. * M_PI) / m_dZoomFactor;
}
} else {
m_x-= m_x0;
m_y -= m_y0;
if (m_x == 0) {
if (m_y == 0)
return;
dAngle = (m_y < 0)? 90: 270;
} else {
// calculate the angle and the real distance
dAngle = atan (-m_y/m_x) * 180 / M_PI;
if (!(m_nState & GDK_CONTROL_MASK))
dAngle = rint(dAngle / 5) * 5;
if (isnan (dAngle))
dAngle = m_dAngle;
else if (m_x < 0.)
dAngle += 180.;
}
m_dAngle = dAngle;
// Calculate number of bonds if Shift key is not pressed and we do use
// an automatic bonds number (otherwise, change the bonds lengths,
// but not their number
dAngle = atan2 (-m_y, m_x) - m_dAngle * M_PI / 180.;
x2 = sqrt ((m_x * m_x + m_y * m_y) * cos (dAngle));
if (m_nState & GDK_SHIFT_MASK)
m_BondLength = x2 / (m_CurPoints - 1) / sin (pDoc->GetBondAngle () / 360. * M_PI) / m_dZoomFactor;
else if (m_Length < 2) {
nb = 1 + (unsigned) rint (x2 / m_dMeanLength);
if (nb < 3)
nb = 3;
if (nb != m_CurPoints) {
m_CurPoints = nb;
gnome_canvas_points_free (m_Points);
m_Points = gnome_canvas_points_new (m_CurPoints);
if (m_CurPoints > m_Atoms.size ());
m_Atoms.resize (m_CurPoints);
}
}
}
m_Positive = m_nState & GDK_LOCK_MASK;
m_Points->coords[0] = m_x0;
m_Points->coords[1] = m_y0;
FindAtoms ();
if (!(m_Allowed = CheckIfAllowed ()))
return;
char tmp[32];
snprintf(tmp, sizeof(tmp) - 1, _("Bonds: %d, Orientation: %g"), m_CurPoints - 1, m_dAngle);
m_pApp->SetStatusText(tmp);
m_pItem = gnome_canvas_item_new(
m_pGroup,
gnome_canvas_line_get_type(),
"points", m_Points,
"fill_color", AddColor,
"width_units", pTheme->GetBondWidth (),
NULL);
}
void gcpChainTool::OnRelease()
{
double x1, y1, x2, y2;
gcpDocument* pDoc = m_pView->GetDoc();
unsigned nb;
gcpOperation *pOp = NULL;
Object *pObject;
char const *Id;
gcpMolecule *pMol = NULL;
gcpBond* pBond = NULL;
m_pApp->ClearStatus();
if (m_pItem) {
gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (m_pItem), &x1, &y1, &x2, &y2);
gtk_object_destroy (GTK_OBJECT(GNOME_CANVAS_ITEM (m_pItem)));
gnome_canvas_request_redraw (GNOME_CANVAS (m_pWidget), (int) x1, (int) y1, (int) x2, (int) y2);
m_pItem = NULL;
} else
return;
if (!m_Allowed)
return;
for (nb = 0; nb < m_CurPoints; nb++) {
if (m_Atoms[nb]) {
if (pMol == NULL) {
pMol = dynamic_cast<gcpMolecule *> (m_Atoms[0]->GetMolecule ());
pMol->Lock (true);
}
pOp = pDoc->GetNewOperation (GCP_MODIFY_OPERATION);
pObject = m_Atoms[0]->GetGroup ();
Id = pObject->GetId ();
pOp->AddObject (pObject);
ModifiedObjects.insert (Id);
} else {
m_Atoms[nb] = new gcpAtom (m_pApp->GetCurZ(),
m_Points->coords[2 * nb] / m_dZoomFactor,
m_Points->coords[2 * nb + 1] / m_dZoomFactor,
0);
pDoc->AddAtom (m_Atoms[nb]);
}
if (nb > 0 && m_Atoms[nb] != m_Atoms[nb - 1]) {
pBond = reinterpret_cast<gcpBond*> (m_Atoms[nb]->GetBond (m_Atoms[nb - 1]));
if (!pBond) {
pBond = new gcpBond (m_Atoms[nb - 1], m_Atoms[nb], 1);
pDoc->AddBond (pBond);
}
}
}
pObject = pBond->GetGroup ();
if (pOp) {
ModifiedObjects.insert (pObject->GetId ());
set<string>::iterator it, end = ModifiedObjects.end ();
for (it = ModifiedObjects.begin (); it != end; it++) {
pObject = pDoc->GetDescendant ((*it).c_str ());
if (pObject)
pOp->AddObject (pObject, 1);
}
} else {
pOp = pDoc->GetNewOperation (GCP_ADD_OPERATION);
pOp->AddObject (pObject);
}
pDoc->FinishOperation ();
if (pMol) {
pMol->Lock (false);
pMol->EmitSignal (OnChangedSignal);
}
ModifiedObjects.clear ();
}
static void on_length_changed (GtkSpinButton *btn, gcpChainTool *tool)
{
tool->SetLength (gtk_spin_button_get_value (btn));
}
static void on_angle_changed (GtkSpinButton *btn, gcpChainTool *tool)
{
tool->SetAngle (gtk_spin_button_get_value (btn));
}
static void on_merge_toggled (GtkToggleButton *btn)
{
MergeAtoms = gtk_toggle_button_get_active (btn);
}
static void on_number_toggled (GtkToggleButton *btn, gcpChainTool *tool)
{
bool active = gtk_toggle_button_get_active (btn);
if (active)
tool->SetChainLength (0);
tool->SetAutoNumber (!gtk_toggle_button_get_active (btn));
}
static void on_number_changed (GtkSpinButton *btn, gcpChainTool *tool)
{
tool->SetChainLength (gtk_spin_button_get_value_as_int (btn));
}
void gcpChainTool::SetAngle (double angle)
{
m_pView->GetDoc ()->SetBondAngle (angle);
}
void gcpChainTool::SetLength (double length)
{
m_pApp->GetActiveDocument ()->SetBondLength (length);
}
GtkWidget *gcpChainTool::GetPropertyPage ()
{
GladeXML *xml = glade_xml_new (GLADEDIR"/chain.glade", "chain", GETTEXT_PACKAGE);
m_LengthBtn = GTK_SPIN_BUTTON (glade_xml_get_widget (xml, "bond-length"));
g_signal_connect (m_LengthBtn, "value-changed", G_CALLBACK (on_length_changed), this);
m_AngleBtn = GTK_SPIN_BUTTON (glade_xml_get_widget (xml, "bond-angle"));
g_signal_connect (m_AngleBtn, "value-changed", G_CALLBACK (on_angle_changed), this);
m_MergeBtn = GTK_TOGGLE_BUTTON (glade_xml_get_widget (xml, "merge"));
g_signal_connect (m_MergeBtn, "toggled", G_CALLBACK (on_merge_toggled), NULL);
m_NumberBtn = GTK_SPIN_BUTTON (glade_xml_get_widget (xml, "bonds-number"));
gtk_widget_set_sensitive (GTK_WIDGET (m_NumberBtn), false);
g_signal_connect (m_NumberBtn, "value-changed", G_CALLBACK (on_number_changed), this);
GtkWidget *w = glade_xml_get_widget (xml, "auto-number");
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), true);
g_signal_connect (w, "toggled", G_CALLBACK (on_number_toggled), this);
return glade_xml_get_widget (xml, "chain");
}
void gcpChainTool::Activate ()
{
gcpDocument *pDoc = m_pApp->GetActiveDocument ();
gtk_spin_button_set_value (m_LengthBtn, pDoc->GetBondLength ());
gtk_spin_button_set_value (m_AngleBtn, pDoc->GetBondAngle ());
gtk_toggle_button_set_active (m_MergeBtn, MergeAtoms);
}
void gcpChainTool::FindAtoms ()
{
double x1 = m_Points->coords[0], y1 = m_Points->coords[1], a;
unsigned nb;
for (nb = 1; nb < m_CurPoints; nb++) {
a = (m_dAngle +
((m_Positive ^ (nb % 2))?
90. - m_pView->GetDoc ()->GetBondAngle () / 2.:
m_pView->GetDoc ()->GetBondAngle () / 2. - 90))
* M_PI / 180.;
x1 += m_BondLength * m_dZoomFactor * cos (a);
y1 -= m_BondLength * m_dZoomFactor * sin (a);
m_Atoms[nb] = NULL;
if (MergeAtoms) {
GnomeCanvasItem* pItem = gnome_canvas_get_item_at (GNOME_CANVAS (m_pWidget), x1, y1);
if (pItem == (GnomeCanvasItem*) m_pBackground)
pItem = NULL;
Object* pObject = NULL;
if (pItem)
pObject = (Object*) g_object_get_data (G_OBJECT (pItem), "object");
if (pObject && pObject != m_pObject) {
if ((pObject->GetType () == BondType) || (pObject->GetType () == FragmentType)) {
m_Atoms[nb] = (gcpAtom*) pObject->GetAtomAt (x1 / m_dZoomFactor, y1 / m_dZoomFactor);
} else if (pObject->GetType () == AtomType) {
m_Atoms[nb] = (gcpAtom*) pObject;
}
}
if (m_Atoms[nb]) {
m_Atoms[nb]->GetCoords(&x1, &y1, NULL);
x1 *= m_dZoomFactor;
y1 *= m_dZoomFactor;
}
}
m_Points->coords[nb * 2] = x1;
m_Points->coords[nb * 2 + 1] = y1;
}
}
bool gcpChainTool::CheckIfAllowed ()
{
unsigned i, n;
for (i = 1; i < m_CurPoints; i++) {
if (m_Atoms[i] == NULL)
continue;
n = (!m_Atoms[i]->GetBond(m_Atoms[i - 1]))? 1: 0;
if ((i < m_CurPoints - 1) && !m_Atoms[i]->GetBond(m_Atoms[i + 1]))
n++;
if (n && !m_Atoms[i]->AcceptNewBonds (n))
return false;
}
return true;
}
syntax highlighted by Code2HTML, v. 0.9.1