1/*
2 * Copyright 2007-2010 Stephan A��mus <superstippi@gmx.de>.
3 * Copyright 2013, Dancs�� R��bert <dancso.robert@d-rendszer.hu>
4 * All rights reserved. Distributed under the terms of the MIT license.
5 */
6
7#include "DiskView.h"
8
9#include <stdio.h>
10
11#include <Application.h>
12#include <Bitmap.h>
13#include <DiskDeviceVisitor.h>
14#include <Catalog.h>
15#include <GroupLayout.h>
16#include <HashMap.h>
17#include <IconUtils.h>
18#include <LayoutItem.h>
19#include <LayoutUtils.h>
20#include <Locale.h>
21#include <PartitioningInfo.h>
22#include <Path.h>
23#include <Resources.h>
24#include <String.h>
25#include <Volume.h>
26#include <VolumeRoster.h>
27
28#include "icons.h"
29#include "EncryptionUtils.h"
30#include "MainWindow.h"
31
32
33#undef B_TRANSLATION_CONTEXT
34#define B_TRANSLATION_CONTEXT "DiskView"
35
36using BPrivate::HashMap;
37using BPrivate::HashKey32;
38
39static const pattern	kStripes		= { { 0xc7, 0x8f, 0x1f, 0x3e,
40											  0x7c, 0xf8, 0xf1, 0xe3 } };
41
42static const float		kLayoutInset	= 6;
43
44
45class PartitionView : public BView {
46public:
47	PartitionView(const char* name, float weight, off_t offset,
48			int32 level, partition_id id, BPartition* partition)
49		:
50		BView(name, B_WILL_DRAW | B_SUPPORTS_LAYOUT | B_FULL_UPDATE_ON_RESIZE),
51		fID(id),
52		fWeight(weight),
53		fOffset(offset),
54		fLevel(level),
55		fSelected(false),
56		fMouseOver(false),
57		fGroupLayout(new BGroupLayout(B_HORIZONTAL, kLayoutInset))
58	{
59		SetLayout(fGroupLayout);
60
61		SetViewColor(B_TRANSPARENT_COLOR);
62		rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
63		base = tint_color(base, B_LIGHTEN_2_TINT);
64		base = tint_color(base, 1 + 0.13 * (level - 1));
65		SetLowColor(base);
66		SetHighColor(tint_color(base, B_DARKEN_1_TINT));
67
68		BFont font;
69		GetFont(&font);
70		font.SetSize(ceilf(font.Size() * 0.85));
71		font.SetRotation(90.0);
72		SetFont(&font);
73
74		fGroupLayout->SetInsets(kLayoutInset, kLayoutInset + font.Size(),
75			kLayoutInset, kLayoutInset);
76
77		SetExplicitMinSize(BSize(font.Size() + 20, 30));
78
79		BVolume volume;
80		partition->GetVolume(&volume);
81
82		BVolume boot;
83		BVolumeRoster().GetBootVolume(&boot);
84		fBoot = volume == boot;
85		fReadOnly = volume.IsReadOnly();
86		fShared = volume.IsShared();
87		fEncrypted = false;
88
89		_ComputeFullName(partition, name);
90
91		fUsed = 100.0 / ((float)volume.Capacity()
92				/ (volume.Capacity() - volume.FreeBytes()));
93		if (fUsed < 0)
94			fUsed = 100.0;
95
96		fIcon = new BBitmap(BRect(0, 0, 15, 15), B_RGBA32);
97
98		fBFS = BString(partition->ContentType()) == "Be File System";
99
100		if (fBoot)
101			BIconUtils::GetVectorIcon(kLeaf, sizeof(kLeaf), fIcon);
102		else if (fEncrypted)
103			BIconUtils::GetVectorIcon(kEncrypted, sizeof(kEncrypted), fIcon);
104		else if (fReadOnly)
105			BIconUtils::GetVectorIcon(kReadOnly, sizeof(kReadOnly), fIcon);
106		else if (fShared)
107			BIconUtils::GetVectorIcon(kShared, sizeof(kShared), fIcon);
108		else if (fVirtual)
109			BIconUtils::GetVectorIcon(kFile, sizeof(kFile), fIcon);
110		else {
111			delete fIcon;
112			fIcon = NULL;
113		}
114	}
115
116	virtual void MouseDown(BPoint where)
117	{
118		BMessage message(MSG_SELECTED_PARTITION_ID);
119		message.AddInt32("partition_id", fID);
120		Window()->PostMessage(&message);
121	}
122
123	virtual void MouseMoved(BPoint where, uint32 transit, const BMessage*)
124	{
125		uint32 buttons;
126		if (Window()->CurrentMessage()->FindInt32("buttons",
127				(int32*)&buttons) < B_OK)
128			buttons = 0;
129
130		_SetMouseOver(buttons == 0
131			&& (transit == B_ENTERED_VIEW || transit == B_INSIDE_VIEW));
132	}
133
134	virtual void Draw(BRect updateRect)
135	{
136		if (fMouseOver) {
137			float tint = (B_NO_TINT + B_LIGHTEN_1_TINT) / 2.0;
138			SetHighColor(tint_color(HighColor(), tint));
139			SetLowColor(tint_color(LowColor(), tint));
140		}
141
142		BRect b(Bounds());
143		if (fSelected) {
144			if (fReadOnly)
145				SetHighColor(make_color(255, 128, 128));
146			else if (fBoot || fBFS)
147				SetHighColor(make_color(128, 255, 128));
148			else
149				SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
150			StrokeRect(b, B_SOLID_HIGH);
151			b.InsetBy(1, 1);
152			StrokeRect(b, B_SOLID_HIGH);
153			b.InsetBy(1, 1);
154		} else if (fLevel >= 0) {
155			StrokeRect(b, B_SOLID_HIGH);
156			b.InsetBy(1, 1);
157		}
158
159		if (CountChildren() == 0)
160			SetLowColor(make_color(255, 255, 255));
161		FillRect(b, B_SOLID_LOW);
162
163		if (fEncrypted && CountChildren() == 0) {
164			SetHighColor(make_color(192, 192, 192));
165			StrokeRect(b, B_SOLID_HIGH);
166			b.InsetBy(1, 1);
167			SetLowColor(make_color(224, 224, 0));
168			BRect e(BPoint(15, b.top), b.RightBottom());
169			e.InsetBy(1, 1);
170			FillRect(e, kStripes);
171		}
172
173		// prevent the text from moving when border width changes
174		if (!fSelected)
175			b.InsetBy(1, 1);
176
177		BRect used(b.LeftTop(), BSize(b.Width() / (100.0 / fUsed), b.Height()));
178
179		SetLowColor(make_color(172, 172, 255));
180
181		if (fReadOnly)
182			SetLowColor(make_color(255, 172, 172));
183		else if (fBoot || fBFS)
184			SetLowColor(make_color(190, 255, 190));
185		if (CountChildren() == 0)
186			FillRect(used, B_SOLID_LOW);
187
188		if (fIcon && CountChildren() == 0) {
189			BPoint where = b.RightBottom() - BPoint(fIcon->Bounds().Width(),
190				fIcon->Bounds().Height());
191			SetDrawingMode(B_OP_OVER);
192			DrawBitmap(fIcon, where);
193			SetDrawingMode(B_OP_COPY);
194		}
195
196		float width;
197		BFont font;
198		GetFont(&font);
199
200		font_height fh;
201		font.GetHeight(&fh);
202
203		// draw the partition label, but only if we have no child partition
204		// views
205		BPoint textOffset;
206		if (CountChildren() > 0) {
207			font.SetRotation(0.0);
208			SetFont(&font);
209			width = b.Width();
210			textOffset = b.LeftTop();
211			textOffset.x += 3;
212			textOffset.y += ceilf(fh.ascent);
213		} else {
214			width = b.Height();
215			textOffset = b.LeftBottom();
216			textOffset.x += ceilf(fh.ascent);
217		}
218
219		BString name(Name());
220		font.TruncateString(&name, B_TRUNCATE_END, width);
221
222		SetHighColor(tint_color(LowColor(), B_DARKEN_4_TINT));
223		DrawString(name.String(), textOffset);
224	}
225
226	float Weight() const
227	{
228		return fWeight;
229	}
230
231	off_t Offset() const
232	{
233		return fOffset;
234	}
235
236	int32 Level() const
237	{
238		return fLevel;
239	}
240
241	BGroupLayout* GroupLayout() const
242	{
243		return fGroupLayout;
244	}
245
246	void SetSelected(bool selected)
247	{
248		if (fSelected == selected)
249			return;
250
251		fSelected = selected;
252		Invalidate();
253	}
254
255	bool IsEncrypted()
256	{
257		return fEncrypted;
258	}
259
260private:
261	void _SetMouseOver(bool mouseOver)
262	{
263		if (fMouseOver == mouseOver)
264			return;
265		fMouseOver = mouseOver;
266		Invalidate();
267	}
268
269	void _ComputeFullName(BPartition* partition, const char* name)
270	{
271		BString fullName(name);
272		fVirtual = partition->Device()->IsFile();
273		if (fVirtual)
274			fullName << " (" << B_TRANSLATE("Virtual") << ")";
275
276		while (partition != NULL) {
277			BPath path;
278			partition->GetPath(&path);
279
280			const char* encrypter = EncryptionType(path.Path());
281			if (encrypter != NULL) {
282				fullName << " (" << encrypter << ")";
283				fEncrypted = true;
284				break;
285			}
286			partition = partition->Parent();
287		}
288
289		SetName(fullName);
290	}
291
292
293private:
294	partition_id	fID;
295	float			fWeight;
296	off_t			fOffset;
297	int32			fLevel;
298	bool			fSelected;
299	bool			fMouseOver;
300	BGroupLayout*	fGroupLayout;
301
302	bool			fBoot;
303	bool			fReadOnly;
304	bool			fShared;
305	bool			fEncrypted;
306	bool			fVirtual;
307	float			fUsed;
308	bool			fBFS;
309	BBitmap*		fIcon;
310};
311
312
313class DiskView::PartitionLayout : public BDiskDeviceVisitor {
314public:
315	PartitionLayout(BView* view, SpaceIDMap& spaceIDMap)
316		:
317		fView(view),
318		fViewMap(),
319		fSelectedPartition(-1),
320		fSpaceIDMap(spaceIDMap)
321	{
322	}
323
324	virtual bool Visit(BDiskDevice* device)
325	{
326		const char* name;
327		if (device->Name() != NULL && device->Name()[0] != '\0')
328			name = device->Name();
329		else
330			name = B_TRANSLATE("Device");
331
332		if (BString(device->ContentName()).Length() > 0)
333			name = device->ContentName();
334
335		PartitionView* view = new PartitionView(name, 1.0,
336			device->Offset(), 0, device->ID(), device);
337		fViewMap.Put(device->ID(), view);
338		fView->GetLayout()->AddView(view);
339		_AddSpaces(device, view);
340		return false;
341	}
342
343	virtual bool Visit(BPartition* partition, int32 level)
344	{
345		if (!partition->Parent()
346			|| !fViewMap.ContainsKey(partition->Parent()->ID()))
347			return false;
348
349		// calculate size factor within parent frame
350		off_t offset = partition->Offset();
351//		off_t parentOffset = partition->Parent()->Offset();
352		off_t size = partition->Size();
353		off_t parentSize = partition->Parent()->Size();
354		double scale = (double)size / parentSize;
355
356		BString name = partition->ContentName();
357		if (name.Length() == 0) {
358			if (partition->CountChildren() > 0)
359				name << partition->Type();
360			else {
361				char buffer[64];
362				snprintf(buffer, 64, B_TRANSLATE("Partition %ld"),
363					partition->ID(), partition);
364				name << buffer;
365			}
366		}
367		partition_id id = partition->ID();
368		PartitionView* view = new PartitionView(name.String(), scale, offset,
369			level, id, partition);
370		view->SetSelected(id == fSelectedPartition);
371		PartitionView* parent = fViewMap.Get(partition->Parent()->ID());
372		BGroupLayout* layout = parent->GroupLayout();
373		layout->AddView(_FindInsertIndex(view, layout), view, scale);
374
375		fViewMap.Put(partition->ID(), view);
376		_AddSpaces(partition, view);
377
378		return false;
379	}
380
381	void SetSelectedPartition(partition_id id)
382	{
383		if (fSelectedPartition == id)
384			return;
385
386		if (fViewMap.ContainsKey(fSelectedPartition)) {
387			PartitionView* view = fViewMap.Get(fSelectedPartition);
388			view->SetSelected(false);
389		}
390
391		fSelectedPartition = id;
392
393		if (fViewMap.ContainsKey(fSelectedPartition)) {
394			PartitionView* view = fViewMap.Get(fSelectedPartition);
395			view->SetSelected(true);
396		}
397	}
398
399	void Unset()
400	{
401		fViewMap.Clear();
402	}
403
404 private:
405	void _AddSpaces(BPartition* partition, PartitionView* parentView)
406	{
407		// add any available space on the partition
408		BPartitioningInfo info;
409		if (partition->GetPartitioningInfo(&info) >= B_OK) {
410			off_t parentSize = partition->Size();
411			off_t offset;
412			off_t size;
413			for (int32 i = 0;
414					info.GetPartitionableSpaceAt(i, &offset, &size) >= B_OK;
415					i++) {
416				// TODO: remove again once Disk Device API is fixed
417				if (!is_valid_partitionable_space(size))
418					continue;
419				//
420				double scale = (double)size / parentSize;
421				partition_id id
422					= fSpaceIDMap.SpaceIDFor(partition->ID(), offset);
423				PartitionView* view = new PartitionView(
424					B_TRANSLATE("Empty space"), scale, offset,
425					parentView->Level() + 1, id, partition);
426
427				fViewMap.Put(id, view);
428				BGroupLayout* layout = parentView->GroupLayout();
429				layout->AddView(_FindInsertIndex(view, layout), view, scale);
430			}
431		}
432	}
433	int32 _FindInsertIndex(PartitionView* view, BGroupLayout* layout) const
434	{
435		int32 insertIndex = 0;
436		int32 count = layout->CountItems();
437		for (int32 i = 0; i < count; i++) {
438			BLayoutItem* item = layout->ItemAt(i);
439			if (!item)
440				break;
441			PartitionView* sibling
442				= dynamic_cast<PartitionView*>(item->View());
443			if (sibling && sibling->Offset() > view->Offset())
444				break;
445			insertIndex++;
446		}
447		return insertIndex;
448	}
449
450	typedef	HashKey32<partition_id>					PartitionKey;
451	typedef HashMap<PartitionKey, PartitionView* >	PartitionViewMap;
452
453	BView*				fView;
454	PartitionViewMap	fViewMap;
455	partition_id		fSelectedPartition;
456	SpaceIDMap&			fSpaceIDMap;
457};
458
459
460// #pragma mark -
461
462
463DiskView::DiskView(const BRect& frame, uint32 resizeMode,
464		SpaceIDMap& spaceIDMap)
465	:
466	Inherited(frame, "diskview", resizeMode,
467		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
468	fDiskCount(0),
469	fDisk(NULL),
470	fSpaceIDMap(spaceIDMap),
471	fPartitionLayout(new PartitionLayout(this, fSpaceIDMap))
472{
473	BGroupLayout* layout = new BGroupLayout(B_HORIZONTAL, kLayoutInset);
474	SetLayout(layout);
475
476	SetViewColor(B_TRANSPARENT_COLOR);
477	SetHighUIColor(B_PANEL_BACKGROUND_COLOR, B_DARKEN_2_TINT);
478	SetLowUIColor(B_PANEL_BACKGROUND_COLOR, 1.221f);
479
480#ifdef HAIKU_TARGET_PLATFORM_LIBBE_TEST
481	PartitionView* view;
482	float scale = 1.0;
483	view = new PartitionView("Disk", scale, 0, 0, -1);
484	layout->AddView(view, scale);
485
486	layout = view->GroupLayout();
487
488	scale = 0.3;
489	view = new PartitionView("Primary", scale, 1, 50, -1);
490	layout->AddView(view, scale);
491	scale = 0.7;
492	view = new PartitionView("Extended", scale, 1, 100, -1);
493	layout->AddView(view, scale);
494
495	layout = view->GroupLayout();
496
497	scale = 0.2;
498	view = new PartitionView("Logical", scale, 2, 200, -1);
499	layout->AddView(view, scale);
500
501	scale = 0.5;
502	view = new PartitionView("Logical", scale, 2, 250, -1);
503	layout->AddView(view, scale);
504
505	scale = 0.005;
506	view = new PartitionView("Logical", scale, 2, 290, -1);
507	layout->AddView(view, scale);
508
509	scale = 0.295;
510	view = new PartitionView("Logical", scale, 2, 420, -1);
511	layout->AddView(view, scale);
512#endif
513}
514
515
516DiskView::~DiskView()
517{
518	SetDisk(NULL, -1);
519	delete fPartitionLayout;
520}
521
522
523void
524DiskView::Draw(BRect updateRect)
525{
526	BRect bounds(Bounds());
527
528	if (fDisk)
529		return;
530
531	FillRect(bounds, kStripes);
532
533	const char* helpfulMessage;
534	if (fDiskCount == 0)
535		helpfulMessage = B_TRANSLATE("No disk devices have been recognized.");
536	else
537		helpfulMessage =
538			B_TRANSLATE("Select a partition from the list below.");
539
540	float width = StringWidth(helpfulMessage);
541	font_height fh;
542	GetFontHeight(&fh);
543	BRect messageBounds(bounds);
544	messageBounds.InsetBy((bounds.Width() - width - fh.ascent * 2) / 2.0,
545		(bounds.Height() - (fh.ascent + fh.descent) * 2) / 2.0);
546
547	FillRoundRect(messageBounds, 4, 4, B_SOLID_LOW);
548	rgb_color color = LowColor();
549	if (color.IsLight())
550		color = tint_color(color, B_DARKEN_4_TINT);
551	else
552		color = tint_color(color, B_LIGHTEN_2_TINT);
553
554	SetHighColor(color);
555	BPoint textOffset;
556	textOffset.x = messageBounds.left + fh.ascent;
557	textOffset.y = (messageBounds.top + messageBounds.bottom
558		- fh.ascent - fh.descent) / 2 + fh.ascent;
559	DrawString(helpfulMessage, textOffset);
560}
561
562
563void
564DiskView::SetDiskCount(int32 count)
565{
566	fDiskCount = count;
567	if (count == 1) {
568		BMessage message(MSG_SELECTED_PARTITION_ID);
569		message.AddInt32("partition_id", 0);
570		Window()->PostMessage(&message);
571	}
572}
573
574
575void
576DiskView::SetDisk(BDiskDevice* disk, partition_id selectedPartition)
577{
578	if (fDisk != disk) {
579		fDisk = disk;
580		ForceUpdate();
581	}
582
583	fPartitionLayout->SetSelectedPartition(selectedPartition);
584}
585
586
587void
588DiskView::ForceUpdate()
589{
590	while (BView* view = ChildAt(0)) {
591		view->RemoveSelf();
592		delete view;
593	}
594
595	fPartitionLayout->Unset();
596
597	if (fDisk) {
598		// we need to prepare the disk for modifications, otherwise
599		// we cannot get information about available spaces on the
600		// device or any of its child partitions
601		// TODO: cancelling modifications here is of course undesired
602		// once we hold off the real modifications until an explicit
603		// command to write them to disk...
604		bool prepared = fDisk->PrepareModifications() == B_OK;
605		fDisk->VisitEachDescendant(fPartitionLayout);
606		if (prepared)
607			fDisk->CancelModifications();
608	}
609
610	Invalidate();
611}
612
613