xref: /haiku/src/servers/mail/DeskbarView.cpp (revision 4e151bc3093293a8ab47aeae854dc80d04e9b41f)
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 what;
250 			dev_t device;
251 			ino_t directory;
252 			const char *name;
253 			entry_ref ref;
254 			message->FindInt32("opcode", &what);
255 			message->FindInt32("device", &device);
256 			message->FindInt64("directory", &directory);
257 			switch (what) {
258 				case B_ENTRY_CREATED:
259 					if (message->FindString("name", &name) == B_OK) {
260 						ref.device = device;
261 						ref.directory = directory;
262 						ref.set_name(name);
263 						if (!_EntryInTrash(&ref))
264 							fNewMessages++;
265 					}
266 					break;
267 				case B_ENTRY_REMOVED:
268 					node_ref node;
269 					node.device = device;
270 					node.node = directory;
271 					BDirectory dir(&node);
272 					BEntry entry(&dir, NULL);
273 					entry.GetRef(&ref);
274 					if (!_EntryInTrash(&ref))
275 						fNewMessages--;
276 					break;
277 			}
278 			fStatus = fNewMessages > 0 ? kStatusNewMail : kStatusNoMail;
279 			Invalidate();
280 			break;
281 		}
282 		case B_QUIT_REQUESTED:
283 			BMailDaemon().Quit();
284 			break;
285 
286 		// open received files in the standard mail application
287 		case B_REFS_RECEIVED:
288 		{
289 			BMessage argv(B_ARGV_RECEIVED);
290 			argv.AddString("argv", "E-mail");
291 
292 			entry_ref ref;
293 			BPath path;
294 			int i = 0;
295 
296 			while (message->FindRef("refs", i++, &ref) == B_OK
297 				&& path.SetTo(&ref) == B_OK) {
298 				//fprintf(stderr,"got %s\n", path.Path());
299 				argv.AddString("argv", path.Path());
300 			}
301 
302 			if (i > 1) {
303 				argv.AddInt32("argc", i);
304 				be_roster->Launch("text/x-email", &argv);
305 			}
306 			break;
307 		}
308 		default:
309 			BView::MessageReceived(message);
310 	}
311 }
312 
313 
314 void
315 DeskbarView::_InitBitmaps()
316 {
317 	for (int i = 0; i < kStatusCount; i++)
318 		fBitmaps[i] = NULL;
319 
320 	image_info info;
321 	if (our_image(info) != B_OK)
322 		return;
323 
324 	BFile file(info.name, B_READ_ONLY);
325 	if (file.InitCheck() != B_OK)
326 		return;
327 
328 	BResources resources(&file);
329 	if (resources.InitCheck() != B_OK)
330 		return;
331 
332 	for (int i = 0; i < kStatusCount; i++) {
333 		const void* data = NULL;
334 		size_t size;
335 		data = resources.LoadResource(B_VECTOR_ICON_TYPE,
336 			kIconNoMail + i, &size);
337 		if (data != NULL) {
338 			BBitmap* icon = new BBitmap(Bounds(), B_RGBA32);
339 			if (icon->InitCheck() == B_OK
340 				&& BIconUtils::GetVectorIcon((const uint8 *)data,
341 					size, icon) == B_OK) {
342 				fBitmaps[i] = icon;
343 			} else
344 				delete icon;
345 		}
346 	}
347 }
348 
349 
350 void
351 DeskbarView::Pulse()
352 {
353 	// TODO: Check if mail_daemon is still running
354 }
355 
356 
357 void
358 DeskbarView::MouseUp(BPoint pos)
359 {
360 	if ((fLastButtons & B_PRIMARY_MOUSE_BUTTON) !=0
361 		&& OpenWithTracker(B_USER_SETTINGS_DIRECTORY, "Mail/mailbox") != B_OK) {
362 		entry_ref ref;
363 		_GetNewQueryRef(ref);
364 
365 		BMessenger trackerMessenger(kTrackerSignature);
366 		BMessage message(B_REFS_RECEIVED);
367 		message.AddRef("refs", &ref);
368 
369 		trackerMessenger.SendMessage(&message);
370 	}
371 
372 	if ((fLastButtons & B_TERTIARY_MOUSE_BUTTON) != 0)
373 		BMailDaemon().CheckMail();
374 }
375 
376 
377 void
378 DeskbarView::MouseDown(BPoint pos)
379 {
380 	Looper()->CurrentMessage()->FindInt32("buttons", &fLastButtons);
381 
382 	if ((fLastButtons & B_SECONDARY_MOUSE_BUTTON) != 0) {
383 		ConvertToScreen(&pos);
384 
385 		BPopUpMenu* menu = _BuildMenu();
386 		menu->Go(pos, true, true, BRect(pos.x - 2, pos.y - 2,
387 			pos.x + 2, pos.y + 2), true);
388 	}
389 }
390 
391 
392 bool
393 DeskbarView::_CreateMenuLinks(BDirectory& directory, BPath& path)
394 {
395 	status_t status = directory.SetTo(path.Path());
396 	if (status == B_OK)
397 		return true;
398 
399 	// Check if the directory has to be created (and do it in this case,
400 	// filling it with some standard links).  Normally the installer will
401 	// create the directory and fill it with links, so normally this doesn't
402 	// get used.
403 
404 	BEntry entry(path.Path());
405 	if (status != B_ENTRY_NOT_FOUND
406 		|| entry.GetParent(&directory) < B_OK
407 		|| directory.CreateDirectory(path.Leaf(), NULL) < B_OK
408 		|| directory.SetTo(path.Path()) < B_OK)
409 		return false;
410 
411 	BPath targetPath;
412 	find_directory(B_USER_DIRECTORY, &targetPath);
413 	targetPath.Append("mail/in");
414 
415 	directory.CreateSymLink("Open Inbox Folder", targetPath.Path(), NULL);
416 	targetPath.GetParent(&targetPath);
417 	directory.CreateSymLink("Open Mail Folder", targetPath.Path(), NULL);
418 
419 	// create the draft query
420 
421 	BFile file;
422 	if (directory.CreateFile("Open Draft", &file) < B_OK)
423 		return true;
424 
425 	BString string("MAIL:draft==1");
426 	file.WriteAttrString("_trk/qrystr", &string);
427 	string = "E-mail";
428 	file.WriteAttrString("_trk/qryinitmime", &string);
429 	BNodeInfo(&file).SetType("application/x-vnd.Be-query");
430 
431 	return true;
432 }
433 
434 
435 void
436 DeskbarView::_CreateNewMailQuery(BEntry& query)
437 {
438 	BFile file(&query, B_READ_WRITE | B_CREATE_FILE);
439 	if (file.InitCheck() != B_OK)
440 		return;
441 
442 	BString string(B_MAIL_ATTR_STATUS "==\"New\"");
443 	file.WriteAttrString("_trk/qrystr", &string);
444 	file.WriteAttrString("_trk/qryinitstr", &string);
445 	int32 mode = 'Fbyq';
446 	file.WriteAttr("_trk/qryinitmode", B_INT32_TYPE, 0, &mode, sizeof(int32));
447 	string = "E-mail";
448 	file.WriteAttrString("_trk/qryinitmime", &string);
449 	BNodeInfo(&file).SetType("application/x-vnd.Be-query");
450 }
451 
452 
453 BPopUpMenu*
454 DeskbarView::_BuildMenu()
455 {
456 	BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
457 	menu->SetFont(be_plain_font);
458 
459 	menu->AddItem(new BMenuItem(B_TRANSLATE("Create new message"
460 		B_UTF8_ELLIPSIS), new BMessage(MD_OPEN_NEW)));
461 	menu->AddSeparatorItem();
462 
463 	BMessenger tracker(kTrackerSignature);
464 	BNavMenu* navMenu;
465 	BMenuItem* item;
466 	BMessage* msg;
467 	entry_ref ref;
468 
469 	BPath path;
470 	find_directory(B_USER_SETTINGS_DIRECTORY, &path);
471 	path.Append("Mail/Menu Links");
472 
473 	BDirectory directory;
474 	if (_CreateMenuLinks(directory, path)) {
475 		int32 count = 0;
476 
477 		while (directory.GetNextRef(&ref) == B_OK) {
478 			count++;
479 
480 			path.SetTo(&ref);
481 			// the true here dereferences the symlinks all the way :)
482 			BEntry entry(&ref, true);
483 
484 			// do we want to use the NavMenu, or just an ordinary BMenuItem?
485 			// we are using the NavMenu only for directories and queries
486 			bool useNavMenu = false;
487 
488 			if (entry.InitCheck() == B_OK) {
489 				if (entry.IsDirectory())
490 					useNavMenu = true;
491 				else if (entry.IsFile()) {
492 					// Files should use the BMenuItem unless they are queries
493 					char mimeString[B_MIME_TYPE_LENGTH];
494 					BNode node(&entry);
495 					BNodeInfo info(&node);
496 					if (info.GetType(mimeString) == B_OK
497 						&& strcmp(mimeString, "application/x-vnd.Be-query")
498 							== 0)
499 						useNavMenu = true;
500 				}
501 				// clobber the existing ref only if the symlink derefernces
502 				// completely, otherwise we'll stick with what we have
503 				entry.GetRef(&ref);
504 			}
505 
506 			msg = new BMessage(B_REFS_RECEIVED);
507 			msg->AddRef("refs", &ref);
508 
509 			if (useNavMenu) {
510 				item = new BMenuItem(navMenu = new BNavMenu(path.Leaf(),
511 					B_REFS_RECEIVED, tracker), msg);
512 				navMenu->SetNavDir(&ref);
513 			} else
514 				item = new BMenuItem(path.Leaf(), msg);
515 
516 			menu->AddItem(item);
517 			if (entry.InitCheck() != B_OK)
518 				item->SetEnabled(false);
519 		}
520 		if (count > 0)
521 			menu->AddSeparatorItem();
522 	}
523 
524 	// Hack for R5's buggy Query Notification
525 	#ifdef HAIKU_TARGET_PLATFORM_BEOS
526 		menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh New Mail Count"),
527 			new BMessage(MD_REFRESH_QUERY)));
528 	#endif
529 
530 	// The New E-mail query
531 
532 	if (fNewMessages > 0) {
533 		static BStringFormat format(B_TRANSLATE(
534 			"{0, plural, one{# new message} other{# new messages}}"));
535 		BString string;
536 		format.Format(string, fNewMessages);
537 
538 		_GetNewQueryRef(ref);
539 
540 		item = new BMenuItem(navMenu = new BNavMenu(string.String(),
541 			B_REFS_RECEIVED, BMessenger(kTrackerSignature)),
542 			msg = new BMessage(B_REFS_RECEIVED));
543 		msg->AddRef("refs", &ref);
544 		navMenu->SetNavDir(&ref);
545 
546 		menu->AddItem(item);
547 	} else {
548 		menu->AddItem(item = new BMenuItem(B_TRANSLATE("No new messages"),
549 			NULL));
550 		item->SetEnabled(false);
551 	}
552 
553 	BMailAccounts accounts;
554 	if ((modifiers() & B_SHIFT_KEY) != 0) {
555 		BMenu *accountMenu = new BMenu(B_TRANSLATE("Check for mails only"));
556 		BFont font;
557 		menu->GetFont(&font);
558 		accountMenu->SetFont(&font);
559 
560 		for (int32 i = 0; i < accounts.CountAccounts(); i++) {
561 			BMailAccountSettings* account = accounts.AccountAt(i);
562 
563 			BMessage* message = new BMessage(MD_CHECK_FOR_MAILS);
564 			message->AddInt32("account", account->AccountID());
565 
566 			accountMenu->AddItem(new BMenuItem(account->Name(), message));
567 		}
568 		if (accounts.CountAccounts() == 0) {
569 			item = new BMenuItem(B_TRANSLATE("<no accounts>"), NULL);
570 			item->SetEnabled(false);
571 			accountMenu->AddItem(item);
572 		}
573 		accountMenu->SetTargetForItems(this);
574 		menu->AddItem(new BMenuItem(accountMenu,
575 			new BMessage(MD_CHECK_FOR_MAILS)));
576 
577 		// Not used:
578 		// menu->AddItem(new BMenuItem(B_TRANSLATE("Check For Mails Only"),
579 		// new BMessage(MD_CHECK_FOR_MAILS)));
580 		menu->AddItem(new BMenuItem(B_TRANSLATE("Send pending mails"),
581 			new BMessage(MD_SEND_MAILS)));
582 	} else {
583 		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Check for mail now"),
584 			new BMessage(MD_CHECK_SEND_NOW)));
585 		if (accounts.CountAccounts() == 0)
586 			item->SetEnabled(false);
587 	}
588 
589 	menu->AddSeparatorItem();
590 	menu->AddItem(new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS),
591 		new BMessage(MD_OPEN_PREFS)));
592 
593 	if (modifiers() & B_SHIFT_KEY) {
594 		menu->AddItem(new BMenuItem(B_TRANSLATE("Shutdown mail services"),
595 			new BMessage(B_QUIT_REQUESTED)));
596 	}
597 
598 	// Reset Item Targets (only those which aren't already set)
599 
600 	for (int32 i = menu->CountItems(); i-- > 0;) {
601 		item = menu->ItemAt(i);
602 		if (item != NULL && (msg = item->Message()) != NULL) {
603 			if (msg->what == B_REFS_RECEIVED)
604 				item->SetTarget(tracker);
605 			else
606 				item->SetTarget(this);
607 		}
608 	}
609 	return menu;
610 }
611 
612 
613 status_t
614 DeskbarView::_GetNewQueryRef(entry_ref& ref)
615 {
616 	BPath path;
617 	find_directory(B_USER_SETTINGS_DIRECTORY, &path);
618 	path.Append("Mail/New E-mail");
619 	BEntry query(path.Path());
620 	if (!query.Exists())
621 		_CreateNewMailQuery(query);
622 	return query.GetRef(&ref);
623 }
624