File: PersistenceManager.cpp

package info (click to toggle)
camitk 6.0.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 389,496 kB
  • sloc: cpp: 103,476; sh: 2,448; python: 1,618; xml: 984; makefile: 128; perl: 84; sed: 20
file content (442 lines) | stat: -rw-r--r-- 17,540 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * 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$
 ****************************************************************************/
#include "PersistenceManager.h"

#include <QString>
#include <QMap>
#include <QVariant>
#include <QList>
#include <QMetaObject>
#include <QMetaProperty>
#include <QMetaType>
#include <QSettings>
#include <QDir>
#include <QFileInfo>
#include <QRegularExpression>

#include "Component.h"
#include "CamiTKFile.h"
#include "Application.h"
#include "TransformationManager.h"
#include "Viewer.h"
#include "MainWindow.h"
#include "PropertyObject.h"
#include "HistoryItem.h"
#include "Action.h"
#include "Log.h"

namespace camitk {

// -------------------- saveWorkspace --------------------
bool PersistenceManager::saveWorkspace(QString filepath) {
    // Get the workspace filepath to compute relative paths for components
    QFileInfo fileInfo(filepath);

    // Create a CamiTKFile
    CamiTKFile file;

    // Save Frames and Transformations data to it
    file.addContent("transformationManager", TransformationManager::toVariant());

    // Add all components to it
    file.addContent("components", fromComponents(Application::getTopLevelComponents(), fileInfo.absoluteDir()));

    // Add all actions that were used
    // From History, get all HistoryItems, get Action names
    // Get those actions and save their properties if they exist
    auto history = Application::getHistory();
    QVariantMap appliedActions;
    for (const HistoryItem& item : history) {
        QString actionName = item.getName();
        if (! appliedActions.contains(actionName)) {
            Action* action = Application::getAction(actionName);
            if (action != nullptr) {
                QVariant actionVariant = action->toVariant();
                if (actionVariant.isValid()) {
                    appliedActions.insert(actionName, actionVariant);
                }
            }
        }
    }
    file.addContent("actions", appliedActions);


    // Add all active viewers
    QVariantMap viewersProperties;
    for (Viewer* v : Application::getViewers()) {
        const PropertyObject* viewerPropertyObject = v->getPropertyObject();
        if (viewerPropertyObject != nullptr) {
            QVariant viewerPropertiesVariant = viewerPropertyObject->toVariant();
            if (viewerPropertiesVariant.isValid()) { // Do not save if the viewer does not have properties to save
                viewersProperties.insert(v->getName(), viewerPropertiesVariant);
            }
        }
    }
    file.addContent("viewers", QVariant(viewersProperties));

    // Add Application properties
    // TODO Not all data (stored in settings) is there (e.g. maxRecentDocuments) -> use InterfacePersistence
    const PropertyObject* applicationProperties = Application::getPropertyObject();
    if (applicationProperties != nullptr) {
        file.addContent(Application::getName(), applicationProperties->toVariant());
    }

    // Add MainWindow properties // TODO save only relevant properties
    const MainWindow* window = Application::getMainWindow();
    if (window != nullptr) {
        file.addContent("mainwindow", fromProperties(window));
    }

    // Write the file
    return file.save(filepath);
}

// -------------------- loadWorkspace --------------------
bool PersistenceManager::loadWorkspace(QString filepath) {
    // Load a CamiTK file
    CamiTKFile file = CamiTKFile::load(filepath);
    if (!file.isValid()) {
        CAMITK_WARNING_ALT("Cannot load CamiTK file " + filepath);
        return false;
    }

    // Is the version supported ?
    if (file.getVersion().toStdString() != CamiTKFile::version) {
        CAMITK_WARNING_ALT("Cannot load CamiTK file " + filepath + "\nUnsupported file format version " + file.getVersion());
        return false;
    }

    QFileInfo fileInfo(filepath);

    // Load frames and transformations
    if (file.hasContent("transformationManager")) {
        TransformationManager::fromVariant(file.getContent("transformationManager"));
    }

    // Load components
    if (file.hasContent("components")) {
        loadComponents(file.getContent("components"), fileInfo.absoluteDir());
    }

    // Load action's properties // DISABLED for now, too many side effects (e.g. setting null components)
    /* if (file.hasContent("actions")) {
        QVariantMap actionsMap = file.getContent("actions").toMap();
        for(QString& actionName : actionsMap.keys()){
            Action *action = Application::getAction(actionName);
            if(action != nullptr){
                loadProperties(action, actionsMap.value(actionName));
            }
        }
    }*/

    // Load viewers' properties
    if (file.hasContent("viewers")) {
        QVariantMap viewersMap = file.getContent("viewers").toMap();
        for (QString& viewerName : viewersMap.keys()) {
            Viewer* viewer = Application::getViewer(viewerName);
            if (viewer != nullptr) {
                viewer->getPropertyObject()->fromVariant(viewersMap.value(viewerName));
            }
        }
    }

    // Load Application properties
    if (file.hasContent(Application::getName())) {
        Application::getPropertyObject()->fromVariant(file.getContent(Application::getName()));
    }

    // Load MainWindow properties
    MainWindow* window = Application::getMainWindow();
    if (file.hasContent("mainwindow") && window != nullptr) {
        loadProperties(window, file.getContent("mainwindow"));
    }

    return true;
}

// -------------------- fromComponents --------------------
QVariant PersistenceManager::fromComponents(QList<Component*> comps, QDir rootPath) {
    QVariantList compsVariant;
    for (Component* c : comps) {
        QVariant compVariant = c->toVariant();
        if (compVariant.userType() == QMetaType::QVariantMap) {
            QVariantMap compVariantMap = compVariant.toMap();
            if (compVariantMap.contains("filename")) {
                compVariantMap.insert("filename", rootPath.relativeFilePath(compVariantMap.value("filename").toString()));
            }
            compsVariant.push_back(QVariant(compVariantMap));
        }
        else {
            compsVariant.push_back(compVariant);
        }
    }
    return compsVariant;
}

// -------------------- loadComponents --------------------
bool PersistenceManager::loadComponents(QVariant comps, QDir rootPath) {
    if (comps.userType() != QMetaType::QVariantList) {
        CAMITK_WARNING_ALT("CamiTKFile components is not a list !");
        return false;
    }

    for (QVariant& comp : comps.toList()) {
        // For each component, we open it first from its filepath, then we ask it to load itself
        if (comp.userType() == QMetaType::QVariantMap && comp.toMap().contains("filename") && !comp.toMap().value("filename").toString().isEmpty()) { // Just ignore it if it is invalid
            Component* c = Application::open(rootPath.absoluteFilePath(comp.toMap().value("filename").toString()), true);
            if (!c) {
                CAMITK_WARNING_ALT("Could not load component from filename " + comp.toMap().value("filename").toString());
            }
            else {
                c->fromVariant(comp);
            }
        }
    }
    return true;
}

// -------------------- fromProperties --------------------
QVariant PersistenceManager::fromProperties(const QObject* qobj) {
    if (qobj == nullptr) {
        return QVariant();
    }

    QVariantMap properties;
    const QMetaObject* metaObj = qobj->metaObject();
    // Static properties (declared in the object class using QT_PROPERTY macro)
    for (int i = 0; i < metaObj->propertyCount(); ++i) {
        auto prop = metaObj->property(i);
        // Only writable properties
        if (prop.isWritable() && QString(prop.name()) != "objectName") {
            // try to convert specific known types
            QString convertedValue = variantToString(prop.read(qobj));
            if (convertedValue.isNull()) {
                // conversion was not possible
                properties[prop.name()] = prop.read(qobj);
            }
            else {
                // specific types was found, store it as a QString (that has a specific format)
                properties[prop.name()] = convertedValue;
            }
        }
    }
    // Dynamic properties, added at runtime using QObject::setProperty and/or camitk::Property
    auto dynPropsNames = qobj->dynamicPropertyNames();
    for (auto name : dynPropsNames) {
        auto value = qobj->property(name);
        // We know that invoking getProperty won't change our const object, but the function itself does not accept a const
        // --> using a const_cast
        Property* camitkProperty = Property::getProperty(const_cast<QObject*>(qobj), QString(name));
        if (camitkProperty == nullptr || !camitkProperty->getReadOnly()) {
            // try to convert specific known types
            QString convertedValue = variantToString(value);
            if (convertedValue.isNull()) {
                // conversion was not possible
                properties[QString(name)] = value;
            }
            else {
                // specific types was found, store it as a QString (that has a specific format)
                properties[QString(name)] = convertedValue;
            }
        }
    }
    if (properties.isEmpty()) {
        return QVariant();
    }
    else {
        return properties;
    }
}

// -------------------- loadProperties --------------------
void PersistenceManager::loadProperties(QObject* qobj, QVariant props) {
    QVariantMap properties = props.toMap();
    auto dynPropsNames = qobj->dynamicPropertyNames();

    for (QString& prop : properties.keys()) {
        QVariant oldValue = qobj->property(prop.toStdString().c_str());
        if (oldValue.isValid()) {
            // Property exists
            QVariant newValue = properties.value(prop);
            updateVariantValueWhilePreservingType(oldValue, newValue, prop);
            // oldValue was filled with newValue converted to the right type
            qobj->setProperty(prop.toStdString().c_str(), oldValue);
        }
        else {
            // new property
            qobj->setProperty(prop.toStdString().c_str(), properties.value(prop));
        }
    }
}

// -------------------- getUuidFromProperties --------------------
QUuid PersistenceManager::getUuidFromProperties(const QObject* qobj) {
    QVariant uuid = qobj->property("uuid");
    if (!uuid.isValid()) {
        return QUuid();
    }
    else {
        return uuid.toUuid();
    }
}

// -------------------- setUuidInProperties --------------------
bool PersistenceManager::setUuidInProperties(QObject* qobj, QUuid uuid) {
    QVariant currentId = qobj->property("uuid");
    // If a valid value already exists, do not set !
    if (!currentId.isValid() || currentId.toUuid().isNull()) {
        qobj->setProperty("uuid", QVariant(uuid));
        return true;
    }
    else {
        return false;
    }

}

// -------------------- updateVariantValueWhilePreservingType --------------------
void PersistenceManager::updateVariantValueWhilePreservingType(QVariant& variant, QVariant& newValue, QString name) {
    QVariant convertedValue;
    //If the variant is a QVariantList, replace the current list
    switch (newValue.userType()) {
        case QMetaType::QVariantList:
            if (variant.userType() == QMetaType::QVariantList) {
                // Does the old variant have the same type ?
                if (variant.toList().size() > 0) {
                    // If there is an element in there, use its type
                    int targetTypeId = variant.toList()[0].userType();
                    QVariantList newList = newValue.toList();
                    for (QVariant& v : newList) {
                        v.convert(targetTypeId);
                    }
                    // Now replace the old list
                    variant = newList;
                }
                else { // No known type, just copy !
                    variant = newValue;
                }
            }
            else {
                // the old variant is not a list ! Warn but store it anyway
                CAMITK_TRACE_ALT(QString("Updating the value of '%1' of original type %2, to type QVariantList. Original type will not be preserved.").arg(name).arg(variant.userType()));
                variant = newValue;
            }
            break;
        case QMetaType::QVariantMap :
            // If the variant is a QVariantMap call recursively for all values
            if (variant.userType() == QMetaType::QVariantMap) {
                QVariantMap newMap = newValue.toMap();
                QVariantMap oldMap = variant.toMap();
                for (QString& key : newMap.keys()) {
                    if (oldMap.contains(key)) {
                        // The key is already there, try to keep the same type
                        updateVariantValueWhilePreservingType(oldMap[key], newMap[key], key);
                    }
                    else {
                        // The key is not there, just insert a copy of the new item;
                        oldMap[key] = newMap[key];
                    }
                }
                // Replace oldVariant with updated map
                variant = oldMap;
            }
            else {
                // old variant is not a QVariantMap -> warn and replace
                CAMITK_TRACE_ALT(QString("Updating the value of '%1' of original type %2, to type QVariantMap. Original type will not be preserved.").arg(name).arg(variant.userType()));
                variant = newValue;
            }
            break;

        case QMetaType::QString:
            // Try to convert to specific supported types
            convertedValue = stringToVariant(newValue.toString());
            if (convertedValue.isValid()) {
                newValue = convertedValue;
            }
        // Do NOT break: if the string was converted to a Variant of an unexpected type, go on trying to convert it in 'default'

        default:
            // Try to convert
            if (newValue.canConvert(variant.userType())) {
                convertedValue = newValue;
                convertedValue.convert(variant.userType());
                variant = convertedValue;
            }
            else {
                CAMITK_TRACE_ALT(QString("Updating the value of '%1' of original type %2 (original value '%3'), to type %4 (new value: '%5'). Original type will not be preserved.").arg(name).arg(variant.userType()).arg(variant.toString()).arg(newValue.userType()).arg(newValue.toString()));
                variant = newValue;
            }
    }
}

// -------------------- variantToString --------------------
QString PersistenceManager::variantToString(const QVariant& variant) {
    QString value;
    // Check for supported types
    switch (variant.userType()) {
        case QMetaType::QRect: {
            QRect r = variant.toRect();
            value = QString::asprintf("@Rect(%d %d %d %d)", r.x(), r.y(), r.width(), r.height());
            break;
        }
        case QMetaType::QByteArray: {
            QByteArray a = variant.toByteArray();
            value = QLatin1String("@ByteArray(") + QLatin1String(a.constData(), a.size()) + QLatin1Char(')');
            break;
        }
        default:
            // nothing to do the value.isNull() is true
            break;
    }
    return value;
}

// -------------------- stringToVariant --------------------
QVariant PersistenceManager::stringToVariant(QString value) {
    QVariant variant;

    // Check the different supported types
    if (value.startsWith("@Rect(")) {
        // the destination type is QMetaType::QRect, check if we can convert using custom conversion
        QRegularExpression regex("\\((\\d+)(?:\\s*)(\\d+)(?:\\s*)(\\d+)(?:\\s*)(\\d+)\\)");
        // Check if the regex matches the input string
        QRegularExpressionMatch match = regex.match(value);
        if (match.hasMatch()) {
            // Extract QRect integer values from the captured groups
            variant = QVariant(QRect(match.captured(1).toInt(), match.captured(2).toInt(), match.captured(3).toInt(), match.captured(4).toInt()));
        }
    }
    else {
        if (value.startsWith(QLatin1String("@ByteArray("))) {
            // The destination type is QMetaType::QByteArray:
            // Extract the raw data inside the (..)
            variant = QVariant(value.mid(11, value.size() - 12).toLatin1());
        }
    }
    return variant;
}


} //namespace