1/*
2 * Copyright 2008, François Revol, <revol@free.fr>. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
5#include <Catalog.h>
6#include <ScrollView.h>
7#include <String.h>
8#include <Window.h>
9#ifdef __HAIKU__
10#include <Layout.h>
11#endif
12
13#include <pwd.h>
14
15#include "LoginApp.h"
16#include "LoginView.h"
17
18#undef B_TRANSLATION_CONTEXT
19#define B_TRANSLATION_CONTEXT "Login View"
20
21#define LW 120
22#define CSEP 15
23#define BH 20
24#define BW 60
25
26class PwdItem : public BStringItem {
27public:
28					PwdItem(struct passwd *pwd, uint32 level = 0,
29						bool expanded = true)
30						 : BStringItem("", level, expanded)
31						{
32							if (pwd) {
33								BString name(pwd->pw_gecos);
34								// TODO: truncate at first ;
35								fLogin = pwd->pw_name;
36								SetText(name.String());
37							}
38						};
39	virtual			~PwdItem() {};
40	const char*		Login() const { return fLogin.String(); };
41private:
42	BString			fLogin;
43};
44
45
46LoginView::LoginView(BRect frame)
47	: BView(frame, "LoginView", B_FOLLOW_ALL, B_PULSE_NEEDED)
48{
49	// TODO: when I don't need to test in BeOS anymore,
50	// rewrite to use layout engine.
51	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
52	SetLowUIColor(ViewUIColor());
53	BRect r;
54	r.Set(CSEP, CSEP, LW, Bounds().Height() - 3 * CSEP - BH);
55	fUserList = new BListView(r, "users");
56	BScrollView *sv = new BScrollView("userssv", fUserList,
57		B_FOLLOW_LEFT | B_FOLLOW_TOP, 0, false, true);
58	AddChild(sv);
59	fUserList->SetSelectionMessage(new BMessage(kUserSelected));
60	fUserList->SetInvocationMessage(new BMessage(kUserInvoked));
61
62	r.Set(LW + 30, Bounds().top + CSEP,
63		Bounds().right - CSEP, Bounds().top + CSEP + CSEP);
64	fLoginControl = new BTextControl(r, "login", B_TRANSLATE("Login:"), "",
65		new BMessage(kLoginEdited));
66	AddChild(fLoginControl);
67
68	r.OffsetBySelf(0, CSEP + CSEP);
69	fPasswordControl = new BTextControl(r, "password",
70		B_TRANSLATE("Password:"), "", new BMessage(kPasswordEdited));
71	fPasswordControl->TextView()->HideTyping(true);
72	AddChild(fPasswordControl);
73
74	r.OffsetBySelf(0, CSEP + CSEP);
75	fHidePasswordCheckBox = new BCheckBox(r, "hidepw",
76		B_TRANSLATE("Hide password"), new BMessage(kHidePassword));
77	fHidePasswordCheckBox->SetValue(1);
78	AddChild(fHidePasswordCheckBox);
79
80	// buttons
81	float buttonWidth = BW; //(Bounds().Width() - 4 * CSEP) / 3;
82	BRect buttonRect(0, Bounds().bottom - BH,
83		buttonWidth, Bounds().bottom);
84	buttonRect.OffsetBySelf(CSEP, -CSEP);
85
86	fHaltButton = new BButton(buttonRect, "halt", B_TRANSLATE("Halt"),
87		new BMessage(kHaltAction));
88	fHaltButton->ResizeToPreferred();
89	AddChild(fHaltButton);
90
91	buttonRect.OffsetBySelf(CSEP + fHaltButton->Frame().Width(), 0);
92	fRebootButton = new BButton(buttonRect, "reboot", B_TRANSLATE("Reboot"),
93		new BMessage(kRebootAction));
94
95	fRebootButton->ResizeToPreferred();
96	AddChild(fRebootButton);
97
98	BRect infoRect(buttonRect);
99	infoRect.OffsetBySelf(fRebootButton->Frame().Width() + CSEP, 0);
100
101	buttonRect.OffsetToSelf(Bounds().Width() - CSEP - buttonWidth,
102		Bounds().Height() - CSEP - BH);
103	fLoginButton = new BButton(buttonRect, "ok", B_TRANSLATE("OK"),
104		new BMessage(kAttemptLogin));
105	fLoginButton->ResizeToPreferred();
106	AddChild(fLoginButton);
107
108	infoRect.right = buttonRect.left - CSEP + 5;
109	BString info;
110	//info << hostname;
111	fInfoView = new BStringView(infoRect, "info", info.String());
112	AddChild(fInfoView);
113}
114
115
116LoginView::~LoginView()
117{
118}
119
120void
121LoginView::AttachedToWindow()
122{
123	fUserList->SetTarget(this);
124	fLoginControl->SetTarget(this);
125	fPasswordControl->SetTarget(this);
126	fHidePasswordCheckBox->SetTarget(this);
127	fHaltButton->SetTarget(be_app_messenger);
128	fRebootButton->SetTarget(be_app_messenger);
129	fLoginButton->SetTarget(this);
130	Window()->SetDefaultButton(fLoginButton);
131	//fLoginControl->MakeFocus();
132	fUserList->MakeFocus();
133	// populate user list
134	BMessenger(this).SendMessage(kAddNextUser);
135
136	// size window relative to buttons
137	BRect bounds = Window()->Bounds();
138	float spacing = fHaltButton->Frame().left;
139	bounds.bottom = fLoginButton->Frame().bottom + spacing;
140	bounds.right = fLoginButton->Frame().right + spacing;
141	Window()->ResizeTo(bounds.Width(), bounds.Height());
142
143	// Center info view
144	BPoint leftTop = fInfoView->Frame().LeftTop();
145	leftTop.y += fHaltButton->Frame().Height() / 2;
146	leftTop.y -= fInfoView->Bounds().Height() / 2;
147	fInfoView->MoveTo(leftTop);
148}
149
150
151void
152LoginView::MessageReceived(BMessage *message)
153{
154	switch (message->what) {
155		case kSetProgress:
156			break;
157		case kAddNextUser:
158			AddNextUser();
159			break;
160		case kUserSelected:
161		{
162			int32 selection = fUserList->CurrentSelection();
163			if (selection > -1) {
164				PwdItem *item = dynamic_cast<PwdItem *>(
165					fUserList->ItemAt(selection));
166				if (item)
167					fLoginControl->SetText(item->Login());
168			}
169			break;
170		}
171		case kUserInvoked:
172			fPasswordControl->MakeFocus();
173			break;
174		case kLoginEdited:
175			break;
176		case kHidePassword:
177			fPasswordControl->TextView()->HideTyping(
178				fHidePasswordCheckBox->Value());
179			break;
180		case kAttemptLogin:
181		{
182			// if no pass specified and we were selecting the user,
183			// give a chance to enter the password
184			// else we might want to enter an empty password.
185			if (strlen(fPasswordControl->Text()) < 1
186				&& (fUserList->IsFocus() || fLoginControl->IsFocus())) {
187				fPasswordControl->MakeFocus();
188				break;
189			}
190			BMessage *m = new BMessage(kAttemptLogin);
191			m->AddString("login", fLoginControl->Text());
192			m->AddString("password", fPasswordControl->Text());
193			be_app->PostMessage(m, NULL, this);
194			break;
195		}
196		case kLoginBad:
197			fPasswordControl->SetText("");
198			EnableControls(false);
199			fInfoView->SetText(B_TRANSLATE("Invalid login!"));
200			if (Window()) {
201				BPoint savedPos = Window()->Frame().LeftTop();
202				for (int i = 0; i < 10; i++) {
203					BPoint p(savedPos);
204					p.x += (i%2) ? 10 : -10;
205					Window()->MoveTo(p);
206					Window()->UpdateIfNeeded();
207					snooze(30000);
208				}
209				Window()->MoveTo(savedPos);
210			}
211			EnableControls(true);
212			break;
213		case kLoginOk:
214			// XXX: quit ?
215			if (Window()) {
216				Window()->Hide();
217			}
218			break;
219		default:
220			message->PrintToStream();
221			BView::MessageReceived(message);
222	}
223}
224
225
226void
227LoginView::Pulse()
228{
229	BString info;
230	time_t now = time(NULL);
231	struct tm *t = localtime(&now);
232	// TODO: use strftime and locale settings
233	info << asctime(t);
234	info.RemoveSet("\r\n");
235	fInfoView->SetText(info.String());
236}
237
238
239void
240LoginView::AddNextUser()
241{
242	struct passwd *pwd;
243	if (fUserList->CountItems() < 1)
244		setpwent();
245
246	pwd = getpwent();
247
248	if (pwd && pwd->pw_shell &&
249		strcmp(pwd->pw_shell, "false") &&
250		strcmp(pwd->pw_shell, "true") &&
251		strcmp(pwd->pw_shell, "/bin/false") &&
252		strcmp(pwd->pw_shell, "/bin/true")) {
253		// not disabled
254		PwdItem *item = new PwdItem(pwd);
255		fUserList->AddItem(item);
256	}
257	if (pwd)
258		BMessenger(this).SendMessage(kAddNextUser);
259	else
260		endpwent();
261}
262
263
264void
265LoginView::EnableControls(bool enable)
266{
267	int32 i;
268	for (i = 0; i < fUserList->CountItems(); i++) {
269		fUserList->ItemAt(i)->SetEnabled(enable);
270	}
271	fLoginControl->SetEnabled(enable);
272	fPasswordControl->SetEnabled(enable);
273	fHidePasswordCheckBox->SetEnabled(enable);
274	fHaltButton->SetEnabled(enable);
275	fRebootButton->SetEnabled(enable);
276	fLoginButton->SetEnabled(enable);
277}
278
279