diff --git a/AUTHORS b/AUTHORS index 4d4cf802..94d8fe22 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1 +1 @@ -Volker Fischer +See Github diff --git a/ChangeLog b/ChangeLog index ad11f9d6..a16872cf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,8 @@ - audio recording for the server, coded by pljones +- SVG server history graph, coded by pljones + diff --git a/Jamulus.pro b/Jamulus.pro index dbd4b49b..c082665f 100755 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -173,7 +173,8 @@ HEADERS += src/audiomixerboard.h \ src/analyzerconsole.h \ src/recorder/jamrecorder.h \ src/recorder/creaperproject.h \ - src/recorder/cwavestream.h + src/recorder/cwavestream.h \ + src/historygraph.h HEADERS_OPUS = libs/opus/include/opus.h \ libs/opus/include/opus_multistream.h \ @@ -270,7 +271,8 @@ SOURCES += src/audiomixerboard.cpp \ src/analyzerconsole.cpp \ src/recorder/jamrecorder.cpp \ src/recorder/creaperproject.cpp \ - src/recorder/cwavestream.cpp + src/recorder/cwavestream.cpp \ + src/historygraph.cpp SOURCES_OPUS = libs/opus/src/opus.c \ libs/opus/src/opus_decoder.c \ diff --git a/README b/README index 9ace63d3..9d88bf78 100755 --- a/README +++ b/README @@ -63,4 +63,4 @@ source: - Some pixmaps are from the Open Clip Art Library (OCAL): http://openclipart.org -- Audio recording for the server, coded by pljones: http://jamulus.drealm.info +- Audio recording for the server and SVG history graph, coded by pljones: http://jamulus.drealm.info diff --git a/src/historygraph.cpp b/src/historygraph.cpp new file mode 100644 index 00000000..96f00aad --- /dev/null +++ b/src/historygraph.cpp @@ -0,0 +1,501 @@ +/******************************************************************************\ + * Copyright (c) 2004-2019 + * + * Author(s): + * Volker Fischer + * Peter L Jones + * + ****************************************************************************** + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +\******************************************************************************/ + +#include "historygraph.h" + + +/* Abstract class *************************************************************/ +AHistoryGraph::AHistoryGraph() : + sFileName ( "" ), + bDoHistory ( false ), + vHistoryDataFifo ( NUM_ITEMS_HISTORY ), + iNumTicksX ( 0 ), // number of days in history + + BackgroundColor ( "white" ), // background + FrameColor ( "black" ), // frame + GridColor ( "gray" ), // grid + TextColor ( "black" ), // text + MarkerNewColor ( "darkCyan" ), // marker for new connection + MarkerNewLocalColor ( "blue" ), // marker for new local connection + MarkerStopColor ( "red" ), // marker for server stop + + canvasRectX ( 0 ), + canvasRectY ( 0 ), + canvasRectWidth ( 640 ), + canvasRectHeight ( 450 ), + + iGridFrameOffset ( 10 ), + iGridWidthWeekend ( 3 ), // should be an odd value + iXAxisTextHeight ( 22 ), + gridFrameX ( canvasRectX + iGridFrameOffset ), + gridFrameY ( canvasRectY + iGridFrameOffset ), + gridFrameWidth ( canvasRectWidth - 2 * iGridFrameOffset ), + gridFrameHeight ( canvasRectHeight - 2 * iGridFrameOffset - iXAxisTextHeight ), + gridFrameRight ( gridFrameX + gridFrameWidth - 1 ), + gridFrameBottom ( gridFrameY + gridFrameHeight - 1 ), + + axisFontFamily ( "Arial" ), + axisFontWeight ( "100" ), + axisFontSize ( 12 ), + + iYAxisStart ( 0 ), + iYAxisEnd ( 24 ), + iNumTicksY ( 5 ), + + iTextOffsetToGrid ( 3 ), + iTextOffsetX ( 18 ), + + iMarkerSizeNewCon ( 11 ), + iMarkerSizeServSt ( 8 ) +{ +} + +void AHistoryGraph::Start ( const QString& sNewFileName ) +{ + QTextStream& tsConsoleStream = *( ( new ConsoleWriterFactory() )->get() ); + tsConsoleStream << QString ( "AHistoryGraph::Start ( %1 )" ).arg ( sNewFileName ) << endl; // on console + + if ( !sNewFileName.isEmpty() ) + { + // save file name + sFileName = sNewFileName; + + // set enable flag + bDoHistory = true; + + // enable timer (update once a day) + TimerDailyUpdate.start ( 3600000 * 24 ); + + // initial update (empty graph) + Update(); + } +} + +void AHistoryGraph::Add ( const QDateTime& newDateTime, const EHistoryItemType curType ) +{ + if ( bDoHistory ) + { + // create and add new element in FIFO + SHistoryData curHistoryData; + curHistoryData.DateTime = newDateTime; + curHistoryData.Type = curType; + + vHistoryDataFifo.Add ( curHistoryData ); + } +} + +void AHistoryGraph::Add ( const QDateTime& newDateTime, const QHostAddress ClientInetAddr ) +{ + if ( bDoHistory ) + { + // add element to history, distinguish between a local connection + // and a remote connection + if ( ( ClientInetAddr == QHostAddress ( "127.0.0.1" ) ) || + ( ClientInetAddr.toString().left ( 7 ).compare ( "192.168" ) == 0 ) ) + { + // local connection + Add ( newDateTime, HIT_LOCAL_CONNECTION ); + } + else + { + // remote connection + Add ( newDateTime, HIT_REMOTE_CONNECTION ); + } + } +} + +void AHistoryGraph::Update ( ) +{ + if ( bDoHistory ) + { + int i; + + // store current date for reference + curDate = QDate::currentDate(); + + // get oldest date in history + QDate oldestDate = curDate.addDays ( 1 ); // one day in the future + const int iNumItemsForHistory = vHistoryDataFifo.Size(); + + for ( i = 0; i < iNumItemsForHistory; i++ ) + { + // only use valid dates + if ( vHistoryDataFifo[i].DateTime.date().isValid() ) + { + if ( vHistoryDataFifo[i].DateTime.date() < oldestDate ) + { + oldestDate = vHistoryDataFifo[i].DateTime.date(); + } + } + } + const int iNumDaysInHistory = -curDate.daysTo ( oldestDate ) + 1; + + // draw frame of the graph + DrawFrame ( iNumDaysInHistory ); + + // add markers + for ( i = 0; i < iNumItemsForHistory; i++ ) + { + // only use valid dates + if ( vHistoryDataFifo[i].DateTime.date().isValid() ) + { + AddMarker ( vHistoryDataFifo[i] ); + } + } + + // save graph as picture in file + Save ( sFileName ); + } +} + +void AHistoryGraph::DrawFrame ( const int iNewNumTicksX ) +{ + int i; + + // store number of x-axis ticks (number of days we want to draw + // the history for + iNumTicksX = iNewNumTicksX; + + + // Create actual plot region (grid frame) ---------------------------------- + rect( gridFrameX, gridFrameY, gridFrameWidth, gridFrameHeight ); + + // calculate step for x-axis ticks so that we get the desired number of + // ticks -> 5 ticks + +// TODO the following equation does not work as expected but results are acceptable + + // we want to have "floor ( iNumTicksX / 5 )" which is the same as + // "iNumTicksX / 5" since "iNumTicksX" is an integer variable + const int iXAxisTickStep = iNumTicksX / 5 + 1; + + // grid (ticks) for x-axis + dayXSpace = static_cast ( gridFrameWidth ) / ( iNumTicksX + 1 ); + + for ( i = 0; i < static_cast ( iNumTicksX ); i++ ) + { + int iBottomExtraTickLen = 0; + const int iCurX = gridFrameX + static_cast ( dayXSpace * ( i + 1 ) ); + const QDate curXAxisDate = curDate.addDays ( 0 - static_cast ( iNumTicksX ) + i + 1 ); + + // text (print only every "iXAxisTickStep" tick) + if ( !( i % iXAxisTickStep ) ) + { + text ( iCurX - iTextOffsetX, gridFrameBottom + iXAxisTextHeight + iTextOffsetToGrid, curXAxisDate.toString ( "dd.MM." ) ); + + iBottomExtraTickLen = 5; + } + + // regular grid + line ( iCurX, 1 + gridFrameY, iCurX, gridFrameBottom + iBottomExtraTickLen ); + + // different grid width for weekends (overwrite regular grid) + if ( ( curXAxisDate.dayOfWeek() == 6 ) || // check for Saturday + ( curXAxisDate.dayOfWeek() == 7 ) ) // check for Sunday + { + const int iGridWidthWeekendHalf = iGridWidthWeekend / 2; + + line ( iCurX, 1 + gridFrameY + iGridWidthWeekendHalf, iCurX, gridFrameBottom - iGridWidthWeekendHalf, iGridWidthWeekend ); + } + } + + // grid (ticks) for y-axis, draw iNumTicksY - 2 grid lines and + // iNumTicksY - 1 text labels (the lowest grid line is the grid frame) + iYSpace = gridFrameHeight / ( iNumTicksY - 1 ); + + for ( i = 0; i < ( static_cast ( iNumTicksY ) - 1 ); i++ ) + { + const int iCurY = gridFrameY + iYSpace * ( i + 1 ); + + // text + text ( gridFrameX + iTextOffsetToGrid, + iCurY - iTextOffsetToGrid, + QString ( "%1:00" ).arg ( ( iYAxisEnd - iYAxisStart ) / ( iNumTicksY - 1 ) * ( ( iNumTicksY - 2 ) - i ) ) ); + + // grid (do not overwrite frame) + if ( i < ( static_cast ( iNumTicksY ) - 2 ) ) + { + line ( gridFrameX, iCurY, gridFrameRight, iCurY ); + } + } +} + +void AHistoryGraph::AddMarker ( const SHistoryData& curHistoryData ) +{ + // calculate x-axis offset (difference of days compared to + // current date) + const int iXAxisOffs = + curDate.daysTo ( curHistoryData.DateTime.date() ); + + // check range, if out of range, do not plot anything + if ( -iXAxisOffs > ( static_cast ( iNumTicksX ) - 1 ) ) + { + return; + } + + // calculate y-axis offset (consider hours and minutes) + const double dYAxisOffs = 24 - curHistoryData.DateTime.time().hour() - + static_cast ( curHistoryData.DateTime.time().minute() ) / 60; + + // calculate the actual point in the graph (in pixels) + int curPointX = gridFrameX + static_cast ( dayXSpace * ( static_cast ( iNumTicksX ) + iXAxisOffs ) ); + int curPointY = gridFrameY + static_cast ( static_cast ( + gridFrameHeight ) / ( iYAxisEnd - iYAxisStart ) * dYAxisOffs ); + + QString curPointColour = MarkerNewColor; + int curPointSize = iMarkerSizeNewCon; + + // we use different markers for new connection and server stop items + switch ( curHistoryData.Type ) + { + case HIT_SERVER_STOP: + curPointColour = MarkerStopColor; + curPointSize = iMarkerSizeServSt; + break; + + case HIT_LOCAL_CONNECTION: + curPointColour = MarkerNewLocalColor; + break; + + case HIT_REMOTE_CONNECTION: + curPointColour = MarkerNewColor; + break; + } + + point ( curPointX - curPointSize / 2, curPointY - curPointSize / 2, curPointSize, curPointColour ); +} + + +/* JPEG History Graph implementation ******************************************/ +CJpegHistoryGraph::CJpegHistoryGraph() : + AHistoryGraph(), + PlotPixmap ( 1, 1, QImage::Format_RGB32 ), + iAxisFontWeight ( -1 ) +{ + // scale pixmap to correct size + PlotPixmap = PlotPixmap.scaled ( canvasRectWidth, canvasRectHeight ); + + // axisFontWeight is a string matching the CSS2 font-weight definition + // Thin = 0, // 100 + // ExtraLight = 12, // 200 + // Light = 25, // 300 + // Normal = 50, // 400 + // Medium = 57, // 500 + // DemiBold = 63, // 600 + // Bold = 75, // 700 + // ExtraBold = 81, // 800 + // Black = 87 // 900 + bool ok; + int weight = axisFontWeight.toInt( &ok ); + + if ( !ok ) + { + if ( !QString ( "normal" ).compare ( axisFontWeight, Qt::CaseSensitivity::CaseInsensitive ) ) + { + iAxisFontWeight = 50; + } + else if ( !QString ( "bold" ).compare ( axisFontWeight, Qt::CaseSensitivity::CaseInsensitive ) ) + { + weight = 75; + } + } + else + { + if ( weight <= 100 ) { iAxisFontWeight = 0; } + else if ( weight <= 200 ) { iAxisFontWeight = 12; } + else if ( weight <= 300 ) { iAxisFontWeight = 25; } + else if ( weight <= 400 ) { iAxisFontWeight = 50; } + else if ( weight <= 500 ) { iAxisFontWeight = 57; } + else if ( weight <= 600 ) { iAxisFontWeight = 63; } + else if ( weight <= 700 ) { iAxisFontWeight = 75; } + else if ( weight <= 800 ) { iAxisFontWeight = 81; } + else if ( weight <= 900 ) { iAxisFontWeight = 87; } + } + // if all else fails, it's left at -1 + + QTextStream& tsConsoleStream = *( ( new ConsoleWriterFactory() )->get() ); + tsConsoleStream << "CJpegHistoryGraph" << endl; // on console + + + // Connections ------------------------------------------------------------- + QObject::connect ( &TimerDailyUpdate, SIGNAL ( timeout() ), + this, SLOT ( OnTimerDailyUpdate() ) ); +} + +// Override Update to blank out the plot area each time +void CJpegHistoryGraph::Update() +{ + if ( bDoHistory ) + { + // create JPEG image + PlotPixmap.fill ( BackgroundColor ); // fill background + + AHistoryGraph::Update(); + } +} + +void CJpegHistoryGraph::Save ( const QString sFileName ) +{ + // save plot as a file + PlotPixmap.save ( sFileName, "JPG", 90 ); +} + +void CJpegHistoryGraph::rect ( const unsigned int x, const unsigned int y, const unsigned int width, const unsigned int height ) +{ + QPainter PlotPainter ( &PlotPixmap ); + PlotPainter.setPen ( FrameColor ); + PlotPainter.drawRect ( x, y, width, height ); +} + +void CJpegHistoryGraph::text ( const unsigned int x, const unsigned int y, const QString& value ) +{ + QPainter PlotPainter ( &PlotPixmap ); + PlotPainter.setPen ( TextColor ); + // QFont(const QString &family, int pointSize = -1, int weight = -1, bool italic = false); + PlotPainter.setFont ( QFont( axisFontFamily, static_cast ( axisFontSize ), iAxisFontWeight ) ); + PlotPainter.drawText ( QPoint ( x, y ), value ); +} + +void CJpegHistoryGraph::line ( const unsigned int x1, const unsigned int y1, const unsigned int x2, const unsigned int y2, const unsigned int strokeWidth ) +{ + QPainter PlotPainter ( &PlotPixmap ); + PlotPainter.setPen ( QPen ( QBrush ( QColor ( GridColor ) ), strokeWidth ) ); + PlotPainter.drawLine ( x1, y1, x2, y2 ); +} + +void CJpegHistoryGraph::point ( const unsigned int x, const unsigned int y, const unsigned int size, const QString& colour ) +{ + QPainter PlotPainter ( &PlotPixmap ); + PlotPainter.setPen ( QPen ( QBrush( QColor ( colour ) ), size ) ); + PlotPainter.drawPoint ( x, y ); +} + + +/* SVG History Graph implementation *******************************************/ +CSvgHistoryGraph::CSvgHistoryGraph() : + AHistoryGraph(), + svgImage ( "" ), + svgStreamWriter ( &svgImage ) +{ + // set SVG veiwBox to correct size to ensure correct scaling + svgRootAttributes.append ( "viewBox", + QString ( "%1, %2, %3, %4" ) + .arg ( canvasRectX ) + .arg ( canvasRectY ) + .arg ( canvasRectWidth ) + .arg ( canvasRectHeight ) + ); + + svgRootAttributes.append ( "xmlns", "http://www.w3.org/2000/svg" ); + svgRootAttributes.append ( "xmlns:xlink", "http://www.w3.org/1999/xlink" ); + + QTextStream& tsConsoleStream = *( ( new ConsoleWriterFactory() )->get() ); + tsConsoleStream << "CSvgHistoryGraph" << endl; // on console + + + // Connections ------------------------------------------------------------- + QObject::connect ( &TimerDailyUpdate, SIGNAL ( timeout() ), + this, SLOT ( OnTimerDailyUpdate() ) ); +} + +// Override Update to create the fresh SVG stream each time +void CSvgHistoryGraph::Update() +{ + if ( bDoHistory ) + { + // create SVG document + svgImage = ""; + + svgStreamWriter.setAutoFormatting ( true ); + svgStreamWriter.writeStartDocument(); + svgStreamWriter.writeStartElement ( "svg" ); + svgStreamWriter.writeAttributes ( svgRootAttributes ); + + AHistoryGraph::Update(); + } +} + +void CSvgHistoryGraph::Save ( const QString sFileName ) +{ + svgStreamWriter.writeEndDocument(); + + QFile outf ( sFileName ); + + if ( !outf.open ( QFile::WriteOnly ) ) + { + throw std::runtime_error ( ( sFileName + " could not be written. Aborting." ).toStdString() ); + } + QTextStream out ( &outf ); + + out << svgImage << endl; +} + +void CSvgHistoryGraph::rect ( const unsigned int x, const unsigned int y, const unsigned int width, const unsigned int height ) +{ + svgStreamWriter.writeEmptyElement ( "rect" ); + svgStreamWriter.writeAttribute ( "x", QString ( "%1" ).arg ( x ) ); + svgStreamWriter.writeAttribute ( "y", QString ( "%1" ).arg ( y ) ); + svgStreamWriter.writeAttribute ( "width", QString ( "%1" ).arg ( width ) ); + svgStreamWriter.writeAttribute ( "height", QString ( "%1" ).arg ( height ) ); + svgStreamWriter.writeAttribute ( "stroke", FrameColor ); + svgStreamWriter.writeAttribute ( "stroke-width", QString ( "1" ) ); + svgStreamWriter.writeAttribute ( "style", QString ( "fill: none;" ) ); +} + +void CSvgHistoryGraph::text ( const unsigned int x, const unsigned int y, const QString& value ) +{ + svgStreamWriter.writeStartElement ( "text" ); + svgStreamWriter.writeAttribute ( "x", QString ( "%1" ).arg ( x ) ); + svgStreamWriter.writeAttribute ( "y", QString ( "%1" ).arg ( y ) ); + svgStreamWriter.writeAttribute ( "stroke", TextColor ); + svgStreamWriter.writeAttribute ( "font-family", axisFontFamily ); + svgStreamWriter.writeAttribute ( "font-weight", axisFontWeight ); + svgStreamWriter.writeAttribute ( "font-size", QString ( "%1" ).arg ( axisFontSize ) ); + + svgStreamWriter.writeCharacters( value ); + svgStreamWriter.writeEndElement(); +} + +void CSvgHistoryGraph::line ( const unsigned int x1, const unsigned int y1, const unsigned int x2, const unsigned int y2, const unsigned int strokeWidth ) +{ + svgStreamWriter.writeEmptyElement ( "line" ); + svgStreamWriter.writeAttribute ( "x1", QString ( "%1" ).arg ( x1 ) ); + svgStreamWriter.writeAttribute ( "y1", QString ( "%1" ).arg ( y1 ) ); + svgStreamWriter.writeAttribute ( "x2", QString ( "%1" ).arg ( x2 ) ); + svgStreamWriter.writeAttribute ( "y2", QString ( "%1" ).arg ( y2 ) ); + svgStreamWriter.writeAttribute ( "stroke", GridColor ); + svgStreamWriter.writeAttribute ( "stroke-width", QString ( "%1" ).arg ( strokeWidth ) ); +} + +void CSvgHistoryGraph::point ( const unsigned int x, const unsigned int y, const unsigned int size, const QString& colour ) +{ + svgStreamWriter.writeEmptyElement ( "rect" ); + svgStreamWriter.writeAttribute ( "x", QString ( "%1" ).arg ( x ) ); + svgStreamWriter.writeAttribute ( "y", QString ( "%1" ).arg ( y ) ); + svgStreamWriter.writeAttribute ( "width", QString ( "%1" ).arg ( size ) ); + svgStreamWriter.writeAttribute ( "height", QString ( "%1" ).arg ( size ) ); + svgStreamWriter.writeAttribute ( "stroke-opacity", "0" ); + svgStreamWriter.writeAttribute ( "fill", colour ); +} diff --git a/src/historygraph.h b/src/historygraph.h new file mode 100644 index 00000000..09ef93bb --- /dev/null +++ b/src/historygraph.h @@ -0,0 +1,186 @@ +/******************************************************************************\ + * Copyright (c) 2004-2019 + * + * Author(s): + * Volker Fischer + * Peter L Jones + * + ****************************************************************************** + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +\******************************************************************************/ + +#ifndef HISTORYGRAPH_H +#define HISTORYGRAPH_H + +#include +#include +#include +#include +#include +#include "global.h" +#include "util.h" + +// for CJpegHistoryGraph +#include +#include + +// for CSvgHistoryGraph +#include +#include + + +/* Definitions ****************************************************************/ +// number of history items to store +#define NUM_ITEMS_HISTORY 600 + + +/* Interface ******************************************************************/ +class AHistoryGraph +{ +public: + enum EHistoryItemType + { + HIT_LOCAL_CONNECTION, + HIT_REMOTE_CONNECTION, + HIT_SERVER_STOP + }; + + AHistoryGraph(); + ~AHistoryGraph() { } + + void Start ( const QString& sNewFileName ); + void Add ( const QDateTime& newDateTime, const EHistoryItemType curType ); + void Add ( const QDateTime& newDateTime, const QHostAddress ClientInetAddr ); + virtual void Update ( ); + +protected: + struct SHistoryData + { + QDateTime DateTime; + EHistoryItemType Type; + }; + void DrawFrame ( const int iNewNumTicksX ); + void AddMarker ( const SHistoryData& curHistoryData ); + virtual void Save ( const QString sFileName ) = 0; + + virtual void rect ( const unsigned int x, const unsigned int y, const unsigned int width, const unsigned int height ) = 0; + virtual void text ( const unsigned int x, const unsigned int y, const QString& value ) = 0; + virtual void line ( const unsigned int x1, const unsigned int y1, const unsigned int x2, const unsigned int y2, const unsigned int strokeWidth = 1 ) = 0; + virtual void point ( const unsigned int x, const unsigned int y, const unsigned int size, const QString& colour ) = 0; + + // Constructor sets these + QString sFileName; + bool bDoHistory; + CFIFO vHistoryDataFifo; + unsigned int iNumTicksX; // Class global, not sure why + + QString BackgroundColor; + QString FrameColor; + QString GridColor; + QString TextColor; + QString MarkerNewColor; + QString MarkerNewLocalColor; + QString MarkerStopColor; + + const unsigned int canvasRectX; + const unsigned int canvasRectY; + const unsigned int canvasRectWidth; + const unsigned int canvasRectHeight; + + const unsigned int iGridFrameOffset; + const unsigned int iGridWidthWeekend; + const unsigned int iXAxisTextHeight; + const unsigned int gridFrameX; + const unsigned int gridFrameY; + const unsigned int gridFrameWidth; + const unsigned int gridFrameHeight; + const unsigned int gridFrameRight; + const unsigned int gridFrameBottom; + + const QString axisFontFamily; + const QString axisFontWeight; + const unsigned int axisFontSize; + + const unsigned int iYAxisStart; + const unsigned int iYAxisEnd; + const unsigned int iNumTicksY; + + const unsigned int iTextOffsetToGrid; + const unsigned int iTextOffsetX; + + const unsigned int iMarkerSizeNewCon; + const unsigned int iMarkerSizeServSt; + + // others + double dayXSpace; + unsigned int iYSpace; + QDate curDate; + QTimer TimerDailyUpdate; +}; + + +/* Implementations ************************************************************/ +class CJpegHistoryGraph : public QObject, virtual public AHistoryGraph +{ + Q_OBJECT + +public: + CJpegHistoryGraph(); + virtual void Update ( ); + +protected: + virtual void Save ( const QString sFileName ); + + virtual void rect ( const unsigned int x, const unsigned int y, const unsigned int width, const unsigned int height ); + virtual void text ( const unsigned int x, const unsigned int y, const QString& value ); + virtual void line ( const unsigned int x1, const unsigned int y1, const unsigned int x2, const unsigned int y2, const unsigned int strokeWidth = 1 ); + virtual void point ( const unsigned int x, const unsigned int y, const unsigned int size, const QString& colour ); + +private: + QImage PlotPixmap; + int iAxisFontWeight; + +public slots: + void OnTimerDailyUpdate() { Update(); } +}; + +class CSvgHistoryGraph : public QObject, virtual public AHistoryGraph +{ + Q_OBJECT + +public: + CSvgHistoryGraph(); + virtual void Update(); + +protected: + virtual void Save ( const QString sFileName ); + + virtual void rect ( const unsigned int x, const unsigned int y, const unsigned int width, const unsigned int height ); + virtual void text ( const unsigned int x, const unsigned int y, const QString& value ); + virtual void line ( const unsigned int x1, const unsigned int y1, const unsigned int x2, const unsigned int y2, const unsigned int strokeWidth = 1 ); + virtual void point ( const unsigned int x, const unsigned int y, const unsigned int size, const QString& colour ); + +private: + QXmlStreamAttributes svgRootAttributes; + QString svgImage; + QXmlStreamWriter svgStreamWriter; + +public slots: + void OnTimerDailyUpdate() { Update(); } +}; + +#endif // HISTORYGRAPH_H diff --git a/src/main.cpp b/src/main.cpp index 91fea668..b043f29b 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -415,8 +415,7 @@ int main ( int argc, char** argv ) // Application object if ( !bUseGUI && !strHistoryFileName.isEmpty() ) { - tsConsole << "Qt5 requires a windowing system to paint a JPEG image; disabling history graph" << endl; - strHistoryFileName = ""; + tsConsole << "Qt5 requires a windowing system to paint a JPEG image; image will use SVG" << endl; } QCoreApplication* pApp = bUseGUI ? new QApplication ( argc, argv ) diff --git a/src/serverlogging.cpp b/src/serverlogging.cpp index 8876c099..eb4e4955 100755 --- a/src/serverlogging.cpp +++ b/src/serverlogging.cpp @@ -24,293 +24,6 @@ #include "serverlogging.h" - -/* Implementation *************************************************************/ -CHistoryGraph::CHistoryGraph() : - sFileName ( "" ), - bDoHistory ( false ), - vHistoryDataFifo ( NUM_ITEMS_HISTORY ), - PlotCanvasRect ( 0, 0, 600, 450 ), // defines total size of graph - iNumTicksX ( 0 ), // just an initialization value, will be overwritten - iYAxisStart ( 0 ), - iYAxisEnd ( 24 ), - iNumTicksY ( 5 ), - iGridFrameOffset ( 10 ), - iGridWidthWeekend ( 3 ), // should be an odd value - iTextOffsetToGrid ( 3 ), - iXAxisTextHeight ( 22 ), - iMarkerSizeNewCon ( 11 ), - iMarkerSizeServSt ( 8 ), - AxisFont ( "Arial", 12 ), - iTextOffsetX ( 18 ), - PlotBackgroundColor ( Qt::white ), // background - PlotFrameColor ( Qt::black ), // frame - PlotGridColor ( Qt::gray ), // grid - PlotTextColor ( Qt::black ), // text - PlotMarkerNewColor ( Qt::darkCyan ), // marker for new connection - PlotMarkerNewLocalColor ( Qt::blue ), // marker for new local connection - PlotMarkerStopColor ( Qt::red ), // marker for server stop - PlotPixmap ( 1, 1, QImage::Format_RGB32 ) -{ - // generate plot grid frame rectangle - PlotGridFrame.setRect ( PlotCanvasRect.x() + iGridFrameOffset, - PlotCanvasRect.y() + iGridFrameOffset, - PlotCanvasRect.width() - 2 * iGridFrameOffset, - PlotCanvasRect.height() - 2 * iGridFrameOffset - iXAxisTextHeight ); - - // scale pixmap to correct size - PlotPixmap = PlotPixmap.scaled ( - PlotCanvasRect.width(), PlotCanvasRect.height() ); - - - // Connections ------------------------------------------------------------- - QObject::connect ( &TimerDailyUpdate, SIGNAL ( timeout() ), - this, SLOT ( OnTimerDailyUpdate() ) ); -} - -void CHistoryGraph::Start ( const QString& sNewFileName ) -{ - if ( !sNewFileName.isEmpty() ) - { - // save file name - sFileName = sNewFileName; - - // set enable flag - bDoHistory = true; - - // enable timer (update once a day) - TimerDailyUpdate.start ( 3600000 * 24 ); - - // initial update (empty graph) - Update(); - } -} - -void CHistoryGraph::DrawFrame ( const int iNewNumTicksX ) -{ - int i; - - // store number of x-axis ticks (number of days we want to draw - // the history for - iNumTicksX = iNewNumTicksX; - - // clear base pixmap for new plotting - PlotPixmap.fill ( PlotBackgroundColor.rgb() ); // fill background - - // create painter - QPainter PlotPainter ( &PlotPixmap ); - - - // Create actual plot region (grid frame) ---------------------------------- - PlotPainter.setPen ( PlotFrameColor ); - PlotPainter.drawRect ( PlotGridFrame ); - - // calculate step for x-axis ticks so that we get the desired number of - // ticks -> 5 ticks - -// TODO the following equation does not work as expected but results are acceptable - - // we want to have "floor ( iNumTicksX / 5 )" which is the same as - // "iNumTicksX / 5" since "iNumTicksX" is an integer variable - const int iXAxisTickStep = iNumTicksX / 5 + 1; - - // grid (ticks) for x-axis - dXSpace = static_cast ( PlotGridFrame.width() ) / ( iNumTicksX + 1 ); - for ( i = 0; i < iNumTicksX; i++ ) - { - int iBottomExtraTickLen = 0; - const int iCurX = PlotGridFrame.x() + static_cast ( dXSpace * ( i + 1 ) ); - const QDate curXAxisDate = curDate.addDays ( i - iNumTicksX + 1 ); - - // text (print only every "iXAxisTickStep" tick) - if ( !( i % iXAxisTickStep ) ) - { - PlotPainter.setPen ( PlotTextColor ); - PlotPainter.setFont ( AxisFont ); - PlotPainter.drawText ( - QPoint ( iCurX - iTextOffsetX, - PlotGridFrame.bottom() + iXAxisTextHeight + iTextOffsetToGrid ), - curXAxisDate.toString ( "dd.MM." ) ); - - iBottomExtraTickLen = 5; - } - - // regular grid - PlotPainter.setPen ( PlotGridColor ); - PlotPainter.drawLine ( iCurX, 1 + PlotGridFrame.y(), - iCurX, PlotGridFrame.bottom() + iBottomExtraTickLen ); - - // different grid width for weekends (overwrite regular grid) - if ( ( curXAxisDate.dayOfWeek() == 6 ) || // check for Saturday - ( curXAxisDate.dayOfWeek() == 7 ) ) // check for Sunday - { - const int iGridWidthWeekendHalf = iGridWidthWeekend / 2; - - PlotPainter.setPen ( QPen ( PlotGridColor, iGridWidthWeekend ) ); - PlotPainter.drawLine ( iCurX, 1 + PlotGridFrame.y() + iGridWidthWeekendHalf, - iCurX, PlotGridFrame.bottom() - iGridWidthWeekendHalf ); - } - } - - // grid (ticks) for y-axis, draw iNumTicksY - 2 grid lines and - // iNumTicksY - 1 text labels (the lowest grid line is the grid frame) - iYSpace = PlotGridFrame.height() / ( iNumTicksY - 1 ); - for ( i = 0; i < ( iNumTicksY - 1 ); i++ ) - { - const int iCurY = PlotGridFrame.y() + iYSpace * ( i + 1 ); - - // text - PlotPainter.setPen ( PlotTextColor ); - PlotPainter.setFont ( AxisFont ); - PlotPainter.drawText ( QPoint ( - PlotGridFrame.x() + iTextOffsetToGrid, - iCurY - iTextOffsetToGrid ), - QString ( "%1:00" ).arg ( - ( iYAxisEnd - iYAxisStart ) / ( iNumTicksY - 1 ) * - ( ( iNumTicksY - 2 ) - i ) ) ); - - // grid (do not overwrite frame) - if ( i < ( iNumTicksY - 2 ) ) - { - PlotPainter.setPen ( PlotGridColor ); - PlotPainter.drawLine ( PlotGridFrame.x(), iCurY, - PlotGridFrame.right(), iCurY ); - } - } -} - -void CHistoryGraph::AddMarker ( const SHistoryData& curHistoryData ) -{ - // calculate x-axis offset (difference of days compared to - // current date) - const int iXAxisOffs = - curDate.daysTo ( curHistoryData.DateTime.date() ); - - // check range, if out of range, do not plot anything - if ( -iXAxisOffs > ( iNumTicksX - 1 ) ) - { - return; - } - - // calculate y-axis offset (consider hours and minutes) - const double dYAxisOffs = 24 - curHistoryData.DateTime.time().hour() - - static_cast ( curHistoryData.DateTime.time().minute() ) / 60; - - // calculate the actual point in the graph (in pixels) - const QPoint curPoint ( - PlotGridFrame.x() + static_cast ( dXSpace * ( iNumTicksX + iXAxisOffs ) ), - PlotGridFrame.y() + static_cast ( static_cast ( - PlotGridFrame.height() ) / ( iYAxisEnd - iYAxisStart ) * dYAxisOffs ) ); - - // create painter for plot - QPainter PlotPainter ( &PlotPixmap ); - - // we use different markers for new connection and server stop items - switch ( curHistoryData.Type ) - { - case HIT_SERVER_STOP: - PlotPainter.setPen ( QPen ( QBrush ( PlotMarkerStopColor ), - iMarkerSizeServSt ) ); - break; - - case HIT_LOCAL_CONNECTION: - PlotPainter.setPen ( QPen ( QBrush ( PlotMarkerNewLocalColor ), - iMarkerSizeNewCon ) ); - break; - - case HIT_REMOTE_CONNECTION: - PlotPainter.setPen ( QPen ( QBrush ( PlotMarkerNewColor ), - iMarkerSizeNewCon ) ); - break; - } - PlotPainter.drawPoint ( curPoint ); -} - -void CHistoryGraph::Save ( const QString sFileName ) -{ - // save plot as a file - PlotPixmap.save ( sFileName, "JPG", 90 ); -} - -void CHistoryGraph::Add ( const QDateTime& newDateTime, - const QHostAddress ClientInetAddr ) -{ - if ( bDoHistory ) - { - // add element to history, distinguish between a local connection - // and a remote connection - if ( ( ClientInetAddr == QHostAddress ( "127.0.0.1" ) ) || - ( ClientInetAddr.toString().left ( 7 ).compare ( "192.168" ) == 0 ) ) - { - // local connection - Add ( newDateTime, CHistoryGraph::HIT_LOCAL_CONNECTION ); - } - else - { - // remote connection - Add ( newDateTime, CHistoryGraph::HIT_REMOTE_CONNECTION ); - } - } -} - -void CHistoryGraph::Add ( const QDateTime& newDateTime, - const EHistoryItemType curType ) -{ - if ( bDoHistory ) - { - // create and add new element in FIFO - SHistoryData curHistoryData; - curHistoryData.DateTime = newDateTime; - curHistoryData.Type = curType; - - vHistoryDataFifo.Add ( curHistoryData ); - } -} - -void CHistoryGraph::Update() -{ - if ( bDoHistory ) - { - int i; - - // store current date for reference - curDate = QDate::currentDate(); - - // get oldest date in history - QDate oldestDate = curDate.addDays ( 1 ); // one day in the future - const int iNumItemsForHistory = vHistoryDataFifo.Size(); - for ( i = 0; i < iNumItemsForHistory; i++ ) - { - // only use valid dates - if ( vHistoryDataFifo[i].DateTime.date().isValid() ) - { - if ( vHistoryDataFifo[i].DateTime.date() < oldestDate ) - { - oldestDate = vHistoryDataFifo[i].DateTime.date(); - } - } - } - const int iNumDaysInHistory = -curDate.daysTo ( oldestDate ) + 1; - - // draw frame of the graph - DrawFrame ( iNumDaysInHistory ); - - // add markers - for ( i = 0; i < iNumItemsForHistory; i++ ) - { - // only use valid dates - if ( vHistoryDataFifo[i].DateTime.date().isValid() ) - { - AddMarker ( vHistoryDataFifo[i] ); - } - } - - // save graph as picture in file - Save ( sFileName ); - } -} - - // Server logging -------------------------------------------------------------- CServerLogging::~CServerLogging() { @@ -325,6 +38,7 @@ void CServerLogging::Start ( const QString& strLoggingFileName ) { // open file File.setFileName ( strLoggingFileName ); + if ( File.open ( QIODevice::Append | QIODevice::Text ) ) { bDoLogging = true; @@ -333,7 +47,14 @@ void CServerLogging::Start ( const QString& strLoggingFileName ) void CServerLogging::EnableHistory ( const QString& strHistoryFileName ) { - HistoryGraph.Start ( strHistoryFileName ); + if ( strHistoryFileName.right ( 4 ).compare ( ".svg", Qt::CaseInsensitive ) == 0 ) + { + SvgHistoryGraph.Start ( strHistoryFileName ); + } + else + { + JpegHistoryGraph.Start ( strHistoryFileName ); + } } void CServerLogging::AddNewConnection ( const QHostAddress& ClientInetAddr ) @@ -342,14 +63,13 @@ void CServerLogging::AddNewConnection ( const QHostAddress& ClientInetAddr ) const QString strLogStr = CurTimeDatetoLogString() + ", " + ClientInetAddr.toString() + ", connected"; -#ifndef _WIN32 - QTextStream tsConsoleStream ( stdout ); + QTextStream& tsConsoleStream = *( ( new ConsoleWriterFactory() )->get() ); tsConsoleStream << strLogStr << endl; // on console -#endif *this << strLogStr; // in log file // add element to history - HistoryGraph.Add ( QDateTime::currentDateTime(), ClientInetAddr ); + JpegHistoryGraph.Add ( QDateTime::currentDateTime(), ClientInetAddr ); + SvgHistoryGraph.Add ( QDateTime::currentDateTime(), ClientInetAddr ); } void CServerLogging::AddServerStopped() @@ -357,17 +77,16 @@ void CServerLogging::AddServerStopped() const QString strLogStr = CurTimeDatetoLogString() + ",, server stopped " "-------------------------------------"; -#ifndef _WIN32 - QTextStream tsConsoleStream ( stdout ); + QTextStream& tsConsoleStream = *( ( new ConsoleWriterFactory() )->get() ); tsConsoleStream << strLogStr << endl; // on console -#endif *this << strLogStr; // in log file // add element to history and update on server stop - HistoryGraph.Add ( QDateTime::currentDateTime(), - CHistoryGraph::HIT_SERVER_STOP ); + JpegHistoryGraph.Add ( QDateTime::currentDateTime(), CJpegHistoryGraph::HIT_SERVER_STOP ); + SvgHistoryGraph.Add ( QDateTime::currentDateTime(), CJpegHistoryGraph::HIT_SERVER_STOP ); - HistoryGraph.Update(); + JpegHistoryGraph.Update(); + SvgHistoryGraph.Update(); } void CServerLogging::operator<< ( const QString& sNewStr ) @@ -396,7 +115,7 @@ void CServerLogging::ParseLogFile ( const QString& strFileName ) QString strCurLine = inStream.readLine(); // parse log file line - QStringList strlistCurLine = strCurLine.split( "," ); + QStringList strlistCurLine = strCurLine.split ( "," ); // check number of separated strings condition if ( strlistCurLine.size() == 4 ) @@ -417,11 +136,12 @@ void CServerLogging::ParseLogFile ( const QString& strFileName ) // check if server stop or new client connection QString strAddress = strlistCurLine.at ( 2 ).trimmed(); + if ( strAddress.isEmpty() ) { // server stop - HistoryGraph.Add ( curDateTime, - CHistoryGraph::HIT_SERVER_STOP ); + JpegHistoryGraph.Add ( curDateTime, CJpegHistoryGraph::HIT_SERVER_STOP ); + SvgHistoryGraph.Add ( curDateTime, CSvgHistoryGraph::HIT_SERVER_STOP ); } else { @@ -431,14 +151,16 @@ void CServerLogging::ParseLogFile ( const QString& strFileName ) if ( curAddress.setAddress ( strlistCurLine.at ( 2 ).trimmed() ) ) { // new client connection - HistoryGraph.Add ( curDateTime, curAddress ); + JpegHistoryGraph.Add ( curDateTime, curAddress ); + SvgHistoryGraph.Add ( curDateTime, curAddress ); } } } } } - HistoryGraph.Update(); + JpegHistoryGraph.Update(); + SvgHistoryGraph.Update(); } QString CServerLogging::CurTimeDatetoLogString() diff --git a/src/serverlogging.h b/src/serverlogging.h index fc281ae6..a8fb8386 100755 --- a/src/serverlogging.h +++ b/src/serverlogging.h @@ -22,11 +22,9 @@ * \******************************************************************************/ -#if !defined ( SERVERLOGGING_HOIHOKIH83JH8_3_43445KJIUHF1912__INCLUDED_ ) -#define SERVERLOGGING_HOIHOKIH83JH8_3_43445KJIUHF1912__INCLUDED_ +#ifndef SERVERLOGGING_H +#define SERVERLOGGING_H -#include -#include #include #include #include @@ -35,84 +33,9 @@ #include "global.h" #include "util.h" - -/* Definitions ****************************************************************/ -// number of history items to store -#define NUM_ITEMS_HISTORY 600 - +#include "historygraph.h" /* Classes ********************************************************************/ -class CHistoryGraph : public QObject -{ - Q_OBJECT - -public: - enum EHistoryItemType - { - HIT_LOCAL_CONNECTION, - HIT_REMOTE_CONNECTION, - HIT_SERVER_STOP - }; - - CHistoryGraph(); - void Start ( const QString& sNewFileName ); - void Add ( const QDateTime& newDateTime, const EHistoryItemType curType ); - void Add ( const QDateTime& newDateTime, const QHostAddress ClientInetAddr ); - void Update(); - -protected: - struct SHistoryData - { - QDateTime DateTime; - EHistoryItemType Type; - }; - void DrawFrame ( const int iNewNumTicksX ); - void AddMarker ( const SHistoryData& curHistoryData ); - void Save ( const QString sFileName ); - - QString sFileName; - - bool bDoHistory; - CFIFO vHistoryDataFifo; - - QRect PlotCanvasRect; - - int iNumTicksX; - int iYAxisStart; - int iYAxisEnd; - int iNumTicksY; - int iGridFrameOffset; - int iGridWidthWeekend; - int iTextOffsetToGrid; - int iXAxisTextHeight; - int iMarkerSizeNewCon; - int iMarkerSizeServSt; - - QFont AxisFont; - int iTextOffsetX; - - QColor PlotBackgroundColor; - QColor PlotFrameColor; - QColor PlotGridColor; - QColor PlotTextColor; - QColor PlotMarkerNewColor; - QColor PlotMarkerNewLocalColor; - QColor PlotMarkerStopColor; - - QImage PlotPixmap; - - double dXSpace; - int iYSpace; - - QDate curDate; - QRect PlotGridFrame; - QTimer TimerDailyUpdate; - -public slots: - void OnTimerDailyUpdate() { Update(); } -}; - - class CServerLogging { public: @@ -130,9 +53,10 @@ protected: void operator<< ( const QString& sNewStr ); QString CurTimeDatetoLogString(); - CHistoryGraph HistoryGraph; - bool bDoLogging; - QFile File; + CJpegHistoryGraph JpegHistoryGraph; + CSvgHistoryGraph SvgHistoryGraph; + bool bDoLogging; + QFile File; }; -#endif /* !defined ( SERVERLOGGING_HOIHOKIH83JH8_3_43445KJIUHF1912__INCLUDED_ ) */ +#endif // SERVERLOGGING_H diff --git a/src/util.cpp b/src/util.cpp index 6463cf59..de65b9a7 100755 --- a/src/util.cpp +++ b/src/util.cpp @@ -385,7 +385,7 @@ CAboutDlg::CAboutDlg ( QWidget* parent ) : QDialog ( parent ) "http://openclipart.org" "
  • Country flag icons from Mark James: " "http://www.famfamfam.com
  • " - "
  • Audio recording for the server, coded by pljones: " + "
  • Audio recording for the server and SVG history graph, coded by pljones: " "http://jamulus.drealm.info
  • " "" "
    ");