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