root/src/add-ons/network_settings/dialup/GeneralAddon.cpp
/*
 * Copyright 2003-2004 Waldemar Kornewald. All rights reserved.
 * Copyright 2017 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 */

//-----------------------------------------------------------------------
// GeneralAddon saves the loaded settings.
// GeneralView saves the current settings.
//-----------------------------------------------------------------------

#include "GeneralAddon.h"

#include "InterfaceUtils.h"
#include "MessageDriverSettingsUtils.h"

#include <Box.h>
#include <Button.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <LayoutBuilder.h>
#include <PopUpMenu.h>
#include <StringView.h>

#include <PPPDefs.h>


// message constants
static const uint32 kMsgSelectDevice = 'SELD';
static const uint32 kMsgSelectAuthenticator = 'SELA';

// labels
static const char *kLabelGeneral = "General";
static const char *kLabelDevice = "Device: ";
static const char *kLabelNoDevicesFound = "No Devices Found!";
static const char *kLabelAuthenticator = "Login: ";
static const char *kLabelNoAuthenticatorsFound = "No Authenticators Found!";
static const char *kLabelName = "Username: ";
static const char *kLabelPassword = "Password: ";
static const char *kLabelSavePassword = "Save Password";
static const char *kLabelNone = "None";

// string constants for information saved in the settings message
static const char *kGeneralTabAuthentication = "Authentication";
static const char *kGeneralTabAuthenticators = "Authenticators";


#define DEFAULT_AUTHENTICATOR           "PAP"
        // this authenticator is selected by default when creating a new interface


GeneralAddon::GeneralAddon(BMessage *addons)
        : DialUpAddon(addons),
        fHasPassword(false),
        fAuthenticatorsCount(0),
        fSettings(NULL),
        fProfile(NULL),
        fGeneralView(NULL)
{
}


GeneralAddon::~GeneralAddon()
{
}


bool
GeneralAddon::NeedsAuthenticationRequest() const
{
        return fGeneralView->AuthenticatorName();
}


DialUpAddon*
GeneralAddon::FindDevice(const BString& moduleName) const
{
        DialUpAddon *addon;
        for(int32 index = 0; Addons()->FindPointer(DUN_DEVICE_ADDON_TYPE, index,
                        reinterpret_cast<void**>(&addon)) == B_OK; index++)
                if(addon && moduleName == addon->KernelModuleName())
                        return addon;

        return NULL;
}


bool
GeneralAddon::LoadSettings(BMessage *settings, BMessage *profile, bool isNew)
{
        fIsNew = isNew;
        fHasPassword = false;
        fDeviceName = fUsername = fPassword = "";
        fDeviceAddon = NULL;
        fAuthenticatorsCount = 0;
        fSettings = settings;
        fProfile = profile;

        if(fGeneralView)
                fGeneralView->Reload();
                        // reset all views (empty settings)

        if(!settings || !profile || isNew)
                return true;

        if(!LoadDeviceSettings())
                return false;

        if(!LoadAuthenticationSettings())
                return false;

        if(fGeneralView)
                fGeneralView->Reload();
                        // reload new settings

        return true;
}


bool
GeneralAddon::LoadDeviceSettings()
{
        int32 index = 0;
        BMessage device;
        if(!FindMessageParameter(PPP_DEVICE_KEY, *fSettings, &device, &index))
                return false;
                        // TODO: tell user that device specification is missing

        if(device.FindString(MDSU_VALUES, &fDeviceName) != B_OK)
                return false;
                        // TODO: tell user that device specification is missing

        device.AddBool(MDSU_VALID, true);
        fSettings->ReplaceMessage(MDSU_PARAMETERS, index, &device);

        fDeviceAddon = FindDevice(fDeviceName);
        if(!fDeviceAddon)
                return false;

        return fDeviceAddon->LoadSettings(fSettings, fProfile, false);
}


bool
GeneralAddon::LoadAuthenticationSettings()
{
        // we only handle the profile (although settings could contain different data)
        int32 itemIndex = 0;
        BMessage authentication, item;

        if(!FindMessageParameter(PPP_AUTHENTICATOR_KEY, *fProfile, &item, &itemIndex))
                return true;

        // find authenticators (though we load all authenticators, we only use one)
        BString name;
        for(int32 index = 0; item.FindString(MDSU_VALUES, index, &name) == B_OK; index++) {
                BMessage authenticator;
                if(!GetAuthenticator(name, &authenticator))
                        return false;
                                // fatal error: we do not know how to handle this authenticator

                MarkAuthenticatorAsValid(name);
                authentication.AddString(kGeneralTabAuthenticators, name);
                ++fAuthenticatorsCount;
        }

        fSettings->AddMessage(kGeneralTabAuthentication, &authentication);

        bool hasUsername = false;
                // a username must be present

        // load username and password
        BMessage parameter;
        int32 parameterIndex = 0;
        if(FindMessageParameter("User", item, &parameter, &parameterIndex)
                        && parameter.FindString(MDSU_VALUES, &fUsername) == B_OK) {
                hasUsername = true;
                parameter.AddBool(MDSU_VALID, true);
                item.ReplaceMessage(MDSU_PARAMETERS, parameterIndex, &parameter);
        }

        parameterIndex = 0;
        if(FindMessageParameter("Password", item, &parameter, &parameterIndex)
                        && parameter.FindString(MDSU_VALUES, &fPassword) == B_OK) {
                fHasPassword = true;
                parameter.AddBool(MDSU_VALID, true);
                item.ReplaceMessage(MDSU_PARAMETERS, parameterIndex, &parameter);
        }

        // tell DUN whether everything is valid
        if(hasUsername)
                item.AddBool(MDSU_VALID, true);

        fProfile->ReplaceMessage(MDSU_PARAMETERS, itemIndex, &item);

        return true;
}


bool
GeneralAddon::HasTemporaryProfile() const
{
        return fGeneralView->HasTemporaryProfile();
}


void
GeneralAddon::IsModified(bool *settings, bool *profile) const
{
        if(!fSettings) {
                *settings = *profile = false;
                return;
        }

        bool deviceSettings, authenticationSettings, deviceProfile, authenticationProfile;

        IsDeviceModified(&deviceSettings, &deviceProfile);
        IsAuthenticationModified(&authenticationSettings, &authenticationProfile);

        *settings = (deviceSettings || authenticationSettings);
        *profile = (deviceProfile || authenticationProfile);
}


void
GeneralAddon::IsDeviceModified(bool *settings, bool *profile) const
{
        fGeneralView->IsDeviceModified(settings, profile);
}


void
GeneralAddon::IsAuthenticationModified(bool *settings, bool *profile) const
{
        // currently we only support selecting one authenticator
        if(fAuthenticatorsCount == 0)
                *settings = fGeneralView->AuthenticatorName();
        else {
                BMessage authentication;
                if(fSettings->FindMessage(kGeneralTabAuthentication, &authentication) != B_OK) {
                        *settings = *profile = false;
                        return;
                                // error!
                }

                BString authenticator;
                if(authentication.FindString(kGeneralTabAuthenticators,
                                &authenticator) != B_OK) {
                        *settings = *profile = false;
                        return;
                                // error!
                }

                *settings = (!fGeneralView->AuthenticatorName()
                        || authenticator != fGeneralView->AuthenticatorName());
        }

        *profile = (*settings || fUsername != fGeneralView->Username()
                || (fPassword != fGeneralView->Password() && fHasPassword)
                || fHasPassword != fGeneralView->DoesSavePassword());
}


bool
GeneralAddon::SaveSettings(BMessage *settings, BMessage *profile, bool saveTemporary)
{
        if(!fSettings || !settings || !fGeneralView->DeviceName())
                return false;
                        // TODO: tell user that a device is needed (if we fail because of this)

        if(!fGeneralView->DeviceAddon() || !fGeneralView->DeviceAddon()->SaveSettings(
                        settings, profile, saveTemporary))
                return false;

        if(fGeneralView->AuthenticatorName()) {
                BMessage authenticator;
                authenticator.AddString(MDSU_NAME, PPP_AUTHENTICATOR_KEY);
                authenticator.AddString(MDSU_VALUES, fGeneralView->AuthenticatorName());
                settings->AddMessage(MDSU_PARAMETERS, &authenticator);

                BMessage username;
                username.AddString(MDSU_NAME, "User");
                username.AddString(MDSU_VALUES, fGeneralView->Username());
                authenticator.AddMessage(MDSU_PARAMETERS, &username);

                if(saveTemporary || fGeneralView->DoesSavePassword()) {
                        // save password, too
                        BMessage password;
                        password.AddString(MDSU_NAME, "Password");
                        password.AddString(MDSU_VALUES, fGeneralView->Password());
                        authenticator.AddMessage(MDSU_PARAMETERS, &password);
                }

                profile->AddMessage(MDSU_PARAMETERS, &authenticator);
        }

        return true;
}


bool
GeneralAddon::GetPreferredSize(float *width, float *height) const
{
        BRect rect;
        if(Addons()->FindRect(DUN_TAB_VIEW_RECT, &rect) != B_OK)
                rect.Set(0, 0, 200, 300);
                        // set default values

        if(width)
                *width = rect.Width();
        if(height)
                *height = rect.Height();

        return true;
}


BView*
GeneralAddon::CreateView()
{
        if (!fGeneralView) {
                fGeneralView = new GeneralView(this);
                fGeneralView->Reload();
        }

        return fGeneralView;
}


bool
GeneralAddon::GetAuthenticator(const BString& moduleName, BMessage *entry) const
{
        if(!entry)
                return false;

        BString name;
        for(int32 index = 0; Addons()->FindMessage(DUN_AUTHENTICATOR_ADDON_TYPE, index,
                        entry) == B_OK; index++) {
                entry->FindString("KernelModuleName", &name);
                if (name == moduleName)
                        return true;
        }

        return false;
}


bool
GeneralAddon::MarkAuthenticatorAsValid(const BString& moduleName)
{
        BMessage authenticator;
        int32 index = 0;
        BString name;

        for(; FindMessageParameter(PPP_AUTHENTICATOR_KEY, *fSettings, &authenticator,
                        &index); index++) {
                authenticator.FindString("KernelModuleName", &name);
                if(name == moduleName) {
                        authenticator.AddBool(MDSU_VALID, true);
                        fSettings->ReplaceMessage(MDSU_PARAMETERS, index, &authenticator);
                        return true;
                }
        }

        return false;
}


GeneralView::GeneralView(GeneralAddon *addon)
        : BView(kLabelGeneral, 0),
        fAddon(addon)
{
        fDeviceBox = new BBox("Device");
        Addon()->Addons()->AddFloat(DUN_DEVICE_VIEW_WIDTH,
                fDeviceBox->Bounds().Width() - 10); // FIXME: remove

        fDeviceField = new BMenuField("Device",
                kLabelDevice, new BPopUpMenu(kLabelNoDevicesFound));
        fDeviceField->Menu()->SetRadioMode(true);
        AddDevices();
        fDeviceBox->SetLabel(fDeviceField);

        fAuthenticatorField = new BMenuField("Authenticator",
                kLabelAuthenticator, new BPopUpMenu(kLabelNoAuthenticatorsFound));
        fAuthenticatorField->Menu()->SetRadioMode(true);
        AddAuthenticators();

        fUsername = new BTextControl("username", kLabelName, NULL, NULL);
        fPassword = new BTextControl("password", kLabelPassword, NULL, NULL);
        fPassword->TextView()->HideTyping(true);

        fSavePassword = new BCheckBox("SavePassword", kLabelSavePassword, NULL);

        BLayoutBuilder::Group<>(this, B_VERTICAL, B_USE_HALF_ITEM_SPACING)
                .SetInsets(B_USE_HALF_ITEM_INSETS)
                .AddGroup(B_HORIZONTAL)
                        .Add(fDeviceBox)
                        .AddGlue()
                .End()
                .Add(fAuthenticatorField)
                .Add(fUsername)
                .Add(fPassword)
                .Add(fSavePassword)
                .AddGlue()
        .End();
}


GeneralView::~GeneralView()
{
}


void
GeneralView::Reload()
{
        fDeviceAddon = NULL;

        BMenuItem *item = NULL;
        for(int32 index = 0; index < fDeviceField->Menu()->CountItems(); index++) {
                item = fDeviceField->Menu()->ItemAt(index);
                if(item && item->Message() && item->Message()->FindPointer("Addon",
                                        reinterpret_cast<void**>(&fDeviceAddon)) == B_OK
                                && fDeviceAddon == Addon()->DeviceAddon())
                        break;
        }

        if(fDeviceAddon && fDeviceAddon == Addon()->DeviceAddon())
                item->SetMarked(true);
        else if(Addon()->IsNew() && fDeviceField->Menu()->CountItems() > 0) {
                item = fDeviceField->Menu()->ItemAt(0);
                item->SetMarked(true);
                item->Message()->FindPointer("Addon", reinterpret_cast<void**>(&fDeviceAddon));
                fDeviceAddon->LoadSettings(Addon()->Settings(), Addon()->Profile(), true);
        } else {
                fDeviceAddon = NULL;
                item = fDeviceField->Menu()->FindMarked();
                if(item)
                        item->SetMarked(false);
        }

        if(Addon()->CountAuthenticators() > 0) {
                BString kernelModule, authenticator;
                BMessage authentication;
                if(Addon()->Settings()->FindMessage(kGeneralTabAuthentication,
                                &authentication) == B_OK)
                        authentication.FindString(kGeneralTabAuthenticators, &authenticator);
                BMenu *menu = fAuthenticatorField->Menu();
                for(int32 index = 0; index < menu->CountItems(); index++) {
                        item = menu->ItemAt(index);
                        if(item && item->Message()
                                        && item->Message()->FindString("KernelModuleName",
                                                &kernelModule) == B_OK && kernelModule == authenticator) {
                                item->SetMarked(true);
                                break;
                        }
                }
        } else if(Addon()->IsNew() && fAuthenticatorDefault)
                fAuthenticatorDefault->SetMarked(true);
        else
                fAuthenticatorNone->SetMarked(true);

        fUsername->SetText(Addon()->Username());
        fPassword->SetText(Addon()->Password());
        fSavePassword->SetValue(Addon()->HasPassword());

        ReloadDeviceView();
        UpdateControls();
}


const char*
GeneralView::DeviceName() const
{
        if(fDeviceAddon)
                return fDeviceAddon->KernelModuleName();

        return NULL;
}


const char*
GeneralView::AuthenticatorName() const
{
        BMenuItem *marked = fAuthenticatorField->Menu()->FindMarked();
        if(marked && marked != fAuthenticatorNone)
                return marked->Message()->FindString("KernelModuleName");

        return NULL;
}


void
GeneralView::IsDeviceModified(bool *settings, bool *profile) const
{
        if(fDeviceAddon != Addon()->DeviceAddon())
                *settings = *profile = true;
        else if(fDeviceAddon)
                fDeviceAddon->IsModified(settings, profile);
        else
                *settings = *profile = false;
}


void
GeneralView::AttachedToWindow()
{
        SetViewColor(Parent()->ViewColor());
        fDeviceField->Menu()->SetTargetForItems(this);
        fAuthenticatorField->Menu()->SetTargetForItems(this);
        fUsername->SetTarget(this);
        fPassword->SetTarget(this);
}


void
GeneralView::MessageReceived(BMessage *message)
{
        switch(message->what) {
                case kMsgSelectDevice:
                        if(message->FindPointer("Addon", reinterpret_cast<void**>(&fDeviceAddon))
                                        != B_OK)
                                fDeviceAddon = NULL;
                        else {
                                if(fDeviceAddon != Addon()->DeviceAddon())
                                        fDeviceAddon->LoadSettings(Addon()->Settings(), Addon()->Profile(),
                                                Addon()->IsNew());

                                ReloadDeviceView();
                        }
                break;

                case kMsgSelectAuthenticator:
                        UpdateControls();
                break;

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


void
GeneralView::ReloadDeviceView()
{
        // first remove existing device view(s)
        while (fDeviceBox->CountChildren() > 1)
                fDeviceBox->RemoveChild(fDeviceBox->ChildAt(1));

        if (!fDeviceAddon)
                return;

        BView* deviceView = fDeviceAddon->CreateView();
        if (deviceView) {
                BLayoutBuilder::Group<>(fDeviceBox, B_VERTICAL)
                        .Add(deviceView)
                .End();
        }
}


void
GeneralView::UpdateControls()
{
        BMenu *menu = fAuthenticatorField->Menu();
        int32 index = menu->IndexOf(menu->FindMarked());
        if (index < 0)
                fAuthenticatorNone->SetMarked(true);

        if (index == 0) {
                fUsername->SetEnabled(false);
                fPassword->SetEnabled(false);
                fSavePassword->SetEnabled(false);
        } else {
                fUsername->SetEnabled(true);
                fPassword->SetEnabled(true);
                fSavePassword->SetEnabled(true);
        }
}


void
GeneralView::AddDevices()
{
        AddAddonsToMenu(Addon()->Addons(), fDeviceField->Menu(), DUN_DEVICE_ADDON_TYPE,
                kMsgSelectDevice);
}


void
GeneralView::AddAuthenticators()
{
        fAuthenticatorDefault = NULL;
        fAuthenticatorNone = new BMenuItem(kLabelNone,
                new BMessage(kMsgSelectAuthenticator));
        fAuthenticatorField->Menu()->AddItem(fAuthenticatorNone);
        fAuthenticatorNone->SetMarked(true);
        fAuthenticatorField->Menu()->AddSeparatorItem();

        BMenuItem *item;
        BMessage addon;
        for(int32 index = 0;
                        Addon()->Addons()->FindMessage(DUN_AUTHENTICATOR_ADDON_TYPE, index,
                        &addon) == B_OK; index++) {
                BMessage *message = new BMessage(kMsgSelectAuthenticator);
                message->AddString("KernelModuleName", addon.FindString("KernelModuleName"));

                BString name, technicalName, friendlyName;
                bool hasTechnicalName
                        = (addon.FindString("TechnicalName", &technicalName) == B_OK);
                bool hasFriendlyName
                        = (addon.FindString("FriendlyName", &friendlyName) == B_OK);
                if(hasTechnicalName) {
                        name << technicalName;
                        if(hasFriendlyName)
                                name << " (";
                }
                if(hasFriendlyName) {
                        name << friendlyName;
                        if(hasTechnicalName)
                                name << ")";
                }

                int32 insertAt = FindNextMenuInsertionIndex(fAuthenticatorField->Menu(),
                        name.String(), 2);
                item = new BMenuItem(name.String(), message);
                if(hasTechnicalName && technicalName == DEFAULT_AUTHENTICATOR)
                        fAuthenticatorDefault = item;
                fAuthenticatorField->Menu()->AddItem(item, insertAt);
        }
}