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