root/src/bin/network/ppp_up/ConnectionView.cpp
/*
 * Copyright 2005, Waldemar Kornewald <wkornew@gmx.net>
 * Distributed under the terms of the MIT License.
 */

#include "ConnectionView.h"
#include "PPPDeskbarReplicant.h"
#include <MessageDriverSettingsUtils.h>

#include <Application.h>
#include <Box.h>
#include <Button.h>
#include <Deskbar.h>
#include <Entry.h>
#include <File.h>
#include <String.h>
#include <StringView.h>
#include <TextControl.h>
#include <Window.h>

#include <PPPInterface.h>
#include <settings_tools.h>
#include <algorithm>
        // for max()

using std::max;

// GUI constants
static const uint32 kDefaultButtonWidth = 80;

// message constants
static const uint32 kMsgCancel = 'CANC';
static const uint32 kMsgConnect = 'CONN';
static const uint32 kMsgUpdate = 'MUPD';

// labels
static const char *kLabelSavePassword = "Save password";
static const char *kLabelName = "Username: ";
static const char *kLabelPassword = "Password: ";
static const char *kLabelConnect = "Connect";
static const char *kLabelCancel = "Cancel";
static const char *kLabelAuthentication = "Authentication";

// connection status strings
static const char *kTextConnecting = "Connecting...";
static const char *kTextConnectionEstablished = "Connection established.";
static const char *kTextNotConnected = "Not connected.";
static const char *kTextDeviceUpFailed = "Failed to connect.";
static const char *kTextAuthenticating = "Authenticating...";
static const char *kTextAuthenticationFailed = "Authentication failed!";
static const char *kTextConnectionLost = "Connection lost!";


ConnectionView::ConnectionView(BRect rect, const BString& interfaceName)
        : BView(rect, "ConnectionView", B_FOLLOW_NONE, 0),
        fListener(this),
        fInterfaceName(interfaceName),
        fKeepLabel(false)
{
        SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
        
        rect = Bounds();
        rect.InsetBy(5, 5);
        rect.bottom = rect.top
                + 25 // space for topmost control
                + 3 * 20 // size of controls
                + 3 * 5; // space beween controls and bottom of box
        BBox *authenticationBox = new BBox(rect, "Authentication");
        authenticationBox->SetLabel(kLabelAuthentication);
        rect = authenticationBox->Bounds();
        rect.InsetBy(10, 20);
        rect.bottom = rect.top + 20;
        fUsername = new BTextControl(rect, "username", kLabelName, NULL, NULL);
        rect.top = rect.bottom + 5;
        rect.bottom = rect.top + 20;
        fPassword = new BTextControl(rect, "password", kLabelPassword, NULL, NULL);
        fPassword->TextView()->HideTyping(true);
        
        // set dividers
        float width = max(StringWidth(fUsername->Label()),
                StringWidth(fPassword->Label()));
        fUsername->SetDivider(width + 5);
        fPassword->SetDivider(width + 5);
        
        rect.top = rect.bottom + 5;
        rect.bottom = rect.top + 20;
        fSavePassword = new BCheckBox(rect, "SavePassword", kLabelSavePassword, NULL);
        
        authenticationBox->AddChild(fUsername);
        authenticationBox->AddChild(fPassword);
        authenticationBox->AddChild(fSavePassword);
        AddChild(authenticationBox);
        
        rect = authenticationBox->Frame();
        rect.top = rect.bottom + 10;
        rect.bottom = rect.top + 15;
        fAttemptView = new BStringView(rect, "AttemptView", "");
        AddChild(fAttemptView);
        
        // add status view
        rect.top = rect.bottom + 5;
        rect.bottom = rect.top + 15;
        fStatusView = new BStringView(rect, "StatusView", "");
        AddChild(fStatusView);
        
        // add "Connect" and "Cancel" buttons
        rect.top = rect.bottom + 10;
        rect.bottom = rect.top + 25;
        rect.right = rect.left + kDefaultButtonWidth;
        fConnectButton = new BButton(rect, "ConnectButton", kLabelConnect,
                new BMessage(kMsgConnect));
        
        rect.left = rect.right + 10;
        rect.right = rect.left + kDefaultButtonWidth;
        fCancelButton = new BButton(rect, "CancelButton", kLabelCancel,
                new BMessage(kMsgCancel));
        
        AddChild(fConnectButton);
        AddChild(fCancelButton);
}


void
ConnectionView::AttachedToWindow()
{
        Reload();
        fListener.WatchManager();
        WatchInterface(fListener.Manager().InterfaceWithName(fInterfaceName.String()));
        
        Window()->SetDefaultButton(fConnectButton);
        fConnectButton->SetTarget(this);
        fCancelButton->SetTarget(this);
}


void
ConnectionView::MessageReceived(BMessage *message)
{
        switch(message->what) {
                case PPP_REPORT_MESSAGE:
                        HandleReportMessage(message);
                break;
                
                case kMsgConnect:
                        Connect();
                break;
                
                case kMsgCancel:
                        Cancel();
                break;
                
                default:
                        BView::MessageReceived(message);
        }
}


// update authentication UI
void
ConnectionView::Reload()
{
        // load username and password
        BString path("ptpnet/");
        path << fInterfaceName;
        fSettings.MakeEmpty();
        ReadMessageDriverSettings(path.String(), &fSettings);
        
        fHasUsername = fHasPassword = false;
        BString username, password;
        
        BMessage parameter;
        int32 parameterIndex = 0;
        if(FindMessageParameter(PPP_USERNAME_KEY, fSettings, &parameter, &parameterIndex)
                        && parameter.FindString(MDSU_VALUES, &username) == B_OK)
                fHasUsername = true;
        
        parameterIndex = 0;
        if(FindMessageParameter(PPP_PASSWORD_KEY, fSettings, &parameter, &parameterIndex)
                        && parameter.FindString(MDSU_VALUES, &password) == B_OK)
                fHasPassword = true;
        
        fUsername->SetText(username.String());
        fPassword->SetText(password.String());
        fSavePassword->SetValue(fHasPassword);
        
        fUsername->SetEnabled(fHasUsername);
        fPassword->SetEnabled(fHasUsername);
        fSavePassword->SetEnabled(fHasUsername);
}


void
ConnectionView::Connect()
{
        PPPInterface interface(PPPManager().CreateInterfaceWithName(
                fInterfaceName.String()));
        interface.SetUsername(Username());
        interface.SetPassword(Password());
        interface.SetAskBeforeConnecting(false);
        interface.Up();
        
        // save settings
        if(fHasUsername) {
                BMessage parameter;
                int32 index = 0;
                if(FindMessageParameter(PPP_USERNAME_KEY, fSettings, &parameter, &index))
                        fSettings.RemoveData(MDSU_PARAMETERS, index);
                parameter.MakeEmpty();
                parameter.AddString(MDSU_NAME, PPP_USERNAME_KEY);
                parameter.AddString(MDSU_VALUES, Username());
                fSettings.AddMessage(MDSU_PARAMETERS, &parameter);
                
                index = 0;
                if(FindMessageParameter(PPP_PASSWORD_KEY, fSettings, &parameter, &index))
                        fSettings.RemoveData(MDSU_PARAMETERS, index);
                if(DoesSavePassword()) {
                        parameter.MakeEmpty();
                        parameter.AddString(MDSU_NAME, PPP_PASSWORD_KEY);
                        parameter.AddString(MDSU_VALUES, Password());
                        fSettings.AddMessage(MDSU_PARAMETERS, &parameter);
                }
                
                BEntry entry;
                if(interface.GetSettingsEntry(&entry) == B_OK) {
                        BFile file(&entry, B_WRITE_ONLY);
                        WriteMessageDriverSettings(file, fSettings);
                }
        }
        
        Reload();
}


void
ConnectionView::Cancel()
{
        PPPInterface interface(fListener.Interface());
        bool quit = false;
        ppp_interface_info_t info;
        if(interface.GetInterfaceInfo(&info) && info.info.phase < PPP_ESTABLISHMENT_PHASE)
                quit = true;
        interface.Down();
        if(quit)
                Window()->Quit();
}


// Clean up before our window quits (called by ConnectionWindow).
void
ConnectionView::CleanUp()
{
        fListener.StopWatchingInterface();
        fListener.StopWatchingManager();
}


BString
ConnectionView::AttemptString() const
{
        PPPInterface interface(fListener.Interface());
        ppp_interface_info_t info;
        if(!interface.GetInterfaceInfo(&info))
                return BString("");
        BString attempt;
        attempt << "Attempt " << info.info.connectAttempt << " of " <<
                info.info.connectRetriesLimit + 1;
        
        return attempt;
}


void
ConnectionView::HandleReportMessage(BMessage *message)
{
        ppp_interface_id id;
        if(message->FindInt32("interface", reinterpret_cast<int32*>(&id)) != B_OK
                        || (fListener.Interface() != PPP_UNDEFINED_INTERFACE_ID
                                && id != fListener.Interface()))
                return;
        
        int32 type, code;
        message->FindInt32("type", &type);
        message->FindInt32("code", &code);
        
        if(type == PPP_MANAGER_REPORT && code == PPP_REPORT_INTERFACE_CREATED) {
                PPPInterface interface(id);
                if(interface.InitCheck() != B_OK || fInterfaceName != interface.Name())
                        return;
                
                WatchInterface(id);
                
                if(((fHasUsername && !fHasPassword) || fAskBeforeConnecting)
                                && Window()->IsHidden())
                        Window()->Show();
        } else if(type == PPP_CONNECTION_REPORT)
                UpdateStatus(code);
        else if(type == PPP_DESTRUCTION_REPORT)
                fListener.StopWatchingInterface();
}


void
ConnectionView::UpdateStatus(int32 code)
{
        BString attemptString = AttemptString();
        fAttemptView->SetText(attemptString.String());
        
        if(code == PPP_REPORT_UP_SUCCESSFUL) {
                fStatusView->SetText(kTextConnectionEstablished);
                PPPDeskbarReplicant *item = new PPPDeskbarReplicant(fListener.Interface());
                BDeskbar().AddItem(item);
                delete item;
                Window()->Quit();
                return;
        }
        
        // maybe the status string must not be changed (codes that set fKeepLabel to false
        // should still be handled)
        if(fKeepLabel && code != PPP_REPORT_GOING_UP && code != PPP_REPORT_UP_SUCCESSFUL)
                return;
        
        if(fListener.InitCheck() != B_OK) {
                fStatusView->SetText(kTextConnectionLost);
                return;
        }
        
        // only errors should set fKeepLabel to true
        switch(code) {
                case PPP_REPORT_GOING_UP:
                        fKeepLabel = false;
                        fStatusView->SetText(kTextConnecting);
                break;
                
                case PPP_REPORT_DOWN_SUCCESSFUL:
                        fStatusView->SetText(kTextNotConnected);
                break;
                
                case PPP_REPORT_DEVICE_UP_FAILED:
                        fKeepLabel = true;
                        fStatusView->SetText(kTextDeviceUpFailed);
                break;
                
                case PPP_REPORT_AUTHENTICATION_REQUESTED:
                        fStatusView->SetText(kTextAuthenticating);
                break;
                
                case PPP_REPORT_AUTHENTICATION_FAILED:
                        fKeepLabel = true;
                        fStatusView->SetText(kTextAuthenticationFailed);
                break;
                
                case PPP_REPORT_CONNECTION_LOST:
                        fKeepLabel = true;
                        fStatusView->SetText(kTextConnectionLost);
                break;
        }
}


void
ConnectionView::WatchInterface(ppp_interface_id ID)
{
        fListener.WatchInterface(ID);
        
        // update status
        Reload();
        PPPInterface interface(fListener.Interface());
        ppp_interface_info_t info;
        if(!interface.GetInterfaceInfo(&info)) {
                UpdateStatus(PPP_REPORT_DOWN_SUCCESSFUL);
                fAskBeforeConnecting = false;
        } else
                fAskBeforeConnecting = info.info.askBeforeConnecting;
}