File indexing completed on 2024-05-12 16:23:25
0001 /*************************************************************************** 0002 * Copyright (C) 2005-2014 by Linuxstopmotion contributors; * 0003 * see the AUTHORS file for details. * 0004 * * 0005 * This program is free software; you can redistribute it and/or modify * 0006 * it under the terms of the GNU General Public License as published by * 0007 * the Free Software Foundation; either version 2 of the License, or * 0008 * (at your option) any later version. * 0009 * * 0010 * This program is distributed in the hope that it will be useful, * 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0013 * GNU General Public License for more details. * 0014 * * 0015 * You should have received a copy of the GNU General Public License * 0016 * along with this program; if not, write to the * 0017 * Free Software Foundation, Inc., * 0018 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * 0019 ***************************************************************************/ 0020 0021 #include "animation.h" 0022 0023 #include "src/foundation/logger.h" 0024 #include "src/foundation/uiexception.h" 0025 #include "src/technical/audio/qtaudiodriver.h" 0026 #include "src/technical/video/videofactory.h" 0027 #include "src/technical/projectserializer.h" 0028 #include "workspacefile.h" 0029 #include "scenevector.h" 0030 #include "sound.h" 0031 #include "scene.h" 0032 #include "frame.h" 0033 #include "src/domain/observernotifier.h" 0034 #include "src/domain/animation/errorhandler.h" 0035 #include "src/domain/undo/commandadd.h" 0036 #include "src/domain/undo/executor.h" 0037 #include "src/domain/undo/addallcommands.h" 0038 #include "src/domain/undo/filelogger.h" 0039 #include "src/presentation/frontends/frontend.h" 0040 #include "src/technical/audio/audiodriver.h" 0041 #include "src/technical/stringiterator.h" 0042 0043 #include <assert.h> 0044 #include <stdlib.h> 0045 #include <string.h> 0046 #include <errno.h> 0047 #include <unistd.h> 0048 #include <vector> 0049 #include <sstream> 0050 #include <string> 0051 #include <memory> 0052 0053 0054 class UndoRedoObserver; 0055 0056 namespace { 0057 bool ensureUnlinked(const char* path) { 0058 return 0 == unlink(path) || errno == ENOENT; 0059 } 0060 0061 class ErrorCapture : public ErrorHandler { 0062 UiException* exception; 0063 public: 0064 ErrorCapture() : exception(0) { 0065 } 0066 ~ErrorCapture() { 0067 delete exception; 0068 } 0069 void error(UiException e) { 0070 // We will only bother recording the first exception we come across. 0071 // Better is clearly possible. 0072 if (!exception) { 0073 exception = new UiException(e); 0074 } 0075 } 0076 void throwException() { 0077 if (exception) { 0078 throw *exception; 0079 } 0080 } 0081 }; 0082 } 0083 0084 FailedToInitializeCommandLogger::FailedToInitializeCommandLogger() { 0085 } 0086 0087 const char* FailedToInitializeCommandLogger::what() const throw () { 0088 return "Failed to initialize command logger"; 0089 } 0090 0091 Animation::Animation() 0092 : scenes(0), executor(0), logger(0), serializer(0), audioDriver(0), 0093 isAudioDriverInitialized(false), frontend(0) { 0094 std::unique_ptr<SceneVector> scs(new SceneVector); 0095 std::unique_ptr<ObserverNotifier> on(new ObserverNotifier(scs.get(), 0)); 0096 scs.release(); 0097 std::unique_ptr<ProjectSerializer> szer(new ProjectSerializer); 0098 std::unique_ptr<AudioDriver> ad(new QtAudioDriver()); 0099 std::unique_ptr<Executor> ex(makeAnimationCommandExecutor(*on)); 0100 std::unique_ptr<FileCommandLogger> lgr(new FileCommandLogger); 0101 ex->setCommandLogger(lgr->getLogger()); 0102 scenes = on.release(); 0103 serializer = szer.release(); 0104 executor = ex.release(); 0105 logger = lgr.release(); 0106 audioDriver = ad.release(); 0107 } 0108 0109 0110 Animation::~Animation() { 0111 delete scenes; 0112 scenes = NULL; 0113 delete serializer; 0114 serializer = NULL; 0115 delete audioDriver; 0116 audioDriver = NULL; 0117 delete executor; 0118 executor = NULL; 0119 delete logger; 0120 logger = NULL; 0121 } 0122 0123 0124 void Animation::addFrames(int scene, int frame, 0125 StringIterator& frameNames) { 0126 if (sceneCount() == 0) { 0127 newScene(0); 0128 } 0129 0130 int count = frameNames.count(); 0131 CommandAddFactory::Parameters params(scene, frame, count); 0132 bool showingProgress = 1 < count; 0133 if (showingProgress) { 0134 frontend->showProgress(Frontend::importingFramesFromDisk, count); 0135 } 0136 std::string error; 0137 // error.empty() is false if string is "\0"! So we set this explicitly on error. 0138 bool isError = false; 0139 int added = 0; 0140 for (; !frameNames.atEnd(); frameNames.next()) { 0141 try { 0142 params.addFrame(frameNames.get()); 0143 ++added; 0144 } catch (UiException& e) { 0145 if (e.warning() != UiException::failedToCopyFilesToWorkspace) 0146 throw; 0147 if (!isError) { 0148 error = frameNames.get(); 0149 isError = true; 0150 } else { 0151 error += ", "; 0152 error += frameNames.get(); 0153 } 0154 } 0155 if (frontend->isOperationAborted()) { 0156 return; 0157 } 0158 if (showingProgress) 0159 frontend->updateProgress(added); 0160 } 0161 if (0 < added) { 0162 executor->execute(Commands::addFrames, params); 0163 params.retainFiles(); 0164 } 0165 if (showingProgress) 0166 frontend->hideProgress(); 0167 if (isError) 0168 throw UiException(UiException::failedToCopyFilesToWorkspace, error.c_str()); 0169 } 0170 0171 0172 void Animation::removeFrames(int32_t scene, int32_t frame, int32_t count) { 0173 assert(0 <= count); 0174 assert(frame + count <= frameCount(scene)); 0175 executor->execute(Commands::removeFrames, scene, frame, count); 0176 } 0177 0178 0179 void Animation::moveFrames(int32_t fromScene, int32_t fromFrame, 0180 int32_t count, int32_t toScene, int32_t toFrame) { 0181 assert(0 <= count); 0182 assert(fromFrame + count <= frameCount(fromScene)); 0183 assert(toFrame <= frameCount(toScene)); 0184 if (toScene == fromScene 0185 && fromFrame <= toFrame && toFrame < fromFrame + count) { 0186 // Attempt to move frames into the same position; ineffective. 0187 return; 0188 } 0189 executor->execute(Commands::moveFrames, 0190 fromScene, fromFrame, count, 0191 toScene, toFrame); 0192 } 0193 0194 0195 void Animation::addSound(int32_t scene, int32_t frameNumber, 0196 const char *soundFile) { 0197 TemporaryWorkspaceFile soundFileWs(soundFile, 0198 WorkspaceFileType::sound()); 0199 std::unique_ptr<Sound> sound(new Sound()); 0200 std::stringstream ss; 0201 std::stringstream::pos_type zeroOffset = ss.tellp(); 0202 ss << "Sound " << WorkspaceFile::getSoundNumber(); 0203 int size = (ss.tellp() - zeroOffset) + 1; 0204 char* soundName = new char[size]; 0205 std::string sc = ss.str(); 0206 strncpy(soundName, sc.c_str(), size); 0207 const char* oldName = sound->setName(soundName); 0208 assert(oldName == NULL); 0209 int32_t index = soundCount(scene, frameNumber); 0210 executor->execute(Commands::addSound, scene, frameNumber, 0211 index, soundFileWs.basename(), soundName); 0212 soundFileWs.retainFile(); 0213 WorkspaceFile::nextSoundNumber(); 0214 } 0215 0216 0217 void Animation::removeSound(int32_t sceneNumber, int32_t frameNumber, 0218 int32_t soundNumber) { 0219 executor->execute(Commands::removeSound, 0220 sceneNumber, frameNumber, soundNumber); 0221 } 0222 0223 0224 void Animation::setSoundName(int32_t sceneNumber, int32_t frameNumber, 0225 int32_t soundNumber, const char *soundName) { 0226 executor->execute(Commands::renameSound, sceneNumber, 0227 frameNumber, soundNumber, soundName); 0228 } 0229 0230 0231 int Animation::frameCount() const { 0232 int modelSize = 0; 0233 int s = sceneCount(); 0234 for (int i = 0; i < s; ++i) { 0235 modelSize += frameCount(i); 0236 } 0237 return modelSize; 0238 } 0239 0240 0241 int Animation::frameCount(int sceneNumber) const { 0242 if (sceneNumber > -1 && sceneNumber < sceneCount()) { 0243 return scenes->frameCount(sceneNumber); 0244 } 0245 return 0; 0246 } 0247 0248 0249 int Animation::sceneCount() const { 0250 return scenes->sceneCount(); 0251 } 0252 0253 0254 const char* Animation::getProjectFile() { 0255 return serializer->getProjectFile(); 0256 } 0257 0258 0259 void Animation::clear() { 0260 logger->setLogFile(0); 0261 scenes->clear(); 0262 executor->clearHistory(); 0263 WorkspaceFile::clear(); 0264 } 0265 0266 0267 void Animation::setScenes(const std::vector<Scene*>& sv) { 0268 int count = sv.size(); 0269 scenes->preallocateScenes(count); 0270 scenes->clear(); 0271 for (int i = 0; i != count; ++i) { 0272 scenes->addScene(i, sv[i]); 0273 } 0274 } 0275 0276 bool Animation::loadFromDat(const char* filename, 0277 const char* projectFilename) { 0278 std::vector<Scene*> sv; 0279 if (ProjectSerializer::openDat(sv, filename)) { 0280 setScenes(sv); 0281 serializer->resetProjectFile(projectFilename); 0282 return true; 0283 } 0284 return false; 0285 } 0286 0287 0288 void Animation::openProject(const char *filename) { 0289 logger->setLogFile(0); 0290 clear(); 0291 initializeCommandLog(); 0292 assert(filename != 0); 0293 vector<Scene*> newScenes = serializer->openSto(filename); 0294 setScenes(newScenes); 0295 } 0296 0297 FILE* Animation::initializeCommandLog() { 0298 WorkspaceFile commandLogger(WorkspaceFile::commandLogFile); 0299 FILE* fh = fopen(commandLogger.path(), "w"); 0300 if (!fh) 0301 throw FailedToInitializeCommandLogger(); 0302 logger->setLogFile(fh); 0303 return fh; 0304 } 0305 0306 void Animation::saveProject(const char *filename) { 0307 assert(filename != 0); 0308 WorkspaceFile newDat(WorkspaceFile::newModelFile); 0309 if (!ensureUnlinked(newDat.path())) { 0310 Logger::get().logWarning("newModelFile not removed prior to saving"); 0311 } 0312 serializer->save(filename, *scenes, frontend); 0313 WorkspaceFile currentDat(WorkspaceFile::currentModelFile); 0314 if (!ensureUnlinked(currentDat.path())) { 0315 Logger::get().logWarning("currentModelFile not removed after saving"); 0316 } 0317 logger->setLogFile(0); 0318 initializeCommandLog(); 0319 if (rename(newDat.path(), currentDat.path()) < 0) 0320 throw FailedToInitializeCommandLogger(); 0321 executor->clearHistory(); 0322 } 0323 0324 0325 void Animation::newProject() { 0326 WorkspaceFile newDat(WorkspaceFile::newModelFile); 0327 WorkspaceFile currentDat(WorkspaceFile::currentModelFile); 0328 if (!ensureUnlinked(newDat.path()) 0329 || !ensureUnlinked(currentDat.path())) { 0330 throw FailedToInitializeCommandLogger(); 0331 } 0332 clear(); 0333 serializer->resetProjectFile(); 0334 initializeCommandLog(); 0335 } 0336 0337 const char* Animation::getImagePath(int scene, int frame) const { 0338 return scenes->getScene(scene)->getFrame(frame)->getImagePath(); 0339 } 0340 0341 const char* Animation::getSoundName(int scene, int frame, 0342 int soundNumber) const { 0343 return scenes->getScene(scene)->getFrame(frame)->getSoundName(soundNumber); 0344 } 0345 0346 bool Animation::isUnsavedChanges() { 0347 return executor->canUndo(); 0348 } 0349 0350 void Animation::setImagePath(int32_t sceneNumber, int32_t frameNumber, 0351 const char* newImagePath) { 0352 TemporaryWorkspaceFile twf(newImagePath,WorkspaceFileType::image()); 0353 executor->execute(Commands::setImage, sceneNumber, frameNumber, twf.basename()); 0354 twf.retainFile(); 0355 } 0356 0357 void Animation::duplicateImage(int32_t sceneNumber, int32_t frameNumber) { 0358 const char* currentPath = getImagePath(sceneNumber, frameNumber); 0359 TemporaryWorkspaceFile twf(currentPath, WorkspaceFileType::image(), 0360 TemporaryWorkspaceFile::forceCopy); 0361 executor->execute(Commands::setImage, sceneNumber, frameNumber, twf.basename()); 0362 twf.retainFile(); 0363 } 0364 0365 void Animation::attach(Observer* o) { 0366 scenes->addObserver(o); 0367 } 0368 0369 void Animation::detach(Observer* o) { 0370 scenes->removeObserver(o); 0371 } 0372 0373 void Animation::registerFrontend(Frontend* fe) { 0374 scenes->registerFrontend(fe); 0375 frontend = fe; 0376 } 0377 0378 Frontend* Animation::getFrontend() { 0379 return frontend; 0380 } 0381 0382 int Animation::soundCount(int scene, int frame) const { 0383 return scenes->soundCount(scene, frame); 0384 } 0385 0386 void Animation::playSounds(int scene, int frame) const { 0387 scenes->playSounds(scene, frame, audioDriver); 0388 } 0389 0390 void Animation::newScene(int32_t index) { 0391 executor->execute(Commands::addScene, index); 0392 } 0393 0394 0395 void Animation::removeScene(int32_t sceneNumber) { 0396 assert(sceneNumber >= 0); 0397 executor->execute(Commands::removeScene, sceneNumber); 0398 } 0399 0400 0401 void Animation::moveScene(int32_t sceneNumber, int32_t movePosition) { 0402 if (sceneNumber != movePosition) { 0403 executor->execute(Commands::moveScene, sceneNumber, movePosition); 0404 } 0405 } 0406 0407 0408 bool Animation::initAudioDevice() { 0409 isAudioDriverInitialized = audioDriver->initialize(); 0410 if (!isAudioDriverInitialized && 0 < scenes->soundCount()) { 0411 UiException e(UiException::failedToInitializeAudioDriver); 0412 // don't want to throw here because we aren't in an exception handler 0413 frontend->handleException(e); 0414 } 0415 return isAudioDriverInitialized; 0416 } 0417 0418 0419 void Animation::shutdownAudioDevice() { 0420 audioDriver->shutdown(); 0421 isAudioDriverInitialized = false; 0422 } 0423 0424 0425 bool Animation::exportToVideo(VideoEncoder * encoder, int playbackSpeed) { 0426 VideoFactory factory(scenes); 0427 frontend->showProgress(Frontend::exporting, 0); 0428 if (factory.createVideoFile(encoder, playbackSpeed) != NULL) { 0429 frontend->hideProgress(); 0430 return true; 0431 } 0432 frontend->hideProgress(); 0433 return false; 0434 } 0435 0436 0437 bool Animation::exportToCinerella(const char*) { 0438 return false; 0439 } 0440 0441 void Animation::accept(FileNameVisitor& v) const { 0442 scenes->accept(v); 0443 } 0444 0445 void Animation::undo() { 0446 executor->undo(); 0447 } 0448 0449 void Animation::redo() { 0450 executor->redo(); 0451 } 0452 0453 void Animation::clearHistory() { 0454 executor->clearHistory(); 0455 } 0456 0457 void Animation::resync(std::exception& e) { 0458 if (frontend) 0459 frontend->reportWarning(e.what()); 0460 scenes->resync(); 0461 } 0462 0463 void Animation::resync(UiException& e) { 0464 if (frontend) 0465 frontend->handleException(e); 0466 scenes->resync(); 0467 } 0468 0469 const char* Animation::getSoundPath(int scene, int frame, int sound) const { 0470 return scenes->getScene(scene)->getSound(frame, sound)->getSoundPath(); 0471 } 0472 0473 int Animation::soundCount() const { 0474 return scenes->soundCount(); 0475 } 0476 0477 void Animation::setCommandLoggerFile(FILE* file) { 0478 logger->setLogFile(file); 0479 } 0480 0481 class GetLine { 0482 FILE* fh; 0483 char* buffer; 0484 size_t size; 0485 public: 0486 GetLine(FILE* handle) : fh(handle), buffer(0), size(0) { 0487 } 0488 ~GetLine() { 0489 free(buffer); 0490 } 0491 const char* get() const { 0492 return buffer; 0493 } 0494 bool next() { 0495 return 0 < getline(&buffer, &size, fh); 0496 } 0497 }; 0498 0499 void Animation::replayCommandLog(FILE* file) { 0500 long startPos = ftell(file); 0501 long length = 0; 0502 if (frontend && startPos != -1 && 0 == fseek(file, 0, SEEK_END)) { 0503 length = ftell(file); 0504 if (0 != fseek(file, startPos, SEEK_SET)) 0505 throw FileException("replayCommandLog", errno); 0506 if (length < 1000) { 0507 length = 0; 0508 } else { 0509 frontend->showProgress(Frontend::restoringProject, length - startPos); 0510 } 0511 } 0512 ErrorCapture handler; 0513 GetLine lineIterator(file); 0514 int r = 0; 0515 while (0 < (r = lineIterator.next())) { 0516 executor->executeFromLog(lineIterator.get(), handler); 0517 if (0 < length) { 0518 long pos = ftell(file) - startPos; 0519 frontend->updateProgress(pos); 0520 } 0521 } 0522 if (0 < length) 0523 frontend->hideProgress(); 0524 if (r < 0) 0525 throw FileException("replayCommandLog", errno); 0526 handler.throwException(); 0527 } 0528 0529 bool Animation::canUndo() { 0530 return executor->canUndo(); 0531 } 0532 0533 bool Animation::canRedo() { 0534 return executor->canRedo(); 0535 } 0536 0537 void Animation::setUndoRedoObserver(UndoRedoObserver* observer) { 0538 executor->setUndoRedoObserver(observer); 0539 }