// -*- C++ -*-
/*
* GChemPaint library
* bond.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 "bond.h"
#include "atom.h"
#include "fragment.h"
#include "cycle.h"
#include "settings.h"
#include "document.h"
#include "theme.h"
#include "view.h"
#include "widgetdata.h"
#include "libgcpcanvas/gcp-canvas-group.h"
#include "libgcpcanvas/gcp-canvas-line.h"
#include "libgcpcanvas/gcp-canvas-bpath.h"
#include "libgcpcanvas/gcp-canvas-polygon.h"
#include <glib/gi18n-lib.h>
#include <cmath>
#include <cstring>
gcpBond::gcpBond(): Bond()
{
m_CoordsCalc = false;
m_type = NormalBondType;
m_level = 0;
}
gcpBond::gcpBond(gcpAtom* first, gcpAtom* last, unsigned char order): Bond(first, last, order)
{
m_CoordsCalc = false;
m_type = NormalBondType;
m_level = 0;
}
gcpBond::~gcpBond()
{
}
void gcpBond::SetType(gcpBondType type)
{
m_type = type;
m_CoordsCalc = false;
if (m_type != NormalBondType) m_order = 1;
}
double gcpBond::GetAngle2D(gcpAtom* pAtom)
{
double x1, y1, x2, y2;
m_Begin->GetCoords(&x1, &y1);
m_End->GetCoords(&x2, &y2);
x2 -= x1;
y2 -= y1;
double length = square(x2) + square(y2);
if (length == 0.0) return HUGE_VAL;
if (pAtom == m_Begin) return atan2(- y2, x2) * 90 / 1.570796326794897;
else if (pAtom == m_End) return atan2(y2, - x2) * 90 / 1.570796326794897;
return HUGE_VAL;
}
double gcpBond::GetAngle2DRad(gcpAtom* pAtom)
{
double x1, y1, x2, y2;
m_Begin->GetCoords(&x1, &y1);
m_End->GetCoords(&x2, &y2);
x2 -= x1;
y2 -= y1;
double length = square(x2) + square(y2);
if (length == 0.0) return HUGE_VAL;
if (pAtom == m_Begin) return atan2(- y2, x2);
else if (pAtom == m_End) return atan2(y2, - x2);
return HUGE_VAL;
}
gcpCycle* gcpBond::GetFirstCycle(std::list<gcpCycle*>::iterator& i, gcpCycle * pCycle)
{
i = m_Cycles.begin();
return GetNextCycle(i, pCycle);
}
gcpCycle* gcpBond::GetNextCycle(std::list<gcpCycle*>::iterator& i, gcpCycle * pCycle)
{
if (*i == pCycle) i++;
if (i == m_Cycles.end()) return NULL;
pCycle = *i;
i++;
return pCycle;
}
bool gcpBond::IsInCycle(gcpCycle* pCycle)
{
std::list<gcpCycle*>::iterator i;
for (i = m_Cycles.begin(); i != m_Cycles.end(); i++)
if ((*i) == pCycle) return true;
return false;
}
bool gcpBond::GetLine2DCoords(unsigned Num, double* x1, double* y1, double* x2, double* y2)
{
if ((Num == 0) || (Num > m_order)) return false;
if (!m_CoordsCalc)
{
gcpTheme *Theme = dynamic_cast <gcpDocument *> (GetDocument ())->GetTheme ();
m_Begin->GetCoords(x1, y1);
m_End->GetCoords(x2, y2);
double dx = *x2 - *x1, dy = *y2 - *y1;
double l = sqrt(square(dx) + square(dy));
double BondDist = Theme->GetBondDist () / Theme->GetZoomFactor ();
dx *= (BondDist / l);
dy *= (BondDist / l);
if (m_order & 1)
{
m_coords[0] = *x1;
m_coords[1] = *y1;
m_coords[2] = *x2;
m_coords[3] = *y2;
if (m_order == 3)
{
m_coords[4] = *x1 - dy;
m_coords[5] = *y1 + dx;
m_coords[6] = *x2 - dy;
m_coords[7] = *y2 + dx;
m_coords[8] = *x1 + dy;
m_coords[9] = *y1 - dx;
m_coords[10] = *x2 + dy;
m_coords[11] = *y2 - dx;
}
}
else if ((m_order == 2) && IsCyclic())
{
m_coords[0] = *x1;
m_coords[1] = *y1;
m_coords[2] = *x2;
m_coords[3] = *y2;
gcpCycle* pCycle;
if (IsCyclic() > 1)
{
//Search prefered cycle
std::list<gcpCycle*>::iterator i = m_Cycles.begin();
pCycle = *i;
for (; i != m_Cycles.end(); i++)
if (pCycle->IsBetterForBonds(*i)) pCycle = *i;
}
else pCycle = m_Cycles.front();
double a0 = atan2(*y1 - *y2, *x2 - *x1), a1, a2;
pCycle->GetAngles2D(this, &a1, &a2);
if (sin(a0 - a1) * sin (a0 - a2) > 0)
{
double sign = sin(a0 - a1) > 0.0 ? 1.0 : -1.0;
double tanb = fabs(tan((3.141592653589793238462643 - a0 + a1) / 2)), cosa = cos(a0), sina = sin(a0);
m_coords[4] = *x1 + BondDist * cosa * tanb - dy * sign;
m_coords[5] = *y1 + dx * sign - BondDist * sina * tanb;
tanb = fabs(tan((a2 - a0) / 2));
m_coords[6] = *x2 - BondDist * cosa * tanb - dy * sign;
m_coords[7] = *y2 + dx * sign + BondDist * sina * tanb;
}
else
{
m_coords[0] = *x1 - dy / 2;
m_coords[1] = *y1 + dx / 2;
m_coords[2] = *x2 - dy / 2;
m_coords[3] = *y2 + dx / 2;
m_coords[4] = *x1 + dy / 2;
m_coords[5] = *y1 - dx / 2;
m_coords[6] = *x2 + dy / 2;
m_coords[7] = *y2 - dx / 2;
}
}
else
{
m_coords[0] = *x1 - dy / 2;
m_coords[1] = *y1 + dx / 2;
m_coords[2] = *x2 - dy / 2;
m_coords[3] = *y2 + dx / 2;
m_coords[4] = *x1 + dy / 2;
m_coords[5] = *y1 - dx / 2;
m_coords[6] = *x2 + dy / 2;
m_coords[7] = *y2 - dx / 2;
if (m_order == 4)
{
m_coords[8] = *x1 - dy * 1.5;
m_coords[9] = *y1 + dx * 1.5;
m_coords[10] = *x2 - dy * 1.5;
m_coords[11] = *y2 + dx * 1.5;
m_coords[12] = *x1 + dy * 1.5;
m_coords[13] = *y1 - dx * 1.5;
m_coords[14] = *x2 + dy * 1.5;
m_coords[15] = *y2 - dx * 1.5;
}
}
m_CoordsCalc = true;
}
Num--;
Num *= 4;
*x1 = m_coords[Num++];
*y1 = m_coords[Num++];
*x2 = m_coords[Num++];
*y2 = m_coords[Num++];
return true;
}
Object* gcpBond::GetAtomAt(double x, double y, double z)
{
double x1, y1;
m_Begin->GetCoords(&x1, &y1);
if ((fabs(x - x1) < 10) && (fabs(y - y1) < 10)) return m_Begin;
m_End->GetCoords(&x1, &y1);
if ((fabs(x - x1) < 10) && (fabs(y - y1) < 10)) return m_End;
return NULL;
}
bool gcpBond::SaveNode(xmlDocPtr xml, xmlNodePtr node)
{
switch(m_type) {
case UpBondType:
xmlNewProp(node, (xmlChar*)"type", (xmlChar*)"up");
break;
case DownBondType:
xmlNewProp(node, (xmlChar*)"type", (xmlChar*)"down");
break;
case ForeBondType:
xmlNewProp(node, (xmlChar*)"type", (xmlChar*)"fore");
break;
case UndeterminedBondType:
xmlNewProp(node, (xmlChar*)"type", (xmlChar*)"undetermined");
break;
default:
break;
}
if (m_level != 0) {
char *buf = g_strdup_printf ("%d", m_level);
xmlNewProp(node, (xmlChar*)"level", (xmlChar*) buf);
g_free (buf);
}
return true;
}
bool gcpBond::LoadNode (xmlNodePtr node)
{
char* buf;
buf = (char*) xmlGetProp (node, (xmlChar*) "type");
if (!buf)
SetType (NormalBondType);
else if (!strcmp (buf, "up"))
SetType (UpBondType);
else if (!strcmp (buf, "down"))
SetType (DownBondType);
else if (!strcmp (buf, "fore"))
SetType (ForeBondType);
else if (!strcmp (buf, "undetermined"))
SetType (UndeterminedBondType);
else
SetType (NormalBondType);
if (buf)
xmlFree (buf);
buf = (char*) xmlGetProp (node, (xmlChar*) "level");
if (buf) {
m_level = atoi (buf);
xmlFree (buf);
}
return true;
}
void gcpBond::IncOrder(int n)
{
if (!((gcpAtom*)GetAtom(0))->AcceptNewBonds() ||
!((gcpAtom*)GetAtom(1))->AcceptNewBonds()) m_order = 1;
else {
Bond::IncOrder(n);
if (m_order == 4) m_order = 1;//avoid quadruple bonds for now
}
m_CoordsCalc = false;
((gcpAtom*)m_Begin)->Update();
((gcpAtom*)m_End)->Update();
}
double gcpBond::GetDist(double x, double y)
{
double x1, y1, x2, y2, l, d1, d2;
gcpTheme *Theme = dynamic_cast <gcpDocument *> (GetDocument ())->GetTheme ();
double BondDist = Theme->GetBondDist () / Theme->GetZoomFactor ();
m_Begin->GetCoords(&x1, &y1);
m_End->GetCoords(&x2, &y2);
d1 = (x2 - x1) * (x1 - x) + (y2 - y1) * (y1 - y);
d2 = (x2 - x1) * (x2 - x) + (y2 - y1) * (y2 - y);
if ((d1 < 0.0) && (d2 < 0.0)) return sqrt(square(x2 - x) + square(y2 - y));
if ((d1 > 0.0) && (d2 > 0.0)) return sqrt(square(x1 - x) + square(y1 - y));
x2 -= x1;
y2 -= y1;
x -= x1;
y -= y1;
l = fabs(x2 * y - y2 * x) / sqrt(square(x2) + square(y2));
return (l < BondDist * (m_order - 1)) ? 0 : l - BondDist * (m_order - 1);
}
void gcpBond::AddCycle(gcpCycle* pCycle)
{
m_Cycles.push_back(pCycle);
if ((m_order == 2) && m_CoordsCalc) SetDirty();
}
void gcpBond::RemoveCycle(gcpCycle* pCycle)
{
m_Cycles.remove(pCycle);
if ((m_order == 2) && m_CoordsCalc) SetDirty();
}
void gcpBond::SetDirty()
{
gcpDocument *pDoc = (gcpDocument*)GetDocument();
if (pDoc) pDoc->NotifyDirty(this);
m_CoordsCalc = false;
}
void gcpBond::RemoveAllCycles()
{
m_Cycles.clear();
if (m_order == 2)
{
gcpDocument *pDoc = (gcpDocument*)GetDocument();
if (pDoc) pDoc->NotifyDirty(this);
m_CoordsCalc = false;
}
}
void gcpBond::Move(double x, double y, double z)
{
m_CoordsCalc = false;
}
void gcpBond::Transform2D(Matrix2D& m, double x, double y)
{
m_CoordsCalc = false;
}
void gcpBond::Revert()
{
m_CoordsCalc = false;
Atom* pAtom = m_Begin;
m_Begin = m_End;
m_End = pAtom;
}
double gcpBond::Get2DLength()
{
double x1, y1, x2, y2;
m_Begin->GetCoords(&x1, &y1);
m_End->GetCoords(&x2, &y2);
return sqrt(square(x1 - x2) + square(y1 - y2));
}
void gcpBond::SetSelected(GtkWidget* w, int state)
{
if (!m_order)
return;
gcpWidgetData* pData = (gcpWidgetData*)g_object_get_data(G_OBJECT(w), "data");
GnomeCanvasGroup* group = pData->Items[this];
gchar* color;
switch (state) {
case SelStateUnselected:
color = Color;
break;
case SelStateSelected:
color = SelectColor;
break;
case SelStateUpdating:
color = AddColor;
break;
case SelStateErasing:
color = DeleteColor;
break;
default:
color = Color;
break;
}
void *obj = g_object_get_data(G_OBJECT(group), "path");
switch (GetType())
{
case NormalBondType:
case UndeterminedBondType:
g_object_set(obj, "outline_color", color, NULL);
break;
case ForeBondType:
case UpBondType:
case DownBondType:
g_object_set(obj, "fill_color", color, NULL);
break;
}
}
void gcpBond::Add(GtkWidget* w)
{
if (!w) return;
gcpWidgetData* pData = (gcpWidgetData*)g_object_get_data(G_OBJECT(w), "data");
gcpTheme *pTheme = pData->View->GetDoc ()->GetTheme ();
Atom *pAtom0, *pAtom1;
if (!(pAtom0 = GetAtom(0))) return;
if (!(pAtom1 = GetAtom(1))) return;
unsigned char order = GetOrder();
if (order == 0) return;
GnomeCanvasGroup* group;
GnomeCanvasItem* item = NULL;
pData = (gcpWidgetData*)g_object_get_data(G_OBJECT(w), "data");
group = GNOME_CANVAS_GROUP(gnome_canvas_item_new(pData->Group, gnome_canvas_group_ext_get_type(), NULL));
g_signal_connect(G_OBJECT(group), "event", G_CALLBACK(on_event), w);
g_object_set_data(G_OBJECT(group), "object", this);
bool result = false;
if (m_Crossing.size () > 0) {
map<gcpBond*, gcpBondCrossing>::iterator i, iend = m_Crossing.end ();
for (i = m_Crossing.begin (); i != iend; i++)
if ((result |= (*i).second.is_before))
break;
}
GnomeCanvasPathDef *path = NULL;
if (result) {
path = BuildCrossingPathDef (pData);
if (path) {
GnomeCanvasItem *item = (m_type == NormalBondType || m_type == UndeterminedBondType)?
gnome_canvas_item_new (
group,
gnome_canvas_bpath_ext_get_type (),
"bpath", path,
"outline_color", "white",
"width_units", pTheme->GetBondWidth () * 3.,
NULL):
gnome_canvas_item_new (
group,
gnome_canvas_bpath_ext_get_type(),
"bpath", path,
"fill_color", "white",
"width_units", 0.,
NULL);
g_object_set_data (G_OBJECT (group), "back", item);
g_object_set_data (G_OBJECT (item), "object", this);
g_signal_connect (G_OBJECT (item), "event", G_CALLBACK (on_event), w);
/* now bring to front, FIXME: not secure ! */
gnome_canvas_item_lower_to_bottom (item);
gnome_canvas_item_raise_to_top (GNOME_CANVAS_ITEM (group));
Atom *pAtom = GetAtom (0);
if (pAtom->GetZ () != 6 || static_cast <gcpAtom*> (pAtom)->GetShowSymbol ())
gnome_canvas_item_raise_to_top (GNOME_CANVAS_ITEM (pData->Items[pAtom]));
pAtom = GetAtom (1);
if (pAtom->GetZ () != 6 || static_cast <gcpAtom*> (pAtom)->GetShowSymbol ())
gnome_canvas_item_raise_to_top (GNOME_CANVAS_ITEM (pData->Items[pAtom]));
gnome_canvas_path_def_unref (path);
}
}
path = BuildPathDef (pData);
switch (GetType())
{
case NormalBondType:
case UndeterminedBondType:
item = gnome_canvas_item_new(
group,
gnome_canvas_bpath_ext_get_type(),
"bpath", path,
"outline_color", (pData->IsSelected(this))? SelectColor: Color,
"width_units", pTheme->GetBondWidth (),
NULL);
break;
case ForeBondType:
case UpBondType:
case DownBondType:
item = gnome_canvas_item_new(
group,
gnome_canvas_bpath_ext_get_type(),
"bpath", path,
"fill_color", (pData->IsSelected(this))? SelectColor: Color,
"width_units", 0.,
NULL);
break;
}
gnome_canvas_path_def_unref (path);
g_object_set_data(G_OBJECT(group), "path", item);
g_object_set_data(G_OBJECT(item), "object", this);
g_signal_connect(G_OBJECT(item), "event", G_CALLBACK(on_event), w);
pData->Items[this] = group;
if (pAtom0->GetParent()->GetType() == FragmentType)
gnome_canvas_item_raise_to_top(GNOME_CANVAS_ITEM(pData->Items[pAtom0->GetParent()]));
else if (pAtom0->GetZ() != 6 || static_cast <gcpAtom*> (pAtom0)->GetShowSymbol ())
gnome_canvas_item_raise_to_top(GNOME_CANVAS_ITEM(pData->Items[pAtom0]));
else
gnome_canvas_item_lower_to_bottom (GNOME_CANVAS_ITEM(pData->Items[pAtom0]));
map<string, Object*>::iterator i;
Object* electron = pAtom0->GetFirstChild (i);
while (electron){
gnome_canvas_item_raise_to_top(GNOME_CANVAS_ITEM(pData->Items[electron]));
electron = pAtom0->GetNextChild (i);
}
if (pAtom1->GetParent()->GetType() == FragmentType)
gnome_canvas_item_raise_to_top(GNOME_CANVAS_ITEM(pData->Items[pAtom1->GetParent()]));
else if (pAtom1->GetZ() != 6 || static_cast <gcpAtom*> (pAtom1)->GetShowSymbol ())
gnome_canvas_item_raise_to_top(GNOME_CANVAS_ITEM(pData->Items[pAtom1]));
else
gnome_canvas_item_lower_to_bottom (GNOME_CANVAS_ITEM (pData->Items[pAtom1]));
electron = pAtom1->GetFirstChild (i);
while (electron){
gnome_canvas_item_raise_to_top(GNOME_CANVAS_ITEM(pData->Items[electron]));
electron = pAtom1->GetNextChild (i);
}
}
bool gcpBond::ReplaceAtom(gcpAtom* oldAtom, gcpAtom* newAtom)
{
if (oldAtom == m_Begin)
{
if (m_End)
m_End->RemoveBond(this);
m_Begin = newAtom;
if (m_Begin && m_End)
m_End->AddBond(this);
}
else if (oldAtom == m_End)
{
if (m_Begin)
m_Begin->RemoveBond(this);
m_End = newAtom;
if (m_Begin && m_End)
m_Begin->AddBond(this);
}
return true;
}
GnomeCanvasPathDef* gcpBond::BuildPathDef (gcpWidgetData* pData)
{
double x1, y1, x2, y2, x, y, dx, dy, dx1, dy1;
int i, n;
GnomeCanvasPathDef* path = gnome_canvas_path_def_new ();
gcpTheme *pTheme = pData->View->GetDoc ()->GetTheme ();
switch (GetType())
{
case NormalBondType:
i = 1;
while (GetLine2DCoords(i++, &x1, &y1, &x2, &y2))
{
gnome_canvas_path_def_moveto (path, x1 * pTheme->GetZoomFactor (), y1 * pTheme->GetZoomFactor ());
gnome_canvas_path_def_lineto (path, x2 * pTheme->GetZoomFactor (), y2 * pTheme->GetZoomFactor ());
}
break;
case UpBondType:
GetLine2DCoords(1, &x1, &y1, &x2, &y2);
gnome_canvas_path_def_moveto (path, x1 * pTheme->GetZoomFactor (), y1 * pTheme->GetZoomFactor ());
x = sqrt(square(x2 - x1) + square(y2 - y1));
dx = (y1 - y2) / x * pTheme->GetStereoBondWidth () / 2;
dy = (x2 - x1) / x * pTheme->GetStereoBondWidth () / 2;
gnome_canvas_path_def_lineto (path, x2 * pTheme->GetZoomFactor () + dx, y2 * pTheme->GetZoomFactor () + dy);
gnome_canvas_path_def_lineto (path, x2 * pTheme->GetZoomFactor () -dx, y2 * pTheme->GetZoomFactor () -dy);
gnome_canvas_path_def_closepath (path);
break;
case DownBondType:
{
double xi[8];
GetLine2DCoords(1, &x1, &y1, &x2, &y2);
x1 *= pTheme->GetZoomFactor ();
y1 *= pTheme->GetZoomFactor ();
x2 *= pTheme->GetZoomFactor ();
y2 *= pTheme->GetZoomFactor ();
x = sqrt(square(x2 - x1) + square(y2 - y1));
n = int(floor(x / (pTheme->GetHashDist () + pTheme->GetHashWidth ())));
dx1 = (x2 - x1) / x * pTheme->GetHashWidth ();
dy1 = (y2 - y1) / x * pTheme->GetHashWidth ();
dx = (y1 - y2) / x * pTheme->GetStereoBondWidth () / 2;
dy = (x2 - x1) / x * pTheme->GetStereoBondWidth () / 2;
gnome_canvas_path_def_moveto (path, xi[0] = x1 + dx, xi[1] = y1 + dy);
gnome_canvas_path_def_lineto (path, xi[2] = x1 - dx, xi[3] = y1 - dy);
dx *= (1 - pTheme->GetHashWidth () / x);
dy *= (1 - pTheme->GetHashWidth () / x);
gnome_canvas_path_def_lineto (path, xi[4] = x1 + dx1 - dx, xi[5] = y1 + dy1 - dy);
gnome_canvas_path_def_lineto (path, xi[6] = x1 + dx1 + dx, xi[7] = y1 + dy1 + dy);
gnome_canvas_path_def_lineto (path, xi[0], xi[1]);
gnome_canvas_path_def_closepath_current (path);
dx = (x2 - x1) / x * (pTheme->GetHashDist () + pTheme->GetHashWidth ())
- (y1 - y2) / x * pTheme->GetStereoBondWidth () / 2 * (pTheme->GetHashDist () + pTheme->GetHashWidth ()) / x;
dy = (y2 - y1) / x * (pTheme->GetHashDist () + pTheme->GetHashWidth ())
- (x2 - x1) / x * pTheme->GetStereoBondWidth () / 2 * (pTheme->GetHashDist () + pTheme->GetHashWidth ()) / x;
dx1 = (x2 - x1) / x * (pTheme->GetHashDist () + pTheme->GetHashWidth ())
+ (y1 - y2) / x * pTheme->GetStereoBondWidth () / 2 * (pTheme->GetHashDist () + pTheme->GetHashWidth ()) / x;
dy1 = (y2 - y1) / x * (pTheme->GetHashDist () + pTheme->GetHashWidth ())
+ (x2 - x1) / x * pTheme->GetStereoBondWidth () / 2 * (pTheme->GetHashDist () + pTheme->GetHashWidth ()) / x;
for (int i = 1; i < n; i++)
{
gnome_canvas_path_def_moveto (path, xi[0] += dx, xi[1] += dy);
gnome_canvas_path_def_lineto (path, xi[2] += dx1, xi[3] += dy1);
gnome_canvas_path_def_lineto (path, xi[4] += dx1, xi[5] += dy1);
gnome_canvas_path_def_lineto (path, xi[6] += dx, xi[7] += dy);
gnome_canvas_path_def_lineto (path, xi[0], xi[1]);
gnome_canvas_path_def_closepath_current (path);
}
break;
}
case ForeBondType:
GetLine2DCoords(1, &x1, &y1, &x2, &y2);
x = sqrt(square(x2 - x1) + square(y2 - y1));
dx = (y1 - y2) / x * pTheme->GetStereoBondWidth () / 2;
dy = (x2 - x1) / x * pTheme->GetStereoBondWidth () / 2;
gnome_canvas_path_def_moveto (path, x1 * pTheme->GetZoomFactor () + dx, y1 * pTheme->GetZoomFactor () + dy);
gnome_canvas_path_def_lineto (path, x2 * pTheme->GetZoomFactor () + dx, y2 * pTheme->GetZoomFactor () + dy);
gnome_canvas_path_def_lineto (path, x2 * pTheme->GetZoomFactor () -dx, y2 * pTheme->GetZoomFactor () -dy);
gnome_canvas_path_def_lineto (path, x1 * pTheme->GetZoomFactor () -dx, y1 * pTheme->GetZoomFactor () -dy);
gnome_canvas_path_def_closepath (path);
break;
case UndeterminedBondType:
{
GetLine2DCoords(1, &x1, &y1, &x2, &y2);
x1 *= pTheme->GetZoomFactor ();
y1 *= pTheme->GetZoomFactor ();
x2 *= pTheme->GetZoomFactor ();
y2 *= pTheme->GetZoomFactor ();
gnome_canvas_path_def_moveto(path, x1, y1);
double length;
length = sqrt(square(x2 - x1) + square(y2 - y1));
n = (int)length / 3;
int s = 1;
dx = (x2 - x1) / n;
dy = (y2 - y1) / n;
x = x1;
y = y1;
for (int i = 1; i < n; i++)
{
x1 = x + dx / 3 + dy /1.5 * s;
y1 = y + dy / 3 - dx /1.5 * s;
dx1 = x + dx / 1.5 + dy /1.5 * s;
dy1 = y + dy / 1.5 - dx /1.5 * s;
x += dx;
y += dy;
s *= -1;
gnome_canvas_path_def_curveto(path, x1, y1, dx1, dy1, x, y);
}
x1 = x + dx / 3 + dy /1.5 * s;
y1 = y + dy / 3 - dx /1.5 * s;
dx1 = x + dx / 1.5 + dy /1.5 * s;
dy1 = y + dy / 1.5 - dx /1.5 * s;
gnome_canvas_path_def_curveto(path, x1, y1, dx1, dy1, x2, y2);
break;
}
}
return path;
}
GnomeCanvasPathDef *gcpBond::BuildCrossingPathDef (gcpWidgetData* pData)
{
gcpTheme *pTheme = pData->View->GetDoc ()->GetTheme ();
GnomeCanvasPathDef* path = NULL;
double x1, y1, x2, y2, x, dx, dy;
int i;
switch (GetType ()) {
case NormalBondType:
i = 1;
path = gnome_canvas_path_def_new ();
while (GetLine2DCoords (i++, &x1, &y1, &x2, &y2)) {
dx = (x2 - x1) / 10.;
dy = (y2 - y1) / 10.;
x1 += dx;
x2 -= dx;
y1 += dy;
y2 -= dy;
gnome_canvas_path_def_moveto (path, x1 * pTheme->GetZoomFactor (), y1 * pTheme->GetZoomFactor ());
gnome_canvas_path_def_lineto (path, x2 * pTheme->GetZoomFactor (), y2 * pTheme->GetZoomFactor ());
}
break;
case UpBondType:
break;
case DownBondType:
break;
case ForeBondType:
path = gnome_canvas_path_def_new ();
GetLine2DCoords(1, &x1, &y1, &x2, &y2);
dx = (x2 - x1) / 10.;
dy = (y2 - y1) / 10.;
x1 += dx;
x2 -= dx;
y1 += dy;
y2 -= dy;
x = sqrt(square(x2 - x1) + square(y2 - y1));
dx = (y1 - y2) / x * pTheme->GetStereoBondWidth () / 2;
dy = (x2 - x1) / x * pTheme->GetStereoBondWidth () / 2;
dx += (dx > 0.)? 1.: -1.;
dy += (dy > 0.)? 1.: -1.;
gnome_canvas_path_def_moveto (path, x1 * pTheme->GetZoomFactor () + dx, y1 * pTheme->GetZoomFactor () + dy);
gnome_canvas_path_def_lineto (path, x2 * pTheme->GetZoomFactor () + dx, y2 * pTheme->GetZoomFactor () + dy);
gnome_canvas_path_def_lineto (path, x2 * pTheme->GetZoomFactor () -dx, y2 * pTheme->GetZoomFactor () -dy);
gnome_canvas_path_def_lineto (path, x1 * pTheme->GetZoomFactor () -dx, y1 * pTheme->GetZoomFactor () -dy);
gnome_canvas_path_def_closepath (path);
break;
case UndeterminedBondType:
break;
}
return path;
}
void gcpBond::Update(GtkWidget* w)
{
if (!w || !m_order)
return;
gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data(G_OBJECT(w), "data");
if (pData->Items[this] != NULL)
return;
gcpTheme *pTheme = pData->View->GetDoc ()->GetTheme ();
bool result = false;
if (m_Crossing.size () > 0) {
map<gcpBond*, gcpBondCrossing>::iterator i, iend = m_Crossing.end ();
for (i = m_Crossing.begin (); i != iend; i++)
if ((result |= (*i).second.is_before))
break;
}
GnomeCanvasPathDef* path;
void * obj;
GnomeCanvasGroup *group = pData->Items[this];
obj = g_object_get_data(G_OBJECT(group), "back");
if (result) {
path = BuildCrossingPathDef (pData);
if (path) {
if (obj)
g_object_set (obj, "bpath", path, NULL);
else {
GnomeCanvasItem *item = (m_type == NormalBondType || m_type == UndeterminedBondType)?
gnome_canvas_item_new (
group,
gnome_canvas_bpath_ext_get_type (),
"bpath", path,
"outline_color", "white",
"width_units", pTheme->GetBondWidth () * 3.,
NULL):
gnome_canvas_item_new (
group,
gnome_canvas_bpath_ext_get_type(),
"bpath", path,
"fill_color", "white",
"width_units", 0.,
NULL);
g_object_set_data (G_OBJECT (group), "back", item);
g_object_set_data (G_OBJECT (item), "object", this);
g_signal_connect (G_OBJECT (item), "event", G_CALLBACK (on_event), w);
/* now bring to front, FIXME: not secure ! */
gnome_canvas_item_lower_to_bottom (item);
gnome_canvas_item_raise_to_top (GNOME_CANVAS_ITEM (group));
Atom *pAtom = GetAtom (0);
if (pAtom->GetZ () != 6 || static_cast <gcpAtom*> (pAtom)->GetShowSymbol ())
gnome_canvas_item_raise_to_top (GNOME_CANVAS_ITEM (pData->Items[pAtom]));
pAtom = GetAtom (1);
if (pAtom->GetZ () != 6 || static_cast <gcpAtom*> (pAtom)->GetShowSymbol ())
gnome_canvas_item_raise_to_top (GNOME_CANVAS_ITEM (pData->Items[pAtom]));
}
gnome_canvas_path_def_unref (path);
}
} else if (obj) {
g_object_set_data (G_OBJECT(group), "back", NULL);
}
path = BuildPathDef (pData);
obj = g_object_get_data(G_OBJECT(group), "path");
g_object_set (obj, "bpath", path, NULL);
if (m_type == NormalBondType || m_type == UndeterminedBondType)
g_object_set (obj, "width_units", pTheme->GetBondWidth (), NULL);
gnome_canvas_path_def_unref (path);
}
double gcpBond::GetYAlign ()
{
double x1, y1, x2, y2;
m_Begin->GetCoords(&x1, &y1);
m_End->GetCoords(&x2, &y2);
return (y1 + y2) / 2;
}
bool gcpBond::IsCrossing (gcpBond *pBond)
{
double a1, a2, b1, b2, c1, c2, d, d1, d2;
double x0, x1, x2, x3, y0, y1, y2, y3, z0, z1, z2, z3;
if (m_Begin == pBond->m_Begin || m_Begin == pBond->m_End ||
m_End == pBond->m_Begin || m_End == pBond->m_End)
return false;
m_Begin->GetCoords(&x0, &y0, &z0);
m_End->GetCoords(&x1, &y1, &z1);
pBond->m_Begin->GetCoords(&x2, &y2, &z2);
pBond->m_End->GetCoords(&x3, &y3, &z3);
a1 = x1 - x0;
a2 = y1 - y0;
b1 = x2 - x3;
b2 = y2 - y3;
d = a1 * b2 - a2 * b1;
if (d == 0.)
return false;
c1 = x2 - x0;
c2 = y2 - y0;
d1 = c1 * b2 - c2 * b1;
d2 = c2 * a1 - c1 * a2;
a1 = d1 / d;
a2 = d2 / d;
if ((a1 <= 0.) || (a1 >= 1.))
return false;
if ((a2 > 0.) && (a2 < 1.)) {
double z = z0 + a1 * (z1 - z0), z_ = z2 + a2 * (z3 - z2);
bool is_before = z > z_ || m_level > pBond->m_level;
if (z == z_ && m_level == pBond->m_level) {
if (m_type == ForeBondType && pBond->m_type != ForeBondType) {
is_before = true;
pBond->m_level -= 1;
} else {
is_before = false;
pBond->m_level += 1;
}
}
m_Crossing[pBond].a = a1;
m_Crossing[pBond].is_before = is_before;
pBond->m_Crossing[this].a = a2;
pBond->m_Crossing[this].is_before = !is_before;
return true;
} else
return false;
}
static void on_bring_to_front (gcpBond *pBond)
{
pBond->BringToFront ();
}
static void on_move_to_back (gcpBond *pBond)
{
pBond->MoveToBack ();
}
bool gcpBond::BuildContextualMenu (GtkUIManager *UIManager, Object *object, double x, double y)
{
bool result = false;
Object *pAtom = GetAtomAt (x, y);
if (pAtom)
result = pAtom->BuildContextualMenu (UIManager, object, x, y);
if (m_Crossing.size () == 0)
return (pAtom)? result: GetParent ()->BuildContextualMenu (UIManager, object, x, y);
bool is_before = false, is_after = false;
if (m_Crossing.size () > 0) {
map<gcpBond*, gcpBondCrossing>::iterator i, iend = m_Crossing.end ();
for (i = m_Crossing.begin (); i != iend; i++) {
if (m_level == (*i).first->m_level || m_type != (*i).first->m_type)
continue;
if ((*i).second.is_before)
is_before = true;
else
is_after = true;
}
}
if (!(is_before || is_after))
return (pAtom)? result: GetParent ()->BuildContextualMenu (UIManager, object, x, y);
GtkActionGroup *group = gtk_action_group_new ("bond");
GtkAction *action;
action = gtk_action_new ("Bond", _("Bond"), NULL, NULL);
gtk_action_group_add_action (group, action);
g_object_unref (action);
if (is_before) {
action = gtk_action_new ("MoveBack", _("Move to back"), NULL, NULL);
g_signal_connect_swapped (action, "activate", G_CALLBACK (on_move_to_back), this);
gtk_action_group_add_action (group, action);
g_object_unref (action);
gtk_ui_manager_add_ui_from_string (UIManager, "<ui><popup><menu action='Bond'><menuitem action='MoveBack'/></menu></popup></ui>", -1, NULL);
}
if (is_after) {
action = gtk_action_new ("BringFront", _("Bring to front"), NULL, NULL);
g_signal_connect_swapped (action, "activate", G_CALLBACK (on_bring_to_front), this);
gtk_action_group_add_action (group, action);
g_object_unref (action);
gtk_ui_manager_add_ui_from_string (UIManager, "<ui><popup><menu action='Bond'><menuitem action='BringFront'/></menu></popup></ui>", -1, NULL);
}
gtk_ui_manager_insert_action_group (UIManager, group, 0);
g_object_unref (group);
if (!pAtom)
GetParent ()->BuildContextualMenu (UIManager, object, x, y);
return true;
}
void gcpBond::MoveToBack ()
{
gcpView *pView = reinterpret_cast<gcpDocument*> (GetDocument ())->GetView ();
map<gcpBond*, gcpBondCrossing>::iterator i, iend = m_Crossing.end ();
for (i = m_Crossing.begin (); i != iend; i++) {
if (m_level > (*i).first->m_level && m_type == (*i).first->m_type) {
m_level = (*i).first->m_level - 1;
(*i).second.is_before = false;
(*i).first->m_Crossing[this].is_before = true;
pView->Update ((*i).first);
}
}
pView->Update (this);
}
void gcpBond::BringToFront ()
{
gcpView *pView = reinterpret_cast<gcpDocument*> (GetDocument ())->GetView ();
map<gcpBond*, gcpBondCrossing>::iterator i, iend = m_Crossing.end ();
for (i = m_Crossing.begin (); i != iend; i++) {
if (m_level < (*i).first->m_level && m_type == (*i).first->m_type) {
m_level = (*i).first->m_level + 1;
(*i).second.is_before = true;
(*i).first->m_Crossing[this].is_before = false;
pView->Update ((*i).first);
}
}
pView->Update (this);
}
syntax highlighted by Code2HTML, v. 0.9.1