xref: /core/sfx2/source/appl/shutdowniconaqua.mm (revision 6f50961e)
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