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