// -*- C++ -*- /* * Gnome Crystal * document.cc * * Copyright (C) 2000-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ #include "config.h" #include "gcrystal.h" #include #include #include #include "application.h" #include "document.h" #include "view.h" #include "window.h" #include "celldlg.h" #include "atomsdlg.h" #include "linesdlg.h" #include "sizedlg.h" #include "cleavagesdlg.h" #include "globals.h" #include #include #include #include //#include #include #include #include #ifdef HAVE_FSTREAM # include #else # include #endif #ifdef HAVE_OSTREAM # include #else # include #endif #ifdef HAVE_SSTREAM # include #else # include #endif #include #include #include #include #define SAVE 1 #define LOAD 2 #define XML 3 #define FORMAT 4 #define PREC 1e-3 using namespace std; gcDocument::gcDocument (gcApplication *pApp) :CrystalDoc (pApp) { Init (); m_filename = NULL; m_title = NULL; m_bClosing = false; m_ReadOnly = false; } gcDocument::~gcDocument() { if (m_filename != NULL) g_free(m_filename); if (m_title) g_free(m_title); Reinit(); Dialog *dialog; while (!m_Dialogs.empty()) { dialog = m_Dialogs.front(); m_Dialogs.pop_front(); dialog->Destroy(); } } void gcDocument::Define (unsigned nPage) { switch(nPage) { case 0: new gcCellDlg (dynamic_cast (m_App), this); break; case 1: new gcAtomsDlg (dynamic_cast (m_App), this); break; case 2: new gcLinesDlg (dynamic_cast (m_App), this); break; case 3: new gcSizeDlg (dynamic_cast (m_App), this); break; case 4: new gcCleavagesDlg (dynamic_cast (m_App), this); break; } } void gcDocument::Update() { CrystalDoc::Update(); UpdateAllViews(); } void gcDocument::UpdateAllViews() { list::iterator i; for (i = m_Views.begin(); i != m_Views.end(); i++) (*i)->Update(); } void gcDocument::GetSize(gdouble* xmin, gdouble* xmax, gdouble* ymin, gdouble* ymax, gdouble* zmin, gdouble* zmax) { *xmin = m_xmin; *xmax = m_xmax; *ymin = m_ymin; *ymax = m_ymax; *zmin = m_zmin; *zmax = m_zmax; } void gcDocument::SetSize(gdouble xmin, gdouble xmax, gdouble ymin, gdouble ymax, gdouble zmin, gdouble zmax) { m_xmin = xmin; m_xmax = xmax; m_ymin = ymin; m_ymax = ymax; m_zmin = zmin; m_zmax = zmax; } void gcDocument::GetCell(gcLattices *lattice, gdouble *a, gdouble *b, gdouble *c, gdouble *alpha, gdouble *beta, gdouble *gamma) { *lattice = m_lattice; *a = m_a; *b = m_b; *c = m_c; *alpha = m_alpha; *beta = m_beta; *gamma = m_gamma; } void gcDocument::SetCell(gcLattices lattice, gdouble a, gdouble b, gdouble c, gdouble alpha, gdouble beta, gdouble gamma) { m_lattice = lattice; m_a = a; m_b = b; m_c = c; m_alpha = alpha; m_beta = beta; m_gamma = gamma; } void gcDocument::SetFileName (const string &filename) { GnomeVFSFileInfo *info = gnome_vfs_file_info_new (); gnome_vfs_get_file_info (filename.c_str (), info, GNOME_VFS_FILE_INFO_DEFAULT); m_ReadOnly = !(info->permissions & (GNOME_VFS_PERM_USER_WRITE | GNOME_VFS_PERM_GROUP_WRITE)); gnome_vfs_file_info_unref (info); if (m_filename) g_free (m_filename); m_filename = g_strdup (filename.c_str ()); char *dirname = g_path_get_dirname (m_filename); m_App->SetCurDir (dirname); g_free (dirname); int i = filename.length () - 1; while ((m_filename[i] != '/') && (i >= 0)) i--; if (i >=0) { m_filename [i] = 0; chdir (m_filename); m_filename[i] = '/'; } i++; int j = filename.length () - 1; while ((i < j) && (m_filename[j] != '.')) j--; gchar* title = (strcmp (m_filename + j, ".gcrystal"))? g_strdup (m_filename + i):g_strndup (m_filename + i, j - i); SetTitle (title); g_free (title); } void gcDocument::SetTitle(const gchar* title) { if (m_title) g_free(m_title); m_title = g_strdup(title); } void gcDocument::Save() { if (!m_filename) return; xmlDocPtr xml = NULL; try { xml = BuildXMLTree(); if (xmlSaveFile (m_filename, xml) < 0) Error (SAVE); xmlFreeDoc (xml); SetDirty (false); m_ReadOnly = false; // if saving succeded, the file is not read only... } catch (int num) { xmlFreeDoc (xml); Error (SAVE); } } void gcDocument::Error (int num) { gchar *mess = NULL; GtkWidget* message; switch (num) { case SAVE: mess = g_strdup_printf (_("Could not save file\n%s"), m_filename); break; case LOAD: mess = g_strdup_printf (_("Could not load file\n%s"), m_filename); break; case XML: mess = g_strdup_printf (_("%s: invalid xml file.\nTree is empty?"), m_filename); break; case FORMAT: mess = g_strdup_printf (_("%s: invalid file format."), m_filename); break; } message = gtk_message_dialog_new (NULL, (GtkDialogFlags) 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, mess); g_signal_connect_swapped (G_OBJECT (message), "response", G_CALLBACK (gtk_widget_destroy), G_OBJECT (message)); gtk_widget_show (message); g_free (mess); } bool gcDocument::Load (const string &filename) { xmlDocPtr xml = NULL; gchar *oldfilename, *oldtitle; if (m_filename) oldfilename = g_strdup (m_filename); else oldfilename = NULL; oldtitle = g_strdup (m_title); try { if (SetFileName (filename), !m_filename || !m_title) throw (int) 0; if (!(xml = xmlParseFile (filename.c_str ()))) throw (int) 1; if (xml->children == NULL) throw (int) 2; if (strcmp ((char*) xml->children->name, "crystal")) throw (int) 3; if (oldfilename) g_free(oldfilename); g_free (oldtitle); ParseXMLTree (xml->children); xmlFreeDoc (xml); return true; } catch (int num) { switch (num) { case 2: Error(XML); break; case 3: Error(FORMAT); break; default: Error(LOAD); } if (num > 0) { if (oldfilename) { SetFileName (oldfilename); g_free (oldfilename); } else { g_free (m_filename); m_filename = NULL; } SetTitle (oldtitle); g_free (oldtitle); } if (num > 1) xmlFreeDoc(xml); return false; } } void gcDocument::ParseXMLTree(xmlNode* xml) { char *old_num_locale, *txt; xmlNodePtr node; bool bViewLoaded = false; Reinit(); old_num_locale = g_strdup(setlocale(LC_NUMERIC, NULL)); setlocale(LC_NUMERIC, "C"); //look for generator node unsigned version = 0xffffff , major, minor, micro; node = xml->children; while (node) { if (!strcmp ((const char*)(node->name), "generator")) break; node = node->next; } if (node) { txt = (char*)xmlNodeGetContent(node); if (txt) { if (sscanf(txt, "Gnome Crystal %d.%d.%d", &major, &minor, µ) == 3) version = micro + minor * 0x100 + major * 0x10000; xmlFree(txt); } } if (version >= 0x500) { CrystalDoc::ParseXMLTree(xml); } else { node = xml->children; while(node) { if (!strcmp((gchar*)node->name, "lattice")) { txt = (char*)xmlNodeGetContent(node); if (txt) { int i = 0; while (strcmp(txt, LatticeName[i]) && (i < 14)) i++; if (i < 14) m_lattice = (gcLattices)i; xmlFree(txt); } } else if (!strcmp((gchar*)node->name, "cell")) { txt = (char*)xmlNodeGetContent(node); if (txt) { sscanf(txt, "%lg %lg %lg %lg %lg %lg", &m_a, &m_b, &m_c, &m_alpha, &m_beta, &m_gamma); xmlFree(txt); } } else if (!strcmp((gchar*)node->name, "size")) { txt = (char*)xmlNodeGetContent(node); if (txt) { sscanf(txt, "%lg %lg %lg %lg %lg %lg", &m_xmin, &m_ymin, &m_zmin, &m_xmax, &m_ymax, &m_zmax); xmlFree(txt); } txt = (char*)xmlGetProp(node, (xmlChar*)"fixed"); if (txt) { if (!strcmp(txt, "true")) m_bFixedSize = true; xmlFree(txt); } } else if (!strcmp((gchar*)node->name, "atom")) { gcAtom *pAtom = new gcAtom(); if (pAtom->LoadOld(node, version)) AtomDef.push_back((CrystalAtom*)pAtom); else delete pAtom; } else if (!strcmp((gchar*)node->name, "line")) { gcLine *pLine = new gcLine(); if (pLine->LoadOld(node, version)) LineDef.push_back((CrystalLine*)pLine); else delete pLine; } else if (!strcmp((gchar*)node->name, "cleavage")) { gcCleavage *pCleavage = new gcCleavage(); if (pCleavage->LoadOld(node)) Cleavages.push_back((CrystalCleavage*)pCleavage); else delete pCleavage; } else if (!strcmp( (gchar*) node->name, "view")) { if (bViewLoaded) { gcWindow *pWindow = new gcWindow (dynamic_cast (m_App), this); gcView *pView = pWindow->GetView (); pView->LoadOld(node); } else { m_Views.front ()->Load (node); //the first view is created with the document bViewLoaded = true; } } node = node->next; } } setlocale(LC_NUMERIC, old_num_locale); g_free(old_num_locale); Update(); } void gcDocument::OnNewDocument() { Reinit(); UpdateAllViews(); } typedef struct {int n; std::list l;} sAtom; typedef struct {int n; std::list l;} sLine; void gcDocument::OnExportVRML (const string &FileName) { char *old_num_locale, tmp[128]; double x0, x1, x2, x3, x4, x5; int n = 0; try { ostringstream file; GnomeVFSHandle *handle = NULL; GnomeVFSFileSize fs; GnomeVFSResult res; std::mapAtomsMap; std::mapLinesMap; if ((res = gnome_vfs_create (&handle, FileName.c_str (), GNOME_VFS_OPEN_WRITE, true, 0644)) != GNOME_VFS_OK) throw (int) res; old_num_locale = g_strdup(setlocale(LC_NUMERIC, NULL)); setlocale(LC_NUMERIC, "C"); file << "#VRML V2.0 utf8" << endl; //Create prototypes for atoms CrystalAtomList::iterator i; for (i = Atoms.begin(); i != Atoms.end(); i++) { (*i)->GetColor(&x0, &x1, &x2, &x3); snprintf(tmp, sizeof(tmp), "%g %g %g %g %g", (*i)->GetSize(), x0, x1, x2, x3); if (AtomsMap[tmp].l.empty()) { AtomsMap[tmp].n = n; file << "PROTO Atom" << n++ << " [] {Shape {" << endl << "\tgeometry Sphere {radius " << (*i)->GetSize() / 100 << "}" << endl; file << "\tappearance Appearance {" << endl << "\t\tmaterial Material {" << endl << "\t\t\tdiffuseColor " << x0 << " " << x1 << " " << x2 << endl; if (x3 < 1) file << "\t\t\ttransparency " << (1 - x3) << endl; file << "\t\t\tspecularColor 1 1 1" << endl << "\t\t\tshininess 0.9" << endl << "\t\t}" << endl << "\t}\r\n}}" << endl; } AtomsMap[tmp].l.push_back(*i); } //Create prototypes for bonds CrystalLineList::iterator j; n = 0; for (j = Lines.begin(); j != Lines.end(); j++) { (*j)->GetColor(&x0, &x1, &x2, &x3); snprintf(tmp, sizeof(tmp), "%g %g %g %g %g %g", (*j)->Long(), (*j)->GetRadius(), x0, x1, x2, x3); if (LinesMap[tmp].l.empty()) { LinesMap[tmp].n = n; file << "PROTO Bond" << n++ << " [] {Shape {" << endl << "\tgeometry Cylinder {radius " << (*j)->GetRadius() / 100 << "\theight " << (*j)->Long() / 100 << "}" << endl; file << "\tappearance Appearance {" << endl << "\t\tmaterial Material {" << endl << "\t\t\tdiffuseColor " << x0 << " " << x1 << " " << x2 << endl; if (x3 < 1) file << "\t\t\ttransparency " << (1 - x3) << endl; file << "\t\t\tspecularColor 1 1 1" << endl << "\t\t\tshininess 0.9" << endl << "\t\t}" << endl << "\t}\r\n}}" << endl; } LinesMap[tmp].l.push_back(*j); } //world begin m_pActiveView->GetBackgroundColor(&x0, &x1, &x2, &x3); file << "Background{skyColor " << x0 << " " << x1 << " " << x2 << "}" << endl; file << "Viewpoint {fieldOfView " << m_pActiveView->GetFoV()/90*1.570796326794897 <<"\tposition 0 0 " << m_pActiveView->GetPos() / 100 << "}" << endl; m_pActiveView->GetRotation(&x0, &x1, &x2); Matrix m(x0/90*1.570796326794897, x1/90*1.570796326794897, x2/90*1.570796326794897, euler); file << "Transform {" << endl << "\tchildren [" << endl; std::map::iterator k; for (k = AtomsMap.begin(); k != AtomsMap.end(); k++) { for (i = (*k).second.l.begin(); i != (*k).second.l.end(); i++) { if (!(*i)->IsCleaved()) { x0 = (*i)->x(); x1 = (*i)->y(); x2 = (*i)->z(); m.Transform(x0, x1, x2); file << "\t\tTransform {translation " << x1 / 100 << " " << x2 / 100 << " " << x0 / 100\ << " children [Atom" << (*k).second.n << " {}]}" << endl; } } (*k).second.l.clear(); } AtomsMap.clear(); std::map::iterator l; n = 0; for (l = LinesMap.begin(); l != LinesMap.end(); l++) { for (j = (*l).second.l.begin(); j != (*l).second.l.end(); j++) { if (!(*j)->IsCleaved()) { x0 = (*j)->X1(); x1 = (*j)->Y1(); x2 = (*j)->Z1(); m.Transform(x0, x1, x2); x3 = (*j)->X2(); x4 = (*j)->Y2(); x5 = (*j)->Z2(); m.Transform(x3, x4, x5); CrystalLine line(gcu::unique, x0, x1, x2, x3, x4, x5, 0.0, 0.0, 0.0, 0.0, 0.0); line.GetRotation(x0, x1, x2, x3); file << "\t\tTransform {" << endl << "\t\t\trotation " << x1 << " " << x2 << " " << x0 << " " << x3 << endl; x0 = (line.X1() + line.X2()) / 200; x1 = (line.Y1() + line.Y2()) / 200; x2 = (line.Z1() + line.Z2()) / 200; file << "\t\t\ttranslation " << x1 << " " << x2 << " " << x0 << endl\ << "\t\t\tchildren [Bond" << (*l).second.n << " {}]}" << endl; } } n++; (*l).second.l.clear(); } LinesMap.clear(); //end of the world file << "\t]" << endl << "}" << endl; setlocale(LC_NUMERIC, old_num_locale); g_free(old_num_locale); if ((res = gnome_vfs_write (handle, file.str ().c_str (), (GnomeVFSFileSize) file.str ().size (), &fs)) != GNOME_VFS_OK) throw (int) res; gnome_vfs_close (handle); } catch (int n) { fprintf (stderr, "gnome-vfs error #%d\n",n); } } gcView *gcDocument::GetNewView() { return NULL; } void gcDocument::AddView(gcView* pView) { m_Views.push_back (pView); RenameViews (); if (!GetEmpty ()) SetDirty (true); } bool gcDocument::RemoveView (gcView* pView) { if (m_Views.size () > 1) { m_Views.remove (pView); RenameViews (); if (!m_bClosing && !GetEmpty ()) SetDirty (true); return true; } if (GetDirty ()) { if (!VerifySaved ()) return false; } dynamic_cast (m_App)->RemoveDocument (this); delete this; return true; } void gcDocument::RemoveAllViews () { while (m_Views.size () > 1) dynamic_cast (m_Views.front ())->GetWindow ()->Destroy (); // The last one is deleted separately since this will be destroyed ! dynamic_cast (m_Views.front ())->GetWindow ()->Destroy (); } bool gcDocument::VerifySaved() { m_bClosing = true; if (!GetDirty ()) return true; gchar* str = g_strdup_printf(_("\"%s\" has been modified. Do you wish to save it?"), m_title); GtkWidget* mbox; int res; do { mbox = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, str); gtk_dialog_add_button(GTK_DIALOG(mbox), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); res = gtk_dialog_run(GTK_DIALOG(mbox)); gtk_widget_destroy(mbox); if (res == GTK_RESPONSE_YES) { if (m_filename == NULL) { list l; l.push_front ("application/x-gcrystal"); FileChooser (m_App, true, l, this); } if (m_filename) Save (); } } while ((res == GTK_RESPONSE_YES) && (m_filename == NULL)); if (res == GTK_RESPONSE_NO) SetDirty (false); else if (res == GTK_RESPONSE_CANCEL) m_bClosing = false; g_free(str); return (res != GTK_RESPONSE_CANCEL); } CrystalView* gcDocument::CreateNewView() { return new gcView(this); } CrystalAtom* gcDocument::CreateNewAtom() { return (CrystalAtom*) new gcAtom(); } CrystalLine* gcDocument::CreateNewLine() { return (CrystalLine*) new gcLine(); } CrystalCleavage* gcDocument::CreateNewCleavage() { return (CrystalCleavage*) new gcCleavage(); } const char* gcDocument::GetProgramId() { return "Gnome Crystal "VERSION; } void gcDocument::SaveAsImage (const string &filename, char const *type, map& options) { m_pActiveView->SaveAsImage (filename, type, options, GetApp ()->GetImageWidth (), GetApp ()->GetImageHeight ()); } bool gcDocument::LoadNewView (xmlNodePtr node) { gcWindow *pWindow = new gcWindow (dynamic_cast (m_App), this); gcView *pView = pWindow->GetView (); bool result = pView->Load (node); if (!result) delete pWindow; return result; } void gcDocument::RenameViews () { list ::iterator i, iend = m_Views.end (); int n = 1, max = m_Views.size (); for (i = m_Views.begin (); i != iend; i++) { gcWindow *window = dynamic_cast (*i)->GetWindow (); GtkWindow *w = window->GetWindow (); if (!w) continue; if (max > 1) { char *t = g_strdup_printf ("%s (%i)", m_title, n++); gtk_window_set_title (w, t); g_free (t); } else gtk_window_set_title (w, m_title); window->ActivateActionWidget ("ui/MainMenu/FileMenu/Save", !m_ReadOnly); window->ActivateActionWidget ("ui/MainToolbar/Save", !m_ReadOnly); } }