1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
22 #include <svtools/controldims.hxx>
24 #include "RemoteDialogClientBox.hxx"
25 #include <RemoteServer.hxx>
27 #include <vcl/settings.hxx>
28 #include <vcl/builderfactory.hxx>
29 #include <vcl/commandevent.hxx>
30 #include <vcl/event.hxx>
32 #include <sdresid.hxx>
33 #include <strings.hrc>
37 using namespace ::com::sun::star
;
41 // struct ClientBoxEntry
43 ClientBoxEntry::ClientBoxEntry(const std::shared_ptr
<ClientInfo
>& pClientInfo
)
45 , m_pClientInfo(pClientInfo
)
49 ClientBoxEntry::~ClientBoxEntry()
54 ClientBox::ClientBox( vcl::Window
* pParent
, WinBits nStyle
) :
55 Control( pParent
, nStyle
),
56 m_bHasScrollBar( false ),
57 m_bHasActive( false ),
58 m_bNeedsRecalc( true ),
59 m_bAdjustActive( false ),
63 m_aPinBox( VclPtr
<NumericBox
>::Create( this, 0 ) ),
64 m_aDeauthoriseButton( VclPtr
<PushButton
>::Create( this ) ),
65 m_aScrollBar( VclPtr
<ScrollBar
>::Create( this, WB_VERT
) )
67 m_aScrollBar
->SetScrollHdl( LINK( this, ClientBox
, ScrollHdl
) );
68 m_aScrollBar
->EnableDrag();
70 m_aPinBox
->SetUseThousandSep(false);
71 m_aDeauthoriseButton
->SetText( SdResId(STR_DEAUTHORISE_CLIENT
) );
72 m_aDeauthoriseButton
->SetClickHdl( LINK( this, ClientBox
, DeauthoriseHdl
) );
74 SetPosPixel( Point( RSC_SP_DLG_INNERBORDER_LEFT
, RSC_SP_DLG_INNERBORDER_TOP
) );
75 long nIconHeight
= 2*TOP_OFFSET
+ SMALL_ICON_SIZE
;
76 long nTitleHeight
= 2*TOP_OFFSET
+ GetTextHeight();
77 if ( nIconHeight
< nTitleHeight
)
78 m_nStdHeight
= nTitleHeight
;
80 m_nStdHeight
= nIconHeight
;
81 m_nStdHeight
+= GetTextHeight() + TOP_OFFSET
;
83 m_nActiveHeight
= m_nStdHeight
;
85 const StyleSettings
& rStyleSettings
= GetSettings().GetStyleSettings();
86 if( IsControlBackground() )
87 SetBackground( GetControlBackground() );
89 SetBackground( rStyleSettings
.GetFieldColor() );
96 VCL_BUILDER_FACTORY_CONSTRUCTOR(ClientBox
, WB_TABSTOP
)
98 Size
ClientBox::GetOptimalSize() const
100 return LogicToPixel(Size(200, 140), MapMode(MapUnit::MapAppFont
));
103 ClientBox::~ClientBox()
108 void ClientBox::dispose()
112 m_aPinBox
.disposeAndClear();
113 m_aDeauthoriseButton
.disposeAndClear();
114 m_aScrollBar
.disposeAndClear();
118 // Title + description
119 void ClientBox::CalcActiveHeight()
121 const ::osl::MutexGuard
aGuard( m_entriesMutex
);
125 long nIconHeight
= 2*TOP_OFFSET
+ SMALL_ICON_SIZE
;
126 long nTitleHeight
= 2*TOP_OFFSET
+ GetTextHeight();
127 if ( nIconHeight
< nTitleHeight
)
128 aTextHeight
= nTitleHeight
;
130 aTextHeight
= nIconHeight
;
133 Size aSize
= GetOutputSizePixel();
134 if ( m_bHasScrollBar
)
135 aSize
.AdjustWidth( -(m_aScrollBar
->GetSizePixel().Width()) );
137 aSize
.AdjustWidth( -ICON_OFFSET
);
139 aSize
= LogicToPixel( Size( RSC_CD_PUSHBUTTON_WIDTH
, RSC_CD_PUSHBUTTON_HEIGHT
),
140 MapMode( MapUnit::MapAppFont
) );
141 aTextHeight
+= aSize
.Height();
143 if ( aTextHeight
< m_nStdHeight
)
144 aTextHeight
= m_nStdHeight
;
146 m_nActiveHeight
= aTextHeight
+ 2;
149 ::tools::Rectangle
ClientBox::GetEntryRect( const long nPos
) const
151 const ::osl::MutexGuard
aGuard( m_entriesMutex
);
153 Size
aSize( GetOutputSizePixel() );
155 if ( m_bHasScrollBar
)
156 aSize
.AdjustWidth( -(m_aScrollBar
->GetSizePixel().Width()) );
158 if ( m_vEntries
[ nPos
]->m_bActive
)
159 aSize
.setHeight( m_nActiveHeight
);
161 aSize
.setHeight( m_nStdHeight
);
163 Point
aPos( 0, -m_nTopIndex
+ nPos
* m_nStdHeight
);
164 if ( m_bHasActive
&& ( nPos
< m_nActive
) )
165 aPos
.AdjustY(m_nActiveHeight
- m_nStdHeight
);
167 return ::tools::Rectangle( aPos
, aSize
);
170 long ClientBox::GetActiveEntryIndex()
178 //This function may be called with nPos < 0
179 void ClientBox::selectEntry( const long nPos
)
181 //ToDo we should not use the guard at such a big scope here.
182 //Currently it is used to guard m_vEntries and m_nActive. m_nActive will be
183 //modified in this function.
184 //It would be probably best to always use a copy of m_vEntries
185 //and some other state variables from ClientBox for
186 //the whole painting operation. See issue i86993
187 osl::MutexGuard
guard(m_entriesMutex
);
191 if ( nPos
== m_nActive
)
194 m_bHasActive
= false;
195 m_vEntries
[ m_nActive
]->m_bActive
= false;
198 if ( ( nPos
>= 0 ) && ( nPos
< static_cast<long>(m_vEntries
.size()) ) )
202 m_vEntries
[ nPos
]->m_bActive
= true;
204 if ( IsReallyVisible() )
206 m_bAdjustActive
= true;
210 // We empty the pin box now too, just in case the user previously
211 // entered a pin, but then changed their selected device.
212 m_aPinBox
->SetText( "" );
215 bool bAlreadyAuthorised
=
216 m_vEntries
[ m_nActive
]->m_pClientInfo
->mbIsAlreadyAuthorised
;
218 if ( bAlreadyAuthorised
)
220 m_aDeauthoriseButton
->GetFocus();
224 m_aPinBox
->GetFocus();
228 if ( IsReallyVisible() )
230 m_bNeedsRecalc
= true;
235 void ClientBox::DrawRow(vcl::RenderContext
& rRenderContext
, const ::tools::Rectangle
& rRect
, const TClientBoxEntry
& rEntry
)
237 const StyleSettings
& rStyleSettings
= rRenderContext
.GetSettings().GetStyleSettings();
239 if (rEntry
->m_bActive
)
240 SetTextColor(rStyleSettings
.GetHighlightTextColor());
242 SetTextColor(rStyleSettings
.GetFieldTextColor());
244 if (rEntry
->m_bActive
)
246 rRenderContext
.SetLineColor();
247 rRenderContext
.SetFillColor(rStyleSettings
.GetHighlightColor());
248 rRenderContext
.DrawRect(rRect
);
252 if(IsControlBackground())
253 SetBackground(GetControlBackground());
255 SetBackground(rStyleSettings
.GetFieldColor());
257 rRenderContext
.SetTextFillColor();
258 rRenderContext
.Erase(rRect
);
261 // FIXME: draw bluetooth or wifi icon
262 Point
aPos(rRect
.TopLeft());
265 vcl::Font
aStdFont(rRenderContext
.GetFont());
266 vcl::Font
aBoldFont(aStdFont
);
267 aBoldFont
.SetWeight(WEIGHT_BOLD
);
268 rRenderContext
.SetFont(aBoldFont
);
269 long aTextHeight
= rRenderContext
.GetTextHeight();
271 // Get max title width
272 long nMaxTitleWidth
= rRect
.GetWidth() - ICON_OFFSET
;
273 nMaxTitleWidth
-= ( 2 * SMALL_ICON_SIZE
) + ( 4 * SPACE_BETWEEN
);
275 long aTitleWidth
= rRenderContext
.GetTextWidth(rEntry
->m_pClientInfo
->mName
) + (aTextHeight
/ 3);
277 aPos
= rRect
.TopLeft() + Point(ICON_OFFSET
, TOP_OFFSET
);
279 if (aTitleWidth
> nMaxTitleWidth
)
281 aTitleWidth
= nMaxTitleWidth
- (aTextHeight
/ 3);
282 OUString aShortTitle
= rRenderContext
.GetEllipsisString(rEntry
->m_pClientInfo
->mName
, aTitleWidth
);
283 rRenderContext
.DrawText(aPos
, aShortTitle
);
286 rRenderContext
.DrawText(aPos
, rEntry
->m_pClientInfo
->mName
);
290 aPos
.AdjustY(aTextHeight
);
291 if (rEntry
->m_bActive
)
293 OUString
sPinText(SdResId(STR_ENTER_PIN
));
294 DrawText(m_sPinTextRect
, sPinText
);
297 rRenderContext
.SetLineColor(COL_LIGHTGRAY
);
298 rRenderContext
.DrawLine(rRect
.BottomLeft(), rRect
.BottomRight());
301 void ClientBox::RecalcAll()
308 Size aPBSize
= LogicToPixel(
309 Size( RSC_CD_PUSHBUTTON_WIDTH
, RSC_CD_PUSHBUTTON_HEIGHT
),
310 MapMode( MapUnit::MapAppFont
) );
311 m_aPinBox
->SetSizePixel( aPBSize
);
312 m_aDeauthoriseButton
->SetSizePixel( m_aDeauthoriseButton
->GetOptimalSize() );
316 m_aPinBox
->Show( false );
317 m_aDeauthoriseButton
->Show( false );
321 ::tools::Rectangle aEntryRect
= GetEntryRect( m_nActive
);
323 Size
aPinBoxSize( m_aPinBox
->GetSizePixel() );
324 Point
aPos( aEntryRect
.Left(),
325 aEntryRect
.Bottom() - TOP_OFFSET
- aPinBoxSize
.Height() );
327 bool bAlreadyAuthorised
= m_vEntries
[ m_nActive
]->m_pClientInfo
->mbIsAlreadyAuthorised
;
329 if ( !bAlreadyAuthorised
)
331 m_sPinTextRect
= ::tools::Rectangle( aPos
.X(), aPos
.Y(),
333 aEntryRect
.Bottom() - TOP_OFFSET
);
335 OUString
sPinText(SdResId(STR_ENTER_PIN
));
337 aPos
= Point( aEntryRect
.Left() + GetTextWidth( sPinText
),
338 aEntryRect
.Bottom() - TOP_OFFSET
- aPinBoxSize
.Height() );
339 m_aPinBox
->SetPosPixel( aPos
);
340 // The text would have it's TOP aligned with the top of
341 // the pin box -- hence we push it down to align baselines.
342 m_sPinTextRect
+= Point( 0, 4 );
346 aPos
+= Point( 20, 0 );
347 m_aDeauthoriseButton
->SetPosPixel( aPos
);
350 m_aPinBox
->Show( !bAlreadyAuthorised
);
351 m_aDeauthoriseButton
->Show( bAlreadyAuthorised
);
353 if ( m_bAdjustActive
)
355 m_bAdjustActive
= false;
357 // If the top of the selected entry isn't visible, make it visible
358 if ( aEntryRect
.Top() < 0 )
360 m_nTopIndex
+= aEntryRect
.Top();
361 aEntryRect
.Move( 0, -aEntryRect
.Top() );
364 // If the bottom of the selected entry isn't visible, make it visible even if now the top
365 // isn't visible any longer ( the buttons are more important )
366 Size aOutputSize
= GetOutputSizePixel();
367 if ( aEntryRect
.Bottom() > aOutputSize
.Height() )
369 m_nTopIndex
+= ( aEntryRect
.Bottom() - aOutputSize
.Height() );
370 aEntryRect
.Move( 0, -( aEntryRect
.Bottom() - aOutputSize
.Height() ) );
373 // If there is unused space below the last entry but all entries don't fit into the box,
374 // move the content down to use the whole space
375 const long nTotalHeight
= GetTotalHeight();
376 if ( m_bHasScrollBar
&& ( aOutputSize
.Height() + m_nTopIndex
> nTotalHeight
) )
378 long nOffset
= m_nTopIndex
;
379 m_nTopIndex
= nTotalHeight
- aOutputSize
.Height();
380 nOffset
-= m_nTopIndex
;
381 aEntryRect
.Move( 0, nOffset
);
384 if ( m_bHasScrollBar
)
385 m_aScrollBar
->SetThumbPos( m_nTopIndex
);
389 m_bNeedsRecalc
= false;
392 bool ClientBox::HandleCursorKey( sal_uInt16 nKeyCode
)
394 if ( m_vEntries
.empty() )
401 long nPageSize
= GetOutputSizePixel().Height() / m_nStdHeight
;
405 if ( ( nKeyCode
== KEY_DOWN
) || ( nKeyCode
== KEY_RIGHT
) )
406 nSelect
= m_nActive
+ 1;
407 else if ( ( nKeyCode
== KEY_UP
) || ( nKeyCode
== KEY_LEFT
) )
408 nSelect
= m_nActive
- 1;
409 else if ( nKeyCode
== KEY_HOME
)
411 else if ( nKeyCode
== KEY_END
)
412 nSelect
= m_vEntries
.size() - 1;
413 else if ( nKeyCode
== KEY_PAGEUP
)
414 nSelect
= m_nActive
- nPageSize
+ 1;
415 else if ( nKeyCode
== KEY_PAGEDOWN
)
416 nSelect
= m_nActive
+ nPageSize
- 1;
418 else // when there is no selected entry, we will select the first or the last.
420 if ( ( nKeyCode
== KEY_DOWN
) || ( nKeyCode
== KEY_PAGEDOWN
) || ( nKeyCode
== KEY_HOME
) )
422 else if ( ( nKeyCode
== KEY_UP
) || ( nKeyCode
== KEY_PAGEUP
) || ( nKeyCode
== KEY_END
) )
423 nSelect
= m_vEntries
.size() - 1;
428 if ( nSelect
>= static_cast<long>(m_vEntries
.size()) )
429 nSelect
= m_vEntries
.size() - 1;
431 selectEntry( nSelect
);
436 void ClientBox::Paint(vcl::RenderContext
& rRenderContext
, const ::tools::Rectangle
&/*rPaintRect*/)
441 Point
aStart(0, -m_nTopIndex
);
442 Size
aSize(GetOutputSizePixel());
445 aSize
.AdjustWidth( -(m_aScrollBar
->GetSizePixel().Width()) );
447 const ::osl::MutexGuard
aGuard(m_entriesMutex
);
449 for (auto& vEntry
: m_vEntries
)
451 aSize
.setHeight( vEntry
->m_bActive
? m_nActiveHeight
: m_nStdHeight
);
452 ::tools::Rectangle
aEntryRect(aStart
, aSize
);
453 DrawRow(rRenderContext
, aEntryRect
, vEntry
);
454 aStart
.AdjustY(aSize
.Height() );
458 long ClientBox::GetTotalHeight() const
460 long nHeight
= m_vEntries
.size() * m_nStdHeight
;
464 nHeight
+= m_nActiveHeight
- m_nStdHeight
;
470 void ClientBox::SetupScrollBar()
472 const Size aSize
= GetOutputSizePixel();
473 const long nScrBarSize
= GetSettings().GetStyleSettings().GetScrollBarSize();
474 const long nTotalHeight
= GetTotalHeight();
475 const bool bNeedsScrollBar
= ( nTotalHeight
> aSize
.Height() );
477 if ( bNeedsScrollBar
)
479 if ( m_nTopIndex
+ aSize
.Height() > nTotalHeight
)
480 m_nTopIndex
= nTotalHeight
- aSize
.Height();
482 m_aScrollBar
->SetPosSizePixel( Point( aSize
.Width() - nScrBarSize
, 0 ),
483 Size( nScrBarSize
, aSize
.Height() ) );
484 m_aScrollBar
->SetRangeMax( nTotalHeight
);
485 m_aScrollBar
->SetVisibleSize( aSize
.Height() );
486 m_aScrollBar
->SetPageSize( ( aSize
.Height() * 4 ) / 5 );
487 m_aScrollBar
->SetLineSize( m_nStdHeight
);
488 m_aScrollBar
->SetThumbPos( m_nTopIndex
);
490 if ( !m_bHasScrollBar
)
491 m_aScrollBar
->Show();
493 else if ( m_bHasScrollBar
)
495 m_aScrollBar
->Hide();
499 m_bHasScrollBar
= bNeedsScrollBar
;
502 void ClientBox::Resize()
507 long ClientBox::PointToPos( const Point
& rPos
)
509 long nPos
= ( rPos
.Y() + m_nTopIndex
) / m_nStdHeight
;
511 if ( m_bHasActive
&& ( nPos
> m_nActive
) )
513 if ( rPos
.Y() + m_nTopIndex
<= m_nActive
*m_nStdHeight
+ m_nActiveHeight
)
516 nPos
= ( rPos
.Y() + m_nTopIndex
- (m_nActiveHeight
- m_nStdHeight
) ) / m_nStdHeight
;
522 OUString
ClientBox::getPin()
524 return OUString::number( m_aPinBox
->GetValue() );
527 void ClientBox::MouseButtonDown( const MouseEvent
& rMEvt
)
529 long nPos
= PointToPos( rMEvt
.GetPosPixel() );
531 if ( rMEvt
.IsLeft() )
533 if ( rMEvt
.IsMod1() && m_bHasActive
)
534 selectEntry( m_vEntries
.size() ); // Selecting an not existing entry will deselect the current one
540 bool ClientBox::EventNotify( NotifyEvent
& rNEvt
)
542 bool bHandled
= false;
544 if ( rNEvt
.GetType() == MouseNotifyEvent::KEYINPUT
)
546 const KeyEvent
* pKEvt
= rNEvt
.GetKeyEvent();
547 vcl::KeyCode aKeyCode
= pKEvt
->GetKeyCode();
548 sal_uInt16 nKeyCode
= aKeyCode
.GetCode();
550 if ( aKeyCode
.GetGroup() == KEYGROUP_CURSOR
)
551 bHandled
= HandleCursorKey( nKeyCode
);
554 if ( rNEvt
.GetType() == MouseNotifyEvent::COMMAND
)
556 if ( m_bHasScrollBar
&&
557 ( rNEvt
.GetCommandEvent()->GetCommand() == CommandEventId::Wheel
) )
559 const CommandWheelData
* pData
= rNEvt
.GetCommandEvent()->GetWheelData();
560 if ( pData
->GetMode() == CommandWheelMode::SCROLL
)
562 long nThumbPos
= m_aScrollBar
->GetThumbPos();
563 if ( pData
->GetDelta() < 0 )
564 m_aScrollBar
->DoScroll( nThumbPos
+ m_nStdHeight
);
566 m_aScrollBar
->DoScroll( nThumbPos
- m_nStdHeight
);
573 return Control::EventNotify(rNEvt
);
578 void ClientBox::addEntry( const std::shared_ptr
<ClientInfo
>& pClientInfo
)
582 TClientBoxEntry
xEntry( new ClientBoxEntry( pClientInfo
) );
585 osl::MutexGuard
guard(m_entriesMutex
);
586 if (m_vEntries
.empty())
588 m_vEntries
.push_back(xEntry
);
592 m_vEntries
.insert(m_vEntries
.begin() + nPos
, xEntry
);
595 //access to m_nActive must be guarded
596 if (m_bHasActive
&& (m_nActive
>= nPos
))
600 if ( IsReallyVisible() )
603 m_bNeedsRecalc
= true;
606 void ClientBox::clearEntries()
609 m_bHasActive
= false;
611 const ::osl::MutexGuard
aGuard( m_entriesMutex
);
614 if ( IsReallyVisible() )
616 m_bNeedsRecalc
= true;
619 void ClientBox::populateEntries()
621 const ::osl::MutexGuard
aGuard( m_entriesMutex
);
625 #ifdef ENABLE_SDREMOTE
626 RemoteServer::ensureDiscoverable();
628 vector
< std::shared_ptr
< ClientInfo
> > aClients( RemoteServer::getClients() );
630 for ( const auto& rxClient
: aClients
)
632 addEntry( rxClient
);
636 if ( IsReallyVisible() )
638 m_bNeedsRecalc
= true;
641 void ClientBox::DoScroll( long nDelta
)
643 m_nTopIndex
+= nDelta
;
644 Point
aNewSBPt( m_aScrollBar
->GetPosPixel() );
646 ::tools::Rectangle
aScrRect( Point(), GetOutputSizePixel() );
647 aScrRect
.AdjustRight( -(m_aScrollBar
->GetSizePixel().Width()) );
648 Scroll( 0, -nDelta
, aScrRect
);
650 m_aScrollBar
->SetPosPixel( aNewSBPt
);
653 IMPL_LINK( ClientBox
, ScrollHdl
, ScrollBar
*, pScrBar
, void )
655 DoScroll( pScrBar
->GetDelta() );
658 IMPL_LINK_NOARG( ClientBox
, DeauthoriseHdl
, Button
*, void )
660 long aSelected
= GetActiveEntryIndex();
663 TClientBoxEntry aEntry
= GetEntryData(aSelected
);
665 #ifdef ENABLE_SDREMOTE
666 RemoteServer::deauthoriseClient( aEntry
->m_pClientInfo
);
673 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */