diff --git a/ionicons/LICENSE b/ionicons/LICENSE new file mode 100644 index 0000000..015269f --- /dev/null +++ b/ionicons/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Drifty (http://drifty.com/) + +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. diff --git a/ionicons/alert-circled.svg b/ionicons/alert-circled.svg new file mode 100644 index 0000000..89d1143 --- /dev/null +++ b/ionicons/alert-circled.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/ionicons/load-a.svg b/ionicons/load-a.svg new file mode 100644 index 0000000..469054b --- /dev/null +++ b/ionicons/load-a.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/ionicons/person-add.svg b/ionicons/person-add.svg new file mode 100644 index 0000000..74a4b84 --- /dev/null +++ b/ionicons/person-add.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/saori.pro b/saori.pro index a16a457..64e08b5 100644 --- a/saori.pro +++ b/saori.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += core gui network +QT += core gui network sql greaterThan(QT_MAJOR_VERSION, 4): QT += widgets @@ -29,7 +29,9 @@ saoriview.cpp \ saoriaddaccountdialog.cpp \ saoriaccount.cpp \ - saoriapplication.cpp + saoriapplication.cpp \ + saoricache.cpp \ + saoriviewentry.cpp HEADERS += \ saoriwindow.h \ @@ -38,7 +40,9 @@ saoriaddaccountdialog.h \ saoridef.h \ saoriaccount.h \ - saoriapplication.h + saoriapplication.h \ + saoricache.h \ + saoriviewentry.h FORMS += \ saoriwindow.ui \ diff --git a/saori.qrc b/saori.qrc index fb65ba0..7626b56 100644 --- a/saori.qrc +++ b/saori.qrc @@ -1,5 +1,7 @@ - + + saori_ja.qm + ionicons/heart.svg ionicons/trash-a.svg @@ -26,5 +28,12 @@ ionicons/chatbubble.svg ionicons/share.svg ionicons/reply.svg + saori.svg + ionicons/load-a.svg + ionicons/alert-circled.svg + ionicons/person-add.svg + + + saoristyle.css diff --git a/saori.svg b/saori.svg new file mode 100644 index 0000000..3a6af8d --- /dev/null +++ b/saori.svg @@ -0,0 +1,53 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/saori_ja.qm b/saori_ja.qm new file mode 100644 index 0000000..beedf00 --- /dev/null +++ b/saori_ja.qm Binary files differ diff --git a/saori_ja.ts b/saori_ja.ts index 7e676a5..461f047 100644 --- a/saori_ja.ts +++ b/saori_ja.ts @@ -6,47 +6,47 @@ Add new Account - + アカウントを追加 Instance URL: - + インスタンスURL: Get Access token: - + アクセストークンを取得: Open Authorize page: - + 認証ページを開く: Check - + 確認 Get! - + 取得! Authorize Code: - + 認証コード: Open WebBrowser - + ブラウザを開く Account: - + アカウント: @@ -57,33 +57,84 @@ - + Pin - + ピン - + Notify - + 通知 - + Auto reload - + 自動更新 - + Newest - + 最新 - + Reload + 更新 + + + + Toot + トゥート + + + + <div>%1</div><div>%2</div><div>%3</div><div>email : %4</div><div>%5</div><div>version : %6</div> - - Toot + + boosted by: %1 + %1 さんがブースト + + + + + created at : + 投稿日時 : + + + + following: + フォロー: + + + + followers: + フォロワー: + + + + %1 mentioned your status. + %1 さんが言及 + + + + %1 boosted your status. + %1 さんがブースト + + + + %1 favourited your status. + %1 さんがお気に入りに追加 + + + + %1 followed you. + %1 さんにフォローされました + + + + %1 @@ -91,113 +142,128 @@ SaoriWindow - SaoriWindow + Saori + SaoriWindow - + &File ファイル(&F) - + &View 表示(&V) - + &Edit 編集(&E) - + &Help ヘルプ(&H) - + Timeline &list タイムラインリスト(&l) - + Auto reload Interval (minutes) - + List リスト - + Account - + Instance - + &Quit - + Show/Hide Timeline &List - + &Open Timeline - + &Add Account - + &Remove Account - + + &TabbedView mode + + + + + T&iled + + + + Accounts アカウント - + home ホーム - + local ローカルタイムライン - + public 連合タイムライン - + notifications 通知 - + + instance + + + + Instances インスタンス - Infomation - 情報 + 情報 diff --git a/saoriaccount.cpp b/saoriaccount.cpp index 201ba0f..a9009bd 100644 --- a/saoriaccount.cpp +++ b/saoriaccount.cpp @@ -25,6 +25,8 @@ ***/ #include "saoriaccount.h" +#include "saoriapplication.h" +#include "saoriview.h" #include "saoridef.h" SaoriAccount::SaoriAccount(const QString accountName, Saoridon *instance, const QString accsessToken, QObject *parent) : QObject(parent) @@ -64,21 +66,16 @@ void SaoriAccount::getAccountInfomation() { - auto manager = new QNetworkAccessManager(); QNetworkRequest request = createHearder(); request.setUrl(QUrl(m_instance->instance().url() + SAORI_MASTODON_APIPATH_ACCOUNTS + "/verify_credentials")); - auto *reply = manager->get(request); - connect(manager,&QNetworkAccessManager::finished,[=](){ - if (reply->NoError == QNetworkReply::NoError) { + auto *reply = SaoriApplication::saori()->manager->get(request); + connect(reply,&QNetworkReply::finished,[=](){ + if (reply->error() == QNetworkReply::NoError) { QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object(); - QMap info; - for(auto it = json.begin();it != json.end();it ++) { - info[it.key()] = it.value().toString(); - } - m_accountInfo = info; + m_accountInfo = json; emit accountInfomationChanged(); } - manager->deleteLater(); + reply->deleteLater(); }); return; } @@ -90,7 +87,7 @@ return request; } -const QString SaoriAccount::accountInfo(const QString key) +const QJsonValue SaoriAccount::accountInfo(const QString key) { return m_accountInfo[key]; } @@ -106,3 +103,23 @@ return tl; } + +void SaoriAccount::getTimelineData(const QString timeline, const QUrlQuery query) +{ + // TODO max_id、since_id、limitの処理を追加すべし。 + QUrl url = instance()->timelineUrl(timeline); + if (url.isEmpty()) return; + QNetworkRequest request = createHearder(); + QUrlQuery q = query; + q.addQueryItem("limit","10"); + request.setUrl(instance()->addQuery(url,q)); + auto *reply = SaoriApplication::saori()->manager->get(request); + connect(reply,&QNetworkReply::finished,[=](){ + if (reply->error() == QNetworkReply::NoError) { + QByteArray data = reply->readAll(); + emit apiData(timeline,data); + } + reply->deleteLater(); + }); + return; +} diff --git a/saoriaccount.h b/saoriaccount.h index 6adbb17..61faf51 100644 --- a/saoriaccount.h +++ b/saoriaccount.h @@ -32,8 +32,8 @@ #include #include #include -#include #include +#include class SaoriAccount : public QObject { @@ -48,23 +48,26 @@ const QString name(); void setName(const QString name); Saoridon *instance(); - const QString accountInfo(const QString key); + const QJsonValue accountInfo(const QString key); const QStringList timelineList(); +public slots: + void getTimelineData(const QString timeline,const QUrlQuery query); + protected: void getAccountInfomation(); QNetworkRequest createHearder(); - void requestGET(QUrl url,QNetworkAccessManager *manager); protected: QString m_name; QString m_accessToken; Saoridon *m_instance; - QMap m_accountInfo; + QJsonObject m_accountInfo; signals: void accessTokenChanged(QString); void accountInfomationChanged(); + void apiData(const QString timeline,const QByteArray data); public slots: }; diff --git a/saoriaddaccountdialog.cpp b/saoriaddaccountdialog.cpp index 828f04a..a26ea72 100644 --- a/saoriaddaccountdialog.cpp +++ b/saoriaddaccountdialog.cpp @@ -29,8 +29,8 @@ #include "saoridef.h" #include "ui_saoriaddaccountdialog.h" #include -#include -#include +#include +#include #include #include #include @@ -49,7 +49,7 @@ ui->getAccessToken_pushButton->setEnabled(false); ui->authorizeCode_lineEdit->setEnabled(false); connect(ui->instanceUrl_comboBox,&QComboBox::editTextChanged,[this](QString) { - m_instance = SaoriApplication::findInstance(ui->instanceUrl_comboBox->currentText()); + m_instance = SaoriApplication::findInstance(QUrl(ui->instanceUrl_comboBox->currentText())); if (m_instance != nullptr) ui->openWebBrowser_pushButton->setEnabled(true); else ui->openWebBrowser_pushButton->setEnabled(false); }); @@ -65,7 +65,8 @@ m_instance = SaoriApplication::findInstance(ui->instanceUrl_comboBox->currentText()); if (m_instance == nullptr) return; if (m_instance->clientId().isEmpty() || m_instance->clientSecret().isEmpty()) { - qDebug() << "regist"; + // FIXED インスタンスは記録されているがclientが登録されていない状態。 + return; } QDesktopServices::openUrl(m_instance->getAuthorizedUrl()); QString text = ui->instanceUrl_comboBox->currentText(); @@ -85,24 +86,23 @@ if (token.isEmpty()) return; m_account = new SaoriAccount("check",m_instance,token); - auto manager = new QNetworkAccessManager(); QEventLoop event; - connect(manager,&QNetworkAccessManager::finished,&event,&QEventLoop::quit); QUrlQuery params; QNetworkRequest request; - QString instance = m_instance->instanceInfo("uri"); + QString instance = m_instance->instanceInfo("uri").toString(); request.setRawHeader("Authorization","Bearer " + token.toLatin1()); request.setUrl(QUrl(m_instance->instance().url() + SAORI_MASTODON_APIPATH_ACCOUNTS + "/verify_credentials")); - auto reply = manager->get(request); + auto reply = SaoriApplication::saori()->manager->get(request); + connect(reply,&QNetworkReply::finished,&event,&QEventLoop::quit); event.exec(); if (reply->error() != QNetworkReply::NoError) { - manager->deleteLater(); + reply->deleteLater(); return; } QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object(); QString account = json["acct"].toString(); - manager->deleteLater(); + reply->deleteLater(); m_account->setName(account + "@" + instance); ui->account_lineEdit->setText(m_account->name()); @@ -110,3 +110,22 @@ return; } + +void SaoriAddAccountDialog::on_check_pushButton_clicked() +{ + if (SaoriApplication::findInstance(QUrl(ui->instanceUrl_comboBox->currentText())) == nullptr) { + auto instance = new Saoridon(QUrl(ui->instanceUrl_comboBox->currentText()),this); + connect(instance,&Saoridon::instanceInfomationChanged,this,[=](){ + if (!instance->instanceInfo("uri").toString().isEmpty()) { + instance->clientRedistration(); + if (!instance->clientSecret().isEmpty()) { + instance->setParent(SaoriApplication::saori()); + SaoriApplication::getInstanceList()->append(instance); + ui->instanceUrl_comboBox->addItem(instance->instance().toString()); + ui->instanceUrl_comboBox->setEditText(instance->instance().toString()); + ui->openWebBrowser_pushButton->setEnabled(true); + } + } + }); + } +} diff --git a/saoriaddaccountdialog.h b/saoriaddaccountdialog.h index ad5c13c..a94021e 100644 --- a/saoriaddaccountdialog.h +++ b/saoriaddaccountdialog.h @@ -52,6 +52,8 @@ void on_getAccessToken_pushButton_clicked(); + void on_check_pushButton_clicked(); + private: Ui::SaoriAddAccountDialog *ui; }; diff --git a/saoriaddaccountdialog.ui b/saoriaddaccountdialog.ui index 11d3d82..6647133 100644 --- a/saoriaddaccountdialog.ui +++ b/saoriaddaccountdialog.ui @@ -66,7 +66,7 @@ - + Check diff --git a/saoriapplication.cpp b/saoriapplication.cpp index 2fdd9d7..6def0c8 100644 --- a/saoriapplication.cpp +++ b/saoriapplication.cpp @@ -41,27 +41,41 @@ setOrganizationDomain(SAORI_QSETTINGS_DOMAIN); setApplicationName(SAORI_QSETTINGS_APPLICATION); + manager = new QNetworkAccessManager; + m_config = new QSettings(SAORI_QSETTINGS_DOMAIN,SAORI_QSETTINGS_APPLICATION,this); loadSettings(); QDir dir; - if (!dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))) { - // CacheLocation is not writable. + if (dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))) { + m_cacheDirectory = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + } else { + m_cacheDirectory = dir; } + if (dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::DataLocation))) { + m_dataDirectory = QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); + } else { + m_dataDirectory = dir; + } + m_db = QSqlDatabase::addDatabase("QSQLITE"); + m_db.setDatabaseName(m_dataDirectory.absolutePath() + "/" + SAORI_SQLFILE); + m_db.open(); + + m_cache = new SaoriCache(this); } SaoriApplication::~SaoriApplication() { saveInstancesSettings(); saveAccountsSettings(); + m_db.close(); } Saoridon *SaoriApplication::findInstance(QUrl instance) { for(int i = 0;i < getInstanceList()->count();i ++) { if (getInstanceList()->at(i)->instance() == instance) return getInstanceList()->at(i); - break; } return nullptr; } @@ -70,7 +84,6 @@ { for(int i = 0;i < getAccountList()->count();i ++) { if (getAccountList()->at(i)->name() == account) return getAccountList()->at(i); - break; } return nullptr; } @@ -90,6 +103,31 @@ return m_self; } +QDir SaoriApplication::cacheDirectory() +{ + return m_cacheDirectory; +} + +QDir SaoriApplication::dataDirectory() +{ + return m_dataDirectory; +} + +QSqlDatabase *SaoriApplication::database() +{ + return &m_db; +} + +SaoriCache *SaoriApplication::cache() +{ + return m_cache; +} + +QSettings *SaoriApplication::setting() +{ + return m_config; +} + void SaoriApplication::loadSettings() { m_config->beginGroup("instances"); @@ -124,7 +162,7 @@ m_config->remove("instances"); m_config->beginGroup("instances"); for(int i = 0;i < m_instanceList.count();i ++) { - m_config->beginGroup(QCryptographicHash::hash(m_instanceList[i]->instance().toString().toLatin1(),QCryptographicHash::Md5)); + m_config->beginGroup(QCryptographicHash::hash(m_instanceList[i]->instance().toString().toLatin1(),QCryptographicHash::Md5).toHex()); m_config->setValue("url",m_instanceList[i]->instance()); m_config->setValue("clientId",m_instanceList[i]->clientId()); m_config->setValue("clientSecret",m_instanceList[i]->clientSecret()); diff --git a/saoriapplication.h b/saoriapplication.h index 9d73de0..62ac0bd 100644 --- a/saoriapplication.h +++ b/saoriapplication.h @@ -30,8 +30,12 @@ #include #include #include +#include #include #include +#include +#include +#include class SaoriApplication : public QApplication { @@ -44,12 +48,24 @@ static QList * getInstanceList(); static QList * getAccountList(); static SaoriApplication * saori(); + QDir cacheDirectory(); + QDir dataDirectory(); + QSqlDatabase * database(); + SaoriCache * cache(); + QSettings * setting(); + +public: + QNetworkAccessManager * manager; protected: QSettings *m_config; QList m_instanceList; QList m_accountList; static SaoriApplication *m_self; + QDir m_cacheDirectory; + QDir m_dataDirectory; + QSqlDatabase m_db; + SaoriCache *m_cache; protected: void loadSettings(); diff --git a/saoricache.cpp b/saoricache.cpp new file mode 100644 index 0000000..5bfd93b --- /dev/null +++ b/saoricache.cpp @@ -0,0 +1,145 @@ +/*** + +The MIT License + +Copyright (c) 2018 Teppei Tamra (TAM) + +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 "saoricache.h" +#include "saoriapplication.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * table file_cache + * url(text unique) + * filename(text) + * size(integer) + * timestamp(integer) + * + */ + +QList SaoriCache::m_nowloading; + +SaoriCache::SaoriCache(QObject *parent) : QObject(parent) +{ + migration(); +} + +void SaoriCache::removeFileCache(const QUrl url) +{ + QSqlQuery query(*SaoriApplication::saori()->database()); + query.prepare("DELETE from file_cache WHERE url=?;"); + query.addBindValue(url.toString()); + query.exec(); + QFile localfile(urlToFilename(url)); + if (localfile.exists()) localfile.remove(); +} + +void SaoriCache::reloadFileCache(const QUrl url) +{ + removeFileCache(url); + download(url); +} + +const QString SaoriCache::fileCache(const QUrl url) +{ + if (isCached(url)) return urlToFilename(url); + else download(url); + return QString(":/icons/ionicons/load-a.svg"); +} + +bool SaoriCache::migration() +{ + if (SaoriApplication::saori()->database()->tables().contains("file_cache")) return true; + QSqlQuery query(*SaoriApplication::saori()->database()); + return query.exec("CREATE TABLE file_cache(url TEXT UNIQUE,filename TEXT,size INTEGER,timestamp INTEGER);"); +} + +void SaoriCache::download(const QUrl url) +{ + if (m_nowloading.contains(url)) return; + if (isCached(url)) return; + m_nowloading.append(url); + QNetworkRequest request; + request.setUrl(url); + auto *reply = SaoriApplication::saori()->manager->get(request); + connect(reply,&QNetworkReply::finished,this,[=](){ + if (reply->error() == QNetworkReply::NoError) { + QFile localfile(urlToFilename(url)); + if (localfile.exists()) localfile.remove(); + localfile.open(QIODevice::WriteOnly); + localfile.write(reply->readAll()); + // TODO 100MBのファイルに100MBのメモリを使うのは如何なものか。 + qint64 size = localfile.size(); + localfile.close(); + QSqlQuery query(*SaoriApplication::saori()->database()); + query.prepare("INSERT INTO file_cache(url,filename,size,timestamp) values(?,?,?,?);"); + query.addBindValue(url.toString()); + query.addBindValue(urlToFilename(url)); + query.addBindValue(size); + query.addBindValue(QDateTime::currentSecsSinceEpoch()); + query.exec(); + emit downloaded(url); + } else { + QSqlQuery query(*SaoriApplication::saori()->database()); + query.prepare("INSERT INTO file_cache(url,filename,size,timestamp) values(?,?,?,?);"); + query.addBindValue(url.toString()); + query.addBindValue(":/icons/ionicons/alert-circled.svg"); + query.addBindValue("-1"); + query.addBindValue(QDateTime::currentSecsSinceEpoch()); + query.exec(); + emit downloaded(url); + } + m_nowloading.removeAll(url); + reply->deleteLater(); + }); +} + +bool SaoriCache::isCached(const QUrl url) +{ + QSqlQuery query(*SaoriApplication::saori()->database()); + query.prepare("SELECT size from file_cache WHERE url=?;"); + query.addBindValue(url.toString()); + query.exec(); + if (query.next()) { + if (query.value(0).toInt() >= 0) return true; + } + return false; +} + +const QString SaoriCache::urlToFilename(QUrl url) +{ + QString dir = QString::fromUtf8(QCryptographicHash::hash(url.adjusted(QUrl::RemovePath).toString().toLatin1(),QCryptographicHash::Md5).toHex()); + QString file = QString::fromUtf8(QCryptographicHash::hash(url.path().toLatin1(),QCryptographicHash::Md5).toHex()); + QDir d; + d.mkpath(SaoriApplication::saori()->cacheDirectory().absolutePath() + "/" + dir); + return QString(SaoriApplication::saori()->cacheDirectory().absolutePath() + "/" + dir + "/" + file); +} diff --git a/saoricache.h b/saoricache.h new file mode 100644 index 0000000..834e139 --- /dev/null +++ b/saoricache.h @@ -0,0 +1,59 @@ +/*** + +The MIT License + +Copyright (c) 2018 Teppei Tamra (TAM) + +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 SAORICACHE_H +#define SAORICACHE_H + +#include +#include +#include + +class SaoriCache : public QObject +{ + Q_OBJECT +public: + explicit SaoriCache(QObject *parent = nullptr); + void removeFileCache(const QUrl url); + void reloadFileCache(const QUrl url); + const QString fileCache(const QUrl url); + +protected: + static QList m_nowloading; + +protected: + bool migration(); + void download(const QUrl url); + bool isCached(const QUrl url); + static const QString urlToFilename(QUrl url); + const QString localfileName(const QUrl url); + +signals: + void downloaded(const QUrl); + +public slots: +}; + +#endif // SAORICACHE_H diff --git a/saoridef.h b/saoridef.h index 234bd60..9873a59 100644 --- a/saoridef.h +++ b/saoridef.h @@ -35,12 +35,15 @@ #define SAORI_QSETTINGS_DOMAIN "net-p.org" #define SAORI_QSETTINGS_APPLICATION "Saori" +#define SAORI_SQLFILE "saoridatastore.sqlite" + #define SAORI_MASTODON_APIPATH_APPS "/api/v1/apps" #define SAORI_MASTODON_APIPATH_AUTHORIZE "/oauth/authorize" #define SAORI_MASTODON_APIPATH_TOKEN "/oauth/token" #define SAORI_MASTODON_APIPATH_INSTANCE "/api/v1/instance" #define SAORI_MASTODON_APIPATH_ACCOUNTS "/api/v1/accounts" -#define SAORI_MASTODON_APIPATH_TIMELINE "/api/v1/timeline" +#define SAORI_MASTODON_APIPATH_TIMELINE "/api/v1/timelines" +#define SAORI_MASTODON_APIPATH_NOTIFICATION "/api/v1/notifications" #endif // SAORIDEF_H diff --git a/saoridon.cpp b/saoridon.cpp index 7a9f86b..dd99481 100644 --- a/saoridon.cpp +++ b/saoridon.cpp @@ -25,11 +25,13 @@ ***/ #include "saoridon.h" +#include "saoriapplication.h" #include "saoridef.h" -#include -#include +#include +#include #include -#include + +#include #include #include #include @@ -74,9 +76,7 @@ bool Saoridon::clientRedistration() { - auto manager = new QNetworkAccessManager(); QEventLoop event; - connect(manager,SIGNAL(finished(QNetworkReply*)),&event,SLOT(quit())); QUrlQuery params; params.addQueryItem("client_name",SAORI_CLIENT_NAME); params.addQueryItem("redirect_uris",SAORI_CLIENT_REDIRECT_URI); @@ -85,10 +85,11 @@ QNetworkRequest request; request.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("application/x-www-form-urlencoded")); request.setUrl(QUrl(instance().url() + SAORI_MASTODON_APIPATH_APPS)); - auto *reply = manager->post(request,params.toString().toLatin1()); + auto *reply = SaoriApplication::saori()->manager->post(request,params.toString().toLatin1()); + connect(reply,&QNetworkReply::finished,&event,&QEventLoop::quit); event.exec(); if (reply->error() != QNetworkReply::NoError) { - manager->deleteLater(); + reply->deleteLater(); return false; } QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object(); @@ -97,7 +98,7 @@ if (id.isEmpty() || secret.isEmpty()) return false; setClientId(id); setClientSecret(secret); - manager->deleteLater(); + reply->deleteLater(); return true; } @@ -119,9 +120,7 @@ const QString Saoridon::getAccessToken(const QString code) { - auto manager = new QNetworkAccessManager(); QEventLoop event; - connect(manager,&QNetworkAccessManager::finished,&event,&QEventLoop::quit); QUrlQuery params; params.addQueryItem("grant_type","authorization_code"); params.addQueryItem("redirect_uri","urn:ietf:wg:oauth:2.0:oob"); @@ -131,46 +130,41 @@ QNetworkRequest request; request.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("application/x-www-form-urlencoded")); request.setUrl(QUrl(instance().url() + SAORI_MASTODON_APIPATH_TOKEN)); - auto *reply = manager->post(request,params.toString().toLatin1()); + auto *reply = SaoriApplication::saori()->manager->post(request,params.toString().toLatin1()); + connect(reply,&QNetworkReply::finished,&event,&QEventLoop::quit); event.exec(); if (reply->error() != QNetworkReply::NoError) { - manager->deleteLater(); + reply->deleteLater(); return QString(); } QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object(); QString token = json["access_token"].toString(); - manager->deleteLater(); + reply->deleteLater(); return token; } void Saoridon::getInstanceInfomation() { - auto manager = new QNetworkAccessManager(); QNetworkRequest request; request.setUrl(QUrl(instance().url() + SAORI_MASTODON_APIPATH_INSTANCE)); - auto *reply = manager->get(request); - connect(manager,&QNetworkAccessManager::finished,[=](){ - if (reply->NoError == QNetworkReply::NoError) { - QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object(); - QMap info; - for (auto it = json.begin();it != json.end();it ++) { - info[it.key()] = it.value().toString(); - } - m_instanceInfo = info; + auto *reply = SaoriApplication::saori()->manager->get(request); + connect(reply,&QNetworkReply::finished,[=](){ + if (reply->error() == QNetworkReply::NoError) { + m_instanceInfo = QJsonDocument::fromJson(reply->readAll()).object(); emit instanceInfomationChanged(); } - manager->deleteLater(); + reply->deleteLater(); }); m_timelineMap.clear(); m_timelineMap["home"] = QUrl(instance().url() + SAORI_MASTODON_APIPATH_TIMELINE + "/home"); - m_timelineMap["local"] = QUrl(instance().url() + SAORI_MASTODON_APIPATH_TIMELINE + "/local"); + m_timelineMap["local"] = QUrl(instance().url() + SAORI_MASTODON_APIPATH_TIMELINE + "/public?local=1"); m_timelineMap["public"] = QUrl(instance().url() + SAORI_MASTODON_APIPATH_TIMELINE + "/public"); - m_timelineMap["notifications"] = QUrl(instance().url() + SAORI_MASTODON_APIPATH_TIMELINE + "/notifications"); + m_timelineMap["notifications"] = QUrl(instance().url() + SAORI_MASTODON_APIPATH_NOTIFICATION + "/"); return; } -const QString Saoridon::instanceInfo(const QString key) +const QJsonValue Saoridon::instanceInfo(const QString key) { return m_instanceInfo[key]; } @@ -180,3 +174,24 @@ return m_timelineMap[timeline]; } +const QUrl Saoridon::addQuery(const QUrl url, const QString key, const QString value) +{ + QUrl newurl = url; + QUrlQuery q(url.query()); + q.addQueryItem(key,value); + newurl.setQuery(q); + return newurl; +} + +const QUrl Saoridon::addQuery(const QUrl url, const QUrlQuery query) +{ + QUrl newurl = url; + QUrlQuery q(url.query()); + auto list = query.queryItems(); + list += q.queryItems(); + q.setQueryItems(list); + newurl.setQuery(q); + return newurl; + +} + diff --git a/saoridon.h b/saoridon.h index 6b23229..428da85 100644 --- a/saoridon.h +++ b/saoridon.h @@ -30,6 +30,7 @@ #include #include #include +#include class Saoridon : public QObject { @@ -49,8 +50,10 @@ bool clientRedistration(); const QUrl getAuthorizedUrl(); const QString getAccessToken(const QString code); - const QString instanceInfo(const QString key); + const QJsonValue instanceInfo(const QString key); const QUrl timelineUrl(const QString timeline); + static const QUrl addQuery(const QUrl url,const QString key,const QString value); + static const QUrl addQuery(const QUrl url,const QUrlQuery query); protected: void getInstanceInfomation(); @@ -59,7 +62,7 @@ QUrl m_instance; QString m_clientId; QString m_clientSecret; - QMap m_instanceInfo; + QJsonObject m_instanceInfo; QMap m_timelineMap; signals: diff --git a/saoristyle.css b/saoristyle.css new file mode 100644 index 0000000..2daa4c0 --- /dev/null +++ b/saoristyle.css @@ -0,0 +1,51 @@ +div.user_info { + font-size:small; +} + +div.created_at { + font-size:small; + text-align:right; +} + +div.content { + float:none; + margin-left:68px; +} + +div.media { + text-align:center; +} + +div.reblogger { + font-size:small; + text-align:right; +} + +div.reblogger img { + float:none; +} + +span.media_preview { + float:left; +} + +span.display_name { + font-size:large; +} + +span.acct { + color:gray; +} + +div.account img { + float:left; +} + +div.notification_type { + font-size:large; + float:none; +} + +div.notification_type img { + float:left; +} diff --git a/saoriview.cpp b/saoriview.cpp index 9aa6d06..f09164b 100644 --- a/saoriview.cpp +++ b/saoriview.cpp @@ -26,7 +26,16 @@ #include "saoriview.h" #include "ui_saoriview.h" +#include +#include +#include +#include +#include +#include #include +#include +#include +#include QList SaoriView::m_viewList; @@ -38,6 +47,29 @@ m_viewname = view; m_account = account; m_viewList.append(this); + m_maxid = 0; + + if (m_viewname == "instance") { + m_entries.append(new SaoriViewEntry(0,ui->scrollAreaWidgetContents)); + m_entries.at(0)->setContent(instanceInfoParser(account)); + ui->scrollAreaWidgetContents->layout()->addWidget(m_entries.at(0)); + return; + } + + auto saoriaccount = SaoriApplication::saori()->findAccount(m_account); + if (saoriaccount) { + connect(saoriaccount,&SaoriAccount::apiData,this,&SaoriView::recived); + saoriaccount->getTimelineData(view,QUrlQuery()); + connect(ui->scrollArea->verticalScrollBar(),&QScrollBar::valueChanged,this,[=](int p){ + if (ui->scrollArea->verticalScrollBar()->maximum() == p) { + if (m_entries.count()) { + QUrlQuery q; + q.addQueryItem("max_id",QString::number(m_entries.last()->id())); + saoriaccount->getTimelineData(view,q); + } + } + }); + } } SaoriView::~SaoriView() @@ -54,7 +86,240 @@ return nullptr; } -void SaoriView::recived(QMap result) +void SaoriView::reload() { - qDebug() << result; + auto saoriaccount = SaoriApplication::saori()->findAccount(m_account); + if (saoriaccount == nullptr) return; + if (m_entries.count() == 0) m_maxid = 0; + else { + m_maxid = m_entries.first()->id(); + } + QUrlQuery query; + query.addQueryItem("since_id",QString::number(m_maxid)); + saoriaccount->getTimelineData(m_viewname,query); +} + +const QString SaoriView::instanceInfoParser(const QString instance) +{ + auto i = SaoriApplication::findInstance(QUrl(instance)); + if (!i) return QString(); + QString result; + result = QString(htmlDiv("instance_title","%1") + + htmlDiv("instance_uri","%2") + + htmlDiv("instance_description","%3") + + htmlDiv("instance_email","email : %4") + + htmlDiv("instance_contact_account","%5") + + htmlDiv("instance_version","Version : %6") + ).arg(i->instanceInfo("title").toString(), + i->instanceInfo("uri").toString(), + i->instanceInfo("description").toString(), + i->instanceInfo("email").toString(), + accountParser(i->instanceInfo("contact_account").toObject()), + i->instanceInfo("version").toString()); + return result; +} + +const QString SaoriView::statusParser(const QJsonObject json) +{ + QString result; + if (json.isEmpty()) return QString(); + + QDateTime dt = QDateTime::fromString(json["created_at"].toString(),"yyyy-MM-ddTHH:mm:ss.zzzZ"); + dt.setTimeSpec(Qt::UTC); + + result = (!json["reblog"].isNull()) ? + (statusParser(json["reblog"].toObject()) + + htmlDiv("reblogger",htmlHr() + + htmlImg("mavatar",json["account"].toObject()["avatar"].toString()) + + tr("boosted by: %1").arg(json["account"].toObject()["display_name"].toString())) + ) : + (htmlDiv("status",accountParser(json["account"].toObject()) + + htmlDiv("created_at",tr("created at :") + + dt.toLocalTime().toString()) + + htmlDiv("content",contentParser(json["content"].toString()))) + ); + if (json["reblog"].isNull()) { + QString media; + for (auto j:json["media_attachments"].toArray()) { + media += mediaParser(j.toObject()); + } + result += htmlDiv("media",media); + } + return result; +} + +const QString SaoriView::accountParser(const QJsonObject json) +{ + QString result; + if (json.isEmpty()) return QString(); + result = htmlDiv("account", + htmlSpan("avatar", + htmlHr() + + htmlImg("avatar",json["avatar"].toString())) + + htmlSpan("display_name", + json["display_name"].toString()) + + " " + + htmlSpan("acct","@" + json["acct"].toString()) + + htmlDiv("user_info", + tr(" following: ") + + QString::number(json["following_count"].toInt()) + + tr(" following: ") + + QString::number(json["followers_count"].toInt())) + ); + return result; +} + +const QString SaoriView::mediaParser(const QJsonObject json) +{ + QString result; + result = htmlSpan("media_preview", + json["type"].toString() == "image" ? + htmlAnc("media:" + json["url"].toString(),htmlImg("media",json["preview_url"].toString())) : + htmlImg("media",json["preview_url"].toString())); + return result; +} + +const QString SaoriView::contentParser(const QString content) +{ + QString result; + auto l = content.split("
"); + for (auto s:l) { + result += "
"; + int i = 0; + for (auto c:s) { + if (c != ' ') break; + result += " "; + } + result += s.right(s.count() - i); + } + result = result.right(result.count() - 6); + + l = result.split("

"); + result = ""; + for (auto s:l) { + result += "

"; + int i = 0; + for (auto c:s) { + if (c != ' ') break; + result += " "; + } + result += s.right(s.count() - i); + } + result = result.right(result.count() - 3); + + return result; +} + +const QString SaoriView::notificationParser(const QJsonObject json) +{ + QString result; + if (json.isEmpty()) return QString(); + + QDateTime dt = QDateTime::fromString(json["created_at"].toString(),"yyyy-MM-ddTHH:mm:ss.zzzZ"); + dt.setTimeSpec(Qt::UTC); + + QStringList type; + type << "mention" << "reblog" << "favourite" << "follow"; + QString title; + switch (type.indexOf(json["type"].toString())) { + case 0: + title += htmlImg("mavatar",":/icons/ionicons/chatbubbles.svg"); + title += tr("%1 mentioned your status.").arg(json["account"].toObject()["display_name"].toString()); + break; + case 1: + title += htmlImg("mavatar",":/icons/ionicons/share.svg"); + title += tr("%1 boosted your status.").arg(json["account"].toObject()["display_name"].toString()); + break; + case 2: + title += htmlImg("mavatar",":/icons/ionicons/heart.svg"); + title += tr("%1 favourited your status.").arg(json["account"].toObject()["display_name"].toString()); + break; + case 3: + title += htmlImg("mavatar",":/icons/ionicons/person-add.svg"); + title += tr("%1 followed you.").arg(json["account"].toObject()["display_name"].toString()); + break; + default: + break; + } + + result = htmlDiv("notification", + htmlDiv("notification_type",title + htmlHr()) + + htmlDiv("created_at",tr("created at :") + dt.toLocalTime().toString()) + + statusParser(json["status"].toObject()) + + htmlDiv("reblogger",htmlHr() + + htmlImg("mavatar",json["account"].toObject()["avatar"].toString()) + + tr("%1 (@%2)").arg(json["account"].toObject()["display_name"].toString(), + json["account"].toObject()["acct"].toString()))); + return result; +} + +const QString SaoriView::htmlDiv(const QString divclass, const QString text) +{ + return QString("

\n%2\n
\n").arg(divclass,text); +} + +const QString SaoriView::htmlSpan(const QString spanclass, const QString text) +{ + return QString("%2").arg(spanclass,text); +} + +const QString SaoriView::htmlImg(const QString type, const QString url) +{ + return QString("").arg(type,url); +} + +const QString SaoriView::htmlAnc(const QString link, const QString text) +{ + return QString("%2").arg(link,text); +} + +const QString SaoriView::htmlHr() +{ + return QString("
"); +} + +void SaoriView::recived(const QString timeline,const QByteArray data) +{ + if (timeline != m_viewname) return; + QJsonArray json = QJsonDocument::fromJson(data).array(); + for (auto j:json) { + int i = 0; + SaoriViewEntry *entry = nullptr; + for(;m_entries.count() > i;i ++) { + if (m_entries.at(i)->id() == j.toObject()["id"].toString().toLongLong()) { + entry = m_entries.at(i); + break; + } + if (m_entries.at(i)->id() < j.toObject()["id"].toString().toLongLong()) break; + } + if (entry == nullptr) { + entry = new SaoriViewEntry(j.toObject()["id"].toString().toLongLong(),ui->scrollAreaWidgetContents); + connect(entry,&SaoriViewEntry::anchorClicked,this,&SaoriView::linkClicked); + m_entries.insert(i,entry); + qobject_cast(ui->scrollAreaWidgetContents->layout())->insertWidget(i,entry); + if (m_viewname == "notifications") entry->setContent(notificationParser(j.toObject())); + else entry->setContent(statusParser(j.toObject())); + } + } + if (m_entries.count()) { + if (m_entries.first()->id() > m_maxid) reload(); + } +} + +void SaoriView::linkClicked(const QUrl &url) +{ + if (url.toString().left(6) == "media:") { + QUrl u(url.toString().mid(6)); + emit openMediaView(u); + } +} + +void SaoriView::on_pushButton_newest_clicked() +{ + ui->scrollArea->verticalScrollBar()->setValue(0); +} + +void SaoriView::on_pushButton_reload_clicked() +{ + reload(); } diff --git a/saoriview.h b/saoriview.h index 2f793be..60540f0 100644 --- a/saoriview.h +++ b/saoriview.h @@ -28,7 +28,10 @@ #define SAORIVIEW_H #include -#include +#include +#include +#include +#include namespace Ui { class SaoriView; @@ -46,13 +49,39 @@ protected: QString m_viewname; QString m_account; + QList m_entries; + qlonglong m_maxid; + static QList m_viewList; +protected: + void reload(); + const QString instanceInfoParser(const QString instance); + const QString statusParser(const QJsonObject json); + const QString accountParser(const QJsonObject json); + const QString mediaParser(const QJsonObject json); + const QString contentParser(const QString content); + const QString notificationParser(const QJsonObject json); + + static const QString htmlDiv(const QString divclass,const QString text); + static const QString htmlSpan(const QString spanclass,const QString text); + static const QString htmlImg(const QString type,const QString url); + static const QString htmlAnc(const QString link,const QString text); + static const QString htmlHr(); + + public slots: - void recived(QMap result); + void recived(const QString timeline,const QByteArray data); + void linkClicked(const QUrl &url); private: Ui::SaoriView *ui; + +signals: + void openMediaView(const QUrl); +private slots: + void on_pushButton_newest_clicked(); + void on_pushButton_reload_clicked(); }; #endif // SAORIVIEW_H diff --git a/saoriview.ui b/saoriview.ui index e4b2987..e8fc732 100644 --- a/saoriview.ui +++ b/saoriview.ui @@ -16,18 +16,42 @@ + + + 0 + 0 + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustIgnored + true + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + 0 0 - 307 + 293 316 + + + 0 + 0 + + @@ -38,7 +62,7 @@ 0 - + Pin @@ -52,7 +76,7 @@ - + Notify @@ -66,7 +90,7 @@ - + Auto reload @@ -80,7 +104,7 @@ - + Newest @@ -91,7 +115,7 @@ - + Reload @@ -115,7 +139,7 @@ - + 0 diff --git a/saoriviewentry.cpp b/saoriviewentry.cpp new file mode 100644 index 0000000..64265d4 --- /dev/null +++ b/saoriviewentry.cpp @@ -0,0 +1,142 @@ +/*** + +The MIT License + +Copyright (c) 2018 Teppei Tamra (TAM) + +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 "saoriviewentry.h" +#include "saoriapplication.h" +#include "saoricache.h" +#include +#include +#include + +SaoriViewEntry::SaoriViewEntry(qlonglong id, QWidget *parent) : + QTextBrowser(parent) +{ + setReadOnly(true); + setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); + setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); + setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Minimum); + setOpenLinks(false); + setOpenExternalLinks(false); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_id = id; + connect(SaoriApplication::saori()->cache(),&SaoriCache::downloaded,this,&SaoriViewEntry::downloaded); +} + +void SaoriViewEntry::setContent(const QString content) +{ + m_original = content; + document()->clear(); + QStringList imgs; + for (int i = 0;(i = m_original.indexOf("cache()->fileCache(QUrl(i.mid(p + 1))); + } else { + imageurl = i.mid(p + 1); + } + m_urlmap[i] = imageurl; + //qDebug() << imageurl; + QImage img(imageurl); + imageResizer(i.left(p),img); + document()->addResource(QTextDocument::ImageResource,QUrl("img:" + imageurl),QVariant(img)); + } + setText(designedText()); + // QTextBrowserのサイズを確定させるトリック。 + QResizeEvent e(size(),size()); + resizeEvent(&e); +} + +qlonglong SaoriViewEntry::id() +{ + return m_id; +} + +void SaoriViewEntry::resizeEvent(QResizeEvent *e) +{ + QTextBrowser::resizeEvent(e); + document()->setTextWidth(qreal(e->size().width())); + setMinimumHeight(document()->size().height() + 5); +} + +const QString SaoriViewEntry::designedText() +{ + QString result; + result += ""; + result += ""; + result += imageReplacer(); + result += ""; + return result; +} + +const QString SaoriViewEntry::imageReplacer() +{ + QString result = m_original; + for (auto k:m_urlmap.keys()) { + result.replace(QString("cache()->fileCache(url); + setContent(m_original); + for (auto i = m_urlmap.begin();i != m_urlmap.end();i ++) { + if (i.value().left(2) == ":/") { + if (i.key().left(2) != ":/") { + return; + } + } + } + disconnect(SaoriApplication::saori()->cache(),&SaoriCache::downloaded,this,&SaoriViewEntry::downloaded); +} diff --git a/saoriviewentry.h b/saoriviewentry.h new file mode 100644 index 0000000..737e5a2 --- /dev/null +++ b/saoriviewentry.h @@ -0,0 +1,59 @@ +/*** + +The MIT License + +Copyright (c) 2018 Teppei Tamra (TAM) + +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 SAORIVIEWENTRY_H +#define SAORIVIEWENTRY_H + +#include +#include +#include + +class SaoriViewEntry : public QTextBrowser +{ + Q_OBJECT + +public: + SaoriViewEntry(qlonglong id,QWidget *parent = nullptr); + void setContent(const QString content); + qlonglong id(); + +protected: + virtual void resizeEvent(QResizeEvent *e); + const QString designedText(); + const QString imageReplacer(); + void imageResizer(const QString type,QImage &image); + +protected: + qlonglong m_id; + QString m_original; + QMap m_urlmap; + +protected slots: + void downloaded(const QUrl url); + +}; + +#endif // SAORIVIEWENTRY_H diff --git a/saoriwindow.cpp b/saoriwindow.cpp index df44ea7..16ece61 100644 --- a/saoriwindow.cpp +++ b/saoriwindow.cpp @@ -25,6 +25,7 @@ ***/ #include +#include #include "saoriwindow.h" #include "ui_saoriwindow.h" #include "saoriapplication.h" @@ -45,19 +46,77 @@ delete ui; } +QPair SaoriWindow::getTimelineTitle(const QString timeline) +{ + // trick for translation. + QStringList trtl; + QString tlText,icon; + trtl << "home" << "local" << "public" << "notifications" << "instance"; + switch (trtl.indexOf(timeline)) { + case 0: + tlText = tr("home"); + icon = ":/icons/ionicons/chatbubbles.svg"; + break; + case 1: + tlText = tr("local"); + icon = ":/icons/ionicons/chatbubble.svg"; + break; + case 2: + tlText = tr("public"); + icon = ":/icons/ionicons/earth.svg"; + break; + case 3: + tlText = tr("notifications"); + icon = ":/icons/ionicons/alert.svg"; + break; + case 4: + tlText = tr("instance"); + icon = ""; + break; + default: + tlText = timeline; + break; + } + + return QPair(tlText,icon); + +} + void SaoriWindow::openView(const QStringList viewName) { if (auto view = SaoriView::findView(viewName.at(1),viewName.at(0))) { ui->mdiArea->setActiveSubWindow(qobject_cast(view->parent())); return; } - QString title = viewName.at(1) + ":" + viewName.at(0); + QString title = getTimelineTitle(viewName.at(1)).first + ":" + viewName.at(0); auto view = new SaoriView(viewName.at(1),viewName.at(0)); auto sub = ui->mdiArea->addSubWindow(view); + connect(view,&SaoriView::openMediaView,this,&SaoriWindow::openMediaView); sub->setWindowTitle(title); + sub->setWindowIcon(QIcon(getTimelineTitle(viewName.at(1)).second)); sub->show(); } +void SaoriWindow::openMediaView(const QUrl url) +{ + // TODO 専用のwidget作った方がいいでしょう。 + auto view = new QLabel(); + auto sub = ui->mdiArea->addSubWindow(view); + sub->setWindowTitle(url.toString()); + QString image = SaoriApplication::saori()->cache()->fileCache(url); + if (image.mid(0,2) == ":/") + connect(SaoriApplication::saori()->cache(),&SaoriCache::downloaded,this,[=](const QUrl i){ + if (i.toString() == url.toString()) { + view->setPixmap(SaoriApplication::saori()->cache()->fileCache(url)); + sub->resize(view->pixmap()->size()); + } + }); + view->setPixmap(image); + view->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); + sub->show(); + sub->resize(view->pixmap()->size()); +} + void SaoriWindow::openAccountDialog() @@ -67,6 +126,7 @@ dialog->m_account->setParent(QApplication::instance()); SaoriApplication::getAccountList()->append(dialog->m_account); } + updateTimelineList(); delete dialog; } @@ -80,35 +140,11 @@ item->setIcon(0,QIcon(":/icons/ionicons/person.svg")); accountItem->addChild(item); for (auto tl:SaoriApplication::getAccountList()->at(i)->timelineList()) { - // trick for translation. - QStringList trtl; - QString tlText,icon; - trtl << "home" << "local" << "public" << "notifications"; - switch (trtl.indexOf(tl)) { - case 0: - tlText = tr("home"); - icon = ":/icons/ionicons/chatbubbles.svg"; - break; - case 1: - tlText = tr("local"); - icon = ":/icons/ionicons/chatbubble.svg"; - break; - case 2: - tlText = tr("public"); - icon = ":/icons/ionicons/earth.svg"; - break; - case 3: - tlText = tr("notifications"); - icon = ":/icons/ionicons/alert.svg"; - break; - default: - tlText = tl; - break; - } - auto tlItem = new QTreeWidgetItem(item,QStringList() << tlText + auto title = getTimelineTitle(tl); + auto tlItem = new QTreeWidgetItem(item,QStringList() << title.first << SaoriApplication::getAccountList()->at(i)->name() << tl); - if (!icon.isEmpty()) tlItem->setIcon(0,QIcon(icon)); + if (!title.second.isEmpty()) tlItem->setIcon(0,QIcon(title.second)); item->addChild(tlItem); } } @@ -116,7 +152,7 @@ for (int i = 0;i < SaoriApplication::getInstanceList()->count();i ++) { auto *item = new QTreeWidgetItem(instanceItem,QStringList() << SaoriApplication::getInstanceList()->at(i)->instance().toString()); instanceItem->addChild(item); - item->addChild(new QTreeWidgetItem(item,QStringList() << tr("Infomation"))); + //item->addChild(new QTreeWidgetItem(item,QStringList() << tr("Infomation"))); } } @@ -124,7 +160,27 @@ { auto account = SaoriApplication::findAccount(item->text(1)); if (account) { - qDebug() << account->instance()->timelineUrl(item->text(2)); openView(QStringList() << item->text(1) << item->text(2)); + return; } + auto instance = SaoriApplication::findInstance(QUrl(item->text(0))); + if (instance) { + openView(QStringList() << instance->instance().toString() << "instance"); + } +} + +void SaoriWindow::on_actionTabbedView_mode_toggled(bool arg1) +{ + if (ui->mdiArea->viewMode() == QMdiArea::TabbedView) { + if (arg1 == false) { + ui->mdiArea->setViewMode(QMdiArea::SubWindowView); + ui->actionTiled->setEnabled(true); + } + } else { + if (arg1 == true) { + ui->mdiArea->setViewMode(QMdiArea::TabbedView); + ui->actionTiled->setEnabled(false); + } + } + return; } diff --git a/saoriwindow.h b/saoriwindow.h index 58587ea..da84032 100644 --- a/saoriwindow.h +++ b/saoriwindow.h @@ -29,6 +29,8 @@ #include #include +#include +#include namespace Ui { class SaoriWindow; @@ -41,10 +43,14 @@ public: explicit SaoriWindow(QWidget *parent = 0); ~SaoriWindow(); + static QPair getTimelineTitle(const QString timeline); protected: void openView(const QStringList viewName); +public slots: + void openMediaView(const QUrl url); + protected slots: void openAccountDialog(); void updateTimelineList(); @@ -54,6 +60,8 @@ private slots: void on_timelineTree_itemDoubleClicked(QTreeWidgetItem *item, int); + void on_actionTabbedView_mode_toggled(bool arg1); + private: Ui::SaoriWindow *ui; }; diff --git a/saoriwindow.ui b/saoriwindow.ui index dff0541..ec66142 100644 --- a/saoriwindow.ui +++ b/saoriwindow.ui @@ -13,6 +13,10 @@ Saori + + + :/icons/saori.svg:/icons/saori.svg + false @@ -136,7 +140,7 @@ - + @@ -209,7 +213,9 @@ - + + + actionShow_Hide_Timeline_List