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