1/*
2 * Copyright 2011, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "DrivesPage.h"
8
9#include <Catalog.h>
10#include <ControlLook.h>
11#include <DiskDeviceRoster.h>
12#include <DiskDevice.h>
13#include <LayoutBuilder.h>
14#include <ListView.h>
15#include <Path.h>
16#include <ScrollView.h>
17#include <TextView.h>
18#include <Bitmap.h>
19
20#include <StringForSize.h>
21
22#include "BootDrive.h"
23#include "WizardView.h"
24
25
26#undef B_TRANSLATION_CONTEXT
27#define B_TRANSLATION_CONTEXT "DrivesPage"
28
29
30const uint32 kMsgSelectionChanged = 'slch';
31
32
33class DriveItem : public BListItem {
34public:
35								DriveItem(const BDiskDevice& device,
36									const BootMenuList& menus);
37	virtual						~DriveItem();
38
39			bool				IsInstalled() const;
40			bool				CanBeInstalled() const;
41			bool				IsBootDrive() const;
42			const char*			Path() const { return fPath.Path(); }
43
44			BootDrive*			Drive() { return fDrive; }
45
46protected:
47	virtual void				DrawItem(BView* owner, BRect frame,
48									bool complete = false);
49	virtual	void				Update(BView* owner, const BFont* font);
50
51private:
52			BootDrive*			fDrive;
53			BBitmap*			fIcon;
54			BString				fName;
55			BPath				fPath;
56			BString				fSize;
57			float				fBaselineOffset;
58			float				fSecondBaselineOffset;
59			float				fSizeWidth;
60			status_t			fCanBeInstalled;
61			bool				fIsInstalled;
62};
63
64
65DriveItem::DriveItem(const BDiskDevice& device, const BootMenuList& menus)
66	:
67	fBaselineOffset(0),
68	fSizeWidth(0)
69{
70	device.GetPath(&fPath);
71	if (device.Name() != NULL && device.Name()[0])
72		fName = device.Name();
73	else if (strstr(fPath.Path(), "usb") != NULL)
74		fName = B_TRANSLATE_COMMENT("USB Drive", "Default disk name");
75	else
76		fName = B_TRANSLATE_COMMENT("Hard Drive", "Default disk name");
77
78	fIcon = new BBitmap(BRect(BPoint(0, 0), be_control_look->ComposeIconSize(B_LARGE_ICON)),
79		B_RGBA32);
80	if (device.GetIcon(fIcon, B_LARGE_ICON) != B_OK)
81		memset(fIcon->Bits(), 0, fIcon->BitsLength());
82
83	fDrive = new BootDrive(fPath.Path());
84
85	fIsInstalled = fDrive->InstalledMenu(menus) != NULL;
86	fCanBeInstalled = fDrive->CanMenuBeInstalled(menus);
87
88	char buffer[256];
89	fSize = string_for_size(device.Size(), buffer, sizeof(buffer));
90}
91
92
93DriveItem::~DriveItem()
94{
95	delete fDrive;
96	delete fIcon;
97}
98
99
100bool
101DriveItem::IsInstalled() const
102{
103	return fIsInstalled;
104}
105
106
107bool
108DriveItem::CanBeInstalled() const
109{
110	return fCanBeInstalled == B_OK;
111}
112
113
114bool
115DriveItem::IsBootDrive() const
116{
117	return fDrive->IsBootDrive();
118}
119
120
121void
122DriveItem::DrawItem(BView* owner, BRect frame, bool complete)
123{
124	owner->PushState();
125	owner->SetDrawingMode(B_OP_ALPHA);
126
127	if (IsSelected() || complete) {
128		if (IsSelected()) {
129			owner->SetHighColor(ui_color(B_LIST_SELECTED_BACKGROUND_COLOR));
130			owner->SetLowColor(owner->HighColor());
131		} else
132			owner->SetHighColor(owner->LowColor());
133
134		owner->FillRect(frame);
135	}
136
137	if (!IsEnabled()) {
138		rgb_color textColor;
139		if (IsSelected())
140			textColor = ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
141		else
142			textColor = ui_color(B_LIST_ITEM_TEXT_COLOR);
143
144		if (textColor.red + textColor.green + textColor.blue > 128 * 3)
145			owner->SetHighColor(tint_color(textColor, B_DARKEN_1_TINT));
146		else
147			owner->SetHighColor(tint_color(textColor, B_LIGHTEN_1_TINT));
148	} else {
149		if (IsSelected())
150			owner->SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR));
151		else
152			owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
153	}
154
155
156	// icon
157	owner->MovePenTo(frame.left + 4, frame.top + 1);
158	owner->DrawBitmap(fIcon);
159
160	// device
161	owner->MovePenTo(frame.left + 8 + fIcon->Bounds().Width(),
162		frame.top + fSecondBaselineOffset);
163	owner->DrawString(fPath.Path());
164
165	// name
166	BFont boldFont;
167	BFont ownerFont;
168	owner->GetFont(&ownerFont);
169	owner->GetFont(&boldFont);
170	boldFont.SetFace(B_BOLD_FACE);
171	owner->SetFont(&boldFont);
172
173	BPoint namePosition(frame.left + 8 + fIcon->Bounds().Width(),
174		frame.top + fBaselineOffset);
175
176	owner->MovePenTo(namePosition);
177	owner->DrawString(fName.String());
178
179	float nameWidth = owner->StringWidth(fName.String());
180	float messageWidth = frame.right - 4 - fSizeWidth
181		- (frame.left + 8 + fIcon->Bounds().Width()) - nameWidth
182		- fBaselineOffset * 2;
183
184	if (fCanBeInstalled != B_OK) {
185		rgb_color highColor = owner->HighColor();
186		owner->SetHighColor(ui_color(B_FAILURE_COLOR));
187		owner->MovePenBy(fBaselineOffset, 0);
188		const char* message;
189		switch (fCanBeInstalled) {
190			case B_PARTITION_TOO_SMALL:
191				message = B_TRANSLATE_COMMENT("No space available!",
192					"Cannot install");
193				break;
194			case B_ENTRY_NOT_FOUND:
195				message = B_TRANSLATE_COMMENT("Incompatible format!",
196					"Cannot install");
197				break;
198			case B_READ_ONLY_DEVICE:
199				message = B_TRANSLATE_COMMENT("Read only!",
200					"Cannot install");
201				break;
202			default:
203				message = B_TRANSLATE_COMMENT("Cannot access!",
204					"Cannot install");
205				break;
206		}
207		BString truncatedMessage = message;
208		owner->TruncateString(&truncatedMessage, B_TRUNCATE_END, messageWidth);
209		owner->DrawString(truncatedMessage);
210		owner->SetHighColor(highColor);
211	}
212	owner->SetFont(&ownerFont);
213		// size
214	BPoint sizePosition(frame.right - 4 - fSizeWidth,
215		frame.top + fBaselineOffset);
216	if (sizePosition.x > namePosition.x + nameWidth) {
217		owner->MovePenTo(sizePosition);
218		owner->DrawString(fSize.String());
219	}
220
221	owner->PopState();
222}
223
224
225void
226DriveItem::Update(BView* owner, const BFont* font)
227{
228	fSizeWidth = font->StringWidth(fSize.String());
229
230	BFont boldFont(font);
231	boldFont.SetFace(B_BOLD_FACE);
232	float width = 8 + boldFont.StringWidth(fPath.Path())
233		+ be_control_look->DefaultItemSpacing() + fSizeWidth;
234	float pathWidth = font->StringWidth(fPath.Path());
235	if (width < pathWidth)
236		width = pathWidth;
237
238	SetWidth(width);
239
240	font_height fheight;
241	font->GetHeight(&fheight);
242
243	float lineHeight = ceilf(fheight.ascent) + ceilf(fheight.descent)
244		+ ceilf(fheight.leading);
245
246	fBaselineOffset = 2 + ceilf(fheight.ascent + fheight.leading / 2);
247	fSecondBaselineOffset = fBaselineOffset + lineHeight;
248
249	SetHeight(2 * lineHeight + 4);
250}
251
252
253// #pragma mark -
254
255
256DrivesPage::DrivesPage(WizardView* wizardView, const BootMenuList& menus,
257	BMessage* settings, const char* name)
258	:
259	WizardPageView(settings, name),
260	fWizardView(wizardView),
261	fHasInstallableItems(false)
262{
263	BString text;
264	text << B_TRANSLATE_COMMENT("Drives", "Title") << "\n"
265		<< B_TRANSLATE("Please select the drive you want the boot manager to "
266			"be installed to or uninstalled from.");
267	BTextView* description = CreateDescription("description", text);
268	MakeHeading(description);
269
270	fDrivesView = new BListView("drives", B_SINGLE_SELECTION_LIST,
271		B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE | B_FULL_UPDATE_ON_RESIZE);
272	fDrivesView->SetSelectionMessage(new BMessage(kMsgSelectionChanged));
273
274	BScrollView* scrollView = new BScrollView("scrollView", fDrivesView, 0,
275		false, true);
276
277	SetLayout(new BGroupLayout(B_VERTICAL));
278
279	BLayoutBuilder::Group<>((BGroupLayout*)GetLayout())
280		.Add(description)
281		.Add(scrollView, 10.0);
282
283	_UpdateWizardButtons(NULL);
284	_FillDrivesView(menus);
285}
286
287
288DrivesPage::~DrivesPage()
289{
290}
291
292
293void
294DrivesPage::PageCompleted()
295{
296	DriveItem* item = _SelectedDriveItem();
297
298	if (fSettings->ReplaceString("disk", item->Path()) != B_OK)
299		fSettings->AddString("disk", item->Path());
300}
301
302
303void
304DrivesPage::AttachedToWindow()
305{
306	fDrivesView->SetTarget(this);
307}
308
309
310void
311DrivesPage::MessageReceived(BMessage* message)
312{
313	switch (message->what) {
314		case kMsgSelectionChanged:
315		{
316			_UpdateWizardButtons(_SelectedDriveItem());
317			break;
318		}
319
320		default:
321			WizardPageView::MessageReceived(message);
322			break;
323	}
324}
325
326
327/*!	Builds the list view items, and adds them to fDriveView.
328	Sets the fHasInstallableItems member to indicate if there
329	are any possible install targets. Automatically
330	selects the boot drive.
331*/
332void
333DrivesPage::_FillDrivesView(const BootMenuList& menus)
334{
335	const char* selected = fSettings->FindString("disk");
336
337	BDiskDeviceRoster roster;
338	BDiskDevice device;
339	while (roster.GetNextDevice(&device) == B_OK) {
340		if (device.HasMedia() && !device.IsReadOnly()) {
341			DriveItem* item = new DriveItem(device, menus);
342			if (item->CanBeInstalled())
343				fHasInstallableItems = true;
344			fDrivesView->AddItem(item);
345
346			if ((selected == NULL && item->IsBootDrive())
347				|| (selected != NULL && !strcmp(item->Path(), selected))) {
348				fDrivesView->Select(fDrivesView->CountItems() - 1);
349				_UpdateWizardButtons(item);
350			}
351		}
352	}
353}
354
355
356DriveItem*
357DrivesPage::_SelectedDriveItem()
358{
359	return (DriveItem*)fDrivesView->ItemAt(fDrivesView->CurrentSelection());
360}
361
362
363void
364DrivesPage::_UpdateWizardButtons(DriveItem* item)
365{
366	fWizardView->SetPreviousButtonHidden(!fHasInstallableItems);
367	fWizardView->SetPreviousButtonLabel(
368		B_TRANSLATE_COMMENT("Uninstall", "Button"));
369	if (item == NULL) {
370		fWizardView->SetPreviousButtonEnabled(false);
371		fWizardView->SetNextButtonEnabled(false);
372	} else {
373		fWizardView->SetPreviousButtonEnabled(
374			item->CanBeInstalled() && item->IsInstalled());
375		fWizardView->SetNextButtonEnabled(item->CanBeInstalled());
376
377		fWizardView->SetNextButtonLabel(
378			item->IsInstalled() && item->CanBeInstalled()
379				? B_TRANSLATE_COMMENT("Update", "Button")
380				: B_TRANSLATE_COMMENT("Install", "Button"));
381	}
382
383}
384