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