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 | |
51 | namespace ui { |
52 | |
53 | Alert::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 | |
93 | void Alert::setTitle(const std::string& title) |
94 | { |
95 | setText(title); |
96 | } |
97 | |
98 | void 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 | |
105 | void Alert::addSeparator() |
106 | { |
107 | auto sep = new Separator("" , HORIZONTAL); |
108 | m_labelsPlaceholder->addChild(sep); |
109 | } |
110 | |
111 | void 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 | |
126 | void 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 | |
135 | CheckBox* 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 | |
143 | void 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 |
150 | AlertPtr 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 |
161 | int 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 | |
171 | int 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 | |
196 | void 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 | |