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