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