xref: /haiku/src/preferences/screen/ScreenWindow.cpp (revision 4f00613311d0bd6b70fa82ce19931c41f071ea4e)
1 /*
2  * Copyright 2001-2005, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Rafael Romo
7  *		Stefano Ceccherini (burton666@libero.it)
8  *		Andrew Bachmann
9  *		Thomas Kurschel
10  *		Axel Dörfler, axeld@pinc-software.de
11  */
12 
13 
14 #include <Alert.h>
15 #include <Application.h>
16 #include <Box.h>
17 #include <Button.h>
18 #include <InterfaceDefs.h>
19 #include <MenuItem.h>
20 #include <MenuField.h>
21 #include <Messenger.h>
22 #include <PopUpMenu.h>
23 #include <Screen.h>
24 #include <String.h>
25 #include <Window.h>
26 
27 #include <cstdio>
28 #include <cstdlib>
29 #include <cstring>
30 
31 #include "AlertWindow.h"
32 #include "Constants.h"
33 #include "RefreshWindow.h"
34 #include "MonitorView.h"
35 #include "ScreenSettings.h"
36 #include "ScreenWindow.h"
37 #include "Utility.h"
38 
39 /* Note, this headers defines a *private* interface to the Radeon accelerant.
40  * It's a solution that works with the current BeOS interface that Haiku
41  * adopted.
42  * However, it's not a nice and clean solution. Don't use this header in any
43  * application if you can avoid it. No other driver is using this, or should
44  * be using this.
45  * It will be replaced as soon as we introduce an updated accelerant interface
46  * which may even happen before R1 hits the streets.
47  */
48 
49 #include "multimon.h"	// the usual: DANGER WILL, ROBINSON!
50 
51 
52 #define USE_FIXED_REFRESH
53 	// define to use fixed standard refresh rates
54 	// undefine to get standard refresh rates from driver
55 
56 
57 // list of officially supported colour spaces
58 static const struct {
59 	color_space	space;
60 	int32		bits_per_pixel;
61 	const char*	label;
62 } kColorSpaces[] = {
63 	{ B_CMAP8, 8, "8 Bits/Pixel, 256 Colors" },
64 	{ B_RGB15, 15, "15 Bits/Pixel, 32768 Colors" },
65 	{ B_RGB16, 16, "16 Bits/Pixel, 65536 Colors" },
66 	{ B_RGB32, 32, "32 Bits/Pixel, 16 Million Colors" }
67 };
68 static const int32 kColorSpaceCount = sizeof(kColorSpaces) / sizeof(kColorSpaces[0]);
69 
70 // list of standard refresh rates
71 static const int32 kRefreshRates[] = {56, 60, 70, 72, 75};
72 static const int32 kRefreshRateCount = sizeof(kRefreshRates) / sizeof(kRefreshRates[0]);
73 
74 
75 // list of combine modes
76 static const struct {
77 	combine_mode	mode;
78 	const char		*name;
79 } kCombineModes[] = {
80 	{ kCombineDisable, "disable" },
81 	{ kCombineHorizontally, "horizontally" },
82 	{ kCombineVertically, "vertically" }
83 };
84 static const int32 kCombineModeCount = sizeof(kCombineModes) / sizeof(kCombineModes[0]);
85 
86 
87 static BString
88 tv_standard_to_string(uint32 mode)
89 {
90 	switch (mode) {
91 		case 0:		return "disabled";
92 		case 1:		return "NTSC";
93 		case 2:		return "NTSC Japan";
94 		case 3:		return "PAL BDGHI";
95 		case 4:		return "PAL M";
96 		case 5:		return "PAL N";
97 		case 6:		return "SECAM";
98 		case 101:	return "NTSC 443";
99 		case 102:	return "PAL 60";
100 		case 103:	return "PAL NC";
101 		default:
102 		{
103 			BString name;
104 			name << "??? (" << mode << ")";
105 
106 			return name;
107 		}
108 	}
109 }
110 
111 
112 static void
113 resolution_to_string(screen_mode& mode, BString &string)
114 {
115 	string << mode.width << " x " << mode.height;
116 }
117 
118 
119 static void
120 refresh_rate_to_string(float refresh, BString &string,
121 	bool appendUnit = true, bool alwaysWithFraction = false)
122 {
123 	snprintf(string.LockBuffer(32), 32, "%.*g", refresh >= 100.0 ? 4 : 3, refresh);
124 	string.UnlockBuffer();
125 
126 	if (appendUnit)
127 		string << " Hz";
128 }
129 
130 
131 static const char*
132 screen_errors(status_t status)
133 {
134 	switch (status) {
135 		case B_ENTRY_NOT_FOUND:
136 			return "Unknown Mode";
137 		// TODO: add more?
138 
139 		default:
140 			return strerror(status);
141 	}
142 }
143 
144 
145 //	#pragma mark -
146 
147 
148 ScreenWindow::ScreenWindow(ScreenSettings *Settings)
149 	: BWindow(Settings->WindowFrame(), "Screen", B_TITLED_WINDOW,
150 		B_NOT_RESIZABLE | B_NOT_ZOOMABLE, B_ALL_WORKSPACES),
151 	fScreenMode(this),
152 	fChangingAllWorkspaces(false)
153 {
154 	BScreen screen(this);
155 	BRect frame(Bounds());
156 
157 	fScreenMode.Get(fOriginal);
158 	fActive = fSelected = fOriginal;
159 
160 	BView *view = new BView(frame, "ScreenView", B_FOLLOW_ALL, B_WILL_DRAW);
161 	view->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
162 	AddChild(view);
163 
164 	fSettings = Settings;
165 
166 	// box on the left with workspace count and monitor view
167 
168 	BRect screenBoxRect(11.0, 18.0, 153.0, 155.0);
169 	BBox *screenBox = new BBox(screenBoxRect, "left box");
170 
171 	fMonitorView = new MonitorView(BRect(20.0, 16.0, 122.0, 93.0), "monitor",
172 		screen.Frame().Width() + 1, screen.Frame().Height() + 1);
173 	screenBox->AddChild(fMonitorView);
174 
175 	BPopUpMenu *popUpMenu = new BPopUpMenu("", true, true);
176 	BMenuField *menuField = new BMenuField(BRect(7.0, 107.0, 135.0, 127.0),
177 		"WorkspaceCountMenu", "Workspace count:", popUpMenu, true);
178 	screenBox->AddChild(menuField);
179 
180 	for (int32 count = 1; count <= 32; count++) {
181 		BString workspaceCount;
182 		workspaceCount << count;
183 
184 		BMessage *message = new BMessage(POP_WORKSPACE_CHANGED_MSG);
185 		message->AddInt32("workspace count", count);
186 
187 		popUpMenu->AddItem(new BMenuItem(workspaceCount.String(),
188 			message));
189 	}
190 
191 	BMenuItem *item = popUpMenu->ItemAt(count_workspaces() - 1);
192 	if (item != NULL)
193 		item->SetMarked(true);
194 
195 	menuField->SetDivider(91.0);
196 	view->AddChild(screenBox);
197 
198 	// box on the right with screen resolution, etc.
199 
200 	popUpMenu = new BPopUpMenu("Current Workspace", true, true);
201 	fAllWorkspacesItem = new BMenuItem("All Workspaces", new BMessage(WORKSPACE_CHECK_MSG));
202 	popUpMenu->AddItem(fAllWorkspacesItem);
203 	item = new BMenuItem("Current Workspace", new BMessage(WORKSPACE_CHECK_MSG));
204 	item->SetMarked(true);
205 	popUpMenu->AddItem(item);
206 
207 	BRect rect(0.0, 0.0, 132.0, 18.0);
208 	menuField = new BMenuField(rect, "WorkspaceMenu", NULL, popUpMenu, true);
209 
210 	rect.Set(164.0, 7.0, 345.0, 155.0);
211 	BBox* controlsBox = new BBox(rect);
212 	controlsBox->SetLabel(menuField);
213 
214 	rect.Set(88.0, 114.0, 200.0, 150.0);
215 	fApplyButton = new BButton(rect, "ApplyButton", "Apply",
216 		new BMessage(BUTTON_APPLY_MSG));
217 	fApplyButton->ResizeToPreferred();
218 	fApplyButton->SetEnabled(false);
219 
220 	controlsBox->AddChild(fApplyButton);
221 
222 	fResolutionMenu = new BPopUpMenu("resolution", true, true);
223 
224 	uint16 previousWidth = 0, previousHeight = 0;
225 	for (int32 i = 0; i < fScreenMode.CountModes(); i++) {
226 		screen_mode mode = fScreenMode.ModeAt(i);
227 
228 		if (mode.width == previousWidth && mode.height == previousHeight)
229 			continue;
230 
231 		previousWidth = mode.width;
232 		previousHeight = mode.height;
233 
234 		BMessage *message = new BMessage(POP_RESOLUTION_MSG);
235 		message->AddInt32("width", mode.width);
236 		message->AddInt32("height", mode.height);
237 
238 		BString name;
239 		name << mode.width << " x " << mode.height;
240 
241 		fResolutionMenu->AddItem(new BMenuItem(name.String(), message));
242 	}
243 
244 	rect.Set(33.0, 30.0, 171.0, 48.0);
245 	fResolutionField = new BMenuField(rect, "ResolutionMenu", "Resolution:",
246 		fResolutionMenu, true);
247 	fResolutionField->SetDivider(55.0);
248 	controlsBox->AddChild(fResolutionField);
249 
250 	fColorsMenu = new BPopUpMenu("colors", true, true);
251 
252 	for (int32 i = 0; i < kColorSpaceCount; i++) {
253 		BMessage *message = new BMessage(POP_COLORS_MSG);
254 		message->AddInt32("bits_per_pixel", kColorSpaces[i].bits_per_pixel);
255 		message->AddInt32("space", kColorSpaces[i].space);
256 
257 		fColorsMenu->AddItem(new BMenuItem(kColorSpaces[i].label, message));
258 	}
259 
260 	rect.Set(50.0, 58.0, 171.0, 76.0);
261 	fColorsField = new BMenuField(rect, "ColorsMenu", "Colors:", fColorsMenu, true);
262 	fColorsField->SetDivider(38.0);
263 	controlsBox->AddChild(fColorsField);
264 
265 	fRefreshMenu = new BPopUpMenu("refresh rate", true, true);
266 
267 #ifdef USE_FIXED_REFRESH
268 	for (int32 i = 0; i < kRefreshRateCount; ++i) {
269 		BString name;
270 		name << kRefreshRates[i] << " Hz";
271 
272 		BMessage *message = new BMessage(POP_REFRESH_MSG);
273 		message->AddFloat("refresh", kRefreshRates[i]);
274 
275 		fRefreshMenu->AddItem(new BMenuItem(name.String(), message));
276 	}
277 #endif
278 
279 	BMessage *message = new BMessage(POP_OTHER_REFRESH_MSG);
280 
281 	fOtherRefresh = new BMenuItem("Other" B_UTF8_ELLIPSIS, message);
282 	fRefreshMenu->AddItem(fOtherRefresh);
283 
284 	rect.Set(19.0, 86.0, 171.0, 104.0);
285 	fRefreshField = new BMenuField(rect, "RefreshMenu", "Refresh Rate:", fRefreshMenu, true);
286 	fRefreshField->SetDivider(69.0);
287 	controlsBox->AddChild(fRefreshField);
288 
289 	view->AddChild(controlsBox);
290 
291 	// enlarged area for multi-monitor settings
292 	{
293 		bool dummy;
294 		uint32 dummy32;
295 		bool multiMonSupport;
296 		bool useLaptopPanelSupport;
297 		bool tvStandardSupport;
298 
299 		multiMonSupport = TestMultiMonSupport(&screen) == B_OK;
300 		useLaptopPanelSupport = GetUseLaptopPanel(&screen, &dummy) == B_OK;
301 		tvStandardSupport = GetTVStandard(&screen, &dummy32) == B_OK;
302 
303 		// even if there is no support, we still create all controls
304 		// to make sure we don't access NULL pointers later on
305 		if (multiMonSupport) {
306 			fApplyButton->MoveTo(275, 114);
307 			controlsBox->ResizeTo(366, 148);
308 			ResizeTo(556, 202);
309 		}
310 
311 		fCombineMenu = new BPopUpMenu("CombineDisplays", true, true);
312 
313 		for (int32 i = 0; i < kCombineModeCount; i++) {
314 			message = new BMessage(POP_COMBINE_DISPLAYS_MSG);
315 			message->AddInt32("mode", kCombineModes[i].mode);
316 
317 			fCombineMenu->AddItem(new BMenuItem(kCombineModes[i].name, message));
318 		}
319 
320 		rect.Set(185, 30, 356, 48);
321 		BMenuField* menuField = new BMenuField(rect, "CombineMenu",
322 			"Combine Displays:", fCombineMenu, true);
323 		menuField->SetDivider(90);
324 		controlsBox->AddChild(menuField);
325 
326 		if (!multiMonSupport)
327 			menuField->Hide();
328 
329 		fSwapDisplaysMenu = new BPopUpMenu("SwapDisplays", true, true);
330 
331 		// !order is important - we rely that boolean value == idx
332 		message = new BMessage(POP_SWAP_DISPLAYS_MSG);
333 		message->AddBool("swap", false);
334 		fSwapDisplaysMenu->AddItem(new BMenuItem("no", message));
335 
336 		message = new BMessage(POP_SWAP_DISPLAYS_MSG);
337 		message->AddBool("swap", true);
338 		fSwapDisplaysMenu->AddItem(new BMenuItem("yes", message));
339 
340 		rect.Set(199, 58, 356, 76);
341 		menuField = new BMenuField(rect, "SwapMenu", "Swap Displays:",
342 			fSwapDisplaysMenu, true);
343 		menuField->SetDivider(76);
344 
345 		controlsBox->AddChild(menuField);
346 		if (!multiMonSupport)
347 			menuField->Hide();
348 
349 		fUseLaptopPanelMenu = new BPopUpMenu("UseLaptopPanel", true, true);
350 
351 		// !order is important - we rely that boolean value == idx
352 		message = new BMessage(POP_USE_LAPTOP_PANEL_MSG);
353 		message->AddBool("use", false);
354 		fUseLaptopPanelMenu->AddItem(new BMenuItem("if needed", message));
355 
356 		message = new BMessage(POP_USE_LAPTOP_PANEL_MSG);
357 		message->AddBool("use", true);
358 		fUseLaptopPanelMenu->AddItem(new BMenuItem("always", message));
359 
360 		rect.Set(184, 86, 356, 104);
361 		menuField = new BMenuField(rect, "UseLaptopPanel", "Use Laptop Panel:",
362 			fUseLaptopPanelMenu, true);
363 		menuField->SetDivider(91);
364 
365 		controlsBox->AddChild(menuField);
366 		if (!useLaptopPanelSupport)
367 			menuField->Hide();
368 
369 		fTVStandardMenu = new BPopUpMenu("TVStandard", true, true);
370 
371 		// arbitrary limit
372 		uint32 i;
373 		for (i = 0; i < 100; ++i) {
374 			uint32 mode;
375 
376 			if (GetNthSupportedTVStandard(&screen, i, &mode) != B_OK)
377 				break;
378 
379 			BString name = tv_standard_to_string(mode);
380 
381 			message = new BMessage(POP_TV_STANDARD_MSG);
382 			message->AddInt32("tv_standard", mode);
383 
384 			fTVStandardMenu->AddItem(new BMenuItem(name.String(), message));
385 		}
386 
387 		rect.Set(15, 114, 171, 132);
388 		menuField = new BMenuField(rect, "tv standard", "Video Format:",
389 			fTVStandardMenu, true);
390 		menuField->SetDivider(73);
391 
392 		if (!tvStandardSupport || i == 0)
393 			menuField->Hide();
394 
395 		controlsBox->AddChild(menuField);
396 	}
397 
398 	rect.Set(10.0, 167, 100.0, 200.0);
399 	fDefaultsButton = new BButton(rect, "DefaultsButton", "Defaults",
400 		new BMessage(BUTTON_DEFAULTS_MSG));
401 	fDefaultsButton->ResizeToPreferred();
402 	view->AddChild(fDefaultsButton);
403 
404 	rect.Set(95.0, 167, 160.0, 200.0);
405 	fRevertButton = new BButton(rect, "RevertButton", "Revert",
406 		new BMessage(BUTTON_REVERT_MSG));
407 	fRevertButton->ResizeToPreferred();
408 	fRevertButton->SetEnabled(false);
409 	view->AddChild(fRevertButton);
410 
411 	UpdateControls();
412 }
413 
414 
415 ScreenWindow::~ScreenWindow()
416 {
417 	delete fSettings;
418 }
419 
420 
421 bool
422 ScreenWindow::QuitRequested()
423 {
424 	fSettings->SetWindowFrame(Frame());
425 	be_app->PostMessage(B_QUIT_REQUESTED);
426 
427 	return BWindow::QuitRequested();
428 }
429 
430 
431 /**	update resolution list according to combine mode
432  *	(some resolution may not be combinable due to memory restrictions)
433  */
434 
435 void
436 ScreenWindow::CheckResolutionMenu()
437 {
438 	for (int32 i = 0; i < fResolutionMenu->CountItems(); i++)
439 		fResolutionMenu->ItemAt(i)->SetEnabled(false);
440 
441 	for (int32 i = 0; i < fScreenMode.CountModes(); i++) {
442 		screen_mode mode = fScreenMode.ModeAt(i);
443 		if (mode.combine != fSelected.combine)
444 			continue;
445 
446 		BString name;
447 		name << mode.width << " x " << mode.height;
448 
449 		BMenuItem *item = fResolutionMenu->FindItem(name.String());
450 		if (item != NULL)
451 			item->SetEnabled(true);
452 	}
453 }
454 
455 
456 /**	update color and refresh options according to current mode
457  *	(a color space is made active if there is any mode with
458  *	given resolution and this colour space; same applies for
459  *	refresh rate, though "Other…" is always possible)
460  */
461 
462 void
463 ScreenWindow::CheckColorMenu()
464 {
465 	for (int32 i = 0; i < kColorSpaceCount; i++) {
466 		bool supported = false;
467 
468 		for (int32 j = 0; j < fScreenMode.CountModes(); j++) {
469 			screen_mode mode = fScreenMode.ModeAt(j);
470 
471 			if (fSelected.width == mode.width
472 				&& fSelected.height == mode.height
473 				&& kColorSpaces[i].space == mode.space
474 				&& fSelected.combine == mode.combine) {
475 				supported = true;
476 				break;
477 			}
478 		}
479 
480 		BMenuItem* item = fColorsMenu->ItemAt(i);
481 		if (item)
482 			item->SetEnabled(supported);
483 	}
484 }
485 
486 
487 /**	Enable/disable refresh options according to current mode.
488  *	Only needed when USE_FIXED_REFRESH is not defined.
489  */
490 
491 void
492 ScreenWindow::CheckRefreshMenu()
493 {
494 #ifndef USE_FIXED_REFRESH
495 	// ToDo: does currently not compile!
496 	for (int32 i = fRefreshMenu->CountItems() - 2; i >= 0; --i) {
497 		delete fRefreshMenu->RemoveItem(i);
498 	}
499 
500 	for (int32 i = 0; i < fModeListCount; ++i) {
501 		if (virtualWidth == fModeList[i].virtual_width
502 			&& virtualHeight == fModeList[i].virtual_height
503 			&& combine == get_combine_mode(&fModeList[i])) {
504 			BString name;
505 			BMenuItem *item;
506 
507 			int32 refresh10 = get_refresh10(fModeList[i]);
508 			refresh10_to_string(name, refresh10);
509 
510 			item = fRefreshMenu->FindItem(name.String());
511 			if (item == NULL) {
512 				BMessage *msg = new BMessage(POP_REFRESH_MSG);
513 				msg->AddFloat("refresh", refresh);
514 
515 				fRefreshMenu->AddItem(new BMenuItem(name.String(), msg),
516 					fRefreshMenu->CountItems() - 1);
517 			}
518 		}
519 	}
520 #endif
521 
522 	// TBD: some drivers lack many refresh rates; still, they
523 	// can be used by generating the mode manually
524 /*
525 	for( i = 0; i < sizeof( refresh_list ) / sizeof( refresh_list[0] ); ++i ) {
526 		BMenuItem *item;
527 		bool supported = false;
528 
529 		for( j = 0; j < fModeListCount; ++j ) {
530 			if( width == fModeList[j].virtual_width &&
531 				height == fModeList[j].virtual_height &&
532 				refresh_list[i].refresh * 10 == getModeRefresh10( &fModeList[j] ))
533 			{
534 				supported = true;
535 				break;
536 			}
537 		}
538 
539 		item = fRefreshMenu->ItemAt( i );
540 		if( item )
541 			item->SetEnabled( supported );
542 	}
543 */
544 }
545 
546 
547 /** activate appropriate menu item according to selected refresh rate */
548 
549 void
550 ScreenWindow::UpdateRefreshControl()
551 {
552 	BString string;
553 	refresh_rate_to_string(fSelected.refresh, string);
554 
555 	BMenuItem* item = fRefreshMenu->FindItem(string.String());
556 	if (item) {
557 		if (!item->IsMarked())
558 			item->SetMarked(true);
559 		// "Other…" items only contains a refresh rate when active
560 		fOtherRefresh->SetLabel("Other…");
561 		return;
562 	}
563 
564 	// this is a non-standard refresh rate
565 
566 	fOtherRefresh->Message()->ReplaceFloat("refresh", fSelected.refresh);
567 	fOtherRefresh->SetMarked(true);
568 
569 	fRefreshMenu->Superitem()->SetLabel(string.String());
570 
571 	string.Append("/Other" B_UTF8_ELLIPSIS);
572 	fOtherRefresh->SetLabel(string.String());
573 }
574 
575 
576 void
577 ScreenWindow::UpdateMonitorView()
578 {
579 	BMessage updateMessage(UPDATE_DESKTOP_MSG);
580 	updateMessage.AddInt32("width", fSelected.width);
581 	updateMessage.AddInt32("height", fSelected.height);
582 
583 	PostMessage(&updateMessage, fMonitorView);
584 }
585 
586 
587 void
588 ScreenWindow::UpdateControls()
589 {
590 	BMenuItem* item = fSwapDisplaysMenu->ItemAt((int32)fSelected.swap_displays);
591 	if (item && !item->IsMarked())
592 		item->SetMarked(true);
593 
594 	item = fUseLaptopPanelMenu->ItemAt((int32)fSelected.use_laptop_panel);
595 	if (item && !item->IsMarked())
596 		item->SetMarked(true);
597 
598 	for (int32 i = 0; i < fTVStandardMenu->CountItems(); i++) {
599 		item = fTVStandardMenu->ItemAt(i);
600 
601 		uint32 tvStandard;
602 		item->Message()->FindInt32("tv_standard", (int32 *)&tvStandard);
603 		if (tvStandard == fSelected.tv_standard) {
604 			if (!item->IsMarked())
605 				item->SetMarked(true);
606 			break;
607 		}
608 	}
609 
610 	CheckResolutionMenu();
611 	CheckColorMenu();
612 	CheckRefreshMenu();
613 
614 	BString string;
615 	resolution_to_string(fSelected, string);
616 	item = fResolutionMenu->FindItem(string.String());
617 
618 	if (item != NULL) {
619 		if (!item->IsMarked())
620 			item->SetMarked(true);
621 	} else {
622 		// this is bad luck - if mode has been set via screen references,
623 		// this case cannot occur; there are three possible solutions:
624 		// 1. add a new resolution to list
625 		//    - we had to remove it as soon as a "valid" one is selected
626 		//    - we don't know which frequencies/bit depths are supported
627 		//    - as long as we haven't the GMT formula to create
628 		//      parameters for any resolution given, we cannot
629 		//      really set current mode - it's just not in the list
630 		// 2. choose nearest resolution
631 		//    - probably a good idea, but implies coding and testing
632 		// 3. choose lowest resolution
633 		//    - do you really think we are so lazy? yes, we are
634 		item = fResolutionMenu->ItemAt(0);
635 		if (item)
636 			item->SetMarked(true);
637 
638 		// okay - at least we set menu label to active resolution
639 		fResolutionMenu->Superitem()->SetLabel(string.String());
640 	}
641 
642 	// mark active combine mode
643 	for (int32 i = 0; i < kCombineModeCount; i++) {
644 		if (kCombineModes[i].mode == fSelected.combine) {
645 			item = fCombineMenu->ItemAt(i);
646 			if (item && !item->IsMarked())
647 				item->SetMarked(true);
648 			break;
649 		}
650 	}
651 
652 	item = fColorsMenu->ItemAt(0);
653 
654 	for (int32 i = kColorSpaceCount; i-- > 0;) {
655 		if (kColorSpaces[i].space == fSelected.space) {
656 			item = fColorsMenu->ItemAt(i);
657 			break;
658 		}
659 	}
660 
661 	if (item && !item->IsMarked())
662 		item->SetMarked(true);
663 
664 	string.Truncate(0);
665 	string << fSelected.BitsPerPixel() << " Bits/Pixel";
666 	if (string != fColorsMenu->Superitem()->Label())
667 		fColorsMenu->Superitem()->SetLabel(string.String());
668 
669 	UpdateMonitorView();
670 	UpdateRefreshControl();
671 
672 	CheckApplyEnabled();
673 }
674 
675 
676 /** reflect active mode in chosen settings */
677 
678 void
679 ScreenWindow::UpdateActiveMode()
680 {
681 	// usually, this function gets called after a mode
682 	// has been set manually; still, as the graphics driver
683 	// is free to fiddle with mode passed, we better ask
684 	// what kind of mode we actually got
685 	fScreenMode.Get(fActive);
686 	fSelected = fActive;
687 
688 	UpdateControls();
689 }
690 
691 
692 void
693 ScreenWindow::ScreenChanged(BRect frame, color_space mode)
694 {
695 	// move window on screen, if necessary
696 	if (frame.right <= Frame().right
697 		&& frame.bottom <= Frame().bottom) {
698 		MoveTo((frame.Width() - Frame().Width()) / 2,
699 			(frame.Height() - Frame().Height()) / 2);
700 	}
701 }
702 
703 
704 void
705 ScreenWindow::WorkspaceActivated(int32 workspace, bool state)
706 {
707 	if (fChangingAllWorkspaces) {
708 		// we're currently changing all workspaces, so there is no need
709 		// to update the interface
710 		return;
711 	}
712 
713 	fScreenMode.Get(fOriginal);
714 	fScreenMode.UpdateOriginalMode();
715 
716 	// only override current settings if they have not been changed yet
717 	if (fSelected == fActive)
718 		UpdateActiveMode();
719 
720 	PostMessage(new BMessage(UPDATE_DESKTOP_COLOR_MSG), fMonitorView);
721 }
722 
723 
724 void
725 ScreenWindow::MessageReceived(BMessage* message)
726 {
727 	switch (message->what) {
728 		case WORKSPACE_CHECK_MSG:
729 			CheckApplyEnabled();
730 			break;
731 
732 		case POP_WORKSPACE_CHANGED_MSG:
733 		{
734 			int32 index;
735 			if (message->FindInt32("index", &index) == B_OK)
736 				set_workspace_count(index + 1);
737 			break;
738 		}
739 
740 		case POP_RESOLUTION_MSG:
741 		{
742 			message->FindInt32("width", &fSelected.width);
743 			message->FindInt32("height", &fSelected.height);
744 
745 			CheckColorMenu();
746 			CheckRefreshMenu();
747 
748 			UpdateMonitorView();
749 			UpdateRefreshControl();
750 
751 			CheckApplyEnabled();
752 			break;
753 		}
754 
755 		case POP_COLORS_MSG:
756 		{
757 			message->FindInt32("space", (int32 *)&fSelected.space);
758 
759 			BString string;
760 			string << fSelected.BitsPerPixel() << " Bits/Pixel";
761 			fColorsMenu->Superitem()->SetLabel(string.String());
762 
763 			CheckApplyEnabled();
764 			break;
765 		}
766 
767 		case POP_REFRESH_MSG:
768 			message->FindFloat("refresh", &fSelected.refresh);
769 			fOtherRefresh->SetLabel("Other" B_UTF8_ELLIPSIS);
770 				// revert "Other…" label - it might have had a refresh rate prefix
771 
772 			CheckApplyEnabled();
773 			break;
774 
775 		case POP_OTHER_REFRESH_MSG:
776 		{
777 			// make sure menu shows something usefull
778 			UpdateRefreshControl();
779 
780 			BRect frame(Frame());
781 			RefreshWindow *fRefreshWindow = new RefreshWindow(BRect(frame.left + 201.0,
782 				frame.top + 34.0, frame.left + 509.0, frame.top + 169.0),
783 				int32(fSelected.refresh * 10));
784 			fRefreshWindow->Show();
785 			break;
786 		}
787 
788 		case SET_CUSTOM_REFRESH_MSG:
789 		{
790 			// user pressed "done" in "Other…" refresh dialog;
791 			// select the refresh rate chosen
792 			message->FindFloat("refresh", &fSelected.refresh);
793 
794 			UpdateRefreshControl();
795 			CheckApplyEnabled();
796 			break;
797 		}
798 
799 		case POP_COMBINE_DISPLAYS_MSG:
800 		{
801 			// new combine mode has bee chosen
802 			int32 mode;
803 			if (message->FindInt32("mode", &mode) == B_OK)
804 				fSelected.combine = (combine_mode)mode;
805 
806 			CheckResolutionMenu();
807 			CheckApplyEnabled();
808 			break;
809 		}
810 
811 		case POP_SWAP_DISPLAYS_MSG:
812 			message->FindBool("swap", &fSelected.swap_displays);
813 			CheckApplyEnabled();
814 			break;
815 
816 		case POP_USE_LAPTOP_PANEL_MSG:
817 			message->FindBool("use", &fSelected.use_laptop_panel);
818 			CheckApplyEnabled();
819 			break;
820 
821 		case POP_TV_STANDARD_MSG:
822 			message->FindInt32("tv_standard", (int32 *)&fSelected.tv_standard);
823 			CheckApplyEnabled();
824 			break;
825 
826 		case BUTTON_DEFAULTS_MSG:
827 		{
828 			fSelected.width = 640;
829 			fSelected.height = 480;
830 			fSelected.space = B_CMAP8;
831 			fSelected.refresh = 60.0;
832 			fSelected.combine = kCombineDisable;
833 			fSelected.swap_displays = false;
834 			fSelected.use_laptop_panel = false;
835 			fSelected.tv_standard = 0;
836 
837 			UpdateControls();
838 			break;
839 		}
840 
841 		case BUTTON_REVERT_MSG:
842 		case SET_INITIAL_MODE_MSG:
843 			fScreenMode.Revert();
844 			UpdateActiveMode();
845 			break;
846 
847 		case BUTTON_APPLY_MSG:
848 			Apply();
849 			break;
850 
851 		case MAKE_INITIAL_MSG: {
852 			// user pressed "keep" in confirmation box;
853 			// select this mode in dialog and mark it as
854 			// previous mode; if "all workspaces" is selected,
855 			// distribute mode to all workspaces
856 
857 			// use the mode that has eventually been set and
858 			// thus we know to be working; it can differ from
859 			// the mode selected by user due to hardware limitation
860 			display_mode newMode;
861 			BScreen screen(this);
862 			screen.GetMode(&newMode);
863 
864 			if (fAllWorkspacesItem->IsMarked()) {
865 				int32 originatingWorkspace;
866 
867 				// the original panel activates each workspace in turn;
868 				// this is disguisting and there is a SetMode
869 				// variant that accepts a workspace id, so let's take
870 				// this one
871 				originatingWorkspace = current_workspace();
872 
873 				// well, this "cannot be reverted" message is not
874 				// entirely true - at least, you can revert it
875 				// for current workspace; to not overwrite original
876 				// mode during workspace switch, we use this flag
877 				fChangingAllWorkspaces = true;
878 
879 				for (int32 i = 0; i < count_workspaces(); i++) {
880 					if (i != originatingWorkspace)
881 						screen.SetMode(i, &newMode, true);
882 				}
883 
884 				fChangingAllWorkspaces = false;
885 			}
886 
887 			UpdateActiveMode();
888 			break;
889 		}
890 
891 		default:
892 			BWindow::MessageReceived(message);
893 			break;
894 	}
895 }
896 
897 
898 bool
899 ScreenWindow::CanApply() const
900 {
901 	if (fAllWorkspacesItem->IsMarked())
902 		return true;
903 
904 	return fSelected != fActive;
905 }
906 
907 
908 bool
909 ScreenWindow::CanRevert() const
910 {
911 	if (fActive != fOriginal)
912 		return true;
913 
914 	return CanApply();
915 }
916 
917 
918 void
919 ScreenWindow::CheckApplyEnabled()
920 {
921 	fApplyButton->SetEnabled(CanApply());
922 	fRevertButton->SetEnabled(CanRevert());
923 }
924 
925 
926 void
927 ScreenWindow::Apply()
928 {
929 	if (fAllWorkspacesItem->IsMarked()) {
930 		BAlert *workspacesAlert = new BAlert("WorkspacesAlert",
931 			"Change all workspaces? This action cannot be reverted.", "Okay", "Cancel",
932 			NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
933 
934 		if (workspacesAlert->Go() == 1)
935 			return;
936 	}
937 
938 	status_t status = fScreenMode.Set(fSelected);
939 	if (status == B_OK) {
940 		fActive = fSelected;
941 
942 		// ToDo: only show alert when this is an unknown mode
943 		BWindow* window = new AlertWindow(this);
944 		window->Show();
945 	} else {
946 		char message[256];
947 		snprintf(message, sizeof(message),
948 			"The screen mode could not be set:\n\t%s\n", screen_errors(status));
949 		BAlert* alert = new BAlert("Screen Alert", message, "Okay", NULL, NULL,
950 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
951 		alert->Go();
952 	}
953 }
954 
955