1// Aseprite UI Library
2// Copyright (C) 2019-2022 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This file is released under the terms of the MIT license.
6// Read LICENSE.txt for more information.
7
8/***********************************************************************
9
10 Alert format:
11 ------------
12
13 "Title Text"
14 "==Centre line of text"
15 "--"
16 "<<Left line of text"
17 ">>Right line of text"
18 "||My &Button||&Your Button||&He Button"
19
20 .-----------------------------------------.
21 | My Message |
22 +=========================================+
23 | Centre line of text |
24 | --------------------------------------- |
25 | Left line of text |
26 | Right line of text |
27 | |
28 | [My Button] [Your Button] [Him Button] |
29 `-----~---------~-------------~-----------'
30 */
31
32#ifdef HAVE_CONFIG_H
33#include "config.h"
34#endif
35
36#include "ui/alert.h"
37
38#include "base/string.h"
39#include "ui/box.h"
40#include "ui/button.h"
41#include "ui/grid.h"
42#include "ui/label.h"
43#include "ui/scale.h"
44#include "ui/separator.h"
45#include "ui/slider.h"
46#include "ui/theme.h"
47
48#include <algorithm>
49#include <cstdio>
50
51namespace ui {
52
53Alert::Alert()
54 : Window(WithTitleBar)
55 , m_progress(nullptr)
56 , m_progressPlaceholder(nullptr)
57{
58 auto box1 = new Box(VERTICAL);
59 auto box2 = new Box(VERTICAL);
60 auto grid = new Grid(1, false);
61 auto box3 = new Box(HORIZONTAL | HOMOGENEOUS);
62
63 // To identify by the user
64 box2->setId("labels");
65 box3->setId("buttons");
66 m_labelsPlaceholder = box2;
67 m_buttonsPlaceholder = box3;
68
69 // Pseudo separators (only to fill blank space)
70 auto box4 = new Box(0);
71 auto box5 = new Box(0);
72 m_progressPlaceholder = new Box(0);
73
74 box4->setExpansive(true);
75 box5->setExpansive(true);
76 box4->noBorderNoChildSpacing();
77 box5->noBorderNoChildSpacing();
78 m_progressPlaceholder->noBorderNoChildSpacing();
79
80 // Setup parent <-> children relationship
81
82 addChild(box1);
83
84 box1->addChild(box4); // Filler
85 box1->addChild(box2); // Labels
86 box1->addChild(m_progressPlaceholder);
87 box1->addChild(box5); // Filler
88 box1->addChild(grid); // Buttons
89
90 grid->addChildInCell(box3, 1, 1, CENTER | BOTTOM | HORIZONTAL);
91}
92
93void Alert::setTitle(const std::string& title)
94{
95 setText(title);
96}
97
98void Alert::addLabel(const std::string& text, const int align)
99{
100 auto label = new Label(text);
101 label->setAlign(align);
102 m_labelsPlaceholder->addChild(label);
103}
104
105void Alert::addSeparator()
106{
107 auto sep = new Separator("", HORIZONTAL);
108 m_labelsPlaceholder->addChild(sep);
109}
110
111void Alert::addButton(const std::string& text)
112{
113 auto button = new Button(text);
114 button->processMnemonicFromText();
115 button->setMinSize(gfx::Size(60*guiscale(), 0));
116 m_buttons.push_back(button);
117
118 char id[256];
119 sprintf(id, "button-%lu", m_buttons.size());
120 button->setId(id);
121 button->Click.connect([this, button]{ closeWindow(button); });
122
123 m_buttonsPlaceholder->addChild(button);
124}
125
126void Alert::addProgress()
127{
128 ASSERT(!m_progress);
129 m_progress = new Slider(0, 100, 0);
130 m_progress->setReadOnly(true);
131 m_progressPlaceholder->addChild(m_progress);
132 m_progressPlaceholder->setVisible(true);
133}
134
135CheckBox* Alert::addCheckBox(const std::string& text)
136{
137 auto checkBox = new CheckBox(text);
138 m_progressPlaceholder->addChild(checkBox);
139 m_progressPlaceholder->setVisible(true);
140 return checkBox;
141}
142
143void Alert::setProgress(double progress)
144{
145 ASSERT(m_progress);
146 m_progress->setValue(int(std::clamp(progress * 100.0, 0.0, 100.0)));
147}
148
149// static
150AlertPtr Alert::create(const std::string& _msg)
151{
152 std::string msg(_msg);
153
154 // Create the alert window
155 AlertPtr window(new Alert);
156 window->processString(msg);
157 return window;
158}
159
160// static
161int Alert::show(const std::string& _msg)
162{
163 std::string msg(_msg);
164
165 // Create the alert window
166 AlertPtr window(new Alert);
167 window->processString(msg);
168 return window->show();
169}
170
171int Alert::show()
172{
173 // Default button is the first one (Enter default option, Esc should
174 // act like the last button)
175 if (!m_buttons.empty())
176 m_buttons[0]->setFocusMagnet(true);
177
178 // Open it
179 openWindowInForeground();
180
181 // Check the closer
182 int ret = 0;
183 if (Widget* closer = this->closer()) {
184 for (int i=0; i<(int)m_buttons.size(); ++i) {
185 if (closer == m_buttons[i]) {
186 ret = i+1;
187 break;
188 }
189 }
190 }
191
192 // And return it
193 return ret;
194}
195
196void Alert::processString(std::string& buf)
197{
198 bool title = true;
199 bool label = false;
200 bool separator = false;
201 bool button = false;
202 int align = 0;
203
204 // Process buffer
205 int c = 0;
206 int beg = 0;
207 for (;;) {
208 // Ignore characters
209 if (buf[c] == '\n' ||
210 buf[c] == '\r') {
211 buf.erase(c, 1);
212 continue;
213 }
214
215 if ((!buf[c]) ||
216 ((buf[c] == buf[c+1]) &&
217 ((buf[c] == '<') ||
218 (buf[c] == '=') ||
219 (buf[c] == '>') ||
220 (buf[c] == '-') ||
221 (buf[c] == '|')))) {
222 if (title || label || separator || button) {
223 std::string item = buf.substr(beg, c-beg);
224
225 if (title)
226 setTitle(item);
227 else if (label)
228 addLabel(item, align);
229 else if (separator)
230 addSeparator();
231 else if (button)
232 addButton(item);
233 }
234
235 // Done
236 if (!buf[c])
237 break;
238 // Next widget
239 else {
240 title = label = separator = button = false;
241 beg = c+2;
242 align = 0;
243
244 switch (buf[c]) {
245 case '<': label=true; align=LEFT; break;
246 case '=': label=true; align=CENTER; break;
247 case '>': label=true; align=RIGHT; break;
248 case '-': separator=true; break;
249 case '|': button=true; break;
250 }
251 ++c;
252 }
253 }
254 ++c;
255 }
256}
257
258} // namespace ui
259