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