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 21#include <unotools/moduleoptions.hxx> 22#include <unotools/dynamicmenuoptions.hxx> 23#include <unotools/historyoptions.hxx> 24#include <rtl/ustring.hxx> 25#include <tools/urlobj.hxx> 26#include <osl/file.h> 27#include <osl/diagnose.h> 28#include <comphelper/sequenceashashmap.hxx> 29#include <sfx2/app.hxx> 30#include <sal/macros.h> 31#include <sfx2/sfxresid.hxx> 32#include <sfx2/strings.hrc> 33#include <vcl/svapp.hxx> 34#include "shutdownicon.hxx" 35 36#include <com/sun/star/util/XStringWidth.hpp> 37 38#include <cppuhelper/implbase.hxx> 39 40#include <set> 41#include <vector> 42 43#include <premac.h> 44#include <objc/objc-runtime.h> 45#include <Cocoa/Cocoa.h> 46#include <postmac.h> 47 48#define MI_OPEN 1 49#define MI_WRITER 2 50#define MI_CALC 3 51#define MI_IMPRESS 4 52#define MI_DRAW 5 53#define MI_BASE 6 54#define MI_MATH 7 55#define MI_TEMPLATE 8 56#define MI_STARTMODULE 9 57 58@interface QSMenuExecute : NSObject 59{ 60} 61-(void)executeMenuItem: (NSMenuItem*)pItem; 62-(void)dockIconClicked: (NSObject*)pSender; 63@end 64 65@implementation QSMenuExecute 66-(void)executeMenuItem: (NSMenuItem*)pItem 67{ 68 switch( [pItem tag] ) 69 { 70 case MI_OPEN: 71 ShutdownIcon::FileOpen(); 72 break; 73 case MI_WRITER: 74 ShutdownIcon::OpenURL( WRITER_URL, "_default" ); 75 break; 76 case MI_CALC: 77 ShutdownIcon::OpenURL( CALC_URL, "_default" ); 78 break; 79 case MI_IMPRESS: 80 ShutdownIcon::OpenURL( IMPRESS_URL, "_default" ); 81 break; 82 case MI_DRAW: 83 ShutdownIcon::OpenURL( DRAW_URL, "_default" ); 84 break; 85 case MI_BASE: 86 ShutdownIcon::OpenURL( BASE_URL, "_default" ); 87 break; 88 case MI_MATH: 89 ShutdownIcon::OpenURL( MATH_URL, "_default" ); 90 break; 91 case MI_TEMPLATE: 92 ShutdownIcon::FromTemplate(); 93 break; 94 case MI_STARTMODULE: 95 ShutdownIcon::OpenURL( STARTMODULE_URL, "_default" ); 96 break; 97 default: 98 break; 99 } 100} 101 102-(void)dockIconClicked: (NSObject*)pSender 103{ 104 (void)pSender; 105 // start module 106 ShutdownIcon::OpenURL( STARTMODULE_URL, "_default" ); 107} 108 109@end 110 111bool ShutdownIcon::IsQuickstarterInstalled() 112{ 113 return true; 114} 115 116static NSMenuItem* pDefMenu = nil, *pDockSubMenu = nil; 117static QSMenuExecute* pExecute = nil; 118 119static std::set< OUString > aShortcuts; 120 121static NSString* getAutoreleasedString( const OUString& rStr ) 122{ 123 return [[[NSString alloc] initWithCharacters: reinterpret_cast<unichar const *>(rStr.getStr()) length: rStr.getLength()] autorelease]; 124} 125 126namespace { 127 128struct RecentMenuEntry 129{ 130 OUString aURL; 131 OUString aFilter; 132 OUString aTitle; 133 OUString aPassword; 134}; 135 136class RecentFilesStringLength : public ::cppu::WeakImplHelper< css::util::XStringWidth > 137{ 138 public: 139 RecentFilesStringLength() {} 140 141 // XStringWidth 142 sal_Int32 SAL_CALL queryStringWidth( const OUString& aString ) override 143 { 144 return aString.getLength(); 145 } 146}; 147 148} 149 150@interface RecentMenuDelegate : NSObject <NSMenuDelegate> 151{ 152 std::vector< RecentMenuEntry >* m_pRecentFilesItems; 153} 154-(id)init; 155-(void)dealloc; 156-(void)menuNeedsUpdate:(NSMenu *)menu; 157-(void)executeRecentEntry: (NSMenuItem*)item; 158@end 159 160@implementation RecentMenuDelegate 161-(id)init 162{ 163 if( (self = [super init]) ) 164 { 165 m_pRecentFilesItems = new std::vector< RecentMenuEntry >(); 166 } 167 return self; 168} 169 170-(void)dealloc 171{ 172 delete m_pRecentFilesItems; 173 [super dealloc]; 174} 175 176-(void)menuNeedsUpdate:(NSMenu *)menu 177{ 178 // clear menu 179 int nItems = [menu numberOfItems]; 180 while( nItems -- ) 181 [menu removeItemAtIndex: 0]; 182 183 // update recent item list 184 std::vector< SvtHistoryOptions::HistoryItem > aHistoryList( SvtHistoryOptions::GetList( EHistoryType::PickList ) ); 185 186 int nPickListMenuItems = ( aHistoryList.size() > 99 ) ? 99 : aHistoryList.size(); 187 188 m_pRecentFilesItems->clear(); 189 if( nPickListMenuItems > 0 ) 190 { 191 for ( int i = 0; i < nPickListMenuItems; i++ ) 192 { 193 const SvtHistoryOptions::HistoryItem & rPickListEntry = aHistoryList[i]; 194 RecentMenuEntry aRecentFile; 195 aRecentFile.aURL = rPickListEntry.sURL; 196 aRecentFile.aFilter = rPickListEntry.sFilter; 197 aRecentFile.aTitle = rPickListEntry.sTitle; 198 m_pRecentFilesItems->push_back( aRecentFile ); 199 } 200 } 201 202 // insert new recent items 203 for ( std::vector<RecentMenuEntry>::size_type i = 0; i < m_pRecentFilesItems->size(); i++ ) 204 { 205 OUString aMenuTitle; 206 INetURLObject aURL( (*m_pRecentFilesItems)[i].aURL ); 207 208 if ( aURL.GetProtocol() == INetProtocol::File ) 209 { 210 // Do handle file URL differently => convert it to a system 211 // path and abbreviate it with a special function: 212 OUString aSystemPath( aURL.getFSysPath( FSysStyle::Detect ) ); 213 OUString aCompactedSystemPath; 214 215 oslFileError nError = osl_abbreviateSystemPath( aSystemPath.pData, &aCompactedSystemPath.pData, 46, nullptr ); 216 if ( !nError ) 217 aMenuTitle = aCompactedSystemPath; 218 else 219 aMenuTitle = aSystemPath; 220 } 221 else 222 { 223 // Use INetURLObject to abbreviate all other URLs 224 css::uno::Reference< css::util::XStringWidth > xStringLength( new RecentFilesStringLength() ); 225 aMenuTitle = aURL.getAbbreviated( xStringLength, 46, INetURLObject::DecodeMechanism::Unambiguous ); 226 } 227 228 NSMenuItem* pNewItem = [[NSMenuItem alloc] initWithTitle: getAutoreleasedString( aMenuTitle ) 229 action: @selector(executeRecentEntry:) 230 keyEquivalent: @""]; 231 [pNewItem setTag: i]; 232 [pNewItem setTarget: self]; 233 [pNewItem setEnabled: YES]; 234 [menu addItem: pNewItem]; 235 [pNewItem autorelease]; 236 } 237} 238 239-(void)executeRecentEntry: (NSMenuItem*)item 240{ 241 sal_Int32 nIndex = [item tag]; 242 if( ( nIndex >= 0 ) && ( nIndex < static_cast<sal_Int32>( m_pRecentFilesItems->size() ) ) ) 243 { 244 const RecentMenuEntry& rRecentFile = (*m_pRecentFilesItems)[ nIndex ]; 245 int NUM_OF_PICKLIST_ARGS = 3; 246 css::uno::Sequence< css::beans::PropertyValue > aArgsList( NUM_OF_PICKLIST_ARGS ); 247 css::beans::PropertyValue* pArgsList = aArgsList.getArray(); 248 249 pArgsList[0].Name = "Referer"; 250 pArgsList[0].Value <<= OUString( "private:user" ); 251 252 // documents in the picklist will never be opened as templates 253 pArgsList[1].Name = "AsTemplate"; 254 pArgsList[1].Value <<= false; 255 256 OUString aFilter( rRecentFile.aFilter ); 257 sal_Int32 nPos = aFilter.indexOf( '|' ); 258 if ( nPos >= 0 ) 259 { 260 OUString aFilterOptions; 261 262 if ( nPos < ( aFilter.getLength() - 1 ) ) 263 aFilterOptions = aFilter.copy( nPos+1 ); 264 265 pArgsList[2].Name = "FilterOptions"; 266 pArgsList[2].Value <<= aFilterOptions; 267 268 aFilter = aFilter.copy( 0, nPos-1 ); 269 aArgsList.realloc( ++NUM_OF_PICKLIST_ARGS ); 270 pArgsList = aArgsList.getArray(); 271 } 272 273 pArgsList[NUM_OF_PICKLIST_ARGS-1].Name = "FilterName"; 274 pArgsList[NUM_OF_PICKLIST_ARGS-1].Value <<= aFilter; 275 276 ShutdownIcon::OpenURL( rRecentFile.aURL, "_default", aArgsList ); 277 } 278} 279@end 280 281static RecentMenuDelegate* pRecentDelegate = nil; 282 283static OUString getShortCut( const OUString& i_rTitle ) 284{ 285 // create shortcut 286 OUString aKeyEquiv; 287 for( sal_Int32 nIndex = 0; nIndex < i_rTitle.getLength(); nIndex++ ) 288 { 289 OUString aShortcut( i_rTitle.copy( nIndex, 1 ).toAsciiLowerCase() ); 290 if( aShortcuts.find( aShortcut ) == aShortcuts.end() ) 291 { 292 aShortcuts.insert( aShortcut ); 293 aKeyEquiv = aShortcut; 294 break; 295 } 296 } 297 298 return aKeyEquiv; 299} 300 301static void appendMenuItem( NSMenu* i_pMenu, NSMenu* i_pDockMenu, const OUString& i_rTitle, int i_nTag, const OUString& i_rKeyEquiv ) 302{ 303 if( ! i_rTitle.getLength() ) 304 return; 305 306 NSMenuItem* pItem = [[NSMenuItem alloc] initWithTitle: getAutoreleasedString( i_rTitle ) 307 action: @selector(executeMenuItem:) 308 keyEquivalent: (i_rKeyEquiv.getLength() ? getAutoreleasedString( i_rKeyEquiv ) : @"") 309 ]; 310 [pItem setTag: i_nTag]; 311 [pItem setTarget: pExecute]; 312 [pItem setEnabled: YES]; 313 [i_pMenu addItem: pItem]; 314 315 if( i_pDockMenu ) 316 { 317 // create a similar entry in the dock menu 318 pItem = [[NSMenuItem alloc] initWithTitle: getAutoreleasedString( i_rTitle ) 319 action: @selector(executeMenuItem:) 320 keyEquivalent: @"" 321 ]; 322 [pItem setTag: i_nTag]; 323 [pItem setTarget: pExecute]; 324 [pItem setEnabled: YES]; 325 [i_pDockMenu addItem: pItem]; 326 } 327} 328 329static void appendRecentMenu( NSMenu* i_pMenu, const OUString& i_rTitle ) 330{ 331 if( ! pRecentDelegate ) 332 pRecentDelegate = [[RecentMenuDelegate alloc] init]; 333 334 NSMenuItem* pItem = [i_pMenu addItemWithTitle: getAutoreleasedString( i_rTitle ) 335 action: @selector(executeMenuItem:) 336 keyEquivalent: @"" 337 ]; 338 [pItem setEnabled: YES]; 339 NSMenu* pRecentMenu = [[NSMenu alloc] initWithTitle: getAutoreleasedString( i_rTitle ) ]; 340 341 [pRecentMenu setDelegate: pRecentDelegate]; 342 343 [pRecentMenu setAutoenablesItems: NO]; 344 [pItem setSubmenu: pRecentMenu]; 345} 346 347 348extern "C" 349{ 350 351void aqua_init_systray() 352{ 353 SolarMutexGuard aGuard; 354 355 ShutdownIcon *pShutdownIcon = ShutdownIcon::getInstance(); 356 if( ! pShutdownIcon ) 357 return; 358 359 // disable shutdown 360 pShutdownIcon->SetVeto( true ); 361 ShutdownIcon::addTerminateListener(); 362 363 if( ! pDefMenu ) 364 { 365 if( [NSApp respondsToSelector: @selector(addFallbackMenuItem:)] ) 366 { 367 aShortcuts.clear(); 368 369 pExecute = [[QSMenuExecute alloc] init]; 370 pDefMenu = [[NSMenuItem alloc] initWithTitle: getAutoreleasedString( SfxResId(STR_QUICKSTART_FILE) ) action: nullptr keyEquivalent: @""]; 371 pDockSubMenu = [[NSMenuItem alloc] initWithTitle: getAutoreleasedString( SfxResId(STR_QUICKSTART_FILE) ) action: nullptr keyEquivalent: @""]; 372 NSMenu* pMenu = [[NSMenu alloc] initWithTitle: getAutoreleasedString( SfxResId(STR_QUICKSTART_FILE) )]; 373 [pMenu setAutoenablesItems: NO]; 374 NSMenu* pDockMenu = [[NSMenu alloc] initWithTitle: getAutoreleasedString( SfxResId(STR_QUICKSTART_FILE) )]; 375 [pDockMenu setAutoenablesItems: NO]; 376 377 // collect the URLs of the entries in the File/New menu 378 SvtModuleOptions aModuleOptions; 379 std::set< OUString > aFileNewAppsAvailable; 380 std::vector < SvtDynMenuEntry > const aNewMenu = SvtDynamicMenuOptions::GetMenu( EDynamicMenuType::NewMenu ); 381 382 for ( SvtDynMenuEntry const & newMenuProp : aNewMenu ) 383 { 384 if ( !newMenuProp.sURL.isEmpty() ) 385 aFileNewAppsAvailable.insert( newMenuProp.sURL ); 386 } 387 388 // describe the menu entries for launching the applications 389 struct MenuEntryDescriptor 390 { 391 SvtModuleOptions::EModule eModuleIdentifier; 392 int nMenuTag; 393 OUString sURLDescription; 394 } static constexpr aMenuItems[] = 395 { 396 { SvtModuleOptions::EModule::WRITER, MI_WRITER, WRITER_URL }, 397 { SvtModuleOptions::EModule::CALC, MI_CALC, CALC_URL }, 398 { SvtModuleOptions::EModule::IMPRESS, MI_IMPRESS, IMPRESS_WIZARD_URL }, 399 { SvtModuleOptions::EModule::DRAW, MI_DRAW, DRAW_URL }, 400 { SvtModuleOptions::EModule::DATABASE, MI_BASE, BASE_URL }, 401 { SvtModuleOptions::EModule::MATH, MI_MATH, MATH_URL } 402 }; 403 404 // insert entry for startcenter 405 if( aModuleOptions.IsModuleInstalled( SvtModuleOptions::EModule::STARTMODULE ) ) 406 { 407 appendMenuItem( pMenu, nil, SfxResId(STR_QUICKSTART_STARTCENTER), MI_STARTMODULE, "n" ); 408 if( [NSApp respondsToSelector: @selector(setDockIconClickHandler:)] ) 409 [NSApp performSelector:@selector(setDockIconClickHandler:) withObject: pExecute]; 410 else 411 OSL_FAIL( "setDockIconClickHandler selector failed on NSApp" ); 412 413 } 414 415 // insert the menu entries for launching the applications 416 for ( size_t i = 0; i < SAL_N_ELEMENTS( aMenuItems ); ++i ) 417 { 418 if ( !aModuleOptions.IsModuleInstalled( aMenuItems[i].eModuleIdentifier ) ) 419 // the complete application is not even installed 420 continue; 421 422 const OUString& sURL( aMenuItems[i].sURLDescription ); 423 424 if ( aFileNewAppsAvailable.find( sURL ) == aFileNewAppsAvailable.end() ) 425 // the application is installed, but the entry has been configured to *not* appear in the File/New 426 // menu => also let not appear it in the quickstarter 427 continue; 428 429 OUString aKeyEquiv( getShortCut( ShutdownIcon::GetUrlDescription( sURL ) ) ); 430 431 appendMenuItem( pMenu, pDockMenu, ShutdownIcon::GetUrlDescription( sURL ), aMenuItems[i].nMenuTag, aKeyEquiv ); 432 } 433 434 // insert the remaining menu entries 435 436 // add recent menu 437 appendRecentMenu( pMenu, SfxResId(STR_QUICKSTART_RECENTDOC) ); 438 439 OUString aTitle( SfxResId(STR_QUICKSTART_FROMTEMPLATE) ); 440 OUString aKeyEquiv( getShortCut( aTitle ) ); 441 appendMenuItem( pMenu, pDockMenu, aTitle, MI_TEMPLATE, aKeyEquiv ); 442 aTitle = SfxResId(STR_QUICKSTART_FILEOPEN); 443 aKeyEquiv = getShortCut( aTitle ); 444 appendMenuItem( pMenu, pDockMenu, aTitle, MI_OPEN, aKeyEquiv ); 445 446 [pDefMenu setSubmenu: pMenu]; 447 [NSApp performSelector:@selector(addFallbackMenuItem:) withObject: pDefMenu]; 448 449 if( [NSApp respondsToSelector: @selector(addDockMenuItem:)] ) 450 { 451 [pDockSubMenu setSubmenu: pDockMenu]; 452 // add the submenu 453 [NSApp performSelector:@selector(addDockMenuItem:) withObject: pDockSubMenu]; 454 } 455 else 456 OSL_FAIL( "addDockMenuItem selector failed on NSApp" ); 457 } 458 else 459 OSL_FAIL( "addFallbackMenuItem selector failed on NSApp" ); 460 } 461} 462 463void SAL_DLLPUBLIC_EXPORT aqua_shutdown_systray() 464{ 465} 466 467} 468 469/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 470
