]> git.sesse.net Git - vlc/blob - modules/gui/skins2/src/window_manager.cpp
Merge branch 'master' into lpcm_encoder
[vlc] / modules / gui / skins2 / src / window_manager.cpp
1 /*****************************************************************************
2  * window_manager.cpp
3  *****************************************************************************
4  * Copyright (C) 2003 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Cyril Deguet     <asmax@via.ecp.fr>
8  *          Olivier Teulière <ipkiss@via.ecp.fr>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #include "window_manager.hpp"
26 #include "generic_layout.hpp"
27 #include "generic_window.hpp"
28 #include "os_factory.hpp"
29 #include "anchor.hpp"
30 #include "tooltip.hpp"
31 #include "var_manager.hpp"
32
33
34 WindowManager::WindowManager( intf_thread_t *pIntf ):
35     SkinObject( pIntf ), m_magnet( 0 ), m_direction( kNone ),
36     m_maximizeRect(0, 0, 50, 50),
37     m_pTooltip( NULL ), m_pPopup( NULL )
38 {
39     // Create and register a variable for the "on top" status
40     VarManager *pVarManager = VarManager::instance( getIntf() );
41     m_cVarOnTop = VariablePtr( new VarBoolImpl( getIntf() ) );
42     pVarManager->registerVar( m_cVarOnTop, "vlc.isOnTop" );
43 }
44
45
46 WindowManager::~WindowManager()
47 {
48     delete m_pTooltip;
49 }
50
51
52 void WindowManager::registerWindow( TopWindow &rWindow )
53 {
54     // Add the window to the set
55     m_allWindows.insert( &rWindow );
56 }
57
58
59 void WindowManager::unregisterWindow( TopWindow &rWindow )
60 {
61     // Erase every possible reference to the window
62     m_allWindows.erase( &rWindow );
63     m_movingWindows.erase( &rWindow );
64     m_dependencies.erase( &rWindow );
65 }
66
67
68 void WindowManager::startMove( TopWindow &rWindow )
69 {
70     // Rebuild the set of moving windows
71     m_movingWindows.clear();
72     buildDependSet( m_movingWindows, &rWindow );
73
74     if( var_InheritBool( getIntf(), "skins2-transparency" ) )
75     {
76         // Change the opacity of the moving windows
77         WinSet_t::const_iterator it;
78         for( it = m_movingWindows.begin(); it != m_movingWindows.end(); it++ )
79         {
80             (*it)->setOpacity( m_moveAlpha );
81         }
82
83         // FIXME: We need to refresh the windows, because if 2 windows overlap
84         // and one of them becomes transparent, the other one is not refreshed
85         // automatically. I don't know why... -- Ipkiss
86         for( it = m_allWindows.begin(); it != m_allWindows.end(); it++ )
87         {
88             (*it)->refresh( 0, 0, (*it)->getWidth(), (*it)->getHeight() );
89         }
90     }
91 }
92
93
94 void WindowManager::stopMove()
95 {
96     WinSet_t::const_iterator itWin1, itWin2;
97     AncList_t::const_iterator itAnc1, itAnc2;
98
99     if( var_InheritBool( getIntf(), "skins2-transparency" ) )
100     {
101         // Restore the opacity of the moving windows
102         WinSet_t::const_iterator it;
103         for( it = m_movingWindows.begin(); it != m_movingWindows.end(); it++ )
104         {
105             (*it)->setOpacity( m_alpha );
106         }
107     }
108
109     // Delete the dependencies
110     m_dependencies.clear();
111
112     // Now we rebuild the dependencies.
113     // Iterate through all the windows
114     for( itWin1 = m_allWindows.begin(); itWin1 != m_allWindows.end(); itWin1++ )
115     {
116         // Get the anchors of the layout associated to the window
117         const AncList_t &ancList1 =
118             (*itWin1)->getActiveLayout().getAnchorList();
119
120         // Iterate through all the windows, starting with (*itWin1)
121         for( itWin2 = itWin1; itWin2 != m_allWindows.end(); itWin2++ )
122         {
123             // A window can't anchor itself...
124             if( (*itWin2) == (*itWin1) )
125                 continue;
126
127             // Now, check for anchoring between the 2 windows
128             const AncList_t &ancList2 =
129                 (*itWin2)->getActiveLayout().getAnchorList();
130             for( itAnc1 = ancList1.begin(); itAnc1 != ancList1.end(); itAnc1++ )
131             {
132                 for( itAnc2 = ancList2.begin();
133                      itAnc2 != ancList2.end(); itAnc2++ )
134                 {
135                     if( (*itAnc1)->isHanging( **itAnc2 ) )
136                     {
137                         // (*itWin1) anchors (*itWin2)
138                         m_dependencies[*itWin1].insert( *itWin2 );
139                     }
140                     else if( (*itAnc2)->isHanging( **itAnc1 ) )
141                     {
142                         // (*itWin2) anchors (*itWin1)
143                         m_dependencies[*itWin2].insert( *itWin1 );
144                     }
145                 }
146             }
147         }
148     }
149 }
150
151
152 void WindowManager::move( TopWindow &rWindow, int left, int top ) const
153 {
154     // Compute the real move offset
155     int xOffset = left - rWindow.getLeft();
156     int yOffset = top - rWindow.getTop();
157
158     // Check anchoring; this can change the values of xOffset and yOffset
159     checkAnchors( &rWindow, xOffset, yOffset );
160
161     // Move all the windows
162     WinSet_t::const_iterator it;
163     for( it = m_movingWindows.begin(); it != m_movingWindows.end(); it++ )
164     {
165         (*it)->move( (*it)->getLeft() + xOffset, (*it)->getTop() + yOffset );
166     }
167 }
168
169
170 void WindowManager::startResize( GenericLayout &rLayout, Direction_t direction )
171 {
172     m_direction = direction;
173
174     // Rebuild the set of moving windows.
175     // From the resized window, we only take into account the anchors which
176     // are mobile with the current type of resizing, and that are hanging a
177     // window. The hanged windows will come will all their dependencies.
178
179     m_resizeMovingE.clear();
180     m_resizeMovingS.clear();
181     m_resizeMovingSE.clear();
182
183     WinSet_t::const_iterator itWin;
184     AncList_t::const_iterator itAnc1, itAnc2;
185     // Get the anchors of the layout
186     const AncList_t &ancList1 = rLayout.getAnchorList();
187
188     // Iterate through all the hanged windows
189     for( itWin = m_dependencies[rLayout.getWindow()].begin();
190          itWin != m_dependencies[rLayout.getWindow()].end(); itWin++ )
191     {
192         // Now, check for anchoring between the 2 windows
193         const AncList_t &ancList2 =
194             (*itWin)->getActiveLayout().getAnchorList();
195         for( itAnc1 = ancList1.begin(); itAnc1 != ancList1.end(); itAnc1++ )
196         {
197             for( itAnc2 = ancList2.begin();
198                  itAnc2 != ancList2.end(); itAnc2++ )
199             {
200                 if( (*itAnc1)->isHanging( **itAnc2 ) )
201                 {
202                     // Add the dependencies of the hanged window to one of the
203                     // lists of moving windows
204                     Position::Ref_t aRefPos =
205                         (*itAnc1)->getPosition().getRefLeftTop();
206                     if( aRefPos == Position::kRightTop )
207                         buildDependSet( m_resizeMovingE, *itWin );
208                     else if( aRefPos == Position::kLeftBottom )
209                         buildDependSet( m_resizeMovingS, *itWin );
210                     else if( aRefPos == Position::kRightBottom )
211                         buildDependSet( m_resizeMovingSE, *itWin );
212                     break;
213                 }
214             }
215         }
216     }
217
218     // The checkAnchors() method will need to have m_movingWindows properly set
219     // so let's insert in it the contents of the other sets
220     m_movingWindows.clear();
221     m_movingWindows.insert( rLayout.getWindow() );
222     m_movingWindows.insert( m_resizeMovingE.begin(), m_resizeMovingE.end() );
223     m_movingWindows.insert( m_resizeMovingS.begin(), m_resizeMovingS.end() );
224     m_movingWindows.insert( m_resizeMovingSE.begin(), m_resizeMovingSE.end() );
225 }
226
227
228 void WindowManager::stopResize()
229 {
230     // Nothing different from stopMove(), luckily
231     stopMove();
232 }
233
234
235 void WindowManager::resize( GenericLayout &rLayout,
236                             int width, int height ) const
237 {
238     // TODO: handle anchored windows
239     // Compute the real resizing offset
240     int xOffset = width - rLayout.getWidth();
241     int yOffset = height - rLayout.getHeight();
242
243     // Check anchoring; this can change the values of xOffset and yOffset
244     checkAnchors( rLayout.getWindow(), xOffset, yOffset );
245     if( m_direction == kResizeS )
246         xOffset = 0;
247     if( m_direction == kResizeE )
248         yOffset = 0;
249
250     int newWidth = rLayout.getWidth() + xOffset;
251     int newHeight = rLayout.getHeight() + yOffset;
252
253     // Check boundaries
254     if( newWidth < rLayout.getMinWidth() )
255     {
256         newWidth = rLayout.getMinWidth();
257     }
258     if( newWidth > rLayout.getMaxWidth() )
259     {
260         newWidth = rLayout.getMaxWidth();
261     }
262     if( newHeight < rLayout.getMinHeight() )
263     {
264         newHeight = rLayout.getMinHeight();
265     }
266     if( newHeight > rLayout.getMaxHeight() )
267     {
268         newHeight = rLayout.getMaxHeight();
269     }
270
271     if( newWidth == rLayout.getWidth() && newHeight == rLayout.getHeight() )
272     {
273         return;
274     }
275
276     // New offset, after the last corrections
277     int xNewOffset = newWidth - rLayout.getWidth();
278     int yNewOffset = newHeight - rLayout.getHeight();
279
280     // Do the actual resizing
281     rLayout.resize( newWidth, newHeight );
282
283     // Move all the anchored windows
284     WinSet_t::const_iterator it;
285     if( m_direction == kResizeE ||
286         m_direction == kResizeSE )
287     {
288         for( it = m_resizeMovingE.begin(); it != m_resizeMovingE.end(); it++ )
289         {
290             (*it)->move( (*it)->getLeft() + xNewOffset,
291                          (*it)->getTop() );
292         }
293     }
294     if( m_direction == kResizeE ||
295         m_direction == kResizeSE )
296     {
297         for( it = m_resizeMovingS.begin(); it != m_resizeMovingS.end(); it++ )
298         {
299             (*it)->move( (*it)->getLeft(),
300                          (*it)->getTop( )+ yNewOffset );
301         }
302     }
303     if( m_direction == kResizeE ||
304         m_direction == kResizeS ||
305         m_direction == kResizeSE )
306     {
307         for( it = m_resizeMovingSE.begin(); it != m_resizeMovingSE.end(); it++ )
308         {
309             (*it)->move( (*it)->getLeft() + xNewOffset,
310                          (*it)->getTop() + yNewOffset );
311         }
312     }
313 }
314
315
316 void WindowManager::maximize( TopWindow &rWindow )
317 {
318     // Save the current position/size of the window, to be able to restore it
319     m_maximizeRect = SkinsRect( rWindow.getLeft(), rWindow.getTop(),
320                                rWindow.getLeft() + rWindow.getWidth(),
321                                rWindow.getTop() + rWindow.getHeight() );
322
323     SkinsRect workArea = OSFactory::instance( getIntf() )->getWorkArea();
324     // Move the window
325     startMove( rWindow );
326     move( rWindow, workArea.getLeft(), workArea.getTop() );
327     stopMove();
328     // Now resize it
329     // FIXME: Ugly const_cast
330     GenericLayout &rLayout = (GenericLayout&)rWindow.getActiveLayout();
331     startResize( rLayout, kResizeSE );
332     resize( rLayout, workArea.getWidth(), workArea.getHeight() );
333     stopResize();
334     rWindow.m_pVarMaximized->set( true );
335
336     // Make the window unmovable by unregistering it
337 //     unregisterWindow( rWindow );
338 }
339
340
341 void WindowManager::unmaximize( TopWindow &rWindow )
342 {
343     // Register the window to allow moving it
344 //     registerWindow( rWindow );
345
346     // Resize the window
347     // FIXME: Ugly const_cast
348     GenericLayout &rLayout = (GenericLayout&)rWindow.getActiveLayout();
349     startResize( rLayout, kResizeSE );
350     resize( rLayout, m_maximizeRect.getWidth(), m_maximizeRect.getHeight() );
351     stopResize();
352     // Now move it
353     startMove( rWindow );
354     move( rWindow, m_maximizeRect.getLeft(), m_maximizeRect.getTop() );
355     stopMove();
356     rWindow.m_pVarMaximized->set( false );
357 }
358
359
360 void WindowManager::synchVisibility() const
361 {
362     WinSet_t::const_iterator it;
363     for( it = m_allWindows.begin(); it != m_allWindows.end(); it++ )
364     {
365         // Show the window if it has to be visible
366         if( (*it)->getVisibleVar().get() )
367         {
368             (*it)->innerShow();
369         }
370     }
371 }
372
373
374 void WindowManager::saveVisibility()
375 {
376     WinSet_t::const_iterator it;
377     m_savedWindows.clear();
378     for( it = m_allWindows.begin(); it != m_allWindows.end(); it++ )
379     {
380         // Remember the window if it is visible
381         if( (*it)->getVisibleVar().get() )
382         {
383             m_savedWindows.insert( *it );
384         }
385     }
386 }
387
388
389 void WindowManager::restoreVisibility() const
390 {
391     // Warning in case we never called saveVisibility()
392     if( m_savedWindows.size() == 0 )
393     {
394         msg_Warn( getIntf(), "restoring visibility for no window" );
395     }
396
397     WinSet_t::const_iterator it;
398     for( it = m_savedWindows.begin(); it != m_savedWindows.end(); it++)
399     {
400         (*it)->show();
401     }
402 }
403
404
405 void WindowManager::raiseAll() const
406 {
407     // Raise all the windows
408     WinSet_t::const_iterator it;
409     for( it = m_allWindows.begin(); it != m_allWindows.end(); it++ )
410     {
411         (*it)->raise();
412     }
413 }
414
415
416 void WindowManager::showAll( bool firstTime ) const
417 {
418     // Show all the windows
419     WinSet_t::const_iterator it;
420     for( it = m_allWindows.begin(); it != m_allWindows.end(); it++ )
421     {
422         // When the theme is opened for the first time,
423         // only show the window if set as visible in the XML
424         if( (*it)->isVisible() || !firstTime )
425         {
426             (*it)->show();
427         }
428     }
429 }
430
431
432 void WindowManager::hideAll() const
433 {
434     WinSet_t::const_iterator it;
435     for( it = m_allWindows.begin(); it != m_allWindows.end(); it++ )
436     {
437         (*it)->hide();
438     }
439 }
440
441
442 void WindowManager::setOnTop( bool b_ontop )
443 {
444     // Update the boolean variable
445     VarBoolImpl *pVarOnTop = (VarBoolImpl*)m_cVarOnTop.get();
446     pVarOnTop->set( b_ontop );
447
448     // set/unset the "on top" status
449     WinSet_t::const_iterator it;
450     for( it = m_allWindows.begin(); it != m_allWindows.end(); it++ )
451     {
452         (*it)->toggleOnTop( b_ontop );
453     }
454 }
455
456
457 void WindowManager::toggleOnTop()
458 {
459     VarBoolImpl *pVarOnTop = (VarBoolImpl*)m_cVarOnTop.get();
460
461     setOnTop( !pVarOnTop->get() );
462 }
463
464
465 void WindowManager::buildDependSet( WinSet_t &rWinSet,
466                                     TopWindow *pWindow )
467 {
468     // pWindow is in the set
469     rWinSet.insert( pWindow );
470
471     // Iterate through the anchored windows
472     const WinSet_t &anchored = m_dependencies[pWindow];
473     WinSet_t::const_iterator iter;
474     for( iter = anchored.begin(); iter != anchored.end(); iter++ )
475     {
476         // Check that the window isn't already in the set before adding it
477         if( rWinSet.find( *iter ) == rWinSet.end() )
478         {
479             buildDependSet( rWinSet, *iter );
480         }
481     }
482 }
483
484
485 void WindowManager::checkAnchors( TopWindow *pWindow,
486                                   int &xOffset, int &yOffset ) const
487 {
488     WinSet_t::const_iterator itMov, itSta;
489     AncList_t::const_iterator itAncMov, itAncSta;
490
491     // Check magnetism with screen edges first (actually it is the work area)
492     SkinsRect workArea = OSFactory::instance( getIntf() )->getWorkArea();
493     // Iterate through the moving windows
494     for( itMov = m_movingWindows.begin();
495          itMov != m_movingWindows.end(); itMov++ )
496     {
497         // Skip the invisible windows
498         if( ! (*itMov)->getVisibleVar().get() )
499         {
500             continue;
501         }
502
503         int newLeft = (*itMov)->getLeft() + xOffset;
504         int newTop = (*itMov)->getTop() + yOffset;
505         if( newLeft > workArea.getLeft() - m_magnet &&
506             newLeft < workArea.getLeft() + m_magnet )
507         {
508             xOffset = workArea.getLeft() - (*itMov)->getLeft();
509         }
510         if( newTop > workArea.getTop() - m_magnet &&
511             newTop < workArea.getTop() + m_magnet )
512         {
513             yOffset = workArea.getTop() - (*itMov)->getTop();
514         }
515         int right = workArea.getLeft() + workArea.getWidth();
516         if( newLeft + (*itMov)->getWidth() > right - m_magnet &&
517             newLeft + (*itMov)->getWidth() < right + m_magnet )
518         {
519             xOffset = right - (*itMov)->getLeft() - (*itMov)->getWidth();
520         }
521         int bottom = workArea.getTop() + workArea.getHeight();
522         if( newTop + (*itMov)->getHeight() > bottom - m_magnet &&
523             newTop + (*itMov)->getHeight() <  bottom + m_magnet )
524         {
525             yOffset =  bottom - (*itMov)->getTop() - (*itMov)->getHeight();
526         }
527     }
528
529     // Iterate through the moving windows
530     for( itMov = m_movingWindows.begin();
531          itMov != m_movingWindows.end(); itMov++ )
532     {
533         // Skip the invisible windows
534         if( ! (*itMov)->getVisibleVar().get() )
535         {
536             continue;
537         }
538
539         // Get the anchors in the main layout of this moving window
540         const AncList_t &movAnchors =
541             (*itMov)->getActiveLayout().getAnchorList();
542
543         // Iterate through the static windows
544         for( itSta = m_allWindows.begin();
545              itSta != m_allWindows.end(); itSta++ )
546         {
547             // Skip the moving windows and the invisible ones
548             if( m_movingWindows.find( (*itSta) ) != m_movingWindows.end() ||
549                 ! (*itSta)->getVisibleVar().get() )
550             {
551                 continue;
552             }
553
554             // Get the anchors in the main layout of this static window
555             const AncList_t &staAnchors =
556                 (*itSta)->getActiveLayout().getAnchorList();
557
558             // Check if there is an anchoring between one of the movAnchors
559             // and one of the staAnchors
560             for( itAncMov = movAnchors.begin();
561                  itAncMov != movAnchors.end(); itAncMov++ )
562             {
563                 for( itAncSta = staAnchors.begin();
564                      itAncSta != staAnchors.end(); itAncSta++ )
565                 {
566                     if( (*itAncSta)->canHang( **itAncMov, xOffset, yOffset ) )
567                     {
568                         // We have found an anchoring!
569                         // There is nothing to do here, since xOffset and
570                         // yOffset are automatically modified by canHang()
571
572                         // Don't check the other anchors, one is enough...
573                         return;
574                     }
575                     else
576                     {
577                         // Temporary variables
578                         int xOffsetTemp = -xOffset;
579                         int yOffsetTemp = -yOffset;
580                         if( (*itAncMov)->canHang( **itAncSta, xOffsetTemp,
581                                                   yOffsetTemp ) )
582                         {
583                             // We have found an anchoring!
584                             // xOffsetTemp and yOffsetTemp have been updated,
585                             // we just need to change xOffset and yOffset
586                             xOffset = -xOffsetTemp;
587                             yOffset = -yOffsetTemp;
588
589                             // Don't check the other anchors, one is enough...
590                             return;
591                         }
592                     }
593                 }
594             }
595         }
596     }
597 }
598
599
600 void WindowManager::createTooltip( const GenericFont &rTipFont )
601 {
602     // Create the tooltip window
603     if( !m_pTooltip )
604     {
605         m_pTooltip = new Tooltip( getIntf(), rTipFont, 500 );
606     }
607     else
608     {
609         msg_Warn( getIntf(), "tooltip already created!" );
610     }
611 }
612
613
614 void WindowManager::showTooltip()
615 {
616     if( m_pTooltip )
617     {
618         m_pTooltip->show();
619     }
620 }
621
622
623 void WindowManager::hideTooltip()
624 {
625     if( m_pTooltip )
626     {
627         m_pTooltip->hide();
628     }
629 }
630
631
632 void WindowManager::addLayout( TopWindow &rWindow, GenericLayout &rLayout )
633 {
634     rWindow.setActiveLayout( &rLayout );
635 }
636
637
638 void WindowManager::setActiveLayout( TopWindow &rWindow,
639                                      GenericLayout &rLayout )
640 {
641     rWindow.setActiveLayout( &rLayout );
642     // Rebuild the dependencies
643     stopMove();
644 }
645