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