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 }