// -*- C++ -*- /* * GChemPaint library * atom.cc * * Copyright (C) 2001-2007 Jean Bréfort * * 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 #include #include #include #include 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::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::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::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::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::iterator i = m_Bonds.begin(), iend = m_Bonds.end (); int nb = 0; while (i != iend) { bond = dynamic_cast ((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 ((*i).second)->GetAngle2D (this); i++; angle -= static_cast ((*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::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::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::iterator i; Object* electron = GetFirstChild (i); while (electron){ electron->Update (w); electron = GetNextChild (i); } } void gcpAtom::UpdateAvailablePositions () { list::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::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::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::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::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::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::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 (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::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::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::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 (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 (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, "", -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, "", -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); }