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