1/************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24// MARKER(update_precomp.py): autogen include statement, do not remove 25#include "precompiled_sfx2.hxx" 26 27#include "unotools/moduleoptions.hxx" 28#include "unotools/dynamicmenuoptions.hxx" 29#include "unotools/historyoptions.hxx" 30#include "tools/urlobj.hxx" 31#include "osl/file.h" 32#include "comphelper/sequenceashashmap.hxx" 33#include "vos/mutex.hxx" 34#include "sfx2/app.hxx" 35#include "app.hrc" 36#define USE_APP_SHORTCUTS 37#include "shutdownicon.hxx" 38 39#include "com/sun/star/util/XStringWidth.hpp" 40 41#include "cppuhelper/implbase1.hxx" 42 43#include <set> 44#include <vector> 45 46#include "premac.h" 47#include <Cocoa/Cocoa.h> 48#include "postmac.h" 49 50using namespace ::rtl; 51using namespace ::osl; 52using namespace ::com::sun::star::uno; 53using namespace ::com::sun::star::task; 54using namespace ::com::sun::star::lang; 55using namespace ::com::sun::star::beans; 56using namespace ::com::sun::star::util; 57 58#define MI_OPEN 1 59#define MI_WRITER 2 60#define MI_CALC 3 61#define MI_IMPRESS 4 62#define MI_DRAW 5 63#define MI_BASE 6 64#define MI_MATH 7 65#define MI_TEMPLATE 8 66#define MI_STARTMODULE 9 67 68@interface QSMenuExecute : NSObject 69{ 70} 71-(void)executeMenuItem: (NSMenuItem*)pItem; 72-(void)dockIconClicked: (NSObject*)pSender; 73@end 74 75@implementation QSMenuExecute 76-(void)executeMenuItem: (NSMenuItem*)pItem 77{ 78 switch( [pItem tag] ) 79 { 80 case MI_OPEN: 81 ShutdownIcon::FileOpen(); 82 break; 83 case MI_WRITER: 84 ShutdownIcon::OpenURL( OUString( RTL_CONSTASCII_USTRINGPARAM( WRITER_URL ) ), OUString( RTL_CONSTASCII_USTRINGPARAM( "_default" ) ) ); 85 break; 86 case MI_CALC: 87 ShutdownIcon::OpenURL( OUString( RTL_CONSTASCII_USTRINGPARAM( CALC_URL ) ), OUString( RTL_CONSTASCII_USTRINGPARAM( "_default" ) ) ); 88 break; 89 case MI_IMPRESS: 90 ShutdownIcon::OpenURL( OUString( RTL_CONSTASCII_USTRINGPARAM( IMPRESS_URL ) ), OUString( RTL_CONSTASCII_USTRINGPARAM( "_default" ) ) ); 91 break; 92 case MI_DRAW: 93 ShutdownIcon::OpenURL( OUString( RTL_CONSTASCII_USTRINGPARAM( DRAW_URL ) ), OUString( RTL_CONSTASCII_USTRINGPARAM( "_default" ) ) ); 94 break; 95 case MI_BASE: 96 ShutdownIcon::OpenURL( OUString( RTL_CONSTASCII_USTRINGPARAM( BASE_URL ) ), OUString( RTL_CONSTASCII_USTRINGPARAM( "_default" ) ) ); 97 break; 98 case MI_MATH: 99 ShutdownIcon::OpenURL( OUString( RTL_CONSTASCII_USTRINGPARAM( MATH_URL ) ), OUString( RTL_CONSTASCII_USTRINGPARAM( "_default" ) ) ); 100 break; 101 case MI_TEMPLATE: 102 ShutdownIcon::FromTemplate(); 103 break; 104 case MI_STARTMODULE: 105 ShutdownIcon::OpenURL( OUString( RTL_CONSTASCII_USTRINGPARAM( STARTMODULE_URL ) ), OUString( RTL_CONSTASCII_USTRINGPARAM( "_default" ) ) ); 106 break; 107 default: 108 break; 109 } 110} 111 112-(void)dockIconClicked: (NSObject*)pSender 113{ 114 (void)pSender; 115 // start start module 116 ShutdownIcon::OpenURL( OUString( RTL_CONSTASCII_USTRINGPARAM( STARTMODULE_URL ) ), OUString( RTL_CONSTASCII_USTRINGPARAM( "_default" ) ) ); 117} 118 119@end 120 121bool ShutdownIcon::IsQuickstarterInstalled() 122{ 123 return true; 124} 125 126static NSMenuItem* pDefMenu = nil, *pDockSubMenu = nil; 127static QSMenuExecute* pExecute = nil; 128 129static std::set< OUString > aShortcuts; 130 131static NSString* getAutoreleasedString( const rtl::OUString& rStr ) 132{ 133 return [[[NSString alloc] initWithCharacters: rStr.getStr() length: rStr.getLength()] autorelease]; 134} 135 136struct RecentMenuEntry 137{ 138 rtl::OUString aURL; 139 rtl::OUString aFilter; 140 rtl::OUString aTitle; 141 rtl::OUString aPassword; 142}; 143 144class RecentFilesStringLength : public ::cppu::WeakImplHelper1< ::com::sun::star::util::XStringWidth > 145{ 146 public: 147 RecentFilesStringLength() {} 148 virtual ~RecentFilesStringLength() {} 149 150 // XStringWidth 151 sal_Int32 SAL_CALL queryStringWidth( const ::rtl::OUString& aString ) 152 throw (::com::sun::star::uno::RuntimeException) 153 { 154 return aString.getLength(); 155 } 156}; 157 158@interface RecentMenuDelegate : NSObject 159{ 160 std::vector< RecentMenuEntry >* m_pRecentFilesItems; 161} 162-(id)init; 163-(void)dealloc; 164-(void)menuNeedsUpdate:(NSMenu *)menu; 165-(void)executeRecentEntry: (NSMenuItem*)item; 166@end 167 168@implementation RecentMenuDelegate 169-(id)init 170{ 171 if( (self = [super init]) ) 172 { 173 m_pRecentFilesItems = new std::vector< RecentMenuEntry >(); 174 } 175 return self; 176} 177 178-(void)dealloc 179{ 180 delete m_pRecentFilesItems; 181 [super dealloc]; 182} 183 184-(void)menuNeedsUpdate:(NSMenu *)menu 185{ 186 // clear menu 187 int nItems = [menu numberOfItems]; 188 while( nItems -- ) 189 [menu removeItemAtIndex: 0]; 190 191 // update recent item list 192 Sequence< Sequence< PropertyValue > > aHistoryList( SvtHistoryOptions().GetList( ePICKLIST ) ); 193 194 int nPickListMenuItems = ( aHistoryList.getLength() > 99 ) ? 99 : aHistoryList.getLength(); 195 196 m_pRecentFilesItems->clear(); 197 if( ( nPickListMenuItems > 0 ) ) 198 { 199 for ( int i = 0; i < nPickListMenuItems; i++ ) 200 { 201 Sequence< PropertyValue >& rPickListEntry = aHistoryList[i]; 202 RecentMenuEntry aRecentFile; 203 204 for ( int j = 0; j < rPickListEntry.getLength(); j++ ) 205 { 206 Any a = rPickListEntry[j].Value; 207 208 if ( rPickListEntry[j].Name == HISTORY_PROPERTYNAME_URL ) 209 a >>= aRecentFile.aURL; 210 else if ( rPickListEntry[j].Name == HISTORY_PROPERTYNAME_FILTER ) 211 a >>= aRecentFile.aFilter; 212 else if ( rPickListEntry[j].Name == HISTORY_PROPERTYNAME_TITLE ) 213 a >>= aRecentFile.aTitle; 214 else if ( rPickListEntry[j].Name == HISTORY_PROPERTYNAME_PASSWORD ) 215 a >>= aRecentFile.aPassword; 216 } 217 218 m_pRecentFilesItems->push_back( aRecentFile ); 219 } 220 } 221 222 // insert new recent items 223 for ( sal_uInt32 i = 0; i < m_pRecentFilesItems->size(); i++ ) 224 { 225 rtl::OUString aMenuTitle; 226 INetURLObject aURL( (*m_pRecentFilesItems)[i].aURL ); 227 228 if ( aURL.GetProtocol() == INET_PROT_FILE ) 229 { 230 // Do handle file URL differently => convert it to a system 231 // path and abbreviate it with a special function: 232 String aFileSystemPath( aURL.getFSysPath( INetURLObject::FSYS_DETECT ) ); 233 234 ::rtl::OUString aSystemPath( aFileSystemPath ); 235 ::rtl::OUString aCompactedSystemPath; 236 237 oslFileError nError = osl_abbreviateSystemPath( aSystemPath.pData, &aCompactedSystemPath.pData, 46, NULL ); 238 if ( !nError ) 239 aMenuTitle = String( aCompactedSystemPath ); 240 else 241 aMenuTitle = aSystemPath; 242 } 243 else 244 { 245 // Use INetURLObject to abbreviate all other URLs 246 Reference< XStringWidth > xStringLength( new RecentFilesStringLength() ); 247 aMenuTitle = aURL.getAbbreviated( xStringLength, 46, INetURLObject::DECODE_UNAMBIGUOUS ); 248 } 249 250 NSMenuItem* pNewItem = [[NSMenuItem alloc] initWithTitle: getAutoreleasedString( aMenuTitle ) 251 action: @selector(executeRecentEntry:) 252 keyEquivalent: @""]; 253 [pNewItem setTag: i]; 254 [pNewItem setTarget: self]; 255 [pNewItem setEnabled: YES]; 256 [menu addItem: pNewItem]; 257 [pNewItem autorelease]; 258 } 259} 260 261-(void)executeRecentEntry: (NSMenuItem*)item 262{ 263 sal_Int32 nIndex = [item tag]; 264 if( ( nIndex >= 0 ) && ( nIndex < static_cast<sal_Int32>( m_pRecentFilesItems->size() ) ) ) 265 { 266 const RecentMenuEntry& rRecentFile = (*m_pRecentFilesItems)[ nIndex ]; 267 int NUM_OF_PICKLIST_ARGS = 3; 268 Sequence< PropertyValue > aArgsList( NUM_OF_PICKLIST_ARGS ); 269 270 aArgsList[0].Name = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Referer" )); 271 aArgsList[0].Value = makeAny( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "private:user" ) ) ); 272 273 // documents in the picklist will never be opened as templates 274 aArgsList[1].Name = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "AsTemplate" )); 275 aArgsList[1].Value = makeAny( (sal_Bool) sal_False ); 276 277 ::rtl::OUString aFilter( rRecentFile.aFilter ); 278 sal_Int32 nPos = aFilter.indexOf( '|' ); 279 if ( nPos >= 0 ) 280 { 281 rtl::OUString aFilterOptions; 282 283 if ( nPos < ( aFilter.getLength() - 1 ) ) 284 aFilterOptions = aFilter.copy( nPos+1 ); 285 286 aArgsList[2].Name = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "FilterOptions" )); 287 aArgsList[2].Value = makeAny( aFilterOptions ); 288 289 aFilter = aFilter.copy( 0, nPos-1 ); 290 aArgsList.realloc( ++NUM_OF_PICKLIST_ARGS ); 291 } 292 293 aArgsList[NUM_OF_PICKLIST_ARGS-1].Name = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "FilterName" )); 294 aArgsList[NUM_OF_PICKLIST_ARGS-1].Value = makeAny( aFilter ); 295 296 ShutdownIcon::OpenURL( rRecentFile.aURL, OUString( RTL_CONSTASCII_USTRINGPARAM( "_default" ) ), aArgsList ); 297 } 298} 299@end 300 301static RecentMenuDelegate* pRecentDelegate = nil; 302 303static rtl::OUString getShortCut( const rtl::OUString i_rTitle ) 304{ 305 // create shortcut 306 rtl::OUString aKeyEquiv; 307 for( sal_Int32 nIndex = 0; nIndex < i_rTitle.getLength(); nIndex++ ) 308 { 309 rtl::OUString aShortcut( i_rTitle.copy( nIndex, 1 ).toAsciiLowerCase() ); 310 if( aShortcuts.find( aShortcut ) == aShortcuts.end() ) 311 { 312 aShortcuts.insert( aShortcut ); 313 aKeyEquiv = aShortcut; 314 break; 315 } 316 } 317 318 return aKeyEquiv; 319} 320 321static void appendMenuItem( NSMenu* i_pMenu, NSMenu* i_pDockMenu, const rtl::OUString& i_rTitle, int i_nTag, const rtl::OUString& i_rKeyEquiv ) 322{ 323 if( ! i_rTitle.getLength() ) 324 return; 325 326 NSMenuItem* pItem = [[NSMenuItem alloc] initWithTitle: getAutoreleasedString( i_rTitle ) 327 action: @selector(executeMenuItem:) 328 keyEquivalent: (i_rKeyEquiv.getLength() ? getAutoreleasedString( i_rKeyEquiv ) : @"") 329 ]; 330 [pItem setTag: i_nTag]; 331 [pItem setTarget: pExecute]; 332 [pItem setEnabled: YES]; 333 [i_pMenu addItem: pItem]; 334 335 if( i_pDockMenu ) 336 { 337 // create a similar entry in the dock menu 338 pItem = [[NSMenuItem alloc] initWithTitle: getAutoreleasedString( i_rTitle ) 339 action: @selector(executeMenuItem:) 340 keyEquivalent: @"" 341 ]; 342 [pItem setTag: i_nTag]; 343 [pItem setTarget: pExecute]; 344 [pItem setEnabled: YES]; 345 [i_pDockMenu addItem: pItem]; 346 } 347} 348 349static void appendRecentMenu( NSMenu* i_pMenu, NSMenu* i_pDockMenu, const String& i_rTitle ) 350{ 351 if( ! pRecentDelegate ) 352 pRecentDelegate = [[RecentMenuDelegate alloc] init]; 353 354 NSMenuItem* pItem = [i_pMenu addItemWithTitle: getAutoreleasedString( i_rTitle ) 355 action: @selector(executeMenuItem:) 356 keyEquivalent: @"" 357 ]; 358 [pItem setEnabled: YES]; 359 NSMenu* pRecentMenu = [[NSMenu alloc] initWithTitle: getAutoreleasedString( i_rTitle ) ]; 360 [pRecentMenu setDelegate: pRecentDelegate]; 361 [pRecentMenu setAutoenablesItems: NO]; 362 [pItem setSubmenu: pRecentMenu]; 363 364 if( i_pDockMenu ) 365 { 366 // create a similar entry in the dock menu 367 pItem = [i_pDockMenu addItemWithTitle: getAutoreleasedString( i_rTitle ) 368 action: @selector(executeMenuItem:) 369 keyEquivalent: @"" 370 ]; 371 [pItem setEnabled: YES]; 372 pRecentMenu = [[NSMenu alloc] initWithTitle: getAutoreleasedString( i_rTitle ) ]; 373 [pRecentMenu setDelegate: pRecentDelegate]; 374 [pRecentMenu setAutoenablesItems: NO]; 375 [pItem setSubmenu: pRecentMenu]; 376 } 377} 378 379 380extern "C" 381{ 382 383void aqua_init_systray() 384{ 385 ::vos::OGuard aGuard( Application::GetSolarMutex() ); 386 387 ShutdownIcon *pShutdownIcon = ShutdownIcon::getInstance(); 388 if( ! pShutdownIcon ) 389 return; 390 391 // disable shutdown 392 pShutdownIcon->SetVeto( true ); 393 pShutdownIcon->addTerminateListener(); 394 395 if( ! pDefMenu ) 396 { 397 if( [NSApp respondsToSelector: @selector(addFallbackMenuItem:)] ) 398 { 399 aShortcuts.clear(); 400 401 pExecute = [[QSMenuExecute alloc] init]; 402 pDefMenu = [[NSMenuItem alloc] initWithTitle: getAutoreleasedString( pShutdownIcon->GetResString( STR_QUICKSTART_FILE ) ) action: NULL keyEquivalent: @""]; 403 pDockSubMenu = [[NSMenuItem alloc] initWithTitle: getAutoreleasedString( pShutdownIcon->GetResString( STR_QUICKSTART_FILE ) ) action: NULL keyEquivalent: @""]; 404 NSMenu* pMenu = [[NSMenu alloc] initWithTitle: getAutoreleasedString( pShutdownIcon->GetResString( STR_QUICKSTART_FILE ) )]; 405 [pMenu setAutoenablesItems: NO]; 406 NSMenu* pDockMenu = [[NSMenu alloc] initWithTitle: getAutoreleasedString( pShutdownIcon->GetResString( STR_QUICKSTART_FILE ) )]; 407 [pDockMenu setAutoenablesItems: NO]; 408 409 // collect the URLs of the entries in the File/New menu 410 SvtModuleOptions aModuleOptions; 411 std::set< rtl::OUString > aFileNewAppsAvailable; 412 SvtDynamicMenuOptions aOpt; 413 Sequence < Sequence < PropertyValue > > aNewMenu = aOpt.GetMenu( E_NEWMENU ); 414 const rtl::OUString sURLKey( RTL_CONSTASCII_USTRINGPARAM( "URL" ) ); 415 416 const Sequence< PropertyValue >* pNewMenu = aNewMenu.getConstArray(); 417 const Sequence< PropertyValue >* pNewMenuEnd = aNewMenu.getConstArray() + aNewMenu.getLength(); 418 for ( ; pNewMenu != pNewMenuEnd; ++pNewMenu ) 419 { 420 comphelper::SequenceAsHashMap aEntryItems( *pNewMenu ); 421 rtl::OUString sURL( aEntryItems.getUnpackedValueOrDefault( sURLKey, rtl::OUString() ) ); 422 if ( sURL.getLength() ) 423 aFileNewAppsAvailable.insert( sURL ); 424 } 425 426 // describe the menu entries for launching the applications 427 struct MenuEntryDescriptor 428 { 429 SvtModuleOptions::EModule eModuleIdentifier; 430 int nMenuTag; 431 const char* pAsciiURLDescription; 432 } aMenuItems[] = 433 { 434 { SvtModuleOptions::E_SWRITER, MI_WRITER, WRITER_URL }, 435 { SvtModuleOptions::E_SCALC, MI_CALC, CALC_URL }, 436 { SvtModuleOptions::E_SIMPRESS, MI_IMPRESS, IMPRESS_WIZARD_URL }, 437 { SvtModuleOptions::E_SDRAW, MI_DRAW, DRAW_URL }, 438 { SvtModuleOptions::E_SDATABASE, MI_BASE, BASE_URL }, 439 { SvtModuleOptions::E_SMATH, MI_MATH, MATH_URL } 440 }; 441 442 // insert entry for startcenter 443 if( aModuleOptions.IsModuleInstalled( SvtModuleOptions::E_SSTARTMODULE ) ) 444 { 445 appendMenuItem( pMenu, nil, pShutdownIcon->GetResString( STR_QUICKSTART_STARTCENTER ), MI_STARTMODULE, rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "n" ) ) ); 446 if( [NSApp respondsToSelector: @selector(setDockIconClickHandler:)] ) 447 [NSApp performSelector:@selector(setDockIconClickHandler:) withObject: pExecute]; 448 else 449 DBG_ERROR( "setDockIconClickHandler selector failed on NSApp\n" ); 450 451 } 452 453 // insert the menu entries for launching the applications 454 for ( size_t i = 0; i < sizeof( aMenuItems ) / sizeof( aMenuItems[0] ); ++i ) 455 { 456 if ( !aModuleOptions.IsModuleInstalled( aMenuItems[i].eModuleIdentifier ) ) 457 // the complete application is not even installed 458 continue; 459 460 rtl::OUString sURL( ::rtl::OUString::createFromAscii( aMenuItems[i].pAsciiURLDescription ) ); 461 462 if ( aFileNewAppsAvailable.find( sURL ) == aFileNewAppsAvailable.end() ) 463 // the application is installed, but the entry has been configured to *not* appear in the File/New 464 // menu => also let not appear it in the quickstarter 465 continue; 466 467 rtl::OUString aKeyEquiv( getShortCut( pShutdownIcon->GetUrlDescription( sURL ) ) ); 468 469 appendMenuItem( pMenu, pDockMenu, pShutdownIcon->GetUrlDescription( sURL ), aMenuItems[i].nMenuTag, aKeyEquiv ); 470 } 471 472 // insert the remaining menu entries 473 474 // add recent menu 475 appendRecentMenu( pMenu, pDockMenu, pShutdownIcon->GetResString( STR_QUICKSTART_RECENTDOC ) ); 476 477 rtl::OUString aTitle( pShutdownIcon->GetResString( STR_QUICKSTART_FROMTEMPLATE ) ); 478 rtl::OUString aKeyEquiv( getShortCut( aTitle ) ); 479 appendMenuItem( pMenu, pDockMenu, aTitle, MI_TEMPLATE, aKeyEquiv ); 480 aTitle = pShutdownIcon->GetResString( STR_QUICKSTART_FILEOPEN ); 481 aKeyEquiv = getShortCut( aTitle ); 482 appendMenuItem( pMenu, pDockMenu, aTitle, MI_OPEN, aKeyEquiv ); 483 484 [pDefMenu setSubmenu: pMenu]; 485 [NSApp performSelector:@selector(addFallbackMenuItem:) withObject: pDefMenu]; 486 487 if( [NSApp respondsToSelector: @selector(addDockMenuItem:)] ) 488 { 489 [pDockSubMenu setSubmenu: pDockMenu]; 490 // insert a separator to the dock menu 491 [NSApp performSelector:@selector(addDockMenuItem:) withObject: [NSMenuItem separatorItem]]; 492 // and now add the submenu 493 [NSApp performSelector:@selector(addDockMenuItem:) withObject: pDockSubMenu]; 494 } 495 else 496 DBG_ERROR( "addDockMenuItem selector failed on NSApp\n" ); 497 } 498 else 499 DBG_ERROR( "addFallbackMenuItem selector failed on NSApp\n" ); 500 } 501} 502 503void SAL_DLLPUBLIC_EXPORT aqua_shutdown_systray() 504{ 505} 506 507} 508