// -*- C++ -*-
/*
* GChemPaint library
* document.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
*/
#undef G_DISABLE_DEPRECATED
#include "gchempaint-config.h"
#include "application.h"
#include "view.h"
#include "document.h"
#include "settings.h"
#include "docprop.h"
#include "reaction.h"
#include "mesomery.h"
#include "text.h"
#include "theme.h"
#include "window.h"
#include <goffice/utils/go-rangefunc.h>
#include <unistd.h>
#include <libgen.h>
#include <gtk/gtk.h>
#include <libgnomevfs/gnome-vfs-file-info.h>
#include <libgnomevfs/gnome-vfs-ops.h>
#include <glib/gi18n-lib.h>
#include <openbabel/mol.h>
#include <openbabel/obconversion.h>
#include <clocale>
#include <stack>
#include <cstring>
using namespace OpenBabel;
using namespace std;
SignalId OnChangedSignal;
SignalId OnDeleteSignal;
SignalId OnThemeChangedSignal;
gcpDocument::gcpDocument(gcpApplication *App, bool StandAlone, gcpWindow *window):
Document (App),
m_FileType ("application/x-gchempaint"),
m_OpID (0),
m_LastStackSize (0)
{
m_pApp = App;
m_pView = NULL;
m_Window = window;
m_filename = NULL;
m_label = NULL;
m_title = NULL;
m_bWriteable = true;
m_PangoAttrList = pango_attr_list_new ();
m_Theme = NULL;
SetTheme (ThemeManager.GetTheme ("Default"));
m_pView = new gcpView(this, !StandAlone);
m_bIsLoading = m_bUndoRedo = false;
g_date_set_time(&CreationDate, time(NULL));
g_date_clear(&RevisionDate, 1);
const char* chn = getenv ("REAL_NAME");
if (!chn)
chn = getenv ("USERNAME");
if (chn)
m_author = g_strdup(chn);
else
m_author = NULL;
chn = getenv ("E_MAIL");
if (!chn)
chn = getenv ("EMAIL_ADDRESS");
if (chn)
m_mail = g_strdup (chn);
else
m_mail = NULL;
m_comment = NULL;
m_pCurOp = NULL;
m_bReadOnly = false;
SetActive ();
if (window && App)
App->AddWindow (window);
}
gcpDocument::~gcpDocument ()
{
m_bIsLoading = true;
if (m_pCurOp)
delete m_pCurOp;
m_pCurOp = NULL;
if (m_filename)
g_free (m_filename);
if (m_title)
g_free (m_title);
if (m_label)
g_free (m_label);
if (m_author)
g_free (m_author);
if (m_mail)
g_free (m_mail);
if (m_comment)
g_free (m_comment);
map<string, Object *>::iterator it;
Object *obj;
while (HasChildren ()) {
obj = GetFirstChild (it);
obj->Lock ();
Remove (obj);
}
if (m_pView)
delete m_pView;
pango_attr_list_unref (m_PangoAttrList);
if (m_Theme)
m_Theme->RemoveClient (this);
if (m_App)
static_cast<gcpApplication*> (m_App)->SetActiveDocument (NULL);
while (!m_RedoList.empty ()) {
delete m_RedoList.front ();
m_RedoList.pop_front ();
}
while (!m_UndoList.empty ()) {
delete m_UndoList.front ();
m_UndoList.pop_front ();
}
}
GtkWidget* gcpDocument::GetWidget()
{
return (m_pView) ? m_pView->GetWidget() : NULL;
}
const gchar* gcpDocument::GetTitle()
{
if (m_title) return m_title;
else return GetLabel();
}
const gchar* gcpDocument::GetLabel()
{
return m_label;
}
void gcpDocument::SetFileName (string const &Name, const gchar* mime_type)
{
if (m_filename)
g_free (m_filename);
m_filename = g_strdup (Name.c_str ());
m_FileType = mime_type;
char *path = g_path_get_dirname (m_filename);
m_pApp->SetCurDir (path);
g_free (path);
int i = strlen (m_filename) - 1;
while ((m_filename[i] != '/') && (i >= 0))
i--;
i++;
int j = strlen (m_filename) - 1;
while ((i < j) && (m_filename[j] != '.'))
j--;
if (m_label)
g_free (m_label);
char *ext = m_filename + j + 1;
list<string> &exts = m_pApp->GetExtensions (m_FileType);
list<string>::iterator cur, end = exts.end ();
for (cur = exts.begin (); cur != end; cur++)
if (*cur == ext) {
m_label = g_strndup (m_filename + i, j - i);
break;
}
if (!m_label)
m_label = g_strdup (m_filename + i);
}
void gcpDocument::BuildBondList(list<gcpBond*>& BondList, Object* obj)
{
Object* pObject;
map<string, Object*>::iterator i;
for (pObject = obj->GetFirstChild(i); pObject; pObject = obj->GetNextChild(i))
if (pObject->GetType() == BondType) BondList.push_back((gcpBond*)(*i).second);
else BuildBondList(BondList, pObject);
}
bool gcpDocument::ImportOB(OBMol& Mol)
{
//Title, dates, author and so on are not imported and so are invalid
if (m_title) {g_free(m_title) ; m_title = NULL;}
if (m_author) {g_free(m_author) ; m_author = NULL;}
if (m_mail) {g_free(m_mail) ; m_mail = NULL;}
if (m_comment) {g_free(m_comment) ; m_comment = NULL;}
g_date_clear(&CreationDate, 1);
g_date_clear(&RevisionDate, 1);
m_title = g_strdup(Mol.GetTitle()); // Hmm, and if there are several molecules?
OBAtom *atom;
gcpAtom* pAtom;
vector<OBNodeBase*>::iterator i;
for (atom = Mol.BeginAtom(i); atom; atom = Mol.NextAtom(i))
{
if (atom->GetAtomicNum () == 0)
continue;
AddAtom(pAtom = new gcpAtom(atom));
}
gcpAtom *begin, *end;
gcpBond *pBond;
unsigned char order;
gchar* Id;
OBBond *bond;
vector<OBEdgeBase*>::iterator j;
for (bond = Mol.BeginBond (j); bond; bond = Mol.NextBond (j)) {
Id = g_strdup_printf ("a%d", bond->GetBeginAtomIdx ());
begin = (gcpAtom*) GetDescendant (Id);//Don't verify it is really an atom?
g_free (Id);
Id = g_strdup_printf ("a%d", bond->GetEndAtomIdx ());
end = (gcpAtom*) GetDescendant (Id);//Don't verify it is really an atom?
g_free (Id);
if (!end)
continue;
order = (unsigned char) (bond->GetBO ());
if ((pBond = (gcpBond*) begin->GetBond (end)) != NULL) {
pBond->IncOrder (order);
m_pView->Update (pBond);
m_pView->Update (begin);
m_pView->Update (end);
} else {
Id = g_strdup_printf ("b%d", bond->GetIdx());
pBond = new gcpBond (begin, end, order);
if (bond->IsWedge ())
pBond->SetType (UpBondType);
else if (bond->IsHash ())
pBond->SetType (DownBondType);
pBond->SetId (Id);
g_free (Id);
AddBond (pBond);
}
}
m_Empty = !HasChildren ();
if (m_Window)
m_Window->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", HasChildren ());
return true;
}
void gcpDocument::BuildAtomTable(map<string, unsigned>& AtomTable, Object* obj, unsigned& index)
{
Object* pObject;
map<string, Object*>::iterator i;
for (pObject = obj->GetFirstChild(i); pObject; pObject = obj->GetNextChild(i))
if (pObject->GetType() == AtomType) AtomTable[(*i).second->GetId()] = index++;
else BuildAtomTable(AtomTable, pObject, index);
}
void gcpDocument::ExportOB()
{
OBMol Mol;
map<string, unsigned>::iterator i;
map<string, unsigned> AtomTable;
list<gcpBond*> BondList;
OBAtom Atom;
gcpAtom* pgAtom;
unsigned index = 1;
double x, y, z;
gchar *old_num_locale;
map< string, Object * >::iterator m;
stack<map< string, Object * >::iterator> iters;
set<Object *> Mols;
Object *Cur = this, *Ob;
try {
ostringstream ofs;
GnomeVFSHandle *handle = NULL;
GnomeVFSFileSize n;
GnomeVFSResult res;
res = gnome_vfs_open (&handle, m_filename, GNOME_VFS_OPEN_WRITE);
if (res == GNOME_VFS_ERROR_NOT_FOUND)
res = gnome_vfs_create (&handle, m_filename, GNOME_VFS_OPEN_WRITE, true, 0666);
if (res != GNOME_VFS_OK)
throw (int) res;
old_num_locale = g_strdup(setlocale(LC_NUMERIC, NULL));
setlocale(LC_NUMERIC, "C");
OBConversion Conv;
OBFormat* pOutFormat = Conv.FormatFromMIME (m_FileType.c_str ());
if (pOutFormat != NULL) {
Conv.SetOutFormat (pOutFormat);
Ob = GetFirstChild (m);
while (Ob) {
if (Ob->GetType () == MoleculeType)
Mols.insert (Ob);
else if (Ob->HasChildren ()) {
Cur = Ob;
iters.push (m);
Ob = Cur->GetFirstChild (m);
continue;
}
Ob = Cur->GetNextChild (m);
while (!Ob && !iters.empty ()) {
m = iters.top ();
iters.pop ();
Cur = Cur->GetParent ();
Ob = Cur->GetNextChild (m);
}
}
set<Object *>::iterator mi, mend = Mols.end ();
unsigned nb = 1;
Conv.SetOneObjectOnly (false);
for (mi = Mols.begin (); mi != mend; mi++)
{
Ob = *mi;
if (nb == Mols.size ())
Conv.SetOneObjectOnly (true);
Mol.BeginModify ();
index = 1;
BuildAtomTable (AtomTable, Ob, index);
Mol.ReserveAtoms (AtomTable.size ());
Mol.SetTitle ((char*) GetTitle ());
Mol.SetDimension (2);
for (i = AtomTable.begin (); i != AtomTable.end (); i++) {
pgAtom = (gcpAtom*) Ob->GetDescendant ((*i).first.data ());
Atom.SetIdx ((*i).second);
Atom.SetAtomicNum (pgAtom->GetZ ());
pgAtom->GetCoords (&x, &y, &z);
Atom.SetVector (x / 100., 4. - y / 100., z / 100.);
Atom.SetFormalCharge (pgAtom->GetCharge ());
Mol.AddAtom (Atom);
Atom.Clear ();
}
BuildBondList (BondList, Ob);
list<gcpBond*>::iterator j;
int start, end, order, flag;
for (j = BondList.begin (); j != BondList.end (); j++)
{
order = (*j)->GetOrder ();
start = AtomTable[(*j)->GetAtom (0)->GetId ()];
end = AtomTable[(*j)->GetAtom (1)->GetId ()];
switch ((*j)->GetType ()) {
case UpBondType:
flag = OB_WEDGE_BOND;
break;
case DownBondType:
flag = OB_HASH_BOND;
break;
default:
flag = 0;
}
Mol.AddBond (start, end, order, flag);
}
Mol.EndModify ();
Conv.SetOutputIndex (nb++);
Conv.Write (&Mol, &ofs);
Mol.Clear ();
AtomTable.clear ();
BondList.clear ();
}
}
setlocale(LC_NUMERIC, old_num_locale);
g_free(old_num_locale);
if ((res = gnome_vfs_write (handle, ofs.str ().c_str (), (GnomeVFSFileSize) ofs.str ().size (), &n)) != GNOME_VFS_OK)
throw (int) res;
gnome_vfs_close (handle);
SetReadOnly (false);
}
catch (int n) {
fprintf(stderr, "gnome-vfs error #%d\n",n);
}
}
void gcpDocument::Print(GnomePrintContext *pc, gdouble width, gdouble height)
{
m_pView->Print(pc, width, height);
}
void gcpDocument::AddAtom(gcpAtom* pAtom)
{
int i = 1;
char id[8];
const gchar *Id;
Id = pAtom->GetId();
if (Id == NULL)
{
id[0] = 'a';
do
snprintf(id+1, 7, "%d", i++);
while (GetDescendant(id) != NULL);
pAtom->SetId(id);
Id = id;
}
m_pView->AddObject(pAtom);
if (m_bIsLoading)
return;
gcpMolecule* mol = new gcpMolecule();
i = 1;
id[0] = 'm';
do
snprintf(id+1, 7, "%d", i++);
while (GetDescendant(id) != NULL);
mol->SetId(id);
AddChild(mol);
mol->AddAtom(pAtom);
}
void gcpDocument::AddFragment(gcpFragment* pFragment)
{
int i = 1;
char id[8];
const gchar *Id;
Id = pFragment->GetId();
if (Id == NULL)
{
id[0] = 'f';
do
snprintf(id+1, 7, "%d", i++);
while (GetDescendant(id) != NULL);
pFragment->SetId(id);
Id = id;
}
m_pView->AddObject(pFragment);
if (m_bIsLoading)
return;
// AddObject(pFragment);
if (!pFragment->GetMolecule())
{
gcpMolecule* mol = new gcpMolecule();
i = 1;
id[0] = 'm';
do
snprintf(id+1, 7, "%d", i++);
while (GetDescendant(id) != NULL);
mol->SetId(id);
AddChild(mol);
mol->AddFragment(pFragment);
}
pFragment->AnalContent();
}
void gcpDocument::AddBond(gcpBond* pBond)
{
int i = 1;
char id[8];
const gchar *Id;
Id = pBond->GetId();
if (Id == NULL)
{
id[0] = 'b';
do
snprintf(id+1, 7, "%d", i++);
while (GetDescendant(id) != NULL);
pBond->SetId(id);
}
AddChild (pBond);
gcpAtom *pAtom0 = (gcpAtom*)pBond->GetAtom(0), *pAtom1 = (gcpAtom*)pBond->GetAtom(1);
m_pView->Update(pAtom0);
m_pView->Update(pAtom1);
m_pView->AddObject(pBond);
if (m_bIsLoading)
return;
//search molecules
gcpMolecule * pMol0 = (gcpMolecule*)pAtom0->GetMolecule(), *pMol1 = (gcpMolecule*)pAtom1->GetMolecule();
if (pMol0 && pMol1)
{
if (pMol0 == pMol1) //new cycle
{
pMol0->UpdateCycles(pBond);
m_pView->Update(pBond);
}
else //merge two molecules
{
pMol0->Merge(pMol1);
}
pMol0->AddBond(pBond);
}
else if (pMol0 || pMol1) //add bond and atom to existing molecule
{
if (!pMol0) pMol0 = pMol1;
pMol0->AddAtom(pAtom0);
pMol0->AddBond(pBond);
}
else //new molecule
{
i = 1;
id[0] = 'm';
do
snprintf(id+1, 7, "%d", i++);
while (GetDescendant(id) != NULL);
pMol0 = new gcpMolecule(pAtom0);
pMol0->SetId(id);
AddChild(pMol0);
}
}
static int cb_xml_to_vfs (GnomeVFSHandle *handle, const char* buf, int nb)
{
GnomeVFSFileSize ndone;
return (int) gnome_vfs_write (handle, buf, nb, &ndone);
}
void gcpDocument::Save()
{
if (!m_filename || !m_bWriteable || m_bReadOnly)
return;
xmlDocPtr xml = NULL;
char *old_num_locale, *old_time_locale;
old_num_locale = g_strdup (setlocale (LC_NUMERIC, NULL));
setlocale(LC_NUMERIC, "C");
old_time_locale = g_strdup (setlocale (LC_TIME, NULL));
setlocale (LC_TIME, "C");
try {
if (m_FileType != "application/x-gchempaint")
ExportOB ();
else {
xml = BuildXMLTree();
xmlSetDocCompressMode (xml, CompressionLevel);
xmlIndentTreeOutput = true;
xmlKeepBlanksDefault(0);
GnomeVFSFileInfo *info = gnome_vfs_file_info_new ();
gnome_vfs_get_file_info (m_filename, info, GNOME_VFS_FILE_INFO_DEFAULT);
if (GNOME_VFS_FILE_INFO_LOCAL (info)) {
gnome_vfs_file_info_unref (info);
if (xmlSaveFormatFile (m_filename, xml, true) < 0) /*Error(SAVE)*/;
} else {
gnome_vfs_file_info_unref (info);
xmlOutputBufferPtr buf = xmlAllocOutputBuffer (NULL);
GnomeVFSHandle *handle;
GnomeVFSResult result = gnome_vfs_open (&handle, m_filename, GNOME_VFS_OPEN_WRITE);
if (result == GNOME_VFS_ERROR_NOT_FOUND)
result = gnome_vfs_create (&handle, m_filename, GNOME_VFS_OPEN_WRITE, true, 0666);
if (result != GNOME_VFS_OK)
throw 1;
buf->context = handle;
buf->closecallback = (xmlOutputCloseCallback) gnome_vfs_close;
buf->writecallback = (xmlOutputWriteCallback) cb_xml_to_vfs;
int n = xmlSaveFormatFileTo (buf, xml, NULL, true);
if (n < 0)
throw 1;
SetReadOnly (false);
}
}
SetDirty (false);
m_LastStackSize = m_UndoList.size ();
m_OpID = m_UndoList.front ()->GetID ();
}
catch (int num) {
if (xml)
xmlFreeDoc (xml);
xml = NULL;
// Error (SAVE);
}
setlocale (LC_NUMERIC, old_num_locale);
g_free (old_num_locale);
setlocale (LC_TIME, old_time_locale);
g_free (old_time_locale);
}
bool gcpDocument::Load (xmlNodePtr root)
{
if (m_title) {
g_free(m_title);
m_title = NULL;
}
if (m_author) {
g_free(m_author);
m_author = NULL;
}
if (m_mail) {
g_free(m_mail);
m_mail = NULL;
}
if (m_comment) {
g_free(m_comment);
m_comment = NULL;
}
g_date_clear (&CreationDate, 1);
g_date_clear (&RevisionDate, 1);
xmlNodePtr node, child;
char* tmp;
Object* pObject;
tmp = (char*) xmlGetProp (root, (xmlChar*) "id");
if (tmp) {
SetId (tmp);
xmlFree (tmp);
}
tmp = (char*) xmlGetProp (root, (xmlChar*) "creation");
if (tmp) {
g_date_set_parse(&CreationDate, tmp);
if (!g_date_valid(&CreationDate)) g_date_clear(&CreationDate, 1);
xmlFree (tmp);
}
tmp = (char*) xmlGetProp (root, (xmlChar*) "revision");
if (tmp) {
g_date_set_parse(&RevisionDate, tmp);
if (!g_date_valid(&RevisionDate)) g_date_clear(&RevisionDate, 1);
xmlFree (tmp);
}
node = GetNodeByName (root, (char*) "title");
if (node)
{
tmp = (char*) xmlNodeGetContent (node);
if (tmp) {
m_title = g_strdup(tmp);
xmlFree (tmp);
}
}
if (m_Window)
m_Window->SetTitle (GetTitle ());
node = GetNodeByName (root, (char*) "author");
if (node)
{
tmp = (char*) xmlGetProp (node, (xmlChar*) "name");
if (tmp) {
m_author = g_strdup(tmp);
xmlFree (tmp);
}
tmp = (char*) xmlGetProp (node, (xmlChar*) "e-mail");
if (tmp) {
m_mail = g_strdup(tmp);
xmlFree (tmp);
}
}
node = GetNodeByName (root, (char*) "comment");
if (node)
{
tmp = (char*) xmlNodeGetContent (node);
if (tmp) {
m_comment = g_strdup(tmp);
xmlFree (tmp);
}
}
node = GetNodeByName (root, (char*) "theme");
if (node) {
gcpTheme *pTheme = new gcpTheme (NULL), *pLocalTheme;
pTheme->Load (node);
pLocalTheme = ThemeManager.GetTheme (_(pTheme->GetName ().c_str ()));
if (!pLocalTheme)
pLocalTheme = ThemeManager.GetTheme (pTheme->GetName ().c_str ());
if (pLocalTheme && *pLocalTheme == *pTheme) {
SetTheme (pLocalTheme);
delete pTheme;
} else {
ThemeManager.AddFileTheme (pTheme, GetTitle ());
SetTheme (pTheme);
}
}
m_bIsLoading = true;
node = root->children;
while (node) {
child = (strcmp ((const char*) node->name, "object"))? node: node->children;
pObject = CreateObject ((const char*) child->name, this);
if (pObject) {
if (!pObject->Load (child))
delete pObject;
else
m_pView->AddObject (pObject);
}
node = node->next;
}
m_pView->Update (this);
m_Empty = !HasChildren ();
Update ();
m_bIsLoading = false;
if (m_Window)
m_Window->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", HasChildren ());
m_pView->EnsureSize ();
return true;
}
void gcpDocument::ParseXMLTree (xmlDocPtr xml)
{
Load (xml->children);
}
xmlDocPtr gcpDocument::BuildXMLTree()
{
xmlDocPtr xml;
xmlNodePtr node;
xmlNsPtr ns;
xml = xmlNewDoc((xmlChar*)"1.0");
//FIXME: do something if an error occurs
if (xml == NULL)
throw (int) 0;
xmlDocSetRootElement (xml, xmlNewDocNode (xml, NULL, (xmlChar*) "chemistry", NULL));
ns = xmlNewNs (xml->children, (xmlChar*) "http://www.nongnu.org/gchempaint", (xmlChar*) "gcp");
xmlSetNs (xml->children, ns);
if (!g_date_valid (&CreationDate))
g_date_set_time (&CreationDate, time (NULL));
g_date_set_time (&RevisionDate, time (NULL));
gchar buf[64];
g_date_strftime (buf, sizeof (buf), "%m/%d/%Y", &CreationDate);
xmlNewProp (xml->children, (xmlChar*) "creation", (xmlChar*) buf);
g_date_strftime (buf, sizeof (buf), "%m/%d/%Y", &RevisionDate);
xmlNewProp (xml->children, (xmlChar*) "revision", (xmlChar*) buf);
node = xmlNewDocNode(xml, NULL, (xmlChar*)"generator", (xmlChar*)"GChemPaint "VERSION);
if (node)
xmlAddChild (xml->children, node);
else
throw (int) 0;
if (m_title && *m_title) {
node = xmlNewDocNode (xml, NULL, (xmlChar*) "title", (xmlChar*) m_title);
if (node)
xmlAddChild (xml->children, node);
else
throw (int) 0;
}
if ((m_author && *m_author) || (m_mail && *m_mail)) {
node = xmlNewDocNode (xml, NULL, (xmlChar*) "author", NULL);
if (node) {
if (m_author && *m_author)
xmlNewProp (node, (xmlChar*) "name", (xmlChar*) m_author);
if (m_mail && *m_mail)
xmlNewProp (node, (xmlChar*) "e-mail", (xmlChar*) m_mail);
xmlAddChild (xml->children, node);
}
else
throw (int) 0;
}
if (m_comment && *m_comment) {
node = xmlNewDocNode (xml, NULL, (xmlChar*) "comment", (xmlChar*) m_comment);
if (node)
xmlAddChild (xml->children, node);
else
throw (int) 0;
}
if (!m_Theme->Save (xml))
throw (int) 0;
if (!SaveChildren (xml, xml->children))
throw 1;
return xml;
}
void gcpDocument::Update()
{
std::set<Object*>::iterator i, end = m_DirtyObjects.end ();
TypeId Id;
for (i = m_DirtyObjects.begin(); i != end; i++) {
Id = (*i)->GetType ();
switch (Id) {
case BondType:
m_pView->Update ((gcpBond*)(*i));
break;
}
}
m_DirtyObjects.clear ();
}
void gcpDocument::RemoveAtom(gcpAtom* pAtom)
{
std::map<Atom*, Bond*>::iterator i;
gcpBond* pBond;
while ((pBond = (gcpBond*)pAtom->GetFirstBond(i)))
{
if (!m_bUndoRedo) m_pCurOp->AddObject(pBond);
RemoveBond(pBond);
}
gcpMolecule *pMol = (gcpMolecule*)pAtom->GetMolecule();
delete pMol;
m_pView->Remove(pAtom);
delete pAtom;
}
void gcpDocument::RemoveFragment(gcpFragment* pFragment)
{
std::map<Atom*, Bond*>::iterator i;
gcpAtom* pAtom = pFragment->GetAtom();
gcpBond* pBond;
while ((pBond = (gcpBond*)pAtom->GetFirstBond(i)))
{
if (!m_bUndoRedo) m_pCurOp->AddObject(pBond);
RemoveBond(pBond);
}
gcpMolecule *pMol = (gcpMolecule*)pFragment->GetMolecule();
delete pMol;
m_pView->Remove(pFragment);
delete pFragment;
}
void gcpDocument::RemoveBond (gcpBond* pBond)
{
m_pView->Remove (pBond);
gcpAtom *pAtom0, *pAtom1;
pAtom0 = (gcpAtom*) pBond->GetAtom (0);
pAtom1 = (gcpAtom*) pBond->GetAtom (1);
gcpMolecule *pMol = (gcpMolecule*) pBond->GetMolecule ();
char id[16];
pMol->Lock (true);
pAtom0->RemoveBond (pBond);
m_pView->Update (pAtom0);
pAtom1->RemoveBond (pBond);
m_pView->Update (pAtom1);
pMol->Lock (false);
if (pBond->IsCyclic ()) {
pMol->Remove (pBond);
pMol->UpdateCycles ();
Update ();
} else {
Object *Parent = pMol->GetParent ();
Parent->Lock ();
int i0 = 1;
string align_id = pMol->GetAlignmentId ();
delete pMol;
gcpMolecule * pMol = new gcpMolecule ();
pMol->Lock (true);
do
snprintf (id, sizeof (id), "m%d", i0++);
while (GetDescendant (id) != NULL);
pMol->SetId (id);
Parent->AddChild (pMol); /* do not update at this point it's the caller responsibility */
Object* pObject = pAtom0->GetParent ();
if (pObject->GetType () == FragmentType)
pMol->AddFragment ((gcpFragment*) pObject);
else
pMol->AddAtom (pAtom0);
pMol->UpdateCycles ();
if (align_id.size ()) {
Object *obj = pMol->GetDescendant (align_id.c_str ());
if (obj)
pMol->SelectAlignmentItem (obj);
align_id = "";
}
pMol->Lock (false);
do
snprintf (id, sizeof (id), "m%d", i0++);
while (GetDescendant (id) != NULL);
pMol = new gcpMolecule ();
pMol->Lock (true);
pMol->SetId (id);
Parent->AddChild (pMol); /* do not update at this point it's the caller responsibility */
pObject = pAtom1->GetParent ();
if (pObject->GetType () == FragmentType)
pMol->AddFragment ((gcpFragment*) pObject);
else
pMol->AddAtom (pAtom1);
pMol->UpdateCycles ();
if (align_id.size ()) {
Object *obj = pMol->GetDescendant (align_id.c_str ());
if (obj)
pMol->SelectAlignmentItem (obj);
}
pMol->Lock (false);
if ((pAtom0->GetZ () == 6) && (pAtom0->GetBondsNumber () == 0))
m_pView->Update (pAtom0);
if ((pAtom1->GetZ () == 6) && (pAtom1->GetBondsNumber () == 0))
m_pView->Update (pAtom1);
Parent->Lock (false);
}
m_DirtyObjects.erase (pBond);
delete pBond;
}
void gcpDocument::Remove(Object* pObject)
{
switch(pObject->GetType())
{
case AtomType:
RemoveAtom((gcpAtom*)pObject);
break;
case FragmentType:
RemoveFragment((gcpFragment*)pObject);
break;
case BondType:
RemoveBond((gcpBond*)pObject);
break;
case MoleculeType: {
((gcpMolecule*)pObject)->Clear();
m_pView->Remove(pObject);
map<string, Object*>::iterator i;
Object* object = pObject->GetFirstChild(i);
while (object)
{
m_pView->Remove(object);
delete object;
object = pObject->GetNextChild(i);
}
delete pObject;
}
break;
default: {
m_pView->Remove(pObject);
map<string, Object*>::iterator i;
Object* object = pObject->GetFirstChild(i);
while (object)
{
if (pObject->IsLocked ())
object->Lock ();
Remove(object);
object = pObject->GetFirstChild(i);
}
delete pObject;
}
break;
}
}
void gcpDocument::Remove(const char* Id)
{
Object* pObj = GetDescendant(Id);
if (pObj) {
pObj->Lock ();
Remove(pObj);
}
}
void gcpDocument::OnProperties()
{
new gcpDocPropDlg(this);
}
void gcpDocument::SetTitle (const gchar* title)
{
g_free (m_title);
m_title = (title && *title)? g_strdup (title): NULL;
}
void gcpDocument::SetAuthor (const gchar* author)
{
g_free (m_author);
m_author = (author && *author)? g_strdup (author): NULL;
}
void gcpDocument::SetMail (const gchar* mail)
{
g_free (m_mail);
m_mail = (mail && *mail)? g_strdup (mail): NULL;
}
void gcpDocument::SetComment (const gchar* comment)
{
g_free (m_comment);
m_comment = (comment && *comment)? g_strdup (comment): NULL;
}
void gcpDocument::AddObject(Object* pObject)
{
if (!pObject->GetParent()) AddChild(pObject);
m_pView->AddObject(pObject);
if (m_bIsLoading || m_bUndoRedo) return;
if (!m_pCurOp) {
m_pCurOp = new gcpAddOperation(this, ++m_OpID);
m_pCurOp->AddObject(pObject);
}
}
void gcpDocument::OnUndo()
{
if (m_pApp->GetActiveTool()->OnUndo()) return;
m_bUndoRedo = true;
if (!m_UndoList.empty())
{
gcpOperation* Op = m_UndoList.front();
Op->Undo();
m_UndoList.pop_front();
m_RedoList.push_front(Op);
m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Redo", true);
}
if (m_UndoList.empty())
m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Undo", false);
m_Window->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", HasChildren ());
m_bUndoRedo = false;
Update ();
EmptyTranslationTable ();
SetDirty (m_LastStackSize != m_UndoList.size () || (m_LastStackSize > 0 && m_OpID != m_UndoList.front ()->GetID ()));
m_Empty = !HasChildren ();
}
void gcpDocument::OnRedo()
{
if (m_pApp->GetActiveTool()->OnRedo()) return;
m_bUndoRedo = true;
if (!m_RedoList.empty())
{
gcpOperation* Op = m_RedoList.front();
Op->Redo();
m_RedoList.pop_front();
m_UndoList.push_front(Op);
m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Undo", true);
}
if (m_RedoList.empty())
m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Redo", false);
m_Window->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", HasChildren ());
m_bUndoRedo = false;
EmptyTranslationTable ();
SetDirty (m_LastStackSize != m_UndoList.size () || (m_LastStackSize > 0 && m_OpID != m_UndoList.front ()->GetID ()));
m_Empty = !HasChildren ();
}
void gcpDocument::FinishOperation()
{
if (!m_pCurOp) return;//FIXME: should at least emit a warning
m_UndoList.push_front(m_pCurOp);
while (!m_RedoList.empty())
{
delete m_RedoList.front();
m_RedoList.pop_front();
}
m_pCurOp = NULL;
SetDirty (true);
m_Empty = !HasChildren ();
if (m_Window) {
m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Undo", true);
m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Redo", false);
m_Window->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", HasChildren ());
}
Update ();
}
void gcpDocument::AbortOperation()
{
if (m_pCurOp) delete m_pCurOp;
m_pCurOp = NULL;
}
void gcpDocument::PopOperation()
{
if (!m_UndoList.empty())
{
delete m_UndoList.front();
m_UndoList.pop_front();
if (m_UndoList.empty() && m_Window)
m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Undo", false);
}
SetDirty (m_LastStackSize != m_UndoList.size () || (m_LastStackSize > 0 && m_OpID != m_UndoList.front ()->GetID ()));
}
void gcpDocument::PushOperation(gcpOperation* operation, bool undo)
{
if (!m_pCurOp || (operation != m_pCurOp))
{
cerr << "Warning: Incorrect operation" << endl;
return;
}
if (undo) FinishOperation();
else
{
while (!m_RedoList.empty())
{
delete m_RedoList.front();
m_RedoList.pop_front();
}
m_RedoList.push_front(operation);
m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Redo", true);
}
m_pCurOp = NULL;
}
void gcpDocument::SetActive ()
{
if (m_Window) {
m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Undo", !m_UndoList.empty ());
m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Redo", !m_RedoList.empty ());
m_Window->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", HasChildren ());
m_Window->ActivateActionWidget ("/MainMenu/FileMenu/Save", !m_bReadOnly);
m_Window->ActivateActionWidget ("/MainToolbar/Save", !m_bReadOnly);
}
}
extern xmlDocPtr pXmlDoc;
void gcpDocument::LoadObjects(xmlNodePtr node)
{
xmlNodePtr child = node->children, child1;
string str;
while (child) //Add everything except bonds
{
if (!strcmp((const char*)child->name, "atom"))
{
gcpAtom* pAtom = new gcpAtom();
AddChild (pAtom);
pAtom->Load(child);
AddAtom(pAtom);
}
else if (!strcmp((const char*)child->name, "fragment"))
{
gcpFragment* pFragment = new gcpFragment();
AddChild(pFragment);
pFragment->Load(child);
AddFragment(pFragment);
}
else if (!strcmp((const char*)child->name, "bond"));
else
{
m_bIsLoading = true;
child1 = (strcmp((const char*)child->name, "object"))? child: child->children;
str = (const char*)child1->name;
Object* pObject = Object::CreateObject(str, this);
pObject->Load(child1);
AddObject(pObject);
m_pView->Update(pObject);//FIXME: should not be necessary, but solve problem with cyclic double bonds
m_bIsLoading = false;
}
child = child->next;
}
//Now, add bonds
child = GetNodeByName (node, (char*) "bond");
while (child)
{
gcpBond* pBond = new gcpBond();
AddChild(pBond);
if (pBond->Load(child))
AddBond(pBond);
else delete pBond;
child = GetNextNodeByName (child->next, (char*) "bond");
}
}
gcpOperation* gcpDocument::GetNewOperation(gcpOperationType type)
{
m_OpID++;
switch (type)
{
case GCP_ADD_OPERATION:
return m_pCurOp = new gcpAddOperation(this, m_OpID);
case GCP_DELETE_OPERATION:
return m_pCurOp = new gcpDeleteOperation(this, m_OpID);
case GCP_MODIFY_OPERATION:
return m_pCurOp = new gcpModifyOperation(this, m_OpID);
default:
return NULL;
}
}
void gcpDocument::AddData(xmlNodePtr node)
{
xmlNodePtr child;
string str;
Object* pObject;
m_bIsLoading = true;
EmptyTranslationTable();
GtkWidget* w = m_pView->GetWidget();
gcpWidgetData* pData = (gcpWidgetData*)g_object_get_data(G_OBJECT(w), "data");
while (node)
{
child = (strcmp((const char*)node->name, "object"))? node: node->children;
str = (const char*)child->name;
pObject = Object::CreateObject(str, this);
AddObject(pObject);
if (!pObject->Load(child)) Remove (pObject);
else
{
m_pView->Update(pObject);//FIXME: should not be necessary, but solve problem with cyclic double bonds
pData->SetSelected(pObject);
}
node = node->next;
}
m_bIsLoading = false;
EmptyTranslationTable();
FinishOperation();
}
void gcpDocument::ExportImage (string const &filename, const char* type, int resolution)
{
m_pView->ExportImage (filename, type, resolution);
}
void gcpDocument::SetReadOnly (bool ro)
{
m_bReadOnly = ro;
if (!ro && (m_FileType != "application/x-gchempaint")) {
OBFormat *f = OBConversion::FormatFromMIME (m_FileType.c_str ());
m_bReadOnly = (f == NULL)? true: f->Flags () & NOTWRITABLE;
}
m_bUndoRedo = true;
if (m_Window) {
m_Window->ActivateActionWidget ("/MainMenu/FileMenu/Save", !m_bReadOnly);
m_Window->ActivateActionWidget ("/MainToolbar/Save", !m_bReadOnly);
}
}
double gcpDocument::GetYAlign ()
{
if (GetChildrenNumber () == 1) {
map<string, Object*>::iterator i;
Object *Child = GetFirstChild (i);
return Child->GetYAlign ();
} else {
gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (GetWidget ()), "data");
ArtDRect rect;
pData->GetObjectBounds (this, &rect);
return (rect.y1 - rect.y0) / 2.;
}
}
void gcpDocument::SetTheme (gcpTheme *theme)
{
if (m_Theme)
m_Theme->RemoveClient (this);
m_Theme = theme;
m_Theme->AddClient (this);
m_BondLength = theme->GetBondLength ();
m_BondAngle = theme->GetBondAngle ();
m_ArrowLength = theme->GetArrowLength ();
m_TextFontFamily = theme->GetTextFontFamily ();
m_TextFontStyle = theme->GetTextFontStyle ();
m_TextFontWeight = theme->GetTextFontWeight ();
m_TextFontVariant = theme->GetTextFontVariant ();
m_TextFontStretch = theme->GetTextFontStretch ();
m_TextFontSize = theme->GetTextFontSize ();
pango_attr_list_unref (m_PangoAttrList);
m_PangoAttrList = pango_attr_list_new ();
pango_attr_list_insert (m_PangoAttrList, pango_attr_family_new (theme->GetFontFamily ()));
pango_attr_list_insert (m_PangoAttrList, pango_attr_style_new (theme->GetFontStyle ()));
pango_attr_list_insert (m_PangoAttrList, pango_attr_weight_new (theme->GetFontWeight ()));
pango_attr_list_insert (m_PangoAttrList, pango_attr_stretch_new (theme->GetFontStretch ()));
pango_attr_list_insert (m_PangoAttrList, pango_attr_variant_new (theme->GetFontVariant ()));
if (m_pView)
m_pView->UpdateTheme ();
}
bool gcpDocument::OnSignal (SignalId Signal, Object *Child)
{
if (Signal == OnThemeChangedSignal) {
m_BondLength = m_Theme->GetBondLength ();
m_BondAngle = m_Theme->GetBondAngle ();
m_ArrowLength = m_Theme->GetArrowLength ();
m_TextFontFamily = m_Theme->GetTextFontFamily ();
m_TextFontStyle = m_Theme->GetTextFontStyle ();
m_TextFontWeight = m_Theme->GetTextFontWeight ();
m_TextFontVariant = m_Theme->GetTextFontVariant ();
m_TextFontStretch = m_Theme->GetTextFontStretch ();
m_TextFontSize = m_Theme->GetTextFontSize ();
pango_attr_list_unref (m_PangoAttrList);
m_PangoAttrList = pango_attr_list_new ();
pango_attr_list_insert (m_PangoAttrList, pango_attr_family_new (m_Theme->GetFontFamily ()));
pango_attr_list_insert (m_PangoAttrList, pango_attr_style_new (m_Theme->GetFontStyle ()));
pango_attr_list_insert (m_PangoAttrList, pango_attr_weight_new (m_Theme->GetFontWeight ()));
pango_attr_list_insert (m_PangoAttrList, pango_attr_stretch_new (m_Theme->GetFontStretch ()));
pango_attr_list_insert (m_PangoAttrList, pango_attr_variant_new (m_Theme->GetFontVariant ()));
m_pView->UpdateTheme ();
}
return false;
}
void gcpDocument::SetDirty (bool isDirty)
{
if (!m_Window)
return;
char *title = g_strdup_printf ((isDirty? "*%s": "%s"), GetTitle ());
m_Window->SetTitle (title);
g_free (title);
Document::SetDirty (isDirty);
}
void gcpDocument::SetLabel(const gchar* label)
{
m_label = g_strdup (label);
m_Window->SetTitle (label);
}
void gcpDocument::OnThemeNamesChanged ()
{
gcpDocPropDlg *dlg = dynamic_cast <gcpDocPropDlg *> (GetDialog ("properties"));
if (dlg)
dlg->OnThemeNamesChanged ();
}
double gcpDocument::GetMedianBondLength ()
{
vector <double> lengths;
int max = 128, nb = 0;
lengths.reserve (max);
double result = 0.;
stack<map< string, Object * >::iterator> iters;
map< string, Object * >::iterator m;
Object *Cur = this, *Ob = GetFirstChild (m);
while (Ob) {
if (Ob->GetType () == BondType) {
if (nb == max) {
max += 128;
lengths.resize (max);
}
lengths[nb++] = static_cast <gcpBond *> (Ob)->Get2DLength ();
} else if (Ob->HasChildren ()) {
Cur = Ob;
iters.push (m);
Ob = Cur->GetFirstChild (m);
continue;
}
Ob = Cur->GetNextChild (m);
while (!Ob && !iters.empty ()) {
m = iters.top ();
iters.pop ();
Cur = Cur->GetParent ();
Ob = Cur->GetNextChild (m);
}
}
if (nb > 0)
#ifdef HAVE_VECTOR_DATA
go_range_median_inter_nonconst (lengths.data (), nb, &result);
#else
go_range_median_inter_nonconst (&(*lengths.begin ()), nb, &result);
#endif
return result;
}
syntax highlighted by Code2HTML, v. 0.9.1