diff --git a/Telegram/PrepareWin.bat b/Telegram/PrepareWin.bat index 4a1fa200f4..3a4b7051c9 100644 --- a/Telegram/PrepareWin.bat +++ b/Telegram/PrepareWin.bat @@ -1,11 +1,11 @@ @echo OFF set "AppVersionStrMajor=0.8" -set "AppVersion=8052" -set "AppVersionStrSmall=0.8.52" -set "AppVersionStr=0.8.52" -set "AppVersionStrFull=0.8.52.0" -set "DevChannel=0" +set "AppVersion=8053" +set "AppVersionStrSmall=0.8.53" +set "AppVersionStr=0.8.53" +set "AppVersionStrFull=0.8.53.0" +set "DevChannel=1" if %DevChannel% neq 0 goto preparedev diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings index eb93c89ae6..b145afa5f4 100644 --- a/Telegram/Resources/lang.strings +++ b/Telegram/Resources/lang.strings @@ -188,6 +188,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org "lng_settings_show_preview" = "Show message preview"; "lng_settings_use_windows" = "Use Windows notifications"; "lng_settings_sound_notify" = "Play sound"; +"lng_settings_include_muted" = "Include muted chats in the unread badge"; "lng_notification_preview" = "You have a new message"; @@ -386,6 +387,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org "lng_profile_files_header" = "Files overview"; "lng_profile_audios" = "{count:_not_used_|# voice message|# voice messages} »"; "lng_profile_audios_header" = "Voice messages overview"; +"lng_profile_shared_links" = "{count:_not_used_|# shared link|# shared links} »"; +"lng_profile_shared_links_header" = "Shared links overview"; "lng_profile_audio_files_header" = "Playlist"; "lng_profile_show_all_types" = "Show all types"; "lng_profile_copy_phone" = "Copy phone number"; @@ -443,6 +446,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org "lng_media_type_videos" = "Video files"; "lng_media_type_files" = "Files"; "lng_media_type_audios" = "Voice messages"; +"lng_media_type_links" = "Shared links"; "lng_media_open_with" = "Open With"; "lng_media_download" = "Download"; diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index bdc6e4276c..656a6e1636 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -1451,6 +1451,10 @@ dropdownMediaAudios: iconedButton(dropdownMediaDocuments) { icon: sprite(62px, 348px, 24px, 24px); downIcon: sprite(62px, 348px, 24px, 24px); } +dropdownMediaLinks: iconedButton(dropdownMediaDocuments) { + icon: sprite(372px, 414px, 24px, 24px); + downIcon: sprite(62px, 348px, 24px, 24px); +} dragFont: font(28px semibold); dragSubfont: font(20px semibold); @@ -2002,3 +2006,15 @@ playerDuration: 200; playlistHoverBg: #f2f2f2; playlistPadding: 10px; + +linksSearchMargin: margins(20px, 20px, 20px, 0px); +linksMaxWidth: 520px; +linksLetterFont: font(24px); +linksMargin: 5px; +linksBorder: 1px; +linksBorderColor: #eaeaea; +linksDateColor: #000; +linksDateMargin: 15px; + +linksPhotoCheck: sprite(184px, 196px, 16px, 16px); +linksPhotoChecked: sprite(168px, 196px, 16px, 16px); diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index b99ceee013..7b36392db4 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -1843,6 +1843,12 @@ namespace App { prepareCorners(BotKeyboardCorners, st::msgRadius, st::botKbBg); prepareCorners(BotKeyboardOverCorners, st::msgRadius, st::botKbOverBg); prepareCorners(BotKeyboardDownCorners, st::msgRadius, st::botKbDownBg); + prepareCorners(PhotoSelectOverlayCorners, st::msgRadius, st::overviewPhotoSelectOverlay); + + prepareCorners(DocRedCorners, st::msgRadius, st::mvDocRedColor); + prepareCorners(DocYellowCorners, st::msgRadius, st::mvDocYellowColor); + prepareCorners(DocGreenCorners, st::msgRadius, st::mvDocGreenColor); + prepareCorners(DocBlueCorners, st::msgRadius, st::mvDocBlueColor); prepareCorners(MessageInCorners, st::msgRadius, st::msgInBg, &st::msgInShadow); prepareCorners(MessageInSelectedCorners, st::msgRadius, st::msgInSelectBg, &st::msgInSelectShadow); diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 51995a5c5f..093b34395e 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -58,6 +58,12 @@ enum RoundCorners { BotKeyboardCorners, BotKeyboardOverCorners, BotKeyboardDownCorners, + PhotoSelectOverlayCorners, + + DocRedCorners, + DocYellowCorners, + DocGreenCorners, + DocBlueCorners, InShadowCorners, // for photos without bg InSelectedShadowCorners, diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 684d76561b..cb1abf4df5 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -673,8 +673,8 @@ void Application::checkMapVersion() { if (Local::oldMapVersion() < AppVersion) { if (Local::oldMapVersion()) { QString versionFeatures; - if (cDevVersion() && Local::oldMapVersion() < 8050) { - versionFeatures = QString::fromUtf8("\xe2\x80\x94 Bug fixes in Windows notifications\n\xe2\x80\x94 Fixed input methods on Linux (Fcitx and IBus)");// .replace('@', qsl("@") + QChar(0x200D)); + if (cDevVersion() && Local::oldMapVersion() < 8053) { + versionFeatures = QString::fromUtf8("\xe2\x80\x94 Include muted chats in the unread badge setting\n\xe2\x80\x94 Shared links overview and search in shared media");// .replace('@', qsl("@") + QChar(0x200D)); } else if (!cDevVersion() && Local::oldMapVersion() < 8052) { versionFeatures = lang(lng_new_version_minor).trimmed(); } diff --git a/Telegram/SourceFiles/art/sprite.png b/Telegram/SourceFiles/art/sprite.png index 648101a311..784f8a93a2 100644 Binary files a/Telegram/SourceFiles/art/sprite.png and b/Telegram/SourceFiles/art/sprite.png differ diff --git a/Telegram/SourceFiles/art/sprite_200x.png b/Telegram/SourceFiles/art/sprite_200x.png index f8d5d7f382..7ef912b1dc 100644 Binary files a/Telegram/SourceFiles/art/sprite_200x.png and b/Telegram/SourceFiles/art/sprite_200x.png differ diff --git a/Telegram/SourceFiles/autoupdater.cpp b/Telegram/SourceFiles/autoupdater.cpp index 87cdba1447..a84b3ca54f 100644 --- a/Telegram/SourceFiles/autoupdater.cpp +++ b/Telegram/SourceFiles/autoupdater.cpp @@ -16,13 +16,13 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014 John Preston, https://desktop.telegram.org */ -#ifndef TDESKTOP_DISABLE_AUTOUPDATE - #include "stdafx.h" #include "application.h" #include "pspecific.h" #include "autoupdater.h" +#ifndef TDESKTOP_DISABLE_AUTOUPDATE + #ifdef Q_OS_WIN typedef DWORD VerInt; typedef WCHAR VerChar; diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 6b9d80a957..974c4127d9 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -17,9 +17,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org */ #pragma once -static const int32 AppVersion = 8052; -static const wchar_t *AppVersionStr = L"0.8.52"; -static const bool DevVersion = false; +static const int32 AppVersion = 8053; +static const wchar_t *AppVersionStr = L"0.8.53"; +static const bool DevVersion = true; static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)"; static const wchar_t *AppName = L"Telegram Desktop"; @@ -81,6 +81,7 @@ enum { AutoSearchTimeout = 900, // 0.9 secs SearchPerPage = 50, SearchManyPerPage = 100, + LinksOverviewPerPage = 12, MediaOverviewStartPerPage = 5, MediaOverviewPreloadCount = 4, diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index e28b385aa0..f2c626da43 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -1700,7 +1700,6 @@ bool DialogsWidget::onSearchMessages(bool searchCache) { return false; } - void DialogsWidget::onNeedSearchMessages() { if (!onSearchMessages(true)) { _searchTimer.start(AutoSearchTimeout); diff --git a/Telegram/SourceFiles/gui/text.cpp b/Telegram/SourceFiles/gui/text.cpp index 9d3e3355c1..ef42afe582 100644 --- a/Telegram/SourceFiles/gui/text.cpp +++ b/Telegram/SourceFiles/gui/text.cpp @@ -20,6 +20,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "lang.h" +#include "pspecific.h" + #include namespace { @@ -788,6 +790,15 @@ void TextLink::onClick(Qt::MouseButton button) const { } } +void EmailLink::onClick(Qt::MouseButton button) const { + if (button == Qt::LeftButton || button == Qt::MiddleButton) { + QUrl url(qstr("mailto:") + _email); + if (!QDesktopServices::openUrl(url)) { + psOpenFile(url.toString(QUrl::FullyEncoded), true); + } + } +} + void MentionLink::onClick(Qt::MouseButton button) const { if (button == Qt::LeftButton || button == Qt::MiddleButton) { App::openUserByName(_tag.mid(1), true); diff --git a/Telegram/SourceFiles/gui/text.h b/Telegram/SourceFiles/gui/text.h index 30304541cb..3fd48128ef 100644 --- a/Telegram/SourceFiles/gui/text.h +++ b/Telegram/SourceFiles/gui/text.h @@ -369,11 +369,7 @@ public: return _email; } - void onClick(Qt::MouseButton button) const { - if (button == Qt::LeftButton || button == Qt::MiddleButton) { - QDesktopServices::openUrl(qsl("mailto:") + _email); - } - } + void onClick(Qt::MouseButton button) const; const QString &readable() const { return _email; diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index f3ae1a3c01..26a684d243 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -644,12 +644,24 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPmessage &msg, boo } const MTPMessageMedia *media = 0; + const QVector *entities = 0; switch (msg.type()) { - case mtpc_message: media = &msg.c_message().vmedia; break; + case mtpc_message: + media = &msg.c_message().vmedia; + entities = msg.c_message().has_entities() ? (&msg.c_message().ventities.c_vector().v) : 0; + break; } if (media) { existing->updateMedia(*media); } + if (entities && !existing->hasTextLinks()) { // index forwarded messages to links overview + existing->setText(qs(msg.c_message().vmessage), linksFromMTP(*entities)); + existing->initDimensions(0); + if (App::main()) App::main()->itemResized(existing); + if (existing->hasTextLinks()) { + existing->history()->addToOverview(existing, OverviewLinks); + } + } return (returnExisting || regged) ? existing : 0; } @@ -888,6 +900,24 @@ void History::createInitialDateBlock(const QDateTime &date) { push_front(dateBlock); // date block } +void History::addToOverview(HistoryItem *item, MediaOverviewType type) { + if (_overviewIds[type].constFind(item->id) == _overviewIds[type].cend()) { + _overview[type].push_back(item->id); + _overviewIds[type].insert(item->id, NullType()); + if (_overviewCount[type] > 0) ++_overviewCount[type]; + if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer, type); + } +} + +bool History::addToOverviewFront(HistoryItem *item, MediaOverviewType type) { + if (_overviewIds[type].constFind(item->id) == _overviewIds[type].cend()) { + _overview[type].push_front(item->id); + _overviewIds[type].insert(item->id, NullType()); + return true; + } + return false; +} + HistoryItem *History::doAddToBack(HistoryBlock *to, bool newBlock, HistoryItem *adding, bool newMsg) { if (!adding) { if (newBlock) delete to; @@ -928,31 +958,14 @@ HistoryItem *History::doAddToBack(HistoryBlock *to, bool newBlock, HistoryItem * HistoryMediaType mt = media->type(); MediaOverviewType t = mediaToOverviewType(mt); if (t != OverviewCount) { - if (_overviewIds[t].constFind(adding->id) == _overviewIds[t].cend()) { - _overview[t].push_back(adding->id); - _overviewIds[t].insert(adding->id, NullType()); - if (_overviewCount[t] > 0) ++_overviewCount[t]; - if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer, t); - } + addToOverview(adding, t); if (mt == MediaTypeDocument && static_cast(media)->document()->song()) { - t = OverviewAudioDocuments; - if (_overviewIds[t].constFind(adding->id) == _overviewIds[t].cend()) { - _overview[t].push_back(adding->id); - _overviewIds[t].insert(adding->id, NullType()); - if (_overviewCount[t] > 0) ++_overviewCount[t]; - if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer, t); - } + addToOverview(adding, OverviewAudioDocuments); } } } if (adding->hasTextLinks()) { - MediaOverviewType t = OverviewLinks; - if (_overviewIds[t].constFind(adding->id) == _overviewIds[t].cend()) { - _overview[t].push_back(adding->id); - _overviewIds[t].insert(adding->id, NullType()); - if (_overviewCount[t] > 0) ++_overviewCount[t]; - if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer, t); - } + addToOverview(adding, OverviewLinks); } if (adding->from()->id) { if (peer->chat) { @@ -1102,28 +1115,14 @@ void History::addToFront(const QVector &slice) { HistoryMediaType mt = media->type(); MediaOverviewType t = mediaToOverviewType(mt); if (t != OverviewCount) { - if (_overviewIds[t].constFind(item->id) == _overviewIds[t].cend()) { - _overview[t].push_front(item->id); - _overviewIds[t].insert(item->id, NullType()); - mask |= (1 << t); - } + if (addToOverviewFront(item, t)) mask |= (1 << t); if (mt == MediaTypeDocument && static_cast(media)->document()->song()) { - t = OverviewAudioDocuments; - if (_overviewIds[t].constFind(item->id) == _overviewIds[t].cend()) { - _overview[t].push_front(item->id); - _overviewIds[t].insert(item->id, NullType()); - mask |= (1 << t); - } + if (addToOverviewFront(item, OverviewAudioDocuments)) mask |= (1 << OverviewAudioDocuments); } } } if (item->hasTextLinks()) { - MediaOverviewType t = OverviewLinks; - if (_overviewIds[t].constFind(item->id) == _overviewIds[t].cend()) { - _overview[t].push_front(item->id); - _overviewIds[t].insert(item->id, NullType()); - mask |= (1 << t); - } + if (addToOverviewFront(item, OverviewLinks)) mask |= (1 << OverviewLinks); } if (item->from()->id) { if (lastAuthors) { // chats @@ -1348,7 +1347,7 @@ void History::setUnreadCount(int32 newUnreadCount, bool psUpdate) { App::histories().unreadFull += newUnreadCount - unreadCount; if (mute) App::histories().unreadMuted += newUnreadCount - unreadCount; unreadCount = newUnreadCount; - if (psUpdate) App::wnd()->updateCounter(); + if (psUpdate && (!mute || cIncludeMuted())) App::wnd()->updateCounter(); if (unreadBar) unreadBar->setCount(unreadCount); } } @@ -5273,6 +5272,12 @@ void HistoryMessage::setText(const QString &text, const LinksInText &links) { } } +void HistoryMessage::getTextWithLinks(QString &text, LinksInText &links) { + if (_text.isEmpty()) return; + links = _text.calcLinksInText(); + text = _text.original(); +} + void HistoryMessage::draw(QPainter &p, uint32 selection) const { textstyleSet(&(out() ? st::outTextStyle : st::inTextStyle)); diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index bcdcc1e3b3..3f1fe63c63 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -185,6 +185,8 @@ struct History : public QList { void addToBack(const QVector &slice); void createInitialDateBlock(const QDateTime &date); HistoryItem *doAddToBack(HistoryBlock *to, bool newBlock, HistoryItem *adding, bool newMsg); + void addToOverview(HistoryItem *item, MediaOverviewType type); + bool addToOverviewFront(HistoryItem *item, MediaOverviewType type); void newItemAdded(HistoryItem *item); void unregTyping(UserData *from); @@ -782,6 +784,8 @@ public: } virtual void setText(const QString &text, const LinksInText &links) { } + virtual void getTextWithLinks(QString &text, LinksInText &links) { + } virtual QString time() const { return QString(); } @@ -1181,6 +1185,10 @@ public: } ImagePtr replyPreview(); + WebPageData *webpage() { + return data; + } + private: WebPageData *data; TextLinkPtr _openl, _photol; @@ -1328,6 +1336,7 @@ public: HistoryMedia *getMedia(bool inOverview = false) const; void setMedia(const MTPmessageMedia &media); void setText(const QString &text, const LinksInText &links); + void getTextWithLinks(QString &text, LinksInText &links); QString time() const { return _time; diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index a1eb4837a3..abb7952db7 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -730,6 +730,14 @@ namespace { cSetSoundNotify(v == 1); } break; + case dbiIncludeMuted: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetIncludeMuted(v == 1); + } break; + case dbiDesktopNotify: { qint32 v; stream >> v; @@ -1281,7 +1289,7 @@ namespace { _writeMap(WriteMapFast); } - uint32 size = 13 * (sizeof(quint32) + sizeof(qint32)); + uint32 size = 14 * (sizeof(quint32) + sizeof(qint32)); size += sizeof(quint32) + _stringSize(cAskDownloadPath() ? QString() : cDownloadPath()); size += sizeof(quint32) + sizeof(qint32) + (cRecentEmojisPreload().isEmpty() ? cGetRecentEmojis().size() : cRecentEmojisPreload().size()) * (sizeof(uint64) + sizeof(ushort)); size += sizeof(quint32) + sizeof(qint32) + cEmojiVariants().size() * (sizeof(uint32) + sizeof(uint64)); @@ -1295,6 +1303,7 @@ namespace { data.stream << quint32(dbiReplaceEmojis) << qint32(cReplaceEmojis() ? 1 : 0); data.stream << quint32(dbiDefaultAttach) << qint32(cDefaultAttach()); data.stream << quint32(dbiSoundNotify) << qint32(cSoundNotify()); + data.stream << quint32(dbiIncludeMuted) << qint32(cIncludeMuted()); data.stream << quint32(dbiDesktopNotify) << qint32(cDesktopNotify()); data.stream << quint32(dbiNotifyView) << qint32(cNotifyView()); data.stream << quint32(dbiWindowsNotifications) << qint32(cWindowsNotifications()); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index cd103a0197..81ed01580b 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1183,6 +1183,7 @@ void MainWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) { case OverviewVideos: connect(_mediaType.addButton(new IconedButton(this, st::dropdownMediaVideos, lang(lng_media_type_videos))), SIGNAL(clicked()), this, SLOT(onVideosSelect())); break; case OverviewDocuments: connect(_mediaType.addButton(new IconedButton(this, st::dropdownMediaDocuments, lang(lng_media_type_files))), SIGNAL(clicked()), this, SLOT(onDocumentsSelect())); break; case OverviewAudios: connect(_mediaType.addButton(new IconedButton(this, st::dropdownMediaAudios, lang(lng_media_type_audios))), SIGNAL(clicked()), this, SLOT(onAudiosSelect())); break; + case OverviewLinks: connect(_mediaType.addButton(new IconedButton(this, st::dropdownMediaLinks, lang(lng_media_type_links))), SIGNAL(clicked()), this, SLOT(onLinksSelect())); break; } } } @@ -1203,6 +1204,9 @@ void MainWidget::itemRemoved(HistoryItem *item) { if (history.peer() == item->history()->peer) { history.itemRemoved(item); } + if (overview && overview->peer() == item->history()->peer) { + overview->itemRemoved(item); + } itemRemovedGif(item); if (!_toForward.isEmpty()) { SelectedItemSet::iterator i = _toForward.find(item->id); @@ -2240,9 +2244,13 @@ void MainWidget::sentDataReceived(uint64 randomId, const MTPmessages_SentMessage if (!text.isEmpty() && !links.isEmpty()) { item = App::histItemById(d.vid.v); if (item) { + bool was = item->hasTextLinks(); item->setText(text, links); item->initDimensions(0); itemResized(item); + if (!was && item->hasTextLinks()) { + item->history()->addToOverview(item, OverviewLinks); + } } } } @@ -2273,9 +2281,13 @@ void MainWidget::sentDataReceived(uint64 randomId, const MTPmessages_SentMessage //if (!text.isEmpty() && !links.isEmpty()) { // item = App::histItemById(d.vid.v); // if (item) { + // bool was = item->hasTextLinks(); // item->setText(text, links); // item->initDimensions(0); // itemResized(item); + // if (!was && item->hasTextLinks()) { + // item->history()->addToOverview(item, OverviewLinks); + // } // } //} } @@ -2544,6 +2556,11 @@ void MainWidget::onAudiosSelect() { _mediaType.hideStart(); } +void MainWidget::onLinksSelect() { + if (overview) overview->switchType(OverviewLinks); + _mediaType.hideStart(); +} + TopBarWidget *MainWidget::topBar() { return &_topBar; } @@ -3389,7 +3406,7 @@ void MainWidget::handleUpdates(const MTPUpdates &updates) { return; } bool out = (d.vflags.v & MTPDmessage_flag_out); - HistoryItem *item = App::histories().addToBack(MTP_message(d.vflags, d.vid, out ? MTP_int(MTP::authedId()) : d.vuser_id, MTP_peerUser(out ? d.vuser_id : MTP_int(MTP::authedId())), d.vfwd_from_id, d.vfwd_date, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, MTPnullEntities)); + HistoryItem *item = App::histories().addToBack(MTP_message(d.vflags, d.vid, out ? MTP_int(MTP::authedId()) : d.vuser_id, MTP_peerUser(out ? d.vuser_id : MTP_int(MTP::authedId())), d.vfwd_from_id, d.vfwd_date, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, d.has_entities() ? d.ventities : MTPnullEntities)); if (item) { history.peerMessagesUpdated(item->history()->peer->id); } @@ -3409,7 +3426,7 @@ void MainWidget::handleUpdates(const MTPUpdates &updates) { _byPtsUpdates.insert(ptsKey(SkippedUpdates), updates); return; } - HistoryItem *item = App::histories().addToBack(MTP_message(d.vflags, d.vid, d.vfrom_id, MTP_peerChat(d.vchat_id), d.vfwd_from_id, d.vfwd_date, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, MTPnullEntities)); + HistoryItem *item = App::histories().addToBack(MTP_message(d.vflags, d.vid, d.vfrom_id, MTP_peerChat(d.vchat_id), d.vfwd_from_id, d.vfwd_date, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, d.has_entities() ? d.ventities : MTPnullEntities)); if (item) { history.peerMessagesUpdated(item->history()->peer->id); } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 9fec76a31c..73491c1c41 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -429,6 +429,7 @@ public slots: void onVideosSelect(); void onDocumentsSelect(); void onAudiosSelect(); + void onLinksSelect(); void onForwardCancel(QObject *obj = 0); diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index cbc3d25f95..26f0b98595 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -27,6 +27,103 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "application.h" #include "gui/filedialog.h" +OverviewInner::CachedLink::CachedLink(HistoryItem *item) : text(st::msgMinWidth) { + QString msgText; + LinksInText msgLinks; + item->getTextWithLinks(msgText, msgLinks); + int32 from = 0, till = msgText.size(), lnk = msgLinks.size(); + for (int32 i = 0; i < lnk; ++i) { + if (msgLinks[i].type != LinkInTextUrl && msgLinks[i].type != LinkInTextCustomUrl && msgLinks[i].type != LinkInTextEmail) { + continue; + } + QString url = msgLinks[i].text, text = msgText.mid(msgLinks[i].offset, msgLinks[i].length); + urls.push_back(Link(url.isEmpty() ? text : url, text)); + } + while (lnk > 0 && till > from) { + --lnk; + if (msgLinks[lnk].type != LinkInTextUrl && msgLinks[lnk].type != LinkInTextCustomUrl && msgLinks[lnk].type != LinkInTextEmail) { + ++lnk; + break; + } + int32 afterLinkStart = msgLinks[lnk].offset + msgLinks[lnk].length; + if (till > afterLinkStart) { + if (!QRegularExpression(qsl("^[,.\\s_=+\\-;:`'\"\\(\\)\\[\\]\\{\\}<>*&^%\\$#@!\\\\/]+$")).match(msgText.mid(afterLinkStart, till - afterLinkStart)).hasMatch()) { + ++lnk; + break; + } + } + till = msgLinks[lnk].offset; + } + if (!lnk) { + if (QRegularExpression(qsl("^[,.\\s\\-;:`'\"\\(\\)\\[\\]\\{\\}<>*&^%\\$#@!\\\\/]+$")).match(msgText.mid(from, till - from)).hasMatch()) { + till = from; + } + } + + HistoryMedia *media = item->getMedia(); + page = (media && media->type() == MediaTypeWebPage) ? static_cast(media)->webpage() : 0; + if (from >= till && page) { + msgText = page->description; + from = 0; + till = msgText.size(); + } + if (till > from) { + TextParseOptions opts = { TextParseMultiline, st::linksMaxWidth, 3 * st::msgFont->height, Qt::LayoutDirectionAuto }; + text.setText(st::msgFont, msgText.mid(from, till - from), opts); + } + if (page && page->photo) { + if (!page->photo->full->loaded()) page->photo->medium->load(false, false); + + int32 tw = convertScale(page->photo->medium->width()), th = convertScale(page->photo->medium->height()); + if (tw > st::dlgPhotoSize) { + if (th > tw) { + th = th * st::dlgPhotoSize / tw; + tw = st::dlgPhotoSize; + } else if (th > st::dlgPhotoSize) { + tw = tw * st::dlgPhotoSize / th; + th = st::dlgPhotoSize; + } + } + pixw = tw; + pixh = th; + if (pixw < 1) pixw = 1; + if (pixh < 1) pixh = 1; + } + + if (page) { + title = page->title; + } + QVector parts = (page ? page->url : (urls.isEmpty() ? QString() : urls.at(0).url)).splitRef('/'); + if (!parts.isEmpty()) { + QStringRef domain = parts.at(0); + if (parts.size() > 2 && domain.endsWith(':') && parts.at(1).isEmpty()) { // http:// and others + domain = parts.at(2); + } + + parts = domain.split('@').back().split('.'); + if (parts.size() > 1) { + letter = parts.at(parts.size() - 2).at(0).toUpper(); + if (title.isEmpty()) { + title.reserve(parts.at(parts.size() - 2).size()); + title.append(letter).append(parts.at(parts.size() - 2).mid(1)); + } + } + } + titleWidth = st::webPageTitleFont->m.width(title); +} + +int32 OverviewInner::CachedLink::countHeight(int32 w) { + int32 result = 0; + if (!title.isEmpty()) { + result += st::webPageTitleFont->height; + } + if (!text.isEmpty()) { + result += qMin(3 * st::msgFont->height, text.countHeight(w - st::dlgPhotoSize - st::dlgPhotoPadding)); + } + result += urls.size() * st::msgFont->height; + return qMax(result, int(st::dlgPhotoSize)) + st::linksMargin * 2 + st::linksBorder; +} + // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html OverviewInner::OverviewInner(OverviewWidget *overview, ScrollArea *scroll, const PeerData *peer, MediaOverviewType type) : QWidget(0) @@ -43,6 +140,16 @@ OverviewInner::OverviewInner(OverviewWidget *overview, ScrollArea *scroll, const , _audioLeft(st::msgMargin.left()) , _audioWidth(st::msgMinWidth) , _audioHeight(st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom()) + , _linksLeft(st::linksSearchMargin.left()) + , _linksWidth(st::msgMinWidth) + , _search(this, st::dlgFilter, lang(lng_dlg_filter)) + , _cancelSearch(this, st::btnCancelSearch) + , _itemsToBeLoaded(LinksOverviewPerPage * 2) + , _inSearch(false) + , _searchFull(false) + , _searchRequest(0) + , _lastSearchId(0) + , _searchedCount(0) , _width(st::wndMinWidth) , _height(0) , _minHeight(0) @@ -53,6 +160,8 @@ OverviewInner::OverviewInner(OverviewWidget *overview, ScrollArea *scroll, const , _dragItemIndex(-1) , _mousedItem(0) , _mousedItemIndex(-1) + , _lnkOverIndex(0) + , _lnkDownIndex(0) , _dragWasInactive(false) , _dragSelFrom(0) , _dragSelTo(0) @@ -81,8 +190,18 @@ OverviewInner::OverviewInner(OverviewWidget *overview, ScrollArea *scroll, const mediaOverviewUpdated(); setMouseTracking(true); - if (_type == OverviewAudioDocuments) { + connect(&_cancelSearch, SIGNAL(clicked()), this, SLOT(onCancelSearch())); + connect(&_search, SIGNAL(cancelled()), this, SLOT(onCancel())); + connect(&_search, SIGNAL(changed()), this, SLOT(onSearchUpdate())); + _searchTimer.setSingleShot(true); + connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchMessages())); + + _cancelSearch.hide(); + if (_type == OverviewLinks) { + _search.show(); + } else { + _search.hide(); } } @@ -165,6 +284,87 @@ void OverviewInner::fixItemIndex(int32 ¤t, MsgId msgId) const { } } +void OverviewInner::searchReceived(bool fromStart, const MTPmessages_Messages &result, mtpRequestId req) { + if (fromStart && !_search.text().isEmpty()) { + SearchQueries::iterator i = _searchQueries.find(req); + if (i != _searchQueries.cend()) { + _searchCache[i.value()] = result; + _searchQueries.erase(i); + } + } + + if (_searchRequest == req) { + const QVector *messages = 0; + switch (result.type()) { + case mtpc_messages_messages: { + App::feedUsers(result.c_messages_messages().vusers); + App::feedChats(result.c_messages_messages().vchats); + messages = &result.c_messages_messages().vmessages.c_vector().v; + _searchedCount = messages->size(); + } break; + + case mtpc_messages_messagesSlice: { + App::feedUsers(result.c_messages_messagesSlice().vusers); + App::feedChats(result.c_messages_messagesSlice().vchats); + messages = &result.c_messages_messagesSlice().vmessages.c_vector().v; + _searchedCount = result.c_messages_messagesSlice().vcount.v; + } break; + } + if (messages) { + if (messages->isEmpty()) { + _searchFull = true; + } + if (fromStart) { + _searchResults.clear(); + _lastSearchId = 0; + _itemsToBeLoaded = LinksOverviewPerPage * 2; + } + for (QVector::const_iterator i = messages->cbegin(), e = messages->cend(); i != e; ++i) { + HistoryItem *item = App::histories().addToBack(*i, -1); + _searchResults.push_front(item->id); + _lastSearchId = item->id; + } + mediaOverviewUpdated(); + } + + _searchRequest = 0; + _overview->onScroll(); + } +} + +bool OverviewInner::searchFailed(const RPCError &error, mtpRequestId req) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + + if (_searchRequest == req) { + _searchRequest = 0; + _searchFull = true; + } + return true; +} + +OverviewInner::CachedLink *OverviewInner::cachedLink(HistoryItem *item) { + CachedLinks::const_iterator i = _links.constFind(item->id); + if (i == _links.cend()) i = _links.insert(item->id, new CachedLink(item)); + return i.value(); +} + +QString OverviewInner::urlByIndex(MsgId msgid, int32 index, int32 lnkIndex) const { + fixItemIndex(index, msgid); + if (index < 0 || !_items[index].link) return QString(); + + if (lnkIndex < 0 && _items[index].link->page) { + return _items[index].link->page->url; + } else if (lnkIndex > 0 && lnkIndex <= _items[index].link->urls.size()) { + return _items[index].link->urls.at(lnkIndex - 1).url; + } + return QString(); +} + +bool OverviewInner::urlIsEmail(const QString &url) const { + int32 at = url.indexOf('@'), slash = url.indexOf('/'); + return (at > 0) && (slash < 0 || slash > at); +} + bool OverviewInner::itemHasPoint(MsgId msgId, int32 index, int32 x, int32 y) const { fixItemIndex(index, msgId); if (index < 0) return false; @@ -177,6 +377,10 @@ bool OverviewInner::itemHasPoint(MsgId msgId, int32 index, int32 x, int32 y) con if (x >= _audioLeft && x < _audioLeft + _audioWidth && y >= 0 && y < _audioHeight) { return true; } + } else if (_type == OverviewLinks) { + if (x >= _linksLeft && x < _linksLeft + _linksWidth && y >= 0 && y < itemHeight(msgId, index)) { + return true; + } } else { HistoryItem *item = App::histItemById(msgId); HistoryMedia *media = item ? item->getMedia(true) : 0; @@ -201,6 +405,9 @@ int32 OverviewInner::itemHeight(MsgId msgId, int32 index) const { } fixItemIndex(index, msgId); + if (_type == OverviewLinks) { + return (index < 0) ? 0 : ((index + 1 < _items.size() ? _items[index + 1].y : (_height - _addToY)) - _items[index].y); + } return (index < 0) ? 0 : (_items[index].y - (index > 0 ? _items[index - 1].y : 0)); } @@ -249,11 +456,10 @@ void OverviewInner::updateMsg(MsgId itemId, int32 itemIndex) { update(int32(col * w), _addToY + int32(row * vsize), qCeil(w), vsize); } else if (_type == OverviewAudioDocuments) { update(_audioLeft, _addToY + int32(itemIndex * _audioHeight), _audioWidth, _audioHeight); + } else if (_type == OverviewLinks) { + update(_linksLeft, _addToY + _items[itemIndex].y, _linksWidth, itemHeight(itemId, itemIndex)); } else { - HistoryItem *item = App::histItemById(itemId); - HistoryMedia *media = item ? item->getMedia(true) : 0; - int32 w = _width - st::msgMargin.left() - st::msgMargin.right(); - if (media) update(0, _addToY + _height - _items[itemIndex].y, _width, media->countHeight(item, w) + st::msgMargin.top() + st::msgMargin.bottom()); + update(0, _addToY + _height - _items[itemIndex].y, _width, itemHeight(itemId, itemIndex)); } } } @@ -377,6 +583,11 @@ void OverviewInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton but App::pressedLinkItem(App::hoveredLinkItem()); updateMsg(App::pressedLinkItem()); } + if (_lnkDownIndex != _lnkOverIndex) { + if (_dragItem) updateMsg(_dragItem, _dragItemIndex); + _lnkDownIndex = _lnkOverIndex; + if (_mousedItem) updateMsg(_mousedItem, _mousedItemIndex); + } _dragAction = NoDrag; _dragItem = _mousedItem; @@ -384,11 +595,11 @@ void OverviewInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton but _dragStartPos = mapMouseToItem(mapFromGlobal(screenPos), _dragItem, _dragItemIndex); _dragWasInactive = App::wnd()->inactivePress(); if (_dragWasInactive) App::wnd()->inactivePress(false); - if (textlnkDown() && _selected.isEmpty()) { + if ((textlnkDown() || _lnkDownIndex) && _selected.isEmpty()) { _dragAction = PrepareDrag; } else if (!_selected.isEmpty()) { if (_selected.cbegin().value() == FullItemSel) { - if (_selected.constFind(_dragItem) != _selected.cend() && textlnkDown()) { + if (_selected.constFind(_dragItem) != _selected.cend() && (textlnkDown() || _lnkDownIndex)) { _dragAction = PrepareDrag; // start items drag } else { _dragAction = PrepareSelect; // start items select @@ -399,7 +610,7 @@ void OverviewInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton but bool afterDragSymbol = false , uponSymbol = false; uint16 symbol = 0; if (!_dragWasInactive) { - if (textlnkDown()) { + if (textlnkDown() || _lnkDownIndex) { _dragSymbol = symbol; uint32 selStatus = (_dragSymbol << 16) | _dragSymbol; if (selStatus != FullItemSel && (_selected.isEmpty() || _selected.cbegin().value() != FullItemSel)) { @@ -439,6 +650,7 @@ void OverviewInner::dragActionCancel() { void OverviewInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton button) { TextLinkPtr needClick; + int32 needClickIndex = 0; dragActionUpdate(screenPos); @@ -447,6 +659,11 @@ void OverviewInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton bu needClick = textlnkDown(); } } + if (_lnkOverIndex) { + if (_lnkDownIndex == _lnkOverIndex && _dragAction != Dragging && !_selMode) { + needClickIndex = _lnkDownIndex; + } + } if (textlnkDown()) { updateMsg(App::pressedLinkItem()); textlnkDown(TextLinkPtr()); @@ -456,11 +673,29 @@ void OverviewInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton bu setCursor(_cursor); } } + if (_lnkDownIndex) { + updateMsg(_dragItem, _dragItemIndex); + _lnkDownIndex = 0; + if (!_lnkOverIndex && _cursor != style::cur_default) { + _cursor = style::cur_default; + setCursor(_cursor); + } + } if (needClick) { needClick->onClick(button); dragActionCancel(); return; } + if (needClickIndex) { + QString url = urlByIndex(_dragItem, _dragItemIndex, needClickIndex); + if (urlIsEmail(url)) { + EmailLink(url).onClick(button); + } else { + TextLink(url).onClick(button); + } + dragActionCancel(); + return; + } if (_dragAction == PrepareSelect && !needClick && !_dragWasInactive && !_selected.isEmpty() && _selected.cbegin().value() == FullItemSel) { SelectedItems::iterator i = _selected.find(_dragItem); if (i == _selected.cend() && _dragItem > 0) { @@ -525,6 +760,16 @@ void OverviewInner::onDragExec() { } else if (textlnkDown()) { sel = textlnkDown()->encoded(); if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') { +// urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o + } + } else if (_lnkDownIndex) { + QString url = urlByIndex(_dragItem, _dragItemIndex, _lnkDownIndex); + if (urlIsEmail(url)) { + sel = EmailLink(url).encoded(); + } else { + sel = TextLink(url).encoded(); + } + if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') { // urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o } } @@ -542,6 +787,7 @@ void OverviewInner::onDragExec() { } drag->setMimeData(mimeData); drag->exec(Qt::CopyAction); + dragActionUpdate(QCursor::pos()); return; } else { HistoryItem *pressedLnkItem = App::pressedLinkItem(), *pressedItem = App::pressedItem(); @@ -566,6 +812,7 @@ void OverviewInner::onDragExec() { drag->setMimeData(mimeData); drag->exec(Qt::CopyAction); + dragActionUpdate(QCursor::pos()); return; } } @@ -629,12 +876,22 @@ QPoint OverviewInner::mapMouseToItem(QPoint p, MsgId itemId, int32 itemIndex) { p.setY(p.y() - _addToY - row * (_vsize + st::overviewPhotoSkip) - st::overviewPhotoSkip); } else if (_type == OverviewAudioDocuments) { p.setY(p.y() - _addToY - itemIndex * _audioHeight); + } else if (_type == OverviewLinks) { + p.setY(p.y() - _addToY - _items[itemIndex].y); } else { p.setY(p.y() - _addToY - (_height - _items[itemIndex].y)); } return p; } +void OverviewInner::activate() { + if (_type == OverviewLinks) { + _search.setFocus(); + } else { + setFocus(); + } +} + void OverviewInner::clear() { _cached.clear(); } @@ -649,6 +906,27 @@ int32 OverviewInner::itemTop(MsgId msgId) const { return -1; } +void OverviewInner::preloadMore() { + if (_inSearch) { + if (!_searchRequest && !_searchFull) { + _searchRequest = MTP::send(MTPmessages_Search(_hist->peer->input, MTP_string(_searchQuery), MTP_inputMessagesFilterUrl(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(_lastSearchId), MTP_int(SearchPerPage)), rpcDone(&OverviewInner::searchReceived, !_lastSearchId), rpcFail(&OverviewInner::searchFailed)); + if (!_lastSearchId) { + _searchQueries.insert(_searchRequest, _searchQuery); + } + } + } else if (App::main()) { + App::main()->loadMediaBack(_hist->peer, _type, _type != OverviewLinks); + } +} + +bool OverviewInner::preloadLocal() { + if (_type != OverviewLinks) return false; + if (_itemsToBeLoaded >= _hist->_overview[_type].size()) return false; + _itemsToBeLoaded += LinksOverviewPerPage; + mediaOverviewUpdated(); + return true; +} + QPixmap OverviewInner::genPix(PhotoData *photo, int32 size) { size *= cIntRetinaFactor(); QImage img = (photo->full->loaded() ? photo->full : (photo->medium->loaded() ? photo->medium : photo->thumb))->pix().toImage(); @@ -680,6 +958,11 @@ void OverviewInner::paintEvent(QPaintEvent *e) { QPoint dogPos((_width - st::msgDogImg.pxWidth()) / 2, ((height() - st::msgDogImg.pxHeight()) * 4) / 9); p.drawPixmap(dogPos, *cChatDogImage()); return; + } else if (_inSearch && _searchResults.isEmpty() && _searchFull && !_searchTimer.isActive()) { + p.setFont(st::noContactsFont->f); + p.setPen(st::noContactsColor->p); + p.drawText(QRect(_linksLeft, _addToY, _linksWidth, _addToY), lng_search_found_results(lt_count, 0), style::al_center); + return; } int32 selfrom = -1, selto = -1; @@ -766,9 +1049,9 @@ void OverviewInner::paintEvent(QPaintEvent *e) { } if (sel == FullItemSel) { p.fillRect(QRect(pos.x(), pos.y(), _vsize, _vsize), st::overviewPhotoSelectOverlay->b); - p.drawPixmap(QPoint(pos.x() + _vsize - st::overviewPhotoChecked.pxWidth(), pos.y() + _vsize - st::overviewPhotoChecked.pxHeight()), App::sprite(), st::overviewPhotoChecked); + p.drawPixmap(QPoint(pos.x() + _vsize - st::overviewPhotoCheck.pxWidth(), pos.y() + _vsize - st::overviewPhotoCheck.pxHeight()), App::sprite(), st::overviewPhotoChecked); } else if (_selMode/* || (selfrom < count && selfrom <= selto && 0 <= selto)*/) { - p.drawPixmap(QPoint(pos.x() + _vsize - st::overviewPhotoChecked.pxWidth(), pos.y() + _vsize - st::overviewPhotoChecked.pxHeight()), App::sprite(), st::overviewPhotoCheck); + p.drawPixmap(QPoint(pos.x() + _vsize - st::overviewPhotoCheck.pxWidth(), pos.y() + _vsize - st::overviewPhotoCheck.pxHeight()), App::sprite(), st::overviewPhotoCheck); } } break; } @@ -800,6 +1083,95 @@ void OverviewInner::paintEvent(QPaintEvent *e) { static_cast(m)->drawInPlaylist(p, item, (sel == FullItemSel), ((_menu ? (App::contextItem() ? App::contextItem()->id : 0) : _selectedMsgId) == item->id), _audioWidth); p.translate(0, _audioHeight); } + } else if (_type == OverviewLinks) { + p.translate(_linksLeft, _addToY); + int32 y = 0, w = _linksWidth; + for (int32 i = 0, l = _items.size(); i < l; ++i) { + if (i + 1 == l || _addToY + _items[i + 1].y > r.top()) { + int32 left = st::dlgPhotoSize + st::dlgPhotoPadding, top = st::linksMargin + st::linksBorder, curY = _items[i].y; + if (_addToY + curY >= r.bottom()) break; + + p.translate(0, curY - y); + if (_items[i].msgid) { // draw item + CachedLink *lnk = _items[i].link; + WebPageData *page = lnk->page; + if (page && page->photo) { + QPixmap pix; + if (page->photo->full->loaded()) { + pix = page->photo->full->pixSingle(lnk->pixw, lnk->pixh, st::dlgPhotoSize, st::dlgPhotoSize); + } else if (page->photo->medium->loaded()) { + pix = page->photo->medium->pixSingle(lnk->pixw, lnk->pixh, st::dlgPhotoSize, st::dlgPhotoSize); + } else { + pix = page->photo->thumb->pixBlurredSingle(lnk->pixw, lnk->pixh, st::dlgPhotoSize, st::dlgPhotoSize); + } + p.drawPixmap(0, top, pix); + } else { + int32 index = lnk->letter.isEmpty() ? 0 : (lnk->letter.at(0).unicode() % 4); + switch (index) { + case 0: App::roundRect(p, QRect(0, top, st::dlgPhotoSize, st::dlgPhotoSize), st::mvDocRedColor, DocRedCorners); break; + case 1: App::roundRect(p, QRect(0, top, st::dlgPhotoSize, st::dlgPhotoSize), st::mvDocYellowColor, DocYellowCorners); break; + case 2: App::roundRect(p, QRect(0, top, st::dlgPhotoSize, st::dlgPhotoSize), st::mvDocGreenColor, DocGreenCorners); break; + case 3: App::roundRect(p, QRect(0, top, st::dlgPhotoSize, st::dlgPhotoSize), st::mvDocBlueColor, DocBlueCorners); break; + } + + if (!lnk->letter.isEmpty()) { + p.setFont(st::linksLetterFont->f); + p.setPen(st::white->p); + p.drawText(QRect(0, top, st::dlgPhotoSize, st::dlgPhotoSize), lnk->letter, style::al_center); + } + } + + uint32 sel = 0; + if (i >= selfrom && i <= selto) { + sel = (_dragSelecting && _items[i].msgid > 0) ? FullItemSel : 0; + } else if (hasSel) { + SelectedItems::const_iterator j = _selected.constFind(_items[i].msgid); + if (j != selEnd) { + sel = j.value(); + } + } + if (sel == FullItemSel) { + App::roundRect(p, QRect(0, top, st::dlgPhotoSize, st::dlgPhotoSize), st::overviewPhotoSelectOverlay, PhotoSelectOverlayCorners); + p.drawPixmap(QPoint(st::dlgPhotoSize - st::linksPhotoCheck.pxWidth(), top + st::dlgPhotoSize - st::linksPhotoCheck.pxHeight()), App::sprite(), st::linksPhotoChecked); + } else if (_selMode/* || (selfrom < count && selfrom <= selto && 0 <= selto)*/) { + p.drawPixmap(QPoint(st::dlgPhotoSize - st::linksPhotoCheck.pxWidth(), top + st::dlgPhotoSize - st::linksPhotoCheck.pxHeight()), App::sprite(), st::linksPhotoCheck); + } + + if (!lnk->title.isEmpty() && lnk->text.isEmpty() && lnk->urls.size() == 1) { + top += (st::dlgPhotoSize - st::webPageTitleFont->height - st::msgFont->height) / 2; + } + + p.setPen(st::black->p); + p.setFont(st::webPageTitleFont->f); + if (!lnk->title.isEmpty()) { + p.drawText(left, top + st::webPageTitleFont->ascent, (_linksWidth - left < lnk->titleWidth) ? st::webPageTitleFont->m.elidedText(lnk->title, Qt::ElideRight, _linksWidth - left) : lnk->title); + top += st::webPageTitleFont->height; + } + p.setFont(st::msgFont->f); + if (!lnk->text.isEmpty()) { + lnk->text.drawElided(p, left, top, _linksWidth - left, 3); + top += qMin(st::msgFont->height * 3, lnk->text.countHeight(_linksWidth - left)); + } + + p.setPen(st::btnYesColor->p); + for (int32 j = 0, c = lnk->urls.size(); j < c; ++j) { + bool sel = (_mousedItem == _items[i].msgid && j + 1 == _lnkOverIndex); + if (sel) p.setFont(st::msgFont->underline()->f); + p.drawText(left, top + st::msgFont->ascent, (_linksWidth - left < lnk->urls[j].width) ? st::msgFont->m.elidedText(lnk->urls[j].text, Qt::ElideRight, _linksWidth - left) : lnk->urls[j].text); + if (sel) p.setFont(st::msgFont->f); + top += st::msgFont->height; + } + p.fillRect(left, _items[i].y - curY, _linksWidth - left, st::linksBorder, st::linksBorderColor->b); + } else { + QString str = langDayOfMonth(_items[i].date); + + p.setPen(st::linksDateColor->p); + p.setFont(st::msgFont->f); + p.drawText(0, st::linksDateMargin + st::msgFont->ascent, str); + } + y = curY; + } + } } else { p.translate(0, st::msgMargin.top() + _addToY); int32 y = 0, w = _width - st::msgMargin.left() - st::msgMargin.right(); @@ -876,6 +1248,7 @@ void OverviewInner::onUpdateSelected() { QPoint m(_overview->clampMousePosition(mousePos)); TextLinkPtr lnk; + int32 lnkIndex = 0; // for OverviewLinks HistoryItem *item = 0; int32 index = -1; int32 newsel = 0; @@ -888,8 +1261,6 @@ void OverviewInner::onUpdateSelected() { bool upon = true; int32 i = row * _photosInRow + inRow - _photosToAdd, count = _hist->_overview[_type].size(); - if (!count) return; - if (i < 0) { i = 0; upon = false; @@ -898,25 +1269,24 @@ void OverviewInner::onUpdateSelected() { i = count - 1; upon = false; } - MsgId msgid = _hist->_overview[_type][i]; - HistoryItem *histItem = App::histItemById(msgid); - if (histItem) { - item = histItem; - index = i; - if (upon && m.x() >= inRow * w + st::overviewPhotoSkip && m.x() < inRow * w + st::overviewPhotoSkip + _vsize) { - if (m.y() >= _addToY + row * vsize + st::overviewPhotoSkip && m.y() < _addToY + (row + 1) * vsize + st::overviewPhotoSkip) { - HistoryMedia *media = item->getMedia(true); - if (media && media->type() == MediaTypePhoto) { - lnk = static_cast(media)->lnk(); + if (i >= 0) { + MsgId msgid = _hist->_overview[_type][i]; + HistoryItem *histItem = App::histItemById(msgid); + if (histItem) { + item = histItem; + index = i; + if (upon && m.x() >= inRow * w + st::overviewPhotoSkip && m.x() < inRow * w + st::overviewPhotoSkip + _vsize) { + if (m.y() >= _addToY + row * vsize + st::overviewPhotoSkip && m.y() < _addToY + (row + 1) * vsize + st::overviewPhotoSkip) { + HistoryMedia *media = item->getMedia(true); + if (media && media->type() == MediaTypePhoto) { + lnk = static_cast(media)->lnk(); + } } } } - } else { - return; } } else if (_type == OverviewAudioDocuments) { int32 i = int32((m.y() - _addToY) / _audioHeight), count = _hist->_overview[_type].size(); - if (!count) return; bool upon = true; if (m.y() < _addToY) { @@ -927,42 +1297,82 @@ void OverviewInner::onUpdateSelected() { i = count - 1; upon = false; } - MsgId msgid = _hist->_overview[_type][i]; - HistoryItem *histItem = App::histItemById(msgid); - if (histItem) { - item = histItem; - index = i; - if (upon && m.x() >= _audioLeft && m.x() < _audioLeft + _audioWidth) { - HistoryMedia *media = item->getMedia(true); - if (media && media->type() == MediaTypeDocument) { - lnk = static_cast(media)->linkInPlaylist(); - newsel = item->id; + if (i >= 0) { + MsgId msgid = _hist->_overview[_type][i]; + HistoryItem *histItem = App::histItemById(msgid); + if (histItem) { + item = histItem; + index = i; + if (upon && m.x() >= _audioLeft && m.x() < _audioLeft + _audioWidth) { + HistoryMedia *media = item->getMedia(true); + if (media && media->type() == MediaTypeDocument) { + lnk = static_cast(media)->linkInPlaylist(); + newsel = item->id; + } } } - if (newsel != _selectedMsgId) { - updateMsg(App::histItemById(_selectedMsgId)); - _selectedMsgId = newsel; - updateMsg(item); + } + if (newsel != _selectedMsgId) { + if (_selectedMsgId) updateMsg(App::histItemById(_selectedMsgId)); + _selectedMsgId = newsel; + updateMsg(item); + } + } else if (_type == OverviewLinks) { + int32 w = _width - st::msgMargin.left() - st::msgMargin.right(); + for (int32 i = 0, l = _items.size(); i < l; ++i) { + if ((i + 1 == l) || (_addToY + _items[i + 1].y > m.y())) { + int32 left = st::dlgPhotoSize + st::dlgPhotoPadding, y = _addToY + _items[i].y; + if (!_items[i].msgid) { // day item + int32 h = 2 * st::linksDateMargin + st::msgFont->height;// itemHeight(_items[i].msgid, i); + if (i > 0 && ((y + h / 2) >= m.y() || i == _items.size() - 1)) { + --i; + if (!_items[i].msgid) break; // wtf + y = _addToY + _items[i].y; + } else if (i < _items.size() - 1 && ((y + h / 2) < m.y() || !i)) { + ++i; + if (!_items[i].msgid) break; // wtf + y = _addToY + _items[i].y; + } else { + break; // wtf + } + } + + HistoryItem *histItem = App::histItemById(_items[i].msgid); + if (histItem) { + item = histItem; + index = i; + + int32 top = y + st::linksMargin + st::linksBorder, left = _linksLeft + st::dlgPhotoSize + st::dlgPhotoPadding, w = _linksWidth - st::dlgPhotoSize - st::dlgPhotoPadding; + if (!_items[i].link->title.isEmpty() && _items[i].link->text.isEmpty() && _items[i].link->urls.size() == 1) { + top += (st::dlgPhotoSize - st::webPageTitleFont->height - st::msgFont->height) / 2; + } + if (QRect(_linksLeft, y + st::linksMargin + st::linksBorder, st::dlgPhotoSize, st::dlgPhotoSize).contains(m)) { + lnkIndex = -1; + } else if (!_items[i].link->title.isEmpty() && QRect(left, top, qMin(w, _items[i].link->titleWidth), st::webPageTitleFont->height).contains(m)) { + lnkIndex = -1; + } else { + if (!_items[i].link->title.isEmpty()) top += st::webPageTitleFont->height; + if (!_items[i].link->text.isEmpty()) top += qMin(st::msgFont->height * 3, _items[i].link->text.countHeight(w)); + for (int32 j = 0, c = _items[i].link->urls.size(); j < c; ++j) { + if (QRect(left, top, qMin(w, _items[i].link->urls[j].width), st::msgFont->height).contains(m)) { + lnkIndex = j + 1; + break; + } + top += st::msgFont->height; + } + } + } + break; } - } else { - if (newsel != _selectedMsgId) { - updateMsg(App::histItemById(_selectedMsgId)); - _selectedMsgId = newsel; - } - return; } } else { int32 w = _width - st::msgMargin.left() - st::msgMargin.right(); - if (_items.isEmpty()) return; - for (int32 i = _items.size(); i > 0;) { --i; if (!i || (_addToY + _height - _items[i - 1].y > m.y())) { int32 y = _addToY + _height - _items[i].y; - if (item) break; - if (!_items[i].msgid) { // day item - int32 h = itemHeight(_items[i].msgid, i); + int32 h = st::msgServiceFont->height + st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom(); // itemHeight(_items[i].msgid, i); if (i > 0 && ((y + h / 2) < m.y() || i == _items.size() - 1)) { --i; if (!_items[i].msgid) break; // wtf @@ -995,13 +1405,14 @@ void OverviewInner::onUpdateSelected() { media->getState(link, cursorState, m.x() - left, m.y() - y - st::msgMargin.top(), item, w); if (link) lnk = link; } - } else { - return; } + break; } } } + MsgId oldMousedItem = _mousedItem; + int32 oldMousedItemIndex = _mousedItemIndex; _mousedItem = item ? item->id : 0; _mousedItemIndex = index; m = mapMouseToItem(m, _mousedItem, _mousedItemIndex); @@ -1017,11 +1428,17 @@ void OverviewInner::onUpdateSelected() { } else { App::mousedItem(item); } + if (lnkIndex != _lnkOverIndex || _mousedItem != oldMousedItem) { + lnkChanged = true; + if (oldMousedItem) updateMsg(App::histItemById(oldMousedItem)); + _lnkOverIndex = lnkIndex; + if (item) updateMsg(item); + } fixItemIndex(_dragItemIndex, _dragItem); fixItemIndex(_mousedItemIndex, _mousedItem); if (_dragAction == NoDrag) { - if (lnk) { + if (lnk || lnkIndex) { cur = style::cur_pointer; } } else { @@ -1037,15 +1454,15 @@ void OverviewInner::onUpdateSelected() { _dragAction = Selecting; } } - cur = textlnkDown() ? style::cur_pointer : style::cur_default; + cur = (textlnkDown() || _lnkDownIndex) ? style::cur_pointer : style::cur_default; if (_dragAction == Selecting) { - if (_mousedItem == _dragItem && lnk && !_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { + if (_mousedItem == _dragItem && (lnk || lnkIndex) && !_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { bool afterSymbol = false, uponSymbol = false; uint16 second = 0; _selected[_dragItem] = 0; updateDragSelection(0, -1, 0, -1, false); } else { - bool selectingDown = ((_type == OverviewPhotos || _type == OverviewAudioDocuments) ? (_mousedItemIndex > _dragItemIndex) : (_mousedItemIndex < _dragItemIndex)) || (_mousedItemIndex == _dragItemIndex && (_type == OverviewPhotos ? (_dragStartPos.x() < m.x()) : (_dragStartPos.y() < m.y()))); + bool selectingDown = ((_type == OverviewPhotos || _type == OverviewAudioDocuments || _type == OverviewLinks) ? (_mousedItemIndex > _dragItemIndex) : (_mousedItemIndex < _dragItemIndex)) || (_mousedItemIndex == _dragItemIndex && (_type == OverviewPhotos ? (_dragStartPos.x() < m.x()) : (_dragStartPos.y() < m.y()))); MsgId dragSelFrom = _dragItem, dragSelTo = _mousedItem; int32 dragSelFromIndex = _dragItemIndex, dragSelToIndex = _mousedItemIndex; if (!itemHasPoint(dragSelFrom, dragSelFromIndex, _dragStartPos.x(), _dragStartPos.y())) { // maybe exclude dragSelFrom @@ -1058,6 +1475,10 @@ void OverviewInner::onUpdateSelected() { if (_dragStartPos.y() >= itemHeight(dragSelFrom, dragSelFromIndex) || ((_mousedItem == dragSelFrom) && (m.y() < _dragStartPos.y() + QApplication::startDragDistance()))) { moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, 1); } + } else if (_type == OverviewLinks) { + if (_dragStartPos.y() >= itemHeight(dragSelFrom, dragSelFromIndex) || ((_mousedItem == dragSelFrom) && (m.y() < _dragStartPos.y() + QApplication::startDragDistance()))) { + moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, 1); + } } else { if (_dragStartPos.y() >= (itemHeight(dragSelFrom, dragSelFromIndex) - st::msgMargin.bottom()) || ((_mousedItem == dragSelFrom) && (m.y() < _dragStartPos.y() + QApplication::startDragDistance()))) { moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, -1); @@ -1072,6 +1493,10 @@ void OverviewInner::onUpdateSelected() { if (_dragStartPos.y() < 0 || ((_mousedItem == dragSelFrom) && (m.y() >= _dragStartPos.y() - QApplication::startDragDistance()))) { moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, -1); } + } else if (_type == OverviewLinks) { + if (_dragStartPos.y() < 0 || ((_mousedItem == dragSelFrom) && (m.y() >= _dragStartPos.y() - QApplication::startDragDistance()))) { + moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, -1); + } } else { if (_dragStartPos.y() < st::msgMargin.top() || ((_mousedItem == dragSelFrom) && (m.y() >= _dragStartPos.y() - QApplication::startDragDistance()))) { moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, 1); @@ -1089,6 +1514,10 @@ void OverviewInner::onUpdateSelected() { if (m.y() < 0) { moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, 1); } + } else if (_type == OverviewLinks) { + if (m.y() < 0) { + moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, -1); + } } else { if (m.y() < st::msgMargin.top()) { moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, 1); @@ -1100,6 +1529,10 @@ void OverviewInner::onUpdateSelected() { moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, 1); } } else if (_type == OverviewAudioDocuments) { + if (m.y() >= itemHeight(dragSelTo, dragSelToIndex)) { + moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, -1); + } + } else if (_type == OverviewLinks) { if (m.y() >= itemHeight(dragSelTo, dragSelToIndex)) { moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, 1); } @@ -1125,7 +1558,7 @@ void OverviewInner::onUpdateSelected() { } else if (_dragAction == Dragging) { } - if (textlnkDown()) { + if (textlnkDown() || _lnkDownIndex) { cur = style::cur_pointer; } else if (_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { if (!_dragSelFrom || !_dragSelTo) { @@ -1176,12 +1609,12 @@ void OverviewInner::mouseReleaseEvent(QMouseEvent *e) { } void OverviewInner::keyPressEvent(QKeyEvent *e) { - if (e->key() == Qt::Key_Escape || e->key() == Qt::Key_Back) { - if (_selected.isEmpty() || e->key() == Qt::Key_Back) { - App::main()->showBackFromStack(); - } else { - _overview->onClearSelected(); - } + if ((_search.isHidden() || !_search.hasFocus()) && !_overview->isHidden() && e->key() == Qt::Key_Escape) { + onCancel(); + } else if (e->key() == Qt::Key_Back) { + App::main()->showBackFromStack(); + } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { + onSearchMessages(); } } @@ -1210,6 +1643,10 @@ void OverviewInner::resizeEvent(QResizeEvent *e) { _width = width(); _audioWidth = qMin(_width - st::profilePadding.left() - st::profilePadding.right(), int(st::profileMaxWidth)); _audioLeft = (_width - _audioWidth) / 2; + _linksWidth = qMin(_width - st::linksSearchMargin.left() - st::linksSearchMargin.right(), int(st::linksMaxWidth)); + _linksLeft = (_width - _linksWidth) / 2; + _search.setGeometry(_linksLeft, st::linksSearchMargin.top(), _linksWidth, _search.height()); + _cancelSearch.move(_linksLeft + _linksWidth - _cancelSearch.width(), _search.y()); showAll(true); onUpdateSelected(); update(); @@ -1226,13 +1663,21 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { dragActionUpdate(e->globalPos()); } + bool ignoreMousedItem = false; + if (_mousedItem > 0) { + QPoint m = mapMouseToItem(mapFromGlobal(e->globalPos()), _mousedItem, _mousedItemIndex); + if (m.y() < 0 || m.y() >= itemHeight(_mousedItem, _mousedItemIndex)) { + ignoreMousedItem = true; + } + } + // -2 - has full selected items, but not over, 0 - no selection, 2 - over full selected items int32 isUponSelected = 0, hasSelected = 0; if (!_selected.isEmpty()) { isUponSelected = -1; if (_selected.cbegin().value() == FullItemSel) { hasSelected = 2; - if (App::hoveredLinkItem() && _selected.constFind(App::hoveredLinkItem()->id) != _selected.cend()) { + if (!ignoreMousedItem && App::mousedItem() && _selected.constFind(App::mousedItem()->id) != _selected.cend()) { isUponSelected = 2; } else { isUponSelected = -2; @@ -1282,8 +1727,23 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { App::contextItem(App::hoveredLinkItem()); updateMsg(App::contextItem()); if (_selectedMsgId > 0) updateMsg(App::histItemById(_selectedMsgId)); - } else if (App::mousedItem() && App::mousedItem()->id == _mousedItem) { + } else if (!ignoreMousedItem && App::mousedItem() && App::mousedItem()->id == _mousedItem) { + _contextMenuUrl = _lnkOverIndex ? urlByIndex(_mousedItem, _mousedItemIndex, _lnkOverIndex) : QString(); _menu = new ContextMenu(_overview); + if ((_contextMenuLnk && dynamic_cast(_contextMenuLnk.data())) || (!_contextMenuUrl.isEmpty() && !urlIsEmail(_contextMenuUrl))) { + _menu->addAction(lang(lng_context_open_link), this, SLOT(openContextUrl()))->setEnabled(true); + _menu->addAction(lang(lng_context_copy_link), this, SLOT(copyContextUrl()))->setEnabled(true); + } else if ((_contextMenuLnk && dynamic_cast(_contextMenuLnk.data())) || (!_contextMenuUrl.isEmpty() && urlIsEmail(_contextMenuUrl))) { + _menu->addAction(lang(lng_context_open_email), this, SLOT(openContextUrl()))->setEnabled(true); + _menu->addAction(lang(lng_context_copy_email), this, SLOT(copyContextUrl()))->setEnabled(true); + } else if (_contextMenuLnk && dynamic_cast(_contextMenuLnk.data())) { + _menu->addAction(lang(lng_context_open_mention), this, SLOT(openContextUrl()))->setEnabled(true); + _menu->addAction(lang(lng_context_copy_mention), this, SLOT(copyContextUrl()))->setEnabled(true); + } else if (_contextMenuLnk && dynamic_cast(_contextMenuLnk.data())) { + _menu->addAction(lang(lng_context_open_hashtag), this, SLOT(openContextUrl()))->setEnabled(true); + _menu->addAction(lang(lng_context_copy_hashtag), this, SLOT(copyContextUrl()))->setEnabled(true); + } else { + } _menu->addAction(lang(lng_context_to_msg), this, SLOT(goToMessage()))->setEnabled(true); if (isUponSelected > 1) { _menu->addAction(lang(lng_context_forward_selected), _overview, SLOT(onForwardSelected())); @@ -1313,7 +1773,13 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { int32 OverviewInner::resizeToWidth(int32 nwidth, int32 scrollTop, int32 minHeight) { if (width() == nwidth && minHeight == _minHeight) return scrollTop; _minHeight = minHeight; - _addToY = (_type == OverviewAudioDocuments) ? st::playlistPadding : ((_height < _minHeight) ? (_minHeight - _height) : 0); + if (_type == OverviewAudioDocuments) { + _addToY = st::playlistPadding; + } else if (_type == OverviewLinks) { + _addToY = st::linksSearchMargin.top() + _search.height() + st::linksSearchMargin.bottom(); + } else { + _addToY = (_height < _minHeight) ? (_minHeight - _height) : 0; + } if (_type == OverviewPhotos && _resizeIndex < 0) { _resizeIndex = _photosInRow * ((scrollTop + minHeight) / int32(_vsize + st::overviewPhotoSkip)) + _photosInRow - 1; _resizeSkip = (scrollTop + minHeight) - ((scrollTop + minHeight) / int32(_vsize + st::overviewPhotoSkip)) * int32(_vsize + st::overviewPhotoSkip); @@ -1344,9 +1810,16 @@ void OverviewInner::switchType(MediaOverviewType type) { _selected.clear(); _dragItemIndex = _mousedItemIndex = _dragSelFromIndex = _dragSelToIndex = -1; _dragItem = _mousedItem = _dragSelFrom = _dragSelTo = 0; + _lnkOverIndex = _lnkDownIndex = 0; _items.clear(); _cached.clear(); _type = type; + if (_type == OverviewLinks) { + _search.show(); + } else { + _search.hide(); + } + _cancelSearch.hide(); } mediaOverviewUpdated(); if (App::wnd()) App::wnd()->update(); @@ -1357,10 +1830,23 @@ void OverviewInner::setSelectMode(bool enabled) { } void OverviewInner::openContextUrl() { - HistoryItem *was = App::hoveredLinkItem(); - App::hoveredLinkItem(App::contextItem()); - _contextMenuLnk->onClick(Qt::LeftButton); - App::hoveredLinkItem(was); + if (_contextMenuLnk) { + HistoryItem *was = App::hoveredLinkItem(); + App::hoveredLinkItem(App::contextItem()); + _contextMenuLnk->onClick(Qt::LeftButton); + App::hoveredLinkItem(was); + } else if (urlIsEmail(_contextMenuUrl)) { + EmailLink(_contextMenuUrl).onClick(Qt::LeftButton); + } else { + TextLink(_contextMenuUrl).onClick(Qt::LeftButton); + } +} + +void OverviewInner::copyContextUrl() { + QString enc = _contextMenuLnk ? _contextMenuLnk->encoded() : _contextMenuUrl; + if (!enc.isEmpty()) { + QApplication::clipboard()->setText(enc); + } } void OverviewInner::goToMessage() { @@ -1433,9 +1919,88 @@ void OverviewInner::openContextFile() { if (lnkDocument) DocumentOpenLink(lnkDocument->document()).onClick(Qt::LeftButton); } +bool OverviewInner::onSearchMessages(bool searchCache) { + QString q = _search.text().trimmed(); + if (q.isEmpty()) { + if (_searchRequest) { + _searchRequest = 0; + } + return true; + } + if (searchCache) { + SearchCache::const_iterator i = _searchCache.constFind(q); + if (i != _searchCache.cend()) { + _searchQuery = q; + _searchFull = false; + _searchRequest = 0; + searchReceived(true, i.value(), 0); + return true; + } + } else if (_searchQuery != q) { + _searchQuery = q; + _searchFull = false; + _searchRequest = MTP::send(MTPmessages_Search(_hist->peer->input, MTP_string(_searchQuery), MTP_inputMessagesFilterUrl(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&OverviewInner::searchReceived, true), rpcFail(&OverviewInner::searchFailed)); + _searchQueries.insert(_searchRequest, _searchQuery); + } + return false; +} + +void OverviewInner::onNeedSearchMessages() { + if (!onSearchMessages(true)) { + _searchTimer.start(AutoSearchTimeout); + if (_inSearch && _searchFull && _searchResults.isEmpty()) { + parentWidget()->update(); + } + } +} + +void OverviewInner::onSearchUpdate() { + QString filterText = _search.text().trimmed(); + bool inSearch = !filterText.isEmpty(), changed = (inSearch != _inSearch); + _inSearch = inSearch; + + onNeedSearchMessages(); + + if (filterText.isEmpty()) { + _searchCache.clear(); + _searchQueries.clear(); + _searchQuery = QString(); + _searchResults.clear(); + _cancelSearch.hide(); + } else if (_cancelSearch.isHidden()) { + _cancelSearch.show(); + } + + if (changed) { + _itemsToBeLoaded = LinksOverviewPerPage * 2; + mediaOverviewUpdated(); + } + _overview->scrollReset(); +} + +void OverviewInner::onCancel() { + if (_selected.isEmpty()) { + if (onCancelSearch()) return; + App::main()->showBackFromStack(); + } else { + _overview->onClearSelected(); + } +} + +bool OverviewInner::onCancelSearch() { + if (_search.isHidden()) return false; + bool clearing = !_search.text().isEmpty(); + _cancelSearch.hide(); + _search.clear(); + _search.updatePlaceholder(); + onSearchUpdate(); + return clearing; +} + void OverviewInner::onMenuDestroy(QObject *obj) { if (_menu == obj) { _menu = 0; + dragActionUpdate(QCursor::pos()); updateMsg(App::contextItem()); if (_selectedMsgId > 0) updateMsg(App::histItemById(_selectedMsgId)); } @@ -1503,7 +2068,85 @@ void OverviewInner::onTouchScrollTimer() { void OverviewInner::mediaOverviewUpdated(bool fromResize) { int32 oldHeight = _height; - if (_type != OverviewPhotos && _type != OverviewAudioDocuments) { + if (_type == OverviewLinks) { + History::MediaOverview &o(_inSearch ? _searchResults : _hist->_overview[_type]); + int32 l = o.size(), tocheck = qMin(l, _itemsToBeLoaded); + _items.reserve(2 * l); // day items + + int32 y = 0, in = 0; + bool allGood = true; + QDate prevDate; + for (int32 i = 0; i < tocheck; ++i) { + MsgId msgid = o.at(l - i - 1); + if (allGood) { + if (_items.size() > in && _items.at(in).msgid == msgid) { + prevDate = _items.at(in).date; + if (fromResize) { + _items[in].y = y; + y += _items[in].link->countHeight(_linksWidth); + } else { + y = (in + 1 < _items.size()) ? _items.at(in + 1).y : _height; + } + ++in; + continue; + } + if (_items.size() > in + 1 && !_items.at(in).msgid && _items.at(in + 1).msgid == msgid) { // day item + if (fromResize) { + _items[in].y = y; + y += st::msgFont->height + st::linksDateMargin * 2 + st::linksBorder; + } + ++in; + prevDate = _items.at(in).date; + if (fromResize) { + _items[in].y = y; + y += _items[in].link->countHeight(_linksWidth); + } else { + y = (in + 1 < _items.size()) ? _items.at(in + 1).y : _height; + } + ++in; + continue; + } + allGood = false; + } + HistoryItem *item = App::histItemById(msgid); + + QDate date = item->date.date(); + if (!in || (in > 0 && date != prevDate)) { + if (_items.size() > in) { + _items[in].msgid = 0; + _items[in].date = date; + _items[in].y = y; + } else { + _items.push_back(CachedItem(0, date, y)); + } + y += st::msgFont->height + st::linksDateMargin * 2 + st::linksBorder; + ++in; + prevDate = date; + } + + if (_items.size() > in) { + _items[in] = CachedItem(item->id, item->date.date(), y); + _items[in].link = cachedLink(item); + y += _items[in].link->countHeight(_linksWidth); + } else { + _items.push_back(CachedItem(item->id, item->date.date(), y)); + _items.back().link = cachedLink(item); + y += _items.back().link->countHeight(_linksWidth); + } + ++in; + } + if (_items.size() != in) { + _items.resize(in); + } + if (_height != _addToY + y + st::linksSearchMargin.top()) { + _height = _addToY + y + st::linksSearchMargin.top(); + if (!fromResize) { + resize(width(), _minHeight > _height ? _minHeight : _height); + } + } + dragActionUpdate(QCursor::pos()); + update(); + } else if (_type != OverviewPhotos && _type != OverviewAudioDocuments) { History::MediaOverview &o(_hist->_overview[_type]); int32 l = o.size(); _items.reserve(2 * l); // day items @@ -1598,7 +2241,7 @@ void OverviewInner::mediaOverviewUpdated(bool fromResize) { if (_height != y) { _height = y; if (!fromResize) { - _addToY = (_type == OverviewAudioDocuments) ? st::playlistPadding : ((_height < _minHeight) ? (_minHeight - _height) : 0); + _addToY = (_height < _minHeight) ? (_minHeight - _height) : 0; resize(width(), _minHeight > _height ? _minHeight : _height); } } @@ -1611,7 +2254,7 @@ void OverviewInner::mediaOverviewUpdated(bool fromResize) { if (!fromResize) { resizeEvent(0); - if (_height != oldHeight) { + if (_height != oldHeight && _type != OverviewLinks) { _overview->scrollBy(_height - oldHeight); } } @@ -1630,6 +2273,19 @@ void OverviewInner::changingMsgId(HistoryItem *row, MsgId newId) { break; } } + if (_links.contains(row->id) && row->id != newId) { + if (_links.contains(newId)) { + for (CachedItems::iterator i = _items.begin(), e = _items.end(); i != e; ++i) { + if (i->msgid == newId && i->link) { + i->link = _links[row->id]; + break; + } + } + } + _links[newId] = _links[row->id]; + delete _links[row->id]; + _links.remove(row->id); + } for (CachedItems::iterator i = _items.begin(), e = _items.end(); i != e; ++i) { if (i->msgid == row->id) { i->msgid = newId; @@ -1665,7 +2321,7 @@ void OverviewInner::itemRemoved(HistoryItem *item) { } void OverviewInner::itemResized(HistoryItem *item, bool scrollToIt) { - if (_type != OverviewPhotos && _type != OverviewAudioDocuments) { + if (_type != OverviewPhotos && _type != OverviewAudioDocuments && _type != OverviewLinks) { HistoryMedia *media = item ? item->getMedia(true) : 0; if (!media) return; @@ -1683,7 +2339,7 @@ void OverviewInner::itemResized(HistoryItem *item, bool scrollToIt) { _items[j].y += newh; } _height = _items[l - 1].y; - _addToY = (_type == OverviewAudioDocuments) ? st::playlistPadding : ((_height < _minHeight) ? (_minHeight - _height) : 0); + _addToY = (_height < _minHeight) ? (_minHeight - _height) : 0; resize(width(), _minHeight > _height ? _minHeight : _height); if (scrollToIt) { if (_addToY + _height - from > _scroll->scrollTop() + _scroll->height()) { @@ -1718,12 +2374,17 @@ void OverviewInner::msgUpdated(const HistoryItem *msg) { if (index >= 0) { update(_audioLeft, _addToY + int32(index * _audioHeight), _audioWidth, _audioHeight); } + } else if (_type == OverviewLinks) { + for (int32 i = 0, l = _items.size(); i != l; ++i) { + if (_items[i].msgid == msgid) { + update(_linksLeft, _addToY + _items[i].y, _linksWidth, itemHeight(msgid, i)); + break; + } + } } else { for (int32 i = 0, l = _items.size(); i != l; ++i) { if (_items[i].msgid == msgid) { - HistoryMedia *media = msg->getMedia(true); - int32 w = _width - st::msgMargin.left() - st::msgMargin.right(); - if (media) update(0, _addToY + _height - _items[i].y, _width, media->countHeight(msg, w) + st::msgMargin.top() + st::msgMargin.bottom()); + update(0, _addToY + _height - _items[i].y, _width, itemHeight(msgid, i)); break; } } @@ -1746,16 +2407,24 @@ void OverviewInner::showAll(bool recountHeights) { } int32 rows = ((_photosToAdd + count) / _photosInRow) + (((_photosToAdd + count) % _photosInRow) ? 1 : 0); newHeight = _height = (_vsize + st::overviewPhotoSkip) * rows + st::overviewPhotoSkip; + _addToY = (_height < _minHeight) ? (_minHeight - _height) : 0; } else if (_type == OverviewAudioDocuments) { int32 count = _hist->_overview[_type].size(), fullCount = _hist->_overviewCount[_type]; newHeight = _height = count * _audioHeight + 2 * st::playlistPadding; + _addToY = st::playlistPadding; + } else if (_type == OverviewLinks) { + if (recountHeights) { // recount heights because of texts + mediaOverviewUpdated(true); + } + newHeight = _height; + _addToY = st::linksSearchMargin.top() + _search.height() + st::linksSearchMargin.bottom(); } else { if (recountHeights && _type == OverviewVideos) { // recount heights because of captions mediaOverviewUpdated(true); } newHeight = _height; + _addToY = (_height < _minHeight) ? (_minHeight - _height) : 0; } - _addToY = (_type == OverviewAudioDocuments) ? st::playlistPadding : ((_height < _minHeight) ? (_minHeight - _height) : 0); if (newHeight < _minHeight) { newHeight = _minHeight; } @@ -1766,6 +2435,10 @@ void OverviewInner::showAll(bool recountHeights) { OverviewInner::~OverviewInner() { _dragAction = NoDrag; + for (CachedLinks::const_iterator i = _links.cbegin(), e = _links.cend(); i != e; ++i) { + delete i.value(); + } + _links.clear(); } OverviewWidget::OverviewWidget(QWidget *parent, const PeerData *peer, MediaOverviewType type) : QWidget(parent) @@ -1801,10 +2474,16 @@ void OverviewWidget::clear() { void OverviewWidget::onScroll() { MTP::clearLoaderPriorities(); - if (_scroll.scrollTop() < _scroll.height() * 5) { - if (App::main()) { - App::main()->loadMediaBack(peer(), type(), true); + int32 preloadThreshold = _scroll.height() * 5; + bool needToPreload = false; + do { + needToPreload = (type() == OverviewLinks) ? (_scroll.scrollTop() + preloadThreshold > _scroll.scrollTopMax()) : (_scroll.scrollTop() < preloadThreshold); + if (!needToPreload || !_inner.preloadLocal()) { + break; } + } while (true); + if (needToPreload) { + _inner.preloadMore(); } if (!_noDropResizeIndex) { _inner.dropResizeIndex(); @@ -1836,7 +2515,7 @@ void OverviewWidget::paintEvent(QPaintEvent *e) { } QRect r(e->rect()); - if (type() == OverviewPhotos || type() == OverviewAudioDocuments) { + if (type() == OverviewPhotos || type() == OverviewAudioDocuments || type() == OverviewLinks) { p.fillRect(r, st::white->b); } else { bool hasTopBar = !App::main()->topBar()->isHidden(), hasPlayer = !App::main()->player()->isHidden(); @@ -1883,6 +2562,10 @@ void OverviewWidget::scrollBy(int32 add) { } } +void OverviewWidget::scrollReset() { + _scroll.scrollToY((type() == OverviewLinks) ? 0 : _scroll.scrollTopMax()); +} + void OverviewWidget::paintTopBar(QPainter &p, float64 over, int32 decreaseWidth) { if (animating() && _showing) { p.setOpacity(a_bgAlpha.current()); @@ -1919,6 +2602,10 @@ MediaOverviewType OverviewWidget::type() const { void OverviewWidget::switchType(MediaOverviewType type) { _selCount = 0; + + disconnect(&_scroll, SIGNAL(scrolled()), &_inner, SLOT(onUpdateSelected())); + disconnect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + _inner.setSelectMode(false); _inner.switchType(type); switch (type) { @@ -1927,12 +2614,18 @@ void OverviewWidget::switchType(MediaOverviewType type) { case OverviewDocuments: _header = lang(lng_profile_files_header); break; case OverviewAudios: _header = lang(lng_profile_audios_header); break; case OverviewAudioDocuments: _header = lang(lng_profile_audio_files_header); break; + case OverviewLinks: _header = lang(lng_profile_shared_links_header); break; } noSelectingScroll(); App::main()->topBar()->showSelected(0); updateTopBarSelection(); - _scroll.scrollToY(_scroll.scrollTopMax()); + scrollReset(); + + connect(&_scroll, SIGNAL(scrolled()), &_inner, SLOT(onUpdateSelected())); + connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + onScroll(); + activate(); } void OverviewWidget::updateTopBarSelection() { @@ -1945,7 +2638,7 @@ void OverviewWidget::updateTopBarSelection() { App::main()->topBar()->update(); } if (App::wnd() && !App::wnd()->layerShown()) { - _inner.setFocus(); + _inner.activate(); } update(); } @@ -1969,6 +2662,8 @@ int32 OverviewWidget::countBestScroll() const { return snap(top - int(_scroll.height() - (st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom())) / 2, 0, _scroll.scrollTopMax()); } } + } else if (type() == OverviewLinks) { + return 0; } return _scroll.scrollTopMax(); } @@ -1978,7 +2673,7 @@ void OverviewWidget::fastShow(bool back, int32 lastScrollTop) { resizeEvent(0); _scrollSetAfterShow = (lastScrollTop < 0 ? countBestScroll() : lastScrollTop); show(); - _inner.setFocus(); + _inner.activate(); doneShow(); } @@ -2001,7 +2696,7 @@ void OverviewWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimT anim::start(this); _showing = true; show(); - _inner.setFocus(); + _inner.activate(); App::main()->topBar()->update(); } @@ -2057,9 +2752,7 @@ void OverviewWidget::msgUpdated(PeerId p, const HistoryItem *msg) { } void OverviewWidget::itemRemoved(HistoryItem *row) { - if (row->history()->peer == peer()) { - _inner.itemRemoved(row); - } + _inner.itemRemoved(row); } void OverviewWidget::itemResized(HistoryItem *row, bool scrollToIt) { @@ -2083,7 +2776,7 @@ OverviewWidget::~OverviewWidget() { } void OverviewWidget::activate() { - _inner.setFocus(); + _inner.activate(); } QPoint OverviewWidget::clampMousePosition(QPoint point) { diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index 6c08d903bb..a48367eb61 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -25,9 +25,14 @@ public: OverviewInner(OverviewWidget *overview, ScrollArea *scroll, const PeerData *peer, MediaOverviewType type); + void activate(); + void clear(); int32 itemTop(MsgId msgId) const; + bool preloadLocal(); + void preloadMore(); + bool event(QEvent *e); void touchEvent(QTouchEvent *e); void paintEvent(QPaintEvent *e); @@ -75,6 +80,7 @@ public slots: void onUpdateSelected(); void openContextUrl(); + void copyContextUrl(); void cancelContextDownload(); void showContextInFolder(); void saveContextFile(); @@ -85,12 +91,19 @@ public slots: void forwardMessage(); void selectMessage(); + void onSearchUpdate(); + void onCancel(); + bool onCancelSearch(); + void onMenuDestroy(QObject *obj); void onTouchSelect(); void onTouchScrollTimer(); void onDragExec(); + bool onSearchMessages(bool searchCache = false); + void onNeedSearchMessages(); + private: void fixItemIndex(int32 ¤t, MsgId msgId) const; @@ -122,11 +135,11 @@ private: // photos int32 _photosInRow, _photosToAdd, _vsize; - typedef struct { + struct CachedSize { int32 vsize; bool medium; QPixmap pix; - } CachedSize; + }; typedef QMap CachedSizes; CachedSizes _cached; bool _selMode; @@ -134,16 +147,65 @@ private: // audio documents int32 _audioLeft, _audioWidth, _audioHeight; - // other - typedef struct _CachedItem { - _CachedItem() : msgid(0), y(0) { + // shared links + int32 _linksLeft, _linksWidth; + struct Link { + Link() : width(0) { } - _CachedItem(MsgId msgid, const QDate &date, int32 y) : msgid(msgid), date(date), y(y) { + Link(const QString &url, const QString &text) : url(url), text(text), width(st::msgFont->m.width(text)) { + } + QString url, text; + int32 width; + }; + struct CachedLink { + CachedLink() : titleWidth(0), page(0), pixw(0), pixh(0), text(st::msgMinWidth) { + } + CachedLink(HistoryItem *item); + int32 countHeight(int32 w); + + QString title, letter; + int32 titleWidth; + WebPageData *page; + int32 pixw, pixh; + Text text; + QVector urls; + }; + typedef QMap CachedLinks; + CachedLinks _links; + FlatInput _search; + IconedButton _cancelSearch; + QVector _results; + int32 _itemsToBeLoaded; + + QTimer _searchTimer; + QString _searchQuery; + bool _inSearch, _searchFull; + mtpRequestId _searchRequest; + History::MediaOverview _searchResults; + MsgId _lastSearchId; + int32 _searchedCount; + void searchReceived(bool fromStart, const MTPmessages_Messages &result, mtpRequestId req); + bool searchFailed(const RPCError &error, mtpRequestId req); + + typedef QMap SearchCache; + SearchCache _searchCache; + + typedef QMap SearchQueries; + SearchQueries _searchQueries; + + CachedLink *cachedLink(HistoryItem *item); + + // other + struct CachedItem { + CachedItem() : msgid(0), y(0) { + } + CachedItem(MsgId msgid, const QDate &date, int32 y) : msgid(msgid), date(date), y(y) { } MsgId msgid; QDate date; int32 y; - } CachedItem; + CachedLink *link; + }; typedef QVector CachedItems; CachedItems _items; @@ -166,9 +228,14 @@ private: int32 _dragItemIndex; MsgId _mousedItem; int32 _mousedItemIndex; + int32 _lnkOverIndex, _lnkDownIndex; // for OverviewLinks, 0 - none, -1 - photo or title, > 0 - lnk index uint16 _dragSymbol; bool _dragWasInactive; + QString urlByIndex(MsgId msgid, int32 index, int32 lnkIndex) const; + bool urlIsEmail(const QString &url) const; + + QString _contextMenuUrl; TextLinkPtr _contextMenuLnk; MsgId _dragSelFrom, _dragSelTo; @@ -202,6 +269,7 @@ public: void contextMenuEvent(QContextMenuEvent *e); void scrollBy(int32 add); + void scrollReset(); void paintTopBar(QPainter &p, float64 over, int32 decreaseWidth); void topBarShadowParams(int32 &x, float64 &o); diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp index d5ee5fcafe..28f8f275cc 100644 --- a/Telegram/SourceFiles/profilewidget.cpp +++ b/Telegram/SourceFiles/profilewidget.cpp @@ -59,10 +59,6 @@ ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, const Pee // shared media _allMediaTypes(false), _mediaShowAll(this, lang(lng_profile_show_all_types)), - _mediaPhotos(this, QString()), - _mediaVideos(this, QString()), - _mediaDocuments(this, QString()), - _mediaAudios(this, QString()), // actions _searchInPeer(this, lang(lng_profile_search_messages)), @@ -161,14 +157,11 @@ ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, const Pee // shared media connect(&_mediaShowAll, SIGNAL(clicked()), this, SLOT(onMediaShowAll())); - connect(&_mediaPhotos, SIGNAL(clicked()), this, SLOT(onMediaPhotos())); - connect(&_mediaVideos, SIGNAL(clicked()), this, SLOT(onMediaVideos())); - connect(&_mediaDocuments, SIGNAL(clicked()), this, SLOT(onMediaDocuments())); - connect(&_mediaAudios, SIGNAL(clicked()), this, SLOT(onMediaAudios())); - _mediaLinks[OverviewPhotos] = &_mediaPhotos; - _mediaLinks[OverviewVideos] = &_mediaVideos; - _mediaLinks[OverviewDocuments] = &_mediaDocuments; - _mediaLinks[OverviewAudios] = &_mediaAudios; + connect((_mediaButtons[OverviewPhotos] = new LinkButton(this, QString())), SIGNAL(clicked()), this, SLOT(onMediaPhotos())); + connect((_mediaButtons[OverviewVideos] = new LinkButton(this, QString())), SIGNAL(clicked()), this, SLOT(onMediaVideos())); + connect((_mediaButtons[OverviewDocuments] = new LinkButton(this, QString())), SIGNAL(clicked()), this, SLOT(onMediaDocuments())); + connect((_mediaButtons[OverviewAudios] = new LinkButton(this, QString())), SIGNAL(clicked()), this, SLOT(onMediaAudios())); + connect((_mediaButtons[OverviewLinks] = new LinkButton(this, QString())), SIGNAL(clicked()), this, SLOT(onMediaLinks())); App::main()->preloadOverviews(_peer); // actions @@ -216,7 +209,7 @@ void ProfileInner::loadProfilePhotos(int32 yFrom) { int32 yTo = yFrom + (parentWidget() ? parentWidget()->height() : App::wnd()->height()) * 5; MTP::clearLoaderPriorities(); - int32 partfrom = _mediaAudios.y() + _mediaAudios.height() + st::profileHeaderSkip; + int32 partfrom = _mediaButtons[OverviewAudios]->y() + _mediaButtons[OverviewAudios]->height() + st::profileHeaderSkip; yFrom -= partfrom; yTo -= partfrom; @@ -361,6 +354,10 @@ void ProfileInner::onMediaAudios() { App::main()->showMediaOverview(_peer, OverviewAudios); } +void ProfileInner::onMediaLinks() { + App::main()->showMediaOverview(_peer, OverviewLinks); +} + void ProfileInner::onInvitationLink() { QApplication::clipboard()->setText(_peerChat->invitationUrl); App::wnd()->showLayer(new ConfirmBox(lang(lng_group_invite_copied), true)); @@ -652,7 +649,7 @@ void ProfileInner::paintEvent(QPaintEvent *e) { if (!_allMediaTypes) { break; } - top += _mediaLinks[i]->height() + st::setLittleSkip; + top += _mediaButtons[i]->height() + st::setLittleSkip; } } if (_allMediaTypes) { @@ -660,13 +657,13 @@ void ProfileInner::paintEvent(QPaintEvent *e) { top -= st::setLittleSkip; } else { p.drawText(_left, top + st::linkFont->ascent, lang(oneState < 0 ? lng_profile_loading : lng_profile_no_media)); - top += _mediaLinks[OverviewPhotos]->height(); + top += _mediaButtons[OverviewPhotos]->height(); } } else { if (!oneState) { p.drawText(_left, top + st::linkFont->ascent, lang(lng_profile_no_media)); } - top += _mediaLinks[OverviewPhotos]->height(); + top += _mediaButtons[OverviewPhotos]->height(); } // actions @@ -934,13 +931,13 @@ void ProfileInner::resizeEvent(QResizeEvent *e) { if (_allMediaTypes) { int32 count = (_hist->_overviewCount[i] > 0) ? _hist->_overviewCount[i] : (_hist->_overviewCount[i] == 0 ? _hist->_overview[i].size() : -1); if (count > 0) { - if (wasCount) top += _mediaLinks[i]->height() + st::setLittleSkip; + if (wasCount) top += _mediaButtons[i]->height() + st::setLittleSkip; wasCount = count; } } - _mediaLinks[i]->move(_left, top); + _mediaButtons[i]->move(_left, top); } - top += _mediaLinks[OverviewPhotos]->height(); + top += _mediaButtons[OverviewPhotos]->height(); // actions top += st::profileHeaderSkip; @@ -1131,17 +1128,17 @@ void ProfileInner::showAll() { if (count > 0 || count < 0) { first = true; } else if (!_allMediaTypes) { - _mediaLinks[i]->hide(); + _mediaButtons[i]->hide(); continue; } if (count > 0) { - _mediaLinks[i]->setText(overviewLinkText(i, count)); - _mediaLinks[i]->show(); + _mediaButtons[i]->setText(overviewLinkText(i, count)); + _mediaButtons[i]->show(); } else { - _mediaLinks[i]->hide(); + _mediaButtons[i]->hide(); } } else { - _mediaLinks[i]->hide(); + _mediaButtons[i]->hide(); } } if (_allMediaTypes || !manyCounts) { @@ -1208,6 +1205,7 @@ QString ProfileInner::overviewLinkText(int32 type, int32 count) { case OverviewVideos: return lng_profile_videos(lt_count, count); case OverviewDocuments: return lng_profile_files(lt_count, count); case OverviewAudios: return lng_profile_audios(lt_count, count); + case OverviewLinks: return lng_profile_shared_links(lt_count, count); } return QString(); } diff --git a/Telegram/SourceFiles/profilewidget.h b/Telegram/SourceFiles/profilewidget.h index 5c742d31fe..4c2d2ef186 100644 --- a/Telegram/SourceFiles/profilewidget.h +++ b/Telegram/SourceFiles/profilewidget.h @@ -92,6 +92,7 @@ public slots: void onMediaVideos(); void onMediaDocuments(); void onMediaAudios(); + void onMediaLinks(); void onMenuDestroy(QObject *obj); void onCopyPhone(); @@ -149,8 +150,8 @@ private: // shared media bool _allMediaTypes; - LinkButton _mediaShowAll, _mediaPhotos, _mediaVideos, _mediaDocuments, _mediaAudios; - LinkButton *_mediaLinks[OverviewCount]; + LinkButton _mediaShowAll; + LinkButton *_mediaButtons[OverviewCount]; QString overviewLinkText(int32 type, int32 count); // actions diff --git a/Telegram/SourceFiles/pspecific_linux.cpp b/Telegram/SourceFiles/pspecific_linux.cpp index 3c96e062bb..2859c9067f 100644 --- a/Telegram/SourceFiles/pspecific_linux.cpp +++ b/Telegram/SourceFiles/pspecific_linux.cpp @@ -252,8 +252,8 @@ namespace { #define GTK_ALPHA 3 QImage _trayIconImageGen() { - int32 counter = App::histories().unreadFull, counterSlice = (counter >= 1000) ? (1000 + (counter % 100)) : counter; - bool muted = (App::histories().unreadMuted >= counter); + int32 counter = App::histories().unreadFull - (cIncludeMuted() ? 0 : App::histories().unreadMuted), counterSlice = (counter >= 1000) ? (1000 + (counter % 100)) : counter; + bool muted = cIncludeMuted() ? (App::histories().unreadMuted >= counter) : false; if (_trayIconImage.isNull() || _trayIconImage.width() != _trayIconSize || muted != _trayIconMuted || counterSlice != _trayIconCount) { if (_trayIconImageBack.isNull() || _trayIconImageBack.width() != _trayIconSize) { _trayIconImageBack = App::wnd()->iconLarge().scaled(_trayIconSize, _trayIconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); @@ -288,8 +288,8 @@ namespace { } QString _trayIconImageFile() { - int32 counter = App::histories().unreadFull, counterSlice = (counter >= 1000) ? (1000 + (counter % 100)) : counter; - bool muted = (App::histories().unreadMuted >= counter); + int32 counter = App::histories().unreadFull - (cIncludeMuted() ? 0 : App::histories().unreadMuted), counterSlice = (counter >= 1000) ? (1000 + (counter % 100)) : counter; + bool muted = cIncludeMuted() ? (App::histories().unreadMuted >= counter) : false; QString name = cWorkingDir() + qsl("tdata/ticons/ico%1_%2_%3.png").arg(muted ? "mute" : "").arg(_trayIconSize).arg(counterSlice); QFileInfo info(name); @@ -641,7 +641,7 @@ void PsMainWindow::psUpdateIndicator() { void PsMainWindow::psUpdateCounter() { setWindowIcon(wndIcon); - int32 counter = App::histories().unreadFull; + int32 counter = App::histories().unreadFull - (cIncludeMuted() ? 0 : App::histories().unreadMuted); setWindowTitle((counter > 0) ? qsl("Telegram (%1)").arg(counter) : qsl("Telegram")); if (_psUnityLauncherEntry) { @@ -665,8 +665,10 @@ void PsMainWindow::psUpdateCounter() { ps_gtk_status_icon_set_from_pixbuf(_trayIcon, _trayPixbuf); } } else if (trayIcon) { - int32 counter = App::histories().unreadFull; - style::color bg = (App::histories().unreadMuted < counter) ? st::counterBG : st::counterMuteBG; + int32 counter = App::histories().unreadFull - (cIncludeMuted() ? 0 : App::histories().unreadMuted); + bool muted = cIncludeMuted() ? (App::histories().unreadMuted >= counter) : false; + + style::color bg = muted ? st::counterMuteBG : st::counterBG; QIcon iconSmall; iconSmall.addPixmap(QPixmap::fromImage(iconWithCounter(16, counter, bg, true), Qt::ColorOnly)); iconSmall.addPixmap(QPixmap::fromImage(iconWithCounter(32, counter, bg, true), Qt::ColorOnly)); diff --git a/Telegram/SourceFiles/pspecific_mac.cpp b/Telegram/SourceFiles/pspecific_mac.cpp index 0cac59f759..68e982f3b1 100644 --- a/Telegram/SourceFiles/pspecific_mac.cpp +++ b/Telegram/SourceFiles/pspecific_mac.cpp @@ -174,7 +174,7 @@ void _placeCounter(QImage &img, int size, int count, style::color bg, style::col } void PsMainWindow::psUpdateCounter() { - int32 counter = App::histories().unreadFull; + int32 counter = App::histories().unreadFull - (cIncludeMuted() ? 0 : App::histories().unreadMuted); setWindowTitle((counter > 0) ? qsl("Telegram (%1)").arg(counter) : qsl("Telegram")); setWindowIcon(wndIcon); @@ -183,14 +183,16 @@ void PsMainWindow::psUpdateCounter() { _private.setWindowBadge(counter ? cnt : QString()); if (trayIcon) { - bool dm = objc_darkMode(), important = (App::histories().unreadMuted < counter); - style::color bg = important ? st::counterBG : st::counterMuteBG; + bool muted = cIncludeMuted() ? (App::histories().unreadMuted >= counter) : false; + bool dm = objc_darkMode(); + + style::color bg = muted ? st::counterMuteBG : st::counterBG; QIcon icon; QImage img(psTrayIcon(dm)), imgsel(psTrayIcon(true)); img.detach(); imgsel.detach(); int32 size = cRetina() ? 44 : 22; - _placeCounter(img, size, counter, bg, (dm && !important) ? st::counterMacInvColor : st::counterColor); + _placeCounter(img, size, counter, bg, (dm && muted) ? st::counterMacInvColor : st::counterColor); _placeCounter(imgsel, size, counter, st::white, st::counterMacInvColor); icon.addPixmap(QPixmap::fromImage(img, Qt::ColorOnly)); icon.addPixmap(QPixmap::fromImage(imgsel, Qt::ColorOnly), QIcon::Selected); diff --git a/Telegram/SourceFiles/pspecific_wnd.cpp b/Telegram/SourceFiles/pspecific_wnd.cpp index af60a328a2..76e48f978a 100644 --- a/Telegram/SourceFiles/pspecific_wnd.cpp +++ b/Telegram/SourceFiles/pspecific_wnd.cpp @@ -1117,8 +1117,10 @@ static HICON _qt_createHIcon(const QIcon &icon, int xSize, int ySize) { } void PsMainWindow::psUpdateCounter() { - int32 counter = App::histories().unreadFull; - style::color bg = (App::histories().unreadMuted < counter) ? st::counterBG : st::counterMuteBG; + int32 counter = App::histories().unreadFull - (cIncludeMuted() ? 0 : App::histories().unreadMuted); + bool muted = cIncludeMuted() ? (App::histories().unreadMuted >= counter) : false; + + style::color bg = muted ? st::counterMuteBG : st::counterBG; QIcon iconSmall, iconBig; iconSmall.addPixmap(QPixmap::fromImage(iconWithCounter(16, counter, bg, true), Qt::ColorOnly)); iconSmall.addPixmap(QPixmap::fromImage(iconWithCounter(32, counter, bg, true), Qt::ColorOnly)); @@ -2123,12 +2125,14 @@ bool psShowOpenWithMenu(int x, int y, const QString &file) { } void psOpenFile(const QString &name, bool openWith) { - std::wstring wname = QDir::toNativeSeparators(name).toStdWString(); + bool mailtoScheme = name.startsWith(qstr("mailto:")); + std::wstring wname = mailtoScheme ? name.toStdWString() : QDir::toNativeSeparators(name).toStdWString(); if (openWith && useOpenAs) { if (shOpenWithDialog) { OPENASINFO info; info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC; + if (mailtoScheme) info.oaifInFlags |= OAIF_FILE_IS_URI | OAIF_URL_PROTOCOL; info.pcszClass = NULL; info.pcszFile = wname.c_str(); shOpenWithDialog(0, &info); diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index da839b6764..78790d5f0d 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -40,6 +40,7 @@ QString gLangErrors; QString gDialogLastPath, gDialogHelperPath; // optimize QFileDialog bool gSoundNotify = true; +bool gIncludeMuted = true; bool gDesktopNotify = true; DBINotifyView gNotifyView = dbinvShowPreview; bool gWindowsNotifications = true; diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index 47c9a36107..ad36a2b1bf 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -96,6 +96,7 @@ DeclareSetting(QPixmapPointer, ChatDogImage); DeclareSetting(bool, TileBackground); DeclareSetting(bool, SoundNotify); +DeclareSetting(bool, IncludeMuted); DeclareSetting(bool, NeedConfigResave); DeclareSetting(bool, DesktopNotify); DeclareSetting(DBINotifyView, NotifyView); diff --git a/Telegram/SourceFiles/settingswidget.cpp b/Telegram/SourceFiles/settingswidget.cpp index dc5459a574..41a1ed9a6f 100644 --- a/Telegram/SourceFiles/settingswidget.cpp +++ b/Telegram/SourceFiles/settingswidget.cpp @@ -124,6 +124,7 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : QWidget(parent), _messagePreview(this, lang(lng_settings_show_preview), cNotifyView() <= dbinvShowPreview), _windowsNotifications(this, lang(lng_settings_use_windows), cWindowsNotifications()), _soundNotify(this, lang(lng_settings_sound_notify), cSoundNotify()), + _includeMuted(this, lang(lng_settings_include_muted), cIncludeMuted()), // general _changeLanguage(this, lang(lng_settings_change_lang)), @@ -225,6 +226,7 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : QWidget(parent), connect(&_messagePreview, SIGNAL(changed()), this, SLOT(onMessagePreview())); connect(&_windowsNotifications, SIGNAL(changed()), this, SLOT(onWindowsNotifications())); connect(&_soundNotify, SIGNAL(changed()), this, SLOT(onSoundNotify())); + connect(&_includeMuted, SIGNAL(changed()), this, SLOT(onIncludeMuted())); // general connect(&_changeLanguage, SIGNAL(clicked()), this, SLOT(onChangeLanguage())); @@ -431,7 +433,8 @@ void SettingsInner::paintEvent(QPaintEvent *e) { if (App::wnd()->psHasNativeNotifications() && cPlatform() == dbipWindows) { top += _windowsNotifications.height() + st::setSectionSkip; } - top += _soundNotify.height(); + top += _soundNotify.height() + st::setSectionSkip; + top += _includeMuted.height(); } // general @@ -662,7 +665,8 @@ void SettingsInner::resizeEvent(QResizeEvent *e) { if (App::wnd()->psHasNativeNotifications() && cPlatform() == dbipWindows) { _windowsNotifications.move(_left, top); top += _windowsNotifications.height() + st::setSectionSkip; } - _soundNotify.move(_left, top); top += _soundNotify.height(); + _soundNotify.move(_left, top); top += _soundNotify.height() + st::setSectionSkip; + _includeMuted.move(_left, top); top += _includeMuted.height(); } // general @@ -976,12 +980,14 @@ void SettingsInner::showAll() { _windowsNotifications.hide(); } _soundNotify.show(); + _includeMuted.show(); } else { _desktopNotify.hide(); _senderName.hide(); _messagePreview.hide(); _windowsNotifications.hide(); _soundNotify.hide(); + _includeMuted.hide(); } // general @@ -1443,6 +1449,12 @@ void SettingsInner::onSoundNotify() { Local::writeUserSettings(); } +void SettingsInner::onIncludeMuted() { + cSetIncludeMuted(_includeMuted.checked()); + if (App::wnd()) App::wnd()->updateCounter(); + Local::writeUserSettings(); +} + void SettingsInner::onWindowsNotifications() { cSetWindowsNotifications(!cWindowsNotifications()); App::wnd()->notifyClearFast(); diff --git a/Telegram/SourceFiles/settingswidget.h b/Telegram/SourceFiles/settingswidget.h index 38d4cb7510..81de3b926f 100644 --- a/Telegram/SourceFiles/settingswidget.h +++ b/Telegram/SourceFiles/settingswidget.h @@ -125,6 +125,7 @@ public slots: void onScaleChange(); void onSoundNotify(); + void onIncludeMuted(); void onDesktopNotify(); void onSenderName(); void onMessagePreview(); @@ -206,7 +207,7 @@ private: LinkButton _chooseUsername; // notifications - FlatCheckbox _desktopNotify, _senderName, _messagePreview, _windowsNotifications, _soundNotify; + FlatCheckbox _desktopNotify, _senderName, _messagePreview, _windowsNotifications, _soundNotify, _includeMuted; // general LinkButton _changeLanguage; diff --git a/Telegram/SourceFiles/title.cpp b/Telegram/SourceFiles/title.cpp index 6de0c04243..bea4b9b56a 100644 --- a/Telegram/SourceFiles/title.cpp +++ b/Telegram/SourceFiles/title.cpp @@ -258,8 +258,10 @@ void TitleWidget::updateWideMode() { void TitleWidget::updateCounter() { if (cWideMode() || !MTP::authedId()) return; - int32 counter = App::histories().unreadFull; - style::color bg = (App::histories().unreadMuted < counter) ? st::counterBG : st::counterMuteBG; + int32 counter = App::histories().unreadFull - (cIncludeMuted() ? 0 : App::histories().unreadMuted); + bool muted = cIncludeMuted() ? (App::histories().unreadMuted >= counter) : false; + + style::color bg = muted ? st::counterMuteBG : st::counterBG; if (counter > 0) { int32 size = cRetina() ? -32 : -16; diff --git a/Telegram/SourceFiles/types.h b/Telegram/SourceFiles/types.h index 976f400855..377f922380 100644 --- a/Telegram/SourceFiles/types.h +++ b/Telegram/SourceFiles/types.h @@ -275,6 +275,7 @@ enum DataBlockId { dbiTryIPv6 = 0x28, dbiSongVolume = 0x29, dbiWindowsNotifications = 0x30, + dbiIncludeMuted = 0x31, dbiEncryptedWithSalt = 333, dbiEncrypted = 444, diff --git a/Telegram/Telegram.plist b/Telegram/Telegram.plist index b4aada0932..7f3e9f0dec 100644 --- a/Telegram/Telegram.plist +++ b/Telegram/Telegram.plist @@ -11,7 +11,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.8.52 + 0.8.53 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) CFBundleSignature diff --git a/Telegram/Telegram.rc b/Telegram/Telegram.rc index 0473e16515..175bf43784 100644 Binary files a/Telegram/Telegram.rc and b/Telegram/Telegram.rc differ diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index 0453f6d52b..1cf00b63c1 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -1707,7 +1707,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0.8.52; + CURRENT_PROJECT_VERSION = 0.8.53; DEBUG_INFORMATION_FORMAT = dwarf; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_OPTIMIZATION_LEVEL = 0; @@ -1725,7 +1725,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 0.8.52; + CURRENT_PROJECT_VERSION = 0.8.53; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_OPTIMIZATION_LEVEL = fast; GCC_PREFIX_HEADER = ./SourceFiles/stdafx.h; @@ -1751,10 +1751,10 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0.8.52; + CURRENT_PROJECT_VERSION = 0.8.53; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DYLIB_COMPATIBILITY_VERSION = 0.8; - DYLIB_CURRENT_VERSION = 0.8.52; + DYLIB_CURRENT_VERSION = 0.8.53; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ""; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; @@ -1885,10 +1885,10 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0.8.52; + CURRENT_PROJECT_VERSION = 0.8.53; DEBUG_INFORMATION_FORMAT = dwarf; DYLIB_COMPATIBILITY_VERSION = 0.8; - DYLIB_CURRENT_VERSION = 0.8.52; + DYLIB_CURRENT_VERSION = 0.8.53; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ""; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; diff --git a/Telegram/Version.sh b/Telegram/Version.sh index 453891b1c8..1b9e8f3a8c 100755 --- a/Telegram/Version.sh +++ b/Telegram/Version.sh @@ -1,2 +1,2 @@ -echo 0.8 8052 0.8.52 0 +echo 0.8 8053 0.8.53 1 # AppVersionStrMajor AppVersion AppVersionStr DevChannel diff --git a/Telegram/_qt_5_5_0_patch.diff b/Telegram/_qt_5_5_0_patch.diff index aed8c6268c..534242a94e 100644 --- a/Telegram/_qt_5_5_0_patch.diff +++ b/Telegram/_qt_5_5_0_patch.diff @@ -11566,6 +11566,22 @@ index da0ba27..1d42b79 100644 void QWindowsXpFileDialogHelper::selectNameFilter(const QString &f) { m_data.setSelectedNameFilter(f); // Dialog cannot be updated at run-time. +diff --git a/qtbase/src/plugins/platforms/windows/qwindowsservices.cpp b/qtbase/src/plugins/platforms/windows/qwindowsservices.cpp +index cc697ba..8e15e86 100644 +--- a/qtbase/src/plugins/platforms/windows/qwindowsservices.cpp ++++ b/qtbase/src/plugins/platforms/windows/qwindowsservices.cpp +@@ -125,8 +125,9 @@ static inline bool launchMail(const QUrl &url) + } + // Pass the url as the parameter. Should use QProcess::startDetached(), + // but that cannot handle a Windows command line [yet]. +- command.replace(QStringLiteral("%1"), url.toString(QUrl::FullyEncoded)); +- if (debug) ++ if (command.indexOf(QStringLiteral("%1")) < 0) return false; ++ command.replace(QStringLiteral("%1"), url.toString(QUrl::FullyEncoded)); ++ if (debug) + qDebug() << __FUNCTION__ << "Launching" << command; + //start the process + PROCESS_INFORMATION pi; diff --git a/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp index 543c081..d80429b 100644 --- a/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp diff --git a/Telegram/_qt_5_5_0_patch/qtbase/src/plugins/platforms/windows/qwindowsservices.cpp b/Telegram/_qt_5_5_0_patch/qtbase/src/plugins/platforms/windows/qwindowsservices.cpp new file mode 100644 index 0000000000..8e15e86d5d --- /dev/null +++ b/Telegram/_qt_5_5_0_patch/qtbase/src/plugins/platforms/windows/qwindowsservices.cpp @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#define QT_NO_URL_CAST_FROM_STRING +#include "qwindowsservices.h" +#include "qtwindows_additional.h" + +#include +#include +#include + +#include +#ifndef Q_OS_WINCE +# include +#endif + +QT_BEGIN_NAMESPACE + +enum { debug = 0 }; + +static inline bool shellExecute(const QUrl &url) +{ +#ifndef Q_OS_WINCE + const QString nativeFilePath = + url.isLocalFile() ? QDir::toNativeSeparators(url.toLocalFile()) : url.toString(QUrl::FullyEncoded); + const quintptr result = (quintptr)ShellExecute(0, 0, (wchar_t*)nativeFilePath.utf16(), 0, 0, SW_SHOWNORMAL); + // ShellExecute returns a value greater than 32 if successful + if (result <= 32) { + qWarning("ShellExecute '%s' failed (error %s).", qPrintable(url.toString()), qPrintable(QString::number(result))); + return false; + } + return true; +#else + Q_UNUSED(url); + return false; +#endif +} + +// Retrieve the commandline for the default mail client. It contains a +// placeholder %1 for the URL. The default key used below is the +// command line for the mailto: shell command. +static inline QString mailCommand() +{ + enum { BufferSize = sizeof(wchar_t) * MAX_PATH }; + + const wchar_t mailUserKey[] = L"Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\mailto\\UserChoice"; + + wchar_t command[MAX_PATH] = {0}; + // Check if user has set preference, otherwise use default. + HKEY handle; + QString keyName; + if (!RegOpenKeyEx(HKEY_CURRENT_USER, mailUserKey, 0, KEY_READ, &handle)) { + DWORD bufferSize = BufferSize; + if (!RegQueryValueEx(handle, L"Progid", 0, 0, reinterpret_cast(command), &bufferSize)) + keyName = QString::fromWCharArray(command); + RegCloseKey(handle); + } + if (keyName.isEmpty()) + keyName = QStringLiteral("mailto"); + keyName += QStringLiteral("\\Shell\\Open\\Command"); + if (debug) + qDebug() << __FUNCTION__ << "keyName=" << keyName; + command[0] = 0; + if (!RegOpenKeyExW(HKEY_CLASSES_ROOT, (const wchar_t*)keyName.utf16(), 0, KEY_READ, &handle)) { + DWORD bufferSize = BufferSize; + RegQueryValueEx(handle, L"", 0, 0, reinterpret_cast(command), &bufferSize); + RegCloseKey(handle); + } + if (!command[0]) + return QString(); +#ifndef Q_OS_WINCE + wchar_t expandedCommand[MAX_PATH] = {0}; + return ExpandEnvironmentStrings(command, expandedCommand, MAX_PATH) ? + QString::fromWCharArray(expandedCommand) : QString::fromWCharArray(command); +#else + return QString(); +#endif +} + +static inline bool launchMail(const QUrl &url) +{ + QString command = mailCommand(); + if (command.isEmpty()) { + qWarning("Cannot launch '%s': There is no mail program installed.", qPrintable(url.toString())); + return false; + } + //Make sure the path for the process is in quotes + const QChar doubleQuote = QLatin1Char('"'); + if (!command.startsWith(doubleQuote)) { + const int exeIndex = command.indexOf(QStringLiteral(".exe "), 0, Qt::CaseInsensitive); + if (exeIndex != -1) { + command.insert(exeIndex + 4, doubleQuote); + command.prepend(doubleQuote); + } + } + // Pass the url as the parameter. Should use QProcess::startDetached(), + // but that cannot handle a Windows command line [yet]. + if (command.indexOf(QStringLiteral("%1")) < 0) return false; + command.replace(QStringLiteral("%1"), url.toString(QUrl::FullyEncoded)); + if (debug) + qDebug() << __FUNCTION__ << "Launching" << command; + //start the process + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + STARTUPINFO si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + if (!CreateProcess(NULL, (wchar_t*)command.utf16(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { + qErrnoWarning("Unable to launch '%s'", qPrintable(command)); + return false; + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return true; +} + +bool QWindowsServices::openUrl(const QUrl &url) +{ + const QString scheme = url.scheme(); + if (scheme == QLatin1String("mailto") && launchMail(url)) + return true; + return shellExecute(url); +} + +bool QWindowsServices::openDocument(const QUrl &url) +{ + return shellExecute(url); +} + +QT_END_NAMESPACE