void CPlotter::SetDemodRanges(int FLowCmin, int FLowCmax, int FHiCmin, int FHiCmax, bool symetric) { m_FLowCmin=FLowCmin; m_FLowCmax=FLowCmax; m_FHiCmin=FHiCmin; m_FHiCmax=FHiCmax; m_symetric=symetric; ClampDemodParameters(); DrawOverlay(); }
////////////////////////////////////////////////////////////////////// // Called when a mouse wheel is turned ////////////////////////////////////////////////////////////////////// void CPlotter::wheelEvent(QWheelEvent * event) { QPoint pt = event->pos(); int numDegrees = event->delta() / 8; int numSteps = numDegrees / 15; /** FIXME: Only used for direction **/ /** FIXME: zooming could use some optimisation **/ if (m_CursorCaptured == YAXIS) { // Vertical zoom. Wheel down: zoom out, wheel up: zoom in // During zoom we try to keep the point (dB or kHz) under the cursor fixed float zoom_fac = event->delta() < 0 ? 1.1 : 0.9; float ratio = (float)pt.y() / (float)m_OverlayPixmap.height(); float db_range = (float)(m_MaxdB - m_MindB); float y_range = (float)m_OverlayPixmap.height(); float db_per_pix = db_range / y_range; float fixed_db = m_MaxdB - pt.y() * db_per_pix; db_range *= zoom_fac; m_MaxdB = fixed_db + ratio*db_range; m_MindB = m_MaxdB - db_range; } else if (m_CursorCaptured == XAXIS) { // calculate new range shown on FFT float zoom_factor = event->delta() < 0 ? 1.1 : 0.9; float new_range = (float)(m_Span) * zoom_factor; // Frequency where event occured is kept fixed under mouse float ratio = (float)pt.x() / (float)m_OverlayPixmap.width(); float fixed_hz = FreqfromX(pt.x()); float f_max = fixed_hz + (1.0 - ratio) * new_range; float f_min = f_max - new_range; qint64 fc = (qint64)(f_min + (f_max - f_min) / 2.0); SetFftCenterFreq(fc-m_CenterFreq); SetSpanFreq((quint32)new_range); zoom_factor = (float)m_SampleFreq/(float)m_Span; qDebug() << QString("Spectrum zoom: %1x").arg(zoom_factor, 0, 'f', 1); } else if (event->modifiers() & Qt::ControlModifier) { // filter width m_DemodLowCutFreq -= numSteps*m_ClickResolution; m_DemodHiCutFreq += numSteps*m_ClickResolution; ClampDemodParameters(); emit NewFilterFreq(m_DemodLowCutFreq, m_DemodHiCutFreq); } else if (event->modifiers() & Qt::ShiftModifier) { // filter shift m_DemodLowCutFreq += numSteps*m_ClickResolution; m_DemodHiCutFreq += numSteps*m_ClickResolution; ClampDemodParameters(); emit NewFilterFreq(m_DemodLowCutFreq, m_DemodHiCutFreq); } else { // inc/dec demod frequency m_DemodCenterFreq += (numSteps*m_ClickResolution); m_DemodCenterFreq = RoundFreq(m_DemodCenterFreq, m_ClickResolution ); emit NewDemodFreq(m_DemodCenterFreq, m_DemodCenterFreq-m_CenterFreq); } if (m_Running) m_DrawOverlay = true; else DrawOverlay(); }
////////////////////////////////////////////////////////////////////// // Called when mouse moves and does different things depending //on the state of the mouse buttons for dragging the demod bar or // filter edges. ////////////////////////////////////////////////////////////////////// void CPlotter::mouseMoveEvent(QMouseEvent* event) { QPoint pt = event->pos(); /* mouse enter / mouse leave events */ if (m_OverlayPixmap.rect().contains(pt)) { //is in Overlay bitmap region if (event->buttons() == Qt::NoButton) { //if no mouse button monitor grab regions and change cursor icon if (IsPointCloseTo(pt.x(), m_DemodFreqX, m_CursorCaptureDelta)) { //in move demod box center frequency region if (CENTER != m_CursorCaptured) setCursor(QCursor(Qt::CrossCursor)); m_CursorCaptured = CENTER; } else if (IsPointCloseTo(pt.x(), m_DemodHiCutFreqX, m_CursorCaptureDelta)) { //in move demod hicut region if (RIGHT != m_CursorCaptured) setCursor(QCursor(Qt::SizeHorCursor)); m_CursorCaptured = RIGHT; } else if (IsPointCloseTo(pt.x(), m_DemodLowCutFreqX, m_CursorCaptureDelta)) { //in move demod lowcut region if (LEFT != m_CursorCaptured) setCursor(QCursor(Qt::SizeHorCursor)); m_CursorCaptured = LEFT; } else if (IsPointCloseTo(pt.x(), m_YAxisWidth/2, m_YAxisWidth/2)) { if (YAXIS != m_CursorCaptured) setCursor(QCursor(Qt::OpenHandCursor)); m_CursorCaptured = YAXIS; } else if (IsPointCloseTo(pt.y(), m_XAxisYCenter, m_CursorCaptureDelta+5)) { if (XAXIS != m_CursorCaptured) setCursor(QCursor(Qt::OpenHandCursor)); m_CursorCaptured = XAXIS; } else { //if not near any grab boundaries if (NONE != m_CursorCaptured) { setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NONE; } } m_GrabPosition = 0; } } else { //not in Overlay region if (event->buttons() == Qt::NoButton) { if (NONE != m_CursorCaptured) setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NONE; m_GrabPosition = 0; } } // process mouse moves while in cursor capture modes if (YAXIS == m_CursorCaptured) { if (event->buttons() & Qt::LeftButton) { setCursor(QCursor(Qt::ClosedHandCursor)); // move Y scale up/down int delta_px = m_Yzero - pt.y(); int delta_db = delta_px * abs(m_MindB-m_MaxdB)/m_OverlayPixmap.height(); m_MindB -= delta_db; m_MaxdB -= delta_db; if (m_Running) m_DrawOverlay = true; else DrawOverlay(); m_Yzero = pt.y(); } } else if (XAXIS == m_CursorCaptured) { if (event->buttons() & (Qt::LeftButton | Qt::MiddleButton)) { setCursor(QCursor(Qt::ClosedHandCursor)); // pan viewable range or move center frequency int delta_px = m_Xzero - pt.x(); qint64 delta_hz = delta_px * m_Span / m_OverlayPixmap.width(); if (event->buttons() & Qt::MiddleButton) { m_CenterFreq += delta_hz; m_DemodCenterFreq += delta_hz; emit NewCenterFreq(m_CenterFreq); } else { m_FftCenter += delta_hz; } if (m_Running) m_DrawOverlay = true; else DrawOverlay(); m_Xzero = pt.x(); } } else if (LEFT == m_CursorCaptured) { // moving in demod lowcut region if (event->buttons() & (Qt::LeftButton|Qt::RightButton)) { //moving in demod lowcut region with left button held if (m_GrabPosition != 0) { m_DemodLowCutFreq = FreqfromX(pt.x()-m_GrabPosition ) - m_DemodCenterFreq; m_DemodLowCutFreq = RoundFreq(m_DemodLowCutFreq, m_FilterClickResolution); if (m_symetric && (event->buttons() & Qt::LeftButton)) // symetric adjustment { m_DemodHiCutFreq = -m_DemodLowCutFreq; } ClampDemodParameters(); emit NewFilterFreq(m_DemodLowCutFreq, m_DemodHiCutFreq); if (m_Running) m_DrawOverlay = true; // schedule update of overlay during draw() else DrawOverlay(); // not running so update oiverlay now } else { //save initial grab postion from m_DemodFreqX m_GrabPosition = pt.x()-m_DemodLowCutFreqX; } } else if (event->buttons() & ~Qt::NoButton) { setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NONE; } } else if (RIGHT == m_CursorCaptured) { // moving in demod highcut region if (event->buttons() & (Qt::LeftButton|Qt::RightButton)) { // moving in demod highcut region with right button held if (m_GrabPosition != 0) { m_DemodHiCutFreq = FreqfromX( pt.x()-m_GrabPosition ) - m_DemodCenterFreq; m_DemodHiCutFreq = RoundFreq(m_DemodHiCutFreq, m_FilterClickResolution); if (m_symetric && (event->buttons() & Qt::LeftButton)) // symetric adjustment { m_DemodLowCutFreq = -m_DemodHiCutFreq; } ClampDemodParameters(); emit NewFilterFreq(m_DemodLowCutFreq, m_DemodHiCutFreq); if (m_Running) m_DrawOverlay = true; // schedule update of overlay during draw() else DrawOverlay(); // not running so update oiverlay now } else { //save initial grab postion from m_DemodFreqX m_GrabPosition = pt.x()-m_DemodHiCutFreqX; } } else if (event->buttons() & ~Qt::NoButton) { setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NONE; } } else if (CENTER == m_CursorCaptured) { // moving inbetween demod lowcut and highcut region if (event->buttons() & Qt::LeftButton) { // moving inbetween demod lowcut and highcut region with left button held if (m_GrabPosition != 0) { m_DemodCenterFreq = RoundFreq(FreqfromX(pt.x()-m_GrabPosition), m_ClickResolution ); emit NewDemodFreq(m_DemodCenterFreq, m_DemodCenterFreq-m_CenterFreq); if (m_Running) m_DrawOverlay = true; // schedule update of overlay during draw() else DrawOverlay(); // not running so update oiverlay now } else { //save initial grab postion from m_DemodFreqX m_GrabPosition = pt.x()-m_DemodFreqX; } } else if (event->buttons() & ~Qt::NoButton) { setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NONE; } } else //if cursor not captured { m_GrabPosition = 0; } if (!this->rect().contains(pt)) { if(NONE != m_CursorCaptured) setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NONE; } }
////////////////////////////////////////////////////////////////////// // Called to draw an overlay bitmap containing grid and text that // does not need to be recreated every fft data update. ////////////////////////////////////////////////////////////////////// void CPlotter::DrawOverlay() { if (m_OverlayPixmap.isNull()) return; int w = m_OverlayPixmap.width(); int h = m_OverlayPixmap.height(); int x,y; float pixperdiv; QRect rect; QPainter painter(&m_OverlayPixmap); painter.initFrom(this); //m_OverlayPixmap.fill(Qt::black); //fill background with gradient QLinearGradient gradient(0, 0, 0 ,h); gradient.setColorAt(0, QColor(0x20,0x20,0x20,0xFF)); gradient.setColorAt(1, QColor(0x4F,0x4F,0x4F,0xFF)); painter.setBrush(gradient); painter.drawRect(0, 0, w, h); //Draw demod filter box ClampDemodParameters(); m_DemodFreqX = XfromFreq(m_DemodCenterFreq); m_DemodLowCutFreqX = XfromFreq(m_DemodCenterFreq + m_DemodLowCutFreq); m_DemodHiCutFreqX = XfromFreq(m_DemodCenterFreq + m_DemodHiCutFreq); int dw = m_DemodHiCutFreqX - m_DemodLowCutFreqX; painter.setBrush(Qt::SolidPattern); painter.setOpacity(0.3); painter.fillRect(m_DemodLowCutFreqX, 0, dw, h, Qt::gray); painter.setOpacity(1.0); // edge of filter -> we don't need different color //painter.setPen(QPen(Qt::gray, 1, Qt::SolidLine)); //painter.drawLine(m_DemodLowCutFreqX, 0, m_DemodLowCutFreqX, h); //painter.drawLine(m_DemodHiCutFreqX, 0, m_DemodHiCutFreqX, h); painter.setPen(QPen(QColor(0xFF,0x71,0x71,0xFF), 1, Qt::SolidLine)); painter.drawLine(m_DemodFreqX, 0, m_DemodFreqX, h); //create Font to use for scales QFont Font("Arial"); Font.setPointSize(9); QFontMetrics metrics(Font); y = h/VERT_DIVS; //if (y < metrics.height()) // Font.setPixelSize(y); Font.setWeight(QFont::Normal); painter.setFont(Font); //draw vertical grids pixperdiv = (float)w / (float)HORZ_DIVS; y = h - h/VERT_DIVS/2; for (int i = 1; i < HORZ_DIVS; i++) { x = (int)((float)i*pixperdiv); if (i == HORZ_DIVS/2) // center line painter.setPen(QPen(QColor(0x78,0x82,0x96,0xFF), 1, Qt::SolidLine)); else painter.setPen(QPen(QColor(0xF0,0xF0,0xF0,0x30), 1, Qt::DotLine)); painter.drawLine(x, 0, x , y); //painter.drawLine(x, h-5, x , h); } //draw frequency values MakeFrequencyStrs(); painter.setPen(QColor(0xD8,0xBA,0xA1,0xFF)); y = h - (h/VERT_DIVS); for (int i = 1; i < HORZ_DIVS; i++) { //if ((i==0) || (i==HORZ_DIVS)) //{ //left justify the leftmost text //x = (int)( (float)i*pixperdiv); //rect.setRect(x ,y, (int)pixperdiv, h/VERT_DIVS); //painter.drawText(rect, Qt::AlignLeft|Qt::AlignVCenter, m_HDivText[i]); //} //else if(HORZ_DIVS == i) //{ //right justify the rightmost text // x = (int)( (float)i*pixperdiv - pixperdiv); // rect.setRect(x ,y, (int)pixperdiv, h/VERT_DIVS); // painter.drawText(rect, Qt::AlignRight|Qt::AlignVCenter, m_HDivText[i]); //} //else //{ //center justify the rest of the text x = (int)((float)i*pixperdiv - pixperdiv/2); rect.setRect(x, y, (int)pixperdiv, h/VERT_DIVS); painter.drawText(rect, Qt::AlignHCenter|Qt::AlignBottom, m_HDivText[i]); //} } //draw horizontal grids pixperdiv = (float)h / (float)VERT_DIVS; painter.setPen(QPen(QColor(0xF0,0xF0,0xF0,0x30), 1,Qt::DotLine)); for (int i = 1; i < VERT_DIVS; i++) { y = (int)((float) i*pixperdiv); painter.drawLine(5*metrics.width("0",-1), y, w, y); } //draw amplitude values painter.setPen(QColor(0xD8,0xBA,0xA1,0xFF)); //Font.setWeight(QFont::Light); painter.setFont(Font); int dB = m_MaxdB; for (int i = 1; i < VERT_DIVS; i++) { dB -= m_dBStepSize; /* move to end if want to include maxdb */ y = (int)((float)i*pixperdiv); rect.setRect(0, y-metrics.height()/2, metrics.width("-120 "), metrics.height()); painter.drawText(rect, Qt::AlignRight|Qt::AlignVCenter, QString::number(dB)); } m_MindB = m_MaxdB - (VERT_DIVS)*m_dBStepSize; if (!m_Running) { //if not running so is no data updates to draw to screen //copy into 2Dbitmap the overlay bitmap. m_2DPixmap = m_OverlayPixmap.copy(0,0,w,h); //trigger a new paintEvent update(); } }
////////////////////////////////////////////////////////////////////// // Called to draw an overlay bitmap containing grid and text that // does not need to be recreated every fft data update. ////////////////////////////////////////////////////////////////////// void CPlotter::DrawOverlay() { if (m_OverlayPixmap.isNull()) return; int w = m_OverlayPixmap.width(); int h = m_OverlayPixmap.height(); int x,y; float pixperdiv; QRect rect; QPainter painter(&m_OverlayPixmap); painter.initFrom(this); // horizontal grids (size and grid calcs could be moved to resize) m_VerDivs = h/m_VdivDelta+1; m_HorDivs = w/m_HdivDelta; if (m_HorDivs % 2) m_HorDivs++; // we want an odd number of divs so that we have a center line //m_OverlayPixmap.fill(Qt::black); //fill background with gradient QLinearGradient gradient(0, 0, 0 ,h); gradient.setColorAt(0, QColor(0x20,0x20,0x20,0xFF)); gradient.setColorAt(1, QColor(0x4F,0x4F,0x4F,0xFF)); painter.setBrush(gradient); painter.drawRect(0, 0, w, h); //Draw demod filter box if (m_FilterBoxEnabled) { ClampDemodParameters(); m_DemodFreqX = XfromFreq(m_DemodCenterFreq); m_DemodLowCutFreqX = XfromFreq(m_DemodCenterFreq + m_DemodLowCutFreq); m_DemodHiCutFreqX = XfromFreq(m_DemodCenterFreq + m_DemodHiCutFreq); int dw = m_DemodHiCutFreqX - m_DemodLowCutFreqX; painter.setBrush(Qt::SolidPattern); painter.setOpacity(0.3); painter.fillRect(m_DemodLowCutFreqX, 0, dw, h, Qt::gray); painter.setOpacity(1.0); painter.setPen(QPen(QColor(0xFF,0x71,0x71,0xFF), 1, Qt::SolidLine)); painter.drawLine(m_DemodFreqX, 0, m_DemodFreqX, h); } //create Font to use for scales QFont Font("Arial"); Font.setPointSize(m_FontSize); QFontMetrics metrics(Font); Font.setWeight(QFont::Normal); painter.setFont(Font); // draw vertical grids pixperdiv = (float)w / (float)m_HorDivs; y = h - h/m_VerDivs/2; for (int i = 1; i < m_HorDivs; i++) { x = (int)((float)i*pixperdiv); if ((i == m_HorDivs/2) && m_CenterLineEnabled) // center line painter.setPen(QPen(QColor(0x78,0x82,0x96,0xFF), 1, Qt::SolidLine)); else painter.setPen(QPen(QColor(0xF0,0xF0,0xF0,0x30), 1, Qt::DotLine)); painter.drawLine(x, 0, x , y); } //draw frequency values MakeFrequencyStrs(); painter.setPen(QColor(0xD8,0xBA,0xA1,0xFF)); y = h - (h/m_VerDivs); for (int i = 1; i < m_HorDivs; i++) { x = (int)((float)i*pixperdiv - pixperdiv/2); rect.setRect(x, y, (int)pixperdiv, h/m_VerDivs); painter.drawText(rect, Qt::AlignHCenter|Qt::AlignBottom, m_HDivText[i]); } m_dBStepSize = abs(m_MaxdB-m_MindB)/(double)m_VerDivs; pixperdiv = (float)h / (float)m_VerDivs; painter.setPen(QPen(QColor(0xF0,0xF0,0xF0,0x30), 1,Qt::DotLine)); for (int i = 1; i < m_VerDivs; i++) { y = (int)((float) i*pixperdiv); painter.drawLine(5*metrics.width("0",-1), y, w, y); } //draw amplitude values painter.setPen(QColor(0xD8,0xBA,0xA1,0xFF)); //Font.setWeight(QFont::Light); painter.setFont(Font); int dB = m_MaxdB; m_YAxisWidth = metrics.width("-120 "); for (int i = 1; i < m_VerDivs; i++) { dB -= m_dBStepSize; /* move to end if want to include maxdb */ y = (int)((float)i*pixperdiv); rect.setRect(0, y-metrics.height()/2, m_YAxisWidth, metrics.height()); painter.drawText(rect, Qt::AlignRight|Qt::AlignVCenter, QString::number(dB)); } if (!m_Running) { //if not running so is no data updates to draw to screen //copy into 2Dbitmap the overlay bitmap. m_2DPixmap = m_OverlayPixmap.copy(0,0,w,h); //trigger a new paintEvent update(); } }