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