// -*- C++ -*- /* * GChemPaint library * fragment.cc * * Copyright (C) 2002-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 "fragment.h" #include "fragment-atom.h" #include "widgetdata.h" #include "document.h" #include "application.h" #include "view.h" #include "text.h" #include "theme.h" #include "settings.h" #include "window.h" #include "libgcpcanvas/gcp-canvas-group.h" #include "libgcpcanvas/gcp-canvas-rect-ellipse.h" #include "libgcpcanvas/gcp-canvas-bpath.h" #include #include #include #include #include #include static void on_fragment_changed (gcpFragment *fragment) { fragment->OnChanged (true); } static void on_fragment_sel_changed (gcpFragment *fragment, struct GnomeCanvasPangoSelBounds *bounds) { fragment->OnSelChanged (bounds); } gcpFragment::gcpFragment (): gcpTextObject (FragmentType) { m_Atom = new gcpFragmentAtom (this, 0); m_BeginAtom = m_EndAtom = 0; m_StartSel = m_EndSel = 0; m_lbearing = 0; m_CHeight = 0.; SetId ((char*) "f1"); } gcpFragment::gcpFragment (double x, double y): gcpTextObject (x, y, FragmentType) { m_Atom = new gcpFragmentAtom (this, 0); m_Atom->SetCoords (x, y); m_BeginAtom = m_EndAtom = 0; m_lbearing = 0; m_CHeight = 0.; SetId ((char*) "f1"); } gcpFragment::~gcpFragment () { if (m_Atom) delete m_Atom; } bool gcpFragment::OnChanged (bool save) { if (m_bLoading) return false; gcpDocument* pDoc = (gcpDocument*) GetDocument (); if (!pDoc) return false; gcpView* pView = pDoc->GetView (); GtkWidget* pWidget = pView->GetWidget (); gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (pWidget), "data"); GnomeCanvasGroup *group = pData->Items[this]; if (!group) { pData->Items.erase (this); m_bLoading = false; return false; } GnomeCanvasPango *PangoItem = GNOME_CANVAS_PANGO (g_object_get_data (G_OBJECT (group), "fragment")); unsigned CurPos = gnome_canvas_pango_get_cur_index (PangoItem); AnalContent (m_StartSel, CurPos); m_bLoading = true; m_buf = pango_layout_get_text (m_Layout); if (m_buf.length ()) { PangoLayoutIter *iter = pango_layout_get_iter (m_Layout); m_ascent = pango_layout_iter_get_baseline (iter) / PANGO_SCALE; pango_layout_iter_free (iter); } /*main atom management*/ if (!m_Atom->GetZ()) { int Z = GetElementAtPos (m_StartSel, CurPos); if (!Z && m_StartSel > m_BeginAtom) Z = GetElementAtPos (m_StartSel = m_BeginAtom, CurPos); if (Z) { m_Atom->SetZ (Z); m_BeginAtom = m_StartSel; m_EndAtom = CurPos; } } else if (m_EndSel <= m_BeginAtom) { int delta = CurPos - m_EndSel; m_BeginAtom += delta; m_EndAtom += delta; } else if ((m_EndAtom <= m_EndSel && m_EndAtom >= m_StartSel) || (m_BeginAtom <= m_EndSel && m_BeginAtom >= m_StartSel) || (m_BeginAtom + 3 >= CurPos)) { if (m_BeginAtom > m_StartSel) m_BeginAtom = m_StartSel; if (m_EndAtom > CurPos) m_EndAtom = CurPos; else if (m_EndAtom < m_BeginAtom + 3) m_EndAtom = m_BeginAtom + 3; int Z = GetElementAtPos (m_BeginAtom, m_EndAtom); m_Atom->SetZ (Z); if (!Z) m_EndAtom = CurPos; } PangoRectangle rect; pango_layout_index_to_pos (m_Layout, m_BeginAtom, &rect); m_lbearing = rect.x / PANGO_SCALE; pango_layout_index_to_pos (m_Layout, m_EndAtom, &rect); m_lbearing += rect.x / PANGO_SCALE; m_lbearing /= 2; pView->Update (this); m_bLoading = false; gcpWindow* pWin = pDoc->GetWindow (); if (m_Atom->GetZ () || ((m_buf.length () == 0) && (m_Atom->GetBondsNumber () == 0))) { if (!pDoc->GetReadOnly ()) { pWin->ActivateActionWidget ("/MainMenu/FileMenu/Save", true); pWin->ActivateActionWidget ("/MainToolbar/Save", true); } pWin->ActivateActionWidget ("/MainMenu/FileMenu/SaveAs", true); pWin->ActivateActionWidget ("/MainMenu/FileMenu/Print", true); } else { pWin->ActivateActionWidget ("/MainMenu/FileMenu/Save", false); pWin->ActivateActionWidget ("/MainMenu/FileMenu/SaveAs", false); pWin->ActivateActionWidget ("/MainMenu/FileMenu/Print", false); pWin->ActivateActionWidget ("/MainToolbar/Save", false); } pango_layout_get_extents (m_Layout, NULL, &rect); m_length = rect.width / PANGO_SCALE; m_height = rect.height / PANGO_SCALE; pView->Update (this); EmitSignal (OnChangedSignal); m_StartSel = m_EndSel = CurPos; if (m_buf.length () == 0) { m_BeginAtom = m_EndAtom = 0; } if (save) { gcpTool* FragmentTool = dynamic_cast (pDoc->GetApplication ())->GetTool ("Fragment"); if (!FragmentTool) return true; xmlNodePtr node = SaveSelected (); if (node) FragmentTool->PushNode (node); } return true; } void gcpFragment::Add (GtkWidget* w) { gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (w), "data"); gcpTheme *pTheme = pData->View->GetDoc ()->GetTheme (); gcpView* pView = pData->View; if (m_ascent <= 0) { PangoContext* pc = pData->View->GetPangoContext (); m_Layout = pango_layout_new (pc); PangoAttrList *l = pango_attr_list_new (); pango_layout_set_attributes (m_Layout, l); 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); pango_layout_set_text (m_Layout, "C", 1); PangoRectangle rect; pango_layout_get_extents (m_Layout, &rect, NULL); m_CHeight = double (rect.height / PANGO_SCALE) / 2.0; pango_layout_set_text (m_Layout, m_buf.c_str (), -1); if (m_AttrList) { pango_layout_set_attributes (m_Layout, m_AttrList); pango_attr_list_unref (m_AttrList); m_AttrList = NULL; } if (m_buf.length () > 0) { m_buf.clear (); pango_layout_index_to_pos (m_Layout, m_BeginAtom, &rect); m_lbearing = rect.x / PANGO_SCALE; pango_layout_index_to_pos (m_Layout, m_EndAtom, &rect); m_lbearing += rect.x / PANGO_SCALE; m_lbearing /= 2; iter = pango_layout_get_iter (m_Layout); m_ascent = pango_layout_iter_get_baseline (iter) / PANGO_SCALE; pango_layout_iter_free (iter); } pango_layout_get_extents (m_Layout, NULL, &rect); m_length = rect.width / PANGO_SCALE; m_height = rect.height / PANGO_SCALE; } GnomeCanvasGroup* group = GNOME_CANVAS_GROUP(gnome_canvas_item_new(pData->Group, gnome_canvas_group_ext_get_type(), NULL)), *chgp; GnomeCanvasItem* item = gnome_canvas_item_new( group, gnome_canvas_rect_ext_get_type(), "x1", m_x * pTheme->GetZoomFactor () - pTheme->GetPadding () - m_lbearing, "y1", m_y * pTheme->GetZoomFactor () - pTheme->GetPadding () - m_ascent + m_CHeight, "x2", m_x * pTheme->GetZoomFactor () + m_length + pTheme->GetPadding () - m_lbearing, "y2", m_y * pTheme->GetZoomFactor () + m_height + pTheme->GetPadding () - m_ascent + m_CHeight, "fill_color", "white", "outline_color", "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 (), "layout", m_Layout, "x", m_x * pTheme->GetZoomFactor () - m_lbearing, "y", m_y * pTheme->GetZoomFactor () - m_ascent + m_CHeight, "editing", false, NULL); g_object_set_data (G_OBJECT (group), "fragment", item); g_object_set_data (G_OBJECT (item), "object", this); g_signal_connect (G_OBJECT(item), "event", G_CALLBACK (on_event), w); g_signal_connect_swapped (G_OBJECT (item), "changed", G_CALLBACK (on_fragment_changed), this); g_signal_connect_swapped (G_OBJECT (item), "sel-changed", G_CALLBACK (on_fragment_sel_changed), this); /* add charge */ int charge = m_Atom->GetCharge (); if (charge) { double x, y, Angle, Dist; unsigned char Pos = m_Atom->gcpAtom::GetChargePosition (&Angle, &Dist); int align = GetChargePosition(m_Atom, Pos, 0., x, y); if (Dist != 0.) { x = m_x + Dist * cos (Angle); y = m_y - Dist * sin (Angle); } x *= pTheme->GetZoomFactor (); y *= pTheme->GetZoomFactor (); switch (align) { case -2: x -= pTheme->GetChargeSignSize () / 2.; y += pTheme->GetChargeSignSize () / 2.; break; case -1: x -= pTheme->GetChargeSignSize () + pTheme->GetPadding (); break; case 0: case -3: x -= pTheme->GetChargeSignSize () / 2.; break; case 1: x += pTheme->GetPadding (); break; case 2: x -= pTheme->GetChargeSignSize () / 2.; y -= pTheme->GetChargeSignSize () / 2.; break; } 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); item = gnome_canvas_item_new ( chgp, gnome_canvas_ellipse_ext_get_type (), "x1", x, "y1", y, "x2", x + pTheme->GetChargeSignSize (), "y2", y + 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 = x + 1.; path[1].code = ART_LINETO; path[1].x3 = x + pTheme->GetChargeSignSize () - 1.; path[0].y3 = path[1].y3 = y + pTheme->GetChargeSignSize () / 2.; if (charge > 0) { path[2].code = ART_MOVETO_OPEN; path[2].y3 = y + 1.; path[3].code = ART_LINETO; path[3].y3 = y + pTheme->GetChargeSignSize () - 1.; path[2].x3 = path[3].x3 = x + 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); } pData->Items[this] = group; } void gcpFragment::SetSelected (GtkWidget* w, int state) { gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (w), "data"); GnomeCanvasGroup* group = pData->Items[this]; gchar *chargecolor, *color; 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); gpointer item; 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); } void gcpFragment::Update (GtkWidget* w) { gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (w), "data"); if (pData->Items[this] != NULL) return; gcpTheme *pTheme = pData->View->GetDoc ()->GetTheme (); GnomeCanvasGroup *group = pData->Items[this]; g_object_set (G_OBJECT (g_object_get_data (G_OBJECT (group), "fragment")), "x", m_x * pTheme->GetZoomFactor () - m_lbearing, "y", m_y * pTheme->GetZoomFactor () - m_ascent + m_CHeight, "width", m_length, "height", m_height, NULL); g_object_set (G_OBJECT (g_object_get_data (G_OBJECT (group), "rect")), "x1", m_x * pTheme->GetZoomFactor () - pTheme->GetPadding () - m_lbearing, "y1", m_y * pTheme->GetZoomFactor () - pTheme->GetPadding () - m_ascent + m_CHeight, "x2", m_x * pTheme->GetZoomFactor () + m_length + pTheme->GetPadding () - m_lbearing, "y2", m_y * pTheme->GetZoomFactor () + m_height + pTheme->GetPadding () - m_ascent + m_CHeight, NULL); void* item = g_object_get_data (G_OBJECT (group), "charge"); int charge = m_Atom->GetCharge (); if (charge) { double x, y, Angle, Dist; unsigned char Pos = m_Atom->gcpAtom::GetChargePosition (&Angle, &Dist); if (item) { int align = GetChargePosition (m_Atom, Pos, Angle, x, y); if (Dist != 0.) { x = m_x + Dist * cos (Angle); y = m_y - Dist * sin (Angle); } x *= pTheme->GetZoomFactor (); y *= pTheme->GetZoomFactor (); switch (align) { case -2: x -= pTheme->GetChargeSignSize () / 2.; y += pTheme->GetChargeSignSize () / 2.; break; case -1: x -= pTheme->GetChargeSignSize () + pTheme->GetPadding (); break; case 0: case -3: x -= pTheme->GetChargeSignSize () / 2.; break; case 1: x += pTheme->GetPadding (); break; case 2: x -= pTheme->GetChargeSignSize () / 2.; y -= pTheme->GetChargeSignSize () / 2.; break; } y -= pTheme->GetChargeSignSize () / 2.; item = g_object_get_data (G_OBJECT (group), "circle"); g_object_set (G_OBJECT (item), "x1", x, "y1", y, "x2", x + pTheme->GetChargeSignSize (), "y2", y + 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 = x + 1.; path[1].code = ART_LINETO; path[1].x3 = x + pTheme->GetChargeSignSize () - 1.; path[0].y3 = path[1].y3 = y + pTheme->GetChargeSignSize () / 2.; if (charge > 0) { path[2].code = ART_MOVETO_OPEN; path[2].y3 = y + 1.; path[3].code = ART_LINETO; path[3].y3 = y + pTheme->GetChargeSignSize () - 1.; path[2].x3 = path[3].x3 = x + 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_Atom, Pos, Angle, x, y); x *= pTheme->GetZoomFactor (); if (Dist != 0.) { x = m_x + Dist * cos (Angle); y = m_y - Dist * sin (Angle); } y *= pTheme->GetZoomFactor (); switch (align) { case -2: x -= pTheme->GetChargeSignSize () / 2.; y += pTheme->GetChargeSignSize () / 2.; break; case -1: x -= pTheme->GetChargeSignSize () + pTheme->GetPadding (); break; case 0: case -3: x -= pTheme->GetChargeSignSize () / 2.; break; case 1: x += pTheme->GetPadding (); break; case 2: x -= pTheme->GetChargeSignSize () / 2.; y -= pTheme->GetChargeSignSize () / 2.; break; } 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); item = gnome_canvas_item_new ( chgp, gnome_canvas_ellipse_ext_get_type (), "x1", x, "y1", y, "x2", x + pTheme->GetChargeSignSize (), "y2", y + 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 = x + 1.; path[1].code = ART_LINETO; path[1].x3 = x + pTheme->GetChargeSignSize () - 1.; path[0].y3 = path[1].y3 = y + pTheme->GetChargeSignSize () / 2.; if (charge > 0) { path[2].code = ART_MOVETO_OPEN; path[2].y3 = y + 1.; path[3].code = ART_LINETO; path[3].y3 = y + pTheme->GetChargeSignSize () - 1.; path[2].x3 = path[3].x3 = x + 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); } } } xmlNodePtr gcpFragment::Save (xmlDocPtr xml) { m_buf = pango_layout_get_text (m_Layout); if (m_RealSave && !Validate ()) return NULL; xmlNodePtr node = xmlNewDocNode (xml, NULL, (xmlChar*) "fragment", NULL); if (m_buf.length ()) { if (!m_Atom->GetBondsNumber () || m_Atom->GetZ ()) { if (!node) return NULL; if (!SavePortion (xml, node, 0, m_BeginAtom)) { xmlFreeNode (node); return NULL; } if (m_Atom->GetZ ()) { xmlNodePtr child = m_Atom->Save(xml); if (!child) { xmlFreeNode(node); return NULL; } xmlAddChild(node, child); } if (!SavePortion(xml, node, m_EndAtom, m_buf.length ())) { xmlFreeNode(node); return NULL; } } } return (SaveNode (xml, node))? node: NULL; } struct FilterStruct { unsigned start, end; list pal; }; bool filter_func (PangoAttribute *attribute, struct FilterStruct *s) { if (attribute->klass->type == PANGO_ATTR_RISE && ((PangoAttrInt*) attribute)->value > 0 && s->start <= attribute->start_index && s->end >= attribute->end_index) { list::iterator i, iend = s->pal.end (); for (i = s->pal.begin (); i != iend; i++) if ((*i)->start_index > attribute->end_index) break; s->pal.insert (i, attribute); } return FALSE; } bool gcpFragment::SavePortion (xmlDocPtr xml, xmlNodePtr node, unsigned start, unsigned end) { xmlNodePtr child; struct FilterStruct s; s.start = start; s.end = end; int charge; if (m_AttrList == NULL) m_AttrList = pango_layout_get_attributes (m_Layout); pango_attr_list_filter (m_AttrList, (PangoAttrFilterFunc) filter_func, &s); list::iterator i, iend = s.pal.end (); string str; char *err; for (i = s.pal.begin (); i != iend; i++) { if (start < (*i)->start_index) { str.assign (m_buf, start, (*i)->start_index - start); xmlNodeAddContent (node, (const xmlChar*) str.c_str ()); } str.assign (m_buf, (*i)->start_index, (*i)->end_index - (*i)->start_index); child = xmlNewDocNode (xml, NULL, (xmlChar*) "charge", NULL); if (!child) return false; charge = strtol (str.c_str (), &err, 10); if (err && strcmp (err, "+") && strcmp (err, "-")) { if (!m_RealSave) { return false; xmlFreeNode (child); } gcpDocument *pDoc = (gcpDocument*) GetDocument (); GtkWidget* w = gtk_message_dialog_new ( pDoc->GetWindow ()->GetWindow (), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _("Invalid charge.")); gtk_window_set_icon_name (GTK_WINDOW (w), "gchempaint"); gtk_dialog_run (GTK_DIALOG (w)); gtk_widget_destroy (w); return false; } else { if (!charge) charge = 1; if (*err == '-') charge = - charge; char *buf = g_strdup_printf ("%d", charge); xmlNewProp (child, (xmlChar*) "value", (xmlChar*) buf); g_free (buf); xmlAddChild (node, child); } start = (*i)->end_index; } if (start < end) { str.assign (m_buf, start, end - start); xmlNodeAddContent (node, (const xmlChar*) str.c_str ()); } return true; } xmlNodePtr gcpFragment::SaveSelection (xmlDocPtr xml) { xmlNodePtr node = xmlNewDocNode (xml, NULL, (xmlChar*) "fragment", NULL); if (!node) return NULL; SavePortion (xml, node, m_StartSel, m_EndSel); return (gcpTextObject::SaveNode (xml, node))? node: NULL; } bool gcpFragment::Load (xmlNodePtr node) { gcpDocument* pDoc = (gcpDocument*) GetDocument (); gcpTheme *pTheme = pDoc->GetTheme (); if (!gcpTextObject::Load (node)) return false; if (m_AttrList != NULL) pango_attr_list_unref (m_AttrList); m_AttrList = pango_attr_list_new (); m_bLoading = true; m_buf.clear (); // just in case xmlNodePtr child = node->children; char* tmp; PangoAttribute *attr; int size = pTheme->GetFontSize (); while (child) { if (!strcmp ((const char*) child->name, "text")) { tmp = (char*) xmlNodeGetContent (child); m_buf += tmp; xmlFree (tmp); } else if (!strcmp ((const char*) child->name, "atom")) { if (!m_Atom->Load (child)) return false; m_BeginAtom = m_buf.length (); m_buf += m_Atom->GetSymbol(); m_Atom->SetCoords (m_x, m_y); m_EndAtom = m_buf.length (); } else if (!strcmp ((const char*) child->name, "charge")) { int start = m_buf.length (), end; tmp = (char*) xmlGetProp (child, (xmlChar*) "value"); int charge = atoi (tmp); xmlFree (tmp); if (abs(charge) > 1) tmp = g_strdup_printf ("%d%c", abs (charge), (charge > 0)? '+': '-'); else if (charge == 1) tmp = g_strdup ("+"); else if (charge == -1) tmp = g_strdup ("-"); else tmp = g_strdup ("");//should not occur! m_buf += tmp; end = m_buf.length (); attr = pango_attr_size_new (size * 2 / 3); attr->start_index = start; attr->end_index = end; pango_attr_list_insert (m_AttrList, attr); attr = pango_attr_rise_new (2 * size / 3); attr->start_index = start; attr->end_index = end; pango_attr_list_insert (m_AttrList, attr); } child = child->next; } if (m_Layout) { pango_layout_set_text (m_Layout, m_buf.c_str (), -1); pango_layout_set_attributes (m_Layout, m_AttrList); } AnalContent (); m_bLoading = false; return true; } void gcpFragment::AnalContent () { if (!m_Atom->GetParent ()) AddChild (m_Atom); unsigned end = (m_Layout)? strlen (pango_layout_get_text (m_Layout)): m_buf.length (); AnalContent(0, end); } typedef struct { unsigned index, end; bool result; } ChargeFindStruct; static bool search_for_charge (PangoAttribute *attr, ChargeFindStruct *s) { if (attr->start_index <= s->index && attr->end_index >= s->index && attr->klass->type == PANGO_ATTR_RISE && ((PangoAttrInt*) attr)->value > 0) { s->result = true; s->index = attr->start_index; s->end = attr->end_index; } return false; } void gcpFragment::AnalContent (unsigned start, unsigned &end) { gcpDocument* pDoc = (gcpDocument*) GetDocument (); if (!pDoc) return; gcpTheme *pTheme = pDoc->GetTheme (); char const *text; PangoAttrList *l; if (m_Layout) { text = pango_layout_get_text (m_Layout); l = pango_layout_get_attributes (m_Layout); } else { text = m_buf.c_str (); l = pango_attr_list_ref (m_AttrList); } bool Charge = false; unsigned start_tag, end_tag, next; char c; ChargeFindStruct s; s.index = s.end = 0; if (start > 0) { // search if a charge is at preceeding character s.result = false; s.index = start; pango_attr_list_filter (l, (PangoAttrFilterFunc) search_for_charge, &s); Charge = s.result; } else if (text[start] == '+' || text[start] == '-') Charge = true; else Charge = false; next = start; start_tag = end_tag = start; while (start < end) { c = text[start]; if ((c >= '0') && (c <= '9')) { s.result = false; s.index = start; pango_attr_list_filter (l, (PangoAttrFilterFunc) search_for_charge, &s); Charge = s.result; next = start + 1; // add new size and rise attributes int size = pTheme->GetFontSize (); PangoAttribute *attr = pango_attr_size_new (size * 2 / 3); attr->start_index = start; attr->end_index = next; pango_attr_list_change (l, attr); if (!Charge) attr = pango_attr_rise_new (-size / 3); else { if (text[start - 1] == '+' || text[start - 1] == '-') { // move character before sign char *new_t = g_strdup (text); new_t[start] = new_t[start - 1]; new_t[start - 1] = c; if (m_Layout) { pango_layout_set_text (m_Layout, new_t, -1); text = pango_layout_get_text (m_Layout); } else m_buf = new_t; text = m_buf.c_str (); } attr = pango_attr_rise_new (size * 2 / 3); } attr->start_index = start; attr->end_index = next; pango_attr_list_change (l, attr); start = next - 1; } else if ((c == '+') || (c == '-')) { if (!m_bLoading) { //do not allow both local and global charges if (m_Atom->GetCharge()) m_Atom->SetCharge(0); next = start + 1; if (!Charge) { Charge = true; int size = pTheme->GetFontSize (); PangoAttribute *attr = pango_attr_size_new (size * 2 / 3); attr->start_index = start; attr->end_index = next; pango_attr_list_change (l, attr); attr = pango_attr_rise_new (2 * size / 3); attr->start_index = start; attr->end_index = next; pango_attr_list_change (l, attr); } else { string old_charge (m_buf, s.index, s.end - s.index); char *nextch = NULL; int charge = strtol (old_charge.c_str (), &nextch, 10); if (charge == 0) charge = 1; if (*nextch == 0) { // no sign, just add it int size = pTheme->GetFontSize (); PangoAttribute *attr = pango_attr_size_new (size * 2 / 3); attr->start_index = start; attr->end_index = next; pango_attr_list_change (l, attr); attr = pango_attr_rise_new (2 * size / 3); attr->start_index = start; attr->end_index = next; pango_attr_list_change (l, attr); } else { if (*nextch == '-') charge = - charge; if (c == '+') charge++; else charge--; char *buf; if (abs(charge) > 1) buf = g_strdup_printf ("%d%c", abs (charge), (charge > 0)? '+': '-'); else if (charge == 1) buf = g_strdup ("+"); else if (charge == -1) buf = g_strdup ("-"); else buf = g_strdup (""); int size = pTheme->GetFontSize (); PangoAttrList *l = pango_attr_list_new (); PangoAttribute *attr = pango_attr_size_new (size * 2 / 3); pango_attr_list_insert (l, attr); attr = pango_attr_rise_new (2 * size / 3); pango_attr_list_insert (l, attr); gcp_pango_layout_replace_text (m_Layout, s.index, s.end - s.index + 1, buf, l); pango_attr_list_unref (l); m_StartSel = m_EndSel = s.index + strlen (buf); end += m_StartSel - start - 1; GnomeCanvasPango* text = pDoc->GetView ()->GetActiveRichText (); gnome_canvas_pango_set_selection_bounds (text, m_StartSel, m_EndSel); g_free (buf); } } } } else Charge = false; start++; } } /*! Must return NULL if active tool is FragmentTool because this tool needs a fragment, not an atom TODO: use x and y to figure the best atom in the fragment */ Object* gcpFragment::GetAtomAt (double x, double y, double z) { gcpDocument* pDoc = (gcpDocument*) GetDocument (); gcpTheme *pTheme = pDoc->GetTheme (); gcpApplication* pApp = pDoc->GetApplication (); if (pApp->GetActiveTool () == pApp->GetTool ("Fragment")) return NULL; if (m_Atom->GetBondsNumber () || m_Atom->GetCharge ()) return m_Atom; if (!pDoc) return NULL; double x0, y0; x0 = (x - m_x) * pTheme->GetZoomFactor () + m_lbearing; y0 = (y - m_y) * pTheme->GetZoomFactor () + m_ascent; if ((x0 < 0.) || (x0 > m_length) || (y0 < 0.) || (y0 > m_height)) return NULL; int index, trailing; pango_layout_xy_to_index (m_Layout, (int) (x0 * PANGO_SCALE), (int) (y0 * PANGO_SCALE), &index, &trailing); char c = m_buf[index]; if ((c >= 'a') && (c <= 'z')) { index--; c = m_buf[index]; } if ((c >= 'a') && (c <= 'z')) { index--; c = m_buf[index]; } int Z = GetElementAtPos((unsigned) index, (unsigned&) trailing); if (!Z) return NULL; m_bLoading = true; m_Atom->SetZ (Z); m_bLoading = false; m_BeginAtom =index; m_EndAtom = trailing; m_x -= m_lbearing / pTheme->GetZoomFactor () ; PangoRectangle rect; pango_layout_index_to_pos (m_Layout, index, &rect); m_lbearing = rect.x / PANGO_SCALE; pango_layout_index_to_pos (m_Layout, trailing, &rect); m_lbearing += rect.x / PANGO_SCALE; m_lbearing /= 2; m_x += m_lbearing / pTheme->GetZoomFactor (); m_Atom->SetCoords(m_x, m_y); return m_Atom; } void gcpFragment::Move (double x, double y, double z) { gcpTextObject::Move (x, y, z); m_Atom->Move (x, y, z); } void gcpFragment::OnChangeAtom () { if (m_bLoading) return; gcpDocument *pDoc = (gcpDocument*) GetDocument (); if (!pDoc) return; char const *sym = m_Atom->GetSymbol (); gcp_pango_layout_replace_text (m_Layout, m_BeginAtom, m_EndAtom - m_BeginAtom, sym, pDoc->GetPangoAttrList ()); m_EndAtom = m_BeginAtom + strlen (sym); OnChanged (false); } int gcpFragment::GetElementAtPos (unsigned start, unsigned &end) { int Z; char text[4]; memset (text, 0, 4); strncpy (text, pango_layout_get_text (m_Layout) + start, 3); for (unsigned i = strlen (text); i > 0; i--) { text[i] = 0; if ((Z = Element::Z (text))) { end = start + i; return Z; } } return 0; } int gcpFragment::GetChargePosition (gcpFragmentAtom *pAtom, unsigned char &Pos, double Angle, double &x, double &y) { if ((pAtom != m_Atom) || (m_Atom->GetZ() == 0)) return 0; double width, height; gcpDocument* pDoc = (gcpDocument*) GetDocument (); gcpTheme *pTheme = pDoc->GetTheme (); if (!pDoc) return 0; GtkWidget* pWidget = pDoc->GetView ()->GetWidget (); gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (pWidget), "data"); GnomeCanvasGroup *item = pData->Items[this]; if (!item) return 0; GnomeCanvasPango* text = (GnomeCanvasPango*) g_object_get_data (G_OBJECT (item), "fragment"); if (!GNOME_IS_CANVAS_PANGO (text)) return 0; struct FilterStruct s; s.start = 0; s.end = m_buf.length (); if (m_AttrList == NULL) m_AttrList = pango_layout_get_attributes (m_Layout); pango_attr_list_filter (m_AttrList, (PangoAttrFilterFunc) filter_func, &s); if (s.pal.size () > 0) return 0; //localized charges are prohibited if a global charge already exists // Get atom bounds int result = 0xff; PangoRectangle rect; pango_layout_index_to_pos (m_Layout, m_BeginAtom, &rect); x = rect.x / PANGO_SCALE; if (m_BeginAtom != 0) result &= 0x6d; pango_layout_index_to_pos (m_Layout, m_EndAtom, &rect); width = rect.x / PANGO_SCALE - x; if (m_EndAtom < m_buf.length ()) result &= 0xb6; width /= pTheme->GetZoomFactor (); height = m_height / pTheme->GetZoomFactor (); if (m_Atom->GetBondsNumber()) { map::iterator i; gcpBond* pBond = (gcpBond*)m_Atom->GetFirstBond(i); double angle = pBond->GetAngle2D(m_Atom) + 180.0; if ((result & CHARGE_NE) && (angle >= 180.0) && (angle <= 270.0)) result -= CHARGE_NE; if ((result & CHARGE_NW) && (((angle >= 270.0) && (angle <= 360.0)) || (fabs(angle) < 0.1))) result -= CHARGE_NW; if ((result & CHARGE_N) && (angle >= 225.0) && (angle <= 315.0)) result -= CHARGE_N; if ((result & CHARGE_SE) && (angle >= 90.0) && (angle <= 180.0)) result -= CHARGE_SE; if ((result & CHARGE_SW) && (((angle >= 0.0) && (angle <= 90.0)) || (fabs(angle - 360.0) < 0.1))) result -= CHARGE_SW; if ((result & CHARGE_S) && (angle >= 45.0) && (angle <= 135.0)) result -= CHARGE_S; if ((result & CHARGE_E) && (angle <= 225.0) && (angle >= 135.0)) result -= CHARGE_E; if ((result & CHARGE_W) && ((angle >= 315.0) || (angle <= 45.0))) result -= CHARGE_W; } if (Pos == 0xff) { if (result) { if (result & CHARGE_NE) Pos = CHARGE_NE; else if (result & CHARGE_NW) Pos = CHARGE_NW; else if (result & CHARGE_N) Pos = CHARGE_N; else if (result & CHARGE_SE) Pos = CHARGE_SE; else if (result & CHARGE_SW) Pos = CHARGE_SW; else if (result & CHARGE_S) Pos = CHARGE_S; else if (result & CHARGE_E) Pos = CHARGE_E; else if (result & CHARGE_W) Pos = CHARGE_W; } else return 0; } else if (Pos) { if (!(Pos & result)) return 0; } else return 0; switch (Pos) { case CHARGE_NE: x = m_x + width / 2.0; y = m_y - height / 2.0; return 1; case CHARGE_NW: x = m_x - width / 2.0; y = m_y - height / 2.0; return -1; case CHARGE_N: x = m_x; y = m_y - height / 2.0; return 2; case CHARGE_SE: x = m_x + width / 2.0; y = m_y + height / 2.0; return 1; case CHARGE_SW: x = m_x - width / 2.0; y = m_y + height / 2.0; return -1; case CHARGE_S: x = m_x; y = m_y + height / 2.0; return -2; case CHARGE_E: x = m_x + width / 2.0; y = m_y; return 1; case CHARGE_W: x = m_x - width / 2.0; y = m_y; return -1; } return 0; } bool gcpFragment::Validate () { char const *charge; char *err; if ((m_buf.length () == 0) && m_Atom->GetBondsNumber () == 0) return true; if (m_Atom->GetZ() == 0) { gcpDocument *pDoc = dynamic_cast (GetDocument ()); GtkWidget* pWidget = pDoc->GetView ()->GetWidget (); gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (pWidget), "data"); GnomeCanvasGroup *item = pData->Items[this]; GnomeCanvasPango* text = GNOME_CANVAS_PANGO (g_object_get_data (G_OBJECT (item), "fragment")); gnome_canvas_pango_set_selection_bounds (text, m_BeginAtom, (m_EndAtom == m_BeginAtom)? m_EndAtom + 1: m_EndAtom); GtkWidget* w = gtk_message_dialog_new ( GTK_WINDOW (pDoc->GetWindow ()->GetWindow ()), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _("Invalid symbol.")); gtk_dialog_run (GTK_DIALOG (w)); gtk_widget_destroy (w); return false; } //now scan for charges and validate struct FilterStruct s; s.start = 0; s.end = m_buf.length (); if (m_AttrList == NULL) m_AttrList = pango_layout_get_attributes (m_Layout); pango_attr_list_filter (m_AttrList, (PangoAttrFilterFunc) filter_func, &s); list::iterator i, iend = s.pal.end (); for (i = s.pal.begin (); i != iend; i++) { charge = m_buf.c_str () + (*i)->start_index; strtol (charge, &err, 10); if (*err != '+' && *err != '-' && err - m_buf.c_str () != (int) (*i)->end_index) { gcpDocument *pDoc = dynamic_cast (GetDocument ()); GtkWidget* pWidget = pDoc->GetView ()->GetWidget (); gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (pWidget), "data"); GnomeCanvasGroup *item = pData->Items[this]; GnomeCanvasPango* text = GNOME_CANVAS_PANGO (g_object_get_data (G_OBJECT (item), "fragment")); gnome_canvas_pango_set_selection_bounds (text, (*i)->start_index, (*i)->end_index); GtkWidget* w = gtk_message_dialog_new ( GTK_WINDOW (pDoc->GetWindow ()->GetWindow ()), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _("Invalid charge.")); gtk_dialog_run(GTK_DIALOG(w)); gtk_widget_destroy(w); return false; } } return true; } void gcpFragment::Transform2D (Matrix2D& m, double x, double y) { m_x -= x; m_y -= y; m.Transform (m_x, m_y); m_x += x; m_y += y; m_Atom->SetCoords (m_x, m_y); } double gcpFragment::GetYAlign () { return m_y; } int gcpFragment::GetAvailablePosition (double& x, double& y) { return 0; } bool gcpFragment::GetPosition (double angle, double& x, double& y) { return false; }