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