From 4bb5dcf50c067e4caf7964180386c25db43a74f6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 18 Dec 2018 17:56:38 +0400 Subject: [PATCH] Simplest poll layout. --- Telegram/Resources/basic.style | 13 - Telegram/Resources/langs/lang.strings | 4 + Telegram/SourceFiles/boxes/share_box.cpp | 1 - .../SourceFiles/data/data_media_types.cpp | 3 +- Telegram/SourceFiles/data/data_poll.cpp | 1 + Telegram/SourceFiles/data/data_poll.h | 2 + Telegram/SourceFiles/data/data_session.cpp | 24 +- Telegram/SourceFiles/history/history.style | 20 ++ .../history/media/history_media_common.cpp | 1 + .../history/media/history_media_poll.cpp | 270 ++++++++++++++++++ .../history/media/history_media_poll.h | 61 ++++ Telegram/SourceFiles/ui/text_options.cpp | 3 +- Telegram/gyp/telegram_sources.txt | 2 + 13 files changed, 374 insertions(+), 31 deletions(-) create mode 100644 Telegram/SourceFiles/history/media/history_media_poll.cpp create mode 100644 Telegram/SourceFiles/history/media/history_media_poll.h diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index dfdda9022..535965958 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -258,19 +258,6 @@ videoIcon: icon { }; locationSize: size(320px, 240px); -webPageLeft: 10px; -webPageBar: 2px; -webPageTitleFont: semiboldFont; -webPageTitleStyle: semiboldTextStyle; -webPageTitleOutFg: historyTextOutFg; -webPageTitleInFg: historyTextInFg; -webPageDescriptionOutFg: historyTextOutFg; -webPageDescriptionInFg: historyTextInFg; -webPageDescriptionFont: normalFont; -webPageDescriptionStyle: defaultTextStyle; -webPagePhotoSize: 100px; -webPagePhotoDelta: 8px; - mediaPlayerSuppressDuration: 150; botDescSkip: 8px; diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e743d7ed4..4b0b29438 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1829,6 +1829,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_launch_exe_sure" = "Run"; "lng_launch_exe_dont_ask" = "Don't ask me again"; +"lng_polls_anonymous" = "Anonymous Poll"; +"lng_polls_votes_count#one" = "{count} vote"; +"lng_polls_votes_count#other" = "{count} votes"; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 12fd6dff1..a13c9c9e7 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -25,7 +25,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text_options.h" #include "chat_helpers/message_field.h" #include "history/history.h" -//#include "history/media/history_media_types.h" #include "history/history_message.h" #include "window/themes/window_theme.h" #include "boxes/peer_list_box.h" diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 5220ade16..1cf800791 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/media/history_media_invoice.h" #include "history/media/history_media_call.h" #include "history/media/history_media_web_page.h" +#include "history/media/history_media_poll.h" #include "ui/image/image.h" #include "ui/image/image_source.h" #include "ui/text_options.h" @@ -1241,7 +1242,7 @@ bool MediaPoll::updateSentMedia(const MTPMessageMedia &media) { std::unique_ptr MediaPoll::createView( not_null message, not_null realParent) { - return nullptr; + return std::make_unique(message, _poll); } } // namespace Data diff --git a/Telegram/SourceFiles/data/data_poll.cpp b/Telegram/SourceFiles/data/data_poll.cpp index eccf2df92..b19b6a8e5 100644 --- a/Telegram/SourceFiles/data/data_poll.cpp +++ b/Telegram/SourceFiles/data/data_poll.cpp @@ -67,6 +67,7 @@ bool PollData::applyChanges(const MTPDpoll &poll) { } } } + ++version; return true; } diff --git a/Telegram/SourceFiles/data/data_poll.h b/Telegram/SourceFiles/data/data_poll.h index f197199c8..cfd5788b6 100644 --- a/Telegram/SourceFiles/data/data_poll.h +++ b/Telegram/SourceFiles/data/data_poll.h @@ -38,6 +38,8 @@ struct PollData { int totalVoters = 0; bool closed = false; + int version = 0; + private: bool applyResultToAnswers( const MTPPollAnswerVoters &result, diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 058d5ac73..e6cc2e70b 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1529,22 +1529,16 @@ not_null Session::poll(const MTPDmessageMediaPoll &data) { } void Session::applyPollUpdate(const MTPDupdateMessagePoll &update) { - const auto poll = [&] { - if (update.has_poll()) { - return update.vpoll.match([&](const MTPDpoll &data) { - const auto i = _polls.find(data.vid.v); - return (i != end(_polls)) ? i->second.get() : nullptr; - }); - } - const auto item = App::histItemById( - peerToChannel(peerFromMTP(update.vpeer)), - update.vmsg_id.v); - return (item && item->media()) - ? item->media()->poll() - : nullptr; + const auto updated = [&] { + const auto i = _polls.find(update.vpoll_id.v); + return (i == end(_polls)) + ? nullptr + : update.has_poll() + ? poll(update.vpoll).get() + : i->second.get(); }(); - if (poll && poll->applyResults(update.vresults)) { - requestPollViewRepaint(poll); + if (updated && updated->applyResults(update.vresults)) { + requestPollViewRepaint(updated); } } diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index 4d4aa9484..1b5a77afd 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -479,6 +479,26 @@ historyAboutProxyPadding: margins(20px, 10px, 20px, 10px); historyMapPoint: icon {{ "map_point", mapPointDrop }}; historyMapPointInner: icon {{ "map_point_inner", mapPointDot }}; +webPageLeft: 10px; +webPageBar: 2px; +webPageTitleFont: semiboldFont; +webPageTitleStyle: semiboldTextStyle; +webPageTitleOutFg: historyTextOutFg; +webPageTitleInFg: historyTextInFg; +webPageDescriptionOutFg: historyTextOutFg; +webPageDescriptionInFg: historyTextInFg; +webPageDescriptionFont: normalFont; +webPageDescriptionStyle: defaultTextStyle; +webPagePhotoSize: 100px; +webPagePhotoDelta: 8px; + +historyPollQuestionStyle: semiboldTextStyle; +historyPollAnswerStyle: defaultTextStyle; +historyPollQuestionTop: 7px; +historyPollSubtitleSkip: 5px; +historyPollAnswerPadding: margins(30px, 16px, 0px, 16px); +historyPollAnswersSkip: 5px; + boxAttachEmoji: IconButton(historyAttachEmoji) { width: 30px; height: 30px; diff --git a/Telegram/SourceFiles/history/media/history_media_common.cpp b/Telegram/SourceFiles/history/media/history_media_common.cpp index a1a2d3ef1..f4a6ce004 100644 --- a/Telegram/SourceFiles/history/media/history_media_common.cpp +++ b/Telegram/SourceFiles/history/media/history_media_common.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/media/history_media_document.h" #include "history/media/history_media_sticker.h" #include "history/media/history_media_video.h" +#include "styles/style_history.h" int documentMaxStatusWidth(DocumentData *document) { auto result = st::normalFont->width(formatDownloadText(document->size, document->size)); diff --git a/Telegram/SourceFiles/history/media/history_media_poll.cpp b/Telegram/SourceFiles/history/media/history_media_poll.cpp new file mode 100644 index 000000000..2297cb315 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_poll.cpp @@ -0,0 +1,270 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/media/history_media_poll.h" + +#include "lang/lang_keys.h" +#include "layout.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "calls/calls_instance.h" +#include "ui/text_options.h" +#include "data/data_media_types.h" +#include "data/data_poll.h" +#include "styles/style_history.h" +#include "styles/style_widgets.h" + +namespace { + +using TextState = HistoryView::TextState; + +struct FormattedLargeNumber { + int rounded = 0; + bool shortened = false; + QString text; +}; + +FormattedLargeNumber FormatLargeNumber(int64 number) { + auto result = FormattedLargeNumber(); + const auto abs = std::abs(number); + const auto shorten = [&](int64 divider, char multiplier) { + const auto sign = (number > 0) ? 1 : -1; + const auto rounded = abs / (divider / 10); + result.rounded = sign * rounded * (divider / 10); + result.text = QString::number(sign * rounded / 10); + if (rounded % 10) { + result.text += '.' + QString::number(rounded % 10) + multiplier; + } else { + result.text += multiplier; + } + result.shortened = true; + }; + if (abs >= 1'000'000) { + shorten(1'000'000, 'M'); + } else if (abs >= 10'000) { + shorten(1'000, 'K'); + } else { + result.rounded = number; + result.text = QString::number(number); + } + return result; +} + +} // namespace + +HistoryPoll::Answer::Answer() : text(st::msgMinWidth / 2) { +} + +HistoryPoll::HistoryPoll( + not_null parent, + not_null poll) +: HistoryMedia(parent) +, _poll(poll) +, _question(st::msgMinWidth / 2) { +} + +QSize HistoryPoll::countOptimalSize() { + updateTexts(); + + const auto paddings = st::msgPadding.left() + st::msgPadding.right(); + + auto maxWidth = st::msgFileMinWidth; + accumulate_max(maxWidth, paddings + _question.maxWidth()); + for (const auto &answer : _answers) { + accumulate_max( + maxWidth, + paddings + + st::historyPollAnswerPadding.left() + + answer.text.maxWidth() + + st::historyPollAnswerPadding.right()); + } + + const auto answersHeight = ranges::accumulate(ranges::view::all( + _answers + ) | ranges::view::transform([](const Answer &answer) { + return st::historyPollAnswerPadding.top() + + 2 * st::defaultCheckbox.textPosition.y() + + answer.text.minHeight() + + st::historyPollAnswerPadding.bottom() + + st::lineWidth; + }), 0); + + auto minHeight = st::historyPollQuestionTop + + _question.minHeight() + + st::historyPollSubtitleSkip + + st::msgDateTextStyle.font->height + + st::historyPollAnswersSkip + + answersHeight + + st::msgPadding.bottom() + + st::msgDateTextStyle.font->height + + st::msgPadding.bottom(); + if (!isBubbleTop()) { + minHeight -= st::msgFileTopMinus; + } + return { maxWidth, minHeight }; +} + +QSize HistoryPoll::countCurrentSize(int newWidth) { + const auto paddings = st::msgPadding.left() + st::msgPadding.right(); + + accumulate_min(newWidth, maxWidth()); + const auto innerWidth = newWidth + - st::msgPadding.left() + - st::msgPadding.right(); + + const auto answerWidth = innerWidth + - st::historyPollAnswerPadding.left() + - st::historyPollAnswerPadding.right(); + const auto answersHeight = ranges::accumulate(ranges::view::all( + _answers + ) | ranges::view::transform([&](const Answer &answer) { + return st::historyPollAnswerPadding.top() + + 2 * st::defaultCheckbox.textPosition.y() + + answer.text.countHeight(answerWidth) + + st::historyPollAnswerPadding.bottom() + + st::lineWidth; + }), 0); + + auto newHeight = st::historyPollQuestionTop + + _question.countHeight(innerWidth) + + st::historyPollSubtitleSkip + + st::msgDateTextStyle.font->height + + st::historyPollAnswersSkip + + answersHeight + + st::msgPadding.bottom() + + st::msgDateTextStyle.font->height + + st::msgPadding.bottom(); + if (!isBubbleTop()) { + newHeight -= st::msgFileTopMinus; + } + return { newWidth, newHeight }; +} + +void HistoryPoll::updateTexts() { + if (_pollVersion == _poll->version) { + return; + } + _pollVersion = _poll->version; + _question.setText( + st::historyPollQuestionStyle, + _poll->question, + Ui::WebpageTextTitleOptions()); + _subtitle.setText( + st::msgDateTextStyle, + lang(lng_polls_anonymous)); + + _answers = ranges::view::all( + _poll->answers + ) | ranges::view::transform([](const PollAnswer &answer) { + auto result = Answer(); + result.option = answer.option; + result.text.setText( + st::historyPollAnswerStyle, + answer.text, + Ui::WebpageTextTitleOptions()); + return result; + }) | ranges::to_vector; + + _voted = ranges::find( + _poll->answers, + true, + [](const PollAnswer &answer) { return answer.chosen; } + ) != end(_poll->answers); +} + +void HistoryPoll::updateVotes() const { + updateTotalVotes(); +} + +void HistoryPoll::updateTotalVotes() const { + if (_totalVotes == _poll->totalVoters) { + return; + } + _totalVotes = _poll->totalVoters; + if (!_totalVotes) { + _totalVotesLabel.clear(); + return; + } + const auto formatted = FormatLargeNumber(_totalVotes); + auto string = lng_polls_votes_count(lt_count, formatted.rounded); + if (formatted.shortened) { + string.replace(QString::number(formatted.rounded), formatted.text); + } + _totalVotesLabel.setText(st::msgDateTextStyle, string); +} + +void HistoryPoll::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + + updateVotes(); + + auto outbg = _parent->hasOutLayout(); + bool selected = (selection == FullSelection); + + auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg); + auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg); + + auto padding = st::msgPadding; + auto tshift = st::historyPollQuestionTop; + if (!isBubbleTop()) { + tshift -= st::msgFileTopMinus; + } + paintw -= padding.left() + padding.right(); + + p.setPen(outbg ? st::webPageTitleOutFg : st::webPageTitleInFg); + _question.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, selection); + tshift += _question.countHeight(paintw) + st::historyPollSubtitleSkip; + + p.setPen(regular); + _subtitle.drawLeftElided(p, padding.left(), tshift, paintw, width()); + tshift += st::msgDateTextStyle.font->height + st::historyPollAnswersSkip; + + const auto aleft = padding.left() + st::historyPollAnswerPadding.left(); + const auto awidth = paintw - st::historyPollAnswerPadding.left() - st::historyPollAnswerPadding.right(); + for (const auto &answer : _answers) { + tshift += st::historyPollAnswerPadding.top(); + + { + PainterHighQualityEnabler hq(p); + const auto &st = st::defaultRadio; + auto pen = regular->p; + pen.setWidth(st.thickness); + p.setPen(pen); + p.setBrush(Qt::NoBrush); + p.drawEllipse(QRectF(padding.left(), tshift, st.diameter, st.diameter).marginsRemoved(QMarginsF(st.thickness / 2., st.thickness / 2., st.thickness / 2., st.thickness / 2.))); + } + + tshift += st::defaultCheckbox.textPosition.y(); + p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); + answer.text.drawLeft(p, aleft, tshift, awidth, width()); + tshift += answer.text.countHeight(awidth) + + st::defaultCheckbox.textPosition.y() + + st::historyPollAnswerPadding.bottom(); + + p.setOpacity(0.5); + p.fillRect(aleft, tshift, width() - aleft, st::lineWidth, regular); + tshift += st::lineWidth; + p.setOpacity(1.); + } + if (!_totalVotesLabel.isEmpty()) { + tshift += st::msgPadding.bottom(); + p.setPen(regular); + _totalVotesLabel.drawLeftElided(p, padding.left(), tshift, paintw, width()); + } +} + +TextState HistoryPoll::textState(QPoint point, StateRequest request) const { + auto result = TextState(_parent); + if (QRect(0, 0, width(), height()).contains(point)) { +// result.link = _link; + return result; + } + return result; +} diff --git a/Telegram/SourceFiles/history/media/history_media_poll.h b/Telegram/SourceFiles/history/media/history_media_poll.h new file mode 100644 index 000000000..75f91bfff --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_poll.h @@ -0,0 +1,61 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "history/media/history_media.h" + +class HistoryPoll : public HistoryMedia { +public: + HistoryPoll( + not_null parent, + not_null poll); + + void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; + TextState textState(QPoint point, StateRequest request) const override; + + bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { + return true; + } + bool dragItemByHandler(const ClickHandlerPtr &p) const override { + return true; + } + + bool needsBubble() const override { + return true; + } + bool customInfoLayout() const override { + return false; + } + +private: + struct Answer { + Answer(); + + Text text; + QByteArray option; + mutable int votes = 0; + mutable bool chosen = false; + }; + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + + void updateTexts(); + void updateVotes() const; + void updateTotalVotes() const; + + not_null _poll; + int _pollVersion = 0; + mutable int _totalVotes = 0; + mutable bool _voted = false; + + Text _question; + Text _subtitle; + std::vector _answers; + mutable Text _totalVotesLabel; + +}; diff --git a/Telegram/SourceFiles/ui/text_options.cpp b/Telegram/SourceFiles/ui/text_options.cpp index 79aa86d99..f052e78ed 100644 --- a/Telegram/SourceFiles/ui/text_options.cpp +++ b/Telegram/SourceFiles/ui/text_options.cpp @@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/text_options.h" -#include "styles/style_window.h" #include "history/history.h" #include "history/history_item.h" +#include "styles/style_history.h" +#include "styles/style_window.h" namespace Ui { namespace { diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 1f100825c..f1c08bb2a 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -244,6 +244,8 @@ <(src_loc)/history/media/history_media_location.cpp <(src_loc)/history/media/history_media_photo.h <(src_loc)/history/media/history_media_photo.cpp +<(src_loc)/history/media/history_media_poll.h +<(src_loc)/history/media/history_media_poll.cpp <(src_loc)/history/media/history_media_sticker.h <(src_loc)/history/media/history_media_sticker.cpp <(src_loc)/history/media/history_media_video.h