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