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