root/src/servers/mail/DeskbarView.cpp
/*
 * 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);
}