webview_dialog.cpp 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "webview/webview_dialog.h"
  8. #include "webview/webview_interface.h"
  9. #include "ui/widgets/separate_panel.h"
  10. #include "ui/widgets/labels.h"
  11. #include "ui/widgets/fields/input_field.h"
  12. #include "ui/widgets/buttons.h"
  13. #include "ui/wrap/vertical_layout.h"
  14. #include "ui/integration.h"
  15. #include "ui/qt_object_factory.h"
  16. #include "base/invoke_queued.h"
  17. #include "base/unique_qptr.h"
  18. #include "base/integration.h"
  19. #include "styles/style_widgets.h"
  20. #include "styles/style_layers.h"
  21. #include <QtCore/QUrl>
  22. #include <QtCore/QString>
  23. #include <QtCore/QEventLoop>
  24. namespace Webview {
  25. namespace {
  26. constexpr auto kPopupsQuicklyLimit = 3;
  27. constexpr auto kPopupsQuicklyDelay = 3 * crl::time(1000);
  28. bool InBlockingPopup/* = false*/;
  29. int PopupsShownQuickly/* = 0*/;
  30. crl::time PopupLastShown/* = 0*/;
  31. } // namespace
  32. PopupResult ShowBlockingPopup(PopupArgs &&args) {
  33. if (InBlockingPopup) {
  34. return {};
  35. }
  36. InBlockingPopup = true;
  37. const auto guard = gsl::finally([] {
  38. InBlockingPopup = false;
  39. });
  40. if (!args.ignoreFloodCheck) {
  41. const auto now = crl::now();
  42. if (!PopupLastShown || PopupLastShown + kPopupsQuicklyDelay <= now) {
  43. PopupsShownQuickly = 1;
  44. } else if (++PopupsShownQuickly > kPopupsQuicklyLimit) {
  45. return {};
  46. }
  47. }
  48. const auto timeguard = gsl::finally([] {
  49. PopupLastShown = crl::now();
  50. });
  51. // This fixes animations in a nested event loop.
  52. base::Integration::Instance().enterFromEventLoop([] {});
  53. auto result = PopupResult();
  54. auto context = QObject();
  55. QEventLoop loop;
  56. auto running = true;
  57. auto widget = base::unique_qptr<Ui::SeparatePanel>();
  58. InvokeQueued(&context, [&] {
  59. widget = base::make_unique_q<Ui::SeparatePanel>(Ui::SeparatePanelArgs{
  60. .parent = args.parent,
  61. });
  62. const auto raw = widget.get();
  63. raw->setWindowFlag(Qt::WindowStaysOnTopHint, false);
  64. raw->setAttribute(Qt::WA_DeleteOnClose, false);
  65. raw->setAttribute(Qt::WA_ShowModal, true);
  66. const auto titleHeight = args.title.isEmpty()
  67. ? st::separatePanelNoTitleHeight
  68. : st::separatePanelTitleHeight;
  69. if (!args.title.isEmpty()) {
  70. raw->setTitle(rpl::single(args.title));
  71. }
  72. raw->setTitleHeight(titleHeight);
  73. auto layout = base::make_unique_q<Ui::VerticalLayout>(raw);
  74. const auto skip = st::boxDividerHeight;
  75. const auto container = layout.get();
  76. const auto addedRightPadding = args.title.isEmpty()
  77. ? (st::separatePanelClose.width - st::boxRowPadding.right())
  78. : 0;
  79. const auto label = container->add(
  80. object_ptr<Ui::FlatLabel>(
  81. container,
  82. rpl::single(args.text),
  83. st::boxLabel),
  84. st::boxRowPadding + QMargins(0, 0, addedRightPadding, 0));
  85. label->resizeToWidth(st::boxWideWidth
  86. - st::boxRowPadding.left()
  87. - st::boxRowPadding.right()
  88. - addedRightPadding);
  89. const auto input = args.value
  90. ? container->add(
  91. object_ptr<Ui::InputField>(
  92. container,
  93. st::defaultInputField,
  94. rpl::single(QString()),
  95. *args.value),
  96. st::boxRowPadding + QMargins(0, 0, 0, skip))
  97. : nullptr;
  98. const auto buttonPadding = st::webviewDialogPadding;
  99. const auto buttons = container->add(
  100. object_ptr<Ui::RpWidget>(container),
  101. QMargins(
  102. buttonPadding.left(),
  103. buttonPadding.top(),
  104. buttonPadding.left(),
  105. buttonPadding.bottom()));
  106. const auto list = buttons->lifetime().make_state<
  107. std::vector<not_null<Ui::RoundButton*>>
  108. >();
  109. list->reserve(args.buttons.size());
  110. for (const auto &descriptor : args.buttons) {
  111. using Type = PopupArgs::Button::Type;
  112. const auto text = [&] {
  113. const auto integration = &Ui::Integration::Instance();
  114. switch (descriptor.type) {
  115. case Type::Default: return descriptor.text;
  116. case Type::Ok: return integration->phraseButtonOk();
  117. case Type::Close: return integration->phraseButtonClose();
  118. case Type::Cancel: return integration->phraseButtonCancel();
  119. case Type::Destructive: return descriptor.text;
  120. }
  121. Unexpected("Button type in blocking popup.");
  122. }();
  123. const auto button = Ui::CreateChild<Ui::RoundButton>(
  124. buttons,
  125. rpl::single(text),
  126. (descriptor.type != Type::Destructive
  127. ? st::webviewDialogButton
  128. : st::webviewDialogDestructiveButton));
  129. button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
  130. button->setClickedCallback([=, &result, id = descriptor.id]{
  131. result.id = id;
  132. if (input) {
  133. result.value = input->getLastText();
  134. }
  135. raw->hideGetDuration();
  136. });
  137. list->push_back(button);
  138. }
  139. buttons->resizeToWidth(st::boxWideWidth - 2 * buttonPadding.left());
  140. buttons->widthValue(
  141. ) | rpl::start_with_next([=](int width) {
  142. const auto count = list->size();
  143. const auto skip = st::webviewDialogPadding.right();
  144. auto buttonsWidth = 0;
  145. for (const auto &button : *list) {
  146. buttonsWidth += button->width() + (buttonsWidth ? skip : 0);
  147. }
  148. const auto vertical = (count > 1) && (buttonsWidth > width);
  149. const auto single = st::webviewDialogSubmit.height;
  150. auto top = 0;
  151. auto right = 0;
  152. for (const auto &button : *list) {
  153. button->moveToRight(right, top, width);
  154. if (vertical) {
  155. top += single + skip;
  156. } else {
  157. right += button->width() + skip;
  158. }
  159. }
  160. const auto height = (top > 0) ? (top - skip) : single;
  161. if (buttons->height() != height) {
  162. buttons->resize(buttons->width(), height);
  163. }
  164. }, buttons->lifetime());
  165. buttons->heightValue(
  166. ) | rpl::start_with_next([=](int height) {
  167. const auto full = titleHeight
  168. + label->height()
  169. + (input ? (skip + input->height()) : 0)
  170. + buttonPadding.top()
  171. + height
  172. + buttonPadding.bottom();
  173. raw->setInnerSize({ st::boxWideWidth, full });
  174. }, buttons->lifetime());
  175. container->resizeToWidth(st::boxWideWidth);
  176. if (input) {
  177. input->selectAll();
  178. input->setFocusFast();
  179. const auto submitted = [=, &result] {
  180. result.value = input->getLastText();
  181. raw->hideGetDuration();
  182. };
  183. input->submits(
  184. ) | rpl::start_with_next(submitted, input->lifetime());
  185. }
  186. container->events(
  187. ) | rpl::start_with_next([=](not_null<QEvent*> event) {
  188. if (input && event->type() == QEvent::FocusIn) {
  189. input->setFocus();
  190. }
  191. }, container->lifetime());
  192. raw->closeRequests() | rpl::start_with_next([=] {
  193. raw->hideGetDuration();
  194. }, raw->lifetime());
  195. const auto finish = [&] {
  196. if (running) {
  197. running = false;
  198. loop.quit();
  199. }
  200. };
  201. QObject::connect(raw, &QObject::destroyed, finish);
  202. raw->closeEvents() | rpl::start_with_next(finish, raw->lifetime());
  203. raw->showInner(std::move(layout));
  204. });
  205. loop.exec(QEventLoop::DialogExec);
  206. widget = nullptr;
  207. return result;
  208. }
  209. DialogResult DefaultDialogHandler(DialogArgs &&args) {
  210. auto buttons = std::vector<PopupArgs::Button>();
  211. buttons.push_back({
  212. .id = "ok",
  213. .type = PopupArgs::Button::Type::Ok,
  214. });
  215. if (args.type != DialogType::Alert) {
  216. buttons.push_back({
  217. .id = "cancel",
  218. .type = PopupArgs::Button::Type::Cancel,
  219. });
  220. }
  221. const auto result = ShowBlockingPopup({
  222. .parent = args.parent,
  223. .title = QUrl(QString::fromStdString(args.url)).host(),
  224. .text = QString::fromStdString(args.text),
  225. .value = (args.type == DialogType::Prompt
  226. ? QString::fromStdString(args.value)
  227. : std::optional<QString>()),
  228. .buttons = std::move(buttons),
  229. });
  230. return {
  231. .text = (result.id == "cancel"
  232. ? std::string()
  233. : result.value.value_or(QString()).toStdString()),
  234. .accepted = (result.id == "ok" || result.value.has_value()),
  235. };
  236. }
  237. } // namespace Webview