1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* 3 * This file is part of the LibreOffice project. 4 * 5 * This Source Code Form is subject to the terms of the Mozilla Public 6 * License, v. 2.0. If a copy of the MPL was not distributed with this 7 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 * 9 * This file incorporates work covered by the following license notice: 10 * 11 * Licensed to the Apache Software Foundation (ASF) under one or more 12 * contributor license agreements. See the NOTICE file distributed 13 * with this work for additional information regarding copyright 14 * ownership. The ASF licenses this file to you under the Apache 15 * License, Version 2.0 (the "License"); you may not use this file 16 * except in compliance with the License. You may obtain a copy of 17 * the License at http://www.apache.org/licenses/LICENSE-2.0 . 18 */ 19 20 #include <strings.hrc> 21 #include <classes/fwkresid.hxx> 22 23 #include <comphelper/propertyvalue.hxx> 24 #include <cppuhelper/supportsservice.hxx> 25 #include <osl/mutex.hxx> 26 #include <svtools/imagemgr.hxx> 27 #include <svtools/popupmenucontrollerbase.hxx> 28 #include <tools/urlobj.hxx> 29 #include <unotools/historyoptions.hxx> 30 #include <vcl/commandinfoprovider.hxx> 31 #include <vcl/graph.hxx> 32 #include <vcl/settings.hxx> 33 #include <vcl/svapp.hxx> 34 35 using namespace css; 36 using namespace com::sun::star::uno; 37 using namespace com::sun::star::lang; 38 using namespace com::sun::star::frame; 39 using namespace com::sun::star::beans; 40 using namespace com::sun::star::util; 41 42 #define MAX_MENU_ITEMS 99 43 44 namespace { 45 46 constexpr OUStringLiteral CMD_CLEAR_LIST = u".uno:ClearRecentFileList"; 47 constexpr OUStringLiteral CMD_OPEN_AS_TEMPLATE = u".uno:OpenTemplate"; 48 constexpr OUStringLiteral CMD_OPEN_REMOTE = u".uno:OpenRemote"; 49 50 class RecentFilesMenuController : public svt::PopupMenuControllerBase 51 { 52 using svt::PopupMenuControllerBase::disposing; 53 54 public: 55 RecentFilesMenuController( const uno::Reference< uno::XComponentContext >& xContext, 56 const uno::Sequence< uno::Any >& args ); 57 58 // XServiceInfo 59 virtual OUString SAL_CALL getImplementationName() override 60 { 61 return "com.sun.star.comp.framework.RecentFilesMenuController"; 62 } 63 64 virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override 65 { 66 return cppu::supportsService(this, ServiceName); 67 } 68 69 virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override 70 { 71 return {"com.sun.star.frame.PopupMenuController"}; 72 } 73 74 // XStatusListener 75 virtual void SAL_CALL statusChanged( const frame::FeatureStateEvent& Event ) override; 76 77 // XMenuListener 78 virtual void SAL_CALL itemSelected( const awt::MenuEvent& rEvent ) override; 79 virtual void SAL_CALL itemActivated( const awt::MenuEvent& rEvent ) override; 80 81 // XDispatchProvider 82 virtual uno::Reference< frame::XDispatch > SAL_CALL queryDispatch( const util::URL& aURL, const OUString& sTarget, sal_Int32 nFlags ) override; 83 84 // XDispatch 85 virtual void SAL_CALL dispatch( const util::URL& aURL, const uno::Sequence< beans::PropertyValue >& seqProperties ) override; 86 87 // XEventListener 88 virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; 89 90 private: 91 virtual void impl_setPopupMenu() override; 92 void fillPopupMenu( css::uno::Reference< css::awt::XPopupMenu > const & rPopupMenu ); 93 void executeEntry( sal_Int32 nIndex ); 94 95 std::vector< OUString > m_aRecentFilesItems; 96 bool m_bDisabled : 1; 97 bool m_bShowToolbarEntries; 98 }; 99 100 RecentFilesMenuController::RecentFilesMenuController( const uno::Reference< uno::XComponentContext >& xContext, 101 const uno::Sequence< uno::Any >& args ) : 102 svt::PopupMenuControllerBase( xContext ), 103 m_bDisabled( false ), 104 m_bShowToolbarEntries( false ) 105 { 106 css::beans::PropertyValue aPropValue; 107 for ( uno::Any const & arg : args ) 108 { 109 arg >>= aPropValue; 110 if ( aPropValue.Name == "InToolbar" ) 111 { 112 aPropValue.Value >>= m_bShowToolbarEntries; 113 break; 114 } 115 } 116 } 117 118 void InsertItem(const css::uno::Reference<css::awt::XPopupMenu>& rPopupMenu, 119 const OUString& rCommand, 120 const css::uno::Reference<css::frame::XFrame>& rFrame) 121 { 122 sal_uInt16 nItemId = rPopupMenu->getItemCount() + 1; 123 124 if (rFrame.is()) 125 { 126 OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(rFrame)); 127 auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rCommand, aModuleName); 128 OUString aLabel(vcl::CommandInfoProvider::GetPopupLabelForCommand(aProperties)); 129 OUString aTooltip(vcl::CommandInfoProvider::GetTooltipForCommand(rCommand, aProperties, rFrame)); 130 css::uno::Reference<css::graphic::XGraphic> xGraphic(vcl::CommandInfoProvider::GetXGraphicForCommand(rCommand, rFrame)); 131 132 rPopupMenu->insertItem(nItemId, aLabel, 0, -1); 133 rPopupMenu->setItemImage(nItemId, xGraphic, false); 134 rPopupMenu->setHelpText(nItemId, aTooltip); 135 } 136 else 137 rPopupMenu->insertItem(nItemId, OUString(), 0, -1); 138 139 rPopupMenu->setCommand(nItemId, rCommand); 140 } 141 142 143 // private function 144 void RecentFilesMenuController::fillPopupMenu( Reference< css::awt::XPopupMenu > const & rPopupMenu ) 145 { 146 SolarMutexGuard aSolarMutexGuard; 147 148 resetPopupMenu( rPopupMenu ); 149 150 std::vector< SvtHistoryOptions::HistoryItem > aHistoryList = SvtHistoryOptions::GetList( EHistoryType::PickList ); 151 152 int nPickListMenuItems = std::min<sal_Int32>( aHistoryList.size(), MAX_MENU_ITEMS ); 153 m_aRecentFilesItems.clear(); 154 155 if (( nPickListMenuItems > 0 ) && !m_bDisabled ) 156 { 157 for ( int i = 0; i < nPickListMenuItems; i++ ) 158 { 159 const SvtHistoryOptions::HistoryItem& rPickListEntry = aHistoryList[i]; 160 m_aRecentFilesItems.push_back( rPickListEntry.sURL ); 161 } 162 } 163 164 if ( !m_aRecentFilesItems.empty() ) 165 { 166 const sal_uInt32 nCount = m_aRecentFilesItems.size(); 167 StyleSettings aIconSettings; 168 bool bIsIconsAllowed = aIconSettings.GetUseImagesInMenus(); 169 170 for ( sal_uInt32 i = 0; i < nCount; i++ ) 171 { 172 173 OUStringBuffer aMenuShortCut; 174 if ( i <= 9 ) 175 { 176 if ( i == 9 ) 177 aMenuShortCut.append( "1~0. " ); 178 else 179 { 180 aMenuShortCut.append( "~N. " ); 181 aMenuShortCut[ 1 ] = sal_Unicode( i + '1' ); 182 } 183 } 184 else 185 { 186 aMenuShortCut.append( sal_Int32( i + 1 ) ); 187 aMenuShortCut.append( ". " ); 188 } 189 190 OUString aURLString = "vnd.sun.star.popup:RecentFileList?entry=" + OUString::number(i); 191 192 // Abbreviate URL 193 OUString aMenuTitle; 194 INetURLObject aURL( m_aRecentFilesItems[i] ); 195 OUString aTipHelpText( aURL.getFSysPath( FSysStyle::Detect ) ); 196 197 if ( aURL.GetProtocol() == INetProtocol::File ) 198 { 199 // Do handle file URL differently: don't show the protocol, just the file name 200 aMenuTitle = aURL.GetLastName(INetURLObject::DecodeMechanism::WithCharset); 201 } 202 else 203 { 204 // In all other URLs show the protocol name before the file name 205 aMenuTitle = INetURLObject::GetSchemeName(aURL.GetProtocol()) + ": " + aURL.getName(); 206 } 207 208 aMenuShortCut.append( aMenuTitle ); 209 210 rPopupMenu->insertItem(sal_uInt16( i+1 ), aMenuShortCut.makeStringAndClear(), 0, -1); 211 212 if ( bIsIconsAllowed ) { 213 BitmapEx aThumbnail(SvFileInformationManager::GetImageId(aURL, false)); 214 rPopupMenu->setItemImage(sal_uInt16(i + 1), Graphic(aThumbnail).GetXGraphic(), false); 215 } 216 217 rPopupMenu->setTipHelpText(sal_uInt16(i + 1), aTipHelpText); 218 rPopupMenu->setCommand(sal_uInt16(i + 1), aURLString); 219 } 220 221 rPopupMenu->insertSeparator(-1); 222 // Clear List menu entry 223 rPopupMenu->insertItem(sal_uInt16(nCount + 1), FwkResId(STR_CLEAR_RECENT_FILES), 0, -1); 224 rPopupMenu->setCommand(sal_uInt16(nCount + 1), CMD_CLEAR_LIST); 225 rPopupMenu->setHelpText(sal_uInt16(nCount + 1), FwkResId(STR_CLEAR_RECENT_FILES_HELP)); 226 227 // Open remote menu entry 228 if ( m_bShowToolbarEntries ) 229 { 230 rPopupMenu->insertSeparator(-1); 231 InsertItem(rPopupMenu, CMD_OPEN_AS_TEMPLATE, m_xFrame); 232 InsertItem(rPopupMenu, CMD_OPEN_REMOTE, m_xFrame); 233 } 234 } 235 else 236 { 237 if ( m_bShowToolbarEntries ) 238 { 239 InsertItem(rPopupMenu, CMD_OPEN_AS_TEMPLATE, m_xFrame); 240 InsertItem(rPopupMenu, CMD_OPEN_REMOTE, m_xFrame); 241 } 242 else 243 { 244 // Add InsertSeparator(), otherwise it will display 245 // the first item icon of recent files instead of displaying no icon. 246 rPopupMenu->insertSeparator(-1); 247 // No recent documents => insert "no documents" string 248 // Do not disable it, otherwise the Toolbar controller and MenuButton 249 // will display SV_RESID_STRING_NOSELECTIONPOSSIBLE instead of STR_NODOCUMENT 250 rPopupMenu->insertItem(1, FwkResId(STR_NODOCUMENT), static_cast<sal_Int16>(MenuItemBits::NOSELECT), -1); 251 } 252 } 253 } 254 255 void RecentFilesMenuController::executeEntry( sal_Int32 nIndex ) 256 { 257 if (( nIndex < 0 ) || 258 ( nIndex >= sal::static_int_cast<sal_Int32>( m_aRecentFilesItems.size() ))) 259 return; 260 261 Sequence< PropertyValue > aArgsList{ 262 comphelper::makePropertyValue("Referer", OUString( "private:user" )), 263 264 // documents in the picklist will never be opened as templates 265 comphelper::makePropertyValue("AsTemplate", false), 266 267 // Type detection needs to know which app we are opening it from. 268 comphelper::makePropertyValue("DocumentService", m_aModuleName) 269 }; 270 dispatchCommand( m_aRecentFilesItems[ nIndex ], aArgsList, "_default" ); 271 } 272 273 // XEventListener 274 void SAL_CALL RecentFilesMenuController::disposing( const EventObject& ) 275 { 276 Reference< css::awt::XMenuListener > xHolder(this); 277 278 osl::MutexGuard aLock( m_aMutex ); 279 m_xFrame.clear(); 280 m_xDispatch.clear(); 281 282 if ( m_xPopupMenu.is() ) 283 m_xPopupMenu->removeMenuListener( Reference< css::awt::XMenuListener >(this) ); 284 m_xPopupMenu.clear(); 285 } 286 287 // XStatusListener 288 void SAL_CALL RecentFilesMenuController::statusChanged( const FeatureStateEvent& Event ) 289 { 290 osl::MutexGuard aLock( m_aMutex ); 291 m_bDisabled = !Event.IsEnabled; 292 } 293 294 void SAL_CALL RecentFilesMenuController::itemSelected( const css::awt::MenuEvent& rEvent ) 295 { 296 Reference< css::awt::XPopupMenu > xPopupMenu; 297 298 { 299 osl::MutexGuard aLock(m_aMutex); 300 xPopupMenu = m_xPopupMenu; 301 } 302 303 if ( !xPopupMenu.is() ) 304 return; 305 306 const OUString aCommand( xPopupMenu->getCommand( rEvent.MenuId ) ); 307 308 if ( aCommand == CMD_CLEAR_LIST ) 309 { 310 SvtHistoryOptions::Clear( EHistoryType::PickList ); 311 dispatchCommand( 312 "vnd.org.libreoffice.recentdocs:ClearRecentFileList", 313 css::uno::Sequence< css::beans::PropertyValue >() ); 314 } 315 else if ( aCommand == CMD_OPEN_REMOTE ) 316 { 317 Sequence< PropertyValue > aArgsList( 0 ); 318 dispatchCommand( CMD_OPEN_REMOTE, aArgsList ); 319 } 320 else if ( aCommand == CMD_OPEN_AS_TEMPLATE ) 321 { 322 Sequence< PropertyValue > aArgsList( 0 ); 323 dispatchCommand( CMD_OPEN_AS_TEMPLATE, aArgsList ); 324 } 325 else 326 executeEntry( rEvent.MenuId-1 ); 327 } 328 329 void SAL_CALL RecentFilesMenuController::itemActivated( const css::awt::MenuEvent& ) 330 { 331 osl::MutexGuard aLock( m_aMutex ); 332 impl_setPopupMenu(); 333 } 334 335 // XPopupMenuController 336 void RecentFilesMenuController::impl_setPopupMenu() 337 { 338 if ( m_xPopupMenu.is() ) 339 fillPopupMenu( m_xPopupMenu ); 340 } 341 342 // XDispatchProvider 343 Reference< XDispatch > SAL_CALL RecentFilesMenuController::queryDispatch( 344 const URL& aURL, 345 const OUString& /*sTarget*/, 346 sal_Int32 /*nFlags*/ ) 347 { 348 osl::MutexGuard aLock( m_aMutex ); 349 350 throwIfDisposed(); 351 352 if ( aURL.Complete.startsWith( m_aBaseURL ) ) 353 return Reference< XDispatch >( this ); 354 else 355 return Reference< XDispatch >(); 356 } 357 358 // XDispatch 359 void SAL_CALL RecentFilesMenuController::dispatch( 360 const URL& aURL, 361 const Sequence< PropertyValue >& /*seqProperties*/ ) 362 { 363 osl::MutexGuard aLock( m_aMutex ); 364 365 throwIfDisposed(); 366 367 if ( !aURL.Complete.startsWith( m_aBaseURL ) ) 368 return; 369 370 // Parse URL to retrieve entry argument and its value 371 sal_Int32 nQueryPart = aURL.Complete.indexOf( '?', m_aBaseURL.getLength() ); 372 if ( nQueryPart <= 0 ) 373 return; 374 375 static const OUStringLiteral aEntryArgStr( u"entry=" ); 376 sal_Int32 nEntryArg = aURL.Complete.indexOf( aEntryArgStr, nQueryPart ); 377 sal_Int32 nEntryPos = nEntryArg + aEntryArgStr.getLength(); 378 if (( nEntryArg <= 0 ) || ( nEntryPos >= aURL.Complete.getLength() )) 379 return; 380 381 sal_Int32 nAddArgs = aURL.Complete.indexOf( '&', nEntryPos ); 382 OUString aEntryArg; 383 384 if ( nAddArgs < 0 ) 385 aEntryArg = aURL.Complete.copy( nEntryPos ); 386 else 387 aEntryArg = aURL.Complete.copy( nEntryPos, nAddArgs-nEntryPos ); 388 389 sal_Int32 nEntry = aEntryArg.toInt32(); 390 executeEntry( nEntry ); 391 } 392 393 } 394 395 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * 396 com_sun_star_comp_framework_RecentFilesMenuController_get_implementation( 397 css::uno::XComponentContext *context, 398 css::uno::Sequence<css::uno::Any> const &args) 399 { 400 return cppu::acquire(new RecentFilesMenuController(context, args)); 401 } 402 403 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 404
