1 /* 2 * Copyright 2006-2010, Axel Dörfler, axeld@pinc-software.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "ApplicationTypeWindow.h" 8 #include "DropTargetListView.h" 9 #include "FileTypes.h" 10 #include "IconView.h" 11 #include "PreferredAppMenu.h" 12 #include "StringView.h" 13 #include "TypeListWindow.h" 14 15 #include <Application.h> 16 #include <Bitmap.h> 17 #include <Box.h> 18 #include <Button.h> 19 #include <Catalog.h> 20 #include <CheckBox.h> 21 #include <ControlLook.h> 22 #include <File.h> 23 #include <GroupView.h> 24 #include <LayoutBuilder.h> 25 #include <ListView.h> 26 #include <Locale.h> 27 #include <MenuBar.h> 28 #include <MenuField.h> 29 #include <MenuItem.h> 30 #include <Mime.h> 31 #include <NodeInfo.h> 32 #include <PopUpMenu.h> 33 #include <RadioButton.h> 34 #include <Roster.h> 35 #include <ScrollView.h> 36 #include <StringView.h> 37 #include <TextControl.h> 38 39 #include <ctype.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <strings.h> 43 44 45 #undef B_TRANSLATION_CONTEXT 46 #define B_TRANSLATION_CONTEXT "Application Type Window" 47 48 49 const uint32 kMsgSave = 'save'; 50 const uint32 kMsgSignatureChanged = 'sgch'; 51 const uint32 kMsgToggleAppFlags = 'tglf'; 52 const uint32 kMsgAppFlagsChanged = 'afch'; 53 54 const uint32 kMsgIconChanged = 'icch'; 55 const uint32 kMsgTypeIconsChanged = 'tich'; 56 57 const uint32 kMsgVersionInfoChanged = 'tvch'; 58 59 const uint32 kMsgTypeSelected = 'tpsl'; 60 const uint32 kMsgAddType = 'adtp'; 61 const uint32 kMsgTypeAdded = 'tpad'; 62 const uint32 kMsgRemoveType = 'rmtp'; 63 const uint32 kMsgTypeRemoved = 'tprm'; 64 65 66 //! TextView that filters the tab key to be able to tab-navigate while editing 67 class TabFilteringTextView : public BTextView { 68 public: 69 TabFilteringTextView(const char* name, 70 uint32 changedMessageWhat = 0); 71 virtual ~TabFilteringTextView(); 72 73 virtual void InsertText(const char* text, int32 length, 74 int32 offset, const text_run_array* runs); 75 virtual void DeleteText(int32 fromOffset, int32 toOffset); 76 virtual void KeyDown(const char* bytes, int32 count); 77 virtual void TargetedByScrollView(BScrollView* scroller); 78 virtual void MakeFocus(bool focused = true); 79 80 private: 81 BScrollView* fScrollView; 82 uint32 fChangedMessageWhat; 83 }; 84 85 86 class SupportedTypeItem : public BStringItem { 87 public: 88 SupportedTypeItem(const char* type); 89 virtual ~SupportedTypeItem(); 90 91 const char* Type() const { return fType.String(); } 92 ::Icon& Icon() { return fIcon; } 93 94 void SetIcon(::Icon* icon); 95 void SetIcon(entry_ref& ref, const char* type); 96 97 static int Compare(const void* _a, const void* _b); 98 99 private: 100 BString fType; 101 ::Icon fIcon; 102 }; 103 104 105 class SupportedTypeListView : public DropTargetListView { 106 public: 107 SupportedTypeListView(const char* name, 108 list_view_type 109 type = B_SINGLE_SELECTION_LIST, 110 uint32 flags = B_WILL_DRAW 111 | B_FRAME_EVENTS | B_NAVIGABLE); 112 virtual ~SupportedTypeListView(); 113 114 virtual void MessageReceived(BMessage* message); 115 virtual bool AcceptsDrag(const BMessage* message); 116 }; 117 118 119 // #pragma mark - 120 121 122 TabFilteringTextView::TabFilteringTextView(const char* name, 123 uint32 changedMessageWhat) 124 : 125 BTextView(name, B_WILL_DRAW | B_PULSE_NEEDED | B_NAVIGABLE), 126 fScrollView(NULL), 127 fChangedMessageWhat(changedMessageWhat) 128 { 129 } 130 131 132 TabFilteringTextView::~TabFilteringTextView() 133 { 134 } 135 136 137 void 138 TabFilteringTextView::InsertText(const char* text, int32 length, int32 offset, 139 const text_run_array* runs) 140 { 141 BTextView::InsertText(text, length, offset, runs); 142 if (fChangedMessageWhat != 0) 143 Window()->PostMessage(fChangedMessageWhat); 144 } 145 146 147 void 148 TabFilteringTextView::DeleteText(int32 fromOffset, int32 toOffset) 149 { 150 BTextView::DeleteText(fromOffset, toOffset); 151 if (fChangedMessageWhat != 0) 152 Window()->PostMessage(fChangedMessageWhat); 153 } 154 155 156 void 157 TabFilteringTextView::KeyDown(const char* bytes, int32 count) 158 { 159 if (bytes[0] == B_TAB) 160 BView::KeyDown(bytes, count); 161 else 162 BTextView::KeyDown(bytes, count); 163 } 164 165 166 void 167 TabFilteringTextView::TargetedByScrollView(BScrollView* scroller) 168 { 169 fScrollView = scroller; 170 } 171 172 173 void 174 TabFilteringTextView::MakeFocus(bool focused) 175 { 176 BTextView::MakeFocus(focused); 177 178 if (fScrollView) 179 fScrollView->SetBorderHighlighted(focused); 180 } 181 182 183 // #pragma mark - 184 185 186 SupportedTypeItem::SupportedTypeItem(const char* type) 187 : BStringItem(type), 188 fType(type) 189 { 190 BMimeType mimeType(type); 191 192 char description[B_MIME_TYPE_LENGTH]; 193 if (mimeType.GetShortDescription(description) == B_OK && description[0]) 194 SetText(description); 195 } 196 197 198 SupportedTypeItem::~SupportedTypeItem() 199 { 200 } 201 202 203 void 204 SupportedTypeItem::SetIcon(::Icon* icon) 205 { 206 if (icon != NULL) 207 fIcon = *icon; 208 else 209 fIcon.Unset(); 210 } 211 212 213 void 214 SupportedTypeItem::SetIcon(entry_ref& ref, const char* type) 215 { 216 fIcon.SetTo(ref, type); 217 } 218 219 220 /*static*/ 221 int 222 SupportedTypeItem::Compare(const void* _a, const void* _b) 223 { 224 const SupportedTypeItem* a = *(const SupportedTypeItem**)_a; 225 const SupportedTypeItem* b = *(const SupportedTypeItem**)_b; 226 227 int compare = strcasecmp(a->Text(), b->Text()); 228 if (compare != 0) 229 return compare; 230 231 return strcasecmp(a->Type(), b->Type()); 232 } 233 234 235 // #pragma mark - 236 237 238 SupportedTypeListView::SupportedTypeListView(const char* name, 239 list_view_type type, uint32 flags) 240 : 241 DropTargetListView(name, type, flags) 242 { 243 } 244 245 246 SupportedTypeListView::~SupportedTypeListView() 247 { 248 } 249 250 251 void 252 SupportedTypeListView::MessageReceived(BMessage* message) 253 { 254 if (message->WasDropped() && AcceptsDrag(message)) { 255 // Add unique types 256 entry_ref ref; 257 for (int32 index = 0; message->FindRef("refs", index++, &ref) == B_OK; ) { 258 BNode node(&ref); 259 BNodeInfo info(&node); 260 if (node.InitCheck() != B_OK || info.InitCheck() != B_OK) 261 continue; 262 263 // TODO: we could identify the file in case it doesn't have a type... 264 char type[B_MIME_TYPE_LENGTH]; 265 if (info.GetType(type) != B_OK) 266 continue; 267 268 // check if that type is already in our list 269 bool found = false; 270 for (int32 i = CountItems(); i-- > 0;) { 271 SupportedTypeItem* item = (SupportedTypeItem*)ItemAt(i); 272 if (!strcmp(item->Text(), type)) { 273 found = true; 274 break; 275 } 276 } 277 278 if (!found) { 279 // add type 280 AddItem(new SupportedTypeItem(type)); 281 } 282 } 283 284 SortItems(&SupportedTypeItem::Compare); 285 } else 286 DropTargetListView::MessageReceived(message); 287 } 288 289 290 bool 291 SupportedTypeListView::AcceptsDrag(const BMessage* message) 292 { 293 type_code type; 294 return message->GetInfo("refs", &type) == B_OK && type == B_REF_TYPE; 295 } 296 297 298 // #pragma mark - 299 300 301 ApplicationTypeWindow::ApplicationTypeWindow(const BMessage& settings, const BEntry& entry) 302 : 303 BWindow(_Frame(settings), B_TRANSLATE("Application type"), B_TITLED_WINDOW, 304 B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS | B_FRAME_EVENTS | B_AUTO_UPDATE_SIZE_LIMITS), 305 fChangedProperties(0) 306 { 307 float padding = be_control_look->DefaultItemSpacing(); 308 BAlignment labelAlignment = be_control_look->DefaultLabelAlignment(); 309 310 BMenuBar* menuBar = new BMenuBar((char*)NULL); 311 menuBar->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP)); 312 313 BMenu* menu = new BMenu(B_TRANSLATE("File")); 314 fSaveMenuItem = new BMenuItem(B_TRANSLATE("Save"), 315 new BMessage(kMsgSave), 'S'); 316 fSaveMenuItem->SetEnabled(false); 317 menu->AddItem(fSaveMenuItem); 318 BMenuItem* item; 319 menu->AddItem(item = new BMenuItem( 320 B_TRANSLATE("Save into resource file" B_UTF8_ELLIPSIS), NULL)); 321 item->SetEnabled(false); 322 323 menu->AddSeparatorItem(); 324 menu->AddItem(new BMenuItem(B_TRANSLATE("Close"), 325 new BMessage(B_QUIT_REQUESTED), 'W', B_COMMAND_KEY)); 326 menuBar->AddItem(menu); 327 328 // Signature 329 330 fSignatureControl = new BTextControl("signature", 331 B_TRANSLATE("Signature:"), NULL, new BMessage(kMsgSignatureChanged)); 332 fSignatureControl->SetModificationMessage( 333 new BMessage(kMsgSignatureChanged)); 334 335 // filter out invalid characters that can't be part of a MIME type name 336 BTextView* textView = fSignatureControl->TextView(); 337 textView->SetMaxBytes(B_MIME_TYPE_LENGTH); 338 const char* disallowedCharacters = "<>@,;:\"()[]?= "; 339 for (int32 i = 0; disallowedCharacters[i]; i++) { 340 textView->DisallowChar(disallowedCharacters[i]); 341 } 342 343 // "Application Flags" group 344 345 BBox* flagsBox = new BBox("flagsBox"); 346 347 fFlagsCheckBox = new BCheckBox("flags", B_TRANSLATE("Application flags"), 348 new BMessage(kMsgToggleAppFlags)); 349 fFlagsCheckBox->SetValue(B_CONTROL_ON); 350 351 fSingleLaunchButton = new BRadioButton("single", 352 B_TRANSLATE("Single launch"), new BMessage(kMsgAppFlagsChanged)); 353 354 fMultipleLaunchButton = new BRadioButton("multiple", 355 B_TRANSLATE("Multiple launch"), new BMessage(kMsgAppFlagsChanged)); 356 357 fExclusiveLaunchButton = new BRadioButton("exclusive", 358 B_TRANSLATE("Exclusive launch"), new BMessage(kMsgAppFlagsChanged)); 359 360 fArgsOnlyCheckBox = new BCheckBox("args only", B_TRANSLATE("Args only"), 361 new BMessage(kMsgAppFlagsChanged)); 362 363 fBackgroundAppCheckBox = new BCheckBox("background", 364 B_TRANSLATE("Background app"), new BMessage(kMsgAppFlagsChanged)); 365 366 BLayoutBuilder::Grid<>(flagsBox, 0, 0) 367 .SetInsets(padding, padding * 2, padding, padding) 368 .Add(fSingleLaunchButton, 0, 0).Add(fArgsOnlyCheckBox, 1, 0) 369 .Add(fMultipleLaunchButton, 0, 1).Add(fBackgroundAppCheckBox, 1, 1) 370 .Add(fExclusiveLaunchButton, 0, 2); 371 flagsBox->SetLabel(fFlagsCheckBox); 372 373 // "Icon" group 374 375 BBox* iconBox = new BBox("IconBox"); 376 iconBox->SetLabel(B_TRANSLATE("Icon")); 377 fIconView = new IconView("icon"); 378 fIconView->SetModificationMessage(new BMessage(kMsgIconChanged)); 379 BLayoutBuilder::Group<>(iconBox, B_HORIZONTAL) 380 .SetInsets(padding, padding * 2, padding, padding) 381 .Add(fIconView); 382 383 // "Supported Types" group 384 385 BBox* typeBox = new BBox("typesBox"); 386 typeBox->SetLabel(B_TRANSLATE("Supported types")); 387 388 fTypeListView = new SupportedTypeListView("Suppported Types", 389 B_SINGLE_SELECTION_LIST); 390 fTypeListView->SetSelectionMessage(new BMessage(kMsgTypeSelected)); 391 392 BScrollView* scrollView = new BScrollView("type scrollview", fTypeListView, 393 B_FRAME_EVENTS | B_WILL_DRAW, false, true); 394 395 fAddTypeButton = new BButton("add type", 396 B_TRANSLATE("Add" B_UTF8_ELLIPSIS), new BMessage(kMsgAddType)); 397 398 fRemoveTypeButton = new BButton("remove type", B_TRANSLATE("Remove"), 399 new BMessage(kMsgRemoveType)); 400 401 fTypeIconView = new IconView("type icon"); 402 BGroupView* iconHolder = new BGroupView(B_HORIZONTAL); 403 iconHolder->AddChild(fTypeIconView); 404 fTypeIconView->SetModificationMessage(new BMessage(kMsgTypeIconsChanged)); 405 406 BLayoutBuilder::Grid<>(typeBox, padding, padding) 407 .SetInsets(padding, padding * 2, padding, padding) 408 .Add(scrollView, 0, 0, 1, 5) 409 .Add(fAddTypeButton, 1, 0, 1, 2) 410 .Add(fRemoveTypeButton, 1, 2, 1, 2) 411 .Add(iconHolder, 2, 1, 1, 2) 412 .SetColumnWeight(0, 3) 413 .SetColumnWeight(1, 2) 414 .SetColumnWeight(2, 1); 415 416 iconHolder->SetExplicitAlignment( 417 BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE)); 418 419 // "Version Info" group 420 421 BBox* versionBox = new BBox("versionBox"); 422 versionBox->SetLabel(B_TRANSLATE("Version info")); 423 424 fMajorVersionControl = new BTextControl("version major", 425 B_TRANSLATE("Version:"), NULL, new BMessage(kMsgVersionInfoChanged)); 426 _MakeNumberTextControl(fMajorVersionControl); 427 428 fMiddleVersionControl = new BTextControl("version middle", ".", NULL, 429 new BMessage(kMsgVersionInfoChanged)); 430 _MakeNumberTextControl(fMiddleVersionControl); 431 432 fMinorVersionControl = new BTextControl("version minor", ".", NULL, 433 new BMessage(kMsgVersionInfoChanged)); 434 _MakeNumberTextControl(fMinorVersionControl); 435 436 fVarietyMenu = new BPopUpMenu("variety", true, true); 437 fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Development"), 438 new BMessage(kMsgVersionInfoChanged))); 439 fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Alpha"), 440 new BMessage(kMsgVersionInfoChanged))); 441 fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Beta"), 442 new BMessage(kMsgVersionInfoChanged))); 443 fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Gamma"), 444 new BMessage(kMsgVersionInfoChanged))); 445 item = new BMenuItem(B_TRANSLATE("Golden master"), 446 new BMessage(kMsgVersionInfoChanged)); 447 fVarietyMenu->AddItem(item); 448 item->SetMarked(true); 449 fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Final"), 450 new BMessage(kMsgVersionInfoChanged))); 451 452 BMenuField* varietyField = new BMenuField("", fVarietyMenu); 453 fInternalVersionControl = new BTextControl("version internal", "/", NULL, 454 new BMessage(kMsgVersionInfoChanged)); 455 fShortDescriptionControl = new BTextControl("short description", 456 B_TRANSLATE("Short description:"), NULL, 457 new BMessage(kMsgVersionInfoChanged)); 458 459 // TODO: workaround for a GCC 4.1.0 bug? Or is that really what the standard says? 460 version_info versionInfo; 461 fShortDescriptionControl->TextView()->SetMaxBytes( 462 sizeof(versionInfo.short_info)); 463 464 BStringView* longLabel = new BStringView(NULL, 465 B_TRANSLATE("Long description:")); 466 longLabel->SetExplicitAlignment(labelAlignment); 467 fLongDescriptionView = new TabFilteringTextView("long desc", 468 kMsgVersionInfoChanged); 469 fLongDescriptionView->SetMaxBytes(sizeof(versionInfo.long_info)); 470 471 scrollView = new BScrollView("desc scrollview", fLongDescriptionView, 472 B_FRAME_EVENTS | B_WILL_DRAW, false, true); 473 474 // TODO: remove workaround (bug #5678) 475 BSize minScrollSize = scrollView->ScrollBar(B_VERTICAL)->MinSize(); 476 minScrollSize.width += fLongDescriptionView->MinSize().width; 477 scrollView->SetExplicitMinSize(minScrollSize); 478 479 // Manually set a minimum size for the version text controls 480 // TODO: the same does not work when applied to the layout items 481 float width = be_plain_font->StringWidth("99") + 16; 482 fMajorVersionControl->TextView()->SetExplicitMinSize( 483 BSize(width, B_SIZE_UNSET)); 484 fMiddleVersionControl->TextView()->SetExplicitMinSize( 485 BSize(width, B_SIZE_UNSET)); 486 fMinorVersionControl->TextView()->SetExplicitMinSize( 487 BSize(width, B_SIZE_UNSET)); 488 fInternalVersionControl->TextView()->SetExplicitMinSize( 489 BSize(width, B_SIZE_UNSET)); 490 491 BLayoutBuilder::Grid<>(versionBox, padding / 2, padding / 2) 492 .SetInsets(padding, padding * 2, padding, padding) 493 .AddTextControl(fMajorVersionControl, 0, 0) 494 .Add(fMiddleVersionControl, 2, 0, 2) 495 .Add(fMinorVersionControl, 4, 0, 2) 496 .Add(varietyField, 6, 0, 3) 497 .Add(fInternalVersionControl, 9, 0, 2) 498 .AddTextControl(fShortDescriptionControl, 0, 1, 499 B_ALIGN_HORIZONTAL_UNSET, 1, 10) 500 .Add(longLabel, 0, 2) 501 .Add(scrollView, 1, 2, 10, 3) 502 .SetRowWeight(3, 3); 503 504 // put it all together 505 BLayoutBuilder::Group<>(this, B_VERTICAL, 0) 506 .SetInsets(0, 0, 0, 0) 507 .Add(menuBar) 508 .AddGroup(B_VERTICAL, padding) 509 .SetInsets(padding, padding, padding, padding) 510 .Add(fSignatureControl) 511 .AddGroup(B_HORIZONTAL, padding) 512 .Add(flagsBox, 3) 513 .Add(iconBox, 1) 514 .End() 515 .Add(typeBox, 2) 516 .Add(versionBox); 517 518 SetKeyMenuBar(menuBar); 519 520 fSignatureControl->MakeFocus(true); 521 BMimeType::StartWatching(this); 522 _SetTo(entry); 523 524 Layout(false); 525 } 526 527 528 ApplicationTypeWindow::~ApplicationTypeWindow() 529 { 530 BMimeType::StopWatching(this); 531 } 532 533 BRect 534 ApplicationTypeWindow::_Frame(const BMessage& settings) const 535 { 536 BRect rect; 537 if (settings.FindRect("app_type_next_frame", &rect) == B_OK) 538 return rect; 539 540 return BRect(100.0f, 110.0f, 250.0f, 340.0f); 541 } 542 543 BString 544 ApplicationTypeWindow::_Title(const BEntry& entry) 545 { 546 char name[B_FILE_NAME_LENGTH]; 547 if (entry.GetName(name) != B_OK) 548 strcpy(name, "\"-\""); 549 550 BString title = B_TRANSLATE("%1 application type"); 551 title.ReplaceFirst("%1", name); 552 return title; 553 } 554 555 556 void 557 ApplicationTypeWindow::_SetTo(const BEntry& entry) 558 { 559 SetTitle(_Title(entry).String()); 560 fEntry = entry; 561 562 // Retrieve Info 563 564 BFile file(&entry, B_READ_ONLY); 565 if (file.InitCheck() != B_OK) 566 return; 567 568 BAppFileInfo info(&file); 569 if (info.InitCheck() != B_OK) 570 return; 571 572 char signature[B_MIME_TYPE_LENGTH]; 573 if (info.GetSignature(signature) != B_OK) 574 signature[0] = '\0'; 575 576 bool gotFlags = false; 577 uint32 flags; 578 if (info.GetAppFlags(&flags) == B_OK) 579 gotFlags = true; 580 else 581 flags = B_MULTIPLE_LAUNCH; 582 583 version_info versionInfo; 584 if (info.GetVersionInfo(&versionInfo, B_APP_VERSION_KIND) != B_OK) 585 memset(&versionInfo, 0, sizeof(version_info)); 586 587 // Set Controls 588 589 fSignatureControl->SetModificationMessage(NULL); 590 fSignatureControl->SetText(signature); 591 fSignatureControl->SetModificationMessage( 592 new BMessage(kMsgSignatureChanged)); 593 594 // flags 595 596 switch (flags & (B_SINGLE_LAUNCH | B_MULTIPLE_LAUNCH | B_EXCLUSIVE_LAUNCH)) { 597 case B_SINGLE_LAUNCH: 598 fSingleLaunchButton->SetValue(B_CONTROL_ON); 599 break; 600 601 case B_EXCLUSIVE_LAUNCH: 602 fExclusiveLaunchButton->SetValue(B_CONTROL_ON); 603 break; 604 605 case B_MULTIPLE_LAUNCH: 606 default: 607 fMultipleLaunchButton->SetValue(B_CONTROL_ON); 608 break; 609 } 610 611 fArgsOnlyCheckBox->SetValue((flags & B_ARGV_ONLY) != 0); 612 fBackgroundAppCheckBox->SetValue((flags & B_BACKGROUND_APP) != 0); 613 fFlagsCheckBox->SetValue(gotFlags); 614 615 _UpdateAppFlagsEnabled(); 616 617 // icon 618 619 entry_ref ref; 620 if (entry.GetRef(&ref) == B_OK) 621 fIcon.SetTo(ref); 622 else 623 fIcon.Unset(); 624 625 fIconView->SetModificationMessage(NULL); 626 fIconView->SetTo(&fIcon); 627 fIconView->SetModificationMessage(new BMessage(kMsgIconChanged)); 628 629 // supported types 630 631 BMessage supportedTypes; 632 info.GetSupportedTypes(&supportedTypes); 633 634 for (int32 i = fTypeListView->CountItems(); i-- > 0;) { 635 BListItem* item = fTypeListView->RemoveItem(i); 636 delete item; 637 } 638 639 const char* type; 640 for (int32 i = 0; supportedTypes.FindString("types", i, &type) == B_OK; i++) { 641 SupportedTypeItem* item = new SupportedTypeItem(type); 642 643 entry_ref ref; 644 if (fEntry.GetRef(&ref) == B_OK) 645 item->SetIcon(ref, type); 646 647 fTypeListView->AddItem(item); 648 } 649 fTypeListView->SortItems(&SupportedTypeItem::Compare); 650 fTypeIconView->SetModificationMessage(NULL); 651 fTypeIconView->SetTo(NULL); 652 fTypeIconView->SetModificationMessage(new BMessage(kMsgTypeIconsChanged)); 653 fTypeIconView->SetEnabled(false); 654 fRemoveTypeButton->SetEnabled(false); 655 656 // version info 657 658 char text[256]; 659 snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.major); 660 fMajorVersionControl->SetText(text); 661 snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.middle); 662 fMiddleVersionControl->SetText(text); 663 snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.minor); 664 fMinorVersionControl->SetText(text); 665 666 if (versionInfo.variety >= (uint32)fVarietyMenu->CountItems()) 667 versionInfo.variety = 0; 668 BMenuItem* item = fVarietyMenu->ItemAt(versionInfo.variety); 669 if (item != NULL) 670 item->SetMarked(true); 671 672 snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.internal); 673 fInternalVersionControl->SetText(text); 674 675 fShortDescriptionControl->SetText(versionInfo.short_info); 676 fLongDescriptionView->SetText(versionInfo.long_info); 677 678 // store original data 679 680 fOriginalInfo.signature = signature; 681 fOriginalInfo.gotFlags = gotFlags; 682 fOriginalInfo.flags = gotFlags ? flags : 0; 683 fOriginalInfo.versionInfo = versionInfo; 684 fOriginalInfo.supportedTypes = _SupportedTypes(); 685 // The list view has the types sorted possibly differently 686 // to the supportedTypes message, so don't use that here, but 687 // get the sorted message instead. 688 fOriginalInfo.iconChanged = false; 689 fOriginalInfo.typeIconsChanged = false; 690 691 fChangedProperties = 0; 692 _CheckSaveMenuItem(0); 693 } 694 695 696 void 697 ApplicationTypeWindow::_UpdateAppFlagsEnabled() 698 { 699 bool enabled = fFlagsCheckBox->Value() != B_CONTROL_OFF; 700 701 fSingleLaunchButton->SetEnabled(enabled); 702 fMultipleLaunchButton->SetEnabled(enabled); 703 fExclusiveLaunchButton->SetEnabled(enabled); 704 fArgsOnlyCheckBox->SetEnabled(enabled); 705 fBackgroundAppCheckBox->SetEnabled(enabled); 706 } 707 708 709 void 710 ApplicationTypeWindow::_MakeNumberTextControl(BTextControl* control) 711 { 712 // filter out invalid characters that can't be part of a MIME type name 713 BTextView* textView = control->TextView(); 714 textView->SetMaxBytes(10); 715 716 for (int32 i = 0; i < 256; i++) { 717 if (!isdigit(i)) 718 textView->DisallowChar(i); 719 } 720 } 721 722 723 void 724 ApplicationTypeWindow::_Save() 725 { 726 BFile file; 727 status_t status = file.SetTo(&fEntry, B_READ_WRITE); 728 if (status != B_OK) 729 return; 730 731 BAppFileInfo info(&file); 732 status = info.InitCheck(); 733 if (status != B_OK) 734 return; 735 736 // Retrieve Info 737 738 uint32 flags = 0; 739 bool gotFlags = _Flags(flags); 740 BMessage supportedTypes = _SupportedTypes(); 741 version_info versionInfo = _VersionInfo(); 742 743 // Save 744 745 status = info.SetSignature(fSignatureControl->Text()); 746 if (status == B_OK) { 747 if (gotFlags) 748 status = info.SetAppFlags(flags); 749 else 750 status = info.RemoveAppFlags(); 751 } 752 if (status == B_OK) 753 status = info.SetVersionInfo(&versionInfo, B_APP_VERSION_KIND); 754 if (status == B_OK) 755 fIcon.CopyTo(info, NULL, true); 756 757 // supported types and their icons 758 if (status == B_OK) 759 status = info.SetSupportedTypes(&supportedTypes); 760 761 for (int32 i = 0; i < fTypeListView->CountItems(); i++) { 762 SupportedTypeItem* item = dynamic_cast<SupportedTypeItem*>( 763 fTypeListView->ItemAt(i)); 764 765 if (item != NULL) 766 item->Icon().CopyTo(info, item->Type(), true); 767 } 768 769 // reset the saved info 770 fOriginalInfo.signature = fSignatureControl->Text(); 771 fOriginalInfo.gotFlags = gotFlags; 772 fOriginalInfo.flags = flags; 773 fOriginalInfo.versionInfo = versionInfo; 774 fOriginalInfo.supportedTypes = supportedTypes; 775 fOriginalInfo.iconChanged = false; 776 fOriginalInfo.typeIconsChanged = false; 777 778 fChangedProperties = 0; 779 _CheckSaveMenuItem(0); 780 } 781 782 783 void 784 ApplicationTypeWindow::_CheckSaveMenuItem(uint32 flags) 785 { 786 fChangedProperties = _NeedsSaving(flags); 787 fSaveMenuItem->SetEnabled(fChangedProperties != 0); 788 } 789 790 791 bool 792 operator!=(const version_info& a, const version_info& b) 793 { 794 return a.major != b.major || a.middle != b.middle || a.minor != b.minor 795 || a.variety != b.variety || a.internal != b.internal 796 || strcmp(a.short_info, b.short_info) != 0 797 || strcmp(a.long_info, b.long_info) != 0; 798 } 799 800 801 uint32 802 ApplicationTypeWindow::_NeedsSaving(uint32 _flags) const 803 { 804 uint32 flags = fChangedProperties; 805 if (_flags & CHECK_SIGNATUR) { 806 if (fOriginalInfo.signature != fSignatureControl->Text()) 807 flags |= CHECK_SIGNATUR; 808 else 809 flags &= ~CHECK_SIGNATUR; 810 } 811 812 if (_flags & CHECK_FLAGS) { 813 uint32 appFlags = 0; 814 bool gotFlags = _Flags(appFlags); 815 if (fOriginalInfo.gotFlags != gotFlags 816 || fOriginalInfo.flags != appFlags) { 817 flags |= CHECK_FLAGS; 818 } else 819 flags &= ~CHECK_FLAGS; 820 } 821 822 if (_flags & CHECK_VERSION) { 823 if (fOriginalInfo.versionInfo != _VersionInfo()) 824 flags |= CHECK_VERSION; 825 else 826 flags &= ~CHECK_VERSION; 827 } 828 829 if (_flags & CHECK_ICON) { 830 if (fOriginalInfo.iconChanged) 831 flags |= CHECK_ICON; 832 else 833 flags &= ~CHECK_ICON; 834 } 835 836 if (_flags & CHECK_TYPES) { 837 if (!fOriginalInfo.supportedTypes.HasSameData(_SupportedTypes())) 838 flags |= CHECK_TYPES; 839 else 840 flags &= ~CHECK_TYPES; 841 } 842 843 if (_flags & CHECK_TYPE_ICONS) { 844 if (fOriginalInfo.typeIconsChanged) 845 flags |= CHECK_TYPE_ICONS; 846 else 847 flags &= ~CHECK_TYPE_ICONS; 848 } 849 850 return flags; 851 } 852 853 854 // #pragma mark - 855 856 857 bool 858 ApplicationTypeWindow::_Flags(uint32& flags) const 859 { 860 flags = 0; 861 if (fFlagsCheckBox->Value() != B_CONTROL_OFF) { 862 if (fSingleLaunchButton->Value() != B_CONTROL_OFF) 863 flags |= B_SINGLE_LAUNCH; 864 else if (fMultipleLaunchButton->Value() != B_CONTROL_OFF) 865 flags |= B_MULTIPLE_LAUNCH; 866 else if (fExclusiveLaunchButton->Value() != B_CONTROL_OFF) 867 flags |= B_EXCLUSIVE_LAUNCH; 868 869 if (fArgsOnlyCheckBox->Value() != B_CONTROL_OFF) 870 flags |= B_ARGV_ONLY; 871 if (fBackgroundAppCheckBox->Value() != B_CONTROL_OFF) 872 flags |= B_BACKGROUND_APP; 873 return true; 874 } 875 return false; 876 } 877 878 879 BMessage 880 ApplicationTypeWindow::_SupportedTypes() const 881 { 882 BMessage supportedTypes; 883 for (int32 i = 0; i < fTypeListView->CountItems(); i++) { 884 SupportedTypeItem* item = dynamic_cast<SupportedTypeItem*>( 885 fTypeListView->ItemAt(i)); 886 887 if (item != NULL) 888 supportedTypes.AddString("types", item->Type()); 889 } 890 return supportedTypes; 891 } 892 893 894 version_info 895 ApplicationTypeWindow::_VersionInfo() const 896 { 897 version_info versionInfo; 898 versionInfo.major = atol(fMajorVersionControl->Text()); 899 versionInfo.middle = atol(fMiddleVersionControl->Text()); 900 versionInfo.minor = atol(fMinorVersionControl->Text()); 901 versionInfo.variety = fVarietyMenu->IndexOf(fVarietyMenu->FindMarked()); 902 versionInfo.internal = atol(fInternalVersionControl->Text()); 903 strlcpy(versionInfo.short_info, fShortDescriptionControl->Text(), 904 sizeof(versionInfo.short_info)); 905 strlcpy(versionInfo.long_info, fLongDescriptionView->Text(), 906 sizeof(versionInfo.long_info)); 907 return versionInfo; 908 } 909 910 911 // #pragma mark - 912 913 914 void 915 ApplicationTypeWindow::MessageReceived(BMessage* message) 916 { 917 switch (message->what) { 918 case kMsgToggleAppFlags: 919 _UpdateAppFlagsEnabled(); 920 _CheckSaveMenuItem(CHECK_FLAGS); 921 break; 922 923 case kMsgSignatureChanged: 924 _CheckSaveMenuItem(CHECK_SIGNATUR); 925 break; 926 927 case kMsgAppFlagsChanged: 928 _CheckSaveMenuItem(CHECK_FLAGS); 929 break; 930 931 case kMsgIconChanged: 932 fOriginalInfo.iconChanged = true; 933 _CheckSaveMenuItem(CHECK_ICON); 934 break; 935 936 case kMsgTypeIconsChanged: 937 fOriginalInfo.typeIconsChanged = true; 938 _CheckSaveMenuItem(CHECK_TYPE_ICONS); 939 break; 940 941 case kMsgVersionInfoChanged: 942 _CheckSaveMenuItem(CHECK_VERSION); 943 break; 944 945 case kMsgSave: 946 _Save(); 947 break; 948 949 case kMsgTypeSelected: 950 { 951 int32 index; 952 if (message->FindInt32("index", &index) == B_OK) { 953 SupportedTypeItem* item 954 = (SupportedTypeItem*)fTypeListView->ItemAt(index); 955 956 fTypeIconView->SetModificationMessage(NULL); 957 fTypeIconView->SetTo(item != NULL ? &item->Icon() : NULL); 958 fTypeIconView->SetModificationMessage( 959 new BMessage(kMsgTypeIconsChanged)); 960 fTypeIconView->SetEnabled(item != NULL); 961 fRemoveTypeButton->SetEnabled(item != NULL); 962 963 _CheckSaveMenuItem(CHECK_TYPES); 964 } 965 break; 966 } 967 968 case kMsgAddType: 969 { 970 BWindow* window = new TypeListWindow(NULL, 971 kMsgTypeAdded, this); 972 window->Show(); 973 break; 974 } 975 976 case kMsgTypeAdded: 977 { 978 const char* type; 979 if (message->FindString("type", &type) != B_OK) 980 break; 981 982 // check if this type already exists 983 984 SupportedTypeItem* newItem = new SupportedTypeItem(type); 985 int32 insertAt = 0; 986 987 for (int32 i = fTypeListView->CountItems(); i-- > 0;) { 988 SupportedTypeItem* item = dynamic_cast<SupportedTypeItem*>( 989 fTypeListView->ItemAt(i)); 990 if (item == NULL) 991 continue; 992 993 int compare = strcasecmp(item->Type(), type); 994 if (!compare) { 995 // type does already exist, select it and bail out 996 delete newItem; 997 newItem = NULL; 998 fTypeListView->Select(i); 999 break; 1000 } 1001 if (compare < 0) 1002 insertAt = i + 1; 1003 } 1004 1005 if (newItem == NULL) 1006 break; 1007 1008 fTypeListView->AddItem(newItem, insertAt); 1009 fTypeListView->Select(insertAt); 1010 1011 _CheckSaveMenuItem(CHECK_TYPES); 1012 break; 1013 } 1014 1015 case kMsgRemoveType: 1016 { 1017 int32 index = fTypeListView->CurrentSelection(); 1018 if (index < 0) 1019 break; 1020 1021 delete fTypeListView->RemoveItem(index); 1022 fTypeIconView->SetModificationMessage(NULL); 1023 fTypeIconView->SetTo(NULL); 1024 fTypeIconView->SetModificationMessage( 1025 new BMessage(kMsgTypeIconsChanged)); 1026 fTypeIconView->SetEnabled(false); 1027 fRemoveTypeButton->SetEnabled(false); 1028 1029 _CheckSaveMenuItem(CHECK_TYPES); 1030 break; 1031 } 1032 1033 case B_SIMPLE_DATA: 1034 { 1035 entry_ref ref; 1036 if (message->FindRef("refs", &ref) != B_OK) 1037 break; 1038 1039 // TODO: add to supported types 1040 break; 1041 } 1042 1043 case B_META_MIME_CHANGED: 1044 const char* type; 1045 int32 which; 1046 if (message->FindString("be:type", &type) != B_OK 1047 || message->FindInt32("be:which", &which) != B_OK) 1048 break; 1049 1050 // TODO: update supported types names 1051 // if (which == B_MIME_TYPE_DELETED) 1052 1053 // _CheckSaveMenuItem(...); 1054 break; 1055 1056 default: 1057 BWindow::MessageReceived(message); 1058 } 1059 } 1060 1061 1062 bool 1063 ApplicationTypeWindow::QuitRequested() 1064 { 1065 if (_NeedsSaving(CHECK_ALL) != 0) { 1066 BAlert* alert = new BAlert(B_TRANSLATE("Save request"), 1067 B_TRANSLATE("Save changes before closing?"), 1068 B_TRANSLATE("Cancel"), B_TRANSLATE("Don't save"), 1069 B_TRANSLATE("Save"), B_WIDTH_AS_USUAL, B_OFFSET_SPACING, 1070 B_WARNING_ALERT); 1071 alert->SetShortcut(0, B_ESCAPE); 1072 alert->SetShortcut(1, 'd'); 1073 alert->SetShortcut(2, 's'); 1074 1075 int32 choice = alert->Go(); 1076 switch (choice) { 1077 case 0: 1078 return false; 1079 case 1: 1080 break; 1081 case 2: 1082 _Save(); 1083 break; 1084 } 1085 } 1086 1087 BMessage update(kMsgSettingsChanged); 1088 update.AddRect("app_type_next_frame", Frame()); 1089 be_app_messenger.SendMessage(&update); 1090 1091 be_app->PostMessage(kMsgTypeWindowClosed); 1092 return true; 1093 } 1094 1095