00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012 #include "digraphview.h"
00013
00014 #include <math.h>
00015 #include <stdlib.h>
00016 #include <qapplication.h>
00017 #include <qpainter.h>
00018 #include <qpaintdevicemetrics.h>
00019 #include <qtextstream.h>
00020 #include <kglobal.h>
00021 #include <klocale.h>
00022 #include <kmessagebox.h>
00023 #include <kprocess.h>
00024 #include <kstandarddirs.h>
00025 #include <kglobalsettings.h>
00026 #include <ktempfile.h>
00027 #include <kdeversion.h>
00028
00029 struct DigraphNode
00030 {
00031 int x;
00032 int y;
00033 int w;
00034 int h;
00035 QString name;
00036 };
00037
00038
00039 struct DigraphEdge
00040 {
00041 QPointArray points;
00042 };
00043
00044
00045 DigraphView::DigraphView(QWidget *parent, const char *name)
00046 : QScrollView(parent, name, WRepaintNoErase|WStaticContents|WResizeNoErase)
00047 {
00048 viewport()->setBackgroundMode(PaletteBase);
00049
00050 QPaintDeviceMetrics m(this);
00051 xscale = m.logicalDpiX();
00052 yscale = m.logicalDpiY();
00053
00054 width = -1;
00055 height = -1;
00056
00057 nodes.setAutoDelete(true);
00058 edges.setAutoDelete(true);
00059 selNode = 0;
00060 }
00061
00062
00063 DigraphView::~DigraphView()
00064 {
00065 }
00066
00067
00068 int DigraphView::toXPixel(double x)
00069 {
00070 return (int) (x*xscale);
00071 }
00072
00073
00074 int DigraphView::toYPixel(double y)
00075 {
00076 return height - (int) (y*yscale);
00077 }
00078
00079
00080 void DigraphView::setRenderedExtent(double w, double h)
00081 {
00082 width = (int) (w*xscale);
00083 height = (int) (h*yscale);
00084 resizeContents(width+1, height+1);
00085 }
00086
00087
00088 void DigraphView::addRenderedNode(const QString &name,
00089 double x, double y, double w, double h)
00090 {
00091 DigraphNode *node = new DigraphNode;
00092 node->x = toXPixel(x);
00093 node->y = toYPixel(y);
00094 node->w = (int) (w*xscale);
00095 node->h = (int) (h*yscale);
00096 node->name = name;
00097 nodes.append(node);
00098 }
00099
00100
00101 void DigraphView::addRenderedEdge(const QString &, const QString &,
00102 QMemArray<double> coords)
00103 {
00104 if (coords.count() < 4)
00105 return;
00106
00107 DigraphEdge *edge = new DigraphEdge;
00108 edge->points.resize(coords.count()/2);
00109
00110 for (uint i = 0; i < edge->points.count(); ++i)
00111 edge->points[i] = QPoint(toXPixel(coords[2*i]), toYPixel(coords[2*i+1]));
00112
00113 edges.append(edge);
00114 }
00115
00116
00117 void DigraphView::addEdge(const QString &name1, const QString &name2)
00118 {
00119 QString line;
00120 line = "\"";
00121 line += name1;
00122 line += "\" -> \"";
00123 line += name2;
00124 line += "\";";
00125 inputs.append(line);
00126 }
00127
00128
00129 void DigraphView::clear()
00130 {
00131 nodes.clear();
00132 edges.clear();
00133 selNode = 0;
00134 viewport()->update();
00135 }
00136
00137
00138 void DigraphView::setSelected(const QString &name)
00139 {
00140 QPtrListIterator<DigraphNode> it(nodes);
00141 for (; it.current(); ++it) {
00142 if (it.current()->name == name) {
00143 updateContents(selNode->x-selNode->w/2, selNode->y-selNode->h/2,
00144 selNode->w, selNode->h);
00145 selNode = it.current();
00146 updateContents(selNode->x-selNode->w/2, selNode->y-selNode->h/2,
00147 selNode->w, selNode->h);
00148 return;
00149 }
00150 }
00151 }
00152
00153
00154 void DigraphView::ensureVisible(const QString &name)
00155 {
00156 QPtrListIterator<DigraphNode> it(nodes);
00157 for (; it.current(); ++it) {
00158 if (it.current()->name == name) {
00159 QScrollView::ensureVisible((*it)->x, (*it)->y, (*it)->w, (*it)->h);
00160 return;
00161 }
00162 }
00163 }
00164
00165
00166 QStringList DigraphView::splitLine(QString str)
00167 {
00168 QStringList result;
00169
00170 while (!str.isEmpty()) {
00171 if (str[0] == '"') {
00172 int pos = str.find('"', 1);
00173 if (pos == -1)
00174 pos = str.length();
00175 result << str.mid(1, pos-1);
00176 str.remove(0, pos+1);
00177 } else {
00178 int pos = str.find(' ');
00179 if (pos == -1)
00180 pos = str.length();
00181 result << str.left(pos);
00182 str.remove(0, pos+1);
00183 }
00184 uint i = 0; while (i<str.length() && str[i] == ' ') ++i;
00185 str.remove(0, i);
00186 }
00187
00188 return result;
00189 }
00190
00191
00192 void DigraphView::parseDotResults(const QStringList &list)
00193 {
00194 QStringList::ConstIterator it;
00195 for (it = list.begin(); it != list.end(); ++it) {
00196 QStringList tokens = splitLine(*it);
00197 if (tokens.count() == 0)
00198 continue;
00199 if (tokens[0] == "graph") {
00200 if (tokens.count() < 4)
00201 continue;
00202 setRenderedExtent(tokens[2].toDouble(), tokens[3].toDouble());
00203 } else if (tokens[0] == "node") {
00204 if (tokens.count() < 6)
00205 continue;
00206 addRenderedNode(tokens[1], tokens[2].toDouble(), tokens[3].toDouble(),
00207 tokens[4].toDouble(), tokens[5].toDouble());
00208 } else if (tokens[0] == "edge") {
00209 if (tokens.count() < 8)
00210 continue;
00211 QMemArray<double> coords(tokens.count()-6);
00212 for (uint i=0; i != tokens.count()-6; ++i)
00213 coords[i] = tokens[i+4].toDouble();
00214 addRenderedEdge(tokens[1], tokens[2], coords);
00215 }
00216 }
00217 }
00218
00219
00220 void DigraphView::process()
00221 {
00222 QString cmd = KGlobal::dirs()->findExe("dot");
00223 if (cmd.isEmpty()) {
00224 KMessageBox::sorry(0, i18n("You do not have 'dot' installed.\nIt can be downloaded from www.graphviz.org."));
00225 return;
00226 }
00227
00228 QStringList results;
00229
00230 KTempFile ifile, ofile;
00231 QTextStream &is = *ifile.textStream();
00232 is << "digraph G {" << endl;
00233 is << "rankdir=LR;" << endl;
00234 is << "node [shape=box,fontname=Helvetica,fontsize=12];" << endl;
00235 QStringList::Iterator it;
00236 for (it = inputs.begin(); it != inputs.end(); ++it)
00237 is << (*it) << endl;
00238 is << "}" << endl;
00239 ifile.close();
00240
00241 KProcess proc;
00242 proc << cmd << "-Tplain" << ifile.name() << "-o" << ofile.name();
00243 proc.start(KProcess::Block);
00244
00245 QTextStream &os = *ofile.textStream();
00246 while (!os.atEnd())
00247 results << os.readLine();
00248 ofile.close();
00249
00250 parseDotResults(results);
00251 inputs.clear();
00252
00253 if (nodes.first())
00254 selNode = nodes.first();
00255 viewport()->update();
00256 }
00257
00258
00259 void DigraphView::drawContents(QPainter* p, int clipx, int clipy, int clipw, int cliph)
00260 {
00261 QRect clipRect(clipx, clipy, clipw, cliph);
00262 p->eraseRect(clipRect);
00263
00264 p->setFont(KGlobalSettings::generalFont());
00265 QPtrListIterator<DigraphNode> it1(nodes);
00266 for (; it1.current(); ++it1) {
00267 QRect r((*it1)->x-(*it1)->w/2, (*it1)->y-(*it1)->h/2, (*it1)->w, (*it1)->h);
00268 if (r.intersects(clipRect)) {
00269 if (it1.current() == selNode)
00270 p->fillRect(r, QBrush(lightGray, SolidPattern));
00271 else
00272 p->drawRect(r);
00273 p->drawText(r, AlignCenter, (*it1)->name);
00274 }
00275 }
00276 p->setBrush(QBrush(black, SolidPattern));
00277 QPtrListIterator<DigraphEdge> it2(edges);
00278 for (; it2.current(); ++it2) {
00279 int n = (*it2)->points.count();
00280 for (int i=0; i+3 < n; i+=3)
00281 {
00282 QPointArray a(4);
00283 QPointArray &b = (*it2)->points;
00284 for (int j=0; j<4; ++j)
00285 a.setPoint(j, b.point(i+j));
00286 if (a.boundingRect().intersects(clipRect))
00287 p->drawCubicBezier((*it2)->points, i);
00288 }
00289 QPoint p1 = (*it2)->points[n-2];
00290 QPoint p2 = (*it2)->points[n-1];
00291 QPoint d = p1-p2;
00292 double l = sqrt(d.x()*d.x()+d.y()*d.y());
00293 double d11 = (10.0)/l*d.x();
00294 double d12 = (10.0)/l*d.y();
00295 double d21 = -(3.0/l)*d.y();
00296 double d22 = (3.0/l)*d.x();
00297 QPointArray triangle(3);
00298 triangle[0] = p2 + QPoint((int)(d11+d21),(int)(d12+d22));
00299 triangle[1] = p2 + QPoint((int)(d11-d21),(int)(d12-d22));
00300 triangle[2] = p2;
00301 p->drawPolygon(triangle, true);
00302 }
00303 }
00304
00305
00306 void DigraphView::contentsMousePressEvent(QMouseEvent *e)
00307 {
00308 QPtrListIterator<DigraphNode> it1(nodes);
00309 for (; it1.current(); ++it1) {
00310 QRect r((*it1)->x-(*it1)->w/2, (*it1)->y-(*it1)->h/2, (*it1)->w, (*it1)->h);
00311 if (r.contains(e->pos())) {
00312 if (selNode) {
00313 QRect oldr(selNode->x-selNode->w/2, selNode->y-selNode->h/2,
00314 selNode->w, selNode->h);
00315 updateContents(oldr);
00316 }
00317 selNode = it1.current();
00318 emit selected(selNode->name);
00319 updateContents(r);
00320 }
00321
00322 }
00323 }
00324
00325
00326 QSize DigraphView::sizeHint() const
00327 {
00328 if (width == -1)
00329 return QSize(100, 100);
00330
00331 #if defined(KDE_IS_VERSION)
00332 #if (KDE_IS_VERSION(3,1,90))
00333 QSize dsize = KGlobalSettings::desktopGeometry(viewport()).size();
00334 #else
00335 QSize dsize = QApplication::desktop()->size();
00336 #endif
00337 #else
00338 QSize dsize = QApplication::desktop()->size();
00339 #endif
00340 return QSize(width, height).boundedTo(QSize(dsize.width()*2/3, dsize.height()*2/3));
00341 }
00342
00343
00344 #if 0
00345 int main(int argc, char **argv)
00346 {
00347 QApplication app(argc, argv);
00348
00349 DigraphView *dw = new DigraphView(0, "dot widget");
00350 dw->addEdge( "5th Edition", "6th Edition");
00351 dw->addEdge( "5th Edition", "PWB 1.0");
00352 dw->addEdge( "6th Edition", "LSX");
00353 dw->addEdge( "6th Edition", "1 BSD");
00354 dw->addEdge( "6th Edition", "Mini Unix");
00355 dw->addEdge( "6th Edition", "Wollongong");
00356 dw->addEdge( "6th Edition", "Interdata");
00357 dw->addEdge( "Interdata", "Unix/TS 3.0");
00358 dw->addEdge( "Interdata", "PWB 2.0");
00359 dw->addEdge( "Interdata", "7th Edition");
00360 dw->addEdge( "7th Edition", "8th Edition");
00361 dw->addEdge( "7th Edition", "32V");
00362 dw->addEdge( "7th Edition", "V7M");
00363 dw->addEdge( "7th Edition", "Ultrix-11");
00364 dw->addEdge( "7th Edition", "Xenix");
00365 dw->addEdge( "7th Edition", "UniPlus+");
00366 dw->addEdge( "V7M", "Ultrix-11");
00367 dw->addEdge( "8th Edition", "9th Edition");
00368 dw->addEdge( "1 BSD", "2 BSD");
00369 dw->addEdge( "2 BSD", "2.8 BSD");
00370 dw->addEdge( "2.8 BSD", "Ultrix-11");
00371 dw->addEdge( "2.8 BSD", "2.9 BSD");
00372 dw->addEdge( "32V", "3 BSD");
00373 dw->addEdge( "3 BSD", "4 BSD");
00374 dw->addEdge( "4 BSD", "4.1 BSD");
00375 dw->addEdge( "4.1 BSD", "4.2 BSD");
00376 dw->addEdge( "4.1 BSD", "2.8 BSD");
00377 dw->addEdge( "4.1 BSD", "8th Edition");
00378 dw->addEdge( "4.2 BSD", "4.3 BSD");
00379 dw->addEdge( "4.2 BSD", "Ultrix-32");
00380 dw->addEdge( "PWB 1.0", "PWB 1.2");
00381 dw->addEdge( "PWB 1.0", "USG 1.0");
00382 dw->addEdge( "PWB 1.2", "PWB 2.0");
00383 dw->addEdge( "USG 1.0", "CB Unix 1");
00384 dw->addEdge( "USG 1.0", "USG 2.0");
00385 dw->addEdge( "CB Unix 1", "CB Unix 2");
00386 dw->addEdge( "CB Unix 2", "CB Unix 3");
00387 dw->addEdge( "CB Unix 3", "Unix/TS++");
00388 dw->addEdge( "CB Unix 3", "PDP-11 Sys V");
00389 dw->addEdge( "USG 2.0", "USG 3.0");
00390 dw->addEdge( "USG 3.0", "Unix/TS 3.0");
00391 dw->addEdge( "PWB 2.0", "Unix/TS 3.0");
00392 dw->addEdge( "Unix/TS 1.0", "Unix/TS 3.0");
00393 dw->addEdge( "Unix/TS 3.0", "TS 4.0");
00394 dw->addEdge( "Unix/TS++", "TS 4.0");
00395 dw->addEdge( "CB Unix 3", "TS 4.0");
00396 dw->addEdge( "TS 4.0", "System V.0");
00397 dw->addEdge( "System V.0", "System V.2");
00398 dw->addEdge( "System V.2", "System V.3");
00399 dw->process();
00400 dw->show();
00401
00402 return app.exec();
00403 }
00404 #endif
00405
00406 #include "digraphview.moc"