⛏️ index : haiku.git

/*
 * Copyright 2011, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Clemens Zeidler <haiku@clemens-zeidler.de>
 */

#include "MusicCollectionWindow.h"

#include <Application.h>
#include <ControlLook.h>
#include <Debug.h>
#include <ScrollView.h>
#include <VolumeRoster.h>

#include <NaturalCompare.h>

#include "ALMLayout.h"
#include "ALMLayoutBuilder.h"

#include <ctype.h>

static int
StringItemComp(const BListItem* first, const BListItem* second)
{
	BStringItem* firstItem = (BStringItem*)first;
	BStringItem* secondItem = (BStringItem*)second;
	return BPrivate::NaturalCompare(firstItem->Text(), secondItem->Text());
}


template <class ListItem = FileListItem>
class ListViewListener : public EntryViewInterface {
public:
	ListViewListener(BOutlineListView* list, BStringView* countView)
		:
		fListView(list),
		fCountView(countView),
		fItemCount(0)
	{

	}


	void
	SetQueryString(const char* string)
	{
		fQueryString = string;
	}


	void
	EntryCreated(WatchedFile* file)
	{
		//ListItem* item1 = new ListItem(file->entry.name, file);
		//fListView->AddItem(item1);

		fItemCount++;
		BString count("Count: ");
		count << fItemCount;
		fCountView->SetText(count);

		const ssize_t bufferSize = 256;
		char buffer[bufferSize];
		BNode node(&file->entry);

		ssize_t readBytes;
		readBytes = node.ReadAttr("Audio:Artist", B_STRING_TYPE, 0, buffer,
			bufferSize);
		if (readBytes < 0)
			readBytes = 0;
		if (readBytes >= bufferSize)
			readBytes = bufferSize - 1;
		buffer[readBytes] = '\0';

		BString artist = (strcmp(buffer, "") == 0) ? "Unknown" : buffer;
		ListItem* artistItem = _AddSuperItem(artist, fArtistList, NULL);

		readBytes = node.ReadAttr("Audio:Album", B_STRING_TYPE, 0, buffer,
			bufferSize);
		if (readBytes < 0)
			readBytes = 0;
		buffer[readBytes] = '\0';
		BString album = (strcmp(buffer, "") == 0) ? "Unknown" : buffer;
		ListItem* albumItem = _AddSuperItem(album, fAlbumList, artistItem);

		readBytes = node.ReadAttr("Media:Title", B_STRING_TYPE, 0, buffer,
			bufferSize);
		if (readBytes < 0)
			readBytes = 0;
		buffer[readBytes] = '\0';
		BString title= (strcmp(buffer, "") == 0) ? file->entry.name
			: buffer;

		ListItem* item = new ListItem(title, file);
		file->cookie = item;
		fListView->AddUnder(item, albumItem);
		fListView->SortItemsUnder(albumItem, true, StringItemComp);

		if (fQueryString == "")
			return;
		if (title.IFindFirst(fQueryString) >= 0) {
			fListView->Expand(artistItem);
			fListView->Expand(albumItem);
		} else if (album.IFindFirst(fQueryString) >= 0) {
			fListView->Expand(artistItem);
		}
	};


	void
	EntryRemoved(WatchedFile* file)
	{
		ListItem* item = (ListItem*)file->cookie;
		ListItem* album = (ListItem*)fListView->Superitem(item);
		fListView->RemoveItem(item);
		if (album != NULL && fListView->CountItemsUnder(album, true) == 0) {
			ListItem* artist = (ListItem*)fListView->Superitem(album);
			fListView->RemoveItem(album);
			if (artist != NULL && fListView->CountItemsUnder(artist, true) == 0)
				fListView->RemoveItem(artist);
		}
	};

	void
	EntryMoved(WatchedFile* file)
	{
		AttrChanged(file);
	};


	void
	AttrChanged(WatchedFile* file)
	{
		EntryRemoved(file);
		EntryCreated(file);
	}


	void
	EntriesCleared()
	{
		for (int32 i = 0; i < fListView->FullListCountItems(); i++)
			delete fListView->FullListItemAt(i);
		fListView->MakeEmpty();

		fArtistList.MakeEmpty();
		fAlbumList.MakeEmpty();

		printf("prev count %i\n", (int)fItemCount);
		fItemCount = 0;
		fCountView->SetText("Count: 0");
	}


private:
	ListItem*
	_AddSuperItem(const char* name, BObjectList<ListItem>& list,
		ListItem* under)
	{
		ListItem* item = _FindStringItem(list, name, under);
		if (item != NULL)
			return item;

		item = new ListItem(name);
		fListView->AddUnder(item, under);
		fListView->SortItemsUnder(under, true, StringItemComp);
		list.AddItem(item);

		fListView->Collapse(item);

		return item;
	}

	ListItem*
	_FindStringItem(BObjectList<ListItem>& list, const char* text,
		ListItem* parent)
	{
		for (int32 i = 0; i < list.CountItems(); i++) {
			ListItem* item = list.ItemAt(i);
			ListItem* superItem = (ListItem*)fListView->Superitem(item);
			if (parent != NULL && parent != superItem)
				continue;
			if (strcmp(item->Text(), text) == 0)
				return item;
		}
		return NULL;
	}

			BOutlineListView*	fListView;
			BStringView*		fCountView;

			BObjectList<ListItem>	fArtistList;
			BObjectList<ListItem> 	fAlbumList;

			BString					fQueryString;
			int32					fItemCount;
};


const uint32 kMsgQueryInput = '&qin';
const uint32 kMsgItemInvoked = '&iin';


MusicCollectionWindow::MusicCollectionWindow(BRect frame, const char* title)
	:
	BWindow(frame, title, B_DOCUMENT_WINDOW, B_AVOID_FRONT)
{
	fQueryField = new BTextControl("Search: ", "", NULL);
	fQueryField->SetExplicitAlignment(BAlignment(B_ALIGN_HORIZONTAL_CENTER,
		B_ALIGN_USE_FULL_HEIGHT));
	fQueryField->SetModificationMessage(new BMessage(kMsgQueryInput));

	fCountView = new BStringView("Count View", "Count:");

	fFileListView = new MusicFileListView("File List View");
	fFileListView->SetInvocationMessage(new BMessage(kMsgItemInvoked));
	BScrollView* scrollView = new BScrollView("list scroll", fFileListView, 0,
		true, true, B_PLAIN_BORDER);

	BALMLayout* layout = new BALMLayout(B_USE_ITEM_SPACING, B_USE_ITEM_SPACING);
	BALM::BALMLayoutBuilder(this, layout)
		.SetInsets(B_USE_WINDOW_INSETS)
		.Add(fQueryField, layout->Left(), layout->Top())
		.StartingAt(fQueryField)
			.AddToRight(fCountView, layout->Right())
			.AddBelow(scrollView, layout->Bottom(), layout->Left(),
				layout->Right());

	Area* area = layout->AreaFor(scrollView);
	area->SetLeftInset(0);
	area->SetRightInset(0);
	area->SetBottomInset(0);

	BSize min = layout->MinSize();
	BSize max = layout->MaxSize();
	SetSizeLimits(min.Width(), max.Width(), min.Height(), max.Height());

	fEntryViewInterface = new ListViewListener<FileListItem>(fFileListView,
		fCountView);
	fQueryHandler = new QueryHandler(fEntryViewInterface);
	AddHandler(fQueryHandler);
	fQueryReader = new QueryReader(fQueryHandler);
	fQueryHandler->SetReadThread(fQueryReader);

	// start initial query
	PostMessage(kMsgQueryInput);
}


MusicCollectionWindow::~MusicCollectionWindow()
{
	delete fQueryReader;
	delete fQueryHandler;
	delete fEntryViewInterface;
}


bool
MusicCollectionWindow::QuitRequested()
{
	be_app->PostMessage(B_QUIT_REQUESTED);
	return true;
}


void
MusicCollectionWindow::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case kMsgQueryInput:
			_StartNewQuery();
			break;

		case kMsgItemInvoked:
			fFileListView->Launch(message);
			break;

		default:
			BWindow::MessageReceived(message);
			break;
	}
}


void
CaseInsensitiveString(BString &instring,  BString &outstring)
{
	outstring = "";

	for (int i = 0; instring[i]; i++) {
		if (isupper(instring[i])) {
			int ch = tolower(instring[i]);
			outstring += "[";
			outstring += ch;
			outstring += instring[i];
			outstring += "]";
		} else if (islower(instring[i])) {
			int ch = toupper(instring[i]);
			outstring += "[";
			outstring += instring[i];
			outstring += ch;
			outstring += "]";
		} else
			outstring += instring[i];
	}
}


void
MusicCollectionWindow::_StartNewQuery()
{
	fQueryReader->Reset();
	fQueryHandler->Reset();

	BString orgString = fQueryField->Text();
	((ListViewListener<FileListItem>*)fEntryViewInterface)->SetQueryString(
		orgString);

	BVolume volume;
	//BVolumeRoster().GetBootVolume(&volume);
	BVolumeRoster roster;
	while (roster.GetNextVolume(&volume) == B_OK) {
		if (!volume.KnowsQuery())
			continue;
		BQuery* query = _CreateQuery(orgString);
		query->SetVolume(&volume);
		fQueryReader->AddQuery(query);
	}

	fQueryReader->Run();
}


BQuery*
MusicCollectionWindow::_CreateQuery(BString& orgString)
{
	BQuery* query = new BQuery;

	BString queryString;
	CaseInsensitiveString(orgString, queryString);

	query->PushAttr("Media:Title");
	query->PushString(queryString);
	query->PushOp(B_CONTAINS);

	query->PushAttr("Audio:Album");
	query->PushString(queryString);
	query->PushOp(B_CONTAINS);
	query->PushOp(B_OR);

	query->PushAttr("Audio:Artist");
	query->PushString(queryString);
	query->PushOp(B_CONTAINS);
	query->PushOp(B_OR);

	if (queryString == "") {
		query->PushAttr("BEOS:TYPE");
		query->PushString("audio/");
		query->PushOp(B_BEGINS_WITH);
		query->PushOp(B_OR);
	}

	query->PushAttr("BEOS:TYPE");
	query->PushString("audio/");
	query->PushOp(B_BEGINS_WITH);

	query->PushAttr("name");
	query->PushString(queryString);
	query->PushOp(B_CONTAINS);
	query->PushOp(B_AND);
	query->PushOp(B_OR);

	return query;
}