~ubuntu-branches/ubuntu/trusty/umbrello/trusty-updates

« back to all changes in this revision

Viewing changes to umbrello/layoutgenerator.cpp

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2014-04-10 22:35:55 UTC
  • mfrom: (1.1.17)
  • Revision ID: package-import@ubuntu.com-20140410223555-3dqzi1qmdok9hahe
Tags: 4:4.13.0-0ubuntu1
New upstream KDE Software Compilation release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/***************************************************************************
 
2
 *   This program is free software; you can redistribute it and/or modify  *
 
3
 *   it under the terms of the GNU General Public License as published by  *
 
4
 *   the Free Software Foundation; either version 2 of the License, or     *
 
5
 *   (at your option) any later version.                                   *
 
6
 *                                                                         *
 
7
 *   copyright (C) 2012-2014                                               *
 
8
 *   Umbrello UML Modeller Authors <umbrello-devel@kde.org>                *
 
9
 ***************************************************************************/
 
10
 
 
11
#include "layoutgenerator.h"
 
12
 
 
13
#include "associationline.h"
 
14
#include "associationwidget.h"
 
15
#include "debug_utils.h"
 
16
#include "floatingtextwidget.h"
 
17
#include "umlwidget.h"
 
18
 
 
19
// app includes
 
20
#include <KConfigGroup>
 
21
#include <KDesktopFile>
 
22
#include <KStandardDirs>
 
23
 
 
24
// qt includes
 
25
#include <QDir>
 
26
#include <QFile>
 
27
#include <QHash>
 
28
#include <QProcess>
 
29
#include <QRegExp>
 
30
#include <QString>
 
31
#include <QTemporaryFile>
 
32
//#include <QTextStream>
 
33
 
 
34
#define LAYOUTGENERATOR_DEBUG
 
35
//#define LAYOUTGENERATOR_DATA_DEBUG
 
36
 
 
37
#ifdef LAYOUTGENERATOR_DEBUG
 
38
 
 
39
static QString pngViewer()
 
40
{
 
41
#ifdef Q_WS_WIN
 
42
    return "start";
 
43
#else
 
44
#ifdef Q_WS_MAC
 
45
    return "unknown";
 
46
#else
 
47
    return "okular";
 
48
#endif
 
49
#endif
 
50
}
 
51
 
 
52
static QString textViewer()
 
53
{
 
54
#ifdef Q_WS_WIN
 
55
    return "start";
 
56
#else
 
57
#ifdef Q_WS_MAC
 
58
    return "unknown";
 
59
#else
 
60
    return "mcedit";
 
61
#endif
 
62
#endif
 
63
}
 
64
#endif
 
65
 
 
66
/**
 
67
 * constructor
 
68
*/
 
69
LayoutGenerator::LayoutGenerator()
 
70
{
 
71
    setUseFullNodeLabels(false);
 
72
}
 
73
 
 
74
/**
 
75
 * Return state if layout generator is enabled.
 
76
 * It is enabled when the dot application has been found.
 
77
 *
 
78
 * @return true if enabled
 
79
*/
 
80
bool LayoutGenerator::isEnabled()
 
81
{
 
82
    Settings::OptionState& optionState = Settings::optionState();
 
83
    if (optionState.autoLayoutState.autoDotPath) {
 
84
        m_dotPath = currentDotPath();
 
85
    }
 
86
    else if (!optionState.autoLayoutState.dotPath.isEmpty()) {
 
87
        m_dotPath = optionState.autoLayoutState.dotPath;
 
88
    }
 
89
    return !m_dotPath.isEmpty();
 
90
}
 
91
 
 
92
/**
 
93
 * Return the path where dot is installed.
 
94
 *
 
95
 * @return string with dot path
 
96
 */
 
97
QString LayoutGenerator::currentDotPath()
 
98
{
 
99
    QString executable = KStandardDirs::findExe("dot");
 
100
    if (!executable.isEmpty()) {
 
101
        QFileInfo fi(executable);
 
102
        return fi.absolutePath();
 
103
    }
 
104
#ifdef Q_OS_WIN
 
105
    // search for dot installation
 
106
    QString appDir(qgetenv("ProgramFiles"));
 
107
    QDir dir(appDir);
 
108
    dir.setFilter(QDir::Dirs);
 
109
    dir.setNameFilters(QStringList() << "Graphviz*");
 
110
    dir.setSorting(QDir::Reversed);
 
111
    QFileInfoList list = dir.entryInfoList();
 
112
    if (list.size() > 0) {
 
113
        QString dotPath = list.at(0).absoluteFilePath();
 
114
        QString exePath = QFile::exists(dotPath + "\\bin") ? dotPath + "\\bin" : dotPath;
 
115
        return QFile::exists(exePath + "\\dot.exe") ? exePath : "";
 
116
    }
 
117
#endif
 
118
    return QString();
 
119
}
 
120
 
 
121
/**
 
122
 * generate layout and apply it to the given diagram.
 
123
 *
 
124
 * @return true if generating succeeded
 
125
*/
 
126
bool LayoutGenerator::generate(UMLScene *scene, const QString &variant)
 
127
{
 
128
    QTemporaryFile in;
 
129
    QTemporaryFile out;
 
130
    QTemporaryFile xdotOut;
 
131
    if (!isEnabled()) {
 
132
        uWarning() << "Could not apply autolayout because graphviz installation has not been found.";
 
133
        return false;
 
134
    }
 
135
 
 
136
#ifdef LAYOUTGENERATOR_DEBUG
 
137
    in.setAutoRemove(false);
 
138
    out.setAutoRemove(false);
 
139
    xdotOut.setAutoRemove(false);
 
140
#endif
 
141
 
 
142
    // generate filenames
 
143
    in.open();
 
144
    in.close();
 
145
    out.open();
 
146
    out.close();
 
147
    xdotOut.open();
 
148
    xdotOut.close();
 
149
 
 
150
#ifdef LAYOUTGENERATOR_DEBUG
 
151
    qDebug() << textViewer() << in.fileName();
 
152
    qDebug() << textViewer() << out.fileName();
 
153
    qDebug() << textViewer() << xdotOut.fileName();
 
154
#endif
 
155
 
 
156
    if (!createDotFile(scene, in.fileName(), variant))
 
157
        return false;
 
158
 
 
159
    QString executable = m_dotPath + "/" + m_generator;
 
160
 
 
161
    QProcess p;
 
162
    QStringList args;
 
163
    args << "-o" << out.fileName() << "-Tplain-ext" << in.fileName();
 
164
    p.start(executable, args);
 
165
    p.waitForFinished();
 
166
 
 
167
    args.clear();
 
168
    args << "-o" << xdotOut.fileName() << "-Txdot" << in.fileName();
 
169
    p.start(executable, args);
 
170
    p.waitForFinished();
 
171
 
 
172
#ifdef LAYOUTGENERATOR_DEBUG
 
173
    QTemporaryFile pngFile;
 
174
    pngFile.setAutoRemove(false);
 
175
    pngFile.setFileTemplate(QDir::tempPath() + "/umbrello-layoutgenerator-XXXXXX.png");
 
176
    pngFile.open();
 
177
    pngFile.close();
 
178
    qDebug() << pngViewer() << pngFile.fileName();
 
179
    args.clear();
 
180
    args << "-o" << pngFile.fileName() << "-Tpng" << in.fileName();
 
181
    p.start(executable, args);
 
182
    p.waitForFinished();
 
183
#endif
 
184
#ifndef USE_XDOT
 
185
    if (!readGeneratedDotFile(out.fileName()))
 
186
#else
 
187
    if (!readGeneratedDotFile(xdotOut.fileName()))
 
188
#endif
 
189
        return false;
 
190
 
 
191
    return true;
 
192
}
 
193
 
 
194
/**
 
195
 * apply auto layout to the given scene
 
196
 * @param scene
 
197
 * @return true if autolayout has been applied
 
198
 */
 
199
bool LayoutGenerator::apply(UMLScene *scene)
 
200
{
 
201
    foreach(AssociationWidget *assoc, scene->associationList()) {
 
202
        AssociationLine *path = assoc->associationLine();
 
203
        QString type = Uml::AssociationType::toString(assoc->associationType()).toLower();
 
204
        QString key = "type::" + type;
 
205
 
 
206
        QString id;
 
207
        if (m_edgeParameters.contains("id::" + key) && m_edgeParameters["id::" + key] == "swap")
 
208
            id = fixID(Uml::ID::toString(assoc->widgetLocalIDForRole(Uml::RoleType::A)) + Uml::ID::toString(assoc->widgetLocalIDForRole(Uml::RoleType::B)));
 
209
        else
 
210
            id = fixID(Uml::ID::toString(assoc->widgetLocalIDForRole(Uml::RoleType::B)) + Uml::ID::toString(assoc->widgetLocalIDForRole(Uml::RoleType::A)));
 
211
 
 
212
        // adjust associations not used in the dot file
 
213
        if (!m_edges.contains(id)) {
 
214
            // shorten line path
 
215
            AssociationLine *path = assoc->associationLine();
 
216
            if (path->count() > 2 && assoc->widgetLocalIDForRole(Uml::RoleType::A) != assoc->widgetLocalIDForRole(Uml::RoleType::B)) {
 
217
                while(path->count() > 2)
 
218
                    path->removePoint(1);
 
219
            }
 
220
            continue;
 
221
        }
 
222
 
 
223
        EdgePoints &p = m_edges[id];
 
224
        int len = p.size();
 
225
 
 
226
        while(path->count() > 1) {
 
227
            path->removePoint(0);
 
228
        }
 
229
        path->setEndPoints(mapToScene(p[0]), mapToScene(p[len-1]));
 
230
 
 
231
        // set label position
 
232
        QPointF &l = m_edgeLabelPosition[id];
 
233
        FloatingTextWidget *tw = assoc->nameWidget();
 
234
        if (tw) {
 
235
            tw->setPos(mapToScene(l));
 
236
        }
 
237
        // FIXME: set remaining association line points
 
238
        /*
 
239
        for(int i = 1; i < len-1; i++) {
 
240
            path->insertPoint(i, mapToScene((p[i]));
 
241
        }
 
242
        */
 
243
        /*
 
244
         * here stuff could be added to add more points from information returned by dot.
 
245
        */
 
246
    }
 
247
 
 
248
    foreach(UMLWidget *widget, scene->widgetList()) {
 
249
        QString id = Uml::ID::toString(widget->localID());
 
250
        if (!m_nodes.contains(id))
 
251
            continue;
 
252
        QPoint p = origin(id);
 
253
        widget->setX(p.x());
 
254
        widget->setY(p.y()-widget->height());
 
255
        widget->adjustAssocs(widget->x(), widget->y());    // adjust assoc lines
 
256
    }
 
257
 
 
258
    foreach(AssociationWidget *assoc, scene->associationList()) {
 
259
        assoc->calculateEndingPoints();
 
260
        if (assoc->associationLine())
 
261
            assoc->associationLine()->update();
 
262
        assoc->resetTextPositions();
 
263
    }
 
264
    return true;
 
265
}
 
266
 
 
267
/**
 
268
 * Return a list of available templates for a given scene type
 
269
 *
 
270
 * @param scene The diagram
 
271
 * @param configFiles will contain the collected list of config files
 
272
 * @return true if collecting succeeds
 
273
 */
 
274
bool LayoutGenerator::availableConfigFiles(UMLScene *scene, QHash<QString,QString> &configFiles)
 
275
{
 
276
    QString diagramType = Uml::DiagramType::toString(scene->type()).toLower();
 
277
    KStandardDirs dirs;
 
278
 
 
279
    QStringList fileNames = dirs.findAllResources("data", QString("umbrello/layouts/%1*.desktop").arg(diagramType));
 
280
    foreach(const QString &fileName, fileNames) {
 
281
        QFileInfo fi(fileName);
 
282
        QString baseName;
 
283
        if (fi.baseName().contains("-"))
 
284
            baseName = fi.baseName().remove(diagramType + "-");
 
285
        else if (fi.baseName() == diagramType)
 
286
            baseName = fi.baseName();
 
287
        else
 
288
            baseName = "default";
 
289
        KDesktopFile desktopFile(fileName);
 
290
        configFiles[baseName] = desktopFile.readName();
 
291
    }
 
292
    return true;
 
293
}
 
294
 
 
295
/**
 
296
 * Return the origin of node based on the bottom/left corner
 
297
 *
 
298
 * @param id The widget id to fetch the origin from
 
299
 * @return QPoint instance with the coordinates
 
300
 */
 
301
QPoint LayoutGenerator::origin(const QString &id)
 
302
{
 
303
    QString key = fixID(id);
 
304
    if (!m_nodes.contains(key)) {
 
305
#ifdef LAYOUTGENERATOR_DATA_DEBUG
 
306
        uDebug() << key;
 
307
#endif
 
308
        return QPoint(0,0);
 
309
    }
 
310
    QRectF &r = m_nodes[key];
 
311
    QPoint p(m_origin.x() + r.x() - r.width()/2, m_boundingRect.height() - r.y() + r.height()/2 + m_origin.y());
 
312
#ifdef LAYOUTGENERATOR_DATA_DEBUG
 
313
    uDebug() << r << p;
 
314
#endif
 
315
    return p;
 
316
}
 
317
 
 
318
/**
 
319
 * Read generated dot file and extract positions
 
320
 * of the contained widgets.
 
321
 *
 
322
 * @return true if extracting succeeded
 
323
*/
 
324
bool LayoutGenerator::readGeneratedDotFile(const QString &fileName)
 
325
{
 
326
    QFile file(fileName);
 
327
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
 
328
        return false;
 
329
 
 
330
    QTextStream in(&file);
 
331
    while (!in.atEnd()) {
 
332
        QString line = in.readLine();
 
333
        while(line.endsWith(','))
 
334
            line += in.readLine();
 
335
        parseLine(line);
 
336
    }
 
337
    return true;
 
338
}
 
339
 
 
340
#ifndef USE_XDOT
 
341
/**
 
342
 * Parse line from dot generated plain-ext output format
 
343
 *
 
344
 *  The format is documented at http://graphviz.org/content/output-formats#dplain-ext and looks like:
 
345
 *
 
346
 *   graph 1 28.083 10.222
 
347
 *   node ITfDmJvJE00m 8.0833 8.7361 0.86111 0.45833 QObject solid box black lightgrey
 
348
 *   edge sL4cKPpHnJkU sL4cKPpHnJkU 7 8.1253 7.2568 8.2695 7.2687 8.375 7.3127 8.375 7.3889 8.375 7.4377 8.3317 7.4733 8.2627 7.4957 Aggregation 8.8472 7.3889 solid black
 
349
 *
 
350
 * @param line line in dot plain-ext output format
 
351
 * @return true if line could be parsed successfully
 
352
*/
 
353
bool LayoutGenerator::parseLine(const QString &line)
 
354
{
 
355
    QStringList a = line.split(' ');
 
356
    if (a[0] == "graph") {
 
357
        m_boundingRect = QRectF(0, 0, a[2].toDouble()*m_scale, a[3].toDouble()*m_scale);
 
358
        return true;
 
359
    } else if (a[0] == "node") {
 
360
        QString key = fixID(a[1]);
 
361
        m_nodes[key] = QRectF(a[2].toDouble()*m_scale, a[3].toDouble()*m_scale, a[4].toDouble()*m_scale, a[5].toDouble()*m_scale);
 
362
        return true;
 
363
    } else if (a[0] == "edge") {
 
364
        QString key = fixID(a[1]+a[2]);
 
365
        EdgePoints p;
 
366
        int len = a[3].toInt();
 
367
        for(int i = 0; i < len; i++)
 
368
            p.append(QPointF(a[i*2+4].toDouble()*m_scale, a[i*2+5].toDouble()*m_scale));
 
369
        m_edges[key] = p;
 
370
 
 
371
        int b = len*2 + 4;
 
372
        bool ok;
 
373
        double x = a[b+1].toDouble(&ok);
 
374
        if (!ok)
 
375
            return true;
 
376
        double y = a[b+2].toDouble(&ok);
 
377
        if (!ok)
 
378
            return true;
 
379
        m_edgeLabelPosition[key] = QPointF(x*m_scale, y*m_scale);
 
380
 
 
381
        return true;
 
382
    } else if (a[0] == "stop") {
 
383
        return true;
 
384
    }
 
385
    return false;
 
386
}
 
387
 
 
388
#else
 
389
typedef QMap<QString,QStringList> ParameterList;
 
390
 
 
391
bool LayoutGenerator::splitParameters(QMap<QString,QStringList> &map, const QString &s)
 
392
{
 
393
    // FIXME: add shape=box without '"'
 
394
    static QRegExp rx("([a-zA-Z_]+)=\"([a-zA-Z0-9.- #]+)\"");
 
395
    static QRegExp rx2("([a-zA-Z_]+)=([a-zA-Z0-9.- #]+)");
 
396
    int pos = 0;
 
397
    int count = 0;
 
398
    /*
 
399
    *        while ((pos = rx2.indexIn(s, pos)) != -1) {
 
400
    *            QString key = rx2.cap(1);
 
401
    *            QString value = rx2.cap(2);
 
402
    *            ++count;
 
403
    *            pos += rx2.matchedLength();
 
404
    *            //qDebug() << key << value;
 
405
    *            if (map.contains(key))
 
406
    *                map[key] << value;
 
407
    *            else
 
408
    *                map[key] = QStringList() << value;
 
409
    }
 
410
    */
 
411
    pos = 0;
 
412
    while ((pos = rx.indexIn(s, pos)) != -1) {
 
413
        QString key = rx.cap(1);
 
414
        QString value = rx.cap(2);
 
415
        ++count;
 
416
        pos += rx.matchedLength();
 
417
        //qDebug() << key << value;
 
418
 
 
419
        QStringList data;
 
420
        if (key == "pos") {
 
421
            value.remove("e,");
 
422
            data = value.split(' ');
 
423
    } else if (key.startsWith('_')) {
 
424
        data = value.split(' ');
 
425
    }
 
426
    else if (key == "label")
 
427
        data = QStringList() << value;
 
428
    else
 
429
        data = value.split(',');
 
430
 
 
431
    if (map.contains(key))
 
432
        map[key] << data;
 
433
    else
 
434
        map[key] = data;
 
435
    }
 
436
    return true;
 
437
}
 
438
 
 
439
/**
 
440
 *
 
441
digraph G {
 
442
    graph [splines=polyline, rankdir=BT, outputorder=nodesfirst, ranksep="0.5", nodesep="0.5"];
 
443
    node [label="\N"];
 
444
    graph [bb="0,0,2893,638",
 
445
    _draw_="c 9 -#ffffffff C 9 -#ffffffff P 4 0 -1 0 638 2894 638 2894 -1 ",
 
446
    xdotversion="1.2"];
 
447
    XC0weWhArzOJ [label=note, shape=box, width="2.5833", height="0.86111", pos="93,31", _draw_="c 9 -#000000ff p 4 186 62 0 62 0 0 186 0 ", _ldraw_="F 14.000000 11 -Times-Roman c 9 -#000000ff T 93 27 0 24 4 -note "];
 
448
    sL4cKPpHnJkU -> ITfDmJvJE00m [arrowhead=normal, weight="1.0", label=" ", pos="e,2326.3,600.47 2299.7,543.57 2306.1,557.22 2314.9,575.99 2322.1,591.39", lp="2319,572", _draw_="c 9 -#000000ff B 4 2300 544 2306 557 2315 576 2322 591 ", _hdraw_="S 5 -solid c 9 -#000000ff C 9 -#000000ff P 3 2319 593 2326 600 2325 590 ", _ldraw_="F 14.000000 11 -Times-Roman c 9 -#000000ff T 2319 568 0 4 1 -  "];
 
449
    sL4cKPpHnJkU -> sL4cKPpHnJkU [label=" ", arrowtail=odiamond, dir=back, constraint=false, pos="s,2339.3,516.43 2351.5,516.59 2365.1,517.35 2375,520.16 2375,525 2375,531.2 2358.7,534.06 2339.3,533.57", lp="2377,525", _draw_="c 9 -#000000ff B 7 2351 517 2365 517 2375 520 2375 525 2375 531 2359 534 2339 534 ", _tdraw_="S 5 -solid c 9 -#000000ff p 4 2351 517 2345 521 2339 516 2345 513 ", _ldraw_="F 14.000000 11 -Times-Roman c 9 -#000000ff T 2377 521 0 4 1 -  "];
 
450
*/
 
451
 
 
452
bool LayoutGenerator::parseLine(const QString &line)
 
453
{
 
454
    static QRegExp m_cols("^[\t ]*(.*)[\t ]*\\[(.*)\\]");
 
455
    static int m_level = -1;
 
456
 
 
457
    if (line.contains('{')) {
 
458
        m_level++;
 
459
        return true;
 
460
    }
 
461
    else if (line.contains('}')) {
 
462
        m_level--;
 
463
        return true;
 
464
    }
 
465
    int pos = 0;
 
466
    if (m_cols.indexIn(line, pos) == -1)
 
467
        return false;
 
468
 
 
469
    QString keyword = m_cols.cap(1).trimmed();
 
470
    QString attributeString = m_cols.cap(2);
 
471
    uDebug() << keyword << attributeString;
 
472
    ParameterList attributes;
 
473
    splitParameters(attributes, attributeString);
 
474
    uDebug() << attributes;
 
475
 
 
476
    if (keyword == "graph") {
 
477
        if (attributes.contains("bb")) {
 
478
            QStringList &a = attributes["bb"];
 
479
            m_boundingRect.setLeft(a[0].toDouble());
 
480
            m_boundingRect.setTop(a[1].toDouble());
 
481
            m_boundingRect.setRight(a[2].toDouble());
 
482
            m_boundingRect.setBottom(a[3].toDouble());
 
483
        }
 
484
    } else if (keyword == "node") {
 
485
        return true;
 
486
    } else if (keyword == "edge") {
 
487
        return true;
 
488
    // transistion
 
489
    } else if (line.contains("->")) {
 
490
        QStringList k = keyword.split(" ");
 
491
        if (k.size() < 3)
 
492
            return false;
 
493
        QString key = fixID(k[0]+k[2]);
 
494
 
 
495
        if (attributes.contains("pos")) {
 
496
            QStringList &a = attributes["pos"];
 
497
            EdgePoints points;
 
498
 
 
499
            for(int i = 1; i < a.size(); i++) {
 
500
                QStringList b = a[i].split(',');
 
501
                QPointF p(b[0].toDouble(), b[1].toDouble());
 
502
                points.append(p);
 
503
            }
 
504
            QStringList b = a[0].split(',');
 
505
            QPointF p(b[0].toDouble(), b[1].toDouble());
 
506
            points.append(p);
 
507
 
 
508
            m_edges[key] = points;
 
509
        }
 
510
        if (0 && attributes.contains("_draw_")) {
 
511
            QStringList &a = attributes["_draw_"];
 
512
            if (a.size() < 5 || (a[3] != "L" && a[3] != "p"))
 
513
                return false;
 
514
            int size = a[4].toInt();
 
515
            EdgePoints points;
 
516
 
 
517
            for(int i = 0; i < size; i++) {
 
518
                QPointF p(a[i*2+5].toDouble(), a[i*2+6].toDouble());
 
519
                points.append(p);
 
520
            }
 
521
            m_edges[key] = points;
 
522
        }
 
523
        return true;
 
524
    // single node
 
525
    } else {
 
526
        double scale = 72.0;
 
527
        QRectF f(0, 0, 0, 0);
 
528
        QString id = fixID(keyword);
 
529
        if (attributes.contains("pos")) {
 
530
            QStringList &a = attributes["pos"];
 
531
            QStringList b = a[0].split(",");
 
532
            f.setLeft(b[0].toDouble());
 
533
            f.setTop(b[1].toDouble());
 
534
        }
 
535
        if (attributes.contains("height")) {
 
536
            QStringList &a = attributes["height"];
 
537
            f.setHeight(a[0].toDouble()*scale);
 
538
        }
 
539
 
 
540
        if (attributes.contains("width")) {
 
541
            QStringList &a = attributes["width"];
 
542
            f.setWidth(a[0].toDouble()*scale);
 
543
        }
 
544
        uDebug() << "adding" << id << f;
 
545
        m_nodes[id] = f;
 
546
    }
 
547
return true;
 
548
}
 
549
#endif
 
550
 
 
551
/**
 
552
 * map dot coordinate to scene coordinate
 
553
 * @param p dot point to map
 
554
 * @return uml scene coordinate
 
555
 */
 
556
QPointF LayoutGenerator::mapToScene(const QPointF &p)
 
557
{
 
558
    return QPointF(p.x()+ m_origin.x(), m_boundingRect.height() - p.y() + m_origin.y());
 
559
}
 
560
 
 
561
#if 0
 
562
static QDebug operator<<(QDebug out, LayoutGenerator &c)
 
563
{
 
564
    out << "LayoutGenerator:"
 
565
        << "m_boundingRect:" << c.m_boundingRect
 
566
        << "m_nodes:" << c.m_nodes
 
567
        << "m_edges:" << c.m_edges
 
568
        << "m_scale:" << c.m_scale
 
569
        << "m_executable:" << c.m_executable;
 
570
    return out;
 
571
}
 
572
#endif