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"