Commit a9beb407 authored by Erwan Tulou's avatar Erwan Tulou

skins2: fix some important playlist limitations or bugs

This patch includes the following :
  - fix item misplacement (items were only appended instead of being inserted)
  - fix slider scrolling that could not adapt to the real size of the playlist
  - enhance drag&drop by allowing users to finely insert item being dropped
    into either the playlist or the media library.
  - optimise refresh (only rebuild playtree in case of visible item)
  - remove keeping a reference to a playlist_item_t* (since it is not
    refcounted, a lookup from the playlist with proper lock mechanism
    is needed)
  - remove the m_deleted flag (corner cases were never dealt with) and
    replace it with a notification prior to deletion
  - implement operator++ to simplify iterating visible items (cosmetics)

A deeper redesign/simplification and support for the new sql playlist would be a good thing though.
parent 1653a66d
......@@ -6,6 +6,7 @@
*
* Authors: Antoine Cellerier <dionoea@videolan.org>
* Clément Stenac <zorglub@videolan.org>
* Erwan Tulou <erwan10 At videolan DoT org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -35,9 +36,10 @@
#include "../events/evt_key.hpp"
#include "../events/evt_mouse.hpp"
#include "../events/evt_scroll.hpp"
#include "../events/evt_dragndrop.hpp"
#include "../vars/playtree.hpp"
#include <vlc_keys.h>
#define SCROLL_STEP 0.05
#define LINE_INTERVAL 1 // Number of pixels inserted between 2 lines
......@@ -59,28 +61,23 @@ CtrlTree::CtrlTree( intf_thread_t *pIntf,
CtrlGeneric( pIntf,rHelp, pVisible), m_rTree( rTree), m_rFont( rFont ),
m_pBgBitmap( pBgBitmap ), m_pItemBitmap( pItemBitmap ),
m_pOpenBitmap( pOpenBitmap ), m_pClosedBitmap( pClosedBitmap ),
m_pScaledBitmap( NULL ),
m_fgColor( fgColor ), m_playColor( playColor ), m_bgColor1( bgColor1 ),
m_bgColor2( bgColor2 ), m_selColor( selColor ),
m_pLastSelected( NULL ), m_pImage( NULL ), m_dontMove( false )
m_pScaledBitmap( NULL ), m_pImage( NULL ),
m_fgColor( fgColor ), m_playColor( playColor ),
m_bgColor1( bgColor1 ), m_bgColor2( bgColor2 ), m_selColor( selColor ),
m_firstPos( m_rTree.end() ), m_lastClicked( m_rTree.end() ),
m_itOver( m_rTree.end() ), m_flat( pFlat->get() ), m_capacity( -1.0 ),
m_bRefreshOnDelete( false )
{
// Observe the tree and position variables
// Observe the tree
m_rTree.addObserver( this );
m_rTree.getPositionVar().addObserver( this );
m_flat = pFlat->get();
m_firstPos = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
makeImage();
m_rTree.setFlat( m_flat );
}
CtrlTree::~CtrlTree()
{
m_rTree.getPositionVar().delObserver( this );
m_rTree.delObserver( this );
delete m_pScaledBitmap;
delete m_pImage;
delete m_pScaledBitmap;
}
int CtrlTree::itemHeight()
......@@ -126,356 +123,260 @@ int CtrlTree::itemImageWidth()
return bitmapWidth + 2;
}
int CtrlTree::maxItems()
float CtrlTree::maxItems()
{
const Position *pPos = getPosition();
if( !pPos )
{
return -1;
}
return pPos->getHeight() / itemHeight();
return (float)pPos->getHeight() / itemHeight();
}
void CtrlTree::onUpdate( Subject<VarTree, tree_update> &rTree,
tree_update *arg )
{
(void)rTree;
if( arg->type == arg->UpdateItem ) // Item update
if( arg->type == arg->ItemInserted )
{
if( arg->b_active_item )
autoScroll();
if( isItemVisible( arg->i_id ) )
if( isItemVisible( arg->it ) )
{
makeImage();
notifyLayout();
}
setSliderFromFirst();
}
else if ( arg->type == arg->ResetAll ) // Global change or deletion
{
m_firstPos = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
makeImage();
notifyLayout();
}
else if ( arg->type == arg->AppendItem ) // Item-append
else if( arg->type == arg->ItemUpdated )
{
if( m_flat && m_firstPos->size() )
if( arg->it->isPlaying() )
{
m_firstPos = m_rTree.getNextLeaf( m_firstPos );
m_rTree.ensureExpanded( arg->it );
ensureVisible( arg->it );
makeImage();
notifyLayout();
setSliderFromFirst();
}
else if( isItemVisible( arg->i_id ) )
else if( isItemVisible( arg->it ) )
{
makeImage();
notifyLayout();
}
}
else if( arg->type == arg->DeleteItem ) // item-del
else if( arg->type == arg->DeletingItem )
{
/* Make sure firstPos is valid */
VarTree::Iterator it_old = m_firstPos;
while( m_firstPos->isDeleted() &&
m_firstPos != (m_flat ? m_rTree.firstLeaf()
: m_rTree.begin()) )
if( isItemVisible( arg->it ) )
m_bRefreshOnDelete = true;
// remove all references to arg->it
// if it is the one about to be deleted
if( m_firstPos == arg->it )
{
m_firstPos = m_flat ? m_rTree.getPrevLeaf( m_firstPos )
: m_rTree.getPrevVisibleItem( m_firstPos );
m_firstPos = getNearestItem( arg->it );
}
if( m_firstPos->isDeleted() )
m_firstPos = m_rTree.begin();
if( m_firstPos != it_old || isItemVisible( arg->i_id ) )
if( m_lastClicked == arg->it )
{
makeImage();
notifyLayout();
m_lastClicked = getNearestItem( arg->it );
m_lastClicked->setSelected( arg->it->isSelected() );
}
}
}
void CtrlTree::onUpdate( Subject<VarPercent> &rPercent, void* arg)
{
(void)rPercent; (void)arg;
// Determine what is the first item to display
VarTree::Iterator it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
if( m_dontMove ) return;
int excessItems;
if( m_flat )
excessItems = m_rTree.countLeafs() - maxItems();
else
excessItems = m_rTree.visibleItems() - maxItems();
if( excessItems > 0)
else if( arg->type == arg->ItemDeleted )
{
VarPercent &rVarPos = m_rTree.getPositionVar();
// a simple (int)(...) causes rounding errors !
#ifdef _MSC_VER
# define lrint (int)
#endif
if( m_flat )
it = m_rTree.getLeaf(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1 );
else
it = m_rTree.getVisibleItem(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1 );
if( m_bRefreshOnDelete )
{
m_bRefreshOnDelete = false;
makeImage();
notifyLayout();
}
setSliderFromFirst();
}
if( m_firstPos != it )
else if( arg->type == arg->ResetAll )
{
// Redraw the control if the position has changed
m_firstPos = it;
m_lastClicked = m_rTree.end();
m_firstPos = getFirstFromSlider();
makeImage();
notifyLayout();
setSliderFromFirst();
}
else if( arg->type == arg->SliderChanged )
{
Iterator it = getFirstFromSlider();
if( m_firstPos != it )
{
m_firstPos = it;
makeImage();
notifyLayout();
}
}
}
void CtrlTree::onResize()
{
// Determine what is the first item to display
VarTree::Iterator it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
int excessItems;
if( m_flat )
excessItems = m_rTree.countLeafs() - maxItems();
else
excessItems = m_rTree.visibleItems() - maxItems();
if( excessItems > 0)
{
VarPercent &rVarPos = m_rTree.getPositionVar();
// a simple (int)(...) causes rounding errors !
#ifdef _MSC_VER
# define lrint (int)
#endif
if( m_flat )
it = m_rTree.getLeaf(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1 );
else
it = m_rTree.getVisibleItem(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1 );
}
// Redraw the control if the position has changed
m_firstPos = it;
makeImage();
onPositionChange();
}
void CtrlTree::onPositionChange()
{
m_capacity = maxItems();
setScrollStep();
m_firstPos = getFirstFromSlider();
makeImage();
}
void CtrlTree::handleEvent( EvtGeneric &rEvent )
{
bool bChangedPosition = false;
VarTree::Iterator toShow; bool needShow = false;
bool needShow = false;
bool needRefresh = false;
Iterator toShow = m_firstPos;
if( rEvent.getAsString().find( "key:down" ) != string::npos )
{
int key = ((EvtKey&)rEvent).getKey();
VarTree::Iterator it;
bool previousWasSelected = false;
/* Delete the selection */
if( key == KEY_DELETE )
{
/* Find first non selected item before m_pLastSelected */
VarTree::Iterator it_sel = m_flat ? m_rTree.firstLeaf()
: m_rTree.begin();
for( it = (m_flat ? m_rTree.firstLeaf() : m_rTree.begin());
it != m_rTree.end();
it = (m_flat ? m_rTree.getNextLeaf( it )
: m_rTree.getNextVisibleItem( it )) )
{
if( &*it == m_pLastSelected ) break;
if( !it->isSelected() ) it_sel = it;
}
/* Delete selected stuff */
m_rTree.delSelected();
/* Verify if there is still sthg selected (e.g read-only items) */
m_pLastSelected = NULL;
for( it = (m_flat ? m_rTree.firstLeaf() : m_rTree.begin());
it != m_rTree.end();
it = (m_flat ? m_rTree.getNextLeaf( it )
: m_rTree.getNextVisibleItem( it )) )
{
if( it->isSelected() )
m_pLastSelected = &*it;
}
/* if everything was deleted, use it_sel as last selection */
if( !m_pLastSelected )
{
it_sel->setSelected( true );
m_pLastSelected = &*it_sel;
}
// Redraw the control
makeImage();
notifyLayout();
}
else if( key == KEY_PAGEDOWN )
{
it = m_firstPos;
int i = (int)(maxItems()*1.5);
while( i >= 0 )
int numSteps = (int)m_capacity / 2;
VarPercent &rVarPos = m_rTree.getPositionVar();
rVarPos.increment( -numSteps );
}
else if( key == KEY_PAGEUP )
{
int numSteps = (int)m_capacity / 2;
VarPercent &rVarPos = m_rTree.getPositionVar();
rVarPos.increment( numSteps );
}
else if( key == KEY_UP )
{
// Scroll up one item
m_rTree.unselectTree();
if( m_lastClicked != m_rTree.end() )
{
VarTree::Iterator it_old = it;
it = m_flat ? m_rTree.getNextLeaf( it )
: m_rTree.getNextVisibleItem( it );
/* End is already visible, dont' scroll */
if( it == m_rTree.end() )
if( --m_lastClicked != m_rTree.end() )
{
it = it_old;
break;
m_lastClicked->setSelected( true );
}
needShow = true;
i--;
}
if( needShow )
if( m_lastClicked == m_rTree.end() )
{
ensureVisible( it );
makeImage();
notifyLayout();
m_lastClicked = m_firstPos;
if( m_lastClicked != m_rTree.end() )
m_lastClicked->setSelected( true );
}
needRefresh = true;
needShow = true; toShow = m_lastClicked;
}
else if (key == KEY_PAGEUP )
else if( key == KEY_DOWN )
{
it = m_firstPos;
int i = maxItems();
while( i >= maxItems()/2 )
// Scroll down one item
m_rTree.unselectTree();
if( m_lastClicked != m_rTree.end() )
{
it = m_flat ? m_rTree.getPrevLeaf( it )
: m_rTree.getPrevVisibleItem( it );
/* End is already visible, dont' scroll */
if( it == ( m_flat ? m_rTree.firstLeaf() : m_rTree.begin() ) )
Iterator it_old = m_lastClicked;
if( ++m_lastClicked != m_rTree.end() )
{
break;
m_lastClicked->setSelected( true );
}
else
{
it_old->setSelected( true );
m_lastClicked = it_old;
}
i--;
}
ensureVisible( it );
makeImage();
notifyLayout();
else
{
m_lastClicked = m_firstPos;
if( m_lastClicked != m_rTree.end() )
m_lastClicked->setSelected( true );
}
needRefresh = true;
needShow = true; toShow = m_lastClicked;
}
else if ( key == KEY_UP ||
key == KEY_DOWN ||
key == KEY_LEFT ||
key == KEY_RIGHT ||
key == KEY_ENTER ||
key == ' ' )
else if( key == KEY_RIGHT )
{
for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
it != m_rTree.end();
it = m_flat ? m_rTree.getNextLeaf( it )
: m_rTree.getNextVisibleItem( it ) )
// Go down one level (and expand node)
Iterator& it = m_lastClicked;
if( it != m_rTree.end() )
{
VarTree::Iterator next = m_flat ?
m_rTree.getNextLeaf( it ) :
m_rTree.getNextVisibleItem( it );
if( key == KEY_UP )
if( !m_flat && !it->isExpanded() && it->size() )
{
// Scroll up one item
if( ( it->parent()
&& it != it->parent()->begin() )
|| &*it != m_pLastSelected )
{
bool nextWasSelected = ( &*next == m_pLastSelected );
it->setSelected( nextWasSelected );
if( nextWasSelected )
{
m_pLastSelected = &*it;
needShow = true; toShow = it;
}
}
it->setExpanded( true );
needRefresh = true;
}
else if( key == KEY_DOWN )
else
{
// Scroll down one item
if( ( it->parent()
&& next != it->parent()->end() )
|| &*it != m_pLastSelected )
m_rTree.unselectTree();
Iterator it_old = m_lastClicked;
if( ++m_lastClicked != m_rTree.end() )
{
it->setSelected( previousWasSelected );
m_lastClicked->setSelected( true );
}
if( previousWasSelected )
else
{
m_pLastSelected = &*it;
needShow = true; toShow = it;
previousWasSelected = false;
it_old->setSelected( true );
m_lastClicked = it_old;
}
else
needRefresh = true;
needShow = true; toShow = m_lastClicked;
}
}
}
else if( key == KEY_LEFT )
{
// Go up one level (and close node)
Iterator& it = m_lastClicked;
if( it != m_rTree.end() )
{
if( m_flat )
{
m_rTree.unselectTree();
if( --m_lastClicked != m_rTree.end() )
{
previousWasSelected = ( &*it == m_pLastSelected );
m_lastClicked->setSelected( true );
}
// Fix last tree item selection
if( ( m_flat ? m_rTree.getNextLeaf( it )
: m_rTree.getNextVisibleItem( it ) ) == m_rTree.end()
&& &*it == m_pLastSelected )
else
{
it->setSelected( true );
m_lastClicked = m_firstPos;
if( m_lastClicked != m_rTree.end() )
m_lastClicked->setSelected( true );
}
needRefresh = true;
needShow = true; toShow = m_lastClicked;
}
else if( key == KEY_RIGHT )
else
{
// Go down one level (and expand node)
if( &*it == m_pLastSelected )
if( it->isExpanded() )
{
if( it->isExpanded() )
{
if( it->size() )
{
it->setSelected( false );
it->begin()->setSelected( true );
m_pLastSelected = &*(it->begin());
}
else
{
m_rTree.action( &*it );
}
}
else
{
it->setExpanded( true );
bChangedPosition = true;
}
it->setExpanded( false );
needRefresh = true;
}
}
else if( key == KEY_LEFT )
{
// Go up one level (and close node)
if( &*it == m_pLastSelected )
else
{
if( it->isExpanded() && it->size() )
{
it->setExpanded( false );
bChangedPosition = true;
}
else
Iterator it_parent = it.getParent();
if( it_parent != m_rTree.end() )
{
if( it->parent() && it->parent() != &m_rTree)
{
it->setSelected( false );
m_pLastSelected = it->parent();
m_pLastSelected->setSelected( true );
}
it->setSelected( false );
m_lastClicked = it_parent;
m_lastClicked->setSelected( true );
needRefresh = true;
needShow = true; toShow = m_lastClicked;
}
}
}
else if( key == KEY_ENTER || key == ' ' )
{
// Go up one level (and close node)
if( &*it == m_pLastSelected )
{
m_rTree.action( &*it );
}
}
}
if( needShow )
ensureVisible( toShow );
// Redraw the control
makeImage();
notifyLayout();
}
else if( key == KEY_ENTER || key == ' ' )
{
// Go up one level (and close node)
if( m_lastClicked != m_rTree.end() )
{
m_rTree.action( &*m_lastClicked );
}
}
else
{
......@@ -484,188 +385,159 @@ void CtrlTree::handleEvent( EvtGeneric &rEvent )
var_SetInteger( getIntf()->p_libvlc, "key-pressed",
rEvtKey.getModKey() );
}
}
else if( rEvent.getAsString().find( "mouse:left" ) != string::npos )
{
EvtMouse &rEvtMouse = (EvtMouse&)rEvent;
const Position *pos = getPosition();
int yPos = ( rEvtMouse.getYPos() - pos->getTop() ) / itemHeight();
int xPos = rEvtMouse.getXPos() - pos->getLeft();
VarTree::Iterator it;
int yPos = ( rEvtMouse.getYPos() - pos->getTop() ) / itemHeight();
if( rEvent.getAsString().find( "mouse:left:down:ctrl,shift" ) !=
string::npos )
Iterator itClicked = findItemAtPos( yPos );
if( itClicked != m_rTree.end() )
{
VarTree::Iterator itClicked = findItemAtPos( yPos );
// Flag to know if the current item must be selected
bool select = false;
for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
it != m_rTree.end();
it = m_flat ? m_rTree.getNextLeaf( it )
: m_rTree.getNextVisibleItem( it ) )
if( rEvent.getAsString().find( "mouse:left:down:ctrl,shift" ) !=
string::npos )
{
bool nextSelect = select;
if( it == itClicked || &*it == m_pLastSelected )
// Flag to know if the current item must be selected
bool select = false;
for( Iterator it = m_firstPos; it != m_rTree.end(); ++it )
{
if( select )
{
nextSelect = false;
}
else
bool nextSelect = select;
if( it == itClicked || it == m_lastClicked )
{
select = true;
nextSelect = true;
if( select )
{
nextSelect = false;
}
else
{
select = true;
if( itClicked != m_lastClicked )
nextSelect = true;
}
}
it->setSelected( it->isSelected() || select );
select = nextSelect;
needRefresh = true;
}
it->setSelected( it->isSelected() || select );
select = nextSelect;
}
// Redraw the control
makeImage();
notifyLayout();
}
else if( rEvent.getAsString().find( "mouse:left:down:ctrl" ) !=
string::npos )
{
// Invert the selection of the item
it = findItemAtPos( yPos );
if( it != m_rTree.end() )
else if( rEvent.getAsString().find( "mouse:left:down:ctrl" ) !=
string::npos )
{
it->toggleSelected();
m_pLastSelected = &*it;
// Invert the selection of the item
itClicked->toggleSelected();
m_lastClicked = itClicked;
needRefresh = true;
}
// Redraw the control
makeImage();
notifyLayout();
}
else if( rEvent.getAsString().find( "mouse:left:down:shift" ) !=
string::npos )
{
VarTree::Iterator itClicked = findItemAtPos( yPos );
// Flag to know if the current item must be selected
bool select = false;
for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
it != m_rTree.end();
it = m_flat ? m_rTree.getNextLeaf( it )
: m_rTree.getNextVisibleItem( it ) )
else if( rEvent.getAsString().find( "mouse:left:down:shift" ) !=
string::npos )
{
bool nextSelect = select;
if( it == itClicked || &*it == m_pLastSelected )
bool select = false;
for( Iterator it = m_firstPos; it != m_rTree.end(); ++it )
{
if( select )
{
nextSelect = false;
}
else
bool nextSelect = select;
if( it == itClicked || it == m_lastClicked )
{
select = true;
nextSelect = true;
if( select )
{
nextSelect = false;
}
else
{
select = true;
if( itClicked != m_lastClicked )
nextSelect = true;
}
}
it->setSelected( select );
select = nextSelect;
}
it->setSelected( select );
select = nextSelect;
needRefresh = true;
}
// Redraw the control
makeImage();
notifyLayout();
}
else if( rEvent.getAsString().find( "mouse:left:down" ) !=
string::npos )
{
it = findItemAtPos(yPos);
if( it != m_rTree.end() )
else if( rEvent.getAsString().find( "mouse:left:down" ) !=
string::npos )
{
if( ( it->size() && xPos > (it->depth() - 1) * itemImageWidth()
&& xPos < it->depth() * itemImageWidth() )
&& !m_flat )
if( !m_flat &&
itClicked->size() &&
xPos > (itClicked->depth() - 1) * itemImageWidth() &&
xPos < itClicked->depth() * itemImageWidth() )
{
// Fold/unfold the item
it->toggleExpanded();
bChangedPosition = true;
itClicked->toggleExpanded();
}
else
{
// Unselect any previously selected item
VarTree::Iterator it2;
for( it2 = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
it2 != m_rTree.end();
it2 = m_flat ? m_rTree.getNextLeaf( it2 )
: m_rTree.getNextVisibleItem( it2 ) )
{
it2->setSelected( false );
}
m_rTree.unselectTree();
// Select the new item
if( it != m_rTree.end() )
{
it->setSelected( true );
m_pLastSelected = &*it;
}
itClicked->setSelected( true );
m_lastClicked = itClicked;
}
needRefresh = true;
}
// Redraw the control
makeImage();
notifyLayout();
}
else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
string::npos )
{
it = findItemAtPos(yPos);
if( it != m_rTree.end() )
else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
string::npos )
{
// Execute the action associated to this item
m_rTree.action( &*it );
m_rTree.action( &*itClicked );
}
// Redraw the control
makeImage();
notifyLayout();
}
}
else if( rEvent.getAsString().find( "scroll" ) != string::npos )
{
// XXX ctrl_slider.cpp has two more (but slightly different)
// XXX implementations of `scroll'. Figure out where it belongs.
int direction = static_cast<EvtScroll&>(rEvent).getDirection();
double percentage = m_rTree.getPositionVar().get();
double step = 2.0 / (double)( m_flat ? m_rTree.countLeafs()
: m_rTree.visibleItems() );
if( direction == EvtScroll::kUp )
{
percentage += step;
}
m_rTree.getPositionVar().increment( +1 );
else
{
percentage -= step;
}
m_rTree.getPositionVar().set( percentage );
m_rTree.getPositionVar().increment( -1 );
}
/* We changed the nodes, let's fix the position var */
if( bChangedPosition )
else if( rEvent.getAsString().find( "drag:over" ) != string::npos )
{
VarTree::Iterator it;
int iFirst = 0;
for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
it != m_rTree.end();
it = m_flat ? m_rTree.getNextLeaf( it )
: m_rTree.getNextVisibleItem( it ) )
EvtDragOver& evt = static_cast<EvtDragOver&>(rEvent);
const Position *pos = getPosition();
int yPos = ( evt.getYPos() - pos->getTop() ) / itemHeight();
Iterator it = findItemAtPos( yPos );
if( it != m_itOver )
{
if( it == m_firstPos )
break;
iFirst++;
if( it != m_rTree.end() )
it->setExpanded( true );
m_itOver = it;
needRefresh = true;
}
}
else if( rEvent.getAsString().find( "drag:drop" ) != string::npos )
{
EvtDragDrop& evt = static_cast<EvtDragDrop&>(rEvent);
Playtree& rPlaytree = static_cast<Playtree&>(m_rTree);
rPlaytree.insertItems( *m_itOver, evt.getFiles(), false );
m_itOver = m_rTree.end();
needRefresh = true;
}
int indexMax = ( m_flat ? m_rTree.countLeafs()
: m_rTree.visibleItems() ) - 1;
float f_new = (float)iFirst / (float)indexMax;
else if( rEvent.getAsString().find( "drag:leave" ) != string::npos )
{
m_itOver = m_rTree.end();
needRefresh = true;
}
m_dontMove = true;
m_rTree.getPositionVar().set( 1.0 - f_new );
m_dontMove = false;
if( needShow )
{
if( toShow == m_rTree.end() ||
!ensureVisible( toShow ) )
needRefresh = true;
}
if( needRefresh )
{
setSliderFromFirst();
makeImage();
notifyLayout();
}
}
......@@ -691,45 +563,6 @@ void CtrlTree::draw( OSGraphics &rImage, int xDest, int yDest, int w, int h)
inter.x, inter.y, inter.width, inter.height );
}
bool CtrlTree::ensureVisible( VarTree::Iterator item )
{
m_rTree.ensureExpanded( item );
int firstPosIndex = m_rTree.getRank( m_firstPos, m_flat) - 1;
int focusItemIndex = m_rTree.getRank( item, m_flat) - 1;
if( focusItemIndex < firstPosIndex ||
focusItemIndex > firstPosIndex + maxItems() - 1 )
{
// Scroll to have the wanted stream visible
VarPercent &rVarPos = m_rTree.getPositionVar();
int indexMax = ( m_flat ? m_rTree.countLeafs()
: m_rTree.visibleItems() ) - 1;
rVarPos.set( 1.0 - (double)focusItemIndex / (double)indexMax );
return true;
}
return false;
}
void CtrlTree::autoScroll()
{
// Find the current playing stream
VarTree::Iterator it;
for( it = m_flat ? m_rTree.firstLeaf() : m_rTree.begin();
it != m_rTree.end();
it = m_flat ? m_rTree.getNextLeaf( it )
: m_rTree.getNextItem( it ) )
{
if( it->isPlaying() )
{
ensureVisible( it );
break;
}
}
}
void CtrlTree::makeImage()
{
stats_TimerStart( getIntf(), "[Skins] Playlist image",
......@@ -752,7 +585,7 @@ void CtrlTree::makeImage()
OSFactory *pOsFactory = OSFactory::instance( getIntf() );
m_pImage = pOsFactory->createOSGraphics( width, height );
VarTree::Iterator it = m_firstPos;
Iterator it = m_firstPos;
if( m_pBgBitmap )
{
......@@ -767,30 +600,23 @@ void CtrlTree::makeImage()
}
m_pImage->drawBitmap( *m_pScaledBitmap, 0, 0 );
for( int yPos = 0; yPos < height; yPos += i_itemHeight )
for( int yPos = 0;
yPos < height && it != m_rTree.end();
yPos += i_itemHeight, ++it )
{
if( it != m_rTree.end() )
if( it->isSelected() )
{
if( it->isSelected() )
{
int rectHeight = __MIN( i_itemHeight, height - yPos );
m_pImage->fillRect( 0, yPos, width, rectHeight,
m_selColor );
}
do
{
it = m_flat ? m_rTree.getNextLeaf( it )
: m_rTree.getNextVisibleItem( it );
} while( it != m_rTree.end() && it->isDeleted() );
int rectHeight = __MIN( i_itemHeight, height - yPos );
m_pImage->fillRect( 0, yPos, width, rectHeight, m_selColor );
}
}
}
else
{
// FIXME (TRYME)
// Fill background with background color
uint32_t bgColor = m_bgColor1;
m_pImage->fillRect( 0, 0, width, height, bgColor );
// Overwrite with alternate colors (bgColor1, bgColor2)
for( int yPos = 0; yPos < height; yPos += i_itemHeight )
{
int rectHeight = __MIN( i_itemHeight, height - yPos );
......@@ -800,11 +626,7 @@ void CtrlTree::makeImage()
{
uint32_t color = ( it->isSelected() ? m_selColor : bgColor );
m_pImage->fillRect( 0, yPos, width, rectHeight, color );
do
{
it = m_flat ? m_rTree.getNextLeaf( it )
: m_rTree.getNextVisibleItem( it );
} while( it != m_rTree.end() && it->isDeleted() );
++it;
}
bgColor = ( bgColor == m_bgColor1 ? m_bgColor2 : m_bgColor1 );
}
......@@ -812,26 +634,25 @@ void CtrlTree::makeImage()
int bitmapWidth = itemImageWidth();
int yPos = 0;
it = m_firstPos;
while( it != m_rTree.end() && yPos < height )
for( int yPos = 0; yPos < height && it != m_rTree.end(); ++it )
{
const GenericBitmap *m_pCurBitmap;
UString *pStr = it->getString();
uint32_t color = ( it->isPlaying() ? m_playColor : m_fgColor );
// Draw the text
if( pStr != NULL )
{
uint32_t color = it->isPlaying() ? m_playColor : m_fgColor;
int depth = m_flat ? 1 : it->depth();
GenericBitmap *pText = m_rFont.drawString( *pStr, color, width - bitmapWidth * depth );
GenericBitmap *pText =
m_rFont.drawString( *pStr, color, width-bitmapWidth*depth );
if( !pText )
{
stats_TimerStop( getIntf(), STATS_TIMER_SKINS_PLAYTREE_IMAGE );
return;
}
if( it->size() )
m_pCurBitmap = it->isExpanded() ? m_pOpenBitmap : m_pClosedBitmap;
m_pCurBitmap =
it->isExpanded() ? m_pOpenBitmap : m_pClosedBitmap;
else
m_pCurBitmap = m_pItemBitmap;
......@@ -844,6 +665,7 @@ void CtrlTree::makeImage()
delete pText;
break;
}
// Draw the icon in front of the text
m_pImage->drawBitmap( *m_pCurBitmap, 0, 0,
bitmapWidth * (depth - 1 ), yPos2,
m_pCurBitmap->getWidth(),
......@@ -858,41 +680,141 @@ void CtrlTree::makeImage()
yPos = 0;
}
int lineHeight = __MIN( pText->getHeight() - ySrc, height - yPos );
// Draw the text
m_pImage->drawBitmap( *pText, 0, ySrc, bitmapWidth * depth, yPos,
pText->getWidth(),
lineHeight, true );
yPos += (pText->getHeight() - ySrc );
if( it == m_itOver )
{
// Draw the underline bar below the text for drag&drop
m_pImage->fillRect(
bitmapWidth * (depth - 1 ), yPos - 2,
bitmapWidth + pText->getWidth(), __MAX( lineHeight/5, 3 ),
m_selColor );
}
delete pText;
}
do
{
it = m_flat ? m_rTree.getNextLeaf( it )
: m_rTree.getNextVisibleItem( it );
} while( it != m_rTree.end() && it->isDeleted() );
}
stats_TimerStop( getIntf(), STATS_TIMER_SKINS_PLAYTREE_IMAGE );
}
VarTree::Iterator CtrlTree::findItemAtPos( int pos )
CtrlTree::Iterator CtrlTree::findItemAtPos( int pos )
{
// The first item is m_firstPos.
// We decrement pos as we try the other items, until pos == 0.
VarTree::Iterator it;
for( it = m_firstPos; it != m_rTree.end() && pos != 0;
it = m_flat ? m_rTree.getNextLeaf( it )
: m_rTree.getNextVisibleItem( it ) )
Iterator it = m_firstPos;
for( ; it != m_rTree.end() && pos != 0; ++it, pos-- );
return it;
}
CtrlTree::Iterator CtrlTree::getFirstFromSlider()
{
// a simple (int)(...) causes rounding errors !
#ifdef _MSC_VER
# define lrint (int)
#endif
VarPercent &rVarPos = m_rTree.getPositionVar();
double percentage = rVarPos.get();
int excessItems = m_flat ? (m_rTree.countLeafs() - (int)m_capacity)
: (m_rTree.visibleItems() - (int)m_capacity);
int index = (excessItems > 0 ) ?
lrint( (1.0 - percentage)*(double)excessItems ) :
0;
Iterator it_first = m_rTree.getItem( index );
if( m_lastClicked == m_rTree.end() )
{
pos--;
m_lastClicked = it_first;
if( m_lastClicked != m_rTree.end() )
m_lastClicked->setSelected( true );
}
return it;
return it_first;
}
bool CtrlTree::isItemVisible( int id )
void CtrlTree::setScrollStep()
{
VarTree::Iterator it = m_rTree.findById( id );
VarPercent &rVarPos = m_rTree.getPositionVar();
int rank1 = m_rTree.getRank( m_firstPos, m_flat );
int rank2 = m_rTree.getRank( it, m_flat );
return ( rank2 >= rank1 && rank2 <= rank1 + maxItems() -1 );
int excessItems = m_flat ? (m_rTree.countLeafs() - (int)m_capacity)
: (m_rTree.visibleItems() - (int)m_capacity);
if( excessItems > 0 )
rVarPos.setStep( (float)1 / excessItems );
else
rVarPos.setStep( 1.0 );
}
void CtrlTree::setSliderFromFirst()
{
VarPercent &rVarPos = m_rTree.getPositionVar();
int excessItems = m_flat ? (m_rTree.countLeafs() - (int)m_capacity)
: (m_rTree.visibleItems() - (int)m_capacity);
int index = m_rTree.getIndex( m_firstPos );
if( excessItems > 0 )
{
rVarPos.set( 1.0 - (float)index/(float)excessItems );
rVarPos.setStep( 1.0 / excessItems );
}
else
{
rVarPos.set( 1.0 );
rVarPos.setStep( 1.0 );
}
}
bool CtrlTree::isItemVisible( const Iterator& it_ref )
{
if( it_ref == m_rTree.end() )
return false;
Iterator it = m_firstPos;
if( it == m_rTree.end() )
return true;
// Ensure a partially visible last item is taken into account
int max = (int)m_capacity;
if( (float)max < m_capacity )
max++;
for( int i = 0; i < max && it != m_rTree.end(); ++it, i++ )
{
if( it == it_ref )
return true;
}
return false;
}
bool CtrlTree::ensureVisible( const Iterator& item )
{
Iterator it = m_firstPos;
int max = (int)m_capacity;
for( int i = 0; i < max && it != m_rTree.end(); ++it, i++ )
{
if( it == item )
return false;
}
m_rTree.setSliderFromItem( item );
return true;
}
CtrlTree::Iterator CtrlTree::getNearestItem( const Iterator& item )
{
// return the previous item if it exists
Iterator newItem = item;
if( --newItem != m_rTree.end() && newItem != item )
return newItem;
// return the next item if no previous item found
newItem = item;
return ++newItem;
}
......@@ -34,10 +34,11 @@ class GenericFont;
class GenericBitmap;
/// Class for control tree
class CtrlTree: public CtrlGeneric, public Observer<VarTree, tree_update>,
public Observer<VarPercent>
class CtrlTree: public CtrlGeneric, public Observer<VarTree, tree_update>
{
public:
typedef VarTree::IteratorVisible Iterator;
CtrlTree( intf_thread_t *pIntf,
VarTree &rTree,
const GenericFont &rFont,
......@@ -76,7 +77,7 @@ public:
/// Make sure an item is visible
/// \param item an iterator to a tree item
/// \return true if it changed the position
bool ensureVisible( VarTree::Iterator item );
bool ensureVisible( const Iterator& it );
private:
/// Tree associated to the control
......@@ -95,6 +96,9 @@ private:
const GenericBitmap *m_pClosedBitmap;
/// scaled bitmap
GenericBitmap *m_pScaledBitmap;
/// Image of the control
OSGraphics *m_pImage;
/// Color of normal test
uint32_t m_fgColor;
/// Color of the playing item
......@@ -103,31 +107,30 @@ private:
uint32_t m_bgColor1, m_bgColor2;
/// Background of selected items
uint32_t m_selColor;
/// Pointer on the last selected item in the tree
VarTree *m_pLastSelected;
/// Image of the control
OSGraphics *m_pImage;
/// First item in the visible area
VarTree::Iterator m_firstPos;
/// Don't move if the position variable is updated
bool m_dontMove;
/// First item in the visible area
Iterator m_firstPos;
/// Pointer on the last clicked item in the tree
Iterator m_lastClicked;
///
Iterator m_itOver;
/// Do we want to "flaten" the tree ?
bool m_flat;
/// Number of visible lines
float m_capacity;
/// flag for item deletion
bool m_bRefreshOnDelete;
/// Method called when the tree variable is modified
virtual void onUpdate( Subject<VarTree, tree_update> &rTree ,
virtual void onUpdate( Subject<VarTree, tree_update> &rTree,
tree_update *);
// Method called when the position variable of the tree is modified
virtual void onUpdate( Subject<VarPercent> &rPercent , void *);
/// Called when the position is set
virtual void onPositionChange();
/// Compute the number of lines that can be displayed
int maxItems();
float maxItems();
/// Compute the item's height (depends on fonts and images used)
int itemHeight();
......@@ -135,21 +138,21 @@ private:
/// Compute the width of an item's bitmap
int itemImageWidth();
/// Check if the tree must be scrolled
void autoScroll();
/// Draw the image of the control
void makeImage();
/// Return the n'th displayed item (starting at position 0)
/**
* Return m_rTree.end() if such an item cannot be found (n < 0, or
* n too big)
*/
VarTree::Iterator findItemAtPos( int n );
/// check if id is within the visible control
bool isItemVisible( int id );
Iterator findItemAtPos( int n );
/// return the nearest item
Iterator getNearestItem( const Iterator& it );
/// return whether the item is visible or not
bool isItemVisible( const Iterator& it );
void setSliderFromFirst();
Iterator getFirstFromSlider();
void setScrollStep();
};
#endif
......@@ -35,7 +35,8 @@ class VarPercent;
class VarPercent: public Variable, public Subject<VarPercent>
{
public:
VarPercent( intf_thread_t *pIntf ): Variable( pIntf ), m_value( 0 ) { }
VarPercent( intf_thread_t *pIntf ) :
Variable( pIntf ), m_value( 0 ), m_step( .05f ) {}
virtual ~VarPercent() { }
/// Get the variable type
......@@ -46,13 +47,19 @@ public:
virtual float get() const { return m_value; }
/// Get the variable preferred step
virtual float getStep() const { return .05f; }
virtual float getStep() const { return m_step; }
virtual void setStep( float val ) { m_step = val; }
/// Increment or decrement variable
void increment( int num ) { return set( m_value + num * m_step ); }
private:
/// Variable type
static const string m_type;
/// Percent value
float m_value;
/// preferred step (for scrolling)
float m_step;
};
#endif
......@@ -6,6 +6,7 @@
*
* Authors: Antoine Cellerier <dionoea@videolan.org>
* Clément Stenac <zorglub@videolan.org>
* Erwan Tulou <erwan10@videolan.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -23,64 +24,89 @@
*****************************************************************************/
#include "var_tree.hpp"
#include <math.h>
const string VarTree::m_type = "tree";
VarTree::VarTree( intf_thread_t *pIntf )
: Variable( pIntf ), m_pParent( NULL ), m_id( 0 ), m_pData( NULL ),
m_readonly( false ), m_selected( false ), m_playing( false ),
m_expanded( false ), m_deleted( false )
: Variable( pIntf ), m_pParent( NULL ), m_id( 0 ),
m_readonly( false ), m_selected( false ),
m_playing( false ), m_expanded( false ),
m_flat( false ), m_dontMove( false )
{
// Create the position variable
m_cPosition = VariablePtr( new VarPercent( pIntf ) );
getPositionVar().set( 1.0 );
getPositionVar().addObserver( this );
}
VarTree::VarTree( intf_thread_t *pIntf, VarTree *pParent, int id,
const UStringPtr &rcString, bool selected, bool playing,
bool expanded, bool readonly, void *pData )
bool expanded, bool readonly )
: Variable( pIntf ), m_pParent( pParent ),
m_id( id ), m_pData( pData ), m_cString( rcString ),
m_readonly( readonly ), m_selected( selected ), m_playing( playing ),
m_expanded( expanded ), m_deleted( false )
m_id( id ), m_cString( rcString ),
m_readonly( readonly ), m_selected( selected ),
m_playing( playing ), m_expanded( expanded ),
m_flat( false ), m_dontMove( false )
{
// Create the position variable
m_cPosition = VariablePtr( new VarPercent( pIntf ) );
getPositionVar().set( 1.0 );
getPositionVar().addObserver( this );
}
VarTree::VarTree( const VarTree& v )
: Variable( v.getIntf() ), m_pParent( v.m_pParent ),
m_id( v.m_id ), m_cString( v.m_cString ),
m_readonly( v.m_readonly ), m_selected( v.m_selected ),
m_playing( v.m_playing ), m_expanded( v.m_expanded ),
m_flat( false ), m_dontMove( false )
{
// Create the position variable
m_cPosition = VariablePtr( new VarPercent( getIntf() ) );
getPositionVar().set( 1.0 );
getPositionVar().addObserver( this );
}
VarTree::~VarTree()
{
/// \todo check that children are deleted
getPositionVar().delObserver( this );
}
void VarTree::add( int id, const UStringPtr &rcString, bool selected,
bool playing, bool expanded, bool readonly, void *pData )
VarTree::Iterator VarTree::add( int id, const UStringPtr &rcString,
bool selected, bool playing, bool expanded, bool readonly,
int pos )
{
m_children.push_back( VarTree( getIntf(), this, id, rcString, selected,
playing, expanded, readonly,
pData ) );
Iterator it;
if( pos == -1 )
{
it = m_children.end();
}
else
{
it = m_children.begin();
for( int i = 0; i < pos && it != m_children.end(); ++it, i++ );
}
return m_children.insert( it,
VarTree( getIntf(), this, id, rcString,
selected, playing,
expanded, readonly ) );
}
void VarTree::delSelected()
{
Iterator it = begin();
while( it != end() )
for( Iterator it = m_children.begin(); it != m_children.end(); )
{
//dig down the tree
if( size() ) it->delSelected();
//stay on some level
if( it->m_selected )
{
Iterator oldIt = it;
++it;
m_children.erase( oldIt );
}
else
{
++it;
}
}
}
......@@ -89,49 +115,32 @@ void VarTree::clear()
m_children.clear();
}
VarTree::Iterator VarTree::operator[]( int n )
{
Iterator it;
int i;
for( it = begin(), i = 0;
i < n && it != end();
++it, i++ );
return it;
}
VarTree::ConstIterator VarTree::operator[]( int n ) const
VarTree::Iterator VarTree::getNextSiblingOrUncle()
{
ConstIterator it;
int i;
for( it = begin(), i = 0;
i < n && it != end();
++it, i++ );
return it;
VarTree *p_parent = parent();
if( p_parent )
{
Iterator it = ++(getSelf());
if( it != p_parent->m_children.end() )
return it;
else
return next_uncle();
}
return root()->m_children.end();
}
VarTree::Iterator VarTree::getNextSiblingOrUncle()
VarTree::Iterator VarTree::getPrevSiblingOrUncle()
{
VarTree *p_parent = parent();
if( p_parent )
{
Iterator it = p_parent->begin();
while( it != p_parent->end() && &(*it) != this ) ++it;
if( it != p_parent->end() )
{
Iterator current = it;
++it;
if( it != p_parent->end() )
return it;
else
return current->next_uncle();
}
Iterator it = getSelf();
if( it != p_parent->m_children.begin() )
return --it;
else
{
msg_Err( getIntf(), "should never occur" );
return end();
}
return prev_uncle();
}
return end();
return root()->m_children.end();
}
/* find iterator to next ancestor
......@@ -139,86 +148,60 @@ VarTree::Iterator VarTree::getNextSiblingOrUncle()
VarTree::Iterator VarTree::next_uncle()
{
VarTree *p_parent = parent();
if( p_parent != NULL )
if( p_parent )
{
VarTree *p_grandparent = p_parent->parent();
while( p_grandparent != NULL )
while( p_grandparent )
{
Iterator it = p_grandparent->begin();
while( it != p_grandparent->end() && &(*it) != p_parent ) ++it;
if( it != p_grandparent->end() )
{
++it;
if( it != p_grandparent->end() )
{
return it;
}
}
if( p_grandparent->parent() )
{
p_parent = p_grandparent;
p_grandparent = p_parent->parent();
}
else
p_grandparent = NULL;
Iterator it = ++(p_parent->getSelf());
if( it != p_grandparent->m_children.end() )
return it;
p_parent = p_grandparent;
p_grandparent = p_parent->parent();
}
}
/* if we didn't return before, it means that we've reached the end */
return root()->end();
return root()->m_children.end();
}
VarTree::Iterator VarTree::prev_uncle()
{
VarTree *p_parent = parent();
if( p_parent != NULL )
if( p_parent )
{
VarTree *p_grandparent = p_parent->parent();
while( p_grandparent != NULL )
while( p_grandparent )
{
Iterator it = p_grandparent->end();
while( it != p_grandparent->begin() && &(*it) != p_parent ) --it;
if( it != p_grandparent->begin() )
{
--it;
if( it != p_grandparent->begin() )
{
return it;
}
}
if( p_grandparent->parent() )
{
p_parent = p_grandparent;
p_grandparent = p_parent->parent();
}
else
p_grandparent = NULL;
Iterator it = p_parent->getSelf();
if( it != p_grandparent->m_children.begin() )
return --it;
p_parent = p_grandparent;
p_grandparent = p_parent->parent();
}
}
/* if we didn't return before, it means that we've reached the end */
return root()->begin();
return root()->m_children.end();
}
int VarTree::visibleItems()
{
int i_count = size();
Iterator it = begin();
while( it != end() )
for( Iterator it = m_children.begin(); it != m_children.end(); ++it )
{
if( it->m_expanded )
{
i_count += it->visibleItems();
}
++it;
}
return i_count;
}
VarTree::Iterator VarTree::getVisibleItem( int n )
{
Iterator it = begin();
while( it != end() )
Iterator it = m_children.begin();
while( it != m_children.end() )
{
n--;
if( n <= 0 )
......@@ -232,13 +215,13 @@ VarTree::Iterator VarTree::getVisibleItem( int n )
}
++it;
}
return end();
return m_children.end();
}
VarTree::Iterator VarTree::getLeaf( int n )
{
Iterator it = begin();
while( it != end() )
Iterator it = m_children.begin();
while( it != m_children.end() )
{
if( it->size() )
{
......@@ -255,21 +238,21 @@ VarTree::Iterator VarTree::getLeaf( int n )
}
++it;
}
return end();
return m_children.end();
}
VarTree::Iterator VarTree::getNextVisibleItem( Iterator it )
{
if( it->m_expanded && it->size() )
{
it = it->begin();
it = it->m_children.begin();
}
else
{
Iterator it_old = it;
++it;
// Was 'it' the last brother? If so, look for uncles
if( it_old->parent() && it_old->parent()->end() == it )
if( it_old->parent() && it_old->parent()->m_children.end() == it )
{
it = it_old->next_uncle();
}
......@@ -279,23 +262,30 @@ VarTree::Iterator VarTree::getNextVisibleItem( Iterator it )
VarTree::Iterator VarTree::getPrevVisibleItem( Iterator it )
{
Iterator it_old = it;
if( it == root()->begin() || it == ++(root()->begin()) ) return it;
if( it == root()->m_children.begin() )
return it;
if( it == root()->m_children.end() )
{
--it;
while( it->size() && it->m_expanded )
it = --(it->m_children.end());
return it;
}
/* Was it the first child of its parent ? */
if( it->parent() && it == it->parent()->begin() )
VarTree *p_parent = it->parent();
if( it == p_parent->m_children.begin() )
{
/* Yes, get previous uncle */
it = it_old->prev_uncle();
/* Yes, get its parent's it */
it = p_parent->getSelf();
}
else
--it;
/* We have found an expanded uncle, take its last child */
while( it != root()->begin() && it->size() && it->m_expanded )
{
it = it->end();
--it;
--it;
/* We have found an older brother, take its last visible child */
while( it->size() && it->m_expanded )
it = --(it->m_children.end());
}
return it;
}
......@@ -304,14 +294,14 @@ VarTree::Iterator VarTree::getNextItem( Iterator it )
{
if( it->size() )
{
it = it->begin();
it = it->m_children.begin();
}
else
{
Iterator it_old = it;
++it;
// Was 'it' the last brother? If so, look for uncles
if( it_old->parent() && it_old->parent()->end() == it )
if( it_old->parent() && it_old->parent()->m_children.end() == it )
{
it = it_old->next_uncle();
}
......@@ -321,23 +311,29 @@ VarTree::Iterator VarTree::getNextItem( Iterator it )
VarTree::Iterator VarTree::getPrevItem( Iterator it )
{
Iterator it_old = it;
if( it == root()->begin() || it == ++(root()->begin()) ) return it;
if( it == root()->m_children.begin() )
return it;
if( it == root()->m_children.end() )
{
--it;
while( it->size() )
it = --(it->m_children.end());
return it;
}
/* Was it the first child of its parent ? */
if( it->parent() && it == it->parent()->begin() )
VarTree *p_parent = it->parent();
if( it == p_parent->m_children.begin() )
{
/* Yes, get previous uncle */
it = it_old->prev_uncle();
/* Yes, get its parent's it */
it = p_parent->getSelf();
}
else
--it;
/* We have found an expanded uncle, take its last child */
while( it != root()->begin() && it->size() )
{
it = it->end();
--it;
--it;
/* We have found an older brother, take its last child */
while( it->size() )
it = --(it->m_children.end());
}
return it;
}
......@@ -348,42 +344,35 @@ VarTree::Iterator VarTree::getNextLeaf( Iterator it )
{
it = getNextItem( it );
}
while( it != root()->end() && it->size() );
while( it != root()->m_children.end() && it->size() );
return it;
}
VarTree::Iterator VarTree::getPrevLeaf( Iterator it )
{
do
{
it = getPrevItem( it );
}
while( it != root()->begin() && it->size() ); /* FIXME ? */
if( it == root()->begin() ) it = firstLeaf();
return it;
Iterator it_new = it->getPrevSiblingOrUncle();
if( it_new == root()->end() )
return it_new;
while( it_new->size() )
it_new = --(it_new->m_children.end());
return it_new;
}
VarTree::Iterator VarTree::findById( int id )
VarTree::Iterator VarTree::getParent( Iterator it )
{
for (Iterator it = begin(); it != end(); ++it )
if( it->parent() )
{
if( it->m_id == id )
{
return it;
}
Iterator result = it->findById( id );
if( result != it->end() ) return result;
return it->parent()->getSelf();
}
return end();
return m_children.end();
}
void VarTree::ensureExpanded( const Iterator& it )
{
/// Don't expand ourselves, only our parents
VarTree *current = &(*it);
current = current->parent();
while( current->parent() != NULL )
while( current->parent() )
{
current->m_expanded = true;
current = current->parent();
......@@ -392,47 +381,91 @@ void VarTree::ensureExpanded( const Iterator& it )
int VarTree::countLeafs()
{
if( size() == 0 ) return 1;
if( size() == 0 )
return 1;
int i_count = 0;
Iterator it = begin();
while( it != end() )
for( Iterator it = m_children.begin(); it != m_children.end(); ++it )
{
i_count += it->countLeafs();
++it;
}
return i_count;
}
VarTree::Iterator VarTree::firstLeaf()
{
Iterator b = root()->begin();
Iterator b = root()->m_children.begin();
if( b->size() ) return getNextLeaf( b );
return b;
}
void VarTree::cascadeDelete()
{
m_deleted = true;
for( Iterator it = begin(); it != end(); ++it )
{
it->cascadeDelete();
}
}
int VarTree::getRank( const Iterator& item, bool flat )
int VarTree::getIndex( const Iterator& item )
{
int index = 1;
int index = 0;
Iterator it;
for( it = flat ? firstLeaf() : begin();
it != end();
it = flat ? getNextLeaf( it ) : getNextVisibleItem( it ) )
for( it = m_flat ? firstLeaf() : m_children.begin();
it != m_children.end();
it = m_flat ? getNextLeaf( it ) : getNextVisibleItem( it ) )
{
if( it->isDeleted() )
continue;
if( it == item )
break;
index++;
}
return (it == item) ? index : -1;
}
VarTree::Iterator VarTree::getItemFromSlider()
{
// a simple (int)(...) causes rounding errors !
#ifdef _MSC_VER
# define lrint (int)
#endif
VarPercent &rVarPos = getPositionVar();
double percentage = rVarPos.get();
int indexMax = m_flat ? (countLeafs() - 1)
: (visibleItems() - 1);
int index = lrint( (1.0 - percentage)*(double)indexMax );
Iterator it_first = m_flat ? getLeaf( index + 1 )
: getVisibleItem( index + 1 );
return it_first;
}
void VarTree::setSliderFromItem( const Iterator& it )
{
VarPercent &rVarPos = getPositionVar();
int indexMax = m_flat ? (countLeafs() - 1)
: (visibleItems() - 1);
int index = getIndex( it );
double percentage = (1.0 - (double)index/(double)indexMax);
m_dontMove = true;
rVarPos.set( (float)percentage );
m_dontMove = false;
}
void VarTree::onUpdate( Subject<VarPercent> &rPercent, void* arg )
{
(void)rPercent; (void)arg;
onUpdateSlider();
}
void VarTree::unselectTree()
{
m_selected = false;
for( Iterator it = m_children.begin(); it != m_children.end(); ++it )
it->unselectTree();
}
VarTree::IteratorVisible VarTree::getItem( int index )
{
Iterator it =
m_flat ? getLeaf( index + 1 )
: getVisibleItem( index + 1 );
return IteratorVisible( it, this );
}
......@@ -26,47 +26,41 @@
#define VAR_TREE_HPP
#include <list>
#include <assert.h>
#include "variable.hpp"
#include "observer.hpp"
#include "ustring.hpp"
#include "var_percent.hpp"
/// Description of an update to the tree
typedef struct tree_update
{
enum type_t
{
UpdateItem,
AppendItem,
DeleteItem,
ResetAll,
};
enum type_t type;
int i_id;
bool b_active_item;
} tree_update;
class VarTree;
struct tree_update;
/// Tree variable
class VarTree: public Variable, public Subject<VarTree, tree_update>
class VarTree: public Variable,
public Subject<VarTree, tree_update>,
public Observer<VarPercent>
{
public:
VarTree( intf_thread_t *pIntf );
VarTree( intf_thread_t *pIntf, VarTree *pParent, int id,
const UStringPtr &rcString, bool selected, bool playing,
bool expanded, bool readonly, void *pData );
bool expanded, bool readonly );
VarTree( const VarTree& );
virtual ~VarTree();
/// Iterators
typedef list<VarTree>::iterator Iterator;
typedef list<VarTree>::const_iterator ConstIterator;
/// Get the variable type
virtual const string &getType() const { return m_type; }
/// Add a pointer on string in the children's list
virtual void add( int id, const UStringPtr &rcString, bool selected,
bool playing, bool expanded, bool readonly, void *pData );
virtual Iterator add( int id, const UStringPtr &rcString, bool selected,
bool playing, bool expanded, bool readonly, int pos = -1 );
/// Remove the selected item from the children's list
virtual void delSelected();
......@@ -75,7 +69,6 @@ public:
virtual void clear();
inline int getId() { return m_id; }
inline void *getData() { return m_pData; }
inline UString* getString() {return (UString*)m_cString.get(); }
inline void setString( UStringPtr val ) { m_cString = val; }
......@@ -83,43 +76,101 @@ public:
inline bool isSelected() { return m_selected; };
inline bool isPlaying() { return m_playing; };
inline bool isExpanded() { return m_expanded; };
inline bool isDeleted() { return m_deleted; };
inline bool isFlat() { return m_flat; };
inline void setSelected( bool val ) { m_selected = val; }
inline void setPlaying( bool val ) { m_playing = val; }
inline void setExpanded( bool val ) { m_expanded = val; }
inline void setDeleted( bool val ) { m_deleted = val; }
inline void setFlat( bool val ) { m_flat = val; }
inline void toggleSelected() { m_selected = !m_selected; }
inline void toggleExpanded() { m_expanded = !m_expanded; }
inline void toggleExpanded() { setExpanded( !m_expanded ); }
/// Get the number of children
int size() const { return m_children.size(); }
/// Iterators
typedef list<VarTree>::iterator Iterator;
typedef list<VarTree>::const_iterator ConstIterator;
/// iterator over visible items
class IteratorVisible : public Iterator
{
public:
IteratorVisible( const VarTree::Iterator& it, VarTree* pRootTree )
: VarTree::Iterator( it ), m_pRootTree( pRootTree ) {}
IteratorVisible& operator++()
{
Iterator& it = *this;
assert( it != end() );
it = isFlat() ? m_pRootTree->getNextLeaf( it ) :
m_pRootTree->getNextVisibleItem( it );
return *this;
}
IteratorVisible& operator--()
{
Iterator& it = *this;
it = isFlat() ? m_pRootTree->getPrevLeaf( it ) :
m_pRootTree->getPrevVisibleItem( it );
return *this;
}
IteratorVisible getParent()
{
IteratorVisible& it = *this;
if( it->parent() && it->parent() != m_pRootTree )
{
return IteratorVisible( it->parent()->getSelf(), m_pRootTree );
}
return end();
}
private:
inline IteratorVisible begin() { return m_pRootTree->begin(); }
inline IteratorVisible end() { return m_pRootTree->end(); }
inline bool isFlat() { return m_pRootTree->m_flat; }
VarTree* m_pRootTree;
};
/// Begining of the children's list
Iterator begin() { return m_children.begin(); }
ConstIterator begin() const { return m_children.begin(); }
/// Beginning of the children's list
IteratorVisible begin()
{
return IteratorVisible(
m_flat ? firstLeaf() : m_children.begin(), this );
}
/// End of children's list
Iterator end() { return m_children.end(); }
ConstIterator end() const { return m_children.end(); }
IteratorVisible end() { return IteratorVisible( m_children.end(), this ); }
/// Back of children's list
VarTree &back() { return m_children.back(); }
/// Return an iterator on the n'th element of the children's list
Iterator operator[]( int n );
ConstIterator operator[]( int n ) const;
/// Parent node
VarTree *parent() { return m_pParent; }
/// Get next sibling
Iterator getNextSiblingOrUncle();
Iterator getPrevSiblingOrUncle();
Iterator getSelf()
{
assert( m_pParent );
Iterator it = m_pParent->m_children.begin();
for( ; &*it != this && it != m_pParent->m_children.end(); ++it );
assert( it != m_pParent->m_children.end() );
return it;
}
int getIndex()
{
if( m_pParent )
{
int i_pos = 0;
for( Iterator it = m_pParent->m_children.begin();
it != m_pParent->m_children.end(); ++it, i_pos++ )
if( &(*it) == this )
return i_pos;
}
return -1;
}
Iterator next_uncle();
Iterator prev_uncle();
......@@ -131,7 +182,7 @@ public:
void removeChild( Iterator it ) { m_children.erase( it ); }
/// Execute the action associated to this item
virtual void action( VarTree *pItem ) { (void)pItem; }
virtual void action( VarTree *pItem ) { VLC_UNUSED(pItem); }
/// Get a reference on the position variable
VarPercent &getPositionVar() const
......@@ -171,17 +222,21 @@ public:
/// Given an iterator to an item, return the previous leaf
Iterator getPrevLeaf( Iterator it );
/// return rank of visible item starting from 1
int getRank( const Iterator& it, bool flat );
/// Given an iterator to an item, return the parent item
Iterator getParent( Iterator it );
/// Find a children node with the given id
Iterator findById( int id );
/// return index of visible item (starting from 0)
int getIndex( const Iterator& it );
/// Ensure an item is expanded
void ensureExpanded( const Iterator& it );
/// flag a whole subtree for deletion
void cascadeDelete();
///
Iterator getItemFromSlider();
void setSliderFromItem( const Iterator& it );
///
void onUpdate( Subject<VarPercent> &rPercent, void* arg);
/// Get depth (root depth is 0)
int depth()
......@@ -193,6 +248,17 @@ public:
return depth;
}
virtual void onUpdateSlider() {}
void unselectTree();
VarTree::IteratorVisible getItem( int index );
protected:
/// List of children
list<VarTree> m_children;
private:
/// Get root node
......@@ -204,14 +270,10 @@ private:
return parent;
}
/// List of children
list<VarTree> m_children;
/// Pointer to parent node
VarTree *m_pParent;
int m_id;
void *m_pData;
UStringPtr m_cString;
/// indicators
......@@ -219,7 +281,8 @@ private:
bool m_selected;
bool m_playing;
bool m_expanded;
bool m_deleted;
bool m_flat;
bool m_dontMove;
/// Variable type
static const string m_type;
......@@ -228,4 +291,23 @@ private:
VariablePtr m_cPosition;
};
/// Description of an update to the tree
typedef struct tree_update
{
enum type_t
{
ItemUpdated,
ItemInserted,
ItemDeleted,
DeletingItem,
ResetAll,
SliderChanged,
};
enum type_t type;
VarTree::IteratorVisible it;
tree_update( enum type_t t, VarTree::IteratorVisible item ) :
type( t ), it( item ) {}
} tree_update;
#endif
......@@ -6,6 +6,7 @@
*
* Authors: Antoine Cellerier <dionoea@videolan.org>
* Clément Stenac <zorglub@videolan.org>
* Erwan Tulou <erwan10@videolan.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -30,133 +31,111 @@
#include "playtree.hpp"
#include <vlc_playlist.h>
#include <vlc_url.h>
#include "../utils/ustring.hpp"
Playtree::Playtree( intf_thread_t *pIntf ): VarTree( pIntf )
Playtree::Playtree( intf_thread_t *pIntf )
: VarTree( pIntf ), m_pPlaylist( pIntf->p_sys->p_playlist )
{
// Get the VLC playlist object
m_pPlaylist = pIntf->p_sys->p_playlist;
getPositionVar().addObserver( this );
buildTree();
}
Playtree::~Playtree()
{
getPositionVar().delObserver( this );
}
void Playtree::delSelected()
{
Iterator it = begin();
playlist_Lock( getIntf()->p_sys->p_playlist );
for( it = begin(); it != end(); it = getNextItem( it ) )
for( Iterator it = m_children.begin(); it != m_children.end(); )
{
if( it->isSelected() && !it->isReadonly() )
{
it->cascadeDelete();
}
}
/// \todo Do this better (handle item-deleted)
tree_update descr;
descr.type = tree_update::DeleteItem;
notify( &descr );
it = begin();
while( it != end() )
{
if( it->isDeleted() )
{
VarTree::Iterator it2;
playlist_item_t *p_item = (playlist_item_t *)(it->getData());
if( p_item->i_children == -1 )
{
playlist_DeleteFromInput( getIntf()->p_sys->p_playlist,
p_item->p_input, pl_Locked );
it2 = getNextItem( it ) ;
}
else
playlist_Lock( m_pPlaylist );
playlist_item_t *pItem =
playlist_ItemGetById( m_pPlaylist, it->getId() );
if( pItem )
{
playlist_NodeDelete( getIntf()->p_sys->p_playlist, p_item,
true, false );
it2 = it->getNextSiblingOrUncle();
if( pItem->i_children == -1 )
{
playlist_DeleteFromInput( m_pPlaylist, pItem->p_input,
pl_Locked );
}
else
{
playlist_NodeDelete( m_pPlaylist, pItem, true, false );
}
}
it->parent()->removeChild( it );
it = it2;
playlist_Unlock( m_pPlaylist );
it = it->getNextSiblingOrUncle();
}
else
{
it = getNextItem( it );
}
}
playlist_Unlock( getIntf()->p_sys->p_playlist );
}
void Playtree::action( VarTree *pItem )
void Playtree::action( VarTree *pElem )
{
playlist_Lock( m_pPlaylist );
VarTree::Iterator it;
playlist_item_t *p_item = (playlist_item_t *)pItem->getData();
playlist_item_t *p_parent = p_item;
while( p_parent )
playlist_item_t *pItem =
playlist_ItemGetById( m_pPlaylist, pElem->getId() );
if( pItem )
{
if( p_parent == m_pPlaylist->p_root_category )
break;
p_parent = p_parent->p_parent;
playlist_Control( m_pPlaylist, PLAYLIST_VIEWPLAY,
pl_Locked, pItem->p_parent, pItem );
}
if( p_parent )
{
playlist_Control( m_pPlaylist, PLAYLIST_VIEWPLAY, pl_Locked, p_parent, p_item );
}
playlist_Unlock( m_pPlaylist );
}
void Playtree::onChange()
{
buildTree();
tree_update descr;
descr.type = tree_update::ResetAll;
tree_update descr( tree_update::ResetAll, end() );
notify( &descr );
}
void Playtree::onUpdateItem( int id )
{
Iterator it = findById( id );
if( it != end() )
if( it != m_children.end() )
{
// Update the item
playlist_item_t* pNode = (playlist_item_t*)(it->getData());
playlist_Lock( m_pPlaylist );
playlist_item_t *pNode =
playlist_ItemGetById( m_pPlaylist, it->getId() );
if( !pNode )
{
playlist_Unlock( m_pPlaylist );
return;
}
UString *pName = new UString( getIntf(), pNode->p_input->psz_name );
it->setString( UStringPtr( pName ) );
playlist_Unlock( m_pPlaylist );
tree_update descr;
descr.type = tree_update::UpdateItem;
descr.i_id = id;
descr.b_active_item = false;
notify( &descr );
if( *pName != *(it->getString()) )
{
it->setString( UStringPtr( pName ) );
tree_update descr(
tree_update::ItemUpdated, IteratorVisible( it, this ) );
notify( &descr );
}
}
else
{
msg_Warn(getIntf(), "cannot find node with id %d", id );
msg_Warn( getIntf(), "cannot find node with id %d", id );
}
}
void Playtree::onUpdateCurrent( bool b_active )
{
for( VarTree::Iterator it = begin(); it != end(); it = getNextItem( it ) )
{
if( it->isPlaying() )
{
it->setPlaying( false );
tree_update descr;
descr.type = tree_update::UpdateItem;
descr.i_id = it->getId();
descr.b_active_item = false;
notify( &descr );
break;
}
}
if( b_active )
{
playlist_Lock( m_pPlaylist );
......@@ -169,81 +148,107 @@ void Playtree::onUpdateCurrent( bool b_active )
}
Iterator it = findById( current->i_id );
if( it != end() )
if( it != m_children.end() )
{
it->setPlaying( true );
tree_update descr(
tree_update::ItemUpdated, IteratorVisible( it, this ) );
notify( &descr );
}
playlist_Unlock( m_pPlaylist );
}
else
{
for( Iterator it = m_children.begin(); it != m_children.end();
it = getNextItem( it ) )
{
if( it->isPlaying() )
{
it->setPlaying( false );
tree_update descr;
descr.type = tree_update::UpdateItem;
descr.i_id = current->i_id;
descr.b_active_item = true;
notify( &descr );
tree_update descr(
tree_update::ItemUpdated, IteratorVisible( it, this ) );
notify( &descr );
break;
}
}
}
}
void Playtree::onDelete( int i_id )
{
Iterator item = findById( i_id ) ;
if( item != end() )
Iterator it = findById( i_id ) ;
if( it != m_children.end() )
{
VarTree* parent = item->parent();
VarTree* parent = it->parent();
if( parent )
{
tree_update descr(
tree_update::DeletingItem, IteratorVisible( it, this ) );
notify( &descr );
item->setDeleted( true );
parent->removeChild( it );
m_allItems.erase( i_id );
tree_update descr;
descr.type = tree_update::DeleteItem;
descr.i_id = i_id;
notify( &descr );
if( parent )
parent->removeChild( item );
tree_update descr2(
tree_update::ItemDeleted, end() );
notify( &descr2 );
}
}
}
void Playtree::onAppend( playlist_add_t *p_add )
{
Iterator node = findById( p_add->i_node );
if( node != end() )
Iterator it_node = findById( p_add->i_node );
if( it_node != m_children.end() )
{
playlist_Lock( m_pPlaylist );
playlist_item_t *p_item =
playlist_item_t *pItem =
playlist_ItemGetById( m_pPlaylist, p_add->i_item );
if( !p_item )
if( !pItem )
{
playlist_Unlock( m_pPlaylist );
return;
}
int pos;
for( pos = 0; pos < pItem->p_parent->i_children; pos++ )
if( pItem->p_parent->pp_children[pos] == pItem ) break;
UString *pName = new UString( getIntf(),
p_item->p_input->psz_name );
node->add( p_add->i_item, UStringPtr( pName ),
false,false, false, p_item->i_flags & PLAYLIST_RO_FLAG,
p_item );
pItem->p_input->psz_name );
playlist_item_t* current = playlist_CurrentPlayingItem( m_pPlaylist );
Iterator it = it_node->add(
p_add->i_item, UStringPtr( pName ), false, pItem == current,
false, pItem->i_flags & PLAYLIST_RO_FLAG, pos );
m_allItems[pItem->i_id] = &*it;
playlist_Unlock( m_pPlaylist );
tree_update descr;
descr.type = tree_update::AppendItem;
descr.i_id = p_add->i_item;
tree_update descr(
tree_update::ItemInserted,
IteratorVisible( it, this ) );
notify( &descr );
}
}
void Playtree::buildNode( playlist_item_t *pNode, VarTree &rTree )
{
UString *pName = new UString( getIntf(), pNode->p_input->psz_name );
Iterator it = rTree.add(
pNode->i_id, UStringPtr( pName ), false,
playlist_CurrentPlayingItem(m_pPlaylist) == pNode,
false, pNode->i_flags & PLAYLIST_RO_FLAG );
m_allItems[pNode->i_id] = &*it;
for( int i = 0; i < pNode->i_children; i++ )
{
UString *pName = new UString( getIntf(),
pNode->pp_children[i]->p_input->psz_name );
rTree.add(
pNode->pp_children[i]->i_id, UStringPtr( pName ), false,
playlist_CurrentPlayingItem(m_pPlaylist) == pNode->pp_children[i],
false, pNode->pp_children[i]->i_flags & PLAYLIST_RO_FLAG,
pNode->pp_children[i] );
if( pNode->pp_children[i]->i_children > 0 )
{
buildNode( pNode->pp_children[i], rTree.back() );
}
buildNode( pNode->pp_children[i], *it );
}
}
......@@ -252,17 +257,84 @@ void Playtree::buildTree()
clear();
playlist_Lock( m_pPlaylist );
clear();
for( int i = 0; i < m_pPlaylist->p_root->i_children; i++ )
{
buildNode( m_pPlaylist->p_root->pp_children[i], *this );
}
/* TODO: Let user choose view - Stick with category ATM */
playlist_Unlock( m_pPlaylist );
}
/* Set the root's name */
UString *pName = new UString( getIntf(),
m_pPlaylist->p_root_category->p_input->psz_name );
setString( UStringPtr( pName ) );
void Playtree::onUpdateSlider()
{
tree_update descr( tree_update::SliderChanged, end() );
notify( &descr );
}
buildNode( m_pPlaylist->p_root_category, *this );
void Playtree::insertItems( VarTree& elem, const list<string>& files, bool start )
{
bool first = true;
VarTree* p_elem = &elem;
playlist_item_t* p_node = NULL;
int i_pos = -1;
playlist_Lock( m_pPlaylist );
if( p_elem->getId() == m_pPlaylist->p_local_category->i_id )
{
p_node = m_pPlaylist->p_local_category;
i_pos = 0;
}
else if( p_elem->getId() == m_pPlaylist->p_ml_category->i_id )
{
p_node = m_pPlaylist->p_ml_category;
i_pos = 0;
}
else if( p_elem->size() )
{
p_node = playlist_ItemGetById( m_pPlaylist, p_elem->getId() );
i_pos = 0;
}
else
{
p_node = playlist_ItemGetById( m_pPlaylist,
p_elem->parent()->getId() );
i_pos = p_elem->getIndex();
i_pos++;
}
if( !p_node )
goto fin;
for( list<string>::const_iterator it = files.begin();
it != files.end(); ++it, i_pos++, first = false )
{
char* psz_uri = make_URI( it->c_str(), NULL );
if( !psz_uri )
continue;
input_item_t* pItem = input_item_New( m_pPlaylist, psz_uri, NULL );
if( pItem )
{
int i_mode = PLAYLIST_APPEND;
if( first && start )
i_mode |= PLAYLIST_GO;
playlist_NodeAddInput( m_pPlaylist, pItem, p_node,
i_mode, i_pos, pl_Locked );
}
free( psz_uri );
}
fin:
playlist_Unlock( m_pPlaylist );
}
VarTree::Iterator Playtree::findById( int id )
{
map<int,VarTree*>::iterator it = m_allItems.find( id );
if( it == m_allItems.end() )
return m_children.end();
else
return it->second->getSelf();
}
......@@ -28,6 +28,8 @@
#include <vlc_playlist.h>
#include "../utils/var_tree.hpp"
#include <map>
/// Variable for VLC playlist (new tree format)
class Playtree: public VarTree
{
......@@ -56,13 +58,25 @@ public:
/// Function called to notify playlist item delete
void onDelete( int );
///
void onUpdateSlider();
///
void insertItems( VarTree& item, const list<string>& files, bool start );
private:
/// VLC playlist object
playlist_t *m_pPlaylist;
///
map< int, VarTree* > m_allItems;
/// Build the list from the VLC playlist
void buildTree();
/// Retrieve an iterator from playlist_item_t->id
Iterator findById( int id );
/// Update Node's children
void buildNode( playlist_item_t *p_node, VarTree &m_pNode );
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment