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