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