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