Pencil2D  ff90c0872e88be3bf81c548cd60f01983012ec49
Pencil2D is an animation software for both bitmap and vector graphics. It is free, multi-platform, and open source.
 All Classes Functions
filemanager.cpp
1 /*
2 
3 Pencil - Traditional Animation Software
4 Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5 Copyright (C) 2012-2017 Matthew Chiawen Chang
6 
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; version 2 of the License.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15 
16 */
17 
18 
19 #include "filemanager.h"
20 #include "pencildef.h"
21 #include "JlCompress.h"
22 #include "fileformat.h"
23 #include "object.h"
24 
25 
26 FileManager::FileManager( QObject *parent ) : QObject( parent ),
27  mLog( "SaveLoader" )
28 {
29  ENABLE_DEBUG_LOG( mLog, true );
30 }
31 
32 Object* FileManager::load( QString strFileName )
33 {
34  if ( !QFile::exists( strFileName ) )
35  {
36  qCDebug( mLog ) << "ERROR - File doesn't exist.";
37  return cleanUpWithErrorCode( Status::FILE_NOT_FOUND );
38  }
39 
40  emit progressUpdated( 0.f );
41 
42  Object* obj = new Object;
43  obj->setFilePath( strFileName );
44  obj->createWorkingDir();
45 
46  QString strMainXMLFile;
47  QString strDataFolder;
48 
49  // Test file format: new zipped .pclx or old .pcl?
50  bool oldFormat = isOldForamt( strFileName );
51 
52  if ( oldFormat )
53  {
54  qCDebug( mLog ) << "Recognized Old Pencil File Format (*.pcl) !";
55 
56  strMainXMLFile = strFileName;
57  strDataFolder = strMainXMLFile + "." + PFF_OLD_DATA_DIR;
58  }
59  else
60  {
61  qCDebug( mLog ) << "Recognized New zipped Pencil File Format (*.pclx) !";
62 
63  unzip( strFileName, obj->workingDir() );
64 
65  strMainXMLFile = QDir( obj->workingDir() ).filePath( PFF_XML_FILE_NAME );
66  strDataFolder = QDir( obj->workingDir() ).filePath( PFF_DATA_DIR );
67  }
68 
69  qDebug() << "XML=" << strMainXMLFile;
70  qDebug() << "Data Folder=" << strDataFolder;
71  qDebug() << "Working Folder=" << obj->workingDir();
72 
73  obj->setDataDir( strDataFolder );
74  obj->setMainXMLFile( strMainXMLFile );
75 
76  QFile file( strMainXMLFile );
77  if ( !file.open( QFile::ReadOnly ) )
78  {
79  return cleanUpWithErrorCode( Status::ERROR_FILE_CANNOT_OPEN );
80  }
81 
82  qCDebug( mLog ) << "Checking main XML file...";
83  QDomDocument xmlDoc;
84  if ( !xmlDoc.setContent( &file ) )
85  {
86  return cleanUpWithErrorCode( Status::ERROR_INVALID_XML_FILE );
87  }
88 
89  QDomDocumentType type = xmlDoc.doctype();
90  if ( !( type.name() == "PencilDocument" || type.name() == "MyObject" ) )
91  {
92  return cleanUpWithErrorCode( Status::ERROR_INVALID_PENCIL_FILE );
93  }
94 
95  QDomElement root = xmlDoc.documentElement();
96  if ( root.isNull() )
97  {
98  return cleanUpWithErrorCode( Status::ERROR_INVALID_PENCIL_FILE );
99  }
100 
101  // Create object.
102  qCDebug( mLog ) << "Start to load object..";
103 
104  loadPalette( obj );
105 
106  bool ok = true;
107 
108  if ( root.tagName() == "document" )
109  {
110  ok = loadObject( obj, root );
111  }
112  else if ( root.tagName() == "object" || root.tagName() == "MyOject" ) // old Pencil format (<=0.4.3)
113  {
114  ok = loadObjectOldWay( obj, root );
115  }
116 
117  if ( !ok )
118  {
119  delete obj;
120  return cleanUpWithErrorCode( Status::ERROR_INVALID_PENCIL_FILE );
121  }
122 
123  verifyObject( obj );
124 
125  return obj;
126 }
127 
128 bool FileManager::loadObject( Object* object, const QDomElement& root )
129 {
130  QDomElement e = root.firstChildElement( "object" );
131  if ( e.isNull() )
132  {
133  return false;
134  }
135 
136  bool isOK = true;
137  for ( QDomNode node = root.firstChild(); !node.isNull(); node = node.nextSibling() )
138  {
139  QDomElement element = node.toElement(); // try to convert the node to an element.
140  if ( element.isNull() )
141  {
142  continue;
143  }
144 
145  if ( element.tagName() == "object" )
146  {
147  qCDebug( mLog ) << "Load object";
148  isOK = object->loadXML( element, [this] ( float f )
149  {
150  emit progressUpdated( f );
151  } );
152  }
153  else if ( element.tagName() == "editor" || element.tagName() == "projectdata" )
154  {
155  ObjectData* projectData = loadProjectData( element );
156  object->setData( projectData );
157  }
158  else
159  {
160  Q_ASSERT( false );
161  }
162  //progress = std::min( progress + 10, 100 );
163  //emit progressValueChanged( progress );
164  }
165 
166  return isOK;
167 }
168 
169 bool FileManager::loadObjectOldWay( Object* object, const QDomElement& root )
170 {
171  return object->loadXML( root, [this]( float f )
172  {
173  emit progressUpdated( f );
174  } );
175 }
176 
177 bool FileManager::isOldForamt( const QString& fileName )
178 {
179  QStringList zippedFileList = JlCompress::getFileList( fileName );
180  return ( zippedFileList.empty() );
181 }
182 
183 Status FileManager::save( Object* object, QString strFileName )
184 {
185  QStringList debugDetails = QStringList() << "FileManager::save" << QString( "strFileName = " ).append( strFileName );
186  if ( object == nullptr )
187  {
188  return Status( Status::INVALID_ARGUMENT, debugDetails << "object parameter is null" );
189  }
190 
191  QFileInfo fileInfo( strFileName );
192  if ( fileInfo.isDir() )
193  {
194  debugDetails << "strFileName points to a directory";
195  return Status( Status::INVALID_ARGUMENT,
196  debugDetails,
197  tr( "Invalid Save Path" ),
198  tr( "The file path you have specified (\"%1\") points to a directory, so the file cannot be saved." ).arg( fileInfo.absoluteFilePath() ) );
199  }
200  if ( fileInfo.exists() && !fileInfo.isWritable() )
201  {
202  debugDetails << "strFileName points to a file that is not writable";
203  return Status( Status::INVALID_ARGUMENT,
204  debugDetails ,
205  tr( "Invalid Save Path" ),
206  tr( "The file path you have specified (\"%1\") cannot be written to, so the file cannot be saved. Please make sure that you have sufficient permissions to save to that location and try again." ).arg( fileInfo.absoluteFilePath() ) );
207  }
208 
209  QString strTempWorkingFolder;
210  QString strMainXMLFile;
211  QString strDataFolder;
212 
213  bool isOldFile = strFileName.endsWith( PFF_OLD_EXTENSION );
214  if ( isOldFile )
215  {
216  qCDebug( mLog ) << "Save in Old Pencil File Format (*.pcl) !";
217 
218  strMainXMLFile = strFileName;
219  strDataFolder = strMainXMLFile + "." + PFF_OLD_DATA_DIR;
220  }
221  else
222  {
223  qCDebug( mLog ) << "Save in New zipped Pencil File Format (*.pclx) !";
224 
225  strTempWorkingFolder = object->workingDir();
226  Q_ASSERT( QDir( strTempWorkingFolder ).exists() );
227  debugDetails << QString( "strTempWorkingFolder = " ).append( strTempWorkingFolder );
228 
229  qCDebug( mLog ) << "Temp Folder=" << strTempWorkingFolder;
230  strMainXMLFile = QDir( strTempWorkingFolder ).filePath( PFF_XML_FILE_NAME );
231  strDataFolder = QDir( strTempWorkingFolder ).filePath( PFF_OLD_DATA_DIR );
232  }
233 
234  QFileInfo dataInfo( strDataFolder );
235  if ( !dataInfo.exists() )
236  {
237  QDir dir( strDataFolder ); // the directory where filePath is or will be saved
238 
239  // creates a directory with the same name +".data"
240  if( !dir.mkpath( strDataFolder ) )
241  {
242  debugDetails << QString( "dir.absolutePath() = %1" ).arg( dir.absolutePath() );
243  if( isOldFile ) {
244  return Status( Status::ERROR_FILE_CANNOT_OPEN, debugDetails, tr( "Cannot Create Data Directory" ), tr( "Cannot create the data directory at \"%1\". Please make sure that you have sufficient permissions to save to that location and try again. Alternatively try saving as pclx format." ).arg( strDataFolder ) );
245  }
246  else {
247  return Status( Status::FAIL, debugDetails, tr("Internal Error"), tr( "Cannot create the data directory at temporary location \"%1\". Please make sure that you have sufficient permissions to save to that location and try again. Alternatively try saving as pcl format." ).arg( strDataFolder ) );
248  }
249  }
250  }
251  if( !dataInfo.isDir() )
252  {
253  debugDetails << QString( "dataInfo.absoluteFilePath() = ").append(dataInfo.absoluteFilePath());
254  if( isOldFile )
255  {
256  return Status( Status::ERROR_FILE_CANNOT_OPEN, debugDetails, tr( "Cannot Create Data Directory" ), tr( "Cannot use the path \"%1\" as a data directory since that currently points to a file. Please move or delete that file and try again. Alternatively try saving with the pclx format." ).arg( dataInfo.absoluteFilePath() ) );
257  }
258  else
259  {
260  return Status( Status::FAIL, debugDetails, tr( "Internal Error" ), tr( "Cannot use the data directory at temporary location \"%1\" since it is a file. Please move or delete that file and try again. Alternatively try saving with the pcl format." ).arg( dataInfo.absoluteFilePath() ) );
261  }
262  }
263 
264  // save data
265  int layerCount = object->getLayerCount();
266  debugDetails << QString("layerCount = %1").arg(layerCount);
267  qCDebug( mLog ) << QString( "Total layers = %1" ).arg( layerCount );
268 
269  bool isOkay = true;
270  for ( int i = 0; i < layerCount; ++i )
271  {
272  Layer* layer = object->getLayer( i );
273  qCDebug( mLog ) << QString( "Saving Layer %1" ).arg( i ).arg( layer->mName );
274 
275  //progressValue = (i * 100) / nLayers;
276  //progress.setValue( progressValue );
277  debugDetails << QString("layer[%1] = Layer[id=%2, name=%3, type=%4]").arg( i ).arg( layer->id() ).arg( layer->name() ).arg( layer->type() );
278  switch ( layer->type() )
279  {
280  case Layer::BITMAP:
281  case Layer::VECTOR:
282  case Layer::SOUND:
283  {
284  Status st = layer->save( strDataFolder );
285  if( !st.ok() )
286  {
287  isOkay = false;
288  QStringList layerDetails = st.detailsList();
289  for ( QString detail : layerDetails )
290  {
291  detail.prepend( "&nbsp;&nbsp;" );
292  }
293  debugDetails << QString( "- Layer[%1] failed to save" ).arg( i ) << layerDetails;
294  }
295  break;
296  }
297  case Layer::CAMERA:
298  break;
299  case Layer::UNDEFINED:
300  case Layer::MOVIE:
301  Q_ASSERT( false );
302  break;
303  }
304  if( !isOkay )
305  {
306  return Status( Status::FAIL, debugDetails, tr( "Internal Error" ), tr( "An internal error occurred while trying to save the file. Some or all of your file may not have saved." ) );
307  }
308  }
309 
310  // save palette
311  object->savePalette( strDataFolder );
312 
313  // -------- save main XML file -----------
314  QScopedPointer<QFile> file( new QFile( strMainXMLFile ) );
315  if ( !file->open( QFile::WriteOnly | QFile::Text ) )
316  {
317  return Status::ERROR_FILE_CANNOT_OPEN;
318  }
319 
320  QDomDocument xmlDoc( "PencilDocument" );
321  QDomElement root = xmlDoc.createElement( "document" );
322  QDomProcessingInstruction encoding = xmlDoc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");
323  xmlDoc.appendChild( encoding );
324  xmlDoc.appendChild( root );
325 
326  // save editor information
327  QDomElement projectDataElement = saveProjectData( object->data(), xmlDoc );
328  root.appendChild( projectDataElement );
329  qCDebug( mLog ) << "Save Project Data";
330 
331  // save object
332  QDomElement objectElement = object->saveXML( xmlDoc );
333  root.appendChild( objectElement );
334  qCDebug( mLog ) << "Save Object Node";
335 
336  const int IndentSize = 2;
337 
338  QTextStream out( file.data() );
339  xmlDoc.save( out, IndentSize );
340 
341  if ( !isOldFile )
342  {
343  qCDebug( mLog ) << "Now compressing data to PFF - PCLX ...";
344 
345  bool ok = JlCompress::compressDir( strFileName, strTempWorkingFolder );
346  if ( !ok )
347  {
348  return Status::FAIL;
349  }
350 
351  qCDebug( mLog ) << "Compressed. File saved.";
352  }
353 
354  object->setFilePath( strFileName );
355  object->setModified( false );
356 
357  return Status::OK;
358 }
359 
360 ObjectData* FileManager::loadProjectData( const QDomElement& docElem )
361 {
362  ObjectData* data = new ObjectData;
363  if ( docElem.isNull() )
364  {
365  return data;
366  }
367 
368  QDomNode tag = docElem.firstChild();
369 
370  while ( !tag.isNull() )
371  {
372  QDomElement element = tag.toElement(); // try to convert the node to an element.
373  if ( element.isNull() )
374  {
375  continue;
376  }
377 
378  extractProjectData( element, data );
379 
380  tag = tag.nextSibling();
381  }
382  return data;
383 }
384 
385 
386 QDomElement FileManager::saveProjectData( ObjectData* data, QDomDocument& xmlDoc )
387 {
388  QDomElement rootTag = xmlDoc.createElement( "projectdata" );
389 
390  // Current Frame
391  QDomElement currentFrameTag = xmlDoc.createElement( "currentFrame" );
392  currentFrameTag.setAttribute( "value", data->getCurrentFrame() );
393  rootTag.appendChild( currentFrameTag );
394 
395  // Current Colour
396  QDomElement currentColorTag = xmlDoc.createElement( "currentColor" );
397  QColor color = data->getCurrentColor();
398  currentColorTag.setAttribute( "r", color.red() );
399  currentColorTag.setAttribute( "g", color.green() );
400  currentColorTag.setAttribute( "b", color.blue() );
401  currentColorTag.setAttribute( "a", color.alpha() );
402  rootTag.appendChild( currentColorTag );
403 
404  // Current Layer
405  QDomElement currentLayerTag = xmlDoc.createElement( "currentLayer" );
406  currentLayerTag.setAttribute( "value", data->getCurrentLayer() );
407  rootTag.appendChild( currentLayerTag );
408 
409  // Current View
410  QDomElement currentViewTag = xmlDoc.createElement( "currentView" );
411  QTransform view = data->getCurrentView();
412  currentViewTag.setAttribute( "m11", view.m11() );
413  currentViewTag.setAttribute( "m12", view.m12() );
414  currentViewTag.setAttribute( "m21", view.m21() );
415  currentViewTag.setAttribute( "m22", view.m22() );
416  currentViewTag.setAttribute( "dx", view.dx() );
417  currentViewTag.setAttribute( "dy", view.dy() );
418  rootTag.appendChild( currentViewTag );
419 
420  // Fps
421  QDomElement fpsTag = xmlDoc.createElement( "fps" );
422  fpsTag.setAttribute( "value", data->getFrameRate() );
423  rootTag.appendChild( fpsTag );
424 
425  // Current Layer
426  QDomElement tagIsLoop = xmlDoc.createElement( "isLoop" );
427  tagIsLoop.setAttribute( "value", data->isLooping() ? "true" : "false" );
428  rootTag.appendChild( tagIsLoop );
429 
430  // Current Layer
431  QDomElement tagRangedPlayback = xmlDoc.createElement( "isRangedPlayback" );
432  tagRangedPlayback.setAttribute( "value", data->isRangedPlayback() ? "true" : "false" );
433  rootTag.appendChild( tagRangedPlayback );
434 
435  // Current Layer
436  QDomElement tagMarkInFrame = xmlDoc.createElement( "markInFrame" );
437  tagMarkInFrame.setAttribute( "value", data->getMarkInFrameNumber() );
438  rootTag.appendChild( tagMarkInFrame );
439 
440  // Current Layer
441  QDomElement tagMarkOutFrame = xmlDoc.createElement( "markOutFrame" );
442  tagMarkOutFrame.setAttribute( "value", data->getMarkOutFrameNumber() );
443  rootTag.appendChild( tagMarkOutFrame );
444 
445  return rootTag;
446 }
447 
448 void FileManager::extractProjectData( const QDomElement& element, ObjectData* data )
449 {
450  Q_ASSERT( data );
451 
452  QString strName = element.tagName();
453  if ( strName == "currentFrame" )
454  {
455  data->setCurrentFrame( element.attribute( "value" ).toInt() );
456  }
457  else if ( strName == "currentColor" )
458  {
459  int r = element.attribute( "r", "255" ).toInt();
460  int g = element.attribute( "g", "255" ).toInt();
461  int b = element.attribute( "b", "255" ).toInt();
462  int a = element.attribute( "a", "255" ).toInt();
463 
464  data->setCurrentColor( QColor( r, g, b, a ) );
465  }
466  else if ( strName == "currentLayer" )
467  {
468  data->setCurrentLayer( element.attribute( "value", "0" ).toInt() );
469  }
470  else if ( strName == "currentView" )
471  {
472  double m11 = element.attribute( "m11", "1" ).toDouble();
473  double m12 = element.attribute( "m12", "0" ).toDouble();
474  double m21 = element.attribute( "m21", "0" ).toDouble();
475  double m22 = element.attribute( "m22", "1" ).toDouble();
476  double dx = element.attribute( "dx", "0" ).toDouble();
477  double dy = element.attribute( "dy", "0" ).toDouble();
478 
479  data->setCurrentView( QTransform( m11, m12, m21, m22, dx, dy ) );
480  }
481  else if ( strName == "fps" || strName == "currentFps" )
482  {
483  data->setFrameRate( element.attribute( "value", "12" ).toInt() );
484  }
485  else if ( strName == "isLoop" )
486  {
487  data->setLooping ( element.attribute( "value", "false" ) == "true" );
488  }
489  else if ( strName == "isRangedPlayback" )
490  {
491  data->setRangedPlayback( ( element.attribute( "value", "false" ) == "true" ) );
492  }
493  else if ( strName == "markInFrame" )
494  {
495  data->setMarkInFrameNumber( element.attribute( "value", "0" ).toInt() );
496  }
497  else if ( strName == "markOutFrame" )
498  {
499  data->setMarkOutFrameNumber( element.attribute( "value", "15" ).toInt() );
500  }
501 }
502 
503 Object* FileManager::cleanUpWithErrorCode( Status error )
504 {
505  mError = error;
506  removePFFTmpDirectory( mstrLastTempFolder );
507  return nullptr;
508 }
509 
510 bool FileManager::loadPalette( Object* obj )
511 {
512  qCDebug( mLog ) << "Load Palette..";
513 
514  QString paletteFilePath = obj->dataDir() + "/" + PFF_PALETTE_FILE;
515  if ( !obj->importPalette( paletteFilePath ) )
516  {
517  obj->loadDefaultPalette();
518  }
519  return true;
520 }
521 
522 void FileManager::unzip( const QString& strZipFile, const QString& strUnzipTarget )
523 {
524  // --removes an old decompression directory first - better approach
525  removePFFTmpDirectory( strUnzipTarget );
526 
527  // --creates a new decompression directory
528  JlCompress::extractDir( strZipFile, strUnzipTarget );
529 
530  mstrLastTempFolder = strUnzipTarget;
531 }
532 
533 QList<ColourRef> FileManager::loadPaletteFile( QString strFilename )
534 {
535  QFileInfo fileInfo( strFilename );
536  if ( !fileInfo.exists() )
537  {
538  return QList<ColourRef>();
539  }
540 
541  // TODO: Load Palette.
542  return QList<ColourRef>();
543 }
544 
545 Status FileManager::verifyObject( Object* obj )
546 {
547  // check current layer.
548  int curLayer = obj->data()->getCurrentLayer();
549  int maxLayer = obj->getLayerCount();
550  if ( curLayer >= maxLayer )
551  {
552  obj->data()->setCurrentLayer(maxLayer - 1);
553  }
554 
555  return Status::OK;
556 }
Definition: layer.h:32
Definition: object.h:71