1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <tools/poly.hxx>
21 
22 #include <vcl/event.hxx>
23 #include <vcl/split.hxx>
24 #include <vcl/svapp.hxx>
25 #include <vcl/syswin.hxx>
26 #include <vcl/taskpanelist.hxx>
27 #include <vcl/lineinfo.hxx>
28 #include <vcl/settings.hxx>
29 #include <vcl/ptrstyle.hxx>
30 
31 #include <rtl/instance.hxx>
32 
33 #include <window.h>
34 
35 namespace
36 {
37     struct ImplBlackWall
38         : public rtl::StaticWithInit<Wallpaper, ImplBlackWall> {
operator ()__anon5b2352350111::ImplBlackWall39         Wallpaper operator () () {
40             return Wallpaper(COL_BLACK);
41         }
42     };
43     struct ImplWhiteWall
44         : public rtl::StaticWithInit<Wallpaper, ImplWhiteWall> {
operator ()__anon5b2352350111::ImplWhiteWall45         Wallpaper operator () () {
46             return Wallpaper(COL_LIGHTGRAY);
47         }
48     };
49 }
50 
51 // Should only be called from an ImplInit method for initialization or
52 // after checking bNew is different from the current mbHorzSplit value.
53 // The public method that does that check is Splitter::SetHorizontal().
ImplInitHorVer(bool bNew)54 void Splitter::ImplInitHorVer(bool bNew)
55 {
56     mbHorzSplit = bNew;
57 
58     PointerStyle ePointerStyle;
59     const StyleSettings& rSettings = GetSettings().GetStyleSettings();
60 
61     if ( mbHorzSplit )
62     {
63         ePointerStyle = PointerStyle::HSplit;
64         SetSizePixel( Size( StyleSettings::GetSplitSize(), rSettings.GetScrollBarSize() ) );
65     }
66     else
67     {
68         ePointerStyle = PointerStyle::VSplit;
69         SetSizePixel( Size( rSettings.GetScrollBarSize(), StyleSettings::GetSplitSize() ) );
70     }
71 
72     SetPointer( ePointerStyle );
73 }
74 
ImplInit(vcl::Window * pParent,WinBits nWinStyle)75 void Splitter::ImplInit( vcl::Window* pParent, WinBits nWinStyle )
76 {
77     Window::ImplInit( pParent, nWinStyle, nullptr );
78 
79     mpRefWin = pParent;
80 
81     ImplInitHorVer(nWinStyle & WB_HSCROLL);
82 
83     if( GetSettings().GetStyleSettings().GetFaceColor().IsDark() )
84         SetBackground( ImplWhiteWall::get() );
85     else
86         SetBackground( ImplBlackWall::get() );
87 
88     TaskPaneList *pTList = GetSystemWindow()->GetTaskPaneList();
89     pTList->AddWindow( this );
90 }
91 
ImplSplitMousePos(Point & rPos)92 void Splitter::ImplSplitMousePos( Point& rPos )
93 {
94     if ( mbHorzSplit )
95     {
96         if ( rPos.X() > maDragRect.Right()-1 )
97             rPos.setX( maDragRect.Right()-1 );
98         if ( rPos.X() < maDragRect.Left()+1 )
99             rPos.setX( maDragRect.Left()+1 );
100     }
101     else
102     {
103         if ( rPos.Y() > maDragRect.Bottom()-1 )
104             rPos.setY( maDragRect.Bottom()-1 );
105         if ( rPos.Y() < maDragRect.Top()+1 )
106             rPos.setY( maDragRect.Top()+1 );
107     }
108 }
109 
ImplDrawSplitter()110 void Splitter::ImplDrawSplitter()
111 {
112     tools::Rectangle aInvRect( maDragRect );
113 
114     if ( mbHorzSplit )
115     {
116         aInvRect.SetLeft( maDragPos.X() - 1 );
117         aInvRect.SetRight( maDragPos.X() + 1 );
118     }
119     else
120     {
121         aInvRect.SetTop( maDragPos.Y() - 1 );
122         aInvRect.SetBottom( maDragPos.Y() + 1 );
123     }
124 
125     mpRefWin->InvertTracking( mpRefWin->PixelToLogic(aInvRect), ShowTrackFlags::Split );
126 }
127 
Splitter(vcl::Window * pParent,WinBits nStyle)128 Splitter::Splitter( vcl::Window* pParent, WinBits nStyle ) :
129     Window( WindowType::SPLITTER ),
130     mpRefWin( nullptr ),
131     mnSplitPos( 0 ),
132     mnLastSplitPos( 0 ),
133     mnStartSplitPos( 0 ),
134     mbDragFull( false ),
135     mbKbdSplitting( false ),
136     mbInKeyEvent( false ),
137     mnKeyboardStepSize( SPLITTER_DEFAULTSTEPSIZE )
138 {
139     ImplGetWindowImpl()->mbSplitter        = true;
140 
141     ImplInit( pParent, nStyle );
142 
143     GetOutDev()->SetLineColor();
144     GetOutDev()->SetFillColor();
145 }
146 
~Splitter()147 Splitter::~Splitter()
148 {
149     disposeOnce();
150 }
151 
dispose()152 void Splitter::dispose()
153 {
154     SystemWindow *pSysWin = GetSystemWindow();
155     if(pSysWin)
156     {
157         TaskPaneList *pTList = pSysWin->GetTaskPaneList();
158         pTList->RemoveWindow(this);
159     }
160     mpRefWin.clear();
161     Window::dispose();
162 }
163 
SetHorizontal(bool bNew)164 void Splitter::SetHorizontal(bool bNew)
165 {
166     if(bNew != mbHorzSplit)
167     {
168         ImplInitHorVer(bNew);
169     }
170 }
171 
SetKeyboardStepSize(tools::Long nStepSize)172 void Splitter::SetKeyboardStepSize( tools::Long nStepSize )
173 {
174     mnKeyboardStepSize = nStepSize;
175 }
176 
ImplFindSibling()177 Splitter* Splitter::ImplFindSibling()
178 {
179     // look for another splitter with the same parent but different orientation
180     vcl::Window *pWin = GetParent()->GetWindow( GetWindowType::FirstChild );
181     Splitter *pSplitter = nullptr;
182     while( pWin )
183     {
184         if( pWin->ImplIsSplitter() )
185         {
186             pSplitter = static_cast<Splitter*>(pWin);
187             if( pSplitter != this && IsHorizontal() != pSplitter->IsHorizontal() )
188                 return pSplitter;
189         }
190         pWin = pWin->GetWindow( GetWindowType::Next );
191     }
192     return nullptr;
193 }
194 
ImplSplitterActive()195 bool Splitter::ImplSplitterActive()
196 {
197     // is splitter in document or at scrollbar handle ?
198 
199     bool bActive = true;
200     const StyleSettings& rSettings = GetSettings().GetStyleSettings();
201     tools::Long nA = rSettings.GetScrollBarSize();
202     tools::Long nB = StyleSettings::GetSplitSize();
203 
204     Size aSize = GetOutDev()->GetOutputSize();
205     if ( mbHorzSplit )
206     {
207         if( aSize.Width() == nB && aSize.Height() == nA )
208             bActive = false;
209     }
210     else
211     {
212         if( aSize.Width() == nA && aSize.Height() == nB )
213             bActive = false;
214     }
215     return bActive;
216 }
217 
MouseButtonDown(const MouseEvent & rMEvt)218 void Splitter::MouseButtonDown( const MouseEvent& rMEvt )
219 {
220     if ( rMEvt.GetClicks() == 2 )
221     {
222         if ( mnLastSplitPos != mnSplitPos )
223         {
224             StartSplit();
225             Point aPos = rMEvt.GetPosPixel();
226             if ( mbHorzSplit )
227                 aPos.setX( mnLastSplitPos );
228             else
229                 aPos.setY( mnLastSplitPos );
230             ImplSplitMousePos( aPos );
231             tools::Long nTemp = mnSplitPos;
232             if ( mbHorzSplit )
233                 SetSplitPosPixel( aPos.X() );
234             else
235                 SetSplitPosPixel( aPos.Y() );
236             mnLastSplitPos = nTemp;
237             Split();
238             EndSplit();
239         }
240     }
241     else
242         StartDrag();
243 }
244 
Tracking(const TrackingEvent & rTEvt)245 void Splitter::Tracking( const TrackingEvent& rTEvt )
246 {
247     if ( rTEvt.IsTrackingEnded() )
248     {
249         if ( !mbDragFull )
250             ImplDrawSplitter();
251 
252         if ( !rTEvt.IsTrackingCanceled() )
253         {
254             tools::Long nNewPos;
255             if ( mbHorzSplit )
256                 nNewPos = maDragPos.X();
257             else
258                 nNewPos = maDragPos.Y();
259             if ( nNewPos != mnStartSplitPos )
260             {
261                 SetSplitPosPixel( nNewPos );
262                 mnLastSplitPos = 0;
263                 Split();
264             }
265             EndSplit();
266         }
267         else if ( mbDragFull )
268         {
269             SetSplitPosPixel( mnStartSplitPos );
270             Split();
271         }
272         mnStartSplitPos = 0;
273     }
274     else
275     {
276         //Point aNewPos = mpRefWin->ScreenToOutputPixel( OutputToScreenPixel( rTEvt.GetMouseEvent().GetPosPixel() ) );
277         Point aNewPos = mpRefWin->NormalizedScreenToOutputPixel( OutputToNormalizedScreenPixel( rTEvt.GetMouseEvent().GetPosPixel() ) );
278         ImplSplitMousePos( aNewPos );
279 
280         if ( mbHorzSplit )
281         {
282             if ( aNewPos.X() == maDragPos.X() )
283                 return;
284         }
285         else
286         {
287             if ( aNewPos.Y() == maDragPos.Y() )
288                 return;
289         }
290 
291         if ( mbDragFull )
292         {
293             maDragPos = aNewPos;
294             tools::Long nNewPos;
295             if ( mbHorzSplit )
296                 nNewPos = maDragPos.X();
297             else
298                 nNewPos = maDragPos.Y();
299             if ( nNewPos != mnSplitPos )
300             {
301                 SetSplitPosPixel( nNewPos );
302                 mnLastSplitPos = 0;
303                 Split();
304             }
305 
306             GetParent()->PaintImmediately();
307         }
308         else
309         {
310             ImplDrawSplitter();
311             maDragPos = aNewPos;
312             ImplDrawSplitter();
313         }
314     }
315 }
316 
ImplKbdTracking(vcl::KeyCode aKeyCode)317 void Splitter::ImplKbdTracking( vcl::KeyCode aKeyCode )
318 {
319     sal_uInt16 nCode = aKeyCode.GetCode();
320     if ( nCode == KEY_ESCAPE || nCode == KEY_RETURN )
321     {
322         if( !mbKbdSplitting )
323             return;
324         else
325             mbKbdSplitting = false;
326 
327         if ( nCode != KEY_ESCAPE )
328         {
329             tools::Long nNewPos;
330             if ( mbHorzSplit )
331                 nNewPos = maDragPos.X();
332             else
333                 nNewPos = maDragPos.Y();
334             if ( nNewPos != mnStartSplitPos )
335             {
336                 SetSplitPosPixel( nNewPos );
337                 mnLastSplitPos = 0;
338                 Split();
339             }
340         }
341         else
342         {
343             SetSplitPosPixel( mnStartSplitPos );
344             Split();
345             EndSplit();
346         }
347         mnStartSplitPos = 0;
348     }
349     else
350     {
351         Point aNewPos;
352         Size aSize = mpRefWin->GetOutDev()->GetOutputSize();
353         Point aPos = GetPosPixel();
354         // depending on the position calc allows continuous moves or snaps to row/columns
355         // continuous mode is active when position is at the origin or end of the splitter
356         // otherwise snap mode is active
357         // default here is snap, holding shift sets continuous mode
358         if( mbHorzSplit )
359             aNewPos = Point( ImplSplitterActive() ? aPos.X() : mnSplitPos, aKeyCode.IsShift() ? 0 : aSize.Height()/2);
360         else
361             aNewPos = Point( aKeyCode.IsShift() ? 0 : aSize.Width()/2, ImplSplitterActive() ? aPos.Y() : mnSplitPos );
362 
363         Point aOldWindowPos = GetPosPixel();
364 
365         int maxiter = 500;  // avoid endless loop
366         int delta=0;
367         int delta_step = mbHorzSplit  ? aSize.Width()/10 : aSize.Height()/10;
368 
369         // use the specified step size if it was set
370         if( mnKeyboardStepSize != SPLITTER_DEFAULTSTEPSIZE )
371             delta_step = mnKeyboardStepSize;
372 
373         while( maxiter-- && aOldWindowPos == GetPosPixel() )
374         {
375             // inc/dec position until application performs changes
376             // thus a single key press really moves the splitter
377             if( aKeyCode.IsShift() )
378                 delta++;
379             else
380                 delta += delta_step;
381 
382             switch( nCode )
383             {
384             case KEY_LEFT:
385                 aNewPos.AdjustX( -delta );
386                 break;
387             case KEY_RIGHT:
388                 aNewPos.AdjustX(delta );
389                 break;
390             case KEY_UP:
391                 aNewPos.AdjustY( -delta );
392                 break;
393             case KEY_DOWN:
394                 aNewPos.AdjustY(delta );
395                 break;
396             default:
397                 maxiter = 0;    // leave loop
398                 break;
399             }
400             ImplSplitMousePos( aNewPos );
401 
402             if ( mbHorzSplit )
403             {
404                 if ( aNewPos.X() == maDragPos.X() )
405                     continue;
406             }
407             else
408             {
409                 if ( aNewPos.Y() == maDragPos.Y() )
410                     continue;
411             }
412 
413             maDragPos = aNewPos;
414             tools::Long nNewPos;
415             if ( mbHorzSplit )
416                 nNewPos = maDragPos.X();
417             else
418                 nNewPos = maDragPos.Y();
419             if ( nNewPos != mnSplitPos )
420             {
421                 SetSplitPosPixel( nNewPos );
422                 mnLastSplitPos = 0;
423                 Split();
424             }
425             GetParent()->PaintImmediately();
426         }
427     }
428 }
429 
StartSplit()430 void Splitter::StartSplit()
431 {
432     maStartSplitHdl.Call( this );
433 }
434 
Split()435 void Splitter::Split()
436 {
437     maSplitHdl.Call( this );
438 }
439 
EndSplit()440 void Splitter::EndSplit()
441 {
442     maEndSplitHdl.Call( this );
443 }
444 
SetDragRectPixel(const tools::Rectangle & rDragRect,vcl::Window * _pRefWin)445 void Splitter::SetDragRectPixel( const tools::Rectangle& rDragRect, vcl::Window* _pRefWin )
446 {
447     maDragRect = rDragRect;
448     if ( !_pRefWin )
449         mpRefWin = GetParent();
450     else
451         mpRefWin = _pRefWin;
452 }
453 
SetSplitPosPixel(tools::Long nNewPos)454 void Splitter::SetSplitPosPixel( tools::Long nNewPos )
455 {
456     mnSplitPos = nNewPos;
457 }
458 
StartDrag()459 void Splitter::StartDrag()
460 {
461     if ( IsTracking() )
462         return;
463 
464     StartSplit();
465 
466     // Tracking starten
467     StartTracking();
468 
469     // Start-Position ermitteln
470     maDragPos = mpRefWin->GetPointerPosPixel();
471     ImplSplitMousePos( maDragPos );
472     if ( mbHorzSplit )
473         mnStartSplitPos = maDragPos.X();
474     else
475         mnStartSplitPos = maDragPos.Y();
476 
477     mbDragFull = bool(Application::GetSettings().GetStyleSettings().GetDragFullOptions() & DragFullOptions::Split);
478     if ( !mbDragFull )
479         ImplDrawSplitter();
480 }
481 
ImplStartKbdSplitting()482 void Splitter::ImplStartKbdSplitting()
483 {
484     if( mbKbdSplitting )
485         return;
486 
487     mbKbdSplitting = true;
488 
489     StartSplit();
490 
491     // determine start position
492     // because we have no mouse position we take either the position
493     // of the splitter window or the last split position
494     // the other coordinate is just the center of the reference window
495     Size aSize = mpRefWin->GetOutDev()->GetOutputSize();
496     Point aPos = GetPosPixel();
497     if( mbHorzSplit )
498         maDragPos = Point( ImplSplitterActive() ? aPos.X() : mnSplitPos, aSize.Height()/2 );
499     else
500         maDragPos = Point( aSize.Width()/2, ImplSplitterActive() ? aPos.Y() : mnSplitPos );
501     ImplSplitMousePos( maDragPos );
502     if ( mbHorzSplit )
503         mnStartSplitPos = maDragPos.X();
504     else
505         mnStartSplitPos = maDragPos.Y();
506 }
507 
ImplRestoreSplitter()508 void Splitter::ImplRestoreSplitter()
509 {
510     // set splitter in the center of the ref window
511     StartSplit();
512     Size aSize = mpRefWin->GetOutDev()->GetOutputSize();
513     Point aPos( aSize.Width()/2 , aSize.Height()/2);
514     if ( mnLastSplitPos != mnSplitPos && mnLastSplitPos > 5 )
515     {
516         // restore last pos if it was a useful position (>5)
517         if ( mbHorzSplit )
518             aPos.setX( mnLastSplitPos );
519         else
520             aPos.setY( mnLastSplitPos );
521     }
522 
523     ImplSplitMousePos( aPos );
524     tools::Long nTemp = mnSplitPos;
525     if ( mbHorzSplit )
526         SetSplitPosPixel( aPos.X() );
527     else
528         SetSplitPosPixel( aPos.Y() );
529     mnLastSplitPos = nTemp;
530     Split();
531     EndSplit();
532 }
533 
GetFocus()534 void Splitter::GetFocus()
535 {
536     if( !ImplSplitterActive() )
537         ImplRestoreSplitter();
538 
539     Invalidate();
540 }
541 
LoseFocus()542 void Splitter::LoseFocus()
543 {
544     if( mbKbdSplitting )
545     {
546         vcl::KeyCode aReturnKey( KEY_RETURN );
547         ImplKbdTracking( aReturnKey );
548         mbKbdSplitting = false;
549     }
550     Invalidate();
551 }
552 
KeyInput(const KeyEvent & rKEvt)553 void Splitter::KeyInput( const KeyEvent& rKEvt )
554 {
555     if( mbInKeyEvent )
556         return;
557 
558     mbInKeyEvent = true;
559 
560     Splitter *pSibling = ImplFindSibling();
561     vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
562     sal_uInt16 nCode = aKeyCode.GetCode();
563     switch ( nCode )
564     {
565         case KEY_UP:
566         case KEY_DOWN:
567             if( !mbHorzSplit )
568             {
569                 ImplStartKbdSplitting();
570                 ImplKbdTracking( aKeyCode );
571             }
572             else
573             {
574                 if( pSibling )
575                 {
576                     pSibling->GrabFocus();
577                     pSibling->KeyInput( rKEvt );
578                 }
579             }
580             break;
581         case KEY_RIGHT:
582         case KEY_LEFT:
583             if( mbHorzSplit )
584             {
585                 ImplStartKbdSplitting();
586                 ImplKbdTracking( aKeyCode );
587             }
588             else
589             {
590                 if( pSibling )
591                 {
592                     pSibling->GrabFocus();
593                     pSibling->KeyInput( rKEvt );
594                 }
595             }
596             break;
597 
598         case KEY_DELETE:
599             if( ImplSplitterActive() )
600             {
601                 if( mbKbdSplitting )
602                 {
603                     vcl::KeyCode aKey( KEY_ESCAPE );
604                     ImplKbdTracking( aKey );
605                 }
606 
607                 StartSplit();
608                 Point aPos;
609                 if ( mbHorzSplit )
610                     aPos.setX( 0 );
611                 else
612                     aPos.setY( 0 );
613                 ImplSplitMousePos( aPos );
614                 tools::Long nTemp = mnSplitPos;
615                 if ( mbHorzSplit )
616                     SetSplitPosPixel( aPos.X() );
617                 else
618                     SetSplitPosPixel( aPos.Y() );
619                 mnLastSplitPos = nTemp;
620                 Split();
621                 EndSplit();
622 
623                 // Shift-Del deletes both splitters
624                 if( aKeyCode.IsShift() && pSibling )
625                     pSibling->KeyInput( rKEvt );
626 
627                 GrabFocusToDocument();
628             }
629             break;
630 
631         case KEY_ESCAPE:
632             if( mbKbdSplitting )
633                 ImplKbdTracking( aKeyCode );
634             else
635                 GrabFocusToDocument();
636             break;
637 
638         case KEY_RETURN:
639             ImplKbdTracking( aKeyCode );
640             GrabFocusToDocument();
641             break;
642         default:    // let any key input fix the splitter
643             Window::KeyInput( rKEvt );
644             GrabFocusToDocument();
645             break;
646     }
647     mbInKeyEvent = false;
648 }
649 
DataChanged(const DataChangedEvent & rDCEvt)650 void Splitter::DataChanged( const DataChangedEvent& rDCEvt )
651 {
652     Window::DataChanged( rDCEvt );
653     if( rDCEvt.GetType() != DataChangedEventType::SETTINGS )
654         return;
655 
656     const AllSettings* pOldSettings = rDCEvt.GetOldSettings();
657     if(!pOldSettings)
658         return;
659 
660     Color oldFaceColor = pOldSettings->GetStyleSettings().GetFaceColor();
661     Color newFaceColor = Application::GetSettings().GetStyleSettings().GetFaceColor();
662     if( oldFaceColor.IsDark() != newFaceColor.IsDark() )
663     {
664         if( newFaceColor.IsDark() )
665             SetBackground( ImplWhiteWall::get() );
666         else
667             SetBackground( ImplBlackWall::get() );
668     }
669 }
670 
Paint(vcl::RenderContext & rRenderContext,const tools::Rectangle & rPaintRect)671 void Splitter::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rPaintRect)
672 {
673     rRenderContext.DrawRect(rPaintRect);
674 
675     tools::Polygon aPoly(rPaintRect);
676     tools::PolyPolygon aPolyPoly(aPoly);
677     rRenderContext.DrawTransparent(aPolyPoly, 85);
678 
679     if (mbKbdSplitting)
680     {
681         LineInfo aInfo( LineStyle::Dash );
682         //aInfo.SetDashLen( 2 );
683         //aInfo.SetDashCount( 1 );
684         aInfo.SetDistance( 1 );
685         aInfo.SetDotLen( 2 );
686         aInfo.SetDotCount( 3 );
687 
688         rRenderContext.DrawPolyLine( aPoly, aInfo );
689     }
690     else
691     {
692         rRenderContext.DrawRect(rPaintRect);
693     }
694 }
695 
GetOptimalSize() const696 Size Splitter::GetOptimalSize() const
697 {
698     return LogicToPixel(Size(3, 3), MapMode(MapUnit::MapAppFont));
699 }
700 
701 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
702