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