libyui-qt-graph  2.45.5
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  // 1 step of a typical mouse wheel results in a delta of +-120
99  // so we scale the view 1.41 or 0.71
100  // Scrolling up means zooming in.
101  scaleView(pow(2.0, event->delta() / 240.0));
102 }
103 
104 
105 // scaleFactor should be near 1.0 so that changes are not too abrupt
106 void
107 QY2Graph::scaleView(qreal scaleFactor)
108 {
109  // the *current* scale
110  qreal f = sqrt(matrix().determinant());
111 
112  // clamp scaleFactor so that the *new* scale will lie between 0.1, 8.0
113  if (scaleFactor * f > 8.0)
114  scaleFactor = 8.0 / f;
115  if (scaleFactor * f < 0.1)
116  scaleFactor = 0.1 / f;
117 
118  scale(scaleFactor, scaleFactor);
119 }
120 
121 
122 void
123 QY2Graph::contextMenuEvent(QContextMenuEvent* event)
124 {
125  QY2Node* node = dynamic_cast<QY2Node*>(itemAt(event->pos()));
126 
127  if (node)
128  emit nodeContextMenuEvent(event, node->name);
129  else
130  emit backgroundContextMenuEvent(event);
131 }
132 
133 
134 void
135 QY2Graph::mouseDoubleClickEvent(QMouseEvent* event)
136 {
137  QY2Node* node = dynamic_cast<QY2Node*>(itemAt(event->pos()));
138 
139  if (node)
140  emit nodeDoubleClickEvent(event, node->name);
141 }
142 
143 
144 QPointF
145 QY2Graph::gToQ(const pointf& p, bool upside_down) const
146 {
147  return upside_down ? QPointF(p.x, graphRect.height() - p.y) : QPointF(p.x, -p.y);
148 }
149 
150 
151 QString
152 QY2Graph::aggetToQString(void* obj, const char* name, const QString& fallback) const
153 {
154  const char* tmp = agget(obj, const_cast<char*>(name));
155  if (tmp == NULL || strlen(tmp) == 0)
156  return fallback;
157  return unescape(tmp);
158 }
159 
160 
161 QColor
162 QY2Graph::aggetToQColor(void* obj, const char* name, const QColor& fallback) const
163 {
164  const char* tmp = agget(obj, const_cast<char*>(name));
165  if (tmp == NULL || strlen(tmp) == 0)
166  return fallback;
167  return QColor(tmp);
168 }
169 
170 
171 Qt::PenStyle
172 QY2Graph::aggetToQPenStyle(void* obj, const char* name, const Qt::PenStyle fallback) const
173 {
174  const char* tmp = agget(obj, const_cast<char*>(name));
175  if (tmp == NULL || strlen(tmp) == 0)
176  return fallback;
177  if (strcmp(tmp, "dashed") == 0)
178  return Qt::DashLine;
179  if (strcmp(tmp, "dotted") == 0)
180  return Qt::DotLine;
181  return fallback;
182 }
183 
184 
185 QPainterPath
186 QY2Graph::makeBezier(const bezier& bezier) const
187 {
188  QPainterPath path;
189  path.moveTo(gToQ(bezier.list[0]));
190  for (int i = 1; i < bezier.size - 1; i += 3)
191  path.cubicTo(gToQ(bezier.list[i]), gToQ(bezier.list[i+1]), gToQ(bezier.list[i+2]));
192  return path;
193 }
194 
195 
196 void
197 QY2Graph::drawArrow(const QLineF& line, const QColor& color, QPainter* painter) const
198 {
199  QLineF n(line.normalVector());
200  QPointF o(n.dx() / 3.0, n.dy() / 3.0);
201 
202  QPolygonF polygon;
203  polygon.append(line.p1() + o);
204  polygon.append(line.p2());
205  polygon.append(line.p1() - o);
206 
207  QPen pen(color);
208  pen.setWidthF(1.0);
209  painter->setPen(pen);
210 
211  QBrush brush(color);
212  painter->setBrush(brush);
213 
214  painter->drawPolygon(polygon);
215 }
216 
217 
218 void
219 QY2Graph::renderGraph(const std::string& filename, const std::string& layoutAlgorithm)
220 {
221  FILE* fp = fopen(filename.c_str(), "r");
222  if (fp)
223  {
224  GVC_t* gvc = gvContext();
225  if (gvc != NULL)
226  {
227 #ifdef WITH_CGRAPH
228  graph_t* graph = agread(fp, NULL);
229 #else
230  graph_t* graph = agread(fp);
231 #endif
232  if (graph != NULL)
233  {
234  if (gvLayout(gvc, graph, const_cast<char*>(layoutAlgorithm.c_str())) == 0)
235  {
236  renderGraph(graph);
237 
238  gvFreeLayout(gvc, graph);
239  }
240  else
241  {
242  qCritical("gvLayout() failed");
243  }
244 
245  agclose(graph);
246  }
247  else
248  {
249  qCritical("agread() failed");
250  }
251 
252  gvFreeContext(gvc);
253  }
254  else
255  {
256  qCritical("gvContext() failed");
257  }
258 
259  fclose(fp);
260  }
261  else
262  {
263  qCritical("failed to open %s", filename.c_str());
264  }
265 }
266 
267 
268 QPolygonF
269 QY2Graph::makeShapeHelper(node_t* node) const
270 {
271  const polygon_t* poly = (polygon_t*) ND_shape_info(node);
272 
273  if (poly->peripheries != 1)
274  {
275  qWarning("unsupported number of peripheries %d", poly->peripheries);
276  }
277 
278  const int sides = poly->sides;
279  const pointf* vertices = poly->vertices;
280 
281  QPolygonF polygon;
282  for (int side = 0; side < sides; side++)
283  polygon.append(gToQ(vertices[side], false));
284  return polygon;
285 }
286 
287 
288 QPainterPath
289 QY2Graph::makeShape(node_t* node) const
290 {
291  QPainterPath path;
292 
293  const char* name = ND_shape(node)->name;
294 
295  if ((strcmp(name, "rectangle") == 0) ||
296  (strcmp(name, "box") == 0) ||
297  (strcmp(name, "hexagon") == 0) ||
298  (strcmp(name, "polygon") == 0) ||
299  (strcmp(name, "diamond") == 0))
300  {
301  QPolygonF polygon = makeShapeHelper(node);
302  polygon.append(polygon[0]);
303  path.addPolygon(polygon);
304  }
305  else if ((strcmp(name, "ellipse") == 0) ||
306  (strcmp(name, "circle") == 0))
307  {
308  QPolygonF polygon = makeShapeHelper(node);
309  path.addEllipse(QRectF(polygon[0], polygon[1]));
310  }
311  else
312  {
313  qWarning("unsupported shape %s", name);
314  }
315 
316  return path;
317 }
318 
319 
320 void
321 QY2Graph::drawLabel(const textlabel_t* textlabel, QPainter* painter) const
322 {
323  painter->setPen(textlabel->fontcolor);
324 
325  // Since I always just take the points from graphviz and pass them to Qt
326  // as pixel I also have to set the pixel size of the font.
327  QFont font(textlabel->fontname, textlabel->fontsize);
328  font.setPixelSize(textlabel->fontsize);
329 
330  if (!font.exactMatch())
331  {
332  QFontInfo fontinfo(font);
333  qWarning("replacing font \"%s\" by font \"%s\"", font.family().toUtf8().data(),
334  fontinfo.family().toUtf8().data());
335  }
336 
337  painter->setFont(font);
338 
339  QString text(unescape(textlabel->text));
340  QFontMetricsF fm(painter->fontMetrics());
341  QRectF rect(fm.boundingRect(text));
342  rect.moveCenter(gToQ(textlabel->pos, false));
343  painter->drawText(rect.adjusted(-2, -2, +2, +2), Qt::AlignCenter, text);
344 }
345 
346 
347 void
348 QY2Graph::clearGraph()
349 {
350  QList<QGraphicsItem*> items(scene->items());
351  while (!items.isEmpty())
352  delete items.takeFirst();
353 }
354 
355 
356 void
357 QY2Graph::renderGraph(graph_t* graph)
358 {
359  clearGraph();
360 
361  if (GD_charset(graph) != 0)
362  {
363  qWarning("unsupported charset");
364  }
365 
366  // don't use gToQ here since it adjusts the values
367  graphRect = QRectF(GD_bb(graph).LL.x, GD_bb(graph).LL.y, GD_bb(graph).UR.x, GD_bb(graph).UR.y);
368  scene->setSceneRect(graphRect.adjusted(-5, -5, +5, +5));
369 
370  scene->setBackgroundBrush(aggetToQColor(graph, "bgcolor", Qt::white));
371 
372  for (node_t* node = agfstnode(graph); node != NULL; node = agnxtnode(graph, node))
373  {
374  QPicture picture;
375  QPainter painter(&picture);
376 
377  painter.begin(this);
378  drawLabel(ND_label(node), &painter);
379  painter.end();
380 
381 #ifdef WITH_CGRAPH
382  QY2Node* item = new QY2Node(makeShape(node), picture, agnameof(node));
383 #else
384  QY2Node* item = new QY2Node(makeShape(node), picture, node->name);
385 #endif
386 
387  item->setPos(gToQ(ND_coord(node)));
388 
389  QPen pen(aggetToQColor(node, "color", Qt::black));
390  pen.setWidthF(1.0);
391  item->setPen(pen);
392 
393  QBrush brush(aggetToQColor(node, "fillcolor", Qt::gray));
394  item->setBrush(brush);
395 
396  QString tooltip = aggetToQString(node, "tooltip", "");
397  if (!tooltip.isEmpty())
398  item->setToolTip(tooltip);
399 
400  scene->addItem(item);
401 
402  for (edge_t* edge = agfstout(graph, node); edge != NULL; edge = agnxtout(graph, edge))
403  {
404  const splines* spl = ED_spl(edge);
405  if (spl == NULL)
406  continue;
407 
408  for (int i = 0; i < spl->size; ++i)
409  {
410  const bezier& bz = spl->list[i];
411 
412  QColor color(aggetToQColor(edge, "color", Qt::black));
413 
414  QPainterPath path(makeBezier(bz));
415 
416  QPicture picture;
417  QPainter painter;
418 
419  painter.begin(&picture);
420  if (bz.sflag)
421  drawArrow(QLineF(gToQ(bz.list[0]), gToQ(bz.sp)), color, &painter);
422  if (bz.eflag)
423  drawArrow(QLineF(gToQ(bz.list[bz.size-1]), gToQ(bz.ep)), color, &painter);
424  painter.end();
425 
426  QY2Edge* item = new QY2Edge(path, picture);
427 
428  QPen pen(color);
429  pen.setStyle(aggetToQPenStyle(edge, "style", Qt::SolidLine));
430  pen.setWidthF(1.0);
431  item->setPen(pen);
432 
433  item->setZValue(-1.0);
434 
435  scene->addItem(item);
436  }
437  }
438  }
439 }
440 
441 
442 QString
443 QY2Graph::unescape(const std::string& s) const
444 {
445  std::string r;
446 
447  bool backslashed = false;
448 
449  for (const char c : s)
450  {
451  if (!backslashed)
452  {
453  switch (c)
454  {
455  case '\\':
456  backslashed = true;
457  break;
458 
459  default:
460  r += c;
461  break;
462  }
463  }
464  else
465  {
466  switch (c)
467  {
468  // treat \n, \l and \r as newline (without alignment information)
469  case 'n':
470  case 'l':
471  case 'r':
472  r += '\n';
473  break;
474 
475  default:
476  r += c;
477  break;
478  }
479 
480  backslashed = false;
481  }
482  }
483 
484  return QString::fromUtf8(r.c_str());
485 }
486 
487 
488 QY2Node::QY2Node(const QPainterPath& path, const QPicture& picture, const QString& name)
489  : QGraphicsPathItem(path),
490  picture(picture),
491  name(name)
492 {
493 }
494 
495 
496 void
497 QY2Node::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
498 {
499  painter->save();
500  QGraphicsPathItem::paint(painter, option, widget);
501  painter->restore();
502 
503  picture.play(painter);
504 }
505 
506 
507 QY2Edge::QY2Edge(const QPainterPath& path, const QPicture& picture)
508  : QGraphicsPathItem(path),
509  picture(picture)
510 {
511 }
512 
513 
514 QRectF
515 QY2Edge::boundingRect() const
516 {
517  return QGraphicsPathItem::boundingRect().united(picture.boundingRect());
518 }
519 
520 
521 void
522 QY2Edge::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
523 {
524  painter->save();
525  QGraphicsPathItem::paint(painter, option, widget);
526  painter->restore();
527 
528  picture.play(painter);
529 }
530 
QY2Edge
Definition: QY2Graph.h:129
QY2Node
Definition: QY2Graph.h:108