1/*
2 * Copyright 2011, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Clemens Zeidler <haiku@clemens-zeidler.de>
7 */
8
9#include "MusicCollectionWindow.h"
10
11#include <Application.h>
12#include <ControlLook.h>
13#include <ScrollView.h>
14#include <VolumeRoster.h>
15
16#include <NaturalCompare.h>
17
18#include "ALMLayout.h"
19#include "ALMLayoutBuilder.h"
20
21
22static int
23StringItemComp(const BListItem* first, const BListItem* second)
24{
25	BStringItem* firstItem = (BStringItem*)first;
26	BStringItem* secondItem = (BStringItem*)second;
27	return BPrivate::NaturalCompare(firstItem->Text(), secondItem->Text());
28}
29
30
31template <class ListItem = FileListItem>
32class ListViewListener : public EntryViewInterface {
33public:
34	ListViewListener(BOutlineListView* list, BStringView* countView)
35		:
36		fListView(list),
37		fCountView(countView),
38		fItemCount(0)
39	{
40
41	}
42
43
44	void
45	SetQueryString(const char* string)
46	{
47		fQueryString = string;
48	}
49
50
51	void
52	EntryCreated(WatchedFile* file)
53	{
54		//ListItem* item1 = new ListItem(file->entry.name, file);
55		//fListView->AddItem(item1);
56
57		fItemCount++;
58		BString count("Count: ");
59		count << fItemCount;
60		fCountView->SetText(count);
61
62		const ssize_t bufferSize = 256;
63		char buffer[bufferSize];
64		BNode node(&file->entry);
65
66		ssize_t readBytes;
67		readBytes = node.ReadAttr("Audio:Artist", B_STRING_TYPE, 0, buffer,
68			bufferSize);
69		if (readBytes < 0)
70			readBytes = 0;
71		if (readBytes >= bufferSize)
72			readBytes = bufferSize - 1;
73		buffer[readBytes] = '\0';
74
75		BString artist = (strcmp(buffer, "") == 0) ? "Unknown" : buffer;
76		ListItem* artistItem = _AddSuperItem(artist, fArtistList, NULL);
77
78		readBytes = node.ReadAttr("Audio:Album", B_STRING_TYPE, 0, buffer,
79			bufferSize);
80		if (readBytes < 0)
81			readBytes = 0;
82		buffer[readBytes] = '\0';
83		BString album = (strcmp(buffer, "") == 0) ? "Unknown" : buffer;
84		ListItem* albumItem = _AddSuperItem(album, fAlbumList, artistItem);
85
86		readBytes = node.ReadAttr("Media:Title", B_STRING_TYPE, 0, buffer,
87			bufferSize);
88		if (readBytes < 0)
89			readBytes = 0;
90		buffer[readBytes] = '\0';
91		BString title= (strcmp(buffer, "") == 0) ? file->entry.name
92			: buffer;
93
94		ListItem* item = new ListItem(title, file);
95		file->cookie = item;
96		fListView->AddUnder(item, albumItem);
97		fListView->SortItemsUnder(albumItem, true, StringItemComp);
98
99		if (fQueryString == "")
100			return;
101		if (title.IFindFirst(fQueryString) >= 0) {
102			fListView->Expand(artistItem);
103			fListView->Expand(albumItem);
104		} else if (album.IFindFirst(fQueryString) >= 0) {
105			fListView->Expand(artistItem);
106		}
107	};
108
109
110	void
111	EntryRemoved(WatchedFile* file)
112	{
113		ListItem* item = (ListItem*)file->cookie;
114		ListItem* album = (ListItem*)fListView->Superitem(item);
115		fListView->RemoveItem(item);
116		if (album != NULL && fListView->CountItemsUnder(album, true) == 0) {
117			ListItem* artist = (ListItem*)fListView->Superitem(album);
118			fListView->RemoveItem(album);
119			if (artist != NULL && fListView->CountItemsUnder(artist, true) == 0)
120				fListView->RemoveItem(artist);
121		}
122	};
123
124	void
125	EntryMoved(WatchedFile* file)
126	{
127		AttrChanged(file);
128	};
129
130
131	void
132	AttrChanged(WatchedFile* file)
133	{
134		EntryRemoved(file);
135		EntryCreated(file);
136	}
137
138
139	void
140	EntriesCleared()
141	{
142		for (int32 i = 0; i < fListView->FullListCountItems(); i++)
143			delete fListView->FullListItemAt(i);
144		fListView->MakeEmpty();
145
146		fArtistList.MakeEmpty();
147		fAlbumList.MakeEmpty();
148
149		printf("prev count %i\n", (int)fItemCount);
150		fItemCount = 0;
151		fCountView->SetText("Count: 0");
152	}
153
154
155private:
156	ListItem*
157	_AddSuperItem(const char* name, BObjectList<ListItem>& list,
158		ListItem* under)
159	{
160		ListItem* item = _FindStringItem(list, name, under);
161		if (item != NULL)
162			return item;
163
164		item = new ListItem(name);
165		fListView->AddUnder(item, under);
166		fListView->SortItemsUnder(under, true, StringItemComp);
167		list.AddItem(item);
168
169		fListView->Collapse(item);
170
171		return item;
172	}
173
174	ListItem*
175	_FindStringItem(BObjectList<ListItem>& list, const char* text,
176		ListItem* parent)
177	{
178		for (int32 i = 0; i < list.CountItems(); i++) {
179			ListItem* item = list.ItemAt(i);
180			ListItem* superItem = (ListItem*)fListView->Superitem(item);
181			if (parent != NULL && parent != superItem)
182				continue;
183			if (strcmp(item->Text(), text) == 0)
184				return item;
185		}
186		return NULL;
187	}
188
189			BOutlineListView*	fListView;
190			BStringView*		fCountView;
191
192			BObjectList<ListItem>	fArtistList;
193			BObjectList<ListItem> 	fAlbumList;
194
195			BString					fQueryString;
196			int32					fItemCount;
197};
198
199
200const uint32 kMsgQueryInput = '&qin';
201const uint32 kMsgItemInvoked = '&iin';
202
203
204MusicCollectionWindow::MusicCollectionWindow(BRect frame, const char* title)
205	:
206	BWindow(frame, title, B_DOCUMENT_WINDOW, B_AVOID_FRONT)
207{
208	fQueryField = new BTextControl("Search: ", "", NULL);
209	fQueryField->SetExplicitAlignment(BAlignment(B_ALIGN_HORIZONTAL_CENTER,
210		B_ALIGN_USE_FULL_HEIGHT));
211	fQueryField->SetModificationMessage(new BMessage(kMsgQueryInput));
212
213	fCountView = new BStringView("Count View", "Count:");
214
215	fFileListView = new MusicFileListView("File List View");
216	fFileListView->SetInvocationMessage(new BMessage(kMsgItemInvoked));
217	BScrollView* scrollView = new BScrollView("list scroll", fFileListView, 0,
218		true, true, B_PLAIN_BORDER);
219
220	BALMLayout* layout = new BALMLayout(B_USE_ITEM_SPACING, B_USE_ITEM_SPACING);
221	BALM::BALMLayoutBuilder(this, layout)
222		.SetInsets(B_USE_WINDOW_INSETS)
223		.Add(fQueryField, layout->Left(), layout->Top())
224		.StartingAt(fQueryField)
225			.AddToRight(fCountView, layout->Right())
226			.AddBelow(scrollView, layout->Bottom(), layout->Left(),
227				layout->Right());
228
229	Area* area = layout->AreaFor(scrollView);
230	area->SetLeftInset(0);
231	area->SetRightInset(0);
232	area->SetBottomInset(0);
233
234	BSize min = layout->MinSize();
235	BSize max = layout->MaxSize();
236	SetSizeLimits(min.Width(), max.Width(), min.Height(), max.Height());
237
238	fEntryViewInterface = new ListViewListener<FileListItem>(fFileListView,
239		fCountView);
240	fQueryHandler = new QueryHandler(fEntryViewInterface);
241	AddHandler(fQueryHandler);
242	fQueryReader = new QueryReader(fQueryHandler);
243	fQueryHandler->SetReadThread(fQueryReader);
244
245	// start initial query
246	PostMessage(kMsgQueryInput);
247}
248
249
250MusicCollectionWindow::~MusicCollectionWindow()
251{
252	delete fQueryReader;
253	delete fQueryHandler;
254	delete fEntryViewInterface;
255}
256
257
258bool
259MusicCollectionWindow::QuitRequested()
260{
261	be_app->PostMessage(B_QUIT_REQUESTED);
262	return true;
263}
264
265
266void
267MusicCollectionWindow::MessageReceived(BMessage* message)
268{
269	switch (message->what) {
270		case kMsgQueryInput:
271			_StartNewQuery();
272			break;
273
274		case kMsgItemInvoked:
275			fFileListView->Launch(message);
276			break;
277
278		default:
279			BWindow::MessageReceived(message);
280	}
281}
282
283
284void
285CaseInsensitiveString(BString &instring,  BString &outstring)
286{
287	outstring = "";
288	int i = 0;
289	while (instring[i])
290	{
291		if (instring[i] >= 65 && instring[i] <= 90) // capital letters
292		{
293			int ch = instring[i] + 32;
294			outstring += "[";
295			outstring += ch;
296			outstring += instring[i];
297			outstring += "]";
298		} else if (instring[i] >= 97 && instring[i] <= 122)
299		{
300			int ch = instring[i]-32;
301			outstring += "[";
302			outstring += instring[i];
303			outstring += ch;
304			outstring += "]";
305		} else
306			outstring += instring[i];
307		i++;
308	}
309}
310
311
312void
313MusicCollectionWindow::_StartNewQuery()
314{
315	fQueryReader->Reset();
316	fQueryHandler->Reset();
317
318	BString orgString = fQueryField->Text();
319	((ListViewListener<FileListItem>*)fEntryViewInterface)->SetQueryString(
320		orgString);
321
322	BVolume volume;
323	//BVolumeRoster().GetBootVolume(&volume);
324	BVolumeRoster roster;
325	while (roster.GetNextVolume(&volume) == B_OK) {
326		if (!volume.KnowsQuery())
327			continue;
328		BQuery* query = _CreateQuery(orgString);
329		query->SetVolume(&volume);
330		fQueryReader->AddQuery(query);
331	}
332
333	fQueryReader->Run();
334}
335
336
337BQuery*
338MusicCollectionWindow::_CreateQuery(BString& orgString)
339{
340	BQuery* query = new BQuery;
341
342	BString queryString;
343	CaseInsensitiveString(orgString, queryString);
344
345	query->PushAttr("Media:Title");
346	query->PushString(queryString);
347	query->PushOp(B_CONTAINS);
348
349	query->PushAttr("Audio:Album");
350	query->PushString(queryString);
351	query->PushOp(B_CONTAINS);
352	query->PushOp(B_OR);
353
354	query->PushAttr("Audio:Artist");
355	query->PushString(queryString);
356	query->PushOp(B_CONTAINS);
357	query->PushOp(B_OR);
358
359	if (queryString == "") {
360		query->PushAttr("BEOS:TYPE");
361		query->PushString("audio/");
362		query->PushOp(B_BEGINS_WITH);
363		query->PushOp(B_OR);
364	}
365
366	query->PushAttr("BEOS:TYPE");
367	query->PushString("audio/");
368	query->PushOp(B_BEGINS_WITH);
369
370	query->PushAttr("name");
371	query->PushString(queryString);
372	query->PushOp(B_CONTAINS);
373	query->PushOp(B_AND);
374	query->PushOp(B_OR);
375
376	return query;
377}
378