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 }