You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
415 lines
12 KiB
415 lines
12 KiB
/***************************************************************************
|
|
* Copyright (C) 2001 by Bernd Gehrmann *
|
|
* bernd@tdevelop.org *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "digraphview.h"
|
|
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <tqapplication.h>
|
|
#include <tqpainter.h>
|
|
#include <tqpaintdevicemetrics.h>
|
|
#include <tqtextstream.h>
|
|
#include <kglobal.h>
|
|
#include <klocale.h>
|
|
#include <kmessagebox.h>
|
|
#include <kprocess.h>
|
|
#include <kstandarddirs.h>
|
|
#include <kglobalsettings.h>
|
|
#include <ktempfile.h>
|
|
#include <tdeversion.h>
|
|
#include <kdebug.h>
|
|
|
|
struct DigraphNode
|
|
{
|
|
int x;
|
|
int y;
|
|
int w;
|
|
int h;
|
|
TQString name;
|
|
};
|
|
|
|
|
|
struct DigraphEdge
|
|
{
|
|
TQPointArray points;
|
|
};
|
|
|
|
|
|
DigraphView::DigraphView(TQWidget *parent, const char *name)
|
|
: TQScrollView(parent, name, WRepaintNoErase|WStaticContents|WResizeNoErase)
|
|
{
|
|
viewport()->setBackgroundMode(PaletteBase);
|
|
|
|
TQPaintDeviceMetrics m(this);
|
|
xscale = m.logicalDpiX();
|
|
yscale = m.logicalDpiY();
|
|
|
|
width = -1;
|
|
height = -1;
|
|
|
|
nodes.setAutoDelete(true);
|
|
edges.setAutoDelete(true);
|
|
selNode = 0;
|
|
}
|
|
|
|
|
|
DigraphView::~DigraphView()
|
|
{
|
|
}
|
|
|
|
|
|
int DigraphView::toXPixel(double x)
|
|
{
|
|
return (int) (x*xscale);
|
|
}
|
|
|
|
|
|
int DigraphView::toYPixel(double y)
|
|
{
|
|
return height - (int) (y*yscale);
|
|
}
|
|
|
|
|
|
void DigraphView::setRenderedExtent(double w, double h)
|
|
{
|
|
width = (int) (w*xscale);
|
|
height = (int) (h*yscale);
|
|
resizeContents(width+1, height+1);
|
|
}
|
|
|
|
|
|
void DigraphView::addRenderedNode(const TQString &name,
|
|
double x, double y, double w, double h)
|
|
{
|
|
DigraphNode *node = new DigraphNode;
|
|
node->x = toXPixel(x);
|
|
node->y = toYPixel(y);
|
|
node->w = (int) (w*xscale);
|
|
node->h = (int) (h*yscale);
|
|
node->name = name;
|
|
nodes.append(node);
|
|
}
|
|
|
|
|
|
void DigraphView::addRenderedEdge(const TQString &/*name1*/, const TQString &/*name2*/,
|
|
TQMemArray<double> coords)
|
|
{
|
|
if (coords.count() < 4)
|
|
return;
|
|
|
|
DigraphEdge *edge = new DigraphEdge;
|
|
edge->points.resize(coords.count()/2);
|
|
|
|
for (uint i = 0; i < edge->points.count(); ++i)
|
|
edge->points[i] = TQPoint(toXPixel(coords[2*i]), toYPixel(coords[2*i+1]));
|
|
|
|
edges.append(edge);
|
|
}
|
|
|
|
|
|
void DigraphView::addEdge(const TQString &name1, const TQString &name2)
|
|
{
|
|
TQString line;
|
|
line = "\"";
|
|
line += name1;
|
|
line += "\" -> \"";
|
|
line += name2;
|
|
line += "\";";
|
|
inputs.append(line);
|
|
}
|
|
|
|
|
|
void DigraphView::clear()
|
|
{
|
|
nodes.clear();
|
|
edges.clear();
|
|
selNode = 0;
|
|
width = -1;
|
|
height = -1;
|
|
inputs.clear();
|
|
viewport()->update();
|
|
}
|
|
|
|
|
|
void DigraphView::setSelected(const TQString &name)
|
|
{
|
|
TQPtrListIterator<DigraphNode> it(nodes);
|
|
for (; it.current(); ++it) {
|
|
if (it.current()->name == name) {
|
|
updateContents(selNode->x-selNode->w/2, selNode->y-selNode->h/2,
|
|
selNode->w, selNode->h);
|
|
selNode = it.current();
|
|
updateContents(selNode->x-selNode->w/2, selNode->y-selNode->h/2,
|
|
selNode->w, selNode->h);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void DigraphView::ensureVisible(const TQString &name)
|
|
{
|
|
TQPtrListIterator<DigraphNode> it(nodes);
|
|
for (; it.current(); ++it) {
|
|
if (it.current()->name == name) {
|
|
TQScrollView::ensureVisible((*it)->x, (*it)->y, (*it)->w, (*it)->h);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
TQStringList DigraphView::splitLine(TQString str)
|
|
{
|
|
TQStringList result;
|
|
|
|
while (!str.isEmpty()) {
|
|
if (str[0] == '"') {
|
|
int pos = str.find('"', 1);
|
|
if (pos == -1)
|
|
pos = str.length();
|
|
result << str.mid(1, pos-1);
|
|
str.remove(0, pos+1);
|
|
} else {
|
|
int pos = str.find(' ');
|
|
if (pos == -1)
|
|
pos = str.length();
|
|
result << str.left(pos);
|
|
str.remove(0, pos+1);
|
|
}
|
|
uint i = 0; while (i<str.length() && str[i] == ' ') ++i;
|
|
str.remove(0, i);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
void DigraphView::parseDotResults(const TQStringList &list)
|
|
{
|
|
TQStringList::ConstIterator it;
|
|
for (it = list.begin(); it != list.end(); ++it) {
|
|
TQStringList tokens = splitLine(*it);
|
|
if (tokens.count() == 0)
|
|
continue;
|
|
if (tokens[0] == "graph") {
|
|
if (tokens.count() < 4)
|
|
continue;
|
|
setRenderedExtent(tokens[2].toDouble(), tokens[3].toDouble());
|
|
} else if (tokens[0] == "node") {
|
|
if (tokens.count() < 6)
|
|
continue;
|
|
addRenderedNode(tokens[1], tokens[2].toDouble(), tokens[3].toDouble(),
|
|
tokens[4].toDouble(), tokens[5].toDouble());
|
|
} else if (tokens[0] == "edge") {
|
|
if (tokens.count() < 8)
|
|
continue;
|
|
TQMemArray<double> coords(tokens.count()-6);
|
|
for (uint i=0; i != tokens.count()-6; ++i)
|
|
coords[i] = tokens[i+4].toDouble();
|
|
addRenderedEdge(tokens[1], tokens[2], coords);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void DigraphView::process( const TQString& file, const TQString& ext )
|
|
{
|
|
TQString cmd = KGlobal::dirs()->findExe("dot");
|
|
if (cmd.isEmpty()) {
|
|
KMessageBox::sorry(0, i18n("You do not have 'dot' installed.\nIt can be downloaded from www.graphviz.org."));
|
|
return;
|
|
}
|
|
|
|
TQStringList results;
|
|
|
|
KTempFile ifile, ofile;
|
|
TQTextStream &is = *ifile.textStream();
|
|
is << "digraph G {" << endl;
|
|
is << "rankdir=LR;" << endl;
|
|
is << "node [tqshape=box,fontname=Helvetica,fontsize=12];" << endl;
|
|
TQStringList::Iterator it;
|
|
for (it = inputs.begin(); it != inputs.end(); ++it)
|
|
is << (*it) << endl;
|
|
is << "}" << endl;
|
|
ifile.close();
|
|
|
|
KProcess proc;
|
|
if( !file.isEmpty() && !ext.isEmpty() )
|
|
{
|
|
proc << cmd << TQString("-T")+ext << ifile.name() << "-o" << file;
|
|
kdDebug() << "Executing: " << cmd <<" "<<TQString("-T")+ext <<" "<< ifile.name() << "-o"<<file << endl;
|
|
}else
|
|
{
|
|
proc << cmd << "-Tplain" << ifile.name() << "-o" << ofile.name();
|
|
}
|
|
proc.start(KProcess::Block);
|
|
|
|
if( !file.isEmpty() && !ext.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
TQTextStream &os = *ofile.textStream();
|
|
while (!os.atEnd())
|
|
results << os.readLine();
|
|
ofile.close();
|
|
|
|
parseDotResults(results);
|
|
inputs.clear();
|
|
|
|
if (nodes.first())
|
|
selNode = nodes.first();
|
|
viewport()->update();
|
|
}
|
|
|
|
void DigraphView::drawContents(TQPainter* p, int clipx, int clipy, int clipw, int cliph)
|
|
{
|
|
TQRect clipRect(clipx, clipy, clipw, cliph);
|
|
p->eraseRect(clipRect);
|
|
|
|
p->setFont(KGlobalSettings::generalFont());
|
|
TQPtrListIterator<DigraphNode> it1(nodes);
|
|
for (; it1.current(); ++it1) {
|
|
TQRect r((*it1)->x-(*it1)->w/2, (*it1)->y-(*it1)->h/2, (*it1)->w, (*it1)->h);
|
|
if (r.intersects(clipRect)) {
|
|
if (it1.current() == selNode)
|
|
p->fillRect(r, TQBrush(lightGray, SolidPattern));
|
|
else
|
|
p->drawRect(r);
|
|
p->drawText(r, AlignCenter, (*it1)->name);
|
|
}
|
|
}
|
|
p->setBrush(TQBrush(black, SolidPattern));
|
|
TQPtrListIterator<DigraphEdge> it2(edges);
|
|
for (; it2.current(); ++it2) {
|
|
int n = (*it2)->points.count();
|
|
for (int i=0; i+3 < n; i+=3)
|
|
{
|
|
TQPointArray a(4);
|
|
TQPointArray &b = (*it2)->points;
|
|
for (int j=0; j<4; ++j)
|
|
a.setPoint(j, b.point(i+j));
|
|
if (a.boundingRect().intersects(clipRect))
|
|
p->drawCubicBezier((*it2)->points, i);
|
|
}
|
|
TQPoint p1 = (*it2)->points[n-2];
|
|
TQPoint p2 = (*it2)->points[n-1];
|
|
TQPoint d = p1-p2;
|
|
double l = sqrt(d.x()*d.x()+d.y()*d.y());
|
|
double d11 = (10.0)/l*d.x();
|
|
double d12 = (10.0)/l*d.y();
|
|
double d21 = -(3.0/l)*d.y();
|
|
double d22 = (3.0/l)*d.x();
|
|
TQPointArray triangle(3);
|
|
triangle[0] = p2 + TQPoint((int)(d11+d21),(int)(d12+d22));
|
|
triangle[1] = p2 + TQPoint((int)(d11-d21),(int)(d12-d22));
|
|
triangle[2] = p2;
|
|
p->tqdrawPolygon(triangle, true);
|
|
}
|
|
}
|
|
|
|
|
|
void DigraphView::contentsMousePressEvent(TQMouseEvent *e)
|
|
{
|
|
TQPtrListIterator<DigraphNode> it1(nodes);
|
|
for (; it1.current(); ++it1) {
|
|
TQRect r((*it1)->x-(*it1)->w/2, (*it1)->y-(*it1)->h/2, (*it1)->w, (*it1)->h);
|
|
if (r.contains(e->pos())) {
|
|
if (selNode) {
|
|
TQRect oldr(selNode->x-selNode->w/2, selNode->y-selNode->h/2,
|
|
selNode->w, selNode->h);
|
|
updateContents(oldr);
|
|
}
|
|
selNode = it1.current();
|
|
emit selected(selNode->name);
|
|
updateContents(r);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
TQSize DigraphView::tqsizeHint() const
|
|
{
|
|
if (width == -1)
|
|
return TQSize(100, 100); // arbitrary
|
|
|
|
TQSize dsize = KGlobalSettings::desktopGeometry(viewport()).size();
|
|
kdDebug(9003) << "sizehint for inheritance diagram" << dsize << " " << width << " " << height << endl;
|
|
return TQSize(width, height).boundedTo(TQSize(dsize.width()*2/3, dsize.height()*2/3));
|
|
}
|
|
|
|
|
|
#if 0
|
|
int main(int argc, char **argv)
|
|
{
|
|
TQApplication app(argc, argv);
|
|
|
|
DigraphView *dw = new DigraphView(0, "dot widget");
|
|
dw->addEdge( "5th Edition", "6th Edition");
|
|
dw->addEdge( "5th Edition", "PWB 1.0");
|
|
dw->addEdge( "6th Edition", "LSX");
|
|
dw->addEdge( "6th Edition", "1 BSD");
|
|
dw->addEdge( "6th Edition", "Mini Unix");
|
|
dw->addEdge( "6th Edition", "Wollongong");
|
|
dw->addEdge( "6th Edition", "Interdata");
|
|
dw->addEdge( "Interdata", "Unix/TS 3.0");
|
|
dw->addEdge( "Interdata", "PWB 2.0");
|
|
dw->addEdge( "Interdata", "7th Edition");
|
|
dw->addEdge( "7th Edition", "8th Edition");
|
|
dw->addEdge( "7th Edition", "32V");
|
|
dw->addEdge( "7th Edition", "V7M");
|
|
dw->addEdge( "7th Edition", "Ultrix-11");
|
|
dw->addEdge( "7th Edition", "Xenix");
|
|
dw->addEdge( "7th Edition", "UniPlus+");
|
|
dw->addEdge( "V7M", "Ultrix-11");
|
|
dw->addEdge( "8th Edition", "9th Edition");
|
|
dw->addEdge( "1 BSD", "2 BSD");
|
|
dw->addEdge( "2 BSD", "2.8 BSD");
|
|
dw->addEdge( "2.8 BSD", "Ultrix-11");
|
|
dw->addEdge( "2.8 BSD", "2.9 BSD");
|
|
dw->addEdge( "32V", "3 BSD");
|
|
dw->addEdge( "3 BSD", "4 BSD");
|
|
dw->addEdge( "4 BSD", "4.1 BSD");
|
|
dw->addEdge( "4.1 BSD", "4.2 BSD");
|
|
dw->addEdge( "4.1 BSD", "2.8 BSD");
|
|
dw->addEdge( "4.1 BSD", "8th Edition");
|
|
dw->addEdge( "4.2 BSD", "4.3 BSD");
|
|
dw->addEdge( "4.2 BSD", "Ultrix-32");
|
|
dw->addEdge( "PWB 1.0", "PWB 1.2");
|
|
dw->addEdge( "PWB 1.0", "USG 1.0");
|
|
dw->addEdge( "PWB 1.2", "PWB 2.0");
|
|
dw->addEdge( "USG 1.0", "CB Unix 1");
|
|
dw->addEdge( "USG 1.0", "USG 2.0");
|
|
dw->addEdge( "CB Unix 1", "CB Unix 2");
|
|
dw->addEdge( "CB Unix 2", "CB Unix 3");
|
|
dw->addEdge( "CB Unix 3", "Unix/TS++");
|
|
dw->addEdge( "CB Unix 3", "PDP-11 Sys V");
|
|
dw->addEdge( "USG 2.0", "USG 3.0");
|
|
dw->addEdge( "USG 3.0", "Unix/TS 3.0");
|
|
dw->addEdge( "PWB 2.0", "Unix/TS 3.0");
|
|
dw->addEdge( "Unix/TS 1.0", "Unix/TS 3.0");
|
|
dw->addEdge( "Unix/TS 3.0", "TS 4.0");
|
|
dw->addEdge( "Unix/TS++", "TS 4.0");
|
|
dw->addEdge( "CB Unix 3", "TS 4.0");
|
|
dw->addEdge( "TS 4.0", "System V.0");
|
|
dw->addEdge( "System V.0", "System V.2");
|
|
dw->addEdge( "System V.2", "System V.3");
|
|
dw->process();
|
|
dw->show();
|
|
|
|
return app.exec();
|
|
}
|
|
#endif
|
|
|
|
#include "digraphview.moc"
|