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