diff --git a/src/apps/Jamfile b/src/apps/Jamfile index c45d119b53..c73a9cd445 100644 --- a/src/apps/Jamfile +++ b/src/apps/Jamfile @@ -35,6 +35,7 @@ HaikuSubInclude mandelbrot ; HaikuSubInclude mediaconverter ; HaikuSubInclude mediaplayer ; HaikuSubInclude midiplayer ; +HaikuSubInclude musiccollection ; HaikuSubInclude networkstatus ; HaikuSubInclude networktime ; HaikuSubInclude overlayimage ; diff --git a/src/apps/musiccollection/FileMonitor.cpp b/src/apps/musiccollection/FileMonitor.cpp new file mode 100644 index 0000000000..620ded5a6b --- /dev/null +++ b/src/apps/musiccollection/FileMonitor.cpp @@ -0,0 +1,310 @@ +/* + * Copyright 2011, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Clemens Zeidler + */ + +#include "FileMonitor.h" + +#include + +#include +#include + + +FileMonitor::FileMonitor(EntryViewInterface* listener) + : + fListener(listener), + fCurrentReadList(NULL), + fCurrentReadIndex(0) +{ + +} + + +FileMonitor::~FileMonitor() +{ + Reset(); +} + + +void +FileMonitor::SetReadThread(ReadThread* readThread) +{ + fReadThread = readThread; +} + + +void +FileMonitor::Reset() +{ + fWatchedFileList.clear(); + stop_watching(this); + + BMessenger messenger(this); + messenger.SendMessage(kMsgCleared); + + if (fCurrentReadList != NULL) + fCurrentReadIndex = fCurrentReadList->size(); +} + + +void +FileMonitor::MessageReceived(BMessage* msg) +{ + switch (msg->what) { + case kMsgAddRefs: + { + if (fCurrentReadList == NULL) + fCurrentReadList = fReadThread->ReadRefList(); + uint32 terminate = fCurrentReadIndex + 50; + for (; fCurrentReadIndex < terminate; fCurrentReadIndex++) { + if (fCurrentReadIndex >= fCurrentReadList->size()) { + fCurrentReadList = NULL; + fCurrentReadIndex = 0; + fReadThread->ReadDone(); + break; + } + + entry_ref& entry = (*fCurrentReadList)[fCurrentReadIndex]; + node_ref nodeRef; + BNode node(&entry); + if (node.GetNodeRef(&nodeRef) != B_OK) + continue; + + EntryCreated(entry.name, entry.directory, entry.device, + nodeRef.node); + } + if (fCurrentReadList) + Looper()->PostMessage(kMsgAddRefs, this); + + break; + } + + case kMsgCleared: + fListener->EntriesCleared(); + break; + + default: + NodeMonitorHandler::MessageReceived(msg); + } +} + + +void +FileMonitor::EntryCreated(const char *name, ino_t directory, dev_t device, + ino_t node) +{ + WatchedFile file; + NodeMonitorHandler::make_node_ref(device, node, &file.node); + if (fWatchedFileList.find(file.node) != fWatchedFileList.end()) + return; + + NodeMonitorHandler::make_entry_ref(device, directory, name, &file.entry); + fWatchedFileList[file.node] = file; + + watch_node(&file.node, B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, this); + fListener->EntryCreated(&fWatchedFileList[file.node]); +} + + +void +FileMonitor::EntryRemoved(const char *name, ino_t directory, dev_t device, + ino_t node) +{ + WatchedFile* file = _FindFile(device, node); + if (file == NULL) + return; + + fListener->EntryRemoved(file); + fWatchedFileList.erase(file->node); +} + + +void +FileMonitor::EntryMoved(const char *name, const char *fromName, + ino_t fromDirectory, ino_t toDirectory, dev_t device, ino_t node, + dev_t nodeDevice) +{ + WatchedFile* file = _FindFile(device, node); + if (file == NULL) + return; + NodeMonitorHandler::make_entry_ref(device, toDirectory, name, &file->entry); + NodeMonitorHandler::make_node_ref(device, node, &file->node); + fListener->EntryMoved(file); +} + + +void +FileMonitor::StatChanged(ino_t node, dev_t device, int32 statFields) +{ + WatchedFile* file = _FindFile(device, node); + if (file == NULL) + return; + fListener->StatChanged(file); +} + + +void +FileMonitor::AttrChanged(ino_t node, dev_t device) +{ + WatchedFile* file = _FindFile(device, node); + if (file == NULL) + return; + fListener->AttrChanged(file); +} + + +WatchedFile* +FileMonitor::_FindFile(dev_t device, ino_t node) +{ + node_ref nodeRef; + NodeMonitorHandler::make_node_ref(device, node, &nodeRef); + + WatchedFileList::iterator it = fWatchedFileList.find(nodeRef); + if (it == fWatchedFileList.end()) + return NULL; + + return &it->second; +} + + +int32 +ReadThreadFunction(void *data) +{ + ReadThread* that = (ReadThread*)data; + return that->Process(); +} + + +ReadThread::ReadThread(FileMonitor* target) + : + fTarget(target), + fReading(false), + fStopped(false), + fThreadId(-1), + fNReaded(0), + fRunning(false) +{ + fWriteRefList = &fRefList1; + fReadRefList = &fRefList2; +} + + +status_t +ReadThread::Run() +{ + if (fThreadId >= 0) + return B_ERROR; + + fStopped = false; + fThreadId = spawn_thread(ReadThreadFunction, "file reader", B_LOW_PRIORITY, + this); + fRunning = true; + status_t status = resume_thread(fThreadId); + if (status != B_OK) + fRunning = false; + return status; +} + + +bool +ReadThread::Running() +{ + return fRunning; +} + + +status_t +ReadThread::Wait() +{ + status_t exitValue; + return wait_for_thread(fThreadId, &exitValue); +} + + +void +ReadThread::Stop() +{ + fStopped = true; +} + + +bool +ReadThread::Stopped() +{ + return fStopped; +} + + +RefList* +ReadThread::ReadRefList() +{ + return fReadRefList; +} + + +void +ReadThread::ReadDone() +{ + fReadRefList->clear(); + // and release the list + fReading = false; + + if (!fRunning && fWriteRefList->size() != 0) { + BMessenger messenger(fTarget); + _PublishEntrys(messenger); + } +} + + +int32 +ReadThread::Process() +{ + BMessenger messenger(fTarget); + + entry_ref entry; + while (ReadNextEntry(entry)) { + if (Stopped()) { + fWriteRefList->clear(); + break; + } + + fWriteRefList->push_back(entry); + + fNReaded++; + if (fNReaded >= 50) + _PublishEntrys(messenger); + } + + fRunning = false; + + _PublishEntrys(messenger); + + fThreadId = -1; + return B_OK; +} + + +void +ReadThread::_SwapLists() +{ + RefList* lastReadList = fReadRefList; + fReadRefList = fWriteRefList; + fWriteRefList = lastReadList; +} + + +void +ReadThread::_PublishEntrys(BMessenger& messenger) +{ + if (fReading || Stopped()) + return; + _SwapLists(); + fReading = true; + fNReaded = 0; + messenger.SendMessage(kMsgAddRefs); +} + diff --git a/src/apps/musiccollection/FileMonitor.h b/src/apps/musiccollection/FileMonitor.h new file mode 100644 index 0000000000..dac34d41da --- /dev/null +++ b/src/apps/musiccollection/FileMonitor.h @@ -0,0 +1,144 @@ +/* + * Copyright 2011, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Clemens Zeidler + */ +#ifndef FILE_MONITOR_H +#define FILE_MONITOR_H + + +#include +#include + +#include +#include + +#include "NodeMonitorHandler.h" + + +struct WatchedFile { + entry_ref entry; + node_ref node; + /*! Don't use it as the primary cookie storage. To be set in EntryCreated + in EntryViewInterface. */ + void* cookie; +}; + + +class NodeRefComp { +public: + bool + operator()(const node_ref& a, const node_ref& b) + { + return a.node < b.node; + } +}; + + +typedef std::map WatchedFileList; + + +class EntryViewInterface { +public: + virtual ~EntryViewInterface() {}; + + virtual void EntryCreated(WatchedFile* file) {}; + virtual void EntryRemoved(WatchedFile* file) {}; + virtual void EntryMoved(WatchedFile* file) {}; + virtual void StatChanged(WatchedFile* file) {}; + virtual void AttrChanged(WatchedFile* file) {}; + + virtual void EntriesCleared() {}; +}; + + +const uint32 kMsgAddRefs = '&adr'; +const uint32 kMsgCleared = '&clr'; + + +typedef std::vector RefList; + + +class ReadThread; + + +class FileMonitor : public NodeMonitorHandler { +public: + FileMonitor(EntryViewInterface* listener); + ~FileMonitor(); + + void SetReadThread(ReadThread* readThread); + + void Reset(); + + virtual void MessageReceived(BMessage* message); + + virtual void EntryCreated(const char *name, ino_t directory, + dev_t device, ino_t node); + virtual void EntryRemoved(const char *name, ino_t directory, + dev_t device, ino_t node); + virtual void EntryMoved(const char *name, + const char *fromName, ino_t fromDirectory, + ino_t toDirectory, dev_t device, + ino_t node, dev_t nodeDevice); + virtual void StatChanged(ino_t node, dev_t device, + int32 statFields); + virtual void AttrChanged(ino_t node, dev_t device); + +private: + WatchedFile* _FindFile(dev_t device, ino_t node); + + EntryViewInterface* fListener; + WatchedFileList fWatchedFileList; + + ReadThread* fReadThread; + RefList* fCurrentReadList; + uint32 fCurrentReadIndex; +}; + + +class ReadThread { +public: + ReadThread(FileMonitor* target); + virtual ~ReadThread() {} + + status_t Run(); + bool Running(); + status_t Wait(); + + void Stop(); + bool Stopped(); + + RefList* ReadRefList(); + void ReadDone(); + +protected: + virtual bool ReadNextEntry(entry_ref& entry) = 0; + + int32 Process(); + + friend int32 ReadThreadFunction(void *data); + + BHandler* fTarget; + + RefList fRefList1; + RefList fRefList2; + RefList* fWriteRefList; + RefList* fReadRefList; + bool fReading; + +private: + void _SwapLists(); + inline void _PublishEntrys(BMessenger& messenger); + + bool fStopped; + thread_id fThreadId; + + int16 fNReaded; + bool fRunning; +}; + + +#endif // FILE_MONITOR_H diff --git a/src/apps/musiccollection/Jamfile b/src/apps/musiccollection/Jamfile new file mode 100644 index 0000000000..e1f756275d --- /dev/null +++ b/src/apps/musiccollection/Jamfile @@ -0,0 +1,29 @@ +SubDir HAIKU_TOP src apps musiccollection ; + +UsePrivateHeaders storage shared ; +UseLibraryHeaders lp_solve linprog alm ; + + +Application MusicCollection : + FileMonitor.cpp + main.cpp + MusicCollectionWindow.cpp + QueryMonitor.cpp + + NodeMonitorHandler.cpp + : + be liblpsolve55.so libalm.so $(HAIKU_LOCALE_LIBS) $(TARGET_LIBSUPC++) + $(TARGET_LIBSTDC++) libshared.a + : + MusicCollection.rdef + ; + +DoCatalogs MusicCollection : + x-vnd.MusicCollection + : + main.cpp +; + + +SEARCH on [ FGristFiles NodeMonitorHandler.cpp ] + += [ FDirName $(SUBDIR) $(DOTDOT) $(DOTDOT) kits storage ] ; diff --git a/src/apps/musiccollection/MusicCollection.rdef b/src/apps/musiccollection/MusicCollection.rdef new file mode 100644 index 0000000000..23b369c1e9 --- /dev/null +++ b/src/apps/musiccollection/MusicCollection.rdef @@ -0,0 +1,47 @@ + +resource app_signature "application/x-vnd.Haiku-MusicCollection"; + +resource app_name_catalog_entry "x-vnd.Haiku-MusicCollection:System name:MusicCollection"; + +resource app_version { + major = 1, + middle = 0, + minor = 0, + variety = B_APPV_BETA, + internal = 0, + short_info = "MusicCollection", + long_info = "MusicCollection ©2011 Haiku, Inc." +}; + +resource vector_icon { + $"6E6369660D05000401740200060238FD543D29B3BD87203955A34A81F24A9838" + $"0075E9FFFF46D3EC0200060238E9203CA6E8BD037C394EBB4B224D4A495A0013" + $"9FB8FF048AA202000602BA5D113612D8B936AEBDA9B54AFE784B9845000FA6C2" + $"FF0B778A0200060234E5DA38ADBFBB9EB437EA864AFEE34B2C5E000499B4FF22" + $"BAD5020106033E2C11B82BB93719683D19E648342D448EA500FFEEACB3FFE789" + $"FFDABC5203C1844303FFFDCE03FFE26D02000602B504C13BBEB1BD9B53B6E83E" + $"4A1A204B5E8900FBFFC0FF46D3EC02000602B5818638F95EBD4FAAB9E7414A06" + $"ED4A930F00FBFFC0FFF8FF8903FF5A00130606B60626345131572B54375A405E" + $"4E50310606FE0F40604A604A60C45760C66D5DC553CC0B555D5B565F57CC59C6" + $"94584C5C4A5649504A5348504A0605B60126345131572B54375A405E3B0A1629" + $"52264C26472A522C52263F263328BAC52E5330542E363538335635573C3A403B" + $"404438583A59404E40553B5B0A04405E44544536403B0A0444544E504E314536" + $"0A03405E44544E500612EEEEEEBA0E2832242F2431242D2A2C2A2626282C2530" + $"27372233213B233B263F243D2341254029462844274829492B50284DB563C6BB" + $"B6974D2C4E2E4A32C4A8B973C2DEBAD84535403A32373939B897BBB60608EEDC" + $"332A2D2F2D2B2D3231313434313337353834323533333130302E30B978302C06" + $"04EE3E2C462E452C47304432432E452F412D0606FB0F3325BBD4B3BB2F293B28" + $"3A2ABCE3B6AB3D2C432A432A402ABEC6B676BEC6B67642233C273B233C270605" + $"B7032A272E22B647B6AB2E2930B5B1B87B2B322F312B2B2C31322C0608BBBB35" + $"2C382ABA5FB87138303832373139333B333E363837C12DBB653D323A313A323A" + $"303C2E06033B482E432DC4E4B8F8492C4C2A512A482A02022356B5E3C82AB273" + $"C8DF285A235B2A5A0202305E2F5FBA01CB122E5DB924CABD2D5E0606FA0B265C" + $"2C5D2F602F603360355E355F355C2D5C2D5C2E5A2A590204323F3740B82D3E2A" + $"422A3F2A45324AB822C30E384C3C473C4B3C430A0B2D422E4531463247364837" + $"4839453645344431443042120A010101000A0001001001178400040A02010200" + $"0A0A0103081FFF0A0201111001178400040A0B0111000A0C0112000A03010400" + $"0A040105000A050106000A0001071815FF01178403040A000107180015011786" + $"03040A060107000A07020809000A08040B0C0A0D0815FF0A0101100815FF0A00" + $"020E0F1815FF01178400040A09020E0F0815FF" +}; + diff --git a/src/apps/musiccollection/MusicCollectionWindow.cpp b/src/apps/musiccollection/MusicCollectionWindow.cpp new file mode 100644 index 0000000000..4bd1dd52a7 --- /dev/null +++ b/src/apps/musiccollection/MusicCollectionWindow.cpp @@ -0,0 +1,382 @@ +/* + * Copyright 2011, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Clemens Zeidler + */ + +#include "MusicCollectionWindow.h" + +#include +#include +#include +#include + +#include + +#include "ALMLayout.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 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& 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& 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 fArtistList; + BObjectList 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) +{ + BView* rootView = new BView(Bounds(), NULL, B_FOLLOW_ALL, B_WILL_DRAW); + AddChild(rootView); + rootView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + 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); + + float spacing = be_control_look->DefaultItemSpacing() / 2; + BALMLayout* layout = new BALMLayout(spacing); + layout->SetInset(spacing); + rootView->SetLayout(layout); + + layout->AddView(fQueryField, layout->Left(), layout->Top()); + layout->AddViewToRight(fCountView, layout->Right()); + layout->AddView(scrollView, layout->Left(), + layout->AreaFor(fQueryField)->Bottom(), layout->Right(), + layout->Bottom()); + + 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(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); + } +} + + +void +CaseInsensitiveString(BString &instring, BString &outstring) +{ + outstring = ""; + int i = 0; + while (instring[i]) + { + if (instring[i] >= 65 && instring[i] <= 90) // capital letters + { + int ch = instring[i] + 32; + outstring += "["; + outstring += ch; + outstring += instring[i]; + outstring += "]"; + } else if (instring[i] >= 97 && instring[i] <= 122) + { + int ch = instring[i]-32; + outstring += "["; + outstring += instring[i]; + outstring += ch; + outstring += "]"; + } else + outstring += instring[i]; + i++; + } +} + + +void +MusicCollectionWindow::_StartNewQuery() +{ + fQueryReader->Reset(); + fQueryHandler->Reset(); + + BString orgString = fQueryField->Text(); + ((ListViewListener*)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; +} diff --git a/src/apps/musiccollection/MusicCollectionWindow.h b/src/apps/musiccollection/MusicCollectionWindow.h new file mode 100644 index 0000000000..0e106ebaa1 --- /dev/null +++ b/src/apps/musiccollection/MusicCollectionWindow.h @@ -0,0 +1,42 @@ +/* + * Copyright 2011, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Clemens Zeidler + */ +#ifndef MUSIC_COLLECTION_WINDOW_H +#define MUSIC_COLLECTION_WINDOW_H + + +#include +#include +#include +#include + +#include "MusicFileListView.h" + + +class MusicCollectionWindow : public BWindow { +public: + MusicCollectionWindow(BRect rect, + const char* name); + virtual ~MusicCollectionWindow(); + + virtual bool QuitRequested(); + virtual void MessageReceived(BMessage* message); + +private: + void _StartNewQuery(); + BQuery* _CreateQuery(BString& queryString); + + BTextControl* fQueryField; + BStringView* fCountView; + MusicFileListView* fFileListView; + + EntryViewInterface* fEntryViewInterface; + QueryHandler* fQueryHandler; + QueryReader* fQueryReader; +}; + +#endif // MUSIC_COLLECTION_WINDOW_H diff --git a/src/apps/musiccollection/MusicFileListView.h b/src/apps/musiccollection/MusicFileListView.h new file mode 100644 index 0000000000..025bec1116 --- /dev/null +++ b/src/apps/musiccollection/MusicFileListView.h @@ -0,0 +1,130 @@ +/* + * Copyright 2011, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Clemens Zeidler + */ +#ifndef MUSIC_FILE_LIST_VIEW_H +#define MUSIC_FILE_LIST_VIEW_H + + +#include +#include +#include +#include + +#include "QueryMonitor.h" + + +class FileListItem : public BStringItem { +public: + FileListItem(const char* text, WatchedFile* file = NULL) + : + BStringItem(text), + fFile(file) + { + } + + + WatchedFile* + File() + { + return fFile; + } + +private: + WatchedFile* fFile; +}; + + +class MusicFileListView : public BOutlineListView { +public: + + MusicFileListView(const char *name) + : + BOutlineListView(name) + { + } + + + bool + InitiateDrag(BPoint where, int32 index, bool wasSelected) + { + int32 itemIndex = IndexOf(where); + FileListItem* item = (FileListItem*)ItemAt(itemIndex); + if (item == NULL) + return false; + + const char* text = item->Text(); + + BRect rect(0, 0, 200, 50); + BBitmap* bitmap = new BBitmap(rect, B_RGB32, true); + BView* bitmapView = new BView(rect, "bitmap", B_FOLLOW_NONE, + B_WILL_DRAW); + + bitmap->Lock(); + bitmap->AddChild(bitmapView); + + bitmapView->SetLowColor(255, 255, 255, 0); // transparent + bitmapView->SetHighColor(0, 0, 0, 100); + bitmapView->SetDrawingMode(B_OP_COPY); + bitmapView->FillRect(bitmapView->Bounds(), B_SOLID_LOW); + + bitmapView->SetDrawingMode(B_OP_OVER); + font_height height; + bitmapView->GetFontHeight(&height); + float fontHeight = height.ascent + height.descent; + BRect latchRect = LatchRect(BRect(0, 0, item->Width(), item->Height()), + item->OutlineLevel()); + bitmapView->DrawString(text, BPoint(latchRect.Width(), fontHeight)); + + bitmapView->Sync(); + bitmap->Unlock(); + + BMessage dragMessage(B_SIMPLE_DATA); + dragMessage.AddPoint("click_location", where); + + _RecursiveAddRefs(dragMessage, item); + + BRect itemFrame(ItemFrame(itemIndex)); + BPoint pt(where.x + itemFrame.left, where.y - itemFrame.top); + DragMessage(&dragMessage, bitmap, B_OP_ALPHA, pt, this); + + return true; + } + + + void + Launch(BMessage* message) + { + int32 index; + for (int32 i = 0; ; i++) { + if (message->FindInt32("index", i, &index) != B_OK) + break; + FileListItem* item = (FileListItem*)ItemAt(index); + + BMessage refs(B_REFS_RECEIVED); + _RecursiveAddRefs(refs, item); + be_roster->Launch("application/x-vnd.Haiku-MediaPlayer", &refs); + } + }; + +private: + + void + _RecursiveAddRefs(BMessage& message, FileListItem* item) + { + WatchedFile* file = item->File(); + if (file != NULL) { + message.AddRef("refs", &(file->entry)); + } else { + for (int32 i = 0; i < CountItemsUnder(item, true); i++) { + _RecursiveAddRefs(message, (FileListItem*)ItemUnderAt( + item, true, i)); + } + } + } +}; + +#endif // MUSIC_FILE_LIST_VIEW_H diff --git a/src/apps/musiccollection/QueryMonitor.cpp b/src/apps/musiccollection/QueryMonitor.cpp new file mode 100644 index 0000000000..b6a586591b --- /dev/null +++ b/src/apps/musiccollection/QueryMonitor.cpp @@ -0,0 +1,90 @@ +/* + * Copyright 2011, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Clemens Zeidler + */ + + +#include "QueryMonitor.h" + + +QueryHandler::QueryHandler(EntryViewInterface* listener) + : + FileMonitor(listener) +{ + +} + + +void +QueryHandler::MessageReceived(BMessage* message) +{ + int32 opcode; + if (message->what == B_QUERY_UPDATE + && message->FindInt32("opcode", &opcode) == B_OK) { + switch (opcode) { + case B_ENTRY_CREATED: + case B_ENTRY_REMOVED: + message->what = B_NODE_MONITOR; + break; + } + } + + FileMonitor::MessageReceived(message); +} + + +QueryReader::QueryReader(QueryHandler* handler) + : + ReadThread(handler) +{ + +} + + +QueryReader::~QueryReader() +{ + Reset(); +} + + +bool +QueryReader::AddQuery(BQuery* query) +{ + query->SetTarget(fTarget); + query->Fetch(); + return fQueries.AddItem(query); +} + + +void +QueryReader::Reset() +{ + Stop(); + Wait(); + + for (int32 i = 0; i < fLiveQueries.CountItems(); i++) + delete fLiveQueries.ItemAt(i); + fLiveQueries.MakeEmpty(); + + for (int32 i = 0; i < fQueries.CountItems(); i++) + delete fQueries.ItemAt(i); + fQueries.MakeEmpty(); +} + + +bool +QueryReader::ReadNextEntry(entry_ref& entry) +{ + BQuery* query = fQueries.ItemAt(0); + if (query == NULL) + return false; + if (query->GetNextRef(&entry) != B_OK) { + fQueries.RemoveItemAt(0); + fLiveQueries.AddItem(query); + return ReadNextEntry(entry); + } + return true; +} diff --git a/src/apps/musiccollection/QueryMonitor.h b/src/apps/musiccollection/QueryMonitor.h new file mode 100644 index 0000000000..0e9a5b350a --- /dev/null +++ b/src/apps/musiccollection/QueryMonitor.h @@ -0,0 +1,49 @@ +/* + * Copyright 2011, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Clemens Zeidler + */ +#ifndef QUERY_MONITOR_H +#define QUERY_MONITOR_H + + +#include + +#include + +#include "FileMonitor.h" + + +/*! Handle live query messages, query reader messages, and node monitor messages +and dispatch them to a EntryViewInterface. */ +class QueryHandler : public FileMonitor { +public: + QueryHandler(EntryViewInterface* listener); + + void MessageReceived(BMessage* message); +}; + + +typedef BObjectList BQueryList; + + +class QueryReader : public ReadThread { +public: + QueryReader(QueryHandler* handler); + ~QueryReader(); + + bool AddQuery(BQuery* query); + void Reset(); + +protected: + bool ReadNextEntry(entry_ref& entry); + +private: + BQueryList fQueries; + BQueryList fLiveQueries; +}; + + +#endif // QUERY_MONITOR_H diff --git a/src/apps/musiccollection/main.cpp b/src/apps/musiccollection/main.cpp new file mode 100644 index 0000000000..c85fb84ce1 --- /dev/null +++ b/src/apps/musiccollection/main.cpp @@ -0,0 +1,31 @@ +/* + * Copyright 2011, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Clemens Zeidler + */ + + +#include +#include + +#include "MusicCollectionWindow.h" + + +const char* kAppSignature = "application/x-vnd.MusicCollection"; + + +int +main(int argc, char* argv[]) +{ + BApplication app(kAppSignature); + + BWindow* window = new MusicCollectionWindow(BRect(100, 100, 350, 500), + B_TRANSLATE_SYSTEM_NAME("Music Collection")); + window->Show(); + + app.Run(); + + return 0; +}