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