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				name.SetToFormat(B_TRANSLATE("Partition %ld"), (long int)partition->ID());
362		}
363		partition_id id = partition->ID();
364		PartitionView* view = new PartitionView(name.String(), scale, offset,
365			level, id, partition);
366		view->SetSelected(id == fSelectedPartition);
367		PartitionView* parent = fViewMap.Get(partition->Parent()->ID());
368		BGroupLayout* layout = parent->GroupLayout();
369		layout->AddView(_FindInsertIndex(view, layout), view, scale);
370
371		fViewMap.Put(partition->ID(), view);
372		_AddSpaces(partition, view);
373
374		return false;
375	}
376
377	void SetSelectedPartition(partition_id id)
378	{
379		if (fSelectedPartition == id)
380			return;
381
382		if (fViewMap.ContainsKey(fSelectedPartition)) {
383			PartitionView* view = fViewMap.Get(fSelectedPartition);
384			view->SetSelected(false);
385		}
386
387		fSelectedPartition = id;
388
389		if (fViewMap.ContainsKey(fSelectedPartition)) {
390			PartitionView* view = fViewMap.Get(fSelectedPartition);
391			view->SetSelected(true);
392		}
393	}
394
395	void Unset()
396	{
397		fViewMap.Clear();
398	}
399
400 private:
401	void _AddSpaces(BPartition* partition, PartitionView* parentView)
402	{
403		// add any available space on the partition
404		BPartitioningInfo info;
405		if (partition->GetPartitioningInfo(&info) >= B_OK) {
406			off_t parentSize = partition->Size();
407			off_t offset;
408			off_t size;
409			for (int32 i = 0;
410					info.GetPartitionableSpaceAt(i, &offset, &size) >= B_OK;
411					i++) {
412				// TODO: remove again once Disk Device API is fixed
413				if (!is_valid_partitionable_space(size))
414					continue;
415				//
416				double scale = (double)size / parentSize;
417				partition_id id
418					= fSpaceIDMap.SpaceIDFor(partition->ID(), offset);
419				PartitionView* view = new PartitionView(
420					B_TRANSLATE("Empty space"), scale, offset,
421					parentView->Level() + 1, id, partition);
422
423				fViewMap.Put(id, view);
424				BGroupLayout* layout = parentView->GroupLayout();
425				layout->AddView(_FindInsertIndex(view, layout), view, scale);
426			}
427		}
428	}
429	int32 _FindInsertIndex(PartitionView* view, BGroupLayout* layout) const
430	{
431		int32 insertIndex = 0;
432		int32 count = layout->CountItems();
433		for (int32 i = 0; i < count; i++) {
434			BLayoutItem* item = layout->ItemAt(i);
435			if (!item)
436				break;
437			PartitionView* sibling
438				= dynamic_cast<PartitionView*>(item->View());
439			if (sibling && sibling->Offset() > view->Offset())
440				break;
441			insertIndex++;
442		}
443		return insertIndex;
444	}
445
446	typedef	HashKey32<partition_id>					PartitionKey;
447	typedef HashMap<PartitionKey, PartitionView* >	PartitionViewMap;
448
449	BView*				fView;
450	PartitionViewMap	fViewMap;
451	partition_id		fSelectedPartition;
452	SpaceIDMap&			fSpaceIDMap;
453};
454
455
456// #pragma mark -
457
458
459DiskView::DiskView(const BRect& frame, uint32 resizeMode,
460		SpaceIDMap& spaceIDMap)
461	:
462	Inherited(frame, "diskview", resizeMode,
463		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
464	fDiskCount(0),
465	fDisk(NULL),
466	fSpaceIDMap(spaceIDMap),
467	fPartitionLayout(new PartitionLayout(this, fSpaceIDMap))
468{
469	BGroupLayout* layout = new BGroupLayout(B_HORIZONTAL, kLayoutInset);
470	SetLayout(layout);
471
472	SetViewColor(B_TRANSPARENT_COLOR);
473	SetHighUIColor(B_PANEL_BACKGROUND_COLOR, B_DARKEN_2_TINT);
474	SetLowUIColor(B_PANEL_BACKGROUND_COLOR, 1.221f);
475
476#ifdef HAIKU_TARGET_PLATFORM_LIBBE_TEST
477	PartitionView* view;
478	float scale = 1.0;
479	view = new PartitionView("Disk", scale, 0, 0, -1);
480	layout->AddView(view, scale);
481
482	layout = view->GroupLayout();
483
484	scale = 0.3;
485	view = new PartitionView("Primary", scale, 1, 50, -1);
486	layout->AddView(view, scale);
487	scale = 0.7;
488	view = new PartitionView("Extended", scale, 1, 100, -1);
489	layout->AddView(view, scale);
490
491	layout = view->GroupLayout();
492
493	scale = 0.2;
494	view = new PartitionView("Logical", scale, 2, 200, -1);
495	layout->AddView(view, scale);
496
497	scale = 0.5;
498	view = new PartitionView("Logical", scale, 2, 250, -1);
499	layout->AddView(view, scale);
500
501	scale = 0.005;
502	view = new PartitionView("Logical", scale, 2, 290, -1);
503	layout->AddView(view, scale);
504
505	scale = 0.295;
506	view = new PartitionView("Logical", scale, 2, 420, -1);
507	layout->AddView(view, scale);
508#endif
509}
510
511
512DiskView::~DiskView()
513{
514	SetDisk(NULL, -1);
515	delete fPartitionLayout;
516}
517
518
519void
520DiskView::Draw(BRect updateRect)
521{
522	BRect bounds(Bounds());
523
524	if (fDisk)
525		return;
526
527	FillRect(bounds, kStripes);
528
529	const char* helpfulMessage;
530	if (fDiskCount == 0)
531		helpfulMessage = B_TRANSLATE("No disk devices have been recognized.");
532	else
533		helpfulMessage =
534			B_TRANSLATE("Select a partition from the list below.");
535
536	float width = StringWidth(helpfulMessage);
537	font_height fh;
538	GetFontHeight(&fh);
539	BRect messageBounds(bounds);
540	messageBounds.InsetBy((bounds.Width() - width - fh.ascent * 2) / 2.0,
541		(bounds.Height() - (fh.ascent + fh.descent) * 2) / 2.0);
542
543	FillRoundRect(messageBounds, 4, 4, B_SOLID_LOW);
544	rgb_color color = LowColor();
545	if (color.IsLight())
546		color = tint_color(color, B_DARKEN_4_TINT);
547	else
548		color = tint_color(color, B_LIGHTEN_2_TINT);
549
550	SetHighColor(color);
551	BPoint textOffset;
552	textOffset.x = messageBounds.left + fh.ascent;
553	textOffset.y = (messageBounds.top + messageBounds.bottom
554		- fh.ascent - fh.descent) / 2 + fh.ascent;
555	DrawString(helpfulMessage, textOffset);
556}
557
558
559void
560DiskView::SetDiskCount(int32 count)
561{
562	fDiskCount = count;
563	if (count == 1) {
564		BMessage message(MSG_SELECTED_PARTITION_ID);
565		message.AddInt32("partition_id", 0);
566		Window()->PostMessage(&message);
567	}
568}
569
570
571void
572DiskView::SetDisk(BDiskDevice* disk, partition_id selectedPartition)
573{
574	if (fDisk != disk) {
575		fDisk = disk;
576		ForceUpdate();
577	}
578
579	fPartitionLayout->SetSelectedPartition(selectedPartition);
580}
581
582
583void
584DiskView::ForceUpdate()
585{
586	while (BView* view = ChildAt(0)) {
587		view->RemoveSelf();
588		delete view;
589	}
590
591	fPartitionLayout->Unset();
592
593	if (fDisk) {
594		// we need to prepare the disk for modifications, otherwise
595		// we cannot get information about available spaces on the
596		// device or any of its child partitions
597		// TODO: cancelling modifications here is of course undesired
598		// once we hold off the real modifications until an explicit
599		// command to write them to disk...
600		bool prepared = fDisk->PrepareModifications() == B_OK;
601		fDisk->VisitEachDescendant(fPartitionLayout);
602		if (prepared)
603			fDisk->CancelModifications();
604	}
605
606	Invalidate();
607}
608
609