1 /*
2  * Copyright (C) 2007-2010 Geometer Plus <contact@geometerplus.com>
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 2 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, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301, USA.
18  */
19 
20 #include <algorithm>
21 
22 #include <windows.h>
23 #include <commctrl.h>
24 
25 #include "../../../../core/src/util/ZLKeyUtil.h"
26 
27 #include "W32Control.h"
28 #include "W32ControlCollection.h"
29 #include "../../../../core/src/win32/util/W32WCHARUtil.h"
30 
31 #undef max
32 #undef min
33 
34 static const WORD CLASS_BUTTON = 0x0080;
35 static const WORD CLASS_EDIT = 0x0081;
36 static const WORD CLASS_STATIC = 0x0082;
37 static const WORD CLASS_LISTBOX = 0x0083;
38 static const WORD CLASS_SCROLLBAR = 0x0084;
39 static const WORD CLASS_COMBOBOX = 0x0085;
40 
41 static const WCHAR CLASSNAME_SPINNER[] = UPDOWN_CLASSW;
42 
W32Control(DWORD style)43 W32Control::W32Control(DWORD style) : myStyle(style | WS_CHILD), myX(1), myY(1), mySize(Size(1, 1)), myEnabled(true), myOwner(0), myWindow(0) {
44 }
45 
setEnabled(bool enabled)46 void W32Control::setEnabled(bool enabled) {
47 	if (myEnabled != enabled) {
48 		myEnabled = enabled;
49 		if (myWindow != 0) {
50 			EnableWindow(myWindow, myEnabled);
51 		}
52 	}
53 }
54 
isEnabled() const55 bool W32Control::isEnabled() const {
56 	return myEnabled;
57 }
58 
setVisible(bool visible)59 void W32Control::setVisible(bool visible) {
60 	if (visible != isVisible()) {
61 		if (visible) {
62 			myStyle |= WS_VISIBLE;
63 		} else {
64 			myStyle &= ~WS_VISIBLE;
65 		}
66 		if (myWindow != 0) {
67 			ShowWindow(myWindow, visible ? SW_SHOW : SW_HIDE);
68 		}
69 		if (myOwner != 0) {
70 			myOwner->invalidate();
71 		}
72 	}
73 }
74 
isVisible() const75 bool W32Control::isVisible() const {
76 	return (myStyle & WS_VISIBLE) == WS_VISIBLE;
77 }
78 
init(HWND parent,W32ControlCollection * collection)79 void W32Control::init(HWND parent, W32ControlCollection *collection) {
80 	myOwner = collection;
81 	myWindow = GetDlgItem(parent, collection->addControl(this));
82 	if (!myEnabled) {
83 		EnableWindow(myWindow, false);
84 	}
85 }
86 
W32StandardControl(DWORD style)87 W32StandardControl::W32StandardControl(DWORD style) : W32Control(style) {
88 }
89 
controlNumber() const90 int W32StandardControl::controlNumber() const {
91 	return 1;
92 }
93 
allocationSize() const94 int W32StandardControl::allocationSize() const {
95 	return 14;
96 }
97 
allocate(WORD * & p,short & id) const98 void W32StandardControl::allocate(WORD *&p, short &id) const {
99 	*p++ = LOWORD(myStyle);
100 	*p++ = HIWORD(myStyle);
101 	*p++ = 0;
102 	*p++ = 0;
103 	*p++ = myX;
104 	*p++ = myY;
105 	/*
106 	if ((mySize.Width == 0) || (mySize.Height == 0)) {
107 		mySize = minimumSize();
108 	}
109 	*/
110 	*p++ = mySize.Width;
111 	*p++ = mySize.Height;
112 	*p++ = id++;
113 	*p++ = 0xFFFF;
114 	*p++ = classId();
115 	*p++ = 0;
116 	*p++ = 0;
117 	*p++ = 0;
118 }
119 
setPosition(int x,int y,Size size)120 void W32Control::setPosition(int x, int y, Size size) {
121 	bool doMove = (myX != x) || (myY != y);
122 	bool doResize = mySize != size;
123 	myX = x;
124 	myY = y;
125 	mySize = size;
126 	if ((doResize || doMove) && (myWindow != 0)) {
127 		RECT r;
128 		r.left = x;
129 		r.top = y;
130 		r.right = x + size.Width;
131 		r.bottom = y + size.Height;
132 		MapDialogRect(GetParent(myWindow), &r);
133 		DWORD flags = SWP_NOZORDER | SWP_NOOWNERZORDER;
134 		if (!doResize) {
135 			flags |= SWP_NOSIZE;
136 		}
137 		if (!doMove) {
138 			flags |= SWP_NOMOVE;
139 		}
140 		SetWindowPos(myWindow, 0, r.left, r.top, r.right - r.left, r.bottom - r.top, flags);
141 	}
142 }
143 
commandCallback(DWORD)144 void W32Control::commandCallback(DWORD) {
145 }
146 
notificationCallback(LPARAM)147 void W32Control::notificationCallback(LPARAM) {
148 }
149 
drawItemCallback(DRAWITEMSTRUCT &)150 void W32Control::drawItemCallback(DRAWITEMSTRUCT&) {
151 }
152 
153 const std::string W32PushButton::RELEASED_EVENT = "PushButton: released";
154 
W32PushButton(const std::string & text,ButtonType type)155 W32PushButton::W32PushButton(const std::string &text, ButtonType type) : W32StandardControl(BS_PUSHBUTTON | WS_TABSTOP), myText(text), myType(type) {
156 	//DWORD style = (it == myButtons.begin()) ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON;
157 }
158 
minimumSize() const159 W32Widget::Size W32PushButton::minimumSize() const {
160 	return Size(4 * (ZLUnicodeUtil::utf8Length(myText) + 3), 15);
161 }
162 
allocate(WORD * & p,short & id) const163 void W32PushButton::allocate(WORD *&p, short &id) const {
164 	if (myType == NORMAL_BUTTON) {
165 		W32StandardControl::allocate(p, id);
166 	} else {
167 		short specialId = (myType == OK_BUTTON) ? IDOK : IDCANCEL;
168 		W32StandardControl::allocate(p, specialId);
169 	}
170 }
171 
classId() const172 WORD W32PushButton::classId() const {
173 	return CLASS_BUTTON;
174 }
175 
init(HWND parent,W32ControlCollection * collection)176 void W32PushButton::init(HWND parent, W32ControlCollection *collection) {
177 	myOwner = collection;
178 	short id = -1;
179 	if (myType == OK_BUTTON) {
180 		id = IDOK;
181 	} else if (myType == CANCEL_BUTTON) {
182 		id = IDCANCEL;
183 	}
184 	myWindow = GetDlgItem(parent, collection->addControl(this, id));
185 	if (!myEnabled) {
186 		EnableWindow(myWindow, false);
187 	}
188 	::setWindowText(myWindow, myText);
189 }
190 
commandCallback(DWORD)191 void W32PushButton::commandCallback(DWORD) {
192 	fireEvent(RELEASED_EVENT);
193 }
194 
W32Label(const std::string & text,Alignment alignment)195 W32Label::W32Label(const std::string &text, Alignment alignment) : W32StandardControl(alignment), myText(text) {
196 	myStringCounter = 1;
197 	myMaxLength = 0;
198 	int index = 0;
199 	int newIndex;
200 	while ((newIndex = myText.find('\n', index)) != -1) {
201 		++myStringCounter;
202 		myMaxLength = std::max(
203 			myMaxLength,
204 			(unsigned short)ZLUnicodeUtil::utf8Length(myText.data() + index, newIndex - index)
205 		);
206 		index = newIndex + 1;
207 	}
208 	myMaxLength = std::max(
209 		myMaxLength,
210 		(unsigned short)ZLUnicodeUtil::utf8Length(myText.data() + index, myText.length() - index)
211 	);
212 }
213 
minimumSize() const214 W32Widget::Size W32Label::minimumSize() const {
215 	return Size(4 * myMaxLength, 12 * myStringCounter);
216 }
217 
setPosition(int x,int y,Size size)218 void W32Label::setPosition(int x, int y, Size size) {
219 	::setWindowText(myWindow, "");
220 	W32StandardControl::setPosition(x, y + 2, Size(size.Width, size.Height - 2));
221 	::setWindowText(myWindow, myText);
222 }
223 
classId() const224 WORD W32Label::classId() const {
225 	return CLASS_STATIC;
226 }
227 
init(HWND parent,W32ControlCollection * collection)228 void W32Label::init(HWND parent, W32ControlCollection *collection) {
229 	W32StandardControl::init(parent, collection);
230 	::setWindowText(myWindow, myText);
231 }
232 
W32StandardIcon(IconId iconId)233 W32StandardIcon::W32StandardIcon(IconId iconId) : W32StandardControl(SS_CENTER | SS_ICON), myIconId(iconId) {
234 }
235 
minimumSize() const236 W32Widget::Size W32StandardIcon::minimumSize() const {
237 	// TODO: why multiplier 2?
238 	return Size(SM_CXICON * 2, SM_CYICON * 2);
239 }
240 
classId() const241 WORD W32StandardIcon::classId() const {
242 	return CLASS_STATIC;
243 }
244 
init(HWND parent,W32ControlCollection * collection)245 void W32StandardIcon::init(HWND parent, W32ControlCollection *collection) {
246 	W32StandardControl::init(parent, collection);
247 	WCHAR *icon;
248 	switch (myIconId) {
249 		case ID_INFORMATION:
250 			icon = IDI_INFORMATION;
251 			break;
252 		case ID_QUESTION:
253 			icon = IDI_QUESTION;
254 			break;
255 		case ID_ERROR:
256 			icon = IDI_ERROR;
257 			break;
258 		default:
259 			icon = IDI_APPLICATION;
260 			break;
261 	}
262 	SendMessage(myWindow, STM_SETICON, (WPARAM)LoadIcon(0, icon), 0);
263 }
264 
265 const std::string W32CheckBox::STATE_CHANGED_EVENT = "CheckBox: state changed";
266 
W32CheckBox(const std::string & text)267 W32CheckBox::W32CheckBox(const std::string &text) : W32StandardControl(BS_AUTOCHECKBOX | WS_TABSTOP), myText(text), myChecked(false) {
268 }
269 
minimumSize() const270 W32Widget::Size W32CheckBox::minimumSize() const {
271 	return Size(4 * (ZLUnicodeUtil::utf8Length(myText) + 1), 12);
272 }
273 
classId() const274 WORD W32CheckBox::classId() const {
275 	return CLASS_BUTTON;
276 }
277 
init(HWND parent,W32ControlCollection * collection)278 void W32CheckBox::init(HWND parent, W32ControlCollection *collection) {
279 	W32StandardControl::init(parent, collection);
280 	::setWindowText(myWindow, myText);
281 	SendMessage(myWindow, BM_SETCHECK, myChecked ? BST_CHECKED : BST_UNCHECKED, 0);
282 }
283 
setEditable(bool editable)284 void W32CheckBox::setEditable(bool editable) {
285 	if (editable) {
286 		myStyle &= ~WS_DISABLED;
287 	} else {
288 		myStyle |= WS_DISABLED;
289 	}
290 	if (myWindow != 0) {
291 		// TODO: check
292 		SetWindowLong(myWindow, GWL_STYLE, myStyle);
293 	}
294 }
295 
setChecked(bool checked)296 void W32CheckBox::setChecked(bool checked) {
297 	if (checked != myChecked) {
298 		myChecked = checked;
299 		if (myWindow != 0) {
300 			SendMessage(myWindow, BM_SETCHECK, myChecked ? BST_CHECKED : BST_UNCHECKED, 0);
301 		}
302 		fireEvent(STATE_CHANGED_EVENT);
303 	}
304 }
305 
isChecked() const306 bool W32CheckBox::isChecked() const {
307 	return myChecked;
308 }
309 
commandCallback(DWORD)310 void W32CheckBox::commandCallback(DWORD) {
311 	if (myChecked != (SendMessage(myWindow, BM_GETCHECK, 0, 0) == BST_CHECKED)) {
312 		myChecked = !myChecked;
313 		fireEvent(STATE_CHANGED_EVENT);
314 	}
315 }
316 
317 const std::string W32TristateBox::STATE_CHANGED_EVENT = "TristateBox: state changed";
318 
buttonState(ZLBoolean3 state)319 int W32TristateBox::buttonState(ZLBoolean3 state) {
320 	switch (state) {
321 		case B3_TRUE:
322 			return BST_CHECKED;
323 		case B3_FALSE:
324 			return BST_UNCHECKED;
325 		default:
326 			return BST_INDETERMINATE;
327 	}
328 }
329 
b3State(int state)330 ZLBoolean3 W32TristateBox::b3State(int state) {
331 	switch (state) {
332 		case BST_CHECKED:
333 			return B3_TRUE;
334 		case BST_UNCHECKED:
335 			return B3_FALSE;
336 		default:
337 			return B3_UNDEFINED;
338 	}
339 }
340 
W32TristateBox(const std::string & text)341 W32TristateBox::W32TristateBox(const std::string &text) : W32StandardControl(BS_AUTO3STATE | WS_TABSTOP), myText(text), myState(B3_UNDEFINED) {
342 }
343 
minimumSize() const344 W32Widget::Size W32TristateBox::minimumSize() const {
345 	return Size(4 * (ZLUnicodeUtil::utf8Length(myText) + 1), 12);
346 }
347 
classId() const348 WORD W32TristateBox::classId() const {
349 	return CLASS_BUTTON;
350 }
351 
init(HWND parent,W32ControlCollection * collection)352 void W32TristateBox::init(HWND parent, W32ControlCollection *collection) {
353 	W32StandardControl::init(parent, collection);
354 	::setWindowText(myWindow, myText);
355 	SendMessage(myWindow, BM_SETCHECK, buttonState(myState), 0);
356 }
357 
setEditable(bool editable)358 void W32TristateBox::setEditable(bool editable) {
359 	if (editable) {
360 		myStyle &= ~WS_DISABLED;
361 	} else {
362 		myStyle |= WS_DISABLED;
363 	}
364 	if (myWindow != 0) {
365 		// TODO: check
366 		SetWindowLong(myWindow, GWL_STYLE, myStyle);
367 	}
368 }
369 
setState(ZLBoolean3 state)370 void W32TristateBox::setState(ZLBoolean3 state) {
371 	if (state != myState) {
372 		myState = state;
373 		if (myWindow != 0) {
374 			SendMessage(myWindow, BM_SETCHECK, buttonState(myState), 0);
375 		}
376 		fireEvent(STATE_CHANGED_EVENT);
377 	}
378 }
379 
state() const380 ZLBoolean3 W32TristateBox::state() const {
381 	return myState;
382 }
383 
commandCallback(DWORD)384 void W32TristateBox::commandCallback(DWORD) {
385 	ZLBoolean3 state = b3State(SendMessage(myWindow, BM_GETCHECK, 0, 0));
386 	if (state != myState) {
387 		myState = state;
388 		fireEvent(STATE_CHANGED_EVENT);
389 	}
390 }
391 
W32AbstractEditor(DWORD style)392 W32AbstractEditor::W32AbstractEditor(DWORD style) : W32StandardControl(style | WS_BORDER | WS_TABSTOP | ES_NOHIDESEL) {
393 }
394 
classId() const395 WORD W32AbstractEditor::classId() const {
396 	return CLASS_EDIT;
397 }
398 
init(HWND parent,W32ControlCollection * collection)399 void W32AbstractEditor::init(HWND parent, W32ControlCollection *collection) {
400 	W32StandardControl::init(parent, collection);
401 }
402 
getTextFromBuffer(const ZLUnicodeUtil::Ucs2String & buffer)403 static std::string getTextFromBuffer(const ZLUnicodeUtil::Ucs2String &buffer) {
404 	ZLUnicodeUtil::Ucs2String copy = buffer;
405 	copy.pop_back();
406 	std::string txt;
407 	ZLUnicodeUtil::ucs2ToUtf8(txt, copy);
408 	return txt;
409 }
410 
411 const std::string W32LineEditor::VALUE_EDITED_EVENT = "LineEditor: value edited";
412 
W32LineEditor(const std::string & text,bool passwordMode)413 W32LineEditor::W32LineEditor(const std::string &text, bool passwordMode) : W32AbstractEditor(passwordMode ? (ES_AUTOHSCROLL | ES_PASSWORD) : ES_AUTOHSCROLL), myBlocked(true) {
414 	::createNTWCHARString(myBuffer, text);
415 }
416 
commandCallback(DWORD hiWParam)417 void W32LineEditor::commandCallback(DWORD hiWParam) {
418 	if ((hiWParam == EN_CHANGE) && !myBlocked) {
419 		const int length = SendMessage(myWindow, EM_LINELENGTH, 0, 0);
420 		myBuffer.clear();
421 		myBuffer.insert(myBuffer.end(), length + 1, 0);
422 		if (length > 0) {
423 			myBuffer[0] = length + 1;
424 			SendMessage(myWindow, EM_GETLINE, 0, (LPARAM)&myBuffer.front());
425 		}
426 		fireEvent(VALUE_EDITED_EVENT);
427 	}
428 }
429 
setText(const std::string & text)430 void W32LineEditor::setText(const std::string &text) {
431 	::createNTWCHARString(myBuffer, text);
432 	if (myWindow != 0) {
433 		SetWindowTextW(myWindow, ::wchar(myBuffer));
434 	}
435 }
436 
text() const437 std::string W32LineEditor::text() const {
438 	return getTextFromBuffer(myBuffer);
439 }
440 
minimumSize() const441 W32Widget::Size W32LineEditor::minimumSize() const {
442 	return Size(4 * std::max(std::min((int)myBuffer.size() + 3, 25), 10), 12);
443 }
444 
init(HWND parent,W32ControlCollection * collection)445 void W32LineEditor::init(HWND parent, W32ControlCollection *collection) {
446 	W32AbstractEditor::init(parent, collection);
447 	SetWindowTextW(myWindow, ::wchar(myBuffer));
448 	myBlocked = false;
449 }
450 
setEditable(bool editable)451 void W32LineEditor::setEditable(bool editable) {
452 	if (editable) {
453 		myStyle &= ~ES_READONLY;
454 	} else {
455 		myStyle |= ES_READONLY;
456 	}
457 	if (myWindow != 0) {
458 		// TODO: check
459 		//PostMessage(myWindow, EM_SETREADONLY, !editable, 0);
460 		SendMessage(myWindow, EM_SETREADONLY, (editable ? FALSE : TRUE), 0);
461 	}
462 }
463 
464 std::map<HWND,W32KeyNameEditor*> W32KeyNameEditor::ourEditors;
465 const std::string W32KeyNameEditor::TEXT_CHANGED_EVENT = "KeyNameEditor: text changed";
466 
W32KeyNameEditor()467 W32KeyNameEditor::W32KeyNameEditor() : W32AbstractEditor(ES_AUTOHSCROLL), myKeyboardModifierMask(0) {
468 	::createNTWCHARString(myBuffer, "");
469 }
470 
~W32KeyNameEditor()471 W32KeyNameEditor::~W32KeyNameEditor() {
472 	if (myWindow != 0) {
473 		ourEditors.erase(myWindow);
474 	}
475 }
476 
clear()477 void W32KeyNameEditor::clear() {
478 	setText(std::string());
479 }
480 
setText(const std::string & text)481 void W32KeyNameEditor::setText(const std::string &text) {
482 	if (text != this->text()) {
483 		::createNTWCHARString(myBuffer, text);
484 		if (myWindow != 0) {
485 			SetWindowTextW(myWindow, ::wchar(myBuffer));
486 		}
487 		fireEvent(TEXT_CHANGED_EVENT);
488 	}
489 }
490 
text() const491 std::string W32KeyNameEditor::text() const {
492 	return getTextFromBuffer(myBuffer);
493 }
494 
minimumSize() const495 W32Widget::Size W32KeyNameEditor::minimumSize() const {
496 	return Size(40, 12);
497 }
498 
Callback(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)499 LRESULT CALLBACK W32KeyNameEditor::Callback(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
500 	W32KeyNameEditor *editor = ourEditors[hWnd];
501 	if (editor == 0) {
502 		return 0;
503 	}
504 	int &mask = editor->myKeyboardModifierMask;
505 	switch (uMsg) {
506 		case WM_GETDLGCODE:
507 			return DLGC_WANTALLKEYS;
508 		case WM_KEYDOWN:
509 		case WM_SYSKEYDOWN:
510 			if (wParam == 0x10) {
511 				mask |= 0x1;
512 			} else if (wParam == 0x11) {
513 				mask |= 0x2;
514 			} else if (wParam == 0x12) {
515 				mask |= 0x4;
516 			} else {
517 				editor->setText(ZLKeyUtil::keyName(wParam, wParam, mask));
518 			}
519 			return 0;
520 		case WM_KEYUP:
521 		case WM_SYSKEYUP:
522 			if (wParam == 0x10) {
523 				mask &= ~0x1;
524 			} else if (wParam == 0x11) {
525 				mask &= ~0x2;
526 			} else if (wParam == 0x12) {
527 				mask &= ~0x4;
528 			}
529 			return 0;
530 		case WM_CHAR:
531 			return 0;
532 		case WM_SETFOCUS:
533 			editor->setText("");
534 			mask = 0;
535 		default:
536 			return editor->myOriginalWndProc(hWnd, uMsg, wParam, lParam);
537 	}
538 }
539 
init(HWND parent,W32ControlCollection * collection)540 void W32KeyNameEditor::init(HWND parent, W32ControlCollection *collection) {
541 	W32AbstractEditor::init(parent, collection);
542 	myOriginalWndProc = (WndProc)SetWindowLong(myWindow, GWL_WNDPROC, (LONG)Callback);
543 	ourEditors[myWindow] = this;
544 }
545 
setEditable(bool editable)546 void W32KeyNameEditor::setEditable(bool editable) {
547 	if (editable) {
548 		myStyle &= ~ES_READONLY;
549 	} else {
550 		myStyle |= ES_READONLY;
551 	}
552 	if (myWindow != 0) {
553 		// TODO: check
554 		//PostMessage(myWindow, EM_SETREADONLY, !editable, 0);
555 		SendMessage(myWindow, EM_SETREADONLY, (editable ? FALSE : TRUE), 0);
556 	}
557 }
558 
W32SpinBox(WORD min,WORD max,WORD initial)559 W32SpinBox::W32SpinBox(WORD min, WORD max, WORD initial) : W32AbstractEditor(ES_NUMBER), myMin(min), myMax(max), myValue(initial), myControlWindow(0) {
560 }
561 
minimumSize() const562 W32Widget::Size W32SpinBox::minimumSize() const {
563 	return Size(36, 12);
564 }
565 
allocate(WORD * & p,short & id) const566 void W32SpinBox::allocate(WORD *&p, short &id) const {
567 	W32AbstractEditor::allocate(p, id);
568 
569 	WORD *start = p;
570 
571 	DWORD style = UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_ARROWKEYS;
572 	if (isVisible()) {
573 		style |= WS_VISIBLE;
574 	}
575 	*p++ = LOWORD(style);
576 	*p++ = HIWORD(style);
577 	*p++ = 0;
578 	*p++ = 0;
579 	*p++ = myX + mySize.Width;
580 	*p++ = myY;
581 	*p++ = mySize.Width;
582 	*p++ = mySize.Height;
583 	*p++ = id++;
584 
585 	static const int classNameLength = lstrlenW(CLASSNAME_SPINNER);
586 	memcpy(p, CLASSNAME_SPINNER, classNameLength * 2);
587 	p += classNameLength;
588 	*p++ = 0;
589 	*p++ = 0;
590 	*p++ = 0;
591 	if ((p - start) % 2 == 1) {
592 		p++;
593 	}
594 }
595 
setPosition(int x,int y,Size size)596 void W32SpinBox::setPosition(int x, int y, Size size) {
597 	W32StandardControl::setPosition(x, y, size);
598 	if (myControlWindow != 0) {
599 		RECT r;
600 		r.left = x + size.Width;
601 		r.top = y;
602 		r.right = x + 2 * size.Width;
603 		r.bottom = y + size.Height;
604 		MapDialogRect(GetParent(myControlWindow), &r);
605 		SetWindowPos(myControlWindow, 0, r.left, r.top, r.right - r.left, r.bottom - r.top, 0);
606 		SendMessage(myControlWindow, UDM_SETBUDDY, (WPARAM)myWindow, 0);
607 		if (isVisible()) {
608 			ShowWindow(myControlWindow, SW_SHOW);
609 		}
610 	}
611 }
612 
init(HWND parent,W32ControlCollection * collection)613 void W32SpinBox::init(HWND parent, W32ControlCollection *collection) {
614 	W32AbstractEditor::init(parent, collection);
615 	myControlWindow = GetDlgItem(parent, collection->addControl(this));
616 	SendMessage(myControlWindow, UDM_SETBUDDY, (WPARAM)myWindow, 0);
617 	SendMessage(myControlWindow, UDM_SETRANGE, 0, MAKELONG(myMax, myMin));
618 	SendMessage(myControlWindow, UDM_SETPOS, 0, MAKELONG(myValue, 0));
619 }
620 
allocationSize() const621 int W32SpinBox::allocationSize() const {
622 	static int const classNameLength = lstrlenW(CLASSNAME_SPINNER);
623 	int size = W32AbstractEditor::allocationSize() + 12 + classNameLength;
624 	return size + size % 2;
625 }
626 
controlNumber() const627 int W32SpinBox::controlNumber() const {
628 	return W32AbstractEditor::controlNumber() + 1;
629 }
630 
setVisible(bool visible)631 void W32SpinBox::setVisible(bool visible) {
632 	W32StandardControl::setVisible(visible);
633 	if (myControlWindow != 0) {
634 		ShowWindow(myControlWindow, visible ? SW_SHOW : SW_HIDE);
635 	}
636 }
637 
commandCallback(DWORD hiWParam)638 void W32SpinBox::commandCallback(DWORD hiWParam) {
639 	if (hiWParam == EN_CHANGE) {
640 		myValue = SendMessage(myControlWindow, UDM_GETPOS, 0, 0);
641 	}
642 }
643 
value() const644 unsigned short W32SpinBox::value() const {
645 	return myValue;
646 }
647 
648 const std::string W32ComboBox::SELECTION_CHANGED_EVENT = "ComboBox: selection changed";
649 const std::string W32ComboBox::VALUE_EDITED_EVENT = "ComboBox: value edited";
650 
W32ComboBox(const std::vector<std::string> & list,int initialIndex)651 W32ComboBox::W32ComboBox(const std::vector<std::string> &list, int initialIndex) : W32StandardControl(CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP), myList(list), myIndex(initialIndex) {
652 	if ((initialIndex >= 0) && ((size_t)initialIndex < myList.size())) {
653 		::createNTWCHARString(myBuffer, list[initialIndex]);
654 	}
655 }
656 
minimumSize() const657 W32Widget::Size W32ComboBox::minimumSize() const {
658 	int len = ((myStyle & CBS_SIMPLE) == CBS_SIMPLE) ? 0 : 10;
659 	for (std::vector<std::string>::const_iterator it = myList.begin(); it != myList.end(); ++it) {
660 		len = std::max(ZLUnicodeUtil::utf8Length(*it), len);
661 	}
662 	return Size(4 * std::min(len, 25), 12);
663 }
664 
classId() const665 WORD W32ComboBox::classId() const {
666 	return CLASS_COMBOBOX;
667 }
668 
init(HWND parent,W32ControlCollection * collection)669 void W32ComboBox::init(HWND parent, W32ControlCollection *collection) {
670 	W32StandardControl::init(parent, collection);
671 	ZLUnicodeUtil::Ucs2String buffer;
672 	for (std::vector<std::string>::const_iterator it = myList.begin(); it != myList.end(); ++it) {
673 		SendMessage(myWindow, CB_ADDSTRING, 0, (LPARAM)::wchar(::createNTWCHARString(buffer, *it)));
674 	}
675 	if (myList.size() > 0) {
676 		SendMessage(myWindow, CB_SETCURSEL, myIndex, 0);
677 		SendMessage(myWindow, CB_SETMINVISIBLE, std::min((int)myList.size(), 7), 0);
678 	}
679 }
680 
setPosition(int x,int y,Size size)681 void W32ComboBox::setPosition(int x, int y, Size size) {
682 	W32Control::setPosition(x, y, size);
683 	if (myWindow != 0) {
684 		SendMessage(myWindow, CB_SETMINVISIBLE, std::min((int)myList.size(), 7), 0);
685 	}
686 }
687 
setEditable(bool editable)688 void W32ComboBox::setEditable(bool editable) {
689 	if (editable) {
690 		myStyle &= ~CBS_SIMPLE;
691 	} else {
692 		myStyle |= CBS_SIMPLE;
693 	}
694 	if (myWindow != 0) {
695 		// TODO: check
696 		SetWindowLong(myWindow, GWL_STYLE, myStyle);
697 	}
698 }
699 
commandCallback(DWORD hiWParam)700 void W32ComboBox::commandCallback(DWORD hiWParam) {
701 	if (hiWParam == CBN_SELCHANGE) {
702 		myIndex = SendMessage(myWindow, CB_GETCURSEL, 0, 0);
703 		const int length = SendMessage(myWindow, CB_GETLBTEXTLEN, myIndex, 0);
704 		myBuffer.clear();
705 		myBuffer.insert(myBuffer.end(), length + 1, 0);
706 		if (length > 0) {
707 			SendMessage(myWindow, CB_GETLBTEXT, myIndex, (LPARAM)&myBuffer.front());
708 		}
709 		fireEvent(SELECTION_CHANGED_EVENT);
710 	} else if (hiWParam == CBN_EDITCHANGE) {
711 		const int length = SendMessage(myWindow, WM_GETTEXTLENGTH, 0, 0);
712 		myBuffer.clear();
713 		myBuffer.insert(myBuffer.end(), length + 1, 0);
714 		if (length > 0) {
715 			SendMessage(myWindow, WM_GETTEXT, length + 1, (LPARAM)&myBuffer.front());
716 		}
717 		fireEvent(VALUE_EDITED_EVENT);
718 	}
719 }
720 
index() const721 int W32ComboBox::index() const {
722 	return myIndex;
723 }
724 
text() const725 std::string W32ComboBox::text() const {
726 	return getTextFromBuffer(myBuffer);
727 }
728 
setList(const std::vector<std::string> & list)729 void W32ComboBox::setList(const std::vector<std::string> &list) {
730 	myList = list;
731 	if (myWindow != 0) {
732 		SendMessage(myWindow, CB_RESETCONTENT, 0, 0);
733 		ZLUnicodeUtil::Ucs2String buffer;
734 		for (std::vector<std::string>::const_iterator it = myList.begin(); it != myList.end(); ++it) {
735 			SendMessage(myWindow, CB_ADDSTRING, 0, (LPARAM)::wchar(::createNTWCHARString(buffer, *it)));
736 		}
737 		if (myList.size() > 0) {
738 			SendMessage(myWindow, CB_SETMINVISIBLE, std::min((int)myList.size(), 7), 0);
739 		}
740 	}
741 }
742 
setSelection(const std::string & value)743 void W32ComboBox::setSelection(const std::string &value) {
744 	std::vector<std::string>::const_iterator it = std::find(myList.begin(), myList.end(), value);
745 	if (it != myList.end()) {
746 		setSelection(it - myList.begin());
747 	}
748 }
749 
setSelection(int index)750 void W32ComboBox::setSelection(int index) {
751 	if ((index >= 0) && (index < (short)myList.size())) {
752 		myIndex = index;
753 		myBuffer.clear();
754 		::createNTWCHARString(myBuffer, myList[index]);
755 		if (myWindow != 0) {
756 			SendMessage(myWindow, CB_SETCURSEL, myIndex, 0);
757 		}
758 	}
759 }
760 
W32RadioButton(W32RadioButtonGroup & group,const std::string & text)761 W32RadioButton::W32RadioButton(W32RadioButtonGroup &group, const std::string &text) : W32StandardControl(BS_RADIOBUTTON | WS_TABSTOP), myGroup(group), myText(text) {
762 }
763 
classId() const764 WORD W32RadioButton::classId() const {
765 	return CLASS_BUTTON;
766 }
767 
minimumSize() const768 W32Widget::Size W32RadioButton::minimumSize() const {
769 	return Size(4 * (ZLUnicodeUtil::utf8Length(myText) + 2), 12);
770 }
771 
init(HWND parent,W32ControlCollection * collection)772 void W32RadioButton::init(HWND parent, W32ControlCollection *collection) {
773 	W32StandardControl::init(parent, collection);
774 	::setWindowText(myWindow, myText);
775 }
776 
commandCallback(DWORD hiWParam)777 void W32RadioButton::commandCallback(DWORD hiWParam) {
778 	if (hiWParam == BN_CLICKED) {
779 		if (SendMessage(myWindow, BM_GETCHECK, 0, 0) != BST_CHECKED) {
780 			myGroup.setChecked(*this);
781 		}
782 	}
783 }
784 
setEditable(bool editable)785 void W32RadioButton::setEditable(bool editable) {
786 	if (editable) {
787 		myStyle &= ~WS_DISABLED;
788 	} else {
789 		myStyle |= WS_DISABLED;
790 	}
791 	if (myWindow != 0) {
792 		// TODO: check
793 		SetWindowLong(myWindow, GWL_STYLE, myStyle);
794 	}
795 }
796 
setChecked(bool checked)797 void W32RadioButton::setChecked(bool checked) {
798 	if (myWindow != 0) {
799 		SendMessage(myWindow, BM_SETCHECK, checked ? BST_CHECKED : BST_UNCHECKED, 0);
800 	}
801 }
802 
W32RadioButtonGroup(const std::string & caption,const std::vector<std::string> & buttonTexts)803 W32RadioButtonGroup::W32RadioButtonGroup(const std::string &caption, const std::vector<std::string> &buttonTexts) : W32StandardControl(BS_GROUPBOX), myCaption(caption), myCheckedIndex(-1) {
804 	myButtons.reserve(buttonTexts.size());
805 	for (std::vector<std::string>::const_iterator it = buttonTexts.begin(); it != buttonTexts.end(); ++it) {
806 		myButtons.push_back(new W32RadioButton(*this, *it));
807 	}
808 }
809 
classId() const810 WORD W32RadioButtonGroup::classId() const {
811 	return CLASS_BUTTON;
812 }
813 
allocate(WORD * & p,short & id) const814 void W32RadioButtonGroup::allocate(WORD *&p, short &id) const {
815 	W32StandardControl::allocate(p, id);
816 	for (W32WidgetList::const_iterator it = myButtons.begin(); it != myButtons.end(); ++it) {
817 		(*it)->allocate(p, id);
818 	}
819 }
820 
allocationSize() const821 int W32RadioButtonGroup::allocationSize() const {
822 	int size = W32StandardControl::allocationSize();
823 	for (W32WidgetList::const_iterator it = myButtons.begin(); it != myButtons.end(); ++it) {
824 		size += (*it)->allocationSize();
825 	}
826 	return size;
827 }
828 
setEditable(bool editable)829 void W32RadioButtonGroup::setEditable(bool editable) {
830 	if (editable) {
831 		myStyle &= ~WS_DISABLED;
832 	} else {
833 		myStyle |= WS_DISABLED;
834 	}
835 	if (myWindow != 0) {
836 		// TODO: check
837 		SetWindowLong(myWindow, GWL_STYLE, myStyle);
838 	}
839 	for (W32WidgetList::const_iterator it = myButtons.begin(); it != myButtons.end(); ++it) {
840 		((W32RadioButton&)**it).setEditable(editable);
841 	}
842 }
843 
setVisible(bool visible)844 void W32RadioButtonGroup::setVisible(bool visible) {
845 	W32StandardControl::setVisible(visible);
846 	for (W32WidgetList::const_iterator it = myButtons.begin(); it != myButtons.end(); ++it) {
847 		(*it)->setVisible(visible);
848 	}
849 }
850 
controlNumber() const851 int W32RadioButtonGroup::controlNumber() const {
852 	int counter = W32StandardControl::controlNumber();
853 	for (W32WidgetList::const_iterator it = myButtons.begin(); it != myButtons.end(); ++it) {
854 		counter += (*it)->controlNumber();
855 	}
856 	return counter;
857 }
858 
setPosition(int x,int y,Size size)859 void W32RadioButtonGroup::setPosition(int x, int y, Size size) {
860 	W32StandardControl::setPosition(x, y, size);
861 	const short deltaY = size.Height / (myButtons.size() + 1);
862 	size.Width -= 16;
863 	size.Height = deltaY;
864 	for (W32WidgetList::iterator it = myButtons.begin(); it != myButtons.end(); ++it) {
865 		y += deltaY;
866 		(*it)->setPosition(x + 8, y, size)	;
867 	}
868 }
869 
minimumSize() const870 W32Widget::Size W32RadioButtonGroup::minimumSize() const {
871 	Size size;
872 	for (W32WidgetList::const_iterator it = myButtons.begin(); it != myButtons.end(); ++it) {
873 		size.Width = std::max(size.Width, (*it)->minimumSize().Width);
874 	}
875 	size.Width += 16;
876 	size.Height = 12 * myButtons.size() + 16;
877 	return size;
878 }
879 
init(HWND parent,W32ControlCollection * collection)880 void W32RadioButtonGroup::init(HWND parent, W32ControlCollection *collection) {
881 	W32StandardControl::init(parent, collection);
882 	::setWindowText(myWindow, myCaption);
883 	for (W32WidgetList::const_iterator it = myButtons.begin(); it != myButtons.end(); ++it) {
884 		(*it)->init(parent, collection);
885 		((W32RadioButton&)**it).setChecked(it == myButtons.begin() + myCheckedIndex);
886 	}
887 }
888 
setChecked(W32RadioButton & button)889 void W32RadioButtonGroup::setChecked(W32RadioButton &button) {
890 	myCheckedIndex = -1;
891 	for (W32WidgetList::const_iterator it = myButtons.begin(); it != myButtons.end(); ++it) {
892 		W32RadioButton &rb = (W32RadioButton&)**it;
893 		if (&rb == &button) {
894 			rb.setChecked(true);
895 			myCheckedIndex = it - myButtons.begin();
896 		} else {
897 			rb.setChecked(false);
898 		}
899 	}
900 }
901 
setChecked(int index)902 void W32RadioButtonGroup::setChecked(int index) {
903 	myCheckedIndex = index;
904 	for (W32WidgetList::const_iterator it = myButtons.begin(); it != myButtons.end(); ++it) {
905 		W32RadioButton &rb = (W32RadioButton&)**it;
906 		rb.setChecked(it == myButtons.begin() + index);
907 	}
908 }
909 
checkedIndex() const910 int W32RadioButtonGroup::checkedIndex() const {
911 	return myCheckedIndex;
912 }
913