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 "FileTypes.h" 8 #include "FileTypeWindow.h" 9 #include "IconView.h" 10 #include "PreferredAppMenu.h" 11 #include "TypeListWindow.h" 12 13 #include <Application.h> 14 #include <Bitmap.h> 15 #include <Box.h> 16 #include <Button.h> 17 #include <File.h> 18 #include <MenuField.h> 19 #include <MenuItem.h> 20 #include <Mime.h> 21 #include <NodeInfo.h> 22 #include <PopUpMenu.h> 23 #include <TextControl.h> 24 25 #include <stdio.h> 26 27 28 const uint32 kMsgTypeEntered = 'type'; 29 const uint32 kMsgSelectType = 'sltp'; 30 const uint32 kMsgTypeSelected = 'tpsd'; 31 const uint32 kMsgSameTypeAs = 'stpa'; 32 const uint32 kMsgSameTypeAsOpened = 'stpO'; 33 34 const uint32 kMsgPreferredAppChosen = 'papc'; 35 const uint32 kMsgSelectPreferredApp = 'slpa'; 36 const uint32 kMsgSamePreferredAppAs = 'spaa'; 37 const uint32 kMsgPreferredAppOpened = 'paOp'; 38 const uint32 kMsgSamePreferredAppAsOpened = 'spaO'; 39 40 41 FileTypeWindow::FileTypeWindow(BPoint position, const BMessage& refs) 42 : BWindow(BRect(0.0f, 0.0f, 200.0f, 200.0f).OffsetBySelf(position), 43 "File Type", B_TITLED_WINDOW, 44 B_NOT_V_RESIZABLE | B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS) 45 { 46 BRect rect = Bounds(); 47 BView* topView = new BView(rect, NULL, B_FOLLOW_ALL, B_WILL_DRAW); 48 topView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 49 AddChild(topView); 50 51 // "File Type" group 52 53 BFont font(be_bold_font); 54 font_height fontHeight; 55 font.GetHeight(&fontHeight); 56 57 rect.InsetBy(8.0f, 8.0f); 58 BBox* box = new BBox(rect, NULL, B_FOLLOW_LEFT_RIGHT); 59 box->SetLabel("File Type"); 60 topView->AddChild(box); 61 62 rect = box->Bounds(); 63 rect.InsetBy(8.0f, 4.0f + fontHeight.ascent + fontHeight.descent); 64 fTypeControl = new BTextControl(rect, "type", NULL, NULL, 65 new BMessage(kMsgTypeEntered), B_FOLLOW_LEFT_RIGHT); 66 fTypeControl->SetDivider(0.0f); 67 float width, height; 68 fTypeControl->GetPreferredSize(&width, &height); 69 fTypeControl->ResizeTo(rect.Width(), height); 70 box->AddChild(fTypeControl); 71 72 // filter out invalid characters that can't be part of a MIME type name 73 BTextView* textView = fTypeControl->TextView(); 74 const char* disallowedCharacters = "<>@,;:\"()[]?="; 75 for (int32 i = 0; disallowedCharacters[i]; i++) { 76 textView->DisallowChar(disallowedCharacters[i]); 77 } 78 79 rect.OffsetBy(0.0f, fTypeControl->Bounds().Height() + 5.0f); 80 fSelectTypeButton = new BButton(rect, "select type", "Select" B_UTF8_ELLIPSIS, 81 new BMessage(kMsgSelectType), B_FOLLOW_LEFT | B_FOLLOW_TOP); 82 fSelectTypeButton->ResizeToPreferred(); 83 box->AddChild(fSelectTypeButton); 84 85 rect.OffsetBy(fSelectTypeButton->Bounds().Width() + 8.0f, 0.0f); 86 fSameTypeAsButton = new BButton(rect, "same type as", "Same As" B_UTF8_ELLIPSIS, 87 new BMessage(kMsgSameTypeAs), B_FOLLOW_LEFT | B_FOLLOW_TOP); 88 fSameTypeAsButton->ResizeToPreferred(); 89 box->AddChild(fSameTypeAsButton); 90 91 width = font.StringWidth("Icon") + 16.0f; 92 if (width < B_LARGE_ICON + 16.0f) 93 width = B_LARGE_ICON + 16.0f; 94 95 height = fSelectTypeButton->Frame().bottom + 8.0f; 96 if (height < 8.0f + B_LARGE_ICON + fontHeight.ascent + fontHeight.descent) 97 height = 8.0f + B_LARGE_ICON + fontHeight.ascent + fontHeight.descent; 98 box->ResizeTo(box->Bounds().Width() - width - 8.0f, height); 99 100 // "Icon" group 101 102 rect = box->Frame(); 103 rect.left = rect.right + 8.0f; 104 rect.right += width + 8.0f; 105 float iconBoxWidth = rect.Width(); 106 box = new BBox(rect, NULL, B_FOLLOW_RIGHT | B_FOLLOW_TOP); 107 box->SetLabel("Icon"); 108 topView->AddChild(box); 109 110 rect = BRect(8.0f, 0.0f, 7.0f + B_LARGE_ICON, B_LARGE_ICON - 1.0f); 111 rect.OffsetBy(0.0f, (box->Bounds().Height() - rect.Height()) / 2.0f); 112 if (rect.top < fontHeight.ascent + fontHeight.descent + 4.0f) 113 rect.top = fontHeight.ascent + fontHeight.descent + 4.0f; 114 fIconView = new IconView(rect, "icon"); 115 box->AddChild(fIconView); 116 117 // "Preferred Application" group 118 119 rect.top = box->Frame().bottom + 8.0f; 120 rect.bottom = rect.top + box->Bounds().Height(); 121 rect.left = 8.0f; 122 rect.right = Bounds().Width() - 8.0f; 123 box = new BBox(rect, NULL, B_FOLLOW_LEFT_RIGHT); 124 box->SetLabel("Preferred Application"); 125 topView->AddChild(box); 126 127 BMenu* menu = new BPopUpMenu("preferred"); 128 BMenuItem* item; 129 menu->AddItem(item = new BMenuItem("Default Application", 130 new BMessage(kMsgPreferredAppChosen))); 131 item->SetMarked(true); 132 133 rect = fTypeControl->Frame(); 134 BView* constrainingView = new BView(rect, NULL, B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW); 135 constrainingView->SetViewColor(topView->ViewColor()); 136 137 fPreferredField = new BMenuField(rect.OffsetToCopy(B_ORIGIN), "preferred", 138 NULL, menu); 139 fPreferredField->GetPreferredSize(&width, &height); 140 fPreferredField->ResizeTo(rect.Width(), height); 141 constrainingView->ResizeTo(rect.Width(), height); 142 constrainingView->AddChild(fPreferredField); 143 // we embed the menu field in another view to make it behave like 144 // we want so that it can't obscure other elements with larger 145 // labels 146 147 box->AddChild(constrainingView); 148 149 rect.OffsetBy(0.0f, height + 5.0f); 150 fSelectAppButton = new BButton(rect, "select app", "Select" B_UTF8_ELLIPSIS, 151 new BMessage(kMsgSelectPreferredApp), B_FOLLOW_LEFT | B_FOLLOW_TOP); 152 fSelectAppButton->ResizeToPreferred(); 153 box->AddChild(fSelectAppButton); 154 155 rect.OffsetBy(fSelectAppButton->Bounds().Width() + 8.0f, 0.0f); 156 fSameAppAsButton = new BButton(rect, "same app as", "Same As" B_UTF8_ELLIPSIS, 157 new BMessage(kMsgSamePreferredAppAs), B_FOLLOW_LEFT | B_FOLLOW_TOP); 158 fSameAppAsButton->ResizeToPreferred(); 159 box->AddChild(fSameAppAsButton); 160 box->ResizeBy(0.0f, height - fTypeControl->Bounds().Height()); 161 162 ResizeTo(fSameAppAsButton->Frame().right + 100.0f, box->Frame().bottom + 8.0f); 163 SetSizeLimits(fSameAppAsButton->Frame().right + iconBoxWidth + 32.0f, 32767.0f, 164 Bounds().Height(), Bounds().Height()); 165 166 fTypeControl->MakeFocus(true); 167 168 BMimeType::StartWatching(this); 169 _SetTo(refs); 170 } 171 172 173 FileTypeWindow::~FileTypeWindow() 174 { 175 BMimeType::StopWatching(this); 176 } 177 178 179 BString 180 FileTypeWindow::_Title(const BMessage& refs) 181 { 182 BString title; 183 entry_ref ref; 184 if (refs.FindRef("refs", 1, &ref) == B_OK) { 185 bool same = false; 186 BEntry entry, parent; 187 if (entry.SetTo(&ref) == B_OK 188 && entry.GetParent(&parent) == B_OK) { 189 same = true; 190 191 // Check if all entries have the same parent directory 192 for (int32 i = 0; refs.FindRef("refs", i, &ref) == B_OK; i++) { 193 BEntry directory; 194 if (entry.SetTo(&ref) == B_OK 195 && entry.GetParent(&directory) == B_OK) { 196 if (directory != parent) { 197 same = false; 198 break; 199 } 200 } 201 } 202 } 203 204 char name[B_FILE_NAME_LENGTH]; 205 if (same && parent.GetName(name) == B_OK) { 206 title = "Multiple Files from \""; 207 title.Append(name); 208 title.Append("\""); 209 } else 210 title = "[Multiple Files]"; 211 } else if (refs.FindRef("refs", 0, &ref) == B_OK) 212 title = ref.name; 213 214 title.Append(" File Type"); 215 return title; 216 } 217 218 219 void 220 FileTypeWindow::_SetTo(const BMessage& refs) 221 { 222 SetTitle(_Title(refs).String()); 223 224 // get common info and icons 225 226 fCommonPreferredApp = ""; 227 fCommonType = ""; 228 entry_ref ref; 229 for (int32 i = 0; refs.FindRef("refs", i, &ref) == B_OK; i++) { 230 BNode node(&ref); 231 if (node.InitCheck() != B_OK) 232 continue; 233 234 BNodeInfo info(&node); 235 if (info.InitCheck() != B_OK) 236 continue; 237 238 // TODO: watch entries? 239 240 entry_ref* copiedRef = new entry_ref(ref); 241 fEntries.AddItem(copiedRef); 242 243 char type[B_MIME_TYPE_LENGTH]; 244 if (info.GetType(type) != B_OK) 245 type[0] = '\0'; 246 247 if (i > 0) { 248 if (fCommonType != type) 249 fCommonType = ""; 250 } else 251 fCommonType = type; 252 253 char preferredApp[B_MIME_TYPE_LENGTH]; 254 if (info.GetPreferredApp(preferredApp) != B_OK) 255 preferredApp[0] = '\0'; 256 257 if (i > 0) { 258 if (fCommonPreferredApp != preferredApp) 259 fCommonPreferredApp = ""; 260 } else 261 fCommonPreferredApp = preferredApp; 262 263 if (i == 0) 264 fIconView->SetTo(ref); 265 } 266 267 fTypeControl->SetText(fCommonType.String()); 268 _UpdatePreferredApps(); 269 270 fIconView->ShowIconHeap(fEntries.CountItems() != 1); 271 } 272 273 274 void 275 FileTypeWindow::_AdoptType(BMessage* message) 276 { 277 entry_ref ref; 278 if (message == NULL || message->FindRef("refs", &ref) != B_OK) 279 return; 280 281 BNode node(&ref); 282 status_t status = node.InitCheck(); 283 284 char type[B_MIME_TYPE_LENGTH]; 285 286 if (status == B_OK) { 287 // get type from file 288 BNodeInfo nodeInfo(&node); 289 status = nodeInfo.InitCheck(); 290 if (status == B_OK) { 291 if (nodeInfo.GetType(type) != B_OK) 292 type[0] = '\0'; 293 } 294 } 295 296 if (status != B_OK) { 297 error_alert("Could not open file", status); 298 return; 299 } 300 301 fCommonType = type; 302 fTypeControl->SetText(type); 303 _AdoptType(); 304 } 305 306 307 void 308 FileTypeWindow::_AdoptType() 309 { 310 for (int32 i = 0; i < fEntries.CountItems(); i++) { 311 BNode node(fEntries.ItemAt(i)); 312 BNodeInfo info(&node); 313 if (node.InitCheck() != B_OK 314 || info.InitCheck() != B_OK) 315 continue; 316 317 info.SetType(fCommonType.String()); 318 } 319 } 320 321 322 void 323 FileTypeWindow::_AdoptPreferredApp(BMessage* message, bool sameAs) 324 { 325 if (retrieve_preferred_app(message, sameAs, fCommonType.String(), 326 fCommonPreferredApp) == B_OK) { 327 _AdoptPreferredApp(); 328 _UpdatePreferredApps(); 329 } 330 } 331 332 333 void 334 FileTypeWindow::_AdoptPreferredApp() 335 { 336 for (int32 i = 0; i < fEntries.CountItems(); i++) { 337 BNode node(fEntries.ItemAt(i)); 338 if (fCommonPreferredApp.Length() == 0) { 339 node.RemoveAttr("BEOS:PREF_APP"); 340 continue; 341 } 342 343 BNodeInfo info(&node); 344 if (node.InitCheck() != B_OK 345 || info.InitCheck() != B_OK) 346 continue; 347 348 info.SetPreferredApp(fCommonPreferredApp.String()); 349 } 350 } 351 352 353 void 354 FileTypeWindow::_UpdatePreferredApps() 355 { 356 BMimeType type(fCommonType.String()); 357 update_preferred_app_menu(fPreferredField->Menu(), &type, 358 kMsgPreferredAppChosen, fCommonPreferredApp.String()); 359 } 360 361 362 void 363 FileTypeWindow::MessageReceived(BMessage* message) 364 { 365 switch (message->what) { 366 // File Type group 367 368 case kMsgTypeEntered: 369 fCommonType = fTypeControl->Text(); 370 _AdoptType(); 371 break; 372 373 case kMsgSelectType: 374 { 375 BWindow* window = new TypeListWindow(fCommonType.String(), 376 kMsgTypeSelected, this); 377 window->Show(); 378 break; 379 } 380 case kMsgTypeSelected: 381 { 382 const char* type; 383 if (message->FindString("type", &type) == B_OK) { 384 fCommonType = type; 385 fTypeControl->SetText(type); 386 _AdoptType(); 387 } 388 break; 389 } 390 391 case kMsgSameTypeAs: 392 { 393 BMessage panel(kMsgOpenFilePanel); 394 panel.AddString("title", "Select Same Type As"); 395 panel.AddInt32("message", kMsgSameTypeAsOpened); 396 panel.AddMessenger("target", this); 397 398 be_app_messenger.SendMessage(&panel); 399 break; 400 } 401 case kMsgSameTypeAsOpened: 402 _AdoptType(message); 403 break; 404 405 // Preferred Application group 406 407 case kMsgPreferredAppChosen: 408 { 409 const char* signature; 410 if (message->FindString("signature", &signature) == B_OK) 411 fCommonPreferredApp = signature; 412 else 413 fCommonPreferredApp = ""; 414 415 _AdoptPreferredApp(); 416 break; 417 } 418 419 case kMsgSelectPreferredApp: 420 { 421 BMessage panel(kMsgOpenFilePanel); 422 panel.AddString("title", "Select Preferred Application"); 423 panel.AddInt32("message", kMsgPreferredAppOpened); 424 panel.AddMessenger("target", this); 425 426 be_app_messenger.SendMessage(&panel); 427 break; 428 } 429 case kMsgPreferredAppOpened: 430 _AdoptPreferredApp(message, false); 431 break; 432 433 case kMsgSamePreferredAppAs: 434 { 435 BMessage panel(kMsgOpenFilePanel); 436 panel.AddString("title", "Select Same Preferred Application As"); 437 panel.AddInt32("message", kMsgSamePreferredAppAsOpened); 438 panel.AddMessenger("target", this); 439 440 be_app_messenger.SendMessage(&panel); 441 break; 442 } 443 case kMsgSamePreferredAppAsOpened: 444 _AdoptPreferredApp(message, true); 445 break; 446 447 // Other 448 449 case B_SIMPLE_DATA: 450 { 451 entry_ref ref; 452 if (message->FindRef("refs", &ref) != B_OK) 453 break; 454 455 BFile file(&ref, B_READ_ONLY); 456 if (is_application(file)) 457 _AdoptPreferredApp(message, false); 458 else 459 _AdoptType(message); 460 break; 461 } 462 463 case B_META_MIME_CHANGED: 464 const char* type; 465 int32 which; 466 if (message->FindString("be:type", &type) != B_OK 467 || message->FindInt32("be:which", &which) != B_OK) 468 break; 469 470 if (which == B_MIME_TYPE_DELETED 471 #ifdef __HAIKU__ 472 || which == B_SUPPORTED_TYPES_CHANGED 473 #endif 474 ) 475 _UpdatePreferredApps(); 476 break; 477 478 default: 479 BWindow::MessageReceived(message); 480 } 481 } 482 483 484 bool 485 FileTypeWindow::QuitRequested() 486 { 487 be_app->PostMessage(kMsgTypeWindowClosed); 488 return true; 489 } 490 491