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
74const char* kBackgroundsSignature = "application/x-vnd.Haiku-Backgrounds";
75
76// list of officially supported colour spaces
77static 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};
88static const int32 kColorSpaceCount = B_COUNT_OF(kColorSpaces);
89
90// list of standard refresh rates
91static const int32 kRefreshRates[] = { 60, 70, 72, 75, 80, 85, 95, 100 };
92static const int32 kRefreshRateCount = B_COUNT_OF(kRefreshRates);
93
94// list of combine modes
95static 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};
103static const int32 kCombineModeCount = B_COUNT_OF(kCombineModes);
104
105
106static BString
107tv_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
131static void
132resolution_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
140static void
141refresh_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
153static const char*
154screen_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
170ScreenWindow::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
623ScreenWindow::~ScreenWindow()
624{
625	delete fSettings;
626}
627
628
629bool
630ScreenWindow::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*/
659void
660ScreenWindow::_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*/
687void
688ScreenWindow::_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. */
774void
775ScreenWindow::_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 */
792void
793ScreenWindow::_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
830void
831ScreenWindow::_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
841void
842ScreenWindow::_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 */
934void
935ScreenWindow::_UpdateActiveMode()
936{
937	_UpdateActiveMode(current_workspace());
938}
939
940
941void
942ScreenWindow::_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
958void
959ScreenWindow::_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
995void
996ScreenWindow::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
1007void
1008ScreenWindow::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
1019void
1020ScreenWindow::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
1245status_t
1246ScreenWindow::_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
1276void
1277ScreenWindow::_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
1292void
1293ScreenWindow::_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
1329void
1330ScreenWindow::_UpdateOriginal()
1331{
1332	BPrivate::get_workspaces_layout(&fOriginalWorkspacesColumns,
1333		&fOriginalWorkspacesRows);
1334
1335	fScreenMode.Get(fOriginal);
1336	fScreenMode.UpdateOriginalModes();
1337}
1338
1339
1340void
1341ScreenWindow::_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				info.min_horizontal_frequency, info.max_horizontal_frequency,
1388				info.min_vertical_frequency, info.max_vertical_frequency,
1389				info.max_pixel_clock / 1000.0);
1390		}
1391		if (info.serial_number[0] && length < sizeof(text)) {
1392			if (length > 0) {
1393				text[length++] = '\n';
1394				text[length++] = '\n';
1395				text[length] = '\0';
1396			}
1397			length += snprintf(text + length, sizeof(text) - length,
1398				B_TRANSLATE("Serial no.: %s"), info.serial_number);
1399			if (info.produced.week != 0 && info.produced.year != 0
1400				&& length < sizeof(text)) {
1401				length += snprintf(text + length, sizeof(text) - length,
1402					" (%u/%u)", info.produced.week, info.produced.year);
1403			}
1404		}
1405	}
1406
1407	if (text[0])
1408		fMonitorView->SetToolTip(text);
1409}
1410
1411
1412void
1413ScreenWindow::_UpdateColorLabel()
1414{
1415	BString string;
1416	string << fSelected.BitsPerPixel() << " " << B_TRANSLATE("bits/pixel");
1417	fColorsMenu->Superitem()->SetLabel(string.String());
1418}
1419
1420
1421void
1422ScreenWindow::_Apply()
1423{
1424	// make checkpoint, so we can undo these changes
1425	fUndoScreenMode.UpdateOriginalModes();
1426
1427	status_t status = fScreenMode.Set(fSelected);
1428	if (status == B_OK) {
1429		// use the mode that has eventually been set and
1430		// thus we know to be working; it can differ from
1431		// the mode selected by user due to hardware limitation
1432		display_mode newMode;
1433		BScreen screen(this);
1434		screen.GetMode(&newMode);
1435
1436		if (fAllWorkspacesItem->IsMarked()) {
1437			int32 originatingWorkspace = current_workspace();
1438			const int32 workspaceCount = count_workspaces();
1439			for (int32 i = 0; i < workspaceCount; i++) {
1440				if (i != originatingWorkspace)
1441					screen.SetMode(i, &newMode, true);
1442			}
1443			fBootWorkspaceApplied = true;
1444		} else {
1445			if (current_workspace() == 0)
1446				fBootWorkspaceApplied = true;
1447		}
1448
1449		fActive = fSelected;
1450
1451		// TODO: only show alert when this is an unknown mode
1452		BAlert* window = new AlertWindow(this);
1453		window->Go(NULL);
1454	} else {
1455		char message[256];
1456		snprintf(message, sizeof(message),
1457			B_TRANSLATE("The screen mode could not be set:\n\t%s\n"),
1458			screen_errors(status));
1459		BAlert* alert = new BAlert(B_TRANSLATE("Warning"), message,
1460			B_TRANSLATE("OK"), NULL, NULL,
1461			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1462		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1463		alert->Go();
1464	}
1465}
1466