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