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. *
8
* Umbrello UML Modeller Authors <uml-devel@uml.sf.net> *
9
***************************************************************************/
12
#include "dotgenerator.h"
15
#include "activitywidget.h"
16
#include "associationwidget.h"
17
#include "classifierwidget.h"
18
#include "signalwidget.h"
19
#include "statewidget.h"
20
#include "debug_utils.h"
21
#include "umlwidget.h"
24
#include <KConfigGroup>
25
#include <KDesktopFile>
26
#include <KStandardDirs>
30
#include <QPaintEngine>
35
#include <QTemporaryFile>
36
#include <QTextStream>
40
* dot specific paint engine
42
class DotPaintEngine : public QPaintEngine
45
DotPaintEngine(PaintEngineFeatures caps = 0 ) {}
46
virtual ~DotPaintEngine() {}
47
virtual bool begin (QPaintDevice * pdev)
51
virtual void drawEllipse(const QRectF & rect) {}
52
virtual void drawEllipse(const QRect & rect) {}
53
virtual void drawImage(const QRectF & rectangle, const QImage & image, const QRectF & sr, Qt::ImageConversionFlags flags = Qt::AutoColor) {}
54
virtual void drawLines(const QLineF * lines, int lineCount) {}
55
virtual void drawLines(const QLine * lines, int lineCount) {}
56
virtual void drawwPath(const QPainterPath & path) {}
57
virtual void drawPixmap(const QRectF & r, const QPixmap & pm, const QRectF & sr) {}
58
virtual void drawPoints(const QPointF * points, int pointCount) {}
59
virtual void drawPoints(const QPoint * points, int pointCount) {}
60
virtual void drawPolygon(const QPointF * points, int pointCount, PolygonDrawMode mode) {}
61
virtual void drawPolygon(const QPoint * points, int pointCount, PolygonDrawMode mode) {}
62
virtual void drawRects(const QRectF * rects, int rectCount) {}
63
virtual void drawRects(const QRect * rects, int rectCount) {}
64
virtual void drawTextItem(const QPointF & p, const QTextItem & textItem)
66
m_data << textItem.text();
68
virtual void drawTiledPixmap(const QRectF & rect, const QPixmap & pixmap, const QPointF & p) {}
73
virtual Type type() const
75
return QPaintEngine::User;
77
virtual void updateState(const QPaintEngineState & state) {}
83
* dot specific paint device
85
class DotPaintDevice : public QPaintDevice
88
DotPaintDevice() : m_engine(new DotPaintEngine)
97
virtual QPaintEngine* paintEngine() const
104
return m_engine->m_data;
108
virtual int metric(PaintDeviceMetric metric) const
111
case QPaintDevice::PdmDpiX: return 1;
112
case QPaintDevice::PdmDpiY: return 1;
113
case QPaintDevice::PdmWidth: return 100;
114
case QPaintDevice::PdmHeight: return 100;
120
DotPaintEngine *m_engine;
123
#define DOTGENERATOR_DEBUG
127
DotGenerator::DotGenerator()
129
m_usePosition(false),
130
m_useFullNodeLabels(true)
135
* return usage of position attribute
137
* @return true if position are used
139
bool DotGenerator::usePosition()
141
return m_usePosition;
145
* set usage of position attribute in dot file
147
* @param state The new state
149
void DotGenerator::setUsePosition(bool state)
151
m_usePosition = state;
155
* return usage of full node labels
157
* @return true if position are used
159
bool DotGenerator::useFullNodeLabels()
161
return m_useFullNodeLabels;
165
* Set usage of full node labels.
166
* When set to true labels are extracted from the
167
* text output generated by the widget's paint method.
169
* @param state The new state
171
void DotGenerator::setUseFullNodeLabels(bool state)
173
m_useFullNodeLabels = state;
177
* Return a list of available templates for a given scene type
179
* @param scene The diagram
180
* @param configFiles will contain the collected list of config files
181
* @return true if collecting succeeds
183
bool DotGenerator::availableConfigFiles(UMLScene *scene, QHash<QString,QString> &configFiles)
185
QString diagramType = scene->type().toString().toLower();
188
QStringList fileNames = dirs.findAllResources("data", QString("umbrello/layouts/%1*.desktop").arg(diagramType));
189
foreach(const QString &fileName, fileNames) {
190
QFileInfo fi(fileName);
192
if (fi.baseName().contains("-"))
193
baseName = fi.baseName().remove(diagramType + "-");
194
else if (fi.baseName() == diagramType)
195
baseName = fi.baseName();
197
baseName = "default";
198
KDesktopFile desktopFile(fileName);
199
configFiles[baseName] = desktopFile.readName();
205
* Read a layout config file
207
* @param diagramType String identifing the diagram
208
* @param variant String identifing the variant
209
* @return true on success
211
bool DotGenerator::readConfigFile(QString diagramType, const QString &variant)
213
QStringList fileNames;
215
if (!variant.isEmpty())
216
fileNames << QString("%1-%2.desktop").arg(diagramType).arg(variant);
217
fileNames << QString("%1-default.desktop").arg(diagramType);
218
fileNames << "default.desktop";
220
QString configFileName;
221
foreach(const QString &fileName, fileNames) {
222
configFileName = KStandardDirs::locate("data", QString("umbrello/layouts/%1").arg(fileName));
223
if (!configFileName.isEmpty())
227
if (configFileName.isEmpty()) {
228
uError() << "could not find layout config file name for diagram type" << diagramType << "and variant" << variant;
231
uDebug() << "reading config file" << configFileName;
232
m_configFileName = configFileName;
233
KDesktopFile desktopFile(configFileName);
234
KConfigGroup edgesAttributes(&desktopFile,"X-UMBRELLO-Dot-Edges");
235
KConfigGroup nodesAttributes(&desktopFile,"X-UMBRELLO-Dot-Nodes");
236
KConfigGroup attributes(&desktopFile,"X-UMBRELLO-Dot-Attributes");
237
// settings are not needed by dotgenerator
238
KConfigGroup settings(&desktopFile,"X-UMBRELLO-Dot-Settings");
240
m_edgeParameters.clear();
241
m_nodeParameters.clear();
242
m_dotParameters.clear();
244
foreach(const QString &key, attributes.keyList()) {
245
QString value = attributes.readEntry(key);
246
if (!value.isEmpty())
247
m_dotParameters[key] = value;
250
foreach(const QString &key, nodesAttributes.keyList()) {
251
QString value = nodesAttributes.readEntry(key);
252
m_nodeParameters[key] = value;
255
foreach(const QString &key, edgesAttributes.keyList()) {
256
QString value = edgesAttributes.readEntry(key);
257
if (m_edgeParameters.contains(key)) {
258
m_edgeParameters[key] += ',' + value;
260
m_edgeParameters[key] = value;
264
QString value = settings.readEntry("origin");
265
QStringList a = value.split(",");
267
m_origin = QPointF(a[0].toDouble(), a[1].toDouble());
269
uError() << "illegal format of entry 'origin'" << value;
271
m_generator = settings.readEntry("generator","dot");
273
#ifdef LAYOUTGENERATOR_DATA_DEBUG
274
uDebug() << m_edgeParameters;
275
uDebug() << m_nodeParameters;
276
uDebug() << m_dotParameters;
282
* Create dot file using displayed widgets
283
* and associations of the provided scene
284
* @note This method could also be used as a base to export diagrams as dot file
286
* @param fileName Filename where to create the dot file
287
* @param scene The diagram from which the widget informations are fetched
289
* @return true if generating finished successfully
291
bool DotGenerator::createDotFile(UMLScene *scene, const QString &fileName, const QString &variant)
293
QFile file(fileName);
294
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
297
QString diagramType = scene->type().toString().toLower();
298
if (!readConfigFile(diagramType, variant))
302
QTextStream out(&data);
304
foreach(UMLWidget *widget, scene->widgetList()) {
307
if (m_nodeParameters.contains("all"))
308
params << m_nodeParameters["all"].split(',');
311
params << QString("pos=\"%1,%2\"").arg(widget->x()+widget->width()/2).arg(widget->y()+widget->height()/2);
313
QString type = QString(widget->baseTypeStr()).toLower().remove("wt_");
315
if (type == "state") {
316
StateWidget *w = static_cast<StateWidget *>(widget);
317
type = w->stateTypeStr().toLower();
319
else if (type == "activity") {
320
ActivityWidget *w = static_cast<ActivityWidget *>(widget);
321
type = w->activityTypeStr().toLower();
323
else if (type == "signal") {
324
SignalWidget *w = static_cast<SignalWidget *>(widget);
325
type = w->signalTypeStr().toLower();
328
QString key = "type::" + type;
332
if (!useFullNodeLabels())
333
label = widget->name() + "\\n" + type;
337
widget->paint(p, 0, 0);
338
label = d.data().join("\\n");
341
if (m_nodeParameters.contains(key))
342
params << m_nodeParameters[key].split(',');
343
else if (m_nodeParameters.contains("type::default"))
344
params << m_nodeParameters["type::default"].split(',');
346
if (!findItem(params,"label="))
347
params << QString("label=\"%1\"").arg(label);
349
if (!findItem(params,"width="))
350
params << QString("width=\"%1\"").arg(widget->width()/m_scale);
352
if (!findItem(params,"height="))
353
params << QString("height=\"%1\"").arg(widget->height()/m_scale);
355
#ifdef DOTGENERATOR_DATA_DEBUG
356
uDebug() << type << params;
358
QString id = fixID(ID2STR(widget->id()));
359
if (widget->baseType() != WidgetBase::wt_Text)
360
out << "\"" << id << "\""
361
<< " [" << params.join(",") << "];\n";
364
foreach(AssociationWidget *assoc, scene->associationList()) {
365
QString type = assoc->associationType().toString().toLower();
366
QString key = "type::" + type;
369
if (m_edgeParameters.contains("id::" + key))
370
swapId = m_edgeParameters["id::" + key] == "swap";
371
else if (m_edgeParameters.contains("id::type::default"))
372
swapId = m_edgeParameters["id::type::default"] == "swap";
375
if (!useFullNodeLabels())
376
label = assoc->name() + "\\n" + type;
378
label = assoc->name();
380
QString headLabel = assoc->roleName(swapId ? Uml::B : Uml::A);
381
QString tailLabel = assoc->roleName(swapId ? Uml::A : Uml::B);
383
if (!headLabel.isEmpty())
384
headLabel.prepend("+");
385
if (!tailLabel.isEmpty())
386
tailLabel.prepend("+");
388
headLabel += QLatin1String(" ") + assoc->multiplicity(swapId ? Uml::B : Uml::A);
389
tailLabel += QLatin1String(" ") + assoc->multiplicity(swapId ? Uml::A : Uml::B);
391
QString edgeParameters;
393
QString rkey = QLatin1String("ranking::") + key;
394
if (m_edgeParameters.contains(rkey))
395
edgeParameters = m_edgeParameters[rkey];
396
else if (m_edgeParameters.contains("ranking::type::default")) {
397
edgeParameters = m_edgeParameters["ranking::type::default"];
399
params << edgeParameters.split(',');
401
QString vkey = QLatin1String("visual::") + key;
402
if (m_edgeParameters.contains(vkey))
403
edgeParameters = m_edgeParameters[vkey];
404
else if (m_edgeParameters.contains("visual::type::default")) {
405
edgeParameters = m_edgeParameters["visual::type::default"];
407
params << edgeParameters.split(',');
409
if (!findItem(params,"label="))
410
params << QString("label=\"%1\"").arg(label);
412
if (!findItem(params,"headlabel="))
413
params << QString("headlabel=\"%1\"").arg(headLabel);
415
if (!findItem(params,"taillabel="))
416
params << QString("taillabel=\"%1\"").arg(tailLabel);
418
#ifdef DOTGENERATOR_DATA_DEBUG
419
uDebug() << type << params;
421
QString aID = fixID(ID2STR(assoc->widgetIDForRole(swapId ? Uml::A : Uml::B)));
422
QString bID = fixID(ID2STR(assoc->widgetIDForRole(swapId ? Uml::B : Uml::A)));
424
out << "\"" << aID << "\" -> \"" << bID << "\"" << " [" << params.join(",") << "];\n";
427
QTextStream o(&file);
428
o << "# generated from " << m_configFileName << "\n";
429
o << "digraph G {\n";
431
foreach(const QString &key, m_dotParameters.keys()) {
432
o << "\t" << key << " [" << m_dotParameters[key] << "];\n";
442
* Find string starting with the search string in string list
443
* @params params string list to search in
444
* @params search string
445
* @return true, when search string has been found
447
bool DotGenerator::findItem(QStringList ¶ms, const QString &search)
449
foreach(const QString &s, params) {
450
if (s.startsWith(search))
457
* There are id wrapped with '"', remove it.
459
QString DotGenerator::fixID(const QString &_id)
461
// FIXME: some widget's ids returned from the list are wrapped with "\"", find and fix them
468
static QDebug operator<<(QDebug out, LayoutGenerator &c)
470
out << "LayoutGenerator:"
471
<< "m_boundingRect:" << c.m_boundingRect
472
<< "m_nodes:" << c.m_nodes
473
<< "m_edges:" << c.m_edges
474
<< "m_scale:" << c.m_scale
475
<< "m_executable:" << c.m_executable;