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