root/src/apps/login/LoginView.cpp
/*
 * Copyright 2008, François Revol, <revol@free.fr>. All rights reserved.
 * Distributed under the terms of the MIT License.
 */
#include <Catalog.h>
#include <ScrollView.h>
#include <String.h>
#include <Window.h>
#ifdef __HAIKU__
#include <Layout.h>
#endif

#include <pwd.h>

#include "LoginApp.h"
#include "LoginView.h"

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Login View"

#define LW 120
#define CSEP 15
#define BH 20
#define BW 60

class PwdItem : public BStringItem {
public:
                                        PwdItem(struct passwd *pwd, uint32 level = 0,
                                                bool expanded = true)
                                                 : BStringItem("", level, expanded)
                                                {
                                                        if (pwd) {
                                                                BString name(pwd->pw_gecos);
                                                                // TODO: truncate at first ;
                                                                fLogin = pwd->pw_name;
                                                                SetText(name.String());
                                                        }
                                                };
        virtual                 ~PwdItem() {};
        const char*             Login() const { return fLogin.String(); };
private:
        BString                 fLogin;
};


LoginView::LoginView(BRect frame)
        : BView(frame, "LoginView", B_FOLLOW_ALL, B_PULSE_NEEDED)
{
        // TODO: when I don't need to test in BeOS anymore,
        // rewrite to use layout engine.
        SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
        SetLowUIColor(ViewUIColor());
        BRect r;
        r.Set(CSEP, CSEP, LW, Bounds().Height() - 3 * CSEP - BH);
        fUserList = new BListView(r, "users");
        BScrollView *sv = new BScrollView("userssv", fUserList,
                B_FOLLOW_LEFT | B_FOLLOW_TOP, 0, false, true);
        AddChild(sv);
        fUserList->SetSelectionMessage(new BMessage(kUserSelected));
        fUserList->SetInvocationMessage(new BMessage(kUserInvoked));

        r.Set(LW + 30, Bounds().top + CSEP,
                Bounds().right - CSEP, Bounds().top + CSEP + CSEP);
        fLoginControl = new BTextControl(r, "login", B_TRANSLATE("Login:"), "",
                new BMessage(kLoginEdited));
        AddChild(fLoginControl);

        r.OffsetBySelf(0, CSEP + CSEP);
        fPasswordControl = new BTextControl(r, "password",
                B_TRANSLATE("Password:"), "", new BMessage(kPasswordEdited));
        fPasswordControl->TextView()->HideTyping(true);
        AddChild(fPasswordControl);

        r.OffsetBySelf(0, CSEP + CSEP);
        fHidePasswordCheckBox = new BCheckBox(r, "hidepw",
                B_TRANSLATE("Hide password"), new BMessage(kHidePassword));
        fHidePasswordCheckBox->SetValue(1);
        AddChild(fHidePasswordCheckBox);

        // buttons
        float buttonWidth = BW; //(Bounds().Width() - 4 * CSEP) / 3;
        BRect buttonRect(0, Bounds().bottom - BH,
                buttonWidth, Bounds().bottom);
        buttonRect.OffsetBySelf(CSEP, -CSEP);

        fHaltButton = new BButton(buttonRect, "halt", B_TRANSLATE("Halt"),
                new BMessage(kHaltAction));
        fHaltButton->ResizeToPreferred();
        AddChild(fHaltButton);

        buttonRect.OffsetBySelf(CSEP + fHaltButton->Frame().Width(), 0);
        fRebootButton = new BButton(buttonRect, "reboot", B_TRANSLATE("Reboot"),
                new BMessage(kRebootAction));

        fRebootButton->ResizeToPreferred();
        AddChild(fRebootButton);

        BRect infoRect(buttonRect);
        infoRect.OffsetBySelf(fRebootButton->Frame().Width() + CSEP, 0);

        buttonRect.OffsetToSelf(Bounds().Width() - CSEP - buttonWidth,
                Bounds().Height() - CSEP - BH);
        fLoginButton = new BButton(buttonRect, "ok", B_TRANSLATE("OK"),
                new BMessage(kAttemptLogin));
        fLoginButton->ResizeToPreferred();
        AddChild(fLoginButton);

        infoRect.right = buttonRect.left - CSEP + 5;
        BString info;
        //info << hostname;
        fInfoView = new BStringView(infoRect, "info", info.String());
        AddChild(fInfoView);
}


LoginView::~LoginView()
{
}

void
LoginView::AttachedToWindow()
{
        fUserList->SetTarget(this);
        fLoginControl->SetTarget(this);
        fPasswordControl->SetTarget(this);
        fHidePasswordCheckBox->SetTarget(this);
        fHaltButton->SetTarget(be_app_messenger);
        fRebootButton->SetTarget(be_app_messenger);
        fLoginButton->SetTarget(this);
        Window()->SetDefaultButton(fLoginButton);
        //fLoginControl->MakeFocus();
        fUserList->MakeFocus();
        // populate user list
        BMessenger(this).SendMessage(kAddNextUser);

        // size window relative to buttons
        BRect bounds = Window()->Bounds();
        float spacing = fHaltButton->Frame().left;
        bounds.bottom = fLoginButton->Frame().bottom + spacing;
        bounds.right = fLoginButton->Frame().right + spacing;
        Window()->ResizeTo(bounds.Width(), bounds.Height());

        // Center info view
        BPoint leftTop = fInfoView->Frame().LeftTop();
        leftTop.y += fHaltButton->Frame().Height() / 2;
        leftTop.y -= fInfoView->Bounds().Height() / 2;
        fInfoView->MoveTo(leftTop);
}


void
LoginView::MessageReceived(BMessage *message)
{
        switch (message->what) {
                case kSetProgress:
                        break;
                case kAddNextUser:
                        AddNextUser();
                        break;
                case kUserSelected:
                {
                        int32 selection = fUserList->CurrentSelection();
                        if (selection > -1) {
                                PwdItem *item = dynamic_cast<PwdItem *>(
                                        fUserList->ItemAt(selection));
                                if (item)
                                        fLoginControl->SetText(item->Login());
                        }
                        break;
                }
                case kUserInvoked:
                        fPasswordControl->MakeFocus();
                        break;
                case kLoginEdited:
                        break;
                case kHidePassword:
                        fPasswordControl->TextView()->HideTyping(
                                fHidePasswordCheckBox->Value());
                        break;
                case kAttemptLogin:
                {
                        // if no pass specified and we were selecting the user,
                        // give a chance to enter the password
                        // else we might want to enter an empty password.
                        if (strlen(fPasswordControl->Text()) < 1
                                && (fUserList->IsFocus() || fLoginControl->IsFocus())) {
                                fPasswordControl->MakeFocus();
                                break;
                        }
                        BMessage *m = new BMessage(kAttemptLogin);
                        m->AddString("login", fLoginControl->Text());
                        m->AddString("password", fPasswordControl->Text());
                        be_app->PostMessage(m, NULL, this);
                        break;
                }
                case kLoginBad:
                        fPasswordControl->SetText("");
                        EnableControls(false);
                        fInfoView->SetText(B_TRANSLATE("Invalid login!"));
                        if (Window()) {
                                BPoint savedPos = Window()->Frame().LeftTop();
                                for (int i = 0; i < 10; i++) {
                                        BPoint p(savedPos);
                                        p.x += (i%2) ? 10 : -10;
                                        Window()->MoveTo(p);
                                        Window()->UpdateIfNeeded();
                                        snooze(30000);
                                }
                                Window()->MoveTo(savedPos);
                        }
                        EnableControls(true);
                        break;
                case kLoginOk:
                        // XXX: quit ?
                        if (Window()) {
                                Window()->Hide();
                        }
                        break;
                default:
                        message->PrintToStream();
                        BView::MessageReceived(message);
        }
}


void
LoginView::Pulse()
{
        BString info;
        time_t now = time(NULL);
        struct tm *t = localtime(&now);
        // TODO: use strftime and locale settings
        info << asctime(t);
        info.RemoveSet("\r\n");
        fInfoView->SetText(info.String());
}


void
LoginView::AddNextUser()
{
        struct passwd *pwd;
        if (fUserList->CountItems() < 1)
                setpwent();

        pwd = getpwent();

        if (pwd && pwd->pw_shell &&
                strcmp(pwd->pw_shell, "false") &&
                strcmp(pwd->pw_shell, "true") &&
                strcmp(pwd->pw_shell, "/bin/false") &&
                strcmp(pwd->pw_shell, "/bin/true")) {
                // not disabled
                PwdItem *item = new PwdItem(pwd);
                fUserList->AddItem(item);
        }
        if (pwd)
                BMessenger(this).SendMessage(kAddNextUser);
        else
                endpwent();
}


void
LoginView::EnableControls(bool enable)
{
        int32 i;
        for (i = 0; i < fUserList->CountItems(); i++) {
                fUserList->ItemAt(i)->SetEnabled(enable);
        }
        fLoginControl->SetEnabled(enable);
        fPasswordControl->SetEnabled(enable);
        fHidePasswordCheckBox->SetEnabled(enable);
        fHaltButton->SetEnabled(enable);
        fRebootButton->SetEnabled(enable);
        fLoginButton->SetEnabled(enable);
}