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
our_image(image_info & image)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*
instantiate_deskbar_item(float maxWidth,float maxHeight)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
DeskbarView(BRect frame)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
DeskbarView(BMessage * message)97 DeskbarView::DeskbarView(BMessage *message)
98 :
99 BView(message),
100 fStatus(kStatusNoMail),
101 fLastButtons(0)
102 {
103 _InitBitmaps();
104 }
105
106
~DeskbarView()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
AttachedToWindow()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
_EntryInTrash(const entry_ref * ref)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
_RefreshMailQuery()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
Instantiate(BMessage * data)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
Archive(BMessage * data,bool deep) const196 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
Draw(BRect)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
MessageReceived(BMessage * message)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
_InitBitmaps()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
Pulse()343 DeskbarView::Pulse()
344 {
345 // TODO: Check if mail_daemon is still running
346 }
347
348
349 void
MouseUp(BPoint pos)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
MouseDown(BPoint pos)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
_CreateMenuLinks(BDirectory & directory,BPath & path)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
_CreateNewMailQuery(BEntry & query)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*
_BuildMenu()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
_GetNewQueryRef(entry_ref & ref)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