File indexing completed on 2024-05-12 15:31:21

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2009 Bastian Holst <bastianholst@gmx.de>
0004 // SPDX-FileCopyrightText: 2012 Illya Kovalevskyy <illya.kovalevskyy@gmail.com>
0005 //
0006 
0007 // Self
0008 #include "WeatherItem.h"
0009 
0010 // Marble
0011 #include "GeoPainter.h"
0012 #include "MarbleDirs.h"
0013 #include "WeatherData.h"
0014 #include "weatherGlobal.h"
0015 #include "FrameGraphicsItem.h"
0016 #include "LabelGraphicsItem.h"
0017 #include "MarbleGraphicsGridLayout.h"
0018 #include "WidgetGraphicsItem.h"
0019 #include "TinyWebBrowser.h"
0020 #include "MarbleDebug.h"
0021 #include "MarbleWidget.h"
0022 #include "layers/PopupLayer.h"
0023 
0024 // Qt
0025 #include <QDate>
0026 #include <QHash>
0027 #include <QString>
0028 #include <QAction>
0029 #include <QIcon>
0030 #include <QFont>
0031 #include <QPushButton>
0032 #include <QSvgRenderer>
0033 
0034 namespace Marble
0035 {
0036     
0037 const QSize borderSpacing( 4, 4 );
0038 const QSize imageSize( 28, 28 );
0039 const double imageSizeRatio = ( (double) imageSize.width()) / ( (double) imageSize.height() );
0040 
0041 class WeatherItemPrivate
0042 {
0043     Q_DECLARE_TR_FUNCTIONS(WeatherItemPrivate)
0044     
0045  public:
0046     WeatherItemPrivate( WeatherItem *parent )
0047         : m_marbleWidget( nullptr ),
0048           m_priority( 0 ),
0049           m_browserAction( tr( "Weather" ), parent ),
0050           m_favoriteAction( parent ),
0051           m_parent( parent ),
0052           m_frameItem( m_parent ),
0053           m_conditionLabel( &m_frameItem ),
0054           m_temperatureLabel( &m_frameItem ),
0055           m_windDirectionLabel( &m_frameItem ),
0056           m_windSpeedLabel( &m_frameItem ),
0057           m_favoriteButton( &m_frameItem )
0058     {
0059         // Setting minimum sizes
0060         m_temperatureLabel.setMinimumSize( QSizeF( 0, imageSize.height() ) );
0061         m_windSpeedLabel.setMinimumSize( QSizeF( 0, imageSize.height() ) );
0062 
0063         QPushButton *button = new QPushButton();
0064         button->setStyleSheet( "border-style: outset;" );
0065         button->setIcon(QIcon(QStringLiteral(":/icons/bookmarks.png")));
0066         button->setFixedSize( 22, 22 );
0067         button->setFlat( true );
0068         button->setCheckable( true );
0069 
0070         m_favoriteButton.setWidget( button );
0071 
0072         // Layouting the item
0073         MarbleGraphicsGridLayout *topLayout = new MarbleGraphicsGridLayout( 1, 1 );
0074         parent->setLayout( topLayout );
0075         topLayout->addItem( &m_frameItem, 0, 0 );
0076 
0077         MarbleGraphicsGridLayout *gridLayout = new MarbleGraphicsGridLayout( 2, 3 );
0078         gridLayout->setAlignment( Qt::AlignCenter );
0079         gridLayout->setSpacing( 4 );
0080         m_frameItem.setLayout( gridLayout );
0081         m_frameItem.setFrame( FrameGraphicsItem::RoundedRectFrame );
0082 
0083         gridLayout->addItem( &m_conditionLabel, 0, 0 );
0084         gridLayout->addItem( &m_temperatureLabel, 0, 1 );
0085         gridLayout->setAlignment( &m_temperatureLabel, Qt::AlignRight | Qt::AlignVCenter );
0086         gridLayout->addItem( &m_windDirectionLabel, 1, 0 );
0087         gridLayout->addItem( &m_windSpeedLabel, 1, 1 );
0088         gridLayout->setAlignment( &m_windSpeedLabel, Qt::AlignRight | Qt::AlignVCenter );
0089         gridLayout->addItem( &m_favoriteButton, 0, 2 );
0090 
0091         updateLabels();
0092     }
0093 
0094     ~WeatherItemPrivate()
0095     {
0096     }
0097 
0098     void updateToolTip()
0099     {
0100         QLocale locale = QLocale::system();
0101         QString toolTip;
0102         toolTip += tr( "Station: %1\n" ).arg( m_parent->stationName() );
0103         if ( m_currentWeather.hasValidCondition() && m_currentWeather.hasValidTemperature() )
0104             toolTip += QString( "%2, %3\n" ).arg( m_currentWeather.conditionString() )
0105                                            .arg( temperatureString() );
0106         else if ( m_currentWeather.hasValidCondition() )
0107             toolTip += QString( "%2\n" ).arg( m_currentWeather.conditionString() );
0108         else if ( m_currentWeather.hasValidTemperature() )
0109             toolTip += QString( "%2\n" ).arg( temperatureString() );
0110 
0111         if ( m_currentWeather.hasValidWindSpeed() && m_currentWeather.hasValidWindDirection() )
0112             toolTip += tr( "Wind: %4, %5\n", "Wind: WindSpeed, WindDirection" )
0113                     .arg( windSpeedString( ) )
0114                     .arg( m_currentWeather.windDirectionString() );
0115         else if ( m_currentWeather.hasValidWindSpeed() )
0116             toolTip += tr( "Wind: %4\n", "Wind: WindSpeed" )
0117                     .arg( m_currentWeather.windSpeedString() );
0118         else if ( m_currentWeather.hasValidWindDirection() )
0119             toolTip += tr( "Wind: %4\n", "Wind: WindDirection" )
0120                     .arg( m_currentWeather.windDirectionString() );
0121 
0122         if ( m_currentWeather.hasValidPressure() && m_currentWeather.hasValidPressureDevelopment() )
0123             toolTip += tr( "Pressure: %6, %7", "Pressure: Pressure, Development" )
0124                     .arg( pressureString() )
0125                     .arg( m_currentWeather.pressureDevelopmentString() );
0126         else if ( m_currentWeather.hasValidPressure() )
0127             toolTip += tr( "Pressure: %6", "Pressure: Pressure" )
0128                     .arg( pressureString() );
0129         else if ( m_currentWeather.hasValidPressureDevelopment() )
0130             toolTip += tr( "Pressure %7", "Pressure Development" )
0131                     .arg( m_currentWeather.pressureDevelopmentString() );
0132 
0133         if ( !m_forecastWeather.isEmpty() ) {
0134             toolTip += QLatin1Char('\n');
0135 
0136             QDate const minDate = QDate::currentDate();
0137             for( const WeatherData& data: m_forecastWeather ) {
0138                 QDate date = data.dataDate();
0139                 if( date >= minDate
0140                     && data.hasValidCondition()
0141                     && data.hasValidMinTemperature()
0142                     && data.hasValidMaxTemperature() )
0143                 {
0144                     toolTip += QLatin1Char('\n') +
0145                                tr( "%1: %2, %3 to %4", "DayOfWeek: Condition, MinTemp to MaxTemp" )
0146                                .arg( locale.standaloneDayName( date.dayOfWeek() ) )
0147                                .arg( data.conditionString() )
0148                                .arg( data.minTemperatureString( temperatureUnit() ) )
0149                                .arg( data.maxTemperatureString( temperatureUnit() ) );
0150                 }
0151             }
0152         }
0153 
0154         m_parent->setToolTip( toolTip );
0155     }
0156 
0157     void updateLabels()
0158     {
0159         if ( isConditionShown() ) {
0160             m_conditionLabel.setImage( m_currentWeather.icon(), imageSize );
0161         }
0162         else {
0163             m_conditionLabel.clear();
0164         }
0165 
0166         if ( isTemperatureShown() ) {
0167             m_temperatureLabel.setText( temperatureString() );
0168         }
0169         else {
0170             m_temperatureLabel.clear();
0171         }
0172 
0173         if ( isWindDirectionShown() ) {
0174             QString windDirectionString = m_currentWeather.windDirectionString();
0175             QSizeF windDirectionImageSize;
0176             QSvgRenderer s_windIcons(MarbleDirs::path(QStringLiteral("weather/wind-arrows.svgz")));
0177             QSizeF windDirectionSizeF = s_windIcons.boundsOnElement( windDirectionString ).size();
0178             double windDirectionRatio = windDirectionSizeF.width() / windDirectionSizeF.height();
0179             if ( windDirectionRatio >= imageSizeRatio ) {
0180                 windDirectionImageSize.setWidth( imageSize.width() );
0181                 windDirectionImageSize.setHeight( imageSize.width() / windDirectionRatio );
0182             }
0183             else {
0184                 windDirectionImageSize.setHeight( imageSize.height() );
0185                 windDirectionImageSize.setWidth( imageSize.height() * windDirectionRatio );
0186             }
0187 
0188             QImage windArrow( windDirectionImageSize.toSize(), QImage::Format_ARGB32 );
0189             windArrow.fill( Qt::transparent );
0190             QPainter painter( &windArrow );
0191             s_windIcons.render( &painter, windDirectionString );
0192             m_windDirectionLabel.setImage( windArrow );
0193         }
0194         else {
0195             m_windDirectionLabel.clear();
0196         }
0197 
0198         if ( isWindSpeedShown() ) {
0199             m_windSpeedLabel.setText( windSpeedString() );
0200         }
0201         else {
0202             m_windSpeedLabel.clear();
0203         }
0204 
0205         m_parent->update();
0206     }
0207 
0208     void updateFavorite()
0209     {
0210         QStringList items = m_settings.value(QStringLiteral("favoriteItems")).toString()
0211                                         .split(QLatin1Char(','), QString::SkipEmptyParts);
0212         bool favorite = items.contains( m_parent->id() );
0213 
0214         m_favoriteButton.setVisible( favorite );
0215         m_favoriteAction.setText( favorite ? tr( "Remove from Favorites" )
0216                                            : tr( "Add to Favorites" ) );
0217 
0218         if ( m_parent->isFavorite() != favorite ) {
0219             m_parent->setFavorite( favorite );
0220         }
0221 
0222         m_parent->update();
0223     }
0224     
0225     bool isConditionShown() const
0226     {
0227         return m_currentWeather.hasValidCondition()
0228                && m_settings.value(QStringLiteral("showCondition"), showConditionDefault).toBool();
0229     }
0230     
0231     bool isTemperatureShown() const
0232     {
0233         return m_currentWeather.hasValidTemperature()
0234                && m_settings.value(QStringLiteral("showTemperature"), showTemperatureDefault).toBool();
0235     }
0236     
0237     bool isWindDirectionShown() const
0238     {
0239         return m_currentWeather.hasValidWindDirection()
0240                && m_settings.value(QStringLiteral("showWindDirection"), showWindDirectionDefault).toBool();
0241     }
0242     
0243     bool isWindSpeedShown() const
0244     {
0245         return m_currentWeather.hasValidWindSpeed()
0246                && m_settings.value(QStringLiteral("showWindSpeed"), showWindSpeedDefault).toBool();
0247     }
0248 
0249     QString temperatureString() const
0250     {
0251         WeatherData::TemperatureUnit tUnit = temperatureUnit();
0252         return m_currentWeather.temperatureString( tUnit );
0253     }
0254 
0255     WeatherData::TemperatureUnit temperatureUnit() const
0256     {
0257         WeatherData::TemperatureUnit tUnit
0258                 = (WeatherData::TemperatureUnit) m_settings.value(QStringLiteral("temperatureUnit"),
0259                                                                    WeatherData::Celsius ).toInt();
0260         return tUnit;
0261     }
0262 
0263     QString windSpeedString() const
0264     {
0265         return m_currentWeather.windSpeedString( speedUnit() );
0266     }
0267 
0268     WeatherData::SpeedUnit speedUnit() const
0269     {
0270         return (WeatherData::SpeedUnit) m_settings.value(QStringLiteral("windSpeedUnit"),
0271                                                           WeatherData::kph ).toInt();
0272     }
0273 
0274     QString pressureString() const
0275     {
0276         return m_currentWeather.pressureString( pressureUnit() );
0277     }
0278 
0279     WeatherData::PressureUnit pressureUnit() const
0280     {
0281         return (WeatherData::PressureUnit) m_settings.value(QStringLiteral("pressureUnit"),
0282                                                              WeatherData::HectoPascal ).toInt();
0283     }
0284 
0285     MarbleWidget *m_marbleWidget;
0286 
0287     WeatherData m_currentWeather;
0288     QMap<QDate, WeatherData> m_forecastWeather;
0289 
0290     quint8 m_priority;
0291     QAction m_browserAction;
0292     QAction m_favoriteAction;
0293     WeatherItem *m_parent;
0294     QString m_stationName;
0295     QHash<QString,QVariant> m_settings;
0296     
0297     static QFont s_font;
0298 
0299     // Labels and Layout
0300     // We are not the owner of these items.
0301     FrameGraphicsItem  m_frameItem;
0302     LabelGraphicsItem  m_conditionLabel;
0303     LabelGraphicsItem  m_temperatureLabel;
0304     LabelGraphicsItem  m_windDirectionLabel;
0305     LabelGraphicsItem  m_windSpeedLabel;
0306     WidgetGraphicsItem m_favoriteButton;
0307 };
0308 
0309 // FIXME: Fonts to be defined globally
0310 #ifdef Q_OS_MACX
0311     QFont WeatherItemPrivate::s_font = QFont( QStringLiteral( "Sans Serif" ), 10 );
0312 #else
0313     QFont WeatherItemPrivate::s_font = QFont( QStringLiteral( "Sans Serif" ), 8 );
0314 #endif
0315 
0316 WeatherItem::WeatherItem(QObject *parent )
0317     : AbstractDataPluginItem( parent ),
0318     d( new WeatherItemPrivate( this ) )
0319 {
0320     setCacheMode( ItemCoordinateCache );
0321 }
0322 
0323 WeatherItem::WeatherItem(MarbleWidget* widget, QObject *parent )
0324     : AbstractDataPluginItem( parent ),
0325     d( new WeatherItemPrivate( this ) )
0326 {
0327     setCacheMode( ItemCoordinateCache );
0328     d->m_marbleWidget = widget;
0329 }
0330 
0331 WeatherItem::~WeatherItem()
0332 {
0333     delete d;
0334 }
0335 
0336 QAction *WeatherItem::action()
0337 {
0338     disconnect( &d->m_browserAction, SIGNAL(triggered()),
0339                 this,        SLOT(openBrowser()) );
0340     connect(    &d->m_browserAction, SIGNAL(triggered()),
0341                 this,        SLOT(openBrowser()) );
0342     return &d->m_browserAction;
0343 }
0344 
0345 bool WeatherItem::request( const QString& type )
0346 {
0347     Q_UNUSED( type )
0348     return true;
0349 }
0350  
0351 bool WeatherItem::initialized() const
0352 {
0353     return d->isConditionShown()
0354            || d->isTemperatureShown()
0355            || d->isWindDirectionShown()
0356            || d->isWindSpeedShown();
0357 }
0358 
0359 bool WeatherItem::operator<( const AbstractDataPluginItem *other ) const
0360 {
0361     const WeatherItem *weatherItem = qobject_cast<const WeatherItem *>(other);
0362     if( weatherItem ) {
0363         return ( priority() > weatherItem->priority() );
0364     }
0365     else {
0366         return false;
0367     }
0368 }
0369 
0370 QString WeatherItem::stationName() const
0371 {
0372     return d->m_stationName;
0373 }
0374 
0375 void WeatherItem::setStationName( const QString& name )
0376 {
0377     if ( name != d->m_stationName ) {
0378         d->m_browserAction.setText( name );
0379         d->m_stationName = name;
0380         d->updateToolTip();
0381         d->updateLabels();
0382         emit stationNameChanged();
0383     }
0384 }
0385 
0386 WeatherData WeatherItem::currentWeather() const
0387 {
0388     return d->m_currentWeather;
0389 }
0390 
0391 void WeatherItem::setCurrentWeather( const WeatherData &weather )
0392 {
0393     d->m_currentWeather = weather;
0394     d->updateToolTip();
0395     d->updateLabels();
0396     emit updated();
0397     emit descriptionChanged();
0398     emit imageChanged();
0399     emit temperatureChanged();
0400 }
0401 
0402 QMap<QDate, WeatherData> WeatherItem::forecastWeather() const
0403 {
0404     return d->m_forecastWeather;
0405 }
0406 
0407 void WeatherItem::setForecastWeather( const QMap<QDate, WeatherData>& forecasts )
0408 {
0409     d->m_forecastWeather = forecasts;
0410 
0411     d->updateToolTip();
0412     emit updated();
0413 }
0414 
0415 void WeatherItem::addForecastWeather( const QList<WeatherData>& forecasts )
0416 {
0417     for( const WeatherData& data: forecasts ) {
0418         QDate date = data.dataDate();
0419         WeatherData other = d->m_forecastWeather.value( date );
0420         if ( !other.isValid() ) {
0421             d->m_forecastWeather.insert( date, data );
0422         }
0423         else if ( other.publishingTime() < data.publishingTime() ) {
0424             d->m_forecastWeather.remove( date );
0425             d->m_forecastWeather.insert( date, data );
0426         }
0427     }
0428 
0429     // Remove old items
0430     QDate const minDate = QDate::currentDate();
0431 
0432     QMap<QDate, WeatherData>::iterator it = d->m_forecastWeather.begin();
0433 
0434     while( it != d->m_forecastWeather.end() ) {
0435         if ( it.key() < minDate ) {
0436             it = d->m_forecastWeather.erase( it );
0437         } else {
0438             ++it;
0439         }
0440     }
0441 
0442     d->updateToolTip();
0443     emit updated();
0444 }
0445 
0446 quint8 WeatherItem::priority() const
0447 {
0448     return d->m_priority;
0449 }
0450 
0451 void WeatherItem::setPriority( quint8 priority )
0452 {
0453     d->m_priority = priority;
0454 }
0455 
0456 void WeatherItem::setSettings( const QHash<QString, QVariant>& settings )
0457 {
0458     if ( d->m_settings == settings ) {
0459         return;
0460     }
0461 
0462     d->m_settings = settings;
0463 
0464     d->updateToolTip();
0465     d->updateLabels();
0466     d->updateFavorite();
0467 }
0468 
0469 void WeatherItem::setMarbleWidget(MarbleWidget *widget)
0470 {
0471     d->m_marbleWidget = widget;
0472 }
0473 
0474 void WeatherItem::openBrowser()
0475 {
0476     if (d->m_marbleWidget) {
0477         PopupLayer *popup = d->m_marbleWidget->popupLayer();
0478         popup->setCoordinates( coordinate(), Qt::AlignRight | Qt::AlignVCenter );
0479         popup->setSize(QSizeF(630, 580)); // +10 pixels for the width
0480         popup->popup();
0481 
0482         QFile weatherHtmlFile(QStringLiteral(":/marble/weather/weather.html"));
0483         if ( !weatherHtmlFile.open(QIODevice::ReadOnly) ) {
0484             return;
0485         }
0486 
0487         QString templateHtml = weatherHtmlFile.readAll();
0488         popup->setContent( createFromTemplate(templateHtml) );
0489     }
0490 }
0491 
0492 QString WeatherItem::createFromTemplate(const QString &templateHtml)
0493 {
0494     QString html = templateHtml;
0495     QLocale locale = QLocale::system();
0496     html.replace("%city_name%", stationName());
0497 
0498     if (!d->m_currentWeather.iconSource().isEmpty()) {
0499         html.replace("%weather_situation%",
0500                      QLatin1String("<img src=\"file://") + d->m_currentWeather.iconSource() + QLatin1String("\" />"));
0501     } else {
0502         html.remove("%weather_situation%");
0503     }
0504 
0505     html.replace("%current_temp%", d->temperatureString());
0506     html.replace("%current_condition%", d->m_currentWeather.conditionString());
0507     html.replace("%wind_direction%", d->m_currentWeather.windDirectionString());
0508     html.replace("%wind_speed%", d->m_currentWeather.windSpeedString());
0509     html.replace("%humidity_level%", d->m_currentWeather.humidityString());
0510     html.replace("%publish_time%", d->m_currentWeather.publishingTime().toString());
0511 
0512     if(d->m_forecastWeather.size() < 1) {
0513         html.replace("%forecast_available%", "none");
0514     } else {
0515         html.replace("%forecast_available%", "block");
0516     }
0517 
0518     int forecastNumber = 0;
0519 
0520     for ( const WeatherData &forecast: d->m_forecastWeather ) {
0521         forecastNumber++;
0522         const QString suffix = QString::number(forecastNumber);
0523         QDate date = forecast.dataDate();
0524         html.replace(QLatin1String("%day_f") + suffix + QLatin1Char('%'),
0525                      locale.standaloneDayName(date.dayOfWeek()));
0526         html.replace(QLatin1String("%weather_situation_f") + suffix + QLatin1Char('%'),
0527                      QLatin1String("file://")+forecast.iconSource());
0528         html.replace(QLatin1String("%max_temp_f") + suffix + QLatin1Char('%'),
0529                       forecast.maxTemperatureString(WeatherData::Celsius));
0530         html.replace(QLatin1String("%min_temp_f") + suffix + QLatin1Char('%'),
0531                       forecast.minTemperatureString(WeatherData::Celsius));
0532         html.replace(QLatin1String("%condition_f") + suffix + QLatin1Char('%'), forecast.conditionString());
0533         html.replace(QLatin1String("%wind_direction_f") + suffix + QLatin1Char('%'), forecast.windDirectionString());
0534         html.replace(QLatin1String("%wind_speed_f") + suffix + QLatin1Char('%'), forecast.windSpeedString());
0535         html.replace(QLatin1String("%publish_time_f") + suffix + QLatin1Char('%'), forecast.publishingTime().toString());
0536     }
0537 
0538     return html;
0539 }
0540 
0541 QList<QAction*> WeatherItem::actions()
0542 {
0543     QList<QAction*> result;
0544     result << &d->m_browserAction;
0545 
0546     disconnect( &d->m_favoriteAction, SIGNAL(triggered()),
0547                 this,        SLOT(toggleFavorite()) );
0548     connect(    &d->m_favoriteAction, SIGNAL(triggered()),
0549                 this,        SLOT(toggleFavorite()) );
0550 
0551     result << &d->m_favoriteAction;
0552 
0553     return result;
0554 }
0555 
0556 QString WeatherItem::description() const
0557 {
0558     return d->m_currentWeather.toHtml( WeatherData::Celsius, WeatherData::kph, WeatherData::Bar );
0559 }
0560 
0561 QString WeatherItem::image() const
0562 {
0563     return d->m_currentWeather.iconSource();
0564 }
0565 
0566 double WeatherItem::temperature() const
0567 {
0568     return d->m_currentWeather.hasValidTemperature()
0569          ? d->m_currentWeather.temperature( WeatherData::Celsius )
0570          : 0.0;
0571 }
0572 
0573 } // namespace Marble
0574 
0575 #include "moc_WeatherItem.cpp"