mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-06 03:29:44 -06:00
Add Notification window to the Launcher (#3595)
This commit is contained in:
@@ -28,6 +28,7 @@ set(HEADER_FILES
|
||||
include/backgroundimage.h
|
||||
include/filesystemaccess.h
|
||||
include/launcherwindow.h
|
||||
include/notificationwindow.h
|
||||
include/settingsdialog.h
|
||||
include/splitcombobox.h
|
||||
include/windowcolors.h
|
||||
@@ -59,6 +60,7 @@ set(SOURCE_FILES
|
||||
src/backgroundimage.cpp
|
||||
src/launcherwindow.cpp
|
||||
src/filesystemaccess.cpp
|
||||
src/notificationwindow.cpp
|
||||
src/settingsdialog.cpp
|
||||
src/splitcombobox.cpp
|
||||
src/windowcolors.cpp
|
||||
|
||||
42
apps/OpenSpace/ext/launcher/include/notificationwindow.h
Normal file
42
apps/OpenSpace/ext/launcher/include/notificationwindow.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2025 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __OPENSPACE_UI_LAUNCHER___NOTIFICATIONWINDOW___H__
|
||||
#define __OPENSPACE_UI_LAUNCHER___NOTIFICATIONWINDOW___H__
|
||||
|
||||
#include <QTextEdit>
|
||||
|
||||
#include <openspace/util/httprequest.h>
|
||||
#include <memory>
|
||||
|
||||
class NotificationWindow final : public QTextEdit {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit NotificationWindow(QWidget* parent);
|
||||
|
||||
private:
|
||||
std::unique_ptr<openspace::HttpMemoryDownload> _request;
|
||||
};
|
||||
|
||||
#endif // __OPENSPACE_UI_LAUNCHER___NOTIFICATIONWINDOW___H__
|
||||
@@ -119,6 +119,12 @@ LauncherWindow QComboBox#config:focus
|
||||
LauncherWindow QPushButton#settings:focus {
|
||||
outline: 2px solid rgb(61, 189, 238);
|
||||
}
|
||||
|
||||
LauncherWindow QTextEdit#notifications {
|
||||
background-color: #242424;
|
||||
color: #d7d7d7;
|
||||
}
|
||||
|
||||
/*
|
||||
* ProfileEdit
|
||||
*/
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "profile/profileedit.h"
|
||||
#include "backgroundimage.h"
|
||||
#include "notificationwindow.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "splitcombobox.h"
|
||||
#include <openspace/openspace.h>
|
||||
@@ -45,8 +46,10 @@
|
||||
using namespace openspace;
|
||||
|
||||
namespace {
|
||||
constexpr int ScreenWidth = 480;
|
||||
constexpr int ScreenHeight = 640;
|
||||
constexpr int MainScreenWidth = 480;
|
||||
constexpr int MainScreenHeight = 640;
|
||||
constexpr int FullScreenWidth = MainScreenWidth;
|
||||
constexpr int FullScreenHeight = 706;
|
||||
|
||||
constexpr int LeftRuler = 40;
|
||||
constexpr int TopRuler = 80;
|
||||
@@ -55,10 +58,12 @@ namespace {
|
||||
constexpr int SmallItemWidth = 100;
|
||||
constexpr int SmallItemHeight = SmallItemWidth / 4;
|
||||
|
||||
constexpr int NotificationShelfHeight = FullScreenHeight - MainScreenHeight;
|
||||
|
||||
constexpr int SettingsIconSize = 35;
|
||||
|
||||
namespace geometry {
|
||||
constexpr QRect BackgroundImage(0, 0, ScreenWidth, ScreenHeight);
|
||||
constexpr QRect BackgroundImage(0, 0, MainScreenWidth, MainScreenHeight);
|
||||
constexpr QRect LogoImage(LeftRuler, TopRuler, ItemWidth, ItemHeight);
|
||||
constexpr QRect ChooseLabel(LeftRuler + 10, TopRuler + 80, 151, 24);
|
||||
constexpr QRect ProfileBox(LeftRuler, TopRuler + 110, ItemWidth, ItemHeight);
|
||||
@@ -80,14 +85,19 @@ namespace {
|
||||
LeftRuler, TopRuler + 400, ItemWidth, ItemHeight
|
||||
);
|
||||
constexpr QRect VersionString(
|
||||
5, ScreenHeight - SmallItemHeight, ItemWidth, SmallItemHeight
|
||||
5, MainScreenHeight - SmallItemHeight, ItemWidth, SmallItemHeight
|
||||
);
|
||||
constexpr QRect SettingsButton(
|
||||
ScreenWidth - SettingsIconSize - 5,
|
||||
ScreenHeight - SettingsIconSize - 5,
|
||||
MainScreenWidth - SettingsIconSize - 5,
|
||||
MainScreenHeight - SettingsIconSize - 5,
|
||||
SettingsIconSize,
|
||||
SettingsIconSize
|
||||
);
|
||||
constexpr QRect NotificationShelf(
|
||||
0,
|
||||
MainScreenHeight,
|
||||
MainScreenWidth,
|
||||
NotificationShelfHeight);
|
||||
} // namespace geometry
|
||||
|
||||
|
||||
@@ -132,7 +142,7 @@ LauncherWindow::LauncherWindow(bool profileEnabled, const Configuration& globalC
|
||||
);
|
||||
|
||||
setWindowTitle("OpenSpace Launcher");
|
||||
setFixedSize(ScreenWidth, ScreenHeight);
|
||||
setFixedSize(FullScreenWidth, FullScreenHeight);
|
||||
setAutoFillBackground(false);
|
||||
|
||||
{
|
||||
@@ -163,6 +173,12 @@ LauncherWindow::LauncherWindow(bool profileEnabled, const Configuration& globalC
|
||||
logoImage->setPixmap(QPixmap(":/images/openspace-horiz-logo-small.png"));
|
||||
}
|
||||
|
||||
{
|
||||
NotificationWindow* notificationWindow = new NotificationWindow(centralWidget);
|
||||
notificationWindow->setGeometry(geometry::NotificationShelf);
|
||||
notificationWindow->show();
|
||||
}
|
||||
|
||||
//
|
||||
// Profile chooser
|
||||
//
|
||||
|
||||
223
apps/OpenSpace/ext/launcher/src/notificationwindow.cpp
Normal file
223
apps/OpenSpace/ext/launcher/src/notificationwindow.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2025 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#include "notificationwindow.h"
|
||||
|
||||
#include <openspace/openspace.h>
|
||||
#include <openspace/engine/settings.h>
|
||||
#include <openspace/util/httprequest.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <ghoul/misc/assert.h>
|
||||
#include <ghoul/misc/stringhelper.h>
|
||||
#include <QGuiApplication>
|
||||
#include <QStyleHints>
|
||||
#include <QTimer>
|
||||
#include <scn/scan.h>
|
||||
#include <date/date.h>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
using namespace openspace;
|
||||
|
||||
namespace {
|
||||
struct Entry {
|
||||
std::string date;
|
||||
std::string text;
|
||||
};
|
||||
|
||||
// Parses a single notification entry out of the list of lines
|
||||
Entry parseEntry(std::vector<std::string>::const_iterator& curr) {
|
||||
ghoul_assert(!curr->empty(), "First line must not be empty");
|
||||
|
||||
std::string date = *curr;
|
||||
std::string text;
|
||||
do {
|
||||
curr++;
|
||||
|
||||
text += *curr;
|
||||
} while (!curr->empty());
|
||||
curr++;
|
||||
|
||||
return { std::move(date), std::move(text) };
|
||||
}
|
||||
|
||||
std::vector<Entry> parseEntries(const std::string& data) {
|
||||
std::vector<Entry> entries;
|
||||
|
||||
std::vector<std::string> lines = ghoul::tokenizeString(data, '\n');
|
||||
std::vector<std::string>::const_iterator curr = lines.cbegin();
|
||||
while (curr != lines.end()) {
|
||||
Entry e = parseEntry(curr);
|
||||
entries.push_back(std::move(e));
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::string formatEntry(const Entry& e, date::year_month_day lastStartedDate) {
|
||||
auto r = scn::scan<int, int, int>(e.date, "{}-{}-{}");
|
||||
ghoul_assert(r, "Invalid date");
|
||||
auto& [year, month, day] = r->values();
|
||||
const date::year_month_day ymd = date::year_month_day(
|
||||
date::year(year),
|
||||
date::month(month),
|
||||
date::day(day)
|
||||
);
|
||||
|
||||
QColor text = QGuiApplication::palette().text().color();
|
||||
text = text.darker();
|
||||
|
||||
if (date::sys_days(ymd) < date::sys_days(lastStartedDate)) {
|
||||
QColor text = QColor(120, 120, 120);
|
||||
return std::format(
|
||||
"<tr>"
|
||||
"<td width='15%'>"
|
||||
"<font color='#{2:x}{3:x}{4:x}'>{0}</font>"
|
||||
"</td>"
|
||||
"<td width='85%' align='left'>"
|
||||
"<font color='#{2:x}{3:x}{4:x}'>{1}</font>"
|
||||
"</td>"
|
||||
"</tr>",
|
||||
e.date, e.text, text.red(), text.green(), text.blue()
|
||||
);
|
||||
}
|
||||
else {
|
||||
return std::format(
|
||||
"<tr>"
|
||||
"<td width='15%'>"
|
||||
"{0}"
|
||||
"</td>"
|
||||
"<td width='85%' align='left'>"
|
||||
"{1}"
|
||||
"</td>"
|
||||
"</tr>",
|
||||
e.date, e.text
|
||||
);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
NotificationWindow::NotificationWindow(QWidget* parent)
|
||||
: QTextEdit(parent)
|
||||
{
|
||||
setAcceptRichText(true);
|
||||
setReadOnly(true);
|
||||
setFocusPolicy(Qt::NoFocus);
|
||||
setObjectName("notifications");
|
||||
|
||||
std::string URL = std::format(
|
||||
"https://raw.githubusercontent.com/OpenSpace/Notifications/refs/heads/master/"
|
||||
"{}.txt",
|
||||
OPENSPACE_IS_RELEASE_BUILD ? OPENSPACE_VERSION_NUMBER : "master"
|
||||
);
|
||||
|
||||
_request = std::make_unique<HttpMemoryDownload>(std::string(URL));
|
||||
_request->start(std::chrono::seconds(1));
|
||||
|
||||
// The download has a timeout of 1s, so after 1250ms we'll definitely have answer.
|
||||
constexpr int TimeOut = 1250;
|
||||
QTimer::singleShot(TimeOut, [this](){
|
||||
while (!_request->hasSucceeded() && !_request->hasFailed()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
}
|
||||
if (_request->hasFailed()) {
|
||||
LWARNINGC("Notification", "Failed to retrieve notification file");
|
||||
// The download has failed for some reason
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Get the downloaded data
|
||||
const std::vector<char>& data = _request->downloadedData();
|
||||
std::string notificationText = std::string(data.begin(), data.end());
|
||||
|
||||
// 2. Parse the retrieved data into entries
|
||||
std::vector<Entry> entries = parseEntries(notificationText);
|
||||
|
||||
// 3. Filter the entries to not show anything that is older than 6 months
|
||||
const date::year_month_day now = date::year_month_day(
|
||||
floor<date::days>(std::chrono::system_clock::now())
|
||||
);
|
||||
std::erase_if(
|
||||
entries,
|
||||
[now](const Entry& e) {
|
||||
auto r = scn::scan<int, int, int>(e.date, "{}-{}-{}");
|
||||
if (!r) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& [year, month, day] = r->values();
|
||||
|
||||
const date::year_month_day ymd = date::year_month_day(
|
||||
date::year(year),
|
||||
date::month(month),
|
||||
date::day(day)
|
||||
);
|
||||
|
||||
const std::chrono::days diff = date::sys_days(now) - date::sys_days(ymd);
|
||||
const bool older = diff.count() > (365 / 2);
|
||||
return older;
|
||||
}
|
||||
);
|
||||
|
||||
// 4. Format the entries into a table format
|
||||
Settings settings = loadSettings();
|
||||
// Picking a date as the default date that is far enough in the past
|
||||
date::year_month_day lastStart = date::year_month_day(
|
||||
date::year(2000),
|
||||
date::month(1),
|
||||
date::day(1)
|
||||
);
|
||||
if (settings.lastStartedDate.has_value()) {
|
||||
auto r = scn::scan<int, int, int>(*settings.lastStartedDate, "{}-{}-{}");
|
||||
if (r) {
|
||||
auto& [year, month, day] = r->values();
|
||||
|
||||
lastStart = date::year_month_day(
|
||||
date::year(year),
|
||||
date::month(month),
|
||||
date::day(day)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
std::string text = std::accumulate(
|
||||
entries.begin(),
|
||||
entries.end(),
|
||||
std::string(),
|
||||
[&lastStart](std::string t, const Entry& e) {
|
||||
return std::format(
|
||||
"{}{}",
|
||||
std::move(t), formatEntry(e, lastStart)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Add the HTML-like table attributes
|
||||
text = std::format("<table border='0'>{}</table>", std::move(text));
|
||||
|
||||
// 5. Set the text
|
||||
QString t = QString::fromStdString(text);
|
||||
setText(t);
|
||||
});
|
||||
}
|
||||
@@ -53,6 +53,7 @@
|
||||
#include <sgct/projection/nonlinearprojection.h>
|
||||
#include <sgct/user.h>
|
||||
#include <sgct/window.h>
|
||||
#include <date/date.h>
|
||||
#include <stb_image.h>
|
||||
#include <tracy/Tracy.hpp>
|
||||
#include <iostream>
|
||||
@@ -1086,6 +1087,12 @@ void setSgctDelegateFunctions() {
|
||||
int main(int argc, char* argv[]) {
|
||||
ZoneScoped;
|
||||
|
||||
// For debugging purposes: Enforce Light Mode in Qt
|
||||
// qputenv("QT_QPA_PLATFORM", "windows:darkmode=0");
|
||||
|
||||
// For debugging purposes: Enforce Dark Mode in Qt
|
||||
// qputenv("QT_QPA_PLATFORM", "windows:darkmode=1");
|
||||
|
||||
#ifdef OPENSPACE_BREAK_ON_FLOATING_POINT_EXCEPTION
|
||||
_clearfp();
|
||||
_controlfp(_controlfp(0, 0) & ~(_EM_ZERODIVIDE | _EM_OVERFLOW), _MCW_EM);
|
||||
@@ -1467,6 +1474,15 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
settings.configuration =
|
||||
isGeneratedWindowConfig ? "" : global::configuration->windowConfiguration;
|
||||
const date::year_month_day now = date::year_month_day(
|
||||
floor<date::days>(std::chrono::system_clock::now())
|
||||
);
|
||||
settings.lastStartedDate = std::format(
|
||||
"{}-{:0>2}-{:0>2}",
|
||||
static_cast<int>(now.year()),
|
||||
static_cast<unsigned>(now.month()),
|
||||
static_cast<unsigned>(now.day())
|
||||
);
|
||||
|
||||
saveSettings(settings, findSettings());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user