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