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