Main Page | Modules | Class Hierarchy | Class List | Directories | File List | Class Members | File Members | Related Pages

ctrl_tree.cpp

00001 /*****************************************************************************
00002  * ctrl_tree.cpp
00003  *****************************************************************************
00004  * Copyright (C) 2003 VideoLAN
00005  * $Id: ctrl_tree.cpp 12950 2005-10-23 17:59:52Z asmax $
00006  *
00007  * Authors: Antoine Cellerier <[email protected]>
00008  *
00009  * This program is free software; you can redistribute it and/or modify
00010  * it under the terms of the GNU General Public License as published by
00011  * the Free Software Foundation; either version 2 of the License, or
00012  * (at your option) any later version.
00013  *
00014  * This program is distributed in the hope that it will be useful,
00015  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00016  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00017  * GNU General Public License for more details.
00018  *
00019  * You should have received a copy of the GNU General Public License
00020  * along with this program; if not, write to the Free Software
00021  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
00022  *****************************************************************************/
00023 
00024 #include <math.h>
00025 #include "ctrl_tree.hpp"
00026 #include "../src/os_factory.hpp"
00027 #include "../src/os_graphics.hpp"
00028 #include "../src/generic_bitmap.hpp"
00029 #include "../src/generic_font.hpp"
00030 #include "../src/scaled_bitmap.hpp"
00031 #include "../utils/position.hpp"
00032 #include "../utils/ustring.hpp"
00033 #include "../events/evt_key.hpp"
00034 #include "../events/evt_mouse.hpp"
00035 #include "../events/evt_scroll.hpp"
00036 #include "vlc_keys.h"
00037 #ifdef sun
00038 #   include "solaris_specific.h" // for lrint
00039 #endif
00040 
00041 #define SCROLL_STEP 0.05
00042 #define LINE_INTERVAL 1  // Number of pixels inserted between 2 lines
00043 
00044 
00045 CtrlTree::CtrlTree( intf_thread_t *pIntf,
00046                     VarTree &rTree,
00047                     const GenericFont &rFont,
00048                     const GenericBitmap *pBgBitmap,
00049                     const GenericBitmap *pItemBitmap,
00050                     const GenericBitmap *pOpenBitmap,
00051                     const GenericBitmap *pClosedBitmap,
00052                     uint32_t fgColor,
00053                     uint32_t playColor,
00054                     uint32_t bgColor1,
00055                     uint32_t bgColor2,
00056                     uint32_t selColor,
00057                     const UString &rHelp,
00058                     VarBool *pVisible ):
00059     CtrlGeneric( pIntf,rHelp, pVisible), m_rTree( rTree), m_rFont( rFont ),
00060     m_pBgBitmap( pBgBitmap ), m_pItemBitmap( pItemBitmap ),
00061     m_pOpenBitmap( pOpenBitmap ), m_pClosedBitmap( pClosedBitmap ),
00062     m_fgColor( fgColor ), m_playColor( playColor ), m_bgColor1( bgColor1 ),
00063     m_bgColor2( bgColor2 ), m_selColor( selColor ),
00064     m_pLastSelected( NULL ), m_pImage( NULL )
00065 {
00066     // Observe the tree and position variables
00067     m_rTree.addObserver( this );
00068     m_rTree.getPositionVar().addObserver( this );
00069 
00070     m_lastPos = m_rTree.begin();
00071 
00072     makeImage();
00073 }
00074 
00075 CtrlTree::~CtrlTree()
00076 {
00077     m_rTree.getPositionVar().delObserver( this );
00078     m_rTree.delObserver( this );
00079     if( m_pImage )
00080     {
00081         delete m_pImage;
00082     }
00083 }
00084 
00085 int CtrlTree::itemHeight()
00086 {
00087     int itemHeight = m_rFont.getSize();
00088     if( m_pClosedBitmap )
00089     {
00090         itemHeight = __MAX( m_pClosedBitmap->getHeight(), itemHeight );
00091     }
00092     if( m_pOpenBitmap )
00093     {
00094         itemHeight = __MAX( m_pOpenBitmap->getHeight(), itemHeight );
00095     }
00096     if( m_pItemBitmap )
00097     {
00098         itemHeight = __MAX( m_pItemBitmap->getHeight(), itemHeight );
00099     }
00100     itemHeight += LINE_INTERVAL;
00101     return itemHeight;
00102 }
00103 
00104 int CtrlTree::itemImageWidth()
00105 {
00106     int bitmapWidth = 5;
00107     if( m_pClosedBitmap )
00108     {
00109         bitmapWidth = __MAX( m_pClosedBitmap->getWidth(), bitmapWidth );
00110     }
00111     if( m_pOpenBitmap )
00112     {
00113         bitmapWidth = __MAX( m_pOpenBitmap->getWidth(), bitmapWidth );
00114     }
00115     if( m_pItemBitmap )
00116     {
00117         bitmapWidth = __MAX( m_pItemBitmap->getWidth(), bitmapWidth );
00118     }
00119     return bitmapWidth + 2;
00120 }
00121 
00122 int CtrlTree::maxItems()
00123 {
00124     const Position *pPos = getPosition();
00125     if( !pPos )
00126     {
00127         return -1;
00128     }
00129     return pPos->getHeight() / itemHeight();
00130 }
00131 
00132 
00133 void CtrlTree::onUpdate( Subject<VarTree> &rTree )
00134 {
00135     // Invalidate the position when the tree is updated
00136     m_lastPos = m_rTree.begin();
00137 
00138     autoScroll();
00139     m_pLastSelected = NULL;
00140 }
00141 
00142 void CtrlTree::onUpdate( Subject<VarPercent> &rPercent )
00143 {
00144     // Determine what is the first item to display
00145     VarTree::Iterator it = m_rTree.begin();
00146 
00147     int excessItems = m_rTree.visibleItems() - maxItems();
00148 
00149     if( excessItems > 0)
00150     {
00151         VarPercent &rVarPos = m_rTree.getPositionVar();
00152         // a simple (int)(...) causes rounding errors !
00153 #ifdef _MSC_VER
00154 #   define lrint (int)
00155 #endif
00156         it = m_rTree.getVisibleItem(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1);
00157     }
00158     if( m_lastPos != it )
00159     {
00160         // Redraw the control if the position has changed
00161         m_lastPos = it;
00162         makeImage();
00163         notifyLayout();
00164     }
00165 }
00166 
00167 void CtrlTree::onResize()
00168 {
00169 // FIXME : shouldn't be the same as the onUpdate function ... but i'm lazy
00170     // Determine what is the first item to display
00171     VarTree::Iterator it = m_rTree.begin();
00172 
00173     int excessItems = m_rTree.visibleItems() - maxItems();
00174 
00175     if( excessItems > 0)
00176     {
00177         VarPercent &rVarPos = m_rTree.getPositionVar();
00178         // a simple (int)(...) causes rounding errors !
00179 #ifdef _MSC_VER
00180 #   define lrint (int)
00181 #endif
00182         it = m_rTree.getVisibleItem(lrint( (1.0 - rVarPos.get()) * (double)excessItems ) + 1);
00183     }
00184     // Redraw the control if the position has changed
00185     m_lastPos = it;
00186     makeImage();
00187     notifyLayout();
00188 #if 0
00189     // Determine what is the first item to display
00190     VarTree::Iterator it = m_rTree.begin();
00191 
00192     int excessItems = m_rTree.visibleItems() - maxItems();
00193 
00194     if( excessItems > 0)
00195     {
00196         /* FIXME VarPercent &rVarPos = m_rTree.getPositionVar();
00197         double newVal = 1.0 - (double)m_lastPos / excessItems;
00198         if( newVal >= 0 )
00199         {
00200             // Change the position to keep the same first displayed item
00201             rVarPos.set( 1.0 - (double)m_lastPos / excessItems );
00202         }
00203         else
00204         {
00205             // We cannot keep the current first item
00206             m_lastPos = excessItems;
00207         }*/
00208         it = m_rTree.getVisibleItem( excessItems );
00209     }
00210     makeImage();
00211     notifyLayout();
00212 #endif
00213 }
00214 
00215 void CtrlTree::onPositionChange()
00216 {
00217     makeImage();
00218     notifyLayout();
00219 }
00220 
00221 void CtrlTree::handleEvent( EvtGeneric &rEvent )
00222 {
00223     if( rEvent.getAsString().find( "key:down" ) != string::npos )
00224     {
00225         int key = ((EvtKey&)rEvent).getKey();
00226         VarTree::Iterator it;
00227         bool previousWasSelected = false;
00228         for( it = m_rTree.begin(); it != m_rTree.end();
00229              it = m_rTree.getNextVisibleItem( it ) )
00230         {
00231             VarTree::Iterator next = m_rTree.getNextVisibleItem( it );
00232             if( key == KEY_UP )
00233             {
00234                 // Scroll up one item
00235                 if( ( it->parent()
00236                       && it != it->parent()->begin() )
00237                     || &*it != m_pLastSelected )
00238                 {
00239                     bool nextWasSelected = ( &*next == m_pLastSelected );
00240                     it->m_selected = nextWasSelected;
00241                     if( nextWasSelected )
00242                     {
00243                         m_pLastSelected = &*it;
00244                     }
00245                 }
00246             }
00247             else if( key == KEY_DOWN )
00248             {
00249                 // Scroll down one item
00250                 if( ( it->parent()
00251                       && next != it->parent()->end() )
00252                     || &*it != m_pLastSelected )
00253                 {
00254                     (*it).m_selected = previousWasSelected;
00255                 }
00256                 if( previousWasSelected )
00257                 {
00258                     m_pLastSelected = &*it;
00259                     previousWasSelected = false;
00260                 }
00261                 else
00262                 {
00263                     previousWasSelected = ( &*it == m_pLastSelected );
00264                 }
00265             }
00266             else if( key == KEY_RIGHT )
00267             {
00268                 // Go down one level (and expand node)
00269                 if( &*it == m_pLastSelected )
00270                 {
00271                     if( it->m_expanded )
00272                     {
00273                         if( it->size() )
00274                         {
00275                             it->m_selected = false;
00276                             it->begin()->m_selected = true;
00277                             m_pLastSelected = &*(it->begin());
00278                         }
00279                         else
00280                         {
00281                             m_rTree.action( &*it );
00282                         }
00283                     }
00284                     else
00285                     {
00286                         it->m_expanded = true;
00287                     }
00288                 }
00289             }
00290             else if( key == KEY_LEFT )
00291             {
00292                 // Go up one level (and close node)
00293                 if( &*it == m_pLastSelected )
00294                 {
00295                     if( it->m_expanded && it->size() )
00296                     {
00297                         it->m_expanded = false;
00298                     }
00299                     else
00300                     {
00301                         if( it->parent() && it->parent() != &m_rTree)
00302                         {
00303                             it->m_selected = false;
00304                             m_pLastSelected = it->parent();
00305                             m_pLastSelected->m_selected = true;
00306                         }
00307                     }
00308                 }
00309             }
00310             else if( key == KEY_ENTER || key == KEY_SPACE )
00311             {
00312                 // Go up one level (and close node)
00313                 if( &*it == m_pLastSelected )
00314                 {
00315                     m_rTree.action( &*it );
00316                 }
00317             }
00318         }
00319 
00320         // Redraw the control
00321         makeImage();
00322         notifyLayout();
00323     }
00324 
00325     else if( rEvent.getAsString().find( "mouse:left" ) != string::npos )
00326     {
00327         EvtMouse &rEvtMouse = (EvtMouse&)rEvent;
00328         const Position *pos = getPosition();
00329         int yPos = ( rEvtMouse.getYPos() - pos->getTop() ) / itemHeight();
00330         int xPos = rEvtMouse.getXPos() - pos->getLeft();
00331         VarTree::Iterator it;
00332 
00333         if( rEvent.getAsString().find( "mouse:left:down:ctrl,shift" ) !=
00334             string::npos )
00335         {
00336             VarTree::Iterator itClicked = findItemAtPos( yPos );
00337             // Flag to know if the current item must be selected
00338             bool select = false;
00339             for( it = m_rTree.begin(); it != m_rTree.end();
00340                  it = m_rTree.getNextVisibleItem( it ) )
00341             {
00342                 bool nextSelect = select;
00343                 if( it == itClicked || &*it == m_pLastSelected )
00344                 {
00345                     if( select )
00346                     {
00347                         nextSelect = false;
00348                     }
00349                     else
00350                     {
00351                         select = true;
00352                         nextSelect = true;
00353                     }
00354                 }
00355                 it->m_selected = (*it).m_selected || select;
00356                 select = nextSelect;
00357             }
00358         }
00359         else if( rEvent.getAsString().find( "mouse:left:down:ctrl" ) !=
00360                  string::npos )
00361         {
00362             // Invert the selection of the item
00363             it = findItemAtPos( yPos );
00364             if( it != m_rTree.end() )
00365             {
00366                 it->m_selected = !it->m_selected;
00367                 m_pLastSelected = &*it;
00368             }
00369         }
00370         else if( rEvent.getAsString().find( "mouse:left:down:shift" ) !=
00371                  string::npos )
00372         {
00373             VarTree::Iterator itClicked = findItemAtPos( yPos );
00374             // Flag to know if the current item must be selected
00375             bool select = false;
00376             for( it = m_rTree.begin(); it != m_rTree.end();
00377                  it = m_rTree.getNextVisibleItem( it ) )
00378             {
00379                 bool nextSelect = select;
00380                 if( it == itClicked || &*it == m_pLastSelected )
00381                 {
00382                     if( select )
00383                     {
00384                         nextSelect = false;
00385                     }
00386                     else
00387                     {
00388                         select = true;
00389                         nextSelect = true;
00390                     }
00391                 }
00392                 it->m_selected = select;
00393                 select = nextSelect;
00394             }
00395         }
00396         else if( rEvent.getAsString().find( "mouse:left:down" ) !=
00397                  string::npos )
00398         {
00399             it = findItemAtPos(yPos);
00400             if( it->size() && xPos > (it->depth() - 1) * itemImageWidth()
00401                 && xPos < it->depth() * itemImageWidth() )
00402             {
00403                 // Fold/unfold the item
00404                 it->m_expanded = !it->m_expanded;
00405             }
00406             else
00407             {
00408                 // Unselect any previously selected item
00409                 for( it = m_rTree.begin(); it != m_rTree.end();
00410                      it = m_rTree.getNextVisibleItem( it ) )
00411                 {
00412                     it->m_selected = false;
00413                 }
00414                 // Select the new item
00415                 if( it != m_rTree.end() )
00416                 {
00417                     it->m_selected = true;
00418                     m_pLastSelected = &*it;
00419                 }
00420             }
00421         }
00422 
00423         else if( rEvent.getAsString().find( "mouse:left:dblclick" ) !=
00424                  string::npos )
00425         {
00426             it = findItemAtPos(yPos);
00427             if( it != m_rTree.end() )
00428             {
00429                // Execute the action associated to this item
00430                m_rTree.action( &*it );
00431             }
00432         }
00433 
00434         // Redraw the control
00435         makeImage();
00436         notifyLayout();
00437     }
00438 
00439     else if( rEvent.getAsString().find( "scroll" ) != string::npos )
00440     {
00441         int direction = ((EvtScroll&)rEvent).getDirection();
00442 
00443         double percentage = m_rTree.getPositionVar().get();
00444         double step = 2.0 / (double)m_rTree.visibleItems();
00445         if( direction == EvtScroll::kUp )
00446         {
00447             percentage += step;
00448         }
00449         else
00450         {
00451             percentage -= step;
00452         }
00453         m_rTree.getPositionVar().set( percentage );
00454     }
00455 }
00456 
00457 bool CtrlTree::mouseOver( int x, int y ) const
00458 {
00459     const Position *pPos = getPosition();
00460     return ( pPos
00461        ? x >= 0 && x <= pPos->getWidth() && y >= 0 && y <= pPos->getHeight()
00462        : false);
00463 }
00464 
00465 void CtrlTree::draw( OSGraphics &rImage, int xDest, int yDest )
00466 {
00467     if( m_pImage )
00468     {
00469         rImage.drawGraphics( *m_pImage, 0, 0, xDest, yDest );
00470     }
00471 }
00472 
00473 void CtrlTree::autoScroll()
00474 {
00475     // Find the current playing stream
00476     int playIndex = 0;
00477     VarTree::Iterator it;
00478     for( it = m_rTree.begin(); it != m_rTree.end();
00479          it = m_rTree.getNextVisibleItem( it ) )
00480     {
00481         if( it->m_playing ) break;
00482         playIndex++;
00483     }
00484 
00485     if( it == m_rTree.end() ) return;
00486 
00487     // Find  m_lastPos
00488     int lastPosIndex = 0;
00489     for( it = m_rTree.begin(); it != m_rTree.end();
00490          it = m_rTree.getNextVisibleItem( it ) )
00491     {
00492         if( it == m_lastPos ) break;
00493         lastPosIndex++;
00494     }
00495 
00496     if( it == m_rTree.end() ) return;
00497 
00498 
00499     if( it != m_rTree.end()
00500         && ( playIndex < lastPosIndex
00501            || playIndex > lastPosIndex + maxItems() ) )
00502     {
00503         // Scroll to have the playing stream visible
00504         VarPercent &rVarPos = m_rTree.getPositionVar();
00505         rVarPos.set( 1.0 - (double)playIndex / (double)m_rTree.visibleItems() );
00506     }
00507     else
00508     {
00509         makeImage();
00510         notifyLayout();
00511     }
00512 }
00513 
00514 void CtrlTree::makeImage()
00515 {
00516     if( m_pImage )
00517     {
00518         delete m_pImage;
00519     }
00520 
00521     // Get the size of the control
00522     const Position *pPos = getPosition();
00523     if( !pPos )
00524     {
00525         return;
00526     }
00527     int width = pPos->getWidth();
00528     int height = pPos->getHeight();
00529 
00530     int i_itemHeight = itemHeight();
00531 
00532     // Create an image
00533     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
00534     m_pImage = pOsFactory->createOSGraphics( width, height );
00535 
00536     VarTree::Iterator it = m_lastPos;
00537 
00538     if( m_pBgBitmap )
00539     {
00540         // Draw the background bitmap
00541         ScaledBitmap bmp( getIntf(), *m_pBgBitmap, width, height );
00542         m_pImage->drawBitmap( bmp, 0, 0 );
00543 
00544         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
00545         {
00546             if( it != m_rTree.end() )
00547             {
00548                 if( (*it).m_selected )
00549                 {
00550                     int rectHeight = __MIN( i_itemHeight, height - yPos );
00551                     m_pImage->fillRect( 0, yPos, width, rectHeight,
00552                                         m_selColor );
00553                 }
00554                 it = m_rTree.getNextVisibleItem( it );
00555             }
00556         }
00557     }
00558     else
00559     {
00560         // FIXME (TRYME)
00561         // Fill background with background color
00562         uint32_t bgColor = m_bgColor1;
00563         m_pImage->fillRect( 0, 0, width, height, bgColor );
00564         for( int yPos = 0; yPos < height; yPos += i_itemHeight )
00565         {
00566             int rectHeight = __MIN( i_itemHeight, height - yPos );
00567             if( it != m_rTree.end() )
00568             {
00569                 uint32_t color = ( it->m_selected ? m_selColor : bgColor );
00570                 m_pImage->fillRect( 0, yPos, width, rectHeight, color );
00571                 it = m_rTree.getNextVisibleItem( it );
00572             }
00573             else
00574             {
00575                 m_pImage->fillRect( 0, yPos, width, rectHeight, bgColor );
00576             }
00577             bgColor = ( bgColor == m_bgColor1 ? m_bgColor2 : m_bgColor1 );
00578         }
00579     }
00580 //    fprintf( stderr, "done\n");
00581 
00582     int bitmapWidth = itemImageWidth();
00583 
00584     int yPos = 0;
00585     it = m_lastPos;
00586     while( it != m_rTree.end() && yPos < height )
00587     {
00588         const GenericBitmap *m_pCurBitmap;
00589         UString *pStr = (UString*)(it->m_cString.get());
00590         uint32_t color = ( it->m_playing ? m_playColor : m_fgColor );
00591         // Draw the text
00592         if( pStr != NULL )
00593         {
00594             int depth = it->depth();
00595             GenericBitmap *pText = m_rFont.drawString( *pStr, color, width - bitmapWidth * depth );
00596             if( !pText )
00597             {
00598                 return;
00599             }
00600             if( it->size() )
00601                 m_pCurBitmap = it->m_expanded ? m_pOpenBitmap : m_pClosedBitmap;
00602             else
00603                 m_pCurBitmap = m_pItemBitmap;
00604 
00605             if( m_pCurBitmap )
00606             {
00607                 int yPos2 = yPos+(i_itemHeight-m_pCurBitmap->getHeight()+1)/2;
00608                 m_pImage->drawBitmap( *m_pCurBitmap, 0, 0,
00609                                       bitmapWidth * (depth - 1 ), yPos2,
00610                                       m_pCurBitmap->getWidth(),
00611                                       __MIN( m_pCurBitmap->getHeight(),
00612                                              height -  yPos2), true );
00613             }
00614             else
00615             {
00616                 /* it would be nice to draw something */
00617             }
00618             yPos += i_itemHeight - pText->getHeight();
00619             int ySrc = 0;
00620             if( yPos < 0 )
00621             {
00622                 ySrc = - yPos;
00623                 yPos = 0;
00624             }
00625             int lineHeight = __MIN( pText->getHeight() - ySrc, height - yPos );
00626             m_pImage->drawBitmap( *pText, 0, ySrc, bitmapWidth * depth, yPos,
00627                                   pText->getWidth(),
00628                                   lineHeight, true );
00629             yPos += (pText->getHeight() - ySrc );
00630             delete pText;
00631         }
00632         it = m_rTree.getNextVisibleItem( it );
00633     }
00634 }
00635 
00636 VarTree::Iterator CtrlTree::findItemAtPos( int pos )
00637 {
00638     // The first item is m_lastPos.
00639     // We decrement pos as we try the other items, until pos == 0.
00640     VarTree::Iterator it;
00641     for( it = m_lastPos; it != m_rTree.end() && pos != 0;
00642          it = m_rTree.getNextVisibleItem( it ) )
00643     {
00644         pos--;
00645     }
00646 
00647     return it;
00648 }
00649 

Generated on Tue Dec 20 10:14:41 2005 for vlc-0.8.4a by  doxygen 1.4.2