// -*- C++ -*-
/*
* GChemPaint arrows plugin
* retrosynthesis.cc
*
* Copyright (C) 2004-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 "retrosynthesis.h"
#include "retrosynthesisarrow.h"
#include "retrosynthesisstep.h"
#include "lib/document.h"
#include "lib/theme.h"
#include "lib/widgetdata.h"
#include "lib/view.h"
#include <glib/gi18n-lib.h>
#include <cmath>
TypeId RetrosynthesisType = NoType;
gcpRetrosynthesis::gcpRetrosynthesis (): Object (RetrosynthesisType)
{
SetId ((char*) "rsy1");
Target = NULL;
}
gcpRetrosynthesis::~gcpRetrosynthesis ()
{
if (IsLocked ())
return;
map<string, Object*>::iterator i;
Object *pObj;
gcpRetrosynthesisArrow *arrow;
gcpDocument *pDoc = reinterpret_cast<gcpDocument *> (GetDocument ());
gcpOperation *pOp = pDoc->GetCurrentOperation ();
while ((pObj = GetFirstChild (i))) {
if ((pObj->GetType () == RetrosynthesisArrowType)) {
arrow = reinterpret_cast<gcpRetrosynthesisArrow *> (pObj);
arrow->SetStartStep (NULL);
arrow->SetEndStep (NULL);
arrow->SetParent (GetParent ());
if (pOp)
pOp->AddObject (arrow, 1);
} else
delete pObj;
}
}
xmlNodePtr gcpRetrosynthesis::Save (xmlDocPtr xml)
{
xmlNodePtr node = Object::Save (xml);
xmlNewProp (node, (xmlChar const*) "target", (xmlChar const*) Target->GetId ());
return node;
}
bool gcpRetrosynthesis::Load (xmlNodePtr node)
{
xmlChar* buf;
xmlNodePtr child;
Object* pObject;
list<xmlNodePtr> arrows;
Lock ();
buf = xmlGetProp (node, (xmlChar*) "id");
if (buf) {
SetId ((char*) buf);
xmlFree (buf);
}
child = node->children;
while (child) {
if (!strcmp ((const char*) child->name, "retrosynthesis-arrow"))
arrows.push_front (child);
else {
pObject = CreateObject ((const char*) child->name, this);
if (pObject) {
if (!pObject->Load (child))
delete pObject;
} else {
Lock (false);
return false;
}
}
child = child->next;
}
while (!arrows.empty ()) {
child = arrows.back ();
pObject = CreateObject ("retrosynthesis-arrow", this);
if (pObject) {
if (!pObject->Load (child))
delete pObject;
} else {
Lock (false);
return false;
}
arrows.pop_back ();
}
Lock (false);
buf = xmlGetProp (node, (xmlChar*) "target");
if (!buf)
return false;
Target = reinterpret_cast<gcpRetrosynthesisStep*> (GetChild ((const char*) buf));
xmlFree (buf);
if (!Target)
return false;
return true;
}
typedef struct
{
double x, y;
ArtDRect r;
gcpRetrosynthesisStep *step;
} ObjectData;
bool gcpRetrosynthesis::Build (list<Object*>& Children) throw (invalid_argument)
{
gcpDocument *pDoc = reinterpret_cast<gcpDocument *> (GetDocument ());
gcpTheme *pTheme = pDoc->GetTheme ();
gcpWidgetData *pData= reinterpret_cast<gcpWidgetData *> (g_object_get_data (G_OBJECT (pDoc->GetWidget ()), "data"));
list<Object*>::iterator i, iend = Children.end ();
map<Object *, ObjectData> Objects;
list<Object *> Arrows;
double minright, minleft, x, y, x0, y0, x1, y1, l, d, ps;
Object *Left, *Right;
ObjectData od;
gcpRetrosynthesisArrow *arrow;
unsigned narrows = 0, nmol = 0;
TypeId Id;
for (i = Children.begin (); i != iend; i++) {
Id = (*i)->GetType ();
if (Id == MoleculeType) {
pData->GetObjectBounds (*i, &od.r);
od.x = (od.r.x0 + od.r.x1) / 2.;
od.y = (*i)->GetYAlign () * pTheme->GetZoomFactor ();
od.step = new gcpRetrosynthesisStep (this, reinterpret_cast<gcpMolecule *>(*i));
nmol++;
Objects[*i] = od;
} else if (Id == RetrosynthesisArrowType) {
narrows++;
Arrows.push_back (*i);
AddChild (*i);
} else
throw invalid_argument (_("Something wrong happened, please file a bug report."));
}
// now, for each arrow, search closiest object on both sides and verify it's a molecule
list<Object *>::iterator j, jend = Arrows.end ();
map<Object *, ObjectData>::iterator k, kend = Objects.end ();
for (j = Arrows.begin (); j != jend; j++) {
arrow = reinterpret_cast<gcpRetrosynthesisArrow *>(*j);
arrow->GetCoords (&x0, &y0, &x1, &y1);
//x0 and y0 should be the center of the arrow, not the beginning, so we must transform them
x0 = (x0 + x1) / 2;
y0 = (y0 + y1) / 2;
// x1, y1 will now be the coordinates of a normalized vector:
x1 -= x0;
y1 -= y0;
x0 *= pTheme->GetZoomFactor ();
y0 *= pTheme->GetZoomFactor ();
l = sqrt (x1 * x1 + y1 * y1);
x1 /= l;
y1 /= l;
l *= pTheme->GetZoomFactor (); // half length of the arrow on the screen
// No molecule should be nearer than that
minright = minleft = DBL_MAX;
Left = Right = NULL;
for (k = Objects.begin (); k != kend; k++) {
od = (*k).second;
x = od.x - x0;
y = od.y - y0;
d = sqrt (x * x + y * y);
ps = (x * x1 + y * y1) / d;
if (ps >= -.71 && ps <= .71)
continue;
if (d < l) {
Left = (*k).first;
Right = *j;
pData->UnselectAll ();
pData->SetSelected (Left);
pData->SetSelected (Right);
throw invalid_argument (_("No space left between molecule and arrow!"));
}
if (ps < 0) {
if (d < minleft) {
Left = od.step;
minleft = d;
}
} else {
if (d < minright) {
Right = od.step;
minright = d;
}
}
}
if (!Left || !Right) { // Do not accept arrows with only one step (?)
Left = *j;
pData->UnselectAll ();
pData->SetSelected (Left);
throw invalid_argument (_("Isolated arrows are not allowed!"));
}
reinterpret_cast<gcpRetrosynthesisArrow *> (*j)->SetStartStep (reinterpret_cast<gcpRetrosynthesisStep *> (Left));
reinterpret_cast<gcpRetrosynthesisArrow *> (*j)->SetEndStep (reinterpret_cast<gcpRetrosynthesisStep *> (Right));
reinterpret_cast<gcpRetrosynthesisStep *> (Left)->AddArrow (reinterpret_cast<gcpRetrosynthesisArrow *> (*j), reinterpret_cast<gcpRetrosynthesisStep *> (Right), true);
reinterpret_cast<gcpRetrosynthesisStep *> (Right)->AddArrow (reinterpret_cast<gcpRetrosynthesisArrow *> (*j), reinterpret_cast<gcpRetrosynthesisStep *> (Left), false);
}
// now, check if each step has at least one arrow, may be we should add missing arrows?
// also check that there are no cyclic relationships and only one start point
for (k = Objects.begin (); k != kend; k++) {
od = (*k).second;
if (!od.step->Validate ()) {
Left = (*k).first;
pData->UnselectAll ();
pData->SetSelected (Left);
throw invalid_argument (_("Isolated molecule!\n Please add missing arrows."));
}
}
switch (Validate (false)) {
case 0:
break;
case 1:
throw invalid_argument (_("No target molecule!"));
case 2:
throw invalid_argument (_("Multiple target molecules or missing arrows."));
case 3:
throw invalid_argument (_("Sorry, cyclic retrosynthesis paths are not supported."));
}
Align ();
return true;
}
static int BuildConnectivity ( set<Object *> &Objects, gcpRetrosynthesisStep* Step)
{
map<gcpRetrosynthesisStep *, gcpRetrosynthesisArrow *> *Arrows = Step->GetArrows ();
map<gcpRetrosynthesisStep *, gcpRetrosynthesisArrow *>::iterator i, end = Arrows->end ();
for (i = Arrows->begin (); i != end; i++) {
Objects.insert ((*i).second);
if (Objects.find ((*i).first) == Objects.end ()) {
Objects.insert ((*i).first);
if (BuildConnectivity (Objects, (*i).first))
return 1;
} else
return 1;
}
return 0;
}
int gcpRetrosynthesis::Validate (bool split)
{
map<string, Object*>::iterator i;
Object *pObj = GetFirstChild (i);
while (pObj && (pObj->GetType () != RetrosynthesisStepType ||
(reinterpret_cast<gcpRetrosynthesisStep *> (pObj))->GetArrow ()))
pObj = GetNextChild (i);
if (pObj == NULL)
return 1;
Target = reinterpret_cast<gcpRetrosynthesisStep *> (pObj);
set<Object *> Objects;
Objects.insert (pObj);
if (BuildConnectivity (Objects, Target))
return 3;
while (Objects.size () < GetChildrenNumber ()) {
if (!split)
return 2;
pObj = GetFirstChild (i);
while (pObj && (pObj->GetType () != RetrosynthesisStepType ||
(reinterpret_cast<gcpRetrosynthesisStep *> (pObj))->GetArrow () ||
pObj == Target))
pObj = GetNextChild (i);
if (reinterpret_cast<gcpRetrosynthesisStep *> (pObj)->Validate ()) {
gcpRetrosynthesis *rs = new gcpRetrosynthesis (GetParent (),
reinterpret_cast<gcpRetrosynthesisStep *> (pObj));
gcpDocument *pDoc = reinterpret_cast<gcpDocument*> (GetDocument ());
gcpOperation *pOp = pDoc->GetCurrentOperation ();
pOp->AddObject (rs, 1);
} else
delete pObj;
}
return 0;
}
typedef struct
{
double x, y;
ArtDRect r;
} StepData;
static void AlignStep (map<Object*, StepData> &data, gcpRetrosynthesisStep *step, gcpView *pView, gcpWidgetData *pData)
{
double x0, y0, x1, y1, x, y, l, dx, dy;
bool horiz;
StepData sd = data[step], sd1;
gcpTheme *pTheme = pView->GetDoc ()->GetTheme ();
map<gcpRetrosynthesisStep *, gcpRetrosynthesisArrow *> *Arrows = step->GetArrows ();
map<gcpRetrosynthesisStep *, gcpRetrosynthesisArrow *>::iterator i, end = Arrows->end ();
for (i = Arrows->begin (); i != end; i++) {
(*i).second->GetCoords (&x0, &y0, &x1, &y1);
x = x1 - x0;
y = y1 - y0;
l = sqrt (x * x + y * y);
x /= l;
y /= l;
if ((fabs (x) > 1e-5) && (fabs (y) > 1e-5))
horiz = (fabs (x) > fabs (y));
else if (fabs (x) > 1e-5)
horiz = true;
else
horiz = false;
if (horiz) {
if (x > 0)
dx = sd.r.x1 - sd.x + pTheme->GetArrowPadding ();
else
dx = sd.r.x0 - sd.x - pTheme->GetArrowPadding ();
dy = dx * y / x;
} else {
if (y > 0)
dy = sd.r.y1 - sd.y + pTheme->GetArrowPadding ();
else
dy = sd.r.y0 - sd.y - pTheme->GetArrowPadding ();
dx = dy * x / y;
}
x1 += x0 = (dx + sd.x) / pTheme->GetZoomFactor () - x0;
y1 += y0 = (dy + sd.y) / pTheme->GetZoomFactor () - y0;
(*i).second->Move (x0, y0);
pView->Update ((*i).second);
sd1 = data[(*i).first];
if (horiz) {
if (x > 0)
dx = sd1.x - sd1.r.x0 + pTheme->GetArrowPadding ();
else
dx = sd1.x - sd1.r.x1 +- pTheme->GetArrowPadding ();
dy = dx * y / x;
} else {
if (y > 0)
dy = sd1.y - sd1.r.y0 + pTheme->GetArrowPadding ();
else
dy = sd1.y - sd1.r.y1 - pTheme->GetArrowPadding ();
dx = dy * x / y;
}
dx = x1 * pTheme->GetZoomFactor () - (sd1.x - dx);
dy = y1 * pTheme->GetZoomFactor () - (sd1.y - dy);
(*i).first->Move (dx / pTheme->GetZoomFactor (), dy / pTheme->GetZoomFactor ());
pView->Update ((*i).first);
sd1.r.x0 += dx;
sd1.r.x1 += dx;
sd1.x += dx;
sd1.r.y0 += dy;
sd1.r.y1 += dy;
sd1.y += dy;
data[(*i).first] = sd1;
AlignStep (data, (*i).first, pView, pData);
}
}
void gcpRetrosynthesis::Align ()
{
gcpDocument *pDoc = reinterpret_cast<gcpDocument *> (GetDocument ());
gcpTheme *pTheme = pDoc->GetTheme ();
gcpView *pView = pDoc->GetView ();
gcpWidgetData *pData = reinterpret_cast<gcpWidgetData *> (g_object_get_data (G_OBJECT (pDoc->GetWidget ()), "data"));
/* Update the canvas if necessary */
GnomeCanvas* w = GNOME_CANVAS (((gcpDocument*) GetDocument ())->GetWidget ());
while (w->idle_id)
gtk_main_iteration();
gnome_canvas_update_now (w);
map<string, Object*>::iterator i;
Object *pObj = GetFirstChild (i);
map<Object*, StepData> positions;
StepData sd;
while (pObj) {
if (pObj->GetType () == RetrosynthesisStepType) {
pData->GetObjectBounds (pObj, &sd.r);
sd.x = (sd.r.x0 + sd.r.x1) / 2.;
sd.y = pObj->GetYAlign () * pTheme->GetZoomFactor ();
positions[pObj] = sd;
}
pObj = GetNextChild (i);
}
AlignStep (positions, Target, pView, pData);
}
double gcpRetrosynthesis::GetYAlign ()
{
return (Target)? Target->GetYAlign (): 0.;
}
static void do_destroy_retrosynthesis (void *data)
{
gcpRetrosynthesis *rs = reinterpret_cast<gcpRetrosynthesis *> (data);
gcpDocument *pDoc = reinterpret_cast<gcpDocument *> (rs->GetDocument ());
gcpWidgetData *pData = reinterpret_cast<gcpWidgetData *> (g_object_get_data (G_OBJECT (pDoc->GetWidget ()), "data"));
pData->Unselect (rs);
gcpOperation *pOp = pDoc->GetNewOperation (GCP_MODIFY_OPERATION);
pOp->AddObject (rs, 0);
delete rs;
pDoc->FinishOperation ();
}
bool gcpRetrosynthesis::BuildContextualMenu (GtkUIManager *UIManager, Object *object, double x, double y)
{
GtkActionGroup *group = gtk_action_group_new ("retrosynthesis");
GtkAction *action = gtk_action_new ("destroy-rs", _("Destroy the retrosynthesis path"), NULL, NULL);
gtk_action_group_add_action (group, action);
g_object_unref (action);
gtk_ui_manager_insert_action_group (UIManager, group, 0);
g_object_unref (group);
char buf[] = "<ui><popup><menuitem action='destroy-rs'/></popup></ui>";
gtk_ui_manager_add_ui_from_string (UIManager, buf, -1, NULL);
GtkWidget *w = gtk_ui_manager_get_widget (UIManager, "/popup/destroy-rs");
g_signal_connect_swapped (w, "activate", G_CALLBACK (do_destroy_retrosynthesis), this);
GetParent ()->BuildContextualMenu (UIManager, object, x, y);
return true;
}
bool gcpRetrosynthesis::OnSignal (SignalId Signal, Object *Child)
{
Validate (true);
Align ();
if (GetChildrenNumber () == 1)
delete this;
return true;
}
gcpRetrosynthesis::gcpRetrosynthesis (Object* parent, gcpRetrosynthesisStep *step): Object (RetrosynthesisType)
{
SetId ((char*) "rsy1");
SetParent (parent);
Target = step;
AddChild (Target);
set<Object *> Objects;
BuildConnectivity (Objects, Target);
set<Object *>::iterator i, end = Objects.end ();
for (i = Objects.begin (); i != end; i++)
AddChild (*i);
Align ();
}
void gcpRetrosynthesis::Transform2D (Matrix2D& m, double x, double y)
{
}
syntax highlighted by Code2HTML, v. 0.9.1