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