File indexing completed on 2024-05-12 16:23:34

0001 /***************************************************************************
0002  *   Copyright (C) 2005-2017 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 #include "qtfrontend.h"
0021 
0022 #include "frontends/frontend.h"
0023 #include "src/application/externalcommand.h"
0024 #include "src/presentation/frontends/qtfrontend/mainwindowgui.h"
0025 #include "src/foundation/preferencestool.h"
0026 #include "src/foundation/logger.h"
0027 #include "src/foundation/uiexception.h"
0028 #include "src/domain/animation/workspacefile.h"
0029 #include "src/domain/domainfacade.h"
0030 
0031 #include <QApplication>
0032 #include <QLabel>
0033 #include <QMessageBox>
0034 #include <QProgressDialog>
0035 #include <QProgressBar>
0036 #include <QRegularExpression>
0037 #include <QStatusBar>
0038 #include <QTimer>
0039 
0040 #include <cstring>
0041 #include <unistd.h>
0042 #include <stdio.h>
0043 #include <assert.h>
0044 #include <string>
0045 
0046 const char* QtFrontend::VERSION = "0.8";
0047 
0048 QtFrontend::QtFrontend(int &argc, char **argv) {
0049     stApp = new QApplication(argc, argv);
0050 
0051     mw = new MainWindowGUI(stApp);
0052     mw->setWindowTitle("Stopmotion");
0053     mw->showMaximized();
0054 
0055     try {
0056         initializePreferences();
0057         PreferencesTool::get()->flush();
0058     } catch (UiException& ex) {
0059         DomainFacade::getFacade()->getFrontend()->handleException(ex);
0060     }
0061 
0062     mw->ConstructUI();
0063 
0064     progressDialog = 0;
0065     progressBar = 0;
0066     infoText = 0;
0067     timer = 0;
0068 }
0069 
0070 
0071 QtFrontend::~QtFrontend() {
0072     delete mw;
0073     mw = 0;
0074     delete stApp;
0075     stApp = 0;
0076 }
0077 
0078 
0079 int QtFrontend::run(int, char **) {
0080     stApp->connect( stApp, SIGNAL(lastWindowClosed()), stApp, SLOT(quit()) );
0081     return stApp->exec();
0082 }
0083 
0084 
0085 void QtFrontend::showProgress(ProgressMessage message, int numOperations) {
0086     QString msg = tr("Please wait...");
0087     switch (message) {
0088     case connectingCamera:
0089         msg = tr("Connecting camera...");
0090         break;
0091     case importingFramesFromDisk:
0092         msg = tr("Importing frames from disk");
0093         break;
0094     case exporting:
0095         msg = tr("Exporting...");
0096         break;
0097     case restoringProject:
0098         msg = tr("Restoring project...");
0099         break;
0100     case savingScenesToDisk:
0101         msg = tr("Saving scenes to disk...");
0102         break;
0103     }
0104     if (numOperations > 0) {
0105         progressDialog = new QProgressDialog(msg, tr("Cancel"), 0,
0106                 numOperations, mw);
0107         progressDialog->show();
0108     } else {
0109         progressBar = new QProgressBar;
0110         progressBar->setFixedWidth(150);
0111         infoText = new QLabel(msg);
0112         mw->statusBar()->addWidget(infoText);
0113         mw->statusBar()->addWidget(progressBar);
0114         timer = new QTimer();
0115         connect( timer, SIGNAL( timeout() ), this, SLOT( updateProgressBar() ) );
0116         timer->start(10);
0117     }
0118 }
0119 
0120 
0121 void QtFrontend::hideProgress() {
0122     if (progressDialog) {
0123         progressDialog->hide();
0124         delete progressDialog;
0125         progressDialog = NULL;
0126     } else if (progressBar) {
0127         timer->stop();
0128         progressBar->hide();
0129         mw->statusBar()->removeWidget(progressBar);
0130         mw->statusBar()->removeWidget(infoText);
0131         delete progressBar;
0132         progressBar = NULL;
0133         delete infoText;
0134         infoText = NULL;
0135         delete timer;
0136         timer = NULL;
0137     }
0138 }
0139 
0140 
0141 void QtFrontend::updateProgress(int numOperationsDone) {
0142     if (progressDialog) {
0143         progressDialog->setValue(numOperationsDone);
0144     }
0145 }
0146 
0147 
0148 void QtFrontend::setProgressInfo(const char *infoText) {
0149     if (progressDialog) {
0150         progressDialog->setLabelText(infoText);
0151     }
0152 }
0153 
0154 
0155 bool QtFrontend::isOperationAborted() {
0156     if (progressDialog) {
0157         return progressDialog->wasCanceled();
0158     }
0159     return false;
0160 }
0161 
0162 
0163 void QtFrontend::processEvents() {
0164     stApp->processEvents();
0165 }
0166 
0167 
0168 void QtFrontend::updateProgressBar() {
0169     int p = progressBar->value();
0170     progressBar->setValue(++p);
0171 }
0172 
0173 long fileSize(const char* filePath) {
0174     FILE* fh = fopen(filePath, "r");
0175     if (!fh)
0176         return -1;
0177     long result = -1;
0178     if (0 == fseek(fh, 0, SEEK_END)) {
0179         result = ftell(fh);
0180     }
0181     fclose(fh);
0182     return result;
0183 }
0184 
0185 bool QtFrontend::loadPreferencesFrom(PreferencesTool* prefs, const char* path) {
0186     if (prefs->load(path))
0187         return true;
0188     if (0 == access(path, R_OK)) {
0189         // file exists and is readable
0190         if (fileSize(path) < 40) {
0191             // not worth keeping a file this small
0192             return false;
0193         }
0194         int ret = QMessageBox::question(0,
0195                 tr("Lose corrupt file"),
0196                 tr("The file %1 seems to be corrupt, it's contents will be lost if you continue. Do you want to continue?").arg(path),
0197                 QMessageBox::Yes, QMessageBox::No, QMessageBox::NoButton);
0198         if (ret == QMessageBox::No)
0199             throw CriticalError();
0200     }
0201     return false;
0202 }
0203 
0204 void QtFrontend::initializePreferences() {
0205     Logger::get().logDebug("Loading preferencestool");
0206 
0207     PreferencesTool *prefs = PreferencesTool::get();
0208     WorkspaceFile preferencesFile(WorkspaceFile::preferencesFile);
0209     WorkspaceFile oldPrefsFile(WorkspaceFile::preferencesFileOld);
0210 
0211     WorkspaceFile::ensureStopmotionDirectoriesExist();
0212 
0213     bool dirty = false;
0214     bool oldIsDirty = true;
0215     if (!loadPreferencesFrom(prefs, preferencesFile.path())) {
0216         dirty = true;
0217         Logger::get().logWarning("Did not load new prefs");
0218         if (loadPreferencesFrom(prefs, oldPrefsFile.path())) {
0219             oldIsDirty = false;
0220         } else {
0221             Logger::get().logWarning("Did not load old prefs");
0222             prefs->setDefaultPreferences(VERSION);
0223             setDefaultPreferences(prefs);
0224         }
0225     }
0226     if (!prefs->isVersion(VERSION)) {
0227         bool useNewPrefsFile = askQuestion(Frontend::useNewerPreferences);
0228         if (useNewPrefsFile) {
0229             // copy to old
0230             prefs->setSavePath(preferencesFile.path(), oldIsDirty);
0231             prefs->flush();
0232             // Create new default preferences
0233             setDefaultPreferences(prefs);
0234         } else {
0235             // Use and update old preferences
0236             prefs->setVersion(VERSION);
0237             updateOldPreferences(prefs);
0238         }
0239         dirty = true;
0240     }
0241     prefs->setSavePath(preferencesFile.path(), dirty);
0242     prefs->flush();
0243 }
0244 
0245 
0246 void QtFrontend::setDefaultPreferences(PreferencesTool *prefs) {
0247     assert(prefs != NULL);
0248     Logger::get().logDebug("Setting default preferences");
0249 
0250     // Default import options ------------------------------------------------
0251     prefs->setPreference("numberofimports", 5);
0252     prefs->setPreference("activedevice", 1);
0253 
0254     // Default import option 1
0255     prefs->setPreference("importname0", tr("vgrabbj").toUtf8().constData());
0256     prefs->setPreference("importdescription0",
0257             tr("The simplest setting. Fairly slow").toUtf8().constData());
0258     prefs->setPreference("importprepoll0",
0259             "vgrabbj -f $IMAGEFILE -d $VIDEODEVICE -b -D 0 -i vga");
0260     prefs->setPreference("importstartdaemon0", "");
0261     prefs->setPreference("importstopdaemon0", "");
0262 
0263     // Default import option 2
0264     prefs->setPreference("importname1", tr("vgrabbj VGA daemon").toUtf8().constData());
0265     prefs->setPreference("importdescription1",
0266             tr("Starts vgrabbj as a daemon. Pretty fast.").toUtf8().constData());
0267     prefs->setPreference("importprepoll1", "");
0268     prefs->setPreference("importstartdaemon1",
0269             "vgrabbj -f $IMAGEFILE -d $VIDEODEVICE -b -D 0 -i vga -L250000");
0270     prefs->setPreference("importstopdaemon1",
0271             "kill $(pidof vgrabbj)");
0272 
0273     // Default import option 3
0274     prefs->setPreference("importname2", tr("uvccapture").toUtf8().constData());
0275     prefs->setPreference("importdescription2",
0276             tr("Grabbing from V4L2 devices").toUtf8().constData());
0277     prefs->setPreference("importprepoll2",
0278             "uvccapture -d$VIDEODEVICE -x640 -y480 -o$IMAGEFILE");
0279     prefs->setPreference("importstartdaemon2", "");
0280     prefs->setPreference("importstopdaemon2", "");
0281 
0282     // Default import option 4
0283     prefs->setPreference("importname3", tr("videodog singleshot").toUtf8().constData());
0284     prefs->setPreference("importdescription3",
0285             tr("Videodog.").toUtf8().constData());
0286     prefs->setPreference("importprepoll3",
0287             "videodog -x 640 -y 480 -w 3 -d $VIDEODEVICE -j -f $IMAGEFILE");
0288     prefs->setPreference("importstartdaemon3", "");
0289     prefs->setPreference("importstopdaemon3", "");
0290 
0291     // Default import option 5
0292     prefs->setPreference("importname4", tr("dvgrab").toUtf8().constData());
0293     prefs->setPreference("importdescription4",
0294             tr("Grabbing from DV-cam.").toUtf8().constData());
0295     prefs->setPreference("importprepoll4", "");
0296     prefs->setPreference("importstartdaemon4",
0297             "dvgrab --format jpeg --jpeg-overwrite --jpeg-temp $(tempfile) "
0298             "--every 25 $IMAGEFILE &");
0299     prefs->setPreference("importstopdaemon4",
0300             "kill -2 $(pidof dvgrab)");
0301     // -----------------------------------------------------------------------
0302 
0303     // Default export options ------------------------------------------------
0304     prefs->setPreference("numEncoders", 5);
0305     prefs->setPreference("activeEncoder", 3);
0306 
0307     // Default export option 1
0308     prefs->setPreference("encoderName0", "mencoder");
0309     prefs->setPreference("encoderDescription0",
0310             tr("Exports from jpeg images to mpeg1 video").toUtf8().constData());
0311     prefs->setPreference("startEncoder0",
0312             "mencoder \"mf://$IMAGEPATH/*.jpg\" -mf w=640:h=480:fps=$FRAMERATE:type=jpg "
0313             "-ovc lavc -lavcopts vcodec=mpeg1video -oac copy -o \"$VIDEOFILE\"");
0314     prefs->setPreference("stopEncoder0", "");
0315 
0316     // Default export option 2
0317     prefs->setPreference("encoderName1", "mencoder");
0318     prefs->setPreference("encoderDescription1",
0319             tr("Exports from jpeg images to mpeg2 video").toUtf8().constData());
0320     prefs->setPreference("startEncoder1",
0321             "mencoder \"mf://$IMAGEPATH/*.jpg\" -mf w=640:h=480:fps=$FRAMERATE:type=jpg "
0322             "-ovc lavc -lavcopts vcodec=mpeg2video -oac copy -o \"$VIDEOFILE\"");
0323     prefs->setPreference("stopEncoder1", "");
0324 
0325     // Default export option 3
0326     prefs->setPreference("encoderName2", "mencoder");
0327     prefs->setPreference("encoderDescription2",
0328             tr("Exports from jpeg images to mpeg4 video").toUtf8().constData());
0329     prefs->setPreference("startEncoder2",
0330             "mencoder -ovc lavc -lavcopts vcodec=msmpeg4v2:vpass=1:$opt -mf type=jpg:fps=$FRAMERATE "
0331             "-o \"$VIDEOFILE\" \"mf://$IMAGEPATH/*.jpg\"");
0332     prefs->setPreference("stopEncoder2", "");
0333 
0334     // Default export option 4
0335     prefs->setPreference("encoderName3", "avconv");
0336     prefs->setPreference("encoderDescription3",
0337             tr("Exports from jpeg images to mpeg4 video").toUtf8().constData());
0338     prefs->setPreference("startEncoder3",
0339             "avconv -y -r $FRAMERATE -i \"$IMAGEPATH/%06d.jpg\" -b 6000 \"$VIDEOFILE\"");
0340     prefs->setPreference("stopEncoder3", "");
0341 
0342     // Default export option 5 -- ffmpeg
0343     prefs->setPreference("encoderName4", "ffmpeg");
0344     prefs->setPreference("encoderDescription4",
0345             tr("Exports from jpeg images to mpeg4 video").toUtf8().constData());
0346     prefs->setPreference("startEncoder4",
0347             "ffmpeg -y -framerate $FRAMERATE -i \"$IMAGEPATH/%06d.jpg\" -codec:v mpeg4 -b:v 6k \"$VIDEOFILE\"");
0348     prefs->setPreference("stopEncoder4", "");
0349 }
0350 
0351 void QtFrontend::handleException(UiException& e) {
0352     bool unhandled = false;
0353     switch (e.error()) {
0354     case UiException::IsWarning:
0355         switch (e.warning()) {
0356         case UiException::unsupportedImageType:
0357             QMessageBox::warning(0, tr("Unsupported image file type"),
0358                     tr("Only JPeg image files can be added to the animation"));
0359             return;
0360         case UiException::invalidAudioFormat:
0361             QMessageBox::warning(0, tr("The selected audio file could not be loaded"),
0362                     tr("Perhaps it is corrupt."));
0363             return;
0364         case UiException::couldNotOpenFile:
0365             QMessageBox::warning(0,
0366                     tr("Cannot open the selected file for reading"),
0367                     tr("The file %1 could not be opened").arg(e.string()));
0368             return;
0369         case UiException::failedToCopyFilesToWorkspace:
0370             QMessageBox::warning(0,
0371                     tr("Could not copy file to workspace"),
0372                     tr("Failed to copy the following files to the workspace (~/.stopmotion): %1").arg(e.string()));
0373             return;
0374         case UiException::failedToInitializeAudioDriver:
0375             QMessageBox::warning(0,
0376                     tr("Failed to initialize audio driver"),
0377                     tr("Sound will not work until this is corrected"));
0378             break;
0379         case UiException::failedToWriteToPreferencesFile:
0380             QMessageBox::warning(0,
0381                     tr("Failed to write preferences"),
0382                     tr("Could not write preferences to file: %1").arg(e.string()));
0383             break;
0384         default:
0385             unhandled = true;
0386             break;
0387         }
0388         break;
0389     case UiException::failedToGetExclusiveLock:
0390         QMessageBox::critical(0,
0391                 tr("Stopmotion cannot be started."),
0392                 tr("Failed to get exclusive lock on command.log. Perhaps Stopmotion is already running."));
0393         break;
0394     case UiException::preferencesFileUnreadable:
0395         QMessageBox::critical(0,
0396                 tr("Preferences file cannot be read"),
0397                 tr("Preferences file %1 is unreadable. Please correct this and try again.").arg(e.string()));
0398         break;
0399     case UiException::preferencesFileMalformed:
0400         QMessageBox::critical(0,
0401                 tr("Preferences file cannot be read"),
0402                 tr("Preferences file %1 is not a valid XML preferences file. Please correct this or delete the file and try again.").arg(e.string()));
0403         break;
0404     case UiException::ArbitraryError:
0405         QMessageBox::critical(0, tr("Fatal"), e.what());
0406         break;
0407     default:
0408         unhandled = true;
0409         break;
0410     }
0411     if (unhandled) {
0412         QMessageBox::critical(0,
0413                 tr("Stopmotion threw an exception it could not handle."),
0414                 tr("Please raise a bug report."));
0415         throw CriticalError();
0416     }
0417 }
0418 
0419 void QtFrontend::reportWarning(const char *message) {
0420     QMessageBox::warning(0, tr("Warning"), message);
0421 }
0422 
0423 void QtFrontend::updateOldPreferences(PreferencesTool *prefs) {
0424     // Replace all occurrences of '(DEFAULTPATH)' with '$IMAGEFILE'  (version 0.3 and 0.4)
0425     // Replace all occurrences of '/dev/xxx' with $VIDEODEVICE (version < 0.7)
0426     int numImports = prefs->getPreference("numberofimports", 1);
0427     for (int i = 0; i < numImports; ++i) {
0428         Preference startPref(QString("importstartdaemon%1")
0429                 .arg(i).toUtf8().constData(), "");
0430         std::string start(startPref.get());
0431         int index = start.find("(DEFAULTPATH)");
0432         if (index != -1) {
0433             start.replace(index, (int) strlen("(DEFAULTPATH)"),
0434                     std::string("$IMAGEFILE"));
0435         }
0436         QString s(start.c_str());
0437         s.replace( QRegularExpression("/dev/(v4l/){0,1}video[0-9]{0,1}"),
0438                 QString("$VIDEODEVICE") );
0439         prefs->setPreference( QString("importstartdaemon%1")
0440                 .arg(i).toUtf8().constData(), s.toUtf8().constData());
0441 
0442         Preference prepollPref(QString("importprepoll%1")
0443                 .arg(i).toUtf8().constData(), "");
0444         std::string prepoll(prepollPref.get());
0445         index = prepoll.find("(DEFAULTPATH)");
0446         if (index != -1) {
0447             prepoll.replace(index, (int) strlen("(DEFAULTPATH)"),
0448                     std::string("$IMAGEFILE"));
0449         }
0450         QString ss(prepoll.c_str());
0451         ss.replace( QRegularExpression("/dev/(v4l/){0,1}video[0-9]{0,1}"), QString("$VIDEODEVICE") );
0452         prefs->setPreference( QString("importprepoll%1").arg(i).toUtf8().constData(), ss.toUtf8().constData());
0453     }
0454 }
0455 
0456 
0457 bool QtFrontend::askQuestion(Question question) {
0458     QString text;
0459     switch (question) {
0460     case useNewerPreferences:
0461         text = tr(
0462                 "A newer version of the preferences file with few more default\n"
0463                 "values exists. Do you want to use this one? (Your old preferences\n "
0464                 "will be saved in ~/.stopmotion/preferences.xml.OLD)");
0465         break;
0466     }
0467     int ret = QMessageBox::question(0,
0468             tr("Question"), text,
0469             QMessageBox::Yes, QMessageBox::No, QMessageBox::NoButton);
0470     return ret == QMessageBox::Yes;
0471 }
0472 
0473 
0474 int QtFrontend::runExternalCommand(const char *command) {
0475     ExternalCommand *ec = new ExternalCommand;
0476     ec->show();
0477     ec->run( QString::fromLocal8Bit(command) );
0478     return 0;
0479 }
0480 
0481 
0482 void QtFrontend::setUndoRedoEnabled() {
0483     mw->activateMenuOptions();
0484 }
0485 
0486 void QtFrontend::openProject(const char* file) {
0487     mw->openProject(file);
0488 }