1 /* 2 * Copyright 2004-2012, Haiku Inc. All Rights Reserved. 3 * Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. 4 * 5 * Distributed under the terms of the MIT License. 6 */ 7 8 9 //! mail_daemon's deskbar menu and view 10 11 12 #include "DeskbarView.h" 13 14 #include <stdio.h> 15 #include <malloc.h> 16 17 #include <Bitmap.h> 18 #include <Catalog.h> 19 #include <Deskbar.h> 20 #include <Directory.h> 21 #include <Entry.h> 22 #include <FindDirectory.h> 23 #include <IconUtils.h> 24 #include <kernel/fs_info.h> 25 #include <kernel/fs_index.h> 26 #include <MenuItem.h> 27 #include <MessageFormat.h> 28 #include <Messenger.h> 29 #include <NodeInfo.h> 30 #include <NodeMonitor.h> 31 #include <OpenWithTracker.h> 32 #include <Path.h> 33 #include <PopUpMenu.h> 34 #include <Query.h> 35 #include <Rect.h> 36 #include <Resources.h> 37 #include <Roster.h> 38 #include <String.h> 39 #include <SymLink.h> 40 #include <VolumeRoster.h> 41 #include <Window.h> 42 43 #include <E-mail.h> 44 #include <MailDaemon.h> 45 #include <MailSettings.h> 46 47 #include <MailPrivate.h> 48 49 #include "DeskbarViewIcons.h" 50 51 52 #undef B_TRANSLATION_CONTEXT 53 #define B_TRANSLATION_CONTEXT "DeskbarView" 54 55 56 const char* kTrackerSignature = "application/x-vnd.Be-TRAK"; 57 58 59 //-----The following #defines get around a bug in get_image_info on ppc--- 60 #if __INTEL__ 61 #define text_part text 62 #define text_part_size text_size 63 #else 64 #define text_part data 65 #define text_part_size data_size 66 #endif 67 68 extern "C" _EXPORT BView* instantiate_deskbar_item(); 69 70 71 status_t our_image(image_info* image) 72 { 73 int32 cookie = 0; 74 status_t ret; 75 while ((ret = get_next_image_info(0, &cookie,image)) == B_OK) { 76 if ((char*)our_image >= (char*)image->text_part && 77 (char*)our_image <= (char*)image->text_part + image->text_part_size) 78 break; 79 } 80 81 return ret; 82 } 83 84 BView* instantiate_deskbar_item(void) 85 { 86 return new DeskbarView(BRect(0, 0, 15, 15)); 87 } 88 89 90 //------------------------------------------------------------------------------- 91 // #pragma mark - 92 93 94 DeskbarView::DeskbarView(BRect frame) 95 : 96 BView(frame, "mail_daemon", B_FOLLOW_NONE, B_WILL_DRAW | B_PULSE_NEEDED), 97 fStatus(kStatusNoMail), 98 fLastButtons(0) 99 { 100 _InitBitmaps(); 101 } 102 103 104 DeskbarView::DeskbarView(BMessage *message) 105 : 106 BView(message), 107 fStatus(kStatusNoMail), 108 fLastButtons(0) 109 { 110 _InitBitmaps(); 111 } 112 113 114 DeskbarView::~DeskbarView() 115 { 116 for (int i = 0; i < kStatusCount; i++) 117 delete fBitmaps[i]; 118 119 for (int32 i = 0; i < fNewMailQueries.CountItems(); i++) 120 delete ((BQuery *)(fNewMailQueries.ItemAt(i))); 121 } 122 123 124 void DeskbarView::AttachedToWindow() 125 { 126 BView::AttachedToWindow(); 127 AdoptParentColors(); 128 129 if (ViewUIColor() == B_NO_COLOR) 130 SetLowColor(ViewColor()); 131 else 132 SetLowUIColor(ViewUIColor()); 133 134 if (be_roster->IsRunning(B_MAIL_DAEMON_SIGNATURE)) { 135 _RefreshMailQuery(); 136 } else { 137 BDeskbar deskbar; 138 deskbar.RemoveItem("mail_daemon"); 139 } 140 } 141 142 143 bool DeskbarView::_EntryInTrash(const entry_ref* ref) 144 { 145 BEntry entry(ref); 146 BVolume volume(ref->device); 147 BPath path; 148 if (volume.InitCheck() != B_OK 149 || find_directory(B_TRASH_DIRECTORY, &path, false, &volume) != B_OK) 150 return false; 151 152 BDirectory trash(path.Path()); 153 return trash.Contains(&entry); 154 } 155 156 157 void DeskbarView::_RefreshMailQuery() 158 { 159 for (int32 i = 0; i < fNewMailQueries.CountItems(); i++) 160 delete ((BQuery *)(fNewMailQueries.ItemAt(i))); 161 fNewMailQueries.MakeEmpty(); 162 163 BVolumeRoster volumes; 164 BVolume volume; 165 fNewMessages = 0; 166 167 while (volumes.GetNextVolume(&volume) == B_OK) { 168 BQuery *newMailQuery = new BQuery; 169 newMailQuery->SetTarget(this); 170 newMailQuery->SetVolume(&volume); 171 newMailQuery->PushAttr(B_MAIL_ATTR_READ); 172 newMailQuery->PushInt32(B_UNREAD); 173 newMailQuery->PushOp(B_EQ); 174 newMailQuery->PushAttr("BEOS:TYPE"); 175 newMailQuery->PushString("text/x-email"); 176 newMailQuery->PushOp(B_EQ); 177 newMailQuery->PushAttr("BEOS:TYPE"); 178 newMailQuery->PushString("text/x-partial-email"); 179 newMailQuery->PushOp(B_EQ); 180 newMailQuery->PushOp(B_OR); 181 newMailQuery->PushOp(B_AND); 182 newMailQuery->Fetch(); 183 184 BEntry entry; 185 while (newMailQuery->GetNextEntry(&entry) == B_OK) { 186 if (entry.InitCheck() == B_OK) { 187 entry_ref ref; 188 entry.GetRef(&ref); 189 if (!_EntryInTrash(&ref)) 190 fNewMessages++; 191 } 192 } 193 194 fNewMailQueries.AddItem(newMailQuery); 195 } 196 197 fStatus = (fNewMessages > 0) ? kStatusNewMail : kStatusNoMail; 198 Invalidate(); 199 } 200 201 202 DeskbarView* DeskbarView::Instantiate(BMessage *data) 203 { 204 if (!validate_instantiation(data, "DeskbarView")) 205 return NULL; 206 207 return new DeskbarView(data); 208 } 209 210 211 status_t DeskbarView::Archive(BMessage *data,bool deep) const 212 { 213 BView::Archive(data, deep); 214 215 data->AddString("add_on", B_MAIL_DAEMON_SIGNATURE); 216 return B_NO_ERROR; 217 } 218 219 220 void 221 DeskbarView::Draw(BRect /*updateRect*/) 222 { 223 if (fBitmaps[fStatus] == NULL) 224 return; 225 226 SetDrawingMode(B_OP_ALPHA); 227 DrawBitmap(fBitmaps[fStatus]); 228 SetDrawingMode(B_OP_COPY); 229 } 230 231 232 void 233 DeskbarView::MessageReceived(BMessage* message) 234 { 235 switch (message->what) { 236 case MD_CHECK_SEND_NOW: 237 // also happens in DeskbarView::MouseUp() with 238 // B_TERTIARY_MOUSE_BUTTON pressed 239 BMailDaemon().CheckAndSendQueuedMail(); 240 break; 241 case MD_CHECK_FOR_MAILS: 242 BMailDaemon().CheckMail(message->FindInt32("account")); 243 break; 244 case MD_SEND_MAILS: 245 BMailDaemon().SendQueuedMail(); 246 break; 247 248 case MD_OPEN_NEW: 249 { 250 char* argv[] = {(char *)"New Message", (char *)"mailto:"}; 251 be_roster->Launch("text/x-email", 2, argv); 252 break; 253 } 254 case MD_OPEN_PREFS: 255 be_roster->Launch("application/x-vnd.Haiku-Mail"); 256 break; 257 258 case MD_REFRESH_QUERY: 259 _RefreshMailQuery(); 260 break; 261 262 case B_QUERY_UPDATE: 263 { 264 int32 what; 265 dev_t device; 266 ino_t directory; 267 const char *name; 268 entry_ref ref; 269 message->FindInt32("opcode", &what); 270 message->FindInt32("device", &device); 271 message->FindInt64("directory", &directory); 272 switch (what) { 273 case B_ENTRY_CREATED: 274 if (message->FindString("name", &name) == B_OK) { 275 ref.device = device; 276 ref.directory = directory; 277 ref.set_name(name); 278 if (!_EntryInTrash(&ref)) 279 fNewMessages++; 280 } 281 break; 282 case B_ENTRY_REMOVED: 283 node_ref node; 284 node.device = device; 285 node.node = directory; 286 BDirectory dir(&node); 287 BEntry entry(&dir, NULL); 288 entry.GetRef(&ref); 289 if (!_EntryInTrash(&ref)) 290 fNewMessages--; 291 break; 292 } 293 fStatus = fNewMessages > 0 ? kStatusNewMail : kStatusNoMail; 294 Invalidate(); 295 break; 296 } 297 case B_QUIT_REQUESTED: 298 BMailDaemon().Quit(); 299 break; 300 301 // open received files in the standard mail application 302 case B_REFS_RECEIVED: 303 { 304 BMessage argv(B_ARGV_RECEIVED); 305 argv.AddString("argv", "E-mail"); 306 307 entry_ref ref; 308 BPath path; 309 int i = 0; 310 311 while (message->FindRef("refs", i++, &ref) == B_OK 312 && path.SetTo(&ref) == B_OK) { 313 //fprintf(stderr,"got %s\n", path.Path()); 314 argv.AddString("argv", path.Path()); 315 } 316 317 if (i > 1) { 318 argv.AddInt32("argc", i); 319 be_roster->Launch("text/x-email", &argv); 320 } 321 break; 322 } 323 default: 324 BView::MessageReceived(message); 325 } 326 } 327 328 329 void 330 DeskbarView::_InitBitmaps() 331 { 332 for (int i = 0; i < kStatusCount; i++) 333 fBitmaps[i] = NULL; 334 335 image_info info; 336 if (our_image(&info) != B_OK) 337 return; 338 339 BFile file(info.name, B_READ_ONLY); 340 if (file.InitCheck() != B_OK) 341 return; 342 343 BResources resources(&file); 344 if (resources.InitCheck() != B_OK) 345 return; 346 347 for (int i = 0; i < kStatusCount; i++) { 348 const void* data = NULL; 349 size_t size; 350 data = resources.LoadResource(B_VECTOR_ICON_TYPE, 351 kIconNoMail + i, &size); 352 if (data != NULL) { 353 BBitmap* icon = new BBitmap(Bounds(), B_RGBA32); 354 if (icon->InitCheck() == B_OK 355 && BIconUtils::GetVectorIcon((const uint8 *)data, 356 size, icon) == B_OK) { 357 fBitmaps[i] = icon; 358 } else 359 delete icon; 360 } 361 } 362 } 363 364 365 void 366 DeskbarView::Pulse() 367 { 368 // TODO: Check if mail_daemon is still running 369 } 370 371 372 void 373 DeskbarView::MouseUp(BPoint pos) 374 { 375 if ((fLastButtons & B_PRIMARY_MOUSE_BUTTON) !=0 376 && OpenWithTracker(B_USER_SETTINGS_DIRECTORY, "Mail/mailbox") != B_OK) { 377 entry_ref ref; 378 _GetNewQueryRef(ref); 379 380 BMessenger trackerMessenger(kTrackerSignature); 381 BMessage message(B_REFS_RECEIVED); 382 message.AddRef("refs", &ref); 383 384 trackerMessenger.SendMessage(&message); 385 } 386 387 if ((fLastButtons & B_TERTIARY_MOUSE_BUTTON) != 0) 388 BMailDaemon().CheckMail(); 389 } 390 391 392 void 393 DeskbarView::MouseDown(BPoint pos) 394 { 395 Looper()->CurrentMessage()->FindInt32("buttons", &fLastButtons); 396 397 if ((fLastButtons & B_SECONDARY_MOUSE_BUTTON) != 0) { 398 ConvertToScreen(&pos); 399 400 BPopUpMenu* menu = _BuildMenu(); 401 menu->Go(pos, true, true, BRect(pos.x - 2, pos.y - 2, 402 pos.x + 2, pos.y + 2), true); 403 } 404 } 405 406 407 bool 408 DeskbarView::_CreateMenuLinks(BDirectory& directory, BPath& path) 409 { 410 status_t status = directory.SetTo(path.Path()); 411 if (status == B_OK) 412 return true; 413 414 // Check if the directory has to be created (and do it in this case, 415 // filling it with some standard links). Normally the installer will 416 // create the directory and fill it with links, so normally this doesn't 417 // get used. 418 419 BEntry entry(path.Path()); 420 if (status != B_ENTRY_NOT_FOUND 421 || entry.GetParent(&directory) < B_OK 422 || directory.CreateDirectory(path.Leaf(), NULL) < B_OK 423 || directory.SetTo(path.Path()) < B_OK) 424 return false; 425 426 BPath targetPath; 427 find_directory(B_USER_DIRECTORY, &targetPath); 428 targetPath.Append("mail/in"); 429 430 directory.CreateSymLink("Open Inbox Folder", targetPath.Path(), NULL); 431 targetPath.GetParent(&targetPath); 432 directory.CreateSymLink("Open Mail Folder", targetPath.Path(), NULL); 433 434 // create the draft query 435 436 BFile file; 437 if (directory.CreateFile("Open Draft", &file) < B_OK) 438 return true; 439 440 BString string("MAIL:draft==1"); 441 file.WriteAttrString("_trk/qrystr", &string); 442 string = "E-mail"; 443 file.WriteAttrString("_trk/qryinitmime", &string); 444 BNodeInfo(&file).SetType("application/x-vnd.Be-query"); 445 446 return true; 447 } 448 449 450 void 451 DeskbarView::_CreateNewMailQuery(BEntry& query) 452 { 453 BFile file(&query, B_READ_WRITE | B_CREATE_FILE); 454 if (file.InitCheck() != B_OK) 455 return; 456 457 BString string("((" B_MAIL_ATTR_READ "<2)&&((BEOS:TYPE==" 458 "\"text/x-email\")||(BEOS:TYPE==\"text/x-partial-email\")))"); 459 file.WriteAttrString("_trk/qrystr", &string); 460 file.WriteAttrString("_trk/qryinitstr", &string); 461 int32 mode = 'Fbyq'; 462 file.WriteAttr("_trk/qryinitmode", B_INT32_TYPE, 0, &mode, sizeof(int32)); 463 string = "E-mail"; 464 file.WriteAttrString("_trk/qryinitmime", &string); 465 BNodeInfo(&file).SetType("application/x-vnd.Be-query"); 466 } 467 468 469 BPopUpMenu* 470 DeskbarView::_BuildMenu() 471 { 472 BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 473 menu->SetFont(be_plain_font); 474 475 menu->AddItem(new BMenuItem(B_TRANSLATE("Create new message" 476 B_UTF8_ELLIPSIS), new BMessage(MD_OPEN_NEW))); 477 menu->AddSeparatorItem(); 478 479 BMessenger tracker(kTrackerSignature); 480 BNavMenu* navMenu; 481 BMenuItem* item; 482 BMessage* msg; 483 entry_ref ref; 484 485 BPath path; 486 find_directory(B_USER_SETTINGS_DIRECTORY, &path); 487 path.Append("Mail/Menu Links"); 488 489 BDirectory directory; 490 if (_CreateMenuLinks(directory, path)) { 491 int32 count = 0; 492 493 while (directory.GetNextRef(&ref) == B_OK) { 494 count++; 495 496 path.SetTo(&ref); 497 // the true here dereferences the symlinks all the way :) 498 BEntry entry(&ref, true); 499 500 // do we want to use the NavMenu, or just an ordinary BMenuItem? 501 // we are using the NavMenu only for directories and queries 502 bool useNavMenu = false; 503 504 if (entry.InitCheck() == B_OK) { 505 if (entry.IsDirectory()) 506 useNavMenu = true; 507 else if (entry.IsFile()) { 508 // Files should use the BMenuItem unless they are queries 509 char mimeString[B_MIME_TYPE_LENGTH]; 510 BNode node(&entry); 511 BNodeInfo info(&node); 512 if (info.GetType(mimeString) == B_OK 513 && strcmp(mimeString, "application/x-vnd.Be-query") 514 == 0) 515 useNavMenu = true; 516 } 517 // clobber the existing ref only if the symlink derefernces 518 // completely, otherwise we'll stick with what we have 519 entry.GetRef(&ref); 520 } 521 522 msg = new BMessage(B_REFS_RECEIVED); 523 msg->AddRef("refs", &ref); 524 525 if (useNavMenu) { 526 item = new BMenuItem(navMenu = new BNavMenu(path.Leaf(), 527 B_REFS_RECEIVED, tracker), msg); 528 navMenu->SetNavDir(&ref); 529 } else 530 item = new BMenuItem(path.Leaf(), msg); 531 532 menu->AddItem(item); 533 if (entry.InitCheck() != B_OK) 534 item->SetEnabled(false); 535 } 536 if (count > 0) 537 menu->AddSeparatorItem(); 538 } 539 540 // Hack for R5's buggy Query Notification 541 #ifdef HAIKU_TARGET_PLATFORM_BEOS 542 menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh New Mail Count"), 543 new BMessage(MD_REFRESH_QUERY))); 544 #endif 545 546 // The New E-mail query 547 548 if (fNewMessages > 0) { 549 static BMessageFormat format(B_TRANSLATE( 550 "{0, plural, one{# new message} other{# new messages}}")); 551 BString string; 552 format.Format(string, fNewMessages); 553 554 _GetNewQueryRef(ref); 555 556 item = new BMenuItem(navMenu = new BNavMenu(string.String(), 557 B_REFS_RECEIVED, BMessenger(kTrackerSignature)), 558 msg = new BMessage(B_REFS_RECEIVED)); 559 msg->AddRef("refs", &ref); 560 navMenu->SetNavDir(&ref); 561 562 menu->AddItem(item); 563 } else { 564 menu->AddItem(item = new BMenuItem(B_TRANSLATE("No new messages"), 565 NULL)); 566 item->SetEnabled(false); 567 } 568 569 BMailAccounts accounts; 570 if ((modifiers() & B_SHIFT_KEY) != 0) { 571 BMenu *accountMenu = new BMenu(B_TRANSLATE("Check for mails only")); 572 BFont font; 573 menu->GetFont(&font); 574 accountMenu->SetFont(&font); 575 576 for (int32 i = 0; i < accounts.CountAccounts(); i++) { 577 BMailAccountSettings* account = accounts.AccountAt(i); 578 579 BMessage* message = new BMessage(MD_CHECK_FOR_MAILS); 580 message->AddInt32("account", account->AccountID()); 581 582 accountMenu->AddItem(new BMenuItem(account->Name(), message)); 583 } 584 if (accounts.CountAccounts() == 0) { 585 item = new BMenuItem(B_TRANSLATE("<no accounts>"), NULL); 586 item->SetEnabled(false); 587 accountMenu->AddItem(item); 588 } 589 accountMenu->SetTargetForItems(this); 590 menu->AddItem(new BMenuItem(accountMenu, 591 new BMessage(MD_CHECK_FOR_MAILS))); 592 593 // Not used: 594 // menu->AddItem(new BMenuItem(B_TRANSLATE("Check For Mails Only"), 595 // new BMessage(MD_CHECK_FOR_MAILS))); 596 menu->AddItem(new BMenuItem(B_TRANSLATE("Send pending mails"), 597 new BMessage(MD_SEND_MAILS))); 598 } else { 599 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Check for mail now"), 600 new BMessage(MD_CHECK_SEND_NOW))); 601 if (accounts.CountAccounts() == 0) 602 item->SetEnabled(false); 603 } 604 605 menu->AddSeparatorItem(); 606 menu->AddItem(new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS), 607 new BMessage(MD_OPEN_PREFS))); 608 609 if (modifiers() & B_SHIFT_KEY) { 610 menu->AddItem(new BMenuItem(B_TRANSLATE("Shutdown mail services"), 611 new BMessage(B_QUIT_REQUESTED))); 612 } 613 614 // Reset Item Targets (only those which aren't already set) 615 616 for (int32 i = menu->CountItems(); i-- > 0;) { 617 item = menu->ItemAt(i); 618 if (item != NULL && (msg = item->Message()) != NULL) { 619 if (msg->what == B_REFS_RECEIVED) 620 item->SetTarget(tracker); 621 else 622 item->SetTarget(this); 623 } 624 } 625 return menu; 626 } 627 628 629 status_t 630 DeskbarView::_GetNewQueryRef(entry_ref& ref) 631 { 632 BPath path; 633 find_directory(B_USER_SETTINGS_DIRECTORY, &path); 634 path.Append("Mail/New E-mail"); 635 BEntry query(path.Path()); 636 if (!query.Exists()) 637 _CreateNewMailQuery(query); 638 return query.GetRef(&ref); 639 } 640