2
* state.cpp - user session state handler
3
* Copyright (C) 2013, D Haley
5
* This program is free software: you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation, either version 3 of the License, or
8
* (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program. If not, see <http://www.gnu.org/licenses/>.
21
#include "common/translation.h"
22
#include "common/xmlHelper.h"
23
#include "common/stringFuncs.h"
25
const unsigned int MAX_UNDO_SIZE=10;
30
AnalysisState::AnalysisState()
32
modifyLevel=STATE_MODIFIED_NONE;
33
useRelativePathsForSave=false;
38
void AnalysisState::operator=(const AnalysisState &oth)
42
activeTree=oth.activeTree;
44
stashedTrees=oth.stashedTrees;
46
effects.resize(oth.effects.size());
47
for(size_t ui=0;ui<effects.size();ui++)
48
effects[ui]= oth.effects[ui]->clone();
50
savedCameras.resize(oth.savedCameras.size());
51
for(size_t ui=0;ui<savedCameras.size();ui++)
52
savedCameras[ui]= oth.savedCameras[ui]->clone();
55
undoTrees=oth.undoTrees;
56
redoTrees=oth.redoTrees;
58
fileName=oth.fileName;
59
workingDir=oth.workingDir;
60
useRelativePathsForSave=oth.useRelativePathsForSave;
66
worldAxisMode=oth.worldAxisMode;
67
activeCamera=oth.activeCamera;
69
modifyLevel=oth.modifyLevel;
72
redoFilterStack=oth.redoFilterStack;
73
undoFilterStack=oth.undoFilterStack;
78
void AnalysisState::clear()
95
void AnalysisState::clearCams()
97
for(size_t ui=0;ui<savedCameras.size();ui++)
98
delete savedCameras[ui];
102
void AnalysisState::clearEffects()
104
for(size_t ui=0;ui<effects.size();ui++)
110
bool AnalysisState::save(const char *cpFilename, std::map<string,string> &fileMapping,
111
bool writePackage) const
113
//Open file for output
114
std::ofstream f(cpFilename);
119
//Write header, also use local language if available
120
const char *headerMessage = NTRANS("This file is a \"state\" file for the 3Depict program, and stores information about a particular analysis session. This file should be a valid \"XML\" file");
122
f << "<!--" << headerMessage;
123
if(TRANS(headerMessage) != headerMessage)
124
f << endl << TRANS(headerMessage);
130
//Write state open tag
131
f<< "<threeDepictstate>" << endl;
132
f<<tabs(1)<< "<writer version=\"" << PROGRAM_VERSION << "\"/>" << endl;
133
//write general settings
135
f << tabs(1) << "<backcolour r=\"" << rBack << "\" g=\"" <<
136
gBack << "\" b=\"" << bBack << "\"/>" << endl;
138
f << tabs(1) << "<showaxis value=\"" << worldAxisMode << "\"/>" << endl;
141
if(useRelativePathsForSave)
143
//Save path information
144
//Note: When writing packages,
145
//- we don't want to leak path names to remote systems
147
//- we cannot assume that directory structures are preserved between systems
149
//so don't keep working directory in this case.
150
if(writePackage || workingDir.empty() )
152
//Are we saving the sate as a package, if so
153
//make sure we let other 3depict loaders know
154
//that we want to use relative paths
155
f << tabs(1) << "<userelativepaths/>"<< endl;
159
//Not saving a package, however we could be,
160
//for example, be autosaving a load-from-package.
161
//We want to keep relative paths, but
162
//want to be able to find files if something goes askew
163
f << tabs(1) << "<userelativepaths origworkdir=\"" << workingDir << "\"/>"<< endl;
172
if(!activeTree.saveXML(f,fileMapping,writePackage,useRelativePathsForSave))
177
f <<tabs(1) << "<cameras>" << endl;
179
//First camera is the "working" camera, which is unnamed
180
f << tabs(2) << "<active value=\"" << activeCamera << "\"/>" << endl;
182
for(unsigned int ui=0;ui<savedCameras.size();ui++)
184
//ask each camera to write its own state, tab indent 2
185
savedCameras[ui]->writeState(f,STATE_FORMAT_XML,2);
187
f <<tabs(1) << "</cameras>" << endl;
189
if(stashedTrees.size())
191
f << tabs(1) << "<stashedfilters>" << endl;
193
for(unsigned int ui=0;ui<stashedTrees.size();ui++)
195
f << tabs(2) << "<stash name=\"" << stashedTrees[ui].first
197
stashedTrees[ui].second.saveXML(f,fileMapping,
198
writePackage,useRelativePathsForSave,3);
199
f << tabs(2) << "</stash>" << endl;
205
f << tabs(1) << "</stashedfilters>" << endl;
211
f <<tabs(1) << "<effects>" << endl;
212
for(unsigned int ui=0;ui<effects.size();ui++)
213
effects[ui]->writeState(f,STATE_FORMAT_XML,1);
214
f <<tabs(1) << "</effects>" << endl;
221
f<< "</threeDepictstate>" << endl;
223
//Debug check to ensure we have written a valid xml file
224
ASSERT(isValidXML(cpFilename));
226
modifyLevel=STATE_MODIFIED_NONE;
231
bool AnalysisState::load(const char *cpFilename, std::ostream &errStream, bool merge)
235
//Load the state from an XML file
237
//here we use libxml2's loading routines
238
//http://xmlsoft.org/
239
//Tutorial: http://xmlsoft.org/tutorial/xmltutorial.pdf
241
xmlParserCtxtPtr context;
243
context =xmlNewParserCtxt();
248
errStream << TRANS("Failed to allocate parser") << std::endl;
253
doc = xmlCtxtReadFile(context, cpFilename, NULL,0);
258
//release the context
259
xmlFreeParserCtxt(context);
262
//By default, lets not use relative paths
264
useRelativePathsForSave=false;
266
//Lets do some parsing goodness
267
//ahh parsing - verbose and boring
268
FilterTree newFilterTree;
269
vector<Camera *> newCameraVec;
270
vector<Effect *> newEffectVec;
271
vector<pair<string,FilterTree > > newStashes;
273
std::string stateDir=onlyDir(cpFilename);
276
std::stack<xmlNodePtr> nodeStack;
278
xmlNodePtr nodePtr = xmlDocGetRootElement(doc);
280
//Umm where is our root node guys?
283
errStream << TRANS("Unable to retrieve root node in input state file... Is this really a non-empty XML file?") << endl;
287
//This *should* be an threeDepict state file
288
if(xmlStrcmp(nodePtr->name, (const xmlChar *)"threeDepictstate"))
290
errStream << TRANS("Base state node missing. Is this really a state XML file??") << endl;
294
nodeStack.push(nodePtr);
296
//Now in threeDepictstate tag
297
nodePtr = nodePtr->xmlChildrenNode;
299
//check for version tag & number
300
if(!XMLHelpFwdToElem(nodePtr,"writer"))
302
xmlString=xmlGetProp(nodePtr, (const xmlChar *)"version");
308
tmpVer =(char *)xmlString;
309
//Check to see if only contains 0-9 period and "-" characters (valid version number)
310
if(tmpVer.find_first_not_of("0123456789.-")== std::string::npos)
312
//Check between the writer reported version, and the current program version
313
vector<string> vecStrs;
314
vecStrs.push_back(tmpVer);
315
vecStrs.push_back(PROGRAM_VERSION);
317
if(getMaxVerStr(vecStrs)!=PROGRAM_VERSION)
319
errStream << TRANS("State was created by a newer version of this program.. ")
320
<< TRANS("file reading will continue, but may fail.") << endl ;
325
errStream<< TRANS("Warning, unparseable version number in state file. File reading will continue, but may fail") << endl;
332
errStream<< TRANS("Unable to find the \"writer\" node") << endl;
337
//Get the background colour
339
float rTmp,gTmp,bTmp;
340
if(XMLHelpFwdToElem(nodePtr,"backcolour"))
342
errStream<< TRANS("Unable to find the \"backcolour\" node.") << endl;
346
xmlString=xmlGetProp(nodePtr,(const xmlChar *)"r");
349
errStream<< TRANS("\"backcolour\" node missing \"r\" value.") << endl;
352
if(stream_cast(rTmp,(char *)xmlString))
354
errStream<< TRANS("Unable to interpret \"backColour\" node's \"r\" value.") << endl;
359
xmlString=xmlGetProp(nodePtr,(const xmlChar *)"g");
362
errStream<< TRANS("\"backcolour\" node missing \"g\" value.") << endl;
366
if(stream_cast(gTmp,(char *)xmlString))
368
errStream<< TRANS("Unable to interpret \"backColour\" node's \"g\" value.") << endl;
373
xmlString=xmlGetProp(nodePtr,(const xmlChar *)"b");
376
errStream<< TRANS("\"backcolour\" node missing \"b\" value.") << endl;
380
if(stream_cast(bTmp,(char *)xmlString))
382
errStream<< TRANS("Unable to interpret \"backColour\" node's \"b\" value.") << endl;
386
if(rTmp > 1.0 || gTmp>1.0 || bTmp > 1.0 ||
387
rTmp < 0.0 || gTmp < 0.0 || bTmp < 0.0)
389
errStream<< TRANS("\"backcolour\"s rgb values must be in range [0,1]") << endl;
398
nodeStack.push(nodePtr);
401
if(!XMLHelpFwdToElem(nodePtr,"userelativepaths"))
403
useRelativePathsForSave=true;
405
//Try to load the original working directory, if possible
406
if(!XMLGetAttrib(nodePtr,workingDir,"origworkdir"))
410
nodePtr=nodeStack.top();
414
//Get the axis visibility
415
if(!XMLGetNextElemAttrib(nodePtr,worldAxisMode,"showaxis","value"))
417
errStream << TRANS("Unable to find or interpret \"showaxis\" node") << endl;
421
//find filtertree data
422
if(XMLHelpFwdToElem(nodePtr,"filtertree"))
424
errStream << TRANS("Unable to locate \"filtertree\" node.") << endl;
428
//Load the filter tree
429
if(newFilterTree.loadXML(nodePtr,errStream,stateDir))
432
//Read camera states, if present
433
nodeStack.push(nodePtr);
434
if(!XMLHelpFwdToElem(nodePtr,"cameras"))
436
//Move to camera active tag
437
nodePtr=nodePtr->xmlChildrenNode;
438
if(XMLHelpFwdToElem(nodePtr,"active"))
440
errStream << TRANS("Cameras section missing \"active\" node.") << endl;
444
//read ID of active cam
445
xmlString=xmlGetProp(nodePtr,(const xmlChar *)"value");
448
errStream<< TRANS("Unable to find property \"value\" for \"cameras->active\" node.") << endl;
452
if(stream_cast(activeCamera,xmlString))
454
errStream<< TRANS("Unable to interpret property \"value\" for \"cameras->active\" node.") << endl;
459
//Spin through the list of each camera
460
while(!XMLHelpNextType(nodePtr,XML_ELEMENT_NODE))
463
tmpStr =(const char *)nodePtr->name;
467
//work out the camera type
468
if(tmpStr == "persplookat")
470
thisCam = new CameraLookAt;
471
if(!thisCam->readState(nodePtr->xmlChildrenNode))
473
std::string s =TRANS("Failed to interpret camera state for camera : ");
475
errStream<< s << newCameraVec.size() << endl;
481
errStream << TRANS("Unable to interpret the camera type for camera : ") << newCameraVec.size() << endl;
486
newCameraVec.push_back(thisCam);
489
//Enforce active cam value validity
490
if(newCameraVec.size() < activeCamera)
495
nodePtr=nodeStack.top();
498
nodeStack.push(nodePtr);
499
//Read stashes if present
500
if(!XMLHelpFwdToElem(nodePtr,"stashedfilters"))
502
nodeStack.push(nodePtr);
505
nodePtr=nodePtr->xmlChildrenNode;
507
while(!XMLHelpFwdToElem(nodePtr,"stash"))
510
FilterTree newStashTree;
511
newStashTree.clear();
514
xmlString=xmlGetProp(nodePtr,(const xmlChar *)"name");
517
errStream << TRANS("Unable to locate stash name for stash ") << newStashTree.size()+1 << endl;
520
stashName=(char *)xmlString;
522
if(!stashName.size())
524
errStream << TRANS("Empty stash name for stash ") << newStashTree.size()+1 << endl;
529
tmpNode=nodePtr->xmlChildrenNode;
531
if(XMLHelpFwdToElem(tmpNode,"filtertree"))
533
errStream << TRANS("No filter tree for stash:") << stashName << endl;
537
if(newStashTree.loadXML(tmpNode,errStream,stateDir))
539
errStream << TRANS("For stash ") << newStashTree.size()+1 << endl;
543
//if there were any valid elements loaded (could be empty, for exmapl)
544
if(newStashTree.size())
545
newStashes.push_back(make_pair(stashName,newStashTree));
548
nodePtr=nodeStack.top();
551
nodePtr=nodeStack.top();
554
//Read effects, if present
555
nodeStack.push(nodePtr);
557
//Read effects if present
558
if(!XMLHelpFwdToElem(nodePtr,"effects"))
561
nodePtr=nodePtr->xmlChildrenNode;
562
while(!XMLHelpNextType(nodePtr,XML_ELEMENT_NODE))
564
tmpStr =(const char *)nodePtr->name;
567
e = makeEffect(tmpStr);
570
errStream << TRANS("Unrecognised effect :") << tmpStr << endl;
574
//Check the effects are unique
575
for(unsigned int ui=0;ui<newEffectVec.size();ui++)
577
if(newEffectVec[ui]->getType()== e->getType())
580
errStream << TRANS("Duplicate effect found") << tmpStr << TRANS(" cannot use.") << endl;
586
nodeStack.push(nodePtr);
588
if(!e->readState(nodePtr))
590
errStream << TRANS("Error reading effect : ") << e->getName() << std::endl;
594
nodePtr=nodeStack.top();
598
newEffectVec.push_back(e);
601
nodePtr=nodeStack.top();
606
nodeStack.push(nodePtr);
610
//Code threw an error, just say "bad parse" and be done with it
616
//Check that stashes are uniquely named
617
// do brute force search, as there are unlikely to be many stashes
618
for(unsigned int ui=0;ui<newStashes.size();ui++)
620
for(unsigned int uj=0;uj<newStashes.size();uj++)
625
//If these match, states not uniquely named,
626
//and thus statefile is invalid.
627
if(newStashes[ui].first == newStashes[uj].first)
635
//Erase any viscontrol data, seeing as we got this far
637
//Now replace it with the new data
638
activeTree.swap(newFilterTree);
639
std::swap(stashedTrees,newStashes);
643
//If we are merging, then there is a chance
644
//of a name-clash. We avoid this by trying to append -merge continuously
645
for(unsigned int ui=0;ui<newStashes.size();ui++)
647
//protect against overload (very unlikely)
648
unsigned int maxCount;
650
while(hasFirstInPairVec(stashedTrees,newStashes[ui]) && --maxCount)
651
newStashes[ui].first+=TRANS("-merge");
654
stashedTrees.push_back(newStashes[ui]);
656
errStream << TRANS(" Unable to merge stashes correctly. This is improbable, so please report this.") << endl;
659
activeTree.addFilterTree(newFilterTree,0);
664
activeTree.initFilterTree();
666
//Wipe the existing cameras, and then put the new cameras in place
668
savedCameras.clear();
670
//Set a default camera as needed. We don't need to track its unique ID, as this is
671
//"invisible" to the UI
672
if(!savedCameras.size())
674
Camera *c=new CameraLookAt();
675
savedCameras.push_back(c);
680
for(unsigned int ui=0;ui<newCameraVec.size();ui++)
684
//Don't merge the default camera (which has no name)
685
if(newCameraVec[ui]->getUserString().empty())
688
//Keep trying new names appending "-merge" each time to obtain a new, and hopefully unique name
689
// Abort after many times
690
unsigned int maxCount;
692
while(camNameExists(newCameraVec[ui]->getUserString()) && --maxCount)
694
newCameraVec[ui]->setUserString(newCameraVec[ui]->getUserString()+"-merge");
697
//If we have any attempts left, then it worked
699
savedCameras.push_back(newCameraVec[ui]);
703
//If there is no userstring, then its a "default"
704
// camera (one that does not show up to the users,
705
// and cannot be erased from the scene)
706
// set it directly. Otherwise, its a user camera.
707
savedCameras.push_back(newCameraVec[ui]);
716
if(workingDir.empty())
719
#if defined(__APPLE__)
720
//Apple defines a special getcwd that just works
721
wd = getcwd(NULL, 0);
722
#elif defined(WIN32) || defined(WIN64)
723
//getcwd under POSIX is not clearly defined, it requires
724
// an input number of bytes that are enough to hold the path,
725
// however it does not define how one goes about obtaining the
726
// number of bytes needed.
727
char *wdtemp = (char*)malloc(PATH_MAX*20);
728
wd=getcwd(wdtemp,PATH_MAX*20);
736
//GNU extension, which just does it (tm).
737
wd = get_current_dir_name();
743
//If we are merging then the default state has been altered
744
// if we are not merging, then it is overwritten
746
setModifyLevel(STATE_MODIFIED_DATA);
748
setModifyLevel(STATE_MODIFIED_NONE);
750
//Perform sanitisation on results
754
bool AnalysisState::camNameExists(const std::string &s) const
756
for(size_t ui=0; ui<savedCameras.size(); ui++)
758
if (savedCameras[ui]->getUserString() == s )
764
void AnalysisState::copyFilterTree(FilterTree &f) const
769
int AnalysisState::getWorldAxisMode() const
771
return worldAxisMode;
774
void AnalysisState::copyCams(vector<Camera *> &cams) const
776
ASSERT(!cams.size());
778
cams.resize(savedCameras.size());
779
for(size_t ui=0;ui<savedCameras.size();ui++)
780
cams[ui] = savedCameras[ui]->clone();
783
void AnalysisState::copyCamsByRef(vector<const Camera *> &camRef) const
785
camRef.resize(savedCameras.size());
786
for(size_t ui=0;ui<camRef.size();ui++)
787
camRef[ui]=savedCameras[ui];
790
const Camera *AnalysisState::getCam(size_t offset) const
792
return savedCameras[offset];
795
void AnalysisState::removeCam(size_t offset)
797
ASSERT(offset < savedCameras.size());
798
savedCameras.erase(savedCameras.begin()+offset);
799
if(activeCamera >=savedCameras.size())
803
void AnalysisState::addCamByClone(const Camera *c)
805
setModifyLevel(STATE_MODIFIED_ANCILLARY);
806
savedCameras.push_back(c->clone());
809
bool AnalysisState::setCamProperty(size_t offset, unsigned int key, const std::string &str)
811
if(offset == activeCamera)
812
setModifyLevel(STATE_MODIFIED_VIEW);
814
setModifyLevel(STATE_MODIFIED_ANCILLARY);
815
return savedCameras[offset]->setProperty(key,str);
818
bool AnalysisState::getUseRelPaths() const
820
return useRelativePathsForSave;
823
void AnalysisState::getBackgroundColour(float &r, float &g, float &b) const
830
void AnalysisState::copyEffects(vector<Effect *> &e) const
833
for(size_t ui=0;ui<effects.size();ui++)
834
e[ui]=effects[ui]->clone();
838
void AnalysisState::setBackgroundColour(float &r, float &g, float &b)
840
if(rBack != r || gBack!=g || bBack!=b)
841
setModifyLevel(STATE_MODIFIED_VIEW);
848
void AnalysisState::setWorldAxisMode(unsigned int mode)
851
setModifyLevel(STATE_MODIFIED_VIEW);
855
void AnalysisState::setCamerasByCopy(vector<Camera *> &c, unsigned int active)
857
setModifyLevel(STATE_MODIFIED_DATA);
860
savedCameras.swap(c);
864
void AnalysisState::setCameraByClone(const Camera *c, unsigned int offset)
866
ASSERT(offset < savedCameras.size());
867
delete savedCameras[offset];
868
savedCameras[offset]=c->clone();
870
if(offset == activeCamera)
871
setModifyLevel(STATE_MODIFIED_VIEW);
873
setModifyLevel(STATE_MODIFIED_ANCILLARY);
876
void AnalysisState::setEffectsByCopy(const vector<const Effect *> &e)
878
setModifyLevel(STATE_MODIFIED_VIEW);
881
effects.resize(e.size());
882
for(size_t ui=0;ui<e.size();ui++)
883
effects[ui] = e[ui]->clone();
888
void AnalysisState::setUseRelPaths(bool useRel)
890
useRelativePathsForSave=useRel;
892
void AnalysisState::setWorkingDir(const std::string &work)
895
setModifyLevel(STATE_MODIFIED_DATA);
900
void AnalysisState::setFilterTreeByClone(const FilterTree &f)
902
setModifyLevel(STATE_MODIFIED_DATA);
906
void AnalysisState::setStashedTreesByClone(const vector<std::pair<string,FilterTree> > &s)
908
setModifyLevel(STATE_MODIFIED_ANCILLARY);
912
void AnalysisState::addStashedTree(const std::pair<string,FilterTree> &s)
914
setModifyLevel(STATE_MODIFIED_ANCILLARY);
915
stashedTrees.push_back(s);
918
void AnalysisState::copyStashedTrees(std::vector<std::pair<string,FilterTree > > &s) const
923
void AnalysisState::copyStashedTree(size_t offset,std::pair<string,FilterTree> &s) const
925
s.first=stashedTrees[offset].first;
926
s.second=stashedTrees[offset].second;
930
void AnalysisState::pushUndoStack()
932
if(undoFilterStack.size() > MAX_UNDO_SIZE)
933
undoFilterStack.pop_front();
935
undoFilterStack.push_back(activeTree);
936
redoFilterStack.clear();
939
void AnalysisState::popUndoStack(bool restorePopped)
941
ASSERT(undoFilterStack.size());
943
//Save the current filters to the redo stack.
944
// note that the copy constructor will generate a clone for us.
945
redoFilterStack.push_back(activeTree);
947
if(redoFilterStack.size() > MAX_UNDO_SIZE)
948
redoFilterStack.pop_front();
952
//Swap the current filter cache out with the undo stack result
953
std::swap(activeTree,undoFilterStack.back());
958
undoFilterStack.pop_back();
960
setModifyLevel(STATE_MODIFIED_DATA);
963
void AnalysisState::popRedoStack()
965
ASSERT(undoFilterStack.size() <=MAX_UNDO_SIZE);
966
undoFilterStack.push_back(activeTree);
969
//Swap the current filter cache out with the redo stack result
970
activeTree.swap(redoFilterStack.back());
973
redoFilterStack.pop_back();
975
setModifyLevel(STATE_MODIFIED_DATA);
979
void AnalysisState::eraseStash(size_t offset)
981
ASSERT(offset < stashedTrees.size());
982
setModifyLevel(STATE_MODIFIED_ANCILLARY);
983
stashedTrees.erase(stashedTrees.begin() + offset);