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