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