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
56const char* kTrackerSignature = "application/x-vnd.Be-TRAK";
57
58
59extern "C" _EXPORT BView* instantiate_deskbar_item(float maxWidth,
60	float maxHeight);
61
62
63static status_t
64our_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
77BView*
78instantiate_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
87DeskbarView::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
97DeskbarView::DeskbarView(BMessage *message)
98	:
99	BView(message),
100	fStatus(kStatusNoMail),
101	fLastButtons(0)
102{
103	_InitBitmaps();
104}
105
106
107DeskbarView::~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
117void 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
136bool 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
150void 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
187DeskbarView* DeskbarView::Instantiate(BMessage *data)
188{
189	if (!validate_instantiation(data, "DeskbarView"))
190		return NULL;
191
192	return new DeskbarView(data);
193}
194
195
196status_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
205void
206DeskbarView::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
217void
218DeskbarView::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
306void
307DeskbarView::_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
342void
343DeskbarView::Pulse()
344{
345	// TODO: Check if mail_daemon is still running
346}
347
348
349void
350DeskbarView::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
369void
370DeskbarView::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
384bool
385DeskbarView::_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
427void
428DeskbarView::_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
445BPopUpMenu*
446DeskbarView::_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
605status_t
606DeskbarView::_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