diff --git a/src/apps/mediaplayer/Jamfile b/src/apps/mediaplayer/Jamfile index 6757b0ba0a..0a5f20efe8 100644 --- a/src/apps/mediaplayer/Jamfile +++ b/src/apps/mediaplayer/Jamfile @@ -7,6 +7,7 @@ AddSubDirSupportedPlatforms libbe_test ; # source directories local sourceDirs = supplier + support ; local sourceDir ; @@ -21,8 +22,16 @@ Application MediaPlayer : MediaTrackVideoSupplier.cpp VideoSupplier.cpp - # . + # support AbstractLOAdapter.cpp + Command.cpp + CommandStack.cpp + Listener.cpp + ListenerAdapter.cpp + Notifier.cpp + RWLocker.cpp + + # . Controller.cpp ControllerObserver.cpp ControllerView.cpp diff --git a/src/apps/mediaplayer/MainWin.cpp b/src/apps/mediaplayer/MainWin.cpp index 20be25950c..e99402b9d9 100644 --- a/src/apps/mediaplayer/MainWin.cpp +++ b/src/apps/mediaplayer/MainWin.cpp @@ -166,6 +166,15 @@ MainWin::MainWin() _SetupWindow(); + // setup the playlist window now, we need to have it + // running for the undo/redo playlist editing + fPlaylistWindow = new PlaylistWindow(BRect(150, 150, 400, 500), + fPlaylist, fController); + fPlaylistWindow->Hide(); + fPlaylistWindow->Show(); + // this makes sure the window thread is running without + // showing the window just yet + Show(); } @@ -683,13 +692,6 @@ MainWin::ShowFileInfo() void MainWin::ShowPlaylistWindow() { - if (!fPlaylistWindow) { - fPlaylistWindow = new PlaylistWindow(BRect(150, 150, 400, 500), - fPlaylist, fController); - fPlaylistWindow->Show(); - return; - } - if (fPlaylistWindow->Lock()) { if (fPlaylistWindow->IsHidden()) fPlaylistWindow->Show(); @@ -728,15 +730,18 @@ MainWin::VideoFormatChange(int width, int height, float width_scale, float heigh void -MainWin::_RefsReceived(BMessage *msg) +MainWin::_RefsReceived(BMessage* msg) { // the playlist ist replaced by dropped files // or the dropped files are appended to the end // of the existing playlist if is pressed int32 appendIndex = modifiers() & B_SHIFT_KEY ? fPlaylist->CountItems() : -1; + msg->AddInt32("append_index", appendIndex); - fPlaylist->AppendRefs(msg, appendIndex); + // forward the message to the playlist window, + // so that undo/redo is used for modifying the playlist + fPlaylistWindow->PostMessage(msg); } diff --git a/src/apps/mediaplayer/PlaylistListView.cpp b/src/apps/mediaplayer/PlaylistListView.cpp index 44ffa0f34d..7aa618a53d 100644 --- a/src/apps/mediaplayer/PlaylistListView.cpp +++ b/src/apps/mediaplayer/PlaylistListView.cpp @@ -252,12 +252,12 @@ PlaylistListView::MessageReceived(BMessage* message) case B_SIMPLE_DATA: if (message->HasRef("refs")) - _RefsReceived(message, fDropIndex); + RefsReceived(message, fDropIndex); else if (message->HasPointer("list")) SimpleListView::MessageReceived(message); break; case B_REFS_RECEIVED: - _RefsReceived(message, fDropIndex); + RefsReceived(message, fDropIndex); break; default: @@ -421,6 +421,18 @@ PlaylistListView::DrawListItem(BView* owner, int32 index, BRect frame) const } +void +PlaylistListView::RefsReceived(BMessage* message, int32 appendIndex) +{ + if (!fPlaylist->Lock()) + return; + + fPlaylist->AppendRefs(message, appendIndex); + + fPlaylist->Unlock(); +} + + // #pragma mark - @@ -489,14 +501,3 @@ PlaylistListView::_SetPlaybackState(uint32 state) } -void -PlaylistListView::_RefsReceived(BMessage* message, int32 dropIndex) -{ - if (!fPlaylist->Lock()) - return; - - fPlaylist->AppendRefs(message, dropIndex); - - fPlaylist->Unlock(); -} - diff --git a/src/apps/mediaplayer/PlaylistListView.h b/src/apps/mediaplayer/PlaylistListView.h index 97959d13a3..87e182c45a 100644 --- a/src/apps/mediaplayer/PlaylistListView.h +++ b/src/apps/mediaplayer/PlaylistListView.h @@ -38,6 +38,10 @@ class PlaylistListView : public SimpleListView { virtual void DrawListItem(BView* owner, int32 index, BRect frame) const; + // PlaylistListView + void RefsReceived(BMessage* message, + int32 appendIndex); + private: void _FullSync(); void _AddItem(const entry_ref& ref, int32 index); @@ -46,9 +50,6 @@ class PlaylistListView : public SimpleListView { void _SetCurrentPlaylistIndex(int32 index); void _SetPlaybackState(uint32 state); - void _RefsReceived(BMessage* message, - int32 dropIndex); - Playlist* fPlaylist; PlaylistObserver* fPlaylistObserver; diff --git a/src/apps/mediaplayer/PlaylistWindow.cpp b/src/apps/mediaplayer/PlaylistWindow.cpp index eb1088039f..9e6b5c2527 100644 --- a/src/apps/mediaplayer/PlaylistWindow.cpp +++ b/src/apps/mediaplayer/PlaylistWindow.cpp @@ -7,25 +7,50 @@ */ #include "PlaylistWindow.h" +#include +#include +#include #include #include +#include +#include "CommandStack.h" #include "PlaylistListView.h" +#include "RWLocker.h" PlaylistWindow::PlaylistWindow(BRect frame, Playlist* playlist, Controller* controller) - : BWindow(frame, "Playlist", B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS) + : BWindow(frame, "Playlist", B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, + B_ASYNCHRONOUS_CONTROLS) + + , fLocker(new RWLocker("command stack lock")) + , fCommandStack(new CommandStack(fLocker)) + , fCommandStackListener(this) { frame = Bounds(); + + _CreateMenu(frame); + frame.right -= B_V_SCROLL_BAR_WIDTH; - PlaylistListView* listView = new PlaylistListView(frame, playlist, - controller); + fListView = new PlaylistListView(frame, playlist, controller); - fTopView = new BScrollView("playlist scrollview", - listView, B_FOLLOW_ALL, 0, false, true, B_NO_BORDER); + BScrollView* scrollView = new BScrollView("playlist scrollview", + fListView, B_FOLLOW_ALL, 0, false, true, B_NO_BORDER); + fTopView = scrollView; AddChild(fTopView); + + // small visual tweak + if (BScrollBar* scrollBar = scrollView->ScrollBar(B_VERTICAL)) { + // make it so the frame of the menubar is also the frame of + // the scroll bar (appears to be) + scrollBar->MoveBy(0, -1); + scrollBar->ResizeBy(0, 1); + } + + fCommandStack->AddListener(&fCommandStackListener); + _ObjectChanged(fCommandStack); } @@ -34,6 +59,10 @@ PlaylistWindow::~PlaylistWindow() // give listeners a chance to detach themselves fTopView->RemoveSelf(); delete fTopView; + + fCommandStack->RemoveListener(&fCommandStackListener); + delete fCommandStack; + delete fLocker; } @@ -53,9 +82,91 @@ PlaylistWindow::MessageReceived(BMessage* message) if (LastMouseMovedView()) PostMessage(message, LastMouseMovedView()); break; + + case B_UNDO: + fCommandStack->Undo(); + break; + case B_REDO: + fCommandStack->Redo(); + break; + + case MSG_OBJECT_CHANGED: { + Notifier* notifier; + if (message->FindPointer("object", (void**)¬ifier) == B_OK) + _ObjectChanged(notifier); + break; + } + + case B_REFS_RECEIVED: + case B_SIMPLE_DATA: { + // only accept this message when it comes from the + // player window, _not_ when it is dropped in this window + // outside of the playlist! + int32 appendIndex; + if (message->FindInt32("append_index", &appendIndex) == B_OK) { + fListView->RefsReceived(message, appendIndex); + } + break; + } + default: BWindow::MessageReceived(message); break; } } + +// #pragma mark - + + +void +PlaylistWindow::_CreateMenu(BRect& frame) +{ + frame.bottom = 15; + BMenuBar* menuBar = new BMenuBar(frame, "main menu"); + BMenu* fileMenu = new BMenu("Playlist"); + menuBar->AddItem(fileMenu); + // TODO add some items: "Open", "Save", "Make Empty"... + + BMenu* editMenu = new BMenu("Edit"); + BMessage* message = new BMessage(B_UNDO); + fUndoMI = new BMenuItem("Undo", message); + editMenu->AddItem(fUndoMI); + message = new BMessage(B_REDO); + fRedoMI = new BMenuItem("Undo", message); + editMenu->AddItem(fRedoMI); + menuBar->AddItem(editMenu); + + AddChild(menuBar); + fileMenu->SetTargetForItems(this); + editMenu->SetTargetForItems(this); + + menuBar->ResizeToPreferred(); + frame = Bounds(); + frame.top = menuBar->Frame().bottom + 1; +} + + +// _ObjectChanged +void +PlaylistWindow::_ObjectChanged(const Notifier* object) +{ + if (object == fCommandStack) { + // relable Undo item and update enabled status + BString label("Undo"); + fUndoMI->SetEnabled(fCommandStack->GetUndoName(label)); + if (fUndoMI->IsEnabled()) + fUndoMI->SetLabel(label.String()); + else + fUndoMI->SetLabel(""); + + // relable Redo item and update enabled status + label.SetTo("Redo"); + fRedoMI->SetEnabled(fCommandStack->GetRedoName(label)); + if (fRedoMI->IsEnabled()) + fRedoMI->SetLabel(label.String()); + else + fRedoMI->SetLabel(""); + } +} + diff --git a/src/apps/mediaplayer/PlaylistWindow.h b/src/apps/mediaplayer/PlaylistWindow.h index 28c65c6427..20501dc8e2 100644 --- a/src/apps/mediaplayer/PlaylistWindow.h +++ b/src/apps/mediaplayer/PlaylistWindow.h @@ -11,8 +11,16 @@ #include +#include "ListenerAdapter.h" + +class BMenuBar; +class BMenuItem; +class CommandStack; class Controller; +class Notifier; class Playlist; +class PlaylistListView; +class RWLocker; class PlaylistWindow : public BWindow { public: @@ -25,7 +33,19 @@ class PlaylistWindow : public BWindow { virtual void MessageReceived(BMessage* message); private: + void _CreateMenu(BRect& frame); + void _ObjectChanged(const Notifier* object); + + Playlist* fPlaylist; + PlaylistListView* fListView; + BView* fTopView; + BMenuItem* fUndoMI; + BMenuItem* fRedoMI; + + RWLocker* fLocker; + CommandStack* fCommandStack; + ListenerAdapter fCommandStackListener; }; #endif // PLAYLIST_WINDOW_H diff --git a/src/apps/mediaplayer/AbstractLOAdapter.cpp b/src/apps/mediaplayer/support/AbstractLOAdapter.cpp similarity index 100% rename from src/apps/mediaplayer/AbstractLOAdapter.cpp rename to src/apps/mediaplayer/support/AbstractLOAdapter.cpp diff --git a/src/apps/mediaplayer/AbstractLOAdapter.h b/src/apps/mediaplayer/support/AbstractLOAdapter.h similarity index 100% rename from src/apps/mediaplayer/AbstractLOAdapter.h rename to src/apps/mediaplayer/support/AbstractLOAdapter.h diff --git a/src/apps/mediaplayer/support/Command.cpp b/src/apps/mediaplayer/support/Command.cpp new file mode 100644 index 0000000000..5728d5142f --- /dev/null +++ b/src/apps/mediaplayer/support/Command.cpp @@ -0,0 +1,91 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "Command.h" + +#include + +#include + +// constructor +Command::Command() + : fTimeStamp(system_time()) +{ +} + +// destructor +Command::~Command() +{ +} + +// InitCheck +status_t +Command::InitCheck() +{ + return B_NO_INIT; +} + +// Perform +status_t +Command::Perform() +{ + return B_ERROR; +} + +// Undo +status_t +Command::Undo() +{ + return B_ERROR; +} + +// Redo +status_t +Command::Redo() +{ + return Perform(); +} + +// GetName +void +Command::GetName(BString& name) +{ + name << "Name of action goes here."; +} + +// UndoesPrevious +bool +Command::UndoesPrevious(const Command* previous) +{ + return false; +} + +// CombineWithNext +bool +Command::CombineWithNext(const Command* next) +{ + return false; +} + +// CombineWithPrevious +bool +Command::CombineWithPrevious(const Command* previous) +{ + return false; +} + + +// _GetString +const char* +Command::_GetString(uint32 key, const char* defaultString) const +{ +// if (LanguageManager* manager = LanguageManager::Default()) +// return manager->GetString(key, defaultString); +// else + return defaultString; +} diff --git a/src/apps/mediaplayer/support/Command.h b/src/apps/mediaplayer/support/Command.h new file mode 100644 index 0000000000..d43c19dc8a --- /dev/null +++ b/src/apps/mediaplayer/support/Command.h @@ -0,0 +1,41 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#ifndef COMMAND_H +#define COMMAND_H + +#include +#include + +class BString; + +class Command { + public: + Command(); + virtual ~Command(); + + virtual status_t InitCheck(); + + virtual status_t Perform(); + virtual status_t Undo(); + virtual status_t Redo(); + + virtual void GetName(BString& name); + + virtual bool UndoesPrevious(const Command* previous); + virtual bool CombineWithNext(const Command* next); + virtual bool CombineWithPrevious(const Command* previous); + + protected: + const char* _GetString(uint32 key, + const char* defaultString) const; + + bigtime_t fTimeStamp; +}; + +#endif // COMMAND_H diff --git a/src/apps/mediaplayer/support/CommandStack.cpp b/src/apps/mediaplayer/support/CommandStack.cpp new file mode 100644 index 0000000000..4a57fa277e --- /dev/null +++ b/src/apps/mediaplayer/support/CommandStack.cpp @@ -0,0 +1,252 @@ +/* + * Copyright 2006-2007, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "CommandStack.h" + +#include +#include + +#include +#include + +#include "Command.h" +#include "RWLocker.h" + +// constructor +CommandStack::CommandStack(RWLocker* locker) + : Notifier() + , fLocker(locker) + , fSavedCommand(NULL) +{ +} + +// destructor +CommandStack::~CommandStack() +{ + Clear(); +} + +// Perform +status_t +CommandStack::Perform(Command* command) +{ + if (!fLocker->WriteLock()) + return B_ERROR; + + status_t ret = command ? B_OK : B_BAD_VALUE; + if (ret == B_OK) + ret = command->InitCheck(); + + if (ret == B_OK) + ret = command->Perform(); + + if (ret == B_OK) + ret = _AddCommand(command); + + if (ret != B_OK) { + // no one else feels responsible... + delete command; + } + fLocker->WriteUnlock(); + + Notify(); + + return ret; +} + +// Undo +status_t +CommandStack::Undo() +{ + if (!fLocker->WriteLock()) + return B_ERROR; + + status_t status = B_ERROR; + if (!fUndoHistory.empty()) { + Command* command = fUndoHistory.top(); + fUndoHistory.pop(); + status = command->Undo(); + if (status == B_OK) + fRedoHistory.push(command); + else + fUndoHistory.push(command); + } + fLocker->WriteUnlock(); + + Notify(); + + return status; +} + +// Redo +status_t +CommandStack::Redo() +{ + if (!fLocker->WriteLock()) + return B_ERROR; + + status_t status = B_ERROR; + if (!fRedoHistory.empty()) { + Command* command = fRedoHistory.top(); + fRedoHistory.pop(); + status = command->Redo(); + if (status == B_OK) + fUndoHistory.push(command); + else + fRedoHistory.push(command); + } + fLocker->WriteUnlock(); + + Notify(); + + return status; +} + +// UndoName +bool +CommandStack::GetUndoName(BString& name) +{ + bool success = false; + if (fLocker->ReadLock()) { + if (!fUndoHistory.empty()) { + name << " "; + fUndoHistory.top()->GetName(name); + success = true; + } + fLocker->ReadUnlock(); + } + return success; +} + +// RedoName +bool +CommandStack::GetRedoName(BString& name) +{ + bool success = false; + if (fLocker->ReadLock()) { + if (!fRedoHistory.empty()) { + name << " "; + fRedoHistory.top()->GetName(name); + success = true; + } + fLocker->ReadUnlock(); + } + return success; +} + +// Clear +void +CommandStack::Clear() +{ + if (fLocker->WriteLock()) { + while (!fUndoHistory.empty()) { + delete fUndoHistory.top(); + fUndoHistory.pop(); + } + while (!fRedoHistory.empty()) { + delete fRedoHistory.top(); + fRedoHistory.pop(); + } + fLocker->WriteUnlock(); + } + + Notify(); +} + +// Save +void +CommandStack::Save() +{ + if (fLocker->WriteLock()) { + if (!fUndoHistory.empty()) + fSavedCommand = fUndoHistory.top(); + fLocker->WriteUnlock(); + } + + Notify(); +} + +// IsSaved +bool +CommandStack::IsSaved() +{ + bool saved = false; + if (fLocker->ReadLock()) { + saved = fUndoHistory.empty(); + if (fSavedCommand && !saved) { + if (fSavedCommand == fUndoHistory.top()) + saved = true; + } + fLocker->ReadUnlock(); + } + return saved; +} + +// #pragma mark - + +// _AddCommand +status_t +CommandStack::_AddCommand(Command* command) +{ + status_t status = B_OK; + + bool add = true; + if (!fUndoHistory.empty()) { + // try to collapse commands to a single command + // or remove this and the previous command if + // they reverse each other + if (Command* top = fUndoHistory.top()) { + if (command->UndoesPrevious(top)) { + add = false; + fUndoHistory.pop(); + delete top; + delete command; + } else if (top->CombineWithNext(command)) { + add = false; + delete command; + // after collapsing, the command might + // have changed it's mind about InitCheck() + // (the commands reversed each other) + if (top->InitCheck() < B_OK) { + fUndoHistory.pop(); + delete top; + } + } else if (command->CombineWithPrevious(top)) { + fUndoHistory.pop(); + delete top; + // after collapsing, the command might + // have changed it's mind about InitCheck() + // (the commands reversed each other) + if (command->InitCheck() < B_OK) { + delete command; + add = false; + } + } + } + } + if (add) { + try { + fUndoHistory.push(command); + } catch (...) { + status = B_ERROR; + } + } + + if (status == B_OK) { + // the redo stack needs to be empty + // as soon as a command was added (also in case of collapsing) + while (!fRedoHistory.empty()) { + delete fRedoHistory.top(); + fRedoHistory.pop(); + } + } + + return status; +} + + diff --git a/src/apps/mediaplayer/support/CommandStack.h b/src/apps/mediaplayer/support/CommandStack.h new file mode 100644 index 0000000000..0e479eebb0 --- /dev/null +++ b/src/apps/mediaplayer/support/CommandStack.h @@ -0,0 +1,48 @@ +/* + * Copyright 2006-2007, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef COMMAND_STACK_H +#define COMMAND_STACK_H + +#include + +#include "Notifier.h" + +class BString; +class RWLocker; +class Command; + +class CommandStack : public Notifier { + public: + CommandStack(RWLocker* locker); + virtual ~CommandStack(); + + status_t Perform(Command* command); + + status_t Undo(); + status_t Redo(); + + bool GetUndoName(BString& name); + bool GetRedoName(BString& name); + + void Clear(); + void Save(); + bool IsSaved(); + + private: + status_t _AddCommand(Command* command); + + RWLocker* fLocker; + + typedef stack command_stack; + + command_stack fUndoHistory; + command_stack fRedoHistory; + Command* fSavedCommand; +}; + +#endif // COMMAND_STACK_H diff --git a/src/apps/mediaplayer/support/Listener.cpp b/src/apps/mediaplayer/support/Listener.cpp new file mode 100644 index 0000000000..fa530974d6 --- /dev/null +++ b/src/apps/mediaplayer/support/Listener.cpp @@ -0,0 +1,18 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "Listener.h" + +Listener::Listener() +{ +} + +Listener::~Listener() +{ +} + diff --git a/src/apps/mediaplayer/support/Listener.h b/src/apps/mediaplayer/support/Listener.h new file mode 100644 index 0000000000..df3dbd25bd --- /dev/null +++ b/src/apps/mediaplayer/support/Listener.h @@ -0,0 +1,23 @@ +/* + * Copyright 2006-2007, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef LISTENER_H +#define LISTENER_H + +#include + +class Notifier; + +class Listener { + public: + Listener(); + virtual ~Listener(); + + virtual void ObjectChanged(const Notifier* object) = 0; +}; + +#endif // LISTENER_H diff --git a/src/apps/mediaplayer/support/ListenerAdapter.cpp b/src/apps/mediaplayer/support/ListenerAdapter.cpp new file mode 100644 index 0000000000..b20c29dd58 --- /dev/null +++ b/src/apps/mediaplayer/support/ListenerAdapter.cpp @@ -0,0 +1,32 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#include "ListenerAdapter.h" + +#include + +// constructor +ListenerAdapter::ListenerAdapter(BHandler* handler) + : Listener() + , AbstractLOAdapter(handler) +{ +} + +// destructor +ListenerAdapter::~ListenerAdapter() +{ +} + +// ObjectChanged +void +ListenerAdapter::ObjectChanged(const Notifier* object) +{ + BMessage message(MSG_OBJECT_CHANGED); + message.AddPointer("object", object); + + DeliverMessage(message); +} diff --git a/src/apps/mediaplayer/support/ListenerAdapter.h b/src/apps/mediaplayer/support/ListenerAdapter.h new file mode 100644 index 0000000000..1c4b4df53d --- /dev/null +++ b/src/apps/mediaplayer/support/ListenerAdapter.h @@ -0,0 +1,26 @@ +/* + * Copyright 2007, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef LISTENER_ADAPTER_H +#define LISTENER_ADAPTER_H + +#include "AbstractLOAdapter.h" +#include "Listener.h" + +enum { + MSG_OBJECT_CHANGED = 'obch' +}; + +class ListenerAdapter : public Listener, public AbstractLOAdapter { + public: + ListenerAdapter(BHandler* handler); + virtual ~ListenerAdapter(); + + virtual void ObjectChanged(const Notifier* object); +}; + +#endif // LISTENER_ADAPTER_H diff --git a/src/apps/mediaplayer/support/NOTE b/src/apps/mediaplayer/support/NOTE new file mode 100644 index 0000000000..7b6ff0ea17 --- /dev/null +++ b/src/apps/mediaplayer/support/NOTE @@ -0,0 +1,3 @@ +This folder contains some generic classes. The same code is used in Icon-O-Matic btw. I should move my lazy butt and place this code somewhere common. Ideas? + +-Stephan diff --git a/src/apps/mediaplayer/support/Notifier.cpp b/src/apps/mediaplayer/support/Notifier.cpp new file mode 100644 index 0000000000..40d0e95391 --- /dev/null +++ b/src/apps/mediaplayer/support/Notifier.cpp @@ -0,0 +1,105 @@ +/* + * Copyright 2006-2007, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "Notifier.h" + +#include +#include + +#include + +#include "Listener.h" + +// constructor +Notifier::Notifier() + : fListeners(2), + fSuspended(0), + fPendingNotifications(false) +{ +} + +// destructor +Notifier::~Notifier() +{ + if (fListeners.CountItems() > 0) { + char message[256]; + Listener* o = (Listener*)fListeners.ItemAt(0); + sprintf(message, "Notifier::~Notifier() - %ld " + "listeners still watching, first: %s\n", + fListeners.CountItems(), typeid(*o).name()); + debugger(message); + } +} + +// AddListener +bool +Notifier::AddListener(Listener* listener) +{ + if (listener && !fListeners.HasItem((void*)listener)) { + return fListeners.AddItem((void*)listener); + } + return false; +} + +// RemoveListener +bool +Notifier::RemoveListener(Listener* listener) +{ + return fListeners.RemoveItem((void*)listener); +} + +// CountListeners +int32 +Notifier::CountListeners() const +{ + return fListeners.CountItems(); +} + +// ListenerAtFast +Listener* +Notifier::ListenerAtFast(int32 index) const +{ + return (Listener*)fListeners.ItemAtFast(index); +} + +// #pragma mark - + +// Notify +void +Notifier::Notify() const +{ + if (!fSuspended) { + BList observers(fListeners); + int32 count = observers.CountItems(); + for (int32 i = 0; i < count; i++) + ((Listener*)observers.ItemAtFast(i))->ObjectChanged(this); + fPendingNotifications = false; + } else { + fPendingNotifications = true; + } +} + +// SuspendNotifications +void +Notifier::SuspendNotifications(bool suspend) +{ + if (suspend) + fSuspended++; + else + fSuspended--; + + if (fSuspended < 0) { + fprintf(stderr, "Notifier::SuspendNotifications(false) - " + "error: suspend level below zero!\n"); + fSuspended = 0; + } + + if (!fSuspended && fPendingNotifications) + Notify(); +} + diff --git a/src/apps/mediaplayer/support/Notifier.h b/src/apps/mediaplayer/support/Notifier.h new file mode 100644 index 0000000000..df4fa7553c --- /dev/null +++ b/src/apps/mediaplayer/support/Notifier.h @@ -0,0 +1,56 @@ +/* + * Copyright 2006-2007, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef NOTIFIER_H +#define NOTIFIER_H + +#include + +class Listener; + +class Notifier { + public: + Notifier(); + virtual ~Notifier(); + + bool AddListener(Listener* listener); + bool RemoveListener(Listener* listener); + + int32 CountListeners() const; + Listener* ListenerAtFast(int32 index) const; + + void Notify() const; + + void SuspendNotifications(bool suspend); + + bool HasPendingNotifications() const + { return fPendingNotifications; } + + private: + BList fListeners; + + int32 fSuspended; + mutable bool fPendingNotifications; +}; + +class AutoNotificationSuspender { + public: + AutoNotificationSuspender(Notifier* object) + : fObject(object) + { + fObject->SuspendNotifications(true); + } + + virtual ~AutoNotificationSuspender() + { + fObject->SuspendNotifications(false); + } + private: + Notifier* fObject; +}; + +#endif // NOTIFIER_H