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