mirror of
https://github.com/ra3xdh/qucs_s
synced 2025-03-28 21:13:26 +00:00
525 lines
16 KiB
C++
525 lines
16 KiB
C++
/***************************************************************************
|
|
timingdiagram.cpp
|
|
-------------------
|
|
begin : Sat Oct 22 2005
|
|
copyright : (C) 2005 by Michael Margraf
|
|
email : michael.margraf@alumni.tu-berlin.de
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
/*!
|
|
\class TimingDiagram
|
|
\brief The TimingDiagram class implements the Timing diagram
|
|
*/
|
|
|
|
#include "timingdiagram.h"
|
|
#include "main.h"
|
|
#include "misc.h"
|
|
|
|
#include <cmath>
|
|
#include <QPolygon>
|
|
#include <QPainter>
|
|
|
|
|
|
TimingDiagram::TimingDiagram(int _cx, int _cy) : TabDiagram(_cx, _cy)
|
|
{
|
|
x1 = 0; // no extension to select area
|
|
y1 = 0;
|
|
x2 = x3 = 300; // initial size of diagram
|
|
y2 = 200;
|
|
Name = "Time";
|
|
xAxis.limit_min = 0.0; // scroll bar position (needs to be saved in file)
|
|
|
|
calcDiagram();
|
|
}
|
|
|
|
TimingDiagram::~TimingDiagram()
|
|
{
|
|
}
|
|
|
|
// ------------------------------------------------------------
|
|
void TimingDiagram::paint(ViewPainter *p)
|
|
{
|
|
// paint all lines
|
|
foreach(Line *pl, Lines) {
|
|
p->Painter->setPen(pl->style);
|
|
p->drawLine(cx+pl->x1, cy-pl->y1, cx+pl->x2, cy-pl->y2);
|
|
}
|
|
|
|
p->Painter->setPen(Qt::black);
|
|
// write whole text
|
|
foreach(Text *pt, Texts)
|
|
p->drawText(pt->s, cx+pt->x, cy-pt->y);
|
|
|
|
|
|
if(y1 > 0) { // paint scroll bar ?
|
|
int x, y, dx, dy;
|
|
QPolygon Points;
|
|
// draw scroll bar
|
|
p->fillRect(cx+yAxis.numGraphs, cy+2, zAxis.numGraphs, 14, QColor(192, 192, 192));
|
|
|
|
int bx = cx+yAxis.numGraphs+zAxis.numGraphs;
|
|
// draw frame for scroll bar
|
|
p->Painter->setPen(QPen(Qt::black,0));
|
|
p->drawLine(cx+xAxis.numGraphs, cy, cx+xAxis.numGraphs, cy+17);
|
|
p->drawLine(cx+xAxis.numGraphs+17, cy, cx+xAxis.numGraphs+17, cy+17);
|
|
p->drawLine(cx+xAxis.numGraphs, cy+17, cx+x2, cy+17);
|
|
p->drawLine(cx+x2, cy, cx+x2, cy+17);
|
|
p->drawLine(cx+x2-17, cy, cx+x2-17, cy+17);
|
|
|
|
// draw the arrows above and below the scroll bar
|
|
p->Painter->setBrush(QColor(192, 192, 192));
|
|
p->Painter->setPen(QColor(152, 152, 152));
|
|
p->drawLine(cx+yAxis.numGraphs, cy+15, bx, cy+15);
|
|
p->drawLine(bx, cy+2, bx, cy+15);
|
|
|
|
p->map(cx+xAxis.numGraphs+3, cy+3, x, y);
|
|
p->map(cx+xAxis.numGraphs+14, cy+14, dx, dy);
|
|
Points.setPoints(3, x, (y+dy)>>1, dx, y, dx, dy);
|
|
p->Painter->drawConvexPolygon(Points);
|
|
p->Painter->setPen(QColor(224, 224, 224));
|
|
p->Painter->drawLine(x, (y+dy)>>1, dx, y);
|
|
p->drawLine(cx+yAxis.numGraphs, cy+2, bx, cy+2);
|
|
p->drawLine(cx+yAxis.numGraphs, cy+2, cx+yAxis.numGraphs, cy+15);
|
|
|
|
p->Painter->setPen(QColor(152, 152, 152));
|
|
dx -= x;
|
|
p->map(cx+x2-3, cy+3, x, y);
|
|
Points.setPoints(3, x, (y+dy)>>1, x-dx, y, x-dx, dy);
|
|
p->Painter->drawConvexPolygon(Points);
|
|
p->Painter->setPen(QColor(208, 208, 208));
|
|
p->Painter->drawLine(x-dx, y, x, (y+dy)>>1);
|
|
p->Painter->setPen(QColor(224, 224, 224));
|
|
p->Painter->drawLine(x-dx, y, x-dx, dy);
|
|
|
|
p->Painter->setBrush(QBrush(Qt::NoBrush));
|
|
}
|
|
|
|
|
|
if(isSelected) {
|
|
p->Painter->setPen(QPen(Qt::darkGray,3));
|
|
p->drawRect(cx-5, cy-y2-5, x2+10, y2+10);
|
|
p->Painter->setPen(QPen(Qt::darkRed,2));
|
|
p->drawResizeRect(cx, cy-y2); // markers for changing the size
|
|
p->drawResizeRect(cx, cy);
|
|
p->drawResizeRect(cx+x2, cy-y2);
|
|
p->drawResizeRect(cx+x2, cy);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------
|
|
int TimingDiagram::calcDiagram()
|
|
{
|
|
Lines.clear();
|
|
Texts.clear();
|
|
|
|
y1 = 0; // no scroll bar
|
|
x3 = x2;
|
|
// get size of text using the screen-compatible metric
|
|
QFontMetrics metrics(QucsSettings.font, 0);
|
|
int tHeight = metrics.lineSpacing();
|
|
QString Str;
|
|
int colWidth=0, x=4, y, xStart = 0, z;
|
|
|
|
int NumAll=0; // how many values per row
|
|
int NumLeft=0; // how many values could not be written
|
|
int invisibleCount = 0; // how many values are invisible
|
|
|
|
if(y2 < (tHeight + 8))
|
|
y2 = tHeight + 8;
|
|
y = y2 - tHeight - 6;
|
|
|
|
// outer frame
|
|
Lines.append(new Line(0, y2, x2, y2, QPen(Qt::black,0)));
|
|
Lines.append(new Line(0, y2, 0, 0, QPen(Qt::black,0)));
|
|
Lines.append(new Line(x2, y2, x2, 0, QPen(Qt::black,0)));
|
|
Lines.append(new Line(0, 0, x2, 0, QPen(Qt::black,0)));
|
|
Lines.append(new Line(0, y+2, x2, y+2, QPen(Qt::black,0)));
|
|
|
|
if(xAxis.limit_min < 0.0)
|
|
xAxis.limit_min = 0.0;
|
|
|
|
Graph *firstGraph;
|
|
|
|
QListIterator<Graph *> ig(Graphs);
|
|
Graph *g = 0;
|
|
if (ig.hasNext()) // point to first graph
|
|
g = ig.next();
|
|
|
|
if(g == 0) { // no variables specified in diagram ?
|
|
Str = QObject::tr("no variables");
|
|
colWidth = checkColumnWidth(Str, metrics, colWidth, x, y2);
|
|
if(colWidth >= 0)
|
|
Texts.append(new Text(x, y2-2, Str)); // independent variable
|
|
return 0;
|
|
}
|
|
|
|
|
|
double *px;
|
|
// any graph with data ?
|
|
while(g->cPointsX.isEmpty()) {
|
|
if (!ig.hasNext()) break; // no more graphs, exit loop
|
|
g = ig.next(); // point to next graph
|
|
}
|
|
|
|
if(g->cPointsX.isEmpty()) { // no graph with data found ?
|
|
Str = QObject::tr("no data");
|
|
colWidth = checkColumnWidth(Str, metrics, colWidth, x, y2);
|
|
if(colWidth < 0) return 0;
|
|
Texts.append(new Text(x, y2-2, Str));
|
|
return 0;
|
|
}
|
|
firstGraph = g;
|
|
|
|
|
|
// First check the maximum bit number of all vectors.
|
|
colWidth = 0;
|
|
foreach(Graph *g, Graphs)
|
|
if(g->cPointsY) {
|
|
if(g->Var.right(2) == ".X") {
|
|
z = strlen((char*)g->cPointsY);
|
|
if(z > colWidth)
|
|
colWidth = z;
|
|
}
|
|
else {
|
|
z = 8;
|
|
if(z > colWidth)
|
|
colWidth = z;
|
|
}
|
|
}
|
|
int TimeStepWidth = colWidth * metrics.width("X") + 8;
|
|
if(TimeStepWidth < 40)
|
|
TimeStepWidth = 40;
|
|
|
|
|
|
colWidth = 0;
|
|
if(!firstGraph->cPointsX.isEmpty()) {
|
|
// ................................................
|
|
if(firstGraph->cPointsX.count() > 1) {
|
|
Str = QObject::tr("wrong dependency");
|
|
colWidth = checkColumnWidth(Str, metrics, colWidth, x, y2);
|
|
if(colWidth >= 0)
|
|
Texts.append(new Text(x, y2-2, Str)); // independent variable
|
|
return 0;
|
|
}
|
|
|
|
|
|
// first, write name of independent variable
|
|
DataX *pD = firstGraph->cPointsX.getFirst();
|
|
NumAll = pD->count;
|
|
Str = pD->Var;
|
|
colWidth = checkColumnWidth(Str, metrics, colWidth, x, y2);
|
|
if(colWidth < 0) return 1;
|
|
Texts.append(new Text(x, y2-2, Str));
|
|
|
|
|
|
y -= 5;
|
|
// write all dependent variable names to get width of first column
|
|
foreach(Graph *g, Graphs) {
|
|
if(y < tHeight) break;
|
|
Str = g->Var;
|
|
colWidth = checkColumnWidth(Str, metrics, colWidth, x, y);
|
|
if(colWidth < 0) return 1;
|
|
Texts.append(new Text(x, y, Str)); // dependent variable
|
|
y -= tHeight + 2;
|
|
}
|
|
x += colWidth + 13;
|
|
xAxis.numGraphs = x -6;
|
|
Lines.append(new Line(x-6, y2, x-6, 0, QPen(Qt::black,0)));
|
|
xStart = x;
|
|
|
|
|
|
invisibleCount = NumAll - (x2-xAxis.numGraphs)/TimeStepWidth;
|
|
if(invisibleCount <= 0) xAxis.limit_min = 0.0; // longer than needed
|
|
else {
|
|
NumLeft = invisibleCount - int(xAxis.limit_min + 0.5);
|
|
if(invisibleCount < int(xAxis.limit_min + 0.5))
|
|
xAxis.limit_min = double(invisibleCount); // adjust limit of scroll bar
|
|
}
|
|
|
|
|
|
// write independent variable values (usually time)
|
|
y = y2-tHeight-4;
|
|
px = pD->Points;
|
|
z = int(xAxis.limit_min + 0.5);
|
|
px += z;
|
|
z = pD->count - z;
|
|
for( ; z>0; z--) {
|
|
Str = num2str(*(px++));
|
|
colWidth = metrics.width(Str); // width of text
|
|
if(x+colWidth+2 >= x2) break;
|
|
|
|
Texts.append(new Text( x, y2-2, Str));
|
|
Lines.append(new Line(x+5, y, x+5, y-3, QPen(Qt::black,0)));
|
|
x += TimeStepWidth;
|
|
}
|
|
|
|
} // of "if no data in graphs"
|
|
|
|
|
|
tHeight += 2;
|
|
// ................................................
|
|
// work on all dependent variables
|
|
QPen Pen;
|
|
int yLast, yNow;
|
|
y = y2-tHeight-9;
|
|
foreach(Graph *g, Graphs) {
|
|
if(y < tHeight) {
|
|
// mark lack of space with a small arrow
|
|
Lines.append(new Line(4, 6, 4, -7, QPen(Qt::red,2)));
|
|
Lines.append(new Line(1, 0, 4, -7, QPen(Qt::red,2)));
|
|
Lines.append(new Line(7, 0, 4, -7, QPen(Qt::red,2)));
|
|
break;
|
|
}
|
|
|
|
x = xStart + 5;
|
|
colWidth = 0;
|
|
|
|
if(g->cPointsY == 0) {
|
|
Str = QObject::tr("no data");
|
|
colWidth = checkColumnWidth(Str, metrics, colWidth, x, y);
|
|
if(colWidth < 0) goto funcEnd;
|
|
Texts.append(new Text(x, y, Str));
|
|
y -= tHeight;
|
|
continue;
|
|
}
|
|
|
|
if(!sameDependencies(g, firstGraph)) {
|
|
Str = QObject::tr("wrong dependency");
|
|
colWidth = checkColumnWidth(Str, metrics, colWidth, x, y);
|
|
if(colWidth < 0) goto funcEnd;
|
|
Texts.append(new Text(x, y, Str));
|
|
y -= tHeight;
|
|
continue;
|
|
}
|
|
|
|
Pen = QPen(g->Color, g->Thick); // default is solid line
|
|
switch(g->Style) {
|
|
case 1: Pen.setStyle(Qt::DashLine); break;
|
|
case 2: Pen.setStyle(Qt::DotLine); break;
|
|
}
|
|
|
|
z = int(xAxis.limit_min + 0.5);
|
|
if(g->Var.right(2) != ".X") { // not digital variable ?
|
|
px = g->cPointsY;
|
|
px += 2 * z;
|
|
z = g->cPointsX.getFirst()->count - z;
|
|
yNow = 1 + ((tHeight - 6) >> 1);
|
|
Lines.append(new Line(x, y-yNow, x+2, y-1, Pen));
|
|
Lines.append(new Line(x+2, y-tHeight+5, x, y-yNow, Pen));
|
|
for( ; z>0; z--) {
|
|
if(x+TimeStepWidth >= x2) break;
|
|
Lines.append(new Line(x+2, y-1, x+TimeStepWidth-2, y-1, Pen));
|
|
Lines.append(new Line(x+2, y-tHeight+5, x+TimeStepWidth-2, y-tHeight+5, Pen));
|
|
|
|
if (*(px+1) == 0.0)
|
|
// output real number
|
|
Texts.append(new Text(x+3, y,QString::number(*px)));
|
|
else
|
|
// output magnitude of (complex) number
|
|
Texts.append(new Text(x+3, y,
|
|
QString::number(sqrt((*px)*(*px) + (*(px+1))*(*(px+1))))));
|
|
|
|
px += 2;
|
|
x += TimeStepWidth;
|
|
Lines.append(new Line(x-2, y-tHeight+5, x+2, y-1, Pen));
|
|
Lines.append(new Line(x+2, y-tHeight+5, x-2, y-1, Pen));
|
|
}
|
|
y -= tHeight;
|
|
continue;
|
|
}
|
|
|
|
|
|
// digital variable !!!
|
|
char *pcx = (char*)g->cPointsY;
|
|
pcx += z * (strlen(pcx)+1);
|
|
|
|
if(strlen(pcx) < 2) { // vector or single bit ?
|
|
|
|
// It is single "bit".
|
|
yLast = 0;
|
|
if(z > 0) yLast += 2; // vertical line before first value ?
|
|
switch(*(pcx-yLast)) { // high or low ?
|
|
case '0': // low
|
|
yLast = tHeight - 5;
|
|
break;
|
|
case '1': // high
|
|
yLast = 1;
|
|
break;
|
|
default:
|
|
yLast = 1 + ((tHeight - 6) >> 1);
|
|
}
|
|
|
|
z = g->cPointsX.getFirst()->count - z;
|
|
for( ; z>0; z--) {
|
|
|
|
switch(*pcx) {
|
|
case '0': // low
|
|
yNow = tHeight - 5;
|
|
break;
|
|
case '1': // high
|
|
yNow = 1;
|
|
break;
|
|
default:
|
|
yNow = 1 + ((tHeight - 6) >> 1);
|
|
}
|
|
|
|
if(yLast != yNow)
|
|
Lines.append(new Line(x, y-yLast, x, y-yNow, Pen));
|
|
if(x+TimeStepWidth >= x2) break;
|
|
if((*pcx & 254) == '0')
|
|
Lines.append(new Line(x, y-yNow, x+TimeStepWidth, y-yNow, Pen));
|
|
else {
|
|
Texts.append(new Text(x+(TimeStepWidth>>1)-3, y, QString(pcx)));
|
|
Lines.append(new Line(x+3, y-1, x+TimeStepWidth-3, y-1, Pen));
|
|
Lines.append(new Line(x+3, y-tHeight+5, x+TimeStepWidth-3, y-tHeight+5, Pen));
|
|
Lines.append(new Line(x, y-yNow, x+3, y-1, Pen));
|
|
Lines.append(new Line(x, y-yNow, x+3, y-tHeight+5, Pen));
|
|
Lines.append(new Line(x+TimeStepWidth-3, y-1, x+TimeStepWidth, y-yNow, Pen));
|
|
Lines.append(new Line(x+TimeStepWidth-3, y-tHeight+5, x+TimeStepWidth, y-yNow, Pen));
|
|
}
|
|
|
|
yLast = yNow;
|
|
x += TimeStepWidth;
|
|
pcx += 2;
|
|
}
|
|
|
|
}
|
|
else { // It is a bit vector !!!
|
|
|
|
z = g->cPointsX.getFirst()->count - z;
|
|
yNow = 1 + ((tHeight - 6) >> 1);
|
|
Lines.append(new Line(x, y-yNow, x+2, y-1, Pen));
|
|
Lines.append(new Line(x+2, y-tHeight+5, x, y-yNow, Pen));
|
|
for( ; z>0; z--) {
|
|
if(x+TimeStepWidth >= x2) break;
|
|
Lines.append(new Line(x+2, y-1, x+TimeStepWidth-2, y-1, Pen));
|
|
Lines.append(new Line(x+2, y-tHeight+5, x+TimeStepWidth-2, y-tHeight+5, Pen));
|
|
|
|
Texts.append(new Text(x+3, y, QString(pcx)));
|
|
|
|
x += TimeStepWidth;
|
|
pcx += strlen(pcx) + 1;
|
|
Lines.append(new Line(x-2, y-tHeight+5, x+2, y-1, Pen));
|
|
Lines.append(new Line(x+2, y-tHeight+5, x-2, y-1, Pen));
|
|
}
|
|
}
|
|
|
|
y -= tHeight;
|
|
} // of "for(Graphs...)"
|
|
|
|
|
|
funcEnd:
|
|
if(invisibleCount > 0) { // could all values be displayed ?
|
|
x = x2 - xAxis.numGraphs - 37;
|
|
if(x < MIN_SCROLLBAR_SIZE+2) { // not enough space for scrollbar ?
|
|
Lines.append(new Line(x2, 0, x2, -17, QPen(Qt::red,0)));
|
|
Lines.append(new Line(xAxis.numGraphs, -17, x2, -17, QPen(Qt::red,0)));
|
|
Lines.append(new Line(xAxis.numGraphs, 0, xAxis.numGraphs, -17, QPen(Qt::red,0)));
|
|
return 1;
|
|
}
|
|
|
|
y1 = 18; // extend the select area to the bottom
|
|
z = int(xAxis.limit_min + 0.5);
|
|
if(NumLeft < 0) NumLeft = 0;
|
|
y = NumAll - NumLeft - z;
|
|
|
|
// number of data (times)
|
|
zAxis.limit_max = double(firstGraph->cPointsX.getFirst()->count);
|
|
|
|
// position of scroll bar in pixel
|
|
yAxis.numGraphs = x * z / NumAll + xAxis.numGraphs + 19;
|
|
|
|
// length of scroll bar
|
|
zAxis.numGraphs = x * y / NumAll;
|
|
if(zAxis.numGraphs < MIN_SCROLLBAR_SIZE) {
|
|
yAxis.numGraphs -= (MIN_SCROLLBAR_SIZE - zAxis.numGraphs + 1)
|
|
* z / NumAll;
|
|
zAxis.numGraphs = MIN_SCROLLBAR_SIZE;
|
|
|
|
x = x2 - 19 - yAxis.numGraphs - zAxis.numGraphs;
|
|
if(x < 0) yAxis.numGraphs += x;
|
|
}
|
|
|
|
xAxis.limit_max = double(y);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// ------------------------------------------------------------
|
|
int TimingDiagram::scroll(int clickPos)
|
|
{
|
|
if(y1 <= 0) return 0; // no scroll bar ?
|
|
int tmp = int(xAxis.limit_min + 0.5);
|
|
|
|
int x = cx;
|
|
if(clickPos > (cx+x2-20)) { // scroll one value to the right ?
|
|
xAxis.limit_min++;
|
|
}
|
|
else {
|
|
x += xAxis.numGraphs + 20;
|
|
if(clickPos < x) { // scroll bar one value to the left ?
|
|
if(xAxis.limit_min <= 0.0) return 0;
|
|
xAxis.limit_min--;
|
|
}
|
|
else {
|
|
x = cx + yAxis.numGraphs;
|
|
if(clickPos < x) // scroll bar one page to the left ?
|
|
xAxis.limit_min -= xAxis.limit_max;
|
|
else {
|
|
x += zAxis.numGraphs;
|
|
if(clickPos > x) // one page to the right ?
|
|
xAxis.limit_min += xAxis.limit_max;
|
|
else
|
|
return 2; // click on position bar
|
|
}
|
|
}
|
|
}
|
|
|
|
calcDiagram();
|
|
if(tmp == int(xAxis.limit_min+0.5))
|
|
return 0; // did anything change ?
|
|
|
|
return 1;
|
|
}
|
|
|
|
// ------------------------------------------------------------
|
|
bool TimingDiagram::scrollTo(int initial, int dx, int)
|
|
{
|
|
int tmp = int(xAxis.limit_min + 0.5);
|
|
xAxis.limit_min = double(initial);
|
|
xAxis.limit_min += double(dx) / double(x2-xAxis.numGraphs-39) * zAxis.limit_max;
|
|
xAxis.limit_min = floor(xAxis.limit_min + 0.5);
|
|
|
|
calcDiagram();
|
|
if(tmp == int(xAxis.limit_min + 0.5))
|
|
return false; // did anything change ?
|
|
|
|
return true;
|
|
}
|
|
|
|
// ------------------------------------------------------------
|
|
Diagram* TimingDiagram::newOne()
|
|
{
|
|
return new TimingDiagram();
|
|
}
|
|
|
|
// ------------------------------------------------------------
|
|
Element* TimingDiagram::info(QString& Name, char* &BitmapFile, bool getNewOne)
|
|
{
|
|
Name = QObject::tr("Timing Diagram");
|
|
BitmapFile = (char *) "timing";
|
|
|
|
if(getNewOne) return new TimingDiagram();
|
|
return 0;
|
|
}
|