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