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 <iderdll.hxx>
21 #include "iderdll2.hxx"
22 #include "macrodlg.hxx"
23 #include "moduldlg.hxx"
24 #include <strings.hrc>
25 #include "baside2.hxx"
26 
27 #include <com/sun/star/document/XScriptInvocationContext.hpp>
28 
29 #include <basic/sbmeth.hxx>
30 #include <comphelper/processfactory.hxx>
31 #include <comphelper/string.hxx>
32 #include <comphelper/sequence.hxx>
33 #include <framework/documentundoguard.hxx>
34 #include <sal/log.hxx>
35 #include <tools/diagnose_ex.h>
36 #include <unotools/moduleoptions.hxx>
37 #include <vcl/settings.hxx>
38 #include <vcl/svapp.hxx>
39 #include <vcl/weld.hxx>
40 
41 #include <memory>
42 #include <vector>
43 #include <algorithm>
44 #include <basic/basmgr.hxx>
45 namespace basctl
46 {
47 
48 using namespace ::com::sun::star;
49 using namespace ::com::sun::star::uno;
50 using namespace ::com::sun::star::container;
51 
52 extern "C" {
basicide_choose_macro(void * pParent,void * pOnlyInDocument_AsXModel,void * pDocFrame_AsXFrame,sal_Bool bChooseOnly)53     SAL_DLLPUBLIC_EXPORT rtl_uString* basicide_choose_macro(void* pParent, void* pOnlyInDocument_AsXModel, void* pDocFrame_AsXFrame, sal_Bool bChooseOnly )
54     {
55         Reference< frame::XModel > aDocument( static_cast< frame::XModel* >( pOnlyInDocument_AsXModel ) );
56         Reference< frame::XFrame > aDocFrame( static_cast< frame::XFrame* >( pDocFrame_AsXFrame ) );
57         OUString aScriptURL = basctl::ChooseMacro(static_cast<weld::Window*>(pParent), aDocument, aDocFrame, bChooseOnly);
58         rtl_uString* pScriptURL = aScriptURL.pData;
59         rtl_uString_acquire( pScriptURL );
60 
61         return pScriptURL;
62     }
basicide_macro_organizer(void * pParent,sal_Int16 nTabId)63     SAL_DLLPUBLIC_EXPORT void basicide_macro_organizer(void *pParent, sal_Int16 nTabId)
64     {
65         SAL_INFO("basctl.basicide","in basicide_macro_organizer");
66         basctl::Organize(static_cast<weld::Window*>(pParent), nTabId);
67     }
68 }
69 
Organize(weld::Window * pParent,sal_Int16 tabId)70 void Organize(weld::Window* pParent, sal_Int16 tabId)
71 {
72     EnsureIde();
73 
74     auto xDlg(std::make_shared<OrganizeDialog>(pParent, tabId));
75     weld::DialogController::runAsync(xDlg, [](int) {});
76 }
77 
IsValidSbxName(const OUString & rName)78 bool IsValidSbxName( const OUString& rName )
79 {
80     for ( sal_Int32 nChar = 0; nChar < rName.getLength(); nChar++ )
81     {
82         sal_Unicode c = rName[nChar];
83         bool bValid = (
84             ( c >= 'A' && c <= 'Z' ) ||
85             ( c >= 'a' && c <= 'z' ) ||
86             ( c >= '0' && c <= '9' && nChar ) ||
87             ( c == '_' )
88         );
89         if ( !bValid )
90             return false;
91     }
92     return true;
93 }
94 
GetMergedLibraryNames(const Reference<script::XLibraryContainer> & xModLibContainer,const Reference<script::XLibraryContainer> & xDlgLibContainer)95 Sequence< OUString > GetMergedLibraryNames( const Reference< script::XLibraryContainer >& xModLibContainer, const Reference< script::XLibraryContainer >& xDlgLibContainer )
96 {
97     // create a list of module library names
98     std::vector<OUString> aLibList;
99     if ( xModLibContainer.is() )
100     {
101         Sequence< OUString > aModLibNames = xModLibContainer->getElementNames();
102         sal_Int32 nModLibCount = aModLibNames.getLength();
103         const OUString* pModLibNames = aModLibNames.getConstArray();
104         for ( sal_Int32 i = 0 ; i < nModLibCount ; i++ )
105             aLibList.push_back( pModLibNames[ i ] );
106     }
107 
108     // create a list of dialog library names
109     if ( xDlgLibContainer.is() )
110     {
111         Sequence< OUString > aDlgLibNames = xDlgLibContainer->getElementNames();
112         sal_Int32 nDlgLibCount = aDlgLibNames.getLength();
113         const OUString* pDlgLibNames = aDlgLibNames.getConstArray();
114         for ( sal_Int32 i = 0 ; i < nDlgLibCount ; i++ )
115             aLibList.push_back( pDlgLibNames[ i ] );
116     }
117 
118     // sort list
119     auto const sort = comphelper::string::NaturalStringSorter(
120         comphelper::getProcessComponentContext(),
121         Application::GetSettings().GetUILanguageTag().getLocale());
122     std::sort(aLibList.begin(), aLibList.end(),
123               [&sort](const OUString& rLHS, const OUString& rRHS) {
124                   return sort.compare(rLHS, rRHS) < 0;
125               });
126     // remove duplicates
127     std::vector<OUString>::iterator aIterEnd = std::unique( aLibList.begin(), aLibList.end() );
128     aLibList.erase( aIterEnd, aLibList.end() );
129 
130     return comphelper::containerToSequence(aLibList);
131 }
132 
RenameModule(weld::Widget * pErrorParent,const ScriptDocument & rDocument,const OUString & rLibName,const OUString & rOldName,const OUString & rNewName)133 bool RenameModule (
134     weld::Widget* pErrorParent,
135     const ScriptDocument& rDocument,
136     const OUString& rLibName,
137     const OUString& rOldName,
138     const OUString& rNewName
139 )
140 {
141     if ( !rDocument.hasModule( rLibName, rOldName ) )
142     {
143         SAL_WARN( "basctl.basicide","basctl::RenameModule: old module name is invalid!" );
144         return false;
145     }
146 
147     if ( rDocument.hasModule( rLibName, rNewName ) )
148     {
149         std::unique_ptr<weld::MessageDialog> xError(Application::CreateMessageDialog(pErrorParent,
150                                                     VclMessageType::Warning, VclButtonsType::Ok, IDEResId(RID_STR_SBXNAMEALLREADYUSED2)));
151         xError->run();
152         return false;
153     }
154 
155     // #i74440
156     if ( rNewName.isEmpty() )
157     {
158         std::unique_ptr<weld::MessageDialog> xError(Application::CreateMessageDialog(pErrorParent,
159                                                     VclMessageType::Warning, VclButtonsType::Ok, IDEResId(RID_STR_BADSBXNAME)));
160         xError->run();
161         return false;
162     }
163 
164     if ( !rDocument.renameModule( rLibName, rOldName, rNewName ) )
165         return false;
166 
167     if (Shell* pShell = GetShell())
168     {
169         if (VclPtr<ModulWindow> pWin = pShell->FindBasWin(rDocument, rLibName, rNewName, false, true))
170         {
171             // set new name in window
172             pWin->SetName( rNewName );
173 
174             // set new module in module window
175             pWin->SetSbModule( pWin->GetBasic()->FindModule( rNewName ) );
176 
177             // update tabwriter
178             sal_uInt16 nId = pShell->GetWindowId( pWin );
179             SAL_WARN_IF( nId == 0 , "basctl.basicide", "No entry in Tabbar!");
180             if ( nId )
181             {
182                 TabBar& rTabBar = pShell->GetTabBar();
183                 rTabBar.SetPageText(nId, rNewName);
184                 rTabBar.Sort();
185                 rTabBar.MakeVisible(rTabBar.GetCurPageId());
186             }
187         }
188     }
189     return true;
190 }
191 
192 namespace
193 {
194     struct MacroExecutionData
195     {
196         ScriptDocument  aDocument;
197         SbMethodRef     xMethod;
198 
MacroExecutionDatabasctl::__anon76db6ed40311::MacroExecutionData199         MacroExecutionData()
200             :aDocument( ScriptDocument::NoDocument )
201         {
202         }
203     };
204 
205     class MacroExecution
206     {
207     public:
208         DECL_STATIC_LINK( MacroExecution, ExecuteMacroEvent, void*, void );
209     };
210 
IMPL_STATIC_LINK(MacroExecution,ExecuteMacroEvent,void *,p,void)211     IMPL_STATIC_LINK( MacroExecution, ExecuteMacroEvent, void*, p, void )
212     {
213         MacroExecutionData* i_pData = static_cast<MacroExecutionData*>(p);
214         ENSURE_OR_RETURN_VOID( i_pData, "wrong MacroExecutionData" );
215         // take ownership of the data
216         std::unique_ptr< MacroExecutionData > pData( i_pData );
217 
218         SAL_WARN_IF( (pData->xMethod->GetParent()->GetFlags() & SbxFlagBits::ExtSearch) == SbxFlagBits::NONE, "basctl.basicide","No EXTSEARCH!" );
219 
220         // in case this is a document-local macro, try to protect the document's Undo Manager from
221         // flawed scripts
222         std::unique_ptr< ::framework::DocumentUndoGuard > pUndoGuard;
223         if ( pData->aDocument.isDocument() )
224             pUndoGuard.reset( new ::framework::DocumentUndoGuard( pData->aDocument.getDocument() ) );
225 
226         RunMethod( pData->xMethod.get() );
227     }
228 }
229 
ChooseMacro(weld::Window * pParent,const uno::Reference<frame::XModel> & rxLimitToDocument,const uno::Reference<frame::XFrame> & xDocFrame,bool bChooseOnly)230 OUString ChooseMacro(weld::Window* pParent,
231                      const uno::Reference< frame::XModel >& rxLimitToDocument,
232                      const uno::Reference< frame::XFrame >& xDocFrame,
233                      bool bChooseOnly)
234 {
235     EnsureIde();
236 
237     GetExtraData()->ChoosingMacro() = true;
238 
239     OUString aScriptURL;
240     SbMethod* pMethod = nullptr;
241 
242     MacroChooser aChooser(pParent, xDocFrame);
243     if ( bChooseOnly || !SvtModuleOptions::IsBasicIDE() )
244         aChooser.SetMode(MacroChooser::ChooseOnly);
245 
246     if ( !bChooseOnly && rxLimitToDocument.is() )
247     {
248         // Hack!
249         aChooser.SetMode(MacroChooser::Recording);
250     }
251 
252     short nRetValue = aChooser.run();
253 
254     GetExtraData()->ChoosingMacro() = false;
255 
256     switch ( nRetValue )
257     {
258         case Macro_OkRun:
259         {
260             bool bError = false;
261 
262             pMethod = aChooser.GetMacro();
263             if ( !pMethod && aChooser.GetMode() == MacroChooser::Recording )
264                 pMethod = aChooser.CreateMacro();
265 
266             if ( !pMethod )
267                 break;
268 
269             SbModule* pModule = pMethod->GetModule();
270             if ( !pModule )
271             {
272                 SAL_WARN( "basctl.basicide", "basctl::ChooseMacro: No Module found!" );
273                 break;
274             }
275 
276             StarBASIC* pBasic = dynamic_cast<StarBASIC*>(pModule->GetParent());
277             if ( !pBasic )
278             {
279                 SAL_WARN( "basctl.basicide", "basctl::ChooseMacro: No Basic found!" );
280                 break;
281             }
282 
283             BasicManager* pBasMgr = FindBasicManager( pBasic );
284             if ( !pBasMgr )
285             {
286                 SAL_WARN( "basctl.basicide", "basctl::ChooseMacro: No BasicManager found!" );
287                 break;
288             }
289 
290             // name
291             OUString aName = pBasic->GetName() + "." + pModule->GetName() + "." + pMethod->GetName();
292 
293             // location
294             OUString aLocation;
295             ScriptDocument aDocument( ScriptDocument::getDocumentForBasicManager( pBasMgr ) );
296             if ( aDocument.isDocument() )
297             {
298                 // document basic
299                 aLocation = "document" ;
300 
301                 if ( rxLimitToDocument.is() )
302                 {
303                     uno::Reference< frame::XModel > xLimitToDocument( rxLimitToDocument );
304 
305                     uno::Reference< document::XEmbeddedScripts > xScripts( rxLimitToDocument, UNO_QUERY );
306                     if ( !xScripts.is() )
307                     {   // the document itself does not support embedding scripts
308                         uno::Reference< document::XScriptInvocationContext > xContext( rxLimitToDocument, UNO_QUERY );
309                         if ( xContext.is() )
310                             xScripts = xContext->getScriptContainer();
311                         if ( xScripts.is() )
312                         {   // but it is able to refer to a document which actually does support this
313                             xLimitToDocument.set( xScripts, UNO_QUERY );
314                             if ( !xLimitToDocument.is() )
315                             {
316                                 SAL_WARN_IF(!xLimitToDocument.is(), "basctl.basicide", "basctl::ChooseMacro: a script container which is no document!?" );
317                                 xLimitToDocument = rxLimitToDocument;
318                             }
319                         }
320                     }
321 
322                     if ( xLimitToDocument != aDocument.getDocument() )
323                     {
324                         // error
325                         bError = true;
326                         std::unique_ptr<weld::MessageDialog> xError(Application::CreateMessageDialog(nullptr,
327                                                                     VclMessageType::Warning, VclButtonsType::Ok, IDEResId(RID_STR_ERRORCHOOSEMACRO)));
328                         xError->run();
329                     }
330                 }
331             }
332             else
333             {
334                 // application basic
335                 aLocation = "application" ;
336             }
337 
338             // script URL
339             if ( !bError )
340             {
341                 aScriptURL = "vnd.sun.star.script:" + aName + "?language=Basic&location=" + aLocation;
342             }
343 
344             if ( !rxLimitToDocument.is() )
345             {
346                 MacroExecutionData* pExecData = new MacroExecutionData;
347                 pExecData->aDocument = aDocument;
348                 pExecData->xMethod = pMethod;   // keep alive until the event has been processed
349                 Application::PostUserEvent( LINK( nullptr, MacroExecution, ExecuteMacroEvent ), pExecData );
350             }
351         }
352         break;
353     }
354 
355     return aScriptURL;
356 }
357 
GetMethodNames(const ScriptDocument & rDocument,const OUString & rLibName,const OUString & rModName)358 Sequence< OUString > GetMethodNames( const ScriptDocument& rDocument, const OUString& rLibName, const OUString& rModName )
359 {
360     Sequence< OUString > aSeqMethods;
361 
362     // get module
363     OUString aOUSource;
364     if ( rDocument.getModule( rLibName, rModName, aOUSource ) )
365     {
366         BasicManager* pBasMgr = rDocument.getBasicManager();
367         StarBASIC* pSb = pBasMgr ? pBasMgr->GetLib( rLibName ) : nullptr;
368         SbModule* pMod = pSb ? pSb->FindModule( rModName ) : nullptr;
369 
370         SbModuleRef xModule;
371         // Only reparse modules if ScriptDocument source is out of sync
372         // with basic's Module
373         if ( !pMod || pMod->GetSource() != aOUSource )
374         {
375             xModule = new SbModule( rModName );
376             xModule->SetSource32( aOUSource );
377             pMod = xModule.get();
378         }
379 
380         sal_uInt16 nCount = pMod->GetMethods()->Count();
381         sal_uInt16 nRealCount = nCount;
382         for ( sal_uInt16 i = 0; i < nCount; i++ )
383         {
384             SbMethod* pMethod = static_cast<SbMethod*>(pMod->GetMethods()->Get( i ));
385             if( pMethod->IsHidden() )
386                 --nRealCount;
387         }
388         aSeqMethods.realloc( nRealCount );
389 
390         sal_uInt16 iTarget = 0;
391         for ( sal_uInt16 i = 0 ; i < nCount; ++i )
392         {
393             SbMethod* pMethod = static_cast<SbMethod*>(pMod->GetMethods()->Get( i ));
394             if( pMethod->IsHidden() )
395                 continue;
396             SAL_WARN_IF( !pMethod, "basctl.basicide","Method not found! (NULL)" );
397             aSeqMethods.getArray()[ iTarget++ ] = pMethod->GetName();
398         }
399     }
400 
401     return aSeqMethods;
402 }
403 
HasMethod(ScriptDocument const & rDocument,OUString const & rLibName,OUString const & rModName,OUString const & rMethName)404 bool HasMethod (
405     ScriptDocument const& rDocument,
406     OUString const& rLibName,
407     OUString const& rModName,
408     OUString const& rMethName
409 )
410 {
411     bool bHasMethod = false;
412 
413     OUString aOUSource;
414     if ( rDocument.hasModule( rLibName, rModName ) && rDocument.getModule( rLibName, rModName, aOUSource ) )
415     {
416         // Check if we really need to scan the source ( again )
417         BasicManager* pBasMgr = rDocument.getBasicManager();
418         StarBASIC* pSb = pBasMgr ? pBasMgr->GetLib( rLibName ) : nullptr;
419         SbModule* pMod = pSb ? pSb->FindModule( rModName ) : nullptr;
420         SbModuleRef xModule;
421         // Only reparse modules if ScriptDocument source is out of sync
422         // with basic's Module
423         if ( !pMod || pMod->GetSource() != aOUSource )
424         {
425             xModule = new SbModule( rModName );
426             xModule->SetSource32( aOUSource );
427             pMod = xModule.get();
428         }
429         SbxArray* pMethods = pMod->GetMethods().get();
430         if ( pMethods )
431         {
432             SbMethod* pMethod = static_cast<SbMethod*>(pMethods->Find( rMethName, SbxClassType::Method ));
433             if ( pMethod && !pMethod->IsHidden() )
434                 bHasMethod = true;
435         }
436     }
437 
438     return bHasMethod;
439 }
440 
441 } // namespace basctl
442 
443 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
444