// -*- C++ -*-
/*
* Gnome Crystal
* document.cc
*
* Copyright (C) 2000-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
#include "config.h"
#include "gcrystal.h"
#include <unistd.h>
#include <GL/gl.h>
#include <GL/glu.h>
#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 <gcu/filechooser.h>
#include <glade/glade.h>
#include <libxml/parserInternals.h>
#include <libxml/xmlmemory.h>
//#include <libgnomevfs/gnome-vfs-file-info.h>
#include <libgnomevfs/gnome-vfs-ops.h>
#include <vector>
#include <map>
#ifdef HAVE_FSTREAM
# include <fstream>
#else
# include <fstream.h>
#endif
#ifdef HAVE_OSTREAM
# include <ostream>
#else
# include <ostream.h>
#endif
#ifdef HAVE_SSTREAM
# include <sstream>
#else
# include <sstream.h>
#endif
#include <glib/gi18n.h>
#include <clocale>
#include <cmath>
#include <cstring>
#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 <gcApplication *> (m_App), this);
break;
case 1:
new gcAtomsDlg (dynamic_cast <gcApplication *> (m_App), this);
break;
case 2:
new gcLinesDlg (dynamic_cast <gcApplication *> (m_App), this);
break;
case 3:
new gcSizeDlg (dynamic_cast <gcApplication *> (m_App), this);
break;
case 4:
new gcCleavagesDlg (dynamic_cast <gcApplication *> (m_App), this);
break;
}
}
void gcDocument::Update()
{
CrystalDoc::Update();
UpdateAllViews();
}
void gcDocument::UpdateAllViews()
{
list<CrystalView*>::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 <gcApplication *> (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<CrystalAtom*> l;} sAtom;
typedef struct {int n; std::list<CrystalLine*> 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::map<std::string, sAtom>AtomsMap;
std::map<std::string, sLine>LinesMap;
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<std::string, sAtom>::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<std::string, sLine>::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 <gcApplication *> (m_App)->RemoveDocument (this);
delete this;
return true;
}
void gcDocument::RemoveAllViews ()
{
while (m_Views.size () > 1)
dynamic_cast<gcView*> (m_Views.front ())->GetWindow ()->Destroy ();
// The last one is deleted separately since this will be destroyed !
dynamic_cast<gcView*> (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<string> 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<string, string>& options)
{
m_pActiveView->SaveAsImage (filename, type, options, GetApp ()->GetImageWidth (), GetApp ()->GetImageHeight ());
}
bool gcDocument::LoadNewView (xmlNodePtr node)
{
gcWindow *pWindow = new gcWindow (dynamic_cast <gcApplication *> (m_App), this);
gcView *pView = pWindow->GetView ();
bool result = pView->Load (node);
if (!result)
delete pWindow;
return result;
}
void gcDocument::RenameViews ()
{
list <CrystalView *>::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 <gcView*> (*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);
}
}
syntax highlighted by Code2HTML, v. 0.9.1