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 "AttributeListView.h" 8 #include "AttributeWindow.h" 9 #include "DropTargetListView.h" 10 #include "ExtensionWindow.h" 11 #include "FileTypes.h" 12 #include "FileTypesWindow.h" 13 #include "IconView.h" 14 #include "MimeTypeListView.h" 15 #include "NewFileTypeWindow.h" 16 #include "PreferredAppMenu.h" 17 #include "StringView.h" 18 19 #include <Alignment.h> 20 #include <AppFileInfo.h> 21 #include <Application.h> 22 #include <Bitmap.h> 23 #include <Box.h> 24 #include <Button.h> 25 #include <Catalog.h> 26 #include <ControlLook.h> 27 #include <LayoutBuilder.h> 28 #include <ListView.h> 29 #include <Locale.h> 30 #include <MenuBar.h> 31 #include <MenuField.h> 32 #include <MenuItem.h> 33 #include <Mime.h> 34 #include <NodeInfo.h> 35 #include <OutlineListView.h> 36 #include <PopUpMenu.h> 37 #include <ScrollView.h> 38 #include <SpaceLayoutItem.h> 39 #include <SplitView.h> 40 #include <TextControl.h> 41 42 #include <OverrideAlert.h> 43 #include <be_apps/Tracker/RecentItems.h> 44 45 #include <stdio.h> 46 #include <stdlib.h> 47 48 49 #undef B_TRANSLATION_CONTEXT 50 #define B_TRANSLATION_CONTEXT "FileTypes Window" 51 52 53 const uint32 kMsgTypeSelected = 'typs'; 54 const uint32 kMsgAddType = 'atyp'; 55 const uint32 kMsgRemoveType = 'rtyp'; 56 57 const uint32 kMsgExtensionSelected = 'exts'; 58 const uint32 kMsgExtensionInvoked = 'exti'; 59 const uint32 kMsgAddExtension = 'aext'; 60 const uint32 kMsgRemoveExtension = 'rext'; 61 const uint32 kMsgRuleEntered = 'rule'; 62 63 const uint32 kMsgAttributeSelected = 'atrs'; 64 const uint32 kMsgAttributeInvoked = 'atri'; 65 const uint32 kMsgAddAttribute = 'aatr'; 66 const uint32 kMsgRemoveAttribute = 'ratr'; 67 const uint32 kMsgMoveUpAttribute = 'muat'; 68 const uint32 kMsgMoveDownAttribute = 'mdat'; 69 70 const uint32 kMsgPreferredAppChosen = 'papc'; 71 const uint32 kMsgSelectPreferredApp = 'slpa'; 72 const uint32 kMsgSamePreferredAppAs = 'spaa'; 73 74 const uint32 kMsgPreferredAppOpened = 'paOp'; 75 const uint32 kMsgSamePreferredAppAsOpened = 'spaO'; 76 77 const uint32 kMsgTypeEntered = 'type'; 78 const uint32 kMsgDescriptionEntered = 'dsce'; 79 80 const uint32 kMsgToggleIcons = 'tgic'; 81 const uint32 kMsgToggleRule = 'tgrl'; 82 83 84 static const char* kAttributeNames[] = { 85 "attr:public_name", 86 "attr:name", 87 "attr:type", 88 "attr:editable", 89 "attr:viewable", 90 "attr:extra", 91 "attr:alignment", 92 "attr:width", 93 "attr:display_as" 94 }; 95 96 97 class TypeIconView : public IconView { 98 public: 99 TypeIconView(const char* name); 100 virtual ~TypeIconView(); 101 102 virtual void Draw(BRect updateRect); 103 virtual void GetPreferredSize(float* _width, float* _height); 104 105 protected: 106 virtual BRect BitmapRect() const; 107 }; 108 109 110 class ExtensionListView : public DropTargetListView { 111 public: 112 ExtensionListView(const char* name, 113 list_view_type type = B_SINGLE_SELECTION_LIST, 114 uint32 flags = B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE); 115 virtual ~ExtensionListView(); 116 117 virtual BSize MinSize(); 118 119 virtual void MessageReceived(BMessage* message); 120 virtual bool AcceptsDrag(const BMessage* message); 121 122 void SetType(BMimeType* type); 123 124 private: 125 BMimeType fType; 126 BSize fMinSize; 127 }; 128 129 130 // #pragma mark - 131 132 133 TypeIconView::TypeIconView(const char* name) 134 : IconView(name) 135 { 136 ShowEmptyFrame(false); 137 SetIconSize(48); 138 } 139 140 141 TypeIconView::~TypeIconView() 142 { 143 } 144 145 146 void 147 TypeIconView::Draw(BRect updateRect) 148 { 149 if (!IsEnabled()) 150 return; 151 152 IconView::Draw(updateRect); 153 154 const char* text = NULL; 155 156 switch (IconSource()) { 157 case kNoIcon: 158 text = B_TRANSLATE("no icon"); 159 break; 160 case kApplicationIcon: 161 text = B_TRANSLATE("(from application)"); 162 break; 163 case kSupertypeIcon: 164 text = B_TRANSLATE("(from super type)"); 165 break; 166 167 default: 168 return; 169 } 170 171 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 172 B_DISABLED_LABEL_TINT)); 173 SetLowColor(ViewColor()); 174 175 font_height fontHeight; 176 GetFontHeight(&fontHeight); 177 178 float y = fontHeight.ascent; 179 if (IconSource() == kNoIcon) { 180 // center text in the middle of the icon 181 y += (IconSize() - fontHeight.ascent - fontHeight.descent) / 2.0f; 182 } else 183 y += IconSize() + 3.0f; 184 185 DrawString(text, BPoint(ceilf((Bounds().Width() - StringWidth(text)) / 2.0f), 186 ceilf(y))); 187 } 188 189 190 void 191 TypeIconView::GetPreferredSize(float* _width, float* _height) 192 { 193 if (_width) { 194 float a = StringWidth(B_TRANSLATE("(from application)")); 195 float b = StringWidth(B_TRANSLATE("(from super type)")); 196 float width = max_c(a, b); 197 if (width < IconSize()) 198 width = IconSize(); 199 200 *_width = ceilf(width); 201 } 202 203 if (_height) { 204 font_height fontHeight; 205 GetFontHeight(&fontHeight); 206 207 *_height = IconSize() + 3.0f + ceilf(fontHeight.ascent 208 + fontHeight.descent); 209 } 210 } 211 212 213 BRect 214 TypeIconView::BitmapRect() const 215 { 216 if (IconSource() == kNoIcon) { 217 // this also defines the drop target area 218 font_height fontHeight; 219 GetFontHeight(&fontHeight); 220 221 float width = StringWidth(B_TRANSLATE("no icon")) + 8.0f; 222 float height = ceilf(fontHeight.ascent + fontHeight.descent) + 6.0f; 223 float x = (Bounds().Width() - width) / 2.0f; 224 float y = ceilf((IconSize() - fontHeight.ascent - fontHeight.descent) 225 / 2.0f) - 3.0f; 226 227 return BRect(x, y, x + width, y + height); 228 } 229 230 float x = (Bounds().Width() - IconSize()) / 2.0f; 231 return BRect(x, 0.0f, x + IconSize() - 1, IconSize() - 1); 232 } 233 234 235 // #pragma mark - 236 237 238 ExtensionListView::ExtensionListView(const char* name, 239 list_view_type type, uint32 flags) 240 : 241 DropTargetListView(name, type, flags) 242 { 243 } 244 245 246 ExtensionListView::~ExtensionListView() 247 { 248 } 249 250 251 BSize 252 ExtensionListView::MinSize() 253 { 254 if (!fMinSize.IsWidthSet()) { 255 BFont font; 256 GetFont(&font); 257 fMinSize.width = font.StringWidth(".mmmmm"); 258 259 font_height height; 260 font.GetHeight(&height); 261 fMinSize.height = (height.ascent + height.descent + height.leading) * 3; 262 } 263 264 return fMinSize; 265 } 266 267 268 void 269 ExtensionListView::MessageReceived(BMessage* message) 270 { 271 if (message->WasDropped() && AcceptsDrag(message)) { 272 // create extension list 273 BList list; 274 entry_ref ref; 275 for (int32 index = 0; message->FindRef("refs", index, &ref) == B_OK; 276 index++) { 277 const char* point = strchr(ref.name, '.'); 278 if (point != NULL && point[1]) 279 list.AddItem(strdup(++point)); 280 } 281 282 merge_extensions(fType, list); 283 284 // delete extension list 285 for (int32 index = list.CountItems(); index-- > 0;) { 286 free(list.ItemAt(index)); 287 } 288 } else 289 DropTargetListView::MessageReceived(message); 290 } 291 292 293 bool 294 ExtensionListView::AcceptsDrag(const BMessage* message) 295 { 296 if (fType.Type() == NULL) 297 return false; 298 299 int32 count = 0; 300 entry_ref ref; 301 302 for (int32 index = 0; message->FindRef("refs", index, &ref) == B_OK; 303 index++) { 304 const char* point = strchr(ref.name, '.'); 305 if (point != NULL && point[1]) 306 count++; 307 } 308 309 return count > 0; 310 } 311 312 313 void 314 ExtensionListView::SetType(BMimeType* type) 315 { 316 if (type != NULL) 317 fType.SetTo(type->Type()); 318 else 319 fType.Unset(); 320 } 321 322 323 // #pragma mark - 324 325 326 FileTypesWindow::FileTypesWindow(const BMessage& settings) 327 : 328 BWindow(_Frame(settings), B_TRANSLATE_SYSTEM_NAME("FileTypes"), 329 B_TITLED_WINDOW, B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS 330 | B_AUTO_UPDATE_SIZE_LIMITS), 331 fNewTypeWindow(NULL) 332 { 333 bool showIcons; 334 bool showRule; 335 if (settings.FindBool("show_icons", &showIcons) != B_OK) 336 showIcons = true; 337 if (settings.FindBool("show_rule", &showRule) != B_OK) 338 showRule = false; 339 340 float padding = be_control_look->DefaultItemSpacing(); 341 BAlignment labelAlignment = be_control_look->DefaultLabelAlignment(); 342 BAlignment fullAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 343 344 // add the menu 345 BMenuBar* menuBar = new BMenuBar(""); 346 347 BMenu* menu = new BMenu(B_TRANSLATE("File")); 348 BMenuItem* item = new BMenuItem( 349 B_TRANSLATE("New resource file" B_UTF8_ELLIPSIS), NULL, 'N', 350 B_COMMAND_KEY); 351 item->SetEnabled(false); 352 menu->AddItem(item); 353 354 BMenu* recentsMenu = BRecentFilesList::NewFileListMenu( 355 B_TRANSLATE("Open" B_UTF8_ELLIPSIS), NULL, NULL, 356 be_app, 10, false, NULL, kSignature); 357 item = new BMenuItem(recentsMenu, new BMessage(kMsgOpenFilePanel)); 358 item->SetShortcut('O', B_COMMAND_KEY); 359 menu->AddItem(item); 360 361 menu->AddItem(new BMenuItem( 362 B_TRANSLATE("Application types" B_UTF8_ELLIPSIS), 363 new BMessage(kMsgOpenApplicationTypesWindow))); 364 menu->AddSeparatorItem(); 365 366 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 367 new BMessage(B_QUIT_REQUESTED), 'Q', B_COMMAND_KEY)); 368 menu->SetTargetForItems(be_app); 369 menuBar->AddItem(menu); 370 371 menu = new BMenu(B_TRANSLATE("Settings")); 372 item = new BMenuItem(B_TRANSLATE("Show icons in list"), 373 new BMessage(kMsgToggleIcons)); 374 item->SetMarked(showIcons); 375 item->SetTarget(this); 376 menu->AddItem(item); 377 378 item = new BMenuItem(B_TRANSLATE("Show recognition rule"), 379 new BMessage(kMsgToggleRule)); 380 item->SetMarked(showRule); 381 item->SetTarget(this); 382 menu->AddItem(item); 383 menuBar->AddItem(menu); 384 menuBar->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP)); 385 386 // MIME Types list 387 BButton* addTypeButton = new BButton("add", 388 B_TRANSLATE("Add" B_UTF8_ELLIPSIS), new BMessage(kMsgAddType)); 389 390 fRemoveTypeButton = new BButton("remove", B_TRANSLATE("Remove"), 391 new BMessage(kMsgRemoveType) ); 392 393 fTypeListView = new MimeTypeListView("typeview", NULL, showIcons, false); 394 fTypeListView->SetSelectionMessage(new BMessage(kMsgTypeSelected)); 395 fTypeListView->SetExplicitMinSize(BSize(200, B_SIZE_UNSET)); 396 397 BScrollView* typeListScrollView = new BScrollView("scrollview", 398 fTypeListView, B_FRAME_EVENTS | B_WILL_DRAW, false, true); 399 400 // "Icon" group 401 402 fIconView = new TypeIconView("icon"); 403 fIconBox = new BBox("Icon BBox"); 404 fIconBox->SetLabel(B_TRANSLATE("Icon")); 405 BLayoutBuilder::Group<>(fIconBox, B_VERTICAL, padding) 406 .SetInsets(padding) 407 .AddGlue(1) 408 .Add(fIconView, 3) 409 .AddGlue(1); 410 411 // "File Recognition" group 412 413 fRecognitionBox = new BBox("Recognition Box"); 414 fRecognitionBox->SetLabel(B_TRANSLATE("File recognition")); 415 fRecognitionBox->SetExplicitAlignment(fullAlignment); 416 417 fExtensionLabel = new StringView(B_TRANSLATE("Extensions:"), NULL); 418 fExtensionLabel->LabelView()->SetExplicitAlignment(labelAlignment); 419 420 fAddExtensionButton = new BButton("add ext", 421 B_TRANSLATE("Add" B_UTF8_ELLIPSIS), new BMessage(kMsgAddExtension)); 422 fAddExtensionButton->SetExplicitMaxSize( 423 BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 424 425 fRemoveExtensionButton = new BButton("remove ext", B_TRANSLATE("Remove"), 426 new BMessage(kMsgRemoveExtension)); 427 428 fExtensionListView = new ExtensionListView("listview ext", 429 B_SINGLE_SELECTION_LIST); 430 fExtensionListView->SetSelectionMessage( 431 new BMessage(kMsgExtensionSelected)); 432 fExtensionListView->SetInvocationMessage( 433 new BMessage(kMsgExtensionInvoked)); 434 435 BScrollView* scrollView = new BScrollView("scrollview ext", 436 fExtensionListView, B_FRAME_EVENTS | B_WILL_DRAW, false, true); 437 438 fRuleControl = new BTextControl("rule", B_TRANSLATE("Rule:"), "", 439 new BMessage(kMsgRuleEntered)); 440 fRuleControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); 441 fRuleControl->Hide(); 442 443 BLayoutBuilder::Grid<>(fRecognitionBox, padding, padding / 2) 444 .SetInsets(padding, padding * 2, padding, padding) 445 .Add(fExtensionLabel->LabelView(), 0, 0) 446 .Add(scrollView, 0, 1, 2, 2) 447 .Add(fAddExtensionButton, 2, 1) 448 .Add(fRemoveExtensionButton, 2, 2) 449 .Add(fRuleControl, 0, 3, 3, 1); 450 451 // "Description" group 452 453 fDescriptionBox = new BBox("description BBox"); 454 fDescriptionBox->SetLabel(B_TRANSLATE("Description")); 455 fDescriptionBox->SetExplicitAlignment(fullAlignment); 456 457 fInternalNameView = new StringView(B_TRANSLATE("Internal name:"), NULL); 458 fInternalNameView->SetEnabled(false); 459 fTypeNameControl = new BTextControl("type", B_TRANSLATE("Type name:"), "", 460 new BMessage(kMsgTypeEntered)); 461 fDescriptionControl = new BTextControl("description", 462 B_TRANSLATE("Description:"), "", new BMessage(kMsgDescriptionEntered)); 463 464 BLayoutBuilder::Grid<>(fDescriptionBox, padding / 2, padding / 2) 465 .SetInsets(padding, padding * 2, padding, padding) 466 .Add(fInternalNameView->LabelView(), 0, 0) 467 .Add(fInternalNameView->TextView(), 1, 0) 468 .Add(fTypeNameControl->CreateLabelLayoutItem(), 0, 1) 469 .Add(fTypeNameControl->CreateTextViewLayoutItem(), 1, 1, 2) 470 .Add(fDescriptionControl->CreateLabelLayoutItem(), 0, 2) 471 .Add(fDescriptionControl->CreateTextViewLayoutItem(), 1, 2, 2); 472 473 // "Preferred Application" group 474 475 fPreferredBox = new BBox("preferred BBox"); 476 fPreferredBox->SetLabel(B_TRANSLATE("Preferred application")); 477 478 menu = new BPopUpMenu("preferred"); 479 menu->AddItem(item = new BMenuItem(B_TRANSLATE("None"), 480 new BMessage(kMsgPreferredAppChosen))); 481 item->SetMarked(true); 482 fPreferredField = new BMenuField("preferred", (char*)NULL, menu); 483 484 fSelectButton = new BButton("select", 485 B_TRANSLATE("Select" B_UTF8_ELLIPSIS), 486 new BMessage(kMsgSelectPreferredApp)); 487 488 fSameAsButton = new BButton("same as", 489 B_TRANSLATE("Same as" B_UTF8_ELLIPSIS), 490 new BMessage(kMsgSamePreferredAppAs)); 491 492 BLayoutBuilder::Group<>(fPreferredBox, B_HORIZONTAL, padding) 493 .SetInsets(padding, padding * 2, padding, padding) 494 .Add(fPreferredField) 495 .Add(fSelectButton) 496 .Add(fSameAsButton); 497 498 // "Extra Attributes" group 499 500 fAttributeBox = new BBox("Attribute Box"); 501 fAttributeBox->SetLabel(B_TRANSLATE("Extra attributes")); 502 503 fAddAttributeButton = new BButton("add attr", 504 B_TRANSLATE("Add" B_UTF8_ELLIPSIS), new BMessage(kMsgAddAttribute)); 505 fAddAttributeButton->SetExplicitMaxSize( 506 BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 507 508 fRemoveAttributeButton = new BButton("remove attr", B_TRANSLATE("Remove"), 509 new BMessage(kMsgRemoveAttribute)); 510 fRemoveAttributeButton->SetExplicitMaxSize( 511 BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 512 513 fMoveUpAttributeButton = new BButton("move up attr", B_TRANSLATE("Move up"), 514 new BMessage(kMsgMoveUpAttribute)); 515 fMoveUpAttributeButton->SetExplicitMaxSize( 516 BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 517 fMoveDownAttributeButton = new BButton("move down attr", 518 B_TRANSLATE("Move down"), new BMessage(kMsgMoveDownAttribute)); 519 fMoveDownAttributeButton->SetExplicitMaxSize( 520 BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 521 522 fAttributeListView = new AttributeListView("listview attr"); 523 fAttributeListView->SetSelectionMessage( 524 new BMessage(kMsgAttributeSelected)); 525 fAttributeListView->SetInvocationMessage( 526 new BMessage(kMsgAttributeInvoked)); 527 528 BScrollView* attributesScroller = new BScrollView("scrollview attr", 529 fAttributeListView, B_FRAME_EVENTS | B_WILL_DRAW, false, true); 530 531 BLayoutBuilder::Group<>(fAttributeBox, B_HORIZONTAL, padding) 532 .SetInsets(padding, padding * 2, padding, padding) 533 .Add(attributesScroller, 1.0f) 534 .AddGroup(B_VERTICAL, padding / 2, 0.0f) 535 .SetInsets(0) 536 .Add(fAddAttributeButton) 537 .Add(fRemoveAttributeButton) 538 .AddStrut(padding) 539 .Add(fMoveUpAttributeButton) 540 .Add(fMoveDownAttributeButton) 541 .AddGlue(); 542 543 fMainSplitView = new BSplitView(B_HORIZONTAL, floorf(padding / 2)); 544 545 BLayoutBuilder::Group<>(this, B_VERTICAL, 0) 546 .SetInsets(0) 547 .Add(menuBar) 548 .AddGroup(B_HORIZONTAL, 0) 549 .SetInsets(padding, padding, padding, padding) 550 .AddSplit(fMainSplitView) 551 .AddGroup(B_VERTICAL, padding) 552 .Add(typeListScrollView) 553 .AddGroup(B_HORIZONTAL, padding) 554 .Add(addTypeButton) 555 .Add(fRemoveTypeButton) 556 .AddGlue() 557 .End() 558 .End() 559 // Right side 560 .AddGroup(B_VERTICAL, padding) 561 .AddGroup(B_HORIZONTAL, padding) 562 .Add(fIconBox, 1) 563 .Add(fRecognitionBox, 3) 564 .End() 565 .Add(fDescriptionBox) 566 .Add(fPreferredBox) 567 .Add(fAttributeBox, 5); 568 569 _SetType(NULL); 570 _ShowSnifferRule(showRule); 571 572 float leftWeight; 573 float rightWeight; 574 if (settings.FindFloat("left_split_weight", &leftWeight) != B_OK 575 || settings.FindFloat("right_split_weight", &rightWeight) != B_OK) { 576 leftWeight = 0.2; 577 rightWeight = 1.0 - leftWeight; 578 } 579 fMainSplitView->SetItemWeight(0, leftWeight, false); 580 fMainSplitView->SetItemWeight(1, rightWeight, true); 581 582 BMimeType::StartWatching(this); 583 } 584 585 586 FileTypesWindow::~FileTypesWindow() 587 { 588 BMimeType::StopWatching(this); 589 } 590 591 592 void 593 FileTypesWindow::MessageReceived(BMessage* message) 594 { 595 switch (message->what) { 596 case B_SIMPLE_DATA: 597 { 598 type_code type; 599 if (message->GetInfo("refs", &type) == B_OK 600 && type == B_REF_TYPE) { 601 be_app->PostMessage(message); 602 } 603 break; 604 } 605 606 case kMsgToggleIcons: 607 { 608 BMenuItem* item; 609 if (message->FindPointer("source", (void **)&item) != B_OK) 610 break; 611 612 item->SetMarked(!fTypeListView->IsShowingIcons()); 613 fTypeListView->ShowIcons(item->IsMarked()); 614 615 // update settings 616 BMessage update(kMsgSettingsChanged); 617 update.AddBool("show_icons", item->IsMarked()); 618 be_app_messenger.SendMessage(&update); 619 break; 620 } 621 622 case kMsgToggleRule: 623 { 624 BMenuItem* item; 625 if (message->FindPointer("source", (void **)&item) != B_OK) 626 break; 627 628 item->SetMarked(fRuleControl->IsHidden()); 629 _ShowSnifferRule(item->IsMarked()); 630 631 // update settings 632 BMessage update(kMsgSettingsChanged); 633 update.AddBool("show_rule", item->IsMarked()); 634 be_app_messenger.SendMessage(&update); 635 break; 636 } 637 638 case kMsgTypeSelected: 639 { 640 int32 index; 641 if (message->FindInt32("index", &index) == B_OK) { 642 MimeTypeItem* item 643 = (MimeTypeItem*)fTypeListView->ItemAt(index); 644 if (item != NULL) { 645 BMimeType type(item->Type()); 646 _SetType(&type); 647 } else 648 _SetType(NULL); 649 } 650 break; 651 } 652 653 case kMsgAddType: 654 if (fNewTypeWindow == NULL) { 655 fNewTypeWindow 656 = new NewFileTypeWindow(this, fCurrentType.Type()); 657 fNewTypeWindow->Show(); 658 } else 659 fNewTypeWindow->Activate(); 660 break; 661 662 case kMsgNewTypeWindowClosed: 663 fNewTypeWindow = NULL; 664 break; 665 666 case kMsgRemoveType: 667 { 668 if (fCurrentType.Type() == NULL) 669 break; 670 671 BAlert* alert; 672 if (fCurrentType.IsSupertypeOnly()) { 673 alert = new BPrivate::OverrideAlert( 674 B_TRANSLATE("FileTypes request"), 675 B_TRANSLATE("Removing a super type cannot be reverted.\n" 676 "All file types that belong to this super type " 677 "will be lost!\n\n" 678 "Are you sure you want to do this? To remove the whole " 679 "group, hold down the Shift key and press \"Remove\"."), 680 B_TRANSLATE("Remove"), B_SHIFT_KEY, B_TRANSLATE("Cancel"), 681 0, NULL, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT); 682 alert->SetShortcut(1, B_ESCAPE); 683 } else { 684 alert = new BAlert(B_TRANSLATE("FileTypes request"), 685 B_TRANSLATE("Removing a file type cannot be reverted.\n" 686 "Are you sure you want to remove it?"), 687 B_TRANSLATE("Remove"), B_TRANSLATE("Cancel"), 688 NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 689 alert->SetShortcut(1, B_ESCAPE); 690 } 691 if (alert->Go()) 692 break; 693 694 status_t status = fCurrentType.Delete(); 695 if (status != B_OK) { 696 fprintf(stderr, B_TRANSLATE( 697 "Could not remove file type: %s\n"), strerror(status)); 698 } 699 break; 700 } 701 702 case kMsgSelectNewType: 703 { 704 const char* type; 705 if (message->FindString("type", &type) == B_OK) 706 fTypeListView->SelectNewType(type); 707 break; 708 } 709 710 // File Recognition group 711 712 case kMsgExtensionSelected: 713 { 714 int32 index; 715 if (message->FindInt32("index", &index) == B_OK) { 716 BStringItem* item 717 = (BStringItem*)fExtensionListView->ItemAt(index); 718 fRemoveExtensionButton->SetEnabled(item != NULL); 719 } 720 break; 721 } 722 723 case kMsgExtensionInvoked: 724 { 725 if (fCurrentType.Type() == NULL) 726 break; 727 728 int32 index; 729 if (message->FindInt32("index", &index) == B_OK) { 730 BStringItem* item 731 = (BStringItem*)fExtensionListView->ItemAt(index); 732 if (item == NULL) 733 break; 734 735 BWindow* window 736 = new ExtensionWindow(this, fCurrentType, item->Text()); 737 window->Show(); 738 } 739 break; 740 } 741 742 case kMsgAddExtension: 743 { 744 if (fCurrentType.Type() == NULL) 745 break; 746 747 BWindow* window = new ExtensionWindow(this, fCurrentType, NULL); 748 window->Show(); 749 break; 750 } 751 752 case kMsgRemoveExtension: 753 { 754 int32 index = fExtensionListView->CurrentSelection(); 755 if (index < 0 || fCurrentType.Type() == NULL) 756 break; 757 758 BMessage extensions; 759 if (fCurrentType.GetFileExtensions(&extensions) == B_OK) { 760 extensions.RemoveData("extensions", index); 761 fCurrentType.SetFileExtensions(&extensions); 762 } 763 break; 764 } 765 766 case kMsgRuleEntered: 767 { 768 // check rule 769 BString parseError; 770 if (BMimeType::CheckSnifferRule(fRuleControl->Text(), 771 &parseError) != B_OK) { 772 parseError.Prepend( 773 B_TRANSLATE("Recognition rule is not valid:\n\n")); 774 error_alert(parseError.String()); 775 } else 776 fCurrentType.SetSnifferRule(fRuleControl->Text()); 777 break; 778 } 779 780 // Description group 781 782 case kMsgTypeEntered: 783 { 784 fCurrentType.SetShortDescription(fTypeNameControl->Text()); 785 break; 786 } 787 788 case kMsgDescriptionEntered: 789 { 790 fCurrentType.SetLongDescription(fDescriptionControl->Text()); 791 break; 792 } 793 794 // Preferred Application group 795 796 case kMsgPreferredAppChosen: 797 { 798 const char* signature; 799 if (message->FindString("signature", &signature) != B_OK) 800 signature = NULL; 801 802 fCurrentType.SetPreferredApp(signature); 803 break; 804 } 805 806 case kMsgSelectPreferredApp: 807 { 808 BMessage panel(kMsgOpenFilePanel); 809 panel.AddString("title", 810 B_TRANSLATE("Select preferred application")); 811 panel.AddInt32("message", kMsgPreferredAppOpened); 812 panel.AddMessenger("target", this); 813 814 be_app_messenger.SendMessage(&panel); 815 break; 816 } 817 case kMsgPreferredAppOpened: 818 _AdoptPreferredApplication(message, false); 819 break; 820 821 case kMsgSamePreferredAppAs: 822 { 823 BMessage panel(kMsgOpenFilePanel); 824 panel.AddString("title", 825 B_TRANSLATE("Select same preferred application as")); 826 panel.AddInt32("message", kMsgSamePreferredAppAsOpened); 827 panel.AddMessenger("target", this); 828 829 be_app_messenger.SendMessage(&panel); 830 break; 831 } 832 case kMsgSamePreferredAppAsOpened: 833 _AdoptPreferredApplication(message, true); 834 break; 835 836 // Extra Attributes group 837 838 case kMsgAttributeSelected: 839 { 840 int32 index; 841 if (message->FindInt32("index", &index) == B_OK) { 842 AttributeItem* item 843 = (AttributeItem*)fAttributeListView->ItemAt(index); 844 fRemoveAttributeButton->SetEnabled(item != NULL); 845 fMoveUpAttributeButton->SetEnabled(index > 0); 846 fMoveDownAttributeButton->SetEnabled(index >= 0 847 && index < fAttributeListView->CountItems() - 1); 848 } 849 break; 850 } 851 852 case kMsgAttributeInvoked: 853 { 854 if (fCurrentType.Type() == NULL) 855 break; 856 857 int32 index; 858 if (message->FindInt32("index", &index) == B_OK) { 859 AttributeItem* item 860 = (AttributeItem*)fAttributeListView->ItemAt(index); 861 if (item == NULL) 862 break; 863 864 BWindow* window = new AttributeWindow(this, fCurrentType, 865 item); 866 window->Show(); 867 } 868 break; 869 } 870 871 case kMsgAddAttribute: 872 { 873 if (fCurrentType.Type() == NULL) 874 break; 875 876 BWindow* window = new AttributeWindow(this, fCurrentType, NULL); 877 window->Show(); 878 break; 879 } 880 881 case kMsgRemoveAttribute: 882 { 883 int32 index = fAttributeListView->CurrentSelection(); 884 if (index < 0 || fCurrentType.Type() == NULL) 885 break; 886 887 BMessage attributes; 888 if (fCurrentType.GetAttrInfo(&attributes) == B_OK) { 889 for (uint32 i = 0; i < 890 sizeof(kAttributeNames) / sizeof(kAttributeNames[0]); 891 i++) { 892 attributes.RemoveData(kAttributeNames[i], index); 893 } 894 895 fCurrentType.SetAttrInfo(&attributes); 896 } 897 break; 898 } 899 900 case kMsgMoveUpAttribute: 901 { 902 int32 index = fAttributeListView->CurrentSelection(); 903 if (index < 1 || fCurrentType.Type() == NULL) 904 break; 905 906 _MoveUpAttributeIndex(index); 907 break; 908 } 909 910 case kMsgMoveDownAttribute: 911 { 912 int32 index = fAttributeListView->CurrentSelection(); 913 if (index < 0 || index == fAttributeListView->CountItems() - 1 914 || fCurrentType.Type() == NULL) { 915 break; 916 } 917 918 _MoveUpAttributeIndex(index + 1); 919 break; 920 } 921 922 case B_META_MIME_CHANGED: 923 { 924 const char* type; 925 int32 which; 926 if (message->FindString("be:type", &type) != B_OK 927 || message->FindInt32("be:which", &which) != B_OK) 928 break; 929 930 if (fCurrentType.Type() == NULL) 931 break; 932 933 if (!strcasecmp(fCurrentType.Type(), type)) { 934 if (which != B_MIME_TYPE_DELETED) 935 _SetType(&fCurrentType, which); 936 else 937 _SetType(NULL); 938 } else { 939 // this change could still affect our current type 940 941 if (which == B_MIME_TYPE_DELETED 942 || which == B_SUPPORTED_TYPES_CHANGED 943 || which == B_PREFERRED_APP_CHANGED) { 944 _UpdatePreferredApps(&fCurrentType); 945 } 946 } 947 break; 948 } 949 950 default: 951 BWindow::MessageReceived(message); 952 } 953 } 954 955 956 void 957 FileTypesWindow::SelectType(const char* type) 958 { 959 fTypeListView->SelectType(type); 960 } 961 962 963 bool 964 FileTypesWindow::QuitRequested() 965 { 966 BMessage update(kMsgSettingsChanged); 967 update.AddRect("file_types_frame", Frame()); 968 update.AddFloat("left_split_weight", fMainSplitView->ItemWeight((int32)0)); 969 update.AddFloat("right_split_weight", fMainSplitView->ItemWeight(1)); 970 be_app_messenger.SendMessage(&update); 971 972 be_app->PostMessage(kMsgTypesWindowClosed); 973 return true; 974 } 975 976 977 void 978 FileTypesWindow::PlaceSubWindow(BWindow* window) 979 { 980 window->MoveTo(Frame().left + (Frame().Width() - window->Frame().Width()) 981 / 2.0f, Frame().top + (Frame().Height() - window->Frame().Height()) 982 / 2.0f); 983 } 984 985 986 // #pragma mark - private 987 988 989 BRect 990 FileTypesWindow::_Frame(const BMessage& settings) const 991 { 992 BRect rect; 993 if (settings.FindRect("file_types_frame", &rect) == B_OK) 994 return rect; 995 996 return BRect(80.0f, 80.0f, 0.0f, 0.0f); 997 } 998 999 1000 void 1001 FileTypesWindow::_ShowSnifferRule(bool show) 1002 { 1003 if (fRuleControl->IsHidden() == !show) 1004 return; 1005 1006 if (!show) 1007 fRuleControl->Hide(); 1008 else 1009 fRuleControl->Show(); 1010 } 1011 1012 1013 void 1014 FileTypesWindow::_UpdateExtensions(BMimeType* type) 1015 { 1016 // clear list 1017 1018 for (int32 i = fExtensionListView->CountItems(); i-- > 0;) { 1019 delete fExtensionListView->ItemAt(i); 1020 } 1021 fExtensionListView->MakeEmpty(); 1022 1023 // fill it again 1024 1025 if (type == NULL) 1026 return; 1027 1028 BMessage extensions; 1029 if (type->GetFileExtensions(&extensions) != B_OK) 1030 return; 1031 1032 const char* extension; 1033 int32 i = 0; 1034 while (extensions.FindString("extensions", i++, &extension) == B_OK) { 1035 char dotExtension[B_FILE_NAME_LENGTH]; 1036 snprintf(dotExtension, B_FILE_NAME_LENGTH, ".%s", extension); 1037 1038 fExtensionListView->AddItem(new BStringItem(dotExtension)); 1039 } 1040 } 1041 1042 1043 void 1044 FileTypesWindow::_AdoptPreferredApplication(BMessage* message, bool sameAs) 1045 { 1046 if (fCurrentType.Type() == NULL) 1047 return; 1048 1049 BString preferred; 1050 if (retrieve_preferred_app(message, sameAs, fCurrentType.Type(), preferred) 1051 != B_OK) { 1052 return; 1053 } 1054 1055 status_t status = fCurrentType.SetPreferredApp(preferred.String()); 1056 if (status != B_OK) 1057 error_alert(B_TRANSLATE("Could not set preferred application"), 1058 status); 1059 } 1060 1061 1062 void 1063 FileTypesWindow::_UpdatePreferredApps(BMimeType* type) 1064 { 1065 update_preferred_app_menu(fPreferredField->Menu(), type, 1066 kMsgPreferredAppChosen); 1067 } 1068 1069 1070 void 1071 FileTypesWindow::_UpdateIcon(BMimeType* type) 1072 { 1073 if (type != NULL) 1074 fIconView->SetTo(*type); 1075 else 1076 fIconView->Unset(); 1077 } 1078 1079 1080 void 1081 FileTypesWindow::_SetType(BMimeType* type, int32 forceUpdate) 1082 { 1083 bool enabled = type != NULL; 1084 1085 // update controls 1086 1087 if (type != NULL) { 1088 if (fCurrentType == *type) { 1089 if (!forceUpdate) 1090 return; 1091 } else 1092 forceUpdate = B_EVERYTHING_CHANGED; 1093 1094 if (&fCurrentType != type) 1095 fCurrentType.SetTo(type->Type()); 1096 1097 fInternalNameView->SetText(type->Type()); 1098 1099 char description[B_MIME_TYPE_LENGTH]; 1100 1101 if ((forceUpdate & B_SHORT_DESCRIPTION_CHANGED) != 0) { 1102 if (type->GetShortDescription(description) != B_OK) 1103 description[0] = '\0'; 1104 fTypeNameControl->SetText(description); 1105 } 1106 1107 if ((forceUpdate & B_LONG_DESCRIPTION_CHANGED) != 0) { 1108 if (type->GetLongDescription(description) != B_OK) 1109 description[0] = '\0'; 1110 fDescriptionControl->SetText(description); 1111 } 1112 1113 if ((forceUpdate & B_SNIFFER_RULE_CHANGED) != 0) { 1114 BString rule; 1115 if (type->GetSnifferRule(&rule) != B_OK) 1116 rule = ""; 1117 fRuleControl->SetText(rule.String()); 1118 } 1119 1120 fExtensionListView->SetType(&fCurrentType); 1121 } else { 1122 fCurrentType.Unset(); 1123 fInternalNameView->SetText(NULL); 1124 fTypeNameControl->SetText(NULL); 1125 fDescriptionControl->SetText(NULL); 1126 fRuleControl->SetText(NULL); 1127 fPreferredField->Menu()->ItemAt(0)->SetMarked(true); 1128 fExtensionListView->SetType(NULL); 1129 fAttributeListView->SetTo(NULL); 1130 } 1131 1132 if ((forceUpdate & B_FILE_EXTENSIONS_CHANGED) != 0) 1133 _UpdateExtensions(type); 1134 1135 if ((forceUpdate & B_PREFERRED_APP_CHANGED) != 0) 1136 _UpdatePreferredApps(type); 1137 1138 if ((forceUpdate & (B_ICON_CHANGED | B_PREFERRED_APP_CHANGED)) != 0) 1139 _UpdateIcon(type); 1140 1141 if ((forceUpdate & B_ATTR_INFO_CHANGED) != 0) 1142 fAttributeListView->SetTo(type); 1143 1144 // enable/disable controls 1145 1146 fIconView->SetEnabled(enabled); 1147 1148 fInternalNameView->SetEnabled(enabled); 1149 fTypeNameControl->SetEnabled(enabled); 1150 fDescriptionControl->SetEnabled(enabled); 1151 fPreferredField->SetEnabled(enabled); 1152 1153 fRemoveTypeButton->SetEnabled(enabled); 1154 1155 fSelectButton->SetEnabled(enabled); 1156 fSameAsButton->SetEnabled(enabled); 1157 1158 fExtensionLabel->SetEnabled(enabled); 1159 fAddExtensionButton->SetEnabled(enabled); 1160 fRemoveExtensionButton->SetEnabled(false); 1161 fRuleControl->SetEnabled(enabled); 1162 1163 fAddAttributeButton->SetEnabled(enabled); 1164 fRemoveAttributeButton->SetEnabled(false); 1165 fMoveUpAttributeButton->SetEnabled(false); 1166 fMoveDownAttributeButton->SetEnabled(false); 1167 } 1168 1169 1170 void 1171 FileTypesWindow::_MoveUpAttributeIndex(int32 index) 1172 { 1173 BMessage attributes; 1174 if (fCurrentType.GetAttrInfo(&attributes) != B_OK) 1175 return; 1176 1177 // Iterate over all known attribute fields, and for each field, 1178 // iterate over all fields of the same name and build a copy 1179 // of the attributes message with the field at the given index swapped 1180 // with the previous field. 1181 BMessage resortedAttributes; 1182 for (uint32 i = 0; i < 1183 sizeof(kAttributeNames) / sizeof(kAttributeNames[0]); 1184 i++) { 1185 1186 type_code type; 1187 int32 count; 1188 bool isFixedSize; 1189 if (attributes.GetInfo(kAttributeNames[i], &type, &count, 1190 &isFixedSize) != B_OK) { 1191 // Apparently the message does not contain this name, 1192 // so just ignore this attribute name. 1193 // NOTE: This shows that the attribute description is 1194 // too fragile. It would have been better to pack each 1195 // attribute description into a separate BMessage. 1196 continue; 1197 } 1198 1199 for (int32 j = 0; j < count; j++) { 1200 const void* data; 1201 ssize_t size; 1202 int32 originalIndex; 1203 if (j == index - 1) 1204 originalIndex = j + 1; 1205 else if (j == index) 1206 originalIndex = j - 1; 1207 else 1208 originalIndex = j; 1209 attributes.FindData(kAttributeNames[i], type, 1210 originalIndex, &data, &size); 1211 if (j == 0) { 1212 resortedAttributes.AddData(kAttributeNames[i], type, 1213 data, size, isFixedSize); 1214 } else { 1215 resortedAttributes.AddData(kAttributeNames[i], type, 1216 data, size); 1217 } 1218 } 1219 } 1220 1221 // Setting it directly on the type will trigger an update of the GUI as 1222 // well. TODO: FileTypes is heavily descructive, it should use an 1223 // Undo/Redo stack. 1224 fCurrentType.SetAttrInfo(&resortedAttributes); 1225 } 1226 1227 1228