1 /* MessageBox.cpp
2  * Copyright (C) 2018, 2019  Sven Jähnichen
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16  */
17 
18 #include "MessageBox.hpp"
19 
20 namespace BWidgets
21 {
MessageBox()22 MessageBox::MessageBox () : MessageBox (0.0, 0.0, 0.0, 0.0, "messagebox", "", "MessageBox") {}
23 
MessageBox(const double x,const double y,const double width,const double height,const std::string & text,std::vector<std::string> buttons)24 MessageBox::MessageBox (const double x, const double y, const double width, const double height,
25 			const std::string& text, std::vector<std::string> buttons) :
26 		MessageBox (x, y, width, height, text, "", text, buttons) {}
27 
MessageBox(const double x,const double y,const double width,const double height,const std::string & title,const std::string & text,std::vector<std::string> buttons)28 MessageBox::MessageBox (const double x, const double y, const double width, const double height,
29 			const std::string& title, const std::string& text, std::vector<std::string> buttons) :
30 		MessageBox (x, y, width, height, title, title, text, buttons) {}
31 
MessageBox(const double x,const double y,const double width,const double height,const std::string & name,const std::string & title,const std::string & text,std::vector<std::string> buttonLabels)32 MessageBox::MessageBox (const double x, const double y, const double width, const double height,
33 			const std::string& name, const std::string& title, const std::string& text, std::vector<std::string> buttonLabels) :
34 		ValueWidget (x, y, width, height, name, 0.0),
35 		titleBox (0, 0, 0, 0, name + BWIDGETS_DEFAULT_MESSAGEBOX_TITLE_NAME, ""),
36 		textBox (0, 0, 0, 0, name + BWIDGETS_DEFAULT_MESSAGEBOX_TEXT_NAME, ""),
37 		okButton (0, 0, BWIDGETS_DEFAULT_BUTTON_WIDTH, BWIDGETS_DEFAULT_BUTTON_HEIGHT, name + BWIDGETS_DEFAULT_MESSAGEBOX_BUTTON_NAME, "OK", 0.0),
38 		buttons ()
39 {
40 	// Title
41 	setTitle (title);
42 	titleBox.setClickable (false);
43 	add (titleBox);
44 
45 	// Text
46 	setText (text);
47 	textBox.setClickable (false);
48 	add (textBox);
49 
50 	// Buttons
51 	if (!buttonLabels. empty ()) addButtons (buttonLabels);
52 
53 	// Or at least the OK button
54 	if (buttons.empty ())
55 	{
56 		okButton.setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, MessageBox::redirectPostValueChanged);
57 		buttons.push_back (&okButton);
58 		add (okButton);
59 	}
60 
61 	background_ = BWIDGETS_DEFAULT_MENU_BACKGROUND;
62 	border_ = BWIDGETS_DEFAULT_MENU_BORDER;
63 	setDraggable (true);
64 }
65 
MessageBox(const MessageBox & that)66 MessageBox::MessageBox (const MessageBox& that) : ValueWidget (that)
67 {
68 	titleBox = that.titleBox;
69 	add (titleBox);
70 	textBox = that.textBox;
71 	add (textBox);
72 
73 	for (TextButton* b : that.buttons)
74 	{
75 		addButton (b->getLabel ()->getText ());
76 	}
77 
78 	okButton = that.okButton;
79 	if (buttons.empty ())
80 	{
81 		okButton.getLabel ()->setTextColors(BColors::darks);
82 		okButton.setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, MessageBox::redirectPostValueChanged);
83 		buttons.push_back (&okButton);
84 		add (okButton);
85 	}
86 
87 	update ();
88 }
89 
~MessageBox()90 MessageBox::~MessageBox ()
91 {
92 	while (!buttons.empty ())
93 	{
94 		TextButton* b = buttons.back ();
95 		if (b && (b != &okButton)) delete b;
96 		else release (b);
97 		buttons.pop_back ();
98 	}
99 }
100 
operator =(const MessageBox & that)101 MessageBox& MessageBox::operator= (const MessageBox& that)
102 {
103 	titleBox = that.titleBox;
104 	textBox = that.textBox;
105 	okButton = that.okButton;
106 
107 	// Clean buttons first
108 	while (!buttons.empty ())
109 	{
110 		TextButton* b = buttons.back ();
111 		if (b && (b != &okButton)) delete b;
112 		else release (b);
113 		buttons.pop_back ();
114 	}
115 
116 	// Hard copy buttons
117 	for (TextButton* b : that.buttons)
118 	{
119 		addButton (b->getLabel ()->getText ());
120 	}
121 
122 	// At least an OK button
123 	if (buttons.empty ())
124 	{
125 		okButton.getLabel ()->setTextColors(BColors::darks);
126 		okButton.setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, MessageBox::redirectPostValueChanged);
127 		buttons.push_back (&okButton);
128 		add (okButton);
129 	}
130 
131 	Widget::operator= (that);
132 	return *this;
133 }
134 
clone() const135 Widget* MessageBox::clone () const {return new MessageBox (*this);}
136 
setTitle(const std::string & title)137 void MessageBox::setTitle (const std::string& title) {titleBox.setText (title);}
138 
getTitle() const139 std::string MessageBox::getTitle () const {return titleBox.getText ();}
140 
setText(const std::string & text)141 void MessageBox::setText (const std::string& text) {textBox.setText (text);}
142 
getText() const143 std::string MessageBox::getText () const {return textBox.getText ();}
144 
addButton(const std::string & label)145 void MessageBox::addButton (const std::string& label)
146 {
147 	TextButton* b = new TextButton(0, 0, BWIDGETS_DEFAULT_BUTTON_WIDTH, BWIDGETS_DEFAULT_BUTTON_HEIGHT,
148 					name_ + BWIDGETS_DEFAULT_MESSAGEBOX_BUTTON_NAME, label, 0.0);
149 	if (b)
150 	{
151 		cairo_t* cr = cairo_create (widgetSurface_);
152 		cairo_text_extents_t ext = b->getLabel()->getFont()->getTextExtents (cr, label);
153 		cairo_destroy (cr);
154 		b->setWidth (ext.width > BWIDGETS_DEFAULT_BUTTON_WIDTH - BWIDGETS_DEFAULT_MENU_PADDING ?
155 				ext.width + BWIDGETS_DEFAULT_MENU_PADDING :
156 				BWIDGETS_DEFAULT_BUTTON_WIDTH);
157 		b->setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, MessageBox::redirectPostValueChanged);
158 		buttons.push_back (b);
159 		add (*b);
160 	}
161 }
162 
addButtons(std::vector<std::string> labels)163 void MessageBox::addButtons (std::vector<std::string> labels)
164 {
165 	for (std::string label : labels) addButton (label);
166 }
167 
removeButton(const std::string & label)168 void MessageBox::removeButton  (const std::string& label)
169 {
170 	for (std::vector<TextButton*>::iterator it = buttons.begin (); it != buttons.end (); ++it)
171 	{
172 		TextButton* b = (TextButton*) *it;
173 		if (b && (b->getLabel ()->getText () == label))
174 		{
175 			if (b != &okButton) delete b;
176 			buttons.erase (it);
177 			return;
178 		}
179 	}
180 }
181 
getButtonValue(const std::string & label) const182 double MessageBox::getButtonValue  (const std::string& label) const
183 {
184 	double nr = 1;
185 	for (TextButton* b : buttons)
186 	{
187 		if (b)
188 		{
189 			if (b->getLabel ()->getText () == label) return nr;
190 			nr++;
191 		}
192 	}
193 	return 0;
194 }
195 
getButtonText(const double value) const196 std::string MessageBox::getButtonText (const double value) const
197 {
198 	size_t v = value;
199 	if ((v < 1) || (v > buttons.size ())) return "";
200 	if (!buttons[v - 1]) return "";
201 	return buttons[v - 1]->getLabel ()-> getText ();
202 }
203 
setTextColors(const BColors::ColorSet & colorset)204 void MessageBox::setTextColors (const BColors::ColorSet& colorset) {textBox.setTextColors (colorset);}
205 
getTextColors()206 BColors::ColorSet* MessageBox::getTextColors () {return textBox.getTextColors ();}
207 
setFont(const BStyles::Font & font)208 void MessageBox::setFont (const BStyles::Font& font) {textBox.setFont (font);}
209 
getFont()210 BStyles::Font* MessageBox::getFont () {return textBox.getFont ();}
211 
applyTheme(BStyles::Theme & theme)212 void MessageBox::applyTheme (BStyles::Theme& theme) {applyTheme (theme, name_);}
213 
applyTheme(BStyles::Theme & theme,const std::string & name)214 void MessageBox::applyTheme (BStyles::Theme& theme, const std::string& name)
215 {
216 	Widget::applyTheme (theme, name);
217 	titleBox.applyTheme (theme, name + BWIDGETS_DEFAULT_MESSAGEBOX_TITLE_NAME);
218 	textBox.applyTheme (theme, name + BWIDGETS_DEFAULT_MESSAGEBOX_TEXT_NAME);
219 	for (TextButton* b : buttons)
220 	{
221 		if (b) b->applyTheme (theme, name + BWIDGETS_DEFAULT_MESSAGEBOX_BUTTON_NAME);
222 	}
223 	update ();
224 }
225 
update()226 void MessageBox::update ()
227 {
228 	// Update super widget first
229 	Widget::update ();
230 
231 	double width = getEffectiveWidth ();
232 	double height = getEffectiveHeight ();
233 
234 	// Title
235 	if (titleBox.getText () != "")
236 	{
237 		titleBox.setWidth (width > 2 * BWIDGETS_DEFAULT_MENU_PADDING ? width - 2 * BWIDGETS_DEFAULT_MENU_PADDING : 0);
238 		titleBox.setHeight
239 		(
240 			height > 3 * BWIDGETS_DEFAULT_MENU_PADDING + BWIDGETS_DEFAULT_BUTTON_HEIGHT ?
241 			height - (3 * BWIDGETS_DEFAULT_MENU_PADDING + BWIDGETS_DEFAULT_BUTTON_HEIGHT) :
242 			0
243 		);
244 		titleBox.getFont ()->setFontWeight (CAIRO_FONT_WEIGHT_BOLD);
245 		double titleheight = titleBox.getTextBlockHeight(titleBox.getTextBlock ());
246 		titleBox.setHeight
247 		(
248 			titleheight < height - (3 * BWIDGETS_DEFAULT_MENU_PADDING + BWIDGETS_DEFAULT_BUTTON_HEIGHT) ?
249 			titleheight :
250 			(
251 				height > (3 * BWIDGETS_DEFAULT_MENU_PADDING + BWIDGETS_DEFAULT_BUTTON_HEIGHT) ?
252 				height - (3 * BWIDGETS_DEFAULT_MENU_PADDING + BWIDGETS_DEFAULT_BUTTON_HEIGHT) :
253 				0
254 			)
255 		);
256 		titleBox.moveTo (BWIDGETS_DEFAULT_MENU_PADDING, BWIDGETS_DEFAULT_MENU_PADDING);
257 	}
258 	else
259 	{
260 		titleBox.setWidth (0);
261 		titleBox.setHeight (0);
262 		titleBox.moveTo (0, 0);
263 	}
264 
265 	// Text
266 	if (textBox.getText () != "")
267 	{
268 		double titleheight = titleBox.getHeight () + BWIDGETS_DEFAULT_MENU_PADDING;
269 		textBox.setWidth (width > 2 * BWIDGETS_DEFAULT_MENU_PADDING ? width - 2 * BWIDGETS_DEFAULT_MENU_PADDING : 0);
270 		textBox.setHeight
271 		(
272 			height > (3 * BWIDGETS_DEFAULT_MENU_PADDING + BWIDGETS_DEFAULT_BUTTON_HEIGHT) + titleheight ?
273 			height - ((3 * BWIDGETS_DEFAULT_MENU_PADDING + BWIDGETS_DEFAULT_BUTTON_HEIGHT) + titleheight) :
274 			0
275 		);
276 		textBox.moveTo (BWIDGETS_DEFAULT_MENU_PADDING, BWIDGETS_DEFAULT_MENU_PADDING + titleheight);
277 	}
278 	else
279 	{
280 		textBox.setWidth (0);
281 		textBox.setHeight (0);
282 		textBox.moveTo (0, 0);
283 	}
284 
285 	// Buttons
286 	// Calculate total width
287 	int nrbuttons = 0;
288 	double totalbuttonwidth = 0.0;
289 	for (TextButton* b : buttons)
290 	{
291 		if (b)
292 		{
293 			++nrbuttons;
294 			totalbuttonwidth += b->getWidth ();
295 		}
296 	}
297 
298 	// Calculate spaces and offset
299 	double buttonspace = (width - totalbuttonwidth) / (nrbuttons + 1);
300 	if (buttonspace < BWIDGETS_DEFAULT_MENU_PADDING) buttonspace = BWIDGETS_DEFAULT_MENU_PADDING;
301 	double buttonxpos = (totalbuttonwidth + (nrbuttons + 1) * buttonspace < width ?
302 			     buttonspace :
303 			     width / 2 - (totalbuttonwidth + (nrbuttons - 1) * buttonspace) / 2);
304 
305 	// Rearrange
306 	for (TextButton* b : buttons)
307 	{
308 		if (b)
309 		{
310 			b->moveTo (buttonxpos, height - BWIDGETS_DEFAULT_MENU_PADDING - BWIDGETS_DEFAULT_BUTTON_HEIGHT);
311 			buttonxpos = buttonxpos + buttonspace + b->getWidth ();
312 		}
313 	}
314 }
315 
redirectPostValueChanged(BEvents::Event * event)316 void MessageBox::redirectPostValueChanged (BEvents::Event* event)
317 {
318 	if (event && (event->getEventType () == BEvents::EventType::VALUE_CHANGED_EVENT) && event->getWidget ())
319 	{
320 		BEvents::ValueChangedEvent* ev = (BEvents::ValueChangedEvent*) event;
321 		TextButton* w = (TextButton*) ev->getWidget ();
322 		if (w->getParent ())
323 		{
324 			std::string label = w->getLabel ()->getText ();
325 			MessageBox* p = (MessageBox*) w->getParent ();
326 			if (p->getMainWindow ())
327 			{
328 				// Button pressed ?
329 				if (w->getValue ())
330 				{
331 					double v = p->getButtonValue (label);
332 
333 					// Emit value changed event and close (hide) message box
334 					if (v)
335 					{
336 						p->setValue (v);
337 						p->postCloseRequest ();
338 					}
339 				}
340 			}
341 		}
342 	}
343 }
344 
345 }
346