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