xref: /haiku/src/preferences/screen/ScreenWindow.cpp (revision 344ded80d400028c8f561b4b876257b94c12db4a)
1 /*
2  * Copyright 2001-2015 Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus, superstippi@gmx.de
7  *		Andrew Bachmann
8  *		Stefano Ceccherini, burton666@libero.it
9  *		Alexandre Deckner, alex@zappotek.com
10  *		Axel Dörfler, axeld@pinc-software.de
11  *		Rene Gollent, rene@gollent.com
12  *		Thomas Kurschel
13  *		Rafael Romo
14  *		John Scipione, jscipione@gmail.com
15  */
16 
17 
18 #include "ScreenWindow.h"
19 
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <strings.h>
23 
24 #include <Alert.h>
25 #include <Application.h>
26 #include <Box.h>
27 #include <Button.h>
28 #include <Catalog.h>
29 #include <ControlLook.h>
30 #include <Directory.h>
31 #include <File.h>
32 #include <FindDirectory.h>
33 #include <InterfaceDefs.h>
34 #include <LayoutBuilder.h>
35 #include <MenuBar.h>
36 #include <MenuField.h>
37 #include <MenuItem.h>
38 #include <Messenger.h>
39 #include <Path.h>
40 #include <PopUpMenu.h>
41 #include <Roster.h>
42 #include <Screen.h>
43 #include <SpaceLayoutItem.h>
44 #include <Spinner.h>
45 #include <String.h>
46 #include <StringView.h>
47 #include <Window.h>
48 
49 #include <InterfacePrivate.h>
50 
51 #include "AlertWindow.h"
52 #include "Constants.h"
53 #include "RefreshWindow.h"
54 #include "MonitorView.h"
55 #include "ScreenSettings.h"
56 #include "Utility.h"
57 
58 /* Note, this headers defines a *private* interface to the Radeon accelerant.
59  * It's a solution that works with the current BeOS interface that Haiku
60  * adopted.
61  * However, it's not a nice and clean solution. Don't use this header in any
62  * application if you can avoid it. No other driver is using this, or should
63  * be using this.
64  * It will be replaced as soon as we introduce an updated accelerant interface
65  * which may even happen before R1 hits the streets.
66  */
67 #include "multimon.h"	// the usual: DANGER WILL, ROBINSON!
68 
69 
70 #undef B_TRANSLATION_CONTEXT
71 #define B_TRANSLATION_CONTEXT "Screen"
72 
73 
74 const char* kBackgroundsSignature = "application/x-vnd.Haiku-Backgrounds";
75 
76 // list of officially supported colour spaces
77 static const struct {
78 	color_space	space;
79 	int32		bits_per_pixel;
80 	const char*	label;
81 } kColorSpaces[] = {
82 	{ B_CMAP8, 8, B_TRANSLATE("8 bits/pixel, 256 colors") },
83 	{ B_RGB15, 15, B_TRANSLATE("15 bits/pixel, 32768 colors") },
84 	{ B_RGB16, 16, B_TRANSLATE("16 bits/pixel, 65536 colors") },
85 	{ B_RGB24, 24, B_TRANSLATE("24 bits/pixel, 16 Million colors") },
86 	{ B_RGB32, 32, B_TRANSLATE("32 bits/pixel, 16 Million colors") }
87 };
88 static const int32 kColorSpaceCount = B_COUNT_OF(kColorSpaces);
89 
90 // list of standard refresh rates
91 static const int32 kRefreshRates[] = { 60, 70, 72, 75, 80, 85, 95, 100 };
92 static const int32 kRefreshRateCount = B_COUNT_OF(kRefreshRates);
93 
94 // list of combine modes
95 static const struct {
96 	combine_mode	mode;
97 	const char		*name;
98 } kCombineModes[] = {
99 	{ kCombineDisable, B_TRANSLATE("disable") },
100 	{ kCombineHorizontally, B_TRANSLATE("horizontally") },
101 	{ kCombineVertically, B_TRANSLATE("vertically") }
102 };
103 static const int32 kCombineModeCount = B_COUNT_OF(kCombineModes);
104 
105 
106 static BString
107 tv_standard_to_string(uint32 mode)
108 {
109 	switch (mode) {
110 		case 0:		return "disabled";
111 		case 1:		return "NTSC";
112 		case 2:		return "NTSC Japan";
113 		case 3:		return "PAL BDGHI";
114 		case 4:		return "PAL M";
115 		case 5:		return "PAL N";
116 		case 6:		return "SECAM";
117 		case 101:	return "NTSC 443";
118 		case 102:	return "PAL 60";
119 		case 103:	return "PAL NC";
120 		default:
121 		{
122 			BString name;
123 			name << "??? (" << mode << ")";
124 
125 			return name;
126 		}
127 	}
128 }
129 
130 
131 static void
132 resolution_to_string(screen_mode& mode, BString &string)
133 {
134 	string.SetToFormat(B_TRANSLATE_COMMENT("%" B_PRId32" × %" B_PRId32,
135 			"The '×' is the Unicode multiplication sign U+00D7"),
136 			mode.width, mode.height);
137 }
138 
139 
140 static void
141 refresh_rate_to_string(float refresh, BString &string,
142 	bool appendUnit = true, bool alwaysWithFraction = false)
143 {
144 	snprintf(string.LockBuffer(32), 32, "%.*g", refresh >= 100.0 ? 4 : 3,
145 		refresh);
146 	string.UnlockBuffer();
147 
148 	if (appendUnit)
149 		string << " " << B_TRANSLATE("Hz");
150 }
151 
152 
153 static const char*
154 screen_errors(status_t status)
155 {
156 	switch (status) {
157 		case B_ENTRY_NOT_FOUND:
158 			return B_TRANSLATE("Unknown mode");
159 		// TODO: add more?
160 
161 		default:
162 			return strerror(status);
163 	}
164 }
165 
166 
167 //	#pragma mark - ScreenWindow
168 
169 
170 ScreenWindow::ScreenWindow(ScreenSettings* settings)
171 	:
172 	BWindow(settings->WindowFrame(), B_TRANSLATE_SYSTEM_NAME("Screen"),
173 		B_TITLED_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE
174 			| B_AUTO_UPDATE_SIZE_LIMITS, B_ALL_WORKSPACES),
175 	fIsVesa(false),
176 	fBootWorkspaceApplied(false),
177 	fUserSelectedColorSpace(NULL),
178 	fOtherRefresh(NULL),
179 	fScreenMode(this),
180 	fUndoScreenMode(this),
181 	fModified(false)
182 {
183 	BScreen screen(this);
184 
185 	accelerant_device_info info;
186 	if (screen.GetDeviceInfo(&info) == B_OK
187 		&& !strcasecmp(info.chipset, "VESA"))
188 		fIsVesa = true;
189 
190 	_UpdateOriginal();
191 	_BuildSupportedColorSpaces();
192 	fActive = fSelected = fOriginal;
193 
194 	fSettings = settings;
195 
196 	// we need the "Current Workspace" first to get its height
197 
198 	BPopUpMenu* popUpMenu = new BPopUpMenu(B_TRANSLATE("Current workspace"),
199 		true, true);
200 	fAllWorkspacesItem = new BMenuItem(B_TRANSLATE("All workspaces"),
201 		new BMessage(WORKSPACE_CHECK_MSG));
202 	popUpMenu->AddItem(fAllWorkspacesItem);
203 	BMenuItem *item = new BMenuItem(B_TRANSLATE("Current workspace"),
204 		new BMessage(WORKSPACE_CHECK_MSG));
205 
206 	popUpMenu->AddItem(item);
207 	fAllWorkspacesItem->SetMarked(true);
208 
209 	BMenuField* workspaceMenuField = new BMenuField("WorkspaceMenu", NULL,
210 		popUpMenu);
211 	workspaceMenuField->ResizeToPreferred();
212 
213 	// box on the left with workspace count and monitor view
214 
215 	fScreenBox = new BBox("screen box");
216 	BGroupView* groupView = new BGroupView(B_VERTICAL, B_USE_SMALL_SPACING);
217 	fScreenBox->AddChild(groupView);
218 	fScreenBox->SetLabel("placeholder");
219 		// Needed for layouting, will be replaced with screen name/size
220 	groupView->GroupLayout()->SetInsets(B_USE_DEFAULT_SPACING,
221 		B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING);
222 
223 	fDeviceInfo = new BStringView("device info", "");
224 	fDeviceInfo->SetAlignment(B_ALIGN_CENTER);
225 	groupView->AddChild(fDeviceInfo);
226 
227 	float scaling = std::max(1.0f, be_plain_font->Size() / 12.0f);
228 	fMonitorView = new MonitorView(BRect(0.0, 0.0, 80.0 * scaling,
229 			80.0 * scaling), "monitor", screen.Frame().IntegerWidth() + 1,
230 		screen.Frame().IntegerHeight() + 1);
231 	fMonitorView->SetToolTip(B_TRANSLATE("Set background" B_UTF8_ELLIPSIS));
232 	groupView->AddChild(fMonitorView);
233 
234 	// brightness slider
235 	fBrightnessSlider = new BSlider("brightness", B_TRANSLATE("Brightness:"),
236 		NULL, 0, 255, B_HORIZONTAL);
237 	groupView->AddChild(fBrightnessSlider);
238 
239 	if (screen.GetBrightness(&fOriginalBrightness) == B_OK) {
240 		fBrightnessSlider->SetModificationMessage(
241 			new BMessage(SLIDER_BRIGHTNESS_MSG));
242 		fBrightnessSlider->SetValue(fOriginalBrightness * 255);
243 	} else {
244 		// The driver does not support changing the brightness,
245 		// so hide the slider
246 		fBrightnessSlider->Hide();
247 		fOriginalBrightness = -1;
248 	}
249 
250 	// box on the left below the screen box with workspaces
251 
252 	BBox* workspacesBox = new BBox("workspaces box");
253 	workspacesBox->SetLabel(B_TRANSLATE("Workspaces"));
254 
255 	BGroupLayout* workspacesLayout = new BGroupLayout(B_VERTICAL);
256 	workspacesLayout->SetInsets(B_USE_DEFAULT_SPACING,
257 		be_control_look->DefaultItemSpacing() * 2, B_USE_DEFAULT_SPACING,
258 		B_USE_DEFAULT_SPACING);
259 	workspacesBox->SetLayout(workspacesLayout);
260 
261 	fColumnsControl = new BSpinner("columns", B_TRANSLATE("Columns:"),
262 		new BMessage(kMsgWorkspaceColumnsChanged));
263 	fColumnsControl->SetAlignment(B_ALIGN_RIGHT);
264 	fColumnsControl->SetRange(1, 32);
265 
266 	fRowsControl = new BSpinner("rows", B_TRANSLATE("Rows:"),
267 		new BMessage(kMsgWorkspaceRowsChanged));
268 	fRowsControl->SetAlignment(B_ALIGN_RIGHT);
269 	fRowsControl->SetRange(1, 32);
270 
271 	uint32 columns;
272 	uint32 rows;
273 	BPrivate::get_workspaces_layout(&columns, &rows);
274 	fColumnsControl->SetValue(columns);
275 	fRowsControl->SetValue(rows);
276 
277 	workspacesBox->AddChild(BLayoutBuilder::Group<>()
278 		.AddGroup(B_VERTICAL, B_USE_SMALL_SPACING)
279 			.AddGroup(B_HORIZONTAL, 0)
280 				.AddGlue()
281 				.AddGrid(B_USE_DEFAULT_SPACING, B_USE_SMALL_SPACING)
282 					// columns
283 					.Add(fColumnsControl->CreateLabelLayoutItem(), 0, 0)
284 					.Add(fColumnsControl->CreateTextViewLayoutItem(), 1, 0)
285 					// rows
286 					.Add(fRowsControl->CreateLabelLayoutItem(), 0, 1)
287 					.Add(fRowsControl->CreateTextViewLayoutItem(), 1, 1)
288 					.End()
289 				.AddGlue()
290 				.End()
291 			.End()
292 		.View());
293 
294 	// put workspaces slider in a vertical group with a half space above so
295 	// if hidden you won't see the extra space.
296 	BView* workspacesView = BLayoutBuilder::Group<>(B_VERTICAL, 0)
297 		.AddStrut(B_USE_HALF_ITEM_SPACING)
298 		.Add(workspacesBox)
299 		.View();
300 
301 	// box on the right with screen resolution, etc.
302 
303 	BBox* controlsBox = new BBox("controls box");
304 	controlsBox->SetLabel(workspaceMenuField);
305 	BGroupView* outerControlsView = new BGroupView(B_VERTICAL);
306 	outerControlsView->GroupLayout()->SetInsets(B_USE_DEFAULT_SPACING,
307 		B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING);
308 	controlsBox->AddChild(outerControlsView);
309 
310 	menu_layout layout = B_ITEMS_IN_COLUMN;
311 
312 	// There are modes in the list with the same resolution but different bpp or refresh rates.
313 	// We don't want to take these into account when computing the menu layout, so we need to
314 	// count how many entries we will really have in the menu.
315 	int fullModeCount = fScreenMode.CountModes();
316 	int modeCount = 0;
317 	int index = 0;
318 	uint16 maxWidth = 0;
319 	uint16 maxHeight = 0;
320 	uint16 previousWidth = 0;
321 	uint16 previousHeight = 0;
322 	for (int32 i = 0; i < fullModeCount; i++) {
323 		screen_mode mode = fScreenMode.ModeAt(i);
324 
325 		if (mode.width == previousWidth && mode.height == previousHeight)
326 			continue;
327 		modeCount++;
328 		previousWidth = mode.width;
329 		previousHeight = mode.height;
330 		if (maxWidth < mode.width)
331 			maxWidth = mode.width;
332 		if (maxHeight < mode.height)
333 			maxHeight = mode.height;
334 	}
335 
336 	if (modeCount > 16)
337 		layout = B_ITEMS_IN_MATRIX;
338 
339 	fResolutionMenu = new BPopUpMenu("resolution", true, true, layout);
340 
341 	// Compute the size we should allocate to each item in the menu
342 	BRect itemRect;
343 	if (layout == B_ITEMS_IN_MATRIX) {
344 		BFont menuFont;
345 		font_height fontHeight;
346 
347 		fResolutionMenu->GetFont(&menuFont);
348 		menuFont.GetHeight(&fontHeight);
349 		itemRect.left = itemRect.top = 0;
350 		itemRect.bottom = fontHeight.ascent + fontHeight.descent + 4;
351 		itemRect.right = menuFont.StringWidth("99999x99999") + 16;
352 		rows = modeCount / 3 + 1;
353 	}
354 
355 	index = 0;
356 	for (int32 i = 0; i < fullModeCount; i++) {
357 		screen_mode mode = fScreenMode.ModeAt(i);
358 
359 		if (mode.width == previousWidth && mode.height == previousHeight)
360 			continue;
361 
362 		previousWidth = mode.width;
363 		previousHeight = mode.height;
364 
365 		BMessage* message = new BMessage(POP_RESOLUTION_MSG);
366 		message->AddInt32("width", mode.width);
367 		message->AddInt32("height", mode.height);
368 
369 		BString name;
370 		name.SetToFormat(B_TRANSLATE_COMMENT("%" B_PRId32" × %" B_PRId32,
371 			"The '×' is the Unicode multiplication sign U+00D7"),
372 			mode.width, mode.height);
373 
374 		if (layout == B_ITEMS_IN_COLUMN)
375 			fResolutionMenu->AddItem(new BMenuItem(name.String(), message));
376 		else {
377 			int y = index % rows;
378 			int x = index / rows;
379 			itemRect.OffsetTo(x * itemRect.Width(), y * itemRect.Height());
380 			fResolutionMenu->AddItem(new BMenuItem(name.String(), message), itemRect);
381 		}
382 
383 		index++;
384 	}
385 
386 	fMonitorView->SetMaxResolution(maxWidth, maxHeight);
387 
388 	fResolutionField = new BMenuField("ResolutionMenu",
389 		B_TRANSLATE("Resolution:"), fResolutionMenu);
390 	fResolutionField->SetAlignment(B_ALIGN_RIGHT);
391 
392 	fColorsMenu = new BPopUpMenu("colors", true, false);
393 
394 	for (int32 i = 0; i < kColorSpaceCount; i++) {
395 		if ((fSupportedColorSpaces & (1 << i)) == 0)
396 			continue;
397 
398 		BMessage* message = new BMessage(POP_COLORS_MSG);
399 		message->AddInt32("bits_per_pixel", kColorSpaces[i].bits_per_pixel);
400 		message->AddInt32("space", kColorSpaces[i].space);
401 
402 		BMenuItem* item = new BMenuItem(kColorSpaces[i].label, message);
403 		if (kColorSpaces[i].space == screen.ColorSpace())
404 			fUserSelectedColorSpace = item;
405 
406 		fColorsMenu->AddItem(item);
407 	}
408 
409 	fColorsField = new BMenuField("ColorsMenu", B_TRANSLATE("Colors:"),
410 		fColorsMenu);
411 	fColorsField->SetAlignment(B_ALIGN_RIGHT);
412 
413 	fRefreshMenu = new BPopUpMenu("refresh rate", true, true);
414 
415 	float min, max;
416 	if (fScreenMode.GetRefreshLimits(fActive, min, max) != B_OK) {
417 		// if we couldn't obtain the refresh limits, reset to the default
418 		// range. Constraints from detected monitors will fine-tune this
419 		// later.
420 		min = kRefreshRates[0];
421 		max = kRefreshRates[kRefreshRateCount - 1];
422 	}
423 
424 	if (min == max) {
425 		// This is a special case for drivers that only support a single
426 		// frequency, like the VESA driver
427 		BString name;
428 		refresh_rate_to_string(min, name);
429 		BMessage *message = new BMessage(POP_REFRESH_MSG);
430 		message->AddFloat("refresh", min);
431 		BMenuItem *item = new BMenuItem(name.String(), message);
432 		fRefreshMenu->AddItem(item);
433 		item->SetEnabled(false);
434 	} else {
435 		monitor_info info;
436 		if (fScreenMode.GetMonitorInfo(info) == B_OK) {
437 			min = max_c(info.min_vertical_frequency, min);
438 			max = min_c(info.max_vertical_frequency, max);
439 		}
440 
441 		for (int32 i = 0; i < kRefreshRateCount; ++i) {
442 			if (kRefreshRates[i] < min || kRefreshRates[i] > max)
443 				continue;
444 
445 			BString name;
446 			name << kRefreshRates[i] << " " << B_TRANSLATE("Hz");
447 
448 			BMessage *message = new BMessage(POP_REFRESH_MSG);
449 			message->AddFloat("refresh", kRefreshRates[i]);
450 
451 			fRefreshMenu->AddItem(new BMenuItem(name.String(), message));
452 		}
453 
454 		fOtherRefresh = new BMenuItem(B_TRANSLATE("Other" B_UTF8_ELLIPSIS),
455 			new BMessage(POP_OTHER_REFRESH_MSG));
456 		fRefreshMenu->AddItem(fOtherRefresh);
457 	}
458 
459 	fRefreshField = new BMenuField("RefreshMenu", B_TRANSLATE("Refresh rate:"),
460 		fRefreshMenu);
461 	fRefreshField->SetAlignment(B_ALIGN_RIGHT);
462 
463 	if (_IsVesa())
464 		fRefreshField->Hide();
465 
466 	// enlarged area for multi-monitor settings
467 	{
468 		bool dummy;
469 		uint32 dummy32;
470 		bool multiMonSupport;
471 		bool useLaptopPanelSupport;
472 		bool tvStandardSupport;
473 
474 		multiMonSupport = TestMultiMonSupport(&screen) == B_OK;
475 		useLaptopPanelSupport = GetUseLaptopPanel(&screen, &dummy) == B_OK;
476 		tvStandardSupport = GetTVStandard(&screen, &dummy32) == B_OK;
477 
478 		// even if there is no support, we still create all controls
479 		// to make sure we don't access NULL pointers later on
480 
481 		fCombineMenu = new BPopUpMenu("CombineDisplays",
482 			true, true);
483 
484 		for (int32 i = 0; i < kCombineModeCount; i++) {
485 			BMessage *message = new BMessage(POP_COMBINE_DISPLAYS_MSG);
486 			message->AddInt32("mode", kCombineModes[i].mode);
487 
488 			fCombineMenu->AddItem(new BMenuItem(kCombineModes[i].name,
489 				message));
490 		}
491 
492 		fCombineField = new BMenuField("CombineMenu",
493 			B_TRANSLATE("Combine displays:"), fCombineMenu);
494 		fCombineField->SetAlignment(B_ALIGN_RIGHT);
495 
496 		if (!multiMonSupport)
497 			fCombineField->Hide();
498 
499 		fSwapDisplaysMenu = new BPopUpMenu("SwapDisplays",
500 			true, true);
501 
502 		// !order is important - we rely that boolean value == idx
503 		BMessage *message = new BMessage(POP_SWAP_DISPLAYS_MSG);
504 		message->AddBool("swap", false);
505 		fSwapDisplaysMenu->AddItem(new BMenuItem(B_TRANSLATE("no"), message));
506 
507 		message = new BMessage(POP_SWAP_DISPLAYS_MSG);
508 		message->AddBool("swap", true);
509 		fSwapDisplaysMenu->AddItem(new BMenuItem(B_TRANSLATE("yes"), message));
510 
511 		fSwapDisplaysField = new BMenuField("SwapMenu",
512 			B_TRANSLATE("Swap displays:"), fSwapDisplaysMenu);
513 		fSwapDisplaysField->SetAlignment(B_ALIGN_RIGHT);
514 
515 		if (!multiMonSupport)
516 			fSwapDisplaysField->Hide();
517 
518 		fUseLaptopPanelMenu = new BPopUpMenu("UseLaptopPanel",
519 			true, true);
520 
521 		// !order is important - we rely that boolean value == idx
522 		message = new BMessage(POP_USE_LAPTOP_PANEL_MSG);
523 		message->AddBool("use", false);
524 		fUseLaptopPanelMenu->AddItem(new BMenuItem(B_TRANSLATE("if needed"),
525 			message));
526 
527 		message = new BMessage(POP_USE_LAPTOP_PANEL_MSG);
528 		message->AddBool("use", true);
529 		fUseLaptopPanelMenu->AddItem(new BMenuItem(B_TRANSLATE("always"),
530 			message));
531 
532 		fUseLaptopPanelField = new BMenuField("UseLaptopPanel",
533 			B_TRANSLATE("Use laptop panel:"), fUseLaptopPanelMenu);
534 		fUseLaptopPanelField->SetAlignment(B_ALIGN_RIGHT);
535 
536 		if (!useLaptopPanelSupport)
537 			fUseLaptopPanelField->Hide();
538 
539 		fTVStandardMenu = new BPopUpMenu("TVStandard", true, true);
540 
541 		// arbitrary limit
542 		uint32 i;
543 		for (i = 0; i < 100; ++i) {
544 			uint32 mode;
545 			if (GetNthSupportedTVStandard(&screen, i, &mode) != B_OK)
546 				break;
547 
548 			BString name = tv_standard_to_string(mode);
549 
550 			message = new BMessage(POP_TV_STANDARD_MSG);
551 			message->AddInt32("tv_standard", mode);
552 
553 			fTVStandardMenu->AddItem(new BMenuItem(name.String(), message));
554 		}
555 
556 		fTVStandardField = new BMenuField("tv standard",
557 			B_TRANSLATE("Video format:"), fTVStandardMenu);
558 		fTVStandardField->SetAlignment(B_ALIGN_RIGHT);
559 
560 		if (!tvStandardSupport || i == 0)
561 			fTVStandardField->Hide();
562 	}
563 
564 	BLayoutBuilder::Group<>(outerControlsView)
565 		.AddGrid(B_USE_DEFAULT_SPACING, B_USE_SMALL_SPACING)
566 			.Add(fResolutionField->CreateLabelLayoutItem(), 0, 0)
567 			.Add(fResolutionField->CreateMenuBarLayoutItem(), 1, 0)
568 			.Add(fColorsField->CreateLabelLayoutItem(), 0, 1)
569 			.Add(fColorsField->CreateMenuBarLayoutItem(), 1, 1)
570 			.Add(fRefreshField->CreateLabelLayoutItem(), 0, 2)
571 			.Add(fRefreshField->CreateMenuBarLayoutItem(), 1, 2)
572 			.Add(fCombineField->CreateLabelLayoutItem(), 0, 3)
573 			.Add(fCombineField->CreateMenuBarLayoutItem(), 1, 3)
574 			.Add(fSwapDisplaysField->CreateLabelLayoutItem(), 0, 4)
575 			.Add(fSwapDisplaysField->CreateMenuBarLayoutItem(), 1, 4)
576 			.Add(fUseLaptopPanelField->CreateLabelLayoutItem(), 0, 5)
577 			.Add(fUseLaptopPanelField->CreateMenuBarLayoutItem(), 1, 5)
578 			.Add(fTVStandardField->CreateLabelLayoutItem(), 0, 6)
579 			.Add(fTVStandardField->CreateMenuBarLayoutItem(), 1, 6)
580 		.End();
581 
582 	// TODO: we don't support getting the screen's preferred settings
583 	/* fDefaultsButton = new BButton(buttonRect, "DefaultsButton", "Defaults",
584 		new BMessage(BUTTON_DEFAULTS_MSG));*/
585 
586 	fApplyButton = new BButton("ApplyButton", B_TRANSLATE("Apply"),
587 		new BMessage(BUTTON_APPLY_MSG));
588 	fApplyButton->SetEnabled(false);
589 	BLayoutBuilder::Group<>(outerControlsView)
590 		.AddGlue()
591 		.AddGroup(B_HORIZONTAL)
592 			.AddGlue()
593 			.Add(fApplyButton);
594 
595 	fRevertButton = new BButton("RevertButton", B_TRANSLATE("Revert"),
596 		new BMessage(BUTTON_REVERT_MSG));
597 	fRevertButton->SetEnabled(false);
598 
599 	BLayoutBuilder::Group<>(this, B_VERTICAL, B_USE_DEFAULT_SPACING)
600 		.AddGroup(B_HORIZONTAL)
601 			.AddGroup(B_VERTICAL, 0, 1)
602 				.AddStrut(floorf(controlsBox->TopBorderOffset()
603 					- fScreenBox->TopBorderOffset()))
604 				.Add(fScreenBox)
605 				.Add(workspacesView)
606 				.End()
607 			.AddGroup(B_VERTICAL, 0, 1)
608 				.Add(controlsBox, 2)
609 				.End()
610 			.End()
611 		.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
612 			.Add(fRevertButton)
613 			.AddGlue()
614 			.End()
615 		.SetInsets(B_USE_WINDOW_SPACING);
616 
617 	_UpdateControls();
618 	_UpdateMonitor();
619 
620 	MoveOnScreen();
621 }
622 
623 
624 ScreenWindow::~ScreenWindow()
625 {
626 	delete fSettings;
627 }
628 
629 
630 bool
631 ScreenWindow::QuitRequested()
632 {
633 	fSettings->SetWindowFrame(Frame());
634 
635 	// Write mode of workspace 0 (the boot workspace) to the vesa settings file
636 	screen_mode vesaMode;
637 	if (fBootWorkspaceApplied && fScreenMode.Get(vesaMode, 0) == B_OK) {
638 		status_t status = _WriteVesaModeFile(vesaMode);
639 		if (status < B_OK) {
640 			BString warning = B_TRANSLATE("Could not write VESA mode settings"
641 				" file:\n\t");
642 			warning << strerror(status);
643 			BAlert* alert = new BAlert(B_TRANSLATE("Warning"),
644 				warning.String(), B_TRANSLATE("OK"), NULL,
645 				NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
646 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
647 			alert->Go();
648 		}
649 	}
650 
651 	be_app->PostMessage(B_QUIT_REQUESTED);
652 
653 	return BWindow::QuitRequested();
654 }
655 
656 
657 /*!	Update resolution list according to combine mode
658 	(some resolutions may not be combinable due to memory restrictions).
659 */
660 void
661 ScreenWindow::_CheckResolutionMenu()
662 {
663 	for (int32 i = 0; i < fResolutionMenu->CountItems(); i++)
664 		fResolutionMenu->ItemAt(i)->SetEnabled(false);
665 
666 	for (int32 i = 0; i < fScreenMode.CountModes(); i++) {
667 		screen_mode mode = fScreenMode.ModeAt(i);
668 		if (mode.combine != fSelected.combine)
669 			continue;
670 
671 		BString name;
672 		name.SetToFormat(B_TRANSLATE_COMMENT("%" B_PRId32" × %" B_PRId32,
673 			"The '×' is the Unicode multiplication sign U+00D7"),
674 			mode.width, mode.height);
675 
676 		BMenuItem *item = fResolutionMenu->FindItem(name.String());
677 		if (item != NULL)
678 			item->SetEnabled(true);
679 	}
680 }
681 
682 
683 /*!	Update color and refresh options according to current mode
684 	(a color space is made active if there is any mode with
685 	given resolution and this colour space; same applies for
686 	refresh rate, though "Other…" is always possible)
687 */
688 void
689 ScreenWindow::_CheckColorMenu()
690 {
691 	int32 supportsAnything = false;
692 	int32 index = 0;
693 
694 	for (int32 i = 0; i < kColorSpaceCount; i++) {
695 		if ((fSupportedColorSpaces & (1 << i)) == 0)
696 			continue;
697 
698 		bool supported = false;
699 
700 		for (int32 j = 0; j < fScreenMode.CountModes(); j++) {
701 			screen_mode mode = fScreenMode.ModeAt(j);
702 
703 			if (fSelected.width == mode.width
704 				&& fSelected.height == mode.height
705 				&& kColorSpaces[i].space == mode.space
706 				&& fSelected.combine == mode.combine) {
707 				supportsAnything = true;
708 				supported = true;
709 				break;
710 			}
711 		}
712 
713 		BMenuItem* item = fColorsMenu->ItemAt(index++);
714 		if (item)
715 			item->SetEnabled(supported);
716 	}
717 
718 	fColorsField->SetEnabled(supportsAnything);
719 
720 	if (!supportsAnything)
721 		return;
722 
723 	// Make sure a valid item is selected
724 
725 	BMenuItem* item = fColorsMenu->FindMarked();
726 	bool changed = false;
727 
728 	if (item != fUserSelectedColorSpace) {
729 		if (fUserSelectedColorSpace != NULL
730 			&& fUserSelectedColorSpace->IsEnabled()) {
731 			fUserSelectedColorSpace->SetMarked(true);
732 			item = fUserSelectedColorSpace;
733 			changed = true;
734 		}
735 	}
736 	if (item != NULL && !item->IsEnabled()) {
737 		// find the next best item
738 		int32 index = fColorsMenu->IndexOf(item);
739 		bool found = false;
740 
741 		for (int32 i = index + 1; i < fColorsMenu->CountItems(); i++) {
742 			item = fColorsMenu->ItemAt(i);
743 			if (item->IsEnabled()) {
744 				found = true;
745 				break;
746 			}
747 		}
748 		if (!found) {
749 			// search backwards as well
750 			for (int32 i = index - 1; i >= 0; i--) {
751 				item = fColorsMenu->ItemAt(i);
752 				if (item->IsEnabled())
753 					break;
754 			}
755 		}
756 
757 		item->SetMarked(true);
758 		changed = true;
759 	}
760 
761 	if (changed) {
762 		// Update selected space
763 
764 		BMessage* message = item->Message();
765 		int32 space;
766 		if (message->FindInt32("space", &space) == B_OK) {
767 			fSelected.space = (color_space)space;
768 			_UpdateColorLabel();
769 		}
770 	}
771 }
772 
773 
774 /*!	Enable/disable refresh options according to current mode. */
775 void
776 ScreenWindow::_CheckRefreshMenu()
777 {
778 	float min, max;
779 	if (fScreenMode.GetRefreshLimits(fSelected, min, max) != B_OK || min == max)
780 		return;
781 
782 	for (int32 i = fRefreshMenu->CountItems(); i-- > 0;) {
783 		BMenuItem* item = fRefreshMenu->ItemAt(i);
784 		BMessage* message = item->Message();
785 		float refresh;
786 		if (message != NULL && message->FindFloat("refresh", &refresh) == B_OK)
787 			item->SetEnabled(refresh >= min && refresh <= max);
788 	}
789 }
790 
791 
792 /*!	Activate appropriate menu item according to selected refresh rate */
793 void
794 ScreenWindow::_UpdateRefreshControl()
795 {
796 	if (isnan(fSelected.refresh)) {
797 		fRefreshMenu->SetEnabled(false);
798 		fOtherRefresh->SetLabel(B_TRANSLATE("Unknown"));
799 		fOtherRefresh->SetMarked(true);
800 		return;
801 	} else {
802 		fRefreshMenu->SetEnabled(true);
803 	}
804 
805 	for (int32 i = 0; i < fRefreshMenu->CountItems(); i++) {
806 		BMenuItem* item = fRefreshMenu->ItemAt(i);
807 		if (item->Message()->FindFloat("refresh") == fSelected.refresh) {
808 			item->SetMarked(true);
809 			// "Other" items only contains a refresh rate when active
810 			if (fOtherRefresh != NULL)
811 				fOtherRefresh->SetLabel(B_TRANSLATE("Other" B_UTF8_ELLIPSIS));
812 			return;
813 		}
814 	}
815 
816 	// this is a non-standard refresh rate
817 	if (fOtherRefresh != NULL) {
818 		fOtherRefresh->Message()->ReplaceFloat("refresh", fSelected.refresh);
819 		fOtherRefresh->SetMarked(true);
820 
821 		BString string;
822 		refresh_rate_to_string(fSelected.refresh, string);
823 		fRefreshMenu->Superitem()->SetLabel(string.String());
824 
825 		string.Append(B_TRANSLATE("/other" B_UTF8_ELLIPSIS));
826 		fOtherRefresh->SetLabel(string.String());
827 	}
828 }
829 
830 
831 void
832 ScreenWindow::_UpdateMonitorView()
833 {
834 	BMessage updateMessage(UPDATE_DESKTOP_MSG);
835 	updateMessage.AddInt32("width", fSelected.width);
836 	updateMessage.AddInt32("height", fSelected.height);
837 
838 	PostMessage(&updateMessage, fMonitorView);
839 }
840 
841 
842 void
843 ScreenWindow::_UpdateControls()
844 {
845 	_UpdateWorkspaceButtons();
846 
847 	BMenuItem* item = fSwapDisplaysMenu->ItemAt((int32)fSelected.swap_displays);
848 	if (item != NULL && !item->IsMarked())
849 		item->SetMarked(true);
850 
851 	item = fUseLaptopPanelMenu->ItemAt((int32)fSelected.use_laptop_panel);
852 	if (item != NULL && !item->IsMarked())
853 		item->SetMarked(true);
854 
855 	for (int32 i = 0; i < fTVStandardMenu->CountItems(); i++) {
856 		item = fTVStandardMenu->ItemAt(i);
857 
858 		uint32 tvStandard;
859 		item->Message()->FindInt32("tv_standard", (int32 *)&tvStandard);
860 		if (tvStandard == fSelected.tv_standard) {
861 			if (!item->IsMarked())
862 				item->SetMarked(true);
863 			break;
864 		}
865 	}
866 
867 	_CheckResolutionMenu();
868 	_CheckColorMenu();
869 	_CheckRefreshMenu();
870 
871 	BString string;
872 	resolution_to_string(fSelected, string);
873 	item = fResolutionMenu->FindItem(string.String());
874 
875 	if (item != NULL) {
876 		if (!item->IsMarked())
877 			item->SetMarked(true);
878 	} else {
879 		// this is bad luck - if mode has been set via screen references,
880 		// this case cannot occur; there are three possible solutions:
881 		// 1. add a new resolution to list
882 		//    - we had to remove it as soon as a "valid" one is selected
883 		//    - we don't know which frequencies/bit depths are supported
884 		//    - as long as we haven't the GMT formula to create
885 		//      parameters for any resolution given, we cannot
886 		//      really set current mode - it's just not in the list
887 		// 2. choose nearest resolution
888 		//    - probably a good idea, but implies coding and testing
889 		// 3. choose lowest resolution
890 		//    - do you really think we are so lazy? yes, we are
891 		item = fResolutionMenu->ItemAt(0);
892 		if (item)
893 			item->SetMarked(true);
894 
895 		// okay - at least we set menu label to active resolution
896 		fResolutionMenu->Superitem()->SetLabel(string.String());
897 	}
898 
899 	// mark active combine mode
900 	for (int32 i = 0; i < kCombineModeCount; i++) {
901 		if (kCombineModes[i].mode == fSelected.combine) {
902 			item = fCombineMenu->ItemAt(i);
903 			if (item != NULL && !item->IsMarked())
904 				item->SetMarked(true);
905 			break;
906 		}
907 	}
908 
909 	item = fColorsMenu->ItemAt(0);
910 
911 	for (int32 i = 0, index = 0; i <  kColorSpaceCount; i++) {
912 		if ((fSupportedColorSpaces & (1 << i)) == 0)
913 			continue;
914 
915 		if (kColorSpaces[i].space == fSelected.space) {
916 			item = fColorsMenu->ItemAt(index);
917 			break;
918 		}
919 
920 		index++;
921 	}
922 
923 	if (item != NULL && !item->IsMarked())
924 		item->SetMarked(true);
925 
926 	_UpdateColorLabel();
927 	_UpdateMonitorView();
928 	_UpdateRefreshControl();
929 
930 	_CheckApplyEnabled();
931 }
932 
933 
934 /*! Reflect active mode in chosen settings */
935 void
936 ScreenWindow::_UpdateActiveMode()
937 {
938 	_UpdateActiveMode(current_workspace());
939 }
940 
941 
942 void
943 ScreenWindow::_UpdateActiveMode(int32 workspace)
944 {
945 	// Usually, this function gets called after a mode
946 	// has been set manually; still, as the graphics driver
947 	// is free to fiddle with mode passed, we better ask
948 	// what kind of mode we actually got
949 	if (fScreenMode.Get(fActive, workspace) == B_OK) {
950 		fSelected = fActive;
951 
952 		_UpdateMonitor();
953 		_BuildSupportedColorSpaces();
954 		_UpdateControls();
955 	}
956 }
957 
958 
959 void
960 ScreenWindow::_UpdateWorkspaceButtons()
961 {
962 	uint32 columns;
963 	uint32 rows;
964 	BPrivate::get_workspaces_layout(&columns, &rows);
965 
966 	// Set the max values enabling/disabling the up/down arrows
967 
968 	if (rows == 1)
969 		fColumnsControl->SetMaxValue(32);
970 	else if (rows == 2)
971 		fColumnsControl->SetMaxValue(16);
972 	else if (rows <= 4)
973 		fColumnsControl->SetMaxValue(8);
974 	else if (rows <= 8)
975 		fColumnsControl->SetMaxValue(4);
976 	else if (rows <= 16)
977 		fColumnsControl->SetMaxValue(2);
978 	else if (rows <= 32)
979 		fColumnsControl->SetMaxValue(1);
980 
981 	if (columns == 1)
982 		fRowsControl->SetMaxValue(32);
983 	else if (columns == 2)
984 		fRowsControl->SetMaxValue(16);
985 	else if (columns <= 4)
986 		fRowsControl->SetMaxValue(8);
987 	else if (columns <= 8)
988 		fRowsControl->SetMaxValue(4);
989 	else if (columns <= 16)
990 		fRowsControl->SetMaxValue(2);
991 	else if (columns <= 32)
992 		fRowsControl->SetMaxValue(1);
993 }
994 
995 
996 void
997 ScreenWindow::ScreenChanged(BRect frame, color_space mode)
998 {
999 	// move window on screen, if necessary
1000 	if (frame.right <= Frame().right
1001 		&& frame.bottom <= Frame().bottom) {
1002 		MoveTo((frame.Width() - Frame().Width()) / 2,
1003 			(frame.Height() - Frame().Height()) / 2);
1004 	}
1005 }
1006 
1007 
1008 void
1009 ScreenWindow::WorkspaceActivated(int32 workspace, bool state)
1010 {
1011 	if (fScreenMode.GetOriginalMode(fOriginal, workspace) == B_OK) {
1012 		_UpdateActiveMode(workspace);
1013 
1014 		BMessage message(UPDATE_DESKTOP_COLOR_MSG);
1015 		PostMessage(&message, fMonitorView);
1016 	}
1017 }
1018 
1019 
1020 void
1021 ScreenWindow::MessageReceived(BMessage* message)
1022 {
1023 	switch (message->what) {
1024 		case WORKSPACE_CHECK_MSG:
1025 			_CheckApplyEnabled();
1026 			break;
1027 
1028 		case kMsgWorkspaceColumnsChanged:
1029 		{
1030 			uint32 newColumns = (uint32)fColumnsControl->Value();
1031 
1032 			uint32 rows;
1033 			BPrivate::get_workspaces_layout(NULL, &rows);
1034 			BPrivate::set_workspaces_layout(newColumns, rows);
1035 
1036 			_UpdateWorkspaceButtons();
1037 			fRowsControl->SetValue(rows);
1038 				// enables/disables up/down arrows
1039 			_CheckApplyEnabled();
1040 
1041 			break;
1042 		}
1043 
1044 		case kMsgWorkspaceRowsChanged:
1045 		{
1046 			uint32 newRows = (uint32)fRowsControl->Value();
1047 
1048 			uint32 columns;
1049 			BPrivate::get_workspaces_layout(&columns, NULL);
1050 			BPrivate::set_workspaces_layout(columns, newRows);
1051 
1052 			_UpdateWorkspaceButtons();
1053 			fColumnsControl->SetValue(columns);
1054 				// enables/disables up/down arrows
1055 			_CheckApplyEnabled();
1056 			break;
1057 		}
1058 
1059 		case POP_RESOLUTION_MSG:
1060 		{
1061 			message->FindInt32("width", &fSelected.width);
1062 			message->FindInt32("height", &fSelected.height);
1063 
1064 			_CheckColorMenu();
1065 			_CheckRefreshMenu();
1066 
1067 			_UpdateMonitorView();
1068 			_UpdateRefreshControl();
1069 
1070 			_CheckApplyEnabled();
1071 			break;
1072 		}
1073 
1074 		case POP_COLORS_MSG:
1075 		{
1076 			int32 space;
1077 			if (message->FindInt32("space", &space) != B_OK)
1078 				break;
1079 
1080 			int32 index;
1081 			if (message->FindInt32("index", &index) == B_OK
1082 				&& fColorsMenu->ItemAt(index) != NULL)
1083 				fUserSelectedColorSpace = fColorsMenu->ItemAt(index);
1084 
1085 			fSelected.space = (color_space)space;
1086 			_UpdateColorLabel();
1087 
1088 			_CheckApplyEnabled();
1089 			break;
1090 		}
1091 
1092 		case POP_REFRESH_MSG:
1093 		{
1094 			message->FindFloat("refresh", &fSelected.refresh);
1095 			fOtherRefresh->SetLabel(B_TRANSLATE("Other" B_UTF8_ELLIPSIS));
1096 				// revert "Other…" label - it might have a refresh rate prefix
1097 
1098 			_CheckApplyEnabled();
1099 			break;
1100 		}
1101 
1102 		case POP_OTHER_REFRESH_MSG:
1103 		{
1104 			// make sure menu shows something useful
1105 			_UpdateRefreshControl();
1106 
1107 			float min = 0, max = 999;
1108 			fScreenMode.GetRefreshLimits(fSelected, min, max);
1109 			if (min < gMinRefresh)
1110 				min = gMinRefresh;
1111 			if (max > gMaxRefresh)
1112 				max = gMaxRefresh;
1113 
1114 			monitor_info info;
1115 			if (fScreenMode.GetMonitorInfo(info) == B_OK) {
1116 				min = max_c(info.min_vertical_frequency, min);
1117 				max = min_c(info.max_vertical_frequency, max);
1118 			}
1119 
1120 			RefreshWindow *fRefreshWindow = new RefreshWindow(
1121 				fRefreshField->ConvertToScreen(B_ORIGIN), fSelected.refresh,
1122 				min, max);
1123 			fRefreshWindow->Show();
1124 			break;
1125 		}
1126 
1127 		case SET_CUSTOM_REFRESH_MSG:
1128 		{
1129 			// user pressed "done" in "Other…" refresh dialog;
1130 			// select the refresh rate chosen
1131 			message->FindFloat("refresh", &fSelected.refresh);
1132 
1133 			_UpdateRefreshControl();
1134 			_CheckApplyEnabled();
1135 			break;
1136 		}
1137 
1138 		case POP_COMBINE_DISPLAYS_MSG:
1139 		{
1140 			// new combine mode has bee chosen
1141 			int32 mode;
1142 			if (message->FindInt32("mode", &mode) == B_OK)
1143 				fSelected.combine = (combine_mode)mode;
1144 
1145 			_CheckResolutionMenu();
1146 			_CheckApplyEnabled();
1147 			break;
1148 		}
1149 
1150 		case POP_SWAP_DISPLAYS_MSG:
1151 			message->FindBool("swap", &fSelected.swap_displays);
1152 			_CheckApplyEnabled();
1153 			break;
1154 
1155 		case POP_USE_LAPTOP_PANEL_MSG:
1156 			message->FindBool("use", &fSelected.use_laptop_panel);
1157 			_CheckApplyEnabled();
1158 			break;
1159 
1160 		case POP_TV_STANDARD_MSG:
1161 			message->FindInt32("tv_standard", (int32 *)&fSelected.tv_standard);
1162 			_CheckApplyEnabled();
1163 			break;
1164 
1165 		case BUTTON_LAUNCH_BACKGROUNDS_MSG:
1166 			if (be_roster->Launch(kBackgroundsSignature) == B_ALREADY_RUNNING) {
1167 				app_info info;
1168 				be_roster->GetAppInfo(kBackgroundsSignature, &info);
1169 				be_roster->ActivateApp(info.team);
1170 			}
1171 			break;
1172 
1173 		case BUTTON_DEFAULTS_MSG:
1174 		{
1175 			// TODO: get preferred settings of screen
1176 			fSelected.width = 640;
1177 			fSelected.height = 480;
1178 			fSelected.space = B_CMAP8;
1179 			fSelected.refresh = 60.0;
1180 			fSelected.combine = kCombineDisable;
1181 			fSelected.swap_displays = false;
1182 			fSelected.use_laptop_panel = false;
1183 			fSelected.tv_standard = 0;
1184 
1185 			// TODO: workspace defaults
1186 
1187 			_UpdateControls();
1188 			break;
1189 		}
1190 
1191 		case BUTTON_UNDO_MSG:
1192 			fUndoScreenMode.Revert();
1193 			_UpdateActiveMode();
1194 			break;
1195 
1196 		case BUTTON_REVERT_MSG:
1197 		{
1198 			fModified = false;
1199 			fBootWorkspaceApplied = false;
1200 
1201 			// ScreenMode::Revert() assumes that we first set the correct
1202 			// number of workspaces
1203 
1204 			BPrivate::set_workspaces_layout(fOriginalWorkspacesColumns,
1205 				fOriginalWorkspacesRows);
1206 			_UpdateWorkspaceButtons();
1207 
1208 			fScreenMode.Revert();
1209 
1210 			BScreen screen(this);
1211 			screen.SetBrightness(fOriginalBrightness);
1212 			fBrightnessSlider->SetValue(fOriginalBrightness * 255);
1213 
1214 			_UpdateActiveMode();
1215 			break;
1216 		}
1217 
1218 		case BUTTON_APPLY_MSG:
1219 			_Apply();
1220 			break;
1221 
1222 		case MAKE_INITIAL_MSG:
1223 			// user pressed "keep" in confirmation dialog
1224 			fModified = true;
1225 			_UpdateActiveMode();
1226 			break;
1227 
1228 		case UPDATE_DESKTOP_COLOR_MSG:
1229 			PostMessage(message, fMonitorView);
1230 			break;
1231 
1232 		case SLIDER_BRIGHTNESS_MSG:
1233 		{
1234 			BScreen screen(this);
1235 			screen.SetBrightness(message->FindInt32("be:value") / 255.f);
1236 			_CheckApplyEnabled();
1237 			break;
1238 		}
1239 
1240 		default:
1241 			BWindow::MessageReceived(message);
1242 	}
1243 }
1244 
1245 
1246 status_t
1247 ScreenWindow::_WriteVesaModeFile(const screen_mode& mode) const
1248 {
1249 	BPath path;
1250 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);
1251 	if (status < B_OK)
1252 		return status;
1253 
1254 	path.Append("kernel/drivers");
1255 	status = create_directory(path.Path(), 0755);
1256 	if (status < B_OK)
1257 		return status;
1258 
1259 	path.Append("vesa");
1260 	BFile file;
1261 	status = file.SetTo(path.Path(), B_CREATE_FILE | B_WRITE_ONLY | B_ERASE_FILE);
1262 	if (status < B_OK)
1263 		return status;
1264 
1265 	char buffer[256];
1266 	snprintf(buffer, sizeof(buffer), "mode %" B_PRId32 " %" B_PRId32 " %"
1267 		B_PRId32 "\n", mode.width, mode.height, mode.BitsPerPixel());
1268 
1269 	ssize_t bytesWritten = file.Write(buffer, strlen(buffer));
1270 	if (bytesWritten < B_OK)
1271 		return bytesWritten;
1272 
1273 	return B_OK;
1274 }
1275 
1276 
1277 void
1278 ScreenWindow::_BuildSupportedColorSpaces()
1279 {
1280 	fSupportedColorSpaces = 0;
1281 
1282 	for (int32 i = 0; i < kColorSpaceCount; i++) {
1283 		for (int32 j = 0; j < fScreenMode.CountModes(); j++) {
1284 			if (fScreenMode.ModeAt(j).space == kColorSpaces[i].space) {
1285 				fSupportedColorSpaces |= 1 << i;
1286 				break;
1287 			}
1288 		}
1289 	}
1290 }
1291 
1292 
1293 void
1294 ScreenWindow::_CheckApplyEnabled()
1295 {
1296 	bool applyEnabled = true;
1297 
1298 	if (fSelected == fActive) {
1299 		applyEnabled = false;
1300 		if (fAllWorkspacesItem->IsMarked()) {
1301 			screen_mode screenMode;
1302 			const int32 workspaceCount = count_workspaces();
1303 			for (int32 i = 0; i < workspaceCount; i++) {
1304 				fScreenMode.Get(screenMode, i);
1305 				if (screenMode != fSelected) {
1306 					applyEnabled = true;
1307 					break;
1308 				}
1309 			}
1310 		}
1311 	}
1312 
1313 	fApplyButton->SetEnabled(applyEnabled);
1314 
1315 	uint32 columns;
1316 	uint32 rows;
1317 	BPrivate::get_workspaces_layout(&columns, &rows);
1318 
1319 	BScreen screen(this);
1320 	float brightness = -1;
1321 	screen.GetBrightness(&brightness);
1322 
1323 	fRevertButton->SetEnabled(columns != fOriginalWorkspacesColumns
1324 		|| rows != fOriginalWorkspacesRows
1325 		|| brightness != fOriginalBrightness
1326 		|| fSelected != fOriginal);
1327 }
1328 
1329 
1330 void
1331 ScreenWindow::_UpdateOriginal()
1332 {
1333 	BPrivate::get_workspaces_layout(&fOriginalWorkspacesColumns,
1334 		&fOriginalWorkspacesRows);
1335 
1336 	fScreenMode.Get(fOriginal);
1337 	fScreenMode.UpdateOriginalModes();
1338 }
1339 
1340 
1341 void
1342 ScreenWindow::_UpdateMonitor()
1343 {
1344 	monitor_info info;
1345 	float diagonalInches;
1346 	status_t status = fScreenMode.GetMonitorInfo(info, &diagonalInches);
1347 	if (status == B_OK) {
1348 		char text[512];
1349 		snprintf(text, sizeof(text), "%s%s%s %g\"", info.vendor,
1350 			info.name[0] ? " " : "", info.name, diagonalInches);
1351 
1352 		fScreenBox->SetLabel(text);
1353 	} else {
1354 		fScreenBox->SetLabel(B_TRANSLATE("Display info"));
1355 	}
1356 
1357 	// Add info about the graphics device
1358 
1359 	accelerant_device_info deviceInfo;
1360 
1361 	if (fScreenMode.GetDeviceInfo(deviceInfo) == B_OK) {
1362 		BString deviceString;
1363 
1364 		if (deviceInfo.name[0] && deviceInfo.chipset[0]) {
1365 			deviceString.SetToFormat("%s (%s)", deviceInfo.name,
1366 				deviceInfo.chipset);
1367 		} else if (deviceInfo.name[0] || deviceInfo.chipset[0]) {
1368 			deviceString
1369 				= deviceInfo.name[0] ? deviceInfo.name : deviceInfo.chipset;
1370 		}
1371 
1372 		fDeviceInfo->SetText(deviceString);
1373 	}
1374 
1375 
1376 	char text[512];
1377 	size_t length = 0;
1378 	text[0] = 0;
1379 
1380 	if (status == B_OK) {
1381 		if (info.min_horizontal_frequency != 0
1382 			&& info.min_vertical_frequency != 0
1383 			&& info.max_pixel_clock != 0) {
1384 			length = snprintf(text, sizeof(text),
1385 				B_TRANSLATE("Horizonal frequency:\t%lu - %lu kHz\n"
1386 					"Vertical frequency:\t%lu - %lu Hz\n\n"
1387 					"Maximum pixel clock:\t%g MHz"),
1388 				(long unsigned)info.min_horizontal_frequency,
1389 				(long unsigned)info.max_horizontal_frequency,
1390 				(long unsigned)info.min_vertical_frequency,
1391 				(long unsigned)info.max_vertical_frequency,
1392 				info.max_pixel_clock / 1000.0);
1393 		}
1394 		if (info.serial_number[0] && length < sizeof(text)) {
1395 			if (length > 0) {
1396 				text[length++] = '\n';
1397 				text[length++] = '\n';
1398 				text[length] = '\0';
1399 			}
1400 			length += snprintf(text + length, sizeof(text) - length,
1401 				B_TRANSLATE("Serial no.: %s"), info.serial_number);
1402 			if (info.produced.week != 0 && info.produced.year != 0
1403 				&& length < sizeof(text)) {
1404 				length += snprintf(text + length, sizeof(text) - length,
1405 					" (%u/%u)", info.produced.week, info.produced.year);
1406 			}
1407 		}
1408 	}
1409 
1410 	if (text[0])
1411 		fMonitorView->SetToolTip(text);
1412 }
1413 
1414 
1415 void
1416 ScreenWindow::_UpdateColorLabel()
1417 {
1418 	BString string;
1419 	string << fSelected.BitsPerPixel() << " " << B_TRANSLATE("bits/pixel");
1420 	fColorsMenu->Superitem()->SetLabel(string.String());
1421 }
1422 
1423 
1424 void
1425 ScreenWindow::_Apply()
1426 {
1427 	// make checkpoint, so we can undo these changes
1428 	fUndoScreenMode.UpdateOriginalModes();
1429 
1430 	status_t status = fScreenMode.Set(fSelected);
1431 	if (status == B_OK) {
1432 		// use the mode that has eventually been set and
1433 		// thus we know to be working; it can differ from
1434 		// the mode selected by user due to hardware limitation
1435 		display_mode newMode;
1436 		BScreen screen(this);
1437 		screen.GetMode(&newMode);
1438 
1439 		if (fAllWorkspacesItem->IsMarked()) {
1440 			int32 originatingWorkspace = current_workspace();
1441 			const int32 workspaceCount = count_workspaces();
1442 			for (int32 i = 0; i < workspaceCount; i++) {
1443 				if (i != originatingWorkspace)
1444 					screen.SetMode(i, &newMode, true);
1445 			}
1446 			fBootWorkspaceApplied = true;
1447 		} else {
1448 			if (current_workspace() == 0)
1449 				fBootWorkspaceApplied = true;
1450 		}
1451 
1452 		fActive = fSelected;
1453 
1454 		// TODO: only show alert when this is an unknown mode
1455 		BAlert* window = new AlertWindow(this);
1456 		window->Go(NULL);
1457 	} else {
1458 		char message[256];
1459 		snprintf(message, sizeof(message),
1460 			B_TRANSLATE("The screen mode could not be set:\n\t%s\n"),
1461 			screen_errors(status));
1462 		BAlert* alert = new BAlert(B_TRANSLATE("Warning"), message,
1463 			B_TRANSLATE("OK"), NULL, NULL,
1464 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1465 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1466 		alert->Go();
1467 	}
1468 }
1469