Chris@195: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@195: 
Chris@195: /*
Chris@195:     Sonic Visualiser
Chris@195:     An audio file viewer and annotation editor.
Chris@195:     Centre for Digital Music, Queen Mary, University of London.
Chris@195:     This file copyright 2006-2007 Chris Cannam and QMUL.
Chris@195:    
Chris@195:     This program is free software; you can redistribute it and/or
Chris@195:     modify it under the terms of the GNU General Public License as
Chris@195:     published by the Free Software Foundation; either version 2 of the
Chris@195:     License, or (at your option) any later version.  See the file
Chris@195:     COPYING included with this distribution for more information.
Chris@195: */
Chris@195: 
Chris@195: #include "PaintAssistant.h"
Chris@195: 
Chris@1078: #include "LayerGeometryProvider.h"
Chris@1078: 
Chris@195: #include "base/AudioLevel.h"
Chris@1147: #include "base/Strings.h"
Chris@195: 
Chris@195: #include <QPaintDevice>
Chris@195: #include <QPainter>
Chris@195: 
Chris@198: #include <iostream>
Chris@230: #include <cmath>
Chris@198: 
Chris@195: void
Chris@195: PaintAssistant::paintVerticalLevelScale(QPainter &paint, QRect rect,
Chris@905: 					double minVal, double maxVal,
Chris@220:                                         Scale scale, int &mult,
Chris@220:                                         std::vector<int> *vy)
Chris@195: {
Chris@905:     static double meterdbs[] = { -40, -30, -20, -15, -10,
Chris@195:                                 -5, -3, -2, -1, -0.5, 0 };
Chris@195: 
Chris@195:     int h = rect.height(), w = rect.width();
Chris@195:     int textHeight = paint.fontMetrics().height();
Chris@195:     int toff = -textHeight/2 + paint.fontMetrics().ascent() + 1;
Chris@195: 
Chris@195:     int lastLabelledY = -1;
Chris@195: 
Chris@195:     int n = 10;
Chris@195:     if (vy) vy->clear();
Chris@195: 
Chris@905:     double step = 0;
Chris@220:     mult = 1;
Chris@220:     if (scale == LinearScale) {
Chris@220:         step = (maxVal - minVal) / n;
Chris@220:         int round = 0, limit = 10000000;
Chris@220:         do {
Chris@220:             round = int(minVal + step * mult);
Chris@220:             mult *= 10;
Chris@220:         } while (!round && mult < limit);
Chris@220:         if (round) {
Chris@220:             mult /= 10;
Chris@682: //            cerr << "\n\nstep goes from " << step;
Chris@905:             step = double(round) / mult;
Chris@905:             n = int(lrint((maxVal - minVal) / step));
Chris@220:             if (mult > 1) {
Chris@220:                 mult /= 10;
Chris@220:             }
Chris@682: //            cerr << " to " << step << " (n = " << n << ")" << endl;
Chris@220:         }
Chris@220:     }
Chris@220: 
Chris@195:     for (int i = 0; i <= n; ++i) {
Chris@195:         
Chris@905:         double val = 0.0, nval = 0.0;
Chris@195:         QString text = "";
Chris@195: 
Chris@195:         switch (scale) {
Chris@195:                 
Chris@195:         case LinearScale:
Chris@220:             val = (minVal + (i * step));
Chris@220:             text = QString("%1").arg(mult * val);
Chris@195:             break;
Chris@195:             
Chris@195:         case MeterScale: // ... min, max
Chris@195:             val = AudioLevel::dB_to_multiplier(meterdbs[i]);
Chris@195:             text = QString("%1").arg(meterdbs[i]);
Chris@195:             if (i == n) text = "0dB";
Chris@195:             if (i == 0) {
Chris@1147:                 text = Strings::minus_infinity;
Chris@195:                 val = 0.0;
Chris@195:             }
Chris@195:             break;
Chris@195: 
Chris@195:         case dBScale: // ... min, max
Chris@195:             val = AudioLevel::dB_to_multiplier(-(10*n) + i * 10);
Chris@195:             text = QString("%1").arg(-(10*n) + i * 10);
Chris@195:             if (i == n) text = "0dB";
Chris@195:             if (i == 0) {
Chris@1147:                 text = Strings::minus_infinity;
Chris@195:                 val = 0.0;
Chris@195:             }
Chris@195:             break;
Chris@195:         }
Chris@195: 
Chris@195:         if (val < minVal || val > maxVal) continue;
Chris@195: 
Chris@198:         int y = getYForValue(scale, val, minVal, maxVal, rect.y(), h);
Chris@195:             
Chris@195:         int ny = y;
Chris@195:         if (nval != 0.0) {
Chris@198:             ny = getYForValue(scale, nval, minVal, maxVal, rect.y(), h);
Chris@195:         }
Chris@195: 
Chris@587: //        SVDEBUG << "PaintAssistant::paintVerticalLevelScale: val = "
Chris@585: //                  << val << ", y = " << y << ", h = " << h << endl;
Chris@198: 
Chris@195:         bool spaceForLabel = (i == 0 ||
Chris@195:                               abs(y - lastLabelledY) >= textHeight - 1);
Chris@195:         
Chris@195:         if (spaceForLabel) {
Chris@195:             
Chris@195:             int tx = 3;
Chris@220: //            if (scale != LinearScale) {
Chris@220:             if (paint.fontMetrics().width(text) < w - 10) {
Chris@195:                 tx = w - 10 - paint.fontMetrics().width(text);
Chris@195:             }
Chris@195:             
Chris@195:             int ty = y;
Chris@198: 
Chris@195:             if (ty < paint.fontMetrics().ascent()) {
Chris@195:                 ty = paint.fontMetrics().ascent();
Chris@198: //            } else if (ty > rect.y() + h - paint.fontMetrics().descent()) {
Chris@198: //                ty = rect.y() + h - paint.fontMetrics().descent();
Chris@195:             } else {
Chris@195:                 ty += toff;
Chris@195:             }
Chris@198: 
Chris@195:             paint.drawText(tx, ty, text);
Chris@195:             
Chris@195:             lastLabelledY = ty - toff;
Chris@195:             /*
Chris@195:             if (ny != y) {
Chris@195:                 ty = ny;
Chris@195:                 if (ty < paint.fontMetrics().ascent()) {
Chris@195:                     ty = paint.fontMetrics().ascent();
Chris@195:                 } else if (ty > h - paint.fontMetrics().descent()) {
Chris@195:                     ty = h - paint.fontMetrics().descent();
Chris@195:                 } else {
Chris@195:                     ty += toff;
Chris@195:                 }
Chris@195:                 paint.drawText(tx, ty, text);
Chris@195:             }
Chris@195:             */
Chris@195:             paint.drawLine(w - 7, y, w, y);
Chris@195:             if (vy) vy->push_back(y);
Chris@195: 
Chris@195:             if (ny != y) {
Chris@195:                 paint.drawLine(w - 7, ny, w, ny);
Chris@195:                 if (vy) vy->push_back(ny);
Chris@195:             }
Chris@195:             
Chris@195:         } else {
Chris@195:             
Chris@195:             paint.drawLine(w - 4, y, w, y);
Chris@195:             if (vy) vy->push_back(y);
Chris@195: 
Chris@195:             if (ny != y) {
Chris@195:                 paint.drawLine(w - 4, ny, w, ny);
Chris@195:                 if (vy) vy->push_back(ny);
Chris@195:             }
Chris@195:         }
Chris@195:     }
Chris@195: }
Chris@195: 
Chris@195: static int
Chris@905: dBscale(double sample, int m, double maxVal, double minVal) 
Chris@195: {
Chris@195:     if (sample < 0.0) return dBscale(-sample, m, maxVal, minVal);
Chris@905:     double dB = AudioLevel::multiplier_to_dB(sample);
Chris@905:     double mindB = AudioLevel::multiplier_to_dB(minVal);
Chris@905:     double maxdB = AudioLevel::multiplier_to_dB(maxVal);
Chris@195:     if (dB < mindB) return 0;
Chris@195:     if (dB > 0.0) return m;
Chris@195:     return int(((dB - mindB) * m) / (maxdB - mindB) + 0.1);
Chris@195: }
Chris@195: 
Chris@195: int
Chris@905: PaintAssistant::getYForValue(Scale scale, double value, 
Chris@905:                              double minVal, double maxVal,
Chris@195:                              int minY, int height)
Chris@195: {
Chris@195:     int vy = 0;
Chris@195: 
Chris@195: //    int m = height/2;
Chris@195: //    int my = minY + m;
Chris@195: 
Chris@195:     switch (scale) {
Chris@195: 
Chris@195:     case LinearScale:
Chris@195: //        vy = my - int(m * value);
Chris@195:         vy = minY + height - int(((value - minVal) / (maxVal - minVal)) * height);
Chris@195:         break;
Chris@195: 
Chris@195:     case MeterScale:
Chris@195: //        vy = my - AudioLevel::multiplier_to_preview(value, m);
Chris@195:         vy = minY + height - AudioLevel::multiplier_to_preview
Chris@195:             ((value - minVal) / (maxVal - minVal), height);
Chris@195:         break;
Chris@195: 
Chris@195:     case dBScale:
Chris@195:         vy = minY + height - dBscale(value, height, maxVal, minVal);
Chris@195:         break;
Chris@195:     }
Chris@195: 
Chris@195:     return vy;
Chris@195: }
Chris@1078: 
Chris@1078: void
Chris@1078: PaintAssistant::drawVisibleText(const LayerGeometryProvider *v,
Chris@1078:                                 QPainter &paint, int x, int y,
Chris@1078:                                 QString text, TextStyle style)
Chris@1078: {
Chris@1078:     if (style == OutlinedText || style == OutlinedItalicText) {
Chris@1078: 
Chris@1078:         paint.save();
Chris@1078: 
Chris@1078:         if (style == OutlinedItalicText) {
Chris@1078:             QFont f(paint.font());
Chris@1078:             f.setItalic(true);
Chris@1078:             paint.setFont(f);
Chris@1078:         }
Chris@1078: 
Chris@1078:         QColor penColour, surroundColour, boxColour;
Chris@1078: 
Chris@1078:         penColour = v->getForeground();
Chris@1078:         surroundColour = v->getBackground();
Chris@1078:         boxColour = surroundColour;
Chris@1078:         boxColour.setAlpha(127);
Chris@1078: 
Chris@1078:         paint.setPen(Qt::NoPen);
Chris@1078:         paint.setBrush(boxColour);
Chris@1078:         
Chris@1078:         QRect r = paint.fontMetrics().boundingRect(text);
Chris@1078:         r.translate(QPoint(x, y));
Chris@1078: //        cerr << "drawVisibleText: r = " << r.x() << "," <<r.y() << " " << r.width() << "x" << r.height() << endl;
Chris@1078:         paint.drawRect(r);
Chris@1078:         paint.setBrush(Qt::NoBrush);
Chris@1078: 
Chris@1078: 	paint.setPen(surroundColour);
Chris@1078: 
Chris@1078: 	for (int dx = -1; dx <= 1; ++dx) {
Chris@1078: 	    for (int dy = -1; dy <= 1; ++dy) {
Chris@1078: 		if (!(dx || dy)) continue;
Chris@1078: 		paint.drawText(x + dx, y + dy, text);
Chris@1078: 	    }
Chris@1078: 	}
Chris@1078: 
Chris@1078: 	paint.setPen(penColour);
Chris@1078: 
Chris@1078: 	paint.drawText(x, y, text);
Chris@1078: 
Chris@1078:         paint.restore();
Chris@1078: 
Chris@1078:     } else {
Chris@1078: 
Chris@1078:         std::cerr << "ERROR: PaintAssistant::drawVisibleText: Boxed style not yet implemented!" << std::endl;
Chris@1078:     }
Chris@1078: }