diff --git a/src/musikwin/Main.cpp b/src/musikwin/Main.cpp index 8fec96f95..6514900e0 100644 --- a/src/musikwin/Main.cpp +++ b/src/musikwin/Main.cpp @@ -69,6 +69,8 @@ int APIENTRY _tWinMain(HINSTANCE instance, HINSTANCE previousInstance, LPTSTR co mainWindow.MoveTo(200, 200); LibraryPtr library = LibraryFactory::Libraries().at(0); + library->SetMessageQueue(mainWindow.Queue()); + MasterTransport transport; PlaybackService playback(mainWindow.Queue(), library, transport); diff --git a/src/musikwin/app/controller/MainController.cpp b/src/musikwin/app/controller/MainController.cpp index 2cc81c9db..0c194d326 100644 --- a/src/musikwin/app/controller/MainController.cpp +++ b/src/musikwin/app/controller/MainController.cpp @@ -34,6 +34,7 @@ #include "MainController.h" #include +#include #include #include #include @@ -41,18 +42,27 @@ using namespace musik::win; using namespace musik::core; using namespace musik::core::audio; +using namespace musik::core::runtime; using namespace musik::glue; using namespace musik::core::library; using namespace win32cpp; using ColumnRef = ListView::ColumnRef; +using CellRendererRef = ListView::CellRendererRef; +using TextCellRenderer = ListView::TextCellRenderer; +using TextCellRendererRef = std::shared_ptr; static ColumnRef sNumberColumn = ListView::Column::Create(_T("#"), 50); -static ColumnRef sTitleColumn = ListView::Column::Create(_T("title")); -static ColumnRef sDurationColumn = ListView::Column::Create(_T("duration"), 100, TextAlignment::TextAlignRight); +static ColumnRef sTitleColumn = ListView::Column::Create(_T("title"), 250); +static ColumnRef sDurationColumn = ListView::Column::Create(_T("time"), 50, TextAlignment::TextAlignRight); +static ColumnRef sAlbumColumn = ListView::Column::Create(_T("album")); static ColumnRef sArtistColumn = ListView::Column::Create(_T("artist")); -class TrackListModel : public ListView::Model { +#define EDIT_VIEW_HEIGHT 22 +#define UPDATE_SEARCH_MESSAGE 1001 +#define UPDATE_SEARCH_DEBOUNCE_MS 750 + +class MainController::TrackListModel : public ListView::Model, public sigslot::has_slots<> { public: static std::shared_ptr Create(PlaybackService& playback) { return std::shared_ptr(new TrackListModel(playback)); @@ -60,30 +70,74 @@ class TrackListModel : public ListView::Model { TrackListModel(PlaybackService& playback) : playback(playback) { - this->SetRowCount(playback.Count()); + playback.TrackChanged.connect(this, &TrackListModel::OnTrackChanged); + + this->normal.reset(new TextCellRenderer()); + + this->bold.reset(new TextCellRenderer()); + FontRef font = Font::Create(); + font->SetBold(true); + this->bold->SetFont(font); + } + + void SetTrackList(std::shared_ptr trackList) { + this->trackList = trackList; + this->SetRowCount(trackList ? trackList->Count() : 0); + this->InvalidateData(); } virtual uistring CellValueToString(int rowIndex, ColumnRef column) { - TrackPtr track = playback.GetTrackAtIndex(rowIndex); - if (track) { - if (column == sNumberColumn) { - return u8to16(track->GetValue(constants::Track::TRACK_NUM)); - } - else if (column == sTitleColumn) { - return u8to16(track->GetValue(constants::Track::TITLE)); - } - else if (column == sDurationColumn) { - return u8to16(duration::Duration(track->GetValue(constants::Track::DURATION))); - } - else if (column == sArtistColumn) { - return u8to16(track->GetValue(constants::Track::ARTIST)); - } - } return _T(""); } + virtual CellRendererRef CellRenderer(int rowIndex, ColumnRef column) { + TrackPtr track = this->trackList->Get(rowIndex); + + if (track) { + TextCellRendererRef renderer = this->normal; + + if (playing && playing->Id() == track->Id()) { + renderer = this->bold; + } + + uistring value; + + if (column == sNumberColumn) { + value = u8to16(track->GetValue(constants::Track::TRACK_NUM)); + } + else if (column == sTitleColumn) { + value = u8to16(track->GetValue(constants::Track::TITLE)); + } + else if (column == sDurationColumn) { + value = u8to16(duration::Duration(track->GetValue(constants::Track::DURATION))); + } + else if (column == sAlbumColumn) { + value = u8to16(track->GetValue(constants::Track::ALBUM)); + } + else if (column == sArtistColumn) { + value = u8to16(track->GetValue(constants::Track::ARTIST)); + } + + if (value.size()) { + renderer->Set(value, column->Alignment()); + return renderer; + } + } + + return TextCellRendererRef(); + } + private: + void OnTrackChanged(size_t index, TrackPtr track) { + this->playing = track; + this->InvalidateData(); + } + + std::shared_ptr trackList; PlaybackService& playback; + TextCellRendererRef normal; + TextCellRendererRef bold; + TrackPtr playing; }; MainController::MainController( @@ -92,22 +146,26 @@ MainController::MainController( LibraryPtr library) : mainWindow(mainWindow) , playback(playback) -, library(library) { - std::shared_ptr query = - std::shared_ptr(new SearchTrackListQuery(library, "o'o")); +, library(library) +, trackListDirty(true) { + library->QueryCompleted.connect(this, &MainController::OnLibraryQueryCompleted); - library->Enqueue(query, ILibrary::QuerySynchronous); + this->trackListQuery.reset(new SearchTrackListQuery(library, "")); + library->Enqueue(this->trackListQuery); - playback.Play(*query->GetResult(), 0); + this->editView = this->mainWindow.AddChild(new EditView()); + this->editView->Changed.connect(this, &MainController::OnSearchEditChanged); - this->categoryList = this->mainWindow.AddChild(new ListView()); + this->trackListView = this->mainWindow.AddChild(new ListView()); + this->trackListView->RowActivated.connect(this, &MainController::OnTrackListRowActivated); + this->trackListView->AddColumn(sNumberColumn); + this->trackListView->AddColumn(sTitleColumn); + this->trackListView->AddColumn(sAlbumColumn); + this->trackListView->AddColumn(sArtistColumn); + this->trackListView->AddColumn(sDurationColumn); - this->trackList = this->mainWindow.AddChild(new ListView()); - this->trackList->AddColumn(sNumberColumn); - this->trackList->AddColumn(sTitleColumn); - this->trackList->AddColumn(sDurationColumn); - this->trackList->AddColumn(sArtistColumn); - this->trackList->SetModel(TrackListModel::Create(this->playback)); + this->trackListModel = TrackListModel::Create(this->playback); + this->trackListView->SetModel(this->trackListModel); this->Layout(); @@ -121,17 +179,57 @@ void MainController::Layout() { auto size = this->mainWindow.ClientSize(); if (size.height > 0) { static int padding = 8; - int categoryCx = size.width / 3; - int tracksCx = size.width - (categoryCx + padding); - int tracksX = categoryCx + padding; - this->categoryList->MoveTo(0, 0); - this->categoryList->Resize(categoryCx, size.height); - this->trackList->MoveTo(tracksX, 0); - this->trackList->Resize(tracksCx, size.height); + int editCx = (size.width * 2) / 3; + int editX = (size.width / 2) - (editCx / 2); + + this->editView->MoveTo(editX, padding); + this->editView->Resize(editCx, EDIT_VIEW_HEIGHT); + + int listY = padding + EDIT_VIEW_HEIGHT + padding; + int tracksCx = size.width; + int listCy = size.height - listY; + + this->trackListView->MoveTo(0, listY); + this->trackListView->Resize(tracksCx, listCy); } } void MainController::OnMainWindowResized(Window* window, Size size) { this->Layout(); -} \ No newline at end of file +} + +void MainController::OnTrackListRowActivated(ListView* list, int index) { + if (trackListDirty) { + this->trackListDirty = false; + if (this->trackList) { + this->playback.Play((*this->trackList), index); + } + } + else { + playback.Play(index); + } +} + +void MainController::ProcessMessage(musik::core::runtime::IMessage &message) { + if (message.Type() == UPDATE_SEARCH_MESSAGE) { + std::string term = u16to8(this->editView->Caption()); + this->trackListQuery.reset(new SearchTrackListQuery(library, term)); + library->Enqueue(this->trackListQuery); + } +} + +void MainController::OnLibraryQueryCompleted(IQueryPtr query) { + if (this->trackListQuery.get() == query.get()) { + this->trackListDirty = true; + this->trackList = ((TrackListQueryBase *)query.get())->GetResult(); + this->trackListModel->SetTrackList(this->trackList); + } +} + +void MainController::OnSearchEditChanged(win32cpp::EditView* editView) { + if (editView == this->editView) { + IMessageQueue& queue = mainWindow.Queue(); + queue.Debounce(Message::Create(this, UPDATE_SEARCH_MESSAGE, 0, 0), UPDATE_SEARCH_DEBOUNCE_MS); + } +} diff --git a/src/musikwin/app/controller/MainController.h b/src/musikwin/app/controller/MainController.h index 895eed16f..082e24c32 100644 --- a/src/musikwin/app/controller/MainController.h +++ b/src/musikwin/app/controller/MainController.h @@ -35,13 +35,21 @@ #pragma once #include + #include #include #include + +#include + +#include #include namespace musik { namespace win { - class MainController : public sigslot::has_slots<> { + class MainController : + public sigslot::has_slots<>, + public musik::core::runtime::IMessageTarget + { public: MainController( MainWindow& mainWindow, @@ -50,15 +58,30 @@ namespace musik { namespace win { virtual ~MainController(); + virtual void ProcessMessage(musik::core::runtime::IMessage &message); + private: + class TrackListModel; + void OnMainWindowResized(win32cpp::Window* window, win32cpp::Size size); + void OnTrackListRowActivated(win32cpp::ListView* list, int index); + void OnLibraryQueryCompleted(musik::core::IQueryPtr query); + void OnSearchEditChanged(win32cpp::EditView* editView); + void Layout(); - MainWindow& mainWindow; musik::core::audio::PlaybackService& playback; musik::core::LibraryPtr library; - win32cpp::ListView* categoryList; - win32cpp::ListView* trackList; + MainWindow& mainWindow; + + std::shared_ptr trackListQuery; + std::shared_ptr trackListModel; + std::shared_ptr trackList; + + win32cpp::ListView* trackListView; + win32cpp::EditView* editView; + + bool trackListDirty; }; } } \ No newline at end of file diff --git a/src/musikwin/win32cpp/DeviceContext.cpp b/src/musikwin/win32cpp/DeviceContext.cpp index 6ca1c14aa..4c3bdd77f 100644 --- a/src/musikwin/win32cpp/DeviceContext.cpp +++ b/src/musikwin/win32cpp/DeviceContext.cpp @@ -8,31 +8,31 @@ // // All rights reserved. // -// Redistribution and use in source and binary forms, with or without +// Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // -// * Neither the name of the author nor the names of other contributors may -// be used to endorse or promote products derived from this software -// without specific prior written permission. +// * Neither the name of the author nor the names of other contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. // -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. // ////////////////////////////////////////////////////////////////////////////// diff --git a/src/musikwin/win32cpp/ListView.hpp b/src/musikwin/win32cpp/ListView.hpp index ae89a6151..a5ec044b6 100644 --- a/src/musikwin/win32cpp/ListView.hpp +++ b/src/musikwin/win32cpp/ListView.hpp @@ -44,6 +44,7 @@ #include #include #include +#include #include @@ -363,6 +364,9 @@ public: // constructors ///\param value The value to render ///\param alignment The alignment to use /*ctor*/ TextCellRenderer(const T& value, TextAlignment alignment = TextAlignLeft); + /*ctor*/ TextCellRenderer(TextAlignment aligntment = TextAlignLeft); + void SetFont(FontRef font); + void Set(const T& value, TextAlignment aligntment = TextAlignLeft); public: // methods void Render(const ListView& listView, RenderParams& renderParams); @@ -370,14 +374,33 @@ public: // methods protected: // instance data uistring textValue; TextAlignment alignment; + FontRef font; }; template /*ctor*/ ListView::TextCellRenderer::TextCellRenderer(const T& value, TextAlignment alignment) -: alignment(alignment) +{ + this->Set(value, alignment); +} + +template +/*ctor*/ ListView::TextCellRenderer::TextCellRenderer(TextAlignment alignment) +{ + this->alignment = alignment; +} + +template +void ListView::TextCellRenderer::Set(const T& value, TextAlignment alignment) { typedef boost::basic_format format; this->textValue = (format(_T("%1%")) % value).str(); + this->alignment = alignment; +} + +template +void ListView::TextCellRenderer::SetFont(FontRef font) +{ + this->font = font; } template @@ -390,13 +413,18 @@ void ListView::TextCellRenderer::Render(const ListView& listView, Rend ::SecureZeroMemory(&drawTextParams, sizeof(DRAWTEXTPARAMS)); drawTextParams.cbSize = sizeof(DRAWTEXTPARAMS); drawTextParams.iLeftMargin = 6; - // + int bufferSize = (int) this->textValue.size() + 4; // DrawTextEx may add up to 4 additional characters std::unique_ptr buffer(new uichar[bufferSize]); ::wcsncpy_s(buffer.get(), bufferSize, this->textValue.c_str(), this->textValue.size()); - // + RECT drawRect = renderParams.rect; - // + HGDIOBJ old = nullptr; + + if (font) { + old = ::SelectObject(renderParams.hdc, font->GetHFONT()); + } + ::DrawTextEx( renderParams.hdc, buffer.get(), @@ -404,6 +432,10 @@ void ListView::TextCellRenderer::Render(const ListView& listView, Rend &drawRect, this->alignment | DT_END_ELLIPSIS | DT_VCENTER | DT_SINGLELINE, &drawTextParams); + + if (font) { + ::SelectObject(renderParams.hdc, old); + } } //////////////////////////////////////////////////////////////////////////////