1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
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>
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"
35 #include <com/sun/star/util/XStringWidth.hpp>
37 #include <cppuhelper/implbase.hxx>
43 #include <objc/objc-runtime.h>
44 #include <Cocoa/Cocoa.h>
55 #define MI_STARTMODULE 9
57 @interface QSMenuExecute : NSObject
60 -(void)executeMenuItem: (NSMenuItem*)pItem;
61 -(void)dockIconClicked: (NSObject*)pSender;
64 @implementation QSMenuExecute
65 -(void)executeMenuItem: (NSMenuItem*)pItem
70 ShutdownIcon::FileOpen();
73 ShutdownIcon::OpenURL( WRITER_URL, "_default" );
76 ShutdownIcon::OpenURL( CALC_URL, "_default" );
79 ShutdownIcon::OpenURL( IMPRESS_URL, "_default" );
82 ShutdownIcon::OpenURL( DRAW_URL, "_default" );
85 ShutdownIcon::OpenURL( BASE_URL, "_default" );
88 ShutdownIcon::OpenURL( MATH_URL, "_default" );
91 ShutdownIcon::FromTemplate();
94 ShutdownIcon::OpenURL( STARTMODULE_URL, "_default" );
101 -(void)dockIconClicked: (NSObject*)pSender
105 ShutdownIcon::OpenURL( STARTMODULE_URL, "_default" );
110 bool ShutdownIcon::IsQuickstarterInstalled()
115 static NSMenuItem* pDefMenu = nil, *pDockSubMenu = nil;
116 static QSMenuExecute* pExecute = nil;
118 static std::set< OUString > aShortcuts;
120 static NSString* getAutoreleasedString( const OUString& rStr )
122 return [[[NSString alloc] initWithCharacters: reinterpret_cast<unichar const *>(rStr.getStr()) length: rStr.getLength()] autorelease];
127 struct RecentMenuEntry
135 class RecentFilesStringLength : public ::cppu::WeakImplHelper< css::util::XStringWidth >
138 RecentFilesStringLength() {}
141 sal_Int32 SAL_CALL queryStringWidth( const OUString& aString ) override
143 return aString.getLength();
149 @interface RecentMenuDelegate : NSObject <NSMenuDelegate>
151 std::vector< RecentMenuEntry >* m_pRecentFilesItems;
155 -(void)menuNeedsUpdate:(NSMenu *)menu;
156 -(void)executeRecentEntry: (NSMenuItem*)item;
159 @implementation RecentMenuDelegate
162 if( (self = [super init]) )
164 m_pRecentFilesItems = new std::vector< RecentMenuEntry >();
171 delete m_pRecentFilesItems;
175 -(void)menuNeedsUpdate:(NSMenu *)menu
178 int nItems = [menu numberOfItems];
180 [menu removeItemAtIndex: 0];
182 // update recent item list
183 css::uno::Sequence< css::uno::Sequence< css::beans::PropertyValue > > aHistoryList( SvtHistoryOptions().GetList( ePICKLIST ) );
185 int nPickListMenuItems = ( aHistoryList.getLength() > 99 ) ? 99 : aHistoryList.getLength();
187 m_pRecentFilesItems->clear();
188 if( nPickListMenuItems > 0 )
190 for ( int i = 0; i < nPickListMenuItems; i++ )
192 css::uno::Sequence< css::beans::PropertyValue > const & rPickListEntry = aHistoryList[i];
193 RecentMenuEntry aRecentFile;
195 for ( const css::beans::PropertyValue& rProp : rPickListEntry )
197 const css::uno::Any& a = rProp.Value;
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;
209 m_pRecentFilesItems->push_back( aRecentFile );
213 // insert new recent items
214 for ( std::vector<RecentMenuEntry>::size_type i = 0; i < m_pRecentFilesItems->size(); i++ )
217 INetURLObject aURL( (*m_pRecentFilesItems)[i].aURL );
219 if ( aURL.GetProtocol() == INetProtocol::File )
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;
226 oslFileError nError = osl_abbreviateSystemPath( aSystemPath.pData, &aCompactedSystemPath.pData, 46, nullptr );
228 aMenuTitle = aCompactedSystemPath;
230 aMenuTitle = aSystemPath;
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 );
239 NSMenuItem* pNewItem = [[NSMenuItem alloc] initWithTitle: getAutoreleasedString( aMenuTitle )
240 action: @selector(executeRecentEntry:)
242 [pNewItem setTag: i];
243 [pNewItem setTarget: self];
244 [pNewItem setEnabled: YES];
245 [menu addItem: pNewItem];
246 [pNewItem autorelease];
250 -(void)executeRecentEntry: (NSMenuItem*)item
252 sal_Int32 nIndex = [item tag];
253 if( ( nIndex >= 0 ) && ( nIndex < static_cast<sal_Int32>( m_pRecentFilesItems->size() ) ) )
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 );
259 aArgsList[0].Name = "Referer";
260 aArgsList[0].Value <<= OUString( "private:user" );
262 // documents in the picklist will never be opened as templates
263 aArgsList[1].Name = "AsTemplate";
264 aArgsList[1].Value <<= false;
266 OUString aFilter( rRecentFile.aFilter );
267 sal_Int32 nPos = aFilter.indexOf( '|' );
270 OUString aFilterOptions;
272 if ( nPos < ( aFilter.getLength() - 1 ) )
273 aFilterOptions = aFilter.copy( nPos+1 );
275 aArgsList[2].Name = "FilterOptions";
276 aArgsList[2].Value <<= aFilterOptions;
278 aFilter = aFilter.copy( 0, nPos-1 );
279 aArgsList.realloc( ++NUM_OF_PICKLIST_ARGS );
282 aArgsList[NUM_OF_PICKLIST_ARGS-1].Name = "FilterName";
283 aArgsList[NUM_OF_PICKLIST_ARGS-1].Value <<= aFilter;
285 ShutdownIcon::OpenURL( rRecentFile.aURL, "_default", aArgsList );
290 static RecentMenuDelegate* pRecentDelegate = nil;
292 static OUString getShortCut( const OUString& i_rTitle )
296 for( sal_Int32 nIndex = 0; nIndex < i_rTitle.getLength(); nIndex++ )
298 OUString aShortcut( i_rTitle.copy( nIndex, 1 ).toAsciiLowerCase() );
299 if( aShortcuts.find( aShortcut ) == aShortcuts.end() )
301 aShortcuts.insert( aShortcut );
302 aKeyEquiv = aShortcut;
310 static void appendMenuItem( NSMenu* i_pMenu, NSMenu* i_pDockMenu, const OUString& i_rTitle, int i_nTag, const OUString& i_rKeyEquiv )
312 if( ! i_rTitle.getLength() )
315 NSMenuItem* pItem = [[NSMenuItem alloc] initWithTitle: getAutoreleasedString( i_rTitle )
316 action: @selector(executeMenuItem:)
317 keyEquivalent: (i_rKeyEquiv.getLength() ? getAutoreleasedString( i_rKeyEquiv ) : @"")
319 [pItem setTag: i_nTag];
320 [pItem setTarget: pExecute];
321 [pItem setEnabled: YES];
322 [i_pMenu addItem: pItem];
326 // create a similar entry in the dock menu
327 pItem = [[NSMenuItem alloc] initWithTitle: getAutoreleasedString( i_rTitle )
328 action: @selector(executeMenuItem:)
331 [pItem setTag: i_nTag];
332 [pItem setTarget: pExecute];
333 [pItem setEnabled: YES];
334 [i_pDockMenu addItem: pItem];
338 static void appendRecentMenu( NSMenu* i_pMenu, NSMenu* i_pDockMenu, const OUString& i_rTitle )
340 if( ! pRecentDelegate )
341 pRecentDelegate = [[RecentMenuDelegate alloc] init];
343 NSMenuItem* pItem = [i_pMenu addItemWithTitle: getAutoreleasedString( i_rTitle )
344 action: @selector(executeMenuItem:)
347 [pItem setEnabled: YES];
348 NSMenu* pRecentMenu = [[NSMenu alloc] initWithTitle: getAutoreleasedString( i_rTitle ) ];
350 [pRecentMenu setDelegate: pRecentDelegate];
352 [pRecentMenu setAutoenablesItems: NO];
353 [pItem setSubmenu: pRecentMenu];
357 // create a similar entry in the dock menu
358 pItem = [i_pDockMenu addItemWithTitle: getAutoreleasedString( i_rTitle )
359 action: @selector(executeMenuItem:)
362 [pItem setEnabled: YES];
363 pRecentMenu = [[NSMenu alloc] initWithTitle: getAutoreleasedString( i_rTitle ) ];
365 [pRecentMenu setDelegate: pRecentDelegate];
367 [pRecentMenu setAutoenablesItems: NO];
368 [pItem setSubmenu: pRecentMenu];
376 void aqua_init_systray()
378 SolarMutexGuard aGuard;
380 ShutdownIcon *pShutdownIcon = ShutdownIcon::getInstance();
381 if( ! pShutdownIcon )
385 pShutdownIcon->SetVeto( true );
386 ShutdownIcon::addTerminateListener();
390 if( [NSApp respondsToSelector: @selector(addFallbackMenuItem:)] )
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];
402 // collect the URLs of the entries in the File/New menu
403 SvtModuleOptions aModuleOptions;
404 std::set< OUString > aFileNewAppsAvailable;
405 SvtDynamicMenuOptions aOpt;
406 css::uno::Sequence < css::uno::Sequence < css::beans::PropertyValue > > const aNewMenu = aOpt.GetMenu( EDynamicMenuType::NewMenu );
408 for ( auto const & newMenuProp : aNewMenu )
410 comphelper::SequenceAsHashMap aEntryItems( newMenuProp );
411 OUString sURL( aEntryItems.getUnpackedValueOrDefault( "URL", OUString() ) );
412 if ( sURL.getLength() )
413 aFileNewAppsAvailable.insert( sURL );
416 // describe the menu entries for launching the applications
417 struct MenuEntryDescriptor
419 SvtModuleOptions::EModule eModuleIdentifier;
421 const char* pAsciiURLDescription;
424 { SvtModuleOptions::EModule::WRITER, MI_WRITER, WRITER_URL },
425 { SvtModuleOptions::EModule::CALC, MI_CALC, CALC_URL },
426 { SvtModuleOptions::EModule::IMPRESS, MI_IMPRESS, IMPRESS_WIZARD_URL },
427 { SvtModuleOptions::EModule::DRAW, MI_DRAW, DRAW_URL },
428 { SvtModuleOptions::EModule::DATABASE, MI_BASE, BASE_URL },
429 { SvtModuleOptions::EModule::MATH, MI_MATH, MATH_URL }
432 // insert entry for startcenter
433 if( aModuleOptions.IsModuleInstalled( SvtModuleOptions::EModule::STARTMODULE ) )
435 appendMenuItem( pMenu, nil, SfxResId(STR_QUICKSTART_STARTCENTER), MI_STARTMODULE, "n" );
436 if( [NSApp respondsToSelector: @selector(setDockIconClickHandler:)] )
437 [NSApp performSelector:@selector(setDockIconClickHandler:) withObject: pExecute];
439 OSL_FAIL( "setDockIconClickHandler selector failed on NSApp" );
443 // insert the menu entries for launching the applications
444 for ( size_t i = 0; i < SAL_N_ELEMENTS( aMenuItems ); ++i )
446 if ( !aModuleOptions.IsModuleInstalled( aMenuItems[i].eModuleIdentifier ) )
447 // the complete application is not even installed
450 OUString sURL( OUString::createFromAscii( aMenuItems[i].pAsciiURLDescription ) );
452 if ( aFileNewAppsAvailable.find( sURL ) == aFileNewAppsAvailable.end() )
453 // the application is installed, but the entry has been configured to *not* appear in the File/New
454 // menu => also let not appear it in the quickstarter
457 OUString aKeyEquiv( getShortCut( ShutdownIcon::GetUrlDescription( sURL ) ) );
459 appendMenuItem( pMenu, pDockMenu, ShutdownIcon::GetUrlDescription( sURL ), aMenuItems[i].nMenuTag, aKeyEquiv );
462 // insert the remaining menu entries
465 appendRecentMenu( pMenu, pDockMenu, SfxResId(STR_QUICKSTART_RECENTDOC) );
467 OUString aTitle( SfxResId(STR_QUICKSTART_FROMTEMPLATE) );
468 OUString aKeyEquiv( getShortCut( aTitle ) );
469 appendMenuItem( pMenu, pDockMenu, aTitle, MI_TEMPLATE, aKeyEquiv );
470 aTitle = SfxResId(STR_QUICKSTART_FILEOPEN);
471 aKeyEquiv = getShortCut( aTitle );
472 appendMenuItem( pMenu, pDockMenu, aTitle, MI_OPEN, aKeyEquiv );
474 [pDefMenu setSubmenu: pMenu];
475 [NSApp performSelector:@selector(addFallbackMenuItem:) withObject: pDefMenu];
477 if( [NSApp respondsToSelector: @selector(addDockMenuItem:)] )
479 [pDockSubMenu setSubmenu: pDockMenu];
481 [NSApp performSelector:@selector(addDockMenuItem:) withObject: pDockSubMenu];
484 OSL_FAIL( "addDockMenuItem selector failed on NSApp" );
487 OSL_FAIL( "addFallbackMenuItem selector failed on NSApp" );
491 void SAL_DLLPUBLIC_EXPORT aqua_shutdown_systray()
497 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */