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