Vous avez reçu un message "Your GitLab account has been locked ..." ? Pas d'inquiétude : lisez cet article https://docs.gricad-pages.univ-grenoble-alpes.fr/help/unlock/

Application.cpp 54.9 KB
Newer Older
1
2
3
4
/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
5
 * (c) 2001-2018 Univ. Grenoble Alpes, CNRS, TIMC-IMAG UMR 5525 (GMCAO)
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
24
 ****************************************************************************/
25
26
27
28
29

// -- Core stuff
#include "Application.h"
#include "Core.h"
#include "MainWindow.h"
30
31
#include "ExtensionManager.h"
#include "Action.h"
32
#include "HistoryComponent.h"
33
#include "ImageComponent.h"
saubatn's avatar
saubatn committed
34
35
#include "Property.h"
#include "PropertyObject.h"
36
#include "Log.h"
37
38
39

// -- QT stuff
#include <QSettings>
40
#include <QMessageBox>
41
42
43
#include <QFileDialog>
#include <QDomDocument>
#include <QTextStream>
44
#include <QDateTime>
45
46
47

// -- VTK stuff (only for updating progress bar via vtk filters)
#include <vtkAlgorithm.h>
48

49
50
#include<array>

51
// see http://doc.qt.nokia.com/latest/resources.html (bottom)
52
inline void initIcons() {
53
    Q_INIT_RESOURCE(CamiTKIcons);
54
}
55

56
namespace camitk {
57
58

// the main window (static, unique instance, verifies singleton)
59
MainWindow* Application::mainWindow = nullptr;
60
61
62
63
64
QSettings Application::settings(QSettings::IniFormat, QSettings::UserScope, "CamiTK", QString(Core::version).remove(QChar(' ')));
QList<QFileInfo> Application::recentDocuments;
QDir Application::lastUsedDirectory;
int Application::maxRecentDocuments = 0;
QString Application::name = Core::version;
65
66
67
68
int Application::argc = 0;
char** Application::argv = nullptr;
QTranslator* Application::translator = nullptr;
PropertyObject* Application::propertyObject = nullptr;
69
70

// ----------------- constructor --------------------
71
Application::Application(QString name, int& theArgc, char** theArgv, bool autoloadExtensions, bool registerFileExtension) : QApplication(theArgc, theArgv) {
72

73
    //-- generic init
74
75
    this->name = name;
    QApplication::setApplicationName(name);    // associate the QProperty to the Qt meta-object
76

77
    CAMITK_INFO(tr("Starting application..."))
78

79
80
    argc = theArgc;
    argv = theArgv;
81
82
    mainWindow = nullptr;
    translator = nullptr;
83

84
85
    //-- sometimes needed to get the icon when compiled in static mode
    initIcons();
86

87
88
89
    // recommended (see exec() comment in QApplication)
    connect(this, SIGNAL(aboutToQuit()), SLOT(quitting()));
    connect(this, SIGNAL(lastWindowClosed()), SLOT(quitting()));
90

91
    //-- Log extensions
92
93
94
95
    // once the log is setup, only now one can try to load the extensions
    if (autoloadExtensions) {
        ExtensionManager::autoload();
    }
96

97
98
99
    //-- initialize recent/lastUsedDirectory documents from the settings
    settings.beginGroup(name + ".Application");

100
101
    // max memorized recent documents
    maxRecentDocuments = settings.value("maxRecentDocuments", 10).toInt();
102

103
104
105
106
107
108
109
    // the recent documents
    QStringList recentDoc = settings.value("recentDocuments").toStringList();
    recentDocuments.clear();

    foreach (QString fileName, recentDoc) {
        recentDocuments.append(fileName);
    }
110

111
112
113
    // the last used directory
    QDir defaultDir(Core::getTestDataDir());
    lastUsedDirectory = settings.value("lastUsedDirectory", defaultDir.absolutePath()).toString();
114
    settings.endGroup();
115

116
    //-- register file association with this application for opening
117
118
119
    if (registerFileExtension) {
        // TODO : remove ifdef WIN32 as soon as we handle file association for other platform than Windows
        // see ExtensionManager::registerFileExtension method.
120
#ifdef WIN32
121
        // TODO : uses a better way to ask the user so that each component can be selected individually
122
123
124
        // File types association with the application for opening
        QStringList newFileExtensions;
        QStringList fileExtensionsAlreadyRegistered = settings.value("fileExtensionsRegistered").toStringList();
125
        // Forbidden list, to avoid user to have common image file type associated with the application
126
127
128
129
130
131
132
133
134
135
136
        QStringList fileExtensionForbidden;
        fileExtensionForbidden.append("jpg");
        fileExtensionForbidden.append("png");
        fileExtensionForbidden.append("bmp");
        fileExtensionForbidden.append("tif");
        fileExtensionForbidden.append("tiff");

        foreach (QString extensionFile, ExtensionManager::getFileExtensions()) {
            // check the application can handle new file type
            if (!fileExtensionsAlreadyRegistered.contains(extensionFile) && !fileExtensionForbidden.contains(extensionFile)) {
                newFileExtensions.append(extensionFile);
137
            }
138
        }
139

140
141
142
        // check if the application handles new file types
        if (!newFileExtensions.isEmpty()) {
            // new component extension(s) allow(s) the application to handle new file types
143
            // Prompt the user if he wishes those file extension to be associated with this application for default file opening.
144
            // CCC Exception: Use a QMessageBox as the user interaction is required
145
146
147
            QMessageBox msgBox;
            msgBox.setWindowTitle(tr("Associate new file extensions for opening."));
            msgBox.setText(tr("New component extension(s) allow(s) this application to handle new file type(s)\n\n")
148
149
150
                           + newFileExtensions.join(", ") + "\n\n"
                           + tr("Do you want this/these file type(s) to be opened by default with this application")
                           + " (" + QApplication::applicationName() + ") ?\n");
151
152
153
154
            msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
            msgBox.setDefaultButton(QMessageBox::Yes);

            if (msgBox.exec() == QMessageBox::Yes) {
155
156
157
                // user agrees : register each new file type
                foreach (QString fileExtensionToRegister, newFileExtensions) {
                    ExtensionManager::registerFileExtension(fileExtensionToRegister);
158
159
                }
            }
160
161
162
163

            // save the file types in the application's settings in order not to be prompt again
            fileExtensionsAlreadyRegistered.append(newFileExtensions);
            settings.setValue("fileExtensionsRegistered", fileExtensionsAlreadyRegistered);
164
        }
165

166
#endif
167
    }
168

169
    //-- Load the resources of the application (mainly its translation file)
170
    initResources();
171

172
    //-- Add application properties
saubatn's avatar
saubatn committed
173
    createProperties();
174
175
176
177
178
179
180
181
182
183
184

    //-- load all property values from the settings
    propertyObject->loadFromSettings(Application::getName() + ".Application");

    //-- Get notified (in evenFilter method) everytime one of the property is modified
    // All event filter on the property object are delegated to the Application class
    // @see eventFilter()
    propertyObject->installEventFilter(this);

    // trigger change for all the values
    eventFilter(this, new QEvent(QEvent::DynamicPropertyChange));
185
}
186

187
188
189
190
// ----------------- destructor --------------------
Application::~Application() {
    // do not use the destructor to clean or free resources, but quitting()

saubatn's avatar
saubatn committed
191
    // delete property object and all its properties !
192
    if (propertyObject) {
saubatn's avatar
saubatn committed
193
        delete propertyObject;
194
    }
195

196
    // finish all logging properly
197
    CAMITK_INFO(tr("Exiting application..."))
198
199
}

200
// ----------------- destructor --------------------
201
202
203
QString Application::getName() {
    return name;
}
204

205
// ----------------- quitting --------------------
206
void Application::quitting() {
Emmanuel Promayon's avatar
Emmanuel Promayon committed
207
    // this is connected to the aboutToQuit signal from QApplication
208
    // it should contain all the code that frees the resources
209

210
    // delete all actions (they are instantiated when the extension is loaded)
211
    ExtensionManager::unloadAllActionExtensions();
212

213
214
215
    if (translator) {
        delete translator;    //delete instance of internationalization support
    }
216
}
217

218
219
220
// ----------------- notify --------------------
bool Application::notify(QObject* receiver, QEvent* event) {
    bool done = true;
221
    std::exception_ptr otherException;
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
    try {
        done = QApplication::notify(receiver, event);
    }
    catch (const std::exception& e) {
        CAMITK_ERROR(tr("Caught a std exception: %1").arg(e.what()))
    }
    catch (...) {
        CAMITK_ERROR(tr("Caught an unknown exception"))
        otherException = std::current_exception();
        try {
            if (otherException) {
                std::rethrow_exception(otherException);
            }
        }
        catch (const std::exception& e) {
            CAMITK_ERROR(tr("Exception: %1").arg(e.what()))
        }
    }
240

241
242
243
    return done;
}

244
// ----------------- setMainWindow --------------------
245
void Application::setMainWindow(MainWindow* mw) {
246
    if (mw == nullptr) {
247
        mainWindow = new MainWindow(name);
248
249
    }
    else {
250
251
        mainWindow = mw;
    }
252

253
254
255
256
257
258
259
260
    // by default redirect to console
    mainWindow->redirectToConsole(true);

    // Set the locale to C for using dot as decimal point dispite locale
    // Set utf8 for output to enforce using utf8 strings.
    //
    // see http://doc.qt.io/qt-5/qcoreapplication.html#locale-settings
    // and various threads or forum discussions such as http://stackoverflow.com/questions/25661295/why-does-qcoreapplication-call-setlocalelc-all-by-default-on-unix-linux
261
    char* statusOk = setlocale(LC_CTYPE, "C.UTF-8");
262

263
    if (statusOk != nullptr) {
264
265
266
        statusOk = setlocale(LC_NUMERIC, "C.UTF-8");
    }

267
    if (statusOk != nullptr) {
268
269
270
271
272
273
274
        statusOk = setlocale(LC_TIME, "C.UTF-8");
    }

    // try without UTF-8
    if (!statusOk) {
        statusOk = setlocale(LC_CTYPE, "C");

275
        if (statusOk != nullptr) {
276
            statusOk = setlocale(LC_NUMERIC, "C");
277
        }
278

279
        if (statusOk != nullptr) {
280
            statusOk = setlocale(LC_TIME, "C");
281
282
        }

283
        if (statusOk == nullptr) {
284
            CAMITK_ERROR(tr("Could not set the locale to C. This is mandatory to enforce dot as the decimal separator and ensure platform independency of numerics.\nThis can cause a lot of trouble for numerics I/O... Beware of decimal dots...\n"))
285
        }
286
    }
287
}
288

289

290
// ----------------- getMainWindow --------------------
291
MainWindow* Application::getMainWindow() {
292
    if (!mainWindow) {
293
        dynamic_cast<Application*>(qApp)->setMainWindow(nullptr);
294
    }
295

296
297
298
    return mainWindow;
}

299
// ----------------- getSettings --------------------
300
QSettings& Application::getSettings() {
301
302
    return settings;
}
303
304

// ----------------- exec --------------------
305
306
int Application::exec() {
    if (!mainWindow) {
307
        dynamic_cast<Application*>(qApp)->setMainWindow(nullptr);
308
    }
309

310
311
    mainWindow->aboutToShow();
    mainWindow->show();
312

313
314
    return QApplication::exec();
}
315

316
// ----------------- refresh --------------------
317
318
319
void Application::refresh() {
    mainWindow->refresh();
}
320
321

// ----------------- showStatusBarMessage --------------------
322
void Application::showStatusBarMessage(QString msg, int timeout) {
323
324
325
    // if this application has no main window (no GUI)
    // there is no status bar, therefore nothing to do
    if (mainWindow) {
326
        QStatusBar* statusBar = mainWindow->statusBar();
327
328
329

        if (statusBar) {
            statusBar->showMessage(msg, timeout);
330
331
        }
        else {
332
            CAMITK_INFO_ALT(msg);
333
        }
334
335
    }
}
336

337
// ----------------- resetProgressBar --------------------
338
void Application::resetProgressBar() {
339
340
341
    // if this application has no main window (no GUI)
    // there is no status bar, therefore nothing to do
    if (mainWindow) {
342
        QProgressBar* progress = mainWindow->getProgressBar();
343

344
345
346
        if (progress) {
            progress->setValue(0);
        }
347
    }
348
}
349

350
// ----------------- setProgressBarValue --------------------
351
void Application::setProgressBarValue(int value) {
352
353
354
    // if this application has no main window (no GUI)
    // there is no status bar, therefore nothing to do
    if (mainWindow) {
355
        QProgressBar* progress = mainWindow->getProgressBar();
356

357
358
359
        if (progress) {
            progress->setValue(value);
        }
360
    }
361
362
}

363
// ----------------- vtkProgressFunction --------------------
364
void Application::vtkProgressFunction(vtkObject* caller, long unsigned int, void*, void*) {
365
366
367
    // if this application has no main window (no GUI)
    // there is no status bar, therefore nothing to do
    if (mainWindow) {
368
        QProgressBar* progress = mainWindow->getProgressBar();
369
        auto* filter = static_cast<vtkAlgorithm*>(caller);
370
371
372
373
374
        int progressVal = filter->GetProgress() * 100;

        if (progress) {
            progress->setValue(progressVal);
        }
375
    }
376
}
377

378
// ----------------- addRecentDocument --------------------
379
380
381
void Application::addRecentDocument(QFileInfo filename) {
    recentDocuments.removeOne(filename);
    recentDocuments.append(filename);
382

383
384
    // update the last used dir
    lastUsedDirectory = recentDocuments.last().absoluteDir();
385

386
387
    // save settings (the last 10 recent files by default)
    settings.beginGroup(name + ".Application");
388

389
390
    // max memorized recent documents
    settings.setValue("maxRecentDocuments", maxRecentDocuments);
391

392
393
    // save all up to maxRecentDocuments
    int firstOpened = recentDocuments.size() - maxRecentDocuments;
394

395
396
397
    if (firstOpened < 0) {
        firstOpened = 0;
    }
398

399
    QStringList recentDoc;
400

401
402
403
    for (int i = firstOpened; i < recentDocuments.size(); i++) {
        recentDoc.append(recentDocuments[i].absoluteFilePath());
    }
404

405
    settings.setValue("recentDocuments", recentDoc);
406

407
408
409
410
    // last used directory
    settings.setValue("lastUsedDirectory", lastUsedDirectory.absolutePath());
    settings.endGroup();
}
411
412

// ----------------- getLastUsedDirectory --------------------
413
414
415
const QDir Application::getLastUsedDirectory() {
    return lastUsedDirectory;
}
416
417

// ----------------- setLastUsedDirectory --------------------
418
419
420
void Application::setLastUsedDirectory(QDir last) {
    lastUsedDirectory = last;
}
421
422

// ----------------- getRecentDocuments --------------------
423
424
425
const QList< QFileInfo > Application::getRecentDocuments() {
    return recentDocuments;
}
426
427

// ----------------- getMaxRecentDocuments --------------------
428
429
430
const int Application::getMaxRecentDocuments() {
    return maxRecentDocuments;
}
431

432
// -------------------- open --------------------
433
Component* Application::open(const QString& fileName) {
434
435
436
    // set waiting cursor
    setOverrideCursor(QCursor(Qt::WaitCursor));

437
    Component* comp = nullptr;
438
439

    // -- Get the corresponding extension... (compatible format)
440
441
    QFileInfo fileInfo(fileName);

442
    // -- ask the plugin instance to create the Component instance using the
443
    // suffix (i.e. = check "bar" extension for fileName equals to "/path/to/file.foo.bar")
444
    ComponentExtension* componentExtension = ExtensionManager::getComponentExtension(fileInfo.suffix());
445
446
447
448

    // -- try harder: file may be compressed (as in ".foo.gz") so we check the complete extension
    if (componentExtension == nullptr) {
        // here check check "foo.bar" extension for fileName equals to "/path/to/file.foo.bar"
449
        componentExtension = ExtensionManager::getComponentExtension(fileInfo.completeSuffix());
450
    }
451

452
    // -- check the validity of the plugin
453
    if (!componentExtension) {
454
455
456
        // restore the normal cursor/progress bar
        restoreOverrideCursor();
        resetProgressBar();
457

458
        CAMITK_ERROR_ALT(tr("ComponentExtension Opening Error: cannot find the appropriate component plugin for opening:\n\"%1\" (extension \"%2\" or \"%3\")\nTo solve this problem, make sure that:\n - A corresponding valid plugin is present in one of the following directories: \"%4\"\n - Your application loaded the the appropriate extension before trying to open a file")
459
460
461
462
                         .arg(fileName,
                              fileInfo.suffix(),
                              fileInfo.completeSuffix(),
                              Core::getComponentDirectories().join(", ")))
463
464
    }
    else {
465
        std::exception_ptr otherException;
466

467
468
469
        // -- ask the plugin to create the top level component
        try {
            // open using transformed path so that anything looking like C:\\Dir1\\Dir2\\foo.zzz is unified to a C:/Dir1/Dir2/foo.zz
470
            comp = componentExtension->open(QFileInfo(fileName).absoluteFilePath());
471

472
            // -- notify the application
473
            if (comp == nullptr) {
474
                showStatusBarMessage(tr("Error loading file:") + fileName);
475
476
            }
            else {
477
478
479
                // add the document to the recent list
                addRecentDocument(fileName);
                showStatusBarMessage(tr(QString("File " + fileName + " successfully loaded").toStdString().c_str()));
480

481
                CAMITK_WARNING_IF_ALT((!comp->isTopLevel()), tr("Instantiating a NON top level component."))
482

483
484
                // refresh all viewers
                refresh();
485
            }
486

487
        }
488
        catch (AbortException& e) {
489
490
            // restore the normal cursor/progress bar
            restoreOverrideCursor();
491
            resetProgressBar();
492
            CAMITK_ERROR_ALT(tr("Opening aborted: extension: \"%1\"\nError: cannot open file \"%2\"\nReason:\n%3").arg(componentExtension->getName(), fileName, e.what()))
493
            comp = nullptr;
494
        }
495
        catch (std::exception& e) {
496
497
498
            // restore the normal cursor/progress bar
            restoreOverrideCursor();
            resetProgressBar();
499
            CAMITK_ERROR_ALT(tr("Opening aborted: extension: \"%1\"\nError: cannot open file \"%2\"\nExternal exception:\nThis exception was not generated directly by the extension,\nbut by one of its dependency.\nReason:\n%3").arg(componentExtension->getName(), fileName, e.what()))
500
            comp = nullptr;
501
        }
502
        catch (...) {
503
504
505
            // restore the normal cursor/progress bar
            restoreOverrideCursor();
            resetProgressBar();
506
            comp = nullptr;
507

508
            CAMITK_ERROR_ALT(tr("Opening aborted: extension: \"%1\"\nError: cannot open file \"%2\"\nUnknown Reason:\nThis unknown exception was not generated directly by the extension,\nbut by one of its dependency.").arg(componentExtension->getName(), fileName))
509
510
511
512
513
514
515
516
517

            // try harder
            otherException = std::current_exception();
            try {
                if (otherException) {
                    std::rethrow_exception(otherException);
                }
            }
            catch (const std::exception& e) {
518
                CAMITK_ERROR_ALT(tr("Reason:\n%1").arg(e.what()))
519
            }
520
521
        }
    }
522

523
524
525
    // restore the normal cursor/progress bar
    restoreOverrideCursor();
    resetProgressBar();
526

527
528
    return comp;
}
529

530
// -------------------- openDirectory --------------------
531
Component* Application::openDirectory(const QString& dirName, const QString& pluginName) {
532
533
534
    // set waiting cursor
    setOverrideCursor(QCursor(Qt::WaitCursor));

535
    Component* comp = nullptr;
536

537
    ComponentExtension* cp = ExtensionManager::getDataDirectoryComponentExtension(pluginName);
538

539
    if (cp != nullptr) {
540
        std::exception_ptr otherException;
541
542
543
544
545
        // Ask the plugin instance to create the Component instance
        try {
            comp = cp->open(QDir(dirName).absolutePath());

            // -- notify the application
546
            if (comp == nullptr) {
547
                CAMITK_ERROR_ALT(tr("Error loading directory: extension: \"%1\" error: cannot open directory: \"%2\"").arg(cp->getName(), dirName))
548
                showStatusBarMessage(tr("Error loading directory:") + dirName);
549
550
            }
            else {
551
                showStatusBarMessage(tr("Directory %1 successfully loaded").arg(dirName).toStdString().c_str());
552
553
                // refresh all viewers
                refresh();
554
            }
555
        }
556
        catch (AbortException& e) {
557
558
559
            // restore the normal cursor/progress bar
            restoreOverrideCursor();
            resetProgressBar();
560
            CAMITK_ERROR_ALT(tr("Opening aborted: extension: \"%1\" error: cannot open directory: \"%2\"\nReason:\n%3").arg(cp->getName(), dirName, e.what()))
561
            comp = nullptr;
562
        }
563
        catch (...) {
564
565
566
            // restore the normal cursor/progress bar
            restoreOverrideCursor();
            resetProgressBar();
567
            comp = nullptr;
568

569
            CAMITK_ERROR_ALT(tr("Opening aborted: extension \"%1\" error: cannot open directory: \"%2\"\nThis exception was not generated directly by the extension,\nbut by one of its dependency.").arg(cp->getName(), dirName))
570
571
572
573
574
575
576
577
578

            // try harder
            otherException = std::current_exception();
            try {
                if (otherException) {
                    std::rethrow_exception(otherException);
                }
            }
            catch (const std::exception& e) {
579
                CAMITK_ERROR_ALT(tr("Reason:\n%1").arg(e.what()))
580
            }
581
        }
582
583
    }
    else {
584
585
586
        // restore the normal cursor/progress bar
        restoreOverrideCursor();
        resetProgressBar();
587
        CAMITK_ERROR_ALT(tr("Opening Error: Cannot find the appropriate component plugin for opening directory:\n%1\n"
588
589
590
591
                            "To solve this problem, make sure that:\n"
                            " - A corresponding valid plugin is present in one of the component directories: \"%2\"\n"
                            " - And either your application is initialized with the autoloadExtensions option\n"
                            " - Or your correctly registered your component in the CamiTK settings").arg(dirName, Core::getComponentDirectories().join(", ")))
592
    }
593

594
595
596
    // restore the normal cursor/progress bar
    restoreOverrideCursor();
    resetProgressBar();
597

598
599
600

    return comp;
}
601

602
// -------------------------- close ------------------------------
603
bool Application::close(Component* comp) {
604
605
606
607
608
609
    int keyPressed = QMessageBox::Discard;
    bool saveOk = true;
    QString compName = comp->getName();

    // check if the top-level component needs to be saved
    if (comp->getModified()) {
610
611
        // Component was modified, propose to save it
        // CCC Exception: Use a QMessageBox::warning instead of CAMITK_WARNING as the user interaction is required
612
        keyPressed = QMessageBox::warning(nullptr, "Closing...", tr("Component \"") + compName + tr("\" has been modified.\nDo you want to save your change before closing?"), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Save);
613
614
615
616

        // Do we have to save or not?
        if (keyPressed == QMessageBox::Save) {
            saveOk = save(comp);
617
        }
618
    }
619

620
621
622
623
    // Do we have to close or not?
    if (saveOk && keyPressed != QMessageBox::Cancel) {
        // delete the data
        delete comp;
624
        comp = nullptr;
625

626
627
        // refresh all viewers
        refresh();
628

629
630
        showStatusBarMessage(compName + tr(" successfully closed..."));
        return true;
631
632
    }
    else {
633
634
635
        // return that the close was cancelled
        showStatusBarMessage(tr("Close cancelled..."));
        return false;
636
637
    }

638
639
}

640
// -------------------- save --------------------
641
bool Application::save(Component* component) {
642

643
644
645
646
    // no name -> save as
    if (component->getFileName().isEmpty()) {
        return (getAction("Save As")->apply() == Action::SUCCESS);
    }
647

648
649
    // -- Get the corresponding extension... (compatible format)
    QFileInfo fileInfo(component->getFileName());
650

651
    // -- ask the plugin instance to create the Component instance using the
652
    // suffix (i.e. = check "bar" extension for fileName equals to "/path/to/file.foo.bar")
653
    ComponentExtension* componentExtension = ExtensionManager::getComponentExtension(fileInfo.suffix());
654
655
656
657

    // -- try harder: file may be compressed (as in ".foo.gz") so we check the complete extension
    if (componentExtension == nullptr) {
        // here check check "foo.bar" extension for fileName equals to "/path/to/file.foo.bar"
658
        componentExtension = ExtensionManager::getComponentExtension(fileInfo.completeSuffix());
659
    }
660

661
    // -- check the validity of the plugin
662
    if (!componentExtension) {
663
        CAMITK_ERROR_ALT(tr("Saving Error: cannot find the appropriate component plugin for saving component:\n"
664
665
666
667
668
669
670
671
672
673
674
                            "\"%1\"\n"
                            "In file:\n"
                            "\"%1\" (extension \"%2\" or \"%3\")\n"
                            "To solve this problem, make sure that:\n"
                            " - A corresponding valid plugin is present in one of the component directories: \"%4\"\n"
                            " - And either your application is initialized with the autoloadExtensions option\n"
                            " - Or your correctly registered your component in the CamiTK settings")
                         .arg(component->getName(),
                              fileInfo.suffix(),
                              fileInfo.completeSuffix(),
                              Core::getComponentDirectories().join(", ")))
675
        return false;
676
    }
677
    else {
678
        // ready to save
679
        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
680

681
        if (componentExtension->save(component)) {
682

683
684
            // update the last used dir
            setLastUsedDirectory(QFileInfo(component->getFileName()).absoluteDir());
685
            showStatusBarMessage(tr("%1 successfully saved...").arg(component->getName()));
686

687
688
            QApplication::restoreOverrideCursor();
            return true;
689
690
        }
        else {
691
692
            QApplication::restoreOverrideCursor();
            return false;
693
694
        }
    }
695
}
696
697
698


// -------------------- getActionMap --------------------
699
700
QMap<QString, Action*>& Application::getActionMap() {
    static QMap<QString, Action*> actionMap;
701

702
703
    return actionMap;
}
704
705

// -------------------- getActions --------------------
706
707
708
const ActionList Application::getActions() {
    return getActionMap().values();
}
709
710

// -------------------- registerAllActions --------------------
711
int Application::registerAllActions(ActionExtension* ext) {
712
713
    int registered = 0;

714
    foreach (Action* action, ext->getActions()) {
715
716
        // check if an action with same name was not already registered
        if (getActionMap().contains(action->getName())) {
717
            CAMITK_ERROR_ALT(tr("Cannot register action: %1 (extension: %2, family: %3, description: \"%4\")\n"
718
719
720
721
722
723
                                "extension of same name already registered by extension \"%5\"")
                             .arg(action->getName(),
                                  action->getExtensionName(),
                                  action->getFamily(),
                                  action->getDescription(),
                                  getAction(action->getName())->getExtensionName()))
724
725
        }
        else {
726
727
            getActionMap().insert(action->getName(), action);
            registered++;
728
        }
729
730
    }

731
732
733
    return registered;
}

734
// -------------------- unregisterAllActions --------------------
735
int Application::unregisterAllActions(ActionExtension* ext) {
736
    int registered = 0;
737

738
    foreach (Action* action, ext->getActions()) {
739
        getActionMap().remove(action->getName());
740
        registered++;
741
    }
742

743
    return registered;
744
745
}

746
// ---------------- actionLessThan ----------------
747
bool actionLessThan(const camitk::Action* a1, const camitk::Action* a2) {
748
749
750
    // This method is needed by qsort in the sort method to sort action by name
    return a1->getName() < a2->getName();
}
751
752

// ---------------- sort ----------------
753
754
755
756
ActionList Application::sort(ActionSet actionSet) {
    // sort actions by name
    ActionList actionList = actionSet.toList();
    qSort(actionList.begin(), actionList.end(), actionLessThan);
757

758
759
    return actionList;
}
760
761

// ---------------- getAction ----------------
762
Action* Application::getAction(QString name) {
763
764
    return getActionMap().value(name);
}
765
766

// ---------------- getActions ----------------
767
ActionList Application::getActions(Component* component) {
768
    ActionSet actions;
769

770
771
772
    if (component) {
        QStringList componentHierarchy = component->getHierarchy();

773
        foreach (Action* currentAct, Application::getActions()) {
774
775
            if (componentHierarchy.contains(currentAct->getComponent())) {
                actions.insert(currentAct);
776
            }
777
        }
778
779
    }
    else {
780
        foreach (Action* currentAct, Application::getActions()) {
781
782
            if (currentAct->getComponent().isEmpty()) {
                actions.insert(currentAct);
783
784
            }
        }
785
    }
786

787
788
789
    return sort(actions);
}

790
// ---------------- getActions ----------------
791
792
793
ActionList Application::getActions(ComponentList cList) {
    // if this is an empty list, return all action not based on a component
    if (cList.size() == 0) {
794
        return getActions(nullptr);
795
796
    }
    else {