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