umldoc.cpp 113 KB
Newer Older
Thibault Normand's avatar
Thibault Normand committed
1
/***************************************************************************
2
3
4
5
 *  This program is free software; you can redistribute it and/or modify   *
 *  it under the terms of the GNU General Public License as published by   *
 *  the Free Software Foundation; either version 2 of the License, or      *
 *  (at your option) any later version.                                    *
Thibault Normand's avatar
Thibault Normand committed
6
 *                                                                         *
Oliver Kellogg's avatar
Oliver Kellogg committed
7
 *  copyright (C) 2002-2014                                                *
Ralf Habacker's avatar
Ralf Habacker committed
8
 *  Umbrello UML Modeller Authors <umbrello-devel@kde.org>                 *
Thibault Normand's avatar
Thibault Normand committed
9
10
11
12
13
14
 ***************************************************************************/

// own header
#include "umldoc.h"

// app includes
15
#include "debug_utils.h"
Thibault Normand's avatar
Thibault Normand committed
16
17
18
19
20
#include "uniqueid.h"
#include "association.h"
#include "package.h"
#include "folder.h"
#include "codegenerator.h"
Thibault Normand's avatar
Thibault Normand committed
21
#include "classifier.h"
22
#include "dialog_utils.h"
Thibault Normand's avatar
Thibault Normand committed
23
24
25
26
27
28
29
30
31
32
#include "enum.h"
#include "entity.h"
#include "docwindow.h"
#include "operation.h"
#include "attribute.h"
#include "template.h"
#include "enumliteral.h"
#include "stereotype.h"
#include "classifierlistitem.h"
#include "object_factory.h"
33
#include "import_argo.h"
Thibault Normand's avatar
Thibault Normand committed
34
#include "import_rose.h"
35
#include "model_utils.h"
Thibault Normand's avatar
Thibault Normand committed
36
37
38
39
#include "uml.h"
#include "umllistview.h"
#include "umllistviewitem.h"
#include "umlview.h"
40
#include "entityconstraint.h"
41
#include "idchangelog.h"
Thibault Normand's avatar
Thibault Normand committed
42
#include "listpopupmenu.h"
43
#include "cmds.h"
44
#include "diagramprintpage.h"
45
#include "umlscene.h"
Ralf Habacker's avatar
Ralf Habacker committed
46
#include "version.h"
47
#include "worktoolbar.h"
Ralf Habacker's avatar
Ralf Habacker committed
48
#include "models/diagramsmodel.h"
49
#include "models/objectsmodel.h"
Ralf Habacker's avatar
Ralf Habacker committed
50
#include "models/stereotypesmodel.h"
Thibault Normand's avatar
Thibault Normand committed
51

Andi Fischer's avatar
Andi Fischer committed
52
53
// kde includes
#include <kio/job.h>
54
#if QT_VERSION < 0x050000
Andi Fischer's avatar
Andi Fischer committed
55
#include <kio/netaccess.h>
56
#endif
57
58
59
#if QT_VERSION >= 0x050000
#include <KJobWidgets>
#endif
60
61
#include <KLocalizedString>
#include <KMessageBox>
62
#if QT_VERSION < 0x050000
Andi Fischer's avatar
Andi Fischer committed
63
#include <kmimetype.h>
64
#endif
Andi Fischer's avatar
Andi Fischer committed
65
#include <ktar.h>
66
#if QT_VERSION < 0x050000
67
68
#include <ktempdir.h>
#include <ktabwidget.h>
Andi Fischer's avatar
Andi Fischer committed
69
#include <ktemporaryfile.h>
70
#endif
Andi Fischer's avatar
Andi Fischer committed
71
72

// qt includes
73
#include <QApplication>
74
75
#include <QBuffer>
#include <QDateTime>
76
#include <QDesktopWidget>
77
78
79
#include <QDir>
#include <QDomDocument>
#include <QDomElement>
80
#include <QListWidget>
81
82
83
#if QT_VERSION >= 0x050000
#include <QMimeDatabase>
#endif
84
85
86
#include <QPainter>
#include <QPrinter>
#include <QRegExp>
87
#if QT_VERSION >= 0x050000
88
#include <QTemporaryDir>
89
90
#include <QTemporaryFile>
#endif
91
92
#include <QTextStream>
#include <QTimer>
Andi Fischer's avatar
Andi Fischer committed
93

94
95
DEBUG_REGISTER(UMLDoc)

96
97
98
/**
 * Constructor for the fileclass of the application.
 */
99
UMLDoc::UMLDoc()
Andi Fischer's avatar
Andi Fischer committed
100
  : m_datatypeRoot(0),
101
    m_stereoList(UMLStereotypeList()),
Andi Fischer's avatar
Andi Fischer committed
102
    m_Name(i18n("UML Model")),
103
104
105
    m_modelID("m1"),
    m_count(0),
    m_modified(false),
106
107
108
#if QT_VERSION >= 0x050000
    m_doc_url(QUrl()),
#else
109
    m_doc_url(KUrl()),
110
#endif
111
112
    m_pChangeLog(0),
    m_bLoading(false),
113
    m_importing(false),
114
115
    m_Doc(QString()),
    m_pAutoSaveTimer(0),
Andi Fischer's avatar
Andi Fischer committed
116
    m_nViewID(Uml::ID::None),
117
    m_bTypesAreResolved(true),
118
    m_pCurrentRoot(0),
119
    m_bClosing(false),
Ralf Habacker's avatar
Ralf Habacker committed
120
    m_diagramsModel(new DiagramsModel),
121
    m_objectsModel(new ObjectsModel),
122
123
    m_stereotypesModel(new StereotypesModel(&m_stereoList)),
    m_resolution(0.0)
124
{
125
126
    for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i)
        m_root[i] = 0;
Thibault Normand's avatar
Thibault Normand committed
127
128
}

129
130
131
132
/**
 * Initialize the UMLDoc.
 * To be called after the constructor, before anything else.
 */
133
134
void UMLDoc::init()
{
Thibault Normand's avatar
Thibault Normand committed
135
    // Initialize predefined folders.
Oliver Kellogg's avatar
Oliver Kellogg committed
136
    const char* nativeRootName[Uml::ModelType::N_MODELTYPES] = {
137
138
139
140
141
142
        "Logical View",
        "Use Case View",
        "Component View",
        "Deployment View",
        "Entity Relationship Model"
    };
143
    const QString localizedRootName[Uml::ModelType::N_MODELTYPES] = {
144
145
146
147
148
149
        i18n("Logical View"),
        i18n("Use Case View"),
        i18n("Component View"),
        i18n("Deployment View"),
        i18n("Entity Relationship Model")
    };
150
    for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) {
Oliver Kellogg's avatar
Oliver Kellogg committed
151
        const QString rootName = QString::fromLatin1(nativeRootName[i]);
152
153
154
        QString id = rootName;
        id.replace(QLatin1Char(' '), QLatin1Char('_'));
        m_root[i] = new UMLFolder(rootName, Uml::ID::fromString(id));
Thibault Normand's avatar
Thibault Normand committed
155
156
        m_root[i]->setLocalName(localizedRootName[i]);
    }
157
    createDatatypeFolder();
Thibault Normand's avatar
Thibault Normand committed
158
159
160

    // Connect signals.
    UMLApp * pApp = UMLApp::app();
Andi Fischer's avatar
Andi Fischer committed
161
162
163
164
    connect(this, SIGNAL(sigDiagramCreated(Uml::ID::Type)), pApp, SLOT(slotUpdateViews()));
    connect(this, SIGNAL(sigDiagramRemoved(Uml::ID::Type)), pApp, SLOT(slotUpdateViews()));
    connect(this, SIGNAL(sigDiagramRenamed(Uml::ID::Type)), pApp, SLOT(slotUpdateViews()));
    connect(this, SIGNAL(sigCurrentViewChanged()),          pApp, SLOT(slotCurrentViewChanged()));
Thibault Normand's avatar
Thibault Normand committed
165
166
}

167
168
169
170
171
/**
 * Create the datatype folder and add it to the logical folder.
 */
void UMLDoc::createDatatypeFolder()
{
Ralf Habacker's avatar
Ralf Habacker committed
172
    delete m_datatypeRoot;
Oliver Kellogg's avatar
Oliver Kellogg committed
173
    m_datatypeRoot = new UMLFolder(QLatin1String("Datatypes"), "Datatypes");
174
175
    m_datatypeRoot->setLocalName(i18n("Datatypes"));
    m_datatypeRoot->setUMLPackage(m_root[Uml::ModelType::Logical]);
Christoph Feck's avatar
Christoph Feck committed
176
    Q_ASSERT(m_root[Uml::ModelType::Logical]);
177
    m_root[Uml::ModelType::Logical]->addObject(m_datatypeRoot);
178
179
}

180
181
182
/**
 * Destructor for the fileclass of the application.
 */
183
184
UMLDoc::~UMLDoc()
{
185
186
187
188
189
190
191
192
193
194
    UMLApp * pApp = UMLApp::app();
    disconnect(this, SIGNAL(sigDiagramCreated(Uml::ID::Type)), pApp, SLOT(slotUpdateViews()));
    disconnect(this, SIGNAL(sigDiagramRemoved(Uml::ID::Type)), pApp, SLOT(slotUpdateViews()));
    disconnect(this, SIGNAL(sigDiagramRenamed(Uml::ID::Type)), pApp, SLOT(slotUpdateViews()));
    disconnect(this, SIGNAL(sigCurrentViewChanged()),          pApp, SLOT(slotCurrentViewChanged()));

    disconnect(m_pAutoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
    delete m_pAutoSaveTimer;

    m_root[Uml::ModelType::Logical]->removeObject(m_datatypeRoot);
Ralf Habacker's avatar
Ralf Habacker committed
195
    delete m_datatypeRoot;
196
197
198
199

    for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) {
        delete m_root[i];
    }
Thibault Normand's avatar
Thibault Normand committed
200
    delete m_pChangeLog;
201
    qDeleteAll(m_stereoList);
202
    delete m_stereotypesModel;
Ralf Habacker's avatar
Ralf Habacker committed
203
    delete m_diagramsModel;
204
    delete m_objectsModel;
Thibault Normand's avatar
Thibault Normand committed
205
206
}

207
208
209
210
211
212
/**
 * Adds a view to the document which represents the document
 * contents. Usually this is your main view.
 *
 * @param view   Pointer to the UMLView to add.
 */
213
214
void UMLDoc::addView(UMLView *view)
{
215
    if (view == 0) {
216
        uError() << "argument is NULL";
Thibault Normand's avatar
Thibault Normand committed
217
218
        return;
    }
219
    UMLFolder *f = view->umlScene()->folder();
220
    if (f == 0) {
221
        uError() << "view folder is not set";
Thibault Normand's avatar
Thibault Normand committed
222
223
        return;
    }
224
    DEBUG(DBG_SRC) << view->umlScene()->name() << " to folder " << *f << " (" << f->name() << ")";
Thibault Normand's avatar
Thibault Normand committed
225
    f->addView(view);
Ralf Habacker's avatar
Ralf Habacker committed
226
    m_diagramsModel->addDiagram(view);
Thibault Normand's avatar
Thibault Normand committed
227
228

    UMLApp * pApp = UMLApp::app();
229
    if (pApp->listView()) {
Andi Fischer's avatar
Andi Fischer committed
230
        connect(this, SIGNAL(sigObjectRemoved(UMLObject*)), view->umlScene(), SLOT(slotObjectRemoved(UMLObject*)));
231
    }
232

233
    if (!m_bLoading || pApp->currentView() == 0) {
Oliver Kellogg's avatar
Oliver Kellogg committed
234
235
        pApp->setCurrentView(view);
    }
236
    if (!m_bLoading) {
237
        view->show();
238
        emit sigDiagramChanged(view->umlScene()->type());
Thibault Normand's avatar
Thibault Normand committed
239
240
241
242
243
244
    }

    pApp->setDiagramMenuItemsState(true);
    pApp->slotUpdateViews();
}

245
246
247
248
/**
 * Removes a view from the list of currently connected views.
 *
 * @param view             Pointer to the UMLView to remove.
249
250
 * @param enforceCurrentView   Switch to determine if we have a current view or not.
 *                         Most of the time, we DO want this, except when exiting the program.
251
 */
Ralf Habacker's avatar
Ralf Habacker committed
252
void UMLDoc::removeView(UMLView *view, bool enforceCurrentView)
253
{
254
    if (!view) {
255
        uError() << "UMLDoc::removeView(UMLView *view) called with view = 0";
Thibault Normand's avatar
Thibault Normand committed
256
257
        return;
    }
258
    DEBUG(DBG_SRC) << "<" << view->umlScene()->name() << ">";
259
    if (UMLApp::app()->listView()) {
260
261
        disconnect(this, SIGNAL(sigObjectRemoved(UMLObject*)),
                   view->umlScene(), SLOT(slotObjectRemoved(UMLObject*)));
Thibault Normand's avatar
Thibault Normand committed
262
263
    }
    view->hide();
264
    UMLFolder *f = view->umlScene()->folder();
265
    if (f == 0) {
266
        uError() << view->umlScene()->name() << ": view->getFolder() returns NULL";
Thibault Normand's avatar
Thibault Normand committed
267
268
        return;
    }
Ralf Habacker's avatar
Ralf Habacker committed
269
    m_diagramsModel->removeDiagram(view);
Thibault Normand's avatar
Thibault Normand committed
270
    f->removeView(view);
271
    UMLView *currentView = UMLApp::app()->currentView();
272
    if (currentView == view) {
273
        UMLApp::app()->setCurrentView(0);
Thibault Normand's avatar
Thibault Normand committed
274
        UMLViewList viewList;
275
        m_root[Uml::ModelType::Logical]->appendViews(viewList);
276
        UMLView* firstView = 0;
277
        if (!viewList.isEmpty()) {
278
279
280
            firstView =  viewList.first();
        }

281
        if (!firstView && enforceCurrentView) {  //create a diagram
282
283
            QString name = createDiagramName(Uml::DiagramType::Class, false);
            createDiagram(m_root[Uml::ModelType::Logical], Uml::DiagramType::Class, name);
Laurent Montel's avatar
Laurent Montel committed
284
            qApp->processEvents();
285
            m_root[Uml::ModelType::Logical]->appendViews(viewList);
Thibault Normand's avatar
Thibault Normand committed
286
287
288
            firstView = viewList.first();
        }

289
290
        if (firstView) {
            changeCurrentView(firstView->umlScene()->ID());
Thibault Normand's avatar
Thibault Normand committed
291
292
293
            UMLApp::app()->setDiagramMenuItemsState(true);
        }
    }
294
    delete view;
Thibault Normand's avatar
Thibault Normand committed
295
296
}

297
298
299
300
301
/**
 * Sets the URL of the document.
 *
 * @param url   The KUrl to set.
 */
302
303
304
#if QT_VERSION >= 0x050000
void UMLDoc::setUrl(const QUrl &url)
#else
305
void UMLDoc::setUrl(const KUrl &url)
306
#endif
307
{
Thibault Normand's avatar
Thibault Normand committed
308
309
310
    m_doc_url = url;
}

311
312
313
314
315
/**
 * Returns the KUrl of the document.
 *
 * @return  The KUrl of this UMLDoc.
 */
316
317
318
#if QT_VERSION >= 0x050000
const QUrl& UMLDoc::url() const
#else
319
const KUrl& UMLDoc::url() const
320
#endif
321
{
Thibault Normand's avatar
Thibault Normand committed
322
323
324
    return m_doc_url;
}

325
326
327
328
329
/**
 * Sets the URL of the document to "Untitled".
 */
void UMLDoc::setUrlUntitled()
{
330
331
332
#if QT_VERSION >= 0x050000
    m_doc_url.setUrl(m_doc_url.toString(QUrl::RemoveFilename) + i18n("Untitled"));
#else
333
    m_doc_url.setFileName(i18n("Untitled"));
334
#endif
335
336
}

337
338
339
340
341
342
/**
 * "save modified" - Asks the user for saving if the document
 * is modified.
 *
 * @return  True if document can be closed.
 */
343
344
bool UMLDoc::saveModified()
{
Thibault Normand's avatar
Thibault Normand committed
345
    bool completed(true);
346
    if (!m_modified) {
Thibault Normand's avatar
Thibault Normand committed
347
        return completed;
348
    }
Thibault Normand's avatar
Thibault Normand committed
349
350

    UMLApp *win = UMLApp::app();
351
352
353
354
    int want_save = KMessageBox::warningYesNoCancel(win,
                                     i18n("The current file has been modified.\nDo you want to save it?"),
                                     i18nc("warning message", "Warning"),
                                     KStandardGuiItem::save(), KStandardGuiItem::discard());
355
    switch(want_save) {
Thibault Normand's avatar
Thibault Normand committed
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
    case KMessageBox::Yes:
        if (m_doc_url.fileName() == i18n("Untitled")) {
            if (win->slotFileSaveAs()) {
                closeDocument();
                completed=true;
            } else {
                completed=false;
            }
        } else {
            saveDocument(url());
            closeDocument();
            completed=true;
        }
        break;

    case KMessageBox::No:
        setModified(false);
        closeDocument();
        completed=true;
        break;

    case KMessageBox::Cancel:
        completed=false;
        break;

    default:
        completed=false;
        break;
    }
    return completed;
}

388
389
390
/**
 * Closes the current document.
 */
391
392
void UMLDoc::closeDocument()
{
393
    m_bClosing = true;
394
    UMLApp::app()->setGenerator(Uml::ProgrammingLanguage::Reserved);  // delete the codegen
395
    m_Doc = QString();
396
    DocWindow* dw = UMLApp::app()->docWindow();
Thibault Normand's avatar
Thibault Normand committed
397
    if (dw) {
398
        dw->reset();
Thibault Normand's avatar
Thibault Normand committed
399
    }
400
    UMLApp::app()->logWindow()->clear();
Thibault Normand's avatar
Thibault Normand committed
401

402
    UMLListView *listView = UMLApp::app()->listView();
Thibault Normand's avatar
Thibault Normand committed
403
    if (listView) {
404
        listView->clean();
Thibault Normand's avatar
Thibault Normand committed
405
406
407
408
409
410
411
412
413
414
415
416
417
418
        // store old setting - for restore of last setting
        bool m_bLoading_old = m_bLoading;
        m_bLoading = true; // This is to prevent document becoming modified.
        // For reference, here is an example of a call sequence that would
        // otherwise result in futile addToUndoStack() calls:
        //  removeAllViews()  =>
        //   UMLView::removeAllAssociations()  =>
        //    UMLView::removeAssoc()  =>
        //     UMLDoc::setModified(true, true)  =>
        //      addToUndoStack().
        removeAllViews();
        m_bLoading = m_bLoading_old;
        // Remove all objects from the predefined folders.
        // @fixme With advanced code generation enabled, this crashes.
419
420
        removeAllObjects();

421
422
        // Remove any stereotypes.
        if (stereotypes().count() > 0) {
423
424
            foreach(UMLStereotype *s, stereotypes()) {
                m_stereotypesModel->removeStereotype(s);
Thibault Normand's avatar
Thibault Normand committed
425
                delete s;
426
            }
Thibault Normand's avatar
Thibault Normand committed
427
428
            m_stereoList.clear();
        }
429
430
431
432
433

        // Restore the datatype folder, it has been deleted above.
        createDatatypeFolder();
        // this creates to much items only Logical View should be created
        listView->init();
Thibault Normand's avatar
Thibault Normand committed
434
    }
435
    m_bClosing = false;
Thibault Normand's avatar
Thibault Normand committed
436
437
}

438
439
440
441
442
/**
 * Initializes the document generally.
 *
 * @return  True if operation successful.
 */
443
444
bool UMLDoc::newDocument()
{
Thibault Normand's avatar
Thibault Normand committed
445
    closeDocument();
446
    UMLApp::app()->setCurrentView(0);
447
    setUrlUntitled();
448
    setResolution(qApp->desktop()->logicalDpiX());
Thibault Normand's avatar
Thibault Normand committed
449
    //see if we need to start with a new diagram
450
    Settings::OptionState optionState = Settings::optionState();
Andi Fischer's avatar
Andi Fischer committed
451
452
    Uml::DiagramType::Enum dt = optionState.generalState.diagram;
    Uml::ModelType::Enum mt = Model_Utils::convert_DT_MT(dt);
453
454
455
    if (mt == Uml::ModelType::N_MODELTYPES) {  // don't allow no diagram
        dt = Uml::DiagramType::Class;
        mt = Uml::ModelType::Logical;
Thibault Normand's avatar
Thibault Normand committed
456
    }
457
458
    QString name = createDiagramName(dt, false);
    createDiagram(m_root[mt], dt, name);
Thibault Normand's avatar
Thibault Normand committed
459
460
461
462
463
464

    UMLApp::app()->initGenerator();

    setModified(false);
    initSaveTimer();

465
    UMLApp::app()->enableUndoAction(false);
466
    UMLApp::app()->clearUndoStack();
Thibault Normand's avatar
Thibault Normand committed
467
468
469
470

    return true;
}

471
472
473
474
475
476
477
478
/**
 * Loads the document by filename and format and emits the
 * updateViews() signal.
 *
 * @param url      The filename in KUrl format.
 * @param format   The format (optional.)
 * @return  True if operation successful.
 */
479
480
481
#if QT_VERSION >= 0x050000
bool UMLDoc::openDocument(const QUrl& url, const char* format /* =0 */)
#else
482
bool UMLDoc::openDocument(const KUrl& url, const char* format /* =0 */)
483
#endif
484
485
{
    Q_UNUSED(format);
486
    if (url.fileName().length() == 0) {
Thibault Normand's avatar
Thibault Normand committed
487
488
489
490
491
492
        newDocument();
        return false;
    }

    m_doc_url = url;
    closeDocument();
493
    setResolution(0.0);
Thibault Normand's avatar
Thibault Normand committed
494
495
    // IMPORTANT: set m_bLoading to true
    // _AFTER_ the call of UMLDoc::closeDocument()
496
    // as it sets m_bLoading to false after it was temporarily
Thibault Normand's avatar
Thibault Normand committed
497
498
    // changed to true to block recording of changes in redo-buffer
    m_bLoading = true;
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
#if QT_VERSION >= 0x050000
    QTemporaryFile tmpfile;
    tmpfile.open();
    QUrl dest(QUrl::fromLocalFile(tmpfile.fileName()));
    DEBUG(DBG_SRC) << "UMLDoc::openDocument: copy from " << url << " to " << dest << ".";
    KIO::FileCopyJob *job = KIO::file_copy(url, dest, -1, KIO::Overwrite);
    KJobWidgets::setWindow(job, UMLApp::app());
    job->exec();
    QFile file(tmpfile.fileName());
    if (job->error() || !tmpfile.exists()) {
        if (!tmpfile.exists())
            DEBUG(DBG_SRC) << "UMLDoc::openDocument: temporary file <" << tmpfile.fileName() << "> failed!";
        if (job->error())
           DEBUG(DBG_SRC) << "UMLDoc::openDocument: " << job->errorString();
        KMessageBox::error(0, i18n("The file <%1> does not exist.", url.toString()), i18n("Load Error"));
        setUrlUntitled();
        m_bLoading = false;
        newDocument();
        return false;
    }
#else
Thibault Normand's avatar
Thibault Normand committed
520
    QString tmpfile;
521
    KIO::NetAccess::download(url, tmpfile, UMLApp::app());
Ralf Habacker's avatar
Ralf Habacker committed
522

523
524
    QFile file(tmpfile);
    if (!file.exists()) {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
525
        KMessageBox::error(0, i18n("The file %1 does not exist.", url.pathOrUrl()), i18n("Load Error"));
526
        setUrlUntitled();
Thibault Normand's avatar
Thibault Normand committed
527
528
529
530
        m_bLoading = false;
        newDocument();
        return false;
    }
531
#endif
Thibault Normand's avatar
Thibault Normand committed
532
533
534
535
536
    // status of XMI loading
    bool status = false;

    // check if the xmi file is a compressed archive like tar.bzip2 or tar.gz
    QString filetype = m_doc_url.fileName();
537
    QString mimetype;
Oliver Kellogg's avatar
Oliver Kellogg committed
538
539
540
541
    if (filetype.endsWith(QLatin1String(".tgz")) || filetype.endsWith(QLatin1String(".tar.gz"))) {
        mimetype = QLatin1String("application/x-gzip");
    } else if (filetype.endsWith(QLatin1String(".tar.bz2"))) {
        mimetype = QLatin1String("application/x-bzip");
Thibault Normand's avatar
Thibault Normand committed
542
543
    }

544
    if (mimetype.isEmpty() == false) {
545
546
547
#if QT_VERSION >= 0x050000
        KTar archive(tmpfile.fileName(), mimetype);
#else
Thibault Normand's avatar
Thibault Normand committed
548
        KTar archive(tmpfile, mimetype);
549
#endif
550
        if (archive.open(QIODevice::ReadOnly) == false) {
551
552
553
#if QT_VERSION >= 0x050000
            KMessageBox::error(0, i18n("The file %1 seems to be corrupted.", url.toString()), i18n("Load Error"));
#else
Christian Ehrlicher's avatar
Christian Ehrlicher committed
554
            KMessageBox::error(0, i18n("The file %1 seems to be corrupted.", url.pathOrUrl()), i18n("Load Error"));
555
            KIO::NetAccess::removeTempFile(tmpfile);
556
#endif
557
            setUrlUntitled();
Thibault Normand's avatar
Thibault Normand committed
558
559
560
561
562
563
564
            m_bLoading = false;
            newDocument();
            return false;
        }

        // get the root directory and all entries in
        const KArchiveDirectory * rootDir = archive.directory();
565
        const QStringList entries = rootDir->entries();
Thibault Normand's avatar
Thibault Normand committed
566
567
        QString entryMimeType;
        bool foundXMI = false;
568
569
        QStringList::ConstIterator it;
        QStringList::ConstIterator end(entries.end());
Thibault Normand's avatar
Thibault Normand committed
570
571

        // now go through all entries till we find an xmi file
572
        for (it = entries.begin(); it != end; ++it) {
Thibault Normand's avatar
Thibault Normand committed
573
            // only check files, we do not go in subdirectories
574
            if (rootDir->entry(*it)->isFile() == true) {
Thibault Normand's avatar
Thibault Normand committed
575
                // we found a file, check the mimetype
576
577
578
579
#if QT_VERSION >= 0x050000
                QMimeDatabase db;
                entryMimeType = db.mimeTypeForFile(*it, QMimeDatabase::MatchExtension).name();
#else
Thibault Normand's avatar
Thibault Normand committed
580
                entryMimeType = KMimeType::findByPath(*it, 0, true)->name();
581
#endif
Oliver Kellogg's avatar
Oliver Kellogg committed
582
                if (entryMimeType == QLatin1String("application/x-uml")) {
Thibault Normand's avatar
Thibault Normand committed
583
584
585
586
587
588
589
                    foundXMI = true;
                    break;
                }
            }
        }

        // if we found an XMI file, we have to extract it to a temporary file
590
        if (foundXMI == true) {
591
592
593
#if QT_VERSION >= 0x050000
            QTemporaryDir tmp_dir;
#else
Thibault Normand's avatar
Thibault Normand committed
594
            KTempDir tmp_dir;
595
#endif
Thibault Normand's avatar
Thibault Normand committed
596
597
598
599
600
            KArchiveEntry * entry;
            KArchiveFile * fileEntry;

            // try to cast the file entry in the archive to an archive entry
            entry = const_cast<KArchiveEntry*>(rootDir->entry(*it));
601
            if (entry == 0) {
602
603
604
605
#if QT_VERSION >= 0x050000
                KMessageBox::error(0, i18n("There was no XMI file found in the compressed file %1.", url.toString()),
                                   i18n("Load Error"));
#else
Christian Ehrlicher's avatar
Christian Ehrlicher committed
606
                KMessageBox::error(0, i18n("There was no XMI file found in the compressed file %1.", url.pathOrUrl()),
607
                                   i18n("Load Error"));
608
                KIO::NetAccess::removeTempFile(tmpfile);
609
#endif
610
                setUrlUntitled();
Thibault Normand's avatar
Thibault Normand committed
611
612
613
614
615
616
617
618
                m_bLoading = false;
                newDocument();
                return false;
            }

            // now try to cast the archive entry to a file entry, so that we can
            // extract the file
            fileEntry = dynamic_cast<KArchiveFile*>(entry);
619
            if (fileEntry == 0) {
620
621
622
623
#if QT_VERSION >= 0x050000
                KMessageBox::error(0, i18n("There was no XMI file found in the compressed file %1.", url.toString()),
                                   i18n("Load Error"));
#else
Christian Ehrlicher's avatar
Christian Ehrlicher committed
624
                KMessageBox::error(0, i18n("There was no XMI file found in the compressed file %1.", url.pathOrUrl()),
625
                                   i18n("Load Error"));
626
                KIO::NetAccess::removeTempFile(tmpfile);
627
#endif
628
                setUrlUntitled();
Thibault Normand's avatar
Thibault Normand committed
629
630
631
632
633
634
                m_bLoading = false;
                newDocument();
                return false;
            }

            // now we can extract the file to the temporary directory
635
636
637
638
639
640
#if QT_VERSION >= 0x050000
            fileEntry->copyTo(tmp_dir.path() + QLatin1Char('/'));

            // now open the extracted file for reading
            QFile xmi_file(tmp_dir.path() + QLatin1Char('/') + *it);
#else
Thibault Normand's avatar
Thibault Normand committed
641
642
643
644
            fileEntry->copyTo(tmp_dir.name());

            // now open the extracted file for reading
            QFile xmi_file(tmp_dir.name() + *it);
645
#endif
646
            if(!xmi_file.open(QIODevice::ReadOnly)) {
647
648
649
650
#if QT_VERSION >= 0x050000
                KMessageBox::error(0, i18n("There was a problem loading the extracted file: %1", url.toString()),
                                   i18n("Load Error"));
#else
Christian Ehrlicher's avatar
Christian Ehrlicher committed
651
                KMessageBox::error(0, i18n("There was a problem loading the extracted file: %1", url.pathOrUrl()),
652
                                   i18n("Load Error"));
653
                KIO::NetAccess::removeTempFile(tmpfile);
654
#endif
655
                setUrlUntitled();
Thibault Normand's avatar
Thibault Normand committed
656
657
658
659
                m_bLoading = false;
                newDocument();
                return false;
            }
660
            m_bTypesAreResolved = false;
661
            status = loadFromXMI1(xmi_file, ENC_UNKNOWN);
Thibault Normand's avatar
Thibault Normand committed
662
663
664
665

            // close the extracted file and the temporary directory
            xmi_file.close();
        } else {
666
667
668
669
#if QT_VERSION >= 0x050000
                KMessageBox::error(0, i18n("There was no XMI file found in the compressed file %1.", url.toString()),
                                   i18n("Load Error"));
#else
Christian Ehrlicher's avatar
Christian Ehrlicher committed
670
            KMessageBox::error(0, i18n("There was no XMI file found in the compressed file %1.", url.pathOrUrl()),
671
                               i18n("Load Error"));
672
            KIO::NetAccess::removeTempFile(tmpfile);
673
#endif
674
            setUrlUntitled();
Thibault Normand's avatar
Thibault Normand committed
675
676
677
678
679
680
            m_bLoading = false;
            newDocument();
            return false;
        }

        archive.close();
681
    } else {
Thibault Normand's avatar
Thibault Normand committed
682
        // no, it seems to be an ordinary file
683
        if (!file.open(QIODevice::ReadOnly)) {
684
685
686
687
#if QT_VERSION >= 0x050000
            KMessageBox::error(0, i18n("There was a problem loading file: %1", url.toString()),
                               i18n("Load Error"));
#else
Christian Ehrlicher's avatar
Christian Ehrlicher committed
688
            KMessageBox::error(0, i18n("There was a problem loading file: %1", url.pathOrUrl()),
689
                               i18n("Load Error"));
690
            KIO::NetAccess::removeTempFile(tmpfile);
691
#endif
692
            setUrlUntitled();
Thibault Normand's avatar
Thibault Normand committed
693
694
695
696
            m_bLoading = false;
            newDocument();
            return false;
        }
697
        if (filetype.endsWith(QLatin1String(".mdl"))) {
698
            setUrlUntitled();
699
            m_bTypesAreResolved = false;
Thibault Normand's avatar
Thibault Normand committed
700
            status = Import_Rose::loadFromMDL(file);
701
            if (status) {
702
703
704
705
706
                if (UMLApp::app()->currentView() == 0) {
                    QString name = createDiagramName(Uml::DiagramType::Class, false);
                    createDiagram(m_root[Uml::ModelType::Logical], Uml::DiagramType::Class, name);
                    setCurrentRoot(Uml::ModelType::Logical);
                }
707
            }
708
        }
709
        else if (filetype.endsWith(QLatin1String(".zargo"))) {
710
            setUrlUntitled();
711
            status = Import_Argo::loadFromZArgoFile(file);
712
713
        }
        else {
714
            m_bTypesAreResolved = false;
715
            status = loadFromXMI1(file, ENC_UNKNOWN);
716
        }
Thibault Normand's avatar
Thibault Normand committed
717
718
    }

719
720
    if (file.isOpen())
        file.close();
721
#if QT_VERSION < 0x050000
722
    KIO::NetAccess::removeTempFile(tmpfile);
723
#endif
724
725
    m_bLoading = false;
    m_bTypesAreResolved = true;
726
    if (!status) {
727
728
729
730
#if QT_VERSION >= 0x050000
        KMessageBox::error(0, i18n("There was a problem loading file: %1", url.toString()),
                           i18n("Load Error"));
#else
Christian Ehrlicher's avatar
Christian Ehrlicher committed
731
        KMessageBox::error(0, i18n("There was a problem loading file: %1", url.pathOrUrl()),
732
                           i18n("Load Error"));
733
#endif
Thibault Normand's avatar
Thibault Normand committed
734
735
736
737
738
739
        newDocument();
        return false;
    }
    setModified(false);
    initSaveTimer();

740
    UMLApp::app()->enableUndoAction(false);
741
    UMLApp::app()->clearUndoStack();
Thibault Normand's avatar
Thibault Normand committed
742
743
744
745
746
747
    // for compatibility
    addDefaultStereotypes();

    return true;
}

748
749
750
751
752
753
754
/**
 * Saves the document using the given filename and format.
 *
 * @param url      The filename in KUrl format.
 * @param format   The format (optional.)
 * @return  True if operation successful.
 */
755
756
757
#if QT_VERSION >= 0x050000
bool UMLDoc::saveDocument(const QUrl& url, const char * format)
#else
758
bool UMLDoc::saveDocument(const KUrl& url, const char * format)
759
#endif
760
761
{
    Q_UNUSED(format);
Thibault Normand's avatar
Thibault Normand committed
762
763
764
765
    m_doc_url = url;
    bool uploaded = true;

    // first, we have to find out which format to use
766
767
768
#if QT_VERSION >= 0x050000
    QString strFileName = url.path();
#else
Thibault Normand's avatar
Thibault Normand committed
769
    QString strFileName = url.path(KUrl::RemoveTrailingSlash);
770
#endif
Thibault Normand's avatar
Thibault Normand committed
771
    QFileInfo fileInfo(strFileName);
772
    QString fileExt = fileInfo.completeSuffix();
Oliver Kellogg's avatar
Oliver Kellogg committed
773
774
775
776
777
778
779
    QString fileFormat = QLatin1String("xmi");
    if (fileExt == QLatin1String("xmi") || fileExt == QLatin1String("bak.xmi")) {
        fileFormat = QLatin1String("xmi");
    } else if (fileExt == QLatin1String("xmi.tgz") || fileExt == QLatin1String("bak.xmi.tgz")) {
        fileFormat = QLatin1String("tgz");
    } else if (fileExt == QLatin1String("xmi.tar.bz2") || fileExt == QLatin1String("bak.xmi.tar.bz2")) {
        fileFormat = QLatin1String("bz2");
Thibault Normand's avatar
Thibault Normand committed
780
    } else {
Oliver Kellogg's avatar
Oliver Kellogg committed
781
        fileFormat = QLatin1String("xmi");
Thibault Normand's avatar
Thibault Normand committed
782
783
784
785
    }

    initSaveTimer();

Oliver Kellogg's avatar
Oliver Kellogg committed
786
    if (fileFormat == QLatin1String("tgz") || fileFormat == QLatin1String("bz2")) {
Thibault Normand's avatar
Thibault Normand committed
787
        KTar * archive;
788
789
790
#if QT_VERSION >= 0x050000
        QTemporaryFile tmp_tgz_file;
#else
Thibault Normand's avatar
Thibault Normand committed
791
        KTemporaryFile tmp_tgz_file;
792
#endif
Thibault Normand's avatar
Thibault Normand committed
793
794
795
796
        tmp_tgz_file.setAutoRemove(false);
        tmp_tgz_file.open();

        // first we have to check if we are saving to a local or remote file
797
        if (url.isLocalFile()) {
Oliver Kellogg's avatar
Oliver Kellogg committed
798
799
            if (fileFormat == QLatin1String("tgz")) {  // check tgz or bzip
                archive = new KTar(url.toLocalFile(), QLatin1String("application/x-gzip"));
Thibault Normand's avatar
Thibault Normand committed
800
            } else {
Oliver Kellogg's avatar
Oliver Kellogg committed
801
                archive = new KTar(url.toLocalFile(), QLatin1String("application/x-bzip"));
Thibault Normand's avatar
Thibault Normand committed
802
803
            }
        } else {
Oliver Kellogg's avatar
Oliver Kellogg committed
804
805
            if (fileFormat == QLatin1String("tgz")) {  // check tgz or bzip2
                archive = new KTar(tmp_tgz_file.fileName(), QLatin1String("application/x-gzip"));
Thibault Normand's avatar
Thibault Normand committed
806
            } else {
Oliver Kellogg's avatar
Oliver Kellogg committed
807
                archive = new KTar(tmp_tgz_file.fileName(), QLatin1String("application/x-bzip"));
Thibault Normand's avatar
Thibault Normand committed
808
809
810
811
            }
        }

        // now check if we can write to the file
812
        if (archive->open(QIODevice::WriteOnly) == false) {
813
814
815
#if QT_VERSION >= 0x050000
            KMessageBox::error(0, i18n("There was a problem saving: %1", url.url(QUrl::PreferLocalFile)), i18n("Save Error"));
#else
Christian Ehrlicher's avatar
Christian Ehrlicher committed
816
            KMessageBox::error(0, i18n("There was a problem saving file: %1", url.pathOrUrl()), i18n("Save Error"));
817
#endif
Laurent Montel's avatar
Laurent Montel committed
818
            delete archive;
Thibault Normand's avatar
Thibault Normand committed
819
820
821
822
823
            return false;
        }

        // we have to create a temporary xmi file
        // we will add this file later to the archive
824
825
826
#if QT_VERSION >= 0x050000
        QTemporaryFile tmp_xmi_file;
#else
Thibault Normand's avatar
Thibault Normand committed
827
        KTemporaryFile tmp_xmi_file;
828
#endif
Thibault Normand's avatar
Thibault Normand committed
829
        tmp_xmi_file.setAutoRemove(false);
830
        if (!tmp_xmi_file.open()) {
831
832
833
#if QT_VERSION >= 0x050000
            KMessageBox::error(0, i18n("There was a problem saving: %1", url.url(QUrl::PreferLocalFile)), i18n("Save Error"));
#else
Christian Ehrlicher's avatar
Christian Ehrlicher committed
834
            KMessageBox::error(0, i18n("There was a problem saving file: %1", url.pathOrUrl()), i18n("Save Error"));
835
#endif
Laurent Montel's avatar
Laurent Montel committed
836
            delete archive;
Thibault Normand's avatar
Thibault Normand committed
837
838
            return false;
        }
Ralf Habacker's avatar
Ralf Habacker committed
839
        saveToXMI1(tmp_xmi_file); // save XMI to this file...
Thibault Normand's avatar
Thibault Normand committed
840
841
842

        // now add this file to the archive, but without the extension
        QString tmpQString = url.fileName();
Oliver Kellogg's avatar
Oliver Kellogg committed
843
844
        if (fileFormat == QLatin1String("tgz")) {
            tmpQString.remove(QRegExp(QLatin1String("\\.tgz$")));
845
846
        }
        else {
Oliver Kellogg's avatar
Oliver Kellogg committed
847
            tmpQString.remove(QRegExp(QLatin1String("\\.tar\\.bz2$")));
Thibault Normand's avatar
Thibault Normand committed
848
849
850
        }
        archive->addLocalFile(tmp_xmi_file.fileName(), tmpQString);

851
        if (!archive->close()) {
852
853
854
#if QT_VERSION >= 0x050000
            KMessageBox::error(0, i18n("There was a problem saving: %1", url.url(QUrl::PreferLocalFile)), i18n("Save Error"));
#else
Christian Ehrlicher's avatar
Christian Ehrlicher committed
855
            KMessageBox::error(0, i18n("There was a problem saving file: %1", url.pathOrUrl()), i18n("Save Error"));
856
#endif
Laurent Montel's avatar
Laurent Montel committed
857
            delete archive;
Thibault Normand's avatar
Thibault Normand committed
858
859
860
861
862
863
            return false;
        }
        // now the xmi file was added to the archive, so we can delete it
        tmp_xmi_file.setAutoRemove(true);

        // now we have to check, if we have to upload the file
864
        if (!url.isLocalFile()) {
865
866
867
868
869
870
#if QT_VERSION >= 0x050000
            KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(tmp_tgz_file.fileName()), m_doc_url);
            KJobWidgets::setWindow(job, UMLApp::app());
            job->exec();
            uploaded = !job->error();
#else
871
            uploaded = KIO::NetAccess::upload(tmp_tgz_file.fileName(), m_doc_url, UMLApp::app());
872
#endif
Thibault Normand's avatar
Thibault Normand committed
873
874
875
876
877
878
879
        }

        // now the archive was written to disk (or remote) so we can delete the
        // objects
        tmp_tgz_file.setAutoRemove(true);
        delete archive;

880
881
    }
    else {
Thibault Normand's avatar
Thibault Normand committed
882
883
        // save as normal uncompressed XMI

884
885
886
#if QT_VERSION >= 0x050000
        QTemporaryFile tmpfile; // we need this tmp file if we are writing to a remote file
#else
Thibault Normand's avatar
Thibault Normand committed
887
        KTemporaryFile tmpfile; // we need this tmp file if we are writing to a remote file
888
#endif
Thibault Normand's avatar
Thibault Normand committed
889
890
891
        tmpfile.setAutoRemove(false);

        // save in _any_ case to a temp file
Ralf Habacker's avatar
Ralf Habacker committed
892
        // -> if something goes wrong during saveToXMI1, the
Thibault Normand's avatar
Thibault Normand committed
893
        //     original content is preserved
894
        //     (e.g. if umbrello dies in the middle of the document model parsing
Ralf Habacker's avatar
Ralf Habacker committed
895
896
        //      for saveToXMI1 due to some problems)
        /// @todo insert some checks in saveToXMI1 to detect a failed save attempt
Thibault Normand's avatar
Thibault Normand committed
897
898

        // lets open the file for writing
899
        if (!tmpfile.open()) {
900
901
902
#if QT_VERSION >= 0x050000
            KMessageBox::error(0, i18n("There was a problem saving: %1", url.url(QUrl::PreferLocalFile)), i18n("Save Error"));
#else
Christian Ehrlicher's avatar
Christian Ehrlicher committed
903
            KMessageBox::error(0, i18n("There was a problem saving file: %1", url.pathOrUrl()), i18n("Save Error"));
904
#endif
Thibault Normand's avatar
Thibault Normand committed
905
906
            return false;
        }
Ralf Habacker's avatar
Ralf Habacker committed
907
        saveToXMI1(tmpfile); // save the xmi stuff to it
908

Thibault Normand's avatar
Thibault Normand committed
909
        // if it is a remote file, we have to upload the tmp file
910
        if (!url.isLocalFile()) {
911
912
913
914
915
916
#if QT_VERSION >= 0x050000
            KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(tmpfile.fileName()), m_doc_url);
            KJobWidgets::setWindow(job, UMLApp::app());
            job->exec();
            uploaded = !job->error();
#else
917
            uploaded = KIO::NetAccess::upload(tmpfile.fileName(), m_doc_url, UMLApp::app());