1/*
2 * Copyright (c) 2008 Stephan Aßmus <superstippi@gmx.de>. All rights reserved.
3 * Distributed under the terms of the MIT/X11 license.
4 *
5 * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software
6 * as long as it is accompanied by it's documentation and this copyright notice.
7 * The software comes with no warranty, etc.
8 */
9
10
11#include "ControlsView.h"
12
13#include <string.h>
14
15#include <Bitmap.h>
16#include <Box.h>
17#include <TabView.h>
18#include <NodeMonitor.h>
19#include <Path.h>
20#include <PopUpMenu.h>
21#include <String.h>
22#include <SupportDefs.h>
23#include <View.h>
24#include <Volume.h>
25#include <VolumeRoster.h>
26#include <Window.h>
27
28#include <LayoutBuilder.h>
29
30#include "DiskUsage.h"
31#include "VolumeView.h"
32
33
34class VolumeTab: public BTab {
35public:
36								VolumeTab(BVolume* volume);
37	virtual						~VolumeTab();
38
39			BVolume*			Volume() const
40									{ return fVolume; }
41			float				IconWidth() const;
42
43	virtual	void				DrawLabel(BView* owner, BRect frame);
44	virtual	void				DrawFocusMark(BView* owner, BRect frame);
45
46private:
47			BBitmap*			fIcon;
48			BVolume*			fVolume;
49};
50
51
52VolumeTab::VolumeTab(BVolume* volume)
53	:
54	BTab(),
55	fIcon(new BBitmap(BRect(0, 0, 15, 15), B_RGBA32)),
56	fVolume(volume)
57{
58	if (fVolume->GetIcon(fIcon, B_MINI_ICON) < B_OK) {
59		delete fIcon;
60		fIcon = NULL;
61	}
62}
63
64
65float
66VolumeTab::IconWidth() const
67{
68	if (fIcon != NULL)
69		// add a small margin
70		return fIcon->Bounds().Width() + kSmallHMargin;
71	else
72		return 0.0f;
73}
74
75
76void
77VolumeTab::DrawLabel(BView* owner, BRect frame)
78{
79	owner->SetDrawingMode(B_OP_OVER);
80	if (fIcon != NULL) {
81		owner->MovePenTo(frame.left + kSmallHMargin,
82			(frame.top + frame.bottom - fIcon->Bounds().Height()) / 2.0);
83		owner->DrawBitmap(fIcon);
84	}
85	font_height fh;
86	owner->GetFontHeight(&fh);
87
88	BString label = Label();
89
90	owner->TruncateString(&label, B_TRUNCATE_END,
91		frame.Width() - IconWidth() - kSmallHMargin);
92
93	owner->SetHighColor(ui_color(B_CONTROL_TEXT_COLOR));
94	owner->DrawString(label,
95		BPoint(frame.left + IconWidth() + kSmallHMargin,
96			(frame.top + frame.bottom - fh.ascent - fh.descent) / 2.0
97				+ fh.ascent));
98}
99
100
101void
102VolumeTab::DrawFocusMark(BView* owner, BRect frame)
103{
104	frame.left += IconWidth();
105	BTab::DrawFocusMark(owner, frame);
106}
107
108
109VolumeTab::~VolumeTab()
110{
111	delete fIcon;
112	delete fVolume;
113}
114
115
116// #pragma mark -
117
118
119class ControlsView::VolumeTabView: public BTabView {
120public:
121								VolumeTabView();
122	virtual						~VolumeTabView();
123
124	virtual	void				AttachedToWindow();
125	virtual	void				MessageReceived(BMessage* message);
126	virtual BRect				TabFrame(int32 index) const;
127
128			BVolume*			FindDeviceFor(dev_t device,
129									bool invoke = false);
130
131private:
132			void				_AddVolume(dev_t device);
133			void				_RemoveVolume(dev_t device);
134
135			BVolumeRoster*		fVolumeRoster;
136};
137
138
139ControlsView::VolumeTabView::VolumeTabView()
140	:
141	BTabView("volume_tabs", B_WIDTH_FROM_LABEL)
142{
143	SetBorder(B_NO_BORDER);
144}
145
146
147ControlsView::VolumeTabView::~VolumeTabView()
148{
149	fVolumeRoster->StopWatching();
150	delete fVolumeRoster;
151}
152
153
154BRect
155ControlsView::VolumeTabView::TabFrame(int32 index) const
156{
157	float height = BTabView::TabFrame(index).Height();
158	float x = 0.0f;
159	float width = 0.0f;
160	float minStringWidth = StringWidth("Haiku");
161	int32 countTabs = CountTabs();
162
163	// calculate the total width if no truncation is made at all
164	float averageWidth = Frame().Width() / countTabs;
165
166	// margins are the deltas with the average widths
167	float* margins = new float[countTabs];
168	for (int32 i = 0; i < countTabs; i++) {
169		float tabLabelWidth = StringWidth(TabAt(i)->Label());
170		if (tabLabelWidth < minStringWidth)
171			tabLabelWidth = minStringWidth;
172
173		float tabWidth = tabLabelWidth + 3.0f * kSmallHMargin
174			+ ((VolumeTab*)TabAt(i))->IconWidth();
175		margins[i] = tabWidth - averageWidth;
176		width += tabWidth;
177	}
178
179	// determine how much we should shave to show all tabs (truncating)
180	float toShave = width - Frame().Width();
181
182	if (toShave > 0.0f) {
183		// the thinest a tab can be to hold the minimum string
184		float minimumMargin = minStringWidth + 3.0f * kSmallHMargin
185			- averageWidth;
186
187		float averageToShave;
188		float oldToShave;
189		/*
190			we might have to do multiple passes because of the minimum
191			tab width we are imposing.
192			we could also fail to totally fit all tabs.
193			TODO: allow paging.
194		*/
195
196		do {
197			averageToShave = toShave / countTabs;
198			oldToShave = toShave;
199			for (int32 i = 0; i < countTabs; i++) {
200				float iconWidth = ((VolumeTab*)TabAt(i))->IconWidth();
201				float newMargin = max_c(margins[i] - averageToShave,
202					minimumMargin + iconWidth);
203				toShave -= margins[i] - newMargin;
204				margins[i] = newMargin;
205			}
206		} while (toShave > 0 && oldToShave != toShave);
207	}
208
209	for (int i = 0; i < index; i++)
210		x += averageWidth + margins[i];
211
212	float margin = margins[index];
213	delete[] margins;
214
215	return BRect(x, 0.0f, x + averageWidth + margin, height);
216}
217
218
219void
220ControlsView::VolumeTabView::AttachedToWindow()
221{
222	// Populate the menu with the persistent volumes.
223	fVolumeRoster = new BVolumeRoster();
224
225	BVolume tempVolume;
226	while (fVolumeRoster->GetNextVolume(&tempVolume) == B_OK) {
227		if (!tempVolume.IsPersistent())
228			continue;
229
230		char name[B_PATH_NAME_LENGTH];
231		if (tempVolume.GetName(name) != B_OK)
232			continue;
233
234		if (strcmp(name, "system") == 0
235			|| strcmp(name, "config") == 0) {
236			// Don't include virtual volumes.
237			continue;
238		}
239
240		BVolume* volume = new BVolume(tempVolume);
241		VolumeView* volumeView = new VolumeView(name, volume);
242		VolumeTab* volumeTab = new VolumeTab(volume);
243		AddTab(volumeView, volumeTab);
244	}
245
246	// Begin watching mount and unmount events.
247	fVolumeRoster->StartWatching(BMessenger(this));
248}
249
250
251void
252ControlsView::VolumeTabView::MessageReceived(BMessage* message)
253{
254	switch (message->what) {
255		case B_NODE_MONITOR:
256			switch (message->FindInt32("opcode")) {
257				case B_DEVICE_MOUNTED:
258					_AddVolume(message->FindInt32("new device"));
259					break;
260
261				case B_DEVICE_UNMOUNTED:
262					_RemoveVolume(message->FindInt32("device"));
263					break;
264			}
265			break;
266
267		case kBtnCancel:
268		case kBtnRescan:
269			ViewForTab(Selection())->MessageReceived(message);
270			break;
271
272		case B_SIMPLE_DATA:
273		case B_REFS_RECEIVED:
274		{
275			entry_ref ref;
276
277			for (int i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
278				BEntry entry(&ref, true);
279				BPath path;
280				entry.GetPath(&path);
281				dev_t device = dev_for_path(path.Path());
282
283				for (int j = 0; VolumeTab* item = (VolumeTab*)TabAt(j); j++) {
284					if (item->Volume()->Device() == device) {
285						Select(j);
286						((VolumeView*)(item->View()))->SetPath(path);
287						break;
288					}
289				}
290			}
291			break;
292		}
293
294		default:
295			BTabView::MessageReceived(message);
296			break;
297	}
298}
299
300
301BVolume*
302ControlsView::VolumeTabView::FindDeviceFor(dev_t device, bool invoke)
303{
304	BVolume* volume = NULL;
305
306	// Iterate through items looking for a BVolume representing this device.
307	for (int i = 0; VolumeTab* item = (VolumeTab*)TabAt(i); i++) {
308		if (item->Volume()->Device() == device) {
309			volume = item->Volume();
310			if (invoke)
311				Select(i);
312			break;
313		}
314	}
315
316	return volume;
317}
318
319
320void
321ControlsView::VolumeTabView::_AddVolume(dev_t device)
322{
323	// Make sure the volume is not already in the menu.
324	for (int i = 0; VolumeTab* item = (VolumeTab*)TabAt(i); i++) {
325		if (item->Volume()->Device() == device)
326			return;
327	}
328
329	BVolume* volume = new BVolume(device);
330
331	VolumeTab* item = new VolumeTab(volume);
332	char name[B_PATH_NAME_LENGTH];
333	volume->GetName(name);
334
335	AddTab(new VolumeView(name, volume), item);
336	Invalidate();
337}
338
339
340void
341ControlsView::VolumeTabView::_RemoveVolume(dev_t device)
342{
343	for (int i = 0; VolumeTab* item = (VolumeTab*)TabAt(i); i++) {
344		if (item->Volume()->Device() == device) {
345			if (i == 0)
346				Select(1);
347			else
348				Select(i - 1);
349			RemoveTab(i);
350			delete item;
351			return;
352		}
353	}
354}
355
356
357// #pragma mark -
358
359
360ControlsView::ControlsView()
361	:
362	BView(NULL, B_WILL_DRAW)
363{
364	SetLayout(new BGroupLayout(B_VERTICAL));
365	fVolumeTabView = new VolumeTabView();
366	AddChild(BLayoutBuilder::Group<>(B_VERTICAL)
367		.Add(fVolumeTabView)
368	);
369}
370
371
372void
373ControlsView::MessageReceived(BMessage* msg)
374{
375	switch (msg->what) {
376		case B_SIMPLE_DATA:
377		case B_REFS_RECEIVED:
378			fVolumeTabView->MessageReceived(msg);
379			break;
380
381		case kBtnCancel:
382		case kBtnRescan:
383			fVolumeTabView->MessageReceived(msg);
384			break;
385
386		default:
387			BView::MessageReceived(msg);
388	}
389}
390
391
392ControlsView::~ControlsView()
393{
394}
395
396
397void
398ControlsView::ShowInfo(const FileInfo* info)
399{
400	((VolumeView*)fVolumeTabView->ViewForTab(
401		fVolumeTabView->Selection()))->ShowInfo(info);
402}
403
404
405void
406ControlsView::EnableRescan()
407{
408	((VolumeView*)fVolumeTabView->ViewForTab(
409		fVolumeTabView->Selection()))->EnableRescan();
410}
411
412
413void
414ControlsView::EnableCancel()
415{
416	((VolumeView*)fVolumeTabView->ViewForTab(
417		fVolumeTabView->Selection()))->EnableCancel();
418}
419
420
421BVolume*
422ControlsView::FindDeviceFor(dev_t device, bool invoke)
423{
424	return fVolumeTabView->FindDeviceFor(device, invoke);
425}
426