diff options
author | Katarina Behrens <Katarina.Behrens@cib.de> | 2017-08-28 18:29:20 +0200 |
---|---|---|
committer | Thorsten Behrens <Thorsten.Behrens@CIB.de> | 2017-10-25 03:23:12 +0200 |
commit | dd7a3147f160813022d1c2724bbdf7fc46ffcc59 (patch) | |
tree | 73398c2d5aadd6366c131d27fa824e61cb215246 /vcl/unx | |
parent | 07cd70c86c8919107ef02dc12ea23ae8d72d5a1a (diff) |
kde5: copy basic kde4 blocks -> kde5 and build againt qt5/kf5 libs
Change-Id: I70f0c4147721a20459e1183ff40cf0ac8adf49e6
Diffstat (limited to 'vcl/unx')
-rw-r--r-- | vcl/unx/kde5/FPServiceInfo.hxx | 28 | ||||
-rw-r--r-- | vcl/unx/kde5/KDE5Data.cxx | 68 | ||||
-rw-r--r-- | vcl/unx/kde5/KDE5Data.hxx | 37 | ||||
-rw-r--r-- | vcl/unx/kde5/KDE5SalDisplay.cxx | 98 | ||||
-rw-r--r-- | vcl/unx/kde5/KDE5SalDisplay.hxx | 47 | ||||
-rw-r--r-- | vcl/unx/kde5/KDE5SalFrame.cxx | 381 | ||||
-rw-r--r-- | vcl/unx/kde5/KDE5SalFrame.hxx | 53 | ||||
-rw-r--r-- | vcl/unx/kde5/KDE5SalGraphics.cxx | 1020 | ||||
-rw-r--r-- | vcl/unx/kde5/KDE5SalGraphics.hxx | 53 | ||||
-rw-r--r-- | vcl/unx/kde5/KDE5SalInstance.cxx | 70 | ||||
-rw-r--r-- | vcl/unx/kde5/KDE5SalInstance.hxx | 45 | ||||
-rw-r--r-- | vcl/unx/kde5/KDE5XLib.cxx | 407 | ||||
-rw-r--r-- | vcl/unx/kde5/KDE5XLib.hxx | 102 | ||||
-rw-r--r-- | vcl/unx/kde5/VCLKDE5Application.cxx | 75 | ||||
-rw-r--r-- | vcl/unx/kde5/VCLKDE5Application.hxx | 40 | ||||
-rw-r--r-- | vcl/unx/kde5/main.cxx | 88 | ||||
-rw-r--r-- | vcl/unx/kde5/tst_exclude_posted_events.hxx | 67 | ||||
-rw-r--r-- | vcl/unx/kde5/tst_exclude_socket_notifiers.hxx | 80 |
18 files changed, 2759 insertions, 0 deletions
diff --git a/vcl/unx/kde5/FPServiceInfo.hxx b/vcl/unx/kde5/FPServiceInfo.hxx new file mode 100644 index 000000000000..fdb285144343 --- /dev/null +++ b/vcl/unx/kde5/FPServiceInfo.hxx @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +// the service names +#define FILE_PICKER_SERVICE_NAME "com.sun.star.ui.dialogs.KDE4FilePicker" + +// the implementation names +#define FILE_PICKER_IMPL_NAME "com.sun.star.ui.dialogs.KDE4FilePicker" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/KDE5Data.cxx b/vcl/unx/kde5/KDE5Data.cxx new file mode 100644 index 000000000000..487a0b254af3 --- /dev/null +++ b/vcl/unx/kde5/KDE5Data.cxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <QtWidgets/QStyle> +#include <QtWidgets/QApplication> + +#undef Region + +#include "KDE5Data.hxx" + +#include "KDE5XLib.hxx" +#include "KDE5SalDisplay.hxx" + +KDEData::~KDEData() +{ +} + +void KDEData::Init() +{ + pXLib_ = new KDEXLib(); + pXLib_->Init(); + SetDisplay( SalKDEDisplay::self() ); +} + +void KDEData::initNWF() +{ + ImplSVData *pSVData = ImplGetSVData(); + + // draw toolbars on separate lines + pSVData->maNWFData.mbDockingAreaSeparateTB = true; + // no borders for menu, theming does that + pSVData->maNWFData.mbFlatMenu = true; + // Qt theme engines may support a rollover menubar + pSVData->maNWFData.mbRolloverMenubar = true; + + pSVData->maNWFData.mbNoFocusRects = true; + + // Styled menus need additional space + QStyle *style = QApplication::style(); + pSVData->maNWFData.mnMenuFormatBorderX = + style->pixelMetric( QStyle::PM_MenuPanelWidth ) + + style->pixelMetric( QStyle::PM_MenuHMargin ); + pSVData->maNWFData.mnMenuFormatBorderY = + style->pixelMetric( QStyle::PM_MenuPanelWidth ) + + style->pixelMetric( QStyle::PM_MenuVMargin ); +} + +void KDEData::deInitNWF() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/KDE5Data.hxx b/vcl/unx/kde5/KDE5Data.hxx new file mode 100644 index 000000000000..4efd23a7df34 --- /dev/null +++ b/vcl/unx/kde5/KDE5Data.hxx @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <unx/saldisp.hxx> +#include <unx/saldata.hxx> + +class KDEData : public X11SalData +{ + public: + explicit KDEData( SalInstance *pInstance ) + : X11SalData( SAL_DATA_KDE4, pInstance ) {} + virtual ~KDEData() override; + + virtual void Init() override; + virtual void initNWF() override; + virtual void deInitNWF() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/KDE5SalDisplay.cxx b/vcl/unx/kde5/KDE5SalDisplay.cxx new file mode 100644 index 000000000000..eddac063048f --- /dev/null +++ b/vcl/unx/kde5/KDE5SalDisplay.cxx @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "VCLKDE5Application.hxx" +#include "KDE5SalDisplay.hxx" + +#ifdef Bool +#undef Bool +#endif + +#include "KDE5XLib.hxx" + +#include <assert.h> + +SalKDEDisplay* SalKDEDisplay::selfptr = nullptr; + +SalKDEDisplay::SalKDEDisplay( Display* pDisp ) + : SalX11Display( pDisp ) +{ + assert( selfptr == nullptr ); + selfptr = this; + xim_protocol = XInternAtom( pDisp_, "_XIM_PROTOCOL", False ); +} + +SalKDEDisplay::~SalKDEDisplay() +{ + // in case never a frame opened + static_cast<KDEXLib*>(GetXLib())->doStartup(); + // clean up own members + doDestruct(); + selfptr = nullptr; + // prevent SalDisplay from closing KApplication's display + pDisp_ = nullptr; +} + +void SalKDEDisplay::Yield() +{ + if( DispatchInternalEvent() ) + return; + + // Prevent blocking from Drag'n'Drop events, which may have already have processed the event + if (XEventsQueued( pDisp_, QueuedAfterReading ) == 0) + return; + + DBG_ASSERT( static_cast<SalYieldMutex*>(GetSalData()->m_pInstance->GetYieldMutex())->GetThreadId() == + osl::Thread::getCurrentIdentifier(), + "will crash soon since solar mutex not locked in SalKDEDisplay::Yield" ); + + /*XEvent event; + XNextEvent( pDisp_, &event ); + if( checkDirectInputEvent( &event )) + return; + qApp->x11ProcessEvent( &event );*/ +} + +// HACK: When using Qt event loop, input methods (japanese, etc.) will get broken because +// of XFilterEvent() getting called twice, once by Qt, once by LO (bnc#665112). +// This function is therefore called before any XEvent is passed to Qt event handling +// and if it is a keyboard event and no Qt widget is the active window (i.e. we are +// processing events for some LO window), then feed the event only to LO directly and skip Qt +// completely. Skipped events are KeyPress, KeyRelease and also _XIM_PROTOCOL client message +// (seems to be necessary too, hopefully there are not other internal XIM messages that +// would need this handling). +bool SalKDEDisplay::checkDirectInputEvent( xcb_generic_event_t* ev ) +{ + switch (ev->response_type & ~0x80) + { + case XCB_CLIENT_MESSAGE: + case XCB_KEY_PRESS: + case XCB_KEY_RELEASE: + if( QApplication::activeWindow() == nullptr ) + { +// Dispatch(ev); + return true; + } + break; + } + return false; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/KDE5SalDisplay.hxx b/vcl/unx/kde5/KDE5SalDisplay.hxx new file mode 100644 index 000000000000..0a31dcedc10d --- /dev/null +++ b/vcl/unx/kde5/KDE5SalDisplay.hxx @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <unx/saldisp.hxx> +#include <xcb/xcb.h> + +#ifdef CursorShape +#undef CursorShape +#endif + +class SalKDEDisplay : public SalX11Display +{ + public: + explicit SalKDEDisplay( Display* pDisp ); + virtual ~SalKDEDisplay() override; + static SalKDEDisplay* self(); + virtual void Yield() override; + bool checkDirectInputEvent( xcb_generic_event_t* ev ); + private: + Atom xim_protocol; + static SalKDEDisplay* selfptr; +}; + +inline SalKDEDisplay* SalKDEDisplay::self() +{ + return selfptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/KDE5SalFrame.cxx b/vcl/unx/kde5/KDE5SalFrame.cxx new file mode 100644 index 000000000000..090abbc240d1 --- /dev/null +++ b/vcl/unx/kde5/KDE5SalFrame.cxx @@ -0,0 +1,381 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <QtGui/QColor> +#include <QtWidgets/QStyle> +#include <QtCore/QDebug> +#include <QtWidgets/QToolTip> +#include <QtWidgets/QApplication> +#include <QtWidgets/QMenuBar> + +#include <KConfig> +#include <KConfigGroup> +#include <KSharedConfig> + +#undef Region + +#include "KDE5SalFrame.hxx" +#include "KDE5XLib.hxx" +#include "KDE5SalGraphics.hxx" + +#include <tools/color.hxx> + +#include <vcl/font.hxx> +#include <vcl/settings.hxx> + +#include "unx/fontmanager.hxx" + +#include <svdata.hxx> + +#include <boost/optional.hpp> + + +KDESalFrame::KDESalFrame( SalFrame* pParent, SalFrameStyleFlags nState ) : + X11SalFrame( pParent, nState ) +{ +} + +void KDESalFrame::Show( bool bVisible, bool bNoActivate ) +{ + if ( !GetParent() && ! (GetStyle() & SalFrameStyleFlags::INTRO) ) + { + KDEXLib* pXLib = static_cast<KDEXLib*>(GetDisplay()->GetXLib()); + pXLib->doStartup(); + } + + X11SalFrame::Show( bVisible, bNoActivate ); +} + +/** Helper function to convert colors. +*/ +static Color toColor( const QColor &rColor ) +{ + return Color( rColor.red(), rColor.green(), rColor.blue() ); +} + + +/** Helper function to add information to Font from QFont. + + Mostly grabbed from the Gtk+ vclplug (salnativewidgets-gtk.cxx). +*/ +static vcl::Font toFont( const QFont &rQFont, const css::lang::Locale& rLocale ) +{ + psp::FastPrintFontInfo aInfo; + QFontInfo qFontInfo( rQFont ); + + // set family name + aInfo.m_aFamilyName = OUString( static_cast<const char *>(rQFont.family().toUtf8()), strlen( static_cast<const char *>(rQFont.family().toUtf8()) ), RTL_TEXTENCODING_UTF8 ); + + // set italic + aInfo.m_eItalic = ( qFontInfo.italic()? ITALIC_NORMAL: ITALIC_NONE ); + + // set weight + int nWeight = qFontInfo.weight(); + if ( nWeight <= QFont::Light ) + aInfo.m_eWeight = WEIGHT_LIGHT; + else if ( nWeight <= QFont::Normal ) + aInfo.m_eWeight = WEIGHT_NORMAL; + else if ( nWeight <= QFont::DemiBold ) + aInfo.m_eWeight = WEIGHT_SEMIBOLD; + else if ( nWeight <= QFont::Bold ) + aInfo.m_eWeight = WEIGHT_BOLD; + else + aInfo.m_eWeight = WEIGHT_ULTRABOLD; + + // set width + int nStretch = rQFont.stretch(); + if ( nStretch <= QFont::UltraCondensed ) + aInfo.m_eWidth = WIDTH_ULTRA_CONDENSED; + else if ( nStretch <= QFont::ExtraCondensed ) + aInfo.m_eWidth = WIDTH_EXTRA_CONDENSED; + else if ( nStretch <= QFont::Condensed ) + aInfo.m_eWidth = WIDTH_CONDENSED; + else if ( nStretch <= QFont::SemiCondensed ) + aInfo.m_eWidth = WIDTH_SEMI_CONDENSED; + else if ( nStretch <= QFont::Unstretched ) + aInfo.m_eWidth = WIDTH_NORMAL; + else if ( nStretch <= QFont::SemiExpanded ) + aInfo.m_eWidth = WIDTH_SEMI_EXPANDED; + else if ( nStretch <= QFont::Expanded ) + aInfo.m_eWidth = WIDTH_EXPANDED; + else if ( nStretch <= QFont::ExtraExpanded ) + aInfo.m_eWidth = WIDTH_EXTRA_EXPANDED; + else + aInfo.m_eWidth = WIDTH_ULTRA_EXPANDED; + + SAL_INFO( "vcl.kde4", "font name BEFORE system match: \"" << aInfo.m_aFamilyName << "\"" ); + + // match font to e.g. resolve "Sans" + psp::PrintFontManager::get().matchFont( aInfo, rLocale ); + + SAL_INFO( "vcl.kde4", "font match " << + (aInfo.m_nID != 0 ? "succeeded" : "failed") << + ", name AFTER: \"" << aInfo.m_aFamilyName << "\"" ); + + // font height + int nPointHeight = qFontInfo.pointSize(); + if ( nPointHeight <= 0 ) + nPointHeight = rQFont.pointSize(); + + // Create the font + vcl::Font aFont( aInfo.m_aFamilyName, Size( 0, nPointHeight ) ); + if( aInfo.m_eWeight != WEIGHT_DONTKNOW ) + aFont.SetWeight( aInfo.m_eWeight ); + if( aInfo.m_eWidth != WIDTH_DONTKNOW ) + aFont.SetWidthType( aInfo.m_eWidth ); + if( aInfo.m_eItalic != ITALIC_DONTKNOW ) + aFont.SetItalic( aInfo.m_eItalic ); + if( aInfo.m_ePitch != PITCH_DONTKNOW ) + aFont.SetPitch( aInfo.m_ePitch ); + + return aFont; +} + +/** Implementation of KDE integration's main method. +*/ +void KDESalFrame::UpdateSettings( AllSettings& rSettings ) +{ + StyleSettings style( rSettings.GetStyleSettings() ); + bool bSetTitleFont = false; + + // General settings + QPalette pal = QApplication::palette(); + + style.SetToolbarIconSize( ToolbarIconSize::Large ); + + style.SetActiveColor(toColor(pal.color(QPalette::Active, QPalette::Window))); + style.SetDeactiveColor(toColor(pal.color(QPalette::Inactive, QPalette::Window))); + + style.SetActiveTextColor(toColor(pal.color(QPalette::Active, QPalette::WindowText))); + style.SetDeactiveTextColor(toColor(pal.color(QPalette::Inactive, QPalette::WindowText))); + + // WM settings + /*KConfig *pConfig = KGlobal::config().data(); + if ( pConfig ) + { + const char *pKey; + + { + KConfigGroup aWMGroup = pConfig->group( "WM" ); + + pKey = "titleFont"; + if (aWMGroup.hasKey(pKey)) + { + vcl::Font aFont = toFont(aWMGroup.readEntry(pKey, QFont()), + rSettings.GetUILanguageTag().getLocale()); + style.SetTitleFont( aFont ); + bSetTitleFont = true; + } + } + + KConfigGroup aIconsGroup = pConfig->group("Icons"); + + pKey = "Theme"; + if (aIconsGroup.hasKey(pKey)) + style.SetPreferredIconTheme( readEntryUntranslated(&aIconsGroup, pKey)); + + //toolbar + pKey = "toolbarFont"; + if (aIconsGroup.hasKey(pKey)) + { + vcl::Font aFont = toFont(aIconsGroup.readEntry(pKey, QFont()), + rSettings.GetUILanguageTag().getLocale()); + style.SetToolFont( aFont ); + } + }*/ + + Color aFore = toColor( pal.color( QPalette::Active, QPalette::WindowText ) ); + Color aBack = toColor( pal.color( QPalette::Active, QPalette::Window ) ); + Color aText = toColor( pal.color( QPalette::Active, QPalette::Text ) ); + Color aBase = toColor( pal.color( QPalette::Active, QPalette::Base ) ); + Color aButn = toColor( pal.color( QPalette::Active, QPalette::ButtonText ) ); + Color aMid = toColor( pal.color( QPalette::Active, QPalette::Mid ) ); + Color aHigh = toColor( pal.color( QPalette::Active, QPalette::Highlight ) ); + Color aHighText = toColor( pal.color( QPalette::Active, QPalette::HighlightedText ) ); + + style.SetSkipDisabledInMenus( true ); + + // Foreground + style.SetRadioCheckTextColor( aFore ); + style.SetLabelTextColor( aFore ); + style.SetDialogTextColor( aFore ); + style.SetGroupTextColor( aFore ); + + // Text + style.SetFieldTextColor( aText ); + style.SetFieldRolloverTextColor( aText ); + style.SetWindowTextColor( aText ); + style.SetToolTextColor( aText ); + + // Base + style.SetFieldColor( aBase ); + style.SetWindowColor( aBase ); + style.SetActiveTabColor( aBase ); + + // Buttons + style.SetButtonTextColor( aButn ); + style.SetButtonRolloverTextColor( aButn ); + + // Tabs + style.SetTabTextColor( aButn ); + style.SetTabRolloverTextColor( aButn ); + style.SetTabHighlightTextColor( aButn ); + + // Disable color + style.SetDisableColor( toColor( pal.color( QPalette::Disabled, QPalette::WindowText ) ) ); + + // Workspace + style.SetWorkspaceColor( aMid ); + + // Background + style.Set3DColors( aBack ); + style.SetFaceColor( aBack ); + style.SetInactiveTabColor( aBack ); + style.SetDialogColor( aBack ); + style.SetCheckedColorSpecialCase( ); + + // Selection + style.SetHighlightColor( aHigh ); + style.SetHighlightTextColor( aHighText ); + + // Tooltip + style.SetHelpColor( toColor( QToolTip::palette().color( QPalette::Active, QPalette::ToolTipBase ))); + style.SetHelpTextColor( toColor( QToolTip::palette().color( QPalette::Active, QPalette::ToolTipText ))); + + // Font + vcl::Font aFont = toFont( QApplication::font(), rSettings.GetUILanguageTag().getLocale() ); + + style.SetAppFont( aFont ); + + style.SetMenuFont( aFont ); // will be changed according to pMenuBar + style.SetLabelFont( aFont ); + style.SetRadioCheckFont( aFont ); + style.SetPushButtonFont( aFont ); + style.SetFieldFont( aFont ); + style.SetIconFont( aFont ); + style.SetTabFont( aFont ); + style.SetGroupFont( aFont ); + + aFont.SetWeight( WEIGHT_BOLD ); + if( !bSetTitleFont ) + { + style.SetTitleFont( aFont ); + } + style.SetFloatTitleFont( aFont ); + + style.SetHelpFont( toFont( QToolTip::font(), rSettings.GetUILanguageTag().getLocale())); + + int flash_time = QApplication::cursorFlashTime(); + style.SetCursorBlinkTime( flash_time != 0 ? flash_time/2 : STYLE_CURSOR_NOBLINKTIME ); + + // Menu + std::unique_ptr<QMenuBar> pMenuBar = std::unique_ptr<QMenuBar>( new QMenuBar() ); + QPalette qMenuCG = pMenuBar->palette(); + + // Menu text and background color, theme specific + Color aMenuFore = toColor( qMenuCG.color( QPalette::WindowText ) ); + Color aMenuBack = toColor( qMenuCG.color( QPalette::Window ) ); + + style.SetMenuTextColor( aMenuFore ); + style.SetMenuBarTextColor( style.GetPersonaMenuBarTextColor().get_value_or( aMenuFore ) ); + style.SetMenuColor( aMenuBack ); + style.SetMenuBarColor( aMenuBack ); + style.SetMenuHighlightColor( toColor ( qMenuCG.color( QPalette::Highlight ) ) ); + style.SetMenuHighlightTextColor( toColor ( qMenuCG.color( QPalette::HighlightedText ) ) ); + + // set special menubar highlight text color + if ( QApplication::style()->inherits( "HighContrastStyle" ) ) + ImplGetSVData()->maNWFData.maMenuBarHighlightTextColor = toColor( qMenuCG.color( QPalette::HighlightedText ) ); + else + ImplGetSVData()->maNWFData.maMenuBarHighlightTextColor = aMenuFore; + + // set menubar rollover color + if ( pMenuBar->style()->styleHint( QStyle::SH_MenuBar_MouseTracking ) ) + { + style.SetMenuBarRolloverColor( toColor ( qMenuCG.color( QPalette::Highlight ) ) ); + style.SetMenuBarRolloverTextColor( ImplGetSVData()->maNWFData.maMenuBarHighlightTextColor ); + } + else + { + style.SetMenuBarRolloverColor( aMenuBack ); + style.SetMenuBarRolloverTextColor( aMenuFore ); + } + style.SetMenuBarHighlightTextColor(style.GetMenuHighlightTextColor()); + + // Font + aFont = toFont( pMenuBar->font(), rSettings.GetUILanguageTag().getLocale() ); + style.SetMenuFont( aFont ); + + // Scroll bar size + style.SetScrollBarSize( QApplication::style()->pixelMetric( QStyle::PM_ScrollBarExtent ) ); + style.SetMinThumbSize( QApplication::style()->pixelMetric( QStyle::PM_ScrollBarSliderMin )); + + // These colors are used for the ruler text and marks + style.SetShadowColor(toColor(pal.color(QPalette::Disabled, QPalette::WindowText))); + style.SetDarkShadowColor(toColor(pal.color(QPalette::Inactive, QPalette::WindowText))); + + rSettings.SetStyleSettings( style ); +} + +void KDESalFrame::ReleaseGraphics( SalGraphics *pGraphics ) +{ + for( int i = 0; i < nMaxGraphics; i++ ) + { + if( m_aGraphics[i].pGraphics.get() == pGraphics ) + { + m_aGraphics[i].bInUse = false; + break; + } + } +} + +void KDESalFrame::updateGraphics( bool bClear ) +{ + Drawable aDrawable = bClear ? None : GetWindow(); + for( int i = 0; i < nMaxGraphics; i++ ) + { + if( m_aGraphics[i].bInUse ) + m_aGraphics[i].pGraphics->SetDrawable( aDrawable, GetScreenNumber() ); + } +} + +SalGraphics* KDESalFrame::AcquireGraphics() +{ + if( GetWindow() ) + { + for( int i = 0; i < nMaxGraphics; i++ ) + { + if( ! m_aGraphics[i].bInUse ) + { + m_aGraphics[i].bInUse = true; + if( ! m_aGraphics[i].pGraphics ) + { + m_aGraphics[i].pGraphics.reset( new KDESalGraphics ); + m_aGraphics[i].pGraphics->Init( this, GetWindow(), GetScreenNumber() ); + } + return m_aGraphics[i].pGraphics.get(); + } + } + } + + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/KDE5SalFrame.hxx b/vcl/unx/kde5/KDE5SalFrame.hxx new file mode 100644 index 000000000000..114cd49bd8d2 --- /dev/null +++ b/vcl/unx/kde5/KDE5SalFrame.hxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <memory> + +#include <unx/saldisp.hxx> +#include <unx/salframe.h> +#include <unx/salgdi.h> + +class KDESalFrame : public X11SalFrame +{ + private: + static const int nMaxGraphics = 2; + + struct GraphicsHolder + { + std::unique_ptr<X11SalGraphics> pGraphics; + bool bInUse; + + GraphicsHolder() : bInUse( false ) {} + }; + + GraphicsHolder m_aGraphics[ nMaxGraphics ]; + + public: + KDESalFrame( SalFrame* pParent, SalFrameStyleFlags nStyle ); + + virtual SalGraphics* AcquireGraphics() override; + virtual void ReleaseGraphics( SalGraphics *pGraphics ) override; + virtual void updateGraphics( bool bClear ) override; + virtual void UpdateSettings( AllSettings& rSettings ) override; + virtual void Show( bool bVisible, bool bNoActivate = false ) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/KDE5SalGraphics.cxx b/vcl/unx/kde5/KDE5SalGraphics.cxx new file mode 100644 index 000000000000..85642f6f91df --- /dev/null +++ b/vcl/unx/kde5/KDE5SalGraphics.cxx @@ -0,0 +1,1020 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <QtGui/QPainter> +#include <QtWidgets/QApplication> +#include <QtWidgets/QStyle> +#include <QtWidgets/QStyleOption> +#include <QtWidgets/QFrame> +#include <QtWidgets/QLabel> + +#undef Region + +#include "KDE5SalGraphics.hxx" +#include "KDE5SalInstance.hxx" + +#include <vcl/settings.hxx> +#include <vcl/decoview.hxx> +#include <rtl/ustrbuf.hxx> + +/** + Conversion function between VCL ControlState together with + ImplControlValue and Qt state flags. + @param nControlState State of the widget (default, focused, ...) in Native Widget Framework. + @param aValue Value held by the widget (on, off, ...) +*/ +QStyle::State vclStateValue2StateFlag( ControlState nControlState, + const ImplControlValue& aValue ) +{ + QStyle::State nState = + ( (nControlState & ControlState::ENABLED)? QStyle::State_Enabled: QStyle::State_None ) | + ( (nControlState & ControlState::FOCUSED)? QStyle::State_HasFocus: QStyle::State_None ) | + ( (nControlState & ControlState::PRESSED)? QStyle::State_Sunken: QStyle::State_None ) | + ( (nControlState & ControlState::SELECTED)? QStyle::State_Selected : QStyle::State_None ) | + ( (nControlState & ControlState::ROLLOVER)? QStyle::State_MouseOver: QStyle::State_None ); + + switch ( aValue.getTristateVal() ) + { + case ButtonValue::On: nState |= QStyle::State_On; break; + case ButtonValue::Off: nState |= QStyle::State_Off; break; + case ButtonValue::Mixed: nState |= QStyle::State_NoChange; break; + default: break; + } + + return nState; +} + +/** + Convert tools::Rectangle to QRect. + @param rControlRegion The tools::Rectangle to convert. + @return The matching QRect +*/ +QRect region2QRect( const tools::Rectangle& rControlRegion ) +{ + return QRect(rControlRegion.Left(), rControlRegion.Top(), rControlRegion.GetWidth(), rControlRegion.GetHeight()); +} + +bool KDESalGraphics::IsNativeControlSupported( ControlType type, ControlPart part ) +{ + switch (type) + { + case ControlType::Pushbutton: + case ControlType::Radiobutton: + case ControlType::Checkbox: + case ControlType::Tooltip: + case ControlType::Progress: + case ControlType::ListNode: + return (part == ControlPart::Entire); + + case ControlType::Menubar: + case ControlType::MenuPopup: + case ControlType::Editbox: + case ControlType::MultilineEditbox: + case ControlType::Combobox: + case ControlType::Toolbar: + case ControlType::Frame: + case ControlType::Scrollbar: + case ControlType::WindowBackground: + case ControlType::Fixedline: + return true; + + case ControlType::Listbox: + return (part == ControlPart::Entire || part == ControlPart::HasBackgroundTexture); + + case ControlType::Spinbox: + return (part == ControlPart::Entire || part == ControlPart::HasBackgroundTexture); + + case ControlType::Slider: + return (part == ControlPart::TrackHorzArea || part == ControlPart::TrackVertArea); + + default: + break; + } + + return false; +} + +/// helper drawing methods +namespace +{ + void draw( QStyle::ControlElement element, QStyleOption* option, QImage* image, QStyle::State const & state, QRect rect = QRect()) + { + option->state |= state; + option->rect = !rect.isNull() ? rect : image->rect(); + + QPainter painter(image); + QApplication::style()->drawControl(element, option, &painter); + } + + void draw( QStyle::PrimitiveElement element, QStyleOption* option, QImage* image, QStyle::State const & state, QRect rect = QRect()) + { + option->state |= state; + option->rect = !rect.isNull() ? rect : image->rect(); + + QPainter painter(image); + QApplication::style()->drawPrimitive(element, option, &painter); + } + + void draw( QStyle::ComplexControl element, QStyleOptionComplex* option, QImage* image, QStyle::State const & state ) + { + option->state |= state; + option->rect = image->rect(); + + QPainter painter(image); + QApplication::style()->drawComplexControl(element, option, &painter); + } + + void lcl_drawFrame( QStyle::PrimitiveElement element, QImage* image, QStyle::State const & state, + QStyle::PixelMetric eLineMetric = QStyle::PM_DefaultFrameWidth ) + { + #if ( QT_VERSION >= QT_VERSION_CHECK( 4, 5, 0 ) ) + QStyleOptionFrameV3 option; + option.frameShape = QFrame::StyledPanel; + option.state = QStyle::State_Sunken; + option.lineWidth = QApplication::style()->pixelMetric( eLineMetric ); + #else + QStyleOptionFrame option; + + QFrame aFrame( nullptr ); + aFrame.setFrameRect( QRect(0, 0, image->width(), image->height()) ); + aFrame.setFrameStyle( QFrame::StyledPanel | QFrame::Sunken ); + aFrame.ensurePolished(); + + option.initFrom( &aFrame ); + option.lineWidth = aFrame.lineWidth(); + option.midLineWidth = aFrame.midLineWidth(); + #endif + draw(element, &option, image, state); + } +} + +#if QT_VERSION >= QT_VERSION_CHECK( 4, 5, 0 ) +#define IMAGE_BASED_PAINTING +#else +#undef IMAGE_BASED_PAINTING +#endif + +#ifdef IMAGE_BASED_PAINTING +// There is a small catch with this function, although hopefully only philosophical. +// Officially Xlib's vcl::Region is an opaque data type, with only functions for manipulating it. +// However, whoever designed it apparently didn't give it that much thought, as it's impossible +// to find out what exactly a region actually is (except for really weird ways like XClipBox() +// and repeated XPointInRegion(), which would be awfully slow). Fortunately, the header file +// describing the structure actually happens to be installed too, and there's at least one +// widely used software using it (Compiz). So access the data directly too and assume that +// everybody who compiles with Qt4 support has Xlib new enough and good enough to support this. +// In case this doesn't work for somebody, try #include <X11/region.h> instead, or build +// without IMAGE_BASED_PAINTING (in which case QApplication::setGraphicsSystem( "native" ) may +// be needed too). +#include <X11/Xregion.h> +static QRegion XRegionToQRegion( Region xr ) +{ + QRegion qr; + for( long i = 0; + i < xr->numRects; + ++i ) + { + BOX& b = xr->rects[ i ]; + qr |= QRect( b.x1, b.y1, b.x2 - b.x1, b.y2 - b.y1 ); // x2,y2 is outside, not the bottom-right corner + } + return qr; +} +#endif + +bool KDESalGraphics::drawNativeControl( ControlType type, ControlPart part, + const tools::Rectangle& rControlRegion, ControlState nControlState, + const ImplControlValue& value, + const OUString& ) +{ + bool nativeSupport = IsNativeControlSupported( type, part ); + if( ! nativeSupport ) { + assert( ! nativeSupport && "drawNativeControl called without native support!" ); + return false; + } + + if( lastPopupRect.isValid() && ( type != ControlType::MenuPopup || part != ControlPart::MenuItem )) + lastPopupRect = QRect(); + + bool returnVal = true; + + QRect widgetRect = region2QRect(rControlRegion); + + //if no image, or resized, make a new image + if (!m_image || m_image->size() != widgetRect.size()) + { + m_image.reset(new QImage( widgetRect.width(), widgetRect.height(), QImage::Format_ARGB32 ) ); + } + + // Default image color - just once + switch (type) + { + case ControlType::MenuPopup: + if( part == ControlPart::MenuItemCheckMark || part == ControlPart::MenuItemRadioMark ) + { + // it is necessary to fill the background transparently first, as this + // is painted after menuitem highlight, otherwise there would be a grey area + m_image->fill( Qt::transparent ); + break; + } + SAL_FALLTHROUGH; // QPalette::Window + case ControlType::Menubar: + case ControlType::WindowBackground: + m_image->fill( QApplication::palette().color(QPalette::Window).rgb() ); + break; + case ControlType::Tooltip: + m_image->fill(QApplication::palette().color(QPalette::ToolTipBase).rgb()); + break; + case ControlType::Pushbutton: + m_image->fill(QApplication::palette().color(QPalette::Button).rgb()); + break; + case ControlType::Scrollbar: + if ((part == ControlPart::DrawBackgroundVert) + || (part == ControlPart::DrawBackgroundHorz)) + { + m_image->fill( QApplication::palette().color(QPalette::Window).rgb() ); + break; + } + SAL_FALLTHROUGH; // Qt::transparent + default: + m_image->fill( Qt::transparent ); + break; + } + + QRegion* localClipRegion = nullptr; + + if (type == ControlType::Pushbutton) + { + QStyleOptionButton option; + draw( QStyle::CE_PushButton, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + } + else if (type == ControlType::Menubar) + { + if (part == ControlPart::MenuItem) + { + QStyleOptionMenuItem option; + if ( ( nControlState & ControlState::ROLLOVER ) + && QApplication::style()->styleHint( QStyle::SH_MenuBar_MouseTracking ) ) + option.state |= QStyle::State_Selected; + + if ( nControlState & ControlState::SELECTED ) // Passing State_Sunken is currently not documented. + option.state |= QStyle::State_Sunken; // But some kinds of QStyle interpret it. + + draw( QStyle::CE_MenuBarItem, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + } + else if (part == ControlPart::Entire) + { + QStyleOptionMenuItem option; + draw( QStyle::CE_MenuBarEmptyArea, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + } + else + { + returnVal = false; + } + } + else if (type == ControlType::MenuPopup) + { + OSL_ASSERT( part == ControlPart::MenuItem ? lastPopupRect.isValid() : !lastPopupRect.isValid()); + if( part == ControlPart::MenuItem ) + { + QStyleOptionMenuItem option; + draw( QStyle::CE_MenuItem, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + // HACK: LO core first paints the entire popup and only then it paints menu items, + // but QMenu::paintEvent() paints popup frame after all items. That means highlighted + // items here would paint the highlight over the frame border. Since calls to ControlPart::MenuItem + // are always preceded by calls to ControlPart::Entire, just remember the size for the whole + // popup (otherwise not possible to get here) and draw the border afterwards. + QRect framerect( lastPopupRect.topLeft() - widgetRect.topLeft(), + widgetRect.size().expandedTo( lastPopupRect.size())); + QStyleOptionFrame frame; + draw( QStyle::PE_FrameMenu, &frame, m_image.get(), vclStateValue2StateFlag( nControlState, value ), framerect ); + } + else if( part == ControlPart::Separator ) + { + QStyleOptionMenuItem option; + option.menuItemType = QStyleOptionMenuItem::Separator; + // Painting the whole menu item area results in different background + // with at least Plastique style, so clip only to the separator itself + // (QSize( 2, 2 ) is hardcoded in Qt) + option.rect = m_image->rect(); + QSize size = QApplication::style()->sizeFromContents( QStyle::CT_MenuItem, &option, QSize( 2, 2 )); + QRect rect = m_image->rect(); + QPoint center = rect.center(); + rect.setHeight( size.height()); + rect.moveCenter( center ); + // don't paint over popup frame border (like the hack above, but here it can be simpler) + int fw = QApplication::style()->pixelMetric( QStyle::PM_MenuPanelWidth ); + localClipRegion = new QRegion(rect.translated(widgetRect.topLeft()).adjusted(fw, 0, -fw, 0)); + draw( QStyle::CE_MenuItem, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value), rect ); + } + else if( part == ControlPart::MenuItemCheckMark || part == ControlPart::MenuItemRadioMark ) + { + QStyleOptionMenuItem option; + option.checkType = ( part == ControlPart::MenuItemCheckMark ) + ? QStyleOptionMenuItem::NonExclusive : QStyleOptionMenuItem::Exclusive; + option.checked = bool( nControlState & ControlState::PRESSED ); + // widgetRect is now the rectangle for the checkbox/radiobutton itself, but Qt + // paints the whole menu item, so translate position (and it'll be clipped); + // it is also necessary to fill the background transparently first, as this + // is painted after menuitem highlight, otherwise there would be a grey area + assert( value.getType() == ControlType::MenuPopup ); + const MenupopupValue* menuVal = static_cast<const MenupopupValue*>(&value); + QRect menuItemRect( region2QRect( menuVal->maItemRect )); + QRect rect( menuItemRect.topLeft() - widgetRect.topLeft(), + widgetRect.size().expandedTo( menuItemRect.size())); + // checkboxes are always displayed next to images in menus, so are never centered + const int focus_size = QApplication::style()->pixelMetric( QStyle::PM_FocusFrameHMargin ); + rect.moveTo( -focus_size, rect.y() ); + draw( QStyle::CE_MenuItem, &option, m_image.get(), + vclStateValue2StateFlag(nControlState & ~ControlState::PRESSED, value), rect ); + } + else if( part == ControlPart::Entire ) + { + QStyleOptionMenuItem option; + draw( QStyle::PE_PanelMenu, &option, m_image.get(), vclStateValue2StateFlag( nControlState, value )); + // Try hard to get any frame! + QStyleOptionFrame frame; + draw( QStyle::PE_FrameMenu, &frame, m_image.get(), vclStateValue2StateFlag( nControlState, value )); + draw( QStyle::PE_FrameWindow, &frame, m_image.get(), vclStateValue2StateFlag( nControlState, value )); + lastPopupRect = widgetRect; + } + else + returnVal = false; + } + else if ( (type == ControlType::Toolbar) && (part == ControlPart::Button) ) + { + QStyleOptionToolButton option; + + option.arrowType = Qt::NoArrow; + option.subControls = QStyle::SC_ToolButton; + + option.state = vclStateValue2StateFlag( nControlState, value ); + option.state |= QStyle::State_Raised | QStyle::State_Enabled | QStyle::State_AutoRaise; + + draw( QStyle::CC_ToolButton, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + } + else if ( (type == ControlType::Toolbar) && (part == ControlPart::Entire) ) + { + QStyleOptionToolBar option; + + option.rect = QRect(0, 0, widgetRect.width(), widgetRect.height()); + option.state = vclStateValue2StateFlag( nControlState, value ); + + draw( QStyle::CE_ToolBar, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + } + else if ( (type == ControlType::Toolbar) + && (part == ControlPart::ThumbVert || part == ControlPart::ThumbHorz) ) + { // reduce paint area only to the handle area + const int handleExtend = QApplication::style()->pixelMetric(QStyle::PM_ToolBarHandleExtent); + QRect rect; + QStyleOption option; + + if (part == ControlPart::ThumbVert) + { + rect = QRect( 0, 0, handleExtend, widgetRect.height()); + localClipRegion = new QRegion(widgetRect.x(), widgetRect.y(), handleExtend, widgetRect.height()); + option.state = QStyle::State_Horizontal; + } + else + { + rect = QRect( 0, 0, widgetRect.width(), handleExtend); + localClipRegion = new QRegion(widgetRect.x(), widgetRect.y(), widgetRect.width(), handleExtend); + } + + draw( QStyle::PE_IndicatorToolBarHandle, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value), rect ); + } + else if (type == ControlType::Editbox || type == ControlType::MultilineEditbox) + { + lcl_drawFrame( QStyle::PE_FrameLineEdit, m_image.get(), + vclStateValue2StateFlag(nControlState, value)); + } + else if (type == ControlType::Combobox) + { + QStyleOptionComboBox option; + option.editable = true; + draw( QStyle::CC_ComboBox, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + } + else if (type == ControlType::Listbox) + { + QStyleOptionComboBox option; + option.editable = false; + switch (part) { + case ControlPart::ListboxWindow: + lcl_drawFrame( QStyle::PE_Frame, m_image.get(), + vclStateValue2StateFlag(nControlState, value), + QStyle::PM_ComboBoxFrameWidth ); + break; + case ControlPart::SubEdit: + draw( QStyle::CE_ComboBoxLabel, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + break; + case ControlPart::Entire: + draw( QStyle::CC_ComboBox, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + break; + case ControlPart::ButtonDown: + option.subControls = QStyle::SC_ComboBoxArrow; + draw( QStyle::CC_ComboBox, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + break; + default: + returnVal = false; + break; + } + } + else if (type == ControlType::ListNode) + { + QStyleOption option; + option.state = QStyle::State_Item | QStyle::State_Children; + + if (value.getTristateVal() == ButtonValue::On) + option.state |= QStyle::State_Open; + + draw( QStyle::PE_IndicatorBranch, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + } + else if (type == ControlType::Checkbox) + { + QStyleOptionButton option; + draw( QStyle::CE_CheckBox, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + } + else if (type == ControlType::Scrollbar) + { + if ((part == ControlPart::DrawBackgroundVert) || (part == ControlPart::DrawBackgroundHorz)) + { + QStyleOptionSlider option; + OSL_ASSERT( value.getType() == ControlType::Scrollbar ); + const ScrollbarValue* sbVal = static_cast<const ScrollbarValue *>(&value); + + //if the scroll bar is active (aka not degenrate...allow for hover events + if (sbVal->mnVisibleSize < sbVal->mnMax) + option.state = QStyle::State_MouseOver; + + bool horizontal = ( part == ControlPart::DrawBackgroundHorz ); //horizontal or vertical + option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical; + if( horizontal ) + option.state |= QStyle::State_Horizontal; + + //setup parameters from the OO values + option.minimum = sbVal->mnMin; + option.maximum = sbVal->mnMax - sbVal->mnVisibleSize; + option.maximum = qMax( option.maximum, option.minimum ); // bnc#619772 + option.sliderValue = sbVal->mnCur; + option.sliderPosition = sbVal->mnCur; + option.pageStep = sbVal->mnVisibleSize; + if (part == ControlPart::DrawBackgroundHorz) + option.upsideDown = sbVal->maButton1Rect.Left() > sbVal->maButton2Rect.Left(); + + //setup the active control...always the slider + if (sbVal->mnThumbState & ControlState::ROLLOVER) + option.activeSubControls = QStyle::SC_ScrollBarSlider; + + draw( QStyle::CC_ScrollBar, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + } + else + { + returnVal = false; + } + } + else if (type == ControlType::Spinbox) + { + QStyleOptionSpinBox option; + option.frame = true; + + // determine active control + if( value.getType() == ControlType::SpinButtons ) + { + const SpinbuttonValue* pSpinVal = static_cast<const SpinbuttonValue *>(&value); + if( (pSpinVal->mnUpperState & ControlState::PRESSED) ) + option.activeSubControls |= QStyle::SC_SpinBoxUp; + if( (pSpinVal->mnLowerState & ControlState::PRESSED) ) + option.activeSubControls |= QStyle::SC_SpinBoxDown; + if( (pSpinVal->mnUpperState & ControlState::ENABLED) ) + option.stepEnabled |= QAbstractSpinBox::StepUpEnabled; + if( (pSpinVal->mnLowerState & ControlState::ENABLED) ) + option.stepEnabled |= QAbstractSpinBox::StepDownEnabled; + if( (pSpinVal->mnUpperState & ControlState::ROLLOVER) ) + option.state = QStyle::State_MouseOver; + if( (pSpinVal->mnLowerState & ControlState::ROLLOVER) ) + option.state = QStyle::State_MouseOver; + } + + draw( QStyle::CC_SpinBox, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + } + else if (type == ControlType::Radiobutton) + { + QStyleOptionButton option; + draw( QStyle::CE_RadioButton, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + } + else if (type == ControlType::Tooltip) + { + QStyleOption option; + draw( QStyle::PE_PanelTipLabel, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + } + else if (type == ControlType::Frame) + { + lcl_drawFrame( QStyle::PE_Frame, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + // draw just the border, see http://qa.openoffice.org/issues/show_bug.cgi?id=107945 + int fw = QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + localClipRegion = new QRegion(QRegion(widgetRect).subtracted(widgetRect.adjusted(fw, fw, -fw, -fw))); + } + else if (type == ControlType::WindowBackground) + { + // Nothing to do - see "Default image color" switch ^^ + } + else if (type == ControlType::Fixedline) + { + QStyleOptionMenuItem option; + option.menuItemType = QStyleOptionMenuItem::Separator; + option.state |= QStyle::State_Item; + + draw( QStyle::CE_MenuItem, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + } + else if (type == ControlType::Slider && (part == ControlPart::TrackHorzArea || part == ControlPart::TrackVertArea)) + { + OSL_ASSERT( value.getType() == ControlType::Slider ); + const SliderValue* slVal = static_cast<const SliderValue *>(&value); + QStyleOptionSlider option; + + option.rect = QRect(0, 0, widgetRect.width(), widgetRect.height()); + option.state = vclStateValue2StateFlag( nControlState, value ); + option.maximum = slVal->mnMax; + option.minimum = slVal->mnMin; + option.sliderPosition = option.sliderValue = slVal->mnCur; + bool horizontal = ( part == ControlPart::TrackHorzArea ); //horizontal or vertical + option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical; + if( horizontal ) + option.state |= QStyle::State_Horizontal; + + draw( QStyle::CC_Slider, &option, m_image.get(), vclStateValue2StateFlag(nControlState, value) ); + } + else if( type == ControlType::Progress && part == ControlPart::Entire ) + { + QStyleOptionProgressBarV2 option; + option.minimum = 0; + option.maximum = widgetRect.width(); + option.progress = value.getNumericVal(); + option.rect = QRect(0, 0, widgetRect.width(), widgetRect.height()); + option.state = vclStateValue2StateFlag( nControlState, value ); + + draw( QStyle::CE_ProgressBar, &option, m_image.get(), + vclStateValue2StateFlag(nControlState, value) ); + } + else + { + returnVal = false; + } + + if (returnVal) + { +#if 0 +#ifdef IMAGE_BASED_PAINTING + // Create a wrapper QPixmap around the destination pixmap, allowing the use of QPainter. + // Using X11SalGraphics::CopyScreenArea() would require using QPixmap and if Qt uses + // other graphics system than native, QPixmap::handle() would be 0 (i.e. it wouldn't work), + // I have no idea how to create QPixmap with non-null handle() in such case, so go this way. + // See XRegionToQRegion() comment for a small catch (although not real hopefully). + QPixmap destPixmap = QPixmap::fromX11Pixmap( GetDrawable(), QPixmap::ExplicitlyShared ); + QPainter paint( &destPixmap ); + if (localClipRegion && mpClipRegion) + paint.setClipRegion(localClipRegion->intersected(XRegionToQRegion(mpClipRegion))); + else if (localClipRegion) + paint.setClipRegion(*localClipRegion); + else if( mpClipRegion ) + paint.setClipRegion( XRegionToQRegion( mpClipRegion )); + paint.drawImage( widgetRect.left(), widgetRect.top(), *m_image, + 0, 0, widgetRect.width(), widgetRect.height(), + Qt::ColorOnly | Qt::OrderedDither | Qt::OrderedAlphaDither ); +#else + GC gc = GetFontGC(); + if( gc ) + { + Region pTempClipRegion = NULL; + if (localClipRegion) + { + pTempClipRegion = XCreateRegion(); + foreach(const QRect& r, localClipRegion->rects()) + { + XRectangle xr; + xr.x = r.x(); + xr.y = r.y(); + xr.width = r.width(); + xr.height = r.height(); + XUnionRectWithRegion( &xr, pTempClipRegion, pTempClipRegion ); + } + if( mpClipRegion ) + XIntersectRegion( pTempClipRegion, mpClipRegion, pTempClipRegion ); + XSetRegion( GetXDisplay(), gc, pTempClipRegion ); + } + QPixmap pixmap = QPixmap::fromImage(*m_image, Qt::ColorOnly | Qt::OrderedDither | Qt::OrderedAlphaDither); + X11SalGraphics::CopyScreenArea( GetXDisplay(), + pixmap.handle(), pixmap.x11Info().screen(), pixmap.x11Info().depth(), + GetDrawable(), GetScreenNumber(), GetVisual().GetDepth(), + gc, 0, 0, widgetRect.width(), widgetRect.height(), widgetRect.left(), widgetRect.top()); + + if( pTempClipRegion ) + { + if( mpClipRegion ) + XSetRegion( GetXDisplay(), gc, mpClipRegion ); + else + XSetClipMask( GetXDisplay(), gc, None ); + XDestroyRegion( pTempClipRegion ); + } + } + else + returnVal = false; +#endif +#endif + } + delete localClipRegion; + return returnVal; +} + +bool KDESalGraphics::getNativeControlRegion( ControlType type, ControlPart part, + const tools::Rectangle& controlRegion, ControlState controlState, + const ImplControlValue& val, + const OUString&, + tools::Rectangle &nativeBoundingRegion, tools::Rectangle &nativeContentRegion ) +{ + bool retVal = false; + + QRect boundingRect = region2QRect( controlRegion ); + QRect contentRect = boundingRect; + QStyleOptionComplex styleOption; + + switch ( type ) + { + // Metrics of the push button + case ControlType::Pushbutton: + if (part == ControlPart::Entire) + { + styleOption.state = vclStateValue2StateFlag(controlState, val); + + if ( controlState & ControlState::DEFAULT ) + { + int size = QApplication::style()->pixelMetric( + QStyle::PM_ButtonDefaultIndicator, &styleOption ); + boundingRect.adjust( -size, -size, size, size ); + retVal = true; + } + } + break; + case ControlType::Editbox: + case ControlType::MultilineEditbox: + { + QStyleOptionFrameV3 fo; + fo.frameShape = QFrame::StyledPanel; + fo.state = QStyle::State_Sunken; + fo.lineWidth = QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + QSize aMinSize = QApplication::style()-> + sizeFromContents( QStyle::CT_LineEdit, &fo, contentRect.size() ); + if( aMinSize.height() > boundingRect.height() ) + { + int nHeight = (aMinSize.height() - boundingRect.height()) / 2; + assert( 0 == (aMinSize.height() - boundingRect.height()) % 2 ); + boundingRect.adjust( 0, -nHeight, 0, nHeight ); + } + if( aMinSize.width() > boundingRect.width() ) + { + int nWidth = (aMinSize.width() - boundingRect.width()) / 2; + assert( 0 == (aMinSize.width() - boundingRect.width()) % 2 ); + boundingRect.adjust( -nWidth, 0, nWidth, 0 ); + } + retVal = true; + break; + } + case ControlType::Checkbox: + if (part == ControlPart::Entire) + { + styleOption.state = vclStateValue2StateFlag(controlState, val); + + contentRect.setWidth(QApplication::style()->pixelMetric( + QStyle::PM_IndicatorWidth, &styleOption)); + contentRect.setHeight(QApplication::style()->pixelMetric( + QStyle::PM_IndicatorHeight, &styleOption)); + + contentRect.adjust(0, 0, + 2 * QApplication::style()->pixelMetric( + QStyle::PM_FocusFrameHMargin, &styleOption), + 2 * QApplication::style()->pixelMetric( + QStyle::PM_FocusFrameVMargin, &styleOption) + ); + + boundingRect = contentRect; + + retVal = true; + } + break; + case ControlType::Combobox: + case ControlType::Listbox: + { + QStyleOptionComboBox cbo; + + cbo.rect = QRect(0, 0, contentRect.width(), contentRect.height()); + cbo.state = vclStateValue2StateFlag(controlState, val); + + switch ( part ) + { + case ControlPart::Entire: + { + // find out the minimum size that should be used + // assume contents is a text ling + int nHeight = QApplication::fontMetrics().height(); + QSize aContentSize( contentRect.width(), nHeight ); + QSize aMinSize = QApplication::style()-> + sizeFromContents( QStyle::CT_ComboBox, &cbo, aContentSize ); + if( aMinSize.height() > contentRect.height() ) + contentRect.adjust( 0, 0, 0, aMinSize.height() - contentRect.height() ); + boundingRect = contentRect; + retVal = true; + break; + } + case ControlPart::ButtonDown: + contentRect = QApplication::style()->subControlRect( + QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxArrow ); + contentRect.translate( boundingRect.left(), boundingRect.top() ); + retVal = true; + break; + case ControlPart::SubEdit: + { + contentRect = QApplication::style()->subControlRect( + QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxEditField ); + contentRect.translate( boundingRect.left(), boundingRect.top() ); + retVal = true; + break; + } + default: + break; + } + break; + } + case ControlType::Spinbox: + { + QStyleOptionSpinBox sbo; + sbo.frame = true; + + sbo.rect = QRect(0, 0, contentRect.width(), contentRect.height()); + sbo.state = vclStateValue2StateFlag(controlState, val); + + switch ( part ) + { + case ControlPart::Entire: + { + int nHeight = QApplication::fontMetrics().height(); + QSize aContentSize( contentRect.width(), nHeight ); + QSize aMinSize = QApplication::style()-> + sizeFromContents( QStyle::CT_SpinBox, &sbo, aContentSize ); + if( aMinSize.height() > contentRect.height() ) + contentRect.adjust( 0, 0, 0, aMinSize.height() - contentRect.height() ); + boundingRect = contentRect; + retVal = true; + break; + } + case ControlPart::ButtonUp: + contentRect = QApplication::style()->subControlRect( + QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxUp ); + contentRect.translate( boundingRect.left(), boundingRect.top() ); + retVal = true; + boundingRect = QRect(); + break; + + case ControlPart::ButtonDown: + contentRect = QApplication::style()->subControlRect( + QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxDown ); + retVal = true; + contentRect.translate( boundingRect.left(), boundingRect.top() ); + boundingRect = QRect(); + break; + + case ControlPart::SubEdit: + contentRect = QApplication::style()->subControlRect( + QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxEditField ); + retVal = true; + contentRect.translate( boundingRect.left(), boundingRect.top() ); + break; + default: + break; + } + break; + } + case ControlType::MenuPopup: + { + int h, w; + switch ( part ) { + case ControlPart::MenuItemCheckMark: + h = QApplication::style()->pixelMetric(QStyle::PM_IndicatorHeight); + w = QApplication::style()->pixelMetric(QStyle::PM_IndicatorWidth); + retVal = true; + break; + case ControlPart::MenuItemRadioMark: + h = QApplication::style()->pixelMetric(QStyle::PM_ExclusiveIndicatorHeight); + w = QApplication::style()->pixelMetric(QStyle::PM_ExclusiveIndicatorWidth); + retVal = true; + break; + default: + break; + } + if (retVal) { + contentRect = QRect(0, 0, w, h); + boundingRect = contentRect; + } + break; + } + case ControlType::Frame: + { + if( part == ControlPart::Border ) + { + auto nStyle = static_cast<DrawFrameFlags>( + val.getNumericVal() & 0xFFF0); + if( nStyle & DrawFrameFlags::NoDraw ) + { + int nFrameWidth = QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + contentRect.adjust(nFrameWidth, nFrameWidth, -nFrameWidth, -nFrameWidth); + } + retVal = true; + } + break; + } + case ControlType::Radiobutton: + { + const int h = QApplication::style()->pixelMetric(QStyle::PM_ExclusiveIndicatorHeight); + const int w = QApplication::style()->pixelMetric(QStyle::PM_ExclusiveIndicatorWidth); + + contentRect = QRect(boundingRect.left(), boundingRect.top(), w, h); + contentRect.adjust(0, 0, + 2 * QApplication::style()->pixelMetric( + QStyle::PM_FocusFrameHMargin, &styleOption), + 2 * QApplication::style()->pixelMetric( + QStyle::PM_FocusFrameVMargin, &styleOption) + ); + boundingRect = contentRect; + + retVal = true; + break; + } + case ControlType::Slider: + { + const int w = QApplication::style()->pixelMetric(QStyle::PM_SliderLength); + if( part == ControlPart::ThumbHorz ) + { + contentRect = QRect(boundingRect.left(), boundingRect.top(), w, boundingRect.height()); + boundingRect = contentRect; + retVal = true; + } + else if( part == ControlPart::ThumbVert ) + { + contentRect = QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), w); + boundingRect = contentRect; + retVal = true; + } + break; + } + case ControlType::Toolbar: + { + const int nWorH = QApplication::style()->pixelMetric(QStyle::PM_ToolBarHandleExtent); + if( part == ControlPart::ThumbHorz ) + { + contentRect = QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), nWorH ); + boundingRect = contentRect; + retVal = true; + } + else if( part == ControlPart::ThumbVert ) + { + contentRect = QRect(boundingRect.left(), boundingRect.top(), nWorH, boundingRect.height() ); + boundingRect = contentRect; + retVal = true; + } + break; + } + case ControlType::Scrollbar: + { + // core can't handle 3-button scrollbars well, so we fix that in hitTestNativeControl(), + // for the rest also provide the track area (i.e. area not taken by buttons) + if( part == ControlPart::TrackVertArea || part == ControlPart::TrackHorzArea ) + { + QStyleOptionSlider option; + bool horizontal = ( part == ControlPart::TrackHorzArea ); //horizontal or vertical + option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical; + if( horizontal ) + option.state |= QStyle::State_Horizontal; + // getNativeControlRegion usually gets ImplControlValue as 'val' (i.e. not the proper + // subclass), so use random sensible values (doesn't matter anyway, as the wanted + // geometry here depends only on button sizes) + option.maximum = 10; + option.minimum = 0; + option.sliderPosition = option.sliderValue = 4; + option.pageStep = 2; + // Adjust coordinates to make the widget appear to be at (0,0), i.e. make + // widget and screen coordinates the same. QStyle functions should use screen + // coordinates but at least QPlastiqueStyle::subControlRect() is buggy + // and sometimes uses widget coordinates. + QRect rect = contentRect; + rect.moveTo( 0, 0 ); + option.rect = rect; + rect = QApplication::style()->subControlRect( QStyle::CC_ScrollBar, &option, + QStyle::SC_ScrollBarGroove ); + rect.translate( contentRect.topLeft()); // reverse the workaround above + contentRect = boundingRect = rect; + retVal = true; + } + break; + } + default: + break; + } + if (retVal) + { + // Bounding region + Point aBPoint( boundingRect.x(), boundingRect.y() ); + Size aBSize( boundingRect.width(), boundingRect.height() ); + nativeBoundingRegion = tools::Rectangle( aBPoint, aBSize ); + + // vcl::Region of the content + Point aPoint( contentRect.x(), contentRect.y() ); + Size aSize( contentRect.width(), contentRect.height() ); + nativeContentRegion = tools::Rectangle( aPoint, aSize ); + } + + return retVal; +} + +/** Test whether the position is in the native widget. + If the return value is TRUE, bIsInside contains information whether + aPos was or was not inside the native widget specified by the + nType/nPart combination. +*/ +bool KDESalGraphics::hitTestNativeControl( ControlType nType, ControlPart nPart, + const tools::Rectangle& rControlRegion, const Point& rPos, + bool& rIsInside ) +{ + if ( nType == ControlType::Scrollbar ) + { + if( nPart != ControlPart::ButtonUp && nPart != ControlPart::ButtonDown + && nPart != ControlPart::ButtonLeft && nPart != ControlPart::ButtonRight ) + { // we adjust only for buttons (because some scrollbars have 3 buttons, + // and LO core doesn't handle such scrollbars well) + return FALSE; + } + rIsInside = FALSE; + bool bHorizontal = ( nPart == ControlPart::ButtonLeft || nPart == ControlPart::ButtonRight ); + QRect rect = region2QRect( rControlRegion ); + QPoint pos( rPos.X(), rPos.Y()); + // Adjust coordinates to make the widget appear to be at (0,0), i.e. make + // widget and screen coordinates the same. QStyle functions should use screen + // coordinates but at least QPlastiqueStyle::subControlRect() is buggy + // and sometimes uses widget coordinates. + pos -= rect.topLeft(); + rect.moveTo( 0, 0 ); + QStyleOptionSlider options; + options.orientation = bHorizontal ? Qt::Horizontal : Qt::Vertical; + if( bHorizontal ) + options.state |= QStyle::State_Horizontal; + options.rect = rect; + // some random sensible values, since we call this code only for scrollbar buttons, + // the slider position does not exactly matter + options.maximum = 10; + options.minimum = 0; + options.sliderPosition = options.sliderValue = 4; + options.pageStep = 2; + QStyle::SubControl control = QApplication::style()->hitTestComplexControl( QStyle::CC_ScrollBar, &options, pos ); + if( nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonLeft ) + rIsInside = ( control == QStyle::SC_ScrollBarSubLine ); + else // DOWN, RIGHT + rIsInside = ( control == QStyle::SC_ScrollBarAddLine ); + return TRUE; + } + return FALSE; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/KDE5SalGraphics.hxx b/vcl/unx/kde5/KDE5SalGraphics.hxx new file mode 100644 index 000000000000..eb513388f369 --- /dev/null +++ b/vcl/unx/kde5/KDE5SalGraphics.hxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <memory> + +#include <rtl/string.hxx> +#include <unx/saldisp.hxx> +#include <unx/salgdi.h> + +#include <QtGui/QImage> + +/** + * Handles native graphics requests and performs the needed drawing operations. + */ +class KDESalGraphics : public X11SalGraphics +{ +public: + virtual bool IsNativeControlSupported( ControlType, ControlPart ) override; + + virtual bool hitTestNativeControl( ControlType, ControlPart, + const tools::Rectangle&, const Point&, bool& ) override; + + virtual bool drawNativeControl( ControlType, ControlPart, const tools::Rectangle&, + ControlState, const ImplControlValue&, const OUString& ) override; + + virtual bool getNativeControlRegion( ControlType, ControlPart, const tools::Rectangle&, + ControlState, const ImplControlValue&, + const OUString&, tools::Rectangle&, tools::Rectangle& ) override; + +private: + std::unique_ptr<QImage> m_image; + QRect lastPopupRect; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/KDE5SalInstance.cxx b/vcl/unx/kde5/KDE5SalInstance.cxx new file mode 100644 index 000000000000..5d4bc5eec5dd --- /dev/null +++ b/vcl/unx/kde5/KDE5SalInstance.cxx @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +//#include "KDE4FilePicker.hxx" +#include <QtCore/QAbstractEventDispatcher> +#include <QtCore/QThread> +#include <QtGui/QClipboard> +#include <QtWidgets/QApplication> +#include <QtWidgets/QFrame> +#include <QtX11Extras/QX11Info> + + +#include "KDE5SalInstance.hxx" +#include "KDE5SalFrame.hxx" +#include "KDE5XLib.hxx" +#include "KDE5SalDisplay.hxx" + +using namespace com::sun::star; + +KDESalInstance::KDESalInstance(SalYieldMutex* pMutex) + : X11SalInstance(pMutex) +{ + ImplSVData* pSVData = ImplGetSVData(); + delete pSVData->maAppData.mpToolkitName; + pSVData->maAppData.mpToolkitName = new OUString("kde4"); +} + +SalFrame* KDESalInstance::CreateFrame( SalFrame *pParent, SalFrameStyleFlags nState ) +{ + return new KDESalFrame( pParent, nState ); +} + +uno::Reference< ui::dialogs::XFilePicker2 > KDESalInstance::createFilePicker( + const uno::Reference< uno::XComponentContext >& xMSF ) +{ + /*KDEXLib* kdeXLib = static_cast<KDEXLib*>( mpXLib ); + if (kdeXLib->allowKdeDialogs()) + return uno::Reference< ui::dialogs::XFilePicker2 >( + kdeXLib->createFilePicker(xMSF) ); + else*/ + return X11SalInstance::createFilePicker( xMSF ); +} + +SalX11Display* KDESalInstance::CreateDisplay() const +{ + return new SalKDEDisplay( QX11Info::display() ); +} + +bool KDESalInstance::IsMainThread() const +{ + return qApp->thread() == QThread::currentThread(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/KDE5SalInstance.hxx b/vcl/unx/kde5/KDE5SalInstance.hxx new file mode 100644 index 000000000000..7b7417caaaf1 --- /dev/null +++ b/vcl/unx/kde5/KDE5SalInstance.hxx @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <unx/salinst.h> + +class SalYieldMutex; +class SalFrame; + +class KDESalInstance : public X11SalInstance +{ +protected: + virtual SalX11Display* CreateDisplay() const override; + +public: + explicit KDESalInstance(SalYieldMutex* pMutex); + virtual SalFrame* CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle ) override; + + virtual bool hasNativeFileSelection() const override { return true; } + + virtual css::uno::Reference< css::ui::dialogs::XFilePicker2 > + createFilePicker( const css::uno::Reference< + css::uno::XComponentContext >& ) override; + + virtual bool IsMainThread() const override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/KDE5XLib.cxx b/vcl/unx/kde5/KDE5XLib.cxx new file mode 100644 index 000000000000..b05e8e6ed047 --- /dev/null +++ b/vcl/unx/kde5/KDE5XLib.cxx @@ -0,0 +1,407 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <QtCore/QAbstractEventDispatcher> +#include <QtCore/QThread> +#include <QtWidgets/QApplication> +#include <QtWidgets/QFrame> +#include <QtGui/QClipboard> +#include <QtGui/QFrame> +#include <QtX11Extras/QX11Info> + + +#include "config_kde5.h" + +#include "KDE5XLib.hxx" +#include "VCLKDE5Application.hxx" +#include "KDE5SalInstance.hxx" + +#include <KAboutData> +#include <KLocalizedString> + +#include "unx/i18n_im.hxx" +#include "unx/i18n_xkb.hxx" +#include "unx/saldata.hxx" +#include <o3tl/make_unique.hxx> +#include "osl/process.h" + +#include "KDE5SalDisplay.hxx" + +#undef Bool + +#if KF5_HAVE_GLIB +//#include "KDE4FilePicker.hxx" +#include "tst_exclude_socket_notifiers.moc" +#include "tst_exclude_posted_events.moc" +#endif + +KDEXLib::KDEXLib() : + SalXLib(), m_bStartupDone(false), + m_nFakeCmdLineArgs( 0 ), + m_isGlibEventLoopType(false), m_allowKdeDialogs(false), + m_timerEventId( -1 ), m_postUserEventId( -1 ) +{ + m_timerEventId = QEvent::registerEventType(); + m_postUserEventId = QEvent::registerEventType(); + + // the timers created here means they belong to the main thread. + // As the timeoutTimer runs the LO event queue, which may block on a dialog, + // the timer has to use a Qt::QueuedConnection, otherwise the nested event + // loop will detect the blocking timer and drop it from the polling + // freezing LO X11 processing. + timeoutTimer.setSingleShot( true ); + connect( &timeoutTimer, SIGNAL( timeout()), this, SLOT( timeoutActivated()), Qt::QueuedConnection ); + + // QTimer::start() can be called only in its (here main) thread, so this will + // forward between threads if needed + connect( this, SIGNAL( startTimeoutTimerSignal()), this, SLOT( startTimeoutTimer()), Qt::QueuedConnection ); + + // this one needs to be blocking, so that the handling in main thread is processed before + // the thread emitting the signal continues + connect( this, SIGNAL( processYieldSignal( bool, bool )), this, SLOT( processYield( bool, bool )), + Qt::BlockingQueuedConnection ); + + // Create the File picker in the main / GUI thread and block the calling thread until + // the FilePicker is created. + connect( this, SIGNAL( createFilePickerSignal( const css::uno::Reference< css::uno::XComponentContext >&) ), + this, SLOT( createFilePicker( const css::uno::Reference< css::uno::XComponentContext >&) ), + Qt::BlockingQueuedConnection ); +} + +KDEXLib::~KDEXLib() +{ + + // free the faked cmdline arguments no longer needed by KApplication + for( int i = 0; i < m_nFakeCmdLineArgs; i++ ) + { + free( m_pFreeCmdLineArgs[i] ); + } +} + +void KDEXLib::Init() +{ + m_pInputMethod = new SalI18N_InputMethod; + m_pInputMethod->SetLocale(); + XrmInitialize(); + + KAboutData *kAboutData = new KAboutData( I18N_NOOP("LibreOffice"), + i18n( "LibreOffice" ), + "5.1.0", + i18n( "LibreOffice with KDE Native Widget Support." ), + KAboutLicense::File, + i18n("Copyright (c) 2000, 2014 LibreOffice contributors" ), + i18n( "LibreOffice is an office suite.\n" ), + "http://libreoffice.org", + QLatin1String("libreoffice@lists.freedesktop.org")); + + kAboutData->addAuthor( i18n( "Jan Holesovsky" ), + i18n( "Original author and maintainer of the KDE NWF." ), + "kendy@artax.karlin.mff.cuni.cz", + "http://artax.karlin.mff.cuni.cz/~kendy" ); + kAboutData->addAuthor( i18n("Roman Shtylman"), + i18n( "Porting to KDE 4." ), + "shtylman@gmail.com", "http://shtylman.com" ); + kAboutData->addAuthor( i18n("Eric Bischoff"), + i18n( "Accessibility fixes, porting to KDE 4." ), + "bischoff@kde.org" ); + + m_nFakeCmdLineArgs = 2; + + sal_uInt32 nParams = osl_getCommandArgCount(); + OString aDisplay; + OUString aParam, aBin; + + for ( sal_uInt32 nIdx = 0; nIdx < nParams; ++nIdx ) + { + osl_getCommandArg( nIdx, &aParam.pData ); + if ( !m_pFreeCmdLineArgs && aParam == "-display" && nIdx + 1 < nParams ) + { + osl_getCommandArg( nIdx + 1, &aParam.pData ); + aDisplay = OUStringToOString( aParam, osl_getThreadTextEncoding() ); + + m_pFreeCmdLineArgs = o3tl::make_unique<char*[]>(m_nFakeCmdLineArgs + 2); + m_pFreeCmdLineArgs[ m_nFakeCmdLineArgs + 0 ] = strdup( "-display" ); + m_pFreeCmdLineArgs[ m_nFakeCmdLineArgs + 1 ] = strdup( aDisplay.getStr() ); + m_nFakeCmdLineArgs += 2; + } + } + if ( !m_pFreeCmdLineArgs ) + m_pFreeCmdLineArgs = o3tl::make_unique<char*[]>(m_nFakeCmdLineArgs); + + osl_getExecutableFile( &aParam.pData ); + osl_getSystemPathFromFileURL( aParam.pData, &aBin.pData ); + OString aExec = OUStringToOString( aBin, osl_getThreadTextEncoding() ); + m_pFreeCmdLineArgs[0] = strdup( aExec.getStr() ); + m_pFreeCmdLineArgs[1] = strdup( "--nocrashhandler" ); + + // make a copy of the string list for freeing it since + // KApplication manipulates the pointers inside the argument vector + // note: KApplication bad ! + m_pAppCmdLineArgs = o3tl::make_unique<char*[]>(m_nFakeCmdLineArgs); + for( int i = 0; i < m_nFakeCmdLineArgs; i++ ) + m_pAppCmdLineArgs[i] = m_pFreeCmdLineArgs[i]; + + // LO does its own session management, so prevent KDE/Qt from interfering + // (QApplication::disableSessionManagement(false) wouldn't quite do, + // since that still actually connects to the session manager, it just + // won't save the application data on session shutdown). + char* session_manager = nullptr; + if( getenv( "SESSION_MANAGER" ) != nullptr ) + { + session_manager = strdup( getenv( "SESSION_MANAGER" )); + unsetenv( "SESSION_MANAGER" ); + } + //m_pApplication.reset( new VCLKDEApplication() ); + if( session_manager != nullptr ) + { + // coverity[tainted_string] - trusted source for setenv + setenv( "SESSION_MANAGER", session_manager, 1 ); + free( session_manager ); + } + + //KApplication::setQuitOnLastWindowClosed(false); + +#if KF5_HAVE_GLIB + m_isGlibEventLoopType = QAbstractEventDispatcher::instance()->inherits( "QEventDispatcherGlib" ); + // Using KDE dialogs (and their nested event loops) works only with a proper event loop integration + // that will release SolarMutex when waiting for more events. + // Moreover there are bugs in Qt event loop code that allow QClipboard recursing because the event + // loop processes also events that it should not at that point, so no dialogs in that case either. + // https://bugreports.qt-project.org/browse/QTBUG-37380 + // https://bugreports.qt-project.org/browse/QTBUG-34614 + if (m_isGlibEventLoopType && (0 == tst_processEventsExcludeSocket()) && tst_excludePostedEvents() == 0 ) + m_allowKdeDialogs = true; +#endif + + setupEventLoop(); + + m_pDisplay = QX11Info::display(); +} + +// When we use Qt event loop, it can actually use its own event loop handling, or wrap +// the Glib event loop (the latter is the default is Qt is built with Glib support +// and $QT_NO_GLIB is not set). We mostly do not care which one it is, as QSocketNotifier's +// and QTimer's can handle it transparently, but it matters for the SolarMutex, which +// needs to be unlocked shortly before entering the main sleep (e.g. select()) and locked +// immediately after. So we need to know which event loop implementation is used and +// hook accordingly. +#if KF5_HAVE_GLIB +#include <glib.h> + +static GPollFunc old_gpoll = nullptr; + +static gint gpoll_wrapper( GPollFD* ufds, guint nfds, gint timeout ) +{ + SalYieldMutexReleaser release; // release YieldMutex (and re-acquire at block end) + return old_gpoll( ufds, nfds, timeout ); +} +#endif + +/*static bool ( *old_qt_event_filter )( void* ); +static bool qt_event_filter( void* m ) +{ + if( old_qt_event_filter != nullptr && old_qt_event_filter( m )) + return true; + if( SalKDEDisplay::self() && SalKDEDisplay::self()->checkDirectInputEvent( static_cast< XEvent* >( m ))) + return true; + return false; +}*/ + +bool KDEXLib::nativeEventFilter(const QByteArray &eventType, void *message, long *) +{ + if (eventType == "xcb_generic_event_t") { + xcb_generic_event_t* ev = static_cast<xcb_generic_event_t *>(message); + if( SalKDEDisplay::self() && SalKDEDisplay::self()->checkDirectInputEvent( ev )) + return true; + } + return false; +} + +void KDEXLib::setupEventLoop() +{ + QAbstractEventDispatcher::instance()->installNativeEventFilter( this ); +#if KF5_HAVE_GLIB + if( m_isGlibEventLoopType ) + { + old_gpoll = g_main_context_get_poll_func( nullptr ); + g_main_context_set_poll_func( nullptr, gpoll_wrapper ); + if( m_allowKdeDialogs ) + QApplication::clipboard()->setProperty( "useEventLoopWhenWaiting", true ); + return; + } +#endif +} + +void KDEXLib::Insert( int fd, void* data, YieldFunc pending, YieldFunc queued, YieldFunc handle ) +{ + if( !m_isGlibEventLoopType ) + return SalXLib::Insert( fd, data, pending, queued, handle ); + SocketData sdata; + sdata.data = data; + sdata.pending = pending; + sdata.queued = queued; + sdata.handle = handle; + // qApp as parent to make sure it uses the main thread event loop + sdata.notifier = new QSocketNotifier( fd, QSocketNotifier::Read, qApp ); + connect( sdata.notifier, SIGNAL( activated( int )), this, SLOT( socketNotifierActivated( int ))); + socketData[ fd ] = sdata; +} + +void KDEXLib::Remove( int fd ) +{ + if( !m_isGlibEventLoopType ) + return SalXLib::Remove( fd ); + SocketData sdata = socketData.take( fd );// according to SalXLib::Remove() this should be safe + delete sdata.notifier; +} + +void KDEXLib::socketNotifierActivated( int fd ) +{ + const SocketData& sdata = socketData[ fd ]; + sdata.handle( fd, sdata.data ); +} + +bool KDEXLib::Yield( bool bWait, bool bHandleAllCurrentEvents ) +{ + if( !m_isGlibEventLoopType ) + { + bool wasEvent = false; + if( qApp->thread() == QThread::currentThread()) + { + // even if we use the LO event loop, still process Qt's events, + // otherwise they can remain unhandled for quite a long while + wasEvent = processYield( false, bHandleAllCurrentEvents ); + } + return SalXLib::Yield(bWait, bHandleAllCurrentEvents) || wasEvent; + } + // if we are the main thread (which is where the event processing is done), + // good, just do it + if( qApp->thread() == QThread::currentThread()) + { + return processYield( bWait, bHandleAllCurrentEvents ); + } + else + { + // we were called from another thread; + // release the yield lock to prevent deadlock with the main thread + // (it's ok to release it here, since even normal processYield() would + // temporarily do it while checking for new events) + SalYieldMutexReleaser aReleaser; + Q_EMIT processYieldSignal( bWait, bHandleAllCurrentEvents ); + return false; + } +} + +/** + * Quoting the Qt docs: [QAbstractEventDispatcher::processEvents] processes + * pending events that match flags until there are no more events to process. + */ +bool KDEXLib::processYield( bool bWait, bool ) +{ + QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance( qApp->thread()); + bool wasEvent = false; + if ( bWait ) + wasEvent = dispatcher->processEvents( QEventLoop::WaitForMoreEvents ); + else + wasEvent = dispatcher->processEvents( QEventLoop::AllEvents ); + return wasEvent; +} + +void KDEXLib::StartTimer( sal_uLong nMS ) +{ + if( !m_isGlibEventLoopType ) + return SalXLib::StartTimer( nMS ); + timeoutTimer.setInterval( nMS ); + // QTimer's can be started only in their thread (main thread here) + if( qApp->thread() == QThread::currentThread()) + startTimeoutTimer(); + else + Q_EMIT startTimeoutTimerSignal(); +} + +void KDEXLib::startTimeoutTimer() +{ + timeoutTimer.start(); +} + +void KDEXLib::StopTimer() +{ + if( !m_isGlibEventLoopType ) + return SalXLib::StopTimer(); + timeoutTimer.stop(); +} + +void KDEXLib::timeoutActivated() +{ + // don't potentially wait in timeout, as QTimer is non-recursive + QApplication::postEvent(this, new QEvent(QEvent::Type( m_timerEventId ))); +} + +void KDEXLib::customEvent(QEvent* e) +{ + if( e->type() == m_timerEventId ) + X11SalData::Timeout(); + else if( e->type() == m_postUserEventId ) + SalKDEDisplay::self()->DispatchInternalEvent(); +} + +void KDEXLib::Wakeup() +{ + if( !m_isGlibEventLoopType ) + return SalXLib::Wakeup(); + QAbstractEventDispatcher::instance( qApp->thread())->wakeUp(); // main thread event loop +} + +void KDEXLib::PostUserEvent() +{ + if( !m_isGlibEventLoopType ) + return SalXLib::PostUserEvent(); + QApplication::postEvent(this, new QEvent(QEvent::Type( m_postUserEventId ))); +} + +void KDEXLib::doStartup() +{ + if( ! m_bStartupDone ) + { + //KStartupInfo::appStarted(); + m_bStartupDone = true; + SAL_INFO( "vcl.kde4", "called KStartupInfo::appStarted()" ); + } +} + +using namespace com::sun::star; + +uno::Reference< ui::dialogs::XFilePicker2 > KDEXLib::createFilePicker( + const uno::Reference< uno::XComponentContext >& xMSF ) +{ +#if KF5_HAVE_GLIB + if( qApp->thread() != QThread::currentThread()) { + SalYieldMutexReleaser aReleaser; + return Q_EMIT createFilePickerSignal( xMSF ); + } + return uno::Reference< ui::dialogs::XFilePicker2 >( new KDE4FilePicker( xMSF ) ); +#else + (void)xMSF; + return NULL; +#endif +} + +#include "KDE5XLib.moc" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/KDE5XLib.hxx b/vcl/unx/kde5/KDE5XLib.hxx new file mode 100644 index 000000000000..cb506edea1c6 --- /dev/null +++ b/vcl/unx/kde5/KDE5XLib.hxx @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <memory> + +#include <unx/saldisp.hxx> + +//#include <fixx11h.h> +// +#include <QtCore/QObject> +#include <QtCore/QHash> +#include <QtCore/QSocketNotifier> +#include <QtCore/QTimer> +#include <QtCore/QAbstractNativeEventFilter> + +#include <unx/salinst.h> + +class VCLKDEApplication; + +class KDEXLib : public QObject, public QAbstractNativeEventFilter, public SalXLib +{ + Q_OBJECT + private: + bool m_bStartupDone; + std::unique_ptr<VCLKDEApplication> m_pApplication; + std::unique_ptr<char*[]> m_pFreeCmdLineArgs; + std::unique_ptr<char*[]> m_pAppCmdLineArgs; + int m_nFakeCmdLineArgs; + struct SocketData + { + void* data; + YieldFunc pending; + YieldFunc queued; + YieldFunc handle; + QSocketNotifier* notifier; + }; + QHash< int, SocketData > socketData; // key is fd + QTimer timeoutTimer; + bool m_isGlibEventLoopType; + bool m_allowKdeDialogs; + int m_timerEventId; + int m_postUserEventId; + + private: + void setupEventLoop(); + + private Q_SLOTS: + void socketNotifierActivated( int fd ); + void timeoutActivated(); + void startTimeoutTimer(); + static bool processYield( bool bWait, bool bHandleAllCurrentEvents ); + + Q_SIGNALS: + void startTimeoutTimerSignal(); + void processYieldSignal( bool bWait, bool bHandleAllCurrentEvents ); + css::uno::Reference< css::ui::dialogs::XFilePicker2 > + createFilePickerSignal( const css::uno::Reference< css::uno::XComponentContext >& ); + + public: + KDEXLib(); + virtual ~KDEXLib() override; + + virtual void Init() override; + virtual bool Yield( bool bWait, bool bHandleAllCurrentEvents ) override; + virtual void Insert( int fd, void* data, YieldFunc pending, YieldFunc queued, YieldFunc handle ) override; + virtual void Remove( int fd ) override; + virtual void StartTimer( sal_uLong nMS ) override; + virtual void StopTimer() override; + virtual void Wakeup() override; + virtual void PostUserEvent() override; + + void doStartup(); + bool allowKdeDialogs() { return m_allowKdeDialogs; } + + virtual void customEvent(QEvent* e) override; + + public Q_SLOTS: + css::uno::Reference< css::ui::dialogs::XFilePicker2 > + createFilePicker( const css::uno::Reference< css::uno::XComponentContext >& ); + public: + virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/VCLKDE5Application.cxx b/vcl/unx/kde5/VCLKDE5Application.cxx new file mode 100644 index 000000000000..cea68a3321f8 --- /dev/null +++ b/vcl/unx/kde5/VCLKDE5Application.cxx @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "VCLKDE5Application.hxx" + +#include <QtGui/QClipboard> +#include <QtCore/QEvent> + +#include "KDE5SalDisplay.hxx" + +VCLKDEApplication::VCLKDEApplication() : + KApplication() +{ +} + +// various hacks to be performed before re-entering Qt's event loop +// because of showing a Qt dialog +void VCLKDEApplication::preDialogSetup() +{ + // KFileDialog integration requires using event loop with QClipboard. + // Opening the KDE file dialog here can lead to QClipboard + // asking for clipboard contents. If LO core is the owner of the clipboard + // content, without event loop use this will block for 5 seconds and timeout, + // since the clipboard thread will not be able to acquire SolarMutex + // and thus won't be able to respond. If the event loops + // are properly integrated and QClipboard can use a nested event loop + // (see the KDE VCL plug), then this won't happen. + // We cannot simply release SolarMutex here, because the event loop started + // by the file dialog would also call back to LO code. + assert( QApplication::clipboard()->property( "useEventLoopWhenWaiting" ).toBool() ); +} + +// various hacks to be performed after a Qt dialog has been closed +void VCLKDEApplication::postDialogCleanup() +{ + // HACK: KFileDialog uses KConfig("kdeglobals") for saving some settings + // (such as the auto-extension flag), but that doesn't update KGlobal::config() + // (which is probably a KDE bug), so force reading the new configuration, + // otherwise the next opening of the dialog would use the old settings. + KGlobal::config()->reparseConfiguration(); + // HACK: If Qt owns clipboard or selection, give up on their ownership now. Otherwise + // LO core might ask for the contents, but it would block while doing so (i.e. it + // doesn't seem to have an equivalent of QClipboard's "useEventLoopWhenWaiting"), + // therefore QClipboard wouldn't be able to respond, and whole LO would block until + // a timeout. Given that Klipper is most probably running, giving up clipboard/selection + // ownership will not only avoid the blocking, but even pasting that content in LO + // will in fact work, if Klipper can handle it. + // Technically proper solution would be of course to allow Qt to process QClipboard + // events while LO waits for clipboard contents, or short-circuit to QClipboard somehow + // (it's a mystery why LO's clipboard handling has its own thread when whole LO can + // get blocked by both trying to send and receive clipboard contents anyway). + QClipboard* clipboard = QApplication::clipboard(); + if( clipboard->ownsSelection()) + clipboard->clear( QClipboard::Selection ); + if( clipboard->ownsClipboard()) + clipboard->clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/VCLKDE5Application.hxx b/vcl/unx/kde5/VCLKDE5Application.hxx new file mode 100644 index 000000000000..6d2b6f2fdae1 --- /dev/null +++ b/vcl/unx/kde5/VCLKDE5Application.hxx @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef _VCLKDE5APPLICATION_HXX_ +#define _VCLKDE5APPLICATION_HXX_ + +#pragma once + +#include <QtWidgets/QApplication> +#include <QtCore/QAbstractNativeEventFilter> + +#undef Region + +class VCLKDEApplication : public QApplication, public QAbstractNativeEventFilter +{ + public: + VCLKDEApplication(); + static void preDialogSetup(); + static void postDialogCleanup(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/main.cxx b/vcl/unx/kde5/main.cxx new file mode 100644 index 000000000000..96af86e54607 --- /dev/null +++ b/vcl/unx/kde5/main.cxx @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <QtGui/QApplication> + +#include "KDE5Data.hxx" +#include "KDE5SalInstance.hxx" + +#include <vclpluginapi.h> + +#include <rtl/string.hxx> + +/// entry point for the KDE4 VCL plugin +extern "C" { + VCLPLUG_KDE4_PUBLIC SalInstance* create_SalInstance() + { + /* #i92121# workaround deadlocks in the X11 implementation + */ + static const char* pNoXInitThreads = getenv( "SAL_NO_XINITTHREADS" ); + /* #i90094# + from now on we know that an X connection will be + established, so protect X against itself + */ + if( ! ( pNoXInitThreads && *pNoXInitThreads ) ) + { +#if QT_VERSION >= 0x040800 + // let Qt call XInitThreads(), so that also Qt knows it's been used + // (otherwise QPixmap may warn about threads not being initialized) + QApplication::setAttribute( Qt::AA_X11InitThreads ); +#else + XInitThreads(); + // just in case somebody builds with old version and then upgrades Qt, + // otherwise this is a no-op + QApplication::setAttribute( static_cast< Qt::ApplicationAttribute >( 10 )); +#endif + } + +#if QT_VERSION < 0x050000 + // Qt 4.x support needs >= 4.1.0 + OString aVersion( qVersion() ); + SAL_INFO( "vcl.kde4", "qt version string is " << aVersion ); + + sal_Int32 nIndex = 0, nMajor = 0, nMinor = 0; + nMajor = aVersion.getToken( 0, '.', nIndex ).toInt32(); + if( nIndex > 0 ) + nMinor = aVersion.getToken( 0, '.', nIndex ).toInt32(); + if( nMajor != 4 || nMinor < 1 ) + { +#if OSL_DEBUG_LEVEL > 1 + sal_Int32 nMicro = 0; + if( nIndex > 0 ) + nMicro = aVersion.getToken( 0, '.', nIndex ).toInt32(); + SAL_INFO( "vcl.kde4", "unsuitable qt version " << nMajor << "." << nMinor << "." << nMicro ); +#endif + return nullptr; + } +#endif + + KDESalInstance* pInstance = new KDESalInstance( new SalYieldMutex() ); + SAL_INFO( "vcl.kde4", "created KDESalInstance " << &pInstance ); + + // initialize SalData + KDEData *salData = new KDEData( pInstance ); + salData->Init(); + salData->initNWF(); + pInstance->SetLib(salData->GetLib()); + + return pInstance; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/kde5/tst_exclude_posted_events.hxx b/vcl/unx/kde5/tst_exclude_posted_events.hxx new file mode 100644 index 000000000000..c07ca895a9dc --- /dev/null +++ b/vcl/unx/kde5/tst_exclude_posted_events.hxx @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + * + * This code is based on the SocketEventsTester from the Qt4 test suite. + */ + +#pragma once + +#include <QtCore/QCoreApplication> +#include <QtCore/QEventLoop> + +const QEvent::Type eventType = QEvent::User; + +class TestExcludePostedEvents + : public QObject +{ + Q_OBJECT + public: + TestExcludePostedEvents(); + virtual bool event( QEvent* e ) override; + bool processed; +}; + +TestExcludePostedEvents::TestExcludePostedEvents() + : processed( false ) +{ +} + +bool TestExcludePostedEvents::event( QEvent* e ) +{ + if( e->type() == eventType ) + processed = true; + return QObject::event( e ); +} + +#define QVERIFY(a) \ + if (!a) return 1; + +static int tst_excludePostedEvents() +{ + TestExcludePostedEvents test; + QCoreApplication::postEvent( &test, new QEvent( eventType )); + QEventLoop loop; + loop.processEvents(QEventLoop::ExcludeUserInputEvents + | QEventLoop::ExcludeSocketNotifiers +// | QEventLoop::WaitForMoreEvents + | QEventLoop::X11ExcludeTimers); + QVERIFY( !test.processed ); + loop.processEvents(); + QVERIFY( test.processed ); + return 0; +} diff --git a/vcl/unx/kde5/tst_exclude_socket_notifiers.hxx b/vcl/unx/kde5/tst_exclude_socket_notifiers.hxx new file mode 100644 index 000000000000..d0acafede239 --- /dev/null +++ b/vcl/unx/kde5/tst_exclude_socket_notifiers.hxx @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + * + * This code is based on the SocketEventsTester from the Qt4 test suite. + */ + +#pragma once + +#include <QtCore/QCoreApplication> +#include <QtCore/QEventLoop> +#include <QtCore/QSocketNotifier> +#include <unistd.h> + +class TestExcludeSocketNotifiers + : public QObject +{ + Q_OBJECT + public: + TestExcludeSocketNotifiers( const int* pipes ); + virtual ~TestExcludeSocketNotifiers() override; + bool received; + public slots: + void slotReceived(); + private: + const int* pipes; +}; + +TestExcludeSocketNotifiers::TestExcludeSocketNotifiers( const int* thePipes ) + : received( false ) + , pipes( thePipes ) +{ +} + +TestExcludeSocketNotifiers::~TestExcludeSocketNotifiers() +{ + close( pipes[ 0 ] ); + close( pipes[ 1 ] ); +} + +void TestExcludeSocketNotifiers::slotReceived() +{ + received = true; +} + +#define QVERIFY(a) \ + if (!a) return 1; + +static int tst_processEventsExcludeSocket() +{ + int pipes[ 2 ]; + if( pipe( pipes ) < 0 ) + return 1; + TestExcludeSocketNotifiers test( pipes ); + QSocketNotifier notifier( pipes[ 0 ], QSocketNotifier::Read ); + QObject::connect( ¬ifier, SIGNAL( activated( int )), &test, SLOT( slotReceived())); + char dummy = 'a'; + if( 1 != write( pipes[ 1 ], &dummy, 1 ) ) + return 1; + QEventLoop loop; + loop.processEvents( QEventLoop::ExcludeSocketNotifiers ); + QVERIFY( !test.received ); + loop.processEvents(); + QVERIFY( test.received ); + return 0; +} |