libyui-qt-graph  2.44.6
QY2Graph.cc
1 /*
2  * Copyright (C) 2009-2012 Novell, Inc
3  * This library is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU Lesser General Public License as
5  * published by the Free Software Foundation; either version 2.1 of the
6  * License, or (at your option) version 3.0 of the License. This library
7  * is distributed in the hope that it will be useful, but WITHOUT ANY
8  * WARRANTY; without even the implied warranty of MERCHANTABILITY or
9  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
10  * License for more details. You should have received a copy of the GNU
11  * Lesser General Public License along with this library; if not, write
12  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
13  * Floor, Boston, MA 02110-1301 USA
14  */
15 
16 /*
17  * File: QY2Graph.cc
18  * Author: Arvin Schnell <aschnell@suse.de>
19  */
20 
21 
22 #include <math.h>
23 
24 #include <QKeyEvent>
25 #include <QWheelEvent>
26 #include <QGraphicsSceneMouseEvent>
27 
28 #include "QY2Graph.h"
29 
30 
31 QY2Graph::QY2Graph(const std::string& filename, const std::string& layoutAlgorithm, QWidget* parent)
32  : QGraphicsView(parent)
33 {
34  init();
35 
36  renderGraph(filename, layoutAlgorithm);
37 }
38 
39 
40 QY2Graph::QY2Graph(graph_t* graph, QWidget* parent)
41  : QGraphicsView(parent)
42 {
43  init();
44 
45  renderGraph(graph);
46 }
47 
48 
49 QY2Graph::~QY2Graph()
50 {
51 }
52 
53 
54 void
55 QY2Graph::init()
56 {
57  setRenderHint(QPainter::Antialiasing);
58  setRenderHint(QPainter::TextAntialiasing);
59  setTransformationAnchor(AnchorUnderMouse);
60  setResizeAnchor(AnchorUnderMouse);
61 
62  scene = new QGraphicsScene(this);
63  scene->setItemIndexMethod(QGraphicsScene::BspTreeIndex);
64  setScene(scene);
65 }
66 
67 
68 void
69 QY2Graph::keyPressEvent(QKeyEvent* event)
70 {
71  switch (event->key())
72  {
73  case Qt::Key_Plus:
74  scaleView(1.2);
75  break;
76  case Qt::Key_Minus:
77  scaleView(1.0 / 1.2);
78  break;
79 
80 #if 0
81  case Qt::Key_Asterisk:
82  rotate(10.0);
83  break;
84  case Qt::Key_Slash:
85  rotate(-10.0);
86  break;
87 #endif
88 
89  default:
90  QGraphicsView::keyPressEvent(event);
91  }
92 }
93 
94 
95 void
96 QY2Graph::wheelEvent(QWheelEvent* event)
97 {
98  scaleView(pow(2.0, -event->delta() / 240.0));
99 }
100 
101 
102 void
103 QY2Graph::scaleView(qreal scaleFactor)
104 {
105  qreal f = sqrt(matrix().determinant());
106 
107  if (scaleFactor * f > 8.0)
108  scaleFactor = 8.0 / f;
109  if (scaleFactor * f < 0.1)
110  scaleFactor = 0.1 / f;
111 
112  scale(scaleFactor, scaleFactor);
113 }
114 
115 
116 void
117 QY2Graph::contextMenuEvent(QContextMenuEvent* event)
118 {
119  QY2Node* node = dynamic_cast<QY2Node*>(itemAt(event->pos()));
120 
121  if (node)
122  emit nodeContextMenuEvent(event, node->name);
123  else
124  emit backgroundContextMenuEvent(event);
125 }
126 
127 
128 void
129 QY2Graph::mouseDoubleClickEvent(QMouseEvent* event)
130 {
131  QY2Node* node = dynamic_cast<QY2Node*>(itemAt(event->pos()));
132 
133  if (node)
134  emit nodeDoubleClickEvent(event, node->name);
135 }
136 
137 
138 QPointF
139 QY2Graph::gToQ(const pointf& p, bool upside_down) const
140 {
141  return upside_down ? QPointF(p.x, graphRect.height() - p.y) : QPointF(p.x, -p.y);
142 }
143 
144 
145 QString
146 QY2Graph::aggetToQString(void* obj, const char* name, const QString& fallback) const
147 {
148  const char* tmp = agget(obj, const_cast<char*>(name));
149  if (tmp == NULL || strlen(tmp) == 0)
150  return fallback;
151  return QString::fromUtf8(tmp);
152 }
153 
154 
155 QColor
156 QY2Graph::aggetToQColor(void* obj, const char* name, const QColor& fallback) const
157 {
158  const char* tmp = agget(obj, const_cast<char*>(name));
159  if (tmp == NULL || strlen(tmp) == 0)
160  return fallback;
161  return QColor(tmp);
162 }
163 
164 
165 Qt::PenStyle
166 QY2Graph::aggetToQPenStyle(void* obj, const char* name, const Qt::PenStyle fallback) const
167 {
168  const char* tmp = agget(obj, const_cast<char*>(name));
169  if (tmp == NULL || strlen(tmp) == 0)
170  return fallback;
171  if (strcmp(tmp, "dashed") == 0)
172  return Qt::DashLine;
173  if (strcmp(tmp, "dotted") == 0)
174  return Qt::DotLine;
175  return fallback;
176 }
177 
178 
179 QPainterPath
180 QY2Graph::makeBezier(const bezier& bezier) const
181 {
182  QPainterPath path;
183  path.moveTo(gToQ(bezier.list[0]));
184  for (int i = 1; i < bezier.size - 1; i += 3)
185  path.cubicTo(gToQ(bezier.list[i]), gToQ(bezier.list[i+1]), gToQ(bezier.list[i+2]));
186  return path;
187 }
188 
189 
190 void
191 QY2Graph::drawArrow(const QLineF& line, const QColor& color, QPainter* painter) const
192 {
193  QLineF n(line.normalVector());
194  QPointF o(n.dx() / 3.0, n.dy() / 3.0);
195 
196  QPolygonF polygon;
197  polygon.append(line.p1() + o);
198  polygon.append(line.p2());
199  polygon.append(line.p1() - o);
200 
201  QPen pen(color);
202  pen.setWidthF(1.0);
203  painter->setPen(pen);
204 
205  QBrush brush(color);
206  painter->setBrush(brush);
207 
208  painter->drawPolygon(polygon);
209 }
210 
211 
212 void
213 QY2Graph::renderGraph(const std::string& filename, const std::string& layoutAlgorithm)
214 {
215  FILE* fp = fopen(filename.c_str(), "r");
216  if (fp)
217  {
218  GVC_t* gvc = gvContext();
219  if (gvc != NULL)
220  {
221 #ifdef WITH_CGRAPH
222  graph_t* graph = agread(fp, NULL);
223 #else
224  graph_t* graph = agread(fp);
225 #endif
226  if (graph != NULL)
227  {
228  if (gvLayout(gvc, graph, const_cast<char*>(layoutAlgorithm.c_str())) == 0)
229  {
230  renderGraph(graph);
231 
232  gvFreeLayout(gvc, graph);
233  }
234  else
235  {
236  qCritical("gvLayout() failed");
237  }
238 
239  agclose(graph);
240  }
241  else
242  {
243  qCritical("agread() failed");
244  }
245 
246  gvFreeContext(gvc);
247  }
248  else
249  {
250  qCritical("gvContext() failed");
251  }
252 
253  fclose(fp);
254  }
255  else
256  {
257  qCritical("failed to open %s", filename.c_str());
258  }
259 }
260 
261 
262 QPolygonF
263 QY2Graph::makeShapeHelper(node_t* node) const
264 {
265  const polygon_t* poly = (polygon_t*) ND_shape_info(node);
266 
267  if (poly->peripheries != 1)
268  {
269  qWarning("unsupported number of peripheries %d", poly->peripheries);
270  }
271 
272  const int sides = poly->sides;
273  const pointf* vertices = poly->vertices;
274 
275  QPolygonF polygon;
276  for (int side = 0; side < sides; side++)
277  polygon.append(gToQ(vertices[side], false));
278  return polygon;
279 }
280 
281 
282 QPainterPath
283 QY2Graph::makeShape(node_t* node) const
284 {
285  QPainterPath path;
286 
287  const char* name = ND_shape(node)->name;
288 
289  if ((strcmp(name, "rectangle") == 0) ||
290  (strcmp(name, "box") == 0) ||
291  (strcmp(name, "hexagon") == 0) ||
292  (strcmp(name, "polygon") == 0) ||
293  (strcmp(name, "diamond") == 0))
294  {
295  QPolygonF polygon = makeShapeHelper(node);
296  polygon.append(polygon[0]);
297  path.addPolygon(polygon);
298  }
299  else if ((strcmp(name, "ellipse") == 0) ||
300  (strcmp(name, "circle") == 0))
301  {
302  QPolygonF polygon = makeShapeHelper(node);
303  path.addEllipse(QRectF(polygon[0], polygon[1]));
304  }
305  else
306  {
307  qWarning("unsupported shape %s", name);
308  }
309 
310  return path;
311 }
312 
313 
314 void
315 QY2Graph::drawLabel(const textlabel_t* textlabel, QPainter* painter) const
316 {
317  painter->setPen(textlabel->fontcolor);
318 
319  // Since I always just take the points from graphviz and pass them to Qt
320  // as pixel I also have to set the pixel size of the font.
321  QFont font(textlabel->fontname, textlabel->fontsize);
322  font.setPixelSize(textlabel->fontsize);
323 
324  if (!font.exactMatch())
325  {
326  QFontInfo fontinfo(font);
327  qWarning("replacing font \"%s\" by font \"%s\"", font.family().toUtf8().data(),
328  fontinfo.family().toUtf8().data());
329  }
330 
331  painter->setFont(font);
332 
333  QString text(QString::fromUtf8(textlabel->text));
334  QFontMetricsF fm(painter->fontMetrics());
335  QRectF rect(fm.boundingRect(text));
336  rect.moveCenter(gToQ(textlabel->pos, false));
337  painter->drawText(rect.adjusted(-2, -2, +2, +2), Qt::AlignCenter, text);
338 }
339 
340 
341 void
342 QY2Graph::clearGraph()
343 {
344  QList<QGraphicsItem*> items(scene->items());
345  while (!items.isEmpty())
346  delete items.takeFirst();
347 }
348 
349 
350 void
351 QY2Graph::renderGraph(graph_t* graph)
352 {
353  clearGraph();
354 
355  if (GD_charset(graph) != 0)
356  {
357  qWarning("unsupported charset");
358  }
359 
360  // don't use gToQ here since it adjusts the values
361  graphRect = QRectF(GD_bb(graph).LL.x, GD_bb(graph).LL.y, GD_bb(graph).UR.x, GD_bb(graph).UR.y);
362  scene->setSceneRect(graphRect.adjusted(-5, -5, +5, +5));
363 
364  scene->setBackgroundBrush(aggetToQColor(graph, "bgcolor", Qt::white));
365 
366  for (node_t* node = agfstnode(graph); node != NULL; node = agnxtnode(graph, node))
367  {
368  QPicture picture;
369  QPainter painter;
370 
371  painter.begin(&picture);
372  painter.initFrom(this);
373  drawLabel(ND_label(node), &painter);
374  painter.end();
375 
376 #ifdef WITH_CGRAPH
377  QY2Node* item = new QY2Node(makeShape(node), picture, agnameof(node));
378 #else
379  QY2Node* item = new QY2Node(makeShape(node), picture, node->name);
380 #endif
381 
382  item->setPos(gToQ(ND_coord(node)));
383 
384  QPen pen(aggetToQColor(node, "color", Qt::black));
385  pen.setWidthF(1.0);
386  item->setPen(pen);
387 
388  QBrush brush(aggetToQColor(node, "fillcolor", Qt::gray));
389  item->setBrush(brush);
390 
391  QString tooltip = aggetToQString(node, "tooltip", "");
392  if (!tooltip.isEmpty())
393  {
394  tooltip.replace("\\n", "\n");
395  item->setToolTip(tooltip);
396  }
397 
398  scene->addItem(item);
399 
400  for (edge_t* edge = agfstout(graph, node); edge != NULL; edge = agnxtout(graph, edge))
401  {
402  const splines* spl = ED_spl(edge);
403  if (spl == NULL)
404  continue;
405 
406  for (int i = 0; i < spl->size; ++i)
407  {
408  const bezier& bz = spl->list[i];
409 
410  QColor color(aggetToQColor(edge, "color", Qt::black));
411 
412  QPainterPath path(makeBezier(bz));
413 
414  QPicture picture;
415  QPainter painter;
416 
417  painter.begin(&picture);
418  if (bz.sflag)
419  drawArrow(QLineF(gToQ(bz.list[0]), gToQ(bz.sp)), color, &painter);
420  if (bz.eflag)
421  drawArrow(QLineF(gToQ(bz.list[bz.size-1]), gToQ(bz.ep)), color, &painter);
422  painter.end();
423 
424  QY2Edge* item = new QY2Edge(path, picture);
425 
426  QPen pen(color);
427  pen.setStyle(aggetToQPenStyle(edge, "style", Qt::SolidLine));
428  pen.setWidthF(1.0);
429  item->setPen(pen);
430 
431  item->setZValue(-1.0);
432 
433  scene->addItem(item);
434  }
435  }
436  }
437 }
438 
439 
440 QY2Node::QY2Node(const QPainterPath& path, const QPicture& picture, const QString& name)
441  : QGraphicsPathItem(path),
442  picture(picture),
443  name(name)
444 {
445 }
446 
447 
448 void
449 QY2Node::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
450 {
451  painter->save();
452  QGraphicsPathItem::paint(painter, option, widget);
453  painter->restore();
454 
455  picture.play(painter);
456 }
457 
458 
459 QY2Edge::QY2Edge(const QPainterPath& path, const QPicture& picture)
460  : QGraphicsPathItem(path),
461  picture(picture)
462 {
463 }
464 
465 
466 QRectF
467 QY2Edge::boundingRect() const
468 {
469  return QGraphicsPathItem::boundingRect().united(picture.boundingRect());
470 }
471 
472 
473 void
474 QY2Edge::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
475 {
476  painter->save();
477  QGraphicsPathItem::paint(painter, option, widget);
478  painter->restore();
479 
480  picture.play(painter);
481 }
482 
483 
484 #include "QY2Graph.moc"