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#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
21#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp>
22#include <com/sun/star/ui/dialogs/ControlActions.hpp>
23#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
24#include <osl/mutex.hxx>
25#include <vcl/svapp.hxx>
26#include "CFStringUtilities.hxx"
27#include "resourceprovider.hxx"
28#include "NSString_OOoAdditions.hxx"
29#include <sal/log.hxx>
30
31#include "ControlHelper.hxx"
32
33#pragma mark DEFINES
34#define POPUP_WIDTH_MIN 200
35#define POPUP_WIDTH_MAX 350
36
37using namespace ::com::sun::star::ui::dialogs;
38using namespace ::com::sun::star::ui::dialogs::TemplateDescription;
39using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds;
40using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds;
41
42namespace {
43
44uno::Any HandleGetListValue(const NSControl* pControl, const sal_Int16 nControlAction)
45{
46    uno::Any aAny;
47
48    if ([pControl class] != [NSPopUpButton class]) {
49        SAL_INFO("fpicker.aqua","not a popup button");
50        return aAny;
51    }
52
53    NSPopUpButton *pButton = static_cast<NSPopUpButton*>(pControl);
54    NSMenu *rMenu = [pButton menu];
55    if (nil == rMenu) {
56        SAL_INFO("fpicker.aqua","button has no menu");
57        return aAny;
58    }
59
60    switch (nControlAction)
61    {
62        case ControlActions::GET_ITEMS:
63        {
64            SAL_INFO("fpicker.aqua","GET_ITEMS");
65            uno::Sequence< OUString > aItemList;
66
67            int nItems = [rMenu numberOfItems];
68            if (nItems > 0) {
69                aItemList.realloc(nItems);
70            }
71            for (int i = 0; i < nItems; i++) {
72                NSString* sCFItem = [pButton itemTitleAtIndex:i];
73                if (nil != sCFItem) {
74                    aItemList[i] = [sCFItem OUString];
75                    SAL_INFO("fpicker.aqua","Return value[" << (i - 1) << "]: " << aItemList[i - 1]);
76                }
77            }
78
79            aAny <<= aItemList;
80        }
81            break;
82        case ControlActions::GET_SELECTED_ITEM:
83        {
84            SAL_INFO("fpicker.aqua","GET_SELECTED_ITEM");
85            NSString* sCFItem = [pButton titleOfSelectedItem];
86            if (nil != sCFItem) {
87                OUString sString = [sCFItem OUString];
88                SAL_INFO("fpicker.aqua","Return value: " << sString);
89                aAny <<= sString;
90            }
91        }
92            break;
93        case ControlActions::GET_SELECTED_ITEM_INDEX:
94        {
95            SAL_INFO("fpicker.aqua","GET_SELECTED_ITEM_INDEX");
96            sal_Int32 nActive = [pButton indexOfSelectedItem];
97            SAL_INFO("fpicker.aqua","Return value: " << nActive);
98            aAny <<= nActive;
99        }
100            break;
101        default:
102            SAL_INFO("fpicker.aqua","undocumented/unimplemented ControlAction for a list");
103            break;
104    }
105
106    return aAny;
107}
108
109NSTextField* createLabelWithString(NSString* labelString)
110{
111    NSTextField *textField = [NSTextField new];
112    [textField setEditable:NO];
113    [textField setSelectable:NO];
114    [textField setDrawsBackground:NO];
115    [textField setBordered:NO];
116    SAL_WNODEPRECATED_DECLARATIONS_PUSH //TODO: 10.9 setTitle
117    [[textField cell] setTitle:labelString];
118    SAL_WNODEPRECATED_DECLARATIONS_POP
119
120    return textField;
121}
122
123}
124
125#pragma mark Constructor / Destructor
126
127// Constructor / Destructor
128
129ControlHelper::ControlHelper()
130: m_pUserPane(nullptr)
131, m_pFilterControl(nil)
132, m_bUserPaneNeeded( false )
133, m_bIsUserPaneLaidOut(false)
134, m_bIsFilterControlNeeded(false)
135, m_pFilterHelper(nullptr)
136{
137    int i;
138
139    for( i = 0; i < TOGGLE_LAST; i++ ) {
140        m_bToggleVisibility[i] = false;
141    }
142
143    for( i = 0; i < LIST_LAST; i++ ) {
144        m_bListVisibility[i] = false;
145    }
146}
147
148ControlHelper::~ControlHelper()
149{
150    NSAutoreleasePool *pool = [NSAutoreleasePool new];
151
152    if (nullptr != m_pUserPane) {
153        [m_pUserPane release];
154    }
155
156    if (m_pFilterControl != nullptr) {
157        [m_pFilterControl setTarget:nil];
158    }
159
160    for (auto const& activeControl : m_aActiveControls)
161    {
162        NSString* sLabelName = m_aMapListLabels[activeControl];
163        if (sLabelName != nil) {
164            [sLabelName release];
165        }
166        if ([activeControl class] == [NSPopUpButton class]) {
167            NSTextField* pField = m_aMapListLabelFields[static_cast<NSPopUpButton*>(activeControl)];
168            if (pField != nil) {
169                [pField release];
170            }
171        }
172        [activeControl release];
173    }
174
175    [pool release];
176}
177
178#pragma mark XInitialization delegate
179
180// XInitialization delegate
181
182void ControlHelper::initialize( sal_Int16 nTemplateId )
183{
184    switch( nTemplateId )
185    {
186        case FILESAVE_AUTOEXTENSION_PASSWORD:
187            m_bToggleVisibility[AUTOEXTENSION] = true;
188            m_bToggleVisibility[PASSWORD] = true;
189            break;
190        case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS:
191            m_bToggleVisibility[AUTOEXTENSION] = true;
192            m_bToggleVisibility[PASSWORD] = true;
193            m_bToggleVisibility[FILTEROPTIONS] = true;
194            break;
195        case FILESAVE_AUTOEXTENSION_SELECTION:
196            m_bToggleVisibility[AUTOEXTENSION] = true;
197            m_bToggleVisibility[SELECTION] = true;
198            break;
199        case FILESAVE_AUTOEXTENSION_TEMPLATE:
200            m_bToggleVisibility[AUTOEXTENSION] = true;
201            m_bListVisibility[TEMPLATE] = true;
202            break;
203        case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE:
204            m_bToggleVisibility[LINK] = true;
205            m_bToggleVisibility[PREVIEW] = true;
206            m_bListVisibility[IMAGE_TEMPLATE] = true;
207            break;
208        case FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR:
209            m_bToggleVisibility[LINK] = true;
210            m_bToggleVisibility[PREVIEW] = true;
211            m_bListVisibility[IMAGE_ANCHOR] = true;
212            break;
213        case FILEOPEN_READONLY_VERSION:
214            m_bToggleVisibility[READONLY] = true;
215            m_bListVisibility[VERSION] = true;
216            break;
217        case FILEOPEN_LINK_PREVIEW:
218            m_bToggleVisibility[LINK] = true;
219            m_bToggleVisibility[PREVIEW] = true;
220            break;
221        case FILESAVE_AUTOEXTENSION:
222            m_bToggleVisibility[AUTOEXTENSION] = true;
223            break;
224        case FILEOPEN_PREVIEW:
225            m_bToggleVisibility[PREVIEW] = true;
226            break;
227        case FILEOPEN_LINK_PLAY:
228            m_bToggleVisibility[LINK] = true;
229    }
230
231    createControls();
232}
233
234#pragma mark XFilePickerControlAccess delegates
235
236// XFilePickerControlAccess functions
237
238
239void ControlHelper::enableControl( const sal_Int16 nControlId, const bool bEnable ) const
240{
241    SolarMutexGuard aGuard;
242
243    if (nControlId == ExtendedFilePickerElementIds::CHECKBOX_PREVIEW) {
244        SAL_INFO("fpicker.aqua"," preview checkbox cannot be changed");
245        return;
246    }
247
248    NSControl* pControl = getControl(nControlId);
249
250    if( pControl != nil ) {
251        if( bEnable ) {
252            SAL_INFO("fpicker.aqua", "enable" );
253        } else {
254            SAL_INFO("fpicker.aqua", "disable" );
255        }
256        [pControl setEnabled:bEnable];
257    } else {
258        SAL_INFO("fpicker.aqua","enable unknown control " << nControlId );
259    }
260}
261
262OUString ControlHelper::getLabel( sal_Int16 nControlId )
263{
264    SolarMutexGuard aGuard;
265
266    NSControl* pControl = getControl( nControlId );
267
268    if( pControl == nil ) {
269        SAL_INFO("fpicker.aqua","Get label for unknown control " << nControlId);
270        return OUString();
271    }
272
273    OUString retVal;
274    if ([pControl class] == [NSPopUpButton class]) {
275        NSString *temp = m_aMapListLabels[pControl];
276        if (temp != nil)
277            retVal = [temp OUString];
278    }
279    else {
280        SAL_WNODEPRECATED_DECLARATIONS_PUSH //TODO: 10.9 title
281        NSString* sLabel = [[pControl cell] title];
282        SAL_WNODEPRECATED_DECLARATIONS_POP
283        retVal = [sLabel OUString];
284    }
285
286    return retVal;
287}
288
289void ControlHelper::setLabel( sal_Int16 nControlId, NSString* aLabel )
290{
291    SolarMutexGuard aGuard;
292
293    NSAutoreleasePool *pool = [NSAutoreleasePool new];
294
295    NSControl* pControl = getControl(nControlId);
296
297    if (nil != pControl) {
298        if ([pControl class] == [NSPopUpButton class]) {
299            NSString *sOldName = m_aMapListLabels[pControl];
300            if (sOldName != nullptr && sOldName != aLabel) {
301                [sOldName release];
302            }
303
304            m_aMapListLabels[pControl] = [aLabel retain];
305        } else if ([pControl class] == [NSButton class]) {
306            SAL_WNODEPRECATED_DECLARATIONS_PUSH //TODO: 10.9 setTitle
307            [[pControl cell] setTitle:aLabel];
308            SAL_WNODEPRECATED_DECLARATIONS_POP
309        }
310    } else {
311        SAL_INFO("fpicker.aqua","Control not found to set label for");
312    }
313
314    layoutControls();
315
316    [pool release];
317}
318
319void ControlHelper::setValue( sal_Int16 nControlId, sal_Int16 nControlAction, const uno::Any& rValue )
320{
321    SolarMutexGuard aGuard;
322
323    if (nControlId == ExtendedFilePickerElementIds::CHECKBOX_PREVIEW) {
324        SAL_INFO("fpicker.aqua"," value for preview is unchangeable");
325    }
326    else {
327        NSControl* pControl = getControl( nControlId );
328
329        if( pControl == nil ) {
330            SAL_INFO("fpicker.aqua","enable unknown control " << nControlId);
331        } else {
332            if( [pControl class] == [NSPopUpButton class] ) {
333                HandleSetListValue(pControl, nControlAction, rValue);
334            } else if( [pControl class] == [NSButton class] ) {
335                bool bChecked = false;
336                rValue >>= bChecked;
337                SAL_INFO("fpicker.aqua"," value is a bool: " << bChecked);
338                [static_cast<NSButton*>(pControl) setState:(bChecked ? NSControlStateValueOn : NSControlStateValueOff)];
339            } else
340            {
341                SAL_INFO("fpicker.aqua","Can't set value on button / list " << nControlId << " " << nControlAction);
342            }
343        }
344    }
345}
346
347uno::Any ControlHelper::getValue( sal_Int16 nControlId, sal_Int16 nControlAction ) const
348{
349    SolarMutexGuard aGuard;
350    uno::Any aRetval;
351
352    NSControl* pControl = getControl( nControlId );
353
354    if( pControl == nil ) {
355        SAL_INFO("fpicker.aqua","get value for unknown control " << nControlId);
356    } else {
357        if( [pControl class] == [NSPopUpButton class] ) {
358            aRetval = HandleGetListValue(pControl, nControlAction);
359        } else if( [pControl class] == [NSButton class] ) {
360            //NSLog(@"control: %@", [[pControl cell] title]);
361            bool bValue = [static_cast<NSButton*>(pControl) state] == NSControlStateValueOn;
362            aRetval <<= bValue;
363            SAL_INFO("fpicker.aqua","value is a bool (checkbox): " << bValue);
364        }
365    }
366
367    return aRetval;
368}
369
370void ControlHelper::createUserPane()
371{
372    if (!m_bUserPaneNeeded) {
373        SAL_INFO("fpicker.aqua","no user pane needed");
374        return;
375    }
376
377    if (nil != m_pUserPane) {
378        SAL_INFO("fpicker.aqua","user pane already exists");
379        return;
380    }
381
382    if (m_bIsFilterControlNeeded && m_pFilterControl == nil) {
383        createFilterControl();
384    }
385
386    NSRect minRect = NSMakeRect(0,0,300,33);
387    m_pUserPane = [[NSView alloc] initWithFrame:minRect];
388
389    int currentHeight = kAquaSpaceBoxFrameViewDiffTop + kAquaSpaceBoxFrameViewDiffBottom;
390    int currentWidth = 300;
391
392    bool bPopupControlPresent = false;
393    bool bButtonControlPresent = false;
394
395    int nCheckboxMaxWidth = 0;
396    int nPopupMaxWidth = 0;
397    int nPopupLabelMaxWidth = 0;
398
399    size_t nLoop = 0;
400    for (auto const& activeControl : m_aActiveControls)
401    {
402        SAL_INFO("fpicker.aqua","currentHeight: " << currentHeight);
403
404        //let the control calculate its size
405        [activeControl sizeToFit];
406
407        NSRect frame = [activeControl frame];
408        SAL_INFO("fpicker.aqua","frame for control " << [[activeControl description] UTF8String] << " is {" << frame.origin.x << ", " << frame.origin.y << ", " << frame.size.width << ", " << frame.size.height << "}");
409
410        int nControlHeight = frame.size.height;
411        int nControlWidth = frame.size.width;
412
413        // Note: controls are grouped by kind, first all popup menus, then checkboxes
414        if ([activeControl class] == [NSPopUpButton class]) {
415            if (bPopupControlPresent) {
416                //this is not the first popup
417                currentHeight += kAquaSpaceBetweenPopupMenus;
418            }
419            else if (nLoop)
420            {
421                currentHeight += kAquaSpaceBetweenControls;
422            }
423
424            bPopupControlPresent = true;
425
426            // we have to add the label text width
427            NSString *label = m_aMapListLabels[activeControl];
428
429            NSTextField *textField = createLabelWithString(label);
430            [textField sizeToFit];
431            m_aMapListLabelFields[static_cast<NSPopUpButton*>(activeControl)] = textField;
432            [m_pUserPane addSubview:textField];
433
434            NSRect tfRect = [textField frame];
435            SAL_INFO("fpicker.aqua","frame for textfield " << [[textField description] UTF8String] << " is {" << tfRect.origin.x << ", " << tfRect.origin.y << ", " << tfRect.size.width << ", " << tfRect.size.height << "}");
436
437            int tfWidth = tfRect.size.width;
438
439            if (nPopupLabelMaxWidth < tfWidth) {
440                nPopupLabelMaxWidth = tfWidth;
441            }
442
443            frame.origin.x += (kAquaSpaceBetweenControls - kAquaSpaceLabelFrameBoundsDiffH - kAquaSpacePopupMenuFrameBoundsDiffLeft) + tfWidth;
444
445            if (nControlWidth < POPUP_WIDTH_MIN) {
446                nControlWidth = POPUP_WIDTH_MIN;
447                frame.size.width = nControlWidth;
448                [activeControl setFrame:frame];
449            }
450
451            if (nControlWidth > POPUP_WIDTH_MAX) {
452                nControlWidth = POPUP_WIDTH_MAX;
453                frame.size.width = nControlWidth;
454                [activeControl setFrame:frame];
455            }
456
457            //set the max size
458            if (nPopupMaxWidth < nControlWidth) {
459                nPopupMaxWidth = nControlWidth;
460            }
461
462            nControlWidth += tfWidth + kAquaSpaceBetweenControls - kAquaSpaceLabelFrameBoundsDiffH - kAquaSpacePopupMenuFrameBoundsDiffLeft;
463            if (nControlHeight < kAquaPopupButtonDefaultHeight) {
464                //maybe the popup has no menu item yet, so set a default height
465                nControlHeight = kAquaPopupButtonDefaultHeight;
466            }
467
468            nControlHeight -= kAquaSpacePopupMenuFrameBoundsDiffV;
469        }
470        else if ([activeControl class] == [NSButton class]) {
471            if (nLoop)
472            {
473                currentHeight += kAquaSpaceBetweenControls;
474            }
475
476            if (nCheckboxMaxWidth < nControlWidth) {
477                nCheckboxMaxWidth = nControlWidth;
478            }
479
480            bButtonControlPresent = true;
481            nControlWidth -= 2 * kAquaSpaceSwitchButtonFrameBoundsDiff;
482            nControlHeight -= 2 * kAquaSpaceSwitchButtonFrameBoundsDiff;
483        }
484
485        // if ((nControlWidth + 2 * kAquaSpaceInsideGroupH) > currentWidth) {
486        //     currentWidth = nControlWidth + 2 * kAquaSpaceInsideGroupH;
487        // }
488
489        currentHeight += nControlHeight;
490
491        [m_pUserPane addSubview:activeControl];
492        ++nLoop;
493    }
494
495    SAL_INFO("fpicker.aqua","height after adding all controls: " << currentHeight);
496
497    if (bPopupControlPresent && bButtonControlPresent)
498    {
499        //after a popup button (array) and before a different kind of control we need some extra space instead of the standard
500        currentHeight -= kAquaSpaceBetweenControls;
501        currentHeight += kAquaSpaceAfterPopupButtonsV;
502        SAL_INFO("fpicker.aqua","popup extra space added, currentHeight: " << currentHeight);
503    }
504
505    int nLongestPopupWidth = nPopupMaxWidth + nPopupLabelMaxWidth + kAquaSpaceBetweenControls - kAquaSpacePopupMenuFrameBoundsDiffLeft - kAquaSpaceLabelFrameBoundsDiffH;
506
507    currentWidth = nLongestPopupWidth > nCheckboxMaxWidth ? nLongestPopupWidth : nCheckboxMaxWidth;
508    SAL_INFO("fpicker.aqua","longest control width: " << currentWidth);
509
510    currentWidth += 2* kAquaSpaceInsideGroupH;
511
512    if (currentWidth < minRect.size.width)
513        currentWidth = minRect.size.width;
514
515    if (currentHeight < minRect.size.height)
516        currentHeight = minRect.size.height;
517
518    NSRect upRect = NSMakeRect(0, 0, currentWidth, currentHeight );
519    SAL_INFO("fpicker.aqua","setting user pane rect to {" << upRect.origin.x << ", " << upRect.origin.y << ", " << upRect.size.width << ", " << upRect.size.height << "}");
520
521    [m_pUserPane setFrame:upRect];
522
523    layoutControls();
524}
525
526#pragma mark Private / Misc
527
528// Private / Misc
529
530void ControlHelper::createControls()
531{
532    for (int i = 0; i < LIST_LAST; i++) {
533        if (m_bListVisibility[i]) {
534            m_bUserPaneNeeded = true;
535
536            int elementName = getControlElementName([NSPopUpButton class], i);
537            NSString* sLabel = CResourceProvider::getResString(elementName);
538
539            m_pListControls[i] = [NSPopUpButton new];
540
541#define MAP_LIST_( elem ) \
542 case elem: \
543     setLabel(ExtendedFilePickerElementIds::LISTBOX_##elem, sLabel); \
544     break
545
546            switch(i) {
547                MAP_LIST_(VERSION);
548                MAP_LIST_(TEMPLATE);
549                MAP_LIST_(IMAGE_TEMPLATE);
550                MAP_LIST_(IMAGE_ANCHOR);
551            }
552
553            m_aActiveControls.push_back(m_pListControls[i]);
554        } else {
555            m_pListControls[i] = nil;
556        }
557    }
558
559    for (int i = 0/*#i102102*/; i < TOGGLE_LAST; i++) {
560        if (m_bToggleVisibility[i]) {
561            m_bUserPaneNeeded = true;
562
563            int elementName = getControlElementName([NSButton class], i);
564            NSString* sLabel = CResourceProvider::getResString(elementName);
565
566            NSButton *button = [NSButton new];
567            [button setTitle:sLabel];
568
569            [button setButtonType:NSButtonTypeSwitch];
570
571            [button setState:NSControlStateValueOff];
572
573            if (i == AUTOEXTENSION) {
574                [button setTarget:m_pDelegate];
575                [button setAction:@selector(autoextensionChanged:)];
576            }
577
578            m_pToggles[i] = button;
579
580            m_aActiveControls.push_back(m_pToggles[i]);
581        } else {
582            m_pToggles[i] = nil;
583        }
584    }
585
586    //preview is always on with macOS
587    NSControl *pPreviewBox = m_pToggles[PREVIEW];
588    if (pPreviewBox != nil) {
589        [pPreviewBox setEnabled:NO];
590        [static_cast<NSButton*>(pPreviewBox) setState:NSControlStateValueOn];
591    }
592}
593
594#define TOGGLE_ELEMENT( elem ) \
595case elem: \
596    nReturn = CHECKBOX_##elem; \
597    return nReturn
598#define LIST_ELEMENT( elem ) \
599case elem: \
600    nReturn = LISTBOX_##elem##_LABEL; \
601    return nReturn
602
603int ControlHelper::getControlElementName(const Class aClazz, const int nControlId)
604{
605    int nReturn = -1;
606    if (aClazz == [NSButton class])
607    {
608        switch (nControlId) {
609            TOGGLE_ELEMENT( AUTOEXTENSION );
610            TOGGLE_ELEMENT( PASSWORD );
611            TOGGLE_ELEMENT( FILTEROPTIONS );
612            TOGGLE_ELEMENT( READONLY );
613            TOGGLE_ELEMENT( LINK );
614            TOGGLE_ELEMENT( PREVIEW );
615            TOGGLE_ELEMENT( SELECTION );
616        }
617    }
618    else if (aClazz == [NSPopUpButton class])
619    {
620        switch (nControlId) {
621            LIST_ELEMENT( VERSION );
622            LIST_ELEMENT( TEMPLATE );
623            LIST_ELEMENT( IMAGE_TEMPLATE );
624            LIST_ELEMENT( IMAGE_ANCHOR );
625        }
626    }
627
628    return nReturn;
629}
630
631void ControlHelper::HandleSetListValue(const NSControl* pControl, const sal_Int16 nControlAction, const uno::Any& rValue)
632{
633    if ([pControl class] != [NSPopUpButton class]) {
634        SAL_INFO("fpicker.aqua","not a popup menu");
635        return;
636    }
637
638    NSPopUpButton *pButton = static_cast<NSPopUpButton*>(pControl);
639    NSMenu *rMenu = [pButton menu];
640    if (nil == rMenu) {
641        SAL_INFO("fpicker.aqua","button has no menu");
642        return;
643    }
644
645    switch (nControlAction)
646    {
647        case ControlActions::ADD_ITEM:
648        {
649            SAL_INFO("fpicker.aqua","ADD_ITEMS");
650            OUString sItem;
651            rValue >>= sItem;
652
653            NSString* sCFItem = [NSString stringWithOUString:sItem];
654            SAL_INFO("fpicker.aqua","Adding menu item: " << sItem);
655            [pButton addItemWithTitle:sCFItem];
656        }
657            break;
658        case ControlActions::ADD_ITEMS:
659        {
660            SAL_INFO("fpicker.aqua","ADD_ITEMS");
661            uno::Sequence< OUString > aStringList;
662            rValue >>= aStringList;
663            sal_Int32 nItemCount = aStringList.getLength();
664            for (sal_Int32 i = 0; i < nItemCount; ++i)
665            {
666                NSString* sCFItem = [NSString stringWithOUString:aStringList[i]];
667                SAL_INFO("fpicker.aqua","Adding menu item: " << aStringList[i]);
668                [pButton addItemWithTitle:sCFItem];
669            }
670        }
671            break;
672        case ControlActions::DELETE_ITEM:
673        {
674            SAL_INFO("fpicker.aqua","DELETE_ITEM");
675            sal_Int32 nPos = -1;
676            rValue >>= nPos;
677            SAL_INFO("fpicker.aqua","Deleting item at position " << (nPos));
678            [rMenu removeItemAtIndex:nPos];
679        }
680            break;
681        case ControlActions::DELETE_ITEMS:
682        {
683            SAL_INFO("fpicker.aqua","DELETE_ITEMS");
684            int nItems = [rMenu numberOfItems];
685            if (nItems == 0) {
686                SAL_INFO("fpicker.aqua","no menu items to delete");
687                return;
688            }
689            for(sal_Int32 i = 0; i < nItems; i++) {
690                [rMenu removeItemAtIndex:i];
691            }
692        }
693            break;
694        case ControlActions::SET_SELECT_ITEM:
695        {
696            sal_Int32 nPos = -1;
697            rValue >>= nPos;
698            SAL_INFO("fpicker.aqua","Selecting item at position " << nPos);
699            [pButton selectItemAtIndex:nPos];
700        }
701            break;
702        default:
703            SAL_INFO("fpicker.aqua","undocumented/unimplemented ControlAction for a list");
704            break;
705    }
706
707    layoutControls();
708}
709
710// cf. offapi/com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.idl
711NSControl* ControlHelper::getControl( const sal_Int16 nControlId ) const
712{
713    NSControl* pWidget = nil;
714
715#define MAP_TOGGLE( elem ) \
716case ExtendedFilePickerElementIds::CHECKBOX_##elem: \
717    pWidget = m_pToggles[elem]; \
718    break
719
720#define MAP_LIST( elem ) \
721case ExtendedFilePickerElementIds::LISTBOX_##elem: \
722    pWidget = m_pListControls[elem]; \
723    break
724
725#define MAP_LIST_LABEL( elem ) \
726case ExtendedFilePickerElementIds::LISTBOX_##elem##_LABEL: \
727    pWidget = m_pListControls[elem]; \
728    break
729
730    switch( nControlId )
731    {
732            MAP_TOGGLE( AUTOEXTENSION );
733            MAP_TOGGLE( PASSWORD );
734            MAP_TOGGLE( FILTEROPTIONS );
735            MAP_TOGGLE( READONLY );
736            MAP_TOGGLE( LINK );
737            MAP_TOGGLE( PREVIEW );
738            MAP_TOGGLE( SELECTION );
739            //MAP_BUTTON( PLAY );
740            MAP_LIST( VERSION );
741            MAP_LIST( TEMPLATE );
742            MAP_LIST( IMAGE_TEMPLATE );
743            MAP_LIST( IMAGE_ANCHOR );
744            MAP_LIST_LABEL( VERSION );
745            MAP_LIST_LABEL( TEMPLATE );
746            MAP_LIST_LABEL( IMAGE_TEMPLATE );
747            MAP_LIST_LABEL( IMAGE_ANCHOR );
748        default:
749            SAL_INFO("fpicker.aqua","Handle unknown control " << nControlId);
750            break;
751    }
752#undef MAP
753
754    return pWidget;
755}
756
757void ControlHelper::layoutControls()
758{
759    SolarMutexGuard aGuard;
760
761    if (nil == m_pUserPane) {
762        SAL_INFO("fpicker.aqua","no user pane to layout");
763        return;
764    }
765
766    if (m_bIsUserPaneLaidOut) {
767        SAL_INFO("fpicker.aqua","user pane already laid out");
768        return;
769    }
770
771    NSRect userPaneRect = [m_pUserPane frame];
772    SAL_INFO("fpicker.aqua","userPane frame: {" << userPaneRect.origin.x << ", " << userPaneRect.origin.y << ", " << userPaneRect.size.width << ", " << userPaneRect.size.height << "}");
773
774    int nUsableWidth = userPaneRect.size.width;
775
776    //NOTE: NSView's coordinate system starts in the lower left hand corner but we start adding controls from the top,
777    // so we subtract from the vertical position as we make our way down the pane.
778    int currenttop = userPaneRect.size.height;
779    int nCheckboxMaxWidth = 0;
780    int nPopupMaxWidth = 0;
781    int nPopupLabelMaxWidth = 0;
782
783    //first loop to determine max sizes
784    for (auto const& activeControl : m_aActiveControls)
785    {
786
787        NSRect controlRect = [activeControl frame];
788        int nControlWidth = controlRect.size.width;
789
790        Class aSubType = [activeControl class];
791        if (aSubType == [NSPopUpButton class]) {
792            if (nPopupMaxWidth < nControlWidth) {
793                nPopupMaxWidth = nControlWidth;
794            }
795            NSTextField *label = m_aMapListLabelFields[static_cast<NSPopUpButton*>(activeControl)];
796            NSRect labelFrame = [label frame];
797            int nLabelWidth = labelFrame.size.width;
798            if (nPopupLabelMaxWidth < nLabelWidth) {
799                nPopupLabelMaxWidth = nLabelWidth;
800            }
801        } else {
802            if (nCheckboxMaxWidth < nControlWidth) {
803                nCheckboxMaxWidth = nControlWidth;
804            }
805        }
806    }
807
808    int nLongestPopupWidth = nPopupMaxWidth + nPopupLabelMaxWidth + kAquaSpaceBetweenControls - kAquaSpacePopupMenuFrameBoundsDiffLeft - kAquaSpaceLabelFrameBoundsDiffH;
809    SAL_INFO("fpicker.aqua","longest popup width: " << nLongestPopupWidth);
810
811    NSControl* previousControl = nil;
812
813    int nDistBetweenControls = 0;
814
815    for (auto const& activeControl : m_aActiveControls)
816    {
817        //get the control's bounds
818        NSRect controlRect = [activeControl frame];
819        int nControlHeight = controlRect.size.height;
820
821        //subtract the height from the current vertical position, because the control's bounds origin rect will be its lower left hand corner
822        currenttop -= nControlHeight;
823
824        Class aSubType = [activeControl class];
825
826        //add space between the previous control and this control according to Apple's HIG
827        nDistBetweenControls = getVerticalDistance(previousControl, activeControl);
828        SAL_INFO("fpicker.aqua","vertical distance: " << nDistBetweenControls);
829        currenttop -= nDistBetweenControls;
830
831        previousControl = activeControl;
832
833        if (aSubType == [NSPopUpButton class]) {
834            //move vertically up some pixels to space the controls between their real (visual) bounds
835            currenttop += kAquaSpacePopupMenuFrameBoundsDiffTop;//from top
836
837            //get the corresponding popup label
838            NSTextField *label = m_aMapListLabelFields[static_cast<NSPopUpButton*>(activeControl)];
839            NSRect labelFrame = [label frame];
840            int totalWidth = nPopupMaxWidth + labelFrame.size.width + kAquaSpaceBetweenControls - kAquaSpacePopupMenuFrameBoundsDiffLeft - kAquaSpaceLabelFrameBoundsDiffH;
841            SAL_INFO("fpicker.aqua","totalWidth: " << totalWidth);
842            //let's center popups
843            int left = (nUsableWidth + nLongestPopupWidth) / 2 - totalWidth;
844            SAL_INFO("fpicker.aqua","left: " << left);
845            labelFrame.origin.x = left;
846            labelFrame.origin.y = currenttop + kAquaSpaceLabelPopupDiffV;
847            SAL_INFO("fpicker.aqua","setting label at: {" << labelFrame.origin.x << ", " << labelFrame.origin.y << ", " << labelFrame.size.width << ", " << labelFrame.size.height << "}");
848            [label setFrame:labelFrame];
849
850            controlRect.origin.x = left + labelFrame.size.width + kAquaSpaceBetweenControls - kAquaSpaceLabelFrameBoundsDiffH - kAquaSpacePopupMenuFrameBoundsDiffLeft;
851            controlRect.origin.y = currenttop;
852            controlRect.size.width = nPopupMaxWidth;
853            SAL_INFO("fpicker.aqua","setting popup at: {" << controlRect.origin.x << ", " << controlRect.origin.y << ", " << controlRect.size.width << ", " << controlRect.size.height << "}");
854            [activeControl setFrame:controlRect];
855
856            //add some space to place the vertical position right below the popup's visual bounds
857            currenttop += kAquaSpacePopupMenuFrameBoundsDiffBottom;
858        } else {
859            currenttop += kAquaSpaceSwitchButtonFrameBoundsDiff;//from top
860
861            int left = (nUsableWidth - nCheckboxMaxWidth) / 2;
862            controlRect.origin.x = left;
863            controlRect.origin.y = currenttop;
864            controlRect.size.width = nPopupMaxWidth;
865            [activeControl setFrame:controlRect];
866            SAL_INFO("fpicker.aqua","setting checkbox at: {" << controlRect.origin.x << ", " << controlRect.origin.y << ", " << controlRect.size.width << ", " << controlRect.size.height << "}");
867
868            currenttop += kAquaSpaceSwitchButtonFrameBoundsDiff;
869        }
870    }
871
872    m_bIsUserPaneLaidOut = true;
873}
874
875void ControlHelper::createFilterControl()
876{
877    NSString* sLabel = CResourceProvider::getResString(CommonFilePickerElementIds::LISTBOX_FILTER_LABEL);
878
879    m_pFilterControl = [NSPopUpButton new];
880
881    [m_pFilterControl setAction:@selector(filterSelectedAtIndex:)];
882    [m_pFilterControl setTarget:m_pDelegate];
883
884    NSMenu *menu = [m_pFilterControl menu];
885
886    for (auto const& filterName : *m_pFilterHelper->getFilterNames())
887    {
888        SAL_INFO("fpicker.aqua","adding filter name: " << [filterName UTF8String]);
889        if ([filterName isEqualToString:@"-"]) {
890            [menu addItem:[NSMenuItem separatorItem]];
891        }
892        else {
893            [m_pFilterControl addItemWithTitle:filterName];
894        }
895    }
896
897    // always add the filter as first item
898    m_aActiveControls.push_front(m_pFilterControl);
899    m_aMapListLabels[m_pFilterControl] = [sLabel retain];
900}
901
902int ControlHelper::getVerticalDistance(const NSControl* first, const NSControl* second)
903{
904    if (first == nil) {
905        return kAquaSpaceBoxFrameViewDiffTop;
906    }
907    else if (second == nil) {
908        return kAquaSpaceBoxFrameViewDiffBottom;
909    }
910    else {
911        Class firstClass = [first class];
912        Class secondClass = [second class];
913
914        if (firstClass == [NSPopUpButton class]) {
915            if (secondClass == [NSPopUpButton class]) {
916                return kAquaSpaceBetweenPopupMenus;
917            }
918            else {
919                return kAquaSpaceAfterPopupButtonsV;
920            }
921        }
922
923        return kAquaSpaceBetweenControls;
924    }
925}
926
927void ControlHelper::updateFilterUI()
928{
929    if (!m_bIsFilterControlNeeded || m_pFilterHelper == nullptr) {
930        SAL_INFO("fpicker.aqua","no filter control needed or no filter helper present");
931        return;
932    }
933
934    int index = m_pFilterHelper->getCurrentFilterIndex();
935
936    if (m_pFilterControl == nil) {
937        createFilterControl();
938    }
939
940    [m_pFilterControl selectItemAtIndex:index];
941}
942
943/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
944