root/src/apps/people/PeopleApp.cpp
/*
 * Copyright 2005-2023, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *              Robert Polic
 *              Axel Dörfler, axeld@pinc-software.de
 *              Stephan Aßmus <superstippi@gmx.de>
 *
 * Copyright 1999, Be Incorporated.   All Rights Reserved.
 * This file may be used under the terms of the Be Sample Code License.
 */


#include "PeopleApp.h"

#include <Alert.h>
#include <AutoDeleter.h>
#include <Bitmap.h>
#include <Catalog.h>
#include <Directory.h>
#include <FindDirectory.h>
#include <fs_index.h>
#include <Locale.h>
#include <Path.h>
#include <Roster.h>
#include <Screen.h>
#include <Volume.h>
#include <VolumeRoster.h>

#include "PersonWindow.h"
#include "PersonIcons.h"

#include <string.h>


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "People"


struct DefaultAttribute {
        const char*     attribute;
        int32           width;
        const char*     name;
};

// TODO: Add flags in attribute info message to find these.
static const char* kNameAttribute = "META:name";
static const char* kCategoryAttribute = "META:group";

struct DefaultAttribute sDefaultAttributes[] = {
        { kNameAttribute, 120, B_TRANSLATE("Contact name") },
        { "META:nickname", 120, B_TRANSLATE("Nickname") },
        { "META:company", 120, B_TRANSLATE("Company") },
        { "META:address", 120, B_TRANSLATE("Address") },
        { "META:city", 90, B_TRANSLATE("City") },
        { "META:state", 50, B_TRANSLATE("State") },
        { "META:zip", 50, B_TRANSLATE("Zip") },
        { "META:country", 120, B_TRANSLATE("Country") },
        { "META:hphone", 90, B_TRANSLATE("Home phone") },
        { "META:mphone", 90, B_TRANSLATE("Mobile phone") },
        { "META:wphone", 90, B_TRANSLATE("Work phone") },
        { "META:fax", 90, B_TRANSLATE("Fax") },
        { "META:email", 120, B_TRANSLATE("E-mail") },
        { "META:url", 120, B_TRANSLATE("URL") },
        { kCategoryAttribute, 120, B_TRANSLATE("Group") },
        { NULL, 0, NULL }
};


TPeopleApp::TPeopleApp()
        :
        BApplication(APP_SIG),
        fWindowCount(0),
        fAttributes(20)
{
        B_TRANSLATE_MARK_SYSTEM_NAME_VOID("People");

        fPosition.Set(6, TITLE_BAR_HEIGHT, 6 + WIND_WIDTH,
                TITLE_BAR_HEIGHT + WIND_HEIGHT);
        BPoint pos = fPosition.LeftTop();

        BPath path;
        find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);

        BDirectory dir(path.Path());
        BEntry entry;
        if (dir.FindEntry("People_data", &entry) == B_OK) {
                fPrefs = new BFile(&entry, B_READ_WRITE);
                if (fPrefs->InitCheck() == B_NO_ERROR) {
                        fPrefs->Read(&pos, sizeof(BPoint));
                        if (BScreen(B_MAIN_SCREEN_ID).Frame().Contains(pos))
                                fPosition.OffsetTo(pos);
                }
        } else {
                fPrefs = new BFile();
                if (dir.CreateFile("People_data", fPrefs) != B_OK) {
                        delete fPrefs;
                        fPrefs = NULL;
                }
        }

        // Read attributes from person mime type. If it does not exist,
        // or if it contains no attribute definitions, install a "clean"
        // person mime type from the hard-coded default attributes.

        bool valid = false;
        BMimeType mime(B_PERSON_MIMETYPE);
        if (mime.IsInstalled()) {
                BMessage info;
                if (mime.GetAttrInfo(&info) == B_NO_ERROR) {
                        int32 index = 0;
                        while (true) {
                                int32 type;
                                if (info.FindInt32("attr:type", index, &type) != B_OK)
                                        break;
                                bool editable;
                                if (info.FindBool("attr:editable", index, &editable) != B_OK)
                                        break;

                                // TODO: Support other types besides string attributes.
                                if (type != B_STRING_TYPE || !editable)
                                        break;

                                Attribute* attribute = new Attribute();
                                ObjectDeleter<Attribute> deleter(attribute);
                                if (info.FindString("attr:public_name", index,
                                                &attribute->name) != B_OK) {
                                        break;
                                }
                                if (info.FindString("attr:name", index,
                                                &attribute->attribute) != B_OK) {
                                        break;
                                }

                                if (!fAttributes.AddItem(attribute))
                                        break;

                                deleter.Detach();
                                index++;
                        }
                }
                if (fAttributes.CountItems() == 0) {
                        valid = false;
                        mime.Delete();
                } else
                        valid = true;
        }
        if (!valid) {
                mime.Install();
                mime.SetShortDescription(B_TRANSLATE_CONTEXT("Person",
                        "Short mimetype description"));
                mime.SetLongDescription(B_TRANSLATE_CONTEXT(
                        "Contact information for a person.",
                        "Long mimetype description"));
                mime.SetIcon(kPersonIcon, sizeof(kPersonIcon));
                mime.SetPreferredApp(APP_SIG);

                // add default person fields to meta-mime type
                BMessage fields;
                for (int32 i = 0; sDefaultAttributes[i].attribute; i++) {
                        fields.AddString("attr:public_name", sDefaultAttributes[i].name);
                        fields.AddString("attr:name", sDefaultAttributes[i].attribute);
                        fields.AddInt32("attr:type", B_STRING_TYPE);
                        fields.AddBool("attr:viewable", true);
                        fields.AddBool("attr:editable", true);
                        fields.AddInt32("attr:width", sDefaultAttributes[i].width);
                        fields.AddInt32("attr:alignment", B_ALIGN_LEFT);
                        fields.AddBool("attr:extra", false);

                        // Add the default attribute to the attribute list, too.
                        Attribute* attribute = new Attribute();
                        attribute->name = sDefaultAttributes[i].name;
                        attribute->attribute = sDefaultAttributes[i].attribute;
                        if (!fAttributes.AddItem(attribute))
                                delete attribute;
                }

                mime.SetAttrInfo(&fields);
        }

        // create indices on all volumes for the found attributes.

        int32 count = fAttributes.CountItems();
        BVolumeRoster volumeRoster;
        BVolume volume;
        while (volumeRoster.GetNextVolume(&volume) == B_OK) {
                for (int32 i = 0; i < count; i++) {
                        Attribute* attribute = fAttributes.ItemAt(i);
                        fs_create_index(volume.Device(), attribute->attribute,
                                B_STRING_TYPE, 0);
                }
        }

}


TPeopleApp::~TPeopleApp()
{
        delete fPrefs;
}


void
TPeopleApp::ArgvReceived(int32 argc, char** argv)
{
        BMessage message(B_REFS_RECEIVED);

        for (int32 i = 1; i < argc; i++) {
                BEntry entry(argv[i]);
                entry_ref ref;
                if (entry.Exists() && entry.GetRef(&ref) == B_OK)
                        message.AddRef("refs", &ref);
        }

        RefsReceived(&message);
}


void
TPeopleApp::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case M_NEW:
                case B_SILENT_RELAUNCH:
                        _NewWindow(NULL, message);
                        break;

                case M_WINDOW_QUITS:
                        _SavePreferences(message);
                        fWindowCount--;
                        if (fWindowCount < 1)
                                PostMessage(B_QUIT_REQUESTED);
                        break;

                case M_CONFIGURE_ATTRIBUTES:
                {
                        const char* arguments[] = { "-type", B_PERSON_MIMETYPE, 0 };
                        status_t ret = be_roster->Launch(
                                "application/x-vnd.Haiku-FileTypes",
                                sizeof(arguments) / sizeof(const char*) - 1,
                                const_cast<char**>(arguments));
                        if (ret != B_OK && ret != B_ALREADY_RUNNING) {
                                BString errorMsg(B_TRANSLATE("Launching the FileTypes "
                                        "preflet to configure Person attributes has failed."
                                        "\n\nError: "));
                                errorMsg << strerror(ret);
                                BAlert* alert = new BAlert(B_TRANSLATE("Error"),
                                        errorMsg.String(), B_TRANSLATE("OK"), NULL, NULL,
                                        B_WIDTH_AS_USUAL, B_STOP_ALERT);
                                alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
                                alert->Go(NULL);
                        }
                        break;
                }

                default:
                        BApplication::MessageReceived(message);
        }
}


void
TPeopleApp::RefsReceived(BMessage* message)
{
        int32 index = 0;
        while (message->HasRef("refs", index)) {
                entry_ref ref;
                message->FindRef("refs", index++, &ref);

                PersonWindow* window = _FindWindow(ref);
                if (window != NULL)
                        window->Activate(true);
                else {
                        BFile file(&ref, B_READ_ONLY);
                        if (file.InitCheck() == B_OK)
                                _NewWindow(&ref, NULL);
                }
        }
}


void
TPeopleApp::ReadyToRun()
{
        if (fWindowCount < 1)
                _NewWindow();
}


// #pragma mark -


PersonWindow*
TPeopleApp::_NewWindow(entry_ref* ref, BMessage* message)
{
        PersonWindow* window = new PersonWindow(fPosition,
                B_TRANSLATE("New person"), kNameAttribute,
                kCategoryAttribute, ref);

        _AddAttributes(window);
        if (message != NULL)
                window->SetInitialValues(message);

        window->Show();

        fWindowCount++;

        // Offset the position for the next window which will be opened and
        // reset it if it would open outside the screen bounds.
        fPosition.OffsetBy(20, 20);
        BScreen screen(window);
        if (fPosition.bottom > screen.Frame().bottom)
                fPosition.OffsetTo(fPosition.left, TITLE_BAR_HEIGHT);
        if (fPosition.right > screen.Frame().right)
                fPosition.OffsetTo(6, fPosition.top);

        return window;
}


void
TPeopleApp::_AddAttributes(PersonWindow* window) const
{
        int32 count = fAttributes.CountItems();
        for (int32 i = 0; i < count; i++) {
                Attribute* attribute = fAttributes.ItemAt(i);
                const char* label = attribute->name;
                if (attribute->attribute == kNameAttribute)
                        label = B_TRANSLATE("Name");

                window->AddAttribute(label, attribute->attribute);
        }
}


PersonWindow*
TPeopleApp::_FindWindow(const entry_ref& ref) const
{
        for (int32 i = 0; BWindow* window = WindowAt(i); i++) {
                PersonWindow* personWindow = dynamic_cast<PersonWindow*>(window);
                if (personWindow == NULL)
                        continue;
                if (personWindow->RefersPersonFile(ref))
                        return personWindow;
        }
        return NULL;
}


void
TPeopleApp::_SavePreferences(BMessage* message) const
{
        BRect frame;
        if (message->FindRect("frame", &frame) != B_OK)
                return;

        BPoint leftTop = frame.LeftTop();

        if (fPrefs != NULL) {
                fPrefs->Seek(0, 0);
                fPrefs->Write(&leftTop, sizeof(BPoint));
        }
}