summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt15
-rw-r--r--MultiMC.cpp5
-rw-r--r--MultiMC.h7
-rw-r--r--config.h.in4
-rw-r--r--gui/MainWindow.cpp42
-rw-r--r--gui/MainWindow.h6
-rw-r--r--gui/MainWindow.ui44
-rw-r--r--logic/news/NewsChecker.cpp135
-rw-r--r--logic/news/NewsChecker.h105
-rw-r--r--logic/news/NewsEntry.cpp77
-rw-r--r--logic/news/NewsEntry.h65
11 files changed, 495 insertions, 10 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ab04c280..9ccd513d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -120,6 +120,11 @@ IF(${BIGENDIAN})
ENDIF(${BIGENDIAN})
+######## Set URLs ########
+
+SET(MultiMC_NEWS_RSS_URL "http://multimc.org/rss.xml" CACHE STRING "URL to fetch MultiMC's news RSS feed from.")
+
+
######## Set version numbers ########
SET(MultiMC_VERSION_MAJOR 1)
SET(MultiMC_VERSION_MINOR 0)
@@ -333,6 +338,12 @@ logic/updater/UpdateChecker.cpp
logic/updater/DownloadUpdateTask.h
logic/updater/DownloadUpdateTask.cpp
+# News System
+logic/news/NewsChecker.h
+logic/news/NewsChecker.cpp
+logic/news/NewsEntry.h
+logic/news/NewsEntry.cpp
+
# legacy instances
logic/LegacyInstance.h
logic/LegacyInstance.cpp
@@ -516,8 +527,8 @@ ADD_EXECUTABLE(MultiMC MACOSX_BUNDLE WIN32 main.cpp ${MULTIMC_RCS})
# Link
TARGET_LINK_LIBRARIES(MultiMC MultiMC_common)
TARGET_LINK_LIBRARIES(MultiMC_common xz-embedded unpack200 quazip libUtil libSettings libGroupView ${MultiMC_LINK_ADDITIONAL_LIBS})
-QT5_USE_MODULES(MultiMC Core Widgets Network Xml Concurrent ${MultiMC_QT_ADDITIONAL_MODULES})
-QT5_USE_MODULES(MultiMC_common Core Widgets Network Xml Concurrent ${MultiMC_QT_ADDITIONAL_MODULES})
+QT5_USE_MODULES(MultiMC Core Widgets Network Xml WebKit Concurrent ${MultiMC_QT_ADDITIONAL_MODULES})
+QT5_USE_MODULES(MultiMC_common Core Widgets Network Xml WebKit Concurrent ${MultiMC_QT_ADDITIONAL_MODULES})
ADD_DEPENDENCIES(MultiMC_common MultiMCLauncher JavaCheck)
################################ INSTALLATION AND PACKAGING ################################
diff --git a/MultiMC.cpp b/MultiMC.cpp
index 93524af7..4e06f558 100644
--- a/MultiMC.cpp
+++ b/MultiMC.cpp
@@ -18,6 +18,8 @@
#include "logic/lists/MinecraftVersionList.h"
#include "logic/lists/ForgeVersionList.h"
+#include "logic/news/NewsChecker.h"
+
#include "logic/InstanceLauncher.h"
#include "logic/net/HttpMetaCache.h"
@@ -162,6 +164,9 @@ MultiMC::MultiMC(int &argc, char **argv, const QString &root)
// initialize the updater
m_updateChecker.reset(new UpdateChecker());
+ // initialize the news checker
+ m_newsChecker.reset(new NewsChecker(NEWS_RSS_URL));
+
// and instances
auto InstDirSetting = m_settings->getSetting("InstanceDir");
m_instances.reset(new InstanceList(InstDirSetting->get().toString(), this));
diff --git a/MultiMC.h b/MultiMC.h
index 602a4673..9ad276ff 100644
--- a/MultiMC.h
+++ b/MultiMC.h
@@ -17,6 +17,7 @@ class QNetworkAccessManager;
class ForgeVersionList;
class JavaVersionList;
class UpdateChecker;
+class NewsChecker;
#if defined(MMC)
#undef MMC
@@ -89,6 +90,11 @@ public:
return m_updateChecker;
}
+ std::shared_ptr<NewsChecker> newsChecker()
+ {
+ return m_newsChecker;
+ }
+
std::shared_ptr<LWJGLVersionList> lwjgllist();
std::shared_ptr<ForgeVersionList> forgelist();
@@ -137,6 +143,7 @@ private:
std::shared_ptr<SettingsObject> m_settings;
std::shared_ptr<InstanceList> m_instances;
std::shared_ptr<UpdateChecker> m_updateChecker;
+ std::shared_ptr<NewsChecker> m_newsChecker;
std::shared_ptr<MojangAccountList> m_accounts;
std::shared_ptr<IconList> m_icons;
std::shared_ptr<QNetworkAccessManager> m_qnam;
diff --git a/config.h.in b/config.h.in
index b58dc322..aa604056 100644
--- a/config.h.in
+++ b/config.h.in
@@ -16,3 +16,7 @@
// This is printed on start to standard output
#define VERSION_STR "@MultiMC_VERSION_STRING@"
+// This is used to fetch the news RSS feed.
+// It defaults in CMakeLists.txt to "http://multimc.org/rss.xml"
+#define NEWS_RSS_URL "@MultiMC_NEWS_RSS_URL@"
+
diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp
index b55be903..42823fa5 100644
--- a/gui/MainWindow.cpp
+++ b/gui/MainWindow.cpp
@@ -75,6 +75,8 @@
#include "logic/updater/DownloadUpdateTask.h"
+#include "logic/news/NewsChecker.h"
+
#include "logic/net/URLConstants.h"
#include "logic/BaseInstance.h"
@@ -116,6 +118,17 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
renameButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
}
+ // Add the news label to the news toolbar.
+ {
+ newsLabel = new QToolButton();
+ newsLabel->setIcon(QIcon(":/icons/toolbar/news"));
+ newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel);
+ QObject::connect(MMC->newsChecker().get(), &NewsChecker::newsLoaded, this, &MainWindow::updateNewsLabel);
+ updateNewsLabel();
+ }
+
// Create the instance list widget
{
view = new KCategorizedView(ui->centralWidget);
@@ -252,6 +265,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
MMC->lwjgllist()->loadList();
}
+ MMC->newsChecker()->reloadNews();
+ updateNewsLabel();
+
// set up the updater object.
auto updater = MMC->updateChecker();
connect(updater.get(), &UpdateChecker::updateAvailable, this,
@@ -439,6 +455,30 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *ev)
return QMainWindow::eventFilter(obj, ev);
}
+void MainWindow::updateNewsLabel()
+{
+ auto newsChecker = MMC->newsChecker();
+ if (newsChecker->isLoadingNews())
+ {
+ newsLabel->setText(tr("Loading news..."));
+ newsLabel->setEnabled(false);
+ }
+ else
+ {
+ QList<NewsEntryPtr> entries = newsChecker->getNewsEntries();
+ if (entries.length() > 0)
+ {
+ newsLabel->setText(entries[0]->title);
+ newsLabel->setEnabled(true);
+ }
+ else
+ {
+ newsLabel->setText(tr("No news available."));
+ newsLabel->setEnabled(false);
+ }
+ }
+}
+
void MainWindow::updateAvailable(QString repo, QString versionName, int versionId)
{
UpdateDialog dlg;
@@ -729,7 +769,7 @@ void MainWindow::on_actionReportBug_triggered()
openWebPage(QUrl("http://multimc.myjetbrains.com/youtrack/dashboard#newissue=yes"));
}
-void MainWindow::on_actionNews_triggered()
+void MainWindow::on_actionMoreNews_triggered()
{
openWebPage(QUrl("http://multimc.org/posts.html"));
}
diff --git a/gui/MainWindow.h b/gui/MainWindow.h
index d6b7b845..f5b25006 100644
--- a/gui/MainWindow.h
+++ b/gui/MainWindow.h
@@ -85,7 +85,7 @@ slots:
void on_actionReportBug_triggered();
- void on_actionNews_triggered();
+ void on_actionMoreNews_triggered();
void on_mainToolBar_visibilityChanged(bool);
@@ -166,6 +166,8 @@ slots:
void changeActiveAccount();
void repopulateAccountsMenu();
+
+ void updateNewsLabel();
/*!
* Runs the DownloadUpdateTask and installs updates.
@@ -185,6 +187,8 @@ private:
MinecraftProcess *proc;
ConsoleWindow *console;
LabeledToolButton *renameButton;
+ QToolButton *changeIconButton;
+ QToolButton* newsLabel;
BaseInstance *m_selectedInstance;
QString m_currentInstIcon;
diff --git a/gui/MainWindow.ui b/gui/MainWindow.ui
index 82e3b05f..1a3f53cd 100644
--- a/gui/MainWindow.ui
+++ b/gui/MainWindow.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>688</width>
- <height>650</height>
+ <height>460</height>
</rect>
</property>
<property name="windowTitle">
@@ -72,7 +72,6 @@
<addaction name="actionSettings"/>
<addaction name="separator"/>
<addaction name="actionReportBug"/>
- <addaction name="actionNews"/>
<addaction name="actionAbout"/>
<addaction name="separator"/>
<addaction name="actionCAT"/>
@@ -121,6 +120,36 @@
<addaction name="separator"/>
<addaction name="actionDeleteInstance"/>
</widget>
+ <widget class="QToolBar" name="newsToolBar">
+ <property name="windowTitle">
+ <string>News Toolbar</string>
+ </property>
+ <property name="movable">
+ <bool>false</bool>
+ </property>
+ <property name="allowedAreas">
+ <set>Qt::BottomToolBarArea</set>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="floatable">
+ <bool>false</bool>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>BottomToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionMoreNews"/>
+ </widget>
<action name="actionAddInstance">
<property name="icon">
<iconset resource="../graphics.qrc">
@@ -229,19 +258,22 @@
<string>Open the bug tracker to report a bug with MultiMC.</string>
</property>
</action>
- <action name="actionNews">
+ <action name="actionMoreNews">
<property name="icon">
<iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/news</normaloff>:/icons/toolbar/news</iconset>
</property>
<property name="text">
- <string>News</string>
+ <string>More News</string>
+ </property>
+ <property name="iconText">
+ <string>More...</string>
</property>
<property name="toolTip">
- <string>Open the MultiMC dev blog to read news about MultiMC.</string>
+ <string>Open the MultiMC development blog to read more news about MultiMC.</string>
</property>
<property name="statusTip">
- <string>Open the MultiMC dev blog to read news about MultiMC.</string>
+ <string>Open the MultiMC development blog to read more news about MultiMC.</string>
</property>
</action>
<action name="actionAbout">
diff --git a/logic/news/NewsChecker.cpp b/logic/news/NewsChecker.cpp
new file mode 100644
index 00000000..8fc44fa9
--- /dev/null
+++ b/logic/news/NewsChecker.cpp
@@ -0,0 +1,135 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "NewsChecker.h"
+
+#include <QByteArray>
+#include <QDomDocument>
+
+#include <logger/QsLog.h>
+
+NewsChecker::NewsChecker(const QString& feedUrl)
+{
+ m_feedUrl = feedUrl;
+}
+
+void NewsChecker::reloadNews()
+{
+ // Start a netjob to download the RSS feed and call rssDownloadFinished() when it's done.
+ if (isLoadingNews())
+ {
+ QLOG_INFO() << "Ignored request to reload news. Currently reloading already.";
+ return;
+ }
+
+ QLOG_INFO() << "Reloading news.";
+
+ NetJob* job = new NetJob("News RSS Feed");
+ job->addNetAction(ByteArrayDownload::make(m_feedUrl));
+ QObject::connect(job, &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished);
+ QObject::connect(job, &NetJob::failed, this, &NewsChecker::rssDownloadFailed);
+ m_newsNetJob.reset(job);
+ job->start();
+}
+
+void NewsChecker::rssDownloadFinished()
+{
+ // Parse the XML file and process the RSS feed entries.
+ QLOG_DEBUG() << "Finished loading RSS feed.";
+
+ QByteArray data;
+ {
+ ByteArrayDownloadPtr dl = std::dynamic_pointer_cast<ByteArrayDownload>(m_newsNetJob->first());
+ data = dl->m_data;
+ m_newsNetJob.reset();
+ }
+
+ QDomDocument doc;
+ {
+ // Stuff to store error info in.
+ QString errorMsg = "Unknown error.";
+ int errorLine = -1;
+ int errorCol = -1;
+
+ // Parse the XML.
+ if (!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol))
+ {
+ QString fullErrorMsg = QString("Error parsing RSS feed XML. %s at %d:%d.").arg(errorMsg, errorLine, errorCol);
+ fail(fullErrorMsg);
+ return;
+ }
+ }
+
+ // If the parsing succeeded, read it.
+ QDomNodeList items = doc.elementsByTagName("item");
+ m_newsEntries.clear();
+ for (int i = 0; i < items.length(); i++)
+ {
+ QDomElement element = items.at(i).toElement();
+ NewsEntryPtr entry;
+ entry.reset(new NewsEntry());
+ QString errorMsg = "An unknown error occurred.";
+ if (NewsEntry::fromXmlElement(element, entry.get(), &errorMsg))
+ {
+ QLOG_DEBUG() << "Loaded news entry" << entry->title;
+ m_newsEntries.append(entry);
+ }
+ else
+ {
+ QLOG_WARN() << "Failed to load news entry at index" << i << ":" << errorMsg;
+ }
+ }
+
+ succeed();
+}
+
+void NewsChecker::rssDownloadFailed()
+{
+ // Set an error message and fail.
+ fail("Failed to load news RSS feed.");
+}
+
+
+QList<NewsEntryPtr> NewsChecker::getNewsEntries() const
+{
+ return m_newsEntries;
+}
+
+bool NewsChecker::isLoadingNews() const
+{
+ return m_newsNetJob.get() != nullptr;
+}
+
+QString NewsChecker::getLastLoadErrorMsg() const
+{
+ return m_lastLoadError;
+}
+
+void NewsChecker::succeed()
+{
+ m_lastLoadError = "";
+ QLOG_DEBUG() << "News loading succeeded.";
+ m_newsNetJob.reset();
+ emit newsLoaded();
+}
+
+void NewsChecker::fail(const QString& errorMsg)
+{
+ m_lastLoadError = errorMsg;
+ QLOG_DEBUG() << "Failed to load news:" << errorMsg;
+ m_newsNetJob.reset();
+ emit newsLoadingFailed(errorMsg);
+}
+
diff --git a/logic/news/NewsChecker.h b/logic/news/NewsChecker.h
new file mode 100644
index 00000000..820fe626
--- /dev/null
+++ b/logic/news/NewsChecker.h
@@ -0,0 +1,105 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <QString>
+#include <QList>
+
+#include <logic/net/NetJob.h>
+
+#include "NewsEntry.h"
+
+class NewsChecker : public QObject
+{
+ Q_OBJECT
+public:
+ /*!
+ * Constructs a news reader to read from the given RSS feed URL.
+ */
+ NewsChecker(const QString& feedUrl);
+
+ /*!
+ * Returns the error message for the last time the news was loaded.
+ * Empty string if the last load was successful.
+ */
+ QString getLastLoadErrorMsg() const;
+
+ /*!
+ * Returns true if the news has been loaded successfully.
+ */
+ bool isNewsLoaded() const;
+
+ //! True if the news is currently loading. If true, reloadNews() will do nothing.
+ bool isLoadingNews() const;
+
+ /*!
+ * Returns a list of news entries.
+ */
+ QList<NewsEntryPtr> getNewsEntries() const;
+
+ /*!
+ * Reloads the news from the website's RSS feed.
+ * If the news is already loading, this does nothing.
+ */
+ void Q_SLOT reloadNews();
+
+signals:
+ /*!
+ * Signal fired after the news has finished loading.
+ */
+ void newsLoaded();
+
+ /*!
+ * Signal fired after the news fails to load.
+ */
+ void newsLoadingFailed(QString errorMsg);
+
+protected slots:
+ void rssDownloadFinished();
+ void rssDownloadFailed();
+
+protected:
+ //! The URL for the RSS feed to fetch.
+ QString m_feedUrl;
+
+ //! List of news entries.
+ QList<NewsEntryPtr> m_newsEntries;
+
+ //! The network job to use to load the news.
+ NetJobPtr m_newsNetJob;
+
+ //! True if news has been loaded.
+ bool m_loadedNews;
+
+ /*!
+ * Gets the error message that was given last time the news was loaded.
+ * If the last news load succeeded, this will be an empty string.
+ */
+ QString m_lastLoadError;
+
+
+ /*!
+ * Emits newsLoaded() and sets m_lastLoadError to empty string.
+ */
+ void Q_SLOT succeed();
+
+ /*!
+ * Emits newsLoadingFailed() and sets m_lastLoadError to the given message.
+ */
+ void Q_SLOT fail(const QString& errorMsg);
+};
+
diff --git a/logic/news/NewsEntry.cpp b/logic/news/NewsEntry.cpp
new file mode 100644
index 00000000..4c940f2e
--- /dev/null
+++ b/logic/news/NewsEntry.cpp
@@ -0,0 +1,77 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "NewsEntry.h"
+
+#include <QDomNodeList>
+#include <QVariant>
+
+NewsEntry::NewsEntry(QObject* parent) :
+ QObject(parent)
+{
+ this->title = tr("Untitled");
+ this->content = tr("No content.");
+ this->link = "";
+ this->author = tr("Unknown Author");
+ this->pubDate = QDateTime::currentDateTime();
+}
+
+NewsEntry::NewsEntry(const QString& title, const QString& content, const QString& link, const QString& author, const QDateTime& pubDate, QObject* parent) :
+ QObject(parent)
+{
+ this->title = title;
+ this->content = content;
+ this->link = link;
+ this->author = author;
+ this->pubDate = pubDate;
+}
+
+/*!
+ * Gets the text content of the given child element as a QVariant.
+ */
+inline QString childValue(const QDomElement& element, const QString& childName, QString defaultVal="")
+{
+ QDomNodeList nodes = element.elementsByTagName(childName);
+ if (nodes.count() > 0)
+ {
+ QDomElement element = nodes.at(0).toElement();
+ return element.text();
+ }
+ else
+ {
+ return defaultVal;
+ }
+}
+
+bool NewsEntry::fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg)
+{
+ QString title = childValue(element, "title", tr("Untitled"));
+ QString content = childValue(element, "description", tr("No content."));
+ QString link = childValue(element, "link");
+ QString author = childValue(element, "dc:creator", tr("Unknown Author"));
+ QString pubDateStr = childValue(element, "pubDate");
+
+ // FIXME: For now, we're just ignoring timezones. We assume that all time zones in the RSS feed are the same.
+ QString dateFormat("ddd, dd MMM yyyy hh:mm:ss");
+ QDateTime pubDate = QDateTime::fromString(pubDateStr, dateFormat);
+
+ entry->title = title;
+ entry->content = content;
+ entry->link = link;
+ entry->author = author;
+ entry->pubDate = pubDate;
+ return true;
+}
+
diff --git a/logic/news/NewsEntry.h b/logic/news/NewsEntry.h
new file mode 100644
index 00000000..6bfa1adc
--- /dev/null
+++ b/logic/news/NewsEntry.h
@@ -0,0 +1,65 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <QString>
+#include <QDomElement>
+#include <QDateTime>
+
+#include <memory>
+
+class NewsEntry : public QObject
+{
+ Q_OBJECT
+
+public:
+ /*!
+ * Constructs an empty news entry.
+ */
+ explicit NewsEntry(QObject* parent=0);
+
+ /*!
+ * Constructs a new news entry.
+ * Note that content may contain HTML.
+ */
+ NewsEntry(const QString& title, const QString& content, const QString& link, const QString& author, const QDateTime& pubDate, QObject* parent=0);
+
+ /*!
+ * Attempts to load information from the given XML element into the given news entry pointer.
+ * If this fails, the function will return false and store an error message in the errorMsg pointer.
+ */
+ static bool fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg=0);
+
+
+ //! The post title.
+ QString title;
+
+ //! The post's content. May contain HTML.
+ QString content;
+
+ //! URL to the post.
+ QString link;
+
+ //! The post's author.
+ QString author;
+
+ //! The date and time that this post was published.
+ QDateTime pubDate;
+};
+
+typedef std::shared_ptr<NewsEntry> NewsEntryPtr;
+