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