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