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