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