// -*- C++ -*-
/*
* GChemPaint library
* atom.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 "atom.h"
#include "electron.h"
#include "bond.h"
#include "molecule.h"
#include "settings.h"
#include "theme.h"
#include "view.h"
#include "document.h"
#include "Hposdlg.h"
#include "libgcpcanvas/gcp-canvas-group.h"
#include "libgcpcanvas/gcp-canvas-rect-ellipse.h"
#include "libgcpcanvas/gcp-canvas-bpath.h"
#include "libgcpcanvas/gcp-canvas-pango.h"
#include <gcu/element.h>
#include <openbabel/mol.h>
#include <glib/gi18n-lib.h>
#include <cmath>
#include <cstring>
using namespace gcu;
#define ATOM_EPSILON 0.1
gcpAtom::gcpAtom(): Atom(),
DialogOwner (),
m_ShowSymbol (false),
m_HPosStyle (AUTO_HPOS)
{
m_Valence = -1; //unspecified
m_nlp = 0;
m_nH = 0;
m_HPos = GetBestSide();
m_ChargeAuto = false;
m_ascent = 0;
m_CHeight = 0.;
m_Changed = 0;
m_AvailPosCached = false;
m_OccupiedPos = 0;
m_ChargePos = 0xff;
m_ChargeAngle = 0.;
m_ChargeDist = 0.;
m_ChargeAutoPos = true;
m_Layout = m_ChargeLayout = NULL;
m_DrawCircle = false;
}
gcpAtom::~gcpAtom()
{
gcpDocument *pDoc = (gcpDocument*) GetDocument ();
if (!pDoc)
return;
gcpView *pView = pDoc->GetView ();
map<string, Object*>::iterator i;
Object* electron = GetFirstChild (i);
while (electron) {
pView->Remove (electron);
electron->SetParent (NULL); // avoids a call to RemoveElectron()
delete electron;
electron = GetFirstChild (i);
}
if (m_Layout)
g_object_unref (G_OBJECT (m_Layout));
if (m_ChargeLayout)
g_object_unref (G_OBJECT (m_ChargeLayout));
}
gcpAtom::gcpAtom(int Z, double x, double y, double z): Atom(Z, x, y, z),
DialogOwner (),
m_ShowSymbol (false),
m_HPosStyle (AUTO_HPOS)
{
m_ChargeAuto = false;
m_HPos = GetBestSide();
m_nlp = 0;
SetZ(Z);
m_ascent = 0;
m_CHeight = 0.;
m_Changed = 0;
m_AvailPosCached = false;
m_OccupiedPos = 0;
m_ChargePos = 0xff;
m_ChargeAngle = 0.;
m_ChargeDist = 0.;
m_ChargeAutoPos = true;
m_Layout = m_ChargeLayout = NULL;
m_DrawCircle = false;
}
gcpAtom::gcpAtom(OBAtom* atom): Atom(),
DialogOwner (),
m_ShowSymbol (false),
m_HPosStyle (AUTO_HPOS)
{
m_x = atom->GetX();
m_y = - atom->GetY();
m_z = atom->GetZ();
m_nlp = 0;
SetZ(atom->GetAtomicNum());
gchar* Id = g_strdup_printf("a%d", atom->GetIdx());
SetId(Id);
g_free(Id);
m_HPos = true;
m_ascent = 0;
m_CHeight = 0.;
m_Changed = 0;
m_AvailPosCached = false;
m_OccupiedPos = 0;
m_ChargePos = 0xff;
m_ChargeAngle = 0.;
m_ChargeDist = 0.;
m_ChargeAutoPos = true;
m_Layout = m_ChargeLayout = NULL;
m_DrawCircle = false;
m_Charge = atom->GetFormalCharge ();
}
void gcpAtom::SetZ(int Z)
{
Atom::SetZ(Z);
m_Element = Element::GetElement (m_Z);
if ((m_Valence = m_Element->GetDefaultValence ()))
m_HPos = (m_HPosStyle == AUTO_HPOS)? GetBestSide(): m_HPosStyle;
else
m_nH = 0;
int max = m_Element->GetMaxValenceElectrons ();
int diff = m_Element->GetTotalValenceElectrons () - m_Element->GetValenceElectrons ();
switch (max) {
case 2:
m_ValenceOrbitals = 1;
break;
case 8:
m_ValenceOrbitals = 4;
break;
case 18:
if (!diff)
m_ValenceOrbitals = 6;
else
m_ValenceOrbitals = 4;
break;
case 32:
if (!diff)
m_ValenceOrbitals = 8;
else if (diff == 14)
m_ValenceOrbitals = 6;
else
m_ValenceOrbitals = 4;
break;
default:
m_ValenceOrbitals = 0; //should not occur
}
Update();
EmitSignal (OnChangedSignal);
}
int gcpAtom::GetTotalBondsNumber()
{
std::map<Atom*, Bond*>::iterator i;
int n = 0;
for (i = m_Bonds.begin(); i != m_Bonds.end(); i++)
n += (*i).second->GetOrder();
return n;
}
void gcpAtom::AddBond(Bond* pBond)
{
Atom::AddBond(pBond);
Update();
}
void gcpAtom::RemoveBond(Bond* pBond)
{
Atom::RemoveBond(pBond);
Update();
}
bool gcpAtom::GetBestSide()
{
if (m_Bonds.size() == 0) return Element::BestSide(m_Z);
std::map<Atom*, Bond*>::iterator i;
double sum = 0.0;
for (i = m_Bonds.begin(); i != m_Bonds.end(); i++)
sum -= cos(((gcpBond*)(*i).second)->GetAngle2DRad(this));
if (fabs(sum) > 0.1) return (sum >= 0.0);
else return Element::BestSide(m_Z);
}
void gcpAtom::Update ()
{
if (m_ChargeAuto) {
m_Charge = 0;
m_ChargeAuto = false;
}
if (m_ChargeAutoPos && m_ChargePos != 0xff) {
NotifyPositionOccupation (m_ChargePos, false);
m_ChargePos = 0xff;
}
int nb, nexplp = 0, nexplu = 0; //nexplp is the number of explicit lone pairs
//nexplu is the number of explicit unpaired electrons
map<string, Object*>::iterator i;
gcpElectron* electron = (gcpElectron*) GetFirstChild (i);
while (electron) {
if (electron->IsPair ())
nexplp++;
else
nexplu++;
electron = (gcpElectron*) GetNextChild (i);
}
int nbonds = GetTotalBondsNumber ();
if (m_Valence > 0) {
m_nlp = (m_Element->GetValenceElectrons () - m_Valence) / 2;
if ((m_Charge > 0) && (m_nlp > 0)) m_nlp -= (m_Charge + 1) / 2;
else if (m_Charge < 0)
m_nlp -= m_Charge;
if (m_nlp < nexplp) // Can this occur ?
m_nlp = nexplp;
else if (m_nlp > m_ValenceOrbitals - nbonds - nexplu)
m_nlp = m_ValenceOrbitals - nbonds - nexplu;
if (m_nlp < 0)
m_nlp = 0;
nb = m_Element->GetValenceElectrons () - 2 * m_nlp - m_Charge;
if (nb + m_nlp > 4) nb -= 2; //octet rule
m_nH = nb - nbonds - nexplu;
if (!m_Charge && m_nH == -1 && m_nlp > 0)
{
m_Charge = m_Element->GetValenceElectrons () - nbonds
- m_nlp * 2 - nexplu;
m_ChargeAuto = true;
m_nH = 0;
}
if (m_nH < 0) { // extended octet or missing core electrons
m_nH = 0;
if (m_nlp || nexplu || nbonds) {
m_Charge = m_Element->GetValenceElectrons () - 2 * m_nlp - nexplu - nbonds;
m_ChargeAuto = true;
}
}
m_HPos = (m_HPosStyle == AUTO_HPOS)? GetBestSide(): m_HPosStyle;
} else {
m_nH = 0;
if (m_ChargeAuto || !m_Charge) {
m_Charge = m_Element->GetValenceElectrons () - 2 * nexplp - nexplu - nbonds;
if (m_Charge > 0)
m_Charge = 0;
m_ChargeAuto = true;
}
}
gcpDocument *pDoc = (gcpDocument *) GetDocument ();
if (pDoc)
m_Changed = pDoc->GetView ()->GetNbWidgets ();
m_AvailPosCached = false;
if (nbonds && GetZ () == 6) {
// update large bonds ends
gcpBond *bond;
gcpBondType type;
bool DrawCircle;
map<Atom*, Bond*>::iterator i = m_Bonds.begin(), iend = m_Bonds.end ();
int nb = 0;
while (i != iend)
{
bond = dynamic_cast<gcpBond*> ((gcpBond*)(*i).second);
type = bond->GetType ();
if (type == ForeBondType || (type == UpBondType && bond->GetAtom (1) == this))
nb++;
i++;
}
DrawCircle = nb > 1;
if (!DrawCircle && GetBondsNumber () == 2) {
i = m_Bonds.begin();
double angle = static_cast<gcpBond*> ((*i).second)->GetAngle2D (this);
i++;
angle -= static_cast<gcpBond*> ((*i).second)->GetAngle2D (this);
while (angle < 0)
angle += 360.;
while (angle > 360.)
angle -= 360;
if (fabs (angle - 180.) < 1)
DrawCircle = true;
}
if (DrawCircle != m_DrawCircle) {
m_DrawCircle = DrawCircle;
m_Changed = true;
}
}
}
bool gcpAtom::IsInCycle(gcpCycle* pCycle)
{
map<Atom*, Bond*>::iterator i;
for (i = m_Bonds.begin(); i != m_Bonds.end(); i++)
if (((gcpBond*)(*i).second)->IsInCycle(pCycle)) return true;
return false;
}
void gcpAtom::Add (GtkWidget* w)
{
if (!w)
return;
if (m_Changed > 0)
m_Changed--;
gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (w), "data");
gcpView* pView = pData->View;
gcpTheme *pTheme = pView->GetDoc ()->GetTheme ();
if (m_Layout == NULL) {
PangoContext* pc = pView->GetPangoContext();
m_Layout = pango_layout_new (pc);
}
if (m_FontName != pView->GetFontName ()) {
pango_layout_set_font_description (m_Layout, pView->GetPangoFontDesc ());
pango_layout_set_text (m_Layout, "l", 1);
PangoLayoutIter* iter = pango_layout_get_iter (m_Layout);
m_ascent = pango_layout_iter_get_baseline (iter) / PANGO_SCALE;
pango_layout_iter_free (iter);
m_FontName = pView->GetFontName ();
m_CHeight = 0.;
}
PangoRectangle rect;
if (m_CHeight == 0.) {
pango_layout_set_text (m_Layout, "C", 1);
pango_layout_get_extents (m_Layout, &rect, NULL);
m_CHeight = double (rect.height / PANGO_SCALE) / 2.0;
}
double x, y, xc = 0., yc;
m_width = m_height = 2.0 * pTheme->GetPadding ();
GetCoords (&x, &y);
x *= pTheme->GetZoomFactor ();
y *= pTheme->GetZoomFactor ();
GnomeCanvasItem *item;
GnomeCanvasGroup *group, *chgp;
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);
if ((GetZ () != 6) || (GetBondsNumber () == 0)) {
int sw, sp;
const gchar* symbol = GetSymbol (), *text;
sw = strlen (symbol);
pango_layout_set_text (m_Layout, symbol, sw);
pango_layout_get_extents (m_Layout, &rect, NULL);
m_width += rect.width / PANGO_SCALE;
int n = GetAttachedHydrogens ();
if (n > 0) {
if (n > 1) {
gchar const *nb = g_strdup_printf ("%d", n);
int np, nw = strlen (nb);
if (m_HPos) {
text = g_strconcat (symbol, "H", nb, NULL);
np = sw + 1;
sp = 0;
} else {
text = g_strconcat ("H", nb, symbol, NULL);
np = 1;
sp = np + nw;
}
pango_layout_set_text (m_Layout, text, -1);
PangoAttrList *pal = pango_attr_list_new ();
PangoAttribute *attr = pango_attr_font_desc_new (pView->GetPangoSmallFontDesc());
attr->start_index = np;
attr->end_index = np + nw;
pango_attr_list_insert (pal, attr);
attr = pango_attr_rise_new (-2 * PANGO_SCALE);
attr->start_index = np;
attr->end_index = np + nw;
pango_attr_list_insert (pal, attr);
pango_layout_set_attributes (m_Layout, pal);
pango_attr_list_unref (pal);
} else {
if (m_HPos) {
text = g_strconcat (symbol, "H", NULL);
sp = 0;
} else {
text = g_strconcat ("H", symbol, NULL);
sp = 1;
}
pango_layout_set_text (m_Layout, text, -1);
}
} else {
text = g_strdup (symbol);
sp = 0;
pango_layout_set_text (m_Layout, text, -1);
}
pango_layout_get_extents (m_Layout, NULL, &rect);
m_length = double (rect.width / PANGO_SCALE);
m_text_height = m_height = rect.height / PANGO_SCALE;
pango_layout_index_to_pos (m_Layout, sp, &rect);
int st = rect.x / PANGO_SCALE;
pango_layout_index_to_pos (m_Layout, sp + sw, &rect);
m_lbearing = (st + rect.x / PANGO_SCALE) / 2.;
item = gnome_canvas_item_new (
group,
gnome_canvas_rect_ext_get_type (),
"x1", x - m_lbearing - pTheme->GetPadding (),
"y1", y - m_ascent + m_CHeight - pTheme->GetPadding (),
"x2", x - m_lbearing + m_length + pTheme->GetPadding (),
"y2", y - m_ascent + m_CHeight + m_height + pTheme->GetPadding (),
"fill_color", (pData->IsSelected (this))? SelectColor: "white",
NULL);
g_object_set_data (G_OBJECT (group), "rect", item);
g_signal_connect (G_OBJECT (item), "event", G_CALLBACK (on_event), w);
g_object_set_data (G_OBJECT (item), "object", this);
item = gnome_canvas_item_new (
group,
gnome_canvas_pango_get_type (),
"x", x - m_lbearing,
"y", y - m_ascent + m_CHeight,
"layout", m_Layout,
NULL);
g_object_set_data (G_OBJECT (group), "symbol", item);
g_object_set_data (G_OBJECT (item), "object", this);
g_signal_connect (G_OBJECT (item), "event", G_CALLBACK (on_event), w);
} else {
item = gnome_canvas_item_new(
group,
gnome_canvas_rect_ext_get_type(),
"x1", x - 3,
"y1", y - 3,
"x2", x + 3,
"y2", y + 3,
"fill_color", (pData->IsSelected(this))? SelectColor: "white",
NULL);
g_object_set_data(G_OBJECT(group), "rect", item);
gnome_canvas_request_redraw((GnomeCanvas*)w, (int)x-3, (int)y-3, (int)x+3, (int)y+3);
gnome_canvas_item_lower_to_bottom(GNOME_CANVAS_ITEM(group));
gnome_canvas_item_raise(GNOME_CANVAS_ITEM(group), 1);
g_signal_connect(G_OBJECT(item), "event", G_CALLBACK(on_event), w);
g_object_set_data(G_OBJECT(item), "object", this);
if (m_DrawCircle) {
double dx = pTheme->GetStereoBondWidth () / 2.;
item = gnome_canvas_item_new(
group,
gnome_canvas_ellipse_ext_get_type(),
"x1", x - dx,
"y1", y - dx,
"x2", x + dx,
"y2", y + dx,
"fill_color", (pData->IsSelected(this))? SelectColor: Color,
NULL);
g_object_set_data(G_OBJECT(group), "bullet", item);
g_signal_connect(G_OBJECT(item), "event", G_CALLBACK(on_event), w);
g_object_set_data(G_OBJECT(item), "object", this);
}
}
pData->Items[this] = group;
m_width /= pTheme->GetZoomFactor ();
m_height /= pTheme->GetZoomFactor ();
/* add charge */
int charge = GetCharge ();
if (charge) {
int align = GetChargePosition (m_ChargePos, m_ChargeAngle * 180 / M_PI, x, y);
if (m_ChargeDist != 0.) {
align = 0;
x = m_x + m_ChargeDist * cos (m_ChargeAngle);
y = m_y - m_ChargeDist * sin (m_ChargeAngle);
}
x *= pTheme->GetZoomFactor ();
y *= pTheme->GetZoomFactor ();
char *fig = NULL;
if (abs (charge) > 1) {
fig = g_strdup_printf ("%d", abs (charge));
PangoRectangle rect;
if (!m_ChargeLayout) {
PangoContext* pc = pData->View->GetPangoContext();
m_ChargeLayout = pango_layout_new (pc);
pango_layout_set_font_description (m_ChargeLayout, pData->View->GetPangoSmallFontDesc ());
}
pango_layout_set_text (m_ChargeLayout, fig, -1);
pango_layout_get_extents (m_ChargeLayout, NULL, &rect);
m_ChargeWidth = rect.width / PANGO_SCALE;
m_ChargeTWidth = m_ChargeWidth + 1. + pTheme->GetChargeSignSize ();
} else {
m_ChargeWidth = 0.;
m_ChargeTWidth = pTheme->GetChargeSignSize ();
}
switch (align) {
case -2:
xc = x + m_ChargeTWidth / 2. - pTheme->GetChargeSignSize ();
y += pTheme->GetChargeSignSize () / 2.;
break;
case -1:
xc = x - pTheme->GetChargeSignSize () - pTheme->GetPadding ();
break;
case 0:
case -3:
xc = x + m_ChargeTWidth / 2. - pTheme->GetChargeSignSize ();
break;
case 1:
xc = x + m_ChargeWidth + pTheme->GetPadding ();
break;
case 2:
xc = x + m_ChargeTWidth / 2. - pTheme->GetChargeSignSize ();
y -= pTheme->GetChargeSignSize () / 2.;
break;
}
x = xc - 1.;
yc = y - pTheme->GetChargeSignSize () / 2.;
chgp = (GnomeCanvasGroup*) gnome_canvas_item_new (
group,
gnome_canvas_group_ext_get_type(),
NULL);
g_object_set_data (G_OBJECT (group), "charge", chgp);
if (fig) {
item = gnome_canvas_item_new(
chgp,
gnome_canvas_pango_get_type(),
"layout", m_ChargeLayout,
"fill_color", (pData->IsSelected(this))? SelectColor: Color,
"anchor", GTK_ANCHOR_EAST,
"x", x,
"y", y,
NULL);
g_object_set_data (G_OBJECT (group), "figure", item);
g_free (fig);
}
item = gnome_canvas_item_new (
chgp,
gnome_canvas_ellipse_ext_get_type (),
"x1", xc,
"y1", yc,
"x2", xc + pTheme->GetChargeSignSize (),
"y2", yc + pTheme->GetChargeSignSize (),
"outline_color", (pData->IsSelected(this))? SelectColor: Color,
"width_units", 0.5,
NULL
);
g_object_set_data (G_OBJECT (group), "circle", item);
ArtBpath *path = art_new (ArtBpath, 5);
path[0].code = ART_MOVETO_OPEN;
path[0].x3 = xc + 1.;
path[1].code = ART_LINETO;
path[1].x3 = xc + pTheme->GetChargeSignSize () - 1.;
path[0].y3 = path[1].y3 = yc + pTheme->GetChargeSignSize () / 2.;
if (charge > 0) {
path[2].code = ART_MOVETO_OPEN;
path[2].y3 = yc + 1.;
path[3].code = ART_LINETO;
path[3].y3 = yc + pTheme->GetChargeSignSize () - 1.;
path[2].x3 = path[3].x3 = xc + pTheme->GetChargeSignSize () / 2.;
path[4].code = ART_END;
} else
path[2].code = ART_END;
GnomeCanvasPathDef *cpd = gnome_canvas_path_def_new_from_bpath (path);
item = gnome_canvas_item_new (
chgp,
gnome_canvas_bpath_ext_get_type (),
"bpath", cpd,
"outline_color", (pData->IsSelected(this))? SelectColor: Color,
"width_units", 1.,
NULL
);
gnome_canvas_path_def_unref (cpd);
g_object_set_data (G_OBJECT (group), "sign", item);
}
map<string, Object*>::iterator i;
Object* electron = GetFirstChild (i);
while (electron){
electron->Add (w);
electron = GetNextChild (i);
}
}
void gcpAtom::Update(GtkWidget* w)
{
if (!w) return;
gcpWidgetData* pData = (gcpWidgetData*)g_object_get_data(G_OBJECT(w), "data");
if (pData->Items[this] != NULL)
return;
gcpTheme *pTheme = pData->View->GetDoc ()->GetTheme ();
double x, y, xc = 0., yc;
GetCoords(&x, &y);
x *= pTheme->GetZoomFactor ();
y *= pTheme->GetZoomFactor ();
GnomeCanvasGroup *group = pData->Items[this];
if (m_FontName != pData->View->GetFontName ()) {
gcpView *pView = pData->View;
PangoContext* pc = pView->GetPangoContext();
PangoLayout *Layout = pango_layout_new (pc);
pango_layout_set_font_description (Layout, pView->GetPangoFontDesc ());
pango_layout_set_font_description (m_Layout, pView->GetPangoFontDesc ());
pango_layout_set_text (Layout, "l", 1);
PangoLayoutIter* iter = pango_layout_get_iter (Layout);
m_ascent = pango_layout_iter_get_baseline (iter) / PANGO_SCALE;
pango_layout_iter_free (iter);
m_FontName = pView->GetFontName ();
pango_layout_set_text (Layout, "C", 1);
PangoRectangle rect;
pango_layout_get_extents (Layout, &rect, NULL);
m_CHeight = double (rect.height / PANGO_SCALE) / 2.0;
g_object_unref (G_OBJECT (Layout));
}
if (m_Changed) {
BuildItems (pData);
} else {
if ((GetZ() != 6) || (GetBondsNumber() == 0) || m_ShowSymbol) {
g_object_set(G_OBJECT(g_object_get_data(G_OBJECT(group), "symbol")),
"x", x - m_lbearing,
"y", y - m_ascent + m_CHeight,
NULL);
g_object_set(G_OBJECT(g_object_get_data(G_OBJECT(group), "rect")),
"x1", x - m_lbearing - pTheme->GetPadding (),
"y1", y - m_ascent + m_CHeight - pTheme->GetPadding (),
"x2", x - m_lbearing + m_length + pTheme->GetPadding (),
"y2", y - m_ascent + m_CHeight + m_text_height + pTheme->GetPadding (),
NULL);
} else {
g_object_set(G_OBJECT(g_object_get_data(G_OBJECT(group), "rect")),
"x1", x - 3,
"y1", y - 3,
"x2", x + 3,
"y2", y + 3,
NULL);
if (m_DrawCircle) {
double dx = pTheme->GetStereoBondWidth () / 2.;
g_object_set(G_OBJECT(g_object_get_data(G_OBJECT(group), "bullet")),
"x1", x - dx,
"y1", y - dx,
"x2", x + dx,
"y2", y + dx,
NULL);
}
}
}
void* item = g_object_get_data (G_OBJECT (group), "charge");
int charge = GetCharge ();
if (charge) {
if (item) {
int align = GetChargePosition(m_ChargePos, m_ChargeAngle * 180 / M_PI, x, y);
if (m_ChargeDist != 0.) {
align = 0;
x = m_x + m_ChargeDist * cos (m_ChargeAngle);
y = m_y - m_ChargeDist * sin (m_ChargeAngle);
}
x *= pTheme->GetZoomFactor ();
y *= pTheme->GetZoomFactor ();
GnomeCanvasItem *figure = (GnomeCanvasItem*) g_object_get_data (G_OBJECT (group), "figure");
char *fig = NULL;
if (abs (charge) > 1) {
fig = g_strdup_printf ("%d", abs (charge));
PangoRectangle rect;
if (!m_ChargeLayout) {
PangoContext* pc = pData->View->GetPangoContext();
m_ChargeLayout = pango_layout_new (pc);
pango_layout_set_font_description (m_ChargeLayout, pData->View->GetPangoSmallFontDesc ());
}
pango_layout_set_text (m_ChargeLayout, fig, -1);
pango_layout_get_extents (m_ChargeLayout, NULL, &rect);
m_ChargeWidth = rect.width / PANGO_SCALE;
} else
m_ChargeWidth = 0.;
m_ChargeTWidth = m_ChargeWidth + 1. + pTheme->GetChargeSignSize ();
if (figure == NULL && fig != NULL) {
figure = gnome_canvas_item_new(
GNOME_CANVAS_GROUP (item),
gnome_canvas_pango_get_type(),
"anchor", GTK_ANCHOR_EAST,
NULL);
g_object_set_data (G_OBJECT (group), "figure", figure);
} else if (figure != NULL && fig == NULL) {
gtk_object_destroy (GTK_OBJECT (figure));
g_object_set_data ((GObject*)group, "figure", NULL);
}
switch (align) {
case -2:
xc = x + m_ChargeTWidth / 2. - pTheme->GetChargeSignSize ();
y += pTheme->GetChargeSignSize () / 2.;
break;
case -1:
xc = x - pTheme->GetChargeSignSize () - pTheme->GetPadding ();
break;
case 0:
case -3:
xc = x + m_ChargeTWidth / 2. - pTheme->GetChargeSignSize ();
break;
case 1:
xc = x + m_ChargeWidth + pTheme->GetPadding ();
break;
case 2:
xc = x + m_ChargeTWidth / 2. - pTheme->GetChargeSignSize ();
y -= pTheme->GetChargeSignSize () / 2.;
break;
}
x = xc -1.;
yc = y - pTheme->GetChargeSignSize () / 2.;
if (fig) {
g_object_set (G_OBJECT (figure),
"layout", m_ChargeLayout,
"x", x,
"y", y,
NULL);
g_free (fig);
}
item = g_object_get_data (G_OBJECT (group), "circle");
g_object_set (G_OBJECT (item),
"x1", xc,
"y1", yc,
"x2", xc + pTheme->GetChargeSignSize (),
"y2", yc + pTheme->GetChargeSignSize (),
NULL);
item = g_object_get_data (G_OBJECT (group), "sign");
ArtBpath *path = art_new (ArtBpath, 5);
path[0].code = ART_MOVETO_OPEN;
path[0].x3 = xc + 1.;
path[1].code = ART_LINETO;
path[1].x3 = xc + pTheme->GetChargeSignSize () - 1.;
path[0].y3 = path[1].y3 = yc + pTheme->GetChargeSignSize () / 2.;
if (charge > 0) {
path[2].code = ART_MOVETO_OPEN;
path[2].y3 = yc + 1.;
path[3].code = ART_LINETO;
path[3].y3 = yc + pTheme->GetChargeSignSize () - 1.;
path[2].x3 = path[3].x3 = xc + pTheme->GetChargeSignSize () / 2.;
path[4].code = ART_END;
} else
path[2].code = ART_END;
GnomeCanvasPathDef *cpd = gnome_canvas_path_def_new_from_bpath (path);
g_object_set (G_OBJECT (item),
"bpath", cpd,
NULL
);
gnome_canvas_path_def_unref (cpd);
} else {
GnomeCanvasGroup *chgp;
int align = GetChargePosition(m_ChargePos, m_ChargeAngle * 180 / M_PI, x, y);
if (m_ChargeDist != 0.) {
align = 0;
x = (m_x + m_ChargeDist * cos (m_ChargeAngle));
y = (m_y - m_ChargeDist * sin (m_ChargeAngle));
}
x *= pTheme->GetZoomFactor ();
y *= pTheme->GetZoomFactor ();
char *fig = NULL;
if (abs (charge) > 1) {
fig = g_strdup_printf ("%d", abs (charge));
if (!m_ChargeLayout) {
PangoContext* pc = pData->View->GetPangoContext();
m_ChargeLayout = pango_layout_new (pc);
pango_layout_set_font_description (m_ChargeLayout, pData->View->GetPangoSmallFontDesc ());
}
pango_layout_set_text (m_ChargeLayout, fig, -1);
PangoRectangle rect;
pango_layout_get_extents (m_ChargeLayout, NULL, &rect);
m_ChargeWidth = rect.width / PANGO_SCALE;
m_ChargeTWidth = m_ChargeWidth + pTheme->GetPadding () + pTheme->GetChargeSignSize ();
} else {
m_ChargeWidth = 0.;
m_ChargeTWidth = pTheme->GetChargeSignSize ();
}
switch (align) {
case -2:
xc = x + m_ChargeTWidth / 2. - pTheme->GetChargeSignSize ();
y += pTheme->GetChargeSignSize () / 2.;
break;
case -1:
xc = x - pTheme->GetChargeSignSize () - pTheme->GetPadding ();
break;
case 0:
case -3:
xc = x + m_ChargeTWidth / 2. - pTheme->GetChargeSignSize ();
break;
case 1:
xc = x + m_ChargeWidth + pTheme->GetPadding ();
break;
case 2:
xc = x + m_ChargeTWidth / 2. - pTheme->GetChargeSignSize ();
y -= pTheme->GetChargeSignSize () / 2.;
break;
}
x = xc - 1.;
yc = y - pTheme->GetChargeSignSize () / 2.;
chgp = (GnomeCanvasGroup*) gnome_canvas_item_new (
group,
gnome_canvas_group_ext_get_type(),
NULL);
g_object_set_data (G_OBJECT (group), "charge", chgp);
if (fig) {
item = gnome_canvas_item_new(
chgp,
gnome_canvas_pango_get_type(),
"layout", m_ChargeLayout,
"fill_color", (pData->IsSelected(this))? SelectColor: Color,
"anchor", GTK_ANCHOR_EAST,
"x", x,
"y", y,
NULL);
g_object_set_data (G_OBJECT (group), "figure", item);
g_free (fig);
}
item = gnome_canvas_item_new (
chgp,
gnome_canvas_ellipse_ext_get_type (),
"x1", xc,
"y1", yc,
"x2", xc + pTheme->GetChargeSignSize (),
"y2", yc + pTheme->GetChargeSignSize (),
"outline_color", (pData->IsSelected(this))? SelectColor: Color,
"width_units", .5,
NULL
);
g_object_set_data (G_OBJECT (group), "circle", item);
ArtBpath *path = art_new (ArtBpath, 5);
path[0].code = ART_MOVETO_OPEN;
path[0].x3 = xc + 1.;
path[1].code = ART_LINETO;
path[1].x3 = xc + pTheme->GetChargeSignSize () - 1.;
path[0].y3 = path[1].y3 = yc + pTheme->GetChargeSignSize () / 2.;
if (charge > 0) {
path[2].code = ART_MOVETO_OPEN;
path[2].y3 = yc + 1.;
path[3].code = ART_LINETO;
path[3].y3 = yc + pTheme->GetChargeSignSize () - 1.;
path[2].x3 = path[3].x3 = xc + pTheme->GetChargeSignSize () / 2.;
path[4].code = ART_END;
} else
path[2].code = ART_END;
GnomeCanvasPathDef *cpd = gnome_canvas_path_def_new_from_bpath (path);
item = gnome_canvas_item_new (
chgp,
gnome_canvas_bpath_ext_get_type (),
"bpath", cpd,
"outline_color", (pData->IsSelected(this))? SelectColor: Color,
"width_units", 1.,
NULL
);
gnome_canvas_path_def_unref (cpd);
g_object_set_data (G_OBJECT (group), "sign", item);
}
} else {
if (item)
gtk_object_destroy (GTK_OBJECT (item));
g_object_set_data ((GObject*)group, "charge", NULL);
g_object_set_data ((GObject*)group, "figure", NULL);
g_object_set_data ((GObject*)group, "circle", NULL);
g_object_set_data ((GObject*)group, "sign", NULL);
}
map<string, Object*>::iterator i;
Object* electron = GetFirstChild (i);
while (electron){
electron->Update (w);
electron = GetNextChild (i);
}
}
void gcpAtom::UpdateAvailablePositions ()
{
list<double>::iterator n;
double angle, delta, dir;
m_AngleList.clear ();
if (((GetZ() != 6 || m_Bonds.size() == 0)) && m_nH)
{
if (m_HPos)
{
m_AvailPos = 0xB6;
m_AngleList.push_front(315.0);
m_AngleList.push_front(45.0);
}
else
{
m_AvailPos = 0x6D;
m_AngleList.push_front(225.0);
m_AngleList.push_front(135.0);
}
}
else m_AvailPos = 0xff;
m_AvailPos &= ~m_OccupiedPos;
map<Atom*, Bond*>::iterator i = m_Bonds.begin();
while (i != m_Bonds.end())
{
n = m_AngleList.begin();
angle = ((gcpBond*)(*i).second)->GetAngle2D(this);
if (angle < 0)
angle += 360.;
while ((n != m_AngleList.end()) && (*n < angle)) n++;
m_AngleList.insert(n, angle);
i++;
if ((m_AvailPos & CHARGE_SW) && (angle >= 180.0 - ATOM_EPSILON) &&
(angle <= 270.0 + ATOM_EPSILON))
m_AvailPos -= CHARGE_SW;
if ((m_AvailPos & CHARGE_SE) && (((angle >= 270.0 - ATOM_EPSILON) &&
(angle <= 360.0 + ATOM_EPSILON)) || (fabs(angle) < ATOM_EPSILON)))
m_AvailPos -= CHARGE_SE;
if ((m_AvailPos & CHARGE_S) && (angle >= 225.0 - ATOM_EPSILON) &&
(angle <= 315.0 + ATOM_EPSILON))
m_AvailPos -= CHARGE_S;
if ((m_AvailPos & CHARGE_NW) && (angle >= 90.0 - ATOM_EPSILON) &&
(angle <= 180.0 + ATOM_EPSILON))
m_AvailPos -= CHARGE_NW;
if ((m_AvailPos & CHARGE_NE) && (((angle >= 0.0 - ATOM_EPSILON) &&
(angle <= 90.0 + ATOM_EPSILON)) || (fabs(angle - 360.0) < ATOM_EPSILON)))
m_AvailPos -= CHARGE_NE;
if ((m_AvailPos & CHARGE_N) && (angle >= 45.0 - ATOM_EPSILON) &&
(angle <= 135.0 + ATOM_EPSILON))
m_AvailPos -= CHARGE_N;
if ((m_AvailPos & CHARGE_W) && ((angle <= 225.0 + ATOM_EPSILON) &&
(angle >= 135.0 - ATOM_EPSILON)))
m_AvailPos -= CHARGE_W;
if ((m_AvailPos & CHARGE_E) && ((angle >= 315.0 - ATOM_EPSILON) ||
(angle <= 45.0 + ATOM_EPSILON)))
m_AvailPos -= CHARGE_E;
}
m_AngleList.push_back ((angle = m_AngleList.front ()) + 360.0);
m_InterBonds.clear ();
for (n = m_AngleList.begin (), n++; n != m_AngleList.end (); n++) {
delta = *n - angle;
while (m_InterBonds.find (delta) != m_InterBonds.end ())
delta -= 1e-8;
dir = (*n + angle) / 2.;
if ((m_AvailPos == 0xff) || (m_HPos && (dir < 135. || dir > 225.)) ||
(!m_HPos && (dir > 45. && dir < 315.)))
m_InterBonds[delta] = dir;
angle = *n;
}
m_AvailPosCached = true;
}
int gcpAtom::GetChargePosition(unsigned char& Pos, double Angle, double& x, double& y)
{
list<double>::iterator n;
double angle;
if (m_ChargePos != 0xff)
m_OccupiedPos &= ~m_ChargePos;
if (!m_AvailPosCached)
UpdateAvailablePositions ();
if (m_ChargePos != 0xff)
m_OccupiedPos |= m_ChargePos;
if (!m_ChargeAutoPos && Pos == 0xff) {
Pos = m_ChargePos;
if (!Pos)
Angle = m_ChargeAngle * 180 / M_PI;
} else if (Pos == 0xff) {
if (m_AvailPos) {
if (m_AvailPos & CHARGE_NE)
Pos = CHARGE_NE;
else if (m_AvailPos & CHARGE_NW)
Pos = CHARGE_NW;
else if (m_AvailPos & CHARGE_N)
Pos = CHARGE_N;
else if (m_AvailPos & CHARGE_SE)
Pos = CHARGE_SE;
else if (m_AvailPos & CHARGE_SW)
Pos = CHARGE_SW;
else if (m_AvailPos & CHARGE_S)
Pos = CHARGE_S;
else if (m_AvailPos & CHARGE_E)
Pos = CHARGE_E;
else if (m_AvailPos & CHARGE_W)
Pos = CHARGE_W;
} else {
Pos = 0;
angle = m_AngleList.front();
double max = 0.0;
//if we are there, there are at least two bonds
for (n = m_AngleList.begin(), n++; n != m_AngleList.end(); n++)
{
if (*n - angle > max)
{
if (*n - angle - max > 0.1) x = (*n + angle) / 2;
if (m_nH)
{
if (m_HPos && ((x > 225.0) || (x < 135.0))) Angle = x;
else if (m_HPos && (x > 45.0) && (x < 315.0)) Angle = x;
}
else Angle = x;
max = *n - angle;
}
angle = *n;
}
}
} else if (Pos) {
if (!(Pos & m_AvailPos) && (Pos != m_ChargePos))
return 0;
} else {
if (Angle > 360.)
Angle -= 360;
else if (Angle < 0.)
Angle += 360;
if (!(((GetZ() == 6) && (m_Bonds.size() != 0)) ||
!m_nH || ((!m_HPos && (Angle < 135. || Angle > 225.)) ||
(m_HPos && (Angle > 45. && Angle < 315.)))))
return 0;
}
switch (Pos) {
case CHARGE_NE:
x = m_x + m_width / 2.0;
y = m_y - m_height / 2.0;
return 1;
case CHARGE_NW:
x = m_x - m_width / 2.0;
y = m_y - m_height / 2.0;
return -1;
case CHARGE_N:
x = m_x;
y = m_y - m_height / 2.0;
return 2;
case CHARGE_SE:
x = m_x + m_width / 2.0;
y = m_y + m_height / 2.0;
return 1;
case CHARGE_SW:
x = m_x - m_width / 2.0;
y = m_y + m_height / 2.0;
return -1;
case CHARGE_S:
x = m_x;
y = m_y + m_height / 2.0;
return -2;
case CHARGE_E:
x = m_x /*+ 12.*/ + m_width / 2.0;
y = m_y;
return 1;
case CHARGE_W:
x = m_x /*- 12.*/ - m_width / 2.0;
y = m_y;
return -1;
default: {
double t = tan (Angle / 180. * M_PI);
double limit = atan (m_height / m_width) * 180. / M_PI;
if (Angle < limit) {
x = m_x /*+ 12. */+ m_width / 2.;
y = m_y - m_width / 2. * t;
return 1;
} else if (Angle < 180. - limit) {
if (!isnan (t))
x = m_x + m_height / 2. / t;
else
x = m_x;
y = m_y - m_height / 2.;
return 2;
} else if (Angle < 180. + limit) {
x = m_x /*- 12.*/ - m_width / 2.;
y = m_y + m_width / 2. * t;
return -1;
} else if (Angle < 360. - limit) {
if (!isnan (t))
x = m_x - m_height / 2. / t;
else
x = m_x;
y = m_y + m_height / 2.;
return -2;
} else {
x = m_x /*+ 12.*/ + m_width / 2.;
y = m_y - m_width / 2. * t;
return 1;
}
}
}
return 0; // should not occur
}
int gcpAtom::GetAvailablePosition(double& x, double& y)
{
list<double>::iterator n;
double angle;
if (!m_AvailPosCached)
UpdateAvailablePositions ();
if (m_AvailPos)
{
if (m_AvailPos & POSITION_N)
{
x = m_x;
y = m_y - m_height / 2.0;
return POSITION_N;
}
if (m_AvailPos & POSITION_S)
{
x = m_x;
y = m_y + m_height / 2.0;
return POSITION_S;
}
if (m_AvailPos & POSITION_E)
{
x = m_x + m_width / 2.0;
y = m_y;
return POSITION_E;
}
if (m_AvailPos & POSITION_W)
{
x = m_x - m_width / 2.0;
y = m_y;
return POSITION_W;
}
if (m_AvailPos & POSITION_NE)
{
x = m_x + m_width / 2.0;
y = m_y - m_height / 2.0;
return POSITION_NE;
}
if (m_AvailPos & POSITION_NW)
{
x = m_x - m_width / 2.0;
y = m_y - m_height / 2.0;
return POSITION_NW;
}
if (m_AvailPos & POSITION_SE)
{
x = m_x + m_width / 2.0;
y = m_y + m_height / 2.0;
return POSITION_SE;
}
if (m_AvailPos & POSITION_SW)
{
x = m_x - m_width / 2.0;
y = m_y + m_height / 2.0;
return POSITION_SW;
}
}
angle = m_AngleList.front();
double dir = 0.0, max = 0.0;
//if we are there, there are at least two bonds
for (n = m_AngleList.begin(), n++; n != m_AngleList.end(); n++)
{
if (*n - angle > max)
{
if (*n - angle - max > 0.1) x = (*n + angle) / 2;
if (m_nH)
{
if (m_HPos && ((x > 225.0) || (x < 135.0))) dir = x;
else if (m_HPos && (x > 45.0) && (x < 315.0)) dir = x;
}
else dir = x;
max = *n - angle;
}
angle = *n;
}
max = sqrt(square(m_width) + square(m_height)) / 2.0 + 24;//Could do better, should replace 24 by something more intelligent
x = m_x + max * cos(dir / 180.0 * M_PI);
y = m_y - max * sin(dir / 180.0 * M_PI);
return 0;
}
bool gcpAtom::LoadNode(xmlNodePtr)
{
SetZ(GetZ());
return true;
}
void gcpAtom::SetSelected(GtkWidget* w, int state)
{
gcpWidgetData* pData = (gcpWidgetData*)g_object_get_data(G_OBJECT(w), "data");
GnomeCanvasGroup* group = pData->Items[this];
gpointer item;
gchar *color, *chargecolor;
switch (state) {
case SelStateUnselected:
color = (char*) "white";
chargecolor = (char*) "black";
break;
case SelStateSelected:
chargecolor = color = SelectColor;
break;
case SelStateUpdating:
chargecolor = color = AddColor;
break;
case SelStateErasing:
chargecolor = color = DeleteColor;
break;
default:
color = (char*) "white";
chargecolor = (char*) "black";
break;
}
g_object_set (G_OBJECT(g_object_get_data(G_OBJECT(group), "rect")),
"fill_color", color, NULL);
if ((item = g_object_get_data (G_OBJECT (group), "bullet")))
g_object_set (item, "fill_color", chargecolor, NULL);
if ((item = g_object_get_data (G_OBJECT (group), "figure")))
g_object_set (item, "fill_color", chargecolor, NULL);
if ((item = g_object_get_data (G_OBJECT (group), "circle")))
g_object_set (item, "outline_color", chargecolor, NULL);
if ((item = g_object_get_data (G_OBJECT (group), "sign")))
g_object_set (item, "outline_color", chargecolor, NULL);
Object::SetSelected (w, state);
}
bool gcpAtom::AcceptNewBonds(int nb)
{
if ((m_Valence > 0) || m_ChargeAuto)
return Element::GetMaxBonds(m_Z) >= (GetTotalBondsNumber() + GetChildrenNumber () + nb);
map<string, Object*>::iterator i;
gcpElectron* electron = (gcpElectron*) GetFirstChild (i);
unsigned nel = 0;
while (electron){
if (electron->IsPair ())
nel += 2;
else
nel++;
electron = (gcpElectron*) GetNextChild (i);
}
nel += GetTotalBondsNumber ();
return (m_ValenceOrbitals - GetTotalBondsNumber () - GetChildrenNumber () > 0)
&& (((m_Element->GetValenceElectrons() - m_Charge) > nel) || m_ChargeAuto);
}
void gcpAtom::AddToMolecule(gcpMolecule* Mol)
{
Mol->AddAtom(this);
}
void gcpAtom::BuildItems (gcpWidgetData* pData)
{
GnomeCanvasGroup* group = pData->Items[this];
void* item;
gcpView* pView = pData->View;
gcpTheme *pTheme = pView->GetDoc ()->GetTheme ();
double x, y;
m_width = m_height = 2.0 * pTheme->GetPadding ();
GetCoords (&x, &y);
x *= pTheme->GetZoomFactor ();
y *= pTheme->GetZoomFactor ();
if ((GetZ() != 6) || (GetBondsNumber() == 0) || m_ShowSymbol) {
int sw, sp;
const gchar* symbol = GetSymbol (), *text;
PangoRectangle rect;
sw = strlen (symbol);
pango_layout_set_text (m_Layout, symbol, sw);
pango_layout_get_extents (m_Layout, &rect, NULL);
m_width += rect.width / PANGO_SCALE;
int n = GetAttachedHydrogens ();
PangoAttrList *pal = pango_attr_list_new ();
if (n > 0) {
if (n > 1) {
gchar const *nb = g_strdup_printf ("%d", n);
int np, nw = strlen (nb);
if (m_HPos) {
text = g_strconcat (symbol, "H", nb, NULL);
np = sw + 1;
sp = 0;
} else {
text = g_strconcat ("H", nb, symbol, NULL);
np = 1;
sp = np + nw;
}
pango_layout_set_text (m_Layout, text, -1);
PangoAttribute *attr = pango_attr_font_desc_new (pView->GetPangoSmallFontDesc());
attr->start_index = np;
attr->end_index = np + nw;
pango_attr_list_insert (pal, attr);
attr = pango_attr_rise_new (-2 * PANGO_SCALE);
attr->start_index = np;
attr->end_index = np + nw;
pango_attr_list_insert (pal, attr);
} else {
if (m_HPos) {
text = g_strconcat (symbol, "H", NULL);
sp = 0;
} else {
text = g_strconcat ("H", symbol, NULL);
sp = 1;
}
pango_layout_set_text (m_Layout, text, -1);
}
pango_layout_set_attributes (m_Layout, pal);
pango_attr_list_unref (pal);
} else {
text = g_strdup (symbol);
sp = 0;
pango_layout_set_text (m_Layout, text, -1);
}
pango_layout_get_extents (m_Layout, NULL, &rect);
m_length = double (rect.width / PANGO_SCALE);
m_text_height = m_height = rect.height / PANGO_SCALE;
pango_layout_index_to_pos (m_Layout, sp, &rect);
int st = rect.x / PANGO_SCALE;
pango_layout_index_to_pos (m_Layout, sp + sw, &rect);
m_lbearing = (st + rect.x / PANGO_SCALE) / 2.;
item = g_object_get_data (G_OBJECT (group), "rect");
g_object_set (item,
"x1", x - m_lbearing - pTheme->GetPadding (),
"y1", y - m_ascent + m_CHeight - pTheme->GetPadding (),
"x2", x - m_lbearing + m_length + pTheme->GetPadding (),
"y2", y - m_ascent + m_CHeight + m_height + pTheme->GetPadding (),
NULL);
item = g_object_get_data (G_OBJECT (group), "symbol");
if (item)
g_object_set (item,
"x", x - m_lbearing,
"y", y - m_ascent + m_CHeight,
NULL);
else {
item = gnome_canvas_item_new (
group,
gnome_canvas_pango_get_type(),
"layout", m_Layout,
"x", x - m_lbearing,
"y", y - m_ascent + m_CHeight,
NULL);
g_object_set_data (G_OBJECT (group), "symbol", item);
g_object_set_data (G_OBJECT (item), "object", this);
g_signal_connect (G_OBJECT (item), "event", G_CALLBACK (on_event), pData->Canvas);
gnome_canvas_item_raise_to_top (GNOME_CANVAS_ITEM (group));
}
item = g_object_get_data (G_OBJECT (group), "bullet");
if (item) {
gtk_object_destroy (GTK_OBJECT (item));
g_object_set_data (G_OBJECT (group), "bullet", NULL);
}
} else {
item = g_object_get_data (G_OBJECT (group), "rect");
g_object_set (item,
"x1", x - 3,
"y1", y - 3,
"x2", x + 3,
"y2", y + 3,
NULL);
item = g_object_get_data (G_OBJECT (group), "symbol");
if (item) {
gtk_object_destroy (GTK_OBJECT (item));
g_object_set_data (G_OBJECT (group), "symbol", NULL);
}
item = g_object_get_data (G_OBJECT (group), "bullet");
if (m_DrawCircle) {
if (!item) {
double dx = pTheme->GetStereoBondWidth () / 2.;
item = gnome_canvas_item_new(
group,
gnome_canvas_ellipse_ext_get_type (),
"x1", x - dx,
"y1", y - dx,
"x2", x + dx,
"y2", y + dx,
"fill_color", (pData->IsSelected (this))? SelectColor: Color,
NULL);
g_object_set_data (G_OBJECT (group), "bullet", item);
g_signal_connect (G_OBJECT (item), "event", G_CALLBACK (on_event), pData->Canvas);
g_object_set_data (G_OBJECT (item), "object", this);
}
} else if (item) {
gtk_object_destroy (GTK_OBJECT (item));
g_object_set_data (G_OBJECT (group), "bullet", NULL);
}
m_length = m_text_height = 0;
gnome_canvas_item_lower_to_bottom (GNOME_CANVAS_ITEM (group));
}
m_width /= pTheme->GetZoomFactor ();
m_height /= pTheme->GetZoomFactor ();
if (m_Changed > 0)
m_Changed--;
}
double gcpAtom::GetYAlign ()
{
return m_y;
}
bool gcpAtom::HasImplicitElectronPairs ()
{
map<string, Object*>::iterator i;
gcpElectron* electron = (gcpElectron*) GetFirstChild (i);
if (m_Valence > 0) {
int nexplp = 0; //nexplp is the number of explicit lone pairs
while (electron){
if (electron->IsPair ())
nexplp++;
electron = (gcpElectron*) GetNextChild (i);
}
return (m_nlp > nexplp);
}
unsigned nel = 0;
while (electron){
if (electron->IsPair ())
nel += 2;
else
nel++;
electron = (gcpElectron*) GetNextChild (i);
}
nel += GetTotalBondsNumber ();
int nocc = GetChildrenNumber () + GetTotalBondsNumber ();
return (nocc < m_ValenceOrbitals) && (((m_Element->GetValenceElectrons() - m_Charge) > nel + 1) || m_ChargeAuto);
}
bool gcpAtom::MayHaveImplicitUnpairedElectrons ()
{
map<string, Object*>::iterator i;
gcpElectron* electron = (gcpElectron*) GetFirstChild (i);
unsigned nel = 0;
while (electron){
if (electron->IsPair ())
nel += 2;
else
nel++;
electron = (gcpElectron*) GetNextChild (i);
}
nel += GetTotalBondsNumber ();
return (m_ValenceOrbitals - GetTotalBondsNumber () - GetChildrenNumber () > 0)
&& (((m_Element->GetValenceElectrons() - m_Charge) > nel) || m_ChargeAuto);
}
bool gcpAtom::GetPosition(double angle, double& x, double& y)
{
if (angle > 360.)
angle -= 360;
else if (angle < 0.)
angle += 360;
if (((GetZ() == 6) && (m_Bonds.size() != 0)) ||
!m_nH || ((!m_HPos && (angle < 135. || angle > 225.)) ||
(m_HPos && (angle > 45. && angle < 315.)))) {
double t = tan (angle / 180. * M_PI);
double limit = atan (m_height / m_width) * 180. / M_PI;
if (angle < limit) {
x = m_x + 12. + m_width / 2.;
y = m_y - m_width / 2. * t;
} else if (angle < 180. - limit) {
if (!isnan (t))
x = m_x + m_height / 2. / t;
else
x = m_x;
y = m_y - m_height / 2.;
} else if (angle < 180. + limit) {
x = m_x - 12. - m_width / 2.;
y = m_y + m_width / 2. * t;
} else if (angle < 360. - limit) {
if (!isnan (t))
x = m_x - m_height / 2. / t;
else
x = m_x;
y = m_y + m_height / 2.;
} else {
x = m_x + 12. + m_width / 2.;
y = m_y - m_width / 2. * t;
}
return true;
}
return false;
}
void gcpAtom::AddElectron (gcpElectron* electron)
{
AddChild (electron);
Update ();
}
void gcpAtom::RemoveElectron (gcpElectron* electron)
{
// remove the electron from children so that it is not taken into account when
// updating.
electron->SetParent (NULL);
Update ();
// Force view update.
gcpDocument *pDoc = reinterpret_cast<gcpDocument*> (GetDocument ());
if (pDoc)
pDoc->GetView ()->Update (this);
}
void gcpAtom::NotifyPositionOccupation (unsigned char pos, bool occupied)
{
if (occupied)
m_OccupiedPos |= pos;
else
m_OccupiedPos &= ~pos;
}
xmlNodePtr gcpAtom::Save (xmlDocPtr xml)
{
xmlNodePtr node = Atom::Save (xml), child;
if (node) {
// Save electrons
map<string, Object*>::iterator i;
gcpElectron* electron = (gcpElectron*) GetFirstChild (i);
while (electron){
child = electron->Save (xml);
if (child)
xmlAddChild (node, child);
electron = (gcpElectron*) GetNextChild (i);
}
}
if (m_Charge && !m_ChargeAutoPos) {
char *buf;
if (m_ChargePos) {
switch (m_ChargePos) {
case CHARGE_NE:
buf = (char*) "ne";
break;
case CHARGE_NW:
buf = (char*) "nw";
break;
case CHARGE_N:
buf = (char*) "n";
break;
case CHARGE_SE:
buf = (char*) "se";
break;
case CHARGE_SW:
buf = (char*) "sw";
break;
case CHARGE_S:
buf = (char*) "s";
break;
case CHARGE_E:
buf = (char*) "e";
break;
case CHARGE_W:
buf = (char*) "w";
break;
default:
buf = (char*) "def"; // should not occur
}
xmlNewProp (node, (xmlChar*) "charge-position", (xmlChar*) buf);
} else {
buf = g_strdup_printf ("%g", m_ChargeAngle * 180. / M_PI);
xmlNewProp (node, (xmlChar*) "charge-angle", (xmlChar*) buf);
g_free (buf);
}
if (m_ChargeDist != 0.) {
buf = g_strdup_printf ("%g", m_ChargeDist);
xmlNewProp (node, (xmlChar*) "charge-dist", (xmlChar*) buf);
g_free (buf);
}
}
if (GetZ () == 6 && m_ShowSymbol) {
xmlNewProp (node, (xmlChar*) "show-symbol", (xmlChar*) "true");
}
if (m_HPosStyle != AUTO_HPOS) {
xmlNewProp (node, (xmlChar*) "H-position",
(xmlChar*) ((m_HPosStyle == LEFT_HPOS)? "left": "right"));
}
return node;
}
bool gcpAtom::Load (xmlNodePtr node)
{
if (!Atom::Load (node))
return false;
//Load electrons
xmlNodePtr child = node->children;
gcpElectron *electron;
while (child) {
electron = NULL;
if (!strcmp ((char*) child->name, "electron"))
electron = new gcpElectron (this, false);
else if (!strcmp ((char*) child->name, "electron-pair"))
electron = new gcpElectron (this, true);
if (electron && !electron->Load (child))
return false;
child = child->next;
}
char *buf = (char*) xmlGetProp (node, (xmlChar*) "charge-position");
m_ChargePos = 0xff;
if (buf) {
if (! strcmp (buf, "ne")) {
m_ChargePos = CHARGE_NE;
m_ChargeAngle = M_PI / 4.;
} else if (! strcmp (buf, "nw")) {
m_ChargePos = CHARGE_NW;
m_ChargeAngle = 3. * M_PI / 4.;
} else if (! strcmp (buf, "n")) {
m_ChargePos = CHARGE_N;
m_ChargeAngle = M_PI / 2.;
} else if (! strcmp (buf, "se")) {
m_ChargePos = CHARGE_SE;
m_ChargeAngle = 7. * M_PI / 4;
} else if (! strcmp (buf, "sw")) {
m_ChargePos = CHARGE_SW;
m_ChargeAngle = 5. * M_PI / 4;
} else if (! strcmp (buf, "s")) {
m_ChargePos = CHARGE_S;
m_ChargeAngle = 3 * M_PI / 2.;
} else if (! strcmp (buf, "e")) {
m_ChargePos = CHARGE_E;
m_ChargeAngle = 0.;
} else if (! strcmp (buf, "w")) {
m_ChargePos = CHARGE_W;
m_ChargeAngle = M_PI;
}
m_ChargeAutoPos = false;
xmlFree (buf);
} else {
buf = (char*) xmlGetProp(node, (xmlChar*)"charge-angle");
if (buf) {
sscanf(buf, "%lg", &m_ChargeAngle);
m_ChargeAngle *= M_PI / 180.;
xmlFree (buf);
m_ChargePos = 0;
m_ChargeAutoPos = false;
}
}
buf = (char*) xmlGetProp(node, (xmlChar*)"charge-dist");
if (buf) {
sscanf(buf, "%lg", &m_ChargeDist);
xmlFree (buf);
m_ChargeAutoPos = false;
} else
m_ChargeDist = 0.;
buf = (char*) xmlGetProp (node, (xmlChar*) "show-symbol");
if (buf) {
if (!strcmp (buf, "true"))
m_ShowSymbol = true;
xmlFree (buf);
}
// Load H atoms position if any
buf = (char*) xmlGetProp (node, (xmlChar*) "H-position");
if (buf) {
if (!strcmp (buf, "left"))
m_HPosStyle = LEFT_HPOS;
else if (!strcmp (buf, "right"))
m_HPosStyle = RIGHT_HPOS;
xmlFree (buf);
Update ();
}
return true;
}
bool gcpAtom::AcceptCharge (int charge) {
unsigned nb = GetTotalBondsNumber (), ne = 0;
map<string, Object*>::iterator i;
gcpElectron* electron = (gcpElectron*) GetFirstChild (i);
while (electron){
if (electron->IsPair ())
ne += 2;
else
ne++;
electron = (gcpElectron*) GetNextChild (i);
}
if (charge < 0)
return (m_Element->GetTotalValenceElectrons () <= m_Element->GetMaxValenceElectrons () + charge - nb - 2 * GetChildrenNumber () + ne);
if (nb)
return (m_Element->GetValenceElectrons () >= charge + nb + ne);
return (charge <= GetZ ());
}
void gcpAtom::SetChargePosition (unsigned char Pos, bool def, double angle, double distance)
{
if (Pos != m_ChargePos) {
m_ChargeAutoPos = def;
if (m_ChargePos > 0)
NotifyPositionOccupation (m_ChargePos, false);
m_ChargePos = Pos;
if (m_ChargePos > 0)
NotifyPositionOccupation (m_ChargePos, true);
}
m_ChargeAngle = angle;
m_ChargeDist = distance;
m_AvailPosCached = false;
}
char gcpAtom::GetChargePosition (double *Angle, double *Dist)
{
if (Angle)
*Angle = m_ChargeAngle;
if (Dist)
*Dist = m_ChargeDist;
return (m_ChargeAutoPos)? -1: m_ChargePos;
}
void gcpAtom::SetCharge (int charge)
{
gcu::Atom::SetCharge (charge);
m_ChargeAuto = false;
Update ();
}
void gcpAtom::Transform2D (Matrix2D& m, double x, double y)
{
Atom::Transform2D (m, x, y);
// Now transform electrons
map<string, Object*>::iterator i;
Object* electron = GetFirstChild (i);
while (electron) {
electron->Transform2D (m, x, y);
electron = GetNextChild (i);
}
if (GetCharge ()) {
if (m_ChargeAutoPos) {
if (m_ChargePos > 0)
NotifyPositionOccupation (m_ChargePos, false);
m_ChargePos = 0xff;
Update ();
} else {
double xc = cos (m_ChargeAngle), yc = - sin (m_ChargeAngle);
m.Transform (xc, yc);
m_ChargeAngle = atan2 (- yc, xc);
if (m_ChargeAngle < 0)
m_ChargeAngle += 2 * M_PI;
SetChargePosition (0, FALSE, m_ChargeAngle, m_ChargeDist);
}
}
}
static void do_display_symbol (GtkToggleAction *action, gcpAtom* Atom)
{
gcpDocument *Doc = static_cast <gcpDocument *> (Atom->GetDocument ());
gcpOperation *Op = Doc->GetNewOperation (GCP_MODIFY_OPERATION);
Object *Obj = Atom->GetGroup ();
Op->AddObject (Obj, 0);
Atom->SetShowSymbol (gtk_toggle_action_get_active (action));
Atom->Update();
Atom->ForceChanged ();
Atom->EmitSignal (OnChangedSignal);
Op->AddObject (Obj, 1);
Doc->FinishOperation ();
Doc->GetView ()->Update (Atom);
}
static void do_choose_H_pos (gcpAtom* Atom)
{
new gcpHPosDlg (static_cast<gcpDocument*> (Atom->GetDocument ()), Atom);
}
bool gcpAtom::BuildContextualMenu (GtkUIManager *UIManager, Object *object, double x, double y)
{
bool result = false;
GtkActionGroup *group = NULL;
GtkAction *action;
if (GetZ () == 6 && GetBondsNumber() != 0) {
group = gtk_action_group_new ("atom");
action = gtk_action_new ("Atom", _("Atom"),NULL, NULL);
gtk_action_group_add_action (group, action);
g_object_unref (action);
action = GTK_ACTION (gtk_toggle_action_new ("show-symbol", _("Display symbol"), _("Whether to display carbon atom symbol or not"), NULL));
gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), m_ShowSymbol);
g_signal_connect (action, "toggled", G_CALLBACK (do_display_symbol), this);
gtk_action_group_add_action (group, action);
g_object_unref (action);
gtk_ui_manager_add_ui_from_string (UIManager, "<ui><popup><menu action='Atom'><menuitem action='show-symbol'/></menu></popup></ui>", -1, NULL);
result = true;
}
if (m_nH) {
if (!group) {
group = gtk_action_group_new ("atom");
action = gtk_action_new ("Atom", _("Atom"),NULL, NULL);
gtk_action_group_add_action (group, action);
g_object_unref (action);
}
action = GTK_ACTION (gtk_action_new ("H-position", _("Hydrogen atoms position"), NULL, NULL));
g_signal_connect_swapped (action, "activate", G_CALLBACK (do_choose_H_pos), this);
gtk_action_group_add_action (group, action);
g_object_unref (action);
gtk_ui_manager_add_ui_from_string (UIManager, "<ui><popup><menu action='Atom'><menuitem action='H-position'/></menu></popup></ui>", -1, NULL);
}
if (group) {
gtk_ui_manager_insert_action_group (UIManager, group, 0);
g_object_unref (group);
}
return result | GetParent ()->BuildContextualMenu (UIManager, object, x, y);
}
syntax highlighted by Code2HTML, v. 0.9.1