* implemented infrastructure for undo/redo of playlist operations,

everything regarding playlist modification is now tunneled through
  the playlist window


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@21312 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Stephan Aßmus
2007-06-03 21:24:47 +00:00
parent 1e651d5b4f
commit cc2fbed22d
19 changed files with 873 additions and 31 deletions
+10 -1
View File
@@ -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
+14 -9
View File
@@ -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 <shift> 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);
}
+14 -13
View File
@@ -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();
}
+4 -3
View File
@@ -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;
+116 -5
View File
@@ -7,25 +7,50 @@
*/
#include "PlaylistWindow.h"
#include <Menu.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <ScrollBar.h>
#include <ScrollView.h>
#include <String.h>
#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**)&notifier) == 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("<nothing to undo>");
// 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("<nothing to redo>");
}
}
+20
View File
@@ -11,8 +11,16 @@
#include <Window.h>
#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
+91
View File
@@ -0,0 +1,91 @@
/*
* Copyright 2006, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#include "Command.h"
#include <stdio.h>
#include <OS.h>
// 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;
}
+41
View File
@@ -0,0 +1,41 @@
/*
* Copyright 2006, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#ifndef COMMAND_H
#define COMMAND_H
#include <SupportDefs.h>
#include <String.h>
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
@@ -0,0 +1,252 @@
/*
* Copyright 2006-2007, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#include "CommandStack.h"
#include <stdio.h>
#include <string.h>
#include <Locker.h>
#include <String.h>
#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;
}
@@ -0,0 +1,48 @@
/*
* Copyright 2006-2007, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#ifndef COMMAND_STACK_H
#define COMMAND_STACK_H
#include <stack.h>
#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*> command_stack;
command_stack fUndoHistory;
command_stack fRedoHistory;
Command* fSavedCommand;
};
#endif // COMMAND_STACK_H
+18
View File
@@ -0,0 +1,18 @@
/*
* Copyright 2006, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#include "Listener.h"
Listener::Listener()
{
}
Listener::~Listener()
{
}
+23
View File
@@ -0,0 +1,23 @@
/*
* Copyright 2006-2007, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#ifndef LISTENER_H
#define LISTENER_H
#include <SupportDefs.h>
class Notifier;
class Listener {
public:
Listener();
virtual ~Listener();
virtual void ObjectChanged(const Notifier* object) = 0;
};
#endif // LISTENER_H
@@ -0,0 +1,32 @@
/*
* Copyright 2006, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#include "ListenerAdapter.h"
#include <Message.h>
// 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);
}
@@ -0,0 +1,26 @@
/*
* Copyright 2007, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#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
+3
View File
@@ -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
+105
View File
@@ -0,0 +1,105 @@
/*
* Copyright 2006-2007, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#include "Notifier.h"
#include <stdio.h>
#include <typeinfo>
#include <OS.h>
#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();
}
+56
View File
@@ -0,0 +1,56 @@
/*
* Copyright 2006-2007, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#ifndef NOTIFIER_H
#define NOTIFIER_H
#include <List.h>
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