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