xref: /haiku/src/apps/drivesetup/DiskView.cpp (revision 9c274ccd098ee3b2674efde2d1582d4e0c68d878)
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 
36 using BPrivate::HashMap;
37 using BPrivate::HashKey32;
38 
39 static const pattern	kStripes		= { { 0xc7, 0x8f, 0x1f, 0x3e,
40 											  0x7c, 0xf8, 0xf1, 0xe3 } };
41 
42 static const float		kLayoutInset	= 6;
43 
44 
45 class PartitionView : public BView {
46 public:
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 
260 private:
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 
293 private:
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 
313 class DiskView::PartitionLayout : public BDiskDeviceVisitor {
314 public:
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 
463 DiskView::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 
516 DiskView::~DiskView()
517 {
518 	SetDisk(NULL, -1);
519 	delete fPartitionLayout;
520 }
521 
522 
523 void
524 DiskView::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 
563 void
564 DiskView::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 
575 void
576 DiskView::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 
587 void
588 DiskView::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