~ubuntu-branches/ubuntu/vivid/kdesdk/vivid

« back to all changes in this revision

Viewing changes to umbrello/umbrello/layoutgenerator.h

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2012-06-06 11:49:54 UTC
  • mfrom: (0.4.21)
  • Revision ID: package-import@ubuntu.com-20120606114954-rdls73fzlpzxglbx
Tags: 4:4.8.80-0ubuntu1
* New uptream beta release
* Update dont_export_private_classes.diff

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