xref: /haiku/src/preferences/screen/ScreenWindow.cpp (revision ba499cdc3336fb89429027418871bf263f1f5e14)
1 /*
2  * Copyright 2001-2006, 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  *		Stephan Aßmus <superstippi@gmx.de>
12  */
13 
14 
15 #include <Alert.h>
16 #include <Application.h>
17 #include <Box.h>
18 #include <Button.h>
19 #include <InterfaceDefs.h>
20 #include <MenuBar.h>
21 #include <MenuItem.h>
22 #include <MenuField.h>
23 #include <Messenger.h>
24 #include <PopUpMenu.h>
25 #include <Screen.h>
26 #include <String.h>
27 #include <Roster.h>
28 #include <Window.h>
29 
30 #include <cstdio>
31 #include <cstdlib>
32 #include <cstring>
33 
34 #include "AlertWindow.h"
35 #include "Constants.h"
36 #include "RefreshWindow.h"
37 #include "MonitorView.h"
38 #include "ScreenSettings.h"
39 #include "ScreenWindow.h"
40 #include "Utility.h"
41 
42 /* Note, this headers defines a *private* interface to the Radeon accelerant.
43  * It's a solution that works with the current BeOS interface that Haiku
44  * adopted.
45  * However, it's not a nice and clean solution. Don't use this header in any
46  * application if you can avoid it. No other driver is using this, or should
47  * be using this.
48  * It will be replaced as soon as we introduce an updated accelerant interface
49  * which may even happen before R1 hits the streets.
50  */
51 
52 #include "multimon.h"	// the usual: DANGER WILL, ROBINSON!
53 
54 
55 const char* kBackgroundsSignature = "application/x-vnd.haiku-backgrounds";
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[] = { 60, 70, 72, 75, 80, 85, 95, 100 };
72 static const int32 kRefreshRateCount = sizeof(kRefreshRates) / sizeof(kRefreshRates[0]);
73 
74 // list of combine modes
75 static const struct {
76 	combine_mode	mode;
77 	const char		*name;
78 } kCombineModes[] = {
79 	{ kCombineDisable, "disable" },
80 	{ kCombineHorizontally, "horizontally" },
81 	{ kCombineVertically, "vertically" }
82 };
83 static const int32 kCombineModeCount = sizeof(kCombineModes) / sizeof(kCombineModes[0]);
84 
85 enum {
86 	SHOW_COMBINE_FIELD		= 0x01,
87 	SHOW_SWAP_FIELD			= 0x02,
88 	SHOW_LAPTOP_PANEL_FIELD	= 0x04,
89 	SHOW_TV_STANDARD_FIELD	= 0x08,
90 };
91 
92 
93 static BString
94 tv_standard_to_string(uint32 mode)
95 {
96 	switch (mode) {
97 		case 0:		return "disabled";
98 		case 1:		return "NTSC";
99 		case 2:		return "NTSC Japan";
100 		case 3:		return "PAL BDGHI";
101 		case 4:		return "PAL M";
102 		case 5:		return "PAL N";
103 		case 6:		return "SECAM";
104 		case 101:	return "NTSC 443";
105 		case 102:	return "PAL 60";
106 		case 103:	return "PAL NC";
107 		default:
108 		{
109 			BString name;
110 			name << "??? (" << mode << ")";
111 
112 			return name;
113 		}
114 	}
115 }
116 
117 
118 static void
119 resolution_to_string(screen_mode& mode, BString &string)
120 {
121 	string << mode.width << " x " << mode.height;
122 }
123 
124 
125 static void
126 refresh_rate_to_string(float refresh, BString &string,
127 	bool appendUnit = true, bool alwaysWithFraction = false)
128 {
129 	snprintf(string.LockBuffer(32), 32, "%.*g", refresh >= 100.0 ? 4 : 3, refresh);
130 	string.UnlockBuffer();
131 
132 	if (appendUnit)
133 		string << " Hz";
134 }
135 
136 
137 static const char*
138 screen_errors(status_t status)
139 {
140 	switch (status) {
141 		case B_ENTRY_NOT_FOUND:
142 			return "Unknown Mode";
143 		// TODO: add more?
144 
145 		default:
146 			return strerror(status);
147 	}
148 }
149 
150 
151 static float
152 max_label_width(BMenuField* control, float widestLabel)
153 {
154 	float labelWidth = control->StringWidth(control->Label());
155 	if (widestLabel < labelWidth)
156 		return labelWidth;
157 	return widestLabel;
158 }
159 
160 
161 static BRect
162 stack_and_align_menu_fields(const BList& menuFields)
163 {
164 	float widestLabel = 0.0;
165 	int32 count = menuFields.CountItems();
166 	for (int32 i = 0; i < count; i++) {
167 		BMenuField* menuField = (BMenuField*)menuFields.ItemAtFast(i);
168 		widestLabel = max_label_width(menuField, widestLabel);
169 	}
170 
171 	// add some room (but only if there is text at all)
172 	if (widestLabel > 0.0)
173 		widestLabel += 5.0;
174 
175 	// make all controls the same width
176 	float widestField = 0.0;
177 	for (int32 i = 0; i < count; i++) {
178 		BMenuField* menuField = (BMenuField*)menuFields.ItemAtFast(i);
179 		menuField->SetAlignment(B_ALIGN_RIGHT);
180 		menuField->SetDivider(widestLabel);
181 		menuField->ResizeToPreferred();
182 		widestField = max_c(menuField->Bounds().Width(), widestField);
183 	}
184 
185 	// layout controls under each other, resize all to size
186 	// of largest of them (they could still have different
187 	// heights though)
188 	BMenuField* topMenuField = (BMenuField*)menuFields.FirstItem();
189 	BPoint leftTop = topMenuField->Frame().LeftTop();
190 	BRect frame = topMenuField->Frame();
191 
192 	for (int32 i = 0; i < count; i++) {
193 		BMenuField* menuField = (BMenuField*)menuFields.ItemAtFast(i);
194 		menuField->MoveTo(leftTop);
195 		float height = menuField->Bounds().Height();
196 		menuField->ResizeTo(widestField, height);
197 		frame = frame | menuField->Frame();
198 		leftTop.y += height + 5.0;
199 	}
200 
201 	return frame;
202 }
203 
204 
205 //	#pragma mark -
206 
207 
208 ScreenWindow::ScreenWindow(ScreenSettings *settings)
209 	: BWindow(settings->WindowFrame(), "Screen", B_TITLED_WINDOW,
210 		B_NOT_RESIZABLE | B_NOT_ZOOMABLE, B_ALL_WORKSPACES),
211 	fScreenMode(this),
212 	fChangingAllWorkspaces(false)
213 {
214 	BScreen screen(this);
215 
216 	fScreenMode.Get(fOriginal);
217 	fActive = fSelected = fOriginal;
218 
219 	BView *view = new BView(Bounds(), "ScreenView", B_FOLLOW_ALL, B_WILL_DRAW);
220 	view->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
221 	AddChild(view);
222 
223 	fSettings = settings;
224 
225 	// we need the "Current Workspace" first to get its height
226 
227 	BPopUpMenu *popUpMenu = new BPopUpMenu("Current Workspace", true, true);
228 	fAllWorkspacesItem = new BMenuItem("All Workspaces", new BMessage(WORKSPACE_CHECK_MSG));
229 	popUpMenu->AddItem(fAllWorkspacesItem);
230 	BMenuItem *item = new BMenuItem("Current Workspace", new BMessage(WORKSPACE_CHECK_MSG));
231 	item->SetMarked(true);
232 	popUpMenu->AddItem(item);
233 
234 	BMenuField* workspaceMenuField = new BMenuField(BRect(0, 0, 100, 15),
235 		"WorkspaceMenu", NULL, popUpMenu, true);
236 	workspaceMenuField->ResizeToPreferred();
237 
238 	// box on the left with workspace count and monitor view
239 
240 	popUpMenu = new BPopUpMenu("", true, true);
241 	fWorkspaceCountField = new BMenuField(BRect(0.0, 0.0, 50.0, 15.0),
242 		"WorkspaceCountMenu", "Workspace count:", popUpMenu, true);
243 	float labelWidth = fWorkspaceCountField->StringWidth(fWorkspaceCountField->Label()) + 5.0;
244 	fWorkspaceCountField->SetDivider(labelWidth);
245 
246 	fScreenBox = new BBox(BRect(0.0, 0.0, 100.0, 100.0), "left box");
247 	fScreenBox->AddChild(fWorkspaceCountField);
248 
249 	for (int32 count = 1; count <= 32; count++) {
250 		BString workspaceCount;
251 		workspaceCount << count;
252 
253 		BMessage *message = new BMessage(POP_WORKSPACE_CHANGED_MSG);
254 		message->AddInt32("workspace count", count);
255 
256 		popUpMenu->AddItem(new BMenuItem(workspaceCount.String(),
257 			message));
258 	}
259 
260 	item = popUpMenu->ItemAt(count_workspaces() - 1);
261 	if (item != NULL)
262 		item->SetMarked(true);
263 
264 	fMonitorView = new MonitorView(BRect(0.0, 0.0, 80.0, 80.0), "monitor",
265 		screen.Frame().Width() + 1, screen.Frame().Height() + 1);
266 	fScreenBox->AddChild(fMonitorView);
267 
268 	view->AddChild(fScreenBox);
269 
270 	// box on the right with screen resolution, etc.
271 
272 	fControlsBox = new BBox(BRect(0.0, 0.0, 100.0, 100.0));
273 	fControlsBox->SetLabel(workspaceMenuField);
274 
275 	fResolutionMenu = new BPopUpMenu("resolution", true, true);
276 
277 	uint16 previousWidth = 0, previousHeight = 0;
278 	for (int32 i = 0; i < fScreenMode.CountModes(); i++) {
279 		screen_mode mode = fScreenMode.ModeAt(i);
280 
281 		if (mode.width == previousWidth && mode.height == previousHeight)
282 			continue;
283 
284 		previousWidth = mode.width;
285 		previousHeight = mode.height;
286 
287 		BMessage *message = new BMessage(POP_RESOLUTION_MSG);
288 		message->AddInt32("width", mode.width);
289 		message->AddInt32("height", mode.height);
290 
291 		BString name;
292 		name << mode.width << " x " << mode.height;
293 
294 		fResolutionMenu->AddItem(new BMenuItem(name.String(), message));
295 	}
296 
297 	BRect rect(0.0, 0.0, 200.0, 15.0);
298 	// fResolutionField needs to be at the correct
299 	// left-top offset, because all other menu fields
300 	// will be layouted relative to it
301 	fResolutionField = new BMenuField(rect.OffsetToCopy(10.0, 30.0),
302 		"ResolutionMenu", "Resolution:", fResolutionMenu, true);
303 	fControlsBox->AddChild(fResolutionField);
304 
305 	fColorsMenu = new BPopUpMenu("colors", true, true);
306 
307 	for (int32 i = 0; i < kColorSpaceCount; i++) {
308 		BMessage *message = new BMessage(POP_COLORS_MSG);
309 		message->AddInt32("bits_per_pixel", kColorSpaces[i].bits_per_pixel);
310 		message->AddInt32("space", kColorSpaces[i].space);
311 
312 		fColorsMenu->AddItem(new BMenuItem(kColorSpaces[i].label, message));
313 	}
314 
315 	rect.OffsetTo(B_ORIGIN);
316 
317 	fColorsField = new BMenuField(rect, "ColorsMenu", "Colors:", fColorsMenu, true);
318 	fControlsBox->AddChild(fColorsField);
319 
320 	fRefreshMenu = new BPopUpMenu("refresh rate", true, true);
321 
322 	BMessage *message;
323 
324 	float min, max;
325 	if (fScreenMode.GetRefreshLimits(fActive, min, max) && min == max) {
326 		// This is a special case for drivers that only support a single
327 		// frequency, like the VESA driver
328 		BString name;
329 		name << min << " Hz";
330 
331 		message = new BMessage(POP_REFRESH_MSG);
332 		message->AddFloat("refresh", min);
333 
334 		fRefreshMenu->AddItem(item = new BMenuItem(name.String(), message));
335 		item->SetEnabled(false);
336 	} else {
337 		for (int32 i = 0; i < kRefreshRateCount; ++i) {
338 			BString name;
339 			name << kRefreshRates[i] << " Hz";
340 
341 			message = new BMessage(POP_REFRESH_MSG);
342 			message->AddFloat("refresh", kRefreshRates[i]);
343 
344 			fRefreshMenu->AddItem(new BMenuItem(name.String(), message));
345 		}
346 
347 		message = new BMessage(POP_OTHER_REFRESH_MSG);
348 
349 		fOtherRefresh = new BMenuItem("Other" B_UTF8_ELLIPSIS, message);
350 		fRefreshMenu->AddItem(fOtherRefresh);
351 	}
352 
353 	fRefreshField = new BMenuField(rect, "RefreshMenu", "Refresh Rate:", fRefreshMenu, true);
354 	fControlsBox->AddChild(fRefreshField);
355 
356 	view->AddChild(fControlsBox);
357 
358 	uint32 controlsFlags = 0;
359 
360 	// enlarged area for multi-monitor settings
361 	{
362 		bool dummy;
363 		uint32 dummy32;
364 		bool multiMonSupport;
365 		bool useLaptopPanelSupport;
366 		bool tvStandardSupport;
367 
368 		multiMonSupport = TestMultiMonSupport(&screen) == B_OK;
369 		useLaptopPanelSupport = GetUseLaptopPanel(&screen, &dummy) == B_OK;
370 		tvStandardSupport = GetTVStandard(&screen, &dummy32) == B_OK;
371 		if (multiMonSupport) {
372 			controlsFlags |= SHOW_COMBINE_FIELD;
373 			controlsFlags |= SHOW_SWAP_FIELD;
374 		}
375 		if (useLaptopPanelSupport)
376 			controlsFlags |= SHOW_LAPTOP_PANEL_FIELD;
377 
378 		// even if there is no support, we still create all controls
379 		// to make sure we don't access NULL pointers later on
380 
381 		fCombineMenu = new BPopUpMenu("CombineDisplays", true, true);
382 
383 		for (int32 i = 0; i < kCombineModeCount; i++) {
384 			message = new BMessage(POP_COMBINE_DISPLAYS_MSG);
385 			message->AddInt32("mode", kCombineModes[i].mode);
386 
387 			fCombineMenu->AddItem(new BMenuItem(kCombineModes[i].name, message));
388 		}
389 
390 		fCombineField = new BMenuField(rect, "CombineMenu",
391 			"Combine Displays:", fCombineMenu, true);
392 		fControlsBox->AddChild(fCombineField);
393 
394 		if (!multiMonSupport)
395 			fCombineField->Hide();
396 
397 		fSwapDisplaysMenu = new BPopUpMenu("SwapDisplays", true, true);
398 
399 		// !order is important - we rely that boolean value == idx
400 		message = new BMessage(POP_SWAP_DISPLAYS_MSG);
401 		message->AddBool("swap", false);
402 		fSwapDisplaysMenu->AddItem(new BMenuItem("no", message));
403 
404 		message = new BMessage(POP_SWAP_DISPLAYS_MSG);
405 		message->AddBool("swap", true);
406 		fSwapDisplaysMenu->AddItem(new BMenuItem("yes", message));
407 
408 		fSwapDisplaysField = new BMenuField(rect, "SwapMenu", "Swap Displays:",
409 			fSwapDisplaysMenu, true);
410 
411 		fControlsBox->AddChild(fSwapDisplaysField);
412 		if (!multiMonSupport)
413 			fSwapDisplaysField->Hide();
414 
415 		fUseLaptopPanelMenu = new BPopUpMenu("UseLaptopPanel", true, true);
416 
417 		// !order is important - we rely that boolean value == idx
418 		message = new BMessage(POP_USE_LAPTOP_PANEL_MSG);
419 		message->AddBool("use", false);
420 		fUseLaptopPanelMenu->AddItem(new BMenuItem("if needed", message));
421 
422 		message = new BMessage(POP_USE_LAPTOP_PANEL_MSG);
423 		message->AddBool("use", true);
424 		fUseLaptopPanelMenu->AddItem(new BMenuItem("always", message));
425 
426 		fUseLaptopPanelField = new BMenuField(rect, "UseLaptopPanel", "Use Laptop Panel:",
427 			fUseLaptopPanelMenu, true);
428 
429 		fControlsBox->AddChild(fUseLaptopPanelField);
430 		if (!useLaptopPanelSupport)
431 			fUseLaptopPanelField->Hide();
432 
433 		fTVStandardMenu = new BPopUpMenu("TVStandard", true, true);
434 
435 		// arbitrary limit
436 		uint32 i;
437 		for (i = 0; i < 100; ++i) {
438 			uint32 mode;
439 
440 			if (GetNthSupportedTVStandard(&screen, i, &mode) != B_OK)
441 				break;
442 
443 			BString name = tv_standard_to_string(mode);
444 
445 			message = new BMessage(POP_TV_STANDARD_MSG);
446 			message->AddInt32("tv_standard", mode);
447 
448 			fTVStandardMenu->AddItem(new BMenuItem(name.String(), message));
449 		}
450 
451 		fTVStandardField = new BMenuField(rect, "tv standard", "Video Format:",
452 			fTVStandardMenu, true);
453 		fTVStandardField->SetAlignment(B_ALIGN_RIGHT);
454 
455 		if (!tvStandardSupport || i == 0) {
456 			fTVStandardField->Hide();
457 		} else {
458 			controlsFlags |= SHOW_TV_STANDARD_FIELD;
459 		}
460 
461 		fControlsBox->AddChild(fTVStandardField);
462 	}
463 
464 	BRect buttonRect(0.0, 0.0, 30.0, 10.0);
465 	fBackgroundsButton = new BButton(buttonRect, "BackgroundsButton",
466 		"Set Background"B_UTF8_ELLIPSIS, new BMessage(BUTTON_LAUNCH_BACKGROUNDS_MSG));
467 	fBackgroundsButton->SetFontSize(be_plain_font->Size() * 0.9);
468 	fScreenBox->AddChild(fBackgroundsButton);
469 
470 	fDefaultsButton = new BButton(buttonRect, "DefaultsButton", "Defaults",
471 		new BMessage(BUTTON_DEFAULTS_MSG));
472 	view->AddChild(fDefaultsButton);
473 
474 	fRevertButton = new BButton(buttonRect, "RevertButton", "Revert",
475 		new BMessage(BUTTON_REVERT_MSG));
476 	fRevertButton->SetEnabled(false);
477 	view->AddChild(fRevertButton);
478 
479 	fApplyButton = new BButton(buttonRect, "ApplyButton", "Apply",
480 		new BMessage(BUTTON_APPLY_MSG));
481 	fApplyButton->SetEnabled(false);
482 	view->AddChild(fApplyButton);
483 
484 	UpdateControls();
485 
486 	LayoutControls(controlsFlags);
487 }
488 
489 
490 ScreenWindow::~ScreenWindow()
491 {
492 	delete fSettings;
493 }
494 
495 
496 bool
497 ScreenWindow::QuitRequested()
498 {
499 	fSettings->SetWindowFrame(Frame());
500 	be_app->PostMessage(B_QUIT_REQUESTED);
501 
502 	return BWindow::QuitRequested();
503 }
504 
505 
506 /**	update resolution list according to combine mode
507  *	(some resolution may not be combinable due to memory restrictions)
508  */
509 
510 void
511 ScreenWindow::CheckResolutionMenu()
512 {
513 	for (int32 i = 0; i < fResolutionMenu->CountItems(); i++)
514 		fResolutionMenu->ItemAt(i)->SetEnabled(false);
515 
516 	for (int32 i = 0; i < fScreenMode.CountModes(); i++) {
517 		screen_mode mode = fScreenMode.ModeAt(i);
518 		if (mode.combine != fSelected.combine)
519 			continue;
520 
521 		BString name;
522 		name << mode.width << " x " << mode.height;
523 
524 		BMenuItem *item = fResolutionMenu->FindItem(name.String());
525 		if (item != NULL)
526 			item->SetEnabled(true);
527 	}
528 }
529 
530 
531 /**	update color and refresh options according to current mode
532  *	(a color space is made active if there is any mode with
533  *	given resolution and this colour space; same applies for
534  *	refresh rate, though "Other…" is always possible)
535  */
536 
537 void
538 ScreenWindow::CheckColorMenu()
539 {
540 	for (int32 i = 0; i < kColorSpaceCount; i++) {
541 		bool supported = false;
542 
543 		for (int32 j = 0; j < fScreenMode.CountModes(); j++) {
544 			screen_mode mode = fScreenMode.ModeAt(j);
545 
546 			if (fSelected.width == mode.width
547 				&& fSelected.height == mode.height
548 				&& kColorSpaces[i].space == mode.space
549 				&& fSelected.combine == mode.combine) {
550 				supported = true;
551 				break;
552 			}
553 		}
554 
555 		BMenuItem* item = fColorsMenu->ItemAt(i);
556 		if (item)
557 			item->SetEnabled(supported);
558 	}
559 }
560 
561 
562 /**	Enable/disable refresh options according to current mode. */
563 
564 void
565 ScreenWindow::CheckRefreshMenu()
566 {
567 	float min, max;
568 	if (fScreenMode.GetRefreshLimits(fSelected, min, max) != B_OK || min == max)
569 		return;
570 
571 	for (int32 i = fRefreshMenu->CountItems(); i-- > 0;) {
572 		BMenuItem* item = fRefreshMenu->ItemAt(i);
573 		BMessage* message = item->Message();
574 		float refresh;
575 		if (message != NULL && message->FindFloat("refresh", &refresh) == B_OK)
576 			item->SetEnabled(refresh >= min && refresh <= max);
577 	}
578 }
579 
580 
581 /** Activate appropriate menu item according to selected refresh rate */
582 
583 void
584 ScreenWindow::UpdateRefreshControl()
585 {
586 	BString string;
587 	refresh_rate_to_string(fSelected.refresh, string);
588 
589 	BMenuItem* item = fRefreshMenu->FindItem(string.String());
590 	if (item) {
591 		if (!item->IsMarked())
592 			item->SetMarked(true);
593 
594 		// "Other…" items only contains a refresh rate when active
595 		fOtherRefresh->SetLabel("Other…");
596 		return;
597 	}
598 
599 	// this is a non-standard refresh rate
600 
601 	fOtherRefresh->Message()->ReplaceFloat("refresh", fSelected.refresh);
602 	fOtherRefresh->SetMarked(true);
603 
604 	fRefreshMenu->Superitem()->SetLabel(string.String());
605 
606 	string.Append("/Other" B_UTF8_ELLIPSIS);
607 	fOtherRefresh->SetLabel(string.String());
608 }
609 
610 
611 void
612 ScreenWindow::UpdateMonitorView()
613 {
614 	BMessage updateMessage(UPDATE_DESKTOP_MSG);
615 	updateMessage.AddInt32("width", fSelected.width);
616 	updateMessage.AddInt32("height", fSelected.height);
617 
618 	PostMessage(&updateMessage, fMonitorView);
619 }
620 
621 
622 void
623 ScreenWindow::UpdateControls()
624 {
625 	BMenuItem* item = fSwapDisplaysMenu->ItemAt((int32)fSelected.swap_displays);
626 	if (item && !item->IsMarked())
627 		item->SetMarked(true);
628 
629 	item = fUseLaptopPanelMenu->ItemAt((int32)fSelected.use_laptop_panel);
630 	if (item && !item->IsMarked())
631 		item->SetMarked(true);
632 
633 	for (int32 i = 0; i < fTVStandardMenu->CountItems(); i++) {
634 		item = fTVStandardMenu->ItemAt(i);
635 
636 		uint32 tvStandard;
637 		item->Message()->FindInt32("tv_standard", (int32 *)&tvStandard);
638 		if (tvStandard == fSelected.tv_standard) {
639 			if (!item->IsMarked())
640 				item->SetMarked(true);
641 			break;
642 		}
643 	}
644 
645 	CheckResolutionMenu();
646 	CheckColorMenu();
647 	CheckRefreshMenu();
648 
649 	BString string;
650 	resolution_to_string(fSelected, string);
651 	item = fResolutionMenu->FindItem(string.String());
652 
653 	if (item != NULL) {
654 		if (!item->IsMarked())
655 			item->SetMarked(true);
656 	} else {
657 		// this is bad luck - if mode has been set via screen references,
658 		// this case cannot occur; there are three possible solutions:
659 		// 1. add a new resolution to list
660 		//    - we had to remove it as soon as a "valid" one is selected
661 		//    - we don't know which frequencies/bit depths are supported
662 		//    - as long as we haven't the GMT formula to create
663 		//      parameters for any resolution given, we cannot
664 		//      really set current mode - it's just not in the list
665 		// 2. choose nearest resolution
666 		//    - probably a good idea, but implies coding and testing
667 		// 3. choose lowest resolution
668 		//    - do you really think we are so lazy? yes, we are
669 		item = fResolutionMenu->ItemAt(0);
670 		if (item)
671 			item->SetMarked(true);
672 
673 		// okay - at least we set menu label to active resolution
674 		fResolutionMenu->Superitem()->SetLabel(string.String());
675 	}
676 
677 	// mark active combine mode
678 	for (int32 i = 0; i < kCombineModeCount; i++) {
679 		if (kCombineModes[i].mode == fSelected.combine) {
680 			item = fCombineMenu->ItemAt(i);
681 			if (item && !item->IsMarked())
682 				item->SetMarked(true);
683 			break;
684 		}
685 	}
686 
687 	item = fColorsMenu->ItemAt(0);
688 
689 	for (int32 i = kColorSpaceCount; i-- > 0;) {
690 		if (kColorSpaces[i].space == fSelected.space) {
691 			item = fColorsMenu->ItemAt(i);
692 			break;
693 		}
694 	}
695 
696 	if (item && !item->IsMarked())
697 		item->SetMarked(true);
698 
699 	string.Truncate(0);
700 	string << fSelected.BitsPerPixel() << " Bits/Pixel";
701 	if (string != fColorsMenu->Superitem()->Label())
702 		fColorsMenu->Superitem()->SetLabel(string.String());
703 
704 	UpdateMonitorView();
705 	UpdateRefreshControl();
706 
707 	CheckApplyEnabled();
708 }
709 
710 
711 /** reflect active mode in chosen settings */
712 
713 void
714 ScreenWindow::UpdateActiveMode()
715 {
716 	// usually, this function gets called after a mode
717 	// has been set manually; still, as the graphics driver
718 	// is free to fiddle with mode passed, we better ask
719 	// what kind of mode we actually got
720 	fScreenMode.Get(fActive);
721 	fSelected = fActive;
722 
723 	UpdateControls();
724 }
725 
726 
727 void
728 ScreenWindow::ScreenChanged(BRect frame, color_space mode)
729 {
730 	// move window on screen, if necessary
731 	if (frame.right <= Frame().right
732 		&& frame.bottom <= Frame().bottom) {
733 		MoveTo((frame.Width() - Frame().Width()) / 2,
734 			(frame.Height() - Frame().Height()) / 2);
735 	}
736 }
737 
738 
739 void
740 ScreenWindow::WorkspaceActivated(int32 workspace, bool state)
741 {
742 	if (fChangingAllWorkspaces) {
743 		// we're currently changing all workspaces, so there is no need
744 		// to update the interface
745 		return;
746 	}
747 
748 	fScreenMode.Get(fOriginal);
749 	fScreenMode.UpdateOriginalMode();
750 
751 	// only override current settings if they have not been changed yet
752 	if (fSelected == fActive)
753 		UpdateActiveMode();
754 
755 	BMessage message(UPDATE_DESKTOP_COLOR_MSG);
756 	PostMessage(&message, fMonitorView);
757 }
758 
759 
760 void
761 ScreenWindow::MessageReceived(BMessage* message)
762 {
763 	switch (message->what) {
764 		case WORKSPACE_CHECK_MSG:
765 			CheckApplyEnabled();
766 			break;
767 
768 		case POP_WORKSPACE_CHANGED_MSG:
769 		{
770 			int32 index;
771 			if (message->FindInt32("index", &index) == B_OK)
772 				set_workspace_count(index + 1);
773 			break;
774 		}
775 
776 		case POP_RESOLUTION_MSG:
777 		{
778 			message->FindInt32("width", &fSelected.width);
779 			message->FindInt32("height", &fSelected.height);
780 
781 			CheckColorMenu();
782 			CheckRefreshMenu();
783 
784 			UpdateMonitorView();
785 			UpdateRefreshControl();
786 
787 			CheckApplyEnabled();
788 			break;
789 		}
790 
791 		case POP_COLORS_MSG:
792 		{
793 			message->FindInt32("space", (int32 *)&fSelected.space);
794 
795 			BString string;
796 			string << fSelected.BitsPerPixel() << " Bits/Pixel";
797 			fColorsMenu->Superitem()->SetLabel(string.String());
798 
799 			CheckApplyEnabled();
800 			break;
801 		}
802 
803 		case POP_REFRESH_MSG:
804 			message->FindFloat("refresh", &fSelected.refresh);
805 			fOtherRefresh->SetLabel("Other" B_UTF8_ELLIPSIS);
806 				// revert "Other…" label - it might have had a refresh rate prefix
807 
808 			CheckApplyEnabled();
809 			break;
810 
811 		case POP_OTHER_REFRESH_MSG:
812 		{
813 			// make sure menu shows something useful
814 			UpdateRefreshControl();
815 
816 			float min = 0, max = 999;
817 			fScreenMode.GetRefreshLimits(fSelected, min, max);
818 			if (min < gMinRefresh)
819 				min = gMinRefresh;
820 			if (max > gMaxRefresh)
821 				max = gMaxRefresh;
822 
823 			RefreshWindow *fRefreshWindow = new RefreshWindow(
824 				fRefreshField->ConvertToScreen(B_ORIGIN), fSelected.refresh, min, max);
825 			fRefreshWindow->Show();
826 			break;
827 		}
828 
829 		case SET_CUSTOM_REFRESH_MSG:
830 		{
831 			// user pressed "done" in "Other…" refresh dialog;
832 			// select the refresh rate chosen
833 			message->FindFloat("refresh", &fSelected.refresh);
834 
835 			UpdateRefreshControl();
836 			CheckApplyEnabled();
837 			break;
838 		}
839 
840 		case POP_COMBINE_DISPLAYS_MSG:
841 		{
842 			// new combine mode has bee chosen
843 			int32 mode;
844 			if (message->FindInt32("mode", &mode) == B_OK)
845 				fSelected.combine = (combine_mode)mode;
846 
847 			CheckResolutionMenu();
848 			CheckApplyEnabled();
849 			break;
850 		}
851 
852 		case POP_SWAP_DISPLAYS_MSG:
853 			message->FindBool("swap", &fSelected.swap_displays);
854 			CheckApplyEnabled();
855 			break;
856 
857 		case POP_USE_LAPTOP_PANEL_MSG:
858 			message->FindBool("use", &fSelected.use_laptop_panel);
859 			CheckApplyEnabled();
860 			break;
861 
862 		case POP_TV_STANDARD_MSG:
863 			message->FindInt32("tv_standard", (int32 *)&fSelected.tv_standard);
864 			CheckApplyEnabled();
865 			break;
866 
867 		case BUTTON_LAUNCH_BACKGROUNDS_MSG:
868 			if (be_roster->Launch(kBackgroundsSignature) == B_ALREADY_RUNNING) {
869 				app_info info;
870 				be_roster->GetAppInfo(kBackgroundsSignature, &info);
871 				be_roster->ActivateApp(info.team);
872 			}
873 			break;
874 
875 		case BUTTON_DEFAULTS_MSG:
876 		{
877 			fSelected.width = 640;
878 			fSelected.height = 480;
879 			fSelected.space = B_CMAP8;
880 			fSelected.refresh = 60.0;
881 			fSelected.combine = kCombineDisable;
882 			fSelected.swap_displays = false;
883 			fSelected.use_laptop_panel = false;
884 			fSelected.tv_standard = 0;
885 
886 			UpdateControls();
887 			break;
888 		}
889 
890 		case BUTTON_REVERT_MSG:
891 		case SET_INITIAL_MODE_MSG:
892 			fScreenMode.Revert();
893 			UpdateActiveMode();
894 			break;
895 
896 		case BUTTON_APPLY_MSG:
897 			Apply();
898 			break;
899 
900 		case MAKE_INITIAL_MSG: {
901 			// user pressed "keep" in confirmation box;
902 			// select this mode in dialog and mark it as
903 			// previous mode; if "all workspaces" is selected,
904 			// distribute mode to all workspaces
905 
906 			// use the mode that has eventually been set and
907 			// thus we know to be working; it can differ from
908 			// the mode selected by user due to hardware limitation
909 			display_mode newMode;
910 			BScreen screen(this);
911 			screen.GetMode(&newMode);
912 
913 			if (fAllWorkspacesItem->IsMarked()) {
914 				int32 originatingWorkspace;
915 
916 				// the original panel activates each workspace in turn;
917 				// this is disguisting and there is a SetMode
918 				// variant that accepts a workspace id, so let's take
919 				// this one
920 				originatingWorkspace = current_workspace();
921 
922 				// well, this "cannot be reverted" message is not
923 				// entirely true - at least, you can revert it
924 				// for current workspace; to not overwrite original
925 				// mode during workspace switch, we use this flag
926 				fChangingAllWorkspaces = true;
927 
928 				for (int32 i = 0; i < count_workspaces(); i++) {
929 					if (i != originatingWorkspace)
930 						screen.SetMode(i, &newMode, true);
931 				}
932 
933 				fChangingAllWorkspaces = false;
934 			}
935 
936 			fScreenMode.UpdateOriginalMode();
937 			UpdateActiveMode();
938 			break;
939 		}
940 
941 		default:
942 			BWindow::MessageReceived(message);
943 			break;
944 	}
945 }
946 
947 
948 bool
949 ScreenWindow::CanApply() const
950 {
951 	if (fAllWorkspacesItem->IsMarked())
952 		return true;
953 
954 	return fSelected != fActive;
955 }
956 
957 
958 bool
959 ScreenWindow::CanRevert() const
960 {
961 	if (fActive != fOriginal)
962 		return true;
963 
964 	return CanApply();
965 }
966 
967 
968 void
969 ScreenWindow::CheckApplyEnabled()
970 {
971 	fApplyButton->SetEnabled(CanApply());
972 	fRevertButton->SetEnabled(CanRevert());
973 }
974 
975 
976 void
977 ScreenWindow::Apply()
978 {
979 	if (fAllWorkspacesItem->IsMarked()) {
980 		BAlert *workspacesAlert = new BAlert("WorkspacesAlert",
981 			"Change all workspaces? This action cannot be reverted.", "Okay", "Cancel",
982 			NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
983 
984 		if (workspacesAlert->Go() == 1)
985 			return;
986 	}
987 
988 	status_t status = fScreenMode.Set(fSelected);
989 	if (status == B_OK) {
990 		fActive = fSelected;
991 
992 		// ToDo: only show alert when this is an unknown mode
993 		BWindow* window = new AlertWindow(this);
994 		window->Show();
995 	} else {
996 		char message[256];
997 		snprintf(message, sizeof(message),
998 			"The screen mode could not be set:\n\t%s\n", screen_errors(status));
999 		BAlert* alert = new BAlert("Screen Alert", message, "Okay", NULL, NULL,
1000 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1001 		alert->Go();
1002 	}
1003 }
1004 
1005 
1006 void
1007 ScreenWindow::LayoutControls(uint32 flags)
1008 {
1009 	// layout the screen box and its controls
1010 	fWorkspaceCountField->ResizeToPreferred();
1011 
1012 	float monitorViewHeight = fMonitorView->Bounds().Height();
1013 	float workspaceFieldHeight = fWorkspaceCountField->Bounds().Height();
1014 	float backgroundsButtonHeight = fBackgroundsButton->Bounds().Height();
1015 
1016 	float screenBoxWidth = fWorkspaceCountField->Bounds().Width() + 20.0;
1017 	float screenBoxHeight = monitorViewHeight + 5.0
1018 							+ workspaceFieldHeight + 5.0
1019 							+ backgroundsButtonHeight
1020 							+ 20.0;
1021 
1022 #ifdef __HAIKU__
1023 	fScreenBox->MoveTo(10.0, 10.0 + fControlsBox->TopBorderOffset());
1024 #else
1025 	fScreenBox->MoveTo(10.0, 10.0 + 3);
1026 #endif
1027 	fScreenBox->ResizeTo(screenBoxWidth, screenBoxHeight);
1028 
1029 	float leftOffset = 10.0;
1030 	float topOffset = 10.0;
1031 	fMonitorView->MoveTo(leftOffset, topOffset);
1032 	fMonitorView->ResizeTo(screenBoxWidth - 20.0, monitorViewHeight);
1033 	fMonitorView->SetResizingMode(B_FOLLOW_ALL);
1034 	topOffset += monitorViewHeight + 5.0;
1035 
1036 	fWorkspaceCountField->MoveTo(leftOffset, topOffset);
1037 	fWorkspaceCountField->SetResizingMode(B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM);
1038 	topOffset += workspaceFieldHeight + 5.0;
1039 
1040 	fBackgroundsButton->MoveTo(leftOffset, topOffset);
1041 	fBackgroundsButton->ResizeTo(screenBoxWidth - 20.0, backgroundsButtonHeight);
1042 	fBackgroundsButton->SetResizingMode(B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM);
1043 
1044 	fControlsBox->MoveTo(fScreenBox->Frame().right + 10.0, 10.0);
1045 
1046 	// layout the right side
1047 	BRect controlsRect = LayoutMenuFields(flags);
1048 	controlsRect.InsetBy(-10.0, -10.0);
1049 	// adjust size of controls box and move aligned buttons along
1050 	float xDiff = controlsRect.right - fControlsBox->Bounds().right;
1051 	float yDiff = controlsRect.bottom - fControlsBox->Bounds().bottom;
1052 	if (yDiff < 0.0) {
1053 		// don't shrink vertically
1054 		yDiff = 0.0;
1055 	}
1056 
1057 	fControlsBox->ResizeBy(xDiff, yDiff);
1058 
1059 	// align bottom of boxen
1060 	float boxBottomDiff = fControlsBox->Frame().bottom - fScreenBox->Frame().bottom;
1061 	if (boxBottomDiff > 0)
1062 		fScreenBox->ResizeBy(0.0, boxBottomDiff);
1063 	else
1064 		fControlsBox->ResizeBy(0.0, -boxBottomDiff);
1065 
1066 	BRect boxFrame = fScreenBox->Frame() | fControlsBox->Frame();
1067 
1068 	// layout rest of buttons
1069 	fDefaultsButton->ResizeToPreferred();
1070 	fDefaultsButton->MoveTo(boxFrame.left, boxFrame.bottom + 8);
1071 
1072 	fRevertButton->ResizeToPreferred();
1073 	fRevertButton->MoveTo(fDefaultsButton->Frame().right + 10,
1074 						  fDefaultsButton->Frame().top);
1075 
1076 	fApplyButton->ResizeToPreferred();
1077 	fApplyButton->MoveTo(boxFrame.right - fApplyButton->Bounds().Width(),
1078 						 fDefaultsButton->Frame().top);
1079 
1080 	ResizeTo(boxFrame.right + 10, fDefaultsButton->Frame().bottom + 10);
1081 }
1082 
1083 
1084 BRect
1085 ScreenWindow::LayoutMenuFields(uint32 flags, bool sideBySide)
1086 {
1087 	BList menuFields;
1088 	menuFields.AddItem((void*)fResolutionField);
1089 	menuFields.AddItem((void*)fColorsField);
1090 	menuFields.AddItem((void*)fRefreshField);
1091 
1092 	BRect frame;
1093 
1094 	if (sideBySide)
1095 		frame = stack_and_align_menu_fields(menuFields);
1096 
1097 	if (sideBySide)
1098 		menuFields.MakeEmpty();
1099 
1100 	if (flags & SHOW_COMBINE_FIELD)
1101 		menuFields.AddItem((void*)fCombineField);
1102 	if (flags & SHOW_SWAP_FIELD)
1103 		menuFields.AddItem((void*)fSwapDisplaysField);
1104 	if (flags & SHOW_LAPTOP_PANEL_FIELD)
1105 		menuFields.AddItem((void*)fUseLaptopPanelField);
1106 	if (flags & SHOW_TV_STANDARD_FIELD)
1107 		menuFields.AddItem((void*)fTVStandardField);
1108 
1109 	if (sideBySide) {
1110 		if (menuFields.CountItems() > 0) {
1111 			((BMenuField*)menuFields.FirstItem())->MoveTo(frame.right + 8.0, frame.top);
1112 			frame = frame | stack_and_align_menu_fields(menuFields);
1113 		}
1114 	} else {
1115 		frame = stack_and_align_menu_fields(menuFields);
1116 	}
1117 
1118 	return frame;
1119 }
1120 
1121