umldoc.cpp 103 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"
Thibault Normand's avatar
Thibault Normand committed
22
23
24
25
26
27
28
29
30
31
#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"
32
#include "import_argo.h"
Thibault Normand's avatar
Thibault Normand committed
33
#include "import_rose.h"
34
#include "model_utils.h"
Thibault Normand's avatar
Thibault Normand committed
35
36
37
38
#include "uml.h"
#include "umllistview.h"
#include "umllistviewitem.h"
#include "umlview.h"
39
#include "entityconstraint.h"
40
#include "idchangelog.h"
Thibault Normand's avatar
Thibault Normand committed
41
#include "listpopupmenu.h"
42
#include "cmds.h"
43
#include "diagramprintpage.h"
44
#include "umlscene.h"
Ralf Habacker's avatar
Ralf Habacker committed
45
#include "version.h"
46
#include "worktoolbar.h"
Thibault Normand's avatar
Thibault Normand committed
47

Andi Fischer's avatar
Andi Fischer committed
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// kde includes
#include <kio/job.h>
#include <kio/netaccess.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kmimetype.h>
#include <ktar.h>
#include <ktempdir.h>
#include <ktemporaryfile.h>
#include <kinputdialog.h>
#include <ktabwidget.h>
#include <kapplication.h>

// qt includes
62
63
64
65
66
67
68
69
70
71
#include <QBuffer>
#include <QDateTime>
#include <QDir>
#include <QDomDocument>
#include <QDomElement>
#include <QPainter>
#include <QPrinter>
#include <QRegExp>
#include <QTextStream>
#include <QTimer>
Andi Fischer's avatar
Andi Fischer committed
72

73
74
DEBUG_REGISTER(UMLDoc)

75
76
77
/**
 * Constructor for the fileclass of the application.
 */
78
UMLDoc::UMLDoc()
Andi Fischer's avatar
Andi Fischer committed
79
  : m_datatypeRoot(0),
80
    m_stereoList(UMLStereotypeList()),
Andi Fischer's avatar
Andi Fischer committed
81
    m_Name(i18n("UML Model")),
82
83
84
85
86
87
88
89
    m_modelID("m1"),
    m_count(0),
    m_modified(false),
    m_doc_url(KUrl()),
    m_pChangeLog(0),
    m_bLoading(false),
    m_Doc(QString()),
    m_pAutoSaveTimer(0),
Andi Fischer's avatar
Andi Fischer committed
90
    m_nViewID(Uml::ID::None),
91
    m_bTypesAreResolved(true),
92
    m_pCurrentRoot(0),
Andi Fischer's avatar
Andi Fischer committed
93
    m_bClosing(false)
94
{
95
96
    for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i)
        m_root[i] = 0;
Thibault Normand's avatar
Thibault Normand committed
97
98
}

99
100
101
102
/**
 * Initialize the UMLDoc.
 * To be called after the constructor, before anything else.
 */
103
104
void UMLDoc::init()
{
Thibault Normand's avatar
Thibault Normand committed
105
    // Initialize predefined folders.
Oliver Kellogg's avatar
Oliver Kellogg committed
106
    const char* nativeRootName[Uml::ModelType::N_MODELTYPES] = {
107
108
109
110
111
112
        "Logical View",
        "Use Case View",
        "Component View",
        "Deployment View",
        "Entity Relationship Model"
    };
113
    const QString localizedRootName[Uml::ModelType::N_MODELTYPES] = {
114
115
116
117
118
119
        i18n("Logical View"),
        i18n("Use Case View"),
        i18n("Component View"),
        i18n("Deployment View"),
        i18n("Entity Relationship Model")
    };
120
    for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) {
Oliver Kellogg's avatar
Oliver Kellogg committed
121
122
        const QString rootName = QString::fromLatin1(nativeRootName[i]);
        m_root[i] = new UMLFolder(rootName, Uml::ID::fromString(rootName));
Thibault Normand's avatar
Thibault Normand committed
123
124
        m_root[i]->setLocalName(localizedRootName[i]);
    }
125
    createDatatypeFolder();
Thibault Normand's avatar
Thibault Normand committed
126
127
128

    // Connect signals.
    UMLApp * pApp = UMLApp::app();
Andi Fischer's avatar
Andi Fischer committed
129
130
131
132
    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
133
134
}

135
136
137
138
139
/**
 * Create the datatype folder and add it to the logical folder.
 */
void UMLDoc::createDatatypeFolder()
{
Ralf Habacker's avatar
Ralf Habacker committed
140
    delete m_datatypeRoot;
Oliver Kellogg's avatar
Oliver Kellogg committed
141
    m_datatypeRoot = new UMLFolder(QLatin1String("Datatypes"), "Datatypes");
142
143
    m_datatypeRoot->setLocalName(i18n("Datatypes"));
    m_datatypeRoot->setUMLPackage(m_root[Uml::ModelType::Logical]);
Christoph Feck's avatar
Christoph Feck committed
144
    Q_ASSERT(m_root[Uml::ModelType::Logical]);
145
    m_root[Uml::ModelType::Logical]->addObject(m_datatypeRoot);
146
147
}

148
149
150
/**
 * Destructor for the fileclass of the application.
 */
151
152
UMLDoc::~UMLDoc()
{
153
154
155
156
157
158
159
160
161
162
    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
163
    delete m_datatypeRoot;
164
165
166
167

    for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) {
        delete m_root[i];
    }
Thibault Normand's avatar
Thibault Normand committed
168
    delete m_pChangeLog;
169
    qDeleteAll(m_stereoList);
Thibault Normand's avatar
Thibault Normand committed
170
171
}

172
173
174
175
176
177
/**
 * 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.
 */
178
179
void UMLDoc::addView(UMLView *view)
{
180
    if (view == 0) {
181
        uError() << "argument is NULL";
Thibault Normand's avatar
Thibault Normand committed
182
183
        return;
    }
184
    UMLFolder *f = view->umlScene()->folder();
185
    if (f == 0) {
186
        uError() << "view folder is not set";
Thibault Normand's avatar
Thibault Normand committed
187
188
        return;
    }
189
    DEBUG(DBG_SRC) << view->umlScene()->name() << " to folder " << *f << " (" << f->name() << ")";
Thibault Normand's avatar
Thibault Normand committed
190
191
192
    f->addView(view);

    UMLApp * pApp = UMLApp::app();
193
    if (pApp->listView()) {
Andi Fischer's avatar
Andi Fischer committed
194
        connect(this, SIGNAL(sigObjectRemoved(UMLObject*)), view->umlScene(), SLOT(slotObjectRemoved(UMLObject*)));
195
    }
196

197
    if (!m_bLoading || pApp->currentView() == NULL) {
Oliver Kellogg's avatar
Oliver Kellogg committed
198
199
        pApp->setCurrentView(view);
    }
200
    if (!m_bLoading) {
201
        view->show();
202
        emit sigDiagramChanged(view->umlScene()->type());
Thibault Normand's avatar
Thibault Normand committed
203
204
205
206
207
208
    }

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

209
210
211
212
/**
 * Removes a view from the list of currently connected views.
 *
 * @param view             Pointer to the UMLView to remove.
213
214
 * @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.
215
 */
Ralf Habacker's avatar
Ralf Habacker committed
216
void UMLDoc::removeView(UMLView *view, bool enforceCurrentView)
217
{
218
    if (!view) {
219
        uError() << "UMLDoc::removeView(UMLView *view) called with view = 0";
Thibault Normand's avatar
Thibault Normand committed
220
221
        return;
    }
222
    DEBUG(DBG_SRC) << "<" << view->umlScene()->name() << ">";
223
    if (UMLApp::app()->listView()) {
224
225
        disconnect(this, SIGNAL(sigObjectRemoved(UMLObject*)),
                   view->umlScene(), SLOT(slotObjectRemoved(UMLObject*)));
Thibault Normand's avatar
Thibault Normand committed
226
227
    }
    view->hide();
228
    UMLFolder *f = view->umlScene()->folder();
229
    if (f == 0) {
230
        uError() << view->umlScene()->name() << ": view->getFolder() returns NULL";
Thibault Normand's avatar
Thibault Normand committed
231
232
233
        return;
    }
    f->removeView(view);
234
    UMLView *currentView = UMLApp::app()->currentView();
235
    if (currentView == view) {
236
        UMLApp::app()->setCurrentView(0);
Thibault Normand's avatar
Thibault Normand committed
237
        UMLViewList viewList;
238
        m_root[Uml::ModelType::Logical]->appendViews(viewList);
239
        UMLView* firstView = 0;
240
        if (!viewList.isEmpty()) {
241
242
243
            firstView =  viewList.first();
        }

244
        if (!firstView && enforceCurrentView) {  //create a diagram
245
246
            QString name = createDiagramName(Uml::DiagramType::Class, false);
            createDiagram(m_root[Uml::ModelType::Logical], Uml::DiagramType::Class, name);
Laurent Montel's avatar
Laurent Montel committed
247
            qApp->processEvents();
248
            m_root[Uml::ModelType::Logical]->appendViews(viewList);
Thibault Normand's avatar
Thibault Normand committed
249
250
251
            firstView = viewList.first();
        }

252
253
        if (firstView) {
            changeCurrentView(firstView->umlScene()->ID());
Thibault Normand's avatar
Thibault Normand committed
254
255
256
257
258
            UMLApp::app()->setDiagramMenuItemsState(true);
        }
    }
}

259
260
261
262
263
/**
 * Sets the URL of the document.
 *
 * @param url   The KUrl to set.
 */
264
265
void UMLDoc::setUrl(const KUrl &url)
{
Thibault Normand's avatar
Thibault Normand committed
266
267
268
    m_doc_url = url;
}

269
270
271
272
273
/**
 * Returns the KUrl of the document.
 *
 * @return  The KUrl of this UMLDoc.
 */
274
275
const KUrl& UMLDoc::url() const
{
Thibault Normand's avatar
Thibault Normand committed
276
277
278
    return m_doc_url;
}

279
280
281
282
283
284
285
286
/**
 * Sets the URL of the document to "Untitled".
 */
void UMLDoc::setUrlUntitled()
{
    m_doc_url.setFileName(i18n("Untitled"));
}

287
288
289
290
291
292
/**
 * "save modified" - Asks the user for saving if the document
 * is modified.
 *
 * @return  True if document can be closed.
 */
293
294
bool UMLDoc::saveModified()
{
Thibault Normand's avatar
Thibault Normand committed
295
    bool completed(true);
296
    if (!m_modified) {
Thibault Normand's avatar
Thibault Normand committed
297
        return completed;
298
    }
Thibault Normand's avatar
Thibault Normand committed
299
300

    UMLApp *win = UMLApp::app();
301
302
303
304
    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());
305
    switch(want_save) {
Thibault Normand's avatar
Thibault Normand committed
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
    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;
}

338
339
340
/**
 * Closes the current document.
 */
341
342
void UMLDoc::closeDocument()
{
343
    m_bClosing = true;
344
    UMLApp::app()->setGenerator(Uml::ProgrammingLanguage::Reserved);  // delete the codegen
345
    m_Doc = QString();
346
    DocWindow* dw = UMLApp::app()->docWindow();
Thibault Normand's avatar
Thibault Normand committed
347
    if (dw) {
348
        dw->reset();
Thibault Normand's avatar
Thibault Normand committed
349
350
    }

351
    UMLListView *listView = UMLApp::app()->listView();
Thibault Normand's avatar
Thibault Normand committed
352
    if (listView) {
353
        listView->clean();
Thibault Normand's avatar
Thibault Normand committed
354
355
356
357
358
359
360
361
362
363
364
365
366
367
        // 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.
368
369
        removeAllObjects();

Thibault Normand's avatar
Thibault Normand committed
370
        // Restore the datatype folder, it has been deleted above.
371
        createDatatypeFolder();
Ralf Habacker's avatar
Ralf Habacker committed
372
        // this creates to much items only Logical View should be created
373
        listView->init();
Thibault Normand's avatar
Thibault Normand committed
374
375
376
377
378
379
380
        /* Remove any stereotypes.
        if (m_stereoList.count() > 0) {
            UMLStereotype *s;
            for (UMLStereotypeListIt sit(m_stereoList); (s = sit.current()) != 0; ++sit)
                delete s;
            m_stereoList.clear();
        }
381
        */
Thibault Normand's avatar
Thibault Normand committed
382
    }
383
    m_bClosing = false;
Thibault Normand's avatar
Thibault Normand committed
384
385
}

386
387
388
389
390
/**
 * Initializes the document generally.
 *
 * @return  True if operation successful.
 */
391
392
bool UMLDoc::newDocument()
{
Thibault Normand's avatar
Thibault Normand committed
393
    closeDocument();
394
    UMLApp::app()->setCurrentView(0);
395
396
    setUrlUntitled();

Thibault Normand's avatar
Thibault Normand committed
397
    //see if we need to start with a new diagram
398
    Settings::OptionState optionState = Settings::optionState();
Andi Fischer's avatar
Andi Fischer committed
399
400
    Uml::DiagramType::Enum dt = optionState.generalState.diagram;
    Uml::ModelType::Enum mt = Model_Utils::convert_DT_MT(dt);
401
402
403
    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
404
    }
405
406
    QString name = createDiagramName(dt, false);
    createDiagram(m_root[mt], dt, name);
Thibault Normand's avatar
Thibault Normand committed
407
408
409
410
411
412

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

    setModified(false);
    initSaveTimer();

413
    UMLApp::app()->enableUndoAction(false);
414
    UMLApp::app()->clearUndoStack();
Thibault Normand's avatar
Thibault Normand committed
415
416
417
418

    return true;
}

419
420
421
422
423
424
425
426
/**
 * 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.
 */
427
428
429
bool UMLDoc::openDocument(const KUrl& url, const char* format /* =0 */)
{
    Q_UNUSED(format);
430
    if (url.fileName().length() == 0) {
Thibault Normand's avatar
Thibault Normand committed
431
432
433
434
435
436
437
438
        newDocument();
        return false;
    }

    m_doc_url = url;
    closeDocument();
    // IMPORTANT: set m_bLoading to true
    // _AFTER_ the call of UMLDoc::closeDocument()
439
    // as it sets m_bLoading to false after it was temporarily
Thibault Normand's avatar
Thibault Normand committed
440
441
442
    // changed to true to block recording of changes in redo-buffer
    m_bLoading = true;
    QString tmpfile;
443
    KIO::NetAccess::download(url, tmpfile, UMLApp::app());
444
445
    QFile file(tmpfile);
    if (!file.exists()) {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
446
        KMessageBox::error(0, i18n("The file %1 does not exist.", url.pathOrUrl()), i18n("Load Error"));
447
448
        setUrlUntitled();

Thibault Normand's avatar
Thibault Normand committed
449
450
451
452
453
454
455
456
457
458
        m_bLoading = false;
        newDocument();
        return false;
    }

    // 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();
459
    QString mimetype;
Oliver Kellogg's avatar
Oliver Kellogg committed
460
461
462
463
    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
464
465
    }

466
    if (mimetype.isEmpty() == false) {
Thibault Normand's avatar
Thibault Normand committed
467
        KTar archive(tmpfile, mimetype);
468
        if (archive.open(QIODevice::ReadOnly) == false) {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
469
            KMessageBox::error(0, i18n("The file %1 seems to be corrupted.", url.pathOrUrl()), i18n("Load Error"));
470
            KIO::NetAccess::removeTempFile(tmpfile);
471
472
            setUrlUntitled();

Thibault Normand's avatar
Thibault Normand committed
473
474
475
476
477
478
479
            m_bLoading = false;
            newDocument();
            return false;
        }

        // get the root directory and all entries in
        const KArchiveDirectory * rootDir = archive.directory();
480
        const QStringList entries = rootDir->entries();
Thibault Normand's avatar
Thibault Normand committed
481
482
        QString entryMimeType;
        bool foundXMI = false;
483
484
        QStringList::ConstIterator it;
        QStringList::ConstIterator end(entries.end());
Thibault Normand's avatar
Thibault Normand committed
485
486

        // now go through all entries till we find an xmi file
487
        for (it = entries.begin(); it != end; ++it) {
Thibault Normand's avatar
Thibault Normand committed
488
            // only check files, we do not go in subdirectories
489
            if (rootDir->entry(*it)->isFile() == true) {
Thibault Normand's avatar
Thibault Normand committed
490
491
                // we found a file, check the mimetype
                entryMimeType = KMimeType::findByPath(*it, 0, true)->name();
Oliver Kellogg's avatar
Oliver Kellogg committed
492
                if (entryMimeType == QLatin1String("application/x-uml")) {
Thibault Normand's avatar
Thibault Normand committed
493
494
495
496
497
498
499
                    foundXMI = true;
                    break;
                }
            }
        }

        // if we found an XMI file, we have to extract it to a temporary file
500
        if (foundXMI == true) {
Thibault Normand's avatar
Thibault Normand committed
501
502
503
504
505
506
            KTempDir tmp_dir;
            KArchiveEntry * entry;
            KArchiveFile * fileEntry;

            // try to cast the file entry in the archive to an archive entry
            entry = const_cast<KArchiveEntry*>(rootDir->entry(*it));
507
            if (entry == 0) {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
508
                KMessageBox::error(0, i18n("There was no XMI file found in the compressed file %1.", url.pathOrUrl()),
509
                                   i18n("Load Error"));
510
                KIO::NetAccess::removeTempFile(tmpfile);
511
512
                setUrlUntitled();

Thibault Normand's avatar
Thibault Normand committed
513
514
515
516
517
518
519
520
                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);
521
            if (fileEntry == 0) {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
522
                KMessageBox::error(0, i18n("There was no XMI file found in the compressed file %1.", url.pathOrUrl()),
523
                                   i18n("Load Error"));
524
                KIO::NetAccess::removeTempFile(tmpfile);
525
526
                setUrlUntitled();

Thibault Normand's avatar
Thibault Normand committed
527
528
529
530
531
532
533
534
535
536
                m_bLoading = false;
                newDocument();
                return false;
            }

            // now we can extract the file to the temporary directory
            fileEntry->copyTo(tmp_dir.name());

            // now open the extracted file for reading
            QFile xmi_file(tmp_dir.name() + *it);
537
            if(!xmi_file.open(QIODevice::ReadOnly)) {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
538
                KMessageBox::error(0, i18n("There was a problem loading the extracted file: %1", url.pathOrUrl()),
539
                                   i18n("Load Error"));
540
                KIO::NetAccess::removeTempFile(tmpfile);
541
542
                setUrlUntitled();

Thibault Normand's avatar
Thibault Normand committed
543
544
545
546
                m_bLoading = false;
                newDocument();
                return false;
            }
547
            m_bTypesAreResolved = false;
548
            status = loadFromXMI(xmi_file, ENC_UNKNOWN);
Thibault Normand's avatar
Thibault Normand committed
549
550
551
552

            // close the extracted file and the temporary directory
            xmi_file.close();
        } else {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
553
            KMessageBox::error(0, i18n("There was no XMI file found in the compressed file %1.", url.pathOrUrl()),
554
                               i18n("Load Error"));
555
            KIO::NetAccess::removeTempFile(tmpfile);
556
557
            setUrlUntitled();

Thibault Normand's avatar
Thibault Normand committed
558
559
560
561
562
563
            m_bLoading = false;
            newDocument();
            return false;
        }

        archive.close();
564
    } else {
Thibault Normand's avatar
Thibault Normand committed
565
        // no, it seems to be an ordinary file
566
        if (!file.open(QIODevice::ReadOnly)) {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
567
            KMessageBox::error(0, i18n("There was a problem loading file: %1", url.pathOrUrl()),
568
                               i18n("Load Error"));
569
            KIO::NetAccess::removeTempFile(tmpfile);
570
571
            setUrlUntitled();

Thibault Normand's avatar
Thibault Normand committed
572
573
574
575
            m_bLoading = false;
            newDocument();
            return false;
        }
576
        if (filetype.endsWith(QLatin1String(".mdl"))) {
577
578
            setUrlUntitled();

579
            m_bTypesAreResolved = false;
Thibault Normand's avatar
Thibault Normand committed
580
            status = Import_Rose::loadFromMDL(file);
581
            if (status) {
582
583
584
585
586
                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);
                }
587
            }
588
        }
589
        else if (filetype.endsWith(QLatin1String(".zargo"))) {
590
591
            setUrlUntitled();

592
            status = Import_Argo::loadFromZArgoFile(file);
593
594
        }
        else {
595
            m_bTypesAreResolved = false;
596
            status = loadFromXMI(file, ENC_UNKNOWN);
597
        }
Thibault Normand's avatar
Thibault Normand committed
598
599
    }

600
601
    if (file.isOpen())
        file.close();
602
    KIO::NetAccess::removeTempFile(tmpfile);
603
604
    m_bLoading = false;
    m_bTypesAreResolved = true;
605
    if (!status) {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
606
        KMessageBox::error(0, i18n("There was a problem loading file: %1", url.pathOrUrl()),
607
                           i18n("Load Error"));
Thibault Normand's avatar
Thibault Normand committed
608
609
610
611
612
613
        newDocument();
        return false;
    }
    setModified(false);
    initSaveTimer();

614
    UMLApp::app()->enableUndoAction(false);
615
    UMLApp::app()->clearUndoStack();
Thibault Normand's avatar
Thibault Normand committed
616
617
618
619
620
621
    // for compatibility
    addDefaultStereotypes();

    return true;
}

622
623
624
625
626
627
628
/**
 * 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.
 */
629
630
631
bool UMLDoc::saveDocument(const KUrl& url, const char * format)
{
    Q_UNUSED(format);
Thibault Normand's avatar
Thibault Normand committed
632
633
634
635
636
637
    m_doc_url = url;
    bool uploaded = true;

    // first, we have to find out which format to use
    QString strFileName = url.path(KUrl::RemoveTrailingSlash);
    QFileInfo fileInfo(strFileName);
638
    QString fileExt = fileInfo.completeSuffix();
Oliver Kellogg's avatar
Oliver Kellogg committed
639
640
641
642
643
644
645
    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
646
    } else {
Oliver Kellogg's avatar
Oliver Kellogg committed
647
        fileFormat = QLatin1String("xmi");
Thibault Normand's avatar
Thibault Normand committed
648
649
650
651
    }

    initSaveTimer();

Oliver Kellogg's avatar
Oliver Kellogg committed
652
    if (fileFormat == QLatin1String("tgz") || fileFormat == QLatin1String("bz2")) {
Thibault Normand's avatar
Thibault Normand committed
653
654
655
656
657
658
        KTar * archive;
        KTemporaryFile tmp_tgz_file;
        tmp_tgz_file.setAutoRemove(false);
        tmp_tgz_file.open();

        // first we have to check if we are saving to a local or remote file
659
        if (url.isLocalFile()) {
Oliver Kellogg's avatar
Oliver Kellogg committed
660
661
            if (fileFormat == QLatin1String("tgz")) {  // check tgz or bzip
                archive = new KTar(url.toLocalFile(), QLatin1String("application/x-gzip"));
Thibault Normand's avatar
Thibault Normand committed
662
            } else {
Oliver Kellogg's avatar
Oliver Kellogg committed
663
                archive = new KTar(url.toLocalFile(), QLatin1String("application/x-bzip"));
Thibault Normand's avatar
Thibault Normand committed
664
665
            }
        } else {
Oliver Kellogg's avatar
Oliver Kellogg committed
666
667
            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
668
            } else {
Oliver Kellogg's avatar
Oliver Kellogg committed
669
                archive = new KTar(tmp_tgz_file.fileName(), QLatin1String("application/x-bzip"));
Thibault Normand's avatar
Thibault Normand committed
670
671
672
673
            }
        }

        // now check if we can write to the file
674
        if (archive->open(QIODevice::WriteOnly) == false) {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
675
            KMessageBox::error(0, i18n("There was a problem saving file: %1", url.pathOrUrl()), i18n("Save Error"));
Laurent Montel's avatar
Laurent Montel committed
676
            delete archive;
Thibault Normand's avatar
Thibault Normand committed
677
678
679
680
681
682
683
            return false;
        }

        // we have to create a temporary xmi file
        // we will add this file later to the archive
        KTemporaryFile tmp_xmi_file;
        tmp_xmi_file.setAutoRemove(false);
684
        if (!tmp_xmi_file.open()) {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
685
            KMessageBox::error(0, i18n("There was a problem saving file: %1", url.pathOrUrl()), i18n("Save Error"));
Laurent Montel's avatar
Laurent Montel committed
686
            delete archive;
Thibault Normand's avatar
Thibault Normand committed
687
688
689
690
691
692
            return false;
        }
        saveToXMI(tmp_xmi_file); // save XMI to this file...

        // now add this file to the archive, but without the extension
        QString tmpQString = url.fileName();
Oliver Kellogg's avatar
Oliver Kellogg committed
693
694
        if (fileFormat == QLatin1String("tgz")) {
            tmpQString.remove(QRegExp(QLatin1String("\\.tgz$")));
695
696
        }
        else {
Oliver Kellogg's avatar
Oliver Kellogg committed
697
            tmpQString.remove(QRegExp(QLatin1String("\\.tar\\.bz2$")));
Thibault Normand's avatar
Thibault Normand committed
698
699
700
        }
        archive->addLocalFile(tmp_xmi_file.fileName(), tmpQString);

701
        if (!archive->close()) {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
702
            KMessageBox::error(0, i18n("There was a problem saving file: %1", url.pathOrUrl()), i18n("Save Error"));
Laurent Montel's avatar
Laurent Montel committed
703
            delete archive;
Thibault Normand's avatar
Thibault Normand committed
704
705
706
707
708
709
            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
710
711
        if (!url.isLocalFile()) {
            uploaded = KIO::NetAccess::upload(tmp_tgz_file.fileName(), m_doc_url, UMLApp::app());
Thibault Normand's avatar
Thibault Normand committed
712
713
714
715
716
717
718
        }

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

719
720
    }
    else {
Thibault Normand's avatar
Thibault Normand committed
721
722
723
724
725
726
727
728
        // save as normal uncompressed XMI

        KTemporaryFile tmpfile; // we need this tmp file if we are writing to a remote file
        tmpfile.setAutoRemove(false);

        // save in _any_ case to a temp file
        // -> if something goes wrong during saveToXmi, the
        //     original content is preserved
729
730
        //     (e.g. if umbrello dies in the middle of the document model parsing
        //      for saveToXMI due to some problems)
731
        /// @todo insert some checks in saveToXMI to detect a failed save attempt
Thibault Normand's avatar
Thibault Normand committed
732
733

        // lets open the file for writing
734
        if (!tmpfile.open()) {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
735
            KMessageBox::error(0, i18n("There was a problem saving file: %1", url.pathOrUrl()), i18n("Save Error"));
Thibault Normand's avatar
Thibault Normand committed
736
737
738
            return false;
        }
        saveToXMI(tmpfile); // save the xmi stuff to it
739

Thibault Normand's avatar
Thibault Normand committed
740
        // if it is a remote file, we have to upload the tmp file
741
742
        if (!url.isLocalFile()) {
            uploaded = KIO::NetAccess::upload(tmpfile.fileName(), m_doc_url, UMLApp::app());
743
744
        }
        else {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
745
            // now remove the original file
746
#ifdef Q_OS_WIN
Christian Ehrlicher's avatar
Christian Ehrlicher committed
747
            tmpfile.setAutoRemove(true);
748
            KIO::FileCopyJob* fcj = KIO::file_copy(tmpfile.fileName(), url, -1, KIO::Overwrite);
749
#else
750
            KIO::FileCopyJob* fcj = KIO::file_move(tmpfile.fileName(), url, -1, KIO::Overwrite);
Christian Ehrlicher's avatar
Christian Ehrlicher committed
751
#endif
752
            if (KIO::NetAccess::synchronousRun(fcj, (QWidget*)UMLApp::app()) == false) {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
753
                KMessageBox::error(0, i18n("There was a problem saving file: %1", url.pathOrUrl()), i18n("Save Error"));
754
755
                setUrlUntitled();

Thibault Normand's avatar
Thibault Normand committed
756
757
758
759
                return false;
            }
        }
    }
760
    if (!uploaded) {
Christian Ehrlicher's avatar
Christian Ehrlicher committed
761
        KMessageBox::error(0, i18n("There was a problem uploading file: %1", url.pathOrUrl()), i18n("Save Error"));
762
763
        setUrlUntitled();

Thibault Normand's avatar
Thibault Normand committed
764
765
766
767
768
    }
    setModified(false);
    return uploaded;
}

769
770
771
/**
 * Sets up the signals needed by the program for it to work.
 */
772
773
void UMLDoc::setupSignals()
{
774
    WorkToolBar *tb = UMLApp::app()->workToolBar();
775
    connect(this, SIGNAL(sigDiagramChanged(Uml::DiagramType::Enum)), tb, SLOT(slotCheckToolBar(Uml::DiagramType::Enum)));
Thibault Normand's avatar
Thibault Normand committed
776
777
}

778
779
780
781
782
783
/**
 * Finds a view (diagram) by the ID given to method.
 *
 * @param id   The ID of the view to search for.
 * @return  Pointer to the view found, or NULL if not found.
 */
Andi Fischer's avatar
Andi Fischer committed
784
UMLView * UMLDoc::findView(Uml::ID::Type id)
785
{
786
    UMLView *v = 0;
787
    for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) {
Thibault Normand's avatar
Thibault Normand committed
788
        v = m_root[i]->findView(id);
789
        if (v) {
Thibault Normand's avatar
Thibault Normand committed
790
            break;
791
        }
Thibault Normand's avatar
Thibault Normand committed
792
793
794
795
    }
    return v;
}

796
797
798
799
800
801
802
803
/**
 * Finds a view (diagram) by the type and name given.
 *
 * @param type            The type of view to find.
 * @param name            The name of the view to find.
 * @param searchAllScopes Search in all subfolders (default: false.)
 * @return  Pointer to the view found, or NULL if not found.
 */
Andi Fischer's avatar
Andi Fischer committed
804
UMLView * UMLDoc::findView(Uml::DiagramType::Enum type, const QString &name,
805
806
                           bool searchAllScopes /* =false */)
{
Andi Fischer's avatar
Andi Fischer committed
807
    Uml::ModelType::Enum mt = Model_Utils::convert_DT_MT(type);
Thibault Normand's avatar
Thibault Normand committed
808
809
810
    return m_root[mt]->findView(type, name, searchAllScopes);
}

811
812
813
814
815
816
/**
 * Used to find a reference to a @ref UMLObject by its ID.
 *
 * @param id   The @ref UMLObject to find.
 * @return  Pointer to the UMLObject found, or NULL if not found.
 */
Andi Fischer's avatar
Andi Fischer committed
817
UMLObject* UMLDoc::findObjectById(Uml::ID::Type id)
818
{
819
    UMLObject *o = 0;
820
    for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) {
821
        if (id == m_root[i]->id()) {
Thibault Normand's avatar
Thibault Normand committed
822
            return m_root[i];
823
        }
Thibault Normand's avatar
Thibault Normand committed
824
        o = m_root[i]->findObjectById(id);
825
        if (o) {
Thibault Normand's avatar
Thibault Normand committed
826
            return o;
827
        }
Thibault Normand's avatar
Thibault Normand committed
828
829
830
831
832
    }
    o = findStereotypeById(id);
    return o;
}

833
834
835
836
/**
 * Used to find a @ref UMLObject by its type and name.
 *
 * @param name         The name of the @ref UMLObject to find.
837
 * @param type         ObjectType of the object to find (optional.)
838
839
840
841
842
843
844
845
 *                     When the given type is ot_UMLObject the type is
 *                     disregarded, i.e. the given name is the only
 *                     search criterion.
 * @param currentObj   Object relative to which to search (optional.)
 *                     If given then the enclosing scope(s) of this
 *                     object are searched before the global scope.
 * @return  Pointer to the UMLObject found, or NULL if not found.
 */
Thibault Normand's avatar
Thibault Normand committed
846
UMLObject* UMLDoc::findUMLObject(const QString &name,
847
                                 UMLObject::ObjectType type /* = ot_UMLObject */,
848
                                 UMLObject *currentObj /* = 0 */)
849
{
Thibault Normand's avatar
Thibault Normand committed
850
    UMLObject *o = m_datatypeRoot->findObject(name);
851
    if (o) {
Thibault Normand's avatar
Thibault Normand committed
852
        return o;
853
    }
854
    for (int i = 0; i < Uml::ModelType::N_MODELTYPES; ++i) {
Thibault Normand's avatar
Thibault Normand committed
855
        UMLObjectList list = m_root[i]->containedObjects();
856
857
        if (list.size() == 0)
            continue;
Thibault Normand's avatar
Thibault Normand committed
858
        o = Model_Utils::findUMLObject(list, name, type, currentObj);
859
        if (o) {
Thibault Normand's avatar
Thibault Normand committed
860
            return o;
861
        }
862
        if ((type == UMLObject::ot_UMLObject || type == UMLObject::ot_Folder) &&
863
             name == m_root[i]->name()) {
Thibault Normand's avatar
Thibault Normand committed
864
            return m_root[i];
865
        }
Thibault Normand's avatar
Thibault Normand committed
866
    }
867
    return 0;
Thibault Normand's avatar
Thibault Normand committed
868
869
}

870
871
872
/**
 * Used to find a @ref UMLObject by its type and raw name.
 *
873
 * @param modelType    The model type in which to search for the object
874
875
876
877
 * @param name         The raw name of the @ref UMLObject to find.
 * @param type         ObjectType of the object to find
 * @return  Pointer to the UMLObject found, or NULL if not found.
 */
Andi Fischer's avatar
Andi Fischer committed
878
UMLObject* UMLDoc::findUMLObjectRaw(Uml::ModelType::Enum modelType,
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
                                    const QString &name,
                                    UMLObject::ObjectType type)
{
    return findUMLObjectRaw(rootFolder(modelType), name, type);
}

/**
 * Used to find a @ref UMLObject by its type and raw name.
 *
 * @param folder       The UMLFolder in which to search for the object
 * @param name         The raw name of the @ref UMLObject to find.
 * @param type         ObjectType of the object to find
 * @return  Pointer to the UMLObject found, or NULL if not found.
 */
UMLObject* UMLDoc::findUMLObjectRaw(UMLFolder *folder,
                                    const QString &name,
                                    UMLObject::ObjectType type)
{
    if (folder == 0)
        return 0;
    UMLObjectList list = folder->containedObjects();
    if (list.size() == 0)
        return 0;
    return Model_Utils::findUMLObjectRaw(list, name, type, 0);
}

905
906
907
908
909
/**
 * Used to find a @ref UMLClassifier by its name.
 *
 * @param name   The name of the @ref UMLObject to find.
 */
910
911
UMLClassifier* UMLDoc::findUMLClassifier(const QString &name)
{
Thibault Normand's avatar
Thibault Normand committed
912
913
914
915
916
    //this is used only by code generator so we don't need to look at Datatypes
    UMLObject * obj = findUMLObject(name);
    return dynamic_cast<UMLClassifier*>(obj);
}

917
918
919
920
921
922
923
/**
 * Adds a UMLObject thats already created but doesn't change
 * any ids or signal.  Use AddUMLObjectPaste if pasting.
 *
 * @param object   The object to add.
 * @return  True if the object was actually added.
 */
924
925
bool UMLDoc::addUMLObject(UMLObject* object)
{
926
    UMLObject::ObjectType ot = object->baseType();
927
928
    if (ot == UMLObject::ot_Attribute || ot == UMLObject::ot_Operation || ot == UMLObject::ot_EnumLiteral
            || ot == UMLObject::ot_EntityAttribute || ot == UMLObject::ot_Template || ot == UMLObject::ot_Stereotype) {
929
        DEBUG(DBG_SRC) << object->name() << ": not adding type " << ot;
Thibault Normand's avatar
Thibault Normand committed
930
931
        return false;
    }
932
    UMLPackage *pkg = object->umlPackage();
933
    if (pkg == 0) {
Thibault Normand's avatar
Thibault Normand committed
934
        pkg = currentRoot();
935
936
        DEBUG(DBG_SRC) << object->name() << ": no parent package set, assuming "
                       << pkg->name();
937
        object->setUMLPackage(pkg);
Thibault Normand's avatar
Thibault Normand committed
938
    }
939

Thibault Normand's avatar
Thibault Normand committed
940
941
942
    return pkg->addObject(object);
}

943
944
/**
 * Write text to the status bar.
945
 * @param text   the text to write
946
 */
947
948
void UMLDoc::writeToStatusBar(const QString &text)
{
Thibault Normand's avatar
Thibault Normand committed
949
950
951
    emit sigWriteToStatusBar(text);
}

952
953
954
955
/**
 * Simple removal of an object.
 * @param object   the UMLObject to be removed
 */
956
957
void UMLDoc::slotRemoveUMLObject(UMLObject* object)
{
Thibault Normand's avatar
Thibault Normand committed
958
    //m_objectList.remove(object);
959
    UMLPackage *pkg = object->umlPackage();
960
    if (pkg == 0) {
961
        uError() << object->name() << ": parent package is not set !";
Thibault Normand's avatar
Thibault Normand committed
962
963
964
965
966
        return;
    }
    pkg->removeObject(object);
}

967
968
969
970
971
972
/**
 * Returns true if the given name is unique within its scope.
 *
 * @param name   The name to check.
 * @return  True if name is unique.
 */
Thibault Normand's avatar
Thibault Normand committed
973
974
bool UMLDoc::isUnique(const QString &name)
{
975
    UMLListView *listView = UMLApp::app()->listView();
Thibault Normand's avatar
Thibault Normand committed
976
977
978
979
980
981
    UMLListViewItem *currentItem = (UMLListViewItem*)listView->currentItem();
    UMLListViewItem *parentItem = 0;

    // check for current item, if its a package, then we do a check on that
    // otherwise, if current item exists, find its parent and check if thats
    // a package..
982
    if (currentItem) {
Thibault Normand's avatar
Thibault Normand committed
983
984
        // its possible that the current item *is* a package, then just
        // do check now
985
986
        if (Model_Utils::typeIsContainer(currentItem->type())) {
            return isUnique (name, (UMLPackage*) currentItem->umlObject());
987
        }
Thibault Normand's avatar
Thibault Normand committed
988
989
990
991
        parentItem = (UMLListViewItem*)currentItem->parent();
    }

    // item is in a package so do check only in that
992
993
    if (parentItem != 0 && Model_Utils::typeIsContainer(parentItem->type())) {
        UMLPackage *parentPkg = static_cast<UMLPackage*>(parentItem->umlObject());
Thibault Normand's avatar
Thibault Normand committed
994
995
996
        return isUnique(name, parentPkg);
    }

997
    uError() << name << ": Not currently in a package";
Thibault Normand's avatar
Thibault Normand committed
998
999
1000
    /* Check against all objects that _don't_ have a parent package.
    for (UMLObjectListIt oit(m_objectList); oit.current(); ++oit) {
        UMLObject *obj = oit.current();
1001
        if ((obj->getUMLPackage() == 0) && (obj->getName() == name))
Thibault Normand's avatar
Thibault Normand committed
1002
1003
1004
1005
1006
1007
            return false;
    }
     */
    return true;
}

1008
1009
1010
1011
1012
1013
1014
/**
 * Returns true if the given name is unique within its scope of given package.
 *
 * @param name      The name to check.
 * @param package   The UMLPackage in which we have to determine the unique-ness
 * @return      True if name is unique.
 */
Thibault Normand's avatar
Thibault Normand committed
1015
1016
1017
bool UMLDoc::isUnique(const QString &name, UMLPackage *package)
{
    // if a package, then only do check in that
1018
    if (package) {
1019
        return (package->findObject(name) == 0);
1020
    }
Thibault Normand's avatar
Thibault Normand committed
1021
1022

    // Not currently in a package: ERROR
1023
    uError() << name << " (2): Not currently in a package";
Thibault Normand's avatar
Thibault Normand committed
1024
1025
1026
    /* Check against all objects that _don't_ have a parent package.
    for (UMLObjectListIt oit(m_objectList); oit.current(); ++oit) {
        UMLObject *obj = oit.current();
1027
        if ((obj->getUMLPackage() == 0) && (obj->getName() == name))