1/*
2 * Copyright 2001-2015, Haiku, Inc.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		DarkWyrm <bpmagic@columbus.rr.com>
7 *		Caz <turok2@currantbun.com>
8 *		Axel D��rfler, axeld@pinc-software.de
9 *		Michael Lotz <mmlr@mlotz.ch>
10 *		Wim van der Meer <WPJvanderMeer@gmail.com>
11 *		Joseph Groover <looncraz@looncraz.net>
12 */
13
14
15/*!	Global functions and variables for the Interface Kit */
16
17
18#include <InterfaceDefs.h>
19
20#include <new>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24
25#include <Bitmap.h>
26#include <Clipboard.h>
27#include <ControlLook.h>
28#include <Font.h>
29#include <Menu.h>
30#include <Point.h>
31#include <Roster.h>
32#include <Screen.h>
33#include <ScrollBar.h>
34#include <String.h>
35#include <TextView.h>
36#include <Window.h>
37
38#include <ApplicationPrivate.h>
39#include <AppServerLink.h>
40#include <ColorConversion.h>
41#include <DecorInfo.h>
42#include <DefaultColors.h>
43#include <DesktopLink.h>
44#include <HaikuControlLook.h>
45#include <InputServerTypes.h>
46#include <input_globals.h>
47#include <InterfacePrivate.h>
48#include <MenuPrivate.h>
49#include <pr_server.h>
50#include <ServerProtocol.h>
51#include <ServerReadOnlyMemory.h>
52#include <truncate_string.h>
53#include <utf8_functions.h>
54#include <WidthBuffer.h>
55#include <WindowInfo.h>
56
57
58using namespace BPrivate;
59
60// some other weird struct exported by BeOS, it's not initialized, though
61struct general_ui_info {
62	rgb_color	background_color;
63	rgb_color	mark_color;
64	rgb_color	highlight_color;
65	bool		color_frame;
66	rgb_color	window_frame_color;
67};
68
69struct general_ui_info general_info;
70
71menu_info *_menu_info_ptr_;
72
73extern "C" const char B_NOTIFICATION_SENDER[] = "be:sender";
74
75static const rgb_color _kDefaultColors[kColorWhichCount] = {
76	{216, 216, 216, 255},	// B_PANEL_BACKGROUND_COLOR
77	{216, 216, 216, 255},	// B_MENU_BACKGROUND_COLOR
78	{255, 203, 0, 255},		// B_WINDOW_TAB_COLOR
79	{0, 0, 229, 255},		// B_KEYBOARD_NAVIGATION_COLOR
80	{51, 102, 152, 255},	// B_DESKTOP_COLOR
81	{153, 153, 153, 255},	// B_MENU_SELECTED_BACKGROUND_COLOR
82	{0, 0, 0, 255},			// B_MENU_ITEM_TEXT_COLOR
83	{0, 0, 0, 255},			// B_MENU_SELECTED_ITEM_TEXT_COLOR
84	{0, 0, 0, 255},			// B_MENU_SELECTED_BORDER_COLOR
85	{0, 0, 0, 255},			// B_PANEL_TEXT_COLOR
86	{255, 255, 255, 255},	// B_DOCUMENT_BACKGROUND_COLOR
87	{0, 0, 0, 255},			// B_DOCUMENT_TEXT_COLOR
88	{245, 245, 245, 255},	// B_CONTROL_BACKGROUND_COLOR
89	{0, 0, 0, 255},			// B_CONTROL_TEXT_COLOR
90	{172, 172, 172, 255},	// B_CONTROL_BORDER_COLOR
91	{102, 152, 203, 255},	// B_CONTROL_HIGHLIGHT_COLOR
92	{0, 0, 0, 255},			// B_NAVIGATION_PULSE_COLOR
93	{255, 255, 255, 255},	// B_SHINE_COLOR
94	{0, 0, 0, 255},			// B_SHADOW_COLOR
95	{255, 255, 216, 255},	// B_TOOLTIP_BACKGROUND_COLOR
96	{0, 0, 0, 255},			// B_TOOLTIP_TEXT_COLOR
97	{0, 0, 0, 255},			// B_WINDOW_TEXT_COLOR
98	{232, 232, 232, 255},	// B_WINDOW_INACTIVE_TAB_COLOR
99	{80, 80, 80, 255},		// B_WINDOW_INACTIVE_TEXT_COLOR
100	{224, 224, 224, 255},	// B_WINDOW_BORDER_COLOR
101	{232, 232, 232, 255},	// B_WINDOW_INACTIVE_BORDER_COLOR
102	{27, 82, 140, 255},     // B_CONTROL_MARK_COLOR
103	{255, 255, 255, 255},	// B_LIST_BACKGROUND_COLOR
104	{190, 190, 190, 255},	// B_LIST_SELECTED_BACKGROUND_COLOR
105	{0, 0, 0, 255},			// B_LIST_ITEM_TEXT_COLOR
106	{0, 0, 0, 255},			// B_LIST_SELECTED_ITEM_TEXT_COLOR
107	{216, 216, 216, 255},	// B_SCROLL_BAR_THUMB_COLOR
108	{51, 102, 187, 255},	// B_LINK_TEXT_COLOR
109	{102, 152, 203, 255},	// B_LINK_HOVER_COLOR
110	{145, 112, 155, 255},	// B_LINK_VISITED_COLOR
111	{121, 142, 203, 255},	// B_LINK_ACTIVE_COLOR
112	{50, 150, 255, 255},	// B_STATUS_BAR_COLOR
113	// 100...
114	{46, 204, 64, 255},		// B_SUCCESS_COLOR
115	{255, 65, 54, 255},		// B_FAILURE_COLOR
116	{}
117};
118const rgb_color* BPrivate::kDefaultColors = &_kDefaultColors[0];
119
120
121static const char* kColorNames[kColorWhichCount] = {
122	"B_PANEL_BACKGROUND_COLOR",
123	"B_MENU_BACKGROUND_COLOR",
124	"B_WINDOW_TAB_COLOR",
125	"B_KEYBOARD_NAVIGATION_COLOR",
126	"B_DESKTOP_COLOR",
127	"B_MENU_SELECTED_BACKGROUND_COLOR",
128	"B_MENU_ITEM_TEXT_COLOR",
129	"B_MENU_SELECTED_ITEM_TEXT_COLOR",
130	"B_MENU_SELECTED_BORDER_COLOR",
131	"B_PANEL_TEXT_COLOR",
132	"B_DOCUMENT_BACKGROUND_COLOR",
133	"B_DOCUMENT_TEXT_COLOR",
134	"B_CONTROL_BACKGROUND_COLOR",
135	"B_CONTROL_TEXT_COLOR",
136	"B_CONTROL_BORDER_COLOR",
137	"B_CONTROL_HIGHLIGHT_COLOR",
138	"B_NAVIGATION_PULSE_COLOR",
139	"B_SHINE_COLOR",
140	"B_SHADOW_COLOR",
141	"B_TOOLTIP_BACKGROUND_COLOR",
142	"B_TOOLTIP_TEXT_COLOR",
143	"B_WINDOW_TEXT_COLOR",
144	"B_WINDOW_INACTIVE_TAB_COLOR",
145	"B_WINDOW_INACTIVE_TEXT_COLOR",
146	"B_WINDOW_BORDER_COLOR",
147	"B_WINDOW_INACTIVE_BORDER_COLOR",
148	"B_CONTROL_MARK_COLOR",
149	"B_LIST_BACKGROUND_COLOR",
150	"B_LIST_SELECTED_BACKGROUND_COLOR",
151	"B_LIST_ITEM_TEXT_COLOR",
152	"B_LIST_SELECTED_ITEM_TEXT_COLOR",
153	"B_SCROLL_BAR_THUMB_COLOR",
154	"B_LINK_TEXT_COLOR",
155	"B_LINK_HOVER_COLOR",
156	"B_LINK_VISITED_COLOR",
157	"B_LINK_ACTIVE_COLOR",
158	"B_STATUS_BAR_COLOR",
159	// 100...
160	"B_SUCCESS_COLOR",
161	"B_FAILURE_COLOR",
162	NULL
163};
164
165static image_id sControlLookAddon = -1;
166
167
168namespace BPrivate {
169
170
171/*!	Fills the \a width, \a height, and \a colorSpace parameters according
172	to the window screen's mode.
173	Returns \c true if the mode is known.
174*/
175bool
176get_mode_parameter(uint32 mode, int32& width, int32& height,
177	uint32& colorSpace)
178{
179	switch (mode) {
180		case B_8_BIT_640x480:
181		case B_8_BIT_800x600:
182		case B_8_BIT_1024x768:
183		case B_8_BIT_1152x900:
184		case B_8_BIT_1280x1024:
185		case B_8_BIT_1600x1200:
186			colorSpace = B_CMAP8;
187			break;
188
189		case B_15_BIT_640x480:
190		case B_15_BIT_800x600:
191		case B_15_BIT_1024x768:
192		case B_15_BIT_1152x900:
193		case B_15_BIT_1280x1024:
194		case B_15_BIT_1600x1200:
195			colorSpace = B_RGB15;
196			break;
197
198		case B_16_BIT_640x480:
199		case B_16_BIT_800x600:
200		case B_16_BIT_1024x768:
201		case B_16_BIT_1152x900:
202		case B_16_BIT_1280x1024:
203		case B_16_BIT_1600x1200:
204			colorSpace = B_RGB16;
205			break;
206
207		case B_32_BIT_640x480:
208		case B_32_BIT_800x600:
209		case B_32_BIT_1024x768:
210		case B_32_BIT_1152x900:
211		case B_32_BIT_1280x1024:
212		case B_32_BIT_1600x1200:
213			colorSpace = B_RGB32;
214			break;
215
216		default:
217			return false;
218	}
219
220	switch (mode) {
221		case B_8_BIT_640x480:
222		case B_15_BIT_640x480:
223		case B_16_BIT_640x480:
224		case B_32_BIT_640x480:
225			width = 640; height = 480;
226			break;
227
228		case B_8_BIT_800x600:
229		case B_15_BIT_800x600:
230		case B_16_BIT_800x600:
231		case B_32_BIT_800x600:
232			width = 800; height = 600;
233			break;
234
235		case B_8_BIT_1024x768:
236		case B_15_BIT_1024x768:
237		case B_16_BIT_1024x768:
238		case B_32_BIT_1024x768:
239			width = 1024; height = 768;
240			break;
241
242		case B_8_BIT_1152x900:
243		case B_15_BIT_1152x900:
244		case B_16_BIT_1152x900:
245		case B_32_BIT_1152x900:
246			width = 1152; height = 900;
247			break;
248
249		case B_8_BIT_1280x1024:
250		case B_15_BIT_1280x1024:
251		case B_16_BIT_1280x1024:
252		case B_32_BIT_1280x1024:
253			width = 1280; height = 1024;
254			break;
255
256		case B_8_BIT_1600x1200:
257		case B_15_BIT_1600x1200:
258		case B_16_BIT_1600x1200:
259		case B_32_BIT_1600x1200:
260			width = 1600; height = 1200;
261			break;
262	}
263
264	return true;
265}
266
267
268void
269get_workspaces_layout(uint32* _columns, uint32* _rows)
270{
271	int32 columns = 1;
272	int32 rows = 1;
273
274	BPrivate::AppServerLink link;
275	link.StartMessage(AS_GET_WORKSPACE_LAYOUT);
276
277	status_t status;
278	if (link.FlushWithReply(status) == B_OK && status == B_OK) {
279		link.Read<int32>(&columns);
280		link.Read<int32>(&rows);
281	}
282
283	if (_columns != NULL)
284		*_columns = columns;
285	if (_rows != NULL)
286		*_rows = rows;
287}
288
289
290void
291set_workspaces_layout(uint32 columns, uint32 rows)
292{
293	if (columns < 1 || rows < 1)
294		return;
295
296	BPrivate::AppServerLink link;
297	link.StartMessage(AS_SET_WORKSPACE_LAYOUT);
298	link.Attach<int32>(columns);
299	link.Attach<int32>(rows);
300	link.Flush();
301}
302
303
304}	// namespace BPrivate
305
306
307void
308set_subpixel_antialiasing(bool subpix)
309{
310	BPrivate::AppServerLink link;
311
312	link.StartMessage(AS_SET_SUBPIXEL_ANTIALIASING);
313	link.Attach<bool>(subpix);
314	link.Flush();
315}
316
317
318status_t
319get_subpixel_antialiasing(bool* subpix)
320{
321	BPrivate::AppServerLink link;
322
323	link.StartMessage(AS_GET_SUBPIXEL_ANTIALIASING);
324	int32 status = B_ERROR;
325	if (link.FlushWithReply(status) != B_OK || status < B_OK)
326		return status;
327	link.Read<bool>(subpix);
328	return B_OK;
329}
330
331
332void
333set_hinting_mode(uint8 hinting)
334{
335	BPrivate::AppServerLink link;
336
337	link.StartMessage(AS_SET_HINTING);
338	link.Attach<uint8>(hinting);
339	link.Flush();
340}
341
342
343status_t
344get_hinting_mode(uint8* hinting)
345{
346	BPrivate::AppServerLink link;
347
348	link.StartMessage(AS_GET_HINTING);
349	int32 status = B_ERROR;
350	if (link.FlushWithReply(status) != B_OK || status < B_OK)
351		return status;
352	link.Read<uint8>(hinting);
353	return B_OK;
354}
355
356
357void
358set_average_weight(uint8 averageWeight)
359{
360	BPrivate::AppServerLink link;
361
362	link.StartMessage(AS_SET_SUBPIXEL_AVERAGE_WEIGHT);
363	link.Attach<uint8>(averageWeight);
364	link.Flush();
365}
366
367
368status_t
369get_average_weight(uint8* averageWeight)
370{
371	BPrivate::AppServerLink link;
372
373	link.StartMessage(AS_GET_SUBPIXEL_AVERAGE_WEIGHT);
374	int32 status = B_ERROR;
375	if (link.FlushWithReply(status) != B_OK || status < B_OK)
376		return status;
377	link.Read<uint8>(averageWeight);
378	return B_OK;
379}
380
381
382void
383set_is_subpixel_ordering_regular(bool subpixelOrdering)
384{
385	BPrivate::AppServerLink link;
386
387	link.StartMessage(AS_SET_SUBPIXEL_ORDERING);
388	link.Attach<bool>(subpixelOrdering);
389	link.Flush();
390}
391
392
393status_t
394get_is_subpixel_ordering_regular(bool* subpixelOrdering)
395{
396	BPrivate::AppServerLink link;
397
398	link.StartMessage(AS_GET_SUBPIXEL_ORDERING);
399	int32 status = B_ERROR;
400	if (link.FlushWithReply(status) != B_OK || status < B_OK)
401		return status;
402	link.Read<bool>(subpixelOrdering);
403	return B_OK;
404}
405
406
407const color_map *
408system_colors()
409{
410	return BScreen(B_MAIN_SCREEN_ID).ColorMap();
411}
412
413
414status_t
415set_screen_space(int32 index, uint32 space, bool stick)
416{
417	int32 width;
418	int32 height;
419	uint32 depth;
420	if (!BPrivate::get_mode_parameter(space, width, height, depth))
421		return B_BAD_VALUE;
422
423	BScreen screen(B_MAIN_SCREEN_ID);
424	display_mode mode;
425
426	// TODO: What about refresh rate ?
427	// currently we get it from the current video mode, but
428	// this might be not so wise.
429	status_t status = screen.GetMode(index, &mode);
430	if (status < B_OK)
431		return status;
432
433	mode.virtual_width = width;
434	mode.virtual_height = height;
435	mode.space = depth;
436
437	return screen.SetMode(index, &mode, stick);
438}
439
440
441status_t
442get_scroll_bar_info(scroll_bar_info *info)
443{
444	if (info == NULL)
445		return B_BAD_VALUE;
446
447	BPrivate::AppServerLink link;
448	link.StartMessage(AS_GET_SCROLLBAR_INFO);
449
450	int32 code;
451	if (link.FlushWithReply(code) == B_OK
452		&& code == B_OK) {
453		link.Read<scroll_bar_info>(info);
454		return B_OK;
455	}
456
457	return B_ERROR;
458}
459
460
461status_t
462set_scroll_bar_info(scroll_bar_info *info)
463{
464	if (info == NULL)
465		return B_BAD_VALUE;
466
467	BPrivate::AppServerLink link;
468	int32 code;
469
470	link.StartMessage(AS_SET_SCROLLBAR_INFO);
471	link.Attach<scroll_bar_info>(*info);
472
473	if (link.FlushWithReply(code) == B_OK
474		&& code == B_OK)
475		return B_OK;
476
477	return B_ERROR;
478}
479
480
481status_t
482get_mouse_type(int32 *type)
483{
484	BMessage command(IS_GET_MOUSE_TYPE);
485	BMessage reply;
486
487	status_t err = _control_input_server_(&command, &reply);
488	if (err != B_OK)
489		return err;
490	return reply.FindInt32("mouse_type", type);
491}
492
493
494status_t
495set_mouse_type(int32 type)
496{
497	BMessage command(IS_SET_MOUSE_TYPE);
498	BMessage reply;
499
500	status_t err = command.AddInt32("mouse_type", type);
501	if (err != B_OK)
502		return err;
503	return _control_input_server_(&command, &reply);
504}
505
506
507status_t
508get_mouse_map(mouse_map *map)
509{
510	BMessage command(IS_GET_MOUSE_MAP);
511	BMessage reply;
512	const void *data = 0;
513	ssize_t count;
514
515	status_t err = _control_input_server_(&command, &reply);
516	if (err == B_OK)
517		err = reply.FindData("mousemap", B_RAW_TYPE, &data, &count);
518	if (err != B_OK)
519		return err;
520
521	memcpy(map, data, count);
522
523	return B_OK;
524}
525
526
527status_t
528set_mouse_map(mouse_map *map)
529{
530	BMessage command(IS_SET_MOUSE_MAP);
531	BMessage reply;
532
533	status_t err = command.AddData("mousemap", B_RAW_TYPE, map,
534		sizeof(mouse_map));
535	if (err != B_OK)
536		return err;
537	return _control_input_server_(&command, &reply);
538}
539
540
541status_t
542get_click_speed(bigtime_t *speed)
543{
544	BMessage command(IS_GET_CLICK_SPEED);
545	BMessage reply;
546
547	status_t err = _control_input_server_(&command, &reply);
548	if (err != B_OK)
549		return err;
550
551	if (reply.FindInt64("speed", speed) != B_OK)
552		*speed = 500000;
553
554	return B_OK;
555}
556
557
558status_t
559set_click_speed(bigtime_t speed)
560{
561	BMessage command(IS_SET_CLICK_SPEED);
562	BMessage reply;
563	command.AddInt64("speed", speed);
564	return _control_input_server_(&command, &reply);
565}
566
567
568status_t
569get_mouse_speed(int32 *speed)
570{
571	BMessage command(IS_GET_MOUSE_SPEED);
572	BMessage reply;
573
574	status_t err = _control_input_server_(&command, &reply);
575	if (err != B_OK)
576		return err;
577
578	if (reply.FindInt32("speed", speed) != B_OK)
579		*speed = 65536;
580
581	return B_OK;
582}
583
584
585status_t
586set_mouse_speed(int32 speed)
587{
588	BMessage command(IS_SET_MOUSE_SPEED);
589	BMessage reply;
590	command.AddInt32("speed", speed);
591	return _control_input_server_(&command, &reply);
592}
593
594
595status_t
596get_mouse_acceleration(int32 *speed)
597{
598	BMessage command(IS_GET_MOUSE_ACCELERATION);
599	BMessage reply;
600
601	_control_input_server_(&command, &reply);
602
603	if (reply.FindInt32("speed", speed) != B_OK)
604		*speed = 65536;
605
606	return B_OK;
607}
608
609
610status_t
611set_mouse_acceleration(int32 speed)
612{
613	BMessage command(IS_SET_MOUSE_ACCELERATION);
614	BMessage reply;
615	command.AddInt32("speed", speed);
616	return _control_input_server_(&command, &reply);
617}
618
619
620status_t
621get_key_repeat_rate(int32 *rate)
622{
623	BMessage command(IS_GET_KEY_REPEAT_RATE);
624	BMessage reply;
625
626	_control_input_server_(&command, &reply);
627
628	if (reply.FindInt32("rate", rate) != B_OK)
629		*rate = 250000;
630
631	return B_OK;
632}
633
634
635status_t
636set_key_repeat_rate(int32 rate)
637{
638	BMessage command(IS_SET_KEY_REPEAT_RATE);
639	BMessage reply;
640	command.AddInt32("rate", rate);
641	return _control_input_server_(&command, &reply);
642}
643
644
645status_t
646get_key_repeat_delay(bigtime_t *delay)
647{
648	BMessage command(IS_GET_KEY_REPEAT_DELAY);
649	BMessage reply;
650
651	_control_input_server_(&command, &reply);
652
653	if (reply.FindInt64("delay", delay) != B_OK)
654		*delay = 200;
655
656	return B_OK;
657}
658
659
660status_t
661set_key_repeat_delay(bigtime_t  delay)
662{
663	BMessage command(IS_SET_KEY_REPEAT_DELAY);
664	BMessage reply;
665	command.AddInt64("delay", delay);
666	return _control_input_server_(&command, &reply);
667}
668
669
670uint32
671modifiers()
672{
673	BMessage command(IS_GET_MODIFIERS);
674	BMessage reply;
675	int32 err, modifier;
676
677	_control_input_server_(&command, &reply);
678
679	if (reply.FindInt32("status", &err) != B_OK)
680		return 0;
681
682	if (reply.FindInt32("modifiers", &modifier) != B_OK)
683		return 0;
684
685	return modifier;
686}
687
688
689status_t
690get_key_info(key_info *info)
691{
692	BMessage command(IS_GET_KEY_INFO);
693	BMessage reply;
694	const void *data = 0;
695	int32 err;
696	ssize_t count;
697
698	_control_input_server_(&command, &reply);
699
700	if (reply.FindInt32("status", &err) != B_OK)
701		return B_ERROR;
702
703	if (reply.FindData("key_info", B_ANY_TYPE, &data, &count) != B_OK)
704		return B_ERROR;
705
706	memcpy(info, data, count);
707	return B_OK;
708}
709
710
711void
712get_key_map(key_map **map, char **key_buffer)
713{
714	_get_key_map(map, key_buffer, NULL);
715}
716
717
718void
719_get_key_map(key_map **map, char **key_buffer, ssize_t *key_buffer_size)
720{
721	BMessage command(IS_GET_KEY_MAP);
722	BMessage reply;
723	ssize_t map_count, key_count;
724	const void *map_array = 0, *key_array = 0;
725	if (key_buffer_size == NULL)
726		key_buffer_size = &key_count;
727
728	_control_input_server_(&command, &reply);
729
730	if (reply.FindData("keymap", B_ANY_TYPE, &map_array, &map_count) != B_OK) {
731		*map = 0; *key_buffer = 0;
732		return;
733	}
734
735	if (reply.FindData("key_buffer", B_ANY_TYPE, &key_array, key_buffer_size)
736			!= B_OK) {
737		*map = 0; *key_buffer = 0;
738		return;
739	}
740
741	*map = (key_map *)malloc(map_count);
742	memcpy(*map, map_array, map_count);
743	*key_buffer = (char *)malloc(*key_buffer_size);
744	memcpy(*key_buffer, key_array, *key_buffer_size);
745}
746
747
748status_t
749get_keyboard_id(uint16 *id)
750{
751	BMessage command(IS_GET_KEYBOARD_ID);
752	BMessage reply;
753	uint16 kid;
754
755	_control_input_server_(&command, &reply);
756
757	status_t err = reply.FindInt16("id", (int16 *)&kid);
758	if (err != B_OK)
759		return err;
760	*id = kid;
761
762	return B_OK;
763}
764
765
766status_t
767get_modifier_key(uint32 modifier, uint32 *key)
768{
769	BMessage command(IS_GET_MODIFIER_KEY);
770	BMessage reply;
771	uint32 rkey;
772
773	command.AddInt32("modifier", modifier);
774	_control_input_server_(&command, &reply);
775
776	status_t err = reply.FindInt32("key", (int32 *) &rkey);
777	if (err != B_OK)
778		return err;
779	*key = rkey;
780
781	return B_OK;
782}
783
784
785void
786set_modifier_key(uint32 modifier, uint32 key)
787{
788	BMessage command(IS_SET_MODIFIER_KEY);
789	BMessage reply;
790
791	command.AddInt32("modifier", modifier);
792	command.AddInt32("key", key);
793	_control_input_server_(&command, &reply);
794}
795
796
797void
798set_keyboard_locks(uint32 modifiers)
799{
800	BMessage command(IS_SET_KEYBOARD_LOCKS);
801	BMessage reply;
802
803	command.AddInt32("locks", modifiers);
804	_control_input_server_(&command, &reply);
805}
806
807
808status_t
809_restore_key_map_()
810{
811	BMessage message(IS_RESTORE_KEY_MAP);
812	BMessage reply;
813
814	return _control_input_server_(&message, &reply);
815}
816
817
818rgb_color
819keyboard_navigation_color()
820{
821	// Queries the app_server
822	return ui_color(B_KEYBOARD_NAVIGATION_COLOR);
823}
824
825
826int32
827count_workspaces()
828{
829	uint32 columns;
830	uint32 rows;
831	BPrivate::get_workspaces_layout(&columns, &rows);
832
833	return columns * rows;
834}
835
836
837void
838set_workspace_count(int32 count)
839{
840	int32 squareRoot = (int32)sqrt(count);
841
842	int32 rows = 1;
843	for (int32 i = 2; i <= squareRoot; i++) {
844		if (count % i == 0)
845			rows = i;
846	}
847
848	int32 columns = count / rows;
849
850	BPrivate::set_workspaces_layout(columns, rows);
851}
852
853
854int32
855current_workspace()
856{
857	int32 index = 0;
858
859	BPrivate::AppServerLink link;
860	link.StartMessage(AS_CURRENT_WORKSPACE);
861
862	int32 status;
863	if (link.FlushWithReply(status) == B_OK && status == B_OK)
864		link.Read<int32>(&index);
865
866	return index;
867}
868
869
870void
871activate_workspace(int32 workspace)
872{
873	BPrivate::AppServerLink link;
874	link.StartMessage(AS_ACTIVATE_WORKSPACE);
875	link.Attach<int32>(workspace);
876	link.Attach<bool>(false);
877	link.Flush();
878}
879
880
881bigtime_t
882idle_time()
883{
884	bigtime_t idletime = 0;
885
886	BPrivate::AppServerLink link;
887	link.StartMessage(AS_IDLE_TIME);
888
889	int32 code;
890	if (link.FlushWithReply(code) == B_OK && code == B_OK)
891		link.Read<int64>(&idletime);
892
893	return idletime;
894}
895
896
897void
898run_select_printer_panel()
899{
900	if (be_roster == NULL)
901		return;
902
903	// Launches the Printer prefs app via the Roster
904	be_roster->Launch(PRNT_SIGNATURE_TYPE);
905}
906
907
908void
909run_add_printer_panel()
910{
911	// Launches the Printer prefs app via the Roster and asks it to
912	// add a printer
913	run_select_printer_panel();
914
915	BMessenger printerPanelMessenger(PRNT_SIGNATURE_TYPE);
916	printerPanelMessenger.SendMessage(PRINTERS_ADD_PRINTER);
917}
918
919
920void
921run_be_about()
922{
923	if (be_roster != NULL)
924		be_roster->Launch("application/x-vnd.Haiku-About");
925}
926
927
928void
929set_focus_follows_mouse(bool follow)
930{
931	// obviously deprecated API
932	set_mouse_mode(B_FOCUS_FOLLOWS_MOUSE);
933}
934
935
936bool
937focus_follows_mouse()
938{
939	return mouse_mode() == B_FOCUS_FOLLOWS_MOUSE;
940}
941
942
943void
944set_mouse_mode(mode_mouse mode)
945{
946	BPrivate::AppServerLink link;
947	link.StartMessage(AS_SET_MOUSE_MODE);
948	link.Attach<mode_mouse>(mode);
949	link.Flush();
950}
951
952
953mode_mouse
954mouse_mode()
955{
956	// Gets the mouse focus style, such as activate to click,
957	// focus to click, ...
958	mode_mouse mode = B_NORMAL_MOUSE;
959
960	BPrivate::AppServerLink link;
961	link.StartMessage(AS_GET_MOUSE_MODE);
962
963	int32 code;
964	if (link.FlushWithReply(code) == B_OK && code == B_OK)
965		link.Read<mode_mouse>(&mode);
966
967	return mode;
968}
969
970
971void
972set_focus_follows_mouse_mode(mode_focus_follows_mouse mode)
973{
974	BPrivate::AppServerLink link;
975	link.StartMessage(AS_SET_FOCUS_FOLLOWS_MOUSE_MODE);
976	link.Attach<mode_focus_follows_mouse>(mode);
977	link.Flush();
978}
979
980
981mode_focus_follows_mouse
982focus_follows_mouse_mode()
983{
984	mode_focus_follows_mouse mode = B_NORMAL_FOCUS_FOLLOWS_MOUSE;
985
986	BPrivate::AppServerLink link;
987	link.StartMessage(AS_GET_FOCUS_FOLLOWS_MOUSE_MODE);
988
989	int32 code;
990	if (link.FlushWithReply(code) == B_OK && code == B_OK)
991		link.Read<mode_focus_follows_mouse>(&mode);
992
993	return mode;
994}
995
996
997status_t
998get_mouse(BPoint* screenWhere, uint32* buttons)
999{
1000	if (screenWhere == NULL && buttons == NULL)
1001		return B_BAD_VALUE;
1002
1003	BPrivate::AppServerLink link;
1004	link.StartMessage(AS_GET_CURSOR_POSITION);
1005
1006	int32 code;
1007	status_t ret = link.FlushWithReply(code);
1008	if (ret != B_OK)
1009		return ret;
1010	if (code != B_OK)
1011		return code;
1012
1013	if (screenWhere != NULL)
1014		ret = link.Read<BPoint>(screenWhere);
1015	else {
1016		BPoint dummy;
1017		ret = link.Read<BPoint>(&dummy);
1018	}
1019	if (ret != B_OK)
1020		return ret;
1021
1022	if (buttons != NULL)
1023		ret = link.Read<uint32>(buttons);
1024	else {
1025		uint32 dummy;
1026		ret = link.Read<uint32>(&dummy);
1027	}
1028
1029	return ret;
1030}
1031
1032
1033status_t
1034get_mouse_bitmap(BBitmap** bitmap, BPoint* hotspot)
1035{
1036	if (bitmap == NULL && hotspot == NULL)
1037		return B_BAD_VALUE;
1038
1039	BPrivate::AppServerLink link;
1040	link.StartMessage(AS_GET_CURSOR_BITMAP);
1041
1042	int32 code;
1043	status_t status = link.FlushWithReply(code);
1044	if (status != B_OK)
1045		return status;
1046	if (code != B_OK)
1047		return code;
1048
1049	uint32 size = 0;
1050	uint32 cursorWidth = 0;
1051	uint32 cursorHeight = 0;
1052
1053	// if link.Read() returns an error, the same error will be returned on
1054	// subsequent calls, so we'll check only the return value of the last call
1055	link.Read<uint32>(&size);
1056	link.Read<uint32>(&cursorWidth);
1057	link.Read<uint32>(&cursorHeight);
1058	if (hotspot == NULL) {
1059		BPoint dummy;
1060		link.Read<BPoint>(&dummy);
1061	} else
1062		link.Read<BPoint>(hotspot);
1063
1064	void* data = NULL;
1065	if (size > 0)
1066		data = malloc(size);
1067	if (data == NULL)
1068		return B_NO_MEMORY;
1069
1070	status = link.Read(data, size);
1071	if (status != B_OK) {
1072		free(data);
1073		return status;
1074	}
1075
1076	BBitmap* cursorBitmap = new (std::nothrow) BBitmap(BRect(0, 0,
1077		cursorWidth - 1, cursorHeight - 1), B_RGBA32);
1078
1079	if (cursorBitmap == NULL) {
1080		free(data);
1081		return B_NO_MEMORY;
1082	}
1083	status = cursorBitmap->InitCheck();
1084	if (status == B_OK)
1085		cursorBitmap->SetBits(data, size, 0, B_RGBA32);
1086
1087	free(data);
1088
1089	if (status == B_OK && bitmap != NULL)
1090		*bitmap = cursorBitmap;
1091	else
1092		delete cursorBitmap;
1093
1094	return status;
1095}
1096
1097
1098void
1099set_accept_first_click(bool acceptFirstClick)
1100{
1101	BPrivate::AppServerLink link;
1102	link.StartMessage(AS_SET_ACCEPT_FIRST_CLICK);
1103	link.Attach<bool>(acceptFirstClick);
1104	link.Flush();
1105}
1106
1107
1108bool
1109accept_first_click()
1110{
1111	// Gets the accept first click status
1112	bool acceptFirstClick = true;
1113
1114	BPrivate::AppServerLink link;
1115	link.StartMessage(AS_GET_ACCEPT_FIRST_CLICK);
1116
1117	int32 code;
1118	if (link.FlushWithReply(code) == B_OK && code == B_OK)
1119		link.Read<bool>(&acceptFirstClick);
1120
1121	return acceptFirstClick;
1122}
1123
1124
1125rgb_color
1126ui_color(color_which which)
1127{
1128	int32 index = color_which_to_index(which);
1129	if (index < 0 || index >= kColorWhichCount) {
1130		fprintf(stderr, "ui_color(): unknown color_which %d\n", which);
1131		return make_color(0, 0, 0);
1132	}
1133
1134	if (be_app != NULL) {
1135		server_read_only_memory* shared
1136			= BApplication::Private::ServerReadOnlyMemory();
1137		if (shared != NULL) {
1138			// check for unset colors
1139			if (shared->colors[index] == B_TRANSPARENT_COLOR)
1140				shared->colors[index] = kDefaultColors[index];
1141
1142			return shared->colors[index];
1143		}
1144	}
1145
1146	return kDefaultColors[index];
1147}
1148
1149
1150const char*
1151ui_color_name(color_which which)
1152{
1153	// Suppress warnings for B_NO_COLOR.
1154	if (which == B_NO_COLOR)
1155		return NULL;
1156
1157	int32 index = color_which_to_index(which);
1158	if (index < 0 || index >= kColorWhichCount) {
1159		fprintf(stderr, "ui_color_name(): unknown color_which %d\n", which);
1160		return NULL;
1161	}
1162
1163	return kColorNames[index];
1164}
1165
1166
1167color_which
1168which_ui_color(const char* name)
1169{
1170	if (name == NULL)
1171		return B_NO_COLOR;
1172
1173	for (int32 index = 0; index < kColorWhichCount; ++index) {
1174		if (!strcmp(kColorNames[index], name))
1175			return index_to_color_which(index);
1176	}
1177
1178	return B_NO_COLOR;
1179}
1180
1181
1182void
1183set_ui_color(const color_which &which, const rgb_color &color)
1184{
1185	int32 index = color_which_to_index(which);
1186	if (index < 0 || index >= kColorWhichCount) {
1187		fprintf(stderr, "set_ui_color(): unknown color_which %d\n", which);
1188		return;
1189	}
1190
1191	if (ui_color(which) == color)
1192		return;
1193
1194	BPrivate::DesktopLink link;
1195	link.StartMessage(AS_SET_UI_COLOR);
1196	link.Attach<color_which>(which);
1197	link.Attach<rgb_color>(color);
1198	link.Flush();
1199}
1200
1201
1202void
1203set_ui_colors(const BMessage* colors)
1204{
1205	if (colors == NULL)
1206		return;
1207
1208	int32 count = 0;
1209	int32 index = 0;
1210	char* name = NULL;
1211	type_code type;
1212	rgb_color color;
1213	color_which which = B_NO_COLOR;
1214
1215	BPrivate::DesktopLink desktop;
1216	if (desktop.InitCheck() != B_OK)
1217		return;
1218
1219	desktop.StartMessage(AS_SET_UI_COLORS);
1220	desktop.Attach<bool>(false);
1221
1222	// Only colors with names that map to system colors will get through.
1223	while (colors->GetInfo(B_RGB_32_BIT_TYPE, index, &name, &type) == B_OK) {
1224
1225		which = which_ui_color(name);
1226		++index;
1227
1228		if (which == B_NO_COLOR || colors->FindColor(name, &color) != B_OK)
1229			continue;
1230
1231		desktop.Attach<color_which>(which);
1232		desktop.Attach<rgb_color>(color);
1233		++count;
1234	}
1235
1236	if (count == 0)
1237		return;
1238
1239	desktop.Attach<color_which>(B_NO_COLOR);
1240	desktop.Flush();
1241}
1242
1243
1244rgb_color
1245tint_color(rgb_color color, float tint)
1246{
1247	rgb_color result;
1248
1249	#define LIGHTEN(x) ((uint8)(255.0f - (255.0f - x) * tint))
1250	#define DARKEN(x)  ((uint8)(x * (2 - tint)))
1251
1252	if (tint < 1.0f) {
1253		result.red   = LIGHTEN(color.red);
1254		result.green = LIGHTEN(color.green);
1255		result.blue  = LIGHTEN(color.blue);
1256		result.alpha = color.alpha;
1257	} else {
1258		result.red   = DARKEN(color.red);
1259		result.green = DARKEN(color.green);
1260		result.blue  = DARKEN(color.blue);
1261		result.alpha = color.alpha;
1262	}
1263
1264	#undef LIGHTEN
1265	#undef DARKEN
1266
1267	return result;
1268}
1269
1270
1271rgb_color shift_color(rgb_color color, float shift);
1272
1273rgb_color
1274shift_color(rgb_color color, float shift)
1275{
1276	return tint_color(color, shift);
1277}
1278
1279
1280extern "C" status_t
1281_init_interface_kit_()
1282{
1283	status_t status = BPrivate::PaletteConverter::InitializeDefault(true);
1284	if (status < B_OK)
1285		return status;
1286
1287	// init global clipboard
1288	if (be_clipboard == NULL)
1289		be_clipboard = new BClipboard(NULL);
1290
1291	BString path;
1292	if (get_control_look(path) && path.Length() > 0) {
1293		BControlLook* (*instantiate)(image_id);
1294
1295		sControlLookAddon = load_add_on(path.String());
1296		if (sControlLookAddon >= 0
1297			&& get_image_symbol(sControlLookAddon,
1298				"instantiate_control_look",
1299				B_SYMBOL_TYPE_TEXT, (void **)&instantiate) == B_OK) {
1300			be_control_look = instantiate(sControlLookAddon);
1301			if (be_control_look == NULL) {
1302				unload_add_on(sControlLookAddon);
1303				sControlLookAddon = -1;
1304			}
1305		}
1306	}
1307	if (be_control_look == NULL)
1308		be_control_look = new HaikuControlLook();
1309
1310	_init_global_fonts_();
1311
1312	BPrivate::gWidthBuffer = new BPrivate::WidthBuffer;
1313	status = BPrivate::MenuPrivate::CreateBitmaps();
1314	if (status != B_OK)
1315		return status;
1316
1317	_menu_info_ptr_ = &BMenu::sMenuInfo;
1318
1319	status = get_menu_info(&BMenu::sMenuInfo);
1320	if (status != B_OK)
1321		return status;
1322
1323	general_info.background_color = ui_color(B_PANEL_BACKGROUND_COLOR);
1324	general_info.mark_color = ui_color(B_CONTROL_MARK_COLOR);
1325	general_info.highlight_color = ui_color(B_CONTROL_HIGHLIGHT_COLOR);
1326	general_info.window_frame_color = ui_color(B_WINDOW_TAB_COLOR);
1327	general_info.color_frame = true;
1328
1329	// TODO: fill the other static members
1330
1331	return status;
1332}
1333
1334
1335extern "C" status_t
1336_fini_interface_kit_()
1337{
1338	BPrivate::MenuPrivate::DeleteBitmaps();
1339
1340	delete BPrivate::gWidthBuffer;
1341	BPrivate::gWidthBuffer = NULL;
1342
1343	delete be_control_look;
1344	be_control_look = NULL;
1345
1346	// Note: if we ever want to support live switching, we cannot just unload
1347	// the old one since some thread might still be in a method of the object.
1348	// maybe locking/unlocking all loopers around would ensure proper exit.
1349	if (sControlLookAddon >= 0)
1350		unload_add_on(sControlLookAddon);
1351	sControlLookAddon = -1;
1352
1353	// TODO: Anything else?
1354
1355	return B_OK;
1356}
1357
1358
1359
1360namespace BPrivate {
1361
1362
1363/*!	\brief queries the server for the current decorator
1364	\param path BString into which to store current decorator's location
1365	\return boolean true/false
1366*/
1367bool
1368get_decorator(BString& path)
1369{
1370	BPrivate::AppServerLink link;
1371	link.StartMessage(AS_GET_DECORATOR);
1372
1373	int32 code;
1374	if (link.FlushWithReply(code) != B_OK || code != B_OK)
1375		return false;
1376
1377	return link.ReadString(path) == B_OK;
1378}
1379
1380
1381/*!	\brief Private function which sets the window decorator for the system.
1382	\param path BString with the path to the decorator to set
1383
1384	Will return detailed error status via status_t
1385*/
1386status_t
1387set_decorator(const BString& path)
1388{
1389	BPrivate::AppServerLink link;
1390
1391	link.StartMessage(AS_SET_DECORATOR);
1392
1393	link.AttachString(path.String());
1394	link.Flush();
1395
1396	status_t error = B_OK;
1397	link.Read<status_t>(&error);
1398
1399	return error;
1400}
1401
1402
1403/*! \brief sets a window to preview a given decorator
1404	\param path path to any given decorator add-on
1405	\param window pointer to BWindow which will show decorator
1406
1407	Piggy-backs on BWindow::SetDecoratorSettings(...)
1408*/
1409status_t
1410preview_decorator(const BString& path, BWindow* window)
1411{
1412	if (window == NULL)
1413		return B_ERROR;
1414
1415	BMessage msg('prVu');
1416	msg.AddString("preview", path.String());
1417
1418	return window->SetDecoratorSettings(msg);
1419}
1420
1421
1422/*!	\brief queries the server for the current ControlLook path
1423	\param path BString into which to store current ControlLook's add-on path
1424	\return boolean true/false
1425*/
1426bool
1427get_control_look(BString& path)
1428{
1429	BPrivate::AppServerLink link;
1430	link.StartMessage(AS_GET_CONTROL_LOOK);
1431
1432	int32 code;
1433	if (link.FlushWithReply(code) != B_OK || code != B_OK)
1434		return false;
1435
1436	return link.ReadString(path) == B_OK;
1437}
1438
1439
1440/*!	\brief Private function which sets the ControlLook for the system.
1441	\param BString with the ControlLook add-on path to set
1442
1443	Will return detailed error status via status_t
1444*/
1445status_t
1446set_control_look(const BString& path)
1447{
1448	BPrivate::AppServerLink link;
1449
1450	link.StartMessage(AS_SET_CONTROL_LOOK);
1451
1452	link.AttachString(path.String());
1453
1454	status_t error = B_OK;
1455	if (link.FlushWithReply(error) != B_OK)
1456		return B_ERROR;
1457
1458	return error;
1459}
1460
1461
1462status_t
1463get_application_order(int32 workspace, team_id** _applications,
1464	int32* _count)
1465{
1466	BPrivate::AppServerLink link;
1467
1468	link.StartMessage(AS_GET_APPLICATION_ORDER);
1469	link.Attach<int32>(workspace);
1470
1471	int32 code;
1472	status_t status = link.FlushWithReply(code);
1473	if (status != B_OK)
1474		return status;
1475	if (code != B_OK)
1476		return code;
1477
1478	int32 count;
1479	link.Read<int32>(&count);
1480
1481	*_applications = (team_id*)malloc(count * sizeof(team_id));
1482	if (*_applications == NULL)
1483		return B_NO_MEMORY;
1484
1485	link.Read(*_applications, count * sizeof(team_id));
1486	*_count = count;
1487	return B_OK;
1488}
1489
1490
1491status_t
1492get_window_order(int32 workspace, int32** _tokens, int32* _count)
1493{
1494	BPrivate::AppServerLink link;
1495
1496	link.StartMessage(AS_GET_WINDOW_ORDER);
1497	link.Attach<int32>(workspace);
1498
1499	int32 code;
1500	status_t status = link.FlushWithReply(code);
1501	if (status != B_OK)
1502		return status;
1503	if (code != B_OK)
1504		return code;
1505
1506	int32 count;
1507	link.Read<int32>(&count);
1508
1509	*_tokens = (int32*)malloc(count * sizeof(int32));
1510	if (*_tokens == NULL)
1511		return B_NO_MEMORY;
1512
1513	link.Read(*_tokens, count * sizeof(int32));
1514	*_count = count;
1515	return B_OK;
1516}
1517
1518
1519}	// namespace BPrivate
1520
1521// These methods were marked with "Danger, will Robinson!" in
1522// the OpenTracker source, so we might not want to be compatible
1523// here.
1524// In any way, we would need to update Deskbar to use our
1525// replacements, so we could as well just implement them...
1526
1527void
1528do_window_action(int32 windowToken, int32 action, BRect zoomRect, bool zoom)
1529{
1530	BPrivate::AppServerLink link;
1531
1532	link.StartMessage(AS_WINDOW_ACTION);
1533	link.Attach<int32>(windowToken);
1534	link.Attach<int32>(action);
1535		// we don't have any zooming effect
1536
1537	link.Flush();
1538}
1539
1540
1541client_window_info*
1542get_window_info(int32 serverToken)
1543{
1544	BPrivate::AppServerLink link;
1545
1546	link.StartMessage(AS_GET_WINDOW_INFO);
1547	link.Attach<int32>(serverToken);
1548
1549	int32 code;
1550	if (link.FlushWithReply(code) != B_OK || code != B_OK)
1551		return NULL;
1552
1553	int32 size;
1554	link.Read<int32>(&size);
1555
1556	client_window_info* info = (client_window_info*)malloc(size);
1557	if (info == NULL)
1558		return NULL;
1559
1560	link.Read(info, size);
1561	return info;
1562}
1563
1564
1565int32*
1566get_token_list(team_id team, int32* _count)
1567{
1568	BPrivate::AppServerLink link;
1569
1570	link.StartMessage(AS_GET_WINDOW_LIST);
1571	link.Attach<team_id>(team);
1572
1573	int32 code;
1574	if (link.FlushWithReply(code) != B_OK || code != B_OK)
1575		return NULL;
1576
1577	int32 count;
1578	link.Read<int32>(&count);
1579
1580	int32* tokens = (int32*)malloc(count * sizeof(int32));
1581	if (tokens == NULL)
1582		return NULL;
1583
1584	link.Read(tokens, count * sizeof(int32));
1585	*_count = count;
1586	return tokens;
1587}
1588
1589
1590void
1591do_bring_to_front_team(BRect zoomRect, team_id team, bool zoom)
1592{
1593	BPrivate::AppServerLink link;
1594
1595	link.StartMessage(AS_BRING_TEAM_TO_FRONT);
1596	link.Attach<team_id>(team);
1597		// we don't have any zooming effect
1598
1599	link.Flush();
1600}
1601
1602
1603void
1604do_minimize_team(BRect zoomRect, team_id team, bool zoom)
1605{
1606	BPrivate::AppServerLink link;
1607
1608	link.StartMessage(AS_MINIMIZE_TEAM);
1609	link.Attach<team_id>(team);
1610		// we don't have any zooming effect
1611
1612	link.Flush();
1613}
1614
1615
1616//	#pragma mark - truncate string
1617
1618
1619void
1620truncate_string(BString& string, uint32 mode, float width,
1621	const float* escapementArray, float fontSize, float ellipsisWidth,
1622	int32 charCount)
1623{
1624	// add a tiny amount to the width to make floating point inaccuracy
1625	// not drop chars that would actually fit exactly
1626	width += 1.f / 128;
1627
1628	switch (mode) {
1629		case B_TRUNCATE_BEGINNING:
1630		{
1631			float totalWidth = 0;
1632			for (int32 i = charCount - 1; i >= 0; i--) {
1633				float charWidth = escapementArray[i] * fontSize;
1634				if (totalWidth + charWidth > width) {
1635					// we need to truncate
1636					while (totalWidth + ellipsisWidth > width) {
1637						// remove chars until there's enough space for the
1638						// ellipsis
1639						if (++i == charCount) {
1640							// we've reached the end of the string and still
1641							// no space, so return an empty string
1642							string.Truncate(0);
1643							return;
1644						}
1645
1646						totalWidth -= escapementArray[i] * fontSize;
1647					}
1648
1649					string.RemoveChars(0, i + 1);
1650					string.PrependChars(B_UTF8_ELLIPSIS, 1);
1651					return;
1652				}
1653
1654				totalWidth += charWidth;
1655			}
1656
1657			break;
1658		}
1659
1660		case B_TRUNCATE_END:
1661		{
1662			float totalWidth = 0;
1663			for (int32 i = 0; i < charCount; i++) {
1664				float charWidth = escapementArray[i] * fontSize;
1665				if (totalWidth + charWidth > width) {
1666					// we need to truncate
1667					while (totalWidth + ellipsisWidth > width) {
1668						// remove chars until there's enough space for the
1669						// ellipsis
1670						if (i-- == 0) {
1671							// we've reached the start of the string and still
1672							// no space, so return an empty string
1673							string.Truncate(0);
1674							return;
1675						}
1676
1677						totalWidth -= escapementArray[i] * fontSize;
1678					}
1679
1680					string.RemoveChars(i, charCount - i);
1681					string.AppendChars(B_UTF8_ELLIPSIS, 1);
1682					return;
1683				}
1684
1685				totalWidth += charWidth;
1686			}
1687
1688			break;
1689		}
1690
1691		case B_TRUNCATE_MIDDLE:
1692		case B_TRUNCATE_SMART:
1693		{
1694			float leftWidth = 0;
1695			float rightWidth = 0;
1696			int32 leftIndex = 0;
1697			int32 rightIndex = charCount - 1;
1698			bool left = true;
1699
1700			for (int32 i = 0; i < charCount; i++) {
1701				float charWidth
1702					= escapementArray[left ? leftIndex : rightIndex] * fontSize;
1703
1704				if (leftWidth + rightWidth + charWidth > width) {
1705					// we need to truncate
1706					while (leftWidth + rightWidth + ellipsisWidth > width) {
1707						// remove chars until there's enough space for the
1708						// ellipsis
1709						if (leftIndex == 0 && rightIndex == charCount - 1) {
1710							// we've reached both ends of the string and still
1711							// no space, so return an empty string
1712							string.Truncate(0);
1713							return;
1714						}
1715
1716						if (leftIndex > 0 && (rightIndex == charCount - 1
1717								|| leftWidth > rightWidth)) {
1718							// remove char on the left
1719							leftWidth -= escapementArray[--leftIndex]
1720								* fontSize;
1721						} else {
1722							// remove char on the right
1723							rightWidth -= escapementArray[++rightIndex]
1724								* fontSize;
1725						}
1726					}
1727
1728					string.RemoveChars(leftIndex, rightIndex + 1 - leftIndex);
1729					string.InsertChars(B_UTF8_ELLIPSIS, 1, leftIndex);
1730					return;
1731				}
1732
1733				if (left) {
1734					leftIndex++;
1735					leftWidth += charWidth;
1736				} else {
1737					rightIndex--;
1738					rightWidth += charWidth;
1739				}
1740
1741				left = rightWidth > leftWidth;
1742			}
1743
1744			break;
1745		}
1746	}
1747
1748	// we've run through without the need to truncate, leave the string as it is
1749}
1750