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