⛏️ index : haiku.git

/*
 * Copyright 2004-2018, Haiku Inc. All Rights Reserved.
 * Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved.
 *
 * Distributed under the terms of the MIT License.
 */


//!	mail_daemon's deskbar menu and view


#include "DeskbarView.h"

#include <stdio.h>
#include <malloc.h>

#include <Bitmap.h>
#include <Catalog.h>
#include <Deskbar.h>
#include <Directory.h>
#include <Entry.h>
#include <FindDirectory.h>
#include <IconUtils.h>
#include <kernel/fs_info.h>
#include <kernel/fs_index.h>
#include <MenuItem.h>
#include <Messenger.h>
#include <NodeInfo.h>
#include <NodeMonitor.h>
#include <OpenWithTracker.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <Query.h>
#include <Rect.h>
#include <Resources.h>
#include <Roster.h>
#include <String.h>
#include <StringFormat.h>
#include <SymLink.h>
#include <VolumeRoster.h>
#include <Window.h>

#include <E-mail.h>
#include <MailDaemon.h>
#include <MailSettings.h>

#include <MailPrivate.h>

#include "DeskbarViewIcons.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "DeskbarView"


const char* kTrackerSignature = "application/x-vnd.Be-TRAK";


extern "C" _EXPORT BView* instantiate_deskbar_item(float maxWidth,
	float maxHeight);


static status_t
our_image(image_info& image)
{
	int32 cookie = 0;
	while (get_next_image_info(B_CURRENT_TEAM, &cookie, &image) == B_OK) {
		if ((char *)our_image >= (char *)image.text
			&& (char *)our_image <= (char *)image.text + image.text_size)
			return B_OK;
	}

	return B_ERROR;
}


BView*
instantiate_deskbar_item(float maxWidth, float maxHeight)
{
	return new DeskbarView(BRect(0, 0, maxHeight - 1, maxHeight - 1));
}


//	#pragma mark -


DeskbarView::DeskbarView(BRect frame)
	:
	BView(frame, "mail_daemon", B_FOLLOW_NONE, B_WILL_DRAW | B_PULSE_NEEDED),
	fStatus(kStatusNoMail),
	fLastButtons(0)
{
	_InitBitmaps();
}


DeskbarView::DeskbarView(BMessage *message)
	:
	BView(message),
	fStatus(kStatusNoMail),
	fLastButtons(0)
{
	_InitBitmaps();
}


DeskbarView::~DeskbarView()
{
	for (int i = 0; i < kStatusCount; i++)
		delete fBitmaps[i];

	for (int32 i = 0; i < fNewMailQueries.CountItems(); i++)
		delete ((BQuery *)(fNewMailQueries.ItemAt(i)));
}


void DeskbarView::AttachedToWindow()
{
	BView::AttachedToWindow();
	AdoptParentColors();

	if (ViewUIColor() == B_NO_COLOR)
		SetLowColor(ViewColor());
	else
		SetLowUIColor(ViewUIColor());

	if (be_roster->IsRunning(B_MAIL_DAEMON_SIGNATURE)) {
		_RefreshMailQuery();
	} else {
		BDeskbar deskbar;
		deskbar.RemoveItem("mail_daemon");
	}
}


bool DeskbarView::_EntryInTrash(const entry_ref* ref)
{
	BEntry entry(ref);
	BVolume volume(ref->device);
	BPath path;
	if (volume.InitCheck() != B_OK
		|| find_directory(B_TRASH_DIRECTORY, &path, false, &volume) != B_OK)
		return false;

	BDirectory trash(path.Path());
	return trash.Contains(&entry);
}


void DeskbarView::_RefreshMailQuery()
{
	for (int32 i = 0; i < fNewMailQueries.CountItems(); i++)
		delete ((BQuery *)(fNewMailQueries.ItemAt(i)));
	fNewMailQueries.MakeEmpty();

	BVolumeRoster volumes;
	BVolume volume;
	fNewMessages = 0;

	while (volumes.GetNextVolume(&volume) == B_OK) {
		BQuery *newMailQuery = new BQuery;
		newMailQuery->SetTarget(this);
		newMailQuery->SetVolume(&volume);
		newMailQuery->PushAttr(B_MAIL_ATTR_STATUS);
		newMailQuery->PushString("New");
		newMailQuery->PushOp(B_EQ);
		newMailQuery->Fetch();

		BEntry entry;
		while (newMailQuery->GetNextEntry(&entry) == B_OK) {
			if (entry.InitCheck() == B_OK) {
				entry_ref ref;
				entry.GetRef(&ref);
				if (!_EntryInTrash(&ref))
					fNewMessages++;
			}
		}

		fNewMailQueries.AddItem(newMailQuery);
	}

	fStatus = (fNewMessages > 0) ? kStatusNewMail : kStatusNoMail;
	Invalidate();
}


DeskbarView* DeskbarView::Instantiate(BMessage *data)
{
	if (!validate_instantiation(data, "DeskbarView"))
		return NULL;

	return new DeskbarView(data);
}


status_t DeskbarView::Archive(BMessage *data,bool deep) const
{
	BView::Archive(data, deep);

	data->AddString("add_on", B_MAIL_DAEMON_SIGNATURE);
	return B_NO_ERROR;
}


void
DeskbarView::Draw(BRect /*updateRect*/)
{
	if (fBitmaps[fStatus] == NULL)
		return;

	SetDrawingMode(B_OP_ALPHA);
	DrawBitmap(fBitmaps[fStatus]);
	SetDrawingMode(B_OP_COPY);
}


void
DeskbarView::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case MD_CHECK_SEND_NOW:
			// also happens in DeskbarView::MouseUp() with
			// B_TERTIARY_MOUSE_BUTTON pressed
			BMailDaemon().CheckAndSendQueuedMail();
			break;
		case MD_CHECK_FOR_MAILS:
			BMailDaemon().CheckMail(message->FindInt32("account"));
			break;
		case MD_SEND_MAILS:
			BMailDaemon().SendQueuedMail();
			break;

		case MD_OPEN_NEW:
		{
			char* argv[] = {(char *)"New Message", (char *)"mailto:"};
			be_roster->Launch("text/x-email", 2, argv);
			break;
		}
		case MD_OPEN_PREFS:
			be_roster->Launch("application/x-vnd.Haiku-Mail");
			break;

		case MD_REFRESH_QUERY:
			_RefreshMailQuery();
			break;

		case B_QUERY_UPDATE:
		{
			int32 opcode;
			message->FindInt32("opcode", &opcode);

			switch (opcode) {
				case B_ENTRY_CREATED:
				case B_ENTRY_REMOVED:
				{
					entry_ref ref;
					message->FindInt32("device", &ref.device);
					message->FindInt64("directory", &ref.directory);

					if (!_EntryInTrash(&ref)) {
						if (opcode == B_ENTRY_CREATED)
							fNewMessages++;
						else
							fNewMessages--;
					}
					break;
				}
			}

			fStatus = fNewMessages > 0 ? kStatusNewMail : kStatusNoMail;
			Invalidate();
			break;
		}
		case B_QUIT_REQUESTED:
			BMailDaemon().Quit();
			break;

		// open received files in the standard mail application
		case B_REFS_RECEIVED:
		{
			BMessage argv(B_ARGV_RECEIVED);
			argv.AddString("argv", "E-mail");

			entry_ref ref;
			BPath path;
			int i = 0;

			while (message->FindRef("refs", i++, &ref) == B_OK
				&& path.SetTo(&ref) == B_OK) {
				//fprintf(stderr,"got %s\n", path.Path());
				argv.AddString("argv", path.Path());
			}

			if (i > 1) {
				argv.AddInt32("argc", i);
				be_roster->Launch("text/x-email", &argv);
			}
			break;
		}
		default:
			BView::MessageReceived(message);
	}
}


void
DeskbarView::_InitBitmaps()
{
	for (int i = 0; i < kStatusCount; i++)
		fBitmaps[i] = NULL;

	image_info info;
	if (our_image(info) != B_OK)
		return;

	BFile file(info.name, B_READ_ONLY);
	if (file.InitCheck() != B_OK)
		return;

	BResources resources(&file);
	if (resources.InitCheck() != B_OK)
		return;

	for (int i = 0; i < kStatusCount; i++) {
		const void* data = NULL;
		size_t size;
		data = resources.LoadResource(B_VECTOR_ICON_TYPE,
			kIconNoMail + i, &size);
		if (data != NULL) {
			BBitmap* icon = new BBitmap(Bounds(), B_RGBA32);
			if (icon->InitCheck() == B_OK
				&& BIconUtils::GetVectorIcon((const uint8 *)data,
					size, icon) == B_OK) {
				fBitmaps[i] = icon;
			} else
				delete icon;
		}
	}
}


void
DeskbarView::Pulse()
{
	// TODO: Check if mail_daemon is still running
}


void
DeskbarView::MouseUp(BPoint pos)
{
	if ((fLastButtons & B_PRIMARY_MOUSE_BUTTON) !=0
		&& OpenWithTracker(B_USER_SETTINGS_DIRECTORY, "Mail/mailbox") != B_OK) {
		entry_ref ref;
		_GetNewQueryRef(ref);

		BMessenger trackerMessenger(kTrackerSignature);
		BMessage message(B_REFS_RECEIVED);
		message.AddRef("refs", &ref);

		trackerMessenger.SendMessage(&message);
	}

	if ((fLastButtons & B_TERTIARY_MOUSE_BUTTON) != 0)
		BMailDaemon().CheckMail();
}


void
DeskbarView::MouseDown(BPoint pos)
{
	Looper()->CurrentMessage()->FindInt32("buttons", &fLastButtons);

	if ((fLastButtons & B_SECONDARY_MOUSE_BUTTON) != 0) {
		ConvertToScreen(&pos);

		BPopUpMenu* menu = _BuildMenu();
		menu->Go(pos, true, true, BRect(pos.x - 2, pos.y - 2,
			pos.x + 2, pos.y + 2), true);
	}
}


bool
DeskbarView::_CreateMenuLinks(BDirectory& directory, BPath& path)
{
	status_t status = directory.SetTo(path.Path());
	if (status == B_OK)
		return true;

	// Check if the directory has to be created (and do it in this case,
	// filling it with some standard links).  Normally the installer will
	// create the directory and fill it with links, so normally this doesn't
	// get used.

	BEntry entry(path.Path());
	if (status != B_ENTRY_NOT_FOUND
		|| entry.GetParent(&directory) < B_OK
		|| directory.CreateDirectory(path.Leaf(), NULL) < B_OK
		|| directory.SetTo(path.Path()) < B_OK)
		return false;

	BPath targetPath;
	find_directory(B_USER_DIRECTORY, &targetPath);
	targetPath.Append("mail/in");

	directory.CreateSymLink("Open Inbox Folder", targetPath.Path(), NULL);
	targetPath.GetParent(&targetPath);
	directory.CreateSymLink("Open Mail Folder", targetPath.Path(), NULL);

	// create the draft query

	BFile file;
	if (directory.CreateFile("Open Draft", &file) < B_OK)
		return true;

	BString string("MAIL:draft==1");
	file.WriteAttrString("_trk/qrystr", &string);
	string = "E-mail";
	file.WriteAttrString("_trk/qryinitmime", &string);
	BNodeInfo(&file).SetType("application/x-vnd.Be-query");

	return true;
}


void
DeskbarView::_CreateNewMailQuery(BEntry& query)
{
	BFile file(&query, B_READ_WRITE | B_CREATE_FILE);
	if (file.InitCheck() != B_OK)
		return;

	BString string(B_MAIL_ATTR_STATUS "==\"New\"");
	file.WriteAttrString("_trk/qrystr", &string);
	file.WriteAttrString("_trk/qryinitstr", &string);
	int32 mode = 'Fbyq';
	file.WriteAttr("_trk/qryinitmode", B_INT32_TYPE, 0, &mode, sizeof(int32));
	string = "E-mail";
	file.WriteAttrString("_trk/qryinitmime", &string);
	BNodeInfo(&file).SetType("application/x-vnd.Be-query");
}


BPopUpMenu*
DeskbarView::_BuildMenu()
{
	BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
	menu->SetFont(be_plain_font);

	menu->AddItem(new BMenuItem(B_TRANSLATE("Create new message"
		B_UTF8_ELLIPSIS), new BMessage(MD_OPEN_NEW)));
	menu->AddSeparatorItem();

	BMessenger tracker(kTrackerSignature);
	BNavMenu* navMenu;
	BMenuItem* item;
	BMessage* msg;
	entry_ref ref;

	BPath path;
	find_directory(B_USER_SETTINGS_DIRECTORY, &path);
	path.Append("Mail/Menu Links");

	BDirectory directory;
	if (_CreateMenuLinks(directory, path)) {
		int32 count = 0;

		while (directory.GetNextRef(&ref) == B_OK) {
			count++;

			path.SetTo(&ref);
			// the true here dereferences the symlinks all the way :)
			BEntry entry(&ref, true);

			// do we want to use the NavMenu, or just an ordinary BMenuItem?
			// we are using the NavMenu only for directories and queries
			bool useNavMenu = false;

			if (entry.InitCheck() == B_OK) {
				if (entry.IsDirectory())
					useNavMenu = true;
				else if (entry.IsFile()) {
					// Files should use the BMenuItem unless they are queries
					char mimeString[B_MIME_TYPE_LENGTH];
					BNode node(&entry);
					BNodeInfo info(&node);
					if (info.GetType(mimeString) == B_OK
						&& strcmp(mimeString, "application/x-vnd.Be-query")
							== 0)
						useNavMenu = true;
				}
				// clobber the existing ref only if the symlink derefernces
				// completely, otherwise we'll stick with what we have
				entry.GetRef(&ref);
			}

			msg = new BMessage(B_REFS_RECEIVED);
			msg->AddRef("refs", &ref);

			if (useNavMenu) {
				item = new BMenuItem(navMenu = new BNavMenu(path.Leaf(),
					B_REFS_RECEIVED, tracker), msg);
				navMenu->SetNavDir(&ref);
			} else
				item = new BMenuItem(path.Leaf(), msg);

			menu->AddItem(item);
			if (entry.InitCheck() != B_OK)
				item->SetEnabled(false);
		}
		if (count > 0)
			menu->AddSeparatorItem();
	}

	// Hack for R5's buggy Query Notification
	#ifdef HAIKU_TARGET_PLATFORM_BEOS
		menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh New Mail Count"),
			new BMessage(MD_REFRESH_QUERY)));
	#endif

	// The New E-mail query

	if (fNewMessages > 0) {
		static BStringFormat format(B_TRANSLATE(
			"{0, plural, one{# new message} other{# new messages}}"));
		BString string;
		format.Format(string, fNewMessages);

		_GetNewQueryRef(ref);

		item = new BMenuItem(navMenu = new BNavMenu(string.String(),
			B_REFS_RECEIVED, BMessenger(kTrackerSignature)),
			msg = new BMessage(B_REFS_RECEIVED));
		msg->AddRef("refs", &ref);
		navMenu->SetNavDir(&ref);

		menu->AddItem(item);
	} else {
		menu->AddItem(item = new BMenuItem(B_TRANSLATE("No new messages"),
			NULL));
		item->SetEnabled(false);
	}

	BMailAccounts accounts;
	if ((modifiers() & B_SHIFT_KEY) != 0) {
		BMenu *accountMenu = new BMenu(B_TRANSLATE("Check for mails only"));
		BFont font;
		menu->GetFont(&font);
		accountMenu->SetFont(&font);

		for (int32 i = 0; i < accounts.CountAccounts(); i++) {
			BMailAccountSettings* account = accounts.AccountAt(i);

			BMessage* message = new BMessage(MD_CHECK_FOR_MAILS);
			message->AddInt32("account", account->AccountID());

			accountMenu->AddItem(new BMenuItem(account->Name(), message));
		}
		if (accounts.CountAccounts() == 0) {
			item = new BMenuItem(B_TRANSLATE("<no accounts>"), NULL);
			item->SetEnabled(false);
			accountMenu->AddItem(item);
		}
		accountMenu->SetTargetForItems(this);
		menu->AddItem(new BMenuItem(accountMenu,
			new BMessage(MD_CHECK_FOR_MAILS)));

		// Not used:
		// menu->AddItem(new BMenuItem(B_TRANSLATE("Check For Mails Only"),
		// new BMessage(MD_CHECK_FOR_MAILS)));
		menu->AddItem(new BMenuItem(B_TRANSLATE("Send pending mails"),
			new BMessage(MD_SEND_MAILS)));
	} else {
		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Check for mail now"),
			new BMessage(MD_CHECK_SEND_NOW)));
		if (accounts.CountAccounts() == 0)
			item->SetEnabled(false);
	}

	menu->AddSeparatorItem();
	menu->AddItem(new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS),
		new BMessage(MD_OPEN_PREFS)));

	if (modifiers() & B_SHIFT_KEY) {
		menu->AddItem(new BMenuItem(B_TRANSLATE("Shutdown mail services"),
			new BMessage(B_QUIT_REQUESTED)));
	}

	// Reset Item Targets (only those which aren't already set)

	for (int32 i = menu->CountItems(); i-- > 0;) {
		item = menu->ItemAt(i);
		if (item != NULL && (msg = item->Message()) != NULL) {
			if (msg->what == B_REFS_RECEIVED)
				item->SetTarget(tracker);
			else
				item->SetTarget(this);
		}
	}
	return menu;
}


status_t
DeskbarView::_GetNewQueryRef(entry_ref& ref)
{
	BPath path;
	find_directory(B_USER_SETTINGS_DIRECTORY, &path);
	path.Append("Mail/New E-mail");
	BEntry query(path.Path());
	if (!query.Exists())
		_CreateNewMailQuery(query);
	return query.GetRef(&ref);
}