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